Showing posts with label compression. Show all posts
Showing posts with label compression. Show all posts

Sunday, April 28, 2013

The Strange Tale of Dart, JavaScript, and Gzip Headers and Footers

‹prev | My Chain | next›

I continue my efforts to convert the ICE Code Editor from JavaScript to Dart. The two big unknowns before I started this were calling JavaScript libraries (e.g. ACE) from Dart and reading gzip data. It turns out that working with JavaScript in Dart is super easy, thanks to js-interop. Working with gzip compressed data in Dart is also easy. But I have trouble reading the data gzip'd with js-deflate

Jos Hirth pointed out that the Dart version was most likely doing what the gzip command-line version was doing: adding a standard gzip header and footer to the body of the deflated data. If that is the case, then I may have a decent migration strategy—add a few bytes before and after the old data and I ought to be good to go.

To test this theory, I start in JavaScript. I have the code that I want to deflate stored in code:
code = "<body></body>\n" +
  "<script src=\"http://gamingJS.com/Three.js\"></script>\n" +
  "<script src=\"http://gamingJS.com/ChromeFixes.js\"></script>\n" + 
  "<script>\n" +
  "  // Your code goes here...\n" +
  "</script>";
Next I use js-deflate to deflate this code into str_d:
str_d = RawDeflate.deflate(code)
This deflated string, str_d should serve as the body of the gzip data. Now I need the header and the footer. Per this onicos document, I should be able to make the header with:
header = [0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03].
  map(function(b) {return String.fromCharCode(b)}).
  join("")
Those are ten bytes that comprise the header, mapped into a string just as the deflated bytes were mapped into str_d. The first two bytes are always those values, per the documentation. The next, 0x08 signifies that the body contains deflate data. The remaining are supposed to hold Unix timestamp data, but I guess that this is not necessary. There are also one or two bytes that are supposed to hold optional data, but again, I leave them empty. The last byte could probably also be left empty, but I set it to 0x03 to signify Unix data.

As for the footer, it is supposed to hold 4 bytes of crc32 and 4 bytes describing the compressed size. For the time being, I leave them completely empty:
ooter = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00].
  map(function(b) {String.fromCharCode(b)}).
  join("")
Hopefully this will result in a warning, but not an error. That is, hopefully, I can still gunzip this data even if a warning is given.

With that, I concatenate header, body, and footer into a single string and then convert from bytes to base64:
btoa(header + str_d + footer)
"H4sIAAAAAAAAA7NJyk+ptLPRB1NcNsXJRZkFJQrFRcm2ShklJQVW+vrpibmZeelewXrJ+bn6IRlFqal6WcVKQC0QtURocs4oys9NdcusSC3GrtWOS0FBX18hMr+0SCE5PyVVIT0/tVghI7UoVU9PjwuuHAAAAAAAAQAAAgAAAwAABAAABQAABgAABwA="
That looks promising—much closer to the Dart output from the other day which was:
H4sIAAAAAAAAA7JJyk+ptLPRB1NcNsXJRZkFJQrFRcm2ShklJQVW+vrpibmZeelewXrJ+bn6IRlFqal6WcVKQC0QtURocs4oys9NdcusSC3GrtWOS0FBX18hMr+0SCE5PyVVIT0/tVghI7UoVU9PjwuuHAAAAP//
To test the JavaScript result, I run it through the Linux base64 and gzip utilities:
➜  ice-code-editor git:(master) ✗ echo -n "H4sIAAAAAAAAA7NJyk+ptLPRB1NcNsXJRZkFJQrFRcm2ShklJQVW+vrpibmZeelewXrJ+bn6IRlFqal6WcVKQC0QtURocs4oys9NdcusSC3GrtWOS0FBX18hMr+0SCE5PyVVIT0/tVghI7UoVU9PjwuuHAAAAAAAAQAAAgAAAwAABAAABQAABgAABwA=" | base64 -d | gunzip -dc
<body></body>
<script src="http://gamingJS.com/Three.js"></script>
<script src="http://gamingJS.com/ChromeFixes.js"></script>
<script>
  // Your code goes here...
</script>
gzip: stdin: invalid compressed data--crc error

gzip: stdin: invalid compressed data--length error
Success! As feared, I see crc32 and length errors, but nonetheless I am finally able to gunzip the JavaScript data.

Now that I understand everything, let's see if I can solve this in Dart. That is, can I take the body that was deflated in JavaScript and inflate it in Dart? The base64 and gzip'd version of the code is stored in the ICE Code Editor's localStorage as:
"s0nKT6m0s9EHU1w2xclFmQUlCsVFybZKGSUlBVb6+umJuZl56V7Besn5ufohGUWpqXpZxUpALRC1RGhyzijKz011y6xILcau1Y5LQUFfXyEyv7RIITk/JVUhPT+1WCEjtShVT0+PC64cAA=="
The built-in Zlib library in Dart operates on streams. So I take this base64/deflated data, add it to a stream, pass the stream through an instance of ZlibInflater, and then finally fold and print the result:
import 'dart:async';
import 'dart:io';
import 'dart:crypto';

main() {
  var data = "s0nKT6m0s9EHU1w2xclFmQUlCsVFybZKGSUlBVb6+umJuZl56V7Besn5ufohGUWpqXpZxUpALRC1RGhyzijKz011y6xILcau1Y5LQUFfXyEyv7RIITk/JVUhPT+1WCEjtShVT0+PC64cAA==";

  var controller = new StreamController();
  controller.stream
    .transform(new ZLibInflater())
    .fold([], (buffer, data) {
      buffer.addAll(data);
      return buffer;
    })
    .then((inflated) {
      print(new String.fromCharCodes(inflated));
    });
  controller.add(CryptoUtils.base64StringToBytes(data));
  controller.close();
}
This fails when I run it because the ZLibInflater expects a header and a footer:
➜  ice-code-editor git:(master) ✗ dart test.dart
Uncaught Error: InternalError: 'Filter error, bad data'
Unhandled exception:
InternalError: 'Filter error, bad data'
So, add the 10 byte header to the stream before adding the body:
var controller = new StreamController();
  controller.stream
    .transform(new ZLibInflater())
    .fold([], (buffer, data) {
      buffer.addAll(data);
      return buffer;
    })
    .then((inflated) {
      print(new String.fromCharCodes(inflated));
    });
  controller.add([0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03]);
  controller.add(CryptoUtils.base64StringToBytes(data));
  controller.close();
}
Which results in:
➜  ice-code-editor git:(master) ✗ dart test.dart
<body></body>
<script src="http://gamingJS.com/Three.js"></script>
<script src="http://gamingJS.com/ChromeFixes.js"></script>
<script>
  // Your code goes here...
</script>
Huzzah! I finally have it. Given js-deflated & base64 encoded data that is stored in the ICE Editor's localStorage, I can read it back in Dart. Interestingly, I do not even need the footer. In fact, if I add the footer to the stream, I again get bad filter data—no doubt due to the bogus crc32 and length information that I feed it. No matter, the footer is not necessary to get what I need.

Of course, none of this really matters until I can convince the fine Dart folks that the Zlib libraries belong in "dart:cypto" instead of "dart:io". The former is available to browsers, which I where I need it if I am to read (and ultimately write) localStorage. The latter is only available server-side which is of no use to me. Thankfully, I can go on merrily using Dart's js-interop to use js-deflate for the time being. And hopefully the time being won't be too long.


Day #735

Friday, April 26, 2013

Gzip and Base64 in Dart

‹prev | My Chain | next›

To convert the ICE Code Editor to Dart, I will need the ability to read and write base64, gzip'd strings. The ICE Code editor gzips and base64 encodes code before storing it to localStorage. Unfortunately, this is almost certainly not going to work in Dart (at least not currently) because Dart's ZLibDeflater and ZLibInflater are part of the "dart:io" package, which is not available in the browser (noooooooooooo!!!!). Even so, I would like to give it a go just to see if it is capable of working with the equivalent JavaScript data.

The JavaScript files use js-deflate for the gzip compression/decompression and the built-in window.btoa() and window.atob() methods for base64 encoding. The result is that a simple code sample like:
<body></body>
<script src="http://gamingJS.com/Three.js"></script>
<script src="http://gamingJS.com/ChromeFixes.js"></script>
<script>
  // Your code goes here...
</script>
Is stored in ICE as:
s0nKT6m0s9EHU1w2xclFmQUlCsVFybZKGSUlBVb6+umJuZl56V7Besn5ufohGUWpqXpZxUpALRC1RGhyzijKz011y6xILcau1Y5LQUFfXyEyv7RIITk/JVUhPT+1WCEjtShVT0+PC64cAA==
My first thought it to try to decode that in Dart. I import all of the necessary packages, declare the encoded code as str and then build up streams:
import 'dart:async';
import 'dart:io';
import 'dart:crypto';

main() {
  var str = "s0nKT6m0s9EHU1w2xclFmQUlCsVFybZKGSUlBVb6+umJuZl56V7Besn5ufohGUWpqXpZxUpALRC1RGhyzijKz011y6xILcau1Y5LQUFfXyEyv7RIITk/JVUhPT+1WCEjtShVT0+PC64cAA==";

  var controller = new StreamController();
  controller.stream
    .transform(new ZLibInflater())
    .fold([], (buffer, data) {
      print(data);
      buffer.addAll(data);
      return buffer;
    })
    .then((inflated) {
      print(new String.fromCharCodes(inflated));
      print(inflated);
    });
  controller.add(CryptoUtils.base64StringToBytes(str));
  controller.close();
}
I create a vanilla stream controller to which I can add data. The data that I add is the base64 decoded version of str. In the stream, I transform the data with a ZlibInflater instance, fold all of the data into a single buffer and then print out the resulting string. I think that ought to work, but...
➜  ice-code-editor git:(master) ✗ dart test.dart
s0nKT6m0s9EHU1w2xclFmQUlCsVFybZKGSUlBVb6+umJuZl56V7Besn5ufohGUWpqXpZxUpALRC1RGhyzijKz011y6xILcau1Y5LQUFfXyEyv7RIITk/JVUhPT+1WCEjtShVT0+PC64cAA==
[179, 73, 202, 79, 169, 180, 179, 209, 7, 83, 92, 54, 197, 201, 69, 153, 5, 37, 10, 197, 69, 201, 182, 74, 25, 37, 37, 5, 86, 250, 250, 233, 137, 185, 153, 121, 233, 94, 193, 122, 201, 249, 185, 250, 33, 25, 69, 169, 169, 122, 89, 197, 74, 64, 45, 16, 181, 68, 104, 114, 206, 40, 202, 207, 77, 117, 203, 172, 72, 45, 198, 174, 213, 142, 75, 65, 65, 95, 95, 33, 50, 191, 180, 72, 33, 57, 63, 37, 85, 33, 61, 63, 181, 88, 33, 35, 181, 40, 85, 79, 79, 143, 11, 174, 28, 0]
Uncaught Error: InternalError: 'Filter error, bad data'
Unhandled exception:
InternalError: 'Filter error, bad data'
#0      _FutureImpl._scheduleUnhandledError.<anonymous closure> (dart:async:325:9)
#1      Timer.run.<anonymous closure> (dart:async:2251:21)
#2      Timer.run.<anonymous closure> (dart:async:2259:13)
#3      Timer.Timer.<anonymous closure> (dart:async-patch:15:15)
#4      _Timer._createTimerHandler._handleTimeout (dart:io:6697:28)
#5      _Timer._createTimerHandler._handleTimeout (dart:io:6705:7)
#6      _Timer._createTimerHandler.<anonymous closure> (dart:io:6713:23)
#7      _ReceivePortImpl._handleMessage (dart:isolate-patch:81:92)
Ick.

I take an alternate approach to try to compress the same string to see what data comes back:
import 'dart:async';
import 'dart:io';
import 'dart:crypto';

main() {
  var str = "s0nKT6m0s9EHU1w2xclFmQUlCsVFybZKGSUlBVb6+umJuZl56V7Besn5ufohGUWpqXpZxUpALRC1RGhyzijKz011y6xILcau1Y5LQUFfXyEyv7RIITk/JVUhPT+1WCEjtShVT0+PC64cAA==";

  var code = '''<body></body>
<script src="http://gamingJS.com/Three.js"></script>
<script src="http://gamingJS.com/ChromeFixes.js"></script>
<script>
  // Your code goes here...
</script>''';

  var controller = new StreamController();
  controller.stream
    .transform(new ZLibDeflater())
    .fold([], (buffer, data) {
      print(data);
      buffer.addAll(data);
      return buffer;
    })
    .then((inflated) {
      print(CryptoUtils.bytesToBase64(inflated));
      print(inflated);
    });
  controller.add(code.codeUnits);
  controller.close();
}
This results in a base64 string of:
H4sIAAAAAAAAA7JJyk+ptLPRB1NcNsXJRZkFJQrFRcm2ShklJQVW+vrpibmZeelewXrJ+bn6IRlFqal6WcVKQC0QtURocs4oys9NdcusSC3GrtWOS0FBX18hMr+0SCE5PyVVIT0/tVghI7UoVU9PjwuuHAAAAP//
Which is larger than what the JavaScript version has created:
s0nKT6m0s9EHU1w2xclFmQUlCsVFybZKGSUlBVb6+umJuZl56V7Besn5ufohGUWpqXpZxUpALRC1RGhyzijKz011y6xILcau1Y5LQUFfXyEyv7RIITk/JVUhPT+1WCEjtShVT0+PC64cAA==
I am at a loss to explain the difference, but the Dart version seems legit—I can even use this on the command line:
➜  ice-code-editor git:(master) ✗ echo -n "H4sIAAAAAAAAA7JJyk+ptLPRB1NcNsXJRZkFJQrFRcm2ShklJQVW+vrpibmZeelewXrJ+bn6IRlFqal6WcVKQC0QtURocs4oys9NdcusSC3GrtWOS0FBX18hMr+0SCE5PyVVIT0/tVghI7UoVU9PjwuuHAAAAP//" | base64 -d | gunzip -dc
<body></body>
<script src="http://gamingJS.com/Three.js"></script>
<script src="http://gamingJS.com/ChromeFixes.js"></script>
<script>
  // Your code goes here...
</script>
gzip: stdin: unexpected end of file
While the JavaScript version will not work:
➜  ice-code-editor git:(master) ✗ echo -n "s0nKT6m0s9EHU1w2xclFmQUlCsVFybZKGSUlBVb6+umJuZl56V7Besn5ufohGUWpqXpZxUpALRC1RGhyzijKz011y6xILcau1Y5LQUFfXyEyv7RIITk/JVUhPT+1WCEjtShVT0+PC64cAA==" | base64 -d | gunzip -dc

gzip: stdin: not in gzip format
It seems unlikely that I will be able to figure out why the two produce such different results. Sadly, it hardly matters. Since the Dart zlib classes are only available in "dart:io", which is only in the server-side VM, not the browser VM, I cannot use it anyway to store and retrieve data from localStorage. In the end, I will likely use js-interop to continue using the JavaScript version.


Day #733

Friday, June 10, 2011

SPDY Data Compression

‹prev | My Chain | next›

I continue working through little odds and ends tonight. A while back, I was unable to get SPDY data compression working with node-spdy. I chalked up the problem to Chrome dropping support (it is a feature that is likely to go away in the near future anyway).

But, as Mike Belshe pointed out in the comments, I missed that data compression get its own compression stream. Also, I was unaware that data compression streams do not get a dictionary like header streams.

To get this to work, I need to muck with node-spdy's zlib context which assumes a dictionary zlib stream:
var ZLib = exports.ZLib = function() {
if (arguments.length > 0 && arguments[0] == false) {
this.context = new ZLibContext();
}
else {
this.context = new ZLibContext(flatDict);
}
};
With that, I can create my own getStreamCompressor in the node-spdy Respose class:
Response.prototype.getStreamCompressor = function(streamID) {
if (this.stream_compressor)
return this.stream_compressor;

this.stream_compressor = new ZLib(false);

return this.stream_compressor;
};
Lastly, I ensure that I am using this non-dictionary zlib context stream and that I am generating a compressed data frame:
  var dframe = createDataFrame(this.getStreamCompressor(), {
streamID: this.streamID,
flags: fin ? enums.DATA_FLAG_FIN : enums.DATA_FLAG_COMPRESSED,
}, Buffer.isBuffer(data) ? data : new Buffer(data, encoding));
Hopefully that will be sufficient. I had convinced myself that setting the DATA_FLAG_COMPRESSED would trigger compression of the data based on:
exports.createDataFrame = function(zlib, headers, data) {
if (headers.flags & enums.DATA_FLAG_COMPRESSED) {
data = zlib.deflate(data);
}

//...
};
But, checking out the dialog in Chrome's about:net-internals // SPDY tab, it seems that the compression is not being set:
t=1307763599828 [st=113]     SPDY_SESSION_RECV_DATA  
--> flags = 0
--> size = 8577
--> stream_id = 7
(the flags are empty)

Bummer. Before investigating, I check the packets in Wireshark just to be sure that the flags are, in fact, empty:
               +----------------------------------+
00 00 00 05 |C| Stream-ID (31bits) |
+----------------------------------+
02 00 00 1b | Flags (8) | Length (24 bits) |
+----------------------------------+
78 9c ca 48 | Data |
cd c9 c9 57 +----------------------------------+
48 2b ca cf
55 28 4e 2d 2a 4b 2d 52 04 08 00 00 00 ff ff
So the compression is being applied after all (a flag of 0x02 means DATA_COMPRESSION ins SPDY). So, uh... Yay!

I think I will continue with data compression a bit longer tomorrow. I ought to take some time to verify that the data really is compressed and see if I can measure and time / size difference between compressed and non-compressed data streams.


Day #46