223 lines
6.8 KiB
JavaScript
223 lines
6.8 KiB
JavaScript
|
// sizeof(T).
|
||
|
var SIZE_INT32 = 4
|
||
|
var SIZE_UINT32 = 4
|
||
|
var SIZE_INT64 = 8
|
||
|
var SIZE_UINT64 = 8
|
||
|
var SIZE_FLOAT = 4
|
||
|
var SIZE_DOUBLE = 8
|
||
|
|
||
|
// The allocation granularity of the payload.
|
||
|
var PAYLOAD_UNIT = 64
|
||
|
|
||
|
// Largest JS number.
|
||
|
var CAPACITY_READ_ONLY = 9007199254740992
|
||
|
|
||
|
// Aligns 'i' by rounding it up to the next multiple of 'alignment'.
|
||
|
var alignInt = function (i, alignment) {
|
||
|
return i + (alignment - (i % alignment)) % alignment
|
||
|
}
|
||
|
|
||
|
// PickleIterator reads data from a Pickle. The Pickle object must remain valid
|
||
|
// while the PickleIterator object is in use.
|
||
|
var PickleIterator = (function () {
|
||
|
function PickleIterator (pickle) {
|
||
|
this.payload = pickle.header
|
||
|
this.payloadOffset = pickle.headerSize
|
||
|
this.readIndex = 0
|
||
|
this.endIndex = pickle.getPayloadSize()
|
||
|
}
|
||
|
|
||
|
PickleIterator.prototype.readBool = function () {
|
||
|
return this.readInt() !== 0
|
||
|
}
|
||
|
|
||
|
PickleIterator.prototype.readInt = function () {
|
||
|
return this.readBytes(SIZE_INT32, Buffer.prototype.readInt32LE)
|
||
|
}
|
||
|
|
||
|
PickleIterator.prototype.readUInt32 = function () {
|
||
|
return this.readBytes(SIZE_UINT32, Buffer.prototype.readUInt32LE)
|
||
|
}
|
||
|
|
||
|
PickleIterator.prototype.readInt64 = function () {
|
||
|
return this.readBytes(SIZE_INT64, Buffer.prototype.readInt64LE)
|
||
|
}
|
||
|
|
||
|
PickleIterator.prototype.readUInt64 = function () {
|
||
|
return this.readBytes(SIZE_UINT64, Buffer.prototype.readUInt64LE)
|
||
|
}
|
||
|
|
||
|
PickleIterator.prototype.readFloat = function () {
|
||
|
return this.readBytes(SIZE_FLOAT, Buffer.prototype.readFloatLE)
|
||
|
}
|
||
|
|
||
|
PickleIterator.prototype.readDouble = function () {
|
||
|
return this.readBytes(SIZE_DOUBLE, Buffer.prototype.readDoubleLE)
|
||
|
}
|
||
|
|
||
|
PickleIterator.prototype.readString = function () {
|
||
|
return this.readBytes(this.readInt()).toString()
|
||
|
}
|
||
|
|
||
|
PickleIterator.prototype.readBytes = function (length, method) {
|
||
|
var readPayloadOffset = this.getReadPayloadOffsetAndAdvance(length)
|
||
|
if (method != null) {
|
||
|
return method.call(this.payload, readPayloadOffset, length)
|
||
|
} else {
|
||
|
return this.payload.slice(readPayloadOffset, readPayloadOffset + length)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
PickleIterator.prototype.getReadPayloadOffsetAndAdvance = function (length) {
|
||
|
if (length > this.endIndex - this.readIndex) {
|
||
|
this.readIndex = this.endIndex
|
||
|
throw new Error('Failed to read data with length of ' + length)
|
||
|
}
|
||
|
var readPayloadOffset = this.payloadOffset + this.readIndex
|
||
|
this.advance(length)
|
||
|
return readPayloadOffset
|
||
|
}
|
||
|
|
||
|
PickleIterator.prototype.advance = function (size) {
|
||
|
var alignedSize = alignInt(size, SIZE_UINT32)
|
||
|
if (this.endIndex - this.readIndex < alignedSize) {
|
||
|
this.readIndex = this.endIndex
|
||
|
} else {
|
||
|
this.readIndex += alignedSize
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return PickleIterator
|
||
|
})()
|
||
|
|
||
|
// This class provides facilities for basic binary value packing and unpacking.
|
||
|
//
|
||
|
// The Pickle class supports appending primitive values (ints, strings, etc.)
|
||
|
// to a pickle instance. The Pickle instance grows its internal memory buffer
|
||
|
// dynamically to hold the sequence of primitive values. The internal memory
|
||
|
// buffer is exposed as the "data" of the Pickle. This "data" can be passed
|
||
|
// to a Pickle object to initialize it for reading.
|
||
|
//
|
||
|
// When reading from a Pickle object, it is important for the consumer to know
|
||
|
// what value types to read and in what order to read them as the Pickle does
|
||
|
// not keep track of the type of data written to it.
|
||
|
//
|
||
|
// The Pickle's data has a header which contains the size of the Pickle's
|
||
|
// payload. It can optionally support additional space in the header. That
|
||
|
// space is controlled by the header_size parameter passed to the Pickle
|
||
|
// constructor.
|
||
|
var Pickle = (function () {
|
||
|
function Pickle (buffer) {
|
||
|
if (buffer) {
|
||
|
this.initFromBuffer(buffer)
|
||
|
} else {
|
||
|
this.initEmpty()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Pickle.prototype.initEmpty = function () {
|
||
|
this.header = new Buffer(0)
|
||
|
this.headerSize = SIZE_UINT32
|
||
|
this.capacityAfterHeader = 0
|
||
|
this.writeOffset = 0
|
||
|
this.resize(PAYLOAD_UNIT)
|
||
|
this.setPayloadSize(0)
|
||
|
}
|
||
|
|
||
|
Pickle.prototype.initFromBuffer = function (buffer) {
|
||
|
this.header = buffer
|
||
|
this.headerSize = buffer.length - this.getPayloadSize()
|
||
|
this.capacityAfterHeader = CAPACITY_READ_ONLY
|
||
|
this.writeOffset = 0
|
||
|
if (this.headerSize > buffer.length) {
|
||
|
this.headerSize = 0
|
||
|
}
|
||
|
if (this.headerSize !== alignInt(this.headerSize, SIZE_UINT32)) {
|
||
|
this.headerSize = 0
|
||
|
}
|
||
|
if (this.headerSize === 0) {
|
||
|
this.header = new Buffer(0)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Pickle.prototype.createIterator = function () {
|
||
|
return new PickleIterator(this)
|
||
|
}
|
||
|
|
||
|
Pickle.prototype.toBuffer = function () {
|
||
|
return this.header.slice(0, this.headerSize + this.getPayloadSize())
|
||
|
}
|
||
|
|
||
|
Pickle.prototype.writeBool = function (value) {
|
||
|
return this.writeInt(value ? 1 : 0)
|
||
|
}
|
||
|
|
||
|
Pickle.prototype.writeInt = function (value) {
|
||
|
return this.writeBytes(value, SIZE_INT32, Buffer.prototype.writeInt32LE)
|
||
|
}
|
||
|
|
||
|
Pickle.prototype.writeUInt32 = function (value) {
|
||
|
return this.writeBytes(value, SIZE_UINT32, Buffer.prototype.writeUInt32LE)
|
||
|
}
|
||
|
|
||
|
Pickle.prototype.writeInt64 = function (value) {
|
||
|
return this.writeBytes(value, SIZE_INT64, Buffer.prototype.writeInt64LE)
|
||
|
}
|
||
|
|
||
|
Pickle.prototype.writeUInt64 = function (value) {
|
||
|
return this.writeBytes(value, SIZE_UINT64, Buffer.prototype.writeUInt64LE)
|
||
|
}
|
||
|
|
||
|
Pickle.prototype.writeFloat = function (value) {
|
||
|
return this.writeBytes(value, SIZE_FLOAT, Buffer.prototype.writeFloatLE)
|
||
|
}
|
||
|
|
||
|
Pickle.prototype.writeDouble = function (value) {
|
||
|
return this.writeBytes(value, SIZE_DOUBLE, Buffer.prototype.writeDoubleLE)
|
||
|
}
|
||
|
|
||
|
Pickle.prototype.writeString = function (value) {
|
||
|
var length = Buffer.byteLength(value, 'utf8')
|
||
|
if (!this.writeInt(length)) {
|
||
|
return false
|
||
|
}
|
||
|
return this.writeBytes(value, length)
|
||
|
}
|
||
|
|
||
|
Pickle.prototype.setPayloadSize = function (payloadSize) {
|
||
|
return this.header.writeUInt32LE(payloadSize, 0)
|
||
|
}
|
||
|
|
||
|
Pickle.prototype.getPayloadSize = function () {
|
||
|
return this.header.readUInt32LE(0)
|
||
|
}
|
||
|
|
||
|
Pickle.prototype.writeBytes = function (data, length, method) {
|
||
|
var dataLength = alignInt(length, SIZE_UINT32)
|
||
|
var newSize = this.writeOffset + dataLength
|
||
|
if (newSize > this.capacityAfterHeader) {
|
||
|
this.resize(Math.max(this.capacityAfterHeader * 2, newSize))
|
||
|
}
|
||
|
if (method != null) {
|
||
|
method.call(this.header, data, this.headerSize + this.writeOffset)
|
||
|
} else {
|
||
|
this.header.write(data, this.headerSize + this.writeOffset, length)
|
||
|
}
|
||
|
var endOffset = this.headerSize + this.writeOffset + length
|
||
|
this.header.fill(0, endOffset, endOffset + dataLength - length)
|
||
|
this.setPayloadSize(newSize)
|
||
|
this.writeOffset = newSize
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
Pickle.prototype.resize = function (newCapacity) {
|
||
|
newCapacity = alignInt(newCapacity, PAYLOAD_UNIT)
|
||
|
this.header = Buffer.concat([this.header, new Buffer(newCapacity)])
|
||
|
this.capacityAfterHeader = newCapacity
|
||
|
}
|
||
|
|
||
|
return Pickle
|
||
|
})()
|
||
|
|
||
|
module.exports = Pickle
|