Search code examples
websockettclxor

Having trouble understanding binary scan results of XOR data frame over web socket?


I'm likely doing something very stupid and I'm having quite a bit of trouble understanding how these results could possibly be generated.

Over a web socket from a browser to a local Tcl script, the following JSON string is being passed. {"ops":"AudioNotes EditSgmtTitle","params":{"doc_id":100,"title":"Short Segment Title (Looks Pretty Good aa Maybe)"},"reqId":1}. I was adding in functionality to edit the segment title from the UI and hit on a string length that is causing issues on the read side by accidentally typing in a few garbage letters to see if the database was being updated.

If this string is pasted into Kate text editor, it appears to have a length of 127. If it is placed in an active tclsh session, the [string length ...] is 127.

The XOR frame is read in Tcl starting with:

if { [binary scan [coread $sock 2] B16 bits] != 1
  || [scan $bits %1b%1b%1b%1b%4b%1b%7b \
        fin rsv1 rsv2 rsv3 op m pl] != 7 } {

It returns a pl for payload length of 126. The result is that, when the next two bytes are read as an unsigned integer, it returns a very large value of varying size. I'm using [binary scan [coread $sock 2] Su pl]

In the title property there is an aa. If that is changed to a single a, pl is still 126 and everthing works, that is, the XOR frame is read without error, decoded, and the correct title is written to the database for doc_id of 100.

My question is, how can both strings be read as length 126 and how can I correct this? (I forgot that the 126 just tells us to read the next 16 bits to get the lengths and those should be 126 and 127 in these examples.)

Odd also, is that one time the error is:

Error in XOR_Read: expected non-negative integer but got "6717638123588400952" Closing socket.

Another time:

Error in XOR_Read: expected non-negative integer but got "18173768903968447991" Closing socket.

Sending the same string (with the aa) in both instances but get a different value when read the next two bytes for a $pl of 126. I write the request out to the console.log in the browser and the string is the same in all instances.

I realize that weird bugging items can take awhile to present themselves, but I've been using the socket and XOR decoding code for about two years now without issue.

Thank you for considering my unusual question.

These are links to the documentation for what those bits are supposed to be.

The WebSocket Protocol and MDN

Perhaps I've done something wrong in the coread coroutine because the length has to be exactly 127 to fail; for 126 and 128 and every other size I've tried works. Must've never had exactly 127 before.

proc ::WEBS::coread {sock reqBytes} {
  set fullRead {}
  set remBytes $reqBytes
  while {![chan eof $sock]} {
    yield
    append fullRead [set loopRead [read $sock $remBytes]]
    if {[incr remBytes -[string length $loopRead]] == 0} {
      return $fullRead
    }
  }
  throw {COREAD EOF} "Unexpected WEBS EOF"
}

Solution

  • You didn't show the code that actually handles the length processing, so I can only guess. In websockets, the length is specified in a 7-bit field. Length values of 126 and 127 have special meaning. If the length field is 126, the actual length is specified in the next 2 bytes. If the length field is 127, the actual length is specified in the next 8 bytes. Perhaps your code is doing something like this to handle that:

    if {$pl == 126} {
        binary scan [coread $sock 2] Su pl
    }
    if {$pl == 127} {
        binary scan [coread $sock 8] Wu pl
    }
    

    This will only fail for a payload length of 127, because both conditions are then true. First pl is 126, causing the code to read the length from the next 2 bytes, making pl 127. If the original pl value was 127, you should get the length from the next 8 bytes. But you must not do both.

    If this is the case, the solution is simple: Put the second check in an else block:

    if {$pl == 126} {
        binary scan [coread $sock 2] Su pl
    } elseif {$pl == 127} {
        binary scan [coread $sock 8] Wu pl
    }
    

    Doing the checks the other way around will also work, assuming a proper implementation of websockets in the browser (i.e.: it doesn't send a length of 126 in 8 bytes).