From 97c1f893454a626ea74a065ff2fd7afb41fc1e08 Mon Sep 17 00:00:00 2001
From: liyong <liyong@3mang.com>
Date: Thu, 5 Jan 2017 18:48:01 +0800
Subject: [PATCH] 新建的项目

---
 .gitignore                      |   15 +++++++++++++++
 docs/README.md                  |    1 +
 etc/README.md                   |    1 +
 etc/umd.template.html           |  344 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 package.json                    |   39 +++++++++++++++++++++++++++++++++++++++
 src/Emiter.js                   |   45 +++++++++++++++++++++++++++++++++++++++++++++
 src/EverSocket.js               |  154 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/Loger.js                    |   83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/MessageEngine.js            |  144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/MessageTypes.js             |   46 ++++++++++++++++++++++++++++++++++++++++++++++
 src/Sass.js                     |  141 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/apes/Ape.js                 |  319 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/apes/ApeConsts.js           |  162 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/apes/ChatApe.js             |   79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/apes/ConferApe.js           |  148 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/apes/DocApe.js              |   79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/apes/VideoApe.js            |  113 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/apes/WhiteBoardApe.js       |   74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/assets/css/README.md        |    1 +
 src/assets/font/README.md       |    1 +
 src/assets/img/README.md        |    1 +
 src/events/RCMessageCallback.js |   19 +++++++++++++++++++
 src/index.js                    |   26 ++++++++++++++++++++++++++
 src/libs/ArrayBufferUtil.js     |   80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/mcu.js                      |  173 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/pdu/PduConsts.js            |   29 +++++++++++++++++++++++++++++
 src/pdu/PduType.js              |   94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/pdu/ProBuf.js               | 9914 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/pdu/index.js                |   96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/pdu/pro.js                  |  850 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/url.js                      |    4 ++++
 src/zlib.min.js                 |   39 +++++++++++++++++++++++++++++++++++++++
 test/README.md                  |    1 +
 test/index.js                   |    2 ++
 webpack.config.umd.js           |   17 +++++++++++++++++
 35 files changed, 13334 insertions(+), 0 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 docs/README.md
 create mode 100644 etc/README.md
 create mode 100644 etc/umd.template.html
 create mode 100644 package.json
 create mode 100644 src/Emiter.js
 create mode 100644 src/EverSocket.js
 create mode 100644 src/Loger.js
 create mode 100644 src/MessageEngine.js
 create mode 100644 src/MessageTypes.js
 create mode 100644 src/Sass.js
 create mode 100644 src/apes/Ape.js
 create mode 100644 src/apes/ApeConsts.js
 create mode 100644 src/apes/ChatApe.js
 create mode 100644 src/apes/ConferApe.js
 create mode 100644 src/apes/DocApe.js
 create mode 100644 src/apes/VideoApe.js
 create mode 100644 src/apes/WhiteBoardApe.js
 create mode 100644 src/assets/css/README.md
 create mode 100644 src/assets/font/README.md
 create mode 100644 src/assets/img/README.md
 create mode 100644 src/events/RCMessageCallback.js
 create mode 100644 src/index.js
 create mode 100644 src/libs/ArrayBufferUtil.js
 create mode 100644 src/mcu.js
 create mode 100644 src/pdu/PduConsts.js
 create mode 100644 src/pdu/PduType.js
 create mode 100644 src/pdu/ProBuf.js
 create mode 100644 src/pdu/index.js
 create mode 100644 src/pdu/pro.js
 create mode 100644 src/url.js
 create mode 100644 src/zlib.min.js
 create mode 100644 test/README.md
 create mode 100644 test/index.js
 create mode 100644 webpack.config.umd.js

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..db30748
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+# exclude all
+/*
+
+# project structure
+!/src/
+!/etc/
+!/docs/
+!/test/
+
+# project files
+!.gitignore
+!README.md
+!package.json
+!LICENSE
+!webpack.config.umd.js
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..38c5bc3
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1 @@
+# DOCS FOLDER
diff --git a/etc/README.md b/etc/README.md
new file mode 100644
index 0000000..24bd094
--- /dev/null
+++ b/etc/README.md
@@ -0,0 +1 @@
+#CONF FOLDER
diff --git a/etc/umd.template.html b/etc/umd.template.html
new file mode 100644
index 0000000..eae6d07
--- /dev/null
+++ b/etc/umd.template.html
@@ -0,0 +1,344 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>学点云课堂</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
+    <link rel="stylesheet" href="stylesheets/style.css"/>
+  </head>
+
+<body style="visibility: hidden ">
+  <div class="container">
+    <div class="video_container">
+      <div class="video_wrapper">
+        <div id="video_mask"></div>
+        <div id="video_div"
+          class="hide">
+          <video id="video"
+            autoplay
+            autostart
+            poster="./images/waiting.jpg"
+            controls
+            playsinline
+            webkit-playsinline>
+            该设备不支持HTML5/VIDEO标签播放.
+          </video>
+        </div>
+        <div id="btn_video"
+          style="display: none;"></div>
+        <div id="btn_document"
+          style="display: none;"></div>
+        <div id="live_document_container"
+          style="display: none;">
+          <div>
+            <div class="document_none">
+              <div class="text-center document_image"></div>
+              <p class="text-center">
+                            矮油,还没有文档共享哦~
+                          </p>
+            </div>
+            <div class="list_sliders">
+              <div class="slideWrapper">
+                <img class="slide">
+              </div>
+              <div class="docAnno"></div>
+            </div>
+          </div>
+          <div class="document_pager dn">
+            <p class="text-center">0/1</p>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="content_container">
+      <ul id="tabmenu_list"
+        style="margin: 0;"
+        class="nav nav-tabs"
+        role="tablist">
+        <li role="chat"
+          class="active">
+            <a role="tab" data-toggle="tab-message">聊天</a>
+          </li>
+        <li role="doc">
+            <a role="tab" data-toggle="tab-board">文档</a>
+          </li>
+        <li role="detail">
+            <a role="tab" data-toggle="tab-detail">详情</a>
+          </li>
+      </ul>
+      <div class="tab_container">
+        <div role="tab-message"
+          class="tab_body tab_message active">
+          <div class="tab_body_container">
+            <div id="panel_message"
+              data-empty="true"
+              class="message_container">
+              <p class="h5 text-center"
+                style="color: #666;">
+                  暂未收到消息
+                </p>
+            </div>
+          </div>
+        </div>
+        <div role="tab-board"
+          class="tab_body">
+          <div class="tab_body_container">
+            <div class="document_none">
+              <div class="text-center document_image"></div>
+              <p class="text-center">
+                  矮油,还没有文档共享哦~
+                </p>
+            </div>
+            <div class="list_sliders">
+              <div class="slideWrapper">
+                <img class="slide">
+              </div>
+              <div class="docAnno"></div>
+            </div>
+          </div>
+          <div id="document_pager"
+            class="document_pager dn">
+            <p class="text-center">
+                0/1
+              </p>
+          </div>
+        </div>
+        <div role="tab-detail"
+          class="tab_body">
+          <div class="tab_body_container">
+            <div id="detail_wrapper"
+              class="detail_wrapper"></div>
+          </div>
+        </div>
+        <div class="input_container">
+          <div class="input_wrapper"
+            style="overflow: hidden;">
+            <div class="btn_more"></div>
+            <div class="btn_face"></div>
+            <div style="float: right; width: 60px;">
+              <button id="btn_send"
+                type="submit"
+                class="btn btn-primary">
+                发送
+              </button>
+            </div>
+            <div style="margin:0 84px 0 85px;">
+              <input id="txt_message"
+                type="text"
+                class="form-control"
+                maxlength="20" onkeyup="this.value=this.value.replace(/^ +| +$/g,'')">
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="panel_more"
+      style="display: none;">
+      <div class="masker"></div>
+      <div class="panel_inner">
+        <div class="list_buttons">
+          <ul>
+            <li class="btn_layout"
+              data-layout="live">
+                  <div>
+                    直播布局
+                  </div>
+              </li>
+            <li class="btn_night">
+                  <div>
+                    夜间模式
+                  </div>
+              </li>
+          </ul>
+          <div class="btn_close">
+            <span></span>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="list_faces"
+      style="display: none;">
+      <div class="list_faces_scroller">
+        <ul>
+          <li><img data-src="images/face/aimu.gif" data-type="aimu">
+            </li>
+          <li><img data-src="images/face/daxiao.gif" data-type="daxiao">
+            </li>
+          <li><img data-src="images/face/huaixiao.gif" data-type="huaixiao">
+            </li>
+          <li><img data-src="images/face/liuhan.gif" data-type="liuhan">
+            </li>
+          <li><img data-src="images/face/shuijiao.gif" data-type="shuijiao">
+            </li>
+          <li><img data-src="images/face/yiwen.gif" data-type="yiwen">
+            </li>
+          <li><img data-src="images/face/aoman.gif" data-type="aoman">
+            </li>
+          <li><img data-src="images/face/jiew.gif" data-type="jiew">
+            </li>
+          <li><img data-src="images/face/liulei.gif" data-type="liulei">
+            </li>
+          <li><img data-src="images/face/sun.gif" data-type="sun">
+            </li>
+          <li><img data-src="images/face/youhh.gif" data-type="youhh">
+            </li>
+          <li><img data-src="images/face/baiyan.gif" data-type="baiyan">
+            </li>
+          <li><img data-src="images/face/fadai.gif" data-type="fadai">
+            </li>
+          <li><img data-src="images/face/jingkong.gif" data-type="jingkong">
+            </li>
+          <li><img data-src="images/face/meiyan.gif" data-type="meiyan">
+            </li>
+          <li><img data-src="images/face/tiaoxie.gif" data-type="tiaoxie">
+            </li>
+          <li><img data-src="images/face/yue.gif" data-type="yue">
+            </li>
+          <li><img data-src="images/face/baoquan.gif" data-type="baoquan">
+            </li>
+          <li><img data-src="images/face/fanu.gif" data-type="fanu">
+            </li>
+          <li><img data-src="images/face/jingya.gif" data-type="jingya">
+            </li>
+          <li><img data-src="images/face/mojing.gif" data-type="mojing">
+            </li>
+          <li><img data-src="images/face/touxiao.gif" data-type="touxiao">
+            </li>
+          <li><img data-src="images/face/yumen.gif" data-type="yumen">
+            </li>
+          <li><img data-src="images/face/bishi.gif" data-type="bishi">
+            </li>
+          <li><img data-src="images/face/fendou.gif" data-type="fendou">
+            </li>
+          <li><img data-src="images/face/kafei.gif" data-type="kafei">
+            </li>
+          <li><img data-src="images/face/piezui.gif" data-type="piezui">
+            </li>
+          <li><img data-src="images/face/tu.gif" data-type="tu">
+            </li>
+          <li><img data-src="images/face/yun.gif" data-type="yun">
+            </li>
+          <li><img data-src="images/face/bizui.gif" data-type="bizui">
+            </li>
+          <li><img data-src="images/face/ganga.gif" data-type="ganga">
+            </li>
+          <li><img data-src="images/face/kelian.gif" data-type="kelian">
+            </li>
+        </ul>
+        <ul>
+          <li><img data-src="images/face/pijiu.gif" data-type="pijiu">
+            </li>
+          <li><img data-src="images/face/weiqu.gif" data-type="weiqu">
+            </li>
+          <li><img data-src="images/face/zaijian.gif" data-type="zaijian">
+            </li>
+          <li><img data-src="images/face/cahan.gif" data-type="cahan">
+            </li>
+          <li><img data-src="images/face/guai.gif" data-type="guai">
+            </li>
+          <li><img data-src="images/face/koubi.gif" data-type="koubi">
+            </li>
+          <li><img data-src="images/face/qiang.gif" data-type="qiang">
+            </li>
+          <li><img data-src="images/face/weixiao.gif" data-type="weixiao">
+            </li>
+          <li><img data-src="images/face/zancheng.gif" data-type="zancheng">
+            </li>
+          <li><img data-src="images/face/cool.gif" data-type="cool">
+            </li>
+          <li><img data-src="images/face/guzhang.gif" data-type="guzhang">
+            </li>
+          <li><img data-src="images/face/kuaikl.gif" data-type="kuaikl">
+            </li>
+          <li><img data-src="images/face/qinqin.gif" data-type="qinqin">
+            </li>
+          <li><img data-src="images/face/woshou.gif" data-type="woshou">
+            </li>
+          <li><img data-src="images/face/zhemo.gif" data-type="zhemo">
+            </li>
+          <li><img data-src="images/face/dabing.gif" data-type="dabing">
+            </li>
+          <li><img data-src="images/face/hanxiao.gif" data-type="hanxiao">
+            </li>
+          <li><img data-src="images/face/kulou.gif" data-type="kulou">
+            </li>
+          <li><img data-src="images/face/qiu.gif" data-type="qiu">
+            </li>
+          <li><img data-src="images/face/xigua.gif" data-type="xigua">
+            </li>
+          <li><img data-src="images/face/zhouma.gif" data-type="zhouma">
+            </li>
+          <li><img data-src="images/face/daku.gif" data-type="daku">
+            </li>
+          <li><img data-src="images/face/haqi.gif" data-type="haqi">
+            </li>
+          <li><img data-src="images/face/kun.gif" data-type="kun">
+            </li>
+          <li><img data-src="images/face/ruo.gif" data-type="ruo">
+            </li>
+          <li><img data-src="images/face/xin.gif" data-type="xin">
+            </li>
+          <li><img data-src="images/face/zuohh.gif" data-type="zuohh">
+            </li>
+          <li><img data-src="images/face/dangao.gif" data-type="dangao">
+            </li>
+          <li><img data-src="images/face/hua.gif" data-type="hua">
+            </li>
+          <li><img data-src="images/face/lianhong.gif" data-type="lianhong">
+            </li>
+          <li><img data-src="images/face/shuai.gif" data-type="shuai">
+            </li>
+          <li><img data-src="images/face/yinxian.gif" data-type="yinxian">
+            </li>
+        </ul>
+      </div>
+      <div class="list_faces_paginator">
+        <b class="selected"></b>
+        <b></b>
+      </div>
+    </div>
+    <div class="dlg_login"
+      style="display: none;">
+      <div class="masker"></div>
+      <div class="dlg_inner">
+        <div class="dlg_title">
+          <p style="font-size: 22px; color: #fff;">
+              Welcome!
+            </p>
+          <p style="font-size: 12px; color: #ccc;">
+              欢迎进入直播互动课堂
+            </p>
+        </div>
+        <div class="dlg_content">
+          <p class="txt_username">
+              <input type="text" class="form-control" placeholder="请输入您的姓名" maxlength="20" />
+            </p>
+          <p class="txt_password">
+              <input type="text" class="form-control" placeholder="请输入课堂密码" maxlength="20" />
+            </p>
+          <p>
+              <button id="btn_login" class="btn_login">
+                进入教室
+              </button>
+            </p>
+        </div>
+      </div>
+    </div>
+
+    <div class="tip_panel"
+      style="display: none;">
+      用户名或者密码错误!
+    </div>
+  </div>
+
+  <div class="tip_message"
+    style="display: none;"></div>
+  <div id="online_users"
+    style="display: none;">
+    0人在线
+  </div>
+</body>
+
+</html>
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..152ab09
--- /dev/null
+++ b/package.json
@@ -0,0 +1,39 @@
+{
+  "name": "",
+  "version": "1.0.1",
+  "description": "McuClient on H5",
+  "main": "dist/main.js",
+  "directories": {
+    "doc": "docs",
+    "test": "test"
+  },
+  "scripts": {
+    "test": "test/index.js"
+  },
+  "keywords": [
+    "LiveClass"
+  ],
+  "author": "www.3mang.com",
+  "license": "MIT",
+  "dependencies": {
+    "base64-js": "^1.2.0",
+    "es6-promise": "^3.2.1",
+    "iscroll": "^5.2.0",
+    "jquery": "^3.1.0",
+    "jquery-touchswipe": "^1.6.15",
+    "protobufjs": "^5.0.1",
+    "string.fromcodepoint": "^0.2.1",
+    "url-parse": "^1.1.1",
+    "utf-8": "^1.0.0",
+    "whatwg-fetch": "^1.0.0",
+    "zlibjs": "^0.2.0"
+  },
+  "wbp": {
+    "project": "umd",
+    "entries": {
+      "main": "./MessageEngine.js"
+    },
+    "source": "src/",
+    "build": "dist/"
+  }
+}
\ No newline at end of file
diff --git a/src/Emiter.js b/src/Emiter.js
new file mode 100644
index 0000000..7db0d0d
--- /dev/null
+++ b/src/Emiter.js
@@ -0,0 +1,45 @@
+export default class Emiter {
+  constructor() {
+    this.MAPS = {};
+  }
+  on(eid, elistener) {
+    if (eid && elistener) {
+      let stub = this.MAPS[eid];
+      if (!stub) {
+        return this.MAPS[eid] = [elistener];
+      }
+      stub.push(elistener);
+    }
+  }
+  off(eid, elistener) {
+    if (eid) {
+      let stub = this.MAPS[eid];
+      if (stub) {
+        if (elistener) {
+          return stub.splice(stub.indexOf(elistener), 1);
+        }
+        stub.length = 0;
+      }
+    }
+  }
+  emit(eid, data) {
+    if (eid) {
+      //eid=* broadcast
+      let asteriskStub = this.MAPS['*'];
+      if (asteriskStub && asteriskStub.length) {
+        asteriskStub.forEach(function (elistener) {
+          elistener(eid, data);
+        })
+      }
+
+      // eid= normal
+      let stub = this.MAPS[eid];
+      if (stub && stub.length) {
+        stub.forEach(function (elistener) {
+          elistener(data);
+        });
+      }
+    }
+  }
+}
+
diff --git a/src/EverSocket.js b/src/EverSocket.js
new file mode 100644
index 0000000..ee5c7c0
--- /dev/null
+++ b/src/EverSocket.js
@@ -0,0 +1,154 @@
+// //////////////////////////////////////////////////////////////////////////////
+//
+//  Copyright (C) 2016-present
+//  All Rights Reserved.
+//
+//  Author: AlexWang
+//  Date: 2016-08-27 21:40:49
+//  Last Modified by:   AlexWang
+//  Last Modified time: 2016-12-05 12:02:48
+//  QQ Email: 1669499355@qq.com
+//  Description: 底层Socket管理器,保持一直在线及异常重连.
+//
+// //////////////////////////////////////////////////////////////////////////////
+
+import Emiter from 'Emiter';
+import Loger from 'Loger';
+let loger = Loger.getLoger('EverSocket');
+
+class EverSocket extends Emiter {
+  constructor() {
+    super();
+    this._connected = false;
+    this._lastActiveTime = 0;
+    this._enableEverSocket = false;
+  }
+
+  begin(ip, port) {
+    loger.log('开始WebSocket应用.');
+    this._enableEverSocket = true;
+    this.wsURL = 'ws://' + ip + ':' + port;
+    this._newConnection();
+  }
+
+  end() {
+    loger.log('停止WebSocket应用.');
+    this._clear();
+  }
+
+  get connected() {
+    return this._connected;
+  }
+
+  send(data) {
+    if (this._connected) {
+      loger.log('SEND MESSAGE---->');
+      this.websocket.send(data);
+    } else {
+      loger.warn('WebSocket未建立连接.消息忽略');
+    }
+  }
+
+  _setConnected(isConn = true) {
+    this._connected = isConn;
+    if (this._connected) {
+      this.emit(EverSocket.OPEN);
+    } else {
+      this.emit(EverSocket.CLOSED);
+    }
+  }
+
+  _newConnection() {
+    this.websocket = new WebSocket(this.wsURL);
+    this.websocket.binaryType = 'arraybuffer';
+    this.websocket.onopen = this._onOpen.bind(this);
+    this.websocket.onclose = this._onClose.bind(this);
+    this.websocket.onerror = this._onError.bind(this);
+    this.websocket.onmessage = this._onMessage.bind(this);
+  }
+
+  _reConnection() {
+    this._clear();
+    this.reConnectionTimeout = window.setTimeout(() => {
+      loger.log('WebSocket重新建立.');
+      window.clearTimeout(this.reConnectionTimeout);
+      this._newConnection();
+    }, EverSocket.RECONN_INTERVAL);
+  }
+
+  _clear() {
+    loger.log('WebSocket,Timers销毁');
+    window.clearInterval(this.pingTimer);
+    window.clearInterval(this.pongTimer);
+    this.websocket.onopen = undefined;
+    this.websocket.onclose = undefined;
+    this.websocket.onerror = undefined;
+    this.websocket.onmessage = undefined;
+    try {
+      this.websocket.close();
+    } catch (e) {
+      loger.log('ignore errors');
+    }
+    this.websocket = undefined;
+    this._enableEverSocket = false;
+    this._setConnected(false);
+  }
+
+  _onOpen() {
+    loger.log('WebSocket建立成功', this.wsURL);
+    //this.pingTimer = window.setInterval(this._sendPingHandler.bind(this), EverSocket.PING_INTERVAL);
+    //this.pongTimer = window.setInterval(this._checkPongHandler.bind(this), EverSocket.PONG_INTERVAL);
+    this._setConnected();
+  }
+
+  _onClose(closeEvent) {
+    loger.log(`WebSocket连接断开 CODE:${closeEvent.code} REASON:${closeEvent.reason} CLEAN: ${closeEvent.wasClean}`, this.wsURL);
+    this._reConnection();
+  }
+
+  _onError() {
+    loger.log('WebSocket错误出现');
+    this._connected = false;
+    this._reConnection();
+  }
+
+  _onMessage(messageEvent) {
+    loger.log('<----RECEIVE MESSAGE');
+    this._lastActiveTime = Date.now();
+    const bufferData = messageEvent.data;
+    if (bufferData.byteLength > 0) {
+      this.emit(EverSocket.MESSAGE, bufferData);
+    }
+  }
+
+  _sendPingHandler() {
+    if (this._connected) {
+      this.websocket.send(new ArrayBuffer);
+    } else {
+      this._reConnection();
+    }
+  }
+
+  _checkPongHandler() {
+    let pongTime = Date.now();
+    if (this._lastActiveTime &&
+      this._lastActiveTime >= pongTime - EverSocket.PONG_INTERVAL &&
+      this._lastActiveTime <= pongTime
+    ) {} else {
+      loger.warn('---服务器PINGPONG超时-----');
+      this._reConnection();
+    }
+  }
+}
+
+EverSocket.prototype.PONG_INTERVAL = EverSocket.PONG_INTERVAL = 5000;
+EverSocket.prototype.PING_INTERVAL = EverSocket.PING_INTERVAL = 3000;
+EverSocket.prototype.RECONN_INTERVAL = EverSocket.RECONN_INTERVAL = 2000;
+EverSocket.prototype.CONNECTING = EverSocket.CONNECTING = 0;
+EverSocket.prototype.OPEN = EverSocket.OPEN = 1;
+EverSocket.prototype.CLOSING = EverSocket.CLOSING = 2;
+EverSocket.prototype.CLOSED = EverSocket.CLOSED = 3;
+EverSocket.prototype.MESSAGE = EverSocket.MESSAGE = 4;
+
+export default new EverSocket();
+
diff --git a/src/Loger.js b/src/Loger.js
new file mode 100644
index 0000000..e3326db
--- /dev/null
+++ b/src/Loger.js
@@ -0,0 +1,83 @@
+// //////////////////////////////////////////////////////////////////////////////
+//
+//  Copyright (C) 2016-present  All Rights Reserved.
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Github Home: https://github.com/AlexWang1987
+//  Author: AlexWang
+//  Date: 2016-08-27 22:58:47
+//  QQ Email: 1669499355@qq.com
+//  Last Modified time: 2016-08-27 23:05:53
+//  Description: LiveClass-Loger
+//
+// //////////////////////////////////////////////////////////////////////////////
+
+class Loger {
+  constructor(info) {
+    this.info = info || '';
+    this.id = this.initId();
+  }
+
+  initId() {
+    const infoType = this.info.constructor.name.toLowerCase();
+    if (infoType === 'string') {
+      return this.info;
+    }
+    if (infoType === 'object') {
+      return this.info.mid || '';
+    }
+    return '';
+  }
+
+  log(...msg) {
+    this._log(Loger.LOG, msg);
+  }
+
+  warn(...msg) {
+    this._log(Loger.WARN, msg);
+  }
+
+  error(...msg) {
+    this._log(Loger.ERROR, msg);
+  }
+
+  _log(type, msg) {
+    msg = JSON.stringify(msg);
+
+    let logMsg = `${this.id} -> ${msg}`;
+    if (type >= Loger.logLevel) {
+      switch (type) {
+      case Loger.LOG:
+        console.log(logMsg);
+        break;
+      case Loger.WARN:
+        console.warn(logMsg);
+        break;
+      case Loger.ERROR:
+        console.error(logMsg);
+        break;
+      }
+    }
+  }
+}
+
+Loger.LOG = 0;
+Loger.WARN = 1;
+Loger.ERROR = 2;
+Loger.NO = Infinity;
+Loger.logLevel = Loger.LOG;
+
+export default {
+  getLoger: function getLoger(info) {
+    return new Loger(info);
+  },
+  setLogLevel: function setLogLevel(logLevel) {
+    Loger.logLevel = logLevel;
+  },
+  LOG: Loger.LOG,
+  WARN: Loger.WARN,
+  ERROR: Loger.ERROR,
+  NO: Loger.NO,
+};
+
diff --git a/src/MessageEngine.js b/src/MessageEngine.js
new file mode 100644
index 0000000..d7857c2
--- /dev/null
+++ b/src/MessageEngine.js
@@ -0,0 +1,144 @@
+import Emiter from './Emiter';
+import h5Sass from 'Sass';
+import mcu from 'mcu';
+import MessageTypes from 'MessageTypes';
+import Loger from 'Loger';
+import ConferApe from 'apes/ConferApe';
+import ChatApe from 'apes/ChatApe';
+import VideoChat from 'apes/VideoApe';
+import DocApe from 'apes/DocApe';
+import WhiteBoardApe from 'apes/WhiteBoardApe';
+
+let loger = Loger.getLoger('MessageEngine');
+
+export default class MessageEngine extends Emiter {
+  constructor() {
+    super();
+
+    // 应用层会议信息
+    this.confInfo = null;
+
+    // Sass平台层
+    this.h5Sass = h5Sass;
+    this.h5Sass.on('*', (type, data) => this.emit(type, data));
+    this.h5Sass.on(h5Sass.SUCCESS, this._h5SassSuccessHandler.bind(this));
+
+    // 底层MCU消息层
+    this.mcu = mcu;
+    this.mcu.on('*', (type, data) => this.emit(type, data));
+    this.mcu.on(MessageTypes.CONFERENCE_JOIN_SUCCESS, this._conferenceJoinSuccessHandler.bind(this));
+
+    // 注册所有应用Ape
+    this.conferApe = new ConferApe();
+    this.conferApe.on('*', (type, data) => this.emit(type, data));
+    this.conferApe.on(MessageTypes.DOC_SWITCH, this.docSwitchHandler.bind(this));
+
+    this.chat_ape = new ChatApe();
+    this.chat_ape.on('*', (type, data) => this.emit(type, data));
+
+    this.video_ape = new VideoChat();
+    this.video_ape.on('*', (type, data) => this.emit(type, data));
+
+    this.doc_ape = new DocApe();
+    this.doc_ape.on('*', (type, data) => this.emit(type, data));
+    this.doc_ape.on(MessageTypes.DOC_UPDATE, this.docUpdateHandler.bind(this));
+
+    this.doc_ape.on(MessageTypes.DOC_DEL, this.docDeleteHandler.bind(this));
+
+    this.wb_ape = new WhiteBoardApe();
+    this.wb_ape.on('*', (type, data) => this.emit(type, data));
+    this.wb_ape.on(MessageTypes.ANNO_UPDATE, this.annoUpdateHandler.bind(this));
+
+    this.on(MessageTypes.DOC_SHOW, this.docShowHandler.bind(this));
+  }
+
+  // 白板笔记更新
+  annoUpdateHandler(annoInfo) {
+    const activeDocId = this.conferApe.activeDocId;
+    const docItem = this.doc_ape.docList[activeDocId];
+    if (docItem && annoInfo.id == docItem.wbid) {
+      this.emit(MessageTypes.DOC_ANNO, annoInfo);
+    }
+  }
+
+  // 文档变更-笔记处理
+  docShowHandler(docItem) {
+    loger.log('Doc Show ->' + docItem.id + '|' + docItem.curPageNo);
+
+    const annoInfo = this.wb_ape.annoInfos[docItem.wbid];
+    if (annoInfo) {
+      this.emit(MessageTypes.DOC_ANNO, annoInfo);
+    } else {
+      this.emit(MessageTypes.DOC_ANNO);
+
+    }
+
+  }
+
+  // 文档切换
+  docSwitchHandler() {
+    const activeDocId = this.conferApe.activeDocId;
+    loger.log('Switch Doc Active -> ' + activeDocId);
+    const docItem = this.doc_ape.docList[activeDocId];
+    if (docItem) {
+      this.emit(MessageTypes.DOC_SHOW, docItem);
+    }
+  }
+
+  // 文档变更
+  docUpdateHandler(docItem) {
+    loger.log('Doc UpdateId ->' + docItem.id + '| activeDocId ->' + this.conferApe.activeDocId);
+    if (docItem.id == this.conferApe.activeDocId) {
+      this.emit(MessageTypes.DOC_SHOW, docItem);
+    }
+  }
+
+  //文档删除
+    docDeleteHandler(docItem){
+        if (docItem.id == this.conferApe.activeDocId) {
+            this.emit(MessageTypes.DOC_DEL, docItem);
+        }
+    }
+
+
+  // MCU 会议成功
+  _conferenceJoinSuccessHandler() {
+    this.h5Sass.getClassDetail();
+  }
+
+  // 通过SASS平台验证
+  _h5SassSuccessHandler() {
+    loger.log('加入底层MCU会议.');
+    this.mcu.joinMCU(this.confInfo);
+  }
+
+  // 进入会议
+  joinClass(_confInfo) {
+    this.confInfo = _confInfo;
+    this.h5Sass.sassChecking(_confInfo);
+  }
+
+  // 离开会议
+  leaveClass() {
+    this.mcu.leaveMCU();
+  }
+
+  // 参会处理
+  conferenceHandler(msg_type) {
+    var msg = {
+      type: msg_type,
+      data: null
+    };
+    this.emit(msg.type, msg);
+  }
+
+  // 发送聊天消息
+  sendChatMsg(to, msg) {
+    if (this.chat_ape) {
+      this.chat_ape.sendChatMsg(to, msg);
+    }
+  }
+}
+
+MessageEngine.MESSAGE_DELAY_TIMMER = 100;
+
diff --git a/src/MessageTypes.js b/src/MessageTypes.js
new file mode 100644
index 0000000..4c282a7
--- /dev/null
+++ b/src/MessageTypes.js
@@ -0,0 +1,46 @@
+// //////////////////////////////////////////////////////////////////////////////
+//
+//  Copyright (C) 2016-present  All Rights Reserved.
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Github Home: https://github.com/AlexWang1987
+//  Author: AlexWang
+//  Date: 2016-08-27 23:04:56
+//  QQ Email: 1669499355@qq.com
+//  Last Modified time: 2016-09-07 15:58:29
+//  Description: LiveClass-MessageTypes
+//
+// //////////////////////////////////////////////////////////////////////////////
+
+function MessageTypes() {}
+
+MessageTypes.CONFERENCE_SHOW_USERNAME = 'show.name.input';
+MessageTypes.CONFERENCE_SHOW_ILLEGAL = 'illegal.user';
+MessageTypes.CONFERENCE_SHOW_USERNAME_PASSWORD = 'show.name.pasword.input';
+MessageTypes.CONFERENCE_SHOW_WRONG_PASSWORD = 'password.wrong';
+MessageTypes.CONFERENCE_JOIN_FAILED = 'join.conference.failed';
+MessageTypes.CONFERENCE_JOIN_SUCCESS = 'join.conference.success';
+MessageTypes.CONFERENCE_JOIN_FULL = 'full.capacity';
+MessageTypes.CONFERENCE_EXIT = 'conference.exit';
+MessageTypes.CONFERENCE_JOIN_CONFILICT = 'have.been.logged.elsewhere';
+MessageTypes.CONFERENCE_SHOW_DETAIL = 'class_detail.message';
+MessageTypes.CONFERENCE_SHOW_ROSTER_NUM = 'roster_num.message';
+
+MessageTypes.NET_ERROR = 'net_error';
+MessageTypes.PRO_ERROR = 'pro_error';
+
+MessageTypes.VIDEO_SHOW = 'video.message';
+MessageTypes.DOC_SHOW = 'document.message';
+MessageTypes.DOC_SWITCH = 'document.switch';
+MessageTypes.DOC_UPDATE = 'document.update';
+MessageTypes.CHAT_RECEIVE = 'chat.message';
+MessageTypes.DOC_ANNO = 'document.anno';
+MessageTypes.ANNO_UPDATE = 'anno_update';
+
+MessageTypes.DOC_DEL='document.delete';
+MessageTypes.AUDIO_SHOW='audio.message';
+
+
+export default MessageTypes;
+
diff --git a/src/Sass.js b/src/Sass.js
new file mode 100644
index 0000000..facb2d2
--- /dev/null
+++ b/src/Sass.js
@@ -0,0 +1,141 @@
+import Emiter from 'Emiter';
+import Loger from 'Loger';
+import MessageTypes from 'MessageTypes';
+
+// 日志对象
+const loger = Loger.getLoger('H5Sass');
+
+let confInfo = {};
+class H5Sass extends Emiter {
+  constructor() {
+    super();
+  }
+
+  // Sass校验
+  sassChecking(_confInfo) {
+    loger.log('发起Sass校验', _confInfo);
+    this.confInfo = _confInfo;
+
+    // 密码校验
+    if (this.confInfo.nopassword === 'true') {
+      return this.sendPWDChecking();
+    }
+
+    // MD5校验
+    this.sendMD5Checking();
+  }
+
+  // 入会校验
+  sendPWDChecking() {
+    let url = `http://${this.confInfo.portal}/3m/getCheckMeetinig.do?siteId=${this.confInfo.siteId}&classId=${this.confInfo.confId}&password=${this.confInfo.password}`;
+    loger.log('会议密码校验', url);
+    fetch(url, {
+        timeout: 5000
+      })
+      .then(ret => {
+        if (ret.status === 200) {
+          return ret.text();
+        } else {
+          loger.error(`会议密码校验-网络异常.状态码:${ret.status}`);
+          this.emit(MessageTypes.NET_ERROR);
+          throw '';
+        }
+      })
+      .then(ret => {
+        if (ret === 'false') {
+          loger.error(`会议密码校验-失败.`);
+          return this.emit(MessageTypes.CONFERENCE_SHOW_WRONG_PASSWORD);
+        }
+        if (ret === 'true') {
+          loger.log(`会议密码校验-成功.`);
+          this.sendMD5Checking();
+          return
+        }
+        loger.error(`会议密码校验-协议异常.`);
+        this.emit(MessageTypes.PRO_ERROR);
+      })
+      .catch(err => {
+        loger.error(`会议密码校验-异常.状态码:${err}`);
+        this.emit(MessageTypes.NET_ERROR);
+      });
+  }
+
+  // 发起入会
+  sendMD5Checking() {
+    let url = `http://${this.confInfo.portal}/3m/meeting/md5CheckMeeting.do?siteId=${this.confInfo.siteId}&meetingNumber=${this.confInfo.confId}&userId=${this.confInfo.userId}&userName=${this.confInfo.userName}&userType=${this.confInfo.userType}&nopassword=${this.confInfo.nopassword}&md5=${this.confInfo.md5}`;
+
+    loger.log('H5SassMD5校验', url);
+
+    fetch(url, {
+        timeout: 5000
+      })
+      .then(ret => {
+        if (ret.status === 200) {
+          return ret.json();
+        } else {
+          loger.error(`H5SassMD5校验-网络异常.状态码:${ret.status}`);
+          this.emit(MessageTypes.NET_ERROR);
+          throw '';
+        }
+      })
+      .then(ret => {
+        if (ret.flag == "true") {
+          if (ret.h5_mcu_list) {
+            let server = ret.h5_mcu_list.split(";")[0];
+            this.confInfo.MCUServerIP = server.split(":")[0];
+            this.confInfo.MCUServerPort = server.split(":")[1];
+          }
+          this.confInfo.maxVideoChannels = ret.maxVideoChannels;
+          this.confInfo.maxAudioChannels = ret.maxAudioChannels;
+          this.confInfo.maxMediaChannels = this.confInfo.maxVideoChannels + this.confInfo.maxAudioChannels;
+          loger.log('H5Sass校验完成');
+          this.emit(H5Sass.SUCCESS);
+        } else {
+          loger.log('H5SassMD5校验-失败.');
+          this.emit(MessageTypes.CONFERENCE_JOIN_FAILED);
+        }
+      })
+      .catch(err => {
+        loger.error(`H5SassMD5校验-异常.状态码:${err}`);
+        this.emit(MessageTypes.NET_ERROR);
+      });
+  }
+
+  // 获取会议详情
+  getClassDetail() {
+    let url = `http://${this.confInfo.portal}/3m/meeting/getClassH5.do?classNumber=${this.confInfo.confId}`;
+
+    loger.log('H5Sass获取Class详情.', url);
+
+    fetch(url, {
+        timeout: 5000
+      })
+      .then(ret => {
+        if (ret.ok) {
+          return ret.json();
+        } else {
+          loger.error(`H5Sass获取Class详情-网络异常.状态码:${ret.status}`);
+          this.emit(MessageTypes.NET_ERROR);
+          throw '';
+        }
+      })
+      .then(ret => {
+        if (ret.errorCode === 0) {
+          loger.log('H5Sass获取Class详情完成');
+          this.emit(MessageTypes.CONFERENCE_SHOW_DETAIL, ret);
+        } else {
+          loger.warn('H5Sass获取Class详情失败.');
+          this.emit(MessageTypes.NET_ERROR);
+        }
+      })
+      .catch(err => {
+        loger.error(`H5Sass获取Class详情异常.状态码:${err}`);
+        this.emit(MessageTypes.NET_ERROR);
+      });
+  }
+}
+
+H5Sass.prototype.SUCCESS = H5Sass.SUCCESS = 'h5sass.success';
+
+export default new H5Sass;
+
diff --git a/src/apes/Ape.js b/src/apes/Ape.js
new file mode 100644
index 0000000..457a184
--- /dev/null
+++ b/src/apes/Ape.js
@@ -0,0 +1,319 @@
+// //////////////////////////////////////////////////////////////////////////////
+//
+//  Copyright (C) 2016-present  All Rights Reserved.
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Github Home: https://github.com/AlexWang1987
+//  Author: AlexWang
+//  Date: 2016-08-26 11:19:56
+//  QQ Email: 1669499355@qq.com
+//  Last Modified time: 2017-01-04 14:38:20
+//  Description: LiveClass-Ape
+//
+// //////////////////////////////////////////////////////////////////////////////
+
+import pdu from 'pdu';
+import Emiter from 'Emiter';
+import mcu from 'mcu';
+import Loger from 'Loger';
+import MessageTypes from 'MessageTypes';
+import ApeConsts from './ApeConsts';
+import ArrayBufferUtil from 'libs/ArrayBufferUtil';
+import PduConsts from 'pdu/PduConsts';
+
+// 日志对象
+const loger = Loger.getLoger('Ape');
+
+export default class Ape extends Emiter {
+  constructor(
+    session_id,
+    session_name,
+    session_tag
+  ) {
+    super();
+    this._session_id = session_id;
+    this._channel_id = session_id; // session_id === channel_id
+    this._session_name = session_name;
+    this._session_tag = session_tag;
+    this._session_channels = {};
+    this._adapter_pdu = new pdu['RCAdapterPdu'];
+    this._confInfo = null;
+    this._rCArrayBufferUtil = ArrayBufferUtil;
+    this._apeDelayed = false;
+    this._apeDelayedMsgs = [];
+    this._apeDelayedTimer = 0;
+
+    //Ape 通用消息处理
+    this.on(pdu.RCPDU_SESSION_JOIN_RESPONSE, this._joinSessionHandler.bind(this));
+    this.on(pdu.RCPDU_CHANNEL_JOIN_RESPONSE, this._joinChannelHandler.bind(this));
+    this.on(pdu.RCPDU_REG_ADAPTER, this._pduMessageHandler.bind(this));
+
+    // 监听底层MCU会议
+    this.mcu = mcu;
+    this.mcu.on(MessageTypes.CONFERENCE_JOIN_SUCCESS, this._mcuConferenceJoinSuccessHandler.bind(this));
+    this.mcu.registerApe(this);
+  }
+
+  regResponsePduHandler() {
+
+  }
+
+  // 消息处理
+  _pduMessageHandler(regBuffer) {
+    if (this._apeDelayed) {
+      // this._apeDelayedMsgs.push(regBuffer);
+      // this._apeDelayedStart();
+      setTimeout(() => {
+        this._pduRegAdapterHandler(regBuffer);
+      }, this._confInfo['mcuDelay'] || 12000);
+      return;
+    }
+    this._pduRegAdapterHandler(regBuffer);
+  }
+
+  // _apeDelayedStart() {
+  //   if (this._apeDelayed && !this._apeDelayedTimer) {
+  //     this._apeDelayedTimer = setInterval(this._delayedMsgHandler.bind(this), this._confInfo['mcuDelay'] || 10000);
+  //   }
+  // }
+
+  // _apeDelayedStop() {
+  //   clearInterval(this._apeDelayedTimer);
+  //   this._apeDelayedTimer = 0;
+  // }
+
+  // // 延迟消息处理
+  // _delayedMsgHandler() {
+  //   if (this._apeDelayedMsgs.length) {
+  //     this._pduRegAdapterHandler(this._apeDelayedMsgs.pop());
+  //     if (!this._apeDelayedMsgs.length) this._apeDelayedStop();
+  //   }
+  // }
+
+  // 数据同步处理
+  _pduRegAdapterHandler(regBuffer) {
+    let regPdu = pdu['RCAdapterPdu'].decode(regBuffer);
+    let regItems = regPdu.item;
+    let regItemSize = regItems.length;
+
+    loger.log(this._session_name + '数据同步消息.同步条数', regItemSize);
+
+    for (var i = 0; i < regItemSize; ++i) {
+      let regItem = regItems[i];
+      let regItemType = regItem.type;
+      let regItemData = regItem.itemData;
+
+      if (pdu.RCPDU_REG_UPDATE_OBJ !== regItemType) {
+        if (pdu.RCPDU_REG_RESPONSE_OBJ == regItemType) {
+          let regResponsePdu = pdu['RCRegistryResponseObjPdu'].decode(regItemData);
+          this.regResponsePduHandler(regResponsePdu);
+        }
+        // 只处理两种类型
+        continue;
+      }
+
+      let regUpdatedItem = pdu['RCRegistryUpdateObjPdu'].decode(regItemData);
+      let sub_type = regUpdatedItem.subType;
+      let object_id = regUpdatedItem.objId;
+      let user_data = regUpdatedItem.userData;
+
+      loger.log('REG OBJECT EVENT ->', pdu.id2type(sub_type));
+      switch (sub_type) {
+      case pdu.RCPDU_REG_ROSTER_INSERT_PDU:
+        let rosterInsertData = pdu['RCRegstryRosterInsertItemPdu'].decode(user_data);
+        let rosterInsertItems = rosterInsertData.items;
+        let rosterInsertItemsLen = rosterInsertItems.length;
+        for (let i = 0; i < rosterInsertItemsLen; ++i) {
+          let record = rosterInsertItems[i];
+          let recordId = record.item_id;
+          let recordData = pdu['RCNodeInfoRecordPdu'].decode(record.item_data);
+          this.rosterInsertHandler(recordId, recordData);
+        }
+        break;
+      case pdu.RCPDU_REG_ROSTER_DELETE_PDU:
+        let rosterDelData = pdu['RCRegistryRosterDeleteItemPdu'].decode(user_data);
+        this.rosterDelHandler(rosterDelData.nodeId);
+        break;
+      case pdu.RCPDU_REG_ROSTER_UPDATE_PDU:
+        let rosterUpdateData = pdu['RCRegistryRosterUpdateItemPdu'].decode(user_data);
+        let rosterUpdateItems = rosterUpdateData.items;
+        let rosterUpdateItemsLen = rosterUpdateItems.length;
+        for (let i = 0; i < rosterUpdateItemsLen; ++i) {
+          let node = rosterUpdateItems[i];
+          let nodeId = node.nodeId;
+          let nodeData = pdu['RCNodeInfoRecordPdu'].decode(node.nodeData);
+          this.rosterUpdateHandler(nodeId, nodeData);
+        }
+        break;
+      case pdu.RCPDU_REG_TABLE_INSERT_PDU:
+        let tableInsertData = pdu['RCRegistryTableInsertItemPdu'].decode(user_data);
+        let tableInsertItems = tableInsertData.items;
+        let tableInsertItemsLen = tableInsertItems.length;
+        for (let i = 0; i < tableInsertItemsLen; ++i) {
+          let insertItem = tableInsertItems[i];
+          this.tableInsertHandler(insertItem.owner, insertItem.itemIdx, insertItem.itemData);
+        }
+        break;
+      case pdu.RCPDU_REG_TABLE_DELETE_PDU:
+        let tableDeleteData = pdu['RCRegistryTableDeleteItemPdu'].decode(user_data);
+        this.tableDeleteHandler(object_id, tableDeleteData);
+        break;
+      case pdu.RCPDU_REG_TABLE_UPDATE_PDU:
+        let tableUpdateData = pdu['RCRegistryTableUpdateItemPdu'].decode(user_data);
+        let tableUpdateItems = tableUpdateData.items;
+        let tableUpdateItemsLen = tableUpdateItems.length;
+        for (let i = 0; i < tableUpdateItemsLen; ++i) {
+          let tableItem = tableUpdateItems[i];
+          this.tableUpdateHandler(tableItem.owner, tableItem.itemIdx, tableItem.itemData);
+        }
+        break;
+      case pdu.RCPDU_REG_QUEUE_UPDATE_PDU:
+      case pdu.RCPDU_REG_QUEUE_DELETE_PDU:
+      case pdu.RCPDU_REG_QUEUE_INSERT_PDU:
+        loger.warn('REG QUEUE ARE IGNORED');
+        break;
+
+      }
+    }
+  }
+
+  rosterInsertHandler(recordId, recordData) {
+    loger.warn(this._session_name + ' rosterInsertHandler 应有子类具体覆盖处理.');
+  }
+  rosterUpdateHandler(nodeId, nodeData) {
+    loger.warn(this._session_name + ' rosterUpdateHandler 应有子类具体覆盖处理.');
+  }
+  rosterDelHandler(recordData) {
+    loger.warn(this._session_name + ' rosterDelHandler 应有子类具体覆盖处理.');
+  }
+  tableInsertHandler(tableId, record) {
+    loger.warn(this._session_name + ' tableInsertHandler 应有子类具体覆盖处理.');
+  }
+  tableUpdateHandler(ownerId, recordId, recordData) {
+    loger.warn(this._session_name + ' tableUpdateHandler 应有子类具体覆盖处理.');
+  }
+  tableDeleteHandler(tableId, record) {
+    loger.warn(this._session_name + ' tableDelHandler 应有子类具体覆盖处理.');
+  }
+
+  // 加入Session处理
+  _joinSessionHandler(data) {
+    loger.log(this._session_name, ' -> 加入Session');
+  }
+
+  // 加入Channel处理
+  _joinChannelHandler(data) {
+    let joinedChannel = pdu['RCChannelJoinResponsePdu'].decode(data);
+    if (joinedChannel.result === pdu.RET_SUCCESS) {
+      loger.log(this._session_name, ' -> 加入Channel成功. ChannelId', joinedChannel.requestedChannelId);
+      this._session_channels[joinedChannel.requestedChannelId] = ApeConsts.CJS_JOINNED;
+    } else {
+      loger.log(this._session_name, ' -> 加入Channel失败.', joinedChannel);
+    }
+  }
+
+  // 依赖的会议创建完毕 - 发起Ape加入
+  _mcuConferenceJoinSuccessHandler(confInfo) {
+    loger.log('创建Ape->',
+      'SessionId',
+      this._session_id,
+      'SessionName',
+      this._session_name,
+      'SessionTag',
+      this._session_tag);
+
+    // 会议依赖底层会议信息
+    this._confInfo = confInfo;
+
+    var joinSessionPdu = new pdu['RCSessionJoinRequestPdu'];
+    joinSessionPdu.id = this._session_id;
+    joinSessionPdu.name = this._session_name;
+    joinSessionPdu.tag = this._session_tag;
+    joinSessionPdu.sessionData = this._adapter_pdu.toArrayBuffer();
+    this.sendUniform(joinSessionPdu, true);
+
+    var joinChannelPdu = new pdu['RCChannelJoinRequestPdu'];
+    joinChannelPdu.initiator = this.mcu.confInfo.nodeId;
+    joinChannelPdu.channelId = this._session_id;
+    this.send(joinChannelPdu);
+  }
+
+  // 注册Key对象
+  registerKey(id, name, tag, user_data) {
+    let adapterItemPdu = new pdu['RCAdapterItemPdu'];
+    adapterItemPdu.type = pdu.RCPDU_REG_REGISTER_KEY;
+
+    // pack register key pdu
+    let registerKeyPdu = new pdu['RCRegistryRegisterKeyPdu'];
+    registerKeyPdu.id = id;
+    registerKeyPdu.name = name;
+    registerKeyPdu.tag = tag;
+    if (user_data.length) {
+      registerKeyPdu.userData = user_data;
+    }
+
+    adapterItemPdu.itemData = registerKeyPdu.toArrayBuffer();
+    this._adapter_pdu.item.push(adapterItemPdu);
+  }
+
+  // 注册Object对象
+  registerObj(type, id, name, tag, owner, user_data) {
+    let adapterItemPdu = new pdu['RCAdapterItemPdu'];
+    adapterItemPdu.type = pdu.RCPDU_REG_REGISTER_OBJ;
+
+    let registerObjPdu = new pdu['RCRegistryRegisterObjPdu'];
+    registerObjPdu.type = type;
+    registerObjPdu.objId = id;
+    registerObjPdu.name = name;
+    registerObjPdu.tag = tag;
+    if (owner) {
+      registerObjPdu.owner = owner;
+    }
+    if (user_data.length) {
+      registerObjPdu.userData = user_data;
+    }
+
+    adapterItemPdu.itemData = registerObjPdu.toArrayBuffer();
+    this._adapter_pdu.item.push(adapterItemPdu);
+  }
+
+  send(appPdu) {
+    loger.log('Ape发送数据NORMAL PDU', appPdu);
+    let normalPdu = pdu.create_normal_pdu(
+      appPdu.type,
+      this._confInfo.nodeId,
+      this._confInfo.confId,
+      this._session_id,
+      this._channel_id,
+      true,
+      true,
+      PduConsts.DP_TOP,
+      this._confInfo.topNodeID,
+      PduConsts.SEG_ONCE
+    );
+    normalPdu.data = appPdu.toArrayBuffer();
+    // Mcu发送
+    this.mcu.send(normalPdu);
+  }
+
+  // 发送当前APE(session uniform包)
+  sendUniform(appPdu, top) {
+    loger.log('Ape发送数据UNIFORM PDU', appPdu);
+    let uniformPdu = pdu.create_uniform_pdu(
+      appPdu.type,
+      this._confInfo.nodeId,
+      this._confInfo.confId,
+      this._session_id,
+      top ? ApeConsts.BROADCAST_CHANNEL_ID : this._channel_id,
+      true,
+      PduConsts.DP_TOP,
+      top ? this._confInfo.topNodeID : (appPdu.peer || 0),
+      PduConsts.SEG_ONCE
+    );
+    uniformPdu.data = appPdu.toArrayBuffer();
+    // Mcu发送
+    this.mcu.send(uniformPdu);
+  }
+}
+
diff --git a/src/apes/ApeConsts.js b/src/apes/ApeConsts.js
new file mode 100644
index 0000000..fb635a1
--- /dev/null
+++ b/src/apes/ApeConsts.js
@@ -0,0 +1,162 @@
+export default function ApeConsts(id) {
+  for (var type_const in ApeConsts) {
+    if (ApeConsts[type_const] === id) {
+      return type_const;
+    }
+  }
+}
+
+ApeConsts.NR_GUEST = 0;
+ApeConsts.NR_NORMAL = 1;
+ApeConsts.NR_ADMIN = 2;
+ApeConsts.NR_MASTER = 4;
+ApeConsts.NR_SLAVE = 8;
+ApeConsts.NR_ASSISTANT = 16;
+ApeConsts.NR_INVISIBLE = 32;
+
+ApeConsts.INVALIDATE_CHANNEL_ID = -1;
+ApeConsts.INVALIDATE_NODE_ID = -1;
+
+// doc update status
+ApeConsts.DOCUMENT_LOAD = 0;
+ApeConsts.DOCUMENT_DEL = 1;
+
+// RCChannelGrabStatus
+ApeConsts.CGS_RELEASED = 0;
+ApeConsts.CGS_PENDING = 1;
+ApeConsts.CGS_OPENNED = 2;
+ApeConsts.CGS_GRABBING = 3;
+
+// defs for common session id
+ApeConsts.CONFERENCE_SESSION_ID = 11;
+ApeConsts.CHAT_SESSION_ID = 12;
+ApeConsts.GIFT_SESSION_ID = 13;
+ApeConsts.AUDIO_SESSION_ID = 14;
+ApeConsts.VIDEO_SESSION_ID = 15;
+ApeConsts.WEBSHARING_SESSION_ID = 16;
+ApeConsts.DOCSHARING_SESSION_ID = 17;
+ApeConsts.WHITEBOARD_SESSION_ID = 18;
+ApeConsts.MEDIA_SESSION_ID = 19;
+ApeConsts.SCREENSHARING_SESSION_ID = 20;
+ApeConsts.POLL_SESSION_ID = 21;
+
+// defs for common channel id
+ApeConsts.BROADCAST_CHANNEL_ID = 0;
+ApeConsts.CONFERENCE_CHANNEL_ID = ApeConsts.CONFERENCE_SESSION_ID;
+ApeConsts.CHAT_CHANNEL_ID = ApeConsts.CHAT_SESSION_ID;
+ApeConsts.GIFT_CHANNEL_ID = ApeConsts.GIFT_SESSION_ID;
+ApeConsts.WEBSHARING_CHANNEL_ID = ApeConsts.WEBSHARING_SESSION_ID;
+ApeConsts.DOCSHARING_CHANNEL_ID = ApeConsts.DOCSHARING_SESSION_ID;
+ApeConsts.WHITEBOARD_CHANNEL_ID = ApeConsts.WHITEBOARD_SESSION_ID;
+ApeConsts.MEDIA_CHANNEL_ID = ApeConsts.MEDIA_SESSION_ID;
+ApeConsts.SCREENSHARING_CHANNEL_ID = ApeConsts.SCREENSHARING_SESSION_ID;
+
+// defs for common session name
+ApeConsts.CONFERENCE_SESSION_NAME = "conference app";
+ApeConsts.CHAT_SESSION_NAME = "chat app";
+ApeConsts.GIFT_SESSION_NAME = "gift app";
+ApeConsts.AUDIO_SESSION_NAME = "audio app";
+ApeConsts.VIDEO_SESSION_NAME = "video app";
+ApeConsts.WEBSHARING_SESSION_NAME = "web sharing app";
+ApeConsts.DOCSHARING_SESSION_NAME = "doc sharing app";
+ApeConsts.WHITEBOARD_SESSION_NAME = "whiteboard app";
+ApeConsts.MEDIA_SESSION_NAME = "media sharing app";
+ApeConsts.SCREENSHARING_SESSION_NAME = "screen sharing app";
+
+// def for common session tag
+ApeConsts.CONFERENCE_SESSION_TAG = "con-tag";
+ApeConsts.CHAT_SESSION_TAG = "cha-tag";
+ApeConsts.GIFT_SESSION_TAG = "gif-tag";
+ApeConsts.AUDIO_SESSION_TAG = "aud-tag";
+ApeConsts.VIDEO_SESSION_TAG = "vid-tag";
+ApeConsts.WEBSHARING_SESSION_TAG = "web-tag";
+ApeConsts.DOCSHARING_SESSION_TAG = "doc-tag";
+ApeConsts.WHITEBOARD_SESSION_TAG = "wbd-tag";
+ApeConsts.MEDIA_SESSION_TAG = "med-tag";
+ApeConsts.SCREENSHARING_SESSION_TAG = "scr-tag";
+
+ApeConsts.CONFERENCE_OBJ_ROSTER_ID = ((ApeConsts.CONFERENCE_SESSION_ID << 16) + 1);
+ApeConsts.CONFERENCE_OBJ_ROSTER_NAME = "node list";
+ApeConsts.CONFERENCE_OBJ_ROSTER_TAG = "node list tag";
+
+ApeConsts.CONFERENCE_OBJ_QUEUE_ID = ((ApeConsts.CONFERENCE_SESSION_ID << 16) + 2);
+ApeConsts.CONFERENCE_OBJ_QUEUE_NAME = "mic list";
+ApeConsts.CONFERENCE_OBJ_QUEUE_TAG = "mic list tag";
+
+// conference tab pages
+ApeConsts.CONFERENCE_OBJ_TABLE_ID = ((ApeConsts.CONFERENCE_SESSION_ID << 16) + 3);
+ApeConsts.CONFERENCE_OBJ_TABLE_NAME = "tabbar list";
+ApeConsts.CONFERENCE_OBJ_TABLE_TAG = "tabbar list tag";
+
+// owned id list is unique in conference
+ApeConsts.CONFERENCE_OBJ_COUNTER_ID = ((ApeConsts.CONFERENCE_SESSION_ID << 16) + 4);
+ApeConsts.CONFERENCE_OBJ_COUNTER_NAME = "id list";
+ApeConsts.CONFERENCE_OBJ_COUNTER_TAG = "id list tag";
+
+// web sharing objects
+ApeConsts.WEBSHARING_OBJ_TABLE_ID = ((ApeConsts.WEBSHARING_SESSION_ID << 16) + 1);
+ApeConsts.WEBSHARING_OBJ_TABLE_NAME = "web list";
+ApeConsts.WEBSHARING_OBJ_TABLE_TAG = "web list tag";
+
+// doc sharing objects
+ApeConsts.DOCSHARING_OBJ_TABLE_ID = ((ApeConsts.DOCSHARING_SESSION_ID << 16) + 1);
+ApeConsts.DOCSHARING_OBJ_TABLE_NAME = "doc list";
+ApeConsts.DOCSHARING_OBJ_TABLE_TAG = "doc list tag";
+
+// doc sharing objects h5
+ApeConsts.DOCSHARING_OBJ_TABLE_ID_H5 = ((ApeConsts.DOCSHARING_SESSION_ID << 16) + 2);
+ApeConsts.DOCSHARING_OBJ_TABLE_NAME_H5 = "doc list h5";
+ApeConsts.DOCSHARING_OBJ_TABLE_TAG_H5 = "doc list tag h5";
+
+// whiteboard objects
+ApeConsts.WHITEBOARD_OBJ_TABLE_ID = ((ApeConsts.WHITEBOARD_SESSION_ID << 16) + 1);
+ApeConsts.WHITEBOARD_OBJ_TABLE_NAME = "wbd list";
+ApeConsts.WHITEBOARD_OBJ_TABLE_TAG = "wbd list tag";
+
+// media sharing objects
+ApeConsts.MEDIA_OBJ_TABLE_ID = ((ApeConsts.MEDIA_SESSION_ID << 16) + 1);
+ApeConsts.MEDIA_OBJ_TABLE_NAME = "med list";
+ApeConsts.MEDIA_OBJ_TABLE_TAG = "med list tag";
+
+// chat sharing objects
+ApeConsts.CHAT_OBJ_TABLE_ID = ((ApeConsts.CHAT_SESSION_ID << 16) + 1);
+ApeConsts.CHAT_OBJ_TABLE_NAME = "chat list";
+ApeConsts.CHAT_OBJ_TABLE_TAG = "chat list tag";
+
+ApeConsts.AUDIO_OBJ_TABLE_ID = ((ApeConsts.AUDIO_SESSION_ID << 16) + 1);
+ApeConsts.AUDIO_OBJ_TABLE_NAME = "audio channel list";
+ApeConsts.AUDIO_OBJ_TABLE_TAG = "audio channel list tag";
+
+ApeConsts.VIDEO_OBJ_TABLE_ID = ((ApeConsts.VIDEO_SESSION_ID << 16) + 1);
+ApeConsts.VIDEO_OBJ_TABLE_NAME = "video channel list";
+ApeConsts.VIDEO_OBJ_TABLE_TAG = "video channel list tag";
+
+// screen sharing objects
+ApeConsts.SCREENSHARING_OBJ_TABLE_ID = ((ApeConsts.SCREENSHARING_SESSION_ID << 16) + 1);
+ApeConsts.SCREEN_OBJ_TABLE_NAME = "scr list";
+ApeConsts.SCREEN_OBJ_TABLE_TAG = "scr list tag";
+
+// poll sharing objects
+ApeConsts.POLL_OBJ_TABLE_ID = ((ApeConsts.POLL_SESSION_ID << 16) + 1);
+ApeConsts.VOTE_OBJ_TABLE_ID = ((ApeConsts.POLL_SESSION_ID << 16) + 2);
+ApeConsts.RECORD_OBJ_TABLE_ID = ((ApeConsts.POLL_SESSION_ID << 16) + 3);
+ApeConsts.SHAMLIVE_OBJ_TABLE_ID = ((ApeConsts.POLL_SESSION_ID << 16) + 4);
+
+ApeConsts.POLL_OBJ_TABLE_NAME = "poll list";
+ApeConsts.POLL_OBJ_TABLE_TAG = "poll list tag";
+
+// registry operation const
+ApeConsts.REG_TABLE_INSERT_TAIL = 0xFFFFFF;
+ApeConsts.REG_TABLE_DELETE_ALL = 0xFFFFFF;
+
+ApeConsts.CJS_RELEASED = 0;
+ApeConsts.CJS_JOINNING = 1;
+ApeConsts.CJS_JOINNED = 2;
+
+// 白板动作
+ApeConsts.WBA_CLOSE = 1;
+ApeConsts.WBA_CHANGE = 2;
+ApeConsts.WBA_OPEN = 3;
+ApeConsts.WBA_DOC_ANNOTATION = 4;
+ApeConsts.WBA_LASER_PEN = 5;
+
diff --git a/src/apes/ChatApe.js b/src/apes/ChatApe.js
new file mode 100644
index 0000000..b8ad8cd
--- /dev/null
+++ b/src/apes/ChatApe.js
@@ -0,0 +1,79 @@
+// //////////////////////////////////////////////////////////////////////////////
+//
+//  Copyright (C) 2016-present  All Rights Reserved.
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Github Home: https://github.com/AlexWang1987
+//  Author: AlexWang
+//  Date: 2016-08-14 15:55:42
+//  QQ Email: 1669499355@qq.com
+//  Last Modified time: 2016-09-06 16:28:14
+//  Description: LiveClass-ChatApe
+//
+// //////////////////////////////////////////////////////////////////////////////
+
+import Ape from './Ape';
+import ApeConsts from './ApeConsts';
+import pdu from 'pdu';
+import Loger from 'Loger';
+import MessageTypes from 'MessageTypes';
+
+let loger = Loger.getLoger('ChatApe');
+
+class ChatApe extends Ape {
+  constructor() {
+    super(
+      ApeConsts.CHAT_SESSION_ID,
+      ApeConsts.CHAT_SESSION_NAME,
+      ApeConsts.CHAT_SESSION_TAG
+    );
+
+    //Ape Models
+    this.registerKey(this._session_id, this._session_name, this._session_tag, new ArrayBuffer);
+    this.registerObj(pdu.RCPDU_REG_REGISTER_TABLE, ApeConsts.CHAT_OBJ_TABLE_ID,
+      ApeConsts.CHAT_OBJ_TABLE_NAME, ApeConsts.CHAT_OBJ_TABLE_TAG, 0, new ArrayBuffer);
+
+    // ape listeners
+    this.on(pdu.RCPDU_CHAT_SEND_DATA_REQUEST, this.chatMsgIncomingHandler.bind(this));
+  }
+
+  chatMsgIncomingHandler(pdu) {
+
+  }
+
+  sendChatMsg(to, message) {
+    loger.log('发送聊天消息.', to, message);
+    let chatSendPdu = new pdu['RCChatSendDataRequestPdu'];
+    chatSendPdu.type = pdu.RCPDU_CHAT_SEND_DATA_REQUEST;
+    chatSendPdu.initiator = this._confInfo.nodeId;
+    chatSendPdu.peer = to;
+    chatSendPdu.isPublic = true;
+    chatSendPdu.userData = this._rCArrayBufferUtil.strToUint8Array("h5" + message);
+    chatSendPdu.fromName = this._rCArrayBufferUtil.strToUint8Array("h5" + this._confInfo.userName);
+    chatSendPdu.fromRole = this._confInfo.classRole;
+    if (!(chatSendPdu.isPublic || 0 === chatSendPdu.peer)) {
+      this.send(chatSendPdu);
+    } else {
+      this.sendUniform(chatSendPdu);
+    }
+  }
+
+  chatMsgIncomingHandler(chatBuffer) {
+    var chatReceivePdu = pdu['RCChatSendDataRequestPdu'].decode(chatBuffer);
+
+    var chatMsg = {};
+    chatMsg.fromNodeID = chatReceivePdu.initiator;
+    chatMsg.toNodeID = chatReceivePdu.peer;
+    chatMsg.message = this._rCArrayBufferUtil.uint8ArrayToStr(chatReceivePdu.userData, 2);
+    chatMsg.fromName = this._rCArrayBufferUtil.uint8ArrayToStr(chatReceivePdu.fromName, 2);
+    chatMsg.fromRole = chatReceivePdu.fromRole;
+
+    loger.log('接受聊天消息.', chatMsg);
+
+    this.emit(MessageTypes.CHAT_RECEIVE, chatMsg);
+  }
+}
+
+export default ChatApe;
+
diff --git a/src/apes/ConferApe.js b/src/apes/ConferApe.js
new file mode 100644
index 0000000..5b1a982
--- /dev/null
+++ b/src/apes/ConferApe.js
@@ -0,0 +1,148 @@
+// //////////////////////////////////////////////////////////////////////////////
+//
+//  Copyright (C) 2016-present  All Rights Reserved.
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Github Home: https://github.com/AlexWang1987
+//  Author: AlexWang
+//  Date: 2016-08-22 09:57:12
+//  QQ Email: 1669499355@qq.com
+//  Last Modified time: 2016-09-07 16:16:35
+//  Description: LiveClass-ConferApe
+//
+// //////////////////////////////////////////////////////////////////////////////
+
+import Ape from './Ape';
+import ApeConsts from './ApeConsts';
+import MessageTypes from 'MessageTypes';
+import pdu from 'pdu';
+import { Zlib } from 'zlibjs/bin/zlib.min';
+import UTF8 from 'utf-8';
+import Loger from 'Loger';
+let loger = Loger.getLoger('ConferApe');
+
+class ConferApe extends Ape {
+  constructor() {
+    super(
+      ApeConsts.CONFERENCE_SESSION_ID,
+      ApeConsts.CONFERENCE_SESSION_NAME,
+      ApeConsts.CONFERENCE_SESSION_TAG
+    );
+
+    // Attribures
+    this.hostNodeId = -1;
+    this.hostUserId = '';
+    this.rosters = {};
+    this.activeDocId = '';
+
+    // Ape Models
+    this.registerKey(this._session_id, this._session_name, this._session_tag, new ArrayBuffer);
+    this.registerObj(pdu.RCPDU_REG_REGISTER_ROSTER, ApeConsts.CONFERENCE_OBJ_ROSTER_ID,
+      ApeConsts.CONFERENCE_OBJ_ROSTER_NAME, ApeConsts.CONFERENCE_OBJ_ROSTER_TAG, 0, new ArrayBuffer);
+    this.registerObj(pdu.RCPDU_REG_REGISTER_QUEUE, ApeConsts.CONFERENCE_OBJ_QUEUE_ID,
+      ApeConsts.CONFERENCE_OBJ_QUEUE_NAME, ApeConsts.CONFERENCE_OBJ_QUEUE_TAG, 0, new ArrayBuffer);
+    this.registerObj(pdu.RCPDU_REG_REGISTER_TABLE, ApeConsts.CONFERENCE_OBJ_TABLE_ID,
+      ApeConsts.CONFERENCE_OBJ_TABLE_NAME, ApeConsts.CONFERENCE_OBJ_TABLE_TAG, 0, new ArrayBuffer);
+    this.registerObj(pdu.RCPDU_REG_REGISTER_COUNTER, ApeConsts.CONFERENCE_OBJ_COUNTER_ID,
+      ApeConsts.CONFERENCE_OBJ_COUNTER_NAME, ApeConsts.CONFERENCE_OBJ_COUNTER_TAG, 0, new ArrayBuffer);
+
+    this.on(pdu.RCPDU_SESSION_JOIN_RESPONSE, this._joinSessionHandler.bind(this));
+  }
+
+  _joinSessionHandler(confInfo) {
+    let nodeInfoRecordPdu = this.mcu.mcuConfInfo.self;
+
+    let userDataPdu = new pdu['RCNodeInfoUserDataPdu'];
+    userDataPdu.qq = '';
+    userDataPdu.skype = '';
+    userDataPdu.mobile = '';
+
+    nodeInfoRecordPdu.userData = userDataPdu.toArrayBuffer();
+    nodeInfoRecordPdu.deviceType = 3;
+
+    let item = new pdu['RCRegistryRosterItemPdu'];
+    item.nodeId = nodeInfoRecordPdu.nodeId;
+    item.nodeData = nodeInfoRecordPdu.toArrayBuffer();
+
+    let rosterUpdateItem = new pdu['RCRegistryRosterUpdateItemPdu'];
+    rosterUpdateItem.type = pdu.RCPDU_REG_ROSTER_UPDATE_PDU;
+    rosterUpdateItem.items.push(item);
+
+    let updateObjPdu = new pdu['RCRegistryUpdateObjPdu'];
+    updateObjPdu.objId = ApeConsts.CONFERENCE_OBJ_ROSTER_ID;
+    updateObjPdu.subType = rosterUpdateItem.type;
+    updateObjPdu.userData = rosterUpdateItem.toArrayBuffer();
+
+    let adapterItemPdu = new pdu['RCAdapterItemPdu'];
+    adapterItemPdu.type = pdu.RCPDU_REG_UPDATE_OBJ;
+    adapterItemPdu.itemData = updateObjPdu.toArrayBuffer();
+
+    let adapterPdu = new pdu['RCAdapterPdu'];
+    adapterPdu.type = pdu.RCPDU_REG_ADAPTER;
+    adapterPdu.item.push(adapterItemPdu);
+
+    this.sendUniform(adapterPdu, true);
+
+
+  }
+
+
+  tableUpdateHandler(owner, recordId, recordData) {
+    try {
+      let tabUpdatePdu = pdu.RCTabUpdateDataRequestPdu.decode(recordData);
+      const uncompressedBytes = new Zlib.Inflate(tabUpdatePdu.action.compact().view).decompress();
+      let tabInfo = UTF8.getStringFromBytes(uncompressedBytes);
+      let tabTypeMatches = tabInfo.match(/<TabType>(.+)<\/TabType>/);
+      if (tabTypeMatches.length > 1 && tabTypeMatches[1] == 'show.docsharing') {
+        if (tabInfo.match(/<visible>(.+)<\/visible>/)[1] == 'true') {
+          this.activeDocId = tabInfo.match(/<TabID>(.+)<\/TabID>/)[1];
+          this.emit(MessageTypes.DOC_SWITCH, this.activeDocId);
+        }
+      }
+    } catch (e) {
+      loger.warn('ConferApe table update got exception.');
+    }
+  }
+
+
+  rosterInsertHandler(nodeId, nodeData) {
+    this.rosterUpdateHandler(nodeId, nodeData);
+  }
+
+  rosterUpdateHandler(nodeId, nodeData) {
+    if (nodeData.role === ApeConsts.NR_MASTER ||
+      nodeData.role === ApeConsts.NR_SLAVE) {
+      this.hostNodeId = nodeData.nodeId;
+      this.hostUserId = nodeData.userId;
+    }
+    if (nodeData.role === ApeConsts.NR_NORMAL &&
+      this.hostNodeId === nodeData.nodeId) {
+      this.hostNodeId = -1;
+      this.hostUserId = '';
+    }
+    let rosterExists = this.rosters[nodeId];
+    this.rosters[nodeId] = nodeData;
+    if (!rosterExists) {
+      this.emitRosterChange();
+    }
+
+
+  }
+
+  rosterDelHandler(nodeId) {
+    delete this.rosters[nodeId];
+    this.emitRosterChange();
+    // 自己退出
+    if (nodeId == this._confInfo.nodeId) {
+      this.emit(MessageTypes.CONFERENCE_EXIT);
+    }
+  }
+
+  emitRosterChange() {
+    this.emit(MessageTypes.CONFERENCE_SHOW_ROSTER_NUM, Object.keys(this.rosters).length);
+  }
+}
+
+export default ConferApe;
+
diff --git a/src/apes/DocApe.js b/src/apes/DocApe.js
new file mode 100644
index 0000000..a8682d0
--- /dev/null
+++ b/src/apes/DocApe.js
@@ -0,0 +1,79 @@
+// //////////////////////////////////////////////////////////////////////////////
+//
+//  Copyright (C) 2016-present  All Rights Reserved.
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Github Home: https://github.com/AlexWang1987
+//  Author: AlexWang
+//  Date: 2016-08-26 17:30:43
+//  QQ Email: 1669499355@qq.com
+//  Last Modified time: 2016-09-21 14:12:25
+//  Description: LiveClass-DocApe
+//
+// //////////////////////////////////////////////////////////////////////////////
+
+import Ape from './Ape';
+import ApeConsts from './ApeConsts';
+import pdu from 'pdu';
+import Loger from 'Loger';
+import MessageTypes from 'MessageTypes';
+
+let loger = Loger.getLoger('DocApe');
+
+class DocApe extends Ape {
+  constructor() {
+    super(
+      ApeConsts.DOCSHARING_SESSION_ID,
+      ApeConsts.DOCSHARING_SESSION_NAME,
+      ApeConsts.DOCSHARING_SESSION_TAG
+    );
+    this.docList = {};
+
+
+    // Ape Models
+    this.registerKey(this._session_id, this._session_name, this._session_tag, new ArrayBuffer);
+    this.registerObj(pdu.RCPDU_REG_REGISTER_TABLE, ApeConsts.DOCSHARING_OBJ_TABLE_ID_H5, ApeConsts.DOCSHARING_OBJ_TABLE_NAME_H5, ApeConsts.DOCSHARING_OBJ_TABLE_TAG_H5, 0, new ArrayBuffer);
+
+    // 延迟
+      this._apeDelayed = true;
+  }
+
+  tableInsertHandler(owner, tableId, recordData) {
+    this.tableUpdateHandler(owner, tableId, recordData);
+  }
+
+  tableDeleteHandler(tableId, record){
+      const re={};
+      re.type=ApeConsts.DOCUMENT_DEL;
+      this.emit(MessageTypes.DOC_DEL, re);
+  }
+  tableUpdateHandler(owner, recordId, recordData) {
+    try {
+      const recordInfo = pdu['RCDocSendDataRequestPdu'].decode(recordData);
+      recordInfo.type = ApeConsts.DOCUMENT_LOAD;
+
+      recordInfo.ext = recordInfo.name.substr(recordInfo.name.indexOf('.') + 1);
+      recordInfo.wbid = (recordInfo.id << 10) + recordInfo.curPageNo;
+      recordInfo.isPicture = ~['bmp', 'png', 'gif', 'jpg', 'jpeg'].indexOf(recordInfo.ext);
+
+      if (recordInfo.isPicture) {
+        recordInfo.namePath = recordInfo.uri.substring(0, recordInfo.uri.lastIndexOf('.'));
+        recordInfo.loadURL = recordInfo.namePath + '.' + recordInfo.ext;
+      } else {
+        recordInfo.namePath = recordInfo.uri.substring(0, recordInfo.uri.lastIndexOf('/'));
+        recordInfo.loadURL = `${recordInfo.namePath}/${recordInfo.curPageNo}.jpg`;
+      }
+      this.docList[recordId] = recordInfo;
+      this.emit(MessageTypes.DOC_UPDATE, recordInfo);
+      loger.log('Doc update ->' + recordId);
+    } catch (e) {
+      loger.warn('Doc Table Update Decode包异常');
+    }
+  }
+
+
+}
+
+export default DocApe;
+
diff --git a/src/apes/VideoApe.js b/src/apes/VideoApe.js
new file mode 100644
index 0000000..d917016
--- /dev/null
+++ b/src/apes/VideoApe.js
@@ -0,0 +1,113 @@
+// //////////////////////////////////////////////////////////////////////////////
+//
+//  Copyright (C) 2016-present  All Rights Reserved.
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Github Home: https://github.com/AlexWang1987
+//  Author: AlexWang
+//  Date: 2016-08-23 18:07:28
+//  QQ Email: 1669499355@qq.com
+//  Last Modified time: 2016-09-06 11:13:59
+//  Description: LiveClass-VideoApe
+//
+// //////////////////////////////////////////////////////////////////////////////
+
+import Ape from './Ape';
+import ApeConsts from './ApeConsts';
+import pdu from 'pdu';
+import Loger from 'Loger';
+import MessageTypes from 'MessageTypes';
+
+let loger = Loger.getLoger('VideoChat');
+
+class VideoChat extends Ape {
+  constructor() {
+    super(
+      ApeConsts.VIDEO_SESSION_ID,
+      ApeConsts.VIDEO_SESSION_NAME,
+      ApeConsts.VIDEO_SESSION_TAG
+
+
+    );
+
+    //Attributes
+    this.videoChannels = {};
+    this.activeChannelId = 0;
+    this.activeURL = '';
+
+
+    // Ape Models
+    this.registerKey(this._session_id, this._session_name, this._session_tag, new ArrayBuffer);
+    this.registerObj(pdu.RCPDU_REG_REGISTER_TABLE, ApeConsts.VIDEO_OBJ_TABLE_ID, ApeConsts.VIDEO_OBJ_TABLE_NAME, ApeConsts.VIDEO_OBJ_TABLE_TAG, 0, new ArrayBuffer);
+
+
+    // ape listeners
+    this.on(pdu.RCPDU_VIDEO_SEND_DATA_REQUEST, this.videoIncomingHandler.bind(this));
+  }
+
+  // 视频消息处理
+  videoIncomingHandler(videoBuffer) {
+    let videoReceivePdu = pdu['VideoSendDataRequestPdu'].decode(videoBuffer);
+
+    let video_data = { };
+    video_data._initiator = videoReceivePdu.initiator;
+    video_data._is_key_frame = videoReceivePdu.keyFrame;
+    video_data._sequence_id = videoReceivePdu.sequenceId;
+    video_data._slice_id = videoReceivePdu.sliceId;
+    video_data._data = videoReceivePdu.userData;
+
+
+    // this._notify(RCApeEvent.E_VIDEO_DATA, videoReceivePdu.sessionId, videoReceivePdu.channelId, video_data);
+    loger.log('接受视频消息.', video_data);
+    this.emit(MessageTypes.VIDEO_SHOW, video_data);
+
+  }
+
+  tableUpdateHandler(ownerId, channelId, channelInfo) {
+   // debugger;
+    let videoChannelInfo = pdu['RCVideoChannelInfoRecordPdu'].decode(channelInfo);
+    videoChannelInfo.owner = ownerId;
+    videoChannelInfo.channelId = channelId;
+    videoChannelInfo.status = ownerId === 0 ? ApeConsts.CGS_RELEASED : videoChannelInfo.status;
+
+    this.videoChannels[channelId] = videoChannelInfo;
+    switch (videoChannelInfo.status) {
+    case ApeConsts.CGS_RELEASED:
+    case ApeConsts.CGS_PENDING:
+    case ApeConsts.CGS_GRABBING:
+      // 只能关闭自己的流
+      if (this.activeChannelId === videoChannelInfo.channelId) {
+        this.activeChannelId = 0;
+        this.activeURL = '';
+        this.emitVideoChange();
+      }
+      break;
+    case ApeConsts.CGS_OPENNED:
+      this.activeChannelId = videoChannelInfo.channelId;
+      // AMS
+      if (this._confInfo.msType === '1') {
+
+            this.activeURL = `http://dazhi.3mang.com/live/${this._confInfo.confId}/${this._confInfo.confId}_${videoChannelInfo.channelId}_flash_cam_mic_aac/playlist.m3u8`;
+
+
+      }else {
+           this.activeURL = `http://hls.3mang.com/live/${this._confInfo.confId}_${videoChannelInfo.channelId}_flash_cam_mic_aac/playlist.m3u8`;
+      }
+      // 任何人都可以打开流
+      this.emitVideoChange();
+      break;
+    }
+  }
+
+  emitVideoChange() {
+    this.emit(MessageTypes.VIDEO_SHOW, {
+      activeChannelId: this.activeChannelId,
+      HLSURL: this.activeURL,
+    });
+  };
+
+}
+
+export default VideoChat;
+
diff --git a/src/apes/WhiteBoardApe.js b/src/apes/WhiteBoardApe.js
new file mode 100644
index 0000000..0a93798
--- /dev/null
+++ b/src/apes/WhiteBoardApe.js
@@ -0,0 +1,74 @@
+// //////////////////////////////////////////////////////////////////////////////
+//
+//  Copyright (C) 2016-present  All Rights Reserved.
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Github Home: https://github.com/AlexWang1987
+//  Author: AlexWang
+//  Date: 2016-08-26 17:36:20
+//  QQ Email: 1669499355@qq.com
+//  Last Modified time: 2016-09-21 14:06:12
+//  Description: LiveClass-WhiteBoardApe
+//
+// //////////////////////////////////////////////////////////////////////////////
+
+import Ape from './Ape';
+import ApeConsts from './ApeConsts';
+import pdu from 'pdu';
+import Loger from 'Loger';
+import MessageTypes from 'MessageTypes';
+import { Zlib } from 'zlibjs/bin/zlib.min';
+import UTF8 from 'utf-8';
+
+let loger = Loger.getLoger('WhiteBoardApe');
+
+class WhiteBoardApe extends Ape {
+  constructor() {
+    super(
+      ApeConsts.WHITEBOARD_SESSION_ID,
+      ApeConsts.WHITEBOARD_SESSION_NAME,
+      ApeConsts.WHITEBOARD_SESSION_TAG
+    );
+    // properties
+    this.annoInfos = {};
+
+    //Ape Models
+    this.registerKey(this._session_id, this._session_name, this._session_tag, new ArrayBuffer);
+    this.registerObj(pdu.RCPDU_REG_REGISTER_TABLE, ApeConsts.WHITEBOARD_OBJ_TABLE_ID,
+      ApeConsts.WHITEBOARD_OBJ_TABLE_NAME, ApeConsts.WHITEBOARD_OBJ_TABLE_TAG, 0, new ArrayBuffer);
+
+    // ape listeners
+    this.on(pdu.RCPDU_CONFERENCE_SEND_DATA_REQUEST, this.whiteboardMsgComingHandler.bind(this));
+
+    // 白板延迟
+     this._apeDelayed = true;
+  }
+
+  whiteboardMsgComingHandler(pdu) {
+      loger.warn('whiteboardMsgComingHandler needs to be handled.');
+  }
+
+  tableInsertHandler(tableId, record) {
+      this.emitDocChange(ApeConsts.DOCUMENT_DEL);
+  }
+
+  tableUpdateHandler(owner, recordId, recordData) {
+    const recordInfo = pdu['RCWhiteboardDataRequestPdu'].decode(recordData);
+    // 目前只处理 文档标注
+    if (recordInfo.type == ApeConsts.WBA_DOC_ANNOTATION) {
+      const uncompressedBytes = new Zlib.Inflate(recordInfo.action.compact().view).decompress();
+      const annoInfo = {
+        id: recordId,
+        svg: UTF8.getStringFromBytes(uncompressedBytes)
+      };
+      this.annoInfos[recordId] = annoInfo;
+      this.emit(MessageTypes.ANNO_UPDATE, annoInfo);
+    } else {
+      loger.log('白板动作忽略,类型:', ApeConsts(recordInfo.type));
+    }
+  }
+}
+
+export default WhiteBoardApe;
+
diff --git a/src/assets/css/README.md b/src/assets/css/README.md
new file mode 100644
index 0000000..03d20b5
--- /dev/null
+++ b/src/assets/css/README.md
@@ -0,0 +1 @@
+# CSS FILE FOLDER
diff --git a/src/assets/font/README.md b/src/assets/font/README.md
new file mode 100644
index 0000000..a4f12db
--- /dev/null
+++ b/src/assets/font/README.md
@@ -0,0 +1 @@
+# FONT FILE FOLDER
diff --git a/src/assets/img/README.md b/src/assets/img/README.md
new file mode 100644
index 0000000..48f9022
--- /dev/null
+++ b/src/assets/img/README.md
@@ -0,0 +1 @@
+# IMAGE FILE FOLDER
diff --git a/src/events/RCMessageCallback.js b/src/events/RCMessageCallback.js
new file mode 100644
index 0000000..fbc199e
--- /dev/null
+++ b/src/events/RCMessageCallback.js
@@ -0,0 +1,19 @@
+export default class RCMessageCallback {
+  constructor() {
+    this.type;
+    this.message;
+  }
+
+  create_notify_message(msgType, msgData) {
+    this.type = msgType;
+    this.message = msgData;
+  }
+}
+
+RCMessageCallback.MESSAGE_VIDEO = "video.message";
+RCMessageCallback.MESSAGE_DOCUMENT = "document.message";
+RCMessageCallback.MESSAGE_CHAT = "chat.message";
+RCMessageCallback.MESSAGE_Notify = "notify.message";
+RCMessageCallback.MESSAGE_ROSTER_NUM = "roster_num.message";
+RCMessageCallback.MESSAGE_CLASS_DETAIL = "class_detail.message";
+
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..9490e9a
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,26 @@
+// //////////////////////////////////////////////////////////////////////////////
+//
+//  Copyright (C) 2016-present  All Rights Reserved.
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Github Home: https://github.com/AlexWang1987
+//  Author: AlexWang
+//  Date: 2016-08-27 23:06:26
+//  QQ Email: 1669499355@qq.com
+//  Last Modified time: 2016-09-04 01:02:29
+//  Description: LiveClass-undefined
+//
+// //////////////////////////////////////////////////////////////////////////////
+import util from 'util';
+import Emiter from 'Emiter';
+import MessageTypes from 'MessageTypes';
+import mcu from 'mcu';
+import mcu from 'mcu.js';
+import muc from 'mcu.js';
+import dfjsd from 'Loger';
+
+import fdsfds from 'mcu';
+
+import alexwang from 'MessageEngine.js';
+
diff --git a/src/libs/ArrayBufferUtil.js b/src/libs/ArrayBufferUtil.js
new file mode 100644
index 0000000..dfff5d1
--- /dev/null
+++ b/src/libs/ArrayBufferUtil.js
@@ -0,0 +1,80 @@
+export default class ArrayBufferUtil {
+  static ab2str(buf) {
+    return String.fromCharCode.apply(null, new Uint16Array(buf));
+  }
+
+  static str2ab(str) {
+    let buf = new ArrayBuffer(str.length * 2); // one char use 2 byte
+    let bufView = new Uint16Array(buf);
+    for (let i = 0, strLen = str.length; i < strLen; i++) {
+      bufView[i] = str.charCodeAt(i);
+    }
+    return buf;
+  }
+
+  static strToUint8Array(str) {
+    let out, i, len, c;
+    out = "";
+    len = str.length;
+    let uintArray = [];
+    for (i = 0; i < len; i++) {
+      c = str.charCodeAt(i);
+      if ((c >= 0x0001) && (c <= 0x007F)) {
+        uintArray.push(c);
+      } else if (c > 0x07FF) {
+        uintArray.push(0xE0 | ((c >> 12) & 0x0F));
+        uintArray.push(0x80 | ((c >> 6) & 0x3F));
+        uintArray.push(0x80 | ((c >> 0) & 0x3F));
+        //out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F));
+        //out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F));
+        //out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
+      } else {
+        uintArray.push(0xC0 | ((c >> 6) & 0x1F));
+        uintArray.push(0x80 | ((c >> 0) & 0x3F));
+        //out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F));
+        //out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
+      }
+    }
+    return new Uint8Array(uintArray);
+  }
+
+  static uint8ArrayToStr(uint8, index) {
+    let out, i, len, c;
+    let char2, char3;
+    out = "";
+    len = uint8.buffer.byteLength;
+    i = uint8.offset + index; //pzm update 前两个字符乱码
+    while (i < uint8.limit) {
+      c = uint8.view[i++];
+      switch (c >> 4) {
+      case 0:
+      case 1:
+      case 2:
+      case 3:
+      case 4:
+      case 5:
+      case 6:
+      case 7:
+        // 0xxxxxxx
+        out += String.fromCharCode(uint8.view[i - 1]);
+        break;
+      case 12:
+      case 13:
+        // 110x xxxx 10xx xxxx
+        char2 = uint8.view[i++];
+        out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
+        break;
+      case 14:
+        // 1110 xxxx 10xx xxxx 10xx xxxx
+        char2 = uint8.view[i++];
+        char3 = uint8.view[i++];
+        out += String.fromCharCode(((c & 0x0F) << 12) |
+          ((char2 & 0x3F) << 6) |
+          ((char3 & 0x3F) << 0));
+        break;
+      }
+    }
+    return out;
+  }
+}
+
diff --git a/src/mcu.js b/src/mcu.js
new file mode 100644
index 0000000..0591d58
--- /dev/null
+++ b/src/mcu.js
@@ -0,0 +1,173 @@
+/*eslint-disable*/
+
+import Emiter from 'Emiter';
+import MessageTypes from 'MessageTypes';
+import Loger from 'Loger';
+import everSocket from 'everSocket';
+import pdu from 'pdu/index';
+import PduType from 'pdu/PduType';
+import PduConsts from 'pdu/PduConsts';
+import ApeConsts from 'apes/ApeConsts';
+import ConferApe from 'apes/ConferApe';
+import ArrayBufferUtil from 'libs/ArrayBufferUtil';
+import Base64 from 'base64-js';
+
+let loger = Loger.getLoger('MCU');
+
+class MCU extends Emiter {
+  constructor() {
+    super();
+    this._apes = {};
+    this._everSocket = everSocket;
+    this._everSocket.on(everSocket.OPEN, this._everSocketOpenHandler.bind(this));
+    this._everSocket.on(everSocket.MESSAGE, this._everSocketMsgReceivedHandler.bind(this));
+  }
+
+  // 注册Ape
+  registerApe(ape) {
+    this._apes[ape._session_id] = ape;
+  }
+
+  // EverSocket建立通道完毕
+  _everSocketOpenHandler() {
+    loger.log('MCU-发送加入会议请求.');
+    const confInfo = this.confInfo;
+
+    // 刷新nodeId
+    confInfo.nodeId = parseInt(Date.now() / 1000);
+
+    var joinRequestPdu = new pdu['RCConferenceJoinRequestPdu'];
+    joinRequestPdu.type = 2;
+    joinRequestPdu.initiator = confInfo.nodeId;
+    joinRequestPdu.nodeType = PduConsts.NT_TERMINAL; //normal
+
+    var descriptorPdu = new pdu['RCConferenceDescriptorPdu'];
+    descriptorPdu.id = confInfo.confId;
+    descriptorPdu.name = new ArrayBuffer;
+    descriptorPdu.mode = 0;
+    descriptorPdu.capacity = 1;
+    joinRequestPdu.confDesc = descriptorPdu;
+
+    let pduMsg = pdu.create_connect_provider_request_pdu(
+      joinRequestPdu.type,
+      confInfo.nodeId,
+      confInfo.confId,
+      0,
+      ApeConsts.BROADCAST_CHANNEL_ID,
+      true,
+      PduConsts.DP_TOP,
+      confInfo.topNodeID,
+      PduConsts.SEG_ONCE
+    );
+
+    pduMsg.set("site", confInfo.siteId);
+    pduMsg.set("userId", confInfo.userId);
+    pduMsg.set("userName", Base64.fromByteArray(ArrayBufferUtil.strToUint8Array(confInfo.userName)));
+    pduMsg.set("userRole", confInfo.userRole);
+    pduMsg.set("deviceType", "");
+    pduMsg.set("data", joinRequestPdu.toArrayBuffer());
+
+    this._everSocket.send(pduMsg.toArrayBuffer());
+  }
+
+  // EverSocket底层消息处理
+  _everSocketMsgReceivedHandler(data) {
+    let pduMsg = pdu.decode_pdu(data);
+    let pduType = pduMsg.get("type");
+    let pduData = pduMsg.get("data");
+    loger.log('MCU-FirstLayer封装消息', 'type', pdu.id2type(pduMsg.type), pduMsg.type, 'sessionId', ApeConsts(pduMsg.sessionId), pduMsg.sessionId);
+    switch (pduType) {
+    case PduType.RCPDU_CONNECT_PROVIDER_RESPONSE:
+      let joinConfPdu = pdu['RCConferenceJoinResponsePdu'].decode(pduData);
+      let pduResultCode = joinConfPdu.get("result");
+      switch (pduResultCode) {
+      case PduConsts.RET_SUCCESS:
+        this._updateMCUConfInfoDesc(joinConfPdu.get("confDesc"));
+        this.emit(MessageTypes.CONFERENCE_JOIN_SUCCESS, this.confInfo);
+        break;
+      case PduConsts.RET_FULL_CAPACITY:
+        this.emit(MessageTypes.CONFERENCE_JOIN_FULL);
+        break;
+      default:
+        loger.warn('JoinConfPdu-未知类型-等待处理.', pduResultCode);
+      }
+      break;
+    case PduType.RCPDU_SEND_DATA_REQUEST:
+      let ape = this._apes[pduMsg.sessionId];
+      let sessionLabel = ApeConsts(pduMsg.sessionId);
+      if (ape) {
+        let subTypeLabel = pdu.id2type(pduMsg.subType);
+        loger.log('MCU-SecondLayer封装消息', 'sessionId', sessionLabel, pduMsg.sessionId, 'subtype', subTypeLabel, pduMsg.subType);
+        ape.emit(pduMsg.subType, pduMsg.data);
+      } else {
+        loger.warn(sessionLabel + '尚未注册');
+      }
+      break;
+    default:
+      loger.warn('PDU-未知类型-等待处理.', pduType);
+    }
+  }
+
+  _updateMCUConfInfoDesc(mcuConfDesc) {
+    let info = this.mcuConfInfo.info;
+    info._conference_name = ArrayBufferUtil.uint8ArrayToStr(mcuConfDesc.name, 0);
+    info._capacity = mcuConfDesc.capacity;
+    info._mode = mcuConfDesc.mode;
+  }
+
+  // MU服务是否连接
+  get connected() {
+    if (this._everSocket && this._everSocket.connected)
+      return true;
+    return false;
+  }
+
+  // 会议发送消息 -- 消息同意序列号
+  send(msg) {
+    if (this.connected) {
+      loger.log('MCU-发送会议数据....', msg);
+      this._everSocket.send(msg.toArrayBuffer());
+    } else {
+      loger.log('MCU-发送会议数据失败,MCU底层通道不可用');
+    }
+  }
+
+  // 主动断开MCU连接
+  leaveMCU() {
+    // for (let ape in this._apes) {
+    //   this._apes[ape].stop();
+    // }
+    this._everSocket.end();
+  }
+
+  // 主动建立MCU连接
+  joinMCU(_confInfo) {
+    loger.log('开始建立EverSocket通道.', _confInfo);
+    _confInfo.confId = parseInt(_confInfo.confId); // confId 必须整形
+    this.confInfo = _confInfo;
+
+    let nodeInfoRecordPdu = new pdu['RCNodeInfoRecordPdu'];
+    nodeInfoRecordPdu.name = this.confInfo.userName;
+    nodeInfoRecordPdu.nodeId = this.confInfo.nodeId;
+    nodeInfoRecordPdu.userId = this.confInfo.userId;
+    nodeInfoRecordPdu.role = 1; //NR_NORMAL
+    nodeInfoRecordPdu.level = 0;
+
+    let conferenceRecord = {}; //RCConferenceRecord_T
+    conferenceRecord._conference_id = this.confInfo.confId;
+    conferenceRecord._top_node_id = this.confInfo.topNodeID;
+
+    this.mcuConfInfo = {}; //RCMeetingInfo_T
+    this.mcuConfInfo.self = nodeInfoRecordPdu;
+    this.mcuConfInfo.info = conferenceRecord;
+
+    // 内部mcuConfInfo
+    this.confInfo.mcuConfInfo = this.mcuConfInfo;
+
+    //开启EverSocket
+    this._everSocket.begin(this.confInfo.MCUServerIP, this.confInfo.MCUServerPort);
+  }
+}
+
+export default new MCU;
+
diff --git a/src/pdu/PduConsts.js b/src/pdu/PduConsts.js
new file mode 100644
index 0000000..7fd1f8d
--- /dev/null
+++ b/src/pdu/PduConsts.js
@@ -0,0 +1,29 @@
+export default function PduConsts() {}
+
+PduConsts.NT_TERMINAL = 0;
+PduConsts.NT_MULTIPORT_TERMINAL = 1;
+PduConsts.NT_MCU = 2;
+
+// PduPriority
+
+PduConsts.DP_TOP = 0;
+PduConsts.DP_HIGH = 1;
+PduConsts.DP_MEDIUM = 2;
+PduConsts.DP_LOW = 3;
+
+// PduSegment
+PduConsts.SEG_BEGIN = 0;
+PduConsts.SEG_END = 1;
+PduConsts.SEG_ONCE = 2;
+
+// PduReturnType
+PduConsts.RET_SUCCESS = 0;
+PduConsts.RET_USER_REJECTED = 1;
+PduConsts.RET_INVALID_CONFERENCE = 2;
+PduConsts.RET_INVALID_PASSWORD = 3;
+PduConsts.RET_INVALID_CONVENER_PASSWORD = 4;
+PduConsts.RET_CHALLENGE_RESPONSE_REQUIRED = 5;
+PduConsts.RET_INVALID_CHALLENGE_RESPONSE = 6;
+PduConsts.RET_NO_CONNECTION = 7;
+PduConsts.RET_FULL_CAPACITY = 8;
+
diff --git a/src/pdu/PduType.js b/src/pdu/PduType.js
new file mode 100644
index 0000000..d367315
--- /dev/null
+++ b/src/pdu/PduType.js
@@ -0,0 +1,94 @@
+export default function RCPduPackage(targe_type_id) {};
+RCPduPackage.RCPDU_CONNECT_PROVIDER_REQUEST = 0;
+RCPduPackage.RCPDU_CONNECT_PROVIDER_RESPONSE = 1;
+RCPduPackage.RCPDU_CONFERENCE_JOIN_REQUEST = 2;
+RCPduPackage.RCPDU_CONFERENCE_JOIN_RESPONSE = 3;
+RCPduPackage.RCPDU_CONFERENCE_INVITE_REQUEST = 10;
+RCPduPackage.RCPDU_CONFERENCE_INVITE_RESPONSE = 11;
+RCPduPackage.RCPDU_CONFERENCE_LOCK_REQUEST = 20;
+RCPduPackage.RCPDU_CONFERENCE_LOCK_RESPONSE = 21;
+RCPduPackage.RCPDU_CONFERENCE_LOCK_INDICATION = 22;
+RCPduPackage.RCPDU_CONFERENCE_UNLOCK_REQUEST = 30;
+RCPduPackage.RCPDU_CONFERENCE_UNLOCK_RESPONSE = 31;
+RCPduPackage.RCPDU_CONFERENCE_UNLOCK_INDICATION = 32;
+RCPduPackage.RCPDU_CONFERENCE_LEAVE_REQUEST = 39;
+RCPduPackage.RCPDU_CONFERENCE_TERMINATE_REQUEST = 40;
+RCPduPackage.RCPDU_CONFERENCE_TERMINATE_RESPONSE = 41;
+RCPduPackage.RCPDU_CONFERENCE_TERMINATE_INDICATION = 42;
+RCPduPackage.RCPDU_CONFERENCE_EJECT_USER_REQUEST = 50;
+RCPduPackage.RCPDU_CONFERENCE_EJECT_USER_RESPONSE = 51;
+RCPduPackage.RCPDU_CONFERENCE_EJECT_USER_INDICATION = 52;
+RCPduPackage.RCPDU_ROSTER_UPDATE_INDICATION = 60;
+RCPduPackage.RCPDU_REGISTRY_UPDATE_REQUEST = 70;
+RCPduPackage.RCPDU_REGISTRY_UPDATE_RESPONSE = 71;
+RCPduPackage.RCPDU_REGISTRY_UPDATE_INDICATION = 72;
+RCPduPackage.RCPDU_FUNCTION_NOT_SUPPORTED_RESPONSE = 80;
+RCPduPackage.RCPDU_SESSION_JOIN_REQUEST = 90;
+RCPduPackage.RCPDU_SESSION_JOIN_RESPONSE = 91;
+RCPduPackage.RCPDU_CHANNEL_GRAB_REQUEST = 100;
+RCPduPackage.RCPDU_CHANNEL_GRAB_RESPONSE = 101;
+RCPduPackage.RCPDU_CHANNEL_GRAB_INDICATION = 102;
+RCPduPackage.RCPDU_CHANNEL_JOIN_REQUEST = 103;
+RCPduPackage.RCPDU_CHANNEL_JOIN_RESPONSE = 104;
+RCPduPackage.RCPDU_CHANNEL_LEAVE_REQUEST = 105;
+RCPduPackage.RCPDU_CHANNEL_RELEASE_REQUEST = 106;
+RCPduPackage.RCPDU_CHANNEL_RELEASE_INDICATION = 107;
+RCPduPackage.RCPDU_SEND_DATA_REQUEST = 120;
+RCPduPackage.RCPDU_SEND_DATA_REQUEST = 120;
+RCPduPackage.RCPDU_SEND_DATA_INDICATION = 121;
+RCPduPackage.RCPDU_UNIFORM_SEND_DATA_REQUEST = 125;
+RCPduPackage.RCPDU_UNIFORM_SEND_DATA_INDICATION = 126;
+RCPduPackage.RCPDU_TOKEN_GRAB_REQUEST = 130;
+RCPduPackage.RCPDU_TOKEN_GRAB_CONFIRM = 131;
+RCPduPackage.RCPDU_TOKEN_INHIBIT_REQUEST = 132;
+RCPduPackage.RCPDU_TOKEN_INHIBIT_CONFIRM = 133;
+RCPduPackage.RCPDU_TOKEN_GIVE_REQUEST = 134;
+RCPduPackage.RCPDU_TOKEN_GIVE_INDICATION = 135;
+RCPduPackage.RCPDU_TOKEN_GIVE_RESPONSE = 136;
+RCPduPackage.RCPDU_TOKEN_GIVE_CONFIRM = 137;
+RCPduPackage.RCPDU_TOKEN_PLEASE_REQUEST = 138;
+RCPduPackage.RCPDU_TOKEN_PLEASE_INDICATION = 139;
+RCPduPackage.RCPDU_TOKEN_RELEASE_REQUEST = 140;
+RCPduPackage.RCPDU_TOKEN_RELEASE_CONFIRM = 141;
+RCPduPackage.RCPDU_TOKEN_TEST_REQUEST = 142;
+RCPduPackage.RCPDU_TOKEN_TEST_CONFIRM = 143;
+RCPduPackage.RCPDU_REG_REGISTER_KEY = 200;
+RCPduPackage.RCPDU_REG_UNREGISTER_KEY = 201;
+RCPduPackage.RCPDU_REG_REGISTER_ROSTER = 202;
+RCPduPackage.RCPDU_REG_REGISTER_TOKEN = 203;
+RCPduPackage.RCPDU_REG_REGISTER_PARAMETER = 204;
+RCPduPackage.RCPDU_REG_REGISTER_COUNTER = 205;
+RCPduPackage.RCPDU_REG_REGISTER_TABLE = 206;
+RCPduPackage.RCPDU_REG_REGISTER_CACHE = 207;
+RCPduPackage.RCPDU_REG_REGISTER_OBJ = 208;
+RCPduPackage.RCPDU_REG_UNREGISTER_OBJ = 209;
+RCPduPackage.RCPDU_REG_UPDATE_OBJ = 210;
+RCPduPackage.RCPDU_REG_ADAPTER = 211;
+RCPduPackage.RCPDU_REG_CLEANUP_NODE = 212;
+RCPduPackage.RCPDU_REG_REGISTER_QUEUE = 213;
+RCPduPackage.RCPDU_REG_TABLE_INSERT_PDU = 230;
+RCPduPackage.RCPDU_REG_TABLE_DELETE_PDU = 231;
+RCPduPackage.RCPDU_REG_TABLE_UPDATE_PDU = 232;
+RCPduPackage.RCPDU_REG_ROSTER_INSERT_PDU = 240;
+RCPduPackage.RCPDU_REG_ROSTER_DELETE_PDU = 241;
+RCPduPackage.RCPDU_REG_ROSTER_UPDATE_PDU = 242;
+RCPduPackage.RCPDU_REG_PARAMETER_UPDATE_PDU = 250;
+RCPduPackage.RCPDU_REG_QUEUE_INSERT_PDU = 255;
+RCPduPackage.RCPDU_REG_QUEUE_DELETE_PDU = 256;
+RCPduPackage.RCPDU_REG_QUEUE_UPDATE_PDU = 257;
+RCPduPackage.RCPDU_CONFERENCE_SEND_DATA_REQUEST = 259;
+RCPduPackage.RCPDU_VIDEO_SEND_DATA_REQUEST = 260;
+RCPduPackage.RCPDU_AUDIO_SEND_DATA_REQUEST = 261;
+RCPduPackage.RCPDU_GIFT_SEND_DATA_REQUEST = 262;
+RCPduPackage.RCPDU_CHAT_SEND_DATA_REQUEST = 263;
+RCPduPackage.RCPDU_VOTING_POLL_RECORD = 265;
+RCPduPackage.RCPDU_REG_REQUEST_OBJ = 290;
+RCPduPackage.RCPDU_REG_RESPONSE_OBJ = 291;
+RCPduPackage.RCPDU_REG_COUNTER_REQUEST_PDU = 292;
+RCPduPackage.RCPDU_REG_COUNTER_RESPONSE_PDU = 293;
+RCPduPackage.RCPDU_INDEX_ADAPTER = 300;
+RCPduPackage.RCPDU_INDEX_SERVER_USERS = 301;
+RCPduPackage.RCPDU_INDEX_CONFERENCE_USER_JOINED = 302;
+RCPduPackage.RCPDU_INDEX_CONFERENCE_USER_EXITED = 303;
+RCPduPackage.RCPDU_INDEX_CONFERENCE_USERS = 304;
+
diff --git a/src/pdu/ProBuf.js b/src/pdu/ProBuf.js
new file mode 100644
index 0000000..ff2a388
--- /dev/null
+++ b/src/pdu/ProBuf.js
@@ -0,0 +1,9914 @@
+/*
+ Copyright 2013 Daniel Wirtz <dcode@dcode.io>
+ Copyright 2009 The Closure Library Authors. All Rights Reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS-IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+/**
+ * @license Long.js (c) 2013 Daniel Wirtz <dcode@dcode.io>
+ * Released under the Apache License, Version 2.0
+ * see: https://github.com/dcodeIO/Long.js for details
+ */
+(function (global, factory) {
+
+  /* AMD */
+  if (typeof define === 'function' && define["amd"])
+    define([], factory);
+  /* CommonJS */
+  else if (typeof require === 'function' && typeof module === "object" && module && module["exports"])
+    module["exports"] = factory();
+  /* Global */
+  else
+    (global["dcodeIO"] = global["dcodeIO"] || {})["Long"] = factory();
+
+})(this, function () {
+  "use strict";
+
+  /**
+   * Constructs a 64 bit two's-complement integer, given its low and high 32 bit values as *signed* integers.
+   *  See the from* functions below for more convenient ways of constructing Longs.
+   * @exports Long
+   * @class A Long class for representing a 64 bit two's-complement integer value.
+   * @param {number} low The low (signed) 32 bits of the long
+   * @param {number} high The high (signed) 32 bits of the long
+   * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed
+   * @constructor
+   */
+  var Long = function (low, high, unsigned) {
+
+    /**
+     * The low 32 bits as a signed value.
+     * @type {number}
+     * @expose
+     */
+    this.low = low | 0;
+
+    /**
+     * The high 32 bits as a signed value.
+     * @type {number}
+     * @expose
+     */
+    this.high = high | 0;
+
+    /**
+     * Whether unsigned or not.
+     * @type {boolean}
+     * @expose
+     */
+    this.unsigned = !!unsigned;
+  };
+
+  // The internal representation of a long is the two given signed, 32-bit values.
+  // We use 32-bit pieces because these are the size of integers on which
+  // Javascript performs bit-operations.  For operations like addition and
+  // multiplication, we split each number into 16 bit pieces, which can easily be
+  // multiplied within Javascript's floating-point representation without overflow
+  // or change in sign.
+  //
+  // In the algorithms below, we frequently reduce the negative case to the
+  // positive case by negating the input(s) and then post-processing the result.
+  // Note that we must ALWAYS check specially whether those values are MIN_VALUE
+  // (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as
+  // a positive number, it overflows back into a negative).  Not handling this
+  // case would often result in infinite recursion.
+  //
+  // Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the from*
+  // methods on which they depend.
+
+  /**
+   * An indicator used to reliably determine if an object is a Long or not.
+   * @type {boolean}
+   * @const
+   * @expose
+   * @private
+   */
+  Long.__isLong__;
+
+  Object.defineProperty(Long.prototype, "__isLong__", {
+    value: true,
+    enumerable: false,
+    configurable: false
+  });
+
+  /**
+   * Tests if the specified object is a Long.
+   * @param {*} obj Object
+   * @returns {boolean}
+   * @expose
+   */
+  Long.isLong = function (obj) {
+    return (obj && obj["__isLong__"]) === true;
+  };
+
+  /**
+   * A cache of the Long representations of small integer values.
+   * @type {!Object}
+   * @inner
+   */
+  var INT_CACHE = {};
+
+  /**
+   * A cache of the Long representations of small unsigned integer values.
+   * @type {!Object}
+   * @inner
+   */
+  var UINT_CACHE = {};
+
+  /**
+   * Returns a Long representing the given 32 bit integer value.
+   * @param {number} value The 32 bit integer in question
+   * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed
+   * @returns {!Long} The corresponding Long value
+   * @expose
+   */
+  Long.fromInt = function (value, unsigned) {
+    var obj, cachedObj;
+    if (!unsigned) {
+      value = value | 0;
+      if (-128 <= value && value < 128) {
+        cachedObj = INT_CACHE[value];
+        if (cachedObj)
+          return cachedObj;
+      }
+      obj = new Long(value, value < 0 ? -1 : 0, false);
+      if (-128 <= value && value < 128)
+        INT_CACHE[value] = obj;
+      return obj;
+    } else {
+      value = value >>> 0;
+      if (0 <= value && value < 256) {
+        cachedObj = UINT_CACHE[value];
+        if (cachedObj)
+          return cachedObj;
+      }
+      obj = new Long(value, (value | 0) < 0 ? -1 : 0, true);
+      if (0 <= value && value < 256)
+        UINT_CACHE[value] = obj;
+      return obj;
+    }
+  };
+
+  /**
+   * Returns a Long representing the given value, provided that it is a finite number. Otherwise, zero is returned.
+   * @param {number} value The number in question
+   * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed
+   * @returns {!Long} The corresponding Long value
+   * @expose
+   */
+  Long.fromNumber = function (value, unsigned) {
+    unsigned = !!unsigned;
+    if (isNaN(value) || !isFinite(value))
+      return Long.ZERO;
+    if (!unsigned && value <= -TWO_PWR_63_DBL)
+      return Long.MIN_VALUE;
+    if (!unsigned && value + 1 >= TWO_PWR_63_DBL)
+      return Long.MAX_VALUE;
+    if (unsigned && value >= TWO_PWR_64_DBL)
+      return Long.MAX_UNSIGNED_VALUE;
+    if (value < 0)
+      return Long.fromNumber(-value, unsigned).negate();
+    return new Long((value % TWO_PWR_32_DBL) | 0, (value / TWO_PWR_32_DBL) | 0, unsigned);
+  };
+
+  /**
+   * Returns a Long representing the 64 bit integer that comes by concatenating the given low and high bits. Each is
+   *  assumed to use 32 bits.
+   * @param {number} lowBits The low 32 bits
+   * @param {number} highBits The high 32 bits
+   * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed
+   * @returns {!Long} The corresponding Long value
+   * @expose
+   */
+  Long.fromBits = function (lowBits, highBits, unsigned) {
+    return new Long(lowBits, highBits, unsigned);
+  };
+
+  /**
+   * Returns a Long representation of the given string, written using the specified radix.
+   * @param {string} str The textual representation of the Long
+   * @param {(boolean|number)=} unsigned Whether unsigned or not, defaults to `false` for signed
+   * @param {number=} radix The radix in which the text is written (2-36), defaults to 10
+   * @returns {!Long} The corresponding Long value
+   * @expose
+   */
+  Long.fromString = function (str, unsigned, radix) {
+    if (str.length === 0)
+      throw Error('number format error: empty string');
+    if (str === "NaN" || str === "Infinity" || str === "+Infinity" || str === "-Infinity")
+      return Long.ZERO;
+    if (typeof unsigned === 'number') // For goog.math.long compatibility
+      radix = unsigned,
+      unsigned = false;
+    radix = radix || 10;
+    if (radix < 2 || 36 < radix)
+      throw Error('radix out of range: ' + radix);
+
+    var p;
+    if ((p = str.indexOf('-')) > 0)
+      throw Error('number format error: interior "-" character: ' + str);
+    else if (p === 0)
+      return Long.fromString(str.substring(1), unsigned, radix).negate();
+
+    // Do several (8) digits each time through the loop, so as to
+    // minimize the calls to the very expensive emulated div.
+    var radixToPower = Long.fromNumber(Math.pow(radix, 8));
+
+    var result = Long.ZERO;
+    for (var i = 0; i < str.length; i += 8) {
+      var size = Math.min(8, str.length - i);
+      var value = parseInt(str.substring(i, i + size), radix);
+      if (size < 8) {
+        var power = Long.fromNumber(Math.pow(radix, size));
+        result = result.multiply(power).add(Long.fromNumber(value));
+      } else {
+        result = result.multiply(radixToPower);
+        result = result.add(Long.fromNumber(value));
+      }
+    }
+    result.unsigned = unsigned;
+    return result;
+  };
+
+  /**
+   * Converts the specified value to a Long.
+   * @param {!Long|number|string|!{low: number, high: number, unsigned: boolean}} val Value
+   * @returns {!Long}
+   * @expose
+   */
+  Long.fromValue = function (val) {
+    if (val /* is compatible */ instanceof Long)
+      return val;
+    if (typeof val === 'number')
+      return Long.fromNumber(val);
+    if (typeof val === 'string')
+      return Long.fromString(val);
+    // Throws for non-objects, converts non-instanceof Long:
+    return new Long(val.low, val.high, val.unsigned);
+  };
+
+  // NOTE: the compiler should inline these constant values below and then remove these variables, so there should be
+  // no runtime penalty for these.
+
+  /**
+   * @type {number}
+   * @const
+   * @inner
+   */
+  var TWO_PWR_16_DBL = 1 << 16;
+
+  /**
+   * @type {number}
+   * @const
+   * @inner
+   */
+  var TWO_PWR_24_DBL = 1 << 24;
+
+  /**
+   * @type {number}
+   * @const
+   * @inner
+   */
+  var TWO_PWR_32_DBL = TWO_PWR_16_DBL * TWO_PWR_16_DBL;
+
+  /**
+   * @type {number}
+   * @const
+   * @inner
+   */
+  var TWO_PWR_64_DBL = TWO_PWR_32_DBL * TWO_PWR_32_DBL;
+
+  /**
+   * @type {number}
+   * @const
+   * @inner
+   */
+  var TWO_PWR_63_DBL = TWO_PWR_64_DBL / 2;
+
+  /**
+   * @type {!Long}
+   * @const
+   * @inner
+   */
+  var TWO_PWR_24 = Long.fromInt(TWO_PWR_24_DBL);
+
+  /**
+   * Signed zero.
+   * @type {!Long}
+   * @expose
+   */
+  Long.ZERO = Long.fromInt(0);
+
+  /**
+   * Unsigned zero.
+   * @type {!Long}
+   * @expose
+   */
+  Long.UZERO = Long.fromInt(0, true);
+
+  /**
+   * Signed one.
+   * @type {!Long}
+   * @expose
+   */
+  Long.ONE = Long.fromInt(1);
+
+  /**
+   * Unsigned one.
+   * @type {!Long}
+   * @expose
+   */
+  Long.UONE = Long.fromInt(1, true);
+
+  /**
+   * Signed negative one.
+   * @type {!Long}
+   * @expose
+   */
+  Long.NEG_ONE = Long.fromInt(-1);
+
+  /**
+   * Maximum signed value.
+   * @type {!Long}
+   * @expose
+   */
+  Long.MAX_VALUE = Long.fromBits(0xFFFFFFFF | 0, 0x7FFFFFFF | 0, false);
+
+  /**
+   * Maximum unsigned value.
+   * @type {!Long}
+   * @expose
+   */
+  Long.MAX_UNSIGNED_VALUE = Long.fromBits(0xFFFFFFFF | 0, 0xFFFFFFFF | 0, true);
+
+  /**
+   * Minimum signed value.
+   * @type {!Long}
+   * @expose
+   */
+  Long.MIN_VALUE = Long.fromBits(0, 0x80000000 | 0, false);
+
+  /**
+   * Converts the Long to a 32 bit integer, assuming it is a 32 bit integer.
+   * @returns {number}
+   * @expose
+   */
+  Long.prototype.toInt = function () {
+    return this.unsigned ? this.low >>> 0 : this.low;
+  };
+
+  /**
+   * Converts the Long to a the nearest floating-point representation of this value (double, 53 bit mantissa).
+   * @returns {number}
+   * @expose
+   */
+  Long.prototype.toNumber = function () {
+    if (this.unsigned) {
+      return ((this.high >>> 0) * TWO_PWR_32_DBL) + (this.low >>> 0);
+    }
+    return this.high * TWO_PWR_32_DBL + (this.low >>> 0);
+  };
+
+  /**
+   * Converts the Long to a string written in the specified radix.
+   * @param {number=} radix Radix (2-36), defaults to 10
+   * @returns {string}
+   * @override
+   * @throws {RangeError} If `radix` is out of range
+   * @expose
+   */
+  Long.prototype.toString = function (radix) {
+    radix = radix || 10;
+    if (radix < 2 || 36 < radix)
+      throw RangeError('radix out of range: ' + radix);
+    if (this.isZero())
+      return '0';
+    var rem;
+    if (this.isNegative()) { // Unsigned Longs are never negative
+      if (this.equals(Long.MIN_VALUE)) {
+        // We need to change the Long value before it can be negated, so we remove
+        // the bottom-most digit in this base and then recurse to do the rest.
+        var radixLong = Long.fromNumber(radix);
+        var div = this.div(radixLong);
+        rem = div.multiply(radixLong).subtract(this);
+        return div.toString(radix) + rem.toInt().toString(radix);
+      } else
+        return '-' + this.negate().toString(radix);
+    }
+
+    // Do several (6) digits each time through the loop, so as to
+    // minimize the calls to the very expensive emulated div.
+    var radixToPower = Long.fromNumber(Math.pow(radix, 6), this.unsigned);
+    rem = this;
+    var result = '';
+    while (true) {
+      var remDiv = rem.div(radixToPower),
+        intval = rem.subtract(remDiv.multiply(radixToPower)).toInt() >>> 0,
+        digits = intval.toString(radix);
+      rem = remDiv;
+      if (rem.isZero())
+        return digits + result;
+      else {
+        while (digits.length < 6)
+          digits = '0' + digits;
+        result = '' + digits + result;
+      }
+    }
+  };
+
+  /**
+   * Gets the high 32 bits as a signed integer.
+   * @returns {number} Signed high bits
+   * @expose
+   */
+  Long.prototype.getHighBits = function () {
+    return this.high;
+  };
+
+  /**
+   * Gets the high 32 bits as an unsigned integer.
+   * @returns {number} Unsigned high bits
+   * @expose
+   */
+  Long.prototype.getHighBitsUnsigned = function () {
+    return this.high >>> 0;
+  };
+
+  /**
+   * Gets the low 32 bits as a signed integer.
+   * @returns {number} Signed low bits
+   * @expose
+   */
+  Long.prototype.getLowBits = function () {
+    return this.low;
+  };
+
+  /**
+   * Gets the low 32 bits as an unsigned integer.
+   * @returns {number} Unsigned low bits
+   * @expose
+   */
+  Long.prototype.getLowBitsUnsigned = function () {
+    return this.low >>> 0;
+  };
+
+  /**
+   * Gets the number of bits needed to represent the absolute value of this Long.
+   * @returns {number}
+   * @expose
+   */
+  Long.prototype.getNumBitsAbs = function () {
+    if (this.isNegative()) // Unsigned Longs are never negative
+      return this.equals(Long.MIN_VALUE) ? 64 : this.negate().getNumBitsAbs();
+    var val = this.high != 0 ? this.high : this.low;
+    for (var bit = 31; bit > 0; bit--)
+      if ((val & (1 << bit)) != 0)
+        break;
+    return this.high != 0 ? bit + 33 : bit + 1;
+  };
+
+  /**
+   * Tests if this Long's value equals zero.
+   * @returns {boolean}
+   * @expose
+   */
+  Long.prototype.isZero = function () {
+    return this.high === 0 && this.low === 0;
+  };
+
+  /**
+   * Tests if this Long's value is negative.
+   * @returns {boolean}
+   * @expose
+   */
+  Long.prototype.isNegative = function () {
+    return !this.unsigned && this.high < 0;
+  };
+
+  /**
+   * Tests if this Long's value is positive.
+   * @returns {boolean}
+   * @expose
+   */
+  Long.prototype.isPositive = function () {
+    return this.unsigned || this.high >= 0;
+  };
+
+  /**
+   * Tests if this Long's value is odd.
+   * @returns {boolean}
+   * @expose
+   */
+  Long.prototype.isOdd = function () {
+    return (this.low & 1) === 1;
+  };
+
+  /**
+   * Tests if this Long's value is even.
+   * @returns {boolean}
+   * @expose
+   */
+  Long.prototype.isEven = function () {
+    return (this.low & 1) === 0;
+  };
+
+  /**
+   * Tests if this Long's value equals the specified's.
+   * @param {!Long|number|string} other Other value
+   * @returns {boolean}
+   * @expose
+   */
+  Long.prototype.equals = function (other) {
+    if (!Long.isLong(other))
+      other = Long.fromValue(other);
+    if (this.unsigned !== other.unsigned && (this.high >>> 31) === 1 && (other.high >>> 31) === 1)
+      return false;
+    return this.high === other.high && this.low === other.low;
+  };
+
+  /**
+   * Tests if this Long's value differs from the specified's.
+   * @param {!Long|number|string} other Other value
+   * @returns {boolean}
+   * @expose
+   */
+  Long.prototype.notEquals = function (other) {
+    return !this.equals( /* validates */ other);
+  };
+
+  /**
+   * Tests if this Long's value is less than the specified's.
+   * @param {!Long|number|string} other Other value
+   * @returns {boolean}
+   * @expose
+   */
+  Long.prototype.lessThan = function (other) {
+    return this.compare( /* validates */ other) < 0;
+  };
+
+  /**
+   * Tests if this Long's value is less than or equal the specified's.
+   * @param {!Long|number|string} other Other value
+   * @returns {boolean}
+   * @expose
+   */
+  Long.prototype.lessThanOrEqual = function (other) {
+    return this.compare( /* validates */ other) <= 0;
+  };
+
+  /**
+   * Tests if this Long's value is greater than the specified's.
+   * @param {!Long|number|string} other Other value
+   * @returns {boolean}
+   * @expose
+   */
+  Long.prototype.greaterThan = function (other) {
+    return this.compare( /* validates */ other) > 0;
+  };
+
+  /**
+   * Tests if this Long's value is greater than or equal the specified's.
+   * @param {!Long|number|string} other Other value
+   * @returns {boolean}
+   * @expose
+   */
+  Long.prototype.greaterThanOrEqual = function (other) {
+    return this.compare( /* validates */ other) >= 0;
+  };
+
+  /**
+   * Compares this Long's value with the specified's.
+   * @param {!Long|number|string} other Other value
+   * @returns {number} 0 if they are the same, 1 if the this is greater and -1
+   *  if the given one is greater
+   * @expose
+   */
+  Long.prototype.compare = function (other) {
+    if (!Long.isLong(other))
+      other = Long.fromValue(other);
+    if (this.equals(other))
+      return 0;
+    var thisNeg = this.isNegative(),
+      otherNeg = other.isNegative();
+    if (thisNeg && !otherNeg)
+      return -1;
+    if (!thisNeg && otherNeg)
+      return 1;
+    // At this point the sign bits are the same
+    if (!this.unsigned)
+      return this.subtract(other).isNegative() ? -1 : 1;
+    // Both are positive if at least one is unsigned
+    return (other.high >>> 0) > (this.high >>> 0) || (other.high === this.high && (other.low >>> 0) > (this.low >>> 0)) ? -1 : 1;
+  };
+
+  /**
+   * Negates this Long's value.
+   * @returns {!Long} Negated Long
+   * @expose
+   */
+  Long.prototype.negate = function () {
+    if (!this.unsigned && this.equals(Long.MIN_VALUE))
+      return Long.MIN_VALUE;
+    return this.not().add(Long.ONE);
+  };
+
+  /**
+   * Returns the sum of this and the specified Long.
+   * @param {!Long|number|string} addend Addend
+   * @returns {!Long} Sum
+   * @expose
+   */
+  Long.prototype.add = function (addend) {
+    if (!Long.isLong(addend))
+      addend = Long.fromValue(addend);
+
+    // Divide each number into 4 chunks of 16 bits, and then sum the chunks.
+
+    var a48 = this.high >>> 16;
+    var a32 = this.high & 0xFFFF;
+    var a16 = this.low >>> 16;
+    var a00 = this.low & 0xFFFF;
+
+    var b48 = addend.high >>> 16;
+    var b32 = addend.high & 0xFFFF;
+    var b16 = addend.low >>> 16;
+    var b00 = addend.low & 0xFFFF;
+
+    var c48 = 0,
+      c32 = 0,
+      c16 = 0,
+      c00 = 0;
+    c00 += a00 + b00;
+    c16 += c00 >>> 16;
+    c00 &= 0xFFFF;
+    c16 += a16 + b16;
+    c32 += c16 >>> 16;
+    c16 &= 0xFFFF;
+    c32 += a32 + b32;
+    c48 += c32 >>> 16;
+    c32 &= 0xFFFF;
+    c48 += a48 + b48;
+    c48 &= 0xFFFF;
+    return Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned);
+  };
+
+  /**
+   * Returns the difference of this and the specified Long.
+   * @param {!Long|number|string} subtrahend Subtrahend
+   * @returns {!Long} Difference
+   * @expose
+   */
+  Long.prototype.subtract = function (subtrahend) {
+    if (!Long.isLong(subtrahend))
+      subtrahend = Long.fromValue(subtrahend);
+    return this.add(subtrahend.negate());
+  };
+
+  /**
+   * Returns the product of this and the specified Long.
+   * @param {!Long|number|string} multiplier Multiplier
+   * @returns {!Long} Product
+   * @expose
+   */
+  Long.prototype.multiply = function (multiplier) {
+    if (this.isZero())
+      return Long.ZERO;
+    if (!Long.isLong(multiplier))
+      multiplier = Long.fromValue(multiplier);
+    if (multiplier.isZero())
+      return Long.ZERO;
+    if (this.equals(Long.MIN_VALUE))
+      return multiplier.isOdd() ? Long.MIN_VALUE : Long.ZERO;
+    if (multiplier.equals(Long.MIN_VALUE))
+      return this.isOdd() ? Long.MIN_VALUE : Long.ZERO;
+
+    if (this.isNegative()) {
+      if (multiplier.isNegative())
+        return this.negate().multiply(multiplier.negate());
+      else
+        return this.negate().multiply(multiplier).negate();
+    } else if (multiplier.isNegative())
+      return this.multiply(multiplier.negate()).negate();
+
+    // If both longs are small, use float multiplication
+    if (this.lessThan(TWO_PWR_24) && multiplier.lessThan(TWO_PWR_24))
+      return Long.fromNumber(this.toNumber() * multiplier.toNumber(), this.unsigned);
+
+    // Divide each long into 4 chunks of 16 bits, and then add up 4x4 products.
+    // We can skip products that would overflow.
+
+    var a48 = this.high >>> 16;
+    var a32 = this.high & 0xFFFF;
+    var a16 = this.low >>> 16;
+    var a00 = this.low & 0xFFFF;
+
+    var b48 = multiplier.high >>> 16;
+    var b32 = multiplier.high & 0xFFFF;
+    var b16 = multiplier.low >>> 16;
+    var b00 = multiplier.low & 0xFFFF;
+
+    var c48 = 0,
+      c32 = 0,
+      c16 = 0,
+      c00 = 0;
+    c00 += a00 * b00;
+    c16 += c00 >>> 16;
+    c00 &= 0xFFFF;
+    c16 += a16 * b00;
+    c32 += c16 >>> 16;
+    c16 &= 0xFFFF;
+    c16 += a00 * b16;
+    c32 += c16 >>> 16;
+    c16 &= 0xFFFF;
+    c32 += a32 * b00;
+    c48 += c32 >>> 16;
+    c32 &= 0xFFFF;
+    c32 += a16 * b16;
+    c48 += c32 >>> 16;
+    c32 &= 0xFFFF;
+    c32 += a00 * b32;
+    c48 += c32 >>> 16;
+    c32 &= 0xFFFF;
+    c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48;
+    c48 &= 0xFFFF;
+    return Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned);
+  };
+
+  /**
+   * Returns this Long divided by the specified.
+   * @param {!Long|number|string} divisor Divisor
+   * @returns {!Long} Quotient
+   * @expose
+   */
+  Long.prototype.div = function (divisor) {
+    if (!Long.isLong(divisor))
+      divisor = Long.fromValue(divisor);
+    if (divisor.isZero())
+      throw (new Error('division by zero'));
+    if (this.isZero())
+      return this.unsigned ? Long.UZERO : Long.ZERO;
+    var approx, rem, res;
+    if (this.equals(Long.MIN_VALUE)) {
+      if (divisor.equals(Long.ONE) || divisor.equals(Long.NEG_ONE))
+        return Long.MIN_VALUE; // recall that -MIN_VALUE == MIN_VALUE
+      else if (divisor.equals(Long.MIN_VALUE))
+        return Long.ONE;
+      else {
+        // At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|.
+        var halfThis = this.shiftRight(1);
+        approx = halfThis.div(divisor).shiftLeft(1);
+        if (approx.equals(Long.ZERO)) {
+          return divisor.isNegative() ? Long.ONE : Long.NEG_ONE;
+        } else {
+          rem = this.subtract(divisor.multiply(approx));
+          res = approx.add(rem.div(divisor));
+          return res;
+        }
+      }
+    } else if (divisor.equals(Long.MIN_VALUE))
+      return this.unsigned ? Long.UZERO : Long.ZERO;
+    if (this.isNegative()) {
+      if (divisor.isNegative())
+        return this.negate().div(divisor.negate());
+      return this.negate().div(divisor).negate();
+    } else if (divisor.isNegative())
+      return this.div(divisor.negate()).negate();
+
+    // Repeat the following until the remainder is less than other:  find a
+    // floating-point that approximates remainder / other *from below*, add this
+    // into the result, and subtract it from the remainder.  It is critical that
+    // the approximate value is less than or equal to the real value so that the
+    // remainder never becomes negative.
+    res = Long.ZERO;
+    rem = this;
+    while (rem.greaterThanOrEqual(divisor)) {
+      // Approximate the result of division. This may be a little greater or
+      // smaller than the actual value.
+      approx = Math.max(1, Math.floor(rem.toNumber() / divisor.toNumber()));
+
+      // We will tweak the approximate result by changing it in the 48-th digit or
+      // the smallest non-fractional digit, whichever is larger.
+      var log2 = Math.ceil(Math.log(approx) / Math.LN2),
+        delta = (log2 <= 48) ? 1 : Math.pow(2, log2 - 48),
+
+        // Decrease the approximation until it is smaller than the remainder.  Note
+        // that if it is too large, the product overflows and is negative.
+        approxRes = Long.fromNumber(approx),
+        approxRem = approxRes.multiply(divisor);
+      while (approxRem.isNegative() || approxRem.greaterThan(rem)) {
+        approx -= delta;
+        approxRes = Long.fromNumber(approx, this.unsigned);
+        approxRem = approxRes.multiply(divisor);
+      }
+
+      // We know the answer can't be zero... and actually, zero would cause
+      // infinite recursion since we would make no progress.
+      if (approxRes.isZero())
+        approxRes = Long.ONE;
+
+      res = res.add(approxRes);
+      rem = rem.subtract(approxRem);
+    }
+    return res;
+  };
+
+  /**
+   * Returns this Long modulo the specified.
+   * @param {!Long|number|string} divisor Divisor
+   * @returns {!Long} Remainder
+   * @expose
+   */
+  Long.prototype.modulo = function (divisor) {
+    if (!Long.isLong(divisor))
+      divisor = Long.fromValue(divisor);
+    return this.subtract(this.div(divisor).multiply(divisor));
+  };
+
+  /**
+   * Returns the bitwise NOT of this Long.
+   * @returns {!Long}
+   * @expose
+   */
+  Long.prototype.not = function () {
+    return Long.fromBits(~this.low, ~this.high, this.unsigned);
+  };
+
+  /**
+   * Returns the bitwise AND of this Long and the specified.
+   * @param {!Long|number|string} other Other Long
+   * @returns {!Long}
+   * @expose
+   */
+  Long.prototype.and = function (other) {
+    if (!Long.isLong(other))
+      other = Long.fromValue(other);
+    return Long.fromBits(this.low & other.low, this.high & other.high, this.unsigned);
+  };
+
+  /**
+   * Returns the bitwise OR of this Long and the specified.
+   * @param {!Long|number|string} other Other Long
+   * @returns {!Long}
+   * @expose
+   */
+  Long.prototype.or = function (other) {
+    if (!Long.isLong(other))
+      other = Long.fromValue(other);
+    return Long.fromBits(this.low | other.low, this.high | other.high, this.unsigned);
+  };
+
+  /**
+   * Returns the bitwise XOR of this Long and the given one.
+   * @param {!Long|number|string} other Other Long
+   * @returns {!Long}
+   * @expose
+   */
+  Long.prototype.xor = function (other) {
+    if (!Long.isLong(other))
+      other = Long.fromValue(other);
+    return Long.fromBits(this.low ^ other.low, this.high ^ other.high, this.unsigned);
+  };
+
+  /**
+   * Returns this Long with bits shifted to the left by the given amount.
+   * @param {number|!Long} numBits Number of bits
+   * @returns {!Long} Shifted Long
+   * @expose
+   */
+  Long.prototype.shiftLeft = function (numBits) {
+    if (Long.isLong(numBits))
+      numBits = numBits.toInt();
+    if ((numBits &= 63) === 0)
+      return this;
+    else if (numBits < 32)
+      return Long.fromBits(this.low << numBits, (this.high << numBits) | (this.low >>> (32 - numBits)), this.unsigned);
+    else
+      return Long.fromBits(0, this.low << (numBits - 32), this.unsigned);
+  };
+
+  /**
+   * Returns this Long with bits arithmetically shifted to the right by the given amount.
+   * @param {number|!Long} numBits Number of bits
+   * @returns {!Long} Shifted Long
+   * @expose
+   */
+  Long.prototype.shiftRight = function (numBits) {
+    if (Long.isLong(numBits))
+      numBits = numBits.toInt();
+    if ((numBits &= 63) === 0)
+      return this;
+    else if (numBits < 32)
+      return Long.fromBits((this.low >>> numBits) | (this.high << (32 - numBits)), this.high >> numBits, this.unsigned);
+    else
+      return Long.fromBits(this.high >> (numBits - 32), this.high >= 0 ? 0 : -1, this.unsigned);
+  };
+
+  /**
+   * Returns this Long with bits logically shifted to the right by the given amount.
+   * @param {number|!Long} numBits Number of bits
+   * @returns {!Long} Shifted Long
+   * @expose
+   */
+  Long.prototype.shiftRightUnsigned = function (numBits) {
+    if (Long.isLong(numBits))
+      numBits = numBits.toInt();
+    numBits &= 63;
+    if (numBits === 0)
+      return this;
+    else {
+      var high = this.high;
+      if (numBits < 32) {
+        var low = this.low;
+        return Long.fromBits((low >>> numBits) | (high << (32 - numBits)), high >>> numBits, this.unsigned);
+      } else if (numBits === 32)
+        return Long.fromBits(high, 0, this.unsigned);
+      else
+        return Long.fromBits(high >>> (numBits - 32), 0, this.unsigned);
+    }
+  };
+
+  /**
+   * Converts this Long to signed.
+   * @returns {!Long} Signed long
+   * @expose
+   */
+  Long.prototype.toSigned = function () {
+    if (!this.unsigned)
+      return this;
+    return new Long(this.low, this.high, false);
+  };
+
+  /**
+   * Converts this Long to unsigned.
+   * @returns {!Long} Unsigned long
+   * @expose
+   */
+  Long.prototype.toUnsigned = function () {
+    if (this.unsigned)
+      return this;
+    return new Long(this.low, this.high, true);
+  };
+
+  return Long;
+});
+/*
+ Copyright 2013-2014 Daniel Wirtz <dcode@dcode.io>
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+/**
+ * @license ByteBuffer.js (c) 2015 Daniel Wirtz <dcode@dcode.io>
+ * [BUILD] ByteBufferAB - Backing buffer: ArrayBuffer, Accessor: Uint8Array
+ * Released under the Apache License, Version 2.0
+ * see: https://github.com/dcodeIO/ByteBuffer.js for details
+ */
+(function (global, factory) {
+
+  /* AMD */
+  if (typeof define === 'function' && define["amd"])
+    define(["Long"], factory);
+  /* CommonJS */
+  else if (typeof require === 'function' && typeof module === "object" && module && module["exports"])
+    module['exports'] = (function () {
+      var Long;
+      try { Long = require("long"); } catch (e) {}
+      return factory(Long);
+    })();
+  /* Global */
+  else
+    (global["dcodeIO"] = global["dcodeIO"] || {})["ByteBuffer"] = factory(global["dcodeIO"]["Long"]);
+
+})(this, function (Long) {
+  "use strict";
+
+  /**
+   * Constructs a new ByteBuffer.
+   * @class The swiss army knife for binary data in JavaScript.
+   * @exports ByteBuffer
+   * @constructor
+   * @param {number=} capacity Initial capacity. Defaults to {@link ByteBuffer.DEFAULT_CAPACITY}.
+   * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to
+   *  {@link ByteBuffer.DEFAULT_ENDIAN}.
+   * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to
+   *  {@link ByteBuffer.DEFAULT_NOASSERT}.
+   * @expose
+   */
+  var ByteBuffer = function (capacity, littleEndian, noAssert) {
+    if (typeof capacity === 'undefined')
+      capacity = ByteBuffer.DEFAULT_CAPACITY;
+    if (typeof littleEndian === 'undefined')
+      littleEndian = ByteBuffer.DEFAULT_ENDIAN;
+    if (typeof noAssert === 'undefined')
+      noAssert = ByteBuffer.DEFAULT_NOASSERT;
+    if (!noAssert) {
+      capacity = capacity | 0;
+      if (capacity < 0)
+        throw RangeError("Illegal capacity");
+      littleEndian = !!littleEndian;
+      noAssert = !!noAssert;
+    }
+
+    /**
+     * Backing ArrayBuffer.
+     * @type {!ArrayBuffer}
+     * @expose
+     */
+    this.buffer = capacity === 0 ? EMPTY_BUFFER : new ArrayBuffer(capacity);
+
+    /**
+     * Uint8Array utilized to manipulate the backing buffer. Becomes `null` if the backing buffer has a capacity of `0`.
+     * @type {?Uint8Array}
+     * @expose
+     */
+    this.view = capacity === 0 ? null : new Uint8Array(this.buffer);
+
+    /**
+     * Absolute read/write offset.
+     * @type {number}
+     * @expose
+     * @see ByteBuffer#flip
+     * @see ByteBuffer#clear
+     */
+    this.offset = 0;
+
+    /**
+     * Marked offset.
+     * @type {number}
+     * @expose
+     * @see ByteBuffer#mark
+     * @see ByteBuffer#reset
+     */
+    this.markedOffset = -1;
+
+    /**
+     * Absolute limit of the contained data. Set to the backing buffer's capacity upon allocation.
+     * @type {number}
+     * @expose
+     * @see ByteBuffer#flip
+     * @see ByteBuffer#clear
+     */
+    this.limit = capacity;
+
+    /**
+     * Whether to use little endian byte order, defaults to `false` for big endian.
+     * @type {boolean}
+     * @expose
+     */
+    this.littleEndian = typeof littleEndian !== 'undefined' ? !!littleEndian : false;
+
+    /**
+     * Whether to skip assertions of offsets and values, defaults to `false`.
+     * @type {boolean}
+     * @expose
+     */
+    this.noAssert = !!noAssert;
+  };
+
+  /**
+   * ByteBuffer version.
+   * @type {string}
+   * @const
+   * @expose
+   */
+  ByteBuffer.VERSION = "4.1.0";
+
+  /**
+   * Little endian constant that can be used instead of its boolean value. Evaluates to `true`.
+   * @type {boolean}
+   * @const
+   * @expose
+   */
+  ByteBuffer.LITTLE_ENDIAN = true;
+
+  /**
+   * Big endian constant that can be used instead of its boolean value. Evaluates to `false`.
+   * @type {boolean}
+   * @const
+   * @expose
+   */
+  ByteBuffer.BIG_ENDIAN = false;
+
+  /**
+   * Default initial capacity of `16`.
+   * @type {number}
+   * @expose
+   */
+  ByteBuffer.DEFAULT_CAPACITY = 16;
+
+  /**
+   * Default endianess of `false` for big endian.
+   * @type {boolean}
+   * @expose
+   */
+  ByteBuffer.DEFAULT_ENDIAN = ByteBuffer.BIG_ENDIAN;
+
+  /**
+   * Default no assertions flag of `false`.
+   * @type {boolean}
+   * @expose
+   */
+  ByteBuffer.DEFAULT_NOASSERT = false;
+
+  /**
+   * A `Long` class for representing a 64-bit two's-complement integer value. May be `null` if Long.js has not been loaded
+   *  and int64 support is not available.
+   * @type {?Long}
+   * @const
+   * @see https://github.com/dcodeIO/Long.js
+   * @expose
+   */
+  ByteBuffer.Long = Long || null;
+
+  /**
+   * @alias ByteBuffer.prototype
+   * @inner
+   */
+  var ByteBufferPrototype = ByteBuffer.prototype;
+
+  /**
+   * An indicator used to reliably determine if an object is a ByteBuffer or not.
+   * @type {boolean}
+   * @const
+   * @expose
+   * @private
+   */
+  ByteBufferPrototype.__isByteBuffer__;
+
+  Object.defineProperty(ByteBufferPrototype, "__isByteBuffer__", {
+    value: true,
+    enumerable: false,
+    configurable: false
+  });
+
+  // helpers
+
+  /**
+   * @type {!ArrayBuffer}
+   * @inner
+   */
+  var EMPTY_BUFFER = new ArrayBuffer(0);
+
+  /**
+   * String.fromCharCode reference for compile-time renaming.
+   * @type {function(...number):string}
+   * @inner
+   */
+  var stringFromCharCode = String.fromCharCode;
+
+  /**
+   * Creates a source function for a string.
+   * @param {string} s String to read from
+   * @returns {function():number|null} Source function returning the next char code respectively `null` if there are
+   *  no more characters left.
+   * @throws {TypeError} If the argument is invalid
+   * @inner
+   */
+  function stringSource(s) {
+    var i = 0;
+    return function () {
+      return i < s.length ? s.charCodeAt(i++) : null;
+    };
+  }
+
+  /**
+   * Creates a destination function for a string.
+   * @returns {function(number=):undefined|string} Destination function successively called with the next char code.
+   *  Returns the final string when called without arguments.
+   * @inner
+   */
+  function stringDestination() {
+    var cs = [],
+      ps = [];
+    return function () {
+      if (arguments.length === 0)
+        return ps.join('') + stringFromCharCode.apply(String, cs);
+      if (cs.length + arguments.length > 1024)
+        ps.push(stringFromCharCode.apply(String, cs)),
+        cs.length = 0;
+      Array.prototype.push.apply(cs, arguments);
+    };
+  }
+
+  /**
+   * Gets the accessor type.
+   * @returns {Function} `Buffer` under node.js, `Uint8Array` respectively `DataView` in the browser (classes)
+   * @expose
+   */
+  ByteBuffer.accessor = function () {
+    return Uint8Array;
+  };
+  /**
+   * Allocates a new ByteBuffer backed by a buffer of the specified capacity.
+   * @param {number=} capacity Initial capacity. Defaults to {@link ByteBuffer.DEFAULT_CAPACITY}.
+   * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to
+   *  {@link ByteBuffer.DEFAULT_ENDIAN}.
+   * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to
+   *  {@link ByteBuffer.DEFAULT_NOASSERT}.
+   * @returns {!ByteBuffer}
+   * @expose
+   */
+  ByteBuffer.allocate = function (capacity, littleEndian, noAssert) {
+    return new ByteBuffer(capacity, littleEndian, noAssert);
+  };
+
+  /**
+   * Concatenates multiple ByteBuffers into one.
+   * @param {!Array.<!ByteBuffer|!ArrayBuffer|!Uint8Array|string>} buffers Buffers to concatenate
+   * @param {(string|boolean)=} encoding String encoding if `buffers` contains a string ("base64", "hex", "binary",
+   *  defaults to "utf8")
+   * @param {boolean=} littleEndian Whether to use little or big endian byte order for the resulting ByteBuffer. Defaults
+   *  to {@link ByteBuffer.DEFAULT_ENDIAN}.
+   * @param {boolean=} noAssert Whether to skip assertions of offsets and values for the resulting ByteBuffer. Defaults to
+   *  {@link ByteBuffer.DEFAULT_NOASSERT}.
+   * @returns {!ByteBuffer} Concatenated ByteBuffer
+   * @expose
+   */
+  ByteBuffer.concat = function (buffers, encoding, littleEndian, noAssert) {
+    if (typeof encoding === 'boolean' || typeof encoding !== 'string') {
+      noAssert = littleEndian;
+      littleEndian = encoding;
+      encoding = undefined;
+    }
+    var capacity = 0;
+    for (var i = 0, k = buffers.length, length; i < k; ++i) {
+      if (!ByteBuffer.isByteBuffer(buffers[i]))
+        buffers[i] = ByteBuffer.wrap(buffers[i], encoding);
+      length = buffers[i].limit - buffers[i].offset;
+      if (length > 0) capacity += length;
+    }
+    if (capacity === 0)
+      return new ByteBuffer(0, littleEndian, noAssert);
+    var bb = new ByteBuffer(capacity, littleEndian, noAssert),
+      bi;
+    i = 0;
+    while (i < k) {
+      bi = buffers[i++];
+      length = bi.limit - bi.offset;
+      if (length <= 0) continue;
+      bb.view.set(bi.view.subarray(bi.offset, bi.limit), bb.offset);
+      bb.offset += length;
+    }
+    bb.limit = bb.offset;
+    bb.offset = 0;
+    return bb;
+  };
+
+  /**
+   * Tests if the specified type is a ByteBuffer.
+   * @param {*} bb ByteBuffer to test
+   * @returns {boolean} `true` if it is a ByteBuffer, otherwise `false`
+   * @expose
+   */
+  ByteBuffer.isByteBuffer = function (bb) {
+    return (bb && bb["__isByteBuffer__"]) === true;
+  };
+  /**
+   * Gets the backing buffer type.
+   * @returns {Function} `Buffer` under node.js, `ArrayBuffer` in the browser (classes)
+   * @expose
+   */
+  ByteBuffer.type = function () {
+    return ArrayBuffer;
+  };
+  /**
+   * Wraps a buffer or a string. Sets the allocated ByteBuffer's {@link ByteBuffer#offset} to `0` and its
+   *  {@link ByteBuffer#limit} to the length of the wrapped data.
+   * @param {!ByteBuffer|!ArrayBuffer|!Uint8Array|string|!Array.<number>} buffer Anything that can be wrapped
+   * @param {(string|boolean)=} encoding String encoding if `buffer` is a string ("base64", "hex", "binary", defaults to
+   *  "utf8")
+   * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to
+   *  {@link ByteBuffer.DEFAULT_ENDIAN}.
+   * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to
+   *  {@link ByteBuffer.DEFAULT_NOASSERT}.
+   * @returns {!ByteBuffer} A ByteBuffer wrapping `buffer`
+   * @expose
+   */
+  ByteBuffer.wrap = function (buffer, encoding, littleEndian, noAssert) {
+    if (typeof encoding !== 'string') {
+      noAssert = littleEndian;
+      littleEndian = encoding;
+      encoding = undefined;
+    }
+    if (typeof buffer === 'string') {
+      if (typeof encoding === 'undefined')
+        encoding = "utf8";
+      switch (encoding) {
+      case "base64":
+        return ByteBuffer.fromBase64(buffer, littleEndian);
+      case "hex":
+        return ByteBuffer.fromHex(buffer, littleEndian);
+      case "binary":
+        return ByteBuffer.fromBinary(buffer, littleEndian);
+      case "utf8":
+        return ByteBuffer.fromUTF8(buffer, littleEndian);
+      case "debug":
+        return ByteBuffer.fromDebug(buffer, littleEndian);
+      default:
+        throw Error("Unsupported encoding: " + encoding);
+      }
+    }
+    if (buffer === null || typeof buffer !== 'object')
+      throw TypeError("Illegal buffer");
+    var bb;
+    if (ByteBuffer.isByteBuffer(buffer)) {
+      bb = ByteBufferPrototype.clone.call(buffer);
+      bb.markedOffset = -1;
+      return bb;
+    }
+    if (buffer instanceof Uint8Array) { // Extract ArrayBuffer from Uint8Array
+      bb = new ByteBuffer(0, littleEndian, noAssert);
+      if (buffer.length > 0) { // Avoid references to more than one EMPTY_BUFFER
+        bb.buffer = buffer.buffer;
+        bb.offset = buffer.byteOffset;
+        bb.limit = buffer.byteOffset + buffer.byteLength;
+        bb.view = new Uint8Array(buffer.buffer);
+      }
+    } else if (buffer instanceof ArrayBuffer) { // Reuse ArrayBuffer
+      bb = new ByteBuffer(0, littleEndian, noAssert);
+      if (buffer.byteLength > 0) {
+        bb.buffer = buffer;
+        bb.offset = 0;
+        bb.limit = buffer.byteLength;
+        bb.view = buffer.byteLength > 0 ? new Uint8Array(buffer) : null;
+      }
+    } else if (Object.prototype.toString.call(buffer) === "[object Array]") { // Create from octets
+      bb = new ByteBuffer(buffer.length, littleEndian, noAssert);
+      bb.limit = buffer.length;
+      for (var i = 0; i < buffer.length; ++i)
+        bb.view[i] = buffer[i];
+    } else
+      throw TypeError("Illegal buffer"); // Otherwise fail
+    return bb;
+  };
+
+  /**
+   * Reads the specified number of bytes.
+   * @param {number} length Number of bytes to read
+   * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `length` if omitted.
+   * @returns {!ByteBuffer}
+   * @expose
+   */
+  ByteBufferPrototype.readBytes = function (length, offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + length > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + length + ") <= " + this.buffer.byteLength);
+    }
+    var slice = this.slice(offset, offset + length);
+    if (relative) this.offset += length;
+    return slice;
+  };
+
+  /**
+   * Writes a payload of bytes. This is an alias of {@link ByteBuffer#append}.
+   * @function
+   * @param {!ByteBuffer|!ArrayBuffer|!Uint8Array|string} source Data to write. If `source` is a ByteBuffer, its offsets
+   *  will be modified according to the performed read operation.
+   * @param {(string|number)=} encoding Encoding if `data` is a string ("base64", "hex", "binary", defaults to "utf8")
+   * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+   *  written if omitted.
+   * @returns {!ByteBuffer} this
+   * @expose
+   */
+  ByteBufferPrototype.writeBytes = ByteBufferPrototype.append;
+
+  // types/ints/int8
+
+  /**
+   * Writes an 8bit signed integer.
+   * @param {number} value Value to write
+   * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `1` if omitted.
+   * @returns {!ByteBuffer} this
+   * @expose
+   */
+  ByteBufferPrototype.writeInt8 = function (value, offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof value !== 'number' || value % 1 !== 0)
+        throw TypeError("Illegal value: " + value + " (not an integer)");
+      value |= 0;
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 0 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 0 + ") <= " + this.buffer.byteLength);
+    }
+    offset += 1;
+    var capacity0 = this.buffer.byteLength;
+    if (offset > capacity0)
+      this.resize((capacity0 *= 2) > offset ? capacity0 : offset);
+    offset -= 1;
+    this.view[offset] = value;
+    if (relative) this.offset += 1;
+    return this;
+  };
+
+  /**
+   * Writes an 8bit signed integer. This is an alias of {@link ByteBuffer#writeInt8}.
+   * @function
+   * @param {number} value Value to write
+   * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `1` if omitted.
+   * @returns {!ByteBuffer} this
+   * @expose
+   */
+  ByteBufferPrototype.writeByte = ByteBufferPrototype.writeInt8;
+
+  /**
+   * Reads an 8bit signed integer.
+   * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `1` if omitted.
+   * @returns {number} Value read
+   * @expose
+   */
+  ByteBufferPrototype.readInt8 = function (offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 1 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 1 + ") <= " + this.buffer.byteLength);
+    }
+    var value = this.view[offset];
+    if ((value & 0x80) === 0x80) value = -(0xFF - value + 1); // Cast to signed
+    if (relative) this.offset += 1;
+    return value;
+  };
+
+  /**
+   * Reads an 8bit signed integer. This is an alias of {@link ByteBuffer#readInt8}.
+   * @function
+   * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `1` if omitted.
+   * @returns {number} Value read
+   * @expose
+   */
+  ByteBufferPrototype.readByte = ByteBufferPrototype.readInt8;
+
+  /**
+   * Writes an 8bit unsigned integer.
+   * @param {number} value Value to write
+   * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `1` if omitted.
+   * @returns {!ByteBuffer} this
+   * @expose
+   */
+  ByteBufferPrototype.writeUint8 = function (value, offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof value !== 'number' || value % 1 !== 0)
+        throw TypeError("Illegal value: " + value + " (not an integer)");
+      value >>>= 0;
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 0 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 0 + ") <= " + this.buffer.byteLength);
+    }
+    offset += 1;
+    var capacity1 = this.buffer.byteLength;
+    if (offset > capacity1)
+      this.resize((capacity1 *= 2) > offset ? capacity1 : offset);
+    offset -= 1;
+    this.view[offset] = value;
+    if (relative) this.offset += 1;
+    return this;
+  };
+
+  /**
+   * Writes an 8bit unsigned integer. This is an alias of {@link ByteBuffer#writeUint8}.
+   * @function
+   * @param {number} value Value to write
+   * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `1` if omitted.
+   * @returns {!ByteBuffer} this
+   * @expose
+   */
+  ByteBufferPrototype.writeUInt8 = ByteBufferPrototype.writeUint8;
+
+  /**
+   * Reads an 8bit unsigned integer.
+   * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `1` if omitted.
+   * @returns {number} Value read
+   * @expose
+   */
+  ByteBufferPrototype.readUint8 = function (offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 1 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 1 + ") <= " + this.buffer.byteLength);
+    }
+    var value = this.view[offset];
+    if (relative) this.offset += 1;
+    return value;
+  };
+
+  /**
+   * Reads an 8bit unsigned integer. This is an alias of {@link ByteBuffer#readUint8}.
+   * @function
+   * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `1` if omitted.
+   * @returns {number} Value read
+   * @expose
+   */
+  ByteBufferPrototype.readUInt8 = ByteBufferPrototype.readUint8;
+
+  // types/ints/int16
+
+  /**
+   * Writes a 16bit signed integer.
+   * @param {number} value Value to write
+   * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `2` if omitted.
+   * @throws {TypeError} If `offset` or `value` is not a valid number
+   * @throws {RangeError} If `offset` is out of bounds
+   * @expose
+   */
+  ByteBufferPrototype.writeInt16 = function (value, offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof value !== 'number' || value % 1 !== 0)
+        throw TypeError("Illegal value: " + value + " (not an integer)");
+      value |= 0;
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 0 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 0 + ") <= " + this.buffer.byteLength);
+    }
+    offset += 2;
+    var capacity2 = this.buffer.byteLength;
+    if (offset > capacity2)
+      this.resize((capacity2 *= 2) > offset ? capacity2 : offset);
+    offset -= 2;
+    if (this.littleEndian) {
+      this.view[offset + 1] = (value & 0xFF00) >>> 8;
+      this.view[offset] = value & 0x00FF;
+    } else {
+      this.view[offset] = (value & 0xFF00) >>> 8;
+      this.view[offset + 1] = value & 0x00FF;
+    }
+    if (relative) this.offset += 2;
+    return this;
+  };
+
+  /**
+   * Writes a 16bit signed integer. This is an alias of {@link ByteBuffer#writeInt16}.
+   * @function
+   * @param {number} value Value to write
+   * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `2` if omitted.
+   * @throws {TypeError} If `offset` or `value` is not a valid number
+   * @throws {RangeError} If `offset` is out of bounds
+   * @expose
+   */
+  ByteBufferPrototype.writeShort = ByteBufferPrototype.writeInt16;
+
+  /**
+   * Reads a 16bit signed integer.
+   * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `2` if omitted.
+   * @returns {number} Value read
+   * @throws {TypeError} If `offset` is not a valid number
+   * @throws {RangeError} If `offset` is out of bounds
+   * @expose
+   */
+  ByteBufferPrototype.readInt16 = function (offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 2 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 2 + ") <= " + this.buffer.byteLength);
+    }
+    var value = 0;
+    if (this.littleEndian) {
+      value = this.view[offset];
+      value |= this.view[offset + 1] << 8;
+    } else {
+      value = this.view[offset] << 8;
+      value |= this.view[offset + 1];
+    }
+    if ((value & 0x8000) === 0x8000) value = -(0xFFFF - value + 1); // Cast to signed
+    if (relative) this.offset += 2;
+    return value;
+  };
+
+  /**
+   * Reads a 16bit signed integer. This is an alias of {@link ByteBuffer#readInt16}.
+   * @function
+   * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `2` if omitted.
+   * @returns {number} Value read
+   * @throws {TypeError} If `offset` is not a valid number
+   * @throws {RangeError} If `offset` is out of bounds
+   * @expose
+   */
+  ByteBufferPrototype.readShort = ByteBufferPrototype.readInt16;
+
+  /**
+   * Writes a 16bit unsigned integer.
+   * @param {number} value Value to write
+   * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `2` if omitted.
+   * @throws {TypeError} If `offset` or `value` is not a valid number
+   * @throws {RangeError} If `offset` is out of bounds
+   * @expose
+   */
+  ByteBufferPrototype.writeUint16 = function (value, offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof value !== 'number' || value % 1 !== 0)
+        throw TypeError("Illegal value: " + value + " (not an integer)");
+      value >>>= 0;
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 0 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 0 + ") <= " + this.buffer.byteLength);
+    }
+    offset += 2;
+    var capacity3 = this.buffer.byteLength;
+    if (offset > capacity3)
+      this.resize((capacity3 *= 2) > offset ? capacity3 : offset);
+    offset -= 2;
+    if (this.littleEndian) {
+      this.view[offset + 1] = (value & 0xFF00) >>> 8;
+      this.view[offset] = value & 0x00FF;
+    } else {
+      this.view[offset] = (value & 0xFF00) >>> 8;
+      this.view[offset + 1] = value & 0x00FF;
+    }
+    if (relative) this.offset += 2;
+    return this;
+  };
+
+  /**
+   * Writes a 16bit unsigned integer. This is an alias of {@link ByteBuffer#writeUint16}.
+   * @function
+   * @param {number} value Value to write
+   * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `2` if omitted.
+   * @throws {TypeError} If `offset` or `value` is not a valid number
+   * @throws {RangeError} If `offset` is out of bounds
+   * @expose
+   */
+  ByteBufferPrototype.writeUInt16 = ByteBufferPrototype.writeUint16;
+
+  /**
+   * Reads a 16bit unsigned integer.
+   * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `2` if omitted.
+   * @returns {number} Value read
+   * @throws {TypeError} If `offset` is not a valid number
+   * @throws {RangeError} If `offset` is out of bounds
+   * @expose
+   */
+  ByteBufferPrototype.readUint16 = function (offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 2 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 2 + ") <= " + this.buffer.byteLength);
+    }
+    var value = 0;
+    if (this.littleEndian) {
+      value = this.view[offset];
+      value |= this.view[offset + 1] << 8;
+    } else {
+      value = this.view[offset] << 8;
+      value |= this.view[offset + 1];
+    }
+    if (relative) this.offset += 2;
+    return value;
+  };
+
+  /**
+   * Reads a 16bit unsigned integer. This is an alias of {@link ByteBuffer#readUint16}.
+   * @function
+   * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `2` if omitted.
+   * @returns {number} Value read
+   * @throws {TypeError} If `offset` is not a valid number
+   * @throws {RangeError} If `offset` is out of bounds
+   * @expose
+   */
+  ByteBufferPrototype.readUInt16 = ByteBufferPrototype.readUint16;
+
+  // types/ints/int32
+
+  /**
+   * Writes a 32bit signed integer.
+   * @param {number} value Value to write
+   * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `4` if omitted.
+   * @expose
+   */
+  ByteBufferPrototype.writeInt32 = function (value, offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof value !== 'number' || value % 1 !== 0)
+        throw TypeError("Illegal value: " + value + " (not an integer)");
+      value |= 0;
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 0 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 0 + ") <= " + this.buffer.byteLength);
+    }
+    offset += 4;
+    var capacity4 = this.buffer.byteLength;
+    if (offset > capacity4)
+      this.resize((capacity4 *= 2) > offset ? capacity4 : offset);
+    offset -= 4;
+    if (this.littleEndian) {
+      this.view[offset + 3] = (value >>> 24) & 0xFF;
+      this.view[offset + 2] = (value >>> 16) & 0xFF;
+      this.view[offset + 1] = (value >>> 8) & 0xFF;
+      this.view[offset] = value & 0xFF;
+    } else {
+      this.view[offset] = (value >>> 24) & 0xFF;
+      this.view[offset + 1] = (value >>> 16) & 0xFF;
+      this.view[offset + 2] = (value >>> 8) & 0xFF;
+      this.view[offset + 3] = value & 0xFF;
+    }
+    if (relative) this.offset += 4;
+    return this;
+  };
+
+  /**
+   * Writes a 32bit signed integer. This is an alias of {@link ByteBuffer#writeInt32}.
+   * @param {number} value Value to write
+   * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `4` if omitted.
+   * @expose
+   */
+  ByteBufferPrototype.writeInt = ByteBufferPrototype.writeInt32;
+
+  /**
+   * Reads a 32bit signed integer.
+   * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `4` if omitted.
+   * @returns {number} Value read
+   * @expose
+   */
+  ByteBufferPrototype.readInt32 = function (offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 4 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 4 + ") <= " + this.buffer.byteLength);
+    }
+    var value = 0;
+    if (this.littleEndian) {
+      value = this.view[offset + 2] << 16;
+      value |= this.view[offset + 1] << 8;
+      value |= this.view[offset];
+      value += this.view[offset + 3] << 24 >>> 0;
+    } else {
+      value = this.view[offset + 1] << 16;
+      value |= this.view[offset + 2] << 8;
+      value |= this.view[offset + 3];
+      value += this.view[offset] << 24 >>> 0;
+    }
+    value |= 0; // Cast to signed
+    if (relative) this.offset += 4;
+    return value;
+  };
+
+  /**
+   * Reads a 32bit signed integer. This is an alias of {@link ByteBuffer#readInt32}.
+   * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `4` if omitted.
+   * @returns {number} Value read
+   * @expose
+   */
+  ByteBufferPrototype.readInt = ByteBufferPrototype.readInt32;
+
+  /**
+   * Writes a 32bit unsigned integer.
+   * @param {number} value Value to write
+   * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `4` if omitted.
+   * @expose
+   */
+  ByteBufferPrototype.writeUint32 = function (value, offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof value !== 'number' || value % 1 !== 0)
+        throw TypeError("Illegal value: " + value + " (not an integer)");
+      value >>>= 0;
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 0 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 0 + ") <= " + this.buffer.byteLength);
+    }
+    offset += 4;
+    var capacity5 = this.buffer.byteLength;
+    if (offset > capacity5)
+      this.resize((capacity5 *= 2) > offset ? capacity5 : offset);
+    offset -= 4;
+    if (this.littleEndian) {
+      this.view[offset + 3] = (value >>> 24) & 0xFF;
+      this.view[offset + 2] = (value >>> 16) & 0xFF;
+      this.view[offset + 1] = (value >>> 8) & 0xFF;
+      this.view[offset] = value & 0xFF;
+    } else {
+      this.view[offset] = (value >>> 24) & 0xFF;
+      this.view[offset + 1] = (value >>> 16) & 0xFF;
+      this.view[offset + 2] = (value >>> 8) & 0xFF;
+      this.view[offset + 3] = value & 0xFF;
+    }
+    if (relative) this.offset += 4;
+    return this;
+  };
+
+  /**
+   * Writes a 32bit unsigned integer. This is an alias of {@link ByteBuffer#writeUint32}.
+   * @function
+   * @param {number} value Value to write
+   * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `4` if omitted.
+   * @expose
+   */
+  ByteBufferPrototype.writeUInt32 = ByteBufferPrototype.writeUint32;
+
+  /**
+   * Reads a 32bit unsigned integer.
+   * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `4` if omitted.
+   * @returns {number} Value read
+   * @expose
+   */
+  ByteBufferPrototype.readUint32 = function (offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 4 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 4 + ") <= " + this.buffer.byteLength);
+    }
+    var value = 0;
+    if (this.littleEndian) {
+      value = this.view[offset + 2] << 16;
+      value |= this.view[offset + 1] << 8;
+      value |= this.view[offset];
+      value += this.view[offset + 3] << 24 >>> 0;
+    } else {
+      value = this.view[offset + 1] << 16;
+      value |= this.view[offset + 2] << 8;
+      value |= this.view[offset + 3];
+      value += this.view[offset] << 24 >>> 0;
+    }
+    if (relative) this.offset += 4;
+    return value;
+  };
+
+  /**
+   * Reads a 32bit unsigned integer. This is an alias of {@link ByteBuffer#readUint32}.
+   * @function
+   * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `4` if omitted.
+   * @returns {number} Value read
+   * @expose
+   */
+  ByteBufferPrototype.readUInt32 = ByteBufferPrototype.readUint32;
+
+  // types/ints/int64
+
+  if (Long) {
+
+    /**
+     * Writes a 64bit signed integer.
+     * @param {number|!Long} value Value to write
+     * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+     * @returns {!ByteBuffer} this
+     * @expose
+     */
+    ByteBufferPrototype.writeInt64 = function (value, offset) {
+      var relative = typeof offset === 'undefined';
+      if (relative) offset = this.offset;
+      if (!this.noAssert) {
+        if (typeof value === 'number')
+          value = Long.fromNumber(value);
+        else if (typeof value === 'string')
+          value = Long.fromString(value);
+        else if (!(value && value instanceof Long))
+          throw TypeError("Illegal value: " + value + " (not an integer or Long)");
+        if (typeof offset !== 'number' || offset % 1 !== 0)
+          throw TypeError("Illegal offset: " + offset + " (not an integer)");
+        offset >>>= 0;
+        if (offset < 0 || offset + 0 > this.buffer.byteLength)
+          throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 0 + ") <= " + this.buffer.byteLength);
+      }
+      if (typeof value === 'number')
+        value = Long.fromNumber(value);
+      else if (typeof value === 'string')
+        value = Long.fromString(value);
+      offset += 8;
+      var capacity6 = this.buffer.byteLength;
+      if (offset > capacity6)
+        this.resize((capacity6 *= 2) > offset ? capacity6 : offset);
+      offset -= 8;
+      var lo = value.low,
+        hi = value.high;
+      if (this.littleEndian) {
+        this.view[offset + 3] = (lo >>> 24) & 0xFF;
+        this.view[offset + 2] = (lo >>> 16) & 0xFF;
+        this.view[offset + 1] = (lo >>> 8) & 0xFF;
+        this.view[offset] = lo & 0xFF;
+        offset += 4;
+        this.view[offset + 3] = (hi >>> 24) & 0xFF;
+        this.view[offset + 2] = (hi >>> 16) & 0xFF;
+        this.view[offset + 1] = (hi >>> 8) & 0xFF;
+        this.view[offset] = hi & 0xFF;
+      } else {
+        this.view[offset] = (hi >>> 24) & 0xFF;
+        this.view[offset + 1] = (hi >>> 16) & 0xFF;
+        this.view[offset + 2] = (hi >>> 8) & 0xFF;
+        this.view[offset + 3] = hi & 0xFF;
+        offset += 4;
+        this.view[offset] = (lo >>> 24) & 0xFF;
+        this.view[offset + 1] = (lo >>> 16) & 0xFF;
+        this.view[offset + 2] = (lo >>> 8) & 0xFF;
+        this.view[offset + 3] = lo & 0xFF;
+      }
+      if (relative) this.offset += 8;
+      return this;
+    };
+
+    /**
+     * Writes a 64bit signed integer. This is an alias of {@link ByteBuffer#writeInt64}.
+     * @param {number|!Long} value Value to write
+     * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+     * @returns {!ByteBuffer} this
+     * @expose
+     */
+    ByteBufferPrototype.writeLong = ByteBufferPrototype.writeInt64;
+
+    /**
+     * Reads a 64bit signed integer.
+     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+     * @returns {!Long}
+     * @expose
+     */
+    ByteBufferPrototype.readInt64 = function (offset) {
+      var relative = typeof offset === 'undefined';
+      if (relative) offset = this.offset;
+      if (!this.noAssert) {
+        if (typeof offset !== 'number' || offset % 1 !== 0)
+          throw TypeError("Illegal offset: " + offset + " (not an integer)");
+        offset >>>= 0;
+        if (offset < 0 || offset + 8 > this.buffer.byteLength)
+          throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 8 + ") <= " + this.buffer.byteLength);
+      }
+      var lo = 0,
+        hi = 0;
+      if (this.littleEndian) {
+        lo = this.view[offset + 2] << 16;
+        lo |= this.view[offset + 1] << 8;
+        lo |= this.view[offset];
+        lo += this.view[offset + 3] << 24 >>> 0;
+        offset += 4;
+        hi = this.view[offset + 2] << 16;
+        hi |= this.view[offset + 1] << 8;
+        hi |= this.view[offset];
+        hi += this.view[offset + 3] << 24 >>> 0;
+      } else {
+        hi = this.view[offset + 1] << 16;
+        hi |= this.view[offset + 2] << 8;
+        hi |= this.view[offset + 3];
+        hi += this.view[offset] << 24 >>> 0;
+        offset += 4;
+        lo = this.view[offset + 1] << 16;
+        lo |= this.view[offset + 2] << 8;
+        lo |= this.view[offset + 3];
+        lo += this.view[offset] << 24 >>> 0;
+      }
+      var value = new Long(lo, hi, false);
+      if (relative) this.offset += 8;
+      return value;
+    };
+
+    /**
+     * Reads a 64bit signed integer. This is an alias of {@link ByteBuffer#readInt64}.
+     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+     * @returns {!Long}
+     * @expose
+     */
+    ByteBufferPrototype.readLong = ByteBufferPrototype.readInt64;
+
+    /**
+     * Writes a 64bit unsigned integer.
+     * @param {number|!Long} value Value to write
+     * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+     * @returns {!ByteBuffer} this
+     * @expose
+     */
+    ByteBufferPrototype.writeUint64 = function (value, offset) {
+      var relative = typeof offset === 'undefined';
+      if (relative) offset = this.offset;
+      if (!this.noAssert) {
+        if (typeof value === 'number')
+          value = Long.fromNumber(value);
+        else if (typeof value === 'string')
+          value = Long.fromString(value);
+        else if (!(value && value instanceof Long))
+          throw TypeError("Illegal value: " + value + " (not an integer or Long)");
+        if (typeof offset !== 'number' || offset % 1 !== 0)
+          throw TypeError("Illegal offset: " + offset + " (not an integer)");
+        offset >>>= 0;
+        if (offset < 0 || offset + 0 > this.buffer.byteLength)
+          throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 0 + ") <= " + this.buffer.byteLength);
+      }
+      if (typeof value === 'number')
+        value = Long.fromNumber(value);
+      else if (typeof value === 'string')
+        value = Long.fromString(value);
+      offset += 8;
+      var capacity7 = this.buffer.byteLength;
+      if (offset > capacity7)
+        this.resize((capacity7 *= 2) > offset ? capacity7 : offset);
+      offset -= 8;
+      var lo = value.low,
+        hi = value.high;
+      if (this.littleEndian) {
+        this.view[offset + 3] = (lo >>> 24) & 0xFF;
+        this.view[offset + 2] = (lo >>> 16) & 0xFF;
+        this.view[offset + 1] = (lo >>> 8) & 0xFF;
+        this.view[offset] = lo & 0xFF;
+        offset += 4;
+        this.view[offset + 3] = (hi >>> 24) & 0xFF;
+        this.view[offset + 2] = (hi >>> 16) & 0xFF;
+        this.view[offset + 1] = (hi >>> 8) & 0xFF;
+        this.view[offset] = hi & 0xFF;
+      } else {
+        this.view[offset] = (hi >>> 24) & 0xFF;
+        this.view[offset + 1] = (hi >>> 16) & 0xFF;
+        this.view[offset + 2] = (hi >>> 8) & 0xFF;
+        this.view[offset + 3] = hi & 0xFF;
+        offset += 4;
+        this.view[offset] = (lo >>> 24) & 0xFF;
+        this.view[offset + 1] = (lo >>> 16) & 0xFF;
+        this.view[offset + 2] = (lo >>> 8) & 0xFF;
+        this.view[offset + 3] = lo & 0xFF;
+      }
+      if (relative) this.offset += 8;
+      return this;
+    };
+
+    /**
+     * Writes a 64bit unsigned integer. This is an alias of {@link ByteBuffer#writeUint64}.
+     * @function
+     * @param {number|!Long} value Value to write
+     * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+     * @returns {!ByteBuffer} this
+     * @expose
+     */
+    ByteBufferPrototype.writeUInt64 = ByteBufferPrototype.writeUint64;
+
+    /**
+     * Reads a 64bit unsigned integer.
+     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+     * @returns {!Long}
+     * @expose
+     */
+    ByteBufferPrototype.readUint64 = function (offset) {
+      var relative = typeof offset === 'undefined';
+      if (relative) offset = this.offset;
+      if (!this.noAssert) {
+        if (typeof offset !== 'number' || offset % 1 !== 0)
+          throw TypeError("Illegal offset: " + offset + " (not an integer)");
+        offset >>>= 0;
+        if (offset < 0 || offset + 8 > this.buffer.byteLength)
+          throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 8 + ") <= " + this.buffer.byteLength);
+      }
+      var lo = 0,
+        hi = 0;
+      if (this.littleEndian) {
+        lo = this.view[offset + 2] << 16;
+        lo |= this.view[offset + 1] << 8;
+        lo |= this.view[offset];
+        lo += this.view[offset + 3] << 24 >>> 0;
+        offset += 4;
+        hi = this.view[offset + 2] << 16;
+        hi |= this.view[offset + 1] << 8;
+        hi |= this.view[offset];
+        hi += this.view[offset + 3] << 24 >>> 0;
+      } else {
+        hi = this.view[offset + 1] << 16;
+        hi |= this.view[offset + 2] << 8;
+        hi |= this.view[offset + 3];
+        hi += this.view[offset] << 24 >>> 0;
+        offset += 4;
+        lo = this.view[offset + 1] << 16;
+        lo |= this.view[offset + 2] << 8;
+        lo |= this.view[offset + 3];
+        lo += this.view[offset] << 24 >>> 0;
+      }
+      var value = new Long(lo, hi, true);
+      if (relative) this.offset += 8;
+      return value;
+    };
+
+    /**
+     * Reads a 64bit unsigned integer. This is an alias of {@link ByteBuffer#readUint64}.
+     * @function
+     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+     * @returns {!Long}
+     * @expose
+     */
+    ByteBufferPrototype.readUInt64 = ByteBufferPrototype.readUint64;
+
+  } // Long
+
+  // types/floats/float32
+
+  /*
+   ieee754 - https://github.com/feross/ieee754
+
+   The MIT License (MIT)
+
+   Copyright (c) Feross Aboukhadijeh
+
+   Permission is hereby granted, free of charge, to any person obtaining a copy
+   of this software and associated documentation files (the "Software"), to deal
+   in the Software without restriction, including without limitation the rights
+   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+   copies of the Software, and to permit persons to whom the Software is
+   furnished to do so, subject to the following conditions:
+
+   The above copyright notice and this permission notice shall be included in
+   all copies or substantial portions of the Software.
+
+   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+   THE SOFTWARE.
+  */
+
+  /**
+   * Reads an IEEE754 float from a byte array.
+   * @param {!Array} buffer
+   * @param {number} offset
+   * @param {boolean} isLE
+   * @param {number} mLen
+   * @param {number} nBytes
+   * @returns {number}
+   * @inner
+   */
+  function ieee754_read(buffer, offset, isLE, mLen, nBytes) {
+    var e, m,
+      eLen = nBytes * 8 - mLen - 1,
+      eMax = (1 << eLen) - 1,
+      eBias = eMax >> 1,
+      nBits = -7,
+      i = isLE ? (nBytes - 1) : 0,
+      d = isLE ? -1 : 1,
+      s = buffer[offset + i];
+
+    i += d;
+
+    e = s & ((1 << (-nBits)) - 1);
+    s >>= (-nBits);
+    nBits += eLen;
+    for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {}
+
+    m = e & ((1 << (-nBits)) - 1);
+    e >>= (-nBits);
+    nBits += mLen;
+    for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {}
+
+    if (e === 0) {
+      e = 1 - eBias;
+    } else if (e === eMax) {
+      return m ? NaN : ((s ? -1 : 1) * Infinity);
+    } else {
+      m = m + Math.pow(2, mLen);
+      e = e - eBias;
+    }
+    return (s ? -1 : 1) * m * Math.pow(2, e - mLen);
+  }
+
+  /**
+   * Writes an IEEE754 float to a byte array.
+   * @param {!Array} buffer
+   * @param {number} value
+   * @param {number} offset
+   * @param {boolean} isLE
+   * @param {number} mLen
+   * @param {number} nBytes
+   * @inner
+   */
+  function ieee754_write(buffer, value, offset, isLE, mLen, nBytes) {
+    var e, m, c,
+      eLen = nBytes * 8 - mLen - 1,
+      eMax = (1 << eLen) - 1,
+      eBias = eMax >> 1,
+      rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0),
+      i = isLE ? 0 : (nBytes - 1),
+      d = isLE ? 1 : -1,
+      s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0;
+
+    value = Math.abs(value);
+
+    if (isNaN(value) || value === Infinity) {
+      m = isNaN(value) ? 1 : 0;
+      e = eMax;
+    } else {
+      e = Math.floor(Math.log(value) / Math.LN2);
+      if (value * (c = Math.pow(2, -e)) < 1) {
+        e--;
+        c *= 2;
+      }
+      if (e + eBias >= 1) {
+        value += rt / c;
+      } else {
+        value += rt * Math.pow(2, 1 - eBias);
+      }
+      if (value * c >= 2) {
+        e++;
+        c /= 2;
+      }
+
+      if (e + eBias >= eMax) {
+        m = 0;
+        e = eMax;
+      } else if (e + eBias >= 1) {
+        m = (value * c - 1) * Math.pow(2, mLen);
+        e = e + eBias;
+      } else {
+        m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen);
+        e = 0;
+      }
+    }
+
+    for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}
+
+    e = (e << mLen) | m;
+    eLen += mLen;
+    for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
+
+    buffer[offset + i - d] |= s * 128;
+  }
+
+  /**
+   * Writes a 32bit float.
+   * @param {number} value Value to write
+   * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `4` if omitted.
+   * @returns {!ByteBuffer} this
+   * @expose
+   */
+  ByteBufferPrototype.writeFloat32 = function (value, offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof value !== 'number')
+        throw TypeError("Illegal value: " + value + " (not a number)");
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 0 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 0 + ") <= " + this.buffer.byteLength);
+    }
+    offset += 4;
+    var capacity8 = this.buffer.byteLength;
+    if (offset > capacity8)
+      this.resize((capacity8 *= 2) > offset ? capacity8 : offset);
+    offset -= 4;
+    ieee754_write(this.view, value, offset, this.littleEndian, 23, 4);
+    if (relative) this.offset += 4;
+    return this;
+  };
+
+  /**
+   * Writes a 32bit float. This is an alias of {@link ByteBuffer#writeFloat32}.
+   * @function
+   * @param {number} value Value to write
+   * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `4` if omitted.
+   * @returns {!ByteBuffer} this
+   * @expose
+   */
+  ByteBufferPrototype.writeFloat = ByteBufferPrototype.writeFloat32;
+
+  /**
+   * Reads a 32bit float.
+   * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `4` if omitted.
+   * @returns {number}
+   * @expose
+   */
+  ByteBufferPrototype.readFloat32 = function (offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 4 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 4 + ") <= " + this.buffer.byteLength);
+    }
+    var value = ieee754_read(this.view, offset, this.littleEndian, 23, 4);
+    if (relative) this.offset += 4;
+    return value;
+  };
+
+  /**
+   * Reads a 32bit float. This is an alias of {@link ByteBuffer#readFloat32}.
+   * @function
+   * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `4` if omitted.
+   * @returns {number}
+   * @expose
+   */
+  ByteBufferPrototype.readFloat = ByteBufferPrototype.readFloat32;
+
+  // types/floats/float64
+
+  /**
+   * Writes a 64bit float.
+   * @param {number} value Value to write
+   * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+   * @returns {!ByteBuffer} this
+   * @expose
+   */
+  ByteBufferPrototype.writeFloat64 = function (value, offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof value !== 'number')
+        throw TypeError("Illegal value: " + value + " (not a number)");
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 0 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 0 + ") <= " + this.buffer.byteLength);
+    }
+    offset += 8;
+    var capacity9 = this.buffer.byteLength;
+    if (offset > capacity9)
+      this.resize((capacity9 *= 2) > offset ? capacity9 : offset);
+    offset -= 8;
+    ieee754_write(this.view, value, offset, this.littleEndian, 52, 8);
+    if (relative) this.offset += 8;
+    return this;
+  };
+
+  /**
+   * Writes a 64bit float. This is an alias of {@link ByteBuffer#writeFloat64}.
+   * @function
+   * @param {number} value Value to write
+   * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+   * @returns {!ByteBuffer} this
+   * @expose
+   */
+  ByteBufferPrototype.writeDouble = ByteBufferPrototype.writeFloat64;
+
+  /**
+   * Reads a 64bit float.
+   * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+   * @returns {number}
+   * @expose
+   */
+  ByteBufferPrototype.readFloat64 = function (offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 8 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 8 + ") <= " + this.buffer.byteLength);
+    }
+    var value = ieee754_read(this.view, offset, this.littleEndian, 52, 8);
+    if (relative) this.offset += 8;
+    return value;
+  };
+
+  /**
+   * Reads a 64bit float. This is an alias of {@link ByteBuffer#readFloat64}.
+   * @function
+   * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+   * @returns {number}
+   * @expose
+   */
+  ByteBufferPrototype.readDouble = ByteBufferPrototype.readFloat64;
+
+  // types/varints/varint32
+
+  /**
+   * Maximum number of bytes required to store a 32bit base 128 variable-length integer.
+   * @type {number}
+   * @const
+   * @expose
+   */
+  ByteBuffer.MAX_VARINT32_BYTES = 5;
+
+  /**
+   * Calculates the actual number of bytes required to store a 32bit base 128 variable-length integer.
+   * @param {number} value Value to encode
+   * @returns {number} Number of bytes required. Capped to {@link ByteBuffer.MAX_VARINT32_BYTES}
+   * @expose
+   */
+  ByteBuffer.calculateVarint32 = function (value) {
+    // ref: src/google/protobuf/io/coded_stream.cc
+    value = value >>> 0;
+    if (value < 1 << 7) return 1;
+    else if (value < 1 << 14) return 2;
+    else if (value < 1 << 21) return 3;
+    else if (value < 1 << 28) return 4;
+    else return 5;
+  };
+
+  /**
+   * Zigzag encodes a signed 32bit integer so that it can be effectively used with varint encoding.
+   * @param {number} n Signed 32bit integer
+   * @returns {number} Unsigned zigzag encoded 32bit integer
+   * @expose
+   */
+  ByteBuffer.zigZagEncode32 = function (n) {
+    return (((n |= 0) << 1) ^ (n >> 31)) >>> 0; // ref: src/google/protobuf/wire_format_lite.h
+  };
+
+  /**
+   * Decodes a zigzag encoded signed 32bit integer.
+   * @param {number} n Unsigned zigzag encoded 32bit integer
+   * @returns {number} Signed 32bit integer
+   * @expose
+   */
+  ByteBuffer.zigZagDecode32 = function (n) {
+    return ((n >>> 1) ^ -(n & 1)) | 0; // // ref: src/google/protobuf/wire_format_lite.h
+  };
+
+  /**
+   * Writes a 32bit base 128 variable-length integer.
+   * @param {number} value Value to write
+   * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+   *  written if omitted.
+   * @returns {!ByteBuffer|number} this if `offset` is omitted, else the actual number of bytes written
+   * @expose
+   */
+  ByteBufferPrototype.writeVarint32 = function (value, offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof value !== 'number' || value % 1 !== 0)
+        throw TypeError("Illegal value: " + value + " (not an integer)");
+      value |= 0;
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 0 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 0 + ") <= " + this.buffer.byteLength);
+    }
+    var size = ByteBuffer.calculateVarint32(value),
+      b;
+    offset += size;
+    var capacity10 = this.buffer.byteLength;
+    if (offset > capacity10)
+      this.resize((capacity10 *= 2) > offset ? capacity10 : offset);
+    offset -= size;
+    value >>>= 0;
+    while (value >= 0x80) {
+      b = (value & 0x7f) | 0x80;
+      this.view[offset++] = b;
+      value >>>= 7;
+    }
+    this.view[offset++] = value;
+    if (relative) {
+      this.offset = offset;
+      return this;
+    }
+    return size;
+  };
+
+  /**
+   * Writes a zig-zag encoded (signed) 32bit base 128 variable-length integer.
+   * @param {number} value Value to write
+   * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+   *  written if omitted.
+   * @returns {!ByteBuffer|number} this if `offset` is omitted, else the actual number of bytes written
+   * @expose
+   */
+  ByteBufferPrototype.writeVarint32ZigZag = function (value, offset) {
+    return this.writeVarint32(ByteBuffer.zigZagEncode32(value), offset);
+  };
+
+  /**
+   * Reads a 32bit base 128 variable-length integer.
+   * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+   *  written if omitted.
+   * @returns {number|!{value: number, length: number}} The value read if offset is omitted, else the value read
+   *  and the actual number of bytes read.
+   * @throws {Error} If it's not a valid varint. Has a property `truncated = true` if there is not enough data available
+   *  to fully decode the varint.
+   * @expose
+   */
+  ByteBufferPrototype.readVarint32 = function (offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 1 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 1 + ") <= " + this.buffer.byteLength);
+    }
+    var c = 0,
+      value = 0 >>> 0,
+      b;
+    do {
+      if (!this.noAssert && offset > this.limit) {
+        var err = Error("Truncated");
+        err['truncated'] = true;
+        throw err;
+      }
+      b = this.view[offset++];
+      if (c < 5)
+        value |= (b & 0x7f) << (7 * c);
+      ++c;
+    } while ((b & 0x80) !== 0);
+    value |= 0;
+    if (relative) {
+      this.offset = offset;
+      return value;
+    }
+    return {
+      "value": value,
+      "length": c
+    };
+  };
+
+  /**
+   * Reads a zig-zag encoded (signed) 32bit base 128 variable-length integer.
+   * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+   *  written if omitted.
+   * @returns {number|!{value: number, length: number}} The value read if offset is omitted, else the value read
+   *  and the actual number of bytes read.
+   * @throws {Error} If it's not a valid varint
+   * @expose
+   */
+  ByteBufferPrototype.readVarint32ZigZag = function (offset) {
+    var val = this.readVarint32(offset);
+    if (typeof val === 'object')
+      val["value"] = ByteBuffer.zigZagDecode32(val["value"]);
+    else
+      val = ByteBuffer.zigZagDecode32(val);
+    return val;
+  };
+
+  // types/varints/varint64
+
+  if (Long) {
+
+    /**
+     * Maximum number of bytes required to store a 64bit base 128 variable-length integer.
+     * @type {number}
+     * @const
+     * @expose
+     */
+    ByteBuffer.MAX_VARINT64_BYTES = 10;
+
+    /**
+     * Calculates the actual number of bytes required to store a 64bit base 128 variable-length integer.
+     * @param {number|!Long} value Value to encode
+     * @returns {number} Number of bytes required. Capped to {@link ByteBuffer.MAX_VARINT64_BYTES}
+     * @expose
+     */
+    ByteBuffer.calculateVarint64 = function (value) {
+      if (typeof value === 'number')
+        value = Long.fromNumber(value);
+      else if (typeof value === 'string')
+        value = Long.fromString(value);
+      // ref: src/google/protobuf/io/coded_stream.cc
+      var part0 = value.toInt() >>> 0,
+        part1 = value.shiftRightUnsigned(28).toInt() >>> 0,
+        part2 = value.shiftRightUnsigned(56).toInt() >>> 0;
+      if (part2 == 0) {
+        if (part1 == 0) {
+          if (part0 < 1 << 14)
+            return part0 < 1 << 7 ? 1 : 2;
+          else
+            return part0 < 1 << 21 ? 3 : 4;
+        } else {
+          if (part1 < 1 << 14)
+            return part1 < 1 << 7 ? 5 : 6;
+          else
+            return part1 < 1 << 21 ? 7 : 8;
+        }
+      } else
+        return part2 < 1 << 7 ? 9 : 10;
+    };
+
+    /**
+     * Zigzag encodes a signed 64bit integer so that it can be effectively used with varint encoding.
+     * @param {number|!Long} value Signed long
+     * @returns {!Long} Unsigned zigzag encoded long
+     * @expose
+     */
+    ByteBuffer.zigZagEncode64 = function (value) {
+      if (typeof value === 'number')
+        value = Long.fromNumber(value, false);
+      else if (typeof value === 'string')
+        value = Long.fromString(value, false);
+      else if (value.unsigned !== false) value = value.toSigned();
+      // ref: src/google/protobuf/wire_format_lite.h
+      return value.shiftLeft(1).xor(value.shiftRight(63)).toUnsigned();
+    };
+
+    /**
+     * Decodes a zigzag encoded signed 64bit integer.
+     * @param {!Long|number} value Unsigned zigzag encoded long or JavaScript number
+     * @returns {!Long} Signed long
+     * @expose
+     */
+    ByteBuffer.zigZagDecode64 = function (value) {
+      if (typeof value === 'number')
+        value = Long.fromNumber(value, false);
+      else if (typeof value === 'string')
+        value = Long.fromString(value, false);
+      else if (value.unsigned !== false) value = value.toSigned();
+      // ref: src/google/protobuf/wire_format_lite.h
+      return value.shiftRightUnsigned(1).xor(value.and(Long.ONE).toSigned().negate()).toSigned();
+    };
+
+    /**
+     * Writes a 64bit base 128 variable-length integer.
+     * @param {number|Long} value Value to write
+     * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+     *  written if omitted.
+     * @returns {!ByteBuffer|number} `this` if offset is omitted, else the actual number of bytes written.
+     * @expose
+     */
+    ByteBufferPrototype.writeVarint64 = function (value, offset) {
+      var relative = typeof offset === 'undefined';
+      if (relative) offset = this.offset;
+      if (!this.noAssert) {
+        if (typeof value === 'number')
+          value = Long.fromNumber(value);
+        else if (typeof value === 'string')
+          value = Long.fromString(value);
+        else if (!(value && value instanceof Long))
+          throw TypeError("Illegal value: " + value + " (not an integer or Long)");
+        if (typeof offset !== 'number' || offset % 1 !== 0)
+          throw TypeError("Illegal offset: " + offset + " (not an integer)");
+        offset >>>= 0;
+        if (offset < 0 || offset + 0 > this.buffer.byteLength)
+          throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 0 + ") <= " + this.buffer.byteLength);
+      }
+      if (typeof value === 'number')
+        value = Long.fromNumber(value, false);
+      else if (typeof value === 'string')
+        value = Long.fromString(value, false);
+      else if (value.unsigned !== false) value = value.toSigned();
+      var size = ByteBuffer.calculateVarint64(value),
+        part0 = value.toInt() >>> 0,
+        part1 = value.shiftRightUnsigned(28).toInt() >>> 0,
+        part2 = value.shiftRightUnsigned(56).toInt() >>> 0;
+      offset += size;
+      var capacity11 = this.buffer.byteLength;
+      if (offset > capacity11)
+        this.resize((capacity11 *= 2) > offset ? capacity11 : offset);
+      offset -= size;
+      switch (size) {
+      case 10:
+        this.view[offset + 9] = (part2 >>> 7) & 0x01;
+      case 9:
+        this.view[offset + 8] = size !== 9 ? (part2) | 0x80 : (part2) & 0x7F;
+      case 8:
+        this.view[offset + 7] = size !== 8 ? (part1 >>> 21) | 0x80 : (part1 >>> 21) & 0x7F;
+      case 7:
+        this.view[offset + 6] = size !== 7 ? (part1 >>> 14) | 0x80 : (part1 >>> 14) & 0x7F;
+      case 6:
+        this.view[offset + 5] = size !== 6 ? (part1 >>> 7) | 0x80 : (part1 >>> 7) & 0x7F;
+      case 5:
+        this.view[offset + 4] = size !== 5 ? (part1) | 0x80 : (part1) & 0x7F;
+      case 4:
+        this.view[offset + 3] = size !== 4 ? (part0 >>> 21) | 0x80 : (part0 >>> 21) & 0x7F;
+      case 3:
+        this.view[offset + 2] = size !== 3 ? (part0 >>> 14) | 0x80 : (part0 >>> 14) & 0x7F;
+      case 2:
+        this.view[offset + 1] = size !== 2 ? (part0 >>> 7) | 0x80 : (part0 >>> 7) & 0x7F;
+      case 1:
+        this.view[offset] = size !== 1 ? (part0) | 0x80 : (part0) & 0x7F;
+      }
+      if (relative) {
+        this.offset += size;
+        return this;
+      } else {
+        return size;
+      }
+    };
+
+    /**
+     * Writes a zig-zag encoded 64bit base 128 variable-length integer.
+     * @param {number|Long} value Value to write
+     * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+     *  written if omitted.
+     * @returns {!ByteBuffer|number} `this` if offset is omitted, else the actual number of bytes written.
+     * @expose
+     */
+    ByteBufferPrototype.writeVarint64ZigZag = function (value, offset) {
+      return this.writeVarint64(ByteBuffer.zigZagEncode64(value), offset);
+    };
+
+    /**
+     * Reads a 64bit base 128 variable-length integer. Requires Long.js.
+     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+     *  read if omitted.
+     * @returns {!Long|!{value: Long, length: number}} The value read if offset is omitted, else the value read and
+     *  the actual number of bytes read.
+     * @throws {Error} If it's not a valid varint
+     * @expose
+     */
+    ByteBufferPrototype.readVarint64 = function (offset) {
+      var relative = typeof offset === 'undefined';
+      if (relative) offset = this.offset;
+      if (!this.noAssert) {
+        if (typeof offset !== 'number' || offset % 1 !== 0)
+          throw TypeError("Illegal offset: " + offset + " (not an integer)");
+        offset >>>= 0;
+        if (offset < 0 || offset + 1 > this.buffer.byteLength)
+          throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 1 + ") <= " + this.buffer.byteLength);
+      }
+      // ref: src/google/protobuf/io/coded_stream.cc
+      var start = offset,
+        part0 = 0,
+        part1 = 0,
+        part2 = 0,
+        b = 0;
+      b = this.view[offset++];
+      part0 = (b & 0x7F);
+      if (b & 0x80) {
+        b = this.view[offset++];
+        part0 |= (b & 0x7F) << 7;
+        if ((b & 0x80) || (this.noAssert && typeof b === 'undefined')) {
+          b = this.view[offset++];
+          part0 |= (b & 0x7F) << 14;
+          if ((b & 0x80) || (this.noAssert && typeof b === 'undefined')) {
+            b = this.view[offset++];
+            part0 |= (b & 0x7F) << 21;
+            if ((b & 0x80) || (this.noAssert && typeof b === 'undefined')) {
+              b = this.view[offset++];
+              part1 = (b & 0x7F);
+              if ((b & 0x80) || (this.noAssert && typeof b === 'undefined')) {
+                b = this.view[offset++];
+                part1 |= (b & 0x7F) << 7;
+                if ((b & 0x80) || (this.noAssert && typeof b === 'undefined')) {
+                  b = this.view[offset++];
+                  part1 |= (b & 0x7F) << 14;
+                  if ((b & 0x80) || (this.noAssert && typeof b === 'undefined')) {
+                    b = this.view[offset++];
+                    part1 |= (b & 0x7F) << 21;
+                    if ((b & 0x80) || (this.noAssert && typeof b === 'undefined')) {
+                      b = this.view[offset++];
+                      part2 = (b & 0x7F);
+                      if ((b & 0x80) || (this.noAssert && typeof b === 'undefined')) {
+                        b = this.view[offset++];
+                        part2 |= (b & 0x7F) << 7;
+                        if ((b & 0x80) || (this.noAssert && typeof b === 'undefined')) {
+                          throw Error("Buffer overrun");
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+      var value = Long.fromBits(part0 | (part1 << 28), (part1 >>> 4) | (part2) << 24, false);
+      if (relative) {
+        this.offset = offset;
+        return value;
+      } else {
+        return {
+          'value': value,
+          'length': offset - start
+        };
+      }
+    };
+
+    /**
+     * Reads a zig-zag encoded 64bit base 128 variable-length integer. Requires Long.js.
+     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+     *  read if omitted.
+     * @returns {!Long|!{value: Long, length: number}} The value read if offset is omitted, else the value read and
+     *  the actual number of bytes read.
+     * @throws {Error} If it's not a valid varint
+     * @expose
+     */
+    ByteBufferPrototype.readVarint64ZigZag = function (offset) {
+      var val = this.readVarint64(offset);
+      if (val && val['value'] instanceof Long)
+        val["value"] = ByteBuffer.zigZagDecode64(val["value"]);
+      else
+        val = ByteBuffer.zigZagDecode64(val);
+      return val;
+    };
+
+  } // Long
+
+  // types/strings/cstring
+
+  /**
+   * Writes a NULL-terminated UTF8 encoded string. For this to work the specified string must not contain any NULL
+   *  characters itself.
+   * @param {string} str String to write
+   * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+   *  contained in `str` + 1 if omitted.
+   * @returns {!ByteBuffer|number} this if offset is omitted, else the actual number of bytes written
+   * @expose
+   */
+  ByteBufferPrototype.writeCString = function (str, offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    var i,
+      k = str.length;
+    if (!this.noAssert) {
+      if (typeof str !== 'string')
+        throw TypeError("Illegal str: Not a string");
+      for (i = 0; i < k; ++i) {
+        if (str.charCodeAt(i) === 0)
+          throw RangeError("Illegal str: Contains NULL-characters");
+      }
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 0 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 0 + ") <= " + this.buffer.byteLength);
+    }
+    // UTF8 strings do not contain zero bytes in between except for the zero character, so:
+    k = utfx.calculateUTF16asUTF8(stringSource(str))[1];
+    offset += k + 1;
+    var capacity12 = this.buffer.byteLength;
+    if (offset > capacity12)
+      this.resize((capacity12 *= 2) > offset ? capacity12 : offset);
+    offset -= k + 1;
+    utfx.encodeUTF16toUTF8(stringSource(str), function (b) {
+      this.view[offset++] = b;
+    }.bind(this));
+    this.view[offset++] = 0;
+    if (relative) {
+      this.offset = offset;
+      return this;
+    }
+    return k;
+  };
+
+  /**
+   * Reads a NULL-terminated UTF8 encoded string. For this to work the string read must not contain any NULL characters
+   *  itself.
+   * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+   *  read if omitted.
+   * @returns {string|!{string: string, length: number}} The string read if offset is omitted, else the string
+   *  read and the actual number of bytes read.
+   * @expose
+   */
+  ByteBufferPrototype.readCString = function (offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 1 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 1 + ") <= " + this.buffer.byteLength);
+    }
+    var start = offset,
+      temp;
+    // UTF8 strings do not contain zero bytes in between except for the zero character itself, so:
+    var sd, b = -1;
+    utfx.decodeUTF8toUTF16(function () {
+      if (b === 0) return null;
+      if (offset >= this.limit)
+        throw RangeError("Illegal range: Truncated data, " + offset + " < " + this.limit);
+      b = this.view[offset++];
+      return b === 0 ? null : b;
+    }.bind(this), sd = stringDestination(), true);
+    if (relative) {
+      this.offset = offset;
+      return sd();
+    } else {
+      return {
+        "string": sd(),
+        "length": offset - start
+      };
+    }
+  };
+
+  // types/strings/istring
+
+  /**
+   * Writes a length as uint32 prefixed UTF8 encoded string.
+   * @param {string} str String to write
+   * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+   *  written if omitted.
+   * @returns {!ByteBuffer|number} `this` if `offset` is omitted, else the actual number of bytes written
+   * @expose
+   * @see ByteBuffer#writeVarint32
+   */
+  ByteBufferPrototype.writeIString = function (str, offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof str !== 'string')
+        throw TypeError("Illegal str: Not a string");
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 0 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 0 + ") <= " + this.buffer.byteLength);
+    }
+    var start = offset,
+      k;
+    k = utfx.calculateUTF16asUTF8(stringSource(str), this.noAssert)[1];
+    offset += 4 + k;
+    var capacity13 = this.buffer.byteLength;
+    if (offset > capacity13)
+      this.resize((capacity13 *= 2) > offset ? capacity13 : offset);
+    offset -= 4 + k;
+    if (this.littleEndian) {
+      this.view[offset + 3] = (k >>> 24) & 0xFF;
+      this.view[offset + 2] = (k >>> 16) & 0xFF;
+      this.view[offset + 1] = (k >>> 8) & 0xFF;
+      this.view[offset] = k & 0xFF;
+    } else {
+      this.view[offset] = (k >>> 24) & 0xFF;
+      this.view[offset + 1] = (k >>> 16) & 0xFF;
+      this.view[offset + 2] = (k >>> 8) & 0xFF;
+      this.view[offset + 3] = k & 0xFF;
+    }
+    offset += 4;
+    utfx.encodeUTF16toUTF8(stringSource(str), function (b) {
+      this.view[offset++] = b;
+    }.bind(this));
+    if (offset !== start + 4 + k)
+      throw RangeError("Illegal range: Truncated data, " + offset + " == " + (offset + 4 + k));
+    if (relative) {
+      this.offset = offset;
+      return this;
+    }
+    return offset - start;
+  };
+
+  /**
+   * Reads a length as uint32 prefixed UTF8 encoded string.
+   * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+   *  read if omitted.
+   * @returns {string|!{string: string, length: number}} The string read if offset is omitted, else the string
+   *  read and the actual number of bytes read.
+   * @expose
+   * @see ByteBuffer#readVarint32
+   */
+  ByteBufferPrototype.readIString = function (offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 4 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 4 + ") <= " + this.buffer.byteLength);
+    }
+    var temp = 0,
+      start = offset,
+      str;
+    if (this.littleEndian) {
+      temp = this.view[offset + 2] << 16;
+      temp |= this.view[offset + 1] << 8;
+      temp |= this.view[offset];
+      temp += this.view[offset + 3] << 24 >>> 0;
+    } else {
+      temp = this.view[offset + 1] << 16;
+      temp |= this.view[offset + 2] << 8;
+      temp |= this.view[offset + 3];
+      temp += this.view[offset] << 24 >>> 0;
+    }
+    offset += 4;
+    var k = offset + temp,
+      sd;
+    utfx.decodeUTF8toUTF16(function () {
+      return offset < k ? this.view[offset++] : null;
+    }.bind(this), sd = stringDestination(), this.noAssert);
+    str = sd();
+    if (relative) {
+      this.offset = offset;
+      return str;
+    } else {
+      return {
+        'string': str,
+        'length': offset - start
+      };
+    }
+  };
+
+  // types/strings/utf8string
+
+  /**
+   * Metrics representing number of UTF8 characters. Evaluates to `c`.
+   * @type {string}
+   * @const
+   * @expose
+   */
+  ByteBuffer.METRICS_CHARS = 'c';
+
+  /**
+   * Metrics representing number of bytes. Evaluates to `b`.
+   * @type {string}
+   * @const
+   * @expose
+   */
+  ByteBuffer.METRICS_BYTES = 'b';
+
+  /**
+   * Writes an UTF8 encoded string.
+   * @param {string} str String to write
+   * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} if omitted.
+   * @returns {!ByteBuffer|number} this if offset is omitted, else the actual number of bytes written.
+   * @expose
+   */
+  ByteBufferPrototype.writeUTF8String = function (str, offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 0 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 0 + ") <= " + this.buffer.byteLength);
+    }
+    var k;
+    var start = offset;
+    k = utfx.calculateUTF16asUTF8(stringSource(str))[1];
+    offset += k;
+    var capacity14 = this.buffer.byteLength;
+    if (offset > capacity14)
+      this.resize((capacity14 *= 2) > offset ? capacity14 : offset);
+    offset -= k;
+    utfx.encodeUTF16toUTF8(stringSource(str), function (b) {
+      this.view[offset++] = b;
+    }.bind(this));
+    if (relative) {
+      this.offset = offset;
+      return this;
+    }
+    return offset - start;
+  };
+
+  /**
+   * Writes an UTF8 encoded string. This is an alias of {@link ByteBuffer#writeUTF8String}.
+   * @function
+   * @param {string} str String to write
+   * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} if omitted.
+   * @returns {!ByteBuffer|number} this if offset is omitted, else the actual number of bytes written.
+   * @expose
+   */
+  ByteBufferPrototype.writeString = ByteBufferPrototype.writeUTF8String;
+
+  /**
+   * Calculates the number of UTF8 characters of a string. JavaScript itself uses UTF-16, so that a string's
+   *  `length` property does not reflect its actual UTF8 size if it contains code points larger than 0xFFFF.
+   * @param {string} str String to calculate
+   * @returns {number} Number of UTF8 characters
+   * @expose
+   */
+  ByteBuffer.calculateUTF8Chars = function (str) {
+    return utfx.calculateUTF16asUTF8(stringSource(str))[0];
+  };
+
+  /**
+   * Calculates the number of UTF8 bytes of a string.
+   * @param {string} str String to calculate
+   * @returns {number} Number of UTF8 bytes
+   * @expose
+   */
+  ByteBuffer.calculateUTF8Bytes = function (str) {
+    return utfx.calculateUTF16asUTF8(stringSource(str))[1];
+  };
+
+  /**
+   * Calculates the number of UTF8 bytes of a string. This is an alias of {@link ByteBuffer.calculateUTF8Bytes}.
+   * @function
+   * @param {string} str String to calculate
+   * @returns {number} Number of UTF8 bytes
+   * @expose
+   */
+  ByteBuffer.calculateString = ByteBuffer.calculateUTF8Bytes;
+
+  /**
+   * Reads an UTF8 encoded string.
+   * @param {number} length Number of characters or bytes to read.
+   * @param {string=} metrics Metrics specifying what `length` is meant to count. Defaults to
+   *  {@link ByteBuffer.METRICS_CHARS}.
+   * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+   *  read if omitted.
+   * @returns {string|!{string: string, length: number}} The string read if offset is omitted, else the string
+   *  read and the actual number of bytes read.
+   * @expose
+   */
+  ByteBufferPrototype.readUTF8String = function (length, metrics, offset) {
+    if (typeof metrics === 'number') {
+      offset = metrics;
+      metrics = undefined;
+    }
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (typeof metrics === 'undefined') metrics = ByteBuffer.METRICS_CHARS;
+    if (!this.noAssert) {
+      if (typeof length !== 'number' || length % 1 !== 0)
+        throw TypeError("Illegal length: " + length + " (not an integer)");
+      length |= 0;
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 0 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 0 + ") <= " + this.buffer.byteLength);
+    }
+    var i = 0,
+      start = offset,
+      sd;
+    if (metrics === ByteBuffer.METRICS_CHARS) { // The same for node and the browser
+      sd = stringDestination();
+      utfx.decodeUTF8(function () {
+        return i < length && offset < this.limit ? this.view[offset++] : null;
+      }.bind(this), function (cp) {
+        ++i;
+        utfx.UTF8toUTF16(cp, sd);
+      });
+      if (i !== length)
+        throw RangeError("Illegal range: Truncated data, " + i + " == " + length);
+      if (relative) {
+        this.offset = offset;
+        return sd();
+      } else {
+        return {
+          "string": sd(),
+          "length": offset - start
+        };
+      }
+    } else if (metrics === ByteBuffer.METRICS_BYTES) {
+      if (!this.noAssert) {
+        if (typeof offset !== 'number' || offset % 1 !== 0)
+          throw TypeError("Illegal offset: " + offset + " (not an integer)");
+        offset >>>= 0;
+        if (offset < 0 || offset + length > this.buffer.byteLength)
+          throw RangeError("Illegal offset: 0 <= " + offset + " (+" + length + ") <= " + this.buffer.byteLength);
+      }
+      var k = offset + length;
+      utfx.decodeUTF8toUTF16(function () {
+        return offset < k ? this.view[offset++] : null;
+      }.bind(this), sd = stringDestination(), this.noAssert);
+      if (offset !== k)
+        throw RangeError("Illegal range: Truncated data, " + offset + " == " + k);
+      if (relative) {
+        this.offset = offset;
+        return sd();
+      } else {
+        return {
+          'string': sd(),
+          'length': offset - start
+        };
+      }
+    } else
+      throw TypeError("Unsupported metrics: " + metrics);
+  };
+
+  /**
+   * Reads an UTF8 encoded string. This is an alias of {@link ByteBuffer#readUTF8String}.
+   * @function
+   * @param {number} length Number of characters or bytes to read
+   * @param {number=} metrics Metrics specifying what `n` is meant to count. Defaults to
+   *  {@link ByteBuffer.METRICS_CHARS}.
+   * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+   *  read if omitted.
+   * @returns {string|!{string: string, length: number}} The string read if offset is omitted, else the string
+   *  read and the actual number of bytes read.
+   * @expose
+   */
+  ByteBufferPrototype.readString = ByteBufferPrototype.readUTF8String;
+
+  // types/strings/vstring
+
+  /**
+   * Writes a length as varint32 prefixed UTF8 encoded string.
+   * @param {string} str String to write
+   * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+   *  written if omitted.
+   * @returns {!ByteBuffer|number} `this` if `offset` is omitted, else the actual number of bytes written
+   * @expose
+   * @see ByteBuffer#writeVarint32
+   */
+  ByteBufferPrototype.writeVString = function (str, offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof str !== 'string')
+        throw TypeError("Illegal str: Not a string");
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 0 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 0 + ") <= " + this.buffer.byteLength);
+    }
+    var start = offset,
+      k, l;
+    k = utfx.calculateUTF16asUTF8(stringSource(str), this.noAssert)[1];
+    l = ByteBuffer.calculateVarint32(k);
+    offset += l + k;
+    var capacity15 = this.buffer.byteLength;
+    if (offset > capacity15)
+      this.resize((capacity15 *= 2) > offset ? capacity15 : offset);
+    offset -= l + k;
+    offset += this.writeVarint32(k, offset);
+    utfx.encodeUTF16toUTF8(stringSource(str), function (b) {
+      this.view[offset++] = b;
+    }.bind(this));
+    if (offset !== start + k + l)
+      throw RangeError("Illegal range: Truncated data, " + offset + " == " + (offset + k + l));
+    if (relative) {
+      this.offset = offset;
+      return this;
+    }
+    return offset - start;
+  };
+
+  /**
+   * Reads a length as varint32 prefixed UTF8 encoded string.
+   * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+   *  read if omitted.
+   * @returns {string|!{string: string, length: number}} The string read if offset is omitted, else the string
+   *  read and the actual number of bytes read.
+   * @expose
+   * @see ByteBuffer#readVarint32
+   */
+  ByteBufferPrototype.readVString = function (offset) {
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 1 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 1 + ") <= " + this.buffer.byteLength);
+    }
+    var temp = this.readVarint32(offset),
+      start = offset,
+      str;
+    offset += temp['length'];
+    temp = temp['value'];
+    var k = offset + temp,
+      sd = stringDestination();
+    utfx.decodeUTF8toUTF16(function () {
+      return offset < k ? this.view[offset++] : null;
+    }.bind(this), sd, this.noAssert);
+    str = sd();
+    if (relative) {
+      this.offset = offset;
+      return str;
+    } else {
+      return {
+        'string': str,
+        'length': offset - start
+      };
+    }
+  };
+
+  /**
+   * Appends some data to this ByteBuffer. This will overwrite any contents behind the specified offset up to the appended
+   *  data's length.
+   * @param {!ByteBuffer|!ArrayBuffer|!Uint8Array|string} source Data to append. If `source` is a ByteBuffer, its offsets
+   *  will be modified according to the performed read operation.
+   * @param {(string|number)=} encoding Encoding if `data` is a string ("base64", "hex", "binary", defaults to "utf8")
+   * @param {number=} offset Offset to append at. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+   *  written if omitted.
+   * @returns {!ByteBuffer} this
+   * @expose
+   * @example A relative `<01 02>03.append(<04 05>)` will result in `<01 02 04 05>, 04 05|`
+   * @example An absolute `<01 02>03.append(04 05>, 1)` will result in `<01 04>05, 04 05|`
+   */
+  ByteBufferPrototype.append = function (source, encoding, offset) {
+    if (typeof encoding === 'number' || typeof encoding !== 'string') {
+      offset = encoding;
+      encoding = undefined;
+    }
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 0 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 0 + ") <= " + this.buffer.byteLength);
+    }
+    if (!(source instanceof ByteBuffer))
+      source = ByteBuffer.wrap(source, encoding);
+    var length = source.limit - source.offset;
+    if (length <= 0) return this; // Nothing to append
+    offset += length;
+    var capacity16 = this.buffer.byteLength;
+    if (offset > capacity16)
+      this.resize((capacity16 *= 2) > offset ? capacity16 : offset);
+    offset -= length;
+    this.view.set(source.view.subarray(source.offset, source.limit), offset);
+    source.offset += length;
+    if (relative) this.offset += length;
+    return this;
+  };
+
+  /**
+   * Appends this ByteBuffer's contents to another ByteBuffer. This will overwrite any contents at and after the
+      specified offset up to the length of this ByteBuffer's data.
+   * @param {!ByteBuffer} target Target ByteBuffer
+   * @param {number=} offset Offset to append to. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+   *  read if omitted.
+   * @returns {!ByteBuffer} this
+   * @expose
+   * @see ByteBuffer#append
+   */
+  ByteBufferPrototype.appendTo = function (target, offset) {
+    target.append(this, offset);
+    return this;
+  };
+
+  /**
+   * Enables or disables assertions of argument types and offsets. Assertions are enabled by default but you can opt to
+   *  disable them if your code already makes sure that everything is valid.
+   * @param {boolean} assert `true` to enable assertions, otherwise `false`
+   * @returns {!ByteBuffer} this
+   * @expose
+   */
+  ByteBufferPrototype.assert = function (assert) {
+    this.noAssert = !assert;
+    return this;
+  };
+
+  /**
+   * Gets the capacity of this ByteBuffer's backing buffer.
+   * @returns {number} Capacity of the backing buffer
+   * @expose
+   */
+  ByteBufferPrototype.capacity = function () {
+    return this.buffer.byteLength;
+  };
+  /**
+   * Clears this ByteBuffer's offsets by setting {@link ByteBuffer#offset} to `0` and {@link ByteBuffer#limit} to the
+   *  backing buffer's capacity. Discards {@link ByteBuffer#markedOffset}.
+   * @returns {!ByteBuffer} this
+   * @expose
+   */
+  ByteBufferPrototype.clear = function () {
+    this.offset = 0;
+    this.limit = this.buffer.byteLength;
+    this.markedOffset = -1;
+    return this;
+  };
+
+  /**
+   * Creates a cloned instance of this ByteBuffer, preset with this ByteBuffer's values for {@link ByteBuffer#offset},
+   *  {@link ByteBuffer#markedOffset} and {@link ByteBuffer#limit}.
+   * @param {boolean=} copy Whether to copy the backing buffer or to return another view on the same, defaults to `false`
+   * @returns {!ByteBuffer} Cloned instance
+   * @expose
+   */
+  ByteBufferPrototype.clone = function (copy) {
+    var bb = new ByteBuffer(0, this.littleEndian, this.noAssert);
+    if (copy) {
+      bb.buffer = new ArrayBuffer(this.buffer.byteLength);
+      bb.view = new Uint8Array(bb.buffer);
+    } else {
+      bb.buffer = this.buffer;
+      bb.view = this.view;
+    }
+    bb.offset = this.offset;
+    bb.markedOffset = this.markedOffset;
+    bb.limit = this.limit;
+    return bb;
+  };
+
+  /**
+   * Compacts this ByteBuffer to be backed by a {@link ByteBuffer#buffer} of its contents' length. Contents are the bytes
+   *  between {@link ByteBuffer#offset} and {@link ByteBuffer#limit}. Will set `offset = 0` and `limit = capacity` and
+   *  adapt {@link ByteBuffer#markedOffset} to the same relative position if set.
+   * @param {number=} begin Offset to start at, defaults to {@link ByteBuffer#offset}
+   * @param {number=} end Offset to end at, defaults to {@link ByteBuffer#limit}
+   * @returns {!ByteBuffer} this
+   * @expose
+   */
+  ByteBufferPrototype.compact = function (begin, end) {
+    if (typeof begin === 'undefined') begin = this.offset;
+    if (typeof end === 'undefined') end = this.limit;
+    if (!this.noAssert) {
+      if (typeof begin !== 'number' || begin % 1 !== 0)
+        throw TypeError("Illegal begin: Not an integer");
+      begin >>>= 0;
+      if (typeof end !== 'number' || end % 1 !== 0)
+        throw TypeError("Illegal end: Not an integer");
+      end >>>= 0;
+      if (begin < 0 || begin > end || end > this.buffer.byteLength)
+        throw RangeError("Illegal range: 0 <= " + begin + " <= " + end + " <= " + this.buffer.byteLength);
+    }
+    if (begin === 0 && end === this.buffer.byteLength)
+      return this; // Already compacted
+    var len = end - begin;
+    if (len === 0) {
+      this.buffer = EMPTY_BUFFER;
+      this.view = null;
+      if (this.markedOffset >= 0) this.markedOffset -= begin;
+      this.offset = 0;
+      this.limit = 0;
+      return this;
+    }
+    var buffer = new ArrayBuffer(len);
+    var view = new Uint8Array(buffer);
+    view.set(this.view.subarray(begin, end));
+    this.buffer = buffer;
+    this.view = view;
+    if (this.markedOffset >= 0) this.markedOffset -= begin;
+    this.offset = 0;
+    this.limit = len;
+    return this;
+  };
+
+  /**
+   * Creates a copy of this ByteBuffer's contents. Contents are the bytes between {@link ByteBuffer#offset} and
+   *  {@link ByteBuffer#limit}.
+   * @param {number=} begin Begin offset, defaults to {@link ByteBuffer#offset}.
+   * @param {number=} end End offset, defaults to {@link ByteBuffer#limit}.
+   * @returns {!ByteBuffer} Copy
+   * @expose
+   */
+  ByteBufferPrototype.copy = function (begin, end) {
+    if (typeof begin === 'undefined') begin = this.offset;
+    if (typeof end === 'undefined') end = this.limit;
+    if (!this.noAssert) {
+      if (typeof begin !== 'number' || begin % 1 !== 0)
+        throw TypeError("Illegal begin: Not an integer");
+      begin >>>= 0;
+      if (typeof end !== 'number' || end % 1 !== 0)
+        throw TypeError("Illegal end: Not an integer");
+      end >>>= 0;
+      if (begin < 0 || begin > end || end > this.buffer.byteLength)
+        throw RangeError("Illegal range: 0 <= " + begin + " <= " + end + " <= " + this.buffer.byteLength);
+    }
+    if (begin === end)
+      return new ByteBuffer(0, this.littleEndian, this.noAssert);
+    var capacity = end - begin,
+      bb = new ByteBuffer(capacity, this.littleEndian, this.noAssert);
+    bb.offset = 0;
+    bb.limit = capacity;
+    if (bb.markedOffset >= 0) bb.markedOffset -= begin;
+    this.copyTo(bb, 0, begin, end);
+    return bb;
+  };
+
+  /**
+   * Copies this ByteBuffer's contents to another ByteBuffer. Contents are the bytes between {@link ByteBuffer#offset} and
+   *  {@link ByteBuffer#limit}.
+   * @param {!ByteBuffer} target Target ByteBuffer
+   * @param {number=} targetOffset Offset to copy to. Will use and increase the target's {@link ByteBuffer#offset}
+   *  by the number of bytes copied if omitted.
+   * @param {number=} sourceOffset Offset to start copying from. Will use and increase {@link ByteBuffer#offset} by the
+   *  number of bytes copied if omitted.
+   * @param {number=} sourceLimit Offset to end copying from, defaults to {@link ByteBuffer#limit}
+   * @returns {!ByteBuffer} this
+   * @expose
+   */
+  ByteBufferPrototype.copyTo = function (target, targetOffset, sourceOffset, sourceLimit) {
+    var relative,
+      targetRelative;
+    if (!this.noAssert) {
+      if (!ByteBuffer.isByteBuffer(target))
+        throw TypeError("Illegal target: Not a ByteBuffer");
+    }
+    targetOffset = (targetRelative = typeof targetOffset === 'undefined') ? target.offset : targetOffset | 0;
+    sourceOffset = (relative = typeof sourceOffset === 'undefined') ? this.offset : sourceOffset | 0;
+    sourceLimit = typeof sourceLimit === 'undefined' ? this.limit : sourceLimit | 0;
+
+    if (targetOffset < 0 || targetOffset > target.buffer.byteLength)
+      throw RangeError("Illegal target range: 0 <= " + targetOffset + " <= " + target.buffer.byteLength);
+    if (sourceOffset < 0 || sourceLimit > this.buffer.byteLength)
+      throw RangeError("Illegal source range: 0 <= " + sourceOffset + " <= " + this.buffer.byteLength);
+
+    var len = sourceLimit - sourceOffset;
+    if (len === 0)
+      return target; // Nothing to copy
+
+    target.ensureCapacity(targetOffset + len);
+
+    target.view.set(this.view.subarray(sourceOffset, sourceLimit), targetOffset);
+
+    if (relative) this.offset += len;
+    if (targetRelative) target.offset += len;
+
+    return this;
+  };
+
+  /**
+   * Makes sure that this ByteBuffer is backed by a {@link ByteBuffer#buffer} of at least the specified capacity. If the
+   *  current capacity is exceeded, it will be doubled. If double the current capacity is less than the required capacity,
+   *  the required capacity will be used instead.
+   * @param {number} capacity Required capacity
+   * @returns {!ByteBuffer} this
+   * @expose
+   */
+  ByteBufferPrototype.ensureCapacity = function (capacity) {
+    var current = this.buffer.byteLength;
+    if (current < capacity)
+      return this.resize((current *= 2) > capacity ? current : capacity);
+    return this;
+  };
+
+  /**
+   * Overwrites this ByteBuffer's contents with the specified value. Contents are the bytes between
+   *  {@link ByteBuffer#offset} and {@link ByteBuffer#limit}.
+   * @param {number|string} value Byte value to fill with. If given as a string, the first character is used.
+   * @param {number=} begin Begin offset. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+   *  written if omitted. defaults to {@link ByteBuffer#offset}.
+   * @param {number=} end End offset, defaults to {@link ByteBuffer#limit}.
+   * @returns {!ByteBuffer} this
+   * @expose
+   * @example `someByteBuffer.clear().fill(0)` fills the entire backing buffer with zeroes
+   */
+  ByteBufferPrototype.fill = function (value, begin, end) {
+    var relative = typeof begin === 'undefined';
+    if (relative) begin = this.offset;
+    if (typeof value === 'string' && value.length > 0)
+      value = value.charCodeAt(0);
+    if (typeof begin === 'undefined') begin = this.offset;
+    if (typeof end === 'undefined') end = this.limit;
+    if (!this.noAssert) {
+      if (typeof value !== 'number' || value % 1 !== 0)
+        throw TypeError("Illegal value: " + value + " (not an integer)");
+      value |= 0;
+      if (typeof begin !== 'number' || begin % 1 !== 0)
+        throw TypeError("Illegal begin: Not an integer");
+      begin >>>= 0;
+      if (typeof end !== 'number' || end % 1 !== 0)
+        throw TypeError("Illegal end: Not an integer");
+      end >>>= 0;
+      if (begin < 0 || begin > end || end > this.buffer.byteLength)
+        throw RangeError("Illegal range: 0 <= " + begin + " <= " + end + " <= " + this.buffer.byteLength);
+    }
+    if (begin >= end)
+      return this; // Nothing to fill
+    while (begin < end) this.view[begin++] = value;
+    if (relative) this.offset = begin;
+    return this;
+  };
+
+  /**
+   * Makes this ByteBuffer ready for a new sequence of write or relative read operations. Sets `limit = offset` and
+   *  `offset = 0`. Make sure always to flip a ByteBuffer when all relative read or write operations are complete.
+   * @returns {!ByteBuffer} this
+   * @expose
+   */
+  ByteBufferPrototype.flip = function () {
+    this.limit = this.offset;
+    this.offset = 0;
+    return this;
+  };
+  /**
+   * Marks an offset on this ByteBuffer to be used later.
+   * @param {number=} offset Offset to mark. Defaults to {@link ByteBuffer#offset}.
+   * @returns {!ByteBuffer} this
+   * @throws {TypeError} If `offset` is not a valid number
+   * @throws {RangeError} If `offset` is out of bounds
+   * @see ByteBuffer#reset
+   * @expose
+   */
+  ByteBufferPrototype.mark = function (offset) {
+    offset = typeof offset === 'undefined' ? this.offset : offset;
+    if (!this.noAssert) {
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 0 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 0 + ") <= " + this.buffer.byteLength);
+    }
+    this.markedOffset = offset;
+    return this;
+  };
+  /**
+   * Sets the byte order.
+   * @param {boolean} littleEndian `true` for little endian byte order, `false` for big endian
+   * @returns {!ByteBuffer} this
+   * @expose
+   */
+  ByteBufferPrototype.order = function (littleEndian) {
+    if (!this.noAssert) {
+      if (typeof littleEndian !== 'boolean')
+        throw TypeError("Illegal littleEndian: Not a boolean");
+    }
+    this.littleEndian = !!littleEndian;
+    return this;
+  };
+
+  /**
+   * Switches (to) little endian byte order.
+   * @param {boolean=} littleEndian Defaults to `true`, otherwise uses big endian
+   * @returns {!ByteBuffer} this
+   * @expose
+   */
+  ByteBufferPrototype.LE = function (littleEndian) {
+    this.littleEndian = typeof littleEndian !== 'undefined' ? !!littleEndian : true;
+    return this;
+  };
+
+  /**
+   * Switches (to) big endian byte order.
+   * @param {boolean=} bigEndian Defaults to `true`, otherwise uses little endian
+   * @returns {!ByteBuffer} this
+   * @expose
+   */
+  ByteBufferPrototype.BE = function (bigEndian) {
+    this.littleEndian = typeof bigEndian !== 'undefined' ? !bigEndian : false;
+    return this;
+  };
+  /**
+   * Prepends some data to this ByteBuffer. This will overwrite any contents before the specified offset up to the
+   *  prepended data's length. If there is not enough space available before the specified `offset`, the backing buffer
+   *  will be resized and its contents moved accordingly.
+   * @param {!ByteBuffer|string|!ArrayBuffer} source Data to prepend. If `source` is a ByteBuffer, its offset will be
+   *  modified according to the performed read operation.
+   * @param {(string|number)=} encoding Encoding if `data` is a string ("base64", "hex", "binary", defaults to "utf8")
+   * @param {number=} offset Offset to prepend at. Will use and decrease {@link ByteBuffer#offset} by the number of bytes
+   *  prepended if omitted.
+   * @returns {!ByteBuffer} this
+   * @expose
+   * @example A relative `00<01 02 03>.prepend(<04 05>)` results in `<04 05 01 02 03>, 04 05|`
+   * @example An absolute `00<01 02 03>.prepend(<04 05>, 2)` results in `04<05 02 03>, 04 05|`
+   */
+  ByteBufferPrototype.prepend = function (source, encoding, offset) {
+    if (typeof encoding === 'number' || typeof encoding !== 'string') {
+      offset = encoding;
+      encoding = undefined;
+    }
+    var relative = typeof offset === 'undefined';
+    if (relative) offset = this.offset;
+    if (!this.noAssert) {
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: " + offset + " (not an integer)");
+      offset >>>= 0;
+      if (offset < 0 || offset + 0 > this.buffer.byteLength)
+        throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 0 + ") <= " + this.buffer.byteLength);
+    }
+    if (!(source instanceof ByteBuffer))
+      source = ByteBuffer.wrap(source, encoding);
+    var len = source.limit - source.offset;
+    if (len <= 0) return this; // Nothing to prepend
+    var diff = len - offset;
+    if (diff > 0) { // Not enough space before offset, so resize + move
+      var buffer = new ArrayBuffer(this.buffer.byteLength + diff);
+      var view = new Uint8Array(buffer);
+      view.set(this.view.subarray(offset, this.buffer.byteLength), len);
+      this.buffer = buffer;
+      this.view = view;
+      this.offset += diff;
+      if (this.markedOffset >= 0) this.markedOffset += diff;
+      this.limit += diff;
+      offset += diff;
+    } else {
+      var arrayView = new Uint8Array(this.buffer);
+    }
+    this.view.set(source.view.subarray(source.offset, source.limit), offset - len);
+
+    source.offset = source.limit;
+    if (relative)
+      this.offset -= len;
+    return this;
+  };
+
+  /**
+   * Prepends this ByteBuffer to another ByteBuffer. This will overwrite any contents before the specified offset up to the
+   *  prepended data's length. If there is not enough space available before the specified `offset`, the backing buffer
+   *  will be resized and its contents moved accordingly.
+   * @param {!ByteBuffer} target Target ByteBuffer
+   * @param {number=} offset Offset to prepend at. Will use and decrease {@link ByteBuffer#offset} by the number of bytes
+   *  prepended if omitted.
+   * @returns {!ByteBuffer} this
+   * @expose
+   * @see ByteBuffer#prepend
+   */
+  ByteBufferPrototype.prependTo = function (target, offset) {
+    target.prepend(this, offset);
+    return this;
+  };
+  /**
+   * Prints debug information about this ByteBuffer's contents.
+   * @param {function(string)=} out Output function to call, defaults to console.log
+   * @expose
+   */
+  ByteBufferPrototype.printDebug = function (out) {
+    if (typeof out !== 'function') out = console.log.bind(console);
+    out(
+      this.toString() + "\n" +
+      "-------------------------------------------------------------------\n" +
+      this.toDebug( /* columns */ true)
+    );
+  };
+
+  /**
+   * Gets the number of remaining readable bytes. Contents are the bytes between {@link ByteBuffer#offset} and
+   *  {@link ByteBuffer#limit}, so this returns `limit - offset`.
+   * @returns {number} Remaining readable bytes. May be negative if `offset > limit`.
+   * @expose
+   */
+  ByteBufferPrototype.remaining = function () {
+    return this.limit - this.offset;
+  };
+  /**
+   * Resets this ByteBuffer's {@link ByteBuffer#offset}. If an offset has been marked through {@link ByteBuffer#mark}
+   *  before, `offset` will be set to {@link ByteBuffer#markedOffset}, which will then be discarded. If no offset has been
+   *  marked, sets `offset = 0`.
+   * @returns {!ByteBuffer} this
+   * @see ByteBuffer#mark
+   * @expose
+   */
+  ByteBufferPrototype.reset = function () {
+    if (this.markedOffset >= 0) {
+      this.offset = this.markedOffset;
+      this.markedOffset = -1;
+    } else {
+      this.offset = 0;
+    }
+    return this;
+  };
+  /**
+   * Resizes this ByteBuffer to be backed by a buffer of at least the given capacity. Will do nothing if already that
+   *  large or larger.
+   * @param {number} capacity Capacity required
+   * @returns {!ByteBuffer} this
+   * @throws {TypeError} If `capacity` is not a number
+   * @throws {RangeError} If `capacity < 0`
+   * @expose
+   */
+  ByteBufferPrototype.resize = function (capacity) {
+    if (!this.noAssert) {
+      if (typeof capacity !== 'number' || capacity % 1 !== 0)
+        throw TypeError("Illegal capacity: " + capacity + " (not an integer)");
+      capacity |= 0;
+      if (capacity < 0)
+        throw RangeError("Illegal capacity: 0 <= " + capacity);
+    }
+    if (this.buffer.byteLength < capacity) {
+      var buffer = new ArrayBuffer(capacity);
+      var view = new Uint8Array(buffer);
+      view.set(this.view);
+      this.buffer = buffer;
+      this.view = view;
+    }
+    return this;
+  };
+  /**
+   * Reverses this ByteBuffer's contents.
+   * @param {number=} begin Offset to start at, defaults to {@link ByteBuffer#offset}
+   * @param {number=} end Offset to end at, defaults to {@link ByteBuffer#limit}
+   * @returns {!ByteBuffer} this
+   * @expose
+   */
+  ByteBufferPrototype.reverse = function (begin, end) {
+    if (typeof begin === 'undefined') begin = this.offset;
+    if (typeof end === 'undefined') end = this.limit;
+    if (!this.noAssert) {
+      if (typeof begin !== 'number' || begin % 1 !== 0)
+        throw TypeError("Illegal begin: Not an integer");
+      begin >>>= 0;
+      if (typeof end !== 'number' || end % 1 !== 0)
+        throw TypeError("Illegal end: Not an integer");
+      end >>>= 0;
+      if (begin < 0 || begin > end || end > this.buffer.byteLength)
+        throw RangeError("Illegal range: 0 <= " + begin + " <= " + end + " <= " + this.buffer.byteLength);
+    }
+    if (begin === end)
+      return this; // Nothing to reverse
+    Array.prototype.reverse.call(this.view.subarray(begin, end));
+    return this;
+  };
+  /**
+   * Skips the next `length` bytes. This will just advance
+   * @param {number} length Number of bytes to skip. May also be negative to move the offset back.
+   * @returns {!ByteBuffer} this
+   * @expose
+   */
+  ByteBufferPrototype.skip = function (length) {
+    if (!this.noAssert) {
+      if (typeof length !== 'number' || length % 1 !== 0)
+        throw TypeError("Illegal length: " + length + " (not an integer)");
+      length |= 0;
+    }
+    var offset = this.offset + length;
+    if (!this.noAssert) {
+      if (offset < 0 || offset > this.buffer.byteLength)
+        throw RangeError("Illegal length: 0 <= " + this.offset + " + " + length + " <= " + this.buffer.byteLength);
+    }
+    this.offset = offset;
+    return this;
+  };
+
+  /**
+   * Slices this ByteBuffer by creating a cloned instance with `offset = begin` and `limit = end`.
+   * @param {number=} begin Begin offset, defaults to {@link ByteBuffer#offset}.
+   * @param {number=} end End offset, defaults to {@link ByteBuffer#limit}.
+   * @returns {!ByteBuffer} Clone of this ByteBuffer with slicing applied, backed by the same {@link ByteBuffer#buffer}
+   * @expose
+   */
+  ByteBufferPrototype.slice = function (begin, end) {
+    if (typeof begin === 'undefined') begin = this.offset;
+    if (typeof end === 'undefined') end = this.limit;
+    if (!this.noAssert) {
+      if (typeof begin !== 'number' || begin % 1 !== 0)
+        throw TypeError("Illegal begin: Not an integer");
+      begin >>>= 0;
+      if (typeof end !== 'number' || end % 1 !== 0)
+        throw TypeError("Illegal end: Not an integer");
+      end >>>= 0;
+      if (begin < 0 || begin > end || end > this.buffer.byteLength)
+        throw RangeError("Illegal range: 0 <= " + begin + " <= " + end + " <= " + this.buffer.byteLength);
+    }
+    var bb = this.clone();
+    bb.offset = begin;
+    bb.limit = end;
+    return bb;
+  };
+  /**
+   * Returns a copy of the backing buffer that contains this ByteBuffer's contents. Contents are the bytes between
+   *  {@link ByteBuffer#offset} and {@link ByteBuffer#limit}.
+   * @param {boolean=} forceCopy If `true` returns a copy, otherwise returns a view referencing the same memory if
+   *  possible. Defaults to `false`
+   * @returns {!ArrayBuffer} Contents as an ArrayBuffer
+   * @expose
+   */
+  ByteBufferPrototype.toBuffer = function (forceCopy) {
+    var offset = this.offset,
+      limit = this.limit;
+    if (!this.noAssert) {
+      if (typeof offset !== 'number' || offset % 1 !== 0)
+        throw TypeError("Illegal offset: Not an integer");
+      offset >>>= 0;
+      if (typeof limit !== 'number' || limit % 1 !== 0)
+        throw TypeError("Illegal limit: Not an integer");
+      limit >>>= 0;
+      if (offset < 0 || offset > limit || limit > this.buffer.byteLength)
+        throw RangeError("Illegal range: 0 <= " + offset + " <= " + limit + " <= " + this.buffer.byteLength);
+    }
+    // NOTE: It's not possible to have another ArrayBuffer reference the same memory as the backing buffer. This is
+    // possible with Uint8Array#subarray only, but we have to return an ArrayBuffer by contract. So:
+    if (!forceCopy && offset === 0 && limit === this.buffer.byteLength)
+      return this.buffer;
+    if (offset === limit)
+      return EMPTY_BUFFER;
+    var buffer = new ArrayBuffer(limit - offset);
+    new Uint8Array(buffer).set(new Uint8Array(this.buffer).subarray(offset, limit), 0);
+    return buffer;
+  };
+
+  /**
+   * Returns a raw buffer compacted to contain this ByteBuffer's contents. Contents are the bytes between
+   *  {@link ByteBuffer#offset} and {@link ByteBuffer#limit}. This is an alias of {@link ByteBuffer#toBuffer}.
+   * @function
+   * @param {boolean=} forceCopy If `true` returns a copy, otherwise returns a view referencing the same memory.
+   *  Defaults to `false`
+   * @returns {!ArrayBuffer} Contents as an ArrayBuffer
+   * @expose
+   */
+  ByteBufferPrototype.toArrayBuffer = ByteBufferPrototype.toBuffer;
+
+  /**
+   * Converts the ByteBuffer's contents to a string.
+   * @param {string=} encoding Output encoding. Returns an informative string representation if omitted but also allows
+   *  direct conversion to "utf8", "hex", "base64" and "binary" encoding. "debug" returns a hex representation with
+   *  highlighted offsets.
+   * @param {number=} begin Offset to begin at, defaults to {@link ByteBuffer#offset}
+   * @param {number=} end Offset to end at, defaults to {@link ByteBuffer#limit}
+   * @returns {string} String representation
+   * @throws {Error} If `encoding` is invalid
+   * @expose
+   */
+  ByteBufferPrototype.toString = function (encoding, begin, end) {
+    if (typeof encoding === 'undefined')
+      return "ByteBufferAB(offset=" + this.offset + ",markedOffset=" + this.markedOffset + ",limit=" + this.limit + ",capacity=" + this.capacity() + ")";
+    if (typeof encoding === 'number')
+      encoding = "utf8",
+      begin = encoding,
+      end = begin;
+    switch (encoding) {
+    case "utf8":
+      return this.toUTF8(begin, end);
+    case "base64":
+      return this.toBase64(begin, end);
+    case "hex":
+      return this.toHex(begin, end);
+    case "binary":
+      return this.toBinary(begin, end);
+    case "debug":
+      return this.toDebug();
+    case "columns":
+      return this.toColumns();
+    default:
+      throw Error("Unsupported encoding: " + encoding);
+    }
+  };
+
+  // lxiv-embeddable
+
+  /**
+   * lxiv-embeddable (c) 2014 Daniel Wirtz <dcode@dcode.io>
+   * Released under the Apache License, Version 2.0
+   * see: https://github.com/dcodeIO/lxiv for details
+   */
+  var lxiv = function () {
+    "use strict";
+
+    /**
+     * lxiv namespace.
+     * @type {!Object.<string,*>}
+     * @exports lxiv
+     */
+    var lxiv = {};
+
+    /**
+     * Character codes for output.
+     * @type {!Array.<number>}
+     * @inner
+     */
+    var aout = [
+      65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
+      81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102,
+      103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118,
+      119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 47
+    ];
+
+    /**
+     * Character codes for input.
+     * @type {!Array.<number>}
+     * @inner
+     */
+    var ain = [];
+    for (var i = 0, k = aout.length; i < k; ++i)
+      ain[aout[i]] = i;
+
+    /**
+     * Encodes bytes to base64 char codes.
+     * @param {!function():number|null} src Bytes source as a function returning the next byte respectively `null` if
+     *  there are no more bytes left.
+     * @param {!function(number)} dst Characters destination as a function successively called with each encoded char
+     *  code.
+     */
+    lxiv.encode = function (src, dst) {
+      var b, t;
+      while ((b = src()) !== null) {
+        dst(aout[(b >> 2) & 0x3f]);
+        t = (b & 0x3) << 4;
+        if ((b = src()) !== null) {
+          t |= (b >> 4) & 0xf;
+          dst(aout[(t | ((b >> 4) & 0xf)) & 0x3f]);
+          t = (b & 0xf) << 2;
+          if ((b = src()) !== null)
+            dst(aout[(t | ((b >> 6) & 0x3)) & 0x3f]),
+            dst(aout[b & 0x3f]);
+          else
+            dst(aout[t & 0x3f]),
+            dst(61);
+        } else
+          dst(aout[t & 0x3f]),
+          dst(61),
+          dst(61);
+      }
+    };
+
+    /**
+     * Decodes base64 char codes to bytes.
+     * @param {!function():number|null} src Characters source as a function returning the next char code respectively
+     *  `null` if there are no more characters left.
+     * @param {!function(number)} dst Bytes destination as a function successively called with the next byte.
+     * @throws {Error} If a character code is invalid
+     */
+    lxiv.decode = function (src, dst) {
+      var c, t1, t2;
+
+      function fail(c) {
+        throw Error("Illegal character code: " + c);
+      }
+      while ((c = src()) !== null) {
+        t1 = ain[c];
+        if (typeof t1 === 'undefined') fail(c);
+        if ((c = src()) !== null) {
+          t2 = ain[c];
+          if (typeof t2 === 'undefined') fail(c);
+          dst((t1 << 2) >>> 0 | (t2 & 0x30) >> 4);
+          if ((c = src()) !== null) {
+            t1 = ain[c];
+            if (typeof t1 === 'undefined')
+              if (c === 61) break;
+              else fail(c);
+            dst(((t2 & 0xf) << 4) >>> 0 | (t1 & 0x3c) >> 2);
+            if ((c = src()) !== null) {
+              t2 = ain[c];
+              if (typeof t2 === 'undefined')
+                if (c === 61) break;
+                else fail(c);
+              dst(((t1 & 0x3) << 6) >>> 0 | t2);
+            }
+          }
+        }
+      }
+    };
+
+    /**
+     * Tests if a string is valid base64.
+     * @param {string} str String to test
+     * @returns {boolean} `true` if valid, otherwise `false`
+     */
+    lxiv.test = function (str) {
+      return /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(str);
+    };
+
+    return lxiv;
+  }();
+
+  // encodings/base64
+
+  /**
+   * Encodes this ByteBuffer's contents to a base64 encoded string.
+   * @param {number=} begin Offset to begin at, defaults to {@link ByteBuffer#offset}.
+   * @param {number=} end Offset to end at, defaults to {@link ByteBuffer#limit}.
+   * @returns {string} Base64 encoded string
+   * @throws {RangeError} If `begin` or `end` is out of bounds
+   * @expose
+   */
+  ByteBufferPrototype.toBase64 = function (begin, end) {
+    if (typeof begin === 'undefined')
+      begin = this.offset;
+    if (typeof end === 'undefined')
+      end = this.limit;
+    begin = begin | 0;
+    end = end | 0;
+    if (begin < 0 || end > this.capacity || begin > end)
+      throw RangeError("begin, end");
+    var sd;
+    lxiv.encode(function () {
+      return begin < end ? this.view[begin++] : null;
+    }.bind(this), sd = stringDestination());
+    return sd();
+  };
+
+  /**
+   * Decodes a base64 encoded string to a ByteBuffer.
+   * @param {string} str String to decode
+   * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to
+   *  {@link ByteBuffer.DEFAULT_ENDIAN}.
+   * @returns {!ByteBuffer} ByteBuffer
+   * @expose
+   */
+  ByteBuffer.fromBase64 = function (str, littleEndian) {
+    if (typeof str !== 'string')
+      throw TypeError("str");
+    var bb = new ByteBuffer(str.length / 4 * 3, littleEndian),
+      i = 0;
+    lxiv.decode(stringSource(str), function (b) {
+      bb.view[i++] = b;
+    });
+    bb.limit = i;
+    return bb;
+  };
+
+  /**
+   * Encodes a binary string to base64 like `window.btoa` does.
+   * @param {string} str Binary string
+   * @returns {string} Base64 encoded string
+   * @see https://developer.mozilla.org/en-US/docs/Web/API/Window.btoa
+   * @expose
+   */
+  ByteBuffer.btoa = function (str) {
+    return ByteBuffer.fromBinary(str).toBase64();
+  };
+
+  /**
+   * Decodes a base64 encoded string to binary like `window.atob` does.
+   * @param {string} b64 Base64 encoded string
+   * @returns {string} Binary string
+   * @see https://developer.mozilla.org/en-US/docs/Web/API/Window.atob
+   * @expose
+   */
+  ByteBuffer.atob = function (b64) {
+    return ByteBuffer.fromBase64(b64).toBinary();
+  };
+
+  // encodings/binary
+
+  /**
+   * Encodes this ByteBuffer to a binary encoded string, that is using only characters 0x00-0xFF as bytes.
+   * @param {number=} begin Offset to begin at. Defaults to {@link ByteBuffer#offset}.
+   * @param {number=} end Offset to end at. Defaults to {@link ByteBuffer#limit}.
+   * @returns {string} Binary encoded string
+   * @throws {RangeError} If `offset > limit`
+   * @expose
+   */
+  ByteBufferPrototype.toBinary = function (begin, end) {
+    if (typeof begin === 'undefined')
+      begin = this.offset;
+    if (typeof end === 'undefined')
+      end = this.limit;
+    begin |= 0;
+    end |= 0;
+    if (begin < 0 || end > this.capacity() || begin > end)
+      throw RangeError("begin, end");
+    if (begin === end)
+      return "";
+    var chars = [],
+      parts = [];
+    while (begin < end) {
+      chars.push(this.view[begin++]);
+      if (chars.length >= 1024)
+        parts.push(String.fromCharCode.apply(String, chars)),
+        chars = [];
+    }
+    return parts.join('') + String.fromCharCode.apply(String, chars);
+  };
+
+  /**
+   * Decodes a binary encoded string, that is using only characters 0x00-0xFF as bytes, to a ByteBuffer.
+   * @param {string} str String to decode
+   * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to
+   *  {@link ByteBuffer.DEFAULT_ENDIAN}.
+   * @returns {!ByteBuffer} ByteBuffer
+   * @expose
+   */
+  ByteBuffer.fromBinary = function (str, littleEndian) {
+    if (typeof str !== 'string')
+      throw TypeError("str");
+    var i = 0,
+      k = str.length,
+      charCode,
+      bb = new ByteBuffer(k, littleEndian);
+    while (i < k) {
+      charCode = str.charCodeAt(i);
+      if (charCode > 0xff)
+        throw RangeError("illegal char code: " + charCode);
+      bb.view[i++] = charCode;
+    }
+    bb.limit = k;
+    return bb;
+  };
+
+  // encodings/debug
+
+  /**
+   * Encodes this ByteBuffer to a hex encoded string with marked offsets. Offset symbols are:
+   * * `<` : offset,
+   * * `'` : markedOffset,
+   * * `>` : limit,
+   * * `|` : offset and limit,
+   * * `[` : offset and markedOffset,
+   * * `]` : markedOffset and limit,
+   * * `!` : offset, markedOffset and limit
+   * @param {boolean=} columns If `true` returns two columns hex + ascii, defaults to `false`
+   * @returns {string|!Array.<string>} Debug string or array of lines if `asArray = true`
+   * @expose
+   * @example `>00'01 02<03` contains four bytes with `limit=0, markedOffset=1, offset=3`
+   * @example `00[01 02 03>` contains four bytes with `offset=markedOffset=1, limit=4`
+   * @example `00|01 02 03` contains four bytes with `offset=limit=1, markedOffset=-1`
+   * @example `|` contains zero bytes with `offset=limit=0, markedOffset=-1`
+   */
+  ByteBufferPrototype.toDebug = function (columns) {
+    var i = -1,
+      k = this.buffer.byteLength,
+      b,
+      hex = "",
+      asc = "",
+      out = "";
+    while (i < k) {
+      if (i !== -1) {
+        b = this.view[i];
+        if (b < 0x10) hex += "0" + b.toString(16).toUpperCase();
+        else hex += b.toString(16).toUpperCase();
+        if (columns)
+          asc += b > 32 && b < 127 ? String.fromCharCode(b) : '.';
+      }
+      ++i;
+      if (columns) {
+        if (i > 0 && i % 16 === 0 && i !== k) {
+          while (hex.length < 3 * 16 + 3) hex += " ";
+          out += hex + asc + "\n";
+          hex = asc = "";
+        }
+      }
+      if (i === this.offset && i === this.limit)
+        hex += i === this.markedOffset ? "!" : "|";
+      else if (i === this.offset)
+        hex += i === this.markedOffset ? "[" : "<";
+      else if (i === this.limit)
+        hex += i === this.markedOffset ? "]" : ">";
+      else
+        hex += i === this.markedOffset ? "'" : (columns || (i !== 0 && i !== k) ? " " : "");
+    }
+    if (columns && hex !== " ") {
+      while (hex.length < 3 * 16 + 3)
+        hex += " ";
+      out += hex + asc + "\n";
+    }
+    return columns ? out : hex;
+  };
+
+  /**
+   * Decodes a hex encoded string with marked offsets to a ByteBuffer.
+   * @param {string} str Debug string to decode (not be generated with `columns = true`)
+   * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to
+   *  {@link ByteBuffer.DEFAULT_ENDIAN}.
+   * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to
+   *  {@link ByteBuffer.DEFAULT_NOASSERT}.
+   * @returns {!ByteBuffer} ByteBuffer
+   * @expose
+   * @see ByteBuffer#toDebug
+   */
+  ByteBuffer.fromDebug = function (str, littleEndian, noAssert) {
+    var k = str.length,
+      bb = new ByteBuffer(((k + 1) / 3) | 0, littleEndian, noAssert);
+    var i = 0,
+      j = 0,
+      ch, b,
+      rs = false, // Require symbol next
+      ho = false,
+      hm = false,
+      hl = false, // Already has offset (ho), markedOffset (hm), limit (hl)?
+      fail = false;
+    while (i < k) {
+      switch (ch = str.charAt(i++)) {
+      case '!':
+        if (!noAssert) {
+          if (ho || hm || hl) {
+            fail = true;
+            break;
+          }
+          ho = hm = hl = true;
+        }
+        bb.offset = bb.markedOffset = bb.limit = j;
+        rs = false;
+        break;
+      case '|':
+        if (!noAssert) {
+          if (ho || hl) {
+            fail = true;
+            break;
+          }
+          ho = hl = true;
+        }
+        bb.offset = bb.limit = j;
+        rs = false;
+        break;
+      case '[':
+        if (!noAssert) {
+          if (ho || hm) {
+            fail = true;
+            break;
+          }
+          ho = hm = true;
+        }
+        bb.offset = bb.markedOffset = j;
+        rs = false;
+        break;
+      case '<':
+        if (!noAssert) {
+          if (ho) {
+            fail = true;
+            break;
+          }
+          ho = true;
+        }
+        bb.offset = j;
+        rs = false;
+        break;
+      case ']':
+        if (!noAssert) {
+          if (hl || hm) {
+            fail = true;
+            break;
+          }
+          hl = hm = true;
+        }
+        bb.limit = bb.markedOffset = j;
+        rs = false;
+        break;
+      case '>':
+        if (!noAssert) {
+          if (hl) {
+            fail = true;
+            break;
+          }
+          hl = true;
+        }
+        bb.limit = j;
+        rs = false;
+        break;
+      case "'":
+        if (!noAssert) {
+          if (hm) {
+            fail = true;
+            break;
+          }
+          hm = true;
+        }
+        bb.markedOffset = j;
+        rs = false;
+        break;
+      case ' ':
+        rs = false;
+        break;
+      default:
+        if (!noAssert) {
+          if (rs) {
+            fail = true;
+            break;
+          }
+        }
+        b = parseInt(ch + str.charAt(i++), 16);
+        if (!noAssert) {
+          if (isNaN(b) || b < 0 || b > 255)
+            throw TypeError("Illegal str: Not a debug encoded string");
+        }
+        bb.view[j++] = b;
+        rs = true;
+      }
+      if (fail)
+        throw TypeError("Illegal str: Invalid symbol at " + i);
+    }
+    if (!noAssert) {
+      if (!ho || !hl)
+        throw TypeError("Illegal str: Missing offset or limit");
+      if (j < bb.buffer.byteLength)
+        throw TypeError("Illegal str: Not a debug encoded string (is it hex?) " + j + " < " + k);
+    }
+    return bb;
+  };
+
+  // encodings/hex
+
+  /**
+   * Encodes this ByteBuffer's contents to a hex encoded string.
+   * @param {number=} begin Offset to begin at. Defaults to {@link ByteBuffer#offset}.
+   * @param {number=} end Offset to end at. Defaults to {@link ByteBuffer#limit}.
+   * @returns {string} Hex encoded string
+   * @expose
+   */
+  ByteBufferPrototype.toHex = function (begin, end) {
+    begin = typeof begin === 'undefined' ? this.offset : begin;
+    end = typeof end === 'undefined' ? this.limit : end;
+    if (!this.noAssert) {
+      if (typeof begin !== 'number' || begin % 1 !== 0)
+        throw TypeError("Illegal begin: Not an integer");
+      begin >>>= 0;
+      if (typeof end !== 'number' || end % 1 !== 0)
+        throw TypeError("Illegal end: Not an integer");
+      end >>>= 0;
+      if (begin < 0 || begin > end || end > this.buffer.byteLength)
+        throw RangeError("Illegal range: 0 <= " + begin + " <= " + end + " <= " + this.buffer.byteLength);
+    }
+    var out = new Array(end - begin),
+      b;
+    while (begin < end) {
+      b = this.view[begin++];
+      if (b < 0x10)
+        out.push("0", b.toString(16));
+      else out.push(b.toString(16));
+    }
+    return out.join('');
+  };
+
+  /**
+   * Decodes a hex encoded string to a ByteBuffer.
+   * @param {string} str String to decode
+   * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to
+   *  {@link ByteBuffer.DEFAULT_ENDIAN}.
+   * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to
+   *  {@link ByteBuffer.DEFAULT_NOASSERT}.
+   * @returns {!ByteBuffer} ByteBuffer
+   * @expose
+   */
+  ByteBuffer.fromHex = function (str, littleEndian, noAssert) {
+    if (!noAssert) {
+      if (typeof str !== 'string')
+        throw TypeError("Illegal str: Not a string");
+      if (str.length % 2 !== 0)
+        throw TypeError("Illegal str: Length not a multiple of 2");
+    }
+    var k = str.length,
+      bb = new ByteBuffer((k / 2) | 0, littleEndian),
+      b;
+    for (var i = 0, j = 0; i < k; i += 2) {
+      b = parseInt(str.substring(i, i + 2), 16);
+      if (!noAssert)
+        if (!isFinite(b) || b < 0 || b > 255)
+          throw TypeError("Illegal str: Contains non-hex characters");
+      bb.view[j++] = b;
+    }
+    bb.limit = j;
+    return bb;
+  };
+
+  // utfx-embeddable
+
+  /**
+   * utfx-embeddable (c) 2014 Daniel Wirtz <dcode@dcode.io>
+   * Released under the Apache License, Version 2.0
+   * see: https://github.com/dcodeIO/utfx for details
+   */
+  var utfx = function () {
+    "use strict";
+
+    /**
+     * utfx namespace.
+     * @inner
+     * @type {!Object.<string,*>}
+     */
+    var utfx = {};
+
+    /**
+     * Maximum valid code point.
+     * @type {number}
+     * @const
+     */
+    utfx.MAX_CODEPOINT = 0x10FFFF;
+
+    /**
+     * Encodes UTF8 code points to UTF8 bytes.
+     * @param {(!function():number|null) | number} src Code points source, either as a function returning the next code point
+     *  respectively `null` if there are no more code points left or a single numeric code point.
+     * @param {!function(number)} dst Bytes destination as a function successively called with the next byte
+     */
+    utfx.encodeUTF8 = function (src, dst) {
+      var cp = null;
+      if (typeof src === 'number')
+        cp = src,
+        src = function () {
+          return null;
+        };
+      while (cp !== null || (cp = src()) !== null) {
+        if (cp < 0x80)
+          dst(cp & 0x7F);
+        else if (cp < 0x800)
+          dst(((cp >> 6) & 0x1F) | 0xC0),
+          dst((cp & 0x3F) | 0x80);
+        else if (cp < 0x10000)
+          dst(((cp >> 12) & 0x0F) | 0xE0),
+          dst(((cp >> 6) & 0x3F) | 0x80),
+          dst((cp & 0x3F) | 0x80);
+        else
+          dst(((cp >> 18) & 0x07) | 0xF0),
+          dst(((cp >> 12) & 0x3F) | 0x80),
+          dst(((cp >> 6) & 0x3F) | 0x80),
+          dst((cp & 0x3F) | 0x80);
+        cp = null;
+      }
+    };
+
+    /**
+     * Decodes UTF8 bytes to UTF8 code points.
+     * @param {!function():number|null} src Bytes source as a function returning the next byte respectively `null` if there
+     *  are no more bytes left.
+     * @param {!function(number)} dst Code points destination as a function successively called with each decoded code point.
+     * @throws {RangeError} If a starting byte is invalid in UTF8
+     * @throws {Error} If the last sequence is truncated. Has an array property `bytes` holding the
+     *  remaining bytes.
+     */
+    utfx.decodeUTF8 = function (src, dst) {
+      var a, b, c, d, fail = function (b) {
+        b = b.slice(0, b.indexOf(null));
+        var err = Error(b.toString());
+        err.name = "TruncatedError";
+        err['bytes'] = b;
+        throw err;
+      };
+      while ((a = src()) !== null) {
+        if ((a & 0x80) === 0)
+          dst(a);
+        else if ((a & 0xE0) === 0xC0)
+          ((b = src()) === null) && fail([a, b]),
+          dst(((a & 0x1F) << 6) | (b & 0x3F));
+        else if ((a & 0xF0) === 0xE0)
+          ((b = src()) === null || (c = src()) === null) && fail([a, b, c]),
+          dst(((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F));
+        else if ((a & 0xF8) === 0xF0)
+          ((b = src()) === null || (c = src()) === null || (d = src()) === null) && fail([a, b, c, d]),
+          dst(((a & 0x07) << 18) | ((b & 0x3F) << 12) | ((c & 0x3F) << 6) | (d & 0x3F));
+        else throw RangeError("Illegal starting byte: " + a);
+      }
+    };
+
+    /**
+     * Converts UTF16 characters to UTF8 code points.
+     * @param {!function():number|null} src Characters source as a function returning the next char code respectively
+     *  `null` if there are no more characters left.
+     * @param {!function(number)} dst Code points destination as a function successively called with each converted code
+     *  point.
+     */
+    utfx.UTF16toUTF8 = function (src, dst) {
+      var c1, c2 = null;
+      while (true) {
+        if ((c1 = c2 !== null ? c2 : src()) === null)
+          break;
+        if (c1 >= 0xD800 && c1 <= 0xDFFF) {
+          if ((c2 = src()) !== null) {
+            if (c2 >= 0xDC00 && c2 <= 0xDFFF) {
+              dst((c1 - 0xD800) * 0x400 + c2 - 0xDC00 + 0x10000);
+              c2 = null;
+              continue;
+            }
+          }
+        }
+        dst(c1);
+      }
+      if (c2 !== null) dst(c2);
+    };
+
+    /**
+     * Converts UTF8 code points to UTF16 characters.
+     * @param {(!function():number|null) | number} src Code points source, either as a function returning the next code point
+     *  respectively `null` if there are no more code points left or a single numeric code point.
+     * @param {!function(number)} dst Characters destination as a function successively called with each converted char code.
+     * @throws {RangeError} If a code point is out of range
+     */
+    utfx.UTF8toUTF16 = function (src, dst) {
+      var cp = null;
+      if (typeof src === 'number')
+        cp = src, src = function () {
+          return null;
+        };
+      while (cp !== null || (cp = src()) !== null) {
+        if (cp <= 0xFFFF)
+          dst(cp);
+        else
+          cp -= 0x10000,
+          dst((cp >> 10) + 0xD800),
+          dst((cp % 0x400) + 0xDC00);
+        cp = null;
+      }
+    };
+
+    /**
+     * Converts and encodes UTF16 characters to UTF8 bytes.
+     * @param {!function():number|null} src Characters source as a function returning the next char code respectively `null`
+     *  if there are no more characters left.
+     * @param {!function(number)} dst Bytes destination as a function successively called with the next byte.
+     */
+    utfx.encodeUTF16toUTF8 = function (src, dst) {
+      utfx.UTF16toUTF8(src, function (cp) {
+        utfx.encodeUTF8(cp, dst);
+      });
+    };
+
+    /**
+     * Decodes and converts UTF8 bytes to UTF16 characters.
+     * @param {!function():number|null} src Bytes source as a function returning the next byte respectively `null` if there
+     *  are no more bytes left.
+     * @param {!function(number)} dst Characters destination as a function successively called with each converted char code.
+     * @throws {RangeError} If a starting byte is invalid in UTF8
+     * @throws {Error} If the last sequence is truncated. Has an array property `bytes` holding the remaining bytes.
+     */
+    utfx.decodeUTF8toUTF16 = function (src, dst) {
+      utfx.decodeUTF8(src, function (cp) {
+        utfx.UTF8toUTF16(cp, dst);
+      });
+    };
+
+    /**
+     * Calculates the byte length of an UTF8 code point.
+     * @param {number} cp UTF8 code point
+     * @returns {number} Byte length
+     */
+    utfx.calculateCodePoint = function (cp) {
+      return (cp < 0x80) ? 1 : (cp < 0x800) ? 2 : (cp < 0x10000) ? 3 : 4;
+    };
+
+    /**
+     * Calculates the number of UTF8 bytes required to store UTF8 code points.
+     * @param {(!function():number|null)} src Code points source as a function returning the next code point respectively
+     *  `null` if there are no more code points left.
+     * @returns {number} The number of UTF8 bytes required
+     */
+    utfx.calculateUTF8 = function (src) {
+      var cp, l = 0;
+      while ((cp = src()) !== null)
+        l += (cp < 0x80) ? 1 : (cp < 0x800) ? 2 : (cp < 0x10000) ? 3 : 4;
+      return l;
+    };
+
+    /**
+     * Calculates the number of UTF8 code points respectively UTF8 bytes required to store UTF16 char codes.
+     * @param {(!function():number|null)} src Characters source as a function returning the next char code respectively
+     *  `null` if there are no more characters left.
+     * @returns {!Array.<number>} The number of UTF8 code points at index 0 and the number of UTF8 bytes required at index 1.
+     */
+    utfx.calculateUTF16asUTF8 = function (src) {
+      var n = 0,
+        l = 0;
+      utfx.UTF16toUTF8(src, function (cp) {
+        ++n;
+        l += (cp < 0x80) ? 1 : (cp < 0x800) ? 2 : (cp < 0x10000) ? 3 : 4;
+      });
+      return [n, l];
+    };
+
+    return utfx;
+  }();
+
+  // encodings/utf8
+
+  /**
+   * Encodes this ByteBuffer's contents between {@link ByteBuffer#offset} and {@link ByteBuffer#limit} to an UTF8 encoded
+   *  string.
+   * @returns {string} Hex encoded string
+   * @throws {RangeError} If `offset > limit`
+   * @expose
+   */
+  ByteBufferPrototype.toUTF8 = function (begin, end) {
+    if (typeof begin === 'undefined') begin = this.offset;
+    if (typeof end === 'undefined') end = this.limit;
+    if (!this.noAssert) {
+      if (typeof begin !== 'number' || begin % 1 !== 0)
+        throw TypeError("Illegal begin: Not an integer");
+      begin >>>= 0;
+      if (typeof end !== 'number' || end % 1 !== 0)
+        throw TypeError("Illegal end: Not an integer");
+      end >>>= 0;
+      if (begin < 0 || begin > end || end > this.buffer.byteLength)
+        throw RangeError("Illegal range: 0 <= " + begin + " <= " + end + " <= " + this.buffer.byteLength);
+    }
+    var sd;
+    try {
+      utfx.decodeUTF8toUTF16(function () {
+        return begin < end ? this.view[begin++] : null;
+      }.bind(this), sd = stringDestination());
+    } catch (e) {
+      if (begin !== end)
+        throw RangeError("Illegal range: Truncated data, " + begin + " != " + end);
+    }
+    return sd();
+  };
+
+  /**
+   * Decodes an UTF8 encoded string to a ByteBuffer.
+   * @param {string} str String to decode
+   * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to
+   *  {@link ByteBuffer.DEFAULT_ENDIAN}.
+   * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to
+   *  {@link ByteBuffer.DEFAULT_NOASSERT}.
+   * @returns {!ByteBuffer} ByteBuffer
+   * @expose
+   */
+  ByteBuffer.fromUTF8 = function (str, littleEndian, noAssert) {
+    if (!noAssert)
+      if (typeof str !== 'string')
+        throw TypeError("Illegal str: Not a string");
+    var bb = new ByteBuffer(utfx.calculateUTF16asUTF8(stringSource(str), true)[1], littleEndian, noAssert),
+      i = 0;
+    utfx.encodeUTF16toUTF8(stringSource(str), function (b) {
+      bb.view[i++] = b;
+    });
+    bb.limit = i;
+    return bb;
+  };
+
+  return ByteBuffer;
+});
+/*
+ Copyright 2013 Daniel Wirtz <dcode@dcode.io>
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+/**
+ * @license ProtoBuf.js (c) 2013 Daniel Wirtz <dcode@dcode.io>
+ * Released under the Apache License, Version 2.0
+ * see: https://github.com/dcodeIO/ProtoBuf.js for details
+ */
+(function (global, factory) {
+
+  /* AMD */
+  if (typeof define === 'function' && define["amd"])
+    define(["ByteBuffer"], factory);
+  /* CommonJS */
+  else if (typeof require === "function" && typeof module === "object" && module && module["exports"])
+    module["exports"] = factory(require("./ByteBuffer"), require);
+  /* Global */
+  else
+    (global["dcodeIO"] = global["dcodeIO"] || {})["ProtoBuf"] = factory(global["dcodeIO"]["ByteBuffer"]);
+
+})(this, function (ByteBuffer, nodeRequire) {
+  "use strict";
+
+  /**
+   * The ProtoBuf namespace.
+   * @exports ProtoBuf
+   * @namespace
+   * @expose
+   */
+  var ProtoBuf = {};
+
+  /**
+   * @type {!function(new: ByteBuffer, ...[*])}
+   * @expose
+   */
+  ProtoBuf.ByteBuffer = ByteBuffer;
+
+  /**
+   * @type {?function(new: Long, ...[*])}
+   * @expose
+   */
+  ProtoBuf.Long = ByteBuffer.Long || null;
+
+  /**
+   * ProtoBuf.js version.
+   * @type {string}
+   * @const
+   * @expose
+   */
+  ProtoBuf.VERSION = "4.1.1";
+
+  /**
+   * Wire types.
+   * @type {Object.<string,number>}
+   * @const
+   * @expose
+   */
+  ProtoBuf.WIRE_TYPES = {};
+
+  /**
+   * Varint wire type.
+   * @type {number}
+   * @expose
+   */
+  ProtoBuf.WIRE_TYPES.VARINT = 0;
+
+  /**
+   * Fixed 64 bits wire type.
+   * @type {number}
+   * @const
+   * @expose
+   */
+  ProtoBuf.WIRE_TYPES.BITS64 = 1;
+
+  /**
+   * Length delimited wire type.
+   * @type {number}
+   * @const
+   * @expose
+   */
+  ProtoBuf.WIRE_TYPES.LDELIM = 2;
+
+  /**
+   * Start group wire type.
+   * @type {number}
+   * @const
+   * @expose
+   */
+  ProtoBuf.WIRE_TYPES.STARTGROUP = 3;
+
+  /**
+   * End group wire type.
+   * @type {number}
+   * @const
+   * @expose
+   */
+  ProtoBuf.WIRE_TYPES.ENDGROUP = 4;
+
+  /**
+   * Fixed 32 bits wire type.
+   * @type {number}
+   * @const
+   * @expose
+   */
+  ProtoBuf.WIRE_TYPES.BITS32 = 5;
+
+  /**
+   * Packable wire types.
+   * @type {!Array.<number>}
+   * @const
+   * @expose
+   */
+  ProtoBuf.PACKABLE_WIRE_TYPES = [
+    ProtoBuf.WIRE_TYPES.VARINT,
+    ProtoBuf.WIRE_TYPES.BITS64,
+    ProtoBuf.WIRE_TYPES.BITS32
+  ];
+
+  /**
+   * Types.
+   * @dict
+   * @type {!Object.<string,{name: string, wireType: number, defaultValue: *}>}
+   * @const
+   * @expose
+   */
+  ProtoBuf.TYPES = {
+    // According to the protobuf spec.
+    "int32": {
+      name: "int32",
+      wireType: ProtoBuf.WIRE_TYPES.VARINT,
+      defaultValue: 0
+    },
+    "uint32": {
+      name: "uint32",
+      wireType: ProtoBuf.WIRE_TYPES.VARINT,
+      defaultValue: 0
+    },
+    "sint32": {
+      name: "sint32",
+      wireType: ProtoBuf.WIRE_TYPES.VARINT,
+      defaultValue: 0
+    },
+    "int64": {
+      name: "int64",
+      wireType: ProtoBuf.WIRE_TYPES.VARINT,
+      defaultValue: ProtoBuf.Long ? ProtoBuf.Long.ZERO : undefined
+    },
+    "uint64": {
+      name: "uint64",
+      wireType: ProtoBuf.WIRE_TYPES.VARINT,
+      defaultValue: ProtoBuf.Long ? ProtoBuf.Long.UZERO : undefined
+    },
+    "sint64": {
+      name: "sint64",
+      wireType: ProtoBuf.WIRE_TYPES.VARINT,
+      defaultValue: ProtoBuf.Long ? ProtoBuf.Long.ZERO : undefined
+    },
+    "bool": {
+      name: "bool",
+      wireType: ProtoBuf.WIRE_TYPES.VARINT,
+      defaultValue: false
+    },
+    "double": {
+      name: "double",
+      wireType: ProtoBuf.WIRE_TYPES.BITS64,
+      defaultValue: 0
+    },
+    "string": {
+      name: "string",
+      wireType: ProtoBuf.WIRE_TYPES.LDELIM,
+      defaultValue: ""
+    },
+    "bytes": {
+      name: "bytes",
+      wireType: ProtoBuf.WIRE_TYPES.LDELIM,
+      defaultValue: null // overridden in the code, must be a unique instance
+    },
+    "fixed32": {
+      name: "fixed32",
+      wireType: ProtoBuf.WIRE_TYPES.BITS32,
+      defaultValue: 0
+    },
+    "sfixed32": {
+      name: "sfixed32",
+      wireType: ProtoBuf.WIRE_TYPES.BITS32,
+      defaultValue: 0
+    },
+    "fixed64": {
+      name: "fixed64",
+      wireType: ProtoBuf.WIRE_TYPES.BITS64,
+      defaultValue: ProtoBuf.Long ? ProtoBuf.Long.UZERO : undefined
+    },
+    "sfixed64": {
+      name: "sfixed64",
+      wireType: ProtoBuf.WIRE_TYPES.BITS64,
+      defaultValue: ProtoBuf.Long ? ProtoBuf.Long.ZERO : undefined
+    },
+    "float": {
+      name: "float",
+      wireType: ProtoBuf.WIRE_TYPES.BITS32,
+      defaultValue: 0
+    },
+    "enum": {
+      name: "enum",
+      wireType: ProtoBuf.WIRE_TYPES.VARINT,
+      defaultValue: 0
+    },
+    "message": {
+      name: "message",
+      wireType: ProtoBuf.WIRE_TYPES.LDELIM,
+      defaultValue: null
+    },
+    "group": {
+      name: "group",
+      wireType: ProtoBuf.WIRE_TYPES.STARTGROUP,
+      defaultValue: null
+    }
+  };
+
+  /**
+   * Valid map key types.
+   * @type {!Array.<!Object.<string,{name: string, wireType: number, defaultValue: *}>>}
+   * @const
+   * @expose
+   */
+  ProtoBuf.MAP_KEY_TYPES = [
+    ProtoBuf.TYPES["int32"],
+    ProtoBuf.TYPES["sint32"],
+    ProtoBuf.TYPES["sfixed32"],
+    ProtoBuf.TYPES["uint32"],
+    ProtoBuf.TYPES["fixed32"],
+    ProtoBuf.TYPES["int64"],
+    ProtoBuf.TYPES["sint64"],
+    ProtoBuf.TYPES["sfixed64"],
+    ProtoBuf.TYPES["uint64"],
+    ProtoBuf.TYPES["fixed64"],
+    ProtoBuf.TYPES["bool"],
+    ProtoBuf.TYPES["string"],
+    ProtoBuf.TYPES["bytes"]
+  ];
+
+  /**
+   * Minimum field id.
+   * @type {number}
+   * @const
+   * @expose
+   */
+  ProtoBuf.ID_MIN = 1;
+
+  /**
+   * Maximum field id.
+   * @type {number}
+   * @const
+   * @expose
+   */
+  ProtoBuf.ID_MAX = 0x1FFFFFFF;
+
+  /**
+   * If set to `true`, field names will be converted from underscore notation to camel case. Defaults to `false`.
+   *  Must be set prior to parsing.
+   * @type {boolean}
+   * @expose
+   */
+  ProtoBuf.convertFieldsToCamelCase = false;
+
+  /**
+   * By default, messages are populated with (setX, set_x) accessors for each field. This can be disabled by
+   *  setting this to `false` prior to building messages.
+   * @type {boolean}
+   * @expose
+   */
+  ProtoBuf.populateAccessors = true;
+
+  /**
+   * By default, messages are populated with default values if a field is not present on the wire. To disable
+   *  this behavior, set this setting to `false`.
+   * @type {boolean}
+   * @expose
+   */
+  ProtoBuf.populateDefaults = true;
+
+  /**
+   * @alias ProtoBuf.Util
+   * @expose
+   */
+  ProtoBuf.Util = (function () {
+    "use strict";
+
+    /**
+     * ProtoBuf utilities.
+     * @exports ProtoBuf.Util
+     * @namespace
+     */
+    var Util = {};
+
+    /**
+     * Flag if running in node or not.
+     * @type {boolean}
+     * @const
+     * @expose
+     */
+    Util.IS_NODE = !!(
+      typeof process === 'object' && process + '' === '[object process]' && !process['browser']
+    );
+
+    /**
+     * Constructs a XMLHttpRequest object.
+     * @return {XMLHttpRequest}
+     * @throws {Error} If XMLHttpRequest is not supported
+     * @expose
+     */
+    Util.XHR = function () {
+      // No dependencies please, ref: http://www.quirksmode.org/js/xmlhttp.html
+      var XMLHttpFactories = [
+        function () {
+          return new XMLHttpRequest()
+        },
+        function () {
+          return new ActiveXObject("Msxml2.XMLHTTP")
+        },
+        function () {
+          return new ActiveXObject("Msxml3.XMLHTTP")
+        },
+        function () {
+          return new ActiveXObject("Microsoft.XMLHTTP")
+        }
+      ];
+      /** @type {?XMLHttpRequest} */
+      var xhr = null;
+      for (var i = 0; i < XMLHttpFactories.length; i++) {
+        try { xhr = XMLHttpFactories[i](); } catch (e) {
+          continue;
+        }
+        break;
+      }
+      if (!xhr)
+        throw Error("XMLHttpRequest is not supported");
+      return xhr;
+    };
+
+    /**
+     * Fetches a resource.
+     * @param {string} path Resource path
+     * @param {function(?string)=} callback Callback receiving the resource's contents. If omitted the resource will
+     *   be fetched synchronously. If the request failed, contents will be null.
+     * @return {?string|undefined} Resource contents if callback is omitted (null if the request failed), else undefined.
+     * @expose
+     */
+    Util.fetch = function (path, callback) {
+      if (callback && typeof callback != 'function')
+        callback = null;
+      if (Util.IS_NODE) {
+        if (callback) {
+          Util.require("fs").readFile(path, function (err, data) {
+            if (err)
+              callback(null);
+            else
+              callback("" + data);
+          });
+        } else
+          try {
+            return Util.require("fs").readFileSync(path);
+          } catch (e) {
+            return null;
+          }
+      } else {
+        var xhr = Util.XHR();
+        xhr.open('GET', path, callback ? true : false);
+        // xhr.setRequestHeader('User-Agent', 'XMLHTTP/1.0');
+        xhr.setRequestHeader('Accept', 'text/plain');
+        if (typeof xhr.overrideMimeType === 'function') xhr.overrideMimeType('text/plain');
+        if (callback) {
+          xhr.onreadystatechange = function () {
+            if (xhr.readyState != 4) return;
+            if ( /* remote */ xhr.status == 200 || /* local */ (xhr.status == 0 && typeof xhr.responseText === 'string'))
+              callback(xhr.responseText);
+            else
+              callback(null);
+          };
+          if (xhr.readyState == 4)
+            return;
+          xhr.send(null);
+        } else {
+          xhr.send(null);
+          if ( /* remote */ xhr.status == 200 || /* local */ (xhr.status == 0 && typeof xhr.responseText === 'string'))
+            return xhr.responseText;
+          return null;
+        }
+      }
+    };
+
+    /**
+     * Requires a node module.
+     * @function
+     * @param {string} path
+     * @returns {*}
+     * @throws Error If node require is not supported
+     * @expose
+     */
+    Util.require = Util.IS_NODE ? function (path) {
+      return nodeRequire(path);
+    } : function (path) {
+      throw Error("node require is not supported by this platform")
+    };
+
+    /**
+     * Converts a string to camel case.
+     * @param {string} str
+     * @returns {string}
+     * @expose
+     */
+    Util.toCamelCase = function (str) {
+      return str.replace(/_([a-zA-Z])/g, function ($0, $1) {
+        return $1.toUpperCase();
+      });
+    };
+
+    return Util;
+  })();
+
+  /**
+   * Language expressions.
+   * @type {!Object.<string,!RegExp>}
+   * @expose
+   */
+  ProtoBuf.Lang = {
+
+    // Characters always ending a statement
+    DELIM: /[\s\{\}=;:\[\],'"\(\)<>]/g,
+
+    // Field rules
+    RULE: /^(?:required|optional|repeated|map)$/,
+
+    // Field types
+    TYPE: /^(?:double|float|int32|uint32|sint32|int64|uint64|sint64|fixed32|sfixed32|fixed64|sfixed64|bool|string|bytes)$/,
+
+    // Names
+    NAME: /^[a-zA-Z_][a-zA-Z_0-9]*$/,
+
+    // Type definitions
+    TYPEDEF: /^[a-zA-Z][a-zA-Z_0-9]*$/,
+
+    // Type references
+    TYPEREF: /^(?:\.?[a-zA-Z_][a-zA-Z_0-9]*)+$/,
+
+    // Fully qualified type references
+    FQTYPEREF: /^(?:\.[a-zA-Z][a-zA-Z_0-9]*)+$/,
+
+    // All numbers
+    NUMBER: /^-?(?:[1-9][0-9]*|0|0[xX][0-9a-fA-F]+|0[0-7]+|([0-9]*(\.[0-9]*)?([Ee][+-]?[0-9]+)?)|inf|nan)$/,
+
+    // Decimal numbers
+    NUMBER_DEC: /^(?:[1-9][0-9]*|0)$/,
+
+    // Hexadecimal numbers
+    NUMBER_HEX: /^0[xX][0-9a-fA-F]+$/,
+
+    // Octal numbers
+    NUMBER_OCT: /^0[0-7]+$/,
+
+    // Floating point numbers
+    NUMBER_FLT: /^([0-9]*(\.[0-9]*)?([Ee][+-]?[0-9]+)?|inf|nan)$/,
+
+    // Booleans
+    BOOL: /^(?:true|false)$/i,
+
+    // Id numbers
+    ID: /^(?:[1-9][0-9]*|0|0[xX][0-9a-fA-F]+|0[0-7]+)$/,
+
+    // Negative id numbers (enum values)
+    NEGID: /^\-?(?:[1-9][0-9]*|0|0[xX][0-9a-fA-F]+|0[0-7]+)$/,
+
+    // Whitespaces
+    WHITESPACE: /\s/,
+
+    // All strings
+    STRING: /(?:"([^"\\]*(?:\\.[^"\\]*)*)")|(?:'([^'\\]*(?:\\.[^'\\]*)*)')/g,
+
+    // Double quoted strings
+    STRING_DQ: /(?:"([^"\\]*(?:\\.[^"\\]*)*)")/g,
+
+    // Single quoted strings
+    STRING_SQ: /(?:'([^'\\]*(?:\\.[^'\\]*)*)')/g
+  };
+
+  /**
+   * @alias ProtoBuf.DotProto
+   * @expose
+   */
+  ProtoBuf.DotProto = (function (ProtoBuf, Lang) {
+    "use strict";
+
+    /**
+     * Utilities to parse .proto files.
+     * @exports ProtoBuf.DotProto
+     * @namespace
+     */
+    var DotProto = {};
+
+    /**
+     * Constructs a new Tokenizer.
+     * @exports ProtoBuf.DotProto.Tokenizer
+     * @class prototype tokenizer
+     * @param {string} proto Proto to tokenize
+     * @constructor
+     */
+    var Tokenizer = function (proto) {
+
+      /**
+       * Source to parse.
+       * @type {string}
+       * @expose
+       */
+      this.source = proto + "";
+
+      /**
+       * Current index.
+       * @type {number}
+       * @expose
+       */
+      this.index = 0;
+
+      /**
+       * Current line.
+       * @type {number}
+       * @expose
+       */
+      this.line = 1;
+
+      /**
+       * Token stack.
+       * @type {!Array.<string>}
+       * @expose
+       */
+      this.stack = [];
+
+      /**
+       * Opening character of the current string read, if any.
+       * @type {?string}
+       * @private
+       */
+      this._stringOpen = null;
+    };
+
+    /**
+     * @alias ProtoBuf.DotProto.Tokenizer.prototype
+     * @inner
+     */
+    var TokenizerPrototype = Tokenizer.prototype;
+
+    /**
+     * Reads a string beginning at the current index.
+     * @return {string}
+     * @private
+     */
+    TokenizerPrototype._readString = function () {
+      var re = this._stringOpen === '"' ? Lang.STRING_DQ : Lang.STRING_SQ;
+      re.lastIndex = this.index - 1; // Include the open quote
+      var match = re.exec(this.source);
+      if (!match)
+        throw Error("unterminated string");
+      this.index = re.lastIndex;
+      this.stack.push(this._stringOpen);
+      this._stringOpen = null;
+      return match[1];
+    };
+
+    /**
+     * Gets the next token and advances by one.
+     * @return {?string} Token or `null` on EOF
+     * @expose
+     */
+    TokenizerPrototype.next = function () {
+      if (this.stack.length > 0)
+        return this.stack.shift();
+      if (this.index >= this.source.length)
+        return null;
+      if (this._stringOpen !== null)
+        return this._readString();
+
+      var repeat,
+        prev,
+        next;
+      do {
+        repeat = false;
+
+        // Strip white spaces
+        while (Lang.WHITESPACE.test(next = this.source.charAt(this.index))) {
+          if (next === '\n')
+            ++this.line;
+          if (++this.index === this.source.length)
+            return null;
+        }
+
+        // Strip comments
+        if (this.source.charAt(this.index) === '/') {
+          ++this.index;
+          if (this.source.charAt(this.index) === '/') { // Line
+            while (this.source.charAt(++this.index) !== '\n')
+              if (this.index == this.source.length)
+                return null;
+              ++this.index;
+            ++this.line;
+            repeat = true;
+          } else if ((next = this.source.charAt(this.index)) === '*') { /* Block */
+            do {
+              if (next === '\n')
+                ++this.line;
+              if (++this.index === this.source.length)
+                return null;
+              prev = next;
+              next = this.source.charAt(this.index);
+            } while (prev !== '*' || next !== '/');
+            ++this.index;
+            repeat = true;
+          } else
+            return '/';
+        }
+      } while (repeat);
+
+      if (this.index === this.source.length)
+        return null;
+
+      // Read the next token
+      var end = this.index;
+      Lang.DELIM.lastIndex = 0;
+      var delim = Lang.DELIM.test(this.source.charAt(end++));
+      if (!delim)
+        while (end < this.source.length && !Lang.DELIM.test(this.source.charAt(end)))
+          ++end;
+      var token = this.source.substring(this.index, this.index = end);
+      if (token === '"' || token === "'")
+        this._stringOpen = token;
+      return token;
+    };
+
+    /**
+     * Peeks for the next token.
+     * @return {?string} Token or `null` on EOF
+     * @expose
+     */
+    TokenizerPrototype.peek = function () {
+      if (this.stack.length === 0) {
+        var token = this.next();
+        if (token === null)
+          return null;
+        this.stack.push(token);
+      }
+      return this.stack[0];
+    };
+
+    /**
+     * Skips a specific token and throws if it differs.
+     * @param {string} expected Expected token
+     * @throws {Error} If the actual token differs
+     */
+    TokenizerPrototype.skip = function (expected) {
+      var actual = this.next();
+      if (actual !== expected)
+        throw Error("illegal '" + actual + "', '" + expected + "' expected");
+    };
+
+    /**
+     * Omits an optional token.
+     * @param {string} expected Expected optional token
+     * @returns {boolean} `true` if the token exists
+     */
+    TokenizerPrototype.omit = function (expected) {
+      if (this.peek() === expected) {
+        this.next();
+        return true;
+      }
+      return false;
+    };
+
+    /**
+     * Returns a string representation of this object.
+     * @return {string} String representation as of "Tokenizer(index/length)"
+     * @expose
+     */
+    TokenizerPrototype.toString = function () {
+      return "Tokenizer (" + this.index + "/" + this.source.length + " at line " + this.line + ")";
+    };
+
+    /**
+     * @alias ProtoBuf.DotProto.Tokenizer
+     * @expose
+     */
+    DotProto.Tokenizer = Tokenizer;
+
+    /**
+     * Constructs a new Parser.
+     * @exports ProtoBuf.DotProto.Parser
+     * @class prototype parser
+     * @param {string} source Source
+     * @constructor
+     */
+    var Parser = function (source) {
+
+      /**
+       * Tokenizer.
+       * @type {!ProtoBuf.DotProto.Tokenizer}
+       * @expose
+       */
+      this.tn = new Tokenizer(source);
+
+      /**
+       * Whether parsing proto3 or not.
+       * @type {boolean}
+       */
+      this.proto3 = false;
+    };
+
+    /**
+     * @alias ProtoBuf.DotProto.Parser.prototype
+     * @inner
+     */
+    var ParserPrototype = Parser.prototype;
+
+    /**
+     * Parses the source.
+     * @returns {!Object}
+     * @throws {Error} If the source cannot be parsed
+     * @expose
+     */
+    ParserPrototype.parse = function () {
+      var topLevel = {
+        "name": "[ROOT]", // temporary
+        "package": null,
+        "messages": [],
+        "enums": [],
+        "imports": [],
+        "options": {},
+        "services": []
+          // "syntax": undefined
+      };
+      var token,
+        head = true;
+      try {
+        while (token = this.tn.next()) {
+          switch (token) {
+          case 'package':
+            if (!head || topLevel["package"] !== null)
+              throw Error("unexpected 'package'");
+            token = this.tn.next();
+            if (!Lang.TYPEREF.test(token))
+              throw Error("illegal package name: " + token);
+            this.tn.skip(";");
+            topLevel["package"] = token;
+            break;
+          case 'import':
+            if (!head)
+              throw Error("unexpected 'import'");
+            token = this.tn.peek();
+            if (token === "public") // ignored
+              this.tn.next();
+            token = this._readString();
+            this.tn.skip(";");
+            topLevel["imports"].push(token);
+            break;
+          case 'syntax':
+            if (!head)
+              throw Error("unexpected 'syntax'");
+            this.tn.skip("=");
+            if ((topLevel["syntax"] = this._readString()) === "proto3")
+              this.proto3 = true;
+            this.tn.skip(";");
+            break;
+          case 'message':
+            this._parseMessage(topLevel, null);
+            head = false;
+            break;
+          case 'enum':
+            this._parseEnum(topLevel);
+            head = false;
+            break;
+          case 'option':
+            this._parseOption(topLevel);
+            break;
+          case 'service':
+            this._parseService(topLevel);
+            break;
+          case 'extend':
+            this._parseExtend(topLevel);
+            break;
+          default:
+            throw Error("unexpected '" + token + "'");
+          }
+        }
+      } catch (e) {
+        e.message = "Parse error at line " + this.tn.line + ": " + e.message;
+        throw e;
+      }
+      delete topLevel["name"];
+      return topLevel;
+    };
+
+    /**
+     * Parses the specified source.
+     * @returns {!Object}
+     * @throws {Error} If the source cannot be parsed
+     * @expose
+     */
+    Parser.parse = function (source) {
+      return new Parser(source).parse();
+    };
+
+    // ----- Conversion ------
+
+    /**
+     * Converts a numerical string to an id.
+     * @param {string} value
+     * @param {boolean=} mayBeNegative
+     * @returns {number}
+     * @inner
+     */
+    function mkId(value, mayBeNegative) {
+      var id = -1,
+        sign = 1;
+      if (value.charAt(0) == '-') {
+        sign = -1;
+        value = value.substring(1);
+      }
+      if (Lang.NUMBER_DEC.test(value))
+        id = parseInt(value);
+      else if (Lang.NUMBER_HEX.test(value))
+        id = parseInt(value.substring(2), 16);
+      else if (Lang.NUMBER_OCT.test(value))
+        id = parseInt(value.substring(1), 8);
+      else
+        throw Error("illegal id value: " + (sign < 0 ? '-' : '') + value);
+      id = (sign * id) | 0; // Force to 32bit
+      if (!mayBeNegative && id < 0)
+        throw Error("illegal id value: " + (sign < 0 ? '-' : '') + value);
+      return id;
+    }
+
+    /**
+     * Converts a numerical string to a number.
+     * @param {string} val
+     * @returns {number}
+     * @inner
+     */
+    function mkNumber(val) {
+      var sign = 1;
+      if (val.charAt(0) == '-') {
+        sign = -1;
+        val = val.substring(1);
+      }
+      if (Lang.NUMBER_DEC.test(val))
+        return sign * parseInt(val, 10);
+      else if (Lang.NUMBER_HEX.test(val))
+        return sign * parseInt(val.substring(2), 16);
+      else if (Lang.NUMBER_OCT.test(val))
+        return sign * parseInt(val.substring(1), 8);
+      else if (val === 'inf')
+        return sign * Infinity;
+      else if (val === 'nan')
+        return NaN;
+      else if (Lang.NUMBER_FLT.test(val))
+        return sign * parseFloat(val);
+      throw Error("illegal number value: " + (sign < 0 ? '-' : '') + val);
+    }
+
+    // ----- Reading ------
+
+    /**
+     * Reads a string.
+     * @returns {string}
+     * @private
+     */
+    ParserPrototype._readString = function () {
+      var value = "",
+        token,
+        delim;
+      do {
+        delim = this.tn.next();
+        if (delim !== "'" && delim !== '"')
+          throw Error("illegal string delimiter: " + delim);
+        value += this.tn.next();
+        this.tn.skip(delim);
+        token = this.tn.peek();
+      } while (token === '"' || token === '"'); // multi line?
+      return value;
+    };
+
+    /**
+     * Reads a value.
+     * @param {boolean=} mayBeTypeRef
+     * @returns {number|boolean|string}
+     * @private
+     */
+    ParserPrototype._readValue = function (mayBeTypeRef) {
+      var token = this.tn.peek(),
+        value;
+      if (token === '"' || token === "'")
+        return this._readString();
+      this.tn.next();
+      if (Lang.NUMBER.test(token))
+        return mkNumber(token);
+      if (Lang.BOOL.test(token))
+        return (token.toLowerCase() === 'true');
+      if (mayBeTypeRef && Lang.TYPEREF.test(token))
+        return token;
+      throw Error("illegal value: " + token);
+
+    };
+
+    // ----- Parsing constructs -----
+
+    /**
+     * Parses a namespace option.
+     * @param {!Object} parent Parent definition
+     * @param {boolean=} isList
+     * @private
+     */
+    ParserPrototype._parseOption = function (parent, isList) {
+      var token = this.tn.next(),
+        custom = false;
+      if (token === '(') {
+        custom = true;
+        token = this.tn.next();
+      }
+      if (!Lang.TYPEREF.test(token))
+      // we can allow options of the form google.protobuf.* since they will just get ignored anyways
+      // if (!/google\.protobuf\./.test(token)) // FIXME: Why should that not be a valid typeref?
+        throw Error("illegal option name: " + token);
+      var name = token;
+      if (custom) { // (my_method_option).foo, (my_method_option), some_method_option, (foo.my_option).bar
+        this.tn.skip(')');
+        name = '(' + name + ')';
+        token = this.tn.peek();
+        if (Lang.FQTYPEREF.test(token)) {
+          name += token;
+          this.tn.next();
+        }
+      }
+      this.tn.skip('=');
+      this._parseOptionValue(parent, name);
+      if (!isList)
+        this.tn.skip(";");
+    };
+
+    /**
+     * Sets an option on the specified options object.
+     * @param {!Object.<string,*>} options
+     * @param {string} name
+     * @param {string|number|boolean} value
+     * @inner
+     */
+    function setOption(options, name, value) {
+      if (typeof options[name] === 'undefined')
+        options[name] = value;
+      else {
+        if (!Array.isArray(options[name]))
+          options[name] = [options[name]];
+        options[name].push(value);
+      }
+    }
+
+    /**
+     * Parses an option value.
+     * @param {!Object} parent
+     * @param {string} name
+     * @private
+     */
+    ParserPrototype._parseOptionValue = function (parent, name) {
+      var token = this.tn.peek();
+      if (token !== '{') { // Plain value
+        setOption(parent["options"], name, this._readValue(true));
+      } else { // Aggregate options
+        this.tn.skip("{");
+        while ((token = this.tn.next()) !== '}') {
+          if (!Lang.NAME.test(token))
+            throw Error("illegal option name: " + name + "." + token);
+          if (this.tn.omit(":"))
+            setOption(parent["options"], name + "." + token, this._readValue(true));
+          else
+            this._parseOptionValue(parent, name + "." + token);
+        }
+      }
+    };
+
+    /**
+     * Parses a service definition.
+     * @param {!Object} parent Parent definition
+     * @private
+     */
+    ParserPrototype._parseService = function (parent) {
+      var token = this.tn.next();
+      if (!Lang.NAME.test(token))
+        throw Error("illegal service name at line " + this.tn.line + ": " + token);
+      var name = token;
+      var svc = {
+        "name": name,
+        "rpc": {},
+        "options": {}
+      };
+      this.tn.skip("{");
+      while ((token = this.tn.next()) !== '}') {
+        if (token === "option")
+          this._parseOption(svc);
+        else if (token === 'rpc')
+          this._parseServiceRPC(svc);
+        else
+          throw Error("illegal service token: " + token);
+      }
+      this.tn.omit(";");
+      parent["services"].push(svc);
+    };
+
+    /**
+     * Parses a RPC service definition of the form ['rpc', name, (request), 'returns', (response)].
+     * @param {!Object} svc Service definition
+     * @private
+     */
+    ParserPrototype._parseServiceRPC = function (svc) {
+      var type = "rpc",
+        token = this.tn.next();
+      if (!Lang.NAME.test(token))
+        throw Error("illegal rpc service method name: " + token);
+      var name = token;
+      var method = {
+        "request": null,
+        "response": null,
+        "request_stream": false,
+        "response_stream": false,
+        "options": {}
+      };
+      this.tn.skip("(");
+      token = this.tn.next();
+      if (token.toLowerCase() === "stream") {
+        method["request_stream"] = true;
+        token = this.tn.next();
+      }
+      if (!Lang.TYPEREF.test(token))
+        throw Error("illegal rpc service request type: " + token);
+      method["request"] = token;
+      this.tn.skip(")");
+      token = this.tn.next();
+      if (token.toLowerCase() !== "returns")
+        throw Error("illegal rpc service request type delimiter: " + token);
+      this.tn.skip("(");
+      token = this.tn.next();
+      if (token.toLowerCase() === "stream") {
+        method["response_stream"] = true;
+        token = this.tn.next();
+      }
+      method["response"] = token;
+      this.tn.skip(")");
+      token = this.tn.peek();
+      if (token === '{') {
+        this.tn.next();
+        while ((token = this.tn.next()) !== '}') {
+          if (token === 'option')
+            this._parseOption(method);
+          else
+            throw Error("illegal rpc service token: " + token);
+        }
+        this.tn.omit(";");
+      } else
+        this.tn.skip(";");
+      if (typeof svc[type] === 'undefined')
+        svc[type] = {};
+      svc[type][name] = method;
+    };
+
+    /**
+     * Parses a message definition.
+     * @param {!Object} parent Parent definition
+     * @param {!Object=} fld Field definition if this is a group
+     * @returns {!Object}
+     * @private
+     */
+    ParserPrototype._parseMessage = function (parent, fld) {
+      var isGroup = !!fld,
+        token = this.tn.next();
+      var msg = {
+        "name": "",
+        "fields": [],
+        "enums": [],
+        "messages": [],
+        "options": {},
+        "oneofs": {}
+        // "extensions": undefined
+      };
+      if (!Lang.NAME.test(token))
+        throw Error("illegal " + (isGroup ? "group" : "message") + " name: " + token);
+      msg["name"] = token;
+      if (isGroup) {
+        this.tn.skip("=");
+        fld["id"] = mkId(this.tn.next());
+        msg["isGroup"] = true;
+      }
+      token = this.tn.peek();
+      if (token === '[' && fld)
+        this._parseFieldOptions(fld);
+      this.tn.skip("{");
+      while ((token = this.tn.next()) !== '}') {
+        if (Lang.RULE.test(token))
+          this._parseMessageField(msg, token);
+        else if (token === "oneof")
+          this._parseMessageOneOf(msg);
+        else if (token === "enum")
+          this._parseEnum(msg);
+        else if (token === "message")
+          this._parseMessage(msg);
+        else if (token === "option")
+          this._parseOption(msg);
+        else if (token === "extensions")
+          this._parseExtensions(msg);
+        else if (token === "extend")
+          this._parseExtend(msg);
+        else if (Lang.TYPEREF.test(token)) {
+          if (!this.proto3)
+            throw Error("illegal field rule: " + token);
+          this._parseMessageField(msg, "optional", token);
+        } else
+          throw Error("illegal message token: " + token);
+      }
+      this.tn.omit(";");
+      parent["messages"].push(msg);
+      return msg;
+    };
+
+    /**
+     * Parses a message field.
+     * @param {!Object} msg Message definition
+     * @param {string} rule Field rule
+     * @param {string=} type Field type if already known (never known for maps)
+     * @returns {!Object} Field descriptor
+     * @private
+     */
+    ParserPrototype._parseMessageField = function (msg, rule, type) {
+      if (!Lang.RULE.test(rule))
+        throw Error("illegal message field rule: " + rule);
+      var fld = {
+        "rule": rule,
+        "type": "",
+        "name": "",
+        "options": {},
+        "id": 0
+      };
+      var token;
+      if (rule === "map") {
+
+        if (type)
+          throw Error("illegal type: " + type);
+        this.tn.skip('<');
+        token = this.tn.next();
+        if (!Lang.TYPE.test(token) && !Lang.TYPEREF.test(token))
+          throw Error("illegal message field type: " + token);
+        fld["keytype"] = token;
+        this.tn.skip(',');
+        token = this.tn.next();
+        if (!Lang.TYPE.test(token) && !Lang.TYPEREF.test(token))
+          throw Error("illegal message field: " + token);
+        fld["type"] = token;
+        this.tn.skip('>');
+        token = this.tn.next();
+        if (!Lang.NAME.test(token))
+          throw Error("illegal message field name: " + token);
+        fld["name"] = token;
+        this.tn.skip("=");
+        fld["id"] = mkId(this.tn.next());
+        token = this.tn.peek();
+        if (token === '[')
+          this._parseFieldOptions(fld);
+        this.tn.skip(";");
+
+      } else {
+
+        type = typeof type !== 'undefined' ? type : this.tn.next();
+
+        if (type === "group") {
+
+          // "A [legacy] group simply combines a nested message type and a field into a single declaration. In your
+          // code, you can treat this message just as if it had a Result type field called result (the latter name is
+          // converted to lower-case so that it does not conflict with the former)."
+          var grp = this._parseMessage(msg, fld);
+          if (!/^[A-Z]/.test(grp["name"]))
+            throw Error('illegal group name: ' + grp["name"]);
+          fld["type"] = grp["name"];
+          fld["name"] = grp["name"].toLowerCase();
+          this.tn.omit(";");
+
+        } else {
+
+          if (!Lang.TYPE.test(type) && !Lang.TYPEREF.test(type))
+            throw Error("illegal message field type: " + type);
+          fld["type"] = type;
+          token = this.tn.next();
+          if (!Lang.NAME.test(token))
+            throw Error("illegal message field name: " + token);
+          fld["name"] = token;
+          this.tn.skip("=");
+          fld["id"] = mkId(this.tn.next());
+          token = this.tn.peek();
+          if (token === "[")
+            this._parseFieldOptions(fld);
+          this.tn.skip(";");
+
+        }
+      }
+      msg["fields"].push(fld);
+      return fld;
+    };
+
+    /**
+     * Parses a message oneof.
+     * @param {!Object} msg Message definition
+     * @private
+     */
+    ParserPrototype._parseMessageOneOf = function (msg) {
+      var token = this.tn.next();
+      if (!Lang.NAME.test(token))
+        throw Error("illegal oneof name: " + token);
+      var name = token,
+        fld;
+      var fields = [];
+      this.tn.skip("{");
+      while ((token = this.tn.next()) !== "}") {
+        fld = this._parseMessageField(msg, "optional", token);
+        fld["oneof"] = name;
+        fields.push(fld["id"]);
+      }
+      this.tn.omit(";");
+      msg["oneofs"][name] = fields;
+    };
+
+    /**
+     * Parses a set of field option definitions.
+     * @param {!Object} fld Field definition
+     * @private
+     */
+    ParserPrototype._parseFieldOptions = function (fld) {
+      this.tn.skip("[");
+      var token,
+        first = true;
+      while ((token = this.tn.peek()) !== ']') {
+        if (!first)
+          this.tn.skip(",");
+        this._parseOption(fld, true);
+        first = false;
+      }
+      this.tn.next();
+    };
+
+    /**
+     * Parses an enum.
+     * @param {!Object} msg Message definition
+     * @private
+     */
+    ParserPrototype._parseEnum = function (msg) {
+      var enm = {
+        "name": "",
+        "values": [],
+        "options": {}
+      };
+      var token = this.tn.next();
+      if (!Lang.NAME.test(token))
+        throw Error("illegal name: " + token);
+      enm["name"] = token;
+      this.tn.skip("{");
+      while ((token = this.tn.next()) !== '}') {
+        if (token === "option")
+          this._parseOption(enm);
+        else {
+          if (!Lang.NAME.test(token))
+            throw Error("illegal name: " + token);
+          this.tn.skip("=");
+          var val = {
+            "name": token,
+            "id": mkId(this.tn.next(), true)
+          };
+          token = this.tn.peek();
+          if (token === "[")
+            this._parseFieldOptions({ "options": {} });
+          this.tn.skip(";");
+          enm["values"].push(val);
+        }
+      }
+      this.tn.omit(";");
+      msg["enums"].push(enm);
+    };
+
+    /**
+     * Parses an extensions statement.
+     * @param {!Object} msg Message object
+     * @private
+     */
+    ParserPrototype._parseExtensions = function (msg) {
+      var token = this.tn.next(),
+        range = [];
+      if (token === "min")
+        range.push(ProtoBuf.ID_MIN);
+      else if (token === "max")
+        range.push(ProtoBuf.ID_MAX);
+      else
+        range.push(mkNumber(token));
+      this.tn.skip("to");
+      token = this.tn.next();
+      if (token === "min")
+        range.push(ProtoBuf.ID_MIN);
+      else if (token === "max")
+        range.push(ProtoBuf.ID_MAX);
+      else
+        range.push(mkNumber(token));
+      this.tn.skip(";");
+      msg["extensions"] = range;
+    };
+
+    /**
+     * Parses an extend block.
+     * @param {!Object} parent Parent object
+     * @private
+     */
+    ParserPrototype._parseExtend = function (parent) {
+      var token = this.tn.next();
+      if (!Lang.TYPEREF.test(token))
+        throw Error("illegal extend reference: " + token);
+      var ext = {
+        "ref": token,
+        "fields": []
+      };
+      this.tn.skip("{");
+      while ((token = this.tn.next()) !== '}') {
+        if (Lang.RULE.test(token))
+          this._parseMessageField(ext, token);
+        else if (Lang.TYPEREF.test(token)) {
+          if (!this.proto3)
+            throw Error("illegal field rule: " + token);
+          this._parseMessageField(ext, "optional", token);
+        } else
+          throw Error("illegal extend token: " + token);
+      }
+      this.tn.omit(";");
+      parent["messages"].push(ext);
+      return ext;
+    };
+
+    // ----- General -----
+
+    /**
+     * Returns a string representation of this parser.
+     * @returns {string}
+     */
+    ParserPrototype.toString = function () {
+      return "Parser at line " + this.tn.line;
+    };
+
+    /**
+     * @alias ProtoBuf.DotProto.Parser
+     * @expose
+     */
+    DotProto.Parser = Parser;
+
+    return DotProto;
+
+  })(ProtoBuf, ProtoBuf.Lang);
+
+  /**
+   * @alias ProtoBuf.Reflect
+   * @expose
+   */
+  ProtoBuf.Reflect = (function (ProtoBuf) {
+    "use strict";
+
+    /**
+     * Reflection types.
+     * @exports ProtoBuf.Reflect
+     * @namespace
+     */
+    var Reflect = {};
+
+    /**
+     * Constructs a Reflect base class.
+     * @exports ProtoBuf.Reflect.T
+     * @constructor
+     * @abstract
+     * @param {!ProtoBuf.Builder} builder Builder reference
+     * @param {?ProtoBuf.Reflect.T} parent Parent object
+     * @param {string} name Object name
+     */
+    var T = function (builder, parent, name) {
+
+      /**
+       * Builder reference.
+       * @type {!ProtoBuf.Builder}
+       * @expose
+       */
+      this.builder = builder;
+
+      /**
+       * Parent object.
+       * @type {?ProtoBuf.Reflect.T}
+       * @expose
+       */
+      this.parent = parent;
+
+      /**
+       * Object name in namespace.
+       * @type {string}
+       * @expose
+       */
+      this.name = name;
+
+      /**
+       * Fully qualified class name
+       * @type {string}
+       * @expose
+       */
+      this.className;
+    };
+
+    /**
+     * @alias ProtoBuf.Reflect.T.prototype
+     * @inner
+     */
+    var TPrototype = T.prototype;
+
+    /**
+     * Returns the fully qualified name of this object.
+     * @returns {string} Fully qualified name as of ".PATH.TO.THIS"
+     * @expose
+     */
+    TPrototype.fqn = function () {
+      var name = this.name,
+        ptr = this;
+      do {
+        ptr = ptr.parent;
+        if (ptr == null)
+          break;
+        name = ptr.name + "." + name;
+      } while (true);
+      return name;
+    };
+
+    /**
+     * Returns a string representation of this Reflect object (its fully qualified name).
+     * @param {boolean=} includeClass Set to true to include the class name. Defaults to false.
+     * @return String representation
+     * @expose
+     */
+    TPrototype.toString = function (includeClass) {
+      return (includeClass ? this.className + " " : "") + this.fqn();
+    };
+
+    /**
+     * Builds this type.
+     * @throws {Error} If this type cannot be built directly
+     * @expose
+     */
+    TPrototype.build = function () {
+      throw Error(this.toString(true) + " cannot be built directly");
+    };
+
+    /**
+     * @alias ProtoBuf.Reflect.T
+     * @expose
+     */
+    Reflect.T = T;
+
+    /**
+     * Constructs a new Namespace.
+     * @exports ProtoBuf.Reflect.Namespace
+     * @param {!ProtoBuf.Builder} builder Builder reference
+     * @param {?ProtoBuf.Reflect.Namespace} parent Namespace parent
+     * @param {string} name Namespace name
+     * @param {Object.<string,*>=} options Namespace options
+     * @param {string?} syntax The syntax level of this definition (e.g., proto3)
+     * @constructor
+     * @extends ProtoBuf.Reflect.T
+     */
+    var Namespace = function (builder, parent, name, options, syntax) {
+      T.call(this, builder, parent, name);
+
+      /**
+       * @override
+       */
+      this.className = "Namespace";
+
+      /**
+       * Children inside the namespace.
+       * @type {!Array.<ProtoBuf.Reflect.T>}
+       */
+      this.children = [];
+
+      /**
+       * Options.
+       * @type {!Object.<string, *>}
+       */
+      this.options = options || {};
+
+      /**
+       * Syntax level (e.g., proto2 or proto3).
+       * @type {!string}
+       */
+      this.syntax = syntax || "proto2";
+    };
+
+    /**
+     * @alias ProtoBuf.Reflect.Namespace.prototype
+     * @inner
+     */
+    var NamespacePrototype = Namespace.prototype = Object.create(T.prototype);
+
+    /**
+     * Returns an array of the namespace's children.
+     * @param {ProtoBuf.Reflect.T=} type Filter type (returns instances of this type only). Defaults to null (all children).
+     * @return {Array.<ProtoBuf.Reflect.T>}
+     * @expose
+     */
+    NamespacePrototype.getChildren = function (type) {
+      type = type || null;
+      if (type == null)
+        return this.children.slice();
+      var children = [];
+      for (var i = 0, k = this.children.length; i < k; ++i)
+        if (this.children[i] instanceof type)
+          children.push(this.children[i]);
+      return children;
+    };
+
+    /**
+     * Adds a child to the namespace.
+     * @param {ProtoBuf.Reflect.T} child Child
+     * @throws {Error} If the child cannot be added (duplicate)
+     * @expose
+     */
+    NamespacePrototype.addChild = function (child) {
+      var other;
+      if (other = this.getChild(child.name)) {
+        // Try to revert camelcase transformation on collision
+        if (other instanceof Message.Field && other.name !== other.originalName && this.getChild(other.originalName) === null)
+          other.name = other.originalName; // Revert previous first (effectively keeps both originals)
+        else if (child instanceof Message.Field && child.name !== child.originalName && this.getChild(child.originalName) === null)
+          child.name = child.originalName;
+        else
+          throw Error("Duplicate name in namespace " + this.toString(true) + ": " + child.name);
+      }
+      this.children.push(child);
+    };
+
+    /**
+     * Gets a child by its name or id.
+     * @param {string|number} nameOrId Child name or id
+     * @return {?ProtoBuf.Reflect.T} The child or null if not found
+     * @expose
+     */
+    NamespacePrototype.getChild = function (nameOrId) {
+      var key = typeof nameOrId === 'number' ? 'id' : 'name';
+      for (var i = 0, k = this.children.length; i < k; ++i)
+        if (this.children[i][key] === nameOrId)
+          return this.children[i];
+      return null;
+    };
+
+    /**
+     * Resolves a reflect object inside of this namespace.
+     * @param {string|!Array.<string>} qn Qualified name to resolve
+     * @param {boolean=} excludeNonNamespace Excludes non-namespace types, defaults to `false`
+     * @return {?ProtoBuf.Reflect.Namespace} The resolved type or null if not found
+     * @expose
+     */
+    NamespacePrototype.resolve = function (qn, excludeNonNamespace) {
+      var part = typeof qn === 'string' ? qn.split(".") : qn,
+        ptr = this,
+        i = 0;
+      if (part[i] === "") { // Fully qualified name, e.g. ".My.Message'
+        while (ptr.parent !== null)
+          ptr = ptr.parent;
+        i++;
+      }
+      var child;
+      do {
+        do {
+          if (!(ptr instanceof Reflect.Namespace)) {
+            ptr = null;
+            break;
+          }
+          child = ptr.getChild(part[i]);
+          if (!child || !(child instanceof Reflect.T) || (excludeNonNamespace && !(child instanceof Reflect.Namespace))) {
+            ptr = null;
+            break;
+          }
+          ptr = child;
+          i++;
+        } while (i < part.length);
+        if (ptr != null)
+          break; // Found
+        // Else search the parent
+        if (this.parent !== null)
+          return this.parent.resolve(qn, excludeNonNamespace);
+      } while (ptr != null);
+      return ptr;
+    };
+
+    /**
+     * Determines the shortest qualified name of the specified type, if any, relative to this namespace.
+     * @param {!ProtoBuf.Reflect.T} t Reflection type
+     * @returns {string} The shortest qualified name or, if there is none, the fqn
+     * @expose
+     */
+    NamespacePrototype.qn = function (t) {
+      var part = [],
+        ptr = t;
+      do {
+        part.unshift(ptr.name);
+        ptr = ptr.parent;
+      } while (ptr !== null);
+      for (var len = 1; len <= part.length; len++) {
+        var qn = part.slice(part.length - len);
+        if (t === this.resolve(qn, t instanceof Reflect.Namespace))
+          return qn.join(".");
+      }
+      return t.fqn();
+    };
+
+    /**
+     * Builds the namespace and returns the runtime counterpart.
+     * @return {Object.<string,Function|Object>} Runtime namespace
+     * @expose
+     */
+    NamespacePrototype.build = function () {
+      /** @dict */
+      var ns = {};
+      var children = this.children;
+      for (var i = 0, k = children.length, child; i < k; ++i) {
+        child = children[i];
+        if (child instanceof Namespace)
+          ns[child.name] = child.build();
+      }
+      if (Object.defineProperty)
+        Object.defineProperty(ns, "$options", { "value": this.buildOpt() });
+      return ns;
+    };
+
+    /**
+     * Builds the namespace's '$options' property.
+     * @return {Object.<string,*>}
+     */
+    NamespacePrototype.buildOpt = function () {
+      var opt = {},
+        keys = Object.keys(this.options);
+      for (var i = 0, k = keys.length; i < k; ++i) {
+        var key = keys[i],
+          val = this.options[keys[i]];
+        // TODO: Options are not resolved, yet.
+        // if (val instanceof Namespace) {
+        //     opt[key] = val.build();
+        // } else {
+        opt[key] = val;
+        // }
+      }
+      return opt;
+    };
+
+    /**
+     * Gets the value assigned to the option with the specified name.
+     * @param {string=} name Returns the option value if specified, otherwise all options are returned.
+     * @return {*|Object.<string,*>}null} Option value or NULL if there is no such option
+     */
+    NamespacePrototype.getOption = function (name) {
+      if (typeof name === 'undefined')
+        return this.options;
+      return typeof this.options[name] !== 'undefined' ? this.options[name] : null;
+    };
+
+    /**
+     * @alias ProtoBuf.Reflect.Namespace
+     * @expose
+     */
+    Reflect.Namespace = Namespace;
+
+    /**
+     * Constructs a new Element implementation that checks and converts values for a
+     * particular field type, as appropriate.
+     *
+     * An Element represents a single value: either the value of a singular field,
+     * or a value contained in one entry of a repeated field or map field. This
+     * class does not implement these higher-level concepts; it only encapsulates
+     * the low-level typechecking and conversion.
+     *
+     * @exports ProtoBuf.Reflect.Element
+     * @param {{name: string, wireType: number}} type Resolved data type
+     * @param {ProtoBuf.Reflect.T|null} resolvedType Resolved type, if relevant
+     * (e.g. submessage field).
+     * @param {boolean} isMapKey Is this element a Map key? The value will be
+     * converted to string form if so.
+     * @param {string} syntax Syntax level of defining message type, e.g.,
+     * proto2 or proto3.
+     * @constructor
+     */
+    var Element = function (type, resolvedType, isMapKey, syntax) {
+
+      /**
+       * Element type, as a string (e.g., int32).
+       * @type {{name: string, wireType: number}}
+       */
+      this.type = type;
+
+      /**
+       * Element type reference to submessage or enum definition, if needed.
+       * @type {ProtoBuf.Reflect.T|null}
+       */
+      this.resolvedType = resolvedType;
+
+      /**
+       * Element is a map key.
+       * @type {boolean}
+       */
+      this.isMapKey = isMapKey;
+
+      /**
+       * Syntax level of defining message type, e.g., proto2 or proto3.
+       * @type {string}
+       */
+      this.syntax = syntax;
+
+      if (isMapKey && ProtoBuf.MAP_KEY_TYPES.indexOf(type) < 0)
+        throw Error("Invalid map key type: " + type.name);
+    };
+
+    var ElementPrototype = Element.prototype;
+
+    /**
+     * Obtains a (new) default value for the specified type.
+     * @param type {string|{name: string, wireType: number}} Field type
+     * @returns {*} Default value
+     * @inner
+     */
+    function mkDefault(type) {
+      if (typeof type === 'string')
+        type = ProtoBuf.TYPES[type];
+      if (typeof type.defaultValue === 'undefined')
+        throw Error("default value for type " + type.name + " is not supported");
+      if (type == ProtoBuf.TYPES["bytes"])
+        return new ByteBuffer(0);
+      return type.defaultValue;
+    }
+
+    /**
+     * Returns the default value for this field in proto3.
+     * @function
+     * @param type {string|{name: string, wireType: number}} the field type
+     * @returns {*} Default value
+     */
+    Element.defaultFieldValue = mkDefault;
+
+    /**
+     * Makes a Long from a value.
+     * @param {{low: number, high: number, unsigned: boolean}|string|number} value Value
+     * @param {boolean=} unsigned Whether unsigned or not, defaults to reuse it from Long-like objects or to signed for
+     *  strings and numbers
+     * @returns {!Long}
+     * @throws {Error} If the value cannot be converted to a Long
+     * @inner
+     */
+    function mkLong(value, unsigned) {
+      if (value && typeof value.low === 'number' && typeof value.high === 'number' && typeof value.unsigned === 'boolean' && value.low === value.low && value.high === value.high)
+        return new ProtoBuf.Long(value.low, value.high, typeof unsigned === 'undefined' ? value.unsigned : unsigned);
+      if (typeof value === 'string')
+        return ProtoBuf.Long.fromString(value, unsigned || false, 10);
+      if (typeof value === 'number')
+        return ProtoBuf.Long.fromNumber(value, unsigned || false);
+      throw Error("not convertible to Long");
+    }
+
+    /**
+     * Checks if the given value can be set for an element of this type (singular
+     * field or one element of a repeated field or map).
+     * @param {*} value Value to check
+     * @return {*} Verified, maybe adjusted, value
+     * @throws {Error} If the value cannot be verified for this element slot
+     * @expose
+     */
+    ElementPrototype.verifyValue = function (value) {
+      var fail = function (val, msg) {
+        throw Error("Illegal value for " + this.toString(true) + " of type " + this.type.name + ": " + val + " (" + msg + ")");
+      }.bind(this);
+      switch (this.type) {
+        // Signed 32bit
+      case ProtoBuf.TYPES["int32"]:
+      case ProtoBuf.TYPES["sint32"]:
+      case ProtoBuf.TYPES["sfixed32"]:
+        // Account for !NaN: value === value
+        if (typeof value !== 'number' || (value === value && value % 1 !== 0))
+          fail(typeof value, "not an integer");
+        return value > 4294967295 ? value | 0 : value;
+
+        // Unsigned 32bit
+      case ProtoBuf.TYPES["uint32"]:
+      case ProtoBuf.TYPES["fixed32"]:
+        if (typeof value !== 'number' || (value === value && value % 1 !== 0))
+          fail(typeof value, "not an integer");
+        return value < 0 ? value >>> 0 : value;
+
+        // Signed 64bit
+      case ProtoBuf.TYPES["int64"]:
+      case ProtoBuf.TYPES["sint64"]:
+      case ProtoBuf.TYPES["sfixed64"]:
+        {
+          if (ProtoBuf.Long)
+            try {
+              return mkLong(value, false);
+            } catch (e) {
+              fail(typeof value, e.message);
+            }
+          else
+            fail(typeof value, "requires Long.js");
+        }
+
+        // Unsigned 64bit
+      case ProtoBuf.TYPES["uint64"]:
+      case ProtoBuf.TYPES["fixed64"]:
+        {
+          if (ProtoBuf.Long)
+            try {
+              return mkLong(value, true);
+            } catch (e) {
+              fail(typeof value, e.message);
+            }
+          else
+            fail(typeof value, "requires Long.js");
+        }
+
+        // Bool
+      case ProtoBuf.TYPES["bool"]:
+        if (typeof value !== 'boolean')
+          fail(typeof value, "not a boolean");
+        return value;
+
+        // Float
+      case ProtoBuf.TYPES["float"]:
+      case ProtoBuf.TYPES["double"]:
+        if (typeof value !== 'number')
+          fail(typeof value, "not a number");
+        return value;
+
+        // Length-delimited string
+      case ProtoBuf.TYPES["string"]:
+        if (typeof value !== 'string' && !(value && value instanceof String))
+          fail(typeof value, "not a string");
+        return "" + value; // Convert String object to string
+
+        // Length-delimited bytes
+      case ProtoBuf.TYPES["bytes"]:
+        if (ByteBuffer.isByteBuffer(value))
+          return value;
+        return ByteBuffer.wrap(value, "base64");
+
+        // Constant enum value
+      case ProtoBuf.TYPES["enum"]:
+        {
+          var values = this.resolvedType.getChildren(ProtoBuf.Reflect.Enum.Value);
+          for (i = 0; i < values.length; i++)
+            if (values[i].name == value)
+              return values[i].id;
+            else if (values[i].id == value)
+            return values[i].id;
+
+          if (this.syntax === 'proto3') {
+            // proto3: just make sure it's an integer.
+            if (typeof value !== 'number' || (value === value && value % 1 !== 0))
+              fail(typeof value, "not an integer");
+            if (value > 4294967295 || value < 0)
+              fail(typeof value, "not in range for uint32");
+            return value;
+          } else {
+            // proto2 requires enum values to be valid.
+            fail(value, "not a valid enum value");
+          }
+        }
+        // Embedded message
+      case ProtoBuf.TYPES["group"]:
+      case ProtoBuf.TYPES["message"]:
+        {
+          if (!value || typeof value !== 'object')
+            fail(typeof value, "object expected");
+          if (value instanceof this.resolvedType.clazz)
+            return value;
+          if (value instanceof ProtoBuf.Builder.Message) {
+            // Mismatched type: Convert to object (see: https://github.com/dcodeIO/ProtoBuf.js/issues/180)
+            var obj = {};
+            for (var i in value)
+              if (value.hasOwnProperty(i))
+                obj[i] = value[i];
+            value = obj;
+          }
+          // Else let's try to construct one from a key-value object
+          return new(this.resolvedType.clazz)(value); // May throw for a hundred of reasons
+        }
+      }
+
+      // We should never end here
+      throw Error("[INTERNAL] Illegal value for " + this.toString(true) + ": " + value + " (undefined type " + this.type + ")");
+    };
+
+    /**
+     * Calculates the byte length of an element on the wire.
+     * @param {number} id Field number
+     * @param {*} value Field value
+     * @returns {number} Byte length
+     * @throws {Error} If the value cannot be calculated
+     * @expose
+     */
+    ElementPrototype.calculateLength = function (id, value) {
+      if (value === null) return 0; // Nothing to encode
+      // Tag has already been written
+      var n;
+      switch (this.type) {
+      case ProtoBuf.TYPES["int32"]:
+        return value < 0 ? ByteBuffer.calculateVarint64(value) : ByteBuffer.calculateVarint32(value);
+      case ProtoBuf.TYPES["uint32"]:
+        return ByteBuffer.calculateVarint32(value);
+      case ProtoBuf.TYPES["sint32"]:
+        return ByteBuffer.calculateVarint32(ByteBuffer.zigZagEncode32(value));
+      case ProtoBuf.TYPES["fixed32"]:
+      case ProtoBuf.TYPES["sfixed32"]:
+      case ProtoBuf.TYPES["float"]:
+        return 4;
+      case ProtoBuf.TYPES["int64"]:
+      case ProtoBuf.TYPES["uint64"]:
+        return ByteBuffer.calculateVarint64(value);
+      case ProtoBuf.TYPES["sint64"]:
+        return ByteBuffer.calculateVarint64(ByteBuffer.zigZagEncode64(value));
+      case ProtoBuf.TYPES["fixed64"]:
+      case ProtoBuf.TYPES["sfixed64"]:
+        return 8;
+      case ProtoBuf.TYPES["bool"]:
+        return 1;
+      case ProtoBuf.TYPES["enum"]:
+        return ByteBuffer.calculateVarint32(value);
+      case ProtoBuf.TYPES["double"]:
+        return 8;
+      case ProtoBuf.TYPES["string"]:
+        n = ByteBuffer.calculateUTF8Bytes(value);
+        return ByteBuffer.calculateVarint32(n) + n;
+      case ProtoBuf.TYPES["bytes"]:
+        if (value.remaining() < 0)
+          throw Error("Illegal value for " + this.toString(true) + ": " + value.remaining() + " bytes remaining");
+        return ByteBuffer.calculateVarint32(value.remaining()) + value.remaining();
+      case ProtoBuf.TYPES["message"]:
+        n = this.resolvedType.calculate(value);
+        return ByteBuffer.calculateVarint32(n) + n;
+      case ProtoBuf.TYPES["group"]:
+        n = this.resolvedType.calculate(value);
+        return n + ByteBuffer.calculateVarint32((id << 3) | ProtoBuf.WIRE_TYPES.ENDGROUP);
+      }
+      // We should never end here
+      throw Error("[INTERNAL] Illegal value to encode in " + this.toString(true) + ": " + value + " (unknown type)");
+    };
+
+    /**
+     * Encodes a value to the specified buffer. Does not encode the key.
+     * @param {number} id Field number
+     * @param {*} value Field value
+     * @param {ByteBuffer} buffer ByteBuffer to encode to
+     * @return {ByteBuffer} The ByteBuffer for chaining
+     * @throws {Error} If the value cannot be encoded
+     * @expose
+     */
+    ElementPrototype.encodeValue = function (id, value, buffer) {
+      if (value === null) return buffer; // Nothing to encode
+      // Tag has already been written
+
+      switch (this.type) {
+        // 32bit signed varint
+      case ProtoBuf.TYPES["int32"]:
+        // "If you use int32 or int64 as the type for a negative number, the resulting varint is always ten bytes
+        // long – it is, effectively, treated like a very large unsigned integer." (see #122)
+        if (value < 0)
+          buffer.writeVarint64(value);
+        else
+          buffer.writeVarint32(value);
+        break;
+
+        // 32bit unsigned varint
+      case ProtoBuf.TYPES["uint32"]:
+        buffer.writeVarint32(value);
+        break;
+
+        // 32bit varint zig-zag
+      case ProtoBuf.TYPES["sint32"]:
+        buffer.writeVarint32ZigZag(value);
+        break;
+
+        // Fixed unsigned 32bit
+      case ProtoBuf.TYPES["fixed32"]:
+        buffer.writeUint32(value);
+        break;
+
+        // Fixed signed 32bit
+      case ProtoBuf.TYPES["sfixed32"]:
+        buffer.writeInt32(value);
+        break;
+
+        // 64bit varint as-is
+      case ProtoBuf.TYPES["int64"]:
+      case ProtoBuf.TYPES["uint64"]:
+        buffer.writeVarint64(value); // throws
+        break;
+
+        // 64bit varint zig-zag
+      case ProtoBuf.TYPES["sint64"]:
+        buffer.writeVarint64ZigZag(value); // throws
+        break;
+
+        // Fixed unsigned 64bit
+      case ProtoBuf.TYPES["fixed64"]:
+        buffer.writeUint64(value); // throws
+        break;
+
+        // Fixed signed 64bit
+      case ProtoBuf.TYPES["sfixed64"]:
+        buffer.writeInt64(value); // throws
+        break;
+
+        // Bool
+      case ProtoBuf.TYPES["bool"]:
+        if (typeof value === 'string')
+          buffer.writeVarint32(value.toLowerCase() === 'false' ? 0 : !!value);
+        else
+          buffer.writeVarint32(value ? 1 : 0);
+        break;
+
+        // Constant enum value
+      case ProtoBuf.TYPES["enum"]:
+        buffer.writeVarint32(value);
+        break;
+
+        // 32bit float
+      case ProtoBuf.TYPES["float"]:
+        buffer.writeFloat32(value);
+        break;
+
+        // 64bit float
+      case ProtoBuf.TYPES["double"]:
+        buffer.writeFloat64(value);
+        break;
+
+        // Length-delimited string
+      case ProtoBuf.TYPES["string"]:
+        buffer.writeVString(value);
+        break;
+
+        // Length-delimited bytes
+      case ProtoBuf.TYPES["bytes"]:
+        if (value.remaining() < 0)
+          throw Error("Illegal value for " + this.toString(true) + ": " + value.remaining() + " bytes remaining");
+        var prevOffset = value.offset;
+        buffer.writeVarint32(value.remaining());
+        buffer.append(value);
+        value.offset = prevOffset;
+        break;
+
+        // Embedded message
+      case ProtoBuf.TYPES["message"]:
+        var bb = new ByteBuffer().LE();
+        this.resolvedType.encode(value, bb);
+        buffer.writeVarint32(bb.offset);
+        buffer.append(bb.flip());
+        break;
+
+        // Legacy group
+      case ProtoBuf.TYPES["group"]:
+        this.resolvedType.encode(value, buffer);
+        buffer.writeVarint32((id << 3) | ProtoBuf.WIRE_TYPES.ENDGROUP);
+        break;
+
+      default:
+        // We should never end here
+        throw Error("[INTERNAL] Illegal value to encode in " + this.toString(true) + ": " + value + " (unknown type)");
+      }
+      return buffer;
+    };
+
+    /**
+     * Decode one element value from the specified buffer.
+     * @param {ByteBuffer} buffer ByteBuffer to decode from
+     * @param {number} wireType The field wire type
+     * @param {number} id The field number
+     * @return {*} Decoded value
+     * @throws {Error} If the field cannot be decoded
+     * @expose
+     */
+    ElementPrototype.decode = function (buffer, wireType, id) {
+      if (wireType != this.type.wireType)
+        throw Error("Unexpected wire type for element");
+
+      var value, nBytes;
+      switch (this.type) {
+        // 32bit signed varint
+      case ProtoBuf.TYPES["int32"]:
+        return buffer.readVarint32() | 0;
+
+        // 32bit unsigned varint
+      case ProtoBuf.TYPES["uint32"]:
+        return buffer.readVarint32() >>> 0;
+
+        // 32bit signed varint zig-zag
+      case ProtoBuf.TYPES["sint32"]:
+        return buffer.readVarint32ZigZag() | 0;
+
+        // Fixed 32bit unsigned
+      case ProtoBuf.TYPES["fixed32"]:
+        return buffer.readUint32() >>> 0;
+
+      case ProtoBuf.TYPES["sfixed32"]:
+        return buffer.readInt32() | 0;
+
+        // 64bit signed varint
+      case ProtoBuf.TYPES["int64"]:
+        return buffer.readVarint64();
+
+        // 64bit unsigned varint
+      case ProtoBuf.TYPES["uint64"]:
+        return buffer.readVarint64().toUnsigned();
+
+        // 64bit signed varint zig-zag
+      case ProtoBuf.TYPES["sint64"]:
+        return buffer.readVarint64ZigZag();
+
+        // Fixed 64bit unsigned
+      case ProtoBuf.TYPES["fixed64"]:
+        return buffer.readUint64();
+
+        // Fixed 64bit signed
+      case ProtoBuf.TYPES["sfixed64"]:
+        return buffer.readInt64();
+
+        // Bool varint
+      case ProtoBuf.TYPES["bool"]:
+        return !!buffer.readVarint32();
+
+        // Constant enum value (varint)
+      case ProtoBuf.TYPES["enum"]:
+        // The following Builder.Message#set will already throw
+        return buffer.readVarint32();
+
+        // 32bit float
+      case ProtoBuf.TYPES["float"]:
+        return buffer.readFloat();
+
+        // 64bit float
+      case ProtoBuf.TYPES["double"]:
+        return buffer.readDouble();
+
+        // Length-delimited string
+      case ProtoBuf.TYPES["string"]:
+        return buffer.readVString();
+
+        // Length-delimited bytes
+      case ProtoBuf.TYPES["bytes"]:
+        {
+          nBytes = buffer.readVarint32();
+          if (buffer.remaining() < nBytes)
+            throw Error("Illegal number of bytes for " + this.toString(true) + ": " + nBytes + " required but got only " + buffer.remaining());
+          value = buffer.clone(); // Offset already set
+          value.limit = value.offset + nBytes;
+          buffer.offset += nBytes;
+          return value;
+        }
+
+        // Length-delimited embedded message
+      case ProtoBuf.TYPES["message"]:
+        {
+          nBytes = buffer.readVarint32();
+          return this.resolvedType.decode(buffer, nBytes);
+        }
+
+        // Legacy group
+      case ProtoBuf.TYPES["group"]:
+        return this.resolvedType.decode(buffer, -1, id);
+      }
+
+      // We should never end here
+      throw Error("[INTERNAL] Illegal decode type");
+    };
+
+    /**
+     * Converts a value from a string to the canonical element type.
+     *
+     * Legal only when isMapKey is true.
+     *
+     * @param {string} str The string value
+     * @returns {*} The value
+     */
+    ElementPrototype.valueFromString = function (str) {
+      if (!this.isMapKey) {
+        throw Error("valueFromString() called on non-map-key element");
+      }
+
+      switch (this.type) {
+      case ProtoBuf.TYPES["int32"]:
+      case ProtoBuf.TYPES["sint32"]:
+      case ProtoBuf.TYPES["sfixed32"]:
+      case ProtoBuf.TYPES["uint32"]:
+      case ProtoBuf.TYPES["fixed32"]:
+        return this.verifyValue(parseInt(str));
+
+      case ProtoBuf.TYPES["int64"]:
+      case ProtoBuf.TYPES["sint64"]:
+      case ProtoBuf.TYPES["sfixed64"]:
+      case ProtoBuf.TYPES["uint64"]:
+      case ProtoBuf.TYPES["fixed64"]:
+        // Long-based fields support conversions from string already.
+        return this.verifyValue(str);
+
+      case ProtoBuf.TYPES["bool"]:
+        return str === "true";
+
+      case ProtoBuf.TYPES["string"]:
+        return this.verifyValue(str);
+
+      case ProtoBuf.TYPES["bytes"]:
+        return ByteBuffer.fromBinary(str);
+      }
+    };
+
+    /**
+     * Converts a value from the canonical element type to a string.
+     *
+     * It should be the case that `valueFromString(valueToString(val))` returns
+     * a value equivalent to `verifyValue(val)` for every legal value of `val`
+     * according to this element type.
+     *
+     * This may be used when the element must be stored or used as a string,
+     * e.g., as a map key on an Object.
+     *
+     * Legal only when isMapKey is true.
+     *
+     * @param {*} val The value
+     * @returns {string} The string form of the value.
+     */
+    ElementPrototype.valueToString = function (value) {
+      if (!this.isMapKey) {
+        throw Error("valueToString() called on non-map-key element");
+      }
+
+      if (this.type === ProtoBuf.TYPES["bytes"]) {
+        return value.toString("binary");
+      } else {
+        return value.toString();
+      }
+    };
+
+    /**
+     * @alias ProtoBuf.Reflect.Element
+     * @expose
+     */
+    Reflect.Element = Element;
+
+    /**
+     * Constructs a new Message.
+     * @exports ProtoBuf.Reflect.Message
+     * @param {!ProtoBuf.Builder} builder Builder reference
+     * @param {!ProtoBuf.Reflect.Namespace} parent Parent message or namespace
+     * @param {string} name Message name
+     * @param {Object.<string,*>=} options Message options
+     * @param {boolean=} isGroup `true` if this is a legacy group
+     * @param {string?} syntax The syntax level of this definition (e.g., proto3)
+     * @constructor
+     * @extends ProtoBuf.Reflect.Namespace
+     */
+    var Message = function (builder, parent, name, options, isGroup, syntax) {
+      Namespace.call(this, builder, parent, name, options, syntax);
+
+      /**
+       * @override
+       */
+      this.className = "Message";
+
+      /**
+       * Extensions range.
+       * @type {!Array.<number>}
+       * @expose
+       */
+      this.extensions = [ProtoBuf.ID_MIN, ProtoBuf.ID_MAX];
+
+      /**
+       * Runtime message class.
+       * @type {?function(new:ProtoBuf.Builder.Message)}
+       * @expose
+       */
+      this.clazz = null;
+
+      /**
+       * Whether this is a legacy group or not.
+       * @type {boolean}
+       * @expose
+       */
+      this.isGroup = !!isGroup;
+
+      // The following cached collections are used to efficiently iterate over or look up fields when decoding.
+
+      /**
+       * Cached fields.
+       * @type {?Array.<!ProtoBuf.Reflect.Message.Field>}
+       * @private
+       */
+      this._fields = null;
+
+      /**
+       * Cached fields by id.
+       * @type {?Object.<number,!ProtoBuf.Reflect.Message.Field>}
+       * @private
+       */
+      this._fieldsById = null;
+
+      /**
+       * Cached fields by name.
+       * @type {?Object.<string,!ProtoBuf.Reflect.Message.Field>}
+       * @private
+       */
+      this._fieldsByName = null;
+    };
+
+    /**
+     * @alias ProtoBuf.Reflect.Message.prototype
+     * @inner
+     */
+    var MessagePrototype = Message.prototype = Object.create(Namespace.prototype);
+
+    /**
+     * Builds the message and returns the runtime counterpart, which is a fully functional class.
+     * @see ProtoBuf.Builder.Message
+     * @param {boolean=} rebuild Whether to rebuild or not, defaults to false
+     * @return {ProtoBuf.Reflect.Message} Message class
+     * @throws {Error} If the message cannot be built
+     * @expose
+     */
+    MessagePrototype.build = function (rebuild) {
+      if (this.clazz && !rebuild)
+        return this.clazz;
+
+      // Create the runtime Message class in its own scope
+      var clazz = (function (ProtoBuf, T) {
+
+        var fields = T.getChildren(ProtoBuf.Reflect.Message.Field),
+          oneofs = T.getChildren(ProtoBuf.Reflect.Message.OneOf);
+
+        /**
+         * Constructs a new runtime Message.
+         * @name ProtoBuf.Builder.Message
+         * @class Barebone of all runtime messages.
+         * @param {!Object.<string,*>|string} values Preset values
+         * @param {...string} var_args
+         * @constructor
+         * @throws {Error} If the message cannot be created
+         */
+        var Message = function (values, var_args) {
+          ProtoBuf.Builder.Message.call(this);
+
+          // Create virtual oneof properties
+          for (var i = 0, k = oneofs.length; i < k; ++i)
+            this[oneofs[i].name] = null;
+          // Create fields and set default values
+          for (i = 0, k = fields.length; i < k; ++i) {
+            var field = fields[i];
+            this[field.name] =
+              field.repeated ? [] :
+              (field.map ? new ProtoBuf.Map(field) : null);
+            if ((field.required || T.syntax === 'proto3') &&
+              field.defaultValue !== null)
+              this[field.name] = field.defaultValue;
+          }
+
+          if (arguments.length > 0) {
+            var value;
+            // Set field values from a values object
+            if (arguments.length === 1 && values !== null && typeof values === 'object' &&
+              /* not _another_ Message */
+              (typeof values.encode !== 'function' || values instanceof Message) &&
+              /* not a repeated field */
+              !Array.isArray(values) &&
+              /* not a Map */
+              !(values instanceof ProtoBuf.Map) &&
+              /* not a ByteBuffer */
+              !ByteBuffer.isByteBuffer(values) &&
+              /* not an ArrayBuffer */
+              !(values instanceof ArrayBuffer) &&
+              /* not a Long */
+              !(ProtoBuf.Long && values instanceof ProtoBuf.Long)) {
+              this.$set(values);
+            } else // Set field values from arguments, in declaration order
+              for (i = 0, k = arguments.length; i < k; ++i)
+              if (typeof (value = arguments[i]) !== 'undefined')
+                this.$set(fields[i].name, value); // May throw
+          }
+        };
+
+        /**
+         * @alias ProtoBuf.Builder.Message.prototype
+         * @inner
+         */
+        var MessagePrototype = Message.prototype = Object.create(ProtoBuf.Builder.Message.prototype);
+
+        /**
+         * Adds a value to a repeated field.
+         * @name ProtoBuf.Builder.Message#add
+         * @function
+         * @param {string} key Field name
+         * @param {*} value Value to add
+         * @param {boolean=} noAssert Whether to assert the value or not (asserts by default)
+         * @returns {!ProtoBuf.Builder.Message} this
+         * @throws {Error} If the value cannot be added
+         * @expose
+         */
+        MessagePrototype.add = function (key, value, noAssert) {
+          var field = T._fieldsByName[key];
+          if (!noAssert) {
+            if (!field)
+              throw Error(this + "#" + key + " is undefined");
+            if (!(field instanceof ProtoBuf.Reflect.Message.Field))
+              throw Error(this + "#" + key + " is not a field: " + field.toString(true)); // May throw if it's an enum or embedded message
+            if (!field.repeated)
+              throw Error(this + "#" + key + " is not a repeated field");
+            value = field.verifyValue(value, true);
+          }
+          if (this[key] === null)
+            this[key] = [];
+          this[key].push(value);
+          return this;
+        };
+
+        /**
+         * Adds a value to a repeated field. This is an alias for {@link ProtoBuf.Builder.Message#add}.
+         * @name ProtoBuf.Builder.Message#$add
+         * @function
+         * @param {string} key Field name
+         * @param {*} value Value to add
+         * @param {boolean=} noAssert Whether to assert the value or not (asserts by default)
+         * @returns {!ProtoBuf.Builder.Message} this
+         * @throws {Error} If the value cannot be added
+         * @expose
+         */
+        MessagePrototype.$add = MessagePrototype.add;
+
+        /**
+         * Sets a field's value.
+         * @name ProtoBuf.Builder.Message#set
+         * @function
+         * @param {string|!Object.<string,*>} keyOrObj String key or plain object holding multiple values
+         * @param {(*|boolean)=} value Value to set if key is a string, otherwise omitted
+         * @param {boolean=} noAssert Whether to not assert for an actual field / proper value type, defaults to `false`
+         * @returns {!ProtoBuf.Builder.Message} this
+         * @throws {Error} If the value cannot be set
+         * @expose
+         */
+        MessagePrototype.set = function (keyOrObj, value, noAssert) {
+          if (keyOrObj && typeof keyOrObj === 'object') {
+            noAssert = value;
+            for (var ikey in keyOrObj)
+              if (keyOrObj.hasOwnProperty(ikey) && typeof (value = keyOrObj[ikey]) !== 'undefined')
+                this.$set(ikey, value, noAssert);
+            return this;
+          }
+          var field = T._fieldsByName[keyOrObj];
+          if (!noAssert) {
+            if (!field)
+              throw Error(this + "#" + keyOrObj + " is not a field: undefined");
+            if (!(field instanceof ProtoBuf.Reflect.Message.Field))
+              throw Error(this + "#" + keyOrObj + " is not a field: " + field.toString(true));
+            this[field.name] = (value = field.verifyValue(value)); // May throw
+          } else
+            this[keyOrObj] = value;
+          if (field && field.oneof) { // Field is part of an OneOf (not a virtual OneOf field)
+            var currentField = this[field.oneof.name]; // Virtual field references currently set field
+            if (value !== null) {
+              if (currentField !== null && currentField !== field.name)
+                this[currentField] = null; // Clear currently set field
+              this[field.oneof.name] = field.name; // Point virtual field at this field
+            } else if ( /* value === null && */ currentField === keyOrObj)
+              this[field.oneof.name] = null; // Clear virtual field (current field explicitly cleared)
+          }
+          return this;
+        };
+
+        /**
+         * Sets a field's value. This is an alias for [@link ProtoBuf.Builder.Message#set}.
+         * @name ProtoBuf.Builder.Message#$set
+         * @function
+         * @param {string|!Object.<string,*>} keyOrObj String key or plain object holding multiple values
+         * @param {(*|boolean)=} value Value to set if key is a string, otherwise omitted
+         * @param {boolean=} noAssert Whether to not assert the value, defaults to `false`
+         * @throws {Error} If the value cannot be set
+         * @expose
+         */
+        MessagePrototype.$set = MessagePrototype.set;
+
+        /**
+         * Gets a field's value.
+         * @name ProtoBuf.Builder.Message#get
+         * @function
+         * @param {string} key Key
+         * @param {boolean=} noAssert Whether to not assert for an actual field, defaults to `false`
+         * @return {*} Value
+         * @throws {Error} If there is no such field
+         * @expose
+         */
+        MessagePrototype.get = function (key, noAssert) {
+          if (noAssert)
+            return this[key];
+          var field = T._fieldsByName[key];
+          if (!field || !(field instanceof ProtoBuf.Reflect.Message.Field))
+            throw Error(this + "#" + key + " is not a field: undefined");
+          if (!(field instanceof ProtoBuf.Reflect.Message.Field))
+            throw Error(this + "#" + key + " is not a field: " + field.toString(true));
+          return this[field.name];
+        };
+
+        /**
+         * Gets a field's value. This is an alias for {@link ProtoBuf.Builder.Message#$get}.
+         * @name ProtoBuf.Builder.Message#$get
+         * @function
+         * @param {string} key Key
+         * @return {*} Value
+         * @throws {Error} If there is no such field
+         * @expose
+         */
+        MessagePrototype.$get = MessagePrototype.get;
+
+        // Getters and setters
+
+        for (var i = 0; i < fields.length; i++) {
+          var field = fields[i];
+          // no setters for extension fields as these are named by their fqn
+          if (field instanceof ProtoBuf.Reflect.Message.ExtensionField)
+            continue;
+
+          if (T.builder.options['populateAccessors'])
+            (function (field) {
+              // set/get[SomeValue]
+              var Name = field.originalName.replace(/(_[a-zA-Z])/g, function (match) {
+                return match.toUpperCase().replace('_', '');
+              });
+              Name = Name.substring(0, 1).toUpperCase() + Name.substring(1);
+
+              // set/get_[some_value] FIXME: Do we really need these?
+              var name = field.originalName.replace(/([A-Z])/g, function (match) {
+                return "_" + match;
+              });
+
+              /**
+               * The current field's unbound setter function.
+               * @function
+               * @param {*} value
+               * @param {boolean=} noAssert
+               * @returns {!ProtoBuf.Builder.Message}
+               * @inner
+               */
+              var setter = function (value, noAssert) {
+                this[field.name] = noAssert ? value : field.verifyValue(value);
+                return this;
+              };
+
+              /**
+               * The current field's unbound getter function.
+               * @function
+               * @returns {*}
+               * @inner
+               */
+              var getter = function () {
+                return this[field.name];
+              };
+
+              if (T.getChild("set" + Name) === null)
+              /**
+               * Sets a value. This method is present for each field, but only if there is no name conflict with
+               *  another field.
+               * @name ProtoBuf.Builder.Message#set[SomeField]
+               * @function
+               * @param {*} value Value to set
+               * @param {boolean=} noAssert Whether to not assert the value, defaults to `false`
+               * @returns {!ProtoBuf.Builder.Message} this
+               * @abstract
+               * @throws {Error} If the value cannot be set
+               */
+                MessagePrototype["set" + Name] = setter;
+
+              if (T.getChild("set_" + name) === null)
+              /**
+               * Sets a value. This method is present for each field, but only if there is no name conflict with
+               *  another field.
+               * @name ProtoBuf.Builder.Message#set_[some_field]
+               * @function
+               * @param {*} value Value to set
+               * @param {boolean=} noAssert Whether to not assert the value, defaults to `false`
+               * @returns {!ProtoBuf.Builder.Message} this
+               * @abstract
+               * @throws {Error} If the value cannot be set
+               */
+                MessagePrototype["set_" + name] = setter;
+
+              if (T.getChild("get" + Name) === null)
+              /**
+               * Gets a value. This method is present for each field, but only if there is no name conflict with
+               *  another field.
+               * @name ProtoBuf.Builder.Message#get[SomeField]
+               * @function
+               * @abstract
+               * @return {*} The value
+               */
+                MessagePrototype["get" + Name] = getter;
+
+              if (T.getChild("get_" + name) === null)
+              /**
+               * Gets a value. This method is present for each field, but only if there is no name conflict with
+               *  another field.
+               * @name ProtoBuf.Builder.Message#get_[some_field]
+               * @function
+               * @return {*} The value
+               * @abstract
+               */
+                MessagePrototype["get_" + name] = getter;
+
+            })(field);
+        }
+
+        // En-/decoding
+
+        /**
+         * Encodes the message.
+         * @name ProtoBuf.Builder.Message#$encode
+         * @function
+         * @param {(!ByteBuffer|boolean)=} buffer ByteBuffer to encode to. Will create a new one and flip it if omitted.
+         * @param {boolean=} noVerify Whether to not verify field values, defaults to `false`
+         * @return {!ByteBuffer} Encoded message as a ByteBuffer
+         * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still
+         *  returns the encoded ByteBuffer in the `encoded` property on the error.
+         * @expose
+         * @see ProtoBuf.Builder.Message#encode64
+         * @see ProtoBuf.Builder.Message#encodeHex
+         * @see ProtoBuf.Builder.Message#encodeAB
+         */
+        MessagePrototype.encode = function (buffer, noVerify) {
+          if (typeof buffer === 'boolean')
+            noVerify = buffer,
+            buffer = undefined;
+          var isNew = false;
+          if (!buffer)
+            buffer = new ByteBuffer(),
+            isNew = true;
+          var le = buffer.littleEndian;
+          try {
+            T.encode(this, buffer.LE(), noVerify);
+            return (isNew ? buffer.flip() : buffer).LE(le);
+          } catch (e) {
+            buffer.LE(le);
+            throw (e);
+          }
+        };
+
+        /**
+         * Encodes a message using the specified data payload.
+         * @param {!Object.<string,*>} data Data payload
+         * @param {(!ByteBuffer|boolean)=} buffer ByteBuffer to encode to. Will create a new one and flip it if omitted.
+         * @param {boolean=} noVerify Whether to not verify field values, defaults to `false`
+         * @return {!ByteBuffer} Encoded message as a ByteBuffer
+         * @expose
+         */
+        Message.encode = function (data, buffer, noVerify) {
+          return new Message(data).encode(buffer, noVerify);
+        };
+
+        /**
+         * Calculates the byte length of the message.
+         * @name ProtoBuf.Builder.Message#calculate
+         * @function
+         * @returns {number} Byte length
+         * @throws {Error} If the message cannot be calculated or if required fields are missing.
+         * @expose
+         */
+        MessagePrototype.calculate = function () {
+          return T.calculate(this);
+        };
+
+        /**
+         * Encodes the varint32 length-delimited message.
+         * @name ProtoBuf.Builder.Message#encodeDelimited
+         * @function
+         * @param {(!ByteBuffer|boolean)=} buffer ByteBuffer to encode to. Will create a new one and flip it if omitted.
+         * @return {!ByteBuffer} Encoded message as a ByteBuffer
+         * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still
+         *  returns the encoded ByteBuffer in the `encoded` property on the error.
+         * @expose
+         */
+        MessagePrototype.encodeDelimited = function (buffer) {
+          var isNew = false;
+          if (!buffer)
+            buffer = new ByteBuffer(),
+            isNew = true;
+          var enc = new ByteBuffer().LE();
+          T.encode(this, enc).flip();
+          buffer.writeVarint32(enc.remaining());
+          buffer.append(enc);
+          return isNew ? buffer.flip() : buffer;
+        };
+
+        /**
+         * Directly encodes the message to an ArrayBuffer.
+         * @name ProtoBuf.Builder.Message#encodeAB
+         * @function
+         * @return {ArrayBuffer} Encoded message as ArrayBuffer
+         * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still
+         *  returns the encoded ArrayBuffer in the `encoded` property on the error.
+         * @expose
+         */
+        MessagePrototype.encodeAB = function () {
+          try {
+            return this.encode().toArrayBuffer();
+          } catch (e) {
+            if (e["encoded"]) e["encoded"] = e["encoded"].toArrayBuffer();
+            throw (e);
+          }
+        };
+
+        /**
+         * Returns the message as an ArrayBuffer. This is an alias for {@link ProtoBuf.Builder.Message#encodeAB}.
+         * @name ProtoBuf.Builder.Message#toArrayBuffer
+         * @function
+         * @return {ArrayBuffer} Encoded message as ArrayBuffer
+         * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still
+         *  returns the encoded ArrayBuffer in the `encoded` property on the error.
+         * @expose
+         */
+        MessagePrototype.toArrayBuffer = MessagePrototype.encodeAB;
+
+        /**
+         * Directly encodes the message to a node Buffer.
+         * @name ProtoBuf.Builder.Message#encodeNB
+         * @function
+         * @return {!Buffer}
+         * @throws {Error} If the message cannot be encoded, not running under node.js or if required fields are
+         *  missing. The later still returns the encoded node Buffer in the `encoded` property on the error.
+         * @expose
+         */
+        MessagePrototype.encodeNB = function () {
+          try {
+            return this.encode().toBuffer();
+          } catch (e) {
+            if (e["encoded"]) e["encoded"] = e["encoded"].toBuffer();
+            throw (e);
+          }
+        };
+
+        /**
+         * Returns the message as a node Buffer. This is an alias for {@link ProtoBuf.Builder.Message#encodeNB}.
+         * @name ProtoBuf.Builder.Message#toBuffer
+         * @function
+         * @return {!Buffer}
+         * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still
+         *  returns the encoded node Buffer in the `encoded` property on the error.
+         * @expose
+         */
+        MessagePrototype.toBuffer = MessagePrototype.encodeNB;
+
+        /**
+         * Directly encodes the message to a base64 encoded string.
+         * @name ProtoBuf.Builder.Message#encode64
+         * @function
+         * @return {string} Base64 encoded string
+         * @throws {Error} If the underlying buffer cannot be encoded or if required fields are missing. The later
+         *  still returns the encoded base64 string in the `encoded` property on the error.
+         * @expose
+         */
+        MessagePrototype.encode64 = function () {
+          try {
+            return this.encode().toBase64();
+          } catch (e) {
+            if (e["encoded"]) e["encoded"] = e["encoded"].toBase64();
+            throw (e);
+          }
+        };
+
+        /**
+         * Returns the message as a base64 encoded string. This is an alias for {@link ProtoBuf.Builder.Message#encode64}.
+         * @name ProtoBuf.Builder.Message#toBase64
+         * @function
+         * @return {string} Base64 encoded string
+         * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still
+         *  returns the encoded base64 string in the `encoded` property on the error.
+         * @expose
+         */
+        MessagePrototype.toBase64 = MessagePrototype.encode64;
+
+        /**
+         * Directly encodes the message to a hex encoded string.
+         * @name ProtoBuf.Builder.Message#encodeHex
+         * @function
+         * @return {string} Hex encoded string
+         * @throws {Error} If the underlying buffer cannot be encoded or if required fields are missing. The later
+         *  still returns the encoded hex string in the `encoded` property on the error.
+         * @expose
+         */
+        MessagePrototype.encodeHex = function () {
+          try {
+            return this.encode().toHex();
+          } catch (e) {
+            if (e["encoded"]) e["encoded"] = e["encoded"].toHex();
+            throw (e);
+          }
+        };
+
+        /**
+         * Returns the message as a hex encoded string. This is an alias for {@link ProtoBuf.Builder.Message#encodeHex}.
+         * @name ProtoBuf.Builder.Message#toHex
+         * @function
+         * @return {string} Hex encoded string
+         * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still
+         *  returns the encoded hex string in the `encoded` property on the error.
+         * @expose
+         */
+        MessagePrototype.toHex = MessagePrototype.encodeHex;
+
+        /**
+         * Clones a message object or field value to a raw object.
+         * @param {*} obj Object to clone
+         * @param {boolean} binaryAsBase64 Whether to include binary data as base64 strings or as a buffer otherwise
+         * @param {boolean} longsAsStrings Whether to encode longs as strings
+         * @param {!ProtoBuf.Reflect.T=} resolvedType The resolved field type if a field
+         * @returns {*} Cloned object
+         * @inner
+         */
+        function cloneRaw(obj, binaryAsBase64, longsAsStrings, resolvedType) {
+          if (obj === null || typeof obj !== 'object') {
+            // Convert enum values to their respective names
+            if (resolvedType && resolvedType instanceof ProtoBuf.Reflect.Enum) {
+              var name = ProtoBuf.Reflect.Enum.getName(resolvedType.object, obj);
+              if (name !== null)
+                return name;
+            }
+            // Pass-through string, number, boolean, null...
+            return obj;
+          }
+          // Convert ByteBuffers to raw buffer or strings
+          if (ByteBuffer.isByteBuffer(obj))
+            return binaryAsBase64 ? obj.toBase64() : obj.toBuffer();
+          // Convert Longs to proper objects or strings
+          if (ProtoBuf.Long.isLong(obj))
+            return longsAsStrings ? obj.toString() : new ProtoBuf.Long(obj);
+          var clone;
+          // Clone arrays
+          if (Array.isArray(obj)) {
+            clone = [];
+            obj.forEach(function (v, k) {
+              clone[k] = cloneRaw(v, binaryAsBase64, longsAsStrings, resolvedType);
+            });
+            return clone;
+          }
+          clone = {};
+          // Convert maps to objects
+          if (obj instanceof ProtoBuf.Map) {
+            var it = obj.entries();
+            for (var e = it.next(); !e.done; e = it.next())
+              clone[obj.keyElem.valueToString(e.value[0])] = cloneRaw(e.value[1], binaryAsBase64, longsAsStrings, obj.valueElem.resolvedType);
+            return clone;
+          }
+          // Everything else is a non-null object
+          var type = obj.$type,
+            field = undefined;
+          for (var i in obj)
+            if (obj.hasOwnProperty(i)) {
+              if (type && (field = type.getChild(i)))
+                clone[i] = cloneRaw(obj[i], binaryAsBase64, longsAsStrings, field.resolvedType);
+              else
+                clone[i] = cloneRaw(obj[i], binaryAsBase64, longsAsStrings);
+            }
+          return clone;
+        }
+
+        /**
+         * Returns the message's raw payload.
+         * @param {boolean=} binaryAsBase64 Whether to include binary data as base64 strings instead of Buffers, defaults to `false`
+         * @param {boolean} longsAsStrings Whether to encode longs as strings
+         * @returns {Object.<string,*>} Raw payload
+         * @expose
+         */
+        MessagePrototype.toRaw = function (binaryAsBase64, longsAsStrings) {
+          return cloneRaw(this, !!binaryAsBase64, !!longsAsStrings, this.$type);
+        };
+
+        /**
+         * Encodes a message to JSON.
+         * @returns {string} JSON string
+         * @expose
+         */
+        MessagePrototype.encodeJSON = function () {
+          return JSON.stringify(
+            cloneRaw(this,
+              /* binary-as-base64 */
+              true,
+              /* longs-as-strings */
+              true,
+              this.$type
+            )
+          );
+        };
+
+        /**
+         * Decodes a message from the specified buffer or string.
+         * @name ProtoBuf.Builder.Message.decode
+         * @function
+         * @param {!ByteBuffer|!ArrayBuffer|!Buffer|string} buffer Buffer to decode from
+         * @param {string=} enc Encoding if buffer is a string: hex, utf8 (not recommended), defaults to base64
+         * @return {!ProtoBuf.Builder.Message} Decoded message
+         * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still
+         *  returns the decoded message with missing fields in the `decoded` property on the error.
+         * @expose
+         * @see ProtoBuf.Builder.Message.decode64
+         * @see ProtoBuf.Builder.Message.decodeHex
+         */
+        Message.decode = function (buffer, enc) {
+          if (typeof buffer === 'string')
+            buffer = ByteBuffer.wrap(buffer, enc ? enc : "base64");
+          buffer = ByteBuffer.isByteBuffer(buffer) ? buffer : ByteBuffer.wrap(buffer); // May throw
+          var le = buffer.littleEndian;
+          try {
+            var msg = T.decode(buffer.LE());
+            buffer.LE(le);
+            return msg;
+          } catch (e) {
+            buffer.LE(le);
+            throw (e);
+          }
+        };
+
+        /**
+         * Decodes a varint32 length-delimited message from the specified buffer or string.
+         * @name ProtoBuf.Builder.Message.decodeDelimited
+         * @function
+         * @param {!ByteBuffer|!ArrayBuffer|!Buffer|string} buffer Buffer to decode from
+         * @param {string=} enc Encoding if buffer is a string: hex, utf8 (not recommended), defaults to base64
+         * @return {ProtoBuf.Builder.Message} Decoded message or `null` if not enough bytes are available yet
+         * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still
+         *  returns the decoded message with missing fields in the `decoded` property on the error.
+         * @expose
+         */
+        Message.decodeDelimited = function (buffer, enc) {
+          if (typeof buffer === 'string')
+            buffer = ByteBuffer.wrap(buffer, enc ? enc : "base64");
+          buffer = ByteBuffer.isByteBuffer(buffer) ? buffer : ByteBuffer.wrap(buffer); // May throw
+          if (buffer.remaining() < 1)
+            return null;
+          var off = buffer.offset,
+            len = buffer.readVarint32();
+          if (buffer.remaining() < len) {
+            buffer.offset = off;
+            return null;
+          }
+          try {
+            var msg = T.decode(buffer.slice(buffer.offset, buffer.offset + len).LE());
+            buffer.offset += len;
+            return msg;
+          } catch (err) {
+            buffer.offset += len;
+            throw err;
+          }
+        };
+
+        /**
+         * Decodes the message from the specified base64 encoded string.
+         * @name ProtoBuf.Builder.Message.decode64
+         * @function
+         * @param {string} str String to decode from
+         * @return {!ProtoBuf.Builder.Message} Decoded message
+         * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still
+         *  returns the decoded message with missing fields in the `decoded` property on the error.
+         * @expose
+         */
+        Message.decode64 = function (str) {
+          return Message.decode(str, "base64");
+        };
+
+        /**
+         * Decodes the message from the specified hex encoded string.
+         * @name ProtoBuf.Builder.Message.decodeHex
+         * @function
+         * @param {string} str String to decode from
+         * @return {!ProtoBuf.Builder.Message} Decoded message
+         * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still
+         *  returns the decoded message with missing fields in the `decoded` property on the error.
+         * @expose
+         */
+        Message.decodeHex = function (str) {
+          return Message.decode(str, "hex");
+        };
+
+        /**
+         * Decodes the message from a JSON string.
+         * @name ProtoBuf.Builder.Message.decodeJSON
+         * @function
+         * @param {string} str String to decode from
+         * @return {!ProtoBuf.Builder.Message} Decoded message
+         * @throws {Error} If the message cannot be decoded or if required fields are
+         * missing.
+         * @expose
+         */
+        Message.decodeJSON = function (str) {
+          return new Message(JSON.parse(str));
+        };
+
+        // Utility
+
+        /**
+         * Returns a string representation of this Message.
+         * @name ProtoBuf.Builder.Message#toString
+         * @function
+         * @return {string} String representation as of ".Fully.Qualified.MessageName"
+         * @expose
+         */
+        MessagePrototype.toString = function () {
+          return T.toString();
+        };
+
+        // Properties
+
+        /**
+         * Message options.
+         * @name ProtoBuf.Builder.Message.$options
+         * @type {Object.<string,*>}
+         * @expose
+         */
+        var $optionsS; // cc needs this
+
+        /**
+         * Message options.
+         * @name ProtoBuf.Builder.Message#$options
+         * @type {Object.<string,*>}
+         * @expose
+         */
+        var $options;
+
+        /**
+         * Reflection type.
+         * @name ProtoBuf.Builder.Message.$type
+         * @type {!ProtoBuf.Reflect.Message}
+         * @expose
+         */
+        var $typeS;
+
+        /**
+         * Reflection type.
+         * @name ProtoBuf.Builder.Message#$type
+         * @type {!ProtoBuf.Reflect.Message}
+         * @expose
+         */
+        var $type;
+
+        if (Object.defineProperty)
+          Object.defineProperty(Message, '$options', { "value": T.buildOpt() }),
+          Object.defineProperty(MessagePrototype, "$options", { "value": Message["$options"] }),
+          Object.defineProperty(Message, "$type", { "value": T }),
+          Object.defineProperty(MessagePrototype, "$type", { "value": T });
+
+        return Message;
+
+      })(ProtoBuf, this);
+
+      // Static enums and prototyped sub-messages / cached collections
+      this._fields = [];
+      this._fieldsById = {};
+      this._fieldsByName = {};
+      for (var i = 0, k = this.children.length, child; i < k; i++) {
+        child = this.children[i];
+        if (child instanceof Enum || child instanceof Message || child instanceof Service) {
+          if (clazz.hasOwnProperty(child.name))
+            throw Error("Illegal reflect child of " + this.toString(true) + ": " + child.toString(true) + " cannot override static property '" + child.name + "'");
+          clazz[child.name] = child.build();
+        } else if (child instanceof Message.Field)
+          child.build(),
+          this._fields.push(child),
+          this._fieldsById[child.id] = child,
+          this._fieldsByName[child.name] = child;
+        else if (!(child instanceof Message.OneOf) && !(child instanceof Extension)) // Not built
+          throw Error("Illegal reflect child of " + this.toString(true) + ": " + this.children[i].toString(true));
+      }
+
+      return this.clazz = clazz;
+    };
+
+    /**
+     * Encodes a runtime message's contents to the specified buffer.
+     * @param {!ProtoBuf.Builder.Message} message Runtime message to encode
+     * @param {ByteBuffer} buffer ByteBuffer to write to
+     * @param {boolean=} noVerify Whether to not verify field values, defaults to `false`
+     * @return {ByteBuffer} The ByteBuffer for chaining
+     * @throws {Error} If required fields are missing or the message cannot be encoded for another reason
+     * @expose
+     */
+    MessagePrototype.encode = function (message, buffer, noVerify) {
+      var fieldMissing = null,
+        field;
+      for (var i = 0, k = this._fields.length, val; i < k; ++i) {
+        field = this._fields[i];
+        val = message[field.name];
+        if (field.required && val === null) {
+          if (fieldMissing === null)
+            fieldMissing = field;
+        } else
+          field.encode(noVerify ? val : field.verifyValue(val), buffer, message);
+      }
+      if (fieldMissing !== null) {
+        var err = Error("Missing at least one required field for " + this.toString(true) + ": " + fieldMissing);
+        err["encoded"] = buffer; // Still expose what we got
+        throw (err);
+      }
+      return buffer;
+    };
+
+    /**
+     * Calculates a runtime message's byte length.
+     * @param {!ProtoBuf.Builder.Message} message Runtime message to encode
+     * @returns {number} Byte length
+     * @throws {Error} If required fields are missing or the message cannot be calculated for another reason
+     * @expose
+     */
+    MessagePrototype.calculate = function (message) {
+      for (var n = 0, i = 0, k = this._fields.length, field, val; i < k; ++i) {
+        field = this._fields[i];
+        val = message[field.name];
+        if (field.required && val === null)
+          throw Error("Missing at least one required field for " + this.toString(true) + ": " + field);
+        else
+          n += field.calculate(val, message);
+      }
+      return n;
+    };
+
+    /**
+     * Skips all data until the end of the specified group has been reached.
+     * @param {number} expectedId Expected GROUPEND id
+     * @param {!ByteBuffer} buf ByteBuffer
+     * @returns {boolean} `true` if a value as been skipped, `false` if the end has been reached
+     * @throws {Error} If it wasn't possible to find the end of the group (buffer overrun or end tag mismatch)
+     * @inner
+     */
+    function skipTillGroupEnd(expectedId, buf) {
+      var tag = buf.readVarint32(), // Throws on OOB
+        wireType = tag & 0x07,
+        id = tag >>> 3;
+      switch (wireType) {
+      case ProtoBuf.WIRE_TYPES.VARINT:
+        do tag = buf.readUint8();
+        while ((tag & 0x80) === 0x80);
+        break;
+      case ProtoBuf.WIRE_TYPES.BITS64:
+        buf.offset += 8;
+        break;
+      case ProtoBuf.WIRE_TYPES.LDELIM:
+        tag = buf.readVarint32(); // reads the varint
+        buf.offset += tag; // skips n bytes
+        break;
+      case ProtoBuf.WIRE_TYPES.STARTGROUP:
+        skipTillGroupEnd(id, buf);
+        break;
+      case ProtoBuf.WIRE_TYPES.ENDGROUP:
+        if (id === expectedId)
+          return false;
+        else
+          throw Error("Illegal GROUPEND after unknown group: " + id + " (" + expectedId + " expected)");
+      case ProtoBuf.WIRE_TYPES.BITS32:
+        buf.offset += 4;
+        break;
+      default:
+        throw Error("Illegal wire type in unknown group " + expectedId + ": " + wireType);
+      }
+      return true;
+    }
+
+    /**
+     * Decodes an encoded message and returns the decoded message.
+     * @param {ByteBuffer} buffer ByteBuffer to decode from
+     * @param {number=} length Message length. Defaults to decode all the available data.
+     * @param {number=} expectedGroupEndId Expected GROUPEND id if this is a legacy group
+     * @return {ProtoBuf.Builder.Message} Decoded message
+     * @throws {Error} If the message cannot be decoded
+     * @expose
+     */
+    MessagePrototype.decode = function (buffer, length, expectedGroupEndId) {
+      length = typeof length === 'number' ? length : -1;
+      var start = buffer.offset,
+        msg = new(this.clazz)(),
+        tag, wireType, id, field;
+      while (buffer.offset < start + length || (length === -1 && buffer.remaining() > 0)) {
+        tag = buffer.readVarint32();
+        wireType = tag & 0x07;
+        id = tag >>> 3;
+        if (wireType === ProtoBuf.WIRE_TYPES.ENDGROUP) {
+          if (id !== expectedGroupEndId)
+            throw Error("Illegal group end indicator for " + this.toString(true) + ": " + id + " (" + (expectedGroupEndId ? expectedGroupEndId + " expected" : "not a group") + ")");
+          break;
+        }
+        if (!(field = this._fieldsById[id])) {
+          // "messages created by your new code can be parsed by your old code: old binaries simply ignore the new field when parsing."
+          switch (wireType) {
+          case ProtoBuf.WIRE_TYPES.VARINT:
+            buffer.readVarint32();
+            break;
+          case ProtoBuf.WIRE_TYPES.BITS32:
+            buffer.offset += 4;
+            break;
+          case ProtoBuf.WIRE_TYPES.BITS64:
+            buffer.offset += 8;
+            break;
+          case ProtoBuf.WIRE_TYPES.LDELIM:
+            var len = buffer.readVarint32();
+            buffer.offset += len;
+            break;
+          case ProtoBuf.WIRE_TYPES.STARTGROUP:
+            while (skipTillGroupEnd(id, buffer)) {}
+            break;
+          default:
+            throw Error("Illegal wire type for unknown field " + id + " in " + this.toString(true) + "#decode: " + wireType);
+          }
+          continue;
+        }
+        if (field.repeated && !field.options["packed"]) {
+          msg[field.name].push(field.decode(wireType, buffer));
+        } else if (field.map) {
+          var keyval = field.decode(wireType, buffer);
+          msg[field.name].set(keyval[0], keyval[1]);
+        } else {
+          msg[field.name] = field.decode(wireType, buffer);
+          if (field.oneof) { // Field is part of an OneOf (not a virtual OneOf field)
+            var currentField = msg[field.oneof.name]; // Virtual field references currently set field
+            if (currentField !== null && currentField !== field.name)
+              msg[currentField] = null; // Clear currently set field
+            msg[field.oneof.name] = field.name; // Point virtual field at this field
+          }
+        }
+      }
+
+      // Check if all required fields are present and set default values for optional fields that are not
+      for (var i = 0, k = this._fields.length; i < k; ++i) {
+        field = this._fields[i];
+        if (msg[field.name] === null) {
+          if (this.syntax === "proto3") { // Proto3 sets default values by specification
+            msg[field.name] = field.defaultValue;
+          } else if (field.required) {
+            var err = Error("Missing at least one required field for " + this.toString(true) + ": " + field.name);
+            err["decoded"] = msg; // Still expose what we got
+            throw (err);
+          } else if (ProtoBuf.populateDefaults && field.defaultValue !== null)
+            msg[field.name] = field.defaultValue;
+        }
+      }
+      return msg;
+    };
+
+    /**
+     * @alias ProtoBuf.Reflect.Message
+     * @expose
+     */
+    Reflect.Message = Message;
+
+    /**
+     * Constructs a new Message Field.
+     * @exports ProtoBuf.Reflect.Message.Field
+     * @param {!ProtoBuf.Builder} builder Builder reference
+     * @param {!ProtoBuf.Reflect.Message} message Message reference
+     * @param {string} rule Rule, one of requried, optional, repeated
+     * @param {string?} keytype Key data type, if any.
+     * @param {string} type Data type, e.g. int32
+     * @param {string} name Field name
+     * @param {number} id Unique field id
+     * @param {Object.<string,*>=} options Options
+     * @param {!ProtoBuf.Reflect.Message.OneOf=} oneof Enclosing OneOf
+     * @param {string?} syntax The syntax level of this definition (e.g., proto3)
+     * @constructor
+     * @extends ProtoBuf.Reflect.T
+     */
+    var Field = function (builder, message, rule, keytype, type, name, id, options, oneof, syntax) {
+      T.call(this, builder, message, name);
+
+      /**
+       * @override
+       */
+      this.className = "Message.Field";
+
+      /**
+       * Message field required flag.
+       * @type {boolean}
+       * @expose
+       */
+      this.required = rule === "required";
+
+      /**
+       * Message field repeated flag.
+       * @type {boolean}
+       * @expose
+       */
+      this.repeated = rule === "repeated";
+
+      /**
+       * Message field map flag.
+       * @type {boolean}
+       * @expose
+       */
+      this.map = rule === "map";
+
+      /**
+       * Message field key type. Type reference string if unresolved, protobuf
+       * type if resolved. Valid only if this.map === true, null otherwise.
+       * @type {string|{name: string, wireType: number}|null}
+       * @expose
+       */
+      this.keyType = keytype || null;
+
+      /**
+       * Message field type. Type reference string if unresolved, protobuf type if
+       * resolved. In a map field, this is the value type.
+       * @type {string|{name: string, wireType: number}}
+       * @expose
+       */
+      this.type = type;
+
+      /**
+       * Resolved type reference inside the global namespace.
+       * @type {ProtoBuf.Reflect.T|null}
+       * @expose
+       */
+      this.resolvedType = null;
+
+      /**
+       * Unique message field id.
+       * @type {number}
+       * @expose
+       */
+      this.id = id;
+
+      /**
+       * Message field options.
+       * @type {!Object.<string,*>}
+       * @dict
+       * @expose
+       */
+      this.options = options || {};
+
+      /**
+       * Default value.
+       * @type {*}
+       * @expose
+       */
+      this.defaultValue = null;
+
+      /**
+       * Enclosing OneOf.
+       * @type {?ProtoBuf.Reflect.Message.OneOf}
+       * @expose
+       */
+      this.oneof = oneof || null;
+
+      /**
+       * Syntax level of this definition (e.g., proto3).
+       * @type {string}
+       * @expose
+       */
+      this.syntax = syntax || 'proto2';
+
+      /**
+       * Original field name.
+       * @type {string}
+       * @expose
+       */
+      this.originalName = this.name; // Used to revert camelcase transformation on naming collisions
+
+      /**
+       * Element implementation. Created in build() after types are resolved.
+       * @type {ProtoBuf.Element}
+       * @expose
+       */
+      this.element = null;
+
+      /**
+       * Key element implementation, for map fields. Created in build() after
+       * types are resolved.
+       * @type {ProtoBuf.Element}
+       * @expose
+       */
+      this.keyElement = null;
+
+      // Convert field names to camel case notation if the override is set
+      if (this.builder.options['convertFieldsToCamelCase'] && !(this instanceof Message.ExtensionField))
+        this.name = ProtoBuf.Util.toCamelCase(this.name);
+    };
+
+    /**
+     * @alias ProtoBuf.Reflect.Message.Field.prototype
+     * @inner
+     */
+    var FieldPrototype = Field.prototype = Object.create(T.prototype);
+
+    /**
+     * Builds the field.
+     * @override
+     * @expose
+     */
+    FieldPrototype.build = function () {
+      this.element = new Element(this.type, this.resolvedType, false, this.syntax);
+      if (this.map)
+        this.keyElement = new Element(this.keyType, undefined, true, this.syntax);
+
+      // In proto3, fields do not have field presence, and every field is set to
+      // its type's default value ("", 0, 0.0, or false).
+      if (this.syntax === 'proto3' && !this.repeated && !this.map)
+        this.defaultValue = Element.defaultFieldValue(this.type);
+
+      // Otherwise, default values are present when explicitly specified
+      else if (typeof this.options['default'] !== 'undefined')
+        this.defaultValue = this.verifyValue(this.options['default']);
+    };
+
+    /**
+     * Checks if the given value can be set for this field.
+     * @param {*} value Value to check
+     * @param {boolean=} skipRepeated Whether to skip the repeated value check or not. Defaults to false.
+     * @return {*} Verified, maybe adjusted, value
+     * @throws {Error} If the value cannot be set for this field
+     * @expose
+     */
+    FieldPrototype.verifyValue = function (value, skipRepeated) {
+      skipRepeated = skipRepeated || false;
+      var fail = function (val, msg) {
+        throw Error("Illegal value for " + this.toString(true) + " of type " + this.type.name + ": " + val + " (" + msg + ")");
+      }.bind(this);
+      if (value === null) { // NULL values for optional fields
+        if (this.required)
+          fail(typeof value, "required");
+        if (this.syntax === 'proto3' && this.type !== ProtoBuf.TYPES["message"])
+          fail(typeof value, "proto3 field without field presence cannot be null");
+        return null;
+      }
+      var i;
+      if (this.repeated && !skipRepeated) { // Repeated values as arrays
+        if (!Array.isArray(value))
+          value = [value];
+        var res = [];
+        for (i = 0; i < value.length; i++)
+          res.push(this.element.verifyValue(value[i]));
+        return res;
+      }
+      if (this.map && !skipRepeated) { // Map values as objects
+        if (!(value instanceof ProtoBuf.Map)) {
+          // If not already a Map, attempt to convert.
+          if (!(value instanceof Object)) {
+            fail(typeof value,
+              "expected ProtoBuf.Map or raw object for map field");
+          }
+          return new ProtoBuf.Map(this, value);
+        } else {
+          return value;
+        }
+      }
+      // All non-repeated fields expect no array
+      if (!this.repeated && Array.isArray(value))
+        fail(typeof value, "no array expected");
+
+      return this.element.verifyValue(value);
+    };
+
+    /**
+     * Determines whether the field will have a presence on the wire given its
+     * value.
+     * @param {*} value Verified field value
+     * @param {!ProtoBuf.Builder.Message} message Runtime message
+     * @return {boolean} Whether the field will be present on the wire
+     */
+    FieldPrototype.hasWirePresence = function (value, message) {
+      if (this.syntax !== 'proto3')
+        return (value !== null);
+      if (this.oneof && message[this.oneof.name] === this.name)
+        return true;
+      switch (this.type) {
+      case ProtoBuf.TYPES["int32"]:
+      case ProtoBuf.TYPES["sint32"]:
+      case ProtoBuf.TYPES["sfixed32"]:
+      case ProtoBuf.TYPES["uint32"]:
+      case ProtoBuf.TYPES["fixed32"]:
+        return value !== 0;
+
+      case ProtoBuf.TYPES["int64"]:
+      case ProtoBuf.TYPES["sint64"]:
+      case ProtoBuf.TYPES["sfixed64"]:
+      case ProtoBuf.TYPES["uint64"]:
+      case ProtoBuf.TYPES["fixed64"]:
+        return value.low !== 0 || value.high !== 0;
+
+      case ProtoBuf.TYPES["bool"]:
+        return value;
+
+      case ProtoBuf.TYPES["float"]:
+      case ProtoBuf.TYPES["double"]:
+        return value !== 0.0;
+
+      case ProtoBuf.TYPES["string"]:
+        return value.length > 0;
+
+      case ProtoBuf.TYPES["bytes"]:
+        return value.remaining() > 0;
+
+      case ProtoBuf.TYPES["enum"]:
+        return value !== 0;
+
+      case ProtoBuf.TYPES["message"]:
+        return value !== null;
+      default:
+        return true;
+      }
+    };
+
+    /**
+     * Encodes the specified field value to the specified buffer.
+     * @param {*} value Verified field value
+     * @param {ByteBuffer} buffer ByteBuffer to encode to
+     * @param {!ProtoBuf.Builder.Message} message Runtime message
+     * @return {ByteBuffer} The ByteBuffer for chaining
+     * @throws {Error} If the field cannot be encoded
+     * @expose
+     */
+    FieldPrototype.encode = function (value, buffer, message) {
+      if (this.type === null || typeof this.type !== 'object')
+        throw Error("[INTERNAL] Unresolved type in " + this.toString(true) + ": " + this.type);
+      if (value === null || (this.repeated && value.length == 0))
+        return buffer; // Optional omitted
+      try {
+        if (this.repeated) {
+          var i;
+          // "Only repeated fields of primitive numeric types (types which use the varint, 32-bit, or 64-bit wire
+          // types) can be declared 'packed'."
+          if (this.options["packed"] && ProtoBuf.PACKABLE_WIRE_TYPES.indexOf(this.type.wireType) >= 0) {
+            // "All of the elements of the field are packed into a single key-value pair with wire type 2
+            // (length-delimited). Each element is encoded the same way it would be normally, except without a
+            // tag preceding it."
+            buffer.writeVarint32((this.id << 3) | ProtoBuf.WIRE_TYPES.LDELIM);
+            buffer.ensureCapacity(buffer.offset += 1); // We do not know the length yet, so let's assume a varint of length 1
+            var start = buffer.offset; // Remember where the contents begin
+            for (i = 0; i < value.length; i++)
+              this.element.encodeValue(this.id, value[i], buffer);
+            var len = buffer.offset - start,
+              varintLen = ByteBuffer.calculateVarint32(len);
+            if (varintLen > 1) { // We need to move the contents
+              var contents = buffer.slice(start, buffer.offset);
+              start += varintLen - 1;
+              buffer.offset = start;
+              buffer.append(contents);
+            }
+            buffer.writeVarint32(len, start - varintLen);
+          } else {
+            // "If your message definition has repeated elements (without the [packed=true] option), the encoded
+            // message has zero or more key-value pairs with the same tag number"
+            for (i = 0; i < value.length; i++)
+              buffer.writeVarint32((this.id << 3) | this.type.wireType),
+              this.element.encodeValue(this.id, value[i], buffer);
+          }
+        } else if (this.map) {
+          // Write out each map entry as a submessage.
+          value.forEach(function (val, key, m) {
+            // Compute the length of the submessage (key, val) pair.
+            var length =
+              ByteBuffer.calculateVarint32((1 << 3) | this.keyType.wireType) +
+              this.keyElement.calculateLength(1, key) +
+              ByteBuffer.calculateVarint32((2 << 3) | this.type.wireType) +
+              this.element.calculateLength(2, val);
+
+            // Submessage with wire type of length-delimited.
+            buffer.writeVarint32((this.id << 3) | ProtoBuf.WIRE_TYPES.LDELIM);
+            buffer.writeVarint32(length);
+
+            // Write out the key and val.
+            buffer.writeVarint32((1 << 3) | this.keyType.wireType);
+            this.keyElement.encodeValue(1, key, buffer);
+            buffer.writeVarint32((2 << 3) | this.type.wireType);
+            this.element.encodeValue(2, val, buffer);
+          }, this);
+        } else {
+          if (this.hasWirePresence(value, message)) {
+            buffer.writeVarint32((this.id << 3) | this.type.wireType);
+            this.element.encodeValue(this.id, value, buffer);
+          }
+        }
+      } catch (e) {
+        throw Error("Illegal value for " + this.toString(true) + ": " + value + " (" + e + ")");
+      }
+      return buffer;
+    };
+
+    /**
+     * Calculates the length of this field's value on the network level.
+     * @param {*} value Field value
+     * @param {!ProtoBuf.Builder.Message} message Runtime message
+     * @returns {number} Byte length
+     * @expose
+     */
+    FieldPrototype.calculate = function (value, message) {
+      value = this.verifyValue(value); // May throw
+      if (this.type === null || typeof this.type !== 'object')
+        throw Error("[INTERNAL] Unresolved type in " + this.toString(true) + ": " + this.type);
+      if (value === null || (this.repeated && value.length == 0))
+        return 0; // Optional omitted
+      var n = 0;
+      try {
+        if (this.repeated) {
+          var i, ni;
+          if (this.options["packed"] && ProtoBuf.PACKABLE_WIRE_TYPES.indexOf(this.type.wireType) >= 0) {
+            n += ByteBuffer.calculateVarint32((this.id << 3) | ProtoBuf.WIRE_TYPES.LDELIM);
+            ni = 0;
+            for (i = 0; i < value.length; i++)
+              ni += this.element.calculateLength(this.id, value[i]);
+            n += ByteBuffer.calculateVarint32(ni);
+            n += ni;
+          } else {
+            for (i = 0; i < value.length; i++)
+              n += ByteBuffer.calculateVarint32((this.id << 3) | this.type.wireType),
+              n += this.element.calculateLength(this.id, value[i]);
+          }
+        } else if (this.map) {
+          // Each map entry becomes a submessage.
+          value.forEach(function (val, key, m) {
+            // Compute the length of the submessage (key, val) pair.
+            var length =
+              ByteBuffer.calculateVarint32((1 << 3) | this.keyType.wireType) +
+              this.keyElement.calculateLength(1, key) +
+              ByteBuffer.calculateVarint32((2 << 3) | this.type.wireType) +
+              this.element.calculateLength(2, val);
+
+            n += ByteBuffer.calculateVarint32((this.id << 3) | ProtoBuf.WIRE_TYPES.LDELIM);
+            n += ByteBuffer.calculateVarint32(length);
+            n += length;
+          }, this);
+        } else {
+          if (this.hasWirePresence(value, message)) {
+            n += ByteBuffer.calculateVarint32((this.id << 3) | this.type.wireType);
+            n += this.element.calculateLength(this.id, value);
+          }
+        }
+      } catch (e) {
+        throw Error("Illegal value for " + this.toString(true) + ": " + value + " (" + e + ")");
+      }
+      return n;
+    };
+
+    /**
+     * Decode the field value from the specified buffer.
+     * @param {number} wireType Leading wire type
+     * @param {ByteBuffer} buffer ByteBuffer to decode from
+     * @param {boolean=} skipRepeated Whether to skip the repeated check or not. Defaults to false.
+     * @return {*} Decoded value: array for packed repeated fields, [key, value] for
+     *             map fields, or an individual value otherwise.
+     * @throws {Error} If the field cannot be decoded
+     * @expose
+     */
+    FieldPrototype.decode = function (wireType, buffer, skipRepeated) {
+      var value, nBytes;
+
+      // We expect wireType to match the underlying type's wireType unless we see
+      // a packed repeated field, or unless this is a map field.
+      var wireTypeOK =
+        (!this.map && wireType == this.type.wireType) ||
+        (!skipRepeated && this.repeated && this.options["packed"] &&
+          wireType == ProtoBuf.WIRE_TYPES.LDELIM) ||
+        (this.map && wireType == ProtoBuf.WIRE_TYPES.LDELIM);
+      if (!wireTypeOK)
+        throw Error("Illegal wire type for field " + this.toString(true) + ": " + wireType + " (" + this.type.wireType + " expected)");
+
+      // Handle packed repeated fields.
+      if (wireType == ProtoBuf.WIRE_TYPES.LDELIM && this.repeated && this.options["packed"] && ProtoBuf.PACKABLE_WIRE_TYPES.indexOf(this.type.wireType) >= 0) {
+        if (!skipRepeated) {
+          nBytes = buffer.readVarint32();
+          nBytes = buffer.offset + nBytes; // Limit
+          var values = [];
+          while (buffer.offset < nBytes)
+            values.push(this.decode(this.type.wireType, buffer, true));
+          return values;
+        }
+        // Read the next value otherwise...
+      }
+
+      // Handle maps.
+      if (this.map) {
+        // Read one (key, value) submessage, and return [key, value]
+        var key = Element.defaultFieldValue(this.keyType);
+        value = Element.defaultFieldValue(this.type);
+
+        // Read the length
+        nBytes = buffer.readVarint32();
+        if (buffer.remaining() < nBytes)
+          throw Error("Illegal number of bytes for " + this.toString(true) + ": " + nBytes + " required but got only " + buffer.remaining());
+
+        // Get a sub-buffer of this key/value submessage
+        var msgbuf = buffer.clone();
+        msgbuf.limit = msgbuf.offset + nBytes;
+        buffer.offset += nBytes;
+
+        while (msgbuf.remaining() > 0) {
+          var tag = msgbuf.readVarint32();
+          wireType = tag & 0x07;
+          var id = tag >>> 3;
+          if (id === 1) {
+            key = this.keyElement.decode(msgbuf, wireType, id);
+          } else if (id === 2) {
+            value = this.element.decode(msgbuf, wireType, id);
+          } else {
+            throw Error("Unexpected tag in map field key/value submessage");
+          }
+        }
+
+        return [key, value];
+      }
+
+      // Handle singular and non-packed repeated field values.
+      return this.element.decode(buffer, wireType, this.id);
+    };
+
+    /**
+     * @alias ProtoBuf.Reflect.Message.Field
+     * @expose
+     */
+    Reflect.Message.Field = Field;
+
+    /**
+     * Constructs a new Message ExtensionField.
+     * @exports ProtoBuf.Reflect.Message.ExtensionField
+     * @param {!ProtoBuf.Builder} builder Builder reference
+     * @param {!ProtoBuf.Reflect.Message} message Message reference
+     * @param {string} rule Rule, one of requried, optional, repeated
+     * @param {string} type Data type, e.g. int32
+     * @param {string} name Field name
+     * @param {number} id Unique field id
+     * @param {!Object.<string,*>=} options Options
+     * @constructor
+     * @extends ProtoBuf.Reflect.Message.Field
+     */
+    var ExtensionField = function (builder, message, rule, type, name, id, options) {
+      Field.call(this, builder, message, rule, /* keytype = */ null, type, name, id, options);
+
+      /**
+       * Extension reference.
+       * @type {!ProtoBuf.Reflect.Extension}
+       * @expose
+       */
+      this.extension;
+    };
+
+    // Extends Field
+    ExtensionField.prototype = Object.create(Field.prototype);
+
+    /**
+     * @alias ProtoBuf.Reflect.Message.ExtensionField
+     * @expose
+     */
+    Reflect.Message.ExtensionField = ExtensionField;
+
+    /**
+     * Constructs a new Message OneOf.
+     * @exports ProtoBuf.Reflect.Message.OneOf
+     * @param {!ProtoBuf.Builder} builder Builder reference
+     * @param {!ProtoBuf.Reflect.Message} message Message reference
+     * @param {string} name OneOf name
+     * @constructor
+     * @extends ProtoBuf.Reflect.T
+     */
+    var OneOf = function (builder, message, name) {
+      T.call(this, builder, message, name);
+
+      /**
+       * Enclosed fields.
+       * @type {!Array.<!ProtoBuf.Reflect.Message.Field>}
+       * @expose
+       */
+      this.fields = [];
+    };
+
+    /**
+     * @alias ProtoBuf.Reflect.Message.OneOf
+     * @expose
+     */
+    Reflect.Message.OneOf = OneOf;
+
+    /**
+     * Constructs a new Enum.
+     * @exports ProtoBuf.Reflect.Enum
+     * @param {!ProtoBuf.Builder} builder Builder reference
+     * @param {!ProtoBuf.Reflect.T} parent Parent Reflect object
+     * @param {string} name Enum name
+     * @param {Object.<string,*>=} options Enum options
+     * @param {string?} syntax The syntax level (e.g., proto3)
+     * @constructor
+     * @extends ProtoBuf.Reflect.Namespace
+     */
+    var Enum = function (builder, parent, name, options, syntax) {
+      Namespace.call(this, builder, parent, name, options, syntax);
+
+      /**
+       * @override
+       */
+      this.className = "Enum";
+
+      /**
+       * Runtime enum object.
+       * @type {Object.<string,number>|null}
+       * @expose
+       */
+      this.object = null;
+    };
+
+    /**
+     * Gets the string name of an enum value.
+     * @param {!ProtoBuf.Builder.Enum} enm Runtime enum
+     * @param {number} value Enum value
+     * @returns {?string} Name or `null` if not present
+     * @expose
+     */
+    Enum.getName = function (enm, value) {
+      var keys = Object.keys(enm);
+      for (var i = 0, key; i < keys.length; ++i)
+        if (enm[key = keys[i]] === value)
+          return key;
+      return null;
+    };
+
+    /**
+     * @alias ProtoBuf.Reflect.Enum.prototype
+     * @inner
+     */
+    var EnumPrototype = Enum.prototype = Object.create(Namespace.prototype);
+
+    /**
+     * Builds this enum and returns the runtime counterpart.
+     * @param {boolean} rebuild Whether to rebuild or not, defaults to false
+     * @returns {!Object.<string,number>}
+     * @expose
+     */
+    EnumPrototype.build = function (rebuild) {
+      if (this.object && !rebuild)
+        return this.object;
+      var enm = new ProtoBuf.Builder.Enum(),
+        values = this.getChildren(Enum.Value);
+      for (var i = 0, k = values.length; i < k; ++i)
+        enm[values[i]['name']] = values[i]['id'];
+      if (Object.defineProperty)
+        Object.defineProperty(enm, '$options', {
+          "value": this.buildOpt(),
+          "enumerable": false
+        });
+      return this.object = enm;
+    };
+
+    /**
+     * @alias ProtoBuf.Reflect.Enum
+     * @expose
+     */
+    Reflect.Enum = Enum;
+
+    /**
+     * Constructs a new Enum Value.
+     * @exports ProtoBuf.Reflect.Enum.Value
+     * @param {!ProtoBuf.Builder} builder Builder reference
+     * @param {!ProtoBuf.Reflect.Enum} enm Enum reference
+     * @param {string} name Field name
+     * @param {number} id Unique field id
+     * @constructor
+     * @extends ProtoBuf.Reflect.T
+     */
+    var Value = function (builder, enm, name, id) {
+      T.call(this, builder, enm, name);
+
+      /**
+       * @override
+       */
+      this.className = "Enum.Value";
+
+      /**
+       * Unique enum value id.
+       * @type {number}
+       * @expose
+       */
+      this.id = id;
+    };
+
+    // Extends T
+    Value.prototype = Object.create(T.prototype);
+
+    /**
+     * @alias ProtoBuf.Reflect.Enum.Value
+     * @expose
+     */
+    Reflect.Enum.Value = Value;
+
+    /**
+     * An extension (field).
+     * @exports ProtoBuf.Reflect.Extension
+     * @constructor
+     * @param {!ProtoBuf.Builder} builder Builder reference
+     * @param {!ProtoBuf.Reflect.T} parent Parent object
+     * @param {string} name Object name
+     * @param {!ProtoBuf.Reflect.Message.Field} field Extension field
+     */
+    var Extension = function (builder, parent, name, field) {
+      T.call(this, builder, parent, name);
+
+      /**
+       * Extended message field.
+       * @type {!ProtoBuf.Reflect.Message.Field}
+       * @expose
+       */
+      this.field = field;
+    };
+
+    // Extends T
+    Extension.prototype = Object.create(T.prototype);
+
+    /**
+     * @alias ProtoBuf.Reflect.Extension
+     * @expose
+     */
+    Reflect.Extension = Extension;
+
+    /**
+     * Constructs a new Service.
+     * @exports ProtoBuf.Reflect.Service
+     * @param {!ProtoBuf.Builder} builder Builder reference
+     * @param {!ProtoBuf.Reflect.Namespace} root Root
+     * @param {string} name Service name
+     * @param {Object.<string,*>=} options Options
+     * @constructor
+     * @extends ProtoBuf.Reflect.Namespace
+     */
+    var Service = function (builder, root, name, options) {
+      Namespace.call(this, builder, root, name, options);
+
+      /**
+       * @override
+       */
+      this.className = "Service";
+
+      /**
+       * Built runtime service class.
+       * @type {?function(new:ProtoBuf.Builder.Service)}
+       */
+      this.clazz = null;
+    };
+
+    /**
+     * @alias ProtoBuf.Reflect.Service.prototype
+     * @inner
+     */
+    var ServicePrototype = Service.prototype = Object.create(Namespace.prototype);
+
+    /**
+     * Builds the service and returns the runtime counterpart, which is a fully functional class.
+     * @see ProtoBuf.Builder.Service
+     * @param {boolean=} rebuild Whether to rebuild or not
+     * @return {Function} Service class
+     * @throws {Error} If the message cannot be built
+     * @expose
+     */
+    ServicePrototype.build = function (rebuild) {
+      if (this.clazz && !rebuild)
+        return this.clazz;
+
+      // Create the runtime Service class in its own scope
+      return this.clazz = (function (ProtoBuf, T) {
+
+        /**
+         * Constructs a new runtime Service.
+         * @name ProtoBuf.Builder.Service
+         * @param {function(string, ProtoBuf.Builder.Message, function(Error, ProtoBuf.Builder.Message=))=} rpcImpl RPC implementation receiving the method name and the message
+         * @class Barebone of all runtime services.
+         * @constructor
+         * @throws {Error} If the service cannot be created
+         */
+        var Service = function (rpcImpl) {
+          ProtoBuf.Builder.Service.call(this);
+
+          /**
+           * Service implementation.
+           * @name ProtoBuf.Builder.Service#rpcImpl
+           * @type {!function(string, ProtoBuf.Builder.Message, function(Error, ProtoBuf.Builder.Message=))}
+           * @expose
+           */
+          this.rpcImpl = rpcImpl || function (name, msg, callback) {
+            // This is what a user has to implement: A function receiving the method name, the actual message to
+            // send (type checked) and the callback that's either provided with the error as its first
+            // argument or null and the actual response message.
+            setTimeout(callback.bind(this, Error("Not implemented, see: https://github.com/dcodeIO/ProtoBuf.js/wiki/Services")), 0); // Must be async!
+          };
+        };
+
+        /**
+         * @alias ProtoBuf.Builder.Service.prototype
+         * @inner
+         */
+        var ServicePrototype = Service.prototype = Object.create(ProtoBuf.Builder.Service.prototype);
+
+        /**
+         * Asynchronously performs an RPC call using the given RPC implementation.
+         * @name ProtoBuf.Builder.Service.[Method]
+         * @function
+         * @param {!function(string, ProtoBuf.Builder.Message, function(Error, ProtoBuf.Builder.Message=))} rpcImpl RPC implementation
+         * @param {ProtoBuf.Builder.Message} req Request
+         * @param {function(Error, (ProtoBuf.Builder.Message|ByteBuffer|Buffer|string)=)} callback Callback receiving
+         *  the error if any and the response either as a pre-parsed message or as its raw bytes
+         * @abstract
+         */
+
+        /**
+         * Asynchronously performs an RPC call using the instance's RPC implementation.
+         * @name ProtoBuf.Builder.Service#[Method]
+         * @function
+         * @param {ProtoBuf.Builder.Message} req Request
+         * @param {function(Error, (ProtoBuf.Builder.Message|ByteBuffer|Buffer|string)=)} callback Callback receiving
+         *  the error if any and the response either as a pre-parsed message or as its raw bytes
+         * @abstract
+         */
+
+        var rpc = T.getChildren(ProtoBuf.Reflect.Service.RPCMethod);
+        for (var i = 0; i < rpc.length; i++) {
+          (function (method) {
+
+            // service#Method(message, callback)
+            ServicePrototype[method.name] = function (req, callback) {
+              try {
+                try {
+                  // If given as a buffer, decode the request. Will throw a TypeError if not a valid buffer.
+                  req = method.resolvedRequestType.clazz.decode(ByteBuffer.wrap(req));
+                } catch (err) {
+                  if (!(err instanceof TypeError))
+                    throw err;
+                }
+                if (req === null || typeof req !== 'object')
+                  throw Error("Illegal arguments");
+                if (!(req instanceof method.resolvedRequestType.clazz))
+                  req = new method.resolvedRequestType.clazz(req);
+                this.rpcImpl(method.fqn(), req, function (err, res) { // Assumes that this is properly async
+                  if (err) {
+                    callback(err);
+                    return;
+                  }
+                  try { res = method.resolvedResponseType.clazz.decode(res); } catch (notABuffer) {}
+                  if (!res || !(res instanceof method.resolvedResponseType.clazz)) {
+                    callback(Error("Illegal response type received in service method " + T.name + "#" + method.name));
+                    return;
+                  }
+                  callback(null, res);
+                });
+              } catch (err) {
+                setTimeout(callback.bind(this, err), 0);
+              }
+            };
+
+            // Service.Method(rpcImpl, message, callback)
+            Service[method.name] = function (rpcImpl, req, callback) {
+              new Service(rpcImpl)[method.name](req, callback);
+            };
+
+            if (Object.defineProperty)
+              Object.defineProperty(Service[method.name], "$options", { "value": method.buildOpt() }),
+              Object.defineProperty(ServicePrototype[method.name], "$options", { "value": Service[method.name]["$options"] });
+          })(rpc[i]);
+        }
+
+        // Properties
+
+        /**
+         * Service options.
+         * @name ProtoBuf.Builder.Service.$options
+         * @type {Object.<string,*>}
+         * @expose
+         */
+        var $optionsS; // cc needs this
+
+        /**
+         * Service options.
+         * @name ProtoBuf.Builder.Service#$options
+         * @type {Object.<string,*>}
+         * @expose
+         */
+        var $options;
+
+        /**
+         * Reflection type.
+         * @name ProtoBuf.Builder.Service.$type
+         * @type {!ProtoBuf.Reflect.Service}
+         * @expose
+         */
+        var $typeS;
+
+        /**
+         * Reflection type.
+         * @name ProtoBuf.Builder.Service#$type
+         * @type {!ProtoBuf.Reflect.Service}
+         * @expose
+         */
+        var $type;
+
+        if (Object.defineProperty)
+          Object.defineProperty(Service, "$options", { "value": T.buildOpt() }),
+          Object.defineProperty(ServicePrototype, "$options", { "value": Service["$options"] }),
+          Object.defineProperty(Service, "$type", { "value": T }),
+          Object.defineProperty(ServicePrototype, "$type", { "value": T });
+
+        return Service;
+
+      })(ProtoBuf, this);
+    };
+
+    /**
+     * @alias ProtoBuf.Reflect.Service
+     * @expose
+     */
+    Reflect.Service = Service;
+
+    /**
+     * Abstract service method.
+     * @exports ProtoBuf.Reflect.Service.Method
+     * @param {!ProtoBuf.Builder} builder Builder reference
+     * @param {!ProtoBuf.Reflect.Service} svc Service
+     * @param {string} name Method name
+     * @param {Object.<string,*>=} options Options
+     * @constructor
+     * @extends ProtoBuf.Reflect.T
+     */
+    var Method = function (builder, svc, name, options) {
+      T.call(this, builder, svc, name);
+
+      /**
+       * @override
+       */
+      this.className = "Service.Method";
+
+      /**
+       * Options.
+       * @type {Object.<string, *>}
+       * @expose
+       */
+      this.options = options || {};
+    };
+
+    /**
+     * @alias ProtoBuf.Reflect.Service.Method.prototype
+     * @inner
+     */
+    var MethodPrototype = Method.prototype = Object.create(T.prototype);
+
+    /**
+     * Builds the method's '$options' property.
+     * @name ProtoBuf.Reflect.Service.Method#buildOpt
+     * @function
+     * @return {Object.<string,*>}
+     */
+    MethodPrototype.buildOpt = NamespacePrototype.buildOpt;
+
+    /**
+     * @alias ProtoBuf.Reflect.Service.Method
+     * @expose
+     */
+    Reflect.Service.Method = Method;
+
+    /**
+     * RPC service method.
+     * @exports ProtoBuf.Reflect.Service.RPCMethod
+     * @param {!ProtoBuf.Builder} builder Builder reference
+     * @param {!ProtoBuf.Reflect.Service} svc Service
+     * @param {string} name Method name
+     * @param {string} request Request message name
+     * @param {string} response Response message name
+     * @param {boolean} request_stream Whether requests are streamed
+     * @param {boolean} response_stream Whether responses are streamed
+     * @param {Object.<string,*>=} options Options
+     * @constructor
+     * @extends ProtoBuf.Reflect.Service.Method
+     */
+    var RPCMethod = function (builder, svc, name, request, response, request_stream, response_stream, options) {
+      Method.call(this, builder, svc, name, options);
+
+      /**
+       * @override
+       */
+      this.className = "Service.RPCMethod";
+
+      /**
+       * Request message name.
+       * @type {string}
+       * @expose
+       */
+      this.requestName = request;
+
+      /**
+       * Response message name.
+       * @type {string}
+       * @expose
+       */
+      this.responseName = response;
+
+      /**
+       * Whether requests are streamed
+       * @type {bool}
+       * @expose
+       */
+      this.requestStream = request_stream;
+
+      /**
+       * Whether responses are streamed
+       * @type {bool}
+       * @expose
+       */
+      this.responseStream = response_stream;
+
+      /**
+       * Resolved request message type.
+       * @type {ProtoBuf.Reflect.Message}
+       * @expose
+       */
+      this.resolvedRequestType = null;
+
+      /**
+       * Resolved response message type.
+       * @type {ProtoBuf.Reflect.Message}
+       * @expose
+       */
+      this.resolvedResponseType = null;
+    };
+
+    // Extends Method
+    RPCMethod.prototype = Object.create(Method.prototype);
+
+    /**
+     * @alias ProtoBuf.Reflect.Service.RPCMethod
+     * @expose
+     */
+    Reflect.Service.RPCMethod = RPCMethod;
+
+    return Reflect;
+
+  })(ProtoBuf);
+
+  /**
+   * @alias ProtoBuf.Builder
+   * @expose
+   */
+  ProtoBuf.Builder = (function (ProtoBuf, Lang, Reflect) {
+    "use strict";
+
+    /**
+     * Constructs a new Builder.
+     * @exports ProtoBuf.Builder
+     * @class Provides the functionality to build protocol messages.
+     * @param {Object.<string,*>=} options Options
+     * @constructor
+     */
+    var Builder = function (options) {
+
+      /**
+       * Namespace.
+       * @type {ProtoBuf.Reflect.Namespace}
+       * @expose
+       */
+      this.ns = new Reflect.Namespace(this, null, ""); // Global namespace
+
+      /**
+       * Namespace pointer.
+       * @type {ProtoBuf.Reflect.T}
+       * @expose
+       */
+      this.ptr = this.ns;
+
+      /**
+       * Resolved flag.
+       * @type {boolean}
+       * @expose
+       */
+      this.resolved = false;
+
+      /**
+       * The current building result.
+       * @type {Object.<string,ProtoBuf.Builder.Message|Object>|null}
+       * @expose
+       */
+      this.result = null;
+
+      /**
+       * Imported files.
+       * @type {Array.<string>}
+       * @expose
+       */
+      this.files = {};
+
+      /**
+       * Import root override.
+       * @type {?string}
+       * @expose
+       */
+      this.importRoot = null;
+
+      /**
+       * Options.
+       * @type {!Object.<string, *>}
+       * @expose
+       */
+      this.options = options || {};
+    };
+
+    /**
+     * @alias ProtoBuf.Builder.prototype
+     * @inner
+     */
+    var BuilderPrototype = Builder.prototype;
+
+    // ----- Definition tests -----
+
+    /**
+     * Tests if a definition most likely describes a message.
+     * @param {!Object} def
+     * @returns {boolean}
+     * @expose
+     */
+    Builder.isMessage = function (def) {
+      // Messages require a string name
+      if (typeof def["name"] !== 'string')
+        return false;
+      // Messages do not contain values (enum) or rpc methods (service)
+      if (typeof def["values"] !== 'undefined' || typeof def["rpc"] !== 'undefined')
+        return false;
+      return true;
+    };
+
+    /**
+     * Tests if a definition most likely describes a message field.
+     * @param {!Object} def
+     * @returns {boolean}
+     * @expose
+     */
+    Builder.isMessageField = function (def) {
+      // Message fields require a string rule, name and type and an id
+      if (typeof def["rule"] !== 'string' || typeof def["name"] !== 'string' || typeof def["type"] !== 'string' || typeof def["id"] === 'undefined')
+        return false;
+      return true;
+    };
+
+    /**
+     * Tests if a definition most likely describes an enum.
+     * @param {!Object} def
+     * @returns {boolean}
+     * @expose
+     */
+    Builder.isEnum = function (def) {
+      // Enums require a string name
+      if (typeof def["name"] !== 'string')
+        return false;
+      // Enums require at least one value
+      if (typeof def["values"] === 'undefined' || !Array.isArray(def["values"]) || def["values"].length === 0)
+        return false;
+      return true;
+    };
+
+    /**
+     * Tests if a definition most likely describes a service.
+     * @param {!Object} def
+     * @returns {boolean}
+     * @expose
+     */
+    Builder.isService = function (def) {
+      // Services require a string name and an rpc object
+      if (typeof def["name"] !== 'string' || typeof def["rpc"] !== 'object' || !def["rpc"])
+        return false;
+      return true;
+    };
+
+    /**
+     * Tests if a definition most likely describes an extended message
+     * @param {!Object} def
+     * @returns {boolean}
+     * @expose
+     */
+    Builder.isExtend = function (def) {
+      // Extends rquire a string ref
+      if (typeof def["ref"] !== 'string')
+        return false;
+      return true;
+    };
+
+    // ----- Building -----
+
+    /**
+     * Resets the pointer to the root namespace.
+     * @returns {!ProtoBuf.Builder} this
+     * @expose
+     */
+    BuilderPrototype.reset = function () {
+      this.ptr = this.ns;
+      return this;
+    };
+
+    /**
+     * Defines a namespace on top of the current pointer position and places the pointer on it.
+     * @param {string} namespace
+     * @return {!ProtoBuf.Builder} this
+     * @expose
+     */
+    BuilderPrototype.define = function (namespace) {
+      if (typeof namespace !== 'string' || !Lang.TYPEREF.test(namespace))
+        throw Error("illegal namespace: " + namespace);
+      namespace.split(".").forEach(function (part) {
+        var ns = this.ptr.getChild(part);
+        if (ns === null) // Keep existing
+          this.ptr.addChild(ns = new Reflect.Namespace(this, this.ptr, part));
+        this.ptr = ns;
+      }, this);
+      return this;
+    };
+
+    /**
+     * Creates the specified definitions at the current pointer position.
+     * @param {!Array.<!Object>} defs Messages, enums or services to create
+     * @returns {!ProtoBuf.Builder} this
+     * @throws {Error} If a message definition is invalid
+     * @expose
+     */
+    BuilderPrototype.create = function (defs) {
+      if (!defs)
+        return this; // Nothing to create
+      if (!Array.isArray(defs))
+        defs = [defs];
+      else {
+        if (defs.length === 0)
+          return this;
+        defs = defs.slice();
+      }
+
+      // It's quite hard to keep track of scopes and memory here, so let's do this iteratively.
+      var stack = [defs];
+      while (stack.length > 0) {
+        defs = stack.pop();
+
+        if (!Array.isArray(defs)) // Stack always contains entire namespaces
+          throw Error("not a valid namespace: " + JSON.stringify(defs));
+
+        while (defs.length > 0) {
+          var def = defs.shift(); // Namespaces always contain an array of messages, enums and services
+
+          if (Builder.isMessage(def)) {
+            var obj = new Reflect.Message(this, this.ptr, def["name"], def["options"], def["isGroup"], def["syntax"]);
+
+            // Create OneOfs
+            var oneofs = {};
+            if (def["oneofs"])
+              Object.keys(def["oneofs"]).forEach(function (name) {
+                obj.addChild(oneofs[name] = new Reflect.Message.OneOf(this, obj, name));
+              }, this);
+
+            // Create fields
+            if (def["fields"])
+              def["fields"].forEach(function (fld) {
+                if (obj.getChild(fld["id"] | 0) !== null)
+                  throw Error("duplicate or invalid field id in " + obj.name + ": " + fld['id']);
+                if (fld["options"] && typeof fld["options"] !== 'object')
+                  throw Error("illegal field options in " + obj.name + "#" + fld["name"]);
+                var oneof = null;
+                if (typeof fld["oneof"] === 'string' && !(oneof = oneofs[fld["oneof"]]))
+                  throw Error("illegal oneof in " + obj.name + "#" + fld["name"] + ": " + fld["oneof"]);
+                fld = new Reflect.Message.Field(this, obj, fld["rule"], fld["keytype"], fld["type"], fld["name"], fld["id"], fld["options"], oneof, def["syntax"]);
+                if (oneof)
+                  oneof.fields.push(fld);
+                obj.addChild(fld);
+              }, this);
+
+            // Push children to stack
+            var subObj = [];
+            if (def["enums"])
+              def["enums"].forEach(function (enm) {
+                subObj.push(enm);
+              });
+            if (def["messages"])
+              def["messages"].forEach(function (msg) {
+                subObj.push(msg);
+              });
+            if (def["services"])
+              def["services"].forEach(function (svc) {
+                subObj.push(svc);
+              });
+
+            // Set extension range
+            if (def["extensions"]) {
+              obj.extensions = def["extensions"];
+              if (obj.extensions[0] < ProtoBuf.ID_MIN)
+                obj.extensions[0] = ProtoBuf.ID_MIN;
+              if (obj.extensions[1] > ProtoBuf.ID_MAX)
+                obj.extensions[1] = ProtoBuf.ID_MAX;
+            }
+
+            // Create on top of current namespace
+            this.ptr.addChild(obj);
+            if (subObj.length > 0) {
+              stack.push(defs); // Push the current level back
+              defs = subObj; // Continue processing sub level
+              subObj = null;
+              this.ptr = obj; // And move the pointer to this namespace
+              obj = null;
+              continue;
+            }
+            subObj = null;
+
+          } else if (Builder.isEnum(def)) {
+
+            obj = new Reflect.Enum(this, this.ptr, def["name"], def["options"], def["syntax"]);
+            def["values"].forEach(function (val) {
+              obj.addChild(new Reflect.Enum.Value(this, obj, val["name"], val["id"]));
+            }, this);
+            this.ptr.addChild(obj);
+
+          } else if (Builder.isService(def)) {
+
+            obj = new Reflect.Service(this, this.ptr, def["name"], def["options"]);
+            Object.keys(def["rpc"]).forEach(function (name) {
+              var mtd = def["rpc"][name];
+              obj.addChild(new Reflect.Service.RPCMethod(this, obj, name, mtd["request"], mtd["response"], !!mtd["request_stream"], !!mtd["response_stream"], mtd["options"]));
+            }, this);
+            this.ptr.addChild(obj);
+
+          } else if (Builder.isExtend(def)) {
+
+            obj = this.ptr.resolve(def["ref"], true);
+            if (obj) {
+              def["fields"].forEach(function (fld) {
+                if (obj.getChild(fld['id'] | 0) !== null)
+                  throw Error("duplicate extended field id in " + obj.name + ": " + fld['id']);
+                if (fld['id'] < obj.extensions[0] || fld['id'] > obj.extensions[1])
+                  throw Error("illegal extended field id in " + obj.name + ": " + fld['id'] + " (" + obj.extensions.join(' to ') + " expected)");
+                // Convert extension field names to camel case notation if the override is set
+                var name = fld["name"];
+                if (this.options['convertFieldsToCamelCase'])
+                  name = ProtoBuf.Util.toCamelCase(name);
+                // see #161: Extensions use their fully qualified name as their runtime key and...
+                var field = new Reflect.Message.ExtensionField(this, obj, fld["rule"], fld["type"], this.ptr.fqn() + '.' + name, fld["id"], fld["options"]);
+                // ...are added on top of the current namespace as an extension which is used for
+                // resolving their type later on (the extension always keeps the original name to
+                // prevent naming collisions)
+                var ext = new Reflect.Extension(this, this.ptr, fld["name"], field);
+                field.extension = ext;
+                this.ptr.addChild(ext);
+                obj.addChild(field);
+              }, this);
+
+            } else if (!/\.?google\.protobuf\./.test(def["ref"])) // Silently skip internal extensions
+              throw Error("extended message " + def["ref"] + " is not defined");
+
+          } else
+            throw Error("not a valid definition: " + JSON.stringify(def));
+
+          def = null;
+          obj = null;
+        }
+        // Break goes here
+        defs = null;
+        this.ptr = this.ptr.parent; // Namespace done, continue at parent
+      }
+      this.resolved = false; // Require re-resolve
+      this.result = null; // Require re-build
+      return this;
+    };
+
+    /**
+     * Propagates syntax to all children.
+     * @param {!Object} parent
+     * @inner
+     */
+    function propagateSyntax(parent) {
+      if (parent['messages']) {
+        parent['messages'].forEach(function (child) {
+          child["syntax"] = parent["syntax"];
+          propagateSyntax(child);
+        });
+      }
+      if (parent['enums']) {
+        parent['enums'].forEach(function (child) {
+          child["syntax"] = parent["syntax"];
+        });
+      }
+    }
+
+    /**
+     * Imports another definition into this builder.
+     * @param {Object.<string,*>} json Parsed import
+     * @param {(string|{root: string, file: string})=} filename Imported file name
+     * @returns {!ProtoBuf.Builder} this
+     * @throws {Error} If the definition or file cannot be imported
+     * @expose
+     */
+    BuilderPrototype["import"] = function (json, filename) {
+      var delim = '/';
+
+      // Make sure to skip duplicate imports
+
+      if (typeof filename === 'string') {
+
+        if (ProtoBuf.Util.IS_NODE)
+          filename = ProtoBuf.Util.require("path")['resolve'](filename);
+        if (this.files[filename] === true)
+          return this.reset();
+        this.files[filename] = true;
+
+      } else if (typeof filename === 'object') { // Object with root, file.
+
+        var root = filename.root;
+        if (ProtoBuf.Util.IS_NODE)
+          root = ProtoBuf.Util.require("path")['resolve'](root);
+        if (root.indexOf("\\") >= 0 || filename.file.indexOf("\\") >= 0)
+          delim = '\\';
+        var fname = root + delim + filename.file;
+        if (this.files[fname] === true)
+          return this.reset();
+        this.files[fname] = true;
+      }
+
+      // Import imports
+
+      if (json['imports'] && json['imports'].length > 0) {
+        var importRoot,
+          resetRoot = false;
+
+        if (typeof filename === 'object') { // If an import root is specified, override
+
+          this.importRoot = filename["root"];
+          resetRoot = true; // ... and reset afterwards
+          importRoot = this.importRoot;
+          filename = filename["file"];
+          if (importRoot.indexOf("\\") >= 0 || filename.indexOf("\\") >= 0)
+            delim = '\\';
+
+        } else if (typeof filename === 'string') {
+
+          if (this.importRoot) // If import root is overridden, use it
+            importRoot = this.importRoot;
+          else { // Otherwise compute from filename
+            if (filename.indexOf("/") >= 0) { // Unix
+              importRoot = filename.replace(/\/[^\/]*$/, "");
+              if ( /* /file.proto */ importRoot === "")
+                importRoot = "/";
+            } else if (filename.indexOf("\\") >= 0) { // Windows
+              importRoot = filename.replace(/\\[^\\]*$/, "");
+              delim = '\\';
+            } else
+              importRoot = ".";
+          }
+
+        } else
+          importRoot = null;
+
+        for (var i = 0; i < json['imports'].length; i++) {
+          if (typeof json['imports'][i] === 'string') { // Import file
+            if (!importRoot)
+              throw Error("cannot determine import root");
+            var importFilename = json['imports'][i];
+            if (importFilename === "google/protobuf/descriptor.proto")
+              continue; // Not needed and therefore not used
+            importFilename = importRoot + delim + importFilename;
+            if (this.files[importFilename] === true)
+              continue; // Already imported
+            if (/\.proto$/i.test(importFilename) && !ProtoBuf.DotProto) // If this is a light build
+              importFilename = importFilename.replace(/\.proto$/, ".json"); // always load the JSON file
+            var contents = ProtoBuf.Util.fetch(importFilename);
+            if (contents === null)
+              throw Error("failed to import '" + importFilename + "' in '" + filename + "': file not found");
+            if (/\.json$/i.test(importFilename)) // Always possible
+              this["import"](JSON.parse(contents + ""), importFilename); // May throw
+            else
+              this["import"](ProtoBuf.DotProto.Parser.parse(contents), importFilename); // May throw
+          } else // Import structure
+          if (!filename)
+            this["import"](json['imports'][i]);
+          else if (/\.(\w+)$/.test(filename)) // With extension: Append _importN to the name portion to make it unique
+            this["import"](json['imports'][i], filename.replace(/^(.+)\.(\w+)$/, function ($0, $1, $2) {
+            return $1 + "_import" + i + "." + $2;
+          }));
+          else // Without extension: Append _importN to make it unique
+            this["import"](json['imports'][i], filename + "_import" + i);
+        }
+        if (resetRoot) // Reset import root override when all imports are done
+          this.importRoot = null;
+      }
+
+      // Import structures
+
+      if (json['package'])
+        this.define(json['package']);
+      if (json['syntax'])
+        propagateSyntax(json);
+      var base = this.ptr;
+      if (json['options'])
+        Object.keys(json['options']).forEach(function (key) {
+          base.options[key] = json['options'][key];
+        });
+      if (json['messages'])
+        this.create(json['messages']),
+        this.ptr = base;
+      if (json['enums'])
+        this.create(json['enums']),
+        this.ptr = base;
+      if (json['services'])
+        this.create(json['services']),
+        this.ptr = base;
+      if (json['extends'])
+        this.create(json['extends']);
+
+      return this.reset();
+    };
+
+    /**
+     * Resolves all namespace objects.
+     * @throws {Error} If a type cannot be resolved
+     * @returns {!ProtoBuf.Builder} this
+     * @expose
+     */
+    BuilderPrototype.resolveAll = function () {
+      // Resolve all reflected objects
+      var res;
+      if (this.ptr == null || typeof this.ptr.type === 'object')
+        return this; // Done (already resolved)
+
+      if (this.ptr instanceof Reflect.Namespace) { // Resolve children
+
+        this.ptr.children.forEach(function (child) {
+          this.ptr = child;
+          this.resolveAll();
+        }, this);
+
+      } else if (this.ptr instanceof Reflect.Message.Field) { // Resolve type
+
+        if (!Lang.TYPE.test(this.ptr.type)) {
+          if (!Lang.TYPEREF.test(this.ptr.type))
+            throw Error("illegal type reference in " + this.ptr.toString(true) + ": " + this.ptr.type);
+          res = (this.ptr instanceof Reflect.Message.ExtensionField ? this.ptr.extension.parent : this.ptr.parent).resolve(this.ptr.type, true);
+          if (!res)
+            throw Error("unresolvable type reference in " + this.ptr.toString(true) + ": " + this.ptr.type);
+          this.ptr.resolvedType = res;
+          if (res instanceof Reflect.Enum) {
+            this.ptr.type = ProtoBuf.TYPES["enum"];
+            if (this.ptr.syntax === 'proto3' && res.syntax !== 'proto3')
+              throw Error("proto3 message cannot reference proto2 enum");
+          } else if (res instanceof Reflect.Message)
+            this.ptr.type = res.isGroup ? ProtoBuf.TYPES["group"] : ProtoBuf.TYPES["message"];
+          else
+            throw Error("illegal type reference in " + this.ptr.toString(true) + ": " + this.ptr.type);
+        } else
+          this.ptr.type = ProtoBuf.TYPES[this.ptr.type];
+
+        // If it's a map field, also resolve the key type. The key type can be only a numeric, string, or bool type
+        // (i.e., no enums or messages), so we don't need to resolve against the current namespace.
+        if (this.ptr.map) {
+          if (!Lang.TYPE.test(this.ptr.keyType))
+            throw Error("illegal key type for map field in " + this.ptr.toString(true) + ": " + this.ptr.keyType);
+          this.ptr.keyType = ProtoBuf.TYPES[this.ptr.keyType];
+        }
+
+      } else if (this.ptr instanceof ProtoBuf.Reflect.Service.Method) {
+
+        if (this.ptr instanceof ProtoBuf.Reflect.Service.RPCMethod) {
+          res = this.ptr.parent.resolve(this.ptr.requestName, true);
+          if (!res || !(res instanceof ProtoBuf.Reflect.Message))
+            throw Error("Illegal type reference in " + this.ptr.toString(true) + ": " + this.ptr.requestName);
+          this.ptr.resolvedRequestType = res;
+          res = this.ptr.parent.resolve(this.ptr.responseName, true);
+          if (!res || !(res instanceof ProtoBuf.Reflect.Message))
+            throw Error("Illegal type reference in " + this.ptr.toString(true) + ": " + this.ptr.responseName);
+          this.ptr.resolvedResponseType = res;
+        } else // Should not happen as nothing else is implemented
+          throw Error("illegal service type in " + this.ptr.toString(true));
+
+      } else if (!(this.ptr instanceof ProtoBuf.Reflect.Message.OneOf) && // Not built
+        !(this.ptr instanceof ProtoBuf.Reflect.Extension) && // Not built
+        !(this.ptr instanceof ProtoBuf.Reflect.Enum.Value) // Built in enum
+      )
+        throw Error("illegal object in namespace: " + typeof (this.ptr) + ": " + this.ptr);
+
+      return this.reset();
+    };
+
+    /**
+     * Builds the protocol. This will first try to resolve all definitions and, if this has been successful,
+     * return the built package.
+     * @param {(string|Array.<string>)=} path Specifies what to return. If omitted, the entire namespace will be returned.
+     * @returns {!ProtoBuf.Builder.Message|!Object.<string,*>}
+     * @throws {Error} If a type could not be resolved
+     * @expose
+     */
+    BuilderPrototype.build = function (path) {
+      this.reset();
+      if (!this.resolved)
+        this.resolveAll(),
+        this.resolved = true,
+        this.result = null; // Require re-build
+      if (this.result === null) // (Re-)Build
+        this.result = this.ns.build();
+      if (!path)
+        return this.result;
+      var part = typeof path === 'string' ? path.split(".") : path,
+        ptr = this.result; // Build namespace pointer (no hasChild etc.)
+      for (var i = 0; i < part.length; i++)
+        if (ptr[part[i]])
+          ptr = ptr[part[i]];
+        else {
+          ptr = null;
+          break;
+        }
+      return ptr;
+    };
+
+    /**
+     * Similar to {@link ProtoBuf.Builder#build}, but looks up the internal reflection descriptor.
+     * @param {string=} path Specifies what to return. If omitted, the entire namespace wiil be returned.
+     * @param {boolean=} excludeNonNamespace Excludes non-namespace types like fields, defaults to `false`
+     * @returns {?ProtoBuf.Reflect.T} Reflection descriptor or `null` if not found
+     */
+    BuilderPrototype.lookup = function (path, excludeNonNamespace) {
+      return path ? this.ns.resolve(path, excludeNonNamespace) : this.ns;
+    };
+
+    /**
+     * Returns a string representation of this object.
+     * @return {string} String representation as of "Builder"
+     * @expose
+     */
+    BuilderPrototype.toString = function () {
+      return "Builder";
+    };
+
+    // ----- Base classes -----
+    // Exist for the sole purpose of being able to "... instanceof ProtoBuf.Builder.Message" etc.
+
+    /**
+     * @alias ProtoBuf.Builder.Message
+     */
+    Builder.Message = function () {};
+
+    /**
+     * @alias ProtoBuf.Builder.Enum
+     */
+    Builder.Enum = function () {};
+
+    /**
+     * @alias ProtoBuf.Builder.Message
+     */
+    Builder.Service = function () {};
+
+    return Builder;
+
+  })(ProtoBuf, ProtoBuf.Lang, ProtoBuf.Reflect);
+
+  /**
+   * @alias ProtoBuf.Map
+   * @expose
+   */
+  ProtoBuf.Map = (function (ProtoBuf, Reflect) {
+    "use strict";
+
+    /**
+     * Constructs a new Map. A Map is a container that is used to implement map
+     * fields on message objects. It closely follows the ES6 Map API; however,
+     * it is distinct because we do not want to depend on external polyfills or
+     * on ES6 itself.
+     *
+     * @exports ProtoBuf.Map
+     * @param {!ProtoBuf.Reflect.Field} field Map field
+     * @param {Object.<string,*>=} contents Initial contents
+     * @constructor
+     */
+    var Map = function (field, contents) {
+      if (!field.map)
+        throw Error("field is not a map");
+
+      /**
+       * The field corresponding to this map.
+       * @type {!ProtoBuf.Reflect.Field}
+       */
+      this.field = field;
+
+      /**
+       * Element instance corresponding to key type.
+       * @type {!ProtoBuf.Reflect.Element}
+       */
+      this.keyElem = new Reflect.Element(field.keyType, null, true, field.syntax);
+
+      /**
+       * Element instance corresponding to value type.
+       * @type {!ProtoBuf.Reflect.Element}
+       */
+      this.valueElem = new Reflect.Element(field.type, field.resolvedType, false, field.syntax);
+
+      /**
+       * Internal map: stores mapping of (string form of key) -> (key, value)
+       * pair.
+       *
+       * We provide map semantics for arbitrary key types, but we build on top
+       * of an Object, which has only string keys. In order to avoid the need
+       * to convert a string key back to its native type in many situations,
+       * we store the native key value alongside the value. Thus, we only need
+       * a one-way mapping from a key type to its string form that guarantees
+       * uniqueness and equality (i.e., str(K1) === str(K2) if and only if K1
+       * === K2).
+       *
+       * @type {!Object<string, {key: *, value: *}>}
+       */
+      this.map = {};
+
+      /**
+       * Returns the number of elements in the map.
+       */
+      Object.defineProperty(this, "size", {
+        get: function () {
+          return Object.keys(this.map).length;
+        }
+      });
+
+      // Fill initial contents from a raw object.
+      if (contents) {
+        var keys = Object.keys(contents);
+        for (var i = 0; i < keys.length; i++) {
+          var key = this.keyElem.valueFromString(keys[i]);
+          var val = this.valueElem.verifyValue(contents[keys[i]]);
+          this.map[this.keyElem.valueToString(key)] = { key: key, value: val };
+        }
+      }
+    };
+
+    var MapPrototype = Map.prototype;
+
+    /**
+     * Helper: return an iterator over an array.
+     * @param {!Array<*>} arr the array
+     * @returns {!Object} an iterator
+     * @inner
+     */
+    function arrayIterator(arr) {
+      var idx = 0;
+      return {
+        next: function () {
+          if (idx < arr.length)
+            return { done: false, value: arr[idx++] };
+          return { done: true };
+        }
+      }
+    }
+
+    /**
+     * Clears the map.
+     */
+    MapPrototype.clear = function () {
+      this.map = {};
+    };
+
+    /**
+     * Deletes a particular key from the map.
+     * @returns {boolean} Whether any entry with this key was deleted.
+     */
+    MapPrototype["delete"] = function (key) {
+      var keyValue = this.keyElem.valueToString(this.keyElem.verifyValue(key));
+      var hadKey = keyValue in this.map;
+      delete this.map[keyValue];
+      return hadKey;
+    };
+
+    /**
+     * Returns an iterator over [key, value] pairs in the map.
+     * @returns {Object} The iterator
+     */
+    MapPrototype.entries = function () {
+      var entries = [];
+      var strKeys = Object.keys(this.map);
+      for (var i = 0, entry; i < strKeys.length; i++)
+        entries.push([(entry = this.map[strKeys[i]]).key, entry.value]);
+      return arrayIterator(entries);
+    };
+
+    /**
+     * Returns an iterator over keys in the map.
+     * @returns {Object} The iterator
+     */
+    MapPrototype.keys = function () {
+      var keys = [];
+      var strKeys = Object.keys(this.map);
+      for (var i = 0; i < strKeys.length; i++)
+        keys.push(this.map[strKeys[i]].key);
+      return arrayIterator(keys);
+    };
+
+    /**
+     * Returns an iterator over values in the map.
+     * @returns {!Object} The iterator
+     */
+    MapPrototype.values = function () {
+      var values = [];
+      var strKeys = Object.keys(this.map);
+      for (var i = 0; i < strKeys.length; i++)
+        values.push(this.map[strKeys[i]].value);
+      return arrayIterator(values);
+    };
+
+    /**
+     * Iterates over entries in the map, calling a function on each.
+     * @param {function(this:*, *, *, *)} cb The callback to invoke with value, key, and map arguments.
+     * @param {Object=} thisArg The `this` value for the callback
+     */
+    MapPrototype.forEach = function (cb, thisArg) {
+      var strKeys = Object.keys(this.map);
+      for (var i = 0, entry; i < strKeys.length; i++)
+        cb.call(thisArg, (entry = this.map[strKeys[i]]).value, entry.key, this);
+    };
+
+    /**
+     * Sets a key in the map to the given value.
+     * @param {*} key The key
+     * @param {*} value The value
+     * @returns {!ProtoBuf.Map} The map instance
+     */
+    MapPrototype.set = function (key, value) {
+      var keyValue = this.keyElem.verifyValue(key);
+      var valValue = this.valueElem.verifyValue(value);
+      this.map[this.keyElem.valueToString(keyValue)] = { key: keyValue, value: valValue };
+      return this;
+    };
+
+    /**
+     * Gets the value corresponding to a key in the map.
+     * @param {*} key The key
+     * @returns {*|undefined} The value, or `undefined` if key not present
+     */
+    MapPrototype.get = function (key) {
+      var keyValue = this.keyElem.valueToString(this.keyElem.verifyValue(key));
+      if (!(keyValue in this.map))
+        return undefined;
+      return this.map[keyValue].value;
+    };
+
+    /**
+     * Determines whether the given key is present in the map.
+     * @param {*} key The key
+     * @returns {boolean} `true` if the key is present
+     */
+    MapPrototype.has = function (key) {
+      var keyValue = this.keyElem.valueToString(this.keyElem.verifyValue(key));
+      return (keyValue in this.map);
+    };
+
+    return Map;
+  })(ProtoBuf, ProtoBuf.Reflect);
+
+  /**
+   * Loads a .proto string and returns the Builder.
+   * @param {string} proto .proto file contents
+   * @param {(ProtoBuf.Builder|string|{root: string, file: string})=} builder Builder to append to. Will create a new one if omitted.
+   * @param {(string|{root: string, file: string})=} filename The corresponding file name if known. Must be specified for imports.
+   * @return {ProtoBuf.Builder} Builder to create new messages
+   * @throws {Error} If the definition cannot be parsed or built
+   * @expose
+   */
+  ProtoBuf.loadProto = function (proto, builder, filename) {
+    if (typeof builder === 'string' || (builder && typeof builder["file"] === 'string' && typeof builder["root"] === 'string'))
+      filename = builder,
+      builder = undefined;
+    return ProtoBuf.loadJson(ProtoBuf.DotProto.Parser.parse(proto), builder, filename);
+  };
+
+  /**
+   * Loads a .proto string and returns the Builder. This is an alias of {@link ProtoBuf.loadProto}.
+   * @function
+   * @param {string} proto .proto file contents
+   * @param {(ProtoBuf.Builder|string)=} builder Builder to append to. Will create a new one if omitted.
+   * @param {(string|{root: string, file: string})=} filename The corresponding file name if known. Must be specified for imports.
+   * @return {ProtoBuf.Builder} Builder to create new messages
+   * @throws {Error} If the definition cannot be parsed or built
+   * @expose
+   */
+  ProtoBuf.protoFromString = ProtoBuf.loadProto; // Legacy
+
+  /**
+   * Loads a .proto file and returns the Builder.
+   * @param {string|{root: string, file: string}} filename Path to proto file or an object specifying 'file' with
+   *  an overridden 'root' path for all imported files.
+   * @param {function(?Error, !ProtoBuf.Builder=)=} callback Callback that will receive `null` as the first and
+   *  the Builder as its second argument on success, otherwise the error as its first argument. If omitted, the
+   *  file will be read synchronously and this function will return the Builder.
+   * @param {ProtoBuf.Builder=} builder Builder to append to. Will create a new one if omitted.
+   * @return {?ProtoBuf.Builder|undefined} The Builder if synchronous (no callback specified, will be NULL if the
+   *   request has failed), else undefined
+   * @expose
+   */
+  ProtoBuf.loadProtoFile = function (filename, callback, builder) {
+    if (callback && typeof callback === 'object')
+      builder = callback,
+      callback = null;
+    else if (!callback || typeof callback !== 'function')
+      callback = null;
+    if (callback)
+      return ProtoBuf.Util.fetch(typeof filename === 'string' ? filename : filename["root"] + "/" + filename["file"], function (contents) {
+        if (contents === null) {
+          callback(Error("Failed to fetch file"));
+          return;
+        }
+        try {
+          callback(null, ProtoBuf.loadProto(contents, builder, filename));
+        } catch (e) {
+          callback(e);
+        }
+      });
+    var contents = ProtoBuf.Util.fetch(typeof filename === 'object' ? filename["root"] + "/" + filename["file"] : filename);
+    return contents === null ? null : ProtoBuf.loadProto(contents, builder, filename);
+  };
+
+  /**
+   * Loads a .proto file and returns the Builder. This is an alias of {@link ProtoBuf.loadProtoFile}.
+   * @function
+   * @param {string|{root: string, file: string}} filename Path to proto file or an object specifying 'file' with
+   *  an overridden 'root' path for all imported files.
+   * @param {function(?Error, !ProtoBuf.Builder=)=} callback Callback that will receive `null` as the first and
+   *  the Builder as its second argument on success, otherwise the error as its first argument. If omitted, the
+   *  file will be read synchronously and this function will return the Builder.
+   * @param {ProtoBuf.Builder=} builder Builder to append to. Will create a new one if omitted.
+   * @return {!ProtoBuf.Builder|undefined} The Builder if synchronous (no callback specified, will be NULL if the
+   *   request has failed), else undefined
+   * @expose
+   */
+  ProtoBuf.protoFromFile = ProtoBuf.loadProtoFile; // Legacy
+
+  /**
+   * Constructs a new empty Builder.
+   * @param {Object.<string,*>=} options Builder options, defaults to global options set on ProtoBuf
+   * @return {!ProtoBuf.Builder} Builder
+   * @expose
+   */
+  ProtoBuf.newBuilder = function (options) {
+    options = options || {};
+    if (typeof options['convertFieldsToCamelCase'] === 'undefined')
+      options['convertFieldsToCamelCase'] = ProtoBuf.convertFieldsToCamelCase;
+    if (typeof options['populateAccessors'] === 'undefined')
+      options['populateAccessors'] = ProtoBuf.populateAccessors;
+    return new ProtoBuf.Builder(options);
+  };
+
+  /**
+   * Loads a .json definition and returns the Builder.
+   * @param {!*|string} json JSON definition
+   * @param {(ProtoBuf.Builder|string|{root: string, file: string})=} builder Builder to append to. Will create a new one if omitted.
+   * @param {(string|{root: string, file: string})=} filename The corresponding file name if known. Must be specified for imports.
+   * @return {ProtoBuf.Builder} Builder to create new messages
+   * @throws {Error} If the definition cannot be parsed or built
+   * @expose
+   */
+  ProtoBuf.loadJson = function (json, builder, filename) {
+    if (typeof builder === 'string' || (builder && typeof builder["file"] === 'string' && typeof builder["root"] === 'string'))
+      filename = builder,
+      builder = null;
+    if (!builder || typeof builder !== 'object')
+      builder = ProtoBuf.newBuilder();
+    if (typeof json === 'string')
+      json = JSON.parse(json);
+    builder["import"](json, filename);
+    builder.resolveAll();
+    return builder;
+  };
+
+  /**
+   * Loads a .json file and returns the Builder.
+   * @param {string|!{root: string, file: string}} filename Path to json file or an object specifying 'file' with
+   *  an overridden 'root' path for all imported files.
+   * @param {function(?Error, !ProtoBuf.Builder=)=} callback Callback that will receive `null` as the first and
+   *  the Builder as its second argument on success, otherwise the error as its first argument. If omitted, the
+   *  file will be read synchronously and this function will return the Builder.
+   * @param {ProtoBuf.Builder=} builder Builder to append to. Will create a new one if omitted.
+   * @return {?ProtoBuf.Builder|undefined} The Builder if synchronous (no callback specified, will be NULL if the
+   *   request has failed), else undefined
+   * @expose
+   */
+  ProtoBuf.loadJsonFile = function (filename, callback, builder) {
+    if (callback && typeof callback === 'object')
+      builder = callback,
+      callback = null;
+    else if (!callback || typeof callback !== 'function')
+      callback = null;
+    if (callback)
+      return ProtoBuf.Util.fetch(typeof filename === 'string' ? filename : filename["root"] + "/" + filename["file"], function (contents) {
+        if (contents === null) {
+          callback(Error("Failed to fetch file"));
+          return;
+        }
+        try {
+          callback(null, ProtoBuf.loadJson(JSON.parse(contents), builder, filename));
+        } catch (e) {
+          callback(e);
+        }
+      });
+    var contents = ProtoBuf.Util.fetch(typeof filename === 'object' ? filename["root"] + "/" + filename["file"] : filename);
+    return contents === null ? null : ProtoBuf.loadJson(JSON.parse(contents), builder, filename);
+  };
+
+  return ProtoBuf;
+});
+
diff --git a/src/pdu/index.js b/src/pdu/index.js
new file mode 100644
index 0000000..3bf6477
--- /dev/null
+++ b/src/pdu/index.js
@@ -0,0 +1,96 @@
+import ProtoBuf from 'protobufjs';
+import pro from './pro';
+import PduType from './PduType';
+import PduConsts from './PduConsts';
+
+let builder = ProtoBuf.newBuilder({ convertFieldsToCamelCase: true });
+ProtoBuf.loadProto(pro, builder);
+
+let pdu = builder.build();
+
+// 底层通信层[Firstlayer] - RCSendDataPdu封包
+function create_pdu(type, sub_type,
+  initiator, conference_id, session_id,
+  channel_id, upward, reliability, priority,
+  peer, seg) {
+  var pduMsg = new pdu['RCSendDataPdu']();
+  pduMsg.set("type", type);
+  pduMsg.set("subType", sub_type);
+  pduMsg.set("initiator", initiator);
+  pduMsg.set("confId", conference_id);
+  pduMsg.set("sessionId", session_id);
+  pduMsg.set("channelId", channel_id);
+  pduMsg.set("upward", upward);
+  pduMsg.set("reliability", reliability);
+  pduMsg.set("priority", priority);
+  pduMsg.set("peer", peer);
+  pduMsg.set("seg", seg);
+  return pduMsg;
+}
+// 底层通信层[Firstlayer] - RCSendDataPdu解包
+pdu.decode_pdu = function (buffer) {
+  return pdu['RCSendDataPdu'].decode(buffer);
+};
+
+pdu.create_connect_provider_request_pdu = function (
+  sub_type,
+  initiator,
+  conference_id,
+  session_id,
+  channel_id,
+  reliability,
+  priority,
+  peer,
+  seg
+) {
+  return create_pdu(PduType.RCPDU_CONNECT_PROVIDER_REQUEST,
+    sub_type, initiator, conference_id, session_id, channel_id, true,
+    reliability, priority, peer, seg);
+};
+
+pdu.create_uniform_pdu = function (
+  sub_type,
+  initiator,
+  conference_id,
+  session_id,
+  channel_id,
+  reliability,
+  priority,
+  peer,
+  seg
+) {
+  return create_pdu(PduType.RCPDU_UNIFORM_SEND_DATA_REQUEST,
+    sub_type, initiator, conference_id, session_id, channel_id, true,
+    reliability, priority, peer, seg);
+};
+
+pdu.create_normal_pdu = function (
+  sub_type,
+  initiator,
+  conference_id,
+  session_id,
+  channel_id,
+  upward,
+  reliability,
+  priority,
+  peer,
+  seg
+) {
+  return create_pdu(PduType.RCPDU_SEND_DATA_REQUEST,
+    sub_type, initiator, conference_id, session_id, channel_id, upward,
+    reliability, priority, peer, seg);
+};
+
+pdu.id2type = function (id) {
+  for (var type_const in PduType) {
+    if (PduType[type_const] === id) {
+      return type_const;
+    }
+  }
+};
+
+// 合并统一对外
+pdu = {...pdu, ...PduType, ...PduConsts };
+
+export default pdu;
+
diff --git a/src/pdu/pro.js b/src/pdu/pro.js
new file mode 100644
index 0000000..59ffa45
--- /dev/null
+++ b/src/pdu/pro.js
@@ -0,0 +1,850 @@
+export default `
+//start
+// pdu_def.proto
+
+// syntax = "proto3";
+
+option optimize_for = LITE_RUNTIME;
+enum RCPduPriority_E {
+    DP_TOP = 0;
+    DP_HIGH = 1;
+    DP_MEDIUM = 2;
+    DP_LOW = 3;
+}
+
+enum RCPduSegment_E {
+    SEG_BEGIN = 0;
+    SEG_END = 1;
+    SEG_ONCE = 2;
+}
+
+enum RCPduTokenStatus_E {
+    TS_NOT_IN_USE = 0;
+    TS_SELF_GRABBED = 1;
+    TS_OTHER_GRABBED = 2;
+    TS_SELF_INHIBITED = 3;
+    TS_OTHER_INHIBITED = 4;
+    TS_SELF_RECIPIENT = 5;
+    TS_SELF_GIVING = 6;
+    TS_OTHER_GIVING = 7;
+}
+
+enum RCPduType_E {
+// GCC PDU
+    RCPDU_CONNECT_PROVIDER_REQUEST = 0;
+    RCPDU_CONNECT_PROVIDER_RESPONSE = 1;
+    RCPDU_CONFERENCE_JOIN_REQUEST = 2;
+    RCPDU_CONFERENCE_JOIN_RESPONSE = 3;
+    RCPDU_CONFERENCE_INVITE_REQUEST = 10;
+    RCPDU_CONFERENCE_INVITE_RESPONSE = 11;
+    RCPDU_CONFERENCE_LOCK_REQUEST = 20;
+    RCPDU_CONFERENCE_LOCK_RESPONSE = 21;
+    RCPDU_CONFERENCE_LOCK_INDICATION = 22;
+    RCPDU_CONFERENCE_UNLOCK_REQUEST = 30;
+    RCPDU_CONFERENCE_UNLOCK_RESPONSE = 31;
+    RCPDU_CONFERENCE_UNLOCK_INDICATION = 32;
+    RCPDU_CONFERENCE_LEAVE_REQUEST = 39;
+    RCPDU_CONFERENCE_TERMINATE_REQUEST = 40;
+    RCPDU_CONFERENCE_TERMINATE_RESPONSE = 41;
+    RCPDU_CONFERENCE_TERMINATE_INDICATION = 42;
+    RCPDU_CONFERENCE_EJECT_USER_REQUEST = 50;
+    RCPDU_CONFERENCE_EJECT_USER_RESPONSE = 51;
+    RCPDU_CONFERENCE_EJECT_USER_INDICATION = 52;
+    RCPDU_ROSTER_UPDATE_INDICATION = 60;
+    RCPDU_REGISTRY_UPDATE_REQUEST = 70; // INCLUDE ALL OBJS OPERATION
+    RCPDU_REGISTRY_UPDATE_RESPONSE = 71;
+    RCPDU_REGISTRY_UPDATE_INDICATION = 72;
+    RCPDU_FUNCTION_NOT_SUPPORTED_RESPONSE = 80;
+
+    // MCS PDU
+    RCPDU_SESSION_JOIN_REQUEST = 90;
+    RCPDU_SESSION_JOIN_RESPONSE = 91;
+    RCPDU_CHANNEL_GRAB_REQUEST = 100;
+    RCPDU_CHANNEL_GRAB_RESPONSE = 101;
+    RCPDU_CHANNEL_GRAB_INDICATION = 102;
+    RCPDU_CHANNEL_JOIN_REQUEST = 103;
+    RCPDU_CHANNEL_JOIN_RESPONSE = 104;
+    RCPDU_CHANNEL_LEAVE_REQUEST = 105;
+    RCPDU_CHANNEL_RELEASE_REQUEST = 106;
+    RCPDU_CHANNEL_RELEASE_INDICATION = 107;
+    RCPDU_SEND_DATA_REQUEST = 120;
+    RCPDU_SEND_DATA_INDICATION = 121;
+    RCPDU_UNIFORM_SEND_DATA_REQUEST = 125;
+    RCPDU_UNIFORM_SEND_DATA_INDICATION = 126;
+    RCPDU_TOKEN_GRAB_REQUEST = 130;
+    RCPDU_TOKEN_GRAB_CONFIRM = 131;
+    RCPDU_TOKEN_INHIBIT_REQUEST = 132;
+    RCPDU_TOKEN_INHIBIT_CONFIRM = 133;
+    RCPDU_TOKEN_GIVE_REQUEST = 134;
+    RCPDU_TOKEN_GIVE_INDICATION = 135;
+    RCPDU_TOKEN_GIVE_RESPONSE = 136;
+    RCPDU_TOKEN_GIVE_CONFIRM = 137;
+    RCPDU_TOKEN_PLEASE_REQUEST = 138;
+    RCPDU_TOKEN_PLEASE_INDICATION = 139;
+    RCPDU_TOKEN_RELEASE_REQUEST = 140;
+    RCPDU_TOKEN_RELEASE_CONFIRM = 141;
+    RCPDU_TOKEN_TEST_REQUEST = 142;
+    RCPDU_TOKEN_TEST_CONFIRM = 143;
+
+    // Registry PDU
+    RCPDU_REG_REGISTER_KEY = 200;
+    RCPDU_REG_UNREGISTER_KEY = 201;
+    RCPDU_REG_REGISTER_ROSTER = 202;
+    RCPDU_REG_REGISTER_TOKEN = 203;
+    RCPDU_REG_REGISTER_PARAMETER = 204;
+    RCPDU_REG_REGISTER_COUNTER = 205;
+    RCPDU_REG_REGISTER_TABLE = 206;
+    RCPDU_REG_REGISTER_CACHE = 207;
+    RCPDU_REG_REGISTER_OBJ = 208;
+    RCPDU_REG_UNREGISTER_OBJ = 209;
+    RCPDU_REG_UPDATE_OBJ = 210;
+    RCPDU_REG_ADAPTER = 211;
+    RCPDU_REG_CLEANUP_NODE = 212;
+    RCPDU_REG_REGISTER_QUEUE = 213;
+
+    // Registry Obj update PDU
+    RCPDU_REG_TABLE_INSERT_PDU = 230;
+    RCPDU_REG_TABLE_DELETE_PDU = 231;
+    RCPDU_REG_TABLE_UPDATE_PDU = 232;
+    RCPDU_REG_ROSTER_INSERT_PDU = 240;
+    RCPDU_REG_ROSTER_DELETE_PDU = 241;
+    RCPDU_REG_ROSTER_UPDATE_PDU = 242;
+    RCPDU_REG_PARAMETER_UPDATE_PDU = 250;
+    RCPDU_REG_QUEUE_INSERT_PDU = 255;
+    RCPDU_REG_QUEUE_DELETE_PDU = 256;
+    RCPDU_REG_QUEUE_UPDATE_PDU = 257;
+
+    // data
+    RCPDU_CONFERENCE_SEND_DATA_REQUEST = 259;
+    RCPDU_VIDEO_SEND_DATA_REQUEST = 260;
+    RCPDU_AUDIO_SEND_DATA_REQUEST = 261;
+    RCPDU_GIFT_SEND_DATA_REQUEST = 262;
+    RCPDU_CHAT_SEND_DATA_REQUEST = 263;
+    RCPDU_VOTING_POLL_RECORD = 265;
+
+    // Registry resource request or response PDU
+    RCPDU_REG_REQUEST_OBJ = 290;
+    RCPDU_REG_RESPONSE_OBJ = 291;
+    RCPDU_REG_COUNTER_REQUEST_PDU = 292;
+    RCPDU_REG_COUNTER_RESPONSE_PDU = 293;
+
+    // Index exchange
+    RCPDU_INDEX_ADAPTER = 300;
+    RCPDU_INDEX_SERVER_USERS = 301;
+    RCPDU_INDEX_CONFERENCE_USER_JOINED = 302;
+    RCPDU_INDEX_CONFERENCE_USER_EXITED = 303;
+    RCPDU_INDEX_CONFERENCE_USERS = 304;
+}
+
+enum RCPduNodeType_E {
+    NT_TERMINAL = 0;
+    NT_MULTIPORT_TERMINAL = 1;
+    NT_MCU = 2;
+}
+
+enum RCPduReason_E {
+    RSN_USERINITIATED = 0;
+    RSN_DISCONNECTED = 1;
+    RSN_SUPER_LEFT = 2;
+}
+
+enum RCPduResult_E {
+    RET_SUCCESS = 0;
+    RET_USER_REJECTED = 1;
+    RET_INVALID_CONFERENCE = 2;
+    RET_INVALID_PASSWORD = 3;
+    RET_INVALID_CONVENER_PASSWORD = 4;
+    RET_CHALLENGE_RESPONSE_REQUIRED = 5;
+    RET_INVALID_CHALLENGE_RESPONSE = 6;
+    RET_NO_CONNECTION = 7;
+    RET_FULL_CAPACITY = 8;
+}
+
+enum RCPduNodeCategory_E {
+    NC_CONVENTIONAL = 0;
+    NC_COUNTED = 1;
+    NC_ANONYMOUS = 2;
+}
+
+message RCConferenceDescriptorPdu {
+    required uint32 id = 1;
+    required bytes name = 2;
+    optional bytes description = 3;
+    //optional string net_address       = 4;
+    optional uint32 mode = 4;
+    optional string password = 5;
+    optional uint32 capacity = 6;
+    optional bytes user_data = 7;
+}
+
+message RCNodeRecordPdu {
+    required uint32 id = 1;
+    optional uint32 superior_node = 2;
+    required RCPduNodeType_E type = 3;
+    required string name = 4;
+    required uint32 capability = 5;
+    optional string net_address = 6;
+    optional RCPduNodeCategory_E category = 7;
+}
+
+message RCApplicationRecordPdu {
+    required uint32 id = 1; // session id
+    required string name = 2;
+    required string tag = 3;
+    repeated uint32 channel_ids = 4 [packed = true];
+    optional uint32 capability = 5;
+}
+
+//reg.proto
+
+option optimize_for = LITE_RUNTIME;
+message RCRegistryRegisterKeyPdu {
+    required RCPduType_E type = 1 [default = RCPDU_REG_REGISTER_KEY];
+    required uint32 id = 2;
+    required string name = 3;
+    required string tag = 4;
+    optional bytes user_data = 5;
+}
+
+message RCRegistryUnregisterKeyPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_REG_UNREGISTER_KEY];
+    required uint32 key_id = 2;
+}
+
+message RCRegistryRegisterObjPdu {
+    optional RCPduType_E type = 1;
+    required uint32 obj_id = 2;
+    required string name = 3;
+    required string tag = 4;
+    optional uint32 owner = 5;
+    optional bytes user_data = 6;
+}
+
+message RCRegistryUnregisterObjPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_REG_UNREGISTER_OBJ];
+    required uint32 obj_id = 2;
+}
+
+message RCRegistryUpdateObjPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_REG_UPDATE_OBJ];
+    required RCPduType_E sub_type = 2;
+    required uint32 obj_id = 3;
+    required bytes user_data = 4;
+}
+
+message RCAdapterItemPdu {
+    required RCPduType_E type = 1;
+    required bytes item_data = 2;
+}
+
+// adapter pdu that used to package a list of pdu.
+message RCAdapterPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_REG_ADAPTER];
+    repeated RCAdapterItemPdu item = 2;
+}
+
+// table operation pdu
+message RCRegistryTableItemPdu {
+    required uint32 item_idx = 1;
+    required uint32 owner = 2;
+    required bytes item_data = 3;
+}
+
+message RCRegistryTableInsertItemPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_REG_TABLE_INSERT_PDU];
+    repeated RCRegistryTableItemPdu items = 2;
+}
+
+message RCRegistryTableDeleteItemPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_REG_TABLE_DELETE_PDU];
+    repeated uint32 item_idx = 2;
+}
+
+message RCRegistryTableUpdateItemPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_REG_TABLE_UPDATE_PDU];
+    repeated RCRegistryTableItemPdu items = 2;
+}
+
+// roster operation pdu
+message RCRegistryRosterItemPdu {
+    required uint32 node_id = 1;
+    required bytes node_data = 2;
+}
+
+message RCRegistryRosterInsertItemPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_REG_ROSTER_INSERT_PDU];
+    repeated RCRegistryRosterItemPdu items = 2;
+}
+
+message RCRegistryRosterDeleteItemPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_REG_ROSTER_DELETE_PDU];
+    required uint32 node_id = 2;
+}
+
+message RCRegistryRosterUpdateItemPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_REG_ROSTER_UPDATE_PDU];
+    repeated RCRegistryRosterItemPdu items = 2;
+}
+
+//message RCCleanupNodePdu
+//{
+//  optional RCPduType_E    type                = 1 [default = RCPDU_REG_CLEANUP_NODE];
+//  required uint32         node_id             = 2;
+//}
+
+// parameter operation pdu
+message RCRegistryParameterUpdatePdu {
+    optional RCPduType_E type = 1 [default = RCPDU_REG_PARAMETER_UPDATE_PDU];
+    required uint32 value = 2 [default = 0];
+    optional uint32 begin_bit = 3 [default = 31];
+    optional uint32 end_bit = 4;
+}
+
+// queue operation pdu
+message RCRegistryQueueItemPdu {
+    required uint32 owner = 1;
+    required uint32 item_id = 2;
+    optional bytes item_data = 3;
+    optional uint32 item_idx = 4;
+}
+
+message RCRegstryQueueInsertItemPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_REG_QUEUE_INSERT_PDU];
+    repeated RCRegistryQueueItemPdu items = 2;
+}
+
+message RCRegistryQueueDeleteItemPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_REG_QUEUE_DELETE_PDU];
+    required uint32 item_id = 2;
+}
+
+message RCRegistryQueueUpdateItemPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_REG_QUEUE_UPDATE_PDU];
+    repeated RCRegistryQueueItemPdu items = 2;
+}
+
+message RCRegistryRequestObjPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_REG_REQUEST_OBJ];
+    required RCPduType_E sub_type = 2;
+    required uint32 obj_id = 3;
+    required bytes user_data = 4;
+}
+
+message RCRegistryResponseObjPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_REG_RESPONSE_OBJ];
+    required RCPduType_E sub_type = 2;
+    required uint32 obj_id = 3;
+    required bytes user_data = 4;
+}
+
+message RCRegistryCounterRequestPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_REG_COUNTER_REQUEST_PDU];
+    required uint32 count = 2;
+}
+
+message RCRegistryCounterResponsePdu {
+    optional RCPduType_E type = 1 [default = RCPDU_REG_COUNTER_RESPONSE_PDU];
+    required uint32 start = 2;
+    required uint32 end = 3;
+}
+
+// mcs
+option optimize_for = LITE_RUNTIME;
+
+// Session management
+message RCSessionJoinRequestPdu {
+    required RCPduType_E type = 1 [default = RCPDU_SESSION_JOIN_REQUEST];
+    required uint32 id = 2;
+    required string name = 3;
+    required string tag = 4;
+    optional bytes session_data = 5; //聚合在一起的registry信息
+}
+
+message RCSessionJoinResponsePdu {
+    optional RCPduType_E type = 1 [default = RCPDU_SESSION_JOIN_RESPONSE];
+    required uint32 id = 2;
+    optional bytes response_data = 3;
+}
+
+// Channel management
+message RCChannelGrabRequestPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_CHANNEL_GRAB_REQUEST];
+    required uint32 initiator = 2;
+    required uint32 channel_id = 3;
+}
+
+message RCChannelGrabResponsePdu {
+    optional RCPduType_E type = 1 [default = RCPDU_CHANNEL_GRAB_RESPONSE];
+    required uint32 initiator = 2;
+    required uint32 requested_channel_id = 3;
+    required RCPduResult_E result = 4;
+    optional uint32 channel_id = 5;
+}
+
+message RCChannelGrabIndicationPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_CHANNEL_GRAB_INDICATION];
+    required uint32 initiator = 2;
+    optional uint32 channel_id = 3;
+}
+
+message RCChannelJoinRequestPdu {
+    required RCPduType_E type = 1 [default = RCPDU_CHANNEL_JOIN_REQUEST];
+    required uint32 initiator = 2;
+    required uint32 channel_id = 3;
+}
+
+message RCChannelJoinResponsePdu {
+    optional RCPduType_E type = 1 [default = RCPDU_CHANNEL_JOIN_RESPONSE];
+    required uint32 initiator = 2;
+    required uint32 requested_channel_id = 3;
+    required RCPduResult_E result = 4;
+}
+
+message RCChannelLeaveRequestPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_CHANNEL_LEAVE_REQUEST];
+    required uint32 initiator = 2;
+    repeated uint32 channel_ids = 3 [packed = true];
+}
+
+message RCChannelReleaseRequestPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_CHANNEL_RELEASE_REQUEST];
+    required uint32 initiator = 2;
+    required uint32 channel_id = 3;
+}
+
+message RCChannelReleaseIndicationPdu {
+    optional RCPduType_E type = 1
+    [default = RCPDU_CHANNEL_RELEASE_INDICATION];
+    required uint32 initiator = 2;
+    required uint32 channel_id = 3;
+}
+
+// Data transfer
+message RCSendDataPdu {
+    required RCPduType_E type = 1 [default = RCPDU_SEND_DATA_REQUEST];
+    required RCPduType_E sub_type = 2;
+    required uint32 initiator = 3;
+    required uint32 conf_id = 4;
+    required uint32 session_id = 5;
+    required uint32 channel_id = 6;
+    required bool upward = 7;
+    required bool reliability = 8;
+    required RCPduPriority_E priority = 9;
+    required bytes data = 10;
+    optional uint32 peer = 11;
+    optional RCPduSegment_E seg = 12;
+    optional uint32 total_size = 13;
+    optional uint32 site_id = 14;
+    optional string user_id = 15;
+    optional string user_name = 16;
+    optional string user_role = 17;
+    optional string device_type = 18;
+    optional string site = 19;
+}
+
+//  Token management
+message RCTokenGrabRequestPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_GRAB_REQUEST];
+    required uint32 initiator = 2;
+    required uint32 token_id = 3;
+}
+
+message RCTokenGrabConfirmPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_GRAB_CONFIRM];
+    required uint32 initiator = 2;
+    required uint32 token_id = 3;
+    required RCPduResult_E result = 4;
+    required RCPduTokenStatus_E status = 5;
+}
+
+message RCTokenInhibitRequestPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_INHIBIT_REQUEST];
+    required uint32 initiator = 2;
+    required uint32 token_id = 3;
+}
+
+message RCTokenInhibitConfirmPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_INHIBIT_CONFIRM];
+    required uint32 initiator = 2;
+    required uint32 token_id = 3;
+    required RCPduResult_E result = 4;
+    required RCPduTokenStatus_E status = 5;
+}
+
+message RCTokenGiveRequestPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_GIVE_REQUEST];
+    required uint32 initiator = 2;
+    required uint32 token_id = 3;
+    required uint64 recipient = 4;
+}
+
+message RCTokenGiveIndicationPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_GIVE_INDICATION];
+    required uint32 initiator = 2;
+    required uint32 token_id = 3;
+    required uint64 recipient = 4;
+}
+
+message RCTokenGiveResponsePdu {
+    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_GIVE_RESPONSE];
+    required uint32 token_id = 2;
+    required uint64 recipient = 3;
+    required RCPduResult_E result = 4;
+}
+
+message RCTokenGiveConfirmPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_GIVE_CONFIRM];
+    required uint32 token_id = 2;
+    required uint64 recipient = 3;
+    required RCPduResult_E result = 4;
+    required RCPduTokenStatus_E status = 5;
+}
+
+message RCTokenPleaseRequestPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_PLEASE_REQUEST];
+    required uint32 initiator = 2;
+    required uint32 token_id = 3;
+}
+
+message RCTokenPleaseIndicationPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_PLEASE_INDICATION];
+    required uint32 initiator = 2;
+    required uint32 token_id = 3;
+}
+
+message RCTokenReleaseRequestPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_RELEASE_REQUEST];
+    required uint32 initiator = 2;
+    required uint32 token_id = 3;
+}
+
+message RCTokenReleaseConfirmPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_TEST_REQUEST];
+    required uint32 initiator = 2;
+    required uint32 token_id = 3;
+    required RCPduResult_E result = 4;
+    required RCPduTokenStatus_E status = 5;
+}
+
+message RCTokenTestRequestPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_TEST_REQUEST];
+    required uint32 initiator = 2;
+    required uint32 token_id = 3;
+}
+
+message RCTokenTestConfirmPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_TEST_CONFIRM];
+    required uint32 initiator = 2;
+    required uint32 token_id = 3;
+    required RCPduTokenStatus_E status = 4;
+}
+
+//gcc.proto
+
+option optimize_for = LITE_RUNTIME;
+message RCConferenceJoinRequestPdu {
+    required RCPduType_E type = 1 [default = RCPDU_CONFERENCE_JOIN_REQUEST];
+    required uint32 initiator = 2;
+    required RCPduNodeType_E node_type = 3;
+    required RCConferenceDescriptorPdu conf_desc = 4;
+}
+
+message RCConferenceJoinResponsePdu {
+    optional RCPduType_E type = 1 [default = RCPDU_CONFERENCE_JOIN_RESPONSE];
+    required uint32 conf_id = 2;
+    required RCPduResult_E result = 3;
+    optional RCConferenceDescriptorPdu conf_desc = 4;
+}
+
+message RCConferenceInviteRequestPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_CONFERENCE_INVITE_REQUEST];
+    required uint32 initiator = 2;
+    required RCConferenceDescriptorPdu conf_desc = 3;
+}
+
+message RCConferenceInviteResponsePdu {
+    optional RCPduType_E type = 1
+    [default = RCPDU_CONFERENCE_INVITE_RESPONSE];
+    required RCPduResult_E result = 2;
+    optional bytes user_data = 3;
+}
+
+message RCConferenceLockRequestPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_CONFERENCE_LOCK_REQUEST];
+}
+
+message RCConferenceLockResponsePdu {
+    optional RCPduType_E type = 1 [default = RCPDU_CONFERENCE_LOCK_RESPONSE];
+    required RCPduResult_E result = 2;
+}
+
+message RCConferenceLockIndicationPdu {
+    optional RCPduType_E type = 1
+    [default = RCPDU_CONFERENCE_LOCK_INDICATION];
+}
+
+message RCConferenceUnlockRequestPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_CONFERENCE_UNLOCK_REQUEST];
+}
+
+message RCConferenceUnlockResponsePdu {
+    optional RCPduType_E type = 1
+    [default = RCPDU_CONFERENCE_UNLOCK_RESPONSE];
+    required RCPduResult_E result = 2;
+}
+
+message RCConferenceUnlockIndicationPdu {
+    optional RCPduType_E type = 1
+    [default = RCPDU_CONFERENCE_UNLOCK_INDICATION];
+}
+
+message RCConferenceLeaveRequestPdu {
+    optional RCPduType_E type = 1 [default = RCPDU_CONFERENCE_LEAVE_REQUEST];
+    required RCPduReason_E reason = 2;
+}
+
+message RCConferenceTerminateRequestPdu {
+    optional RCPduType_E type = 1
+    [default = RCPDU_CONFERENCE_TERMINATE_REQUEST];
+    required RCPduReason_E reason = 2;
+}
+
+message RCConferenceTerminateResponsePdu {
+    optional RCPduType_E type = 1
+    [default = RCPDU_CONFERENCE_TERMINATE_RESPONSE];
+    required RCPduResult_E result = 2;
+}
+
+message RCConferenceTerminateIndicationPdu { // MCS_Uniform_Send_Data on GCC_Broadcast_Channel
+    optional RCPduType_E type = 1
+    [default = RCPDU_CONFERENCE_TERMINATE_INDICATION];
+    required RCPduReason_E reason = 2;
+}
+
+message RCConferenceEjectUserRequestPdu { // MCS_Send_Data on Node ID Channel of Top GCC
+    optional RCPduType_E type = 1
+    [default = RCPDU_CONFERENCE_EJECT_USER_REQUEST];
+    required uint32 ejected_node_id = 2;
+    required RCPduReason_E reason = 3;
+}
+
+message RCConferenceEjectUserResponsePdu { // MCS_Send_Data on Node ID Channel of requester
+    optional RCPduType_E type = 1
+    [default = RCPDU_CONFERENCE_EJECT_USER_RESPONSE];
+    required uint32 ejected_node_id = 2;
+    required RCPduResult_E result = 3;
+}
+
+message RCConferenceEjectUserIndicationPdu { // MCS_Uniform_Send_Data on GCC_Broadcast_Channel
+    optional RCPduType_E type = 1
+    [default = RCPDU_CONFERENCE_EJECT_USER_INDICATION];
+    required uint32 ejected_node_id = 2;
+    required RCPduReason_E reason = 3;
+}
+
+message RCRosterUpdateIndicationPdu { // MCS_Send_Data on Node ID Channel or
+// MCS_Uniform_Send_Data on GCC_Broadcast_Channel
+    optional RCPduType_E type = 1 [default = RCPDU_ROSTER_UPDATE_INDICATION];
+    required bool full_refresh = 2; // Conference Roster and all
+    repeated RCNodeRecordPdu node_record = 3;
+    repeated RCApplicationRecordPdu app_record = 4;
+}
+
+message RCRegistryUpdateRequestPdu { // MCS_Send_Data on Node ID Channel of Top GCC
+    optional RCPduType_E type = 1 [default = RCPDU_REGISTRY_UPDATE_REQUEST];
+    required uint32 key_id = 2;
+    required uint32 obj_id = 3;
+    required bytes user_data = 4;
+}
+
+message RCRegistryUpdateIndicationPdu { // MCS_Send_Data on Node ID Channel of Top GCC
+    optional RCPduType_E type = 1 [default = RCPDU_REGISTRY_UPDATE_RESPONSE];
+    required uint32 key_id = 2;
+    required uint32 obj_id = 3;
+    required bytes user_data = 4;
+}
+
+message RCRegistryUpdateResponsePdu { // MCS_Send_Data on Node ID Channel of requester
+    optional RCPduType_E type = 1 [default = RCPDU_REGISTRY_UPDATE_INDICATION];
+    required uint32 key_id = 2;
+    required uint32 obj_id = 3;
+    required RCPduResult_E result = 4;
+}
+
+message RCFunctionNotSupportedResponsePdu {
+    optional RCPduType_E type = 1
+    [default = RCPDU_FUNCTION_NOT_SUPPORTED_RESPONSE];
+    required uint32 request_pdu_id = 2;
+}
+
+//ape.proto
+
+option optimize_for = LITE_RUNTIME;
+message RCConferenceSendDataRequestPdu {
+    optional uint32 initiator = 1;
+    optional uint32 peer = 2;
+    required bool is_public = 3;
+    required bytes user_data = 4;
+}
+
+message RCChatSendDataRequestPdu {
+    optional uint32 initiator = 1;
+    optional uint32 peer = 2;
+    required bool is_public = 3;
+    required bytes user_data = 4;
+    required uint32 from_role = 5;
+    required bytes from_name = 6;
+}
+
+message RCChatSendH5DataRequestPdu {
+    optional uint32 initiator = 1;
+    optional uint32 peer = 2;
+    required bool is_public = 3;
+    required string user_data = 4;
+    required string from_role = 5;
+    required string from_name = 6;
+}
+
+message RCDocSendDataRequestPdu {
+    required int32 id = 1;
+    required string doc_type = 2;
+    required string uri  = 3;
+    optional int32 owner = 4;
+    optional int32 from = 5;
+    optional string name = 6;
+    optional int32 cur_page_no = 7;
+    optional int32 cur_H = 8;
+    optional int32 cur_V = 9;
+    optional int32 scale = 10;
+    optional int32 page_num = 11;
+}
+
+message RCGiftSendDataRequestPdu {
+    optional uint32 initiator = 1;
+    required uint32 peer = 2;
+    required uint32 index = 3;
+    required uint32 num = 4;
+    optional bytes user_data = 5;
+}
+
+message RCAudioSendDataRequestPdu {
+    optional uint32 initiator = 1;
+    required bytes user_data = 2;
+}
+
+message RCVideoSendDataRequestPdu {
+    optional uint32 initiator = 1;
+    required bool key_frame = 2;
+    required uint32 sequence_id = 3;
+    required uint32 slice_id = 4;
+    required bytes user_data = 5;
+}
+
+message RCAudioChannelInfoRecordPdu {
+    required uint32 status = 1;
+    required uint32 device_id = 2;
+    required uint32 framerate = 3;
+    required uint32 bitrate = 4;
+    required uint32 codec = 5;
+}
+
+message RCVideoChannelInfoRecordPdu {
+    optional uint32 status = 1;
+    optional uint32 device_id = 2;
+    optional uint32 width = 3;
+    optional uint32 height = 4;
+    optional uint32 framerate = 5;
+    optional uint32 bitrate = 6;
+    optional uint32 codec = 7;
+    optional string peer_id = 8;
+    optional string url = 9;
+    optional uint32 type = 10;
+    optional string shamlive = 11;
+    optional uint32 livetype = 12;
+    optional uint32 releaseGrab = 13;
+    optional string curTime = 14;
+}
+
+message RCAudioDeviceInfoRecordPdu {
+    required uint32 device_id = 1;
+    required string device_name = 2;
+}
+
+message RCVideoDeviceInfoRecordPdu {
+    required uint32 device_id = 1;
+    required string device_name = 2;
+}
+
+message RCNodeInfoRecordPdu {
+    required uint32 node_id = 1;
+    required string name = 2;
+    required uint32 role = 3;
+    required uint32 level = 4;
+    repeated RCAudioDeviceInfoRecordPdu audio_records = 5;
+    repeated RCVideoDeviceInfoRecordPdu video_records = 6;
+    optional uint32 status = 7;
+    optional bytes user_data = 8;
+    optional string user_id = 9;
+    optional uint32 handUpTime = 10;
+    optional uint32 deviceType = 11;
+    optional uint32 mobileDirection = 12;
+}
+
+message RCVotingPollSettingsPdu {
+    required bool timer = 1;
+    optional uint32 time_limit = 2;
+    optional uint32 total_score = 3;
+}
+
+message RCVotingPollResultPdu {
+    required string title = 1;
+    required string content = 2;
+    optional uint32 score = 3;
+}
+
+message RCVotingPollQuestionPdu {
+    required uint32 index = 1;
+    required uint32 type = 2;
+    required string title = 3;
+    repeated string options = 4;
+    optional uint32 score = 5;
+    optional uint32 time_limit = 6;
+    optional string restrict_input = 7;
+    optional uint32 char_limit = 8;
+    optional string answer = 9;
+    repeated uint32 selections = 10;
+    repeated string responses = 11;
+}
+
+message RCVotingPollRecordPdu {
+    required RCVotingPollSettingsPdu settings = 1;
+    required string title = 2;
+    repeated RCVotingPollResultPdu results = 3;
+    repeated RCVotingPollQuestionPdu questions = 4;
+}
+
+message RCNodeInfoUserDataPdu {
+    optional string qq = 1;
+    optional string skype = 2;
+    optional string mobile = 3;
+}
+
+message RCWhiteboardDataRequestPdu {
+    required uint32 id = 1;
+    required uint32 type = 2;
+    required uint32 initiator = 3;
+    optional bytes action = 4;
+    optional uint32 uncomprLen = 5;
+}
+
+message RCTabUpdateDataRequestPdu {
+    optional uint32 id = 1;
+    optional bytes action = 2;
+    optional uint32 uncomprLen =3;
+}
+
+
+
+//end
+`;
+
diff --git a/src/url.js b/src/url.js
new file mode 100644
index 0000000..5a2d805
--- /dev/null
+++ b/src/url.js
@@ -0,0 +1,4 @@
+import urlParse from 'url-parse';
+let url = urlParse(location.href, true);
+export default url;
+
diff --git a/src/zlib.min.js b/src/zlib.min.js
new file mode 100644
index 0000000..3a98c2f
--- /dev/null
+++ b/src/zlib.min.js
@@ -0,0 +1,39 @@
+/** @license zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License */(function() {'use strict';function l(d){throw d;}var v=void 0,x=!0,aa=this;function D(d,a){var c=d.split("."),e=aa;!(c[0]in e)&&e.execScript&&e.execScript("var "+c[0]);for(var b;c.length&&(b=c.shift());)!c.length&&a!==v?e[b]=a:e=e[b]?e[b]:e[b]={}};var F="undefined"!==typeof Uint8Array&&"undefined"!==typeof Uint16Array&&"undefined"!==typeof Uint32Array&&"undefined"!==typeof DataView;function H(d,a){this.index="number"===typeof a?a:0;this.i=0;this.buffer=d instanceof(F?Uint8Array:Array)?d:new (F?Uint8Array:Array)(32768);2*this.buffer.length<=this.index&&l(Error("invalid index"));this.buffer.length<=this.index&&this.f()}H.prototype.f=function(){var d=this.buffer,a,c=d.length,e=new (F?Uint8Array:Array)(c<<1);if(F)e.set(d);else for(a=0;a<c;++a)e[a]=d[a];return this.buffer=e};
+H.prototype.d=function(d,a,c){var e=this.buffer,b=this.index,f=this.i,g=e[b],h;c&&1<a&&(d=8<a?(N[d&255]<<24|N[d>>>8&255]<<16|N[d>>>16&255]<<8|N[d>>>24&255])>>32-a:N[d]>>8-a);if(8>a+f)g=g<<a|d,f+=a;else for(h=0;h<a;++h)g=g<<1|d>>a-h-1&1,8===++f&&(f=0,e[b++]=N[g],g=0,b===e.length&&(e=this.f()));e[b]=g;this.buffer=e;this.i=f;this.index=b};H.prototype.finish=function(){var d=this.buffer,a=this.index,c;0<this.i&&(d[a]<<=8-this.i,d[a]=N[d[a]],a++);F?c=d.subarray(0,a):(d.length=a,c=d);return c};
+var fa=new (F?Uint8Array:Array)(256),O;for(O=0;256>O;++O){for(var P=O,Q=P,ga=7,P=P>>>1;P;P>>>=1)Q<<=1,Q|=P&1,--ga;fa[O]=(Q<<ga&255)>>>0}var N=fa;function ha(d){this.buffer=new (F?Uint16Array:Array)(2*d);this.length=0}ha.prototype.getParent=function(d){return 2*((d-2)/4|0)};ha.prototype.push=function(d,a){var c,e,b=this.buffer,f;c=this.length;b[this.length++]=a;for(b[this.length++]=d;0<c;)if(e=this.getParent(c),b[c]>b[e])f=b[c],b[c]=b[e],b[e]=f,f=b[c+1],b[c+1]=b[e+1],b[e+1]=f,c=e;else break;return this.length};
+ha.prototype.pop=function(){var d,a,c=this.buffer,e,b,f;a=c[0];d=c[1];this.length-=2;c[0]=c[this.length];c[1]=c[this.length+1];for(f=0;;){b=2*f+2;if(b>=this.length)break;b+2<this.length&&c[b+2]>c[b]&&(b+=2);if(c[b]>c[f])e=c[f],c[f]=c[b],c[b]=e,e=c[f+1],c[f+1]=c[b+1],c[b+1]=e;else break;f=b}return{index:d,value:a,length:this.length}};function R(d){var a=d.length,c=0,e=Number.POSITIVE_INFINITY,b,f,g,h,k,n,q,r,p,m;for(r=0;r<a;++r)d[r]>c&&(c=d[r]),d[r]<e&&(e=d[r]);b=1<<c;f=new (F?Uint32Array:Array)(b);g=1;h=0;for(k=2;g<=c;){for(r=0;r<a;++r)if(d[r]===g){n=0;q=h;for(p=0;p<g;++p)n=n<<1|q&1,q>>=1;m=g<<16|r;for(p=n;p<b;p+=k)f[p]=m;++h}++g;h<<=1;k<<=1}return[f,c,e]};function ia(d,a){this.h=ma;this.w=0;this.input=F&&d instanceof Array?new Uint8Array(d):d;this.b=0;a&&(a.lazy&&(this.w=a.lazy),"number"===typeof a.compressionType&&(this.h=a.compressionType),a.outputBuffer&&(this.a=F&&a.outputBuffer instanceof Array?new Uint8Array(a.outputBuffer):a.outputBuffer),"number"===typeof a.outputIndex&&(this.b=a.outputIndex));this.a||(this.a=new (F?Uint8Array:Array)(32768))}var ma=2,na={NONE:0,r:1,k:ma,O:3},oa=[],S;
+for(S=0;288>S;S++)switch(x){case 143>=S:oa.push([S+48,8]);break;case 255>=S:oa.push([S-144+400,9]);break;case 279>=S:oa.push([S-256+0,7]);break;case 287>=S:oa.push([S-280+192,8]);break;default:l("invalid literal: "+S)}
+ia.prototype.j=function(){var d,a,c,e,b=this.input;switch(this.h){case 0:c=0;for(e=b.length;c<e;){a=F?b.subarray(c,c+65535):b.slice(c,c+65535);c+=a.length;var f=a,g=c===e,h=v,k=v,n=v,q=v,r=v,p=this.a,m=this.b;if(F){for(p=new Uint8Array(this.a.buffer);p.length<=m+f.length+5;)p=new Uint8Array(p.length<<1);p.set(this.a)}h=g?1:0;p[m++]=h|0;k=f.length;n=~k+65536&65535;p[m++]=k&255;p[m++]=k>>>8&255;p[m++]=n&255;p[m++]=n>>>8&255;if(F)p.set(f,m),m+=f.length,p=p.subarray(0,m);else{q=0;for(r=f.length;q<r;++q)p[m++]=
+f[q];p.length=m}this.b=m;this.a=p}break;case 1:var s=new H(F?new Uint8Array(this.a.buffer):this.a,this.b);s.d(1,1,x);s.d(1,2,x);var w=pa(this,b),y,ja,A;y=0;for(ja=w.length;y<ja;y++)if(A=w[y],H.prototype.d.apply(s,oa[A]),256<A)s.d(w[++y],w[++y],x),s.d(w[++y],5),s.d(w[++y],w[++y],x);else if(256===A)break;this.a=s.finish();this.b=this.a.length;break;case ma:var C=new H(F?new Uint8Array(this.a.buffer):this.a,this.b),Ea,M,U,V,W,gb=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],ba,Fa,ca,Ga,ka,ra=Array(19),
+Ha,X,la,z,Ia;Ea=ma;C.d(1,1,x);C.d(Ea,2,x);M=pa(this,b);ba=qa(this.M,15);Fa=sa(ba);ca=qa(this.L,7);Ga=sa(ca);for(U=286;257<U&&0===ba[U-1];U--);for(V=30;1<V&&0===ca[V-1];V--);var Ja=U,Ka=V,I=new (F?Uint32Array:Array)(Ja+Ka),t,J,u,da,G=new (F?Uint32Array:Array)(316),E,B,K=new (F?Uint8Array:Array)(19);for(t=J=0;t<Ja;t++)I[J++]=ba[t];for(t=0;t<Ka;t++)I[J++]=ca[t];if(!F){t=0;for(da=K.length;t<da;++t)K[t]=0}t=E=0;for(da=I.length;t<da;t+=J){for(J=1;t+J<da&&I[t+J]===I[t];++J);u=J;if(0===I[t])if(3>u)for(;0<
+u--;)G[E++]=0,K[0]++;else for(;0<u;)B=138>u?u:138,B>u-3&&B<u&&(B=u-3),10>=B?(G[E++]=17,G[E++]=B-3,K[17]++):(G[E++]=18,G[E++]=B-11,K[18]++),u-=B;else if(G[E++]=I[t],K[I[t]]++,u--,3>u)for(;0<u--;)G[E++]=I[t],K[I[t]]++;else for(;0<u;)B=6>u?u:6,B>u-3&&B<u&&(B=u-3),G[E++]=16,G[E++]=B-3,K[16]++,u-=B}d=F?G.subarray(0,E):G.slice(0,E);ka=qa(K,7);for(z=0;19>z;z++)ra[z]=ka[gb[z]];for(W=19;4<W&&0===ra[W-1];W--);Ha=sa(ka);C.d(U-257,5,x);C.d(V-1,5,x);C.d(W-4,4,x);for(z=0;z<W;z++)C.d(ra[z],3,x);z=0;for(Ia=d.length;z<
+Ia;z++)if(X=d[z],C.d(Ha[X],ka[X],x),16<=X){z++;switch(X){case 16:la=2;break;case 17:la=3;break;case 18:la=7;break;default:l("invalid code: "+X)}C.d(d[z],la,x)}var La=[Fa,ba],Ma=[Ga,ca],L,Na,ea,ua,Oa,Pa,Qa,Ra;Oa=La[0];Pa=La[1];Qa=Ma[0];Ra=Ma[1];L=0;for(Na=M.length;L<Na;++L)if(ea=M[L],C.d(Oa[ea],Pa[ea],x),256<ea)C.d(M[++L],M[++L],x),ua=M[++L],C.d(Qa[ua],Ra[ua],x),C.d(M[++L],M[++L],x);else if(256===ea)break;this.a=C.finish();this.b=this.a.length;break;default:l("invalid compression type")}return this.a};
+function ta(d,a){this.length=d;this.H=a}
+var va=function(){function d(b){switch(x){case 3===b:return[257,b-3,0];case 4===b:return[258,b-4,0];case 5===b:return[259,b-5,0];case 6===b:return[260,b-6,0];case 7===b:return[261,b-7,0];case 8===b:return[262,b-8,0];case 9===b:return[263,b-9,0];case 10===b:return[264,b-10,0];case 12>=b:return[265,b-11,1];case 14>=b:return[266,b-13,1];case 16>=b:return[267,b-15,1];case 18>=b:return[268,b-17,1];case 22>=b:return[269,b-19,2];case 26>=b:return[270,b-23,2];case 30>=b:return[271,b-27,2];case 34>=b:return[272,
+b-31,2];case 42>=b:return[273,b-35,3];case 50>=b:return[274,b-43,3];case 58>=b:return[275,b-51,3];case 66>=b:return[276,b-59,3];case 82>=b:return[277,b-67,4];case 98>=b:return[278,b-83,4];case 114>=b:return[279,b-99,4];case 130>=b:return[280,b-115,4];case 162>=b:return[281,b-131,5];case 194>=b:return[282,b-163,5];case 226>=b:return[283,b-195,5];case 257>=b:return[284,b-227,5];case 258===b:return[285,b-258,0];default:l("invalid length: "+b)}}var a=[],c,e;for(c=3;258>=c;c++)e=d(c),a[c]=e[2]<<24|e[1]<<
+16|e[0];return a}(),wa=F?new Uint32Array(va):va;
+function pa(d,a){function c(b,c){var a=b.H,d=[],e=0,f;f=wa[b.length];d[e++]=f&65535;d[e++]=f>>16&255;d[e++]=f>>24;var g;switch(x){case 1===a:g=[0,a-1,0];break;case 2===a:g=[1,a-2,0];break;case 3===a:g=[2,a-3,0];break;case 4===a:g=[3,a-4,0];break;case 6>=a:g=[4,a-5,1];break;case 8>=a:g=[5,a-7,1];break;case 12>=a:g=[6,a-9,2];break;case 16>=a:g=[7,a-13,2];break;case 24>=a:g=[8,a-17,3];break;case 32>=a:g=[9,a-25,3];break;case 48>=a:g=[10,a-33,4];break;case 64>=a:g=[11,a-49,4];break;case 96>=a:g=[12,a-
+65,5];break;case 128>=a:g=[13,a-97,5];break;case 192>=a:g=[14,a-129,6];break;case 256>=a:g=[15,a-193,6];break;case 384>=a:g=[16,a-257,7];break;case 512>=a:g=[17,a-385,7];break;case 768>=a:g=[18,a-513,8];break;case 1024>=a:g=[19,a-769,8];break;case 1536>=a:g=[20,a-1025,9];break;case 2048>=a:g=[21,a-1537,9];break;case 3072>=a:g=[22,a-2049,10];break;case 4096>=a:g=[23,a-3073,10];break;case 6144>=a:g=[24,a-4097,11];break;case 8192>=a:g=[25,a-6145,11];break;case 12288>=a:g=[26,a-8193,12];break;case 16384>=
+a:g=[27,a-12289,12];break;case 24576>=a:g=[28,a-16385,13];break;case 32768>=a:g=[29,a-24577,13];break;default:l("invalid distance")}f=g;d[e++]=f[0];d[e++]=f[1];d[e++]=f[2];var h,k;h=0;for(k=d.length;h<k;++h)p[m++]=d[h];w[d[0]]++;y[d[3]]++;s=b.length+c-1;r=null}var e,b,f,g,h,k={},n,q,r,p=F?new Uint16Array(2*a.length):[],m=0,s=0,w=new (F?Uint32Array:Array)(286),y=new (F?Uint32Array:Array)(30),ja=d.w,A;if(!F){for(f=0;285>=f;)w[f++]=0;for(f=0;29>=f;)y[f++]=0}w[256]=1;e=0;for(b=a.length;e<b;++e){f=h=0;
+for(g=3;f<g&&e+f!==b;++f)h=h<<8|a[e+f];k[h]===v&&(k[h]=[]);n=k[h];if(!(0<s--)){for(;0<n.length&&32768<e-n[0];)n.shift();if(e+3>=b){r&&c(r,-1);f=0;for(g=b-e;f<g;++f)A=a[e+f],p[m++]=A,++w[A];break}0<n.length?(q=xa(a,e,n),r?r.length<q.length?(A=a[e-1],p[m++]=A,++w[A],c(q,0)):c(r,-1):q.length<ja?r=q:c(q,0)):r?c(r,-1):(A=a[e],p[m++]=A,++w[A])}n.push(e)}p[m++]=256;w[256]++;d.M=w;d.L=y;return F?p.subarray(0,m):p}
+function xa(d,a,c){var e,b,f=0,g,h,k,n,q=d.length;h=0;n=c.length;a:for(;h<n;h++){e=c[n-h-1];g=3;if(3<f){for(k=f;3<k;k--)if(d[e+k-1]!==d[a+k-1])continue a;g=f}for(;258>g&&a+g<q&&d[e+g]===d[a+g];)++g;g>f&&(b=e,f=g);if(258===g)break}return new ta(f,a-b)}
+function qa(d,a){var c=d.length,e=new ha(572),b=new (F?Uint8Array:Array)(c),f,g,h,k,n;if(!F)for(k=0;k<c;k++)b[k]=0;for(k=0;k<c;++k)0<d[k]&&e.push(k,d[k]);f=Array(e.length/2);g=new (F?Uint32Array:Array)(e.length/2);if(1===f.length)return b[e.pop().index]=1,b;k=0;for(n=e.length/2;k<n;++k)f[k]=e.pop(),g[k]=f[k].value;h=ya(g,g.length,a);k=0;for(n=f.length;k<n;++k)b[f[k].index]=h[k];return b}
+function ya(d,a,c){function e(b){var c=k[b][n[b]];c===a?(e(b+1),e(b+1)):--g[c];++n[b]}var b=new (F?Uint16Array:Array)(c),f=new (F?Uint8Array:Array)(c),g=new (F?Uint8Array:Array)(a),h=Array(c),k=Array(c),n=Array(c),q=(1<<c)-a,r=1<<c-1,p,m,s,w,y;b[c-1]=a;for(m=0;m<c;++m)q<r?f[m]=0:(f[m]=1,q-=r),q<<=1,b[c-2-m]=(b[c-1-m]/2|0)+a;b[0]=f[0];h[0]=Array(b[0]);k[0]=Array(b[0]);for(m=1;m<c;++m)b[m]>2*b[m-1]+f[m]&&(b[m]=2*b[m-1]+f[m]),h[m]=Array(b[m]),k[m]=Array(b[m]);for(p=0;p<a;++p)g[p]=c;for(s=0;s<b[c-1];++s)h[c-
+1][s]=d[s],k[c-1][s]=s;for(p=0;p<c;++p)n[p]=0;1===f[c-1]&&(--g[0],++n[c-1]);for(m=c-2;0<=m;--m){w=p=0;y=n[m+1];for(s=0;s<b[m];s++)w=h[m+1][y]+h[m+1][y+1],w>d[p]?(h[m][s]=w,k[m][s]=a,y+=2):(h[m][s]=d[p],k[m][s]=p,++p);n[m]=0;1===f[m]&&e(m)}return g}
+function sa(d){var a=new (F?Uint16Array:Array)(d.length),c=[],e=[],b=0,f,g,h,k;f=0;for(g=d.length;f<g;f++)c[d[f]]=(c[d[f]]|0)+1;f=1;for(g=16;f<=g;f++)e[f]=b,b+=c[f]|0,b<<=1;f=0;for(g=d.length;f<g;f++){b=e[d[f]];e[d[f]]+=1;h=a[f]=0;for(k=d[f];h<k;h++)a[f]=a[f]<<1|b&1,b>>>=1}return a};function T(d,a){this.l=[];this.m=32768;this.e=this.g=this.c=this.q=0;this.input=F?new Uint8Array(d):d;this.s=!1;this.n=za;this.C=!1;if(a||!(a={}))a.index&&(this.c=a.index),a.bufferSize&&(this.m=a.bufferSize),a.bufferType&&(this.n=a.bufferType),a.resize&&(this.C=a.resize);switch(this.n){case Aa:this.b=32768;this.a=new (F?Uint8Array:Array)(32768+this.m+258);break;case za:this.b=0;this.a=new (F?Uint8Array:Array)(this.m);this.f=this.K;this.t=this.I;this.o=this.J;break;default:l(Error("invalid inflate mode"))}}
+var Aa=0,za=1,Ba={F:Aa,D:za};
+T.prototype.p=function(){for(;!this.s;){var d=Y(this,3);d&1&&(this.s=x);d>>>=1;switch(d){case 0:var a=this.input,c=this.c,e=this.a,b=this.b,f=a.length,g=v,h=v,k=e.length,n=v;this.e=this.g=0;c+1>=f&&l(Error("invalid uncompressed block header: LEN"));g=a[c++]|a[c++]<<8;c+1>=f&&l(Error("invalid uncompressed block header: NLEN"));h=a[c++]|a[c++]<<8;g===~h&&l(Error("invalid uncompressed block header: length verify"));c+g>a.length&&l(Error("input buffer is broken"));switch(this.n){case Aa:for(;b+g>e.length;){n=
+k-b;g-=n;if(F)e.set(a.subarray(c,c+n),b),b+=n,c+=n;else for(;n--;)e[b++]=a[c++];this.b=b;e=this.f();b=this.b}break;case za:for(;b+g>e.length;)e=this.f({v:2});break;default:l(Error("invalid inflate mode"))}if(F)e.set(a.subarray(c,c+g),b),b+=g,c+=g;else for(;g--;)e[b++]=a[c++];this.c=c;this.b=b;this.a=e;break;case 1:this.o(Ca,Da);break;case 2:Sa(this);break;default:l(Error("unknown BTYPE: "+d))}}return this.t()};
+var Ta=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],Ua=F?new Uint16Array(Ta):Ta,Va=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,258,258],Wa=F?new Uint16Array(Va):Va,Xa=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0],Ya=F?new Uint8Array(Xa):Xa,Za=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],$a=F?new Uint16Array(Za):Za,ab=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,
+10,11,11,12,12,13,13],bb=F?new Uint8Array(ab):ab,cb=new (F?Uint8Array:Array)(288),Z,db;Z=0;for(db=cb.length;Z<db;++Z)cb[Z]=143>=Z?8:255>=Z?9:279>=Z?7:8;var Ca=R(cb),eb=new (F?Uint8Array:Array)(30),fb,hb;fb=0;for(hb=eb.length;fb<hb;++fb)eb[fb]=5;var Da=R(eb);function Y(d,a){for(var c=d.g,e=d.e,b=d.input,f=d.c,g=b.length,h;e<a;)f>=g&&l(Error("input buffer is broken")),c|=b[f++]<<e,e+=8;h=c&(1<<a)-1;d.g=c>>>a;d.e=e-a;d.c=f;return h}
+function ib(d,a){for(var c=d.g,e=d.e,b=d.input,f=d.c,g=b.length,h=a[0],k=a[1],n,q;e<k&&!(f>=g);)c|=b[f++]<<e,e+=8;n=h[c&(1<<k)-1];q=n>>>16;d.g=c>>q;d.e=e-q;d.c=f;return n&65535}
+function Sa(d){function a(a,b,c){var d,e=this.z,f,g;for(g=0;g<a;)switch(d=ib(this,b),d){case 16:for(f=3+Y(this,2);f--;)c[g++]=e;break;case 17:for(f=3+Y(this,3);f--;)c[g++]=0;e=0;break;case 18:for(f=11+Y(this,7);f--;)c[g++]=0;e=0;break;default:e=c[g++]=d}this.z=e;return c}var c=Y(d,5)+257,e=Y(d,5)+1,b=Y(d,4)+4,f=new (F?Uint8Array:Array)(Ua.length),g,h,k,n;for(n=0;n<b;++n)f[Ua[n]]=Y(d,3);if(!F){n=b;for(b=f.length;n<b;++n)f[Ua[n]]=0}g=R(f);h=new (F?Uint8Array:Array)(c);k=new (F?Uint8Array:Array)(e);
+d.z=0;d.o(R(a.call(d,c,g,h)),R(a.call(d,e,g,k)))}T.prototype.o=function(d,a){var c=this.a,e=this.b;this.u=d;for(var b=c.length-258,f,g,h,k;256!==(f=ib(this,d));)if(256>f)e>=b&&(this.b=e,c=this.f(),e=this.b),c[e++]=f;else{g=f-257;k=Wa[g];0<Ya[g]&&(k+=Y(this,Ya[g]));f=ib(this,a);h=$a[f];0<bb[f]&&(h+=Y(this,bb[f]));e>=b&&(this.b=e,c=this.f(),e=this.b);for(;k--;)c[e]=c[e++-h]}for(;8<=this.e;)this.e-=8,this.c--;this.b=e};
+T.prototype.J=function(d,a){var c=this.a,e=this.b;this.u=d;for(var b=c.length,f,g,h,k;256!==(f=ib(this,d));)if(256>f)e>=b&&(c=this.f(),b=c.length),c[e++]=f;else{g=f-257;k=Wa[g];0<Ya[g]&&(k+=Y(this,Ya[g]));f=ib(this,a);h=$a[f];0<bb[f]&&(h+=Y(this,bb[f]));e+k>b&&(c=this.f(),b=c.length);for(;k--;)c[e]=c[e++-h]}for(;8<=this.e;)this.e-=8,this.c--;this.b=e};
+T.prototype.f=function(){var d=new (F?Uint8Array:Array)(this.b-32768),a=this.b-32768,c,e,b=this.a;if(F)d.set(b.subarray(32768,d.length));else{c=0;for(e=d.length;c<e;++c)d[c]=b[c+32768]}this.l.push(d);this.q+=d.length;if(F)b.set(b.subarray(a,a+32768));else for(c=0;32768>c;++c)b[c]=b[a+c];this.b=32768;return b};
+T.prototype.K=function(d){var a,c=this.input.length/this.c+1|0,e,b,f,g=this.input,h=this.a;d&&("number"===typeof d.v&&(c=d.v),"number"===typeof d.G&&(c+=d.G));2>c?(e=(g.length-this.c)/this.u[2],f=258*(e/2)|0,b=f<h.length?h.length+f:h.length<<1):b=h.length*c;F?(a=new Uint8Array(b),a.set(h)):a=h;return this.a=a};
+T.prototype.t=function(){var d=0,a=this.a,c=this.l,e,b=new (F?Uint8Array:Array)(this.q+(this.b-32768)),f,g,h,k;if(0===c.length)return F?this.a.subarray(32768,this.b):this.a.slice(32768,this.b);f=0;for(g=c.length;f<g;++f){e=c[f];h=0;for(k=e.length;h<k;++h)b[d++]=e[h]}f=32768;for(g=this.b;f<g;++f)b[d++]=a[f];this.l=[];return this.buffer=b};
+T.prototype.I=function(){var d,a=this.b;F?this.C?(d=new Uint8Array(a),d.set(this.a.subarray(0,a))):d=this.a.subarray(0,a):(this.a.length>a&&(this.a.length=a),d=this.a);return this.buffer=d};function jb(d){if("string"===typeof d){var a=d.split(""),c,e;c=0;for(e=a.length;c<e;c++)a[c]=(a[c].charCodeAt(0)&255)>>>0;d=a}for(var b=1,f=0,g=d.length,h,k=0;0<g;){h=1024<g?1024:g;g-=h;do b+=d[k++],f+=b;while(--h);b%=65521;f%=65521}return(f<<16|b)>>>0};function kb(d,a){var c,e;this.input=d;this.c=0;if(a||!(a={}))a.index&&(this.c=a.index),a.verify&&(this.N=a.verify);c=d[this.c++];e=d[this.c++];switch(c&15){case lb:this.method=lb;break;default:l(Error("unsupported compression method"))}0!==((c<<8)+e)%31&&l(Error("invalid fcheck flag:"+((c<<8)+e)%31));e&32&&l(Error("fdict flag is not supported"));this.B=new T(d,{index:this.c,bufferSize:a.bufferSize,bufferType:a.bufferType,resize:a.resize})}
+kb.prototype.p=function(){var d=this.input,a,c;a=this.B.p();this.c=this.B.c;this.N&&(c=(d[this.c++]<<24|d[this.c++]<<16|d[this.c++]<<8|d[this.c++])>>>0,c!==jb(a)&&l(Error("invalid adler-32 checksum")));return a};var lb=8;function mb(d,a){this.input=d;this.a=new (F?Uint8Array:Array)(32768);this.h=$.k;var c={},e;if((a||!(a={}))&&"number"===typeof a.compressionType)this.h=a.compressionType;for(e in a)c[e]=a[e];c.outputBuffer=this.a;this.A=new ia(this.input,c)}var $=na;
+mb.prototype.j=function(){var d,a,c,e,b,f,g,h=0;g=this.a;d=lb;switch(d){case lb:a=Math.LOG2E*Math.log(32768)-8;break;default:l(Error("invalid compression method"))}c=a<<4|d;g[h++]=c;switch(d){case lb:switch(this.h){case $.NONE:b=0;break;case $.r:b=1;break;case $.k:b=2;break;default:l(Error("unsupported compression type"))}break;default:l(Error("invalid compression method"))}e=b<<6|0;g[h++]=e|31-(256*c+e)%31;f=jb(this.input);this.A.b=h;g=this.A.j();h=g.length;F&&(g=new Uint8Array(g.buffer),g.length<=
+h+4&&(this.a=new Uint8Array(g.length+4),this.a.set(g),g=this.a),g=g.subarray(0,h+4));g[h++]=f>>24&255;g[h++]=f>>16&255;g[h++]=f>>8&255;g[h++]=f&255;return g};function nb(d,a){var c,e,b,f;if(Object.keys)c=Object.keys(a);else for(e in c=[],b=0,a)c[b++]=e;b=0;for(f=c.length;b<f;++b)e=c[b],D(d+"."+e,a[e])};D("Zlib.Inflate",kb);D("Zlib.Inflate.prototype.decompress",kb.prototype.p);nb("Zlib.Inflate.BufferType",{ADAPTIVE:Ba.D,BLOCK:Ba.F});D("Zlib.Deflate",mb);D("Zlib.Deflate.compress",function(d,a){return(new mb(d,a)).j()});D("Zlib.Deflate.prototype.compress",mb.prototype.j);nb("Zlib.Deflate.CompressionType",{NONE:$.NONE,FIXED:$.r,DYNAMIC:$.k});}).call(this); //@ sourceMappingURL=zlib.min.js.map
diff --git a/test/README.md b/test/README.md
new file mode 100644
index 0000000..a349ce3
--- /dev/null
+++ b/test/README.md
@@ -0,0 +1 @@
+# TEST FOLDER
diff --git a/test/index.js b/test/index.js
new file mode 100644
index 0000000..d3774f1
--- /dev/null
+++ b/test/index.js
@@ -0,0 +1,2 @@
+console.log('test cases have not been finished.');
+
diff --git a/webpack.config.umd.js b/webpack.config.umd.js
new file mode 100644
index 0000000..ce1d134
--- /dev/null
+++ b/webpack.config.umd.js
@@ -0,0 +1,17 @@
+module.exports = function (umdConf) {
+  umdConf.devServer.host = '0.0.0.0';
+  //umdConf.webpackFeatures.enableEntryHTML();//生成
+  umdConf.output.publicPath = '';
+  umdConf.output.library = 'MessageEngine';
+
+  //console.dir(umdConf);
+
+  if (umdConf.devMode) {
+    umdConf.webpackFeatures.enableEntryHot();
+  } else {
+    umdConf.webpackFeatures.enableUglifyJs({
+      comments: false
+    });
+  }
+};
+
--
libgit2 0.24.0