From c5cafcc95699d900499730a862a48f0c6086fc6b Mon Sep 17 00:00:00 2001 From: "Devgru @ nemetz" Date: Tue, 30 Jul 2013 20:28:45 +0400 Subject: [PATCH] custom BEB128 implementation --- src/beb.coffee | 32 ++++++++++++++++++++++++++++++++ src/compose.coffee | 6 +++--- src/leb.coffee | 34 ---------------------------------- src/parse.coffee | 4 ++-- test/beb.coffee | 14 ++++++++++++++ 5 files changed, 51 insertions(+), 39 deletions(-) create mode 100644 src/beb.coffee delete mode 100644 src/leb.coffee create mode 100644 test/beb.coffee diff --git a/src/beb.coffee b/src/beb.coffee new file mode 100644 index 0000000..05ee2ee --- /dev/null +++ b/src/beb.coffee @@ -0,0 +1,32 @@ +### +BEB128 implementation. Tarantool uses BEB128 and calls it LEB128 somewhy. + +Big-endian base-128 uses 7 bits for value and most significant bit (MSB) for flag +If flag is 1 — there is more bytes, continue reading. +So, 127 is 0x7f, 128 is 0x81 0x00, 129 is 0x81 0x01 and so on. +### + +encode = (value) -> + bytes = [] + + bytes.push value >> 28 | 0x80 if value >= 1 << 28 + bytes.push value >> 21 | 0x80 if value >= 1 << 21 + bytes.push value >> 14 | 0x80 if value >= 1 << 14 + bytes.push value >> 7 | 0x80 if value >= 1 << 7 + bytes.push value & 0x7F + + new Buffer bytes + +decode = (buffer, position = 0) -> + value = 0 + loop # stops after reading byte with MSB=0 + byte = buffer[position++] + value = (value << 7) + (byte & 0x7F) + break unless byte & 0x80 + + value: value + nextIndex: position + +module.exports = + encode: encode + decode: decode diff --git a/src/compose.coffee b/src/compose.coffee index 12b4673..ac6248a 100644 --- a/src/compose.coffee +++ b/src/compose.coffee @@ -1,4 +1,4 @@ -leb = require './leb' +beb = require './beb' module.exports = compose = ### @@ -21,14 +21,14 @@ module.exports = compose = stringField: (value) -> # what about string encoding? stringBuffer = new Buffer value, 'utf-8' # default - lengthBuffer = leb.encodeUInt32 stringBuffer.length + lengthBuffer = beb.encode stringBuffer.length Buffer.concat [lengthBuffer, stringBuffer] tuple: (tuple) -> buffers = [compose.int32s tuple.length] for field in tuple - buffers.push leb.encodeUInt32 field.length + buffers.push beb.encode field.length buffers.push field Buffer.concat buffers diff --git a/src/leb.coffee b/src/leb.coffee deleted file mode 100644 index 8cebcd3..0000000 --- a/src/leb.coffee +++ /dev/null @@ -1,34 +0,0 @@ -# this file fixes LEB128 behaviour to be compatible with Tarantool - -leb = require 'leb' - -fix = (result) -> - len = result.length - # we have to reverse it - reversed = new Buffer len - for i in [0...len] - reversed[i] = result[len - 1 - i] - - # and toggle #7 bit in first and last bytes - reversed[0] ^= 0x80 - reversed[len - 1] ^= 0x80 - - reversed - -encode = (data) -> fix leb.encodeUInt32 data - -decode = (buf, pos = 0) -> - # we have to know what amount of bytes to read - temp = leb.decodeUInt32 buf, pos - - # then slice the buffer, remembering position - buf = buf.slice pos, temp.nextIndex - result = leb.decodeUInt32 fix buf - - # fix the position - result.nextIndex += pos - result - -module.exports = - encodeUInt32: encode - decodeUInt32: decode diff --git a/src/parse.coffee b/src/parse.coffee index 5c52e7b..ed18780 100644 --- a/src/parse.coffee +++ b/src/parse.coffee @@ -1,4 +1,4 @@ -leb = require './leb' +beb = require './beb' module.exports = parse = ### @@ -29,7 +29,7 @@ module.exports = parse = fields = [] while count > 0 - sizeLeb = leb.decodeUInt32 tuple, bytesRead + sizeLeb = beb.decode tuple, bytesRead bytesRead = sizeLeb.nextIndex size = sizeLeb.value fields.push tuple.slice bytesRead, bytesRead += size diff --git a/test/beb.coffee b/test/beb.coffee new file mode 100644 index 0000000..b4c3f95 --- /dev/null +++ b/test/beb.coffee @@ -0,0 +1,14 @@ +beb = require '../src/beb' + +exports['encode'] = (test) -> + test.equalBuffer = (b1, b2) -> + @equal b1.toString('hex'), b2.toString('hex') + test.equalBuffer (new Buffer [0x80 | 1, 0]), beb.encode 128 + test.equalBuffer (new Buffer [0x80 | 1, 1]), beb.encode 129 + do test.done + +exports['fuzz'] = (test) -> + for i in [0...(1<<15)] by 1<<5 + test.equal i, beb.decode(beb.encode(i)).value + do test.done +