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