Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

New serializer API.

 * Have `write` and `buffer` methods to emit data events or else gather the
   serialized bytes into a buffer or array.
 * Fixed serialization of named elements, last check in was premature.
  • Loading branch information...
commit 363e8b497aab47818f7b3de0c356a7520ad81c49 1 parent cab2fac
@bigeasy bigeasy authored
Showing with 195 additions and 234 deletions.
  1. +78 −29 src/lib/packet.coffee
  2. +117 −205 test/serializer.coffee
View
107 src/lib/packet.coffee
@@ -387,7 +387,7 @@ class WritableStream extends stream.Stream
if typeof buffer is "string"
buffer = new Buffer(buffer, encoding or "utf8")
@_written += buffer.length
- @_serializer._write buffer
+ @_serializer.write buffer
if @_written > @_length
throw new Error "buffer overflow"
else if @_wrtten == @_length
@@ -454,38 +454,95 @@ module.exports.Serializer = class Serializer extends Packet
stream: (length) ->
@_stream = new WritableStream(length, @)
+ _pipe: (destination, options) ->
+ if destination instanceof Array
+ @_buffer = destination
+ @_bufferLength = Number.MAX_VALUE
+ @_streaming = false
+ else if destination instanceof Buffer
+ @_buffer = destination
+ @_bufferLength = destination.length
+ @_streaming = false
+ else
+ @_buffer = (@_ownBuffer or= new Buffer(1024))
+ @_bufferLength = @_ownBuffer.length
+ @_streaming = true
+ super destination, options
+
skip: (length, fill) ->
while length
size = Math.min(length, @_buffer.length)
length -= size
for i in [0...size]
@_buffer[i] = fill
- @_write @_buffer.slice 0, size
+ @write @_buffer.slice 0, size
buffer: (shiftable...) ->
+ if Array.isArray(shiftable[0])
+ buffer = shiftable.shift()
+ bufferLength = Number.MAX_VALUE
+ ownsBuffer = false
+ else if Buffer.isBuffer(shiftable[0])
+ buffer = shiftable.shift()
+ bufferLength = buffer.length
+ ownsBuffer = false
+ else
+ buffer = @_buffer
+ bufferLength = @_buffer.length
+ ownsBuffer = true
+
callback = @_reset(shiftable)
+ @callback = noop
+
read = 0
while @pattern
- if read is @_buffer.length
- buffer = new Buffer buffer.length * 2
- @_buffer.copy(buffer)
- @_buffer = buffer
- read += @write(@_buffer, read, @_buffer.length - read)
- callback.call @self, @_buffer.slice 0, read
-
- serialize: (shiftable...) ->
- @callback = @_reset(shiftable)
- # Implementing pause requires callbacks.
- if @streaming
+ if read is bufferLength
+ if ownsBuffer
+ expanded = new Buffer buffer.length * 2
+ buffer.copy(expanded)
+ buffer = expanded
+ else
+ @emit "error", new Error "buffer overflow"
+ return
+ read += @_serialize buffer, read, bufferLength - read
+
+ @_buffer = buffer if ownsBuffer
+
+ slice = buffer.slice 0, read
+ callback.call @self, slice
+
+ null
+
+ # Using the airity of the callback might be too clever, when we could simply
+ # choose a name, such as `serialize` versus `buffer`, or maybe `write` versus
+ # `buffer`, where write writes the buffer when it fills, and buffer gathers
+ # everthing in a buffer, and gives the user an opportunity to make last minute
+ # changes before writing to the stream.
+ #
+ # Already have plenty of magic with named versus positional arguments.
+ write: (shiftable...) ->
+ if Array.isArray(shiftable[0])
+ shiftable[0] = new Buffer(shiftable[0])
+ if Buffer.isBuffer(shiftable[0])
+ slice = shiftable.shift()
+ if @_decoder
+ string = @_decoder.write(slice)
+ @emit "data", string if string.length
+ else
+ @emit "data", slice
+ else
+ callback = @_reset(shiftable)
+ # Implementing pause requires callbacks.
+ @callback = noop
while @pattern
- read = @write(@_buffer, 0, @_buffer.length)
- @_write @_buffer.slice 0, read
+ read = @_serialize(@_buffer, 0, @_buffer.length)
+ @write @_buffer.slice 0, read
+ callback.call @self
_reset: (shiftable) ->
if typeof shiftable[shiftable.length - 1] == 'function'
callback = shiftable.pop()
-
nameOrPattern = shiftable.shift()
packet = @_packets[nameOrPattern] or {}
pattern = packet.pattern or parsePattern(nameOrPattern)
@@ -502,10 +559,11 @@ module.exports.Serializer = class Serializer extends Packet
if shiftable.length is 1 and
typeof shiftable[0] is "object" and
- not shiftable[0] instanceof Array
+ not (shiftable[0] instanceof Array)
+ object = shiftable.shift()
@_outgoing = []
- for pattern in @pattern
- @_outgoing.push if pattern.name then shiftable[pattern.name] else null
+ for part in @pattern
+ @_outgoing.push if part.name then object[part.name] else null
else
@_outgoing = shiftable
@@ -517,13 +575,6 @@ module.exports.Serializer = class Serializer extends Packet
callback
- _write: (slice) ->
- if @_decoder
- string = @_decoder.write(slice)
- @emit "data", string if string.length
- else
- @emit "data", slice
-
close: ->
@emit "end"
@@ -542,9 +593,7 @@ module.exports.Serializer = class Serializer extends Packet
# written, or the end of the buffer is reached.
# Write to the `buffer` in the region defined by the given `offset` and `length`.
- write: (buffer, offset, length) ->
- offset or= 0
- length or= buffer.length
+ _serialize: (buffer, offset, length) ->
start = offset
end = offset + length
View
322 test/serializer.coffee
@@ -1,287 +1,199 @@
{TwerpTest} = require "twerp"
{Serializer} = require "packet"
-class module.exports.SerializerTest extends TwerpTest
- start: (done) ->
- @parser = new Serializer()
- @serializer = new Serializer()
- done()
+toArray = (buffer) ->
+ array = []
+ for i in [0...buffer.length]
+ array[i] = buffer[i]
+ array
+
+class module.exports.SerializerTest extends TwerpTest
'test: write a byte': (done) ->
- buffer = []
- @serializer.serialize "b8", 0x01, (engine) =>
- @equal engine.getBytesWritten(), 1
- @serializer.write buffer, 0, 1
- @equal buffer[0], 0x01
- done 2
+ serializer = new Serializer()
+ serializer.buffer "b8", 0x01, (buffer) =>
+ @equal serializer.getBytesWritten(), 1
+ @equal buffer[0], 0x01
+ done 2
'test: write a little-endian 16 bit integer': (done) ->
- buffer = []
- @parser.reset()
- @parser.serialize "l16", 0x1FF, (engine) =>
- @equal engine.getBytesWritten(), 2
- @parser.write buffer, 0, 2
- @deepEqual buffer, [ 0xFF, 0x01 ]
- done 2
+ serializer = new Serializer()
+ serializer.buffer "l16", 0x1FF, (buffer) =>
+ @equal serializer.getBytesWritten(), 2
+ @deepEqual toArray(buffer), [ 0xFF, 0x01 ]
+ done 2
'test: write a big-endian 16 bit integer': (done) ->
- buffer = []
- @parser.reset()
- @parser.serialize "b16", 0x1FF, (engine) =>
- @equal engine.getBytesWritten(), 2
- @parser.write buffer, 0, 2
- @deepEqual buffer, [ 0x01, 0xFF ]
- done 2
+ serializer = new Serializer()
+ serializer.buffer "b16", 0x1FF, (buffer) =>
+ @equal serializer.getBytesWritten(), 2
+ @deepEqual toArray(buffer), [ 0x01, 0xFF ]
+ done 2
'test: write a little-endian followed by a big-endian': (done) ->
- buffer = []
- @parser.reset()
- @parser.serialize "l16, b16", 0x1FF, 0x1FF, (engine) =>
- @equal engine.getBytesWritten(), 4
- @parser.write buffer, 0, 4
- @deepEqual buffer, [ 0xFF, 0x01, 0x01, 0xFF ]
- done 2
+ serializer = new Serializer()
+ serializer.buffer "l16, b16", 0x1FF, 0x1FF, (buffer) =>
+ @equal serializer.getBytesWritten(), 4
+ @deepEqual toArray(buffer), [ 0xFF, 0x01, 0x01, 0xFF ]
+ done 2
'test: write a 16 bit integer after skipping two bytes': (done) ->
+ serializer = new Serializer()
buffer = [ 0xff, 0xff, 0xff, 0xff ]
- @parser.reset()
- @parser.serialize "x16, b16", 1, (engine) =>
- @equal engine.getBytesWritten(), 4
- @parser.write buffer, 0, 6
+ serializer.buffer buffer, "x16, b16", 1
+ @equal serializer.getBytesWritten(), 4
@deepEqual buffer, [ 0xff, 0xff, 0x00, 0x01 ]
done 2
'test: write a 16 bit integer after filling two bytes': (done) ->
+ serializer = new Serializer()
buffer = [ 0xff, 0xff, 0xff, 0xff ]
- @parser.reset()
- @parser.serialize "x16{0}, b16", 1, (engine) =>
- @equal engine.getBytesWritten(), 4
- @parser.write buffer, 0, 4
+ serializer.buffer buffer, "x16{0}, b16", 1
+ @equal serializer.getBytesWritten(), 4
@deepEqual buffer, [ 0x00, 0x00, 0x00, 0x01 ]
done 2
'test: write a little-endian 64 bit IEEE 754 float': (done) ->
+ serializer = new Serializer()
writeDoubleFloat = (bytes, value) =>
- buffer = []
- invoked = false
- @parser.reset()
- @parser.serialize "b64f", value, (engine) =>
- @equal engine.getBytesWritten(), 8
- invoked = true
- @parser.write buffer, 0, 8
- @ok invoked
- @deepEqual buffer, bytes
+ serializer.reset()
+ serializer.buffer "b64f", value, (buffer) =>
+ @equal serializer.getBytesWritten(), 8
+ @deepEqual toArray(buffer), bytes
writeDoubleFloat [ 0xdb, 0x01, 0x32, 0xcf, 0xf6, 0xee, 0xc1, 0xc0 ], -9.1819281981e3
writeDoubleFloat [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0xc0 ], -10
- done 2 * 3
+ done 2 * 2
'test: write an array of 8 bytes': (done) ->
- buffer = []
- invoked = false
bytes = [ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 ]
- @parser.reset()
- @parser.serialize "b8[8]", bytes, (engine) =>
- @equal engine.getBytesWritten(), 8
- invoked = true
- @parser.write buffer, 0, 10
- @ok invoked
- @deepEqual buffer, bytes
- done 3
+ serializer = new Serializer()
+ serializer.buffer "b8[8]", bytes, (buffer) =>
+ @equal serializer.getBytesWritten(), 8
+ @deepEqual toArray(buffer), bytes
+ done 2
'test: write a 16 bit integer after skipping four bytes': (done) ->
buffer = [ 0xff, 0xff, 0xff, 0xff ]
- @parser.reset()
- @parser.serialize "x16[2], b16", 1, (engine) =>
- @equal engine.getBytesWritten(), 6
- @parser.write buffer, 0, 7
+ serializer = new Serializer()
+ serializer.buffer buffer, "x16[2], b16", 1
+ @equal serializer.getBytesWritten(), 6
@deepEqual buffer, [ 0xff, 0xff, 0xff, 0xff, 0x00, 0x01 ]
done 2
'test: write a 16 bit integer after filling four bytes': (done) ->
buffer = [ 0x01, 0x01, 0x01, 0x01 ]
- @parser.reset()
- @parser.serialize "x16[2]{0}, b16", 1, (engine) =>
- @equal engine.getBytesWritten(), 6
- @parser.write buffer, 0, 7
+ serializer = new Serializer()
+ serializer.buffer buffer, "x16[2]{0}, b16", 1
+ @equal serializer.getBytesWritten(), 6
@deepEqual buffer, [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 ]
done 2
'test: write 2 zero filled bytes then two 2 filled bytes': (done) ->
buffer = [ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 ]
- @parser.reset()
- @parser.serialize "x8[2]{0},x8[2]{2}", (engine) =>
- @equal engine.getBytesWritten(), 4
- @parser.write buffer, 0, 7
+ serializer = new Serializer()
+ serializer.buffer buffer, "x8[2]{0},x8[2]{2}"
+ @equal serializer.getBytesWritten(), 4
@deepEqual buffer, [ 0x00, 0x00, 0x02, 0x02, 0x01, 0x01 ]
done 2
'test: write a length encoded array of bytes': (done) ->
- buffer = []
- invoked = false
bytes = [ 0x03, 0x02, 0x03, 0x04 ]
- @parser.reset()
- @parser.serialize "b8/b8", bytes.slice(1), (engine) =>
- @equal engine.getBytesWritten(), 4
- invoked = true
- @parser.write buffer, 0, 10
- @ok invoked
- @deepEqual buffer, bytes
- done 3
+ serializer = new Serializer()
+ serializer.buffer "b8/b8", bytes.slice(1), (buffer) =>
+ @equal serializer.getBytesWritten(), 4
+ @deepEqual toArray(buffer), bytes
+ done 2
'test: write a zero terminated array of bytes': (done) ->
- buffer = []
- invoked = false
bytes = [ 0x01, 0x02, 0x03, 0x04 ]
- @parser.reset()
- @parser.serialize "b8z", bytes, (engine) =>
- @equal engine.getBytesWritten(), 5
- invoked = true
- @parser.write buffer, 0, 10
- @ok invoked
- bytes.push 0
- @deepEqual buffer, bytes
- done 3
+ serializer = new Serializer()
+ serializer.buffer "b8z", bytes, (buffer) =>
+ @equal serializer.getBytesWritten(), 5
+ bytes.push 0
+ @deepEqual toArray(buffer), bytes
+ done 2
'test: write a zero terminated array of 8 bytes': (done) ->
- buffer = []
- invoked = false
bytes = [ 0x01, 0x02, 0x03, 0x04 ]
- @parser.reset()
- @parser.serialize "b8[8]z", bytes, (engine) =>
- @equal engine.getBytesWritten(), 8
- invoked = true
- buffer = []
- for i in [0...8]
- buffer[i] = 0x01
- @parser.write buffer, 0, 10
- @ok invoked
+ buffer = [ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 ]
+ serializer = new Serializer()
+ serializer.buffer buffer, "b8[8]z", bytes
+ @equal serializer.getBytesWritten(), 8
@deepEqual buffer, [ 0x01, 0x02, 0x03, 0x04, 0x00, 0x01, 0x01, 0x01 ]
- done 3
+ done 2
'test: write a zero terminated array of 8 bytes zero filled': (done) ->
- buffer = []
- invoked = false
bytes = [ 0x01, 0x02, 0x03, 0x04 ]
- @parser.reset()
- @parser.serialize "b8[8]{0}z", bytes, (engine) =>
- @equal engine.getBytesWritten(), 8
- invoked = true
- buffer = []
- for i in [0...8]
- buffer[i] = 0x01
- @parser.write buffer, 0, 10
- @ok invoked
+ buffer = [ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 ]
+ serializer = new Serializer()
+ serializer.buffer buffer, "b8[8]{0}z", bytes
+ @equal serializer.getBytesWritten(), 8
@deepEqual buffer, [ 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00 ]
- done 3
+ done 2
'test: write a zero terminated array of 8 bytes filled': (done) ->
- buffer = []
- invoked = false
bytes = [ 0x01, 0x02, 0x03, 0x04 ]
- @parser.reset()
- @parser.serialize "b8[8]{2}z", bytes, (engine) =>
- @equal engine.getBytesWritten(), 8
- invoked = true
- buffer = []
- for i in [0...8]
- buffer[i] = 0x01
- @parser.write buffer, 0, 10
- @ok invoked
+ buffer = [ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 ]
+ serializer = new Serializer()
+ serializer.buffer buffer, "b8[8]{2}z", bytes
+ @equal serializer.getBytesWritten(), 8
@deepEqual buffer, [ 0x01, 0x02, 0x03, 0x04, 0x00, 0x02, 0x02, 0x02 ]
- done 3
+ done 2
'test: write a 3 byte ASCII string': (done) ->
- buffer = []
- invoked = false
- @parser.reset()
- @parser.serialize "b8[3]|ascii()", "ABC", (engine) =>
- @equal engine.getBytesWritten(), 3
- invoked = true
- @parser.write buffer, 0, 10
- @ok invoked
- @deepEqual buffer, [ 0x41, 0x42, 0x43 ]
- done 3
+ serializer = new Serializer()
+ serializer.buffer "b8[3]|ascii()", "ABC", (buffer) =>
+ @equal serializer.getBytesWritten(), 3
+ @deepEqual toArray(buffer), [ 0x41, 0x42, 0x43 ]
+ done 2
'test: write a zero terminated UTF-8 string': (done) ->
- buffer = []
- invoked = false
- @parser.reset()
- @parser.serialize "b8z|utf8()", "ABC", (engine) =>
- @equal engine.getBytesWritten(), 4
- invoked = true
- @parser.write buffer, 0, 10
- @ok invoked
- @deepEqual buffer, [ 0x41, 0x42, 0x43, 0x00 ]
- done 3
+ serializer = new Serializer()
+ serializer.buffer "b8z|utf8()", "ABC", (buffer) =>
+ @equal serializer.getBytesWritten(), 4
+ @deepEqual toArray(buffer), [ 0x41, 0x42, 0x43, 0x00 ]
+ done 2
'test: write a zero terminated 8 byte padded UTF-8 string': (done) ->
- buffer = []
-
- invoked = false
- @parser.reset()
- @parser.serialize "b8[8]{1}z|utf8()", "ABC", (engine) =>
- @equal engine.getBytesWritten(), 8
- invoked = true
- @parser.write buffer, 0, 10
-
- @ok invoked
- @deepEqual buffer, [ 0x41, 0x42, 0x43, 0x00, 0x01, 0x01, 0x01, 0x01 ]
- done 3
+ serializer = new Serializer()
+ serializer.buffer "b8[8]{1}z|utf8()", "ABC", (buffer) =>
+ @equal serializer.getBytesWritten(), 8
+ @deepEqual toArray(buffer), [ 0x41, 0x42, 0x43, 0x00, 0x01, 0x01, 0x01, 0x01 ]
+ done 2
'test: write a zero terminated 8 byte padded UTF-8 string that is 7 characters long': (done) ->
- buffer = []
- invoked = false
- @parser.reset()
- @parser.serialize "b8[8]{0}z|utf8()", "0000ABC", (engine) =>
- @equal engine.getBytesWritten(), 8
- invoked = true
- @parser.write buffer, 0, 10
- @ok invoked
- @deepEqual buffer, [ 0x30, 0x30, 0x30, 0x30, 0x41, 0x42, 0x43, 0x00 ]
- done 3
+ serializer = new Serializer()
+ serializer.buffer "b8[8]{0}z|utf8()", "0000ABC", (buffer) =>
+ @equal serializer.getBytesWritten(), 8
+ @deepEqual toArray(buffer), [ 0x30, 0x30, 0x30, 0x30, 0x41, 0x42, 0x43, 0x00 ]
+ done 2
'test: write an integer converted to a zero terminated UTF-8 string': (done) ->
- buffer = []
-
- invoked = false
- @parser.reset()
- @parser.serialize "b8z|utf8()|atoi(10)", "42", (engine) =>
- @equal engine.getBytesWritten(), 3
- invoked = true
- @parser.write buffer, 0, 10
-
- @ok invoked
- @deepEqual buffer, [ 0x34, 0x32, 0x00 ]
- done 3
+ serializer = new Serializer()
+ serializer.buffer "b8z|utf8()|atoi(10)", "42", (buffer) =>
+ @equal serializer.getBytesWritten(), 3
+ @deepEqual toArray(buffer), [ 0x34, 0x32, 0x00 ]
+ done 2
'test: write an integer converted to a zero padded zero terminated UTF-8 string': (done) ->
- buffer = []
- invoked = false
- @parser.reset()
- @parser.serialize "b8z|utf8()|pad('0', 7)|atoi(10)", "42", (engine) =>
- @equal engine.getBytesWritten(), 8
- invoked = true
- @parser.write buffer, 0, 10
- @ok invoked
- @deepEqual buffer, [ 0x30, 0x30, 0x30, 0x30, 0x30, 0x34, 0x32, 0x00 ]
- done 3
+ serializer = new Serializer()
+ serializer.buffer "b8z|utf8()|pad('0', 7)|atoi(10)", "42", (buffer) =>
+ @equal serializer.getBytesWritten(), 8
+ @deepEqual toArray(buffer), [ 0x30, 0x30, 0x30, 0x30, 0x30, 0x34, 0x32, 0x00 ]
+ done 2
"test: set self": (done) ->
- self = {}
+ self = this
serializer = new Serializer(self)
- invoked = false
- serializer.reset()
- serializer.serialize "b8", 0x01, (engine) ->
- invoked = self is this
- serializer.write [ 1 ]
- @ok invoked
- done 1
+ serializer.write "b8", 0x01, ->
+ @ok self is this
+ done 1
"test: write object": (done) ->
serializer = new Serializer()
- serializer.pipe buffer = []
- object = { length: 257, type: 8, name: "ABC" }
- serializer.serialize "b16 => length, b8 => type, b8z => name", "42", object, =>
+ serializer.streaming = true
+ object = { length: 258, type: 8, name: "ABC" }
+ serializer.buffer "b16 => length, b8 => type, b8z|utf8() => name", object, (buffer) =>
@equal serializer.getBytesWritten(), 7
- @deepEqual buffer, [ 0x30, 0x30, 0x30, 0x30, 0x30, 0x34, 0x32, 0x00 ]
+ @deepEqual toArray(buffer), [ 0x01, 0x02, 0x08, 0x41, 0x42, 0x43, 0x00 ]
done 2
Please sign in to comment.
Something went wrong with that request. Please try again.