UNPKG

ndn-js-contrib

Version:

Reusable 'Classes' for Named Data Networking: NameTree, PIT, FIB, ContentStore, Interfaces, and Transports

1,057 lines (956 loc) 30.4 kB
// 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. // Copyright 2005 Google Inc. All Rights Reserved. /** * @fileoverview SHA-1 cryptographic hash. * Variable names follow the notation in FIPS PUB 180-3: * http://csrc.nist.gov/publications/fips/fips180-3/fips180-3_final.pdf. * * Usage: * var sha1 = new goog.crypt.sha1(); * sha1.update(bytes); * var hash = sha1.digest(); * */ /** * SHA-1 cryptographic hash constructor. * * The properties declared here are discussed in the above algorithm document. * @constructor */ var Sha1 = function() { /** * Holds the previous values of accumulated variables a-e in the compress_ * function. * @type {Array.<number>} * @private */ this.chain_ = []; /** * A buffer holding the partially computed hash result. * @type {Array.<number>} * @private */ this.buf_ = []; /** * An array of 80 bytes, each a part of the message to be hashed. Referred to * as the message schedule in the docs. * @type {Array.<number>} * @private */ this.W_ = []; /** * Contains data needed to pad messages less than 64 bytes. * @type {Array.<number>} * @private */ this.pad_ = []; this.pad_[0] = 128; for (var i = 1; i < 64; ++i) { this.pad_[i] = 0; } this.reset(); }; /** * Resets the internal accumulator. */ Sha1.prototype.reset = function() { this.chain_[0] = 0x67452301; this.chain_[1] = 0xefcdab89; this.chain_[2] = 0x98badcfe; this.chain_[3] = 0x10325476; this.chain_[4] = 0xc3d2e1f0; this.inbuf_ = 0; this.total_ = 0; }; /** * Internal helper performing 32 bit left rotate. * @return {number} w rotated left by r bits. * @private */ Sha1.prototype.rotl_ = function(w, r) { return ((w << r) | (w >>> (32 - r))) & 0xffffffff; }; /** * Internal compress helper function. * @param {Array} buf containing block to compress. * @private */ Sha1.prototype.compress_ = function(buf) { var W = this.W_; // get 16 big endian words for (var i = 0; i < 64; i += 4) { var w = (buf[i] << 24) | (buf[i + 1] << 16) | (buf[i + 2] << 8) | (buf[i + 3]); W[i / 4] = w; } // expand to 80 words for (var i = 16; i < 80; i++) { W[i] = this.rotl_(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1); } var a = this.chain_[0]; var b = this.chain_[1]; var c = this.chain_[2]; var d = this.chain_[3]; var e = this.chain_[4]; var f, k; for (var i = 0; i < 80; i++) { if (i < 40) { if (i < 20) { f = d ^ (b & (c ^ d)); k = 0x5a827999; } else { f = b ^ c ^ d; k = 0x6ed9eba1; } } else { if (i < 60) { f = (b & c) | (d & (b | c)); k = 0x8f1bbcdc; } else { f = b ^ c ^ d; k = 0xca62c1d6; } } var t = (this.rotl_(a, 5) + f + e + k + W[i]) & 0xffffffff; e = d; d = c; c = this.rotl_(b, 30); b = a; a = t; } this.chain_[0] = (this.chain_[0] + a) & 0xffffffff; this.chain_[1] = (this.chain_[1] + b) & 0xffffffff; this.chain_[2] = (this.chain_[2] + c) & 0xffffffff; this.chain_[3] = (this.chain_[3] + d) & 0xffffffff; this.chain_[4] = (this.chain_[4] + e) & 0xffffffff; }; /** * Adds a byte array to internal accumulator. * @param {Array.<number>} bytes to add to digest. * @param {number} opt_length is # of bytes to compress. */ Sha1.prototype.update = function(bytes, opt_length) { if (!opt_length) { opt_length = bytes.length; } var n = 0; // Optimize for 64 byte chunks at 64 byte boundaries. if (this.inbuf_ == 0) { while (n + 64 < opt_length) { this.compress_(bytes.slice(n, n + 64)); n += 64; this.total_ += 64; } } while (n < opt_length) { this.buf_[this.inbuf_++] = bytes[n++]; this.total_++; if (this.inbuf_ == 64) { this.inbuf_ = 0; this.compress_(this.buf_); // Pick up 64 byte chunks. while (n + 64 < opt_length) { this.compress_(bytes.slice(n, n + 64)); n += 64; this.total_ += 64; } } } }; /** * @return {Array} byte[20] containing finalized hash. */ Sha1.prototype.digest = function() { var digest = []; var totalBits = this.total_ * 8; // Add pad 0x80 0x00*. if (this.inbuf_ < 56) { this.update(this.pad_, 56 - this.inbuf_); } else { this.update(this.pad_, 64 - (this.inbuf_ - 56)); } // Add # bits. for (var i = 63; i >= 56; i--) { this.buf_[i] = totalBits & 255; totalBits >>>= 8; } this.compress_(this.buf_); var n = 0; for (var i = 0; i < 5; i++) { for (var j = 24; j >= 0; j -= 8) { digest[n++] = (this.chain_[i] >> j) & 255; } } return digest; }; /** * Copyright (c) 2013 The Chromium Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. **/ window.http = function() { var chrome = {}; var socket = (chrome && chrome.experimental && chrome.experimental.socket) || chrome.socket; // If this does not have chrome.socket, then return an empty http namespace. if (!socket) return {}; // Http response code strings. var responseMap = { 200: 'OK', 301: 'Moved Permanently', 304: 'Not Modified', 400: 'Bad Request', 401: 'Unauthorized', 403: 'Forbidden', 404: 'Not Found', 413: 'Request Entity Too Large', 414: 'Request-URI Too Long', 500: 'Internal Server Error'}; /** * Convert from an ArrayBuffer to a string. * @param {ArrayBuffer} buffer The array buffer to convert. * @return {string} The textual representation of the array. */ var arrayBufferToString = function(buffer) { var array = new Uint8Array(buffer); var str = ''; for (var i = 0; i < array.length; ++i) { str += String.fromCharCode(array[i]); } return str; }; /** * Convert from an UTF-8 array to UTF-8 string. * @param {array} UTF-8 array * @return {string} UTF-8 string */ var ary2utf8 = (function() { var patterns = [ {pattern: '0xxxxxxx', bytes: 1}, {pattern: '110xxxxx', bytes: 2}, {pattern: '1110xxxx', bytes: 3}, {pattern: '11110xxx', bytes: 4}, {pattern: '111110xx', bytes: 5}, {pattern: '1111110x', bytes: 6} ]; patterns.forEach(function(item) { item.header = item.pattern.replace(/[^10]/g, ''); item.pattern01 = item.pattern.replace(/[^10]/g, '0'); item.pattern01 = parseInt(item.pattern01, 2); item.mask_length = item.header.length; item.data_length = 8 - item.header.length; var mask = ''; for (var i = 0, len = item.mask_length; i < len; i++) { mask += '1'; } for (var i = 0, len = item.data_length; i < len; i++) { mask += '0'; } item.mask = mask; item.mask = parseInt(item.mask, 2); }); return function(ary) { var codes = []; var cur = 0; while(cur < ary.length) { var first = ary[cur]; var pattern = null; for (var i = 0, len = patterns.length; i < len; i++) { if ((first & patterns[i].mask) == patterns[i].pattern01) { pattern = patterns[i]; break; } } if (pattern == null) { throw 'utf-8 decode error'; } var rest = ary.slice(cur + 1, cur + pattern.bytes); cur += pattern.bytes; var code = ''; code += ('00000000' + (first & (255 ^ pattern.mask)).toString(2)).slice(-pattern.data_length); for (var i = 0, len = rest.length; i < len; i++) { code += ('00000000' + (rest[i] & parseInt('111111', 2)).toString(2)).slice(-6); } codes.push(parseInt(code, 2)); } return String.fromCharCode.apply(null, codes); }; })(); /** * Convert from an UTF-8 string to UTF-8 array. * @param {string} UTF-8 string * @return {array} UTF-8 array */ var utf82ary = (function() { var patterns = [ {pattern: '0xxxxxxx', bytes: 1}, {pattern: '110xxxxx', bytes: 2}, {pattern: '1110xxxx', bytes: 3}, {pattern: '11110xxx', bytes: 4}, {pattern: '111110xx', bytes: 5}, {pattern: '1111110x', bytes: 6} ]; patterns.forEach(function(item) { item.header = item.pattern.replace(/[^10]/g, ''); item.mask_length = item.header.length; item.data_length = 8 - item.header.length; item.max_bit_length = (item.bytes - 1) * 6 + item.data_length; }); var code2utf8array = function(code) { var pattern = null; var code01 = code.toString(2); for (var i = 0, len = patterns.length; i < len; i++) { if (code01.length <= patterns[i].max_bit_length) { pattern = patterns[i]; break; } } if (pattern == null) { throw 'utf-8 encode error'; } var ary = []; for (var i = 0, len = pattern.bytes - 1; i < len; i++) { ary.unshift(parseInt('10' + ('000000' + code01.slice(-6)).slice(-6), 2)); code01 = code01.slice(0, -6); } ary.unshift(parseInt(pattern.header + ('00000000' + code01).slice(-pattern.data_length), 2)); return ary; }; return function(str) { var codes = []; for (var i = 0, len = str.length; i < len; i++) { var code = str.charCodeAt(i); Array.prototype.push.apply(codes, code2utf8array(code)); } return codes; }; })(); /** * Convert a string to an ArrayBuffer. * @param {string} string The string to convert. * @return {ArrayBuffer} An array buffer whose bytes correspond to the string. */ var stringToArrayBuffer = function(string) { var buffer = new ArrayBuffer(string.length); var bufferView = new Uint8Array(buffer); for (var i = 0; i < string.length; i++) { bufferView[i] = string.charCodeAt(i); } return buffer; }; /** * An event source can dispatch events. These are dispatched to all of the * functions listening for that event type with arguments. * @constructor */ function EventSource() { this.listeners_ = {}; }; EventSource.prototype = { /** * Add |callback| as a listener for |type| events. * @param {string} type The type of the event. * @param {function(Object|undefined): boolean} callback The function to call * when this event type is dispatched. Arguments depend on the event * source and type. The function returns whether the event was "handled" * which will prevent delivery to the rest of the listeners. */ addEventListener: function(type, callback) { if (!this.listeners_[type]) this.listeners_[type] = []; this.listeners_[type].push(callback); }, /** * Remove |callback| as a listener for |type| events. * @param {string} type The type of the event. * @param {function(Object|undefined): boolean} callback The callback * function to remove from the event listeners for events having type * |type|. */ removeEventListener: function(type, callback) { if (!this.listeners_[type]) return; for (var i = this.listeners_[type].length - 1; i >= 0; i--) { if (this.listeners_[type][i] == callback) { this.listeners_[type].splice(i, 1); } } }, /** * Dispatch an event to all listeners for events of type |type|. * @param {type} type The type of the event being dispatched. * @param {...Object} var_args The arguments to pass when calling the * callback function. * @return {boolean} Returns true if the event was handled. */ dispatchEvent: function(type, var_args) { if (!this.listeners_[type]) return false; for (var i = 0; i < this.listeners_[type].length; i++) { if (this.listeners_[type][i].apply( /* this */ null, /* var_args */ Array.prototype.slice.call(arguments, 1))) { return true; } } } }; /** * HttpServer provides a lightweight Http web server. Currently it only * supports GET requests and upgrading to other protocols (i.e. WebSockets). * @constructor */ function HttpServer() { EventSource.apply(this); this.readyState_ = 0; } HttpServer.prototype = { __proto__: EventSource.prototype, /** * Listen for connections on |port| using the interface |host|. * @param {number} port The port to listen for incoming connections on. * @param {string=} opt_host The host interface to listen for connections on. * This will default to 0.0.0.0 if not specified which will listen on * all interfaces. */ listen: function(port, opt_host) { var t = this; socket.create('tcp', {}, function(socketInfo) { t.socketInfo_ = socketInfo; socket.listen(t.socketInfo_.socketId, opt_host || '0.0.0.0', port, 50, function(result) { t.readyState_ = 1; t.acceptConnection_(t.socketInfo_.socketId); }); }); }, acceptConnection_: function(socketId) { var t = this; socket.accept(this.socketInfo_.socketId, function(acceptInfo) { t.onConnection_(acceptInfo); t.acceptConnection_(socketId); }); }, onConnection_: function(acceptInfo) { this.readRequestFromSocket_(acceptInfo.socketId); }, readRequestFromSocket_: function(socketId) { var t = this; var requestData = ''; var endIndex = 0; var onDataRead = function(readInfo) { // Check if connection closed. if (readInfo.resultCode <= 0) { console.log("disconnect in readRequestFromSocket", readInfo, this) socket.disconnect(socketId); socket.destroy(socketId); return; } requestData += arrayBufferToString(readInfo.data).replace(/\r\n/g, '\n'); // Check for end of request. endIndex = requestData.indexOf('\n\n', endIndex); if (endIndex == -1) { endIndex = requestData.length - 1; socket.read(socketId, onDataRead); return; } var headers = requestData.substring(0, endIndex).split('\n'); var headerMap = {}; // headers[0] should be the Request-Line var requestLine = headers[0].split(' '); headerMap['method'] = requestLine[0]; headerMap['url'] = requestLine[1]; headerMap['Http-Version'] = requestLine[2]; for (var i = 1; i < headers.length; i++) { requestLine = headers[i].split(':', 2); if (requestLine.length == 2) headerMap[requestLine[0]] = requestLine[1].trim(); } var request = new HttpRequest(headerMap, socketId); t.onRequest_(request); } socket.read(socketId, onDataRead); }, onRequest_: function(request) { var type = request.headers['Upgrade'] ? 'upgrade' : 'request'; var keepAlive = request.headers['Connection'] == 'keep-alive'; if (!this.dispatchEvent(type, request)) request.close(); else if (keepAlive) this.readRequestFromSocket_(request.socketId_); }, }; // MIME types for common extensions. var extensionTypes = { 'css': 'text/css', 'html': 'text/html', 'htm': 'text/html', 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'js': 'text/javascript', 'png': 'image/png', 'svg': 'image/svg+xml', 'txt': 'text/plain'}; /** * Constructs an HttpRequest object which tracks all of the request headers and * socket for an active Http request. * @param {Object} headers The HTTP request headers. * @param {number} socketId The socket Id to use for the response. * @constructor */ function HttpRequest(headers, socketId) { this.version = 'HTTP/1.1'; this.headers = headers; this.responseHeaders_ = {}; this.headersSent = false; this.socketId_ = socketId; this.writes_ = 0; this.bytesRemaining = 0; this.finished_ = false; this.readyState = 1; } HttpRequest.prototype = { __proto__: EventSource.prototype, /** * Closes the Http request. */ close: function() { // The socket for keep alive connections will be re-used by the server. // Just stop referencing or using the socket in this HttpRequest. if (this.headers['Connection'] != 'keep-alive') { console.log("socket.disconnect in close", this) var er = new Error() console.log(er.stack) socket.disconnect(this.socketId_); socket.destroy(this.socketId_); } this.socketId_ = 0; this.readyState = 3; }, /** * Write the provided headers as a response to the request. * @param {int} responseCode The HTTP status code to respond with. * @param {Object} responseHeaders The response headers describing the * response. */ writeHead: function(responseCode, responseHeaders) { var headerString = this.version + ' ' + responseCode + ' ' + (responseMap[responseCode] || 'Unknown'); this.responseHeaders_ = responseHeaders; if (this.headers['Connection'] == 'keep-alive') responseHeaders['Connection'] = 'keep-alive'; if (!responseHeaders['Content-Length'] && responseHeaders['Connection'] == 'keep-alive') responseHeaders['Transfer-Encoding'] = 'chunked'; for (var i in responseHeaders) { headerString += '\r\n' + i + ': ' + responseHeaders[i]; } headerString += '\r\n\r\n'; this.write_(stringToArrayBuffer(headerString)); }, /** * Writes data to the response stream. * @param {string|ArrayBuffer} data The data to write to the stream. */ write: function(data) { if (this.responseHeaders_['Transfer-Encoding'] == 'chunked') { var newline = '\r\n'; var byteLength = (data instanceof ArrayBuffer) ? data.byteLength : data.length; var chunkLength = byteLength.toString(16).toUpperCase() + newline; var buffer = new ArrayBuffer(chunkLength.length + byteLength + newline.length); var bufferView = new Uint8Array(buffer); for (var i = 0; i < chunkLength.length; i++) bufferView[i] = chunkLength.charCodeAt(i); if (data instanceof ArrayBuffer) { bufferView.set(new Uint8Array(data), chunkLength.length); } else { for (var i = 0; i < data.length; i++) bufferView[chunkLength.length + i] = data.charCodeAt(i); } for (var i = 0; i < newline.length; i++) bufferView[chunkLength.length + byteLength + i] = newline.charCodeAt(i); data = buffer; } else if (!(data instanceof ArrayBuffer)) { data = stringToArrayBuffer(data); } this.write_(data); }, /** * Finishes the HTTP response writing |data| before closing. * @param {string|ArrayBuffer=} opt_data Optional data to write to the stream * before closing it. */ end: function(opt_data) { if (opt_data) this.write(opt_data); if (this.responseHeaders_['Transfer-Encoding'] == 'chunked') this.write(''); this.finished_ = true; this.checkFinished_(); }, /** * Automatically serve the given |url| request. * @param {string} url The URL to fetch the file to be served from. This is * retrieved via an XmlHttpRequest and served as the response to the * request. */ serveUrl: function(url) { var t = this; var xhr = new XMLHttpRequest(); xhr.onloadend = function() { var type = 'text/plain'; if (this.getResponseHeader('Content-Type')) { type = this.getResponseHeader('Content-Type'); } else if (url.indexOf('.') != -1) { var extension = url.substr(url.indexOf('.') + 1); type = extensionTypes[extension] || type; } console.log('Served ' + url); var contentLength = this.getResponseHeader('Content-Length'); if (xhr.status == 200) contentLength = (this.response && this.response.byteLength) || 0; t.writeHead(this.status, { 'Content-Type': type, 'Content-Length': contentLength}); t.end(this.response); }; xhr.open('GET', url, true); xhr.responseType = 'arraybuffer'; xhr.send(); }, write_: function(array) { var t = this; this.bytesRemaining += array.byteLength; socket.write(this.socketId_, array, function(writeInfo) { if (writeInfo.bytesWritten < 0) { console.error('Error writing to socket, code '+writeInfo.bytesWritten); return; } t.bytesRemaining -= writeInfo.bytesWritten; t.checkFinished_(); }); }, checkFinished_: function() { if (!this.finished_ || this.bytesRemaining > 0) return; this.close(); } }; /** * Constructs a server which is capable of accepting WebSocket connections. * @param {HttpServer} httpServer The Http Server to listen and handle * WebSocket upgrade requests on. * @constructor */ function WebSocketServer(httpServer) { EventSource.apply(this); httpServer.addEventListener('upgrade', this.upgradeToWebSocket_.bind(this)); } WebSocketServer.prototype = { __proto__: EventSource.prototype, upgradeToWebSocket_: function(request) { if (request.headers['Upgrade'] != 'websocket' || !request.headers['Sec-WebSocket-Key']) { return false; } if (this.dispatchEvent('request', new WebSocketRequest(request))) { if (request.socketId_) request.reject(); return true; } return false; } }; /** * Constructs a WebSocket request object from an Http request. This invalidates * the Http request's socket and offers accept and reject methods for accepting * and rejecting the WebSocket upgrade request. * @param {HttpRequest} httpRequest The HTTP request to upgrade. */ function WebSocketRequest(httpRequest) { // We'll assume control of the socket for this request. HttpRequest.apply(this, [httpRequest.headers, httpRequest.socketId_]); httpRequest.socketId_ = 0; } WebSocketRequest.prototype = { __proto__: HttpRequest.prototype, /** * Accepts the WebSocket request. * @return {WebSocketServerSocket} The websocket for the accepted request. */ accept: function() { // Construct WebSocket response key. var clientKey = this.headers['Sec-WebSocket-Key']; var toArray = function(str) { var a = []; for (var i = 0; i < str.length; i++) { a.push(str.charCodeAt(i)); } return a; } var toString = function(a) { var str = ''; for (var i = 0; i < a.length; i++) { str += String.fromCharCode(a[i]); } return str; } // Magic string used for websocket connection key hashing: // http://en.wikipedia.org/wiki/WebSocket var magicStr = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; // clientKey is base64 encoded key. clientKey += magicStr; var sha1 = new Sha1(); sha1.reset(); sha1.update(toArray(clientKey)); var responseKey = btoa(toString(sha1.digest())); var responseHeader = { 'Upgrade': 'websocket', 'Connection': 'Upgrade', 'Sec-WebSocket-Accept': responseKey}; if (this.headers['Sec-WebSocket-Protocol']) responseHeader['Sec-WebSocket-Protocol'] = this.headers['Sec-WebSocket-Protocol']; this.writeHead(101, responseHeader); var socket = new WebSocketServerSocket(this.socketId_); // Detach the socket so that we don't use it anymore. this.socketId_ = 0; return socket; }, /** * Rejects the WebSocket request, closing the connection. */ reject: function() { this.close(); } } /** * Constructs a WebSocketServerSocket using the given socketId. This should be * a socket which has already been upgraded from an Http request. * @param {number} socketId The socket id with an active websocket connection. */ function WebSocketServerSocket(socketId) { this.socketId_ = socketId; EventSource.apply(this); this.readFromSocket_(); } WebSocketServerSocket.prototype = { __proto__: EventSource.prototype, onmessage : function(callback){ this.addEventListener("message", function(a1, a2){ console.log("CHROME_WEBSOCKET_ONMESSAGE", a1, a2) callback(a1, a2); }); }, onclose : function(callback){ this.addEventListener("close", callback) }, onerror : function(callback){ this.addEventListener("error", callback) }, /** * Send |data| on the WebSocket. * @param {string|Array.<number>|ArrayBuffer} data The data to send over the WebSocket. */ send: function(data) { // WebSocket must specify opcode when send frame. // The opcode for data frame is 1(text) or 2(binary). if (typeof data == 'string' || data instanceof String) { this.sendFrame_(1, data); } else { this.sendFrame_(2, data); } }, /** * Begin closing the WebSocket. Note that the WebSocket protocol uses a * handshake to close the connection, so this call will begin the closing * process. */ close: function() { this.sendFrame_(8); this.readyState = 2; }, readFromSocket_: function() { var t = this; var data = []; var message = ''; var fragmentedOp = 0; var fragmentedMessages = []; var onDataRead = function(readInfo) { if (readInfo.resultCode <= 0) { t.close_(); return; } if (!readInfo.data.byteLength) { socket.read(t.socketId_, onDataRead); return; } var a = new Uint8Array(readInfo.data); for (var i = 0; i < a.length; i++) data.push(a[i]); while (data.length) { var length_code = -1; var data_start = 6; var mask; var fin = (data[0] & 128) >> 7; var op = data[0] & 15; if (data.length > 1) length_code = data[1] & 127; if (length_code > 125) { if ((length_code == 126 && data.length > 7) || (length_code == 127 && data.length > 14)) { if (length_code == 126) { length_code = data[2] * 256 + data[3]; mask = data.slice(4, 8); data_start = 8; } else if (length_code == 127) { length_code = 0; for (var i = 0; i < 8; i++) { length_code = length_code * 256 + data[2 + i]; } mask = data.slice(10, 14); data_start = 14; } } else { length_code = -1; // Insufficient data to compute length } } else { if (data.length > 5) mask = data.slice(2, 6); } if (length_code > -1 && data.length >= data_start + length_code) { var decoded = data.slice(data_start, data_start + length_code).map(function(byte, index) { return byte ^ mask[index % 4]; }); if (op == 1) { decoded = ary2utf8(decoded); } data = data.slice(data_start + length_code); if (fin && op > 0) { // Unfragmented message. if (!t.onFrame_(op, decoded)) return; } else { // Fragmented message. fragmentedOp = fragmentedOp || op; fragmentedMessages.push(decoded); if (fin) { var joinMessage = null; if (op == 1) { joinMessage = fragmentedMessagess.join(''); } else { joinMessage = fragmentedMessages.reduce(function(pre, cur) { return Array.prototype.push.apply(pre, cur); }, []); } if (!t.onFrame_(fragmentedOp, joinMessage)) return; fragmentedOp = 0; fragmentedMessages = []; } } } else { break; // Insufficient data, wait for more. } } socket.read(t.socketId_, onDataRead); }; socket.read(this.socketId_, onDataRead); }, onFrame_: function(op, data) { if (op == 1 || op == 2) { if (typeof data == 'string' || data instanceof String) { // Don't do anything. } else if (Array.isArray(data)) { data = new Uint8Array(data).buffer; } else if (data instanceof ArrayBuffer) { // Don't do anything. } else { data = data.buffer; } this.dispatchEvent('message', {'data': data}); } else if (op == 8) { // A close message must be confirmed before the websocket is closed. if (this.readyState == 1) { this.sendFrame_(8); } else { this.close_(); return false; } } return true; }, sendFrame_: function(op, data) { var t = this; var WebsocketFrameData = function(op, data) { var ary = data; if (typeof data == 'string' || data instanceof String) { ary = utf82ary(data); } if (Array.isArray(ary)) { ary = new Uint8Array(ary); } if (ary instanceof ArrayBuffer) { ary = new Uint8Array(ary); } ary = new Uint8Array(ary.buffer); var length = ary.length; if (ary.length > 65535) length += 10; else if (ary.length > 125) length += 4; else length += 2; var lengthBytes = 0; var buffer = new ArrayBuffer(length); var bv = new Uint8Array(buffer); bv[0] = 128 | (op & 15); // Fin and type text. bv[1] = ary.length > 65535 ? 127 : (ary.length > 125 ? 126 : ary.length); if (ary.length > 65535) lengthBytes = 8; else if (ary.length > 125) lengthBytes = 2; var len = ary.length; for (var i = lengthBytes - 1; i >= 0; i--) { bv[2 + i] = len & 255; len = len >> 8; } var dataStart = lengthBytes + 2; for (var i = 0; i < ary.length; i++) { bv[dataStart + i] = ary[i]; } return buffer; } var array = WebsocketFrameData(op, data || ''); socket.write(this.socketId_, array, function(writeInfo) { if (writeInfo.resultCode < 0 || writeInfo.bytesWritten !== array.byteLength) { t.close_(); } }); }, close_: function() { console.log("calling socket.disconnect", this._socketId_, this) chrome.socket.disconnect(this.socketId_); chrome.socket.destroy(this.socketId_); this.readyState = 3; this.dispatchEvent('close'); } }; return { 'Server': HttpServer, 'WebSocketServer': WebSocketServer, }; }();