]> git.proxmox.com Git - mirror_novnc.git/blame - include/websock.js
Fix tight decoding when using binary/non-base64 connection.
[mirror_novnc.git] / include / websock.js
CommitLineData
72f1348b
JM
1/*
2 * Websock: high-performance binary WebSockets
d58f8b51 3 * Copyright (C) 2012 Joel Martin
72f1348b
JM
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
ff4bfcb7
JM
17/*jslint browser: true, bitwise: false, plusplus: false */
18/*global Util, Base64 */
19
72f1348b
JM
20
21// Load Flash WebSocket emulator if needed
22
bee36506 23// To force WebSocket emulator even when native WebSocket available
e5d5a7d3 24//window.WEB_SOCKET_FORCE_FLASH = true;
bee36506 25// To enable WebSocket emulator debug:
e5d5a7d3 26//window.WEB_SOCKET_DEBUG=1;
bee36506 27
f2d85676 28if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
72f1348b 29 Websock_native = true;
f2d85676 30} else if (window.MozWebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
ce3bdbcc
JM
31 Websock_native = true;
32 window.WebSocket = window.MozWebSocket;
72f1348b
JM
33} else {
34 /* no builtin WebSocket so load web_socket.js */
b688a909 35
72f1348b
JM
36 Websock_native = false;
37 (function () {
38 function get_INCLUDE_URI() {
39 return (typeof INCLUDE_URI !== "undefined") ?
40 INCLUDE_URI : "include/";
41 }
42
43 var start = "<script src='" + get_INCLUDE_URI(),
44 end = "'><\/script>", extra = "";
45
ff4bfcb7 46 window.WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
61fc1f22 47 "web-socket-js/WebSocketMain.swf";
0981845e
JM
48 if (Util.Engine.trident) {
49 Util.Debug("Forcing uncached load of WebSocketMain.swf");
ff4bfcb7 50 window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
0981845e 51 }
72f1348b 52 extra += start + "web-socket-js/swfobject.js" + end;
72f1348b
JM
53 extra += start + "web-socket-js/web_socket.js" + end;
54 document.write(extra);
55 }());
56}
57
58
59function Websock() {
d890e864 60"use strict";
72f1348b
JM
61
62var api = {}, // Public API
63 websocket = null, // WebSocket object
4dd1bb1e
JM
64 protocols, // Protocols to request in priority order
65 mode = 'base64',
72f1348b
JM
66 rQ = [], // Receive queue
67 rQi = 0, // Receive queue index
68 rQmax = 10000, // Max receive queue size before compacting
69 sQ = [], // Send queue
70
71 eventHandlers = {
72 'message' : function() {},
73 'open' : function() {},
74 'close' : function() {},
75 'error' : function() {}
fa8f14d5
JM
76 },
77
78 test_mode = false;
72f1348b
JM
79
80
81//
82// Queue public functions
83//
84
85function get_sQ() {
86 return sQ;
87}
88
89function get_rQ() {
90 return rQ;
91}
92function get_rQi() {
93 return rQi;
94}
d890e864 95function set_rQi(val) {
72f1348b 96 rQi = val;
ff4bfcb7 97}
72f1348b
JM
98
99function rQlen() {
100 return rQ.length - rQi;
101}
102
103function rQpeek8() {
104 return (rQ[rQi] );
105}
106function rQshift8() {
107 return (rQ[rQi++] );
108}
109function rQunshift8(num) {
110 if (rQi === 0) {
111 rQ.unshift(num);
112 } else {
113 rQi -= 1;
114 rQ[rQi] = num;
115 }
116
117}
118function rQshift16() {
119 return (rQ[rQi++] << 8) +
120 (rQ[rQi++] );
121}
122function rQshift32() {
123 return (rQ[rQi++] << 24) +
124 (rQ[rQi++] << 16) +
125 (rQ[rQi++] << 8) +
126 (rQ[rQi++] );
127}
4dd1bb1e
JM
128function rQslice(start, end) {
129 if (mode === 'binary') {
130 if (end) {
131 return rQ.subarray(rQi + start, rQi + end);
132 } else {
133 return rQ.subarray(rQi + start);
134 }
135 } else {
136 if (end) {
137 return rQ.slice(rQi + start, rQi + end);
138 } else {
139 return rQ.slice(rQi + start);
140 }
141 }
142}
143
72f1348b 144function rQshiftStr(len) {
f2d85676 145 if (typeof(len) === 'undefined') { len = rQlen(); }
4dd1bb1e 146 var arr = rQslice(0, len);
72f1348b 147 rQi += len;
4dd1bb1e 148 return String.fromCharCode.apply(null, arr);
72f1348b
JM
149}
150function rQshiftBytes(len) {
f2d85676 151 if (typeof(len) === 'undefined') { len = rQlen(); }
4dd1bb1e
JM
152 var a = rQslice(0, len), b = [];
153 if (mode === 'binary') {
154 // Convert to plain array
155 b.push.apply(b, a);
72f1348b 156 } else {
4dd1bb1e
JM
157 // Already plain array, just return the original
158 b = a
72f1348b 159 }
4dd1bb1e
JM
160 rQi += len;
161 return b;
162}
163function rQshiftArray(len) {
164 if (typeof(len) === 'undefined') { len = rQlen(); }
165 var a = rQslice(0, len);
166 rQi += len;
167 return a;
72f1348b
JM
168}
169
170// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
171// to be available in the receive queue. Return true if we need to
172// wait (and possibly print a debug message), otherwise false.
173function rQwait(msg, num, goback) {
174 var rQlen = rQ.length - rQi; // Skip rQlen() function call
175 if (rQlen < num) {
176 if (goback) {
177 if (rQi < goback) {
178 throw("rQwait cannot backup " + goback + " bytes");
179 }
180 rQi -= goback;
181 }
182 //Util.Debug(" waiting for " + (num-rQlen) +
183 // " " + msg + " byte(s)");
184 return true; // true means need more data
185 }
186 return false;
187}
188
189//
190// Private utility routines
191//
192
193function encode_message() {
194 /* base64 encode */
4dd1bb1e
JM
195 if (mode === 'binary') {
196 return (new Uint8Array(sQ)).buffer;
197 } else {
198 return Base64.encode(sQ);
199 }
72f1348b
JM
200}
201
202function decode_message(data) {
203 //Util.Debug(">> decode_message: " + data);
4dd1bb1e
JM
204 if (mode === 'binary') {
205 // Create new arraybuffer and dump old and new data into it
206 // TODO: this could be far more efficient and re-use the array
207 var new_rQ = new Uint8Array(rQ.length + data.byteLength);
208 new_rQ.set(rQ);
209 new_rQ.set(new Uint8Array(data), rQ.length);
210 rQ = new_rQ;
211 } else {
212 /* base64 decode and concat to the end */
213 rQ = rQ.concat(Base64.decode(data, 0));
214 }
72f1348b
JM
215 //Util.Debug(">> decode_message, rQ: " + rQ);
216}
217
218
219//
220// Public Send functions
221//
222
223function flush() {
8b502df2
JM
224 if (websocket.bufferedAmount !== 0) {
225 Util.Debug("bufferedAmount: " + websocket.bufferedAmount);
226 }
7cc5fbc5 227 if (websocket.bufferedAmount < api.maxBufferedAmount) {
72f1348b
JM
228 //Util.Debug("arr: " + arr);
229 //Util.Debug("sQ: " + sQ);
f31eeaa8 230 if (sQ.length > 0) {
72f1348b
JM
231 websocket.send(encode_message(sQ));
232 sQ = [];
233 }
234 return true;
235 } else {
8b502df2
JM
236 Util.Info("Delaying send, bufferedAmount: " +
237 websocket.bufferedAmount);
72f1348b
JM
238 return false;
239 }
240}
241
242// overridable for testing
243function send(arr) {
244 //Util.Debug(">> send_array: " + arr);
245 sQ = sQ.concat(arr);
1756a30a 246 return flush();
43cf7bd8 247}
72f1348b
JM
248
249function send_string(str) {
250 //Util.Debug(">> send_string: " + str);
251 api.send(str.split('').map(
252 function (chr) { return chr.charCodeAt(0); } ) );
253}
254
255//
256// Other public functions
257
258function recv_message(e) {
259 //Util.Debug(">> recv_message: " + e.data.length);
260
261 try {
262 decode_message(e.data);
263 if (rQlen() > 0) {
43cf7bd8 264 eventHandlers.message();
72f1348b
JM
265 // Compact the receive queue
266 if (rQ.length > rQmax) {
267 //Util.Debug("Compacting receive queue");
4dd1bb1e 268 rQ = rQslice(rQi);
72f1348b
JM
269 rQi = 0;
270 }
271 } else {
272 Util.Debug("Ignoring empty message");
273 }
274 } catch (exc) {
275 if (typeof exc.stack !== 'undefined') {
276 Util.Warn("recv_message, caught exception: " + exc.stack);
277 } else if (typeof exc.description !== 'undefined') {
278 Util.Warn("recv_message, caught exception: " + exc.description);
279 } else {
280 Util.Warn("recv_message, caught exception:" + exc);
281 }
282 if (typeof exc.name !== 'undefined') {
43cf7bd8 283 eventHandlers.error(exc.name + ": " + exc.message);
72f1348b 284 } else {
43cf7bd8 285 eventHandlers.error(exc);
72f1348b
JM
286 }
287 }
288 //Util.Debug("<< recv_message");
43cf7bd8 289}
72f1348b
JM
290
291
292// Set event handlers
293function on(evt, handler) {
294 eventHandlers[evt] = handler;
295}
296
297function init() {
298 rQ = [];
299 rQi = 0;
300 sQ = [];
4dd1bb1e
JM
301 websocket = null,
302 protocols = "base64";
303
304 var bt = false,
305 wsbt = false;
306
307 if (('Uint8Array' in window) &&
308 ('set' in Uint8Array.prototype)) {
309 bt = true;
310 }
311 // TODO: this sucks, the property should exist on the prototype
312 // but it does not.
313 try {
314 if (bt && ('binaryType' in (new WebSocket("ws://localhost:17523")))) {
315 wsbt = true;
316 }
317 } catch (exc) {
318 // Just ignore failed test localhost connections
319 }
320 if (bt && wsbt) {
321 Util.Info("Detected binaryType support in WebSockets");
322 protocols = ['binary', 'base64'];
323 } else {
324 Util.Info("No binaryType support in WebSockets, using base64 encoding");
325 protocols = 'base64';
326 }
72f1348b
JM
327}
328
329function open(uri) {
330 init();
331
fa8f14d5
JM
332 if (test_mode) {
333 websocket = {};
334 } else {
4dd1bb1e 335 websocket = new WebSocket(uri, protocols);
fa8f14d5 336 }
72f1348b
JM
337
338 websocket.onmessage = recv_message;
d890e864 339 websocket.onopen = function() {
72f1348b 340 Util.Debug(">> WebSock.onopen");
d890e864 341 if (websocket.protocol) {
4dd1bb1e 342 mode = websocket.protocol;
d890e864 343 Util.Info("Server chose sub-protocol: " + websocket.protocol);
bee36506 344 } else {
4dd1bb1e 345 mode = 'base64';
bee36506 346 Util.Error("Server select no sub-protocol!: " + websocket.protocol);
d890e864 347 }
4dd1bb1e
JM
348 if (mode === 'binary') {
349 websocket.binaryType = 'arraybuffer';
350 }
43cf7bd8 351 eventHandlers.open();
72f1348b 352 Util.Debug("<< WebSock.onopen");
43cf7bd8 353 };
72f1348b
JM
354 websocket.onclose = function(e) {
355 Util.Debug(">> WebSock.onclose");
d890e864 356 eventHandlers.close(e);
72f1348b 357 Util.Debug("<< WebSock.onclose");
43cf7bd8 358 };
72f1348b 359 websocket.onerror = function(e) {
d890e864 360 Util.Debug(">> WebSock.onerror: " + e);
43cf7bd8 361 eventHandlers.error(e);
d890e864 362 Util.Debug("<< WebSock.onerror");
43cf7bd8 363 };
72f1348b
JM
364}
365
366function close() {
367 if (websocket) {
368 if ((websocket.readyState === WebSocket.OPEN) ||
369 (websocket.readyState === WebSocket.CONNECTING)) {
370 Util.Info("Closing WebSocket connection");
371 websocket.close();
372 }
373 websocket.onmessage = function (e) { return; };
374 }
375}
376
fa8f14d5
JM
377// Override internal functions for testing
378// Takes a send function, returns reference to recv function
379function testMode(override_send) {
380 test_mode = true;
381 api.send = override_send;
382 api.close = function () {};
383 return recv_message;
384}
385
72f1348b 386function constructor() {
7cc5fbc5
JM
387 // Configuration settings
388 api.maxBufferedAmount = 200;
389
72f1348b
JM
390 // Direct access to send and receive queues
391 api.get_sQ = get_sQ;
392 api.get_rQ = get_rQ;
393 api.get_rQi = get_rQi;
394 api.set_rQi = set_rQi;
395
396 // Routines to read from the receive queue
397 api.rQlen = rQlen;
398 api.rQpeek8 = rQpeek8;
399 api.rQshift8 = rQshift8;
400 api.rQunshift8 = rQunshift8;
401 api.rQshift16 = rQshift16;
402 api.rQshift32 = rQshift32;
403 api.rQshiftStr = rQshiftStr;
404 api.rQshiftBytes = rQshiftBytes;
405 api.rQslice = rQslice;
406 api.rQwait = rQwait;
407
408 api.flush = flush;
409 api.send = send;
410 api.send_string = send_string;
411
72f1348b
JM
412 api.on = on;
413 api.init = init;
414 api.open = open;
415 api.close = close;
fa8f14d5 416 api.testMode = testMode;
72f1348b
JM
417
418 return api;
419}
420
421return constructor();
422
423}