1/*
2 * Websock: high-performance binary WebSockets
3 * Copyright (C) 2011 Joel Martin
4 * Licensed under LGPL-3 (see LICENSE.txt)
5 *
6 * Websock is similar to the standard WebSocket object but Websock
7 * enables communication with raw TCP sockets (i.e. the binary stream)
8 * via websockify. This is accomplished by base64 encoding the data
9 * stream between Websock and websockify.
10 *
11 * Websock has built-in receive queue buffering; the message event
12 * does not contain actual data but is simply a notification that
13 * there is new data available. Several rQ* methods are available to
14 * read binary data off of the receive queue.
15 */
16
17/*jslint browser: true, bitwise: false, plusplus: false */
18/*global Util, Base64 */
19
20
21// Load Flash WebSocket emulator if needed
22
23if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
24    Websock_native = true;
25} else if (window.MozWebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
26    Websock_native = true;
27    window.WebSocket = window.MozWebSocket;
28} else {
29    /* no builtin WebSocket so load web_socket.js */
30
31    // To enable debug:
32    // window.WEB_SOCKET_DEBUG=1;
33
34    Websock_native = false;
35    (function () {
36        function get_INCLUDE_URI() {
37            return (typeof INCLUDE_URI !== "undefined") ?
38                INCLUDE_URI : "include/";
39        }
40
41        var start = "<script src='" + get_INCLUDE_URI(),
42            end = "'><\/script>", extra = "";
43
44        window.WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
45                    "web-socket-js/WebSocketMain.swf";
46        if (Util.Engine.trident) {
47            Util.Debug("Forcing uncached load of WebSocketMain.swf");
48            window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
49        }
50        extra += start + "web-socket-js/swfobject.js" + end;
51        extra += start + "web-socket-js/web_socket.js" + end;
52        document.write(extra);
53    }());
54}
55
56
57function Websock() {
58"use strict";
59
60var api = {},         // Public API
61    websocket = null, // WebSocket object
62    rQ = [],          // Receive queue
63    rQi = 0,          // Receive queue index
64    rQmax = 10000,    // Max receive queue size before compacting
65    sQ = [],          // Send queue
66
67    eventHandlers = {
68        'message' : function() {},
69        'open'    : function() {},
70        'close'   : function() {},
71        'error'   : function() {}
72    },
73
74    test_mode = false;
75
76
77//
78// Queue public functions
79//
80
81function get_sQ() {
82    return sQ;
83}
84
85function get_rQ() {
86    return rQ;
87}
88function get_rQi() {
89    return rQi;
90}
91function set_rQi(val) {
92    rQi = val;
93}
94
95function rQlen() {
96    return rQ.length - rQi;
97}
98
99function rQpeek8() {
100    return (rQ[rQi]      );
101}
102function rQshift8() {
103    return (rQ[rQi++]      );
104}
105function rQunshift8(num) {
106    if (rQi === 0) {
107        rQ.unshift(num);
108    } else {
109        rQi -= 1;
110        rQ[rQi] = num;
111    }
112
113}
114function rQshift16() {
115    return (rQ[rQi++] <<  8) +
116           (rQ[rQi++]      );
117}
118function rQshift32() {
119    return (rQ[rQi++] << 24) +
120           (rQ[rQi++] << 16) +
121           (rQ[rQi++] <<  8) +
122           (rQ[rQi++]      );
123}
124function rQshiftStr(len) {
125    if (typeof(len) === 'undefined') { len = rQlen(); }
126    var arr = rQ.slice(rQi, rQi + len);
127    rQi += len;
128    return arr.map(function (num) {
129            return String.fromCharCode(num); } ).join('');
130
131}
132function rQshiftBytes(len) {
133    if (typeof(len) === 'undefined') { len = rQlen(); }
134    rQi += len;
135    return rQ.slice(rQi-len, rQi);
136}
137
138function rQslice(start, end) {
139    if (end) {
140        return rQ.slice(rQi + start, rQi + end);
141    } else {
142        return rQ.slice(rQi + start);
143    }
144}
145
146// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
147// to be available in the receive queue. Return true if we need to
148// wait (and possibly print a debug message), otherwise false.
149function rQwait(msg, num, goback) {
150    var rQlen = rQ.length - rQi; // Skip rQlen() function call
151    if (rQlen < num) {
152        if (goback) {
153            if (rQi < goback) {
154                throw("rQwait cannot backup " + goback + " bytes");
155            }
156            rQi -= goback;
157        }
158        //Util.Debug("   waiting for " + (num-rQlen) +
159        //           " " + msg + " byte(s)");
160        return true;  // true means need more data
161    }
162    return false;
163}
164
165//
166// Private utility routines
167//
168
169function encode_message() {
170    /* base64 encode */
171    return Base64.encode(sQ);
172}
173
174function decode_message(data) {
175    //Util.Debug(">> decode_message: " + data);
176    /* base64 decode */
177    rQ = rQ.concat(Base64.decode(data, 0));
178    //Util.Debug(">> decode_message, rQ: " + rQ);
179}
180
181
182//
183// Public Send functions
184//
185
186function flush() {
187    if (websocket.bufferedAmount !== 0) {
188        Util.Debug("bufferedAmount: " + websocket.bufferedAmount);
189    }
190    if (websocket.bufferedAmount < api.maxBufferedAmount) {
191        //Util.Debug("arr: " + arr);
192        //Util.Debug("sQ: " + sQ);
193        if (sQ.length > 0) {
194            websocket.send(encode_message(sQ));
195            sQ = [];
196        }
197        return true;
198    } else {
199        Util.Info("Delaying send, bufferedAmount: " +
200                websocket.bufferedAmount);
201        return false;
202    }
203}
204
205// overridable for testing
206function send(arr) {
207    //Util.Debug(">> send_array: " + arr);
208    sQ = sQ.concat(arr);
209    return flush();
210}
211
212function send_string(str) {
213    //Util.Debug(">> send_string: " + str);
214    api.send(str.split('').map(
215        function (chr) { return chr.charCodeAt(0); } ) );
216}
217
218//
219// Other public functions
220
221function recv_message(e) {
222    //Util.Debug(">> recv_message: " + e.data.length);
223
224    try {
225        decode_message(e.data);
226        if (rQlen() > 0) {
227            eventHandlers.message();
228            // Compact the receive queue
229            if (rQ.length > rQmax) {
230                //Util.Debug("Compacting receive queue");
231                rQ = rQ.slice(rQi);
232                rQi = 0;
233            }
234        } else {
235            Util.Debug("Ignoring empty message");
236        }
237    } catch (exc) {
238        if (typeof exc.stack !== 'undefined') {
239            Util.Warn("recv_message, caught exception: " + exc.stack);
240        } else if (typeof exc.description !== 'undefined') {
241            Util.Warn("recv_message, caught exception: " + exc.description);
242        } else {
243            Util.Warn("recv_message, caught exception:" + exc);
244        }
245        if (typeof exc.name !== 'undefined') {
246            eventHandlers.error(exc.name + ": " + exc.message);
247        } else {
248            eventHandlers.error(exc);
249        }
250    }
251    //Util.Debug("<< recv_message");
252}
253
254
255// Set event handlers
256function on(evt, handler) {
257    eventHandlers[evt] = handler;
258}
259
260function init() {
261    rQ         = [];
262    rQi        = 0;
263    sQ         = [];
264    websocket  = null;
265}
266
267function open(uri) {
268    init();
269
270    if (test_mode) {
271        websocket = {};
272    } else {
273        websocket = new WebSocket(uri, 'base64');
274        // TODO: future native binary support
275        //websocket = new WebSocket(uri, ['binary', 'base64']);
276    }
277
278    websocket.onmessage = recv_message;
279    websocket.onopen = function() {
280        Util.Debug(">> WebSock.onopen");
281        if (websocket.protocol) {
282            Util.Info("Server chose sub-protocol: " + websocket.protocol);
283        }
284        eventHandlers.open();
285        Util.Debug("<< WebSock.onopen");
286    };
287    websocket.onclose = function(e) {
288        Util.Debug(">> WebSock.onclose");
289        eventHandlers.close(e);
290        Util.Debug("<< WebSock.onclose");
291    };
292    websocket.onerror = function(e) {
293        Util.Debug(">> WebSock.onerror: " + e);
294        eventHandlers.error(e);
295        Util.Debug("<< WebSock.onerror");
296    };
297}
298
299function close() {
300    if (websocket) {
301        if ((websocket.readyState === WebSocket.OPEN) ||
302            (websocket.readyState === WebSocket.CONNECTING)) {
303            Util.Info("Closing WebSocket connection");
304            websocket.close();
305        }
306        websocket.onmessage = function (e) { return; };
307    }
308}
309
310// Override internal functions for testing
311// Takes a send function, returns reference to recv function
312function testMode(override_send) {
313    test_mode = true;
314    api.send = override_send;
315    api.close = function () {};
316    return recv_message;
317}
318
319function constructor() {
320    // Configuration settings
321    api.maxBufferedAmount = 200;
322
323    // Direct access to send and receive queues
324    api.get_sQ       = get_sQ;
325    api.get_rQ       = get_rQ;
326    api.get_rQi      = get_rQi;
327    api.set_rQi      = set_rQi;
328
329    // Routines to read from the receive queue
330    api.rQlen        = rQlen;
331    api.rQpeek8      = rQpeek8;
332    api.rQshift8     = rQshift8;
333    api.rQunshift8   = rQunshift8;
334    api.rQshift16    = rQshift16;
335    api.rQshift32    = rQshift32;
336    api.rQshiftStr   = rQshiftStr;
337    api.rQshiftBytes = rQshiftBytes;
338    api.rQslice      = rQslice;
339    api.rQwait       = rQwait;
340
341    api.flush        = flush;
342    api.send         = send;
343    api.send_string  = send_string;
344
345    api.on           = on;
346    api.init         = init;
347    api.open         = open;
348    api.close        = close;
349    api.testMode     = testMode;
350
351    return api;
352}
353
354return constructor();
355
356}
357