Browse Source

Add web-socket-js support with packet re-ordering.

- web-socket-js is from http://github.com/gimite/web-socket-js. It is
  a flash object that emultates WebSockets.

Unfortunately, events (or packets) from the web-socket-js object can
get re-ordered so we need to know the packet order.

- So wsproxy.py prepends the sequence number of the packet when
  sending.

- If the client receives packets out of order it queues them up and
  scans the queue for the sequence number it's looking for until
  things are back on track. Gross, but hey: It works!

- Also, add packet sequence checking to wstest.*
Joel Martin 15 years ago
parent
commit
5d8e7ec068
7 changed files with 377 additions and 190 deletions
  1. 2 15
      TODO
  2. 11 6
      include/base64.js
  3. 19 1
      vnc.html
  4. 213 117
      vnc.js
  5. 63 24
      wsproxy.py
  6. 21 8
      wstest.html
  7. 48 19
      wstest.py

+ 2 - 15
TODO

@@ -1,19 +1,6 @@
-- Better status and error feedback.
-
-- Get working in firefox using flash web-socket-js:
-    http://github.com/gimite/web-socket-js
+- Make packet sequence number optional based on WebSockets 'path'.
 
 
-    - Only load Flash stuff if needed:
-        var x='< script type="text/javascript" src=';
-        var y='><\/script>';
-        var t='';
-        t+= x+'file1.js'+y;
-        t+= x+'file2.js'+y;
-        t+= x+'fileA.txt'+y;
-        document.write(t);
-
-- Version without mootools:
-    - test cross-browser
+- Better status and error feedback.
 
 
 - Add WSS/https/SSL support to page and wsproxy.py
 - Add WSS/https/SSL support to page and wsproxy.py
 
 

+ 11 - 6
include/base64.js

@@ -90,25 +90,30 @@ toBinaryTable : [
     41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
     41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
 ],
 ],
 
 
-decode: function (data) {
+decode: function (data, offset) {
+    offset = typeof(offset) != 'undefined' ? offset : 0;
     var binTable = Base64.toBinaryTable;
     var binTable = Base64.toBinaryTable;
     var pad = Base64.base64Pad;
     var pad = Base64.base64Pad;
     var leftbits = 0; // number of bits decoded, but yet to be appended
     var leftbits = 0; // number of bits decoded, but yet to be appended
     var leftdata = 0; // bits decoded, but yet to be appended
     var leftdata = 0; // bits decoded, but yet to be appended
 
 
     /* Every four characters is 3 resulting numbers */
     /* Every four characters is 3 resulting numbers */
-    var data_length = data.indexOf('=');
-    if (data_length == -1) data_length = data.length;
-    var result_length = (data_length >> 2) * 3 + (data.length % 4 - 1);
+    var data_length = data.indexOf('=') - offset;
+    if (data_length < 0) data_length = data.length - offset;
+
+    var result_length = (data_length >> 2) * 3 + ((data.length - offset) % 4 - 1);
     var result = new Array(result_length);
     var result = new Array(result_length);
 
 
     // Convert one by one.
     // Convert one by one.
     var idx = 0;
     var idx = 0;
-    for (var i = 0; i < data.length; i++) {
+    for (var i = offset; i < data.length; i++) {
         var c = binTable[data[i].charCodeAt(0) & 0x7f];
         var c = binTable[data[i].charCodeAt(0) & 0x7f];
         var padding = (data[i] == pad);
         var padding = (data[i] == pad);
         // Skip illegal characters and whitespace
         // Skip illegal characters and whitespace
-        if (c == -1) continue;
+        if (c == -1) {
+            console.log("Illegal character '" + data[i].charCodeAt(0) + "'");
+            continue;
+        }
         
         
         // Collect data into leftdata, update bitcount
         // Collect data into leftdata, update bitcount
         leftdata = (leftdata << 6) | c;
         leftdata = (leftdata << 6) | c;

+ 19 - 1
vnc.html

@@ -2,7 +2,7 @@
 
 
     <head><title>VNC Client</title></head>
     <head><title>VNC Client</title></head>
 
 
-    <body onload="draw();">
+    <body>
 
 
         Host: <input id='host' style='width:100'>&nbsp;
         Host: <input id='host' style='width:100'>&nbsp;
         Port: <input id='port' style='width:50'>&nbsp;
         Port: <input id='port' style='width:50'>&nbsp;
@@ -35,8 +35,26 @@
     <script src="vnc.js"></script>
     <script src="vnc.js"></script>
 
 
     <script>
     <script>
+        var native_ws = true;
+
+        /* If no builtin websockets then load web_socket.js */
+        if (! window.WebSocket) {
+            var extra = "<script src='include/web-socket-js/swfobject.js'><\/script>";
+            extra += "<script src='include/web-socket-js/FABridge.js'><\/script>";
+            extra += "<script src='include/web-socket-js/web_socket.js'><\/script>";
+            document.write(extra);
+            native_ws = false;
+        }
+
         window.onload = function() {
         window.onload = function() {
             console.log("onload");
             console.log("onload");
+            if (native_ws) {
+                console.log("Using native WebSockets");
+            } else {
+                console.log("Using web-socket-js flash bridge");
+                WebSocket.__swfLocation = "web-socket-js/WebSocketMain.swf";
+                RFB.force_copy = true;
+            }
             var url = document.location.href;
             var url = document.location.href;
             $('host').value = (url.match(/host=([^&#]*)/) || ['',''])[1];
             $('host').value = (url.match(/host=([^&#]*)/) || ['',''])[1];
             $('port').value = (url.match(/port=([^&#]*)/) || ['',''])[1];
             $('port').value = (url.match(/port=([^&#]*)/) || ['',''])[1];

+ 213 - 117
vnc.js

@@ -74,10 +74,16 @@ Mouse = {
  * RFB namespace
  * RFB namespace
  */
  */
 
 
+RQ         = [];  // Receive Queue
+RQ_reorder = [];  // Receive Queue re-order list
+RQ_seq_num = 0;   // Expected sequence number
+SQ         = "";  // Send Queue
+
 RFB = {
 RFB = {
 
 
 ws        : null,  // Web Socket object
 ws        : null,  // Web Socket object
-d         : [],    // Received data accumulator
+sendID    : null,
+force_copy : false,
 
 
 version   : "RFB 003.003\n",
 version   : "RFB 003.003\n",
 state     : 'ProtocolVersion',
 state     : 'ProtocolVersion',
@@ -112,13 +118,13 @@ init_msg: function () {
     switch (RFB.state) {
     switch (RFB.state) {
 
 
     case 'ProtocolVersion' :
     case 'ProtocolVersion' :
-        if (RFB.d.length != 12) {
+        if (RQ.length != 12) {
             console.log("Invalid protocol version from server");
             console.log("Invalid protocol version from server");
             //RFB.state = 'reset';
             //RFB.state = 'reset';
             RFB.state = 'failed';
             RFB.state = 'failed';
             return;
             return;
         }
         }
-        var server_version = RFB.d.shiftStr(12);
+        var server_version = RQ.shiftStr(12);
         console.log("Server  ProtocolVersion: " + server_version.substr(0,11));
         console.log("Server  ProtocolVersion: " + server_version.substr(0,11));
         console.log("Sending ProtocolVersion: " + RFB.version.substr(0,11));
         console.log("Sending ProtocolVersion: " + RFB.version.substr(0,11));
         RFB.send_string(RFB.version);
         RFB.send_string(RFB.version);
@@ -126,17 +132,17 @@ init_msg: function () {
         break;
         break;
 
 
     case 'Authentication' :
     case 'Authentication' :
-        if (RFB.d.length < 4) {
+        if (RQ.length < 4) {
             console.log("Invalid auth frame");
             console.log("Invalid auth frame");
             RFB.state = 'reset';
             RFB.state = 'reset';
             return;
             return;
         }
         }
-        var scheme = RFB.d.shift32();
+        var scheme = RQ.shift32();
         console.log("Auth scheme: " + scheme);
         console.log("Auth scheme: " + scheme);
         switch (scheme) {
         switch (scheme) {
             case 0:  // connection failed
             case 0:  // connection failed
-                var strlen = RFB.d.shift32();
-                var reason = RFB.d.shiftStr(strlen);
+                var strlen = RQ.shift32();
+                var reason = RQ.shiftStr(strlen);
                 console.log("auth failed: " + reason);
                 console.log("auth failed: " + reason);
                 RFB.state = "failed";
                 RFB.state = "failed";
                 return;
                 return;
@@ -145,7 +151,7 @@ init_msg: function () {
                 RFB.state = "ServerInitialisation";
                 RFB.state = "ServerInitialisation";
                 break;
                 break;
             case 2:  // VNC authentication
             case 2:  // VNC authentication
-                var challenge = RFB.d.shiftBytes(16);
+                var challenge = RQ.shiftBytes(16);
                 console.log("Password: " + RFB.password);
                 console.log("Password: " + RFB.password);
                 console.log("Challenge: " + challenge + "(" + challenge.length + ")");
                 console.log("Challenge: " + challenge + "(" + challenge.length + ")");
                 passwd = RFB.passwdTwiddle(RFB.password);
                 passwd = RFB.passwdTwiddle(RFB.password);
@@ -164,12 +170,12 @@ init_msg: function () {
         break;
         break;
 
 
     case 'SecurityResult' :
     case 'SecurityResult' :
-        if (RFB.d.length != 4) {
+        if (RQ.length != 4) {
             console.log("Invalid server auth response");
             console.log("Invalid server auth response");
             RFB.state = 'reset';
             RFB.state = 'reset';
             return;
             return;
         }
         }
-        var resp = RFB.d.shift32();
+        var resp = RQ.shift32();
         switch (resp) {
         switch (resp) {
             case 0:  // OK
             case 0:  // OK
                 console.log("Authentication OK");
                 console.log("Authentication OK");
@@ -188,24 +194,24 @@ init_msg: function () {
         break;
         break;
 
 
     case 'ServerInitialisation' :
     case 'ServerInitialisation' :
-        if (RFB.d.length < 24) {
+        if (RQ.length < 24) {
             console.log("Invalid server initialisation");
             console.log("Invalid server initialisation");
             RFB.state = 'reset';
             RFB.state = 'reset';
             return;
             return;
         }
         }
 
 
         /* Screen size */
         /* Screen size */
-        //console.log("RFB.d: " + RFB.d);
-        RFB.fb_width  = RFB.d.shift16();
-        RFB.fb_height = RFB.d.shift16();
+        //console.log("RQ: " + RQ);
+        RFB.fb_width  = RQ.shift16();
+        RFB.fb_height = RQ.shift16();
 
 
         console.log("Screen size: " + RFB.fb_width + "x" + RFB.fb_height);
         console.log("Screen size: " + RFB.fb_width + "x" + RFB.fb_height);
 
 
         /* PIXEL_FORMAT */
         /* PIXEL_FORMAT */
-        var bpp            = RFB.d.shift8();
-        var depth          = RFB.d.shift8();
-        var big_endian     = RFB.d.shift8();
-        var true_color     = RFB.d.shift8();
+        var bpp            = RQ.shift8();
+        var depth          = RQ.shift8();
+        var big_endian     = RQ.shift8();
+        var true_color     = RQ.shift8();
 
 
         console.log("bpp: " + bpp);
         console.log("bpp: " + bpp);
         console.log("depth: " + depth);
         console.log("depth: " + depth);
@@ -213,9 +219,9 @@ init_msg: function () {
         console.log("true_color: " + true_color);
         console.log("true_color: " + true_color);
 
 
         /* Connection name/title */
         /* Connection name/title */
-        RFB.d.shiftStr(12);
-        var name_length   = RFB.d.shift32();
-        RFB.fb_name = RFB.d.shiftStr(name_length);
+        RQ.shiftStr(12);
+        var name_length   = RQ.shift32();
+        RFB.fb_name = RQ.shiftStr(name_length);
 
 
         console.log("Name: " + RFB.fb_name);
         console.log("Name: " + RFB.fb_name);
         $('status').innerHTML = "Connected to: " + RFB.fb_name;
         $('status').innerHTML = "Connected to: " + RFB.fb_name;
@@ -243,38 +249,40 @@ init_msg: function () {
 /* Normal RFB/VNC server messages */
 /* Normal RFB/VNC server messages */
 normal_msg: function () {
 normal_msg: function () {
     //console.log(">> normal_msg");
     //console.log(">> normal_msg");
+    var ret = true;
     if (FBU.rects > 0) {
     if (FBU.rects > 0) {
         var msg_type = 0;
         var msg_type = 0;
     } else if (RFB.cuttext != 'none') {
     } else if (RFB.cuttext != 'none') {
         var msg_type = 3;
         var msg_type = 3;
     } else {
     } else {
-        var msg_type = RFB.d.shift8();
+        var msg_type = RQ.shift8();
     }
     }
     switch (msg_type) {
     switch (msg_type) {
     case 0:  // FramebufferUpdate
     case 0:  // FramebufferUpdate
         if (FBU.rects == 0) {
         if (FBU.rects == 0) {
-            if (RFB.d.length < 3) {
+            if (RQ.length < 3) {
+                RQ.unshift(msg_type);
                 console.log("   waiting for FBU header bytes");
                 console.log("   waiting for FBU header bytes");
-                return;
+                return false;
             }
             }
-            RFB.d.shift8();
-            FBU.rects = RFB.d.shift16();
+            RQ.shift8();
+            FBU.rects = RQ.shift16();
             //console.log("FramebufferUpdate, rects:" + FBU.rects);
             //console.log("FramebufferUpdate, rects:" + FBU.rects);
             FBU.bytes = 0;
             FBU.bytes = 0;
         }
         }
 
 
-        while ((FBU.rects > 0) && (RFB.d.length >= FBU.bytes)) {
+        while ((FBU.rects > 0) && (RQ.length >= FBU.bytes)) {
             if (FBU.bytes == 0) {
             if (FBU.bytes == 0) {
-                if (RFB.d.length < 12) {
+                if (RQ.length < 12) {
                     console.log("   waiting for rect header bytes");
                     console.log("   waiting for rect header bytes");
-                    return;
+                    return false;
                 }
                 }
                 /* New FramebufferUpdate */
                 /* New FramebufferUpdate */
-                FBU.x      = RFB.d.shift16();
-                FBU.y      = RFB.d.shift16();
-                FBU.width  = RFB.d.shift16();
-                FBU.height = RFB.d.shift16();
-                FBU.encoding = parseInt(RFB.d.shift32(), 10);
+                FBU.x      = RQ.shift16();
+                FBU.y      = RQ.shift16();
+                FBU.width  = RQ.shift16();
+                FBU.height = RQ.shift16();
+                FBU.encoding = parseInt(RQ.shift32(), 10);
 
 
                 // Debug:
                 // Debug:
                 /*
                 /*
@@ -287,64 +295,66 @@ normal_msg: function () {
                     default:
                     default:
                         console.log("Unsupported encoding " + FBU.encoding);
                         console.log("Unsupported encoding " + FBU.encoding);
                         RFB.state = "failed";
                         RFB.state = "failed";
-                        return;
+                        return false;
                 }
                 }
-                msg += ", RFB.d.length: " + RFB.d.length
+                msg += ", RQ.length: " + RQ.length
                 console.log(msg);
                 console.log(msg);
                 */
                 */
             }
             }
 
 
-            //console.log("> RFB.d.length: " + RFB.d.length + ", arr[0..30]: " + RFB.d.slice(0,30));
+            //console.log("> RQ.length: " + RQ.length + ", arr[0..30]: " + RQ.slice(0,30));
             switch (FBU.encoding) {
             switch (FBU.encoding) {
-                case 0: RFB.display_raw();       break; // Raw
-                case 1: RFB.display_copy_rect(); break; // Copy-Rect
-                case 2: RFB.display_rre();       break; // RRE
-                case 5: RFB.display_hextile();   break; // hextile
+                case 0: ret = RFB.display_raw();       break; // Raw
+                case 1: ret = RFB.display_copy_rect(); break; // Copy-Rect
+                case 2: ret = RFB.display_rre();       break; // RRE
+                case 5: ret = RFB.display_hextile();   break; // hextile
             }
             }
-            //console.log("< RFB.d.length: " + RFB.d.length + ", FBU.bytes: " + FBU.bytes);
-            if (RFB.state != "normal") return;
+            //console.log("< RQ.length: " + RQ.length + ", FBU.bytes: " + FBU.bytes);
+            if (RFB.state != "normal") return true;
         }
         }
 
 
         //console.log("Finished frame buffer update");
         //console.log("Finished frame buffer update");
         break;
         break;
     case 1:  // SetColourMapEntries
     case 1:  // SetColourMapEntries
         console.log("SetColourMapEntries (unsupported)");
         console.log("SetColourMapEntries (unsupported)");
-        RFB.d.shift8();  // Padding
-        RFB.d.shift16(); // First colour
-        var num_colours = RFB.d.shift16();
-        RFB.d.shiftBytes(num_colours * 6);
+        RQ.shift8();  // Padding
+        RQ.shift16(); // First colour
+        var num_colours = RQ.shift16();
+        RQ.shiftBytes(num_colours * 6);
         break;
         break;
     case 2:  // Bell
     case 2:  // Bell
         console.log("Bell (unsupported)");
         console.log("Bell (unsupported)");
         break;
         break;
     case 3:  // ServerCutText
     case 3:  // ServerCutText
         console.log("ServerCutText");
         console.log("ServerCutText");
-        console.log("RFB.d:" + RFB.d.slice(0,20));
+        console.log("RQ:" + RQ.slice(0,20));
         if (RFB.cuttext == 'none') {
         if (RFB.cuttext == 'none') {
             RFB.cuttext = 'header';
             RFB.cuttext = 'header';
         }
         }
         if (RFB.cuttext == 'header') {
         if (RFB.cuttext == 'header') {
-            if (RFB.d.length < 7) {
+            if (RQ.length < 7) {
                 console.log("waiting for ServerCutText header");
                 console.log("waiting for ServerCutText header");
-                break;
+                return false;
             }
             }
-            RFB.d.shiftBytes(3);  // Padding
-            RFB.ct_length = RFB.d.shift32();
+            RQ.shiftBytes(3);  // Padding
+            RFB.ct_length = RQ.shift32();
         }
         }
         RFB.cuttext = 'bytes';
         RFB.cuttext = 'bytes';
-        if (RFB.d.length < RFB.ct_length) {
+        if (RQ.length < RFB.ct_length) {
             console.log("waiting for ServerCutText bytes");
             console.log("waiting for ServerCutText bytes");
-            break;
+            return false;
         }
         }
-        RFB.clipboardCopyTo(RFB.d.shiftStr(RFB.ct_length));
+        RFB.clipboardCopyTo(RQ.shiftStr(RFB.ct_length));
         RFB.cuttext = 'none';
         RFB.cuttext = 'none';
         break;
         break;
     default:
     default:
-        console.log("Unknown server message type: " + msg_type);
+        console.error("Illegal server message type: " + msg_type);
+        console.log("RQ.slice(0,30):" + RQ.slice(0,30));
         RFB.state = "failed";
         RFB.state = "failed";
         break;
         break;
     }
     }
     //console.log("<< normal_msg");
     //console.log("<< normal_msg");
+    return ret;
 },
 },
 
 
 
 
@@ -358,15 +368,15 @@ display_raw: function () {
         FBU.lines = FBU.height;
         FBU.lines = FBU.height;
     }
     }
     FBU.bytes = FBU.width * RFB.fb_Bpp; // At least a line
     FBU.bytes = FBU.width * RFB.fb_Bpp; // At least a line
-    if (RFB.d.length < FBU.bytes) {
-        //console.log("   waiting for " + (FBU.bytes - RFB.d.length) + " RAW bytes");
+    if (RQ.length < FBU.bytes) {
+        //console.log("   waiting for " + (FBU.bytes - RQ.length) + " RAW bytes");
         return;
         return;
     }
     }
     var cur_y = FBU.y + (FBU.height - FBU.lines);
     var cur_y = FBU.y + (FBU.height - FBU.lines);
-    var cur_height = Math.min(FBU.lines, Math.floor(RFB.d.length/(FBU.width * RFB.fb_Bpp)));
+    var cur_height = Math.min(FBU.lines, Math.floor(RQ.length/(FBU.width * RFB.fb_Bpp)));
     //console.log("cur_y:" + cur_y + ", cur_height:" + cur_height);
     //console.log("cur_y:" + cur_y + ", cur_height:" + cur_height);
-    Canvas.rgbxImage(FBU.x, cur_y, FBU.width, cur_height, RFB.d);
-    RFB.d.shiftBytes(FBU.width * cur_height * RFB.fb_Bpp);
+    Canvas.rgbxImage(FBU.x, cur_y, FBU.width, cur_height, RQ);
+    RQ.shiftBytes(FBU.width * cur_height * RFB.fb_Bpp);
     FBU.lines -= cur_height;
     FBU.lines -= cur_height;
 
 
     if (FBU.lines > 0) {
     if (FBU.lines > 0) {
@@ -379,36 +389,36 @@ display_raw: function () {
 
 
 display_copy_rect: function () {
 display_copy_rect: function () {
     //console.log(">> display_copy_rect");
     //console.log(">> display_copy_rect");
-    if (RFB.d.length < 4) {
-        //console.log("   waiting for " + (FBU.bytes - RFB.d.length) + " COPY-RECT bytes");
+    if (RQ.length < 4) {
+        //console.log("   waiting for " + (FBU.bytes - RQ.length) + " COPY-RECT bytes");
         return;
         return;
     }
     }
-    var old_x = RFB.d.shift16();
-    var old_y = RFB.d.shift16();
+    var old_x = RQ.shift16();
+    var old_y = RQ.shift16();
     Canvas.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, FBU.height);
     Canvas.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, FBU.height);
     FBU.rects --;
     FBU.rects --;
     FBU.bytes = 0;
     FBU.bytes = 0;
 },
 },
 
 
 display_rre: function () {
 display_rre: function () {
-    //console.log(">> display_rre (" + RFB.d.length + " bytes)");
+    //console.log(">> display_rre (" + RQ.length + " bytes)");
     if (FBU.subrects == 0) {
     if (FBU.subrects == 0) {
         ;
         ;
-        if (RFB.d.length < 4 + RFB.fb_Bpp) {
-            //console.log("   waiting for " + (4 + RFB.fb_Bpp - RFB.d.length) + " RRE bytes");
+        if (RQ.length < 4 + RFB.fb_Bpp) {
+            //console.log("   waiting for " + (4 + RFB.fb_Bpp - RQ.length) + " RRE bytes");
             return;
             return;
         }
         }
-        FBU.subrects = RFB.d.shift32();
+        FBU.subrects = RQ.shift32();
         //console.log(">> display_rre " + "(" + FBU.subrects + " subrects)");
         //console.log(">> display_rre " + "(" + FBU.subrects + " subrects)");
-        var color = RFB.d.shiftBytes(RFB.fb_Bpp); // Background
+        var color = RQ.shiftBytes(RFB.fb_Bpp); // Background
         Canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color);
         Canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color);
     }
     }
-    while ((FBU.subrects > 0) && (RFB.d.length >= (RFB.fb_Bpp + 8))) {
-        var color = RFB.d.shiftBytes(RFB.fb_Bpp);
-        var x = RFB.d.shift16();
-        var y = RFB.d.shift16();
-        var width = RFB.d.shift16();
-        var height = RFB.d.shift16();
+    while ((FBU.subrects > 0) && (RQ.length >= (RFB.fb_Bpp + 8))) {
+        var color = RQ.shiftBytes(RFB.fb_Bpp);
+        var x = RQ.shift16();
+        var y = RQ.shift16();
+        var width = RQ.shift16();
+        var height = RQ.shift16();
         Canvas.fillRect(FBU.x + x, FBU.y + y, width, height, color);
         Canvas.fillRect(FBU.x + x, FBU.y + y, width, height, color);
         FBU.subrects --;
         FBU.subrects --;
     }
     }
@@ -425,7 +435,7 @@ display_rre: function () {
 },
 },
 
 
 display_hextile: function() {
 display_hextile: function() {
-    //console.log(">> display_hextile, tiles: " + FBU.tiles + ", arr.length: " + RFB.d.length + ", bytes: " + FBU.bytes);
+    //console.log(">> display_hextile, tiles: " + FBU.tiles + ", arr.length: " + RQ.length + ", bytes: " + FBU.bytes);
     var subencoding, subrects, cur_tile, tile_x, x, w, tile_y, y, h;
     var subencoding, subrects, cur_tile, tile_x, x, w, tile_y, y, h;
 
 
     if (FBU.tiles == 0) {
     if (FBU.tiles == 0) {
@@ -435,16 +445,17 @@ display_hextile: function() {
         FBU.tiles = FBU.total_tiles;
         FBU.tiles = FBU.total_tiles;
     }
     }
 
 
-    /* FBU.bytes comes in as 1, RFB.d.length at least 1 */
+    /* FBU.bytes comes in as 1, RQ.length at least 1 */
     while (FBU.tiles > 0) {
     while (FBU.tiles > 0) {
         FBU.bytes = 1;
         FBU.bytes = 1;
-        if (RFB.d.length < FBU.bytes) {
+        if (RQ.length < FBU.bytes) {
             console.log("   waiting for HEXTILE subencoding byte");
             console.log("   waiting for HEXTILE subencoding byte");
             return;
             return;
         }
         }
-        subencoding = RFB.d[0];  // Peek
+        subencoding = RQ[0];  // Peek
         if (subencoding > 30) { // Raw
         if (subencoding > 30) { // Raw
-            console.log("Illegal subencoding " + subencoding);
+            console.error("Illegal subencoding " + subencoding);
+            console.log("RQ.slice(0,30):" + RQ.slice(0,30));
             RFB.state = "failed";
             RFB.state = "failed";
             return;
             return;
         }
         }
@@ -470,12 +481,12 @@ display_hextile: function() {
             }
             }
             if (subencoding & 0x08) { // AnySubrects
             if (subencoding & 0x08) { // AnySubrects
                 FBU.bytes++;   // Since we aren't shifting it off
                 FBU.bytes++;   // Since we aren't shifting it off
-                if (RFB.d.length < FBU.bytes) {
+                if (RQ.length < FBU.bytes) {
                     /* Wait for subrects byte */
                     /* Wait for subrects byte */
                     console.log("   waiting for hextile subrects header byte");
                     console.log("   waiting for hextile subrects header byte");
                     return;
                     return;
                 }
                 }
-                subrects = RFB.d[FBU.bytes-1]; // Peek
+                subrects = RQ[FBU.bytes-1]; // Peek
                 if (subencoding & 0x10) { // SubrectsColoured
                 if (subencoding & 0x10) { // SubrectsColoured
                     FBU.bytes += subrects * (RFB.fb_Bpp + 2);
                     FBU.bytes += subrects * (RFB.fb_Bpp + 2);
                 } else {
                 } else {
@@ -484,15 +495,15 @@ display_hextile: function() {
             }
             }
         }
         }
 
 
-        //console.log("   tile:" + cur_tile + "/" + (FBU.total_tiles - 1) + ", subencoding:" + subencoding + "(last: " + FBU.lastsubencoding + "), subrects:" + subrects + ", tile:" + tile_x + "," + tile_y + " [" + x + "," + y + "]@" + w + "x" + h + ", arr.length:" + RFB.d.length + ", bytes:" + FBU.bytes);
-        //console.log("   arr[0..30]: " + RFB.d.slice(0,30));
-        if (RFB.d.length < FBU.bytes) {
-            //console.log("   waiting for " + (FBU.bytes - RFB.d.length) + " hextile bytes");
+        //console.log("   tile:" + cur_tile + "/" + (FBU.total_tiles - 1) + ", subencoding:" + subencoding + "(last: " + FBU.lastsubencoding + "), subrects:" + subrects + ", tile:" + tile_x + "," + tile_y + " [" + x + "," + y + "]@" + w + "x" + h + ", d.length:" + RQ.length + ", bytes:" + FBU.bytes + " last:" + RQ.slice(FBU.bytes-10, FBU.bytes) + " next:" + RQ.slice(FBU.bytes-1, FBU.bytes+10));
+        //console.log("   arr[0..30]: " + RQ.slice(0,30));
+        if (RQ.length < FBU.bytes) {
+            //console.log("   waiting for " + (FBU.bytes - RQ.length) + " hextile bytes");
             return;
             return;
         }
         }
 
 
         /* We know the encoding and have a whole tile */
         /* We know the encoding and have a whole tile */
-        FBU.subencoding = RFB.d.shift8();
+        FBU.subencoding = RQ.shift8();
         FBU.bytes--;
         FBU.bytes--;
         if (FBU.subencoding == 0) {
         if (FBU.subencoding == 0) {
             if (FBU.lastsubencoding & 0x01) {
             if (FBU.lastsubencoding & 0x01) {
@@ -502,37 +513,37 @@ display_hextile: function() {
             }
             }
             Canvas.fillRect(x, y, w, h, FBU.background);
             Canvas.fillRect(x, y, w, h, FBU.background);
         } else if (FBU.subencoding & 0x01) { // Raw
         } else if (FBU.subencoding & 0x01) { // Raw
-            Canvas.rgbxImage(x, y, w, h, RFB.d);
+            Canvas.rgbxImage(x, y, w, h, RQ);
         } else {
         } else {
             var idx = 0;
             var idx = 0;
             if (FBU.subencoding & 0x02) { // Background
             if (FBU.subencoding & 0x02) { // Background
-                FBU.background = RFB.d.slice(idx, idx + RFB.fb_Bpp);
+                FBU.background = RQ.slice(idx, idx + RFB.fb_Bpp);
                 idx += RFB.fb_Bpp;
                 idx += RFB.fb_Bpp;
                 //console.log("   background: " + FBU.background);
                 //console.log("   background: " + FBU.background);
             }
             }
             if (FBU.subencoding & 0x04) { // Foreground
             if (FBU.subencoding & 0x04) { // Foreground
-                FBU.foreground = RFB.d.slice(idx, idx + RFB.fb_Bpp);
+                FBU.foreground = RQ.slice(idx, idx + RFB.fb_Bpp);
                 idx += RFB.fb_Bpp;
                 idx += RFB.fb_Bpp;
                 //console.log("   foreground: " + FBU.foreground);
                 //console.log("   foreground: " + FBU.foreground);
             }
             }
             Canvas.fillRect(x, y, w, h, FBU.background);
             Canvas.fillRect(x, y, w, h, FBU.background);
             if (FBU.subencoding & 0x08) { // AnySubrects
             if (FBU.subencoding & 0x08) { // AnySubrects
-                subrects = RFB.d[idx];
+                subrects = RQ[idx];
                 idx++;
                 idx++;
                 var color, xy, sx, sy, wh, sw, sh;
                 var color, xy, sx, sy, wh, sw, sh;
                 for (var i = 0; i < subrects; i ++) {
                 for (var i = 0; i < subrects; i ++) {
                     if (FBU.subencoding & 0x10) { // SubrectsColoured
                     if (FBU.subencoding & 0x10) { // SubrectsColoured
-                        color = RFB.d.slice(idx, idx + RFB.fb_Bpp);
+                        color = RQ.slice(idx, idx + RFB.fb_Bpp);
                         idx += RFB.fb_Bpp;
                         idx += RFB.fb_Bpp;
                     } else {
                     } else {
                         color = FBU.foreground;
                         color = FBU.foreground;
                     }
                     }
-                    xy = RFB.d[idx];
+                    xy = RQ[idx];
                     idx++;
                     idx++;
                     sx = x + (xy >> 4);
                     sx = x + (xy >> 4);
                     sy = y + (xy & 0x0f);
                     sy = y + (xy & 0x0f);
 
 
-                    wh = RFB.d[idx];
+                    wh = RQ[idx];
                     idx++;
                     idx++;
                     sw = (wh >> 4)   + 1;
                     sw = (wh >> 4)   + 1;
                     sh = (wh & 0x0f) + 1;
                     sh = (wh & 0x0f) + 1;
@@ -541,7 +552,7 @@ display_hextile: function() {
                 }
                 }
             }
             }
         }
         }
-        RFB.d.shiftBytes(FBU.bytes);
+        RQ.shiftBytes(FBU.bytes);
         FBU.lastsubencoding = FBU.subencoding;
         FBU.lastsubencoding = FBU.subencoding;
         FBU.bytes = 0;
         FBU.bytes = 0;
         FBU.tiles --;
         FBU.tiles --;
@@ -551,7 +562,7 @@ display_hextile: function() {
         FBU.rects --;
         FBU.rects --;
     }
     }
 
 
-    //console.log("<< display_hextile, rects:" + FBU.rects, " d:" + RFB.d.slice(0,40));
+    //console.log("<< display_hextile, rects:" + FBU.rects, " d:" + RQ.slice(0,40));
 },
 },
 
 
 
 
@@ -674,7 +685,13 @@ send_string: function (str) {
 send_array: function (arr) {
 send_array: function (arr) {
     //console.log(">> send_array: " + arr);
     //console.log(">> send_array: " + arr);
     //console.log(">> send_array: " + Base64.encode(arr));
     //console.log(">> send_array: " + Base64.encode(arr));
-    RFB.ws.send(Base64.encode(arr));
+    SQ = SQ + Base64.encode(arr);
+    if (RFB.ws.bufferedAmount == 0) {
+        RFB.ws.send(SQ);
+        SQ = ""
+    } else {
+        console.log("Delaying send");
+    }
 },
 },
 
 
 /* Mirror bits of each character and return as array */
 /* Mirror bits of each character and return as array */
@@ -698,7 +715,12 @@ passwdTwiddle: function (passwd) {
 flushClient: function () {
 flushClient: function () {
     var arr = [];
     var arr = [];
     if (Mouse.arr.length > 0) {
     if (Mouse.arr.length > 0) {
-        RFB.send_array(Mouse.arr.concat(RFB.fbUpdateRequest(1)));
+        //RFB.send_array(Mouse.arr.concat(RFB.fbUpdateRequest(1)));
+        RFB.send_array(Mouse.arr)
+        setTimeout(function() {
+                RFB.send_array(RFB.fbUpdateRequest(1));
+            }, 50);
+
         Mouse.arr = [];
         Mouse.arr = [];
         return true;
         return true;
     } else {
     } else {
@@ -715,8 +737,8 @@ checkEvents: function () {
                 RFB.send_array(RFB.fbUpdateRequest(1));
                 RFB.send_array(RFB.fbUpdateRequest(1));
             }
             }
         }
         }
-        RFB.checkEvents.delay(RFB.check_rate);
     }
     }
+    RFB.checkEvents.delay(RFB.check_rate);
 },
 },
 
 
 _keyX: function (e, down) {
 _keyX: function (e, down) {
@@ -797,36 +819,101 @@ clipboardClear: function () {
  */
  */
 
 
 init_ws: function () {
 init_ws: function () {
+
     console.log(">> init_ws");
     console.log(">> init_ws");
     var uri = "ws://" + RFB.host + ":" + RFB.port;
     var uri = "ws://" + RFB.host + ":" + RFB.port;
     console.log("connecting to " + uri);
     console.log("connecting to " + uri);
     RFB.ws = new WebSocket(uri);
     RFB.ws = new WebSocket(uri);
     RFB.ws.onmessage = function(e) {
     RFB.ws.onmessage = function(e) {
         //console.log(">> WebSockets.onmessage");
         //console.log(">> WebSockets.onmessage");
-        RFB.d = RFB.d.concat(Base64.decode(e.data));
-        if (RFB.state == 'closed') {
-            console.log("onmessage while close");
-        } else if (RFB.state == 'reset') {
+
+        //console.log(e.data);
+        var offset = e.data.indexOf(":") + 1;
+        var seq_num = parseInt(e.data.substr(0, offset-1));
+        //console.log("RQ_seq_num:" + RQ_seq_num + ", seq_num:" + seq_num);
+        if (RQ_seq_num == seq_num) {
+            RQ = RQ.concat(Base64.decode(e.data, offset));
+            RQ_seq_num++;
+        } else {
+            console.warn("sequence number mismatch RQ_seq_num:" + RQ_seq_num + ", seq_num:" + seq_num);
+            if (RQ_reorder.length > 20) {
+                console.log("Re-order queue too long");
+                RFB.state = 'failed';
+            } else {
+                RQ_reorder = RQ_reorder.concat(e.data.substr(0));
+                var i = 0;
+                while (i < RQ_reorder.length) {
+                    var offset = RQ_reorder[i].indexOf(":") + 1;
+                    var seq_num = parseInt(RQ_reorder[i].substr(0, offset-1));
+                    console.log("Searching reorder list item " + i + ", seq_num " + seq_num);
+                    if (seq_num == RQ_seq_num) {
+                        /* Remove it from reorder queue, decode it and
+                         * add it to the receive queue */
+                        console.log("Found re-ordered packet seq_num " + seq_num);
+                        RQ = RQ.concat(Base64.decode(RQ_reorder.splice(i, 1)[0], offset));
+                        RQ_seq_num++;
+                        i = 0;  // Start search again for next one
+                    } else {
+                        i++;
+                    }
+                }
+                
+            }
+        }
+
+        switch (RFB.state) {
+        case 'closed':
+            console.log("onmessage while closed");
+            break;
+        case 'reset':
             /* close and reset connection */
             /* close and reset connection */
             RFB.disconnect();
             RFB.disconnect();
             RFB.init_ws();
             RFB.init_ws();
-        } else if (RFB.state == 'failed') {
+            break;
+        case 'failed':
             console.log("Giving up!");
             console.log("Giving up!");
             RFB.disconnect();
             RFB.disconnect();
-        } else if (RFB.state != 'normal') {
-            RFB.init_msg();
-        } else {
+            break;
+        case 'normal':
             RFB.normal_msg();
             RFB.normal_msg();
+            /*
+            while (RQ.length > 0) {
+                if (RFB.normal_msg() && RFB.state == 'normal') {
+                    console.log("More to process");
+                } else {
+                    break;
+                }
+            }
+            */
+            break;
+        default:
+            RFB.init_msg();
+            break;
         }
         }
         //console.log("<< WebSockets.onmessage");
         //console.log("<< WebSockets.onmessage");
     };
     };
     RFB.ws.onopen = function(e) {
     RFB.ws.onopen = function(e) {
         console.log(">> WebSockets.onopen");
         console.log(">> WebSockets.onopen");
         RFB.state = "ProtocolVersion";
         RFB.state = "ProtocolVersion";
+        RFB.sendID = setInterval(function() {
+                /*
+                 * Send updates either at a rate of one update every 50ms,
+                 * or whatever slower rate the network can handle
+                 */
+                if (RFB.ws.bufferedAmount == 0) {
+                    if (SQ) {
+                        RFB.ws.send(SQ);
+                        SQ = "";
+                    }
+                } else {
+                    console.log("Delaying send");
+                }
+            }, 50);
         console.log("<< WebSockets.onopen");
         console.log("<< WebSockets.onopen");
     };
     };
     RFB.ws.onclose = function(e) {
     RFB.ws.onclose = function(e) {
         console.log(">> WebSockets.onclose");
         console.log(">> WebSockets.onclose");
+        clearInterval(RFB.sendID);
         RFB.state = "closed";
         RFB.state = "closed";
         console.log("<< WebSockets.onclose");
         console.log("<< WebSockets.onclose");
     };
     };
@@ -839,6 +926,22 @@ init_ws: function () {
     console.log("<< init_ws");
     console.log("<< init_ws");
 },
 },
 
 
+init_vars: function () {
+    /* Reset state */
+    RFB.cuttext  = 'none';
+    RFB.ct_length = 0;
+    RQ           = [];
+    RQ_seq_num   = 0;
+    SQ           = "";
+    FBU.rects    = 0;
+    FBU.subrects = 0;  // RRE and HEXTILE
+    FBU.lines    = 0,  // RAW
+    FBU.tiles    = 0,  // HEXTILE
+    Mouse.buttonmask = 0;
+    Mouse.arr    = [];
+},
+
+
 connect: function () {
 connect: function () {
     console.log(">> connect");
     console.log(">> connect");
     RFB.host = $('host').value;
     RFB.host = $('host').value;
@@ -849,15 +952,7 @@ connect: function () {
         return;
         return;
     }
     }
 
 
-    /* Reset state */
-    RFB.cuttext  = 'none';
-    RFB.ct_length = 0;
-    FBU.rects    = 0;
-    FBU.subrects = 0;  // RRE and HEXTILE
-    FBU.lines    = 0,  // RAW
-    FBU.tiles    = 0,  // HEXTILE
-    Mouse.buttonmask = 0;
-    Mouse.arr    = [];
+    RFB.init_vars();
 
 
     if (RFB.ws) {
     if (RFB.ws) {
         RFB.ws.close();
         RFB.ws.close();
@@ -873,6 +968,7 @@ connect: function () {
 disconnect: function () {
 disconnect: function () {
     console.log(">> disconnect");
     console.log(">> disconnect");
     if (RFB.ws) {
     if (RFB.ws) {
+        RFB.state = "closed";
         RFB.ws.close();
         RFB.ws.close();
     }
     }
     if (Canvas.ctx) {
     if (Canvas.ctx) {

+ 63 - 24
wsproxy.py

@@ -5,6 +5,7 @@ from base64 import b64encode, b64decode
 from select import select
 from select import select
 
 
 buffer_size = 65536
 buffer_size = 65536
+send_seq = 0
 
 
 server_handshake = """HTTP/1.1 101 Web Socket Protocol Handshake\r
 server_handshake = """HTTP/1.1 101 Web Socket Protocol Handshake\r
 Upgrade: WebSocket\r
 Upgrade: WebSocket\r
@@ -17,6 +18,19 @@ WebSocket-Protocol: sample\r
 
 
 policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>"""
 policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>"""
 
 
+traffic_legend = """
+Traffic Legend:
+    }  - Client receive
+    }. - Client receive partial
+    {  - Target receive
+
+    >  - Target send
+    >. - Target send partial
+    <  - Client send
+    <. - Client send partial
+"""
+
+
 def handshake(client):
 def handshake(client):
     handshake = client.recv(1024)
     handshake = client.recv(1024)
     print "Handshake [%s]" % handshake
     print "Handshake [%s]" % handshake
@@ -38,12 +52,14 @@ def traffic(token="."):
 def decode(buf):
 def decode(buf):
     """ Parse out WebSocket packets. """
     """ Parse out WebSocket packets. """
     if buf.count('\xff') > 1:
     if buf.count('\xff') > 1:
-        return [d[1:] for d in buf.split('\xff')]
+        traffic(str(buf.count('\xff')))
+        return [b64decode(d[1:]) for d in buf.split('\xff')]
     else:
     else:
         return [b64decode(buf[1:-1])]
         return [b64decode(buf[1:-1])]
 
 
 def proxy(client, target):
 def proxy(client, target):
     """ Proxy WebSocket to normal socket. """
     """ Proxy WebSocket to normal socket. """
+    global send_seq
     cqueue = []
     cqueue = []
     cpartial = ""
     cpartial = ""
     tqueue = []
     tqueue = []
@@ -53,51 +69,72 @@ def proxy(client, target):
         ins, outs, excepts = select(socks, socks, socks, 1)
         ins, outs, excepts = select(socks, socks, socks, 1)
         if excepts: raise Exception("Socket exception")
         if excepts: raise Exception("Socket exception")
 
 
+        if tqueue and target in outs:
+            #print "Target send: %s" % repr(tqueue[0])
+            log.write("Target send: %s\n" % map(ord, tqueue[0]))
+            dat = tqueue.pop(0)
+            sent = target.send(dat)
+            if sent == len(dat):
+                traffic(">")
+            else:
+                tqueue.insert(0, dat[sent:])
+                traffic(">.")
+
+        if cqueue and client in outs:
+            dat = cqueue.pop(0)
+            sent = client.send(dat)
+            if sent == len(dat):
+                traffic("<")
+                log.write("Client send: %s\n" % repr(dat))
+            else:
+                cqueue.insert(0, dat[sent:])
+                traffic("<.")
+                log.write("Client send partial: %s\n" % repr(dat[0:send]))
+
+
+        if target in ins:
+            buf = target.recv(buffer_size)
+            if len(buf) == 0: raise Exception("Target closed")
+
+            #enc = b64encode(buf)
+            #chksum = sum([ord(c) for c in enc])
+            #cqueue.append("\x00^" + str(chksum) + "@" + enc + "$\xff")
+
+            cqueue.append("\x00%d:%s\xff" % (send_seq, b64encode(buf)))
+            send_seq += 1
+
+            log.write("Target recv (%d): %s\n" % (len(buf), map(ord, buf)))
+            traffic("{")
+
         if client in ins:
         if client in ins:
             buf = client.recv(buffer_size)
             buf = client.recv(buffer_size)
             if len(buf) == 0: raise Exception("Client closed")
             if len(buf) == 0: raise Exception("Client closed")
 
 
             if buf[-1] == "\xff":
             if buf[-1] == "\xff":
+                traffic("}")
+                log.write("Client recv (%d): %s\n" % (len(buf), repr(buf)))
                 if cpartial:
                 if cpartial:
                     tqueue.extend(decode(cpartial + buf))
                     tqueue.extend(decode(cpartial + buf))
                     cpartial = ""
                     cpartial = ""
                 else:
                 else:
                     tqueue.extend(decode(buf))
                     tqueue.extend(decode(buf))
-                traffic("}")
             else:
             else:
-                traffic(".}")
+                traffic("}.")
+                log.write("Client recv partial (%d): %s\n" % (len(buf), repr(buf)))
                 cpartial = cpartial + buf
                 cpartial = cpartial + buf
 
 
-            #print "Client recv: %s (%d)" % (repr(buf, len(buf))
-
-        if target in ins:
-            buf = target.recv(buffer_size)
-            if len(buf) == 0: raise Exception("Target closed")
-            cqueue.append("\x00" + b64encode(buf) + "\xff")
-            #print "Target recv: %s (%d)" % (repr(buf), len(buf))
-            traffic("{")
-
-        if cqueue and client in outs:
-            while cqueue:
-                #print "Client send: %s" % repr(cqueue[0])
-                client.send(cqueue.pop(0))
-                traffic("<")
-
-        if tqueue and target in outs:
-            while tqueue:
-                #print "Target send: %s" % repr(tqueue[0])
-                target.send(tqueue.pop(0))
-                traffic(">")
 
 
 def start_server(listen_port, target_host, target_port):
 def start_server(listen_port, target_host, target_port):
+    global send_seq
     lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     lsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
     lsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
     lsock.bind(('', listen_port))
     lsock.bind(('', listen_port))
     lsock.listen(100)
     lsock.listen(100)
+    print traffic_legend
     while True:
     while True:
         try:
         try:
             csock = tsock = None
             csock = tsock = None
-            print 'listening on port %s' % listen_port
+            print 'waiting for connection on port %s' % listen_port
             csock, address = lsock.accept()
             csock, address = lsock.accept()
             print 'Got client connection from %s' % address[0]
             print 'Got client connection from %s' % address[0]
             handshake(csock)
             handshake(csock)
@@ -105,6 +142,7 @@ def start_server(listen_port, target_host, target_port):
             tsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
             tsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
             tsock.connect((target_host, target_port))
             tsock.connect((target_host, target_port))
 
 
+            send_seq = 0
             proxy(csock, tsock)
             proxy(csock, tsock)
 
 
         except Exception:
         except Exception:
@@ -114,6 +152,7 @@ def start_server(listen_port, target_host, target_port):
             if tsock: tsock.close()
             if tsock: tsock.close()
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
+    log = open("ws.log", 'w')
     try:
     try:
         if len(sys.argv) != 4: raise
         if len(sys.argv) != 4: raise
         listen_port = int(sys.argv[1])
         listen_port = int(sys.argv[1])

+ 21 - 8
wstest.html

@@ -36,6 +36,7 @@
         var ws = null, update_ref = null, send_ref = null;
         var ws = null, update_ref = null, send_ref = null;
         var sent = 0, received = 0, errors = 0;
         var sent = 0, received = 0, errors = 0;
         var max_send = 2000;
         var max_send = 2000;
+        var recv_cnt = 0, send_cnt = 0;
 
 
         Array.prototype.pushStr = function (str) {
         Array.prototype.pushStr = function (str) {
             var n = str.length;
             var n = str.length;
@@ -70,18 +71,26 @@
             arr = decoded.map(function(num) {
             arr = decoded.map(function(num) {
                     return String.fromCharCode(num); 
                     return String.fromCharCode(num); 
                 } ).join('').split(':');
                 } ).join('').split(':');
-            length = arr[0];
-            chksum = arr[1];
-            nums = arr[2];
+            cnt    = arr[0];
+            length = arr[1];
+            chksum = arr[2];
+            nums = arr[3];
             //console.log("   length:" + length + " chksum:" + chksum + " nums:" + nums);
             //console.log("   length:" + length + " chksum:" + chksum + " nums:" + nums);
+            if (cnt != recv_cnt) {
+                console.error("Expected count " + recv_cnt + " but got " + cnt);
+                recv_cnt = parseInt(cnt,10) + 1;   // Back on track
+                errors++;
+                return;
+            }
+            recv_cnt++;
             if (nums.length != length) {
             if (nums.length != length) {
-                console.error("Real length " + nums.length + " is not " + length);
+                console.error("Expected length " + length + " but got " + nums.length);
                 errors++;
                 errors++;
                 return;
                 return;
             }
             }
             real_chksum = nums.split('').reduce(add);
             real_chksum = nums.split('').reduce(add);
             if (real_chksum != chksum) {
             if (real_chksum != chksum) {
-                console.error("Real chksum " + real_chksum + " is not " + chksum);
+                console.error("Expected chksum " + chksum + " but real chksum is " + real_chksum);
                 errors++
                 errors++
                 return;
                 return;
             }
             }
@@ -91,6 +100,10 @@
         }
         }
 
 
         function send() {
         function send() {
+            if (ws.bufferedAmount > 0) {
+                console.log("Delaying send");
+                return;
+            }
             var length = Math.floor(Math.random()*(max_send-9)) + 10; // 10 - max_send
             var length = Math.floor(Math.random()*(max_send-9)) + 10; // 10 - max_send
             var numlist = [], arr = [];
             var numlist = [], arr = [];
             for (var i=0; i < length; i++) {
             for (var i=0; i < length; i++) {
@@ -98,7 +111,8 @@
             }
             }
             chksum = numlist.reduce(add);
             chksum = numlist.reduce(add);
             var nums = numlist.join('');
             var nums = numlist.join('');
-            arr.pushStr("^" + length + ":" + chksum + ":" + nums + "$")
+            arr.pushStr("^" + send_cnt + ":" + length + ":" + chksum + ":" + nums + "$")
+            send_cnt ++;
             ws.send(Base64.encode(arr));
             ws.send(Base64.encode(arr));
             sent++;
             sent++;
         }
         }
@@ -126,6 +140,7 @@
             };
             };
             ws.onclose = function(e) {
             ws.onclose = function(e) {
                 console.log(">> WebSockets.onclose");
                 console.log(">> WebSockets.onclose");
+                $clear(send_ref);
                 console.log("<< WebSockets.onclose");
                 console.log("<< WebSockets.onclose");
             };
             };
             ws.onerror = function(e) {
             ws.onerror = function(e) {
@@ -167,8 +182,6 @@
             $clear(update_ref);
             $clear(update_ref);
             update_stats(); // Final numbers
             update_stats(); // Final numbers
 
 
-            $clear(send_ref);
-
             $('connectButton').value = "Start";
             $('connectButton').value = "Start";
             $('connectButton').onclick = connect;
             $('connectButton').onclick = connect;
             console.log("<< disconnect");
             console.log("<< disconnect");

+ 48 - 19
wstest.py

@@ -5,6 +5,7 @@ from base64 import b64encode, b64decode
 from select import select
 from select import select
 
 
 buffer_size = 65536
 buffer_size = 65536
+recv_cnt = send_cnt = 0
 
 
 server_handshake = """HTTP/1.1 101 Web Socket Protocol Handshake\r
 server_handshake = """HTTP/1.1 101 Web Socket Protocol Handshake\r
 Upgrade: WebSocket\r
 Upgrade: WebSocket\r
@@ -35,57 +36,83 @@ def traffic(token="."):
     sys.stdout.write(token)
     sys.stdout.write(token)
     sys.stdout.flush()
     sys.stdout.flush()
 
 
+
+def decode(buf):
+    """ Parse out WebSocket packets. """
+    if buf.count('\xff') > 1:
+        traffic(str(buf.count('\xff')))
+        return [b64decode(d[1:]) for d in buf.split('\xff')]
+    else:
+        return [b64decode(buf[1:-1])]
+
 def check(buf):
 def check(buf):
+    global recv_cnt
 
 
     try:
     try:
-        data = b64decode(buf[1:-1])
+        data_list = decode(buf)
     except:
     except:
         print "\n<BOF>" + repr(buf) + "<EOF>"
         print "\n<BOF>" + repr(buf) + "<EOF>"
         return "Failed to decode"
         return "Failed to decode"
 
 
-    chunks = data.count('$')
-    if chunks > 1:
-        traffic(str(chunks))
-
-    for chunk in data.split("$"):
-        if not chunk: continue
+    err = ""
+    for data in data_list:
+        if data.count('$') > 1:
+            raise Exception("Multiple parts within single packet")
+        if len(data) == 0:
+            traffic("_")
+            continue
 
 
-        if chunk[0] != "^":
-            return "buf did not start with '^'"
+        if data[0] != "^":
+            err += "buf did not start with '^'\n"
+            continue
 
 
         try:
         try:
-            length, chksum, nums = chunk[1:].split(':')
+            cnt, length, chksum, nums = data[1:-1].split(':')
+            cnt    = int(cnt)
             length = int(length)
             length = int(length)
             chksum = int(chksum)
             chksum = int(chksum)
         except:
         except:
             print "\n<BOF>" + repr(data) + "<EOF>"
             print "\n<BOF>" + repr(data) + "<EOF>"
-            return "Invalid data format"
+            err += "Invalid data format\n"
+            continue
+
+        if recv_cnt != cnt:
+            err += "Expected count %d but got %d\n" % (recv_cnt, cnt)
+            recv_cnt = cnt + 1
+            continue
+
+        recv_cnt += 1
 
 
         if len(nums) != length:
         if len(nums) != length:
-            return "Real length %d is not %d" % (len(nums), length)
+            err += "Expected length %d but got %d\n" % (length, len(nums))
+            continue
 
 
         inv = nums.translate(None, "0123456789")
         inv = nums.translate(None, "0123456789")
         if inv:
         if inv:
-            return "Invalid characters found: %s" % inv
+            err += "Invalid characters found: %s\n" % inv
+            continue
 
 
         real_chksum = 0
         real_chksum = 0
         for num in nums:
         for num in nums:
             real_chksum += int(num)
             real_chksum += int(num)
 
 
         if real_chksum != chksum:
         if real_chksum != chksum:
-            return "Real checksum %d is not %d" % (real_chksum, chksum)
+            err += "Expected checksum %d but real chksum is %d\n" % (chksum, real_chksum)
+    return err
 
 
 
 
 def generate():
 def generate():
-    length = random.randint(10, 10000)
-    numlist = rand_array[10000-length:]
+    global send_cnt
+    length = random.randint(10, 100000)
+    numlist = rand_array[100000-length:]
     # Error in length
     # Error in length
     #numlist.append(5)
     #numlist.append(5)
     chksum = sum(numlist)
     chksum = sum(numlist)
     # Error in checksum
     # Error in checksum
     #numlist[0] = 5
     #numlist[0] = 5
     nums = "".join( [str(n) for n in numlist] )
     nums = "".join( [str(n) for n in numlist] )
-    data = "^%d:%d:%s$" % (length, chksum, nums)
+    data = "^%d:%d:%d:%s$" % (send_cnt, length, chksum, nums)
+    send_cnt += 1
 
 
     buf = "\x00" + b64encode(data) + "\xff"
     buf = "\x00" + b64encode(data) + "\xff"
     return buf
     return buf
@@ -129,7 +156,7 @@ def responder(client, delay=500):
             traffic("<")
             traffic("<")
 
 
 def start_server(listen_port, delay=500):
 def start_server(listen_port, delay=500):
-    global errors
+    global errors, send_cnt, recv_cnt
     lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     lsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
     lsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
     lsock.bind(('', listen_port))
     lsock.bind(('', listen_port))
@@ -142,6 +169,8 @@ def start_server(listen_port, delay=500):
             print 'Got client connection from %s' % address[0]
             print 'Got client connection from %s' % address[0]
             handshake(csock)
             handshake(csock)
 
 
+            send_cnt = 0
+            recv_cnt = 0
             responder(csock, delay=delay)
             responder(csock, delay=delay)
 
 
         except Exception:
         except Exception:
@@ -166,7 +195,7 @@ if __name__ == '__main__':
 
 
     print "Prepopulating random array"
     print "Prepopulating random array"
     rand_array = []
     rand_array = []
-    for i in range(0, 10000):
+    for i in range(0, 100000):
         rand_array.append(random.randint(0, 9))
         rand_array.append(random.randint(0, 9))
 
 
     start_server(listen_port, delay=delay)
     start_server(listen_port, delay=delay)