├── .gitignore ├── .npmignore ├── .travis.yml ├── lib ├── exports.js └── pickle.js ├── test └── pickle-test.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | npm-debug.log 4 | *.swp 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /build 2 | /test 3 | *.log 4 | *~ 5 | npm-debug.log 6 | .node-version 7 | .npmignore 8 | .travis.yml 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - '4' 5 | 6 | branches: 7 | only: 8 | - master 9 | 10 | notifications: 11 | email: 12 | on_success: never 13 | on_failure: change 14 | -------------------------------------------------------------------------------- /lib/exports.js: -------------------------------------------------------------------------------- 1 | var Pickle = require('./pickle') 2 | 3 | module.exports = { 4 | createEmpty: function () { 5 | return new Pickle() 6 | }, 7 | 8 | createFromBuffer: function (buffer) { 9 | return new Pickle(buffer) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/pickle-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | var Pickle = require('..') 3 | 4 | var describe = global.describe 5 | var it = global.it 6 | 7 | describe('Pickle', function () { 8 | it('supports multi-byte characters', function () { 9 | var write = Pickle.createEmpty() 10 | write.writeString('女の子.txt') 11 | 12 | var read = Pickle.createFromBuffer(write.toBuffer()) 13 | assert.equal(read.createIterator().readString(), '女の子.txt') 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./lib/exports.js", 3 | "name": "chromium-pickle-js", 4 | "description": "Binary value packing and unpacking", 5 | "version": "0.2.0", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/electron/node-chromium-pickle-js.git" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/electron/node-chromium-pickle-js/issues" 13 | }, 14 | "scripts": { 15 | "test": "mocha test && standard" 16 | }, 17 | "devDependencies": { 18 | "mocha": "^3.0.2", 19 | "standard": "^8.0.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chromium-pickle-js [![Build Status](https://travis-ci.org/electron/node-chromium-pickle-js.svg?branch=master)](https://travis-ci.org/electron/node-chromium-pickle-js) 2 | 3 | This module ports Chromium's `Pickle` class to Node, see `Pickle`'s header for 4 | introduction: 5 | 6 | > This class provides facilities for basic binary value packing and unpacking. 7 | > 8 | > The Pickle class supports appending primitive values (ints, strings, etc.) 9 | > to a pickle instance. The Pickle instance grows its internal memory buffer 10 | > dynamically to hold the sequence of primitive values. The internal memory 11 | > buffer is exposed as the "data" of the Pickle. This "data" can be passed 12 | > to a Pickle object to initialize it for reading. 13 | > 14 | > When reading from a Pickle object, it is important for the consumer to know 15 | > what value types to read and in what order to read them as the Pickle does 16 | > not keep track of the type of data written to it. 17 | > 18 | > The Pickle's data has a header which contains the size of the Pickle's 19 | > payload. It can optionally support additional space in the header. That 20 | > space is controlled by the header_size parameter passed to the Pickle 21 | > constructor. 22 | 23 | ## Install 24 | 25 | ```bash 26 | $ npm install chromium-pickle-js 27 | ``` 28 | 29 | ## Usage 30 | 31 | ### createEmpty() 32 | 33 | Returns an empty `Pickle` object. 34 | 35 | ### createFromBuffer(buffer) 36 | 37 | * `buffer` Buffer 38 | 39 | Returns a `Pickle` object that initialized from a `buffer`. The data is not 40 | copied so you have to ensure the `buffer` lives when using the Pickle object, 41 | and you should never modify the Pickle object created this way. 42 | 43 | ### Pickle.createIterator() 44 | 45 | Returns a `PickleIterator` object that can be used to read data from this 46 | `Pickle` object. 47 | 48 | ### Pickle.toBuffer() 49 | 50 | Returns a `Buffer` object that contains this `Pickle` object's data. 51 | 52 | ### Pickle.writeBool(value) 53 | 54 | Writes `value` to `Pickle` object as `bool`. Returns `true` when succeeded and 55 | returns `false` when failed. 56 | 57 | ### Pickle.writeInt(value) 58 | 59 | Writes `value` to `Pickle` object as `int`. Returns `true` when succeeded and 60 | returns `false` when failed. 61 | 62 | ### Pickle.writeUInt32(value) 63 | 64 | Writes `value` to `Pickle` object as `uint32`. Returns `true` when succeeded and 65 | returns `false` when failed. 66 | 67 | ### Pickle.writeInt64(value) 68 | 69 | Writes `value` to `Pickle` object as `int64`. Returns `true` when succeeded and 70 | returns `false` when failed. 71 | 72 | ### Pickle.writeUInt64(value) 73 | 74 | Writes `value` to `Pickle` object as `uint64`. Returns `true` when succeeded and 75 | returns `false` when failed. 76 | 77 | ### Pickle.writeFloat(value) 78 | 79 | Writes `value` to `Pickle` object as `float`. Returns `true` when succeeded and 80 | returns `false` when failed. 81 | 82 | ### Pickle.writeDouble(value) 83 | 84 | Writes `value` to `Pickle` object as `Double`. Returns `true` when succeeded and 85 | returns `false` when failed. 86 | 87 | ### Pickle.writeString(str) 88 | 89 | * `str` String 90 | 91 | Writes `str` to `Pickle` object. Returns `true` when succeeded and returns 92 | `false` when failed. 93 | 94 | ### PickleIterator.readBool() 95 | 96 | Returns current value as `bool` and seeks to next data. A`TypeError` exception 97 | would be thrown when failed. 98 | 99 | ### PickleIterator.readInt() 100 | 101 | Returns current value as `int` and seeks to next data. A`TypeError` exception 102 | would be thrown when failed. 103 | 104 | ### PickleIterator.readUInt32() 105 | 106 | Returns current value as `uint32` and seeks to next data. A`TypeError` exception 107 | would be thrown when failed. 108 | 109 | ### PickleIterator.readInt64() 110 | 111 | Returns current value as `int64` and seeks to next data. A`TypeError` exception 112 | would be thrown when failed. 113 | 114 | ### PickleIterator.readUInt64() 115 | 116 | Returns current value as `uint64` and seeks to next data. A`TypeError` exception 117 | would be thrown when failed. 118 | 119 | ### PickleIterator.readFloat() 120 | 121 | Returns current value as `float` and seeks to next data. A`TypeError` exception 122 | would be thrown when failed. 123 | 124 | ### PickleIterator.readDouble() 125 | 126 | Returns current value as `double` and seeks to next data. A`TypeError` exception 127 | would be thrown when failed. 128 | 129 | ### PickleIterator.readString() 130 | 131 | Returns current value as `String` and seeks to next data. A`TypeError` exception 132 | would be thrown when failed. 133 | -------------------------------------------------------------------------------- /lib/pickle.js: -------------------------------------------------------------------------------- 1 | // sizeof(T). 2 | var SIZE_INT32 = 4 3 | var SIZE_UINT32 = 4 4 | var SIZE_INT64 = 8 5 | var SIZE_UINT64 = 8 6 | var SIZE_FLOAT = 4 7 | var SIZE_DOUBLE = 8 8 | 9 | // The allocation granularity of the payload. 10 | var PAYLOAD_UNIT = 64 11 | 12 | // Largest JS number. 13 | var CAPACITY_READ_ONLY = 9007199254740992 14 | 15 | // Aligns 'i' by rounding it up to the next multiple of 'alignment'. 16 | var alignInt = function (i, alignment) { 17 | return i + (alignment - (i % alignment)) % alignment 18 | } 19 | 20 | // PickleIterator reads data from a Pickle. The Pickle object must remain valid 21 | // while the PickleIterator object is in use. 22 | var PickleIterator = (function () { 23 | function PickleIterator (pickle) { 24 | this.payload = pickle.header 25 | this.payloadOffset = pickle.headerSize 26 | this.readIndex = 0 27 | this.endIndex = pickle.getPayloadSize() 28 | } 29 | 30 | PickleIterator.prototype.readBool = function () { 31 | return this.readInt() !== 0 32 | } 33 | 34 | PickleIterator.prototype.readInt = function () { 35 | return this.readBytes(SIZE_INT32, Buffer.prototype.readInt32LE) 36 | } 37 | 38 | PickleIterator.prototype.readUInt32 = function () { 39 | return this.readBytes(SIZE_UINT32, Buffer.prototype.readUInt32LE) 40 | } 41 | 42 | PickleIterator.prototype.readInt64 = function () { 43 | return this.readBytes(SIZE_INT64, Buffer.prototype.readInt64LE) 44 | } 45 | 46 | PickleIterator.prototype.readUInt64 = function () { 47 | return this.readBytes(SIZE_UINT64, Buffer.prototype.readUInt64LE) 48 | } 49 | 50 | PickleIterator.prototype.readFloat = function () { 51 | return this.readBytes(SIZE_FLOAT, Buffer.prototype.readFloatLE) 52 | } 53 | 54 | PickleIterator.prototype.readDouble = function () { 55 | return this.readBytes(SIZE_DOUBLE, Buffer.prototype.readDoubleLE) 56 | } 57 | 58 | PickleIterator.prototype.readString = function () { 59 | return this.readBytes(this.readInt()).toString() 60 | } 61 | 62 | PickleIterator.prototype.readBytes = function (length, method) { 63 | var readPayloadOffset = this.getReadPayloadOffsetAndAdvance(length) 64 | if (method != null) { 65 | return method.call(this.payload, readPayloadOffset, length) 66 | } else { 67 | return this.payload.slice(readPayloadOffset, readPayloadOffset + length) 68 | } 69 | } 70 | 71 | PickleIterator.prototype.getReadPayloadOffsetAndAdvance = function (length) { 72 | if (length > this.endIndex - this.readIndex) { 73 | this.readIndex = this.endIndex 74 | throw new Error('Failed to read data with length of ' + length) 75 | } 76 | var readPayloadOffset = this.payloadOffset + this.readIndex 77 | this.advance(length) 78 | return readPayloadOffset 79 | } 80 | 81 | PickleIterator.prototype.advance = function (size) { 82 | var alignedSize = alignInt(size, SIZE_UINT32) 83 | if (this.endIndex - this.readIndex < alignedSize) { 84 | this.readIndex = this.endIndex 85 | } else { 86 | this.readIndex += alignedSize 87 | } 88 | } 89 | 90 | return PickleIterator 91 | })() 92 | 93 | // This class provides facilities for basic binary value packing and unpacking. 94 | // 95 | // The Pickle class supports appending primitive values (ints, strings, etc.) 96 | // to a pickle instance. The Pickle instance grows its internal memory buffer 97 | // dynamically to hold the sequence of primitive values. The internal memory 98 | // buffer is exposed as the "data" of the Pickle. This "data" can be passed 99 | // to a Pickle object to initialize it for reading. 100 | // 101 | // When reading from a Pickle object, it is important for the consumer to know 102 | // what value types to read and in what order to read them as the Pickle does 103 | // not keep track of the type of data written to it. 104 | // 105 | // The Pickle's data has a header which contains the size of the Pickle's 106 | // payload. It can optionally support additional space in the header. That 107 | // space is controlled by the header_size parameter passed to the Pickle 108 | // constructor. 109 | var Pickle = (function () { 110 | function Pickle (buffer) { 111 | if (buffer) { 112 | this.initFromBuffer(buffer) 113 | } else { 114 | this.initEmpty() 115 | } 116 | } 117 | 118 | Pickle.prototype.initEmpty = function () { 119 | this.header = new Buffer(0) 120 | this.headerSize = SIZE_UINT32 121 | this.capacityAfterHeader = 0 122 | this.writeOffset = 0 123 | this.resize(PAYLOAD_UNIT) 124 | this.setPayloadSize(0) 125 | } 126 | 127 | Pickle.prototype.initFromBuffer = function (buffer) { 128 | this.header = buffer 129 | this.headerSize = buffer.length - this.getPayloadSize() 130 | this.capacityAfterHeader = CAPACITY_READ_ONLY 131 | this.writeOffset = 0 132 | if (this.headerSize > buffer.length) { 133 | this.headerSize = 0 134 | } 135 | if (this.headerSize !== alignInt(this.headerSize, SIZE_UINT32)) { 136 | this.headerSize = 0 137 | } 138 | if (this.headerSize === 0) { 139 | this.header = new Buffer(0) 140 | } 141 | } 142 | 143 | Pickle.prototype.createIterator = function () { 144 | return new PickleIterator(this) 145 | } 146 | 147 | Pickle.prototype.toBuffer = function () { 148 | return this.header.slice(0, this.headerSize + this.getPayloadSize()) 149 | } 150 | 151 | Pickle.prototype.writeBool = function (value) { 152 | return this.writeInt(value ? 1 : 0) 153 | } 154 | 155 | Pickle.prototype.writeInt = function (value) { 156 | return this.writeBytes(value, SIZE_INT32, Buffer.prototype.writeInt32LE) 157 | } 158 | 159 | Pickle.prototype.writeUInt32 = function (value) { 160 | return this.writeBytes(value, SIZE_UINT32, Buffer.prototype.writeUInt32LE) 161 | } 162 | 163 | Pickle.prototype.writeInt64 = function (value) { 164 | return this.writeBytes(value, SIZE_INT64, Buffer.prototype.writeInt64LE) 165 | } 166 | 167 | Pickle.prototype.writeUInt64 = function (value) { 168 | return this.writeBytes(value, SIZE_UINT64, Buffer.prototype.writeUInt64LE) 169 | } 170 | 171 | Pickle.prototype.writeFloat = function (value) { 172 | return this.writeBytes(value, SIZE_FLOAT, Buffer.prototype.writeFloatLE) 173 | } 174 | 175 | Pickle.prototype.writeDouble = function (value) { 176 | return this.writeBytes(value, SIZE_DOUBLE, Buffer.prototype.writeDoubleLE) 177 | } 178 | 179 | Pickle.prototype.writeString = function (value) { 180 | var length = Buffer.byteLength(value, 'utf8') 181 | if (!this.writeInt(length)) { 182 | return false 183 | } 184 | return this.writeBytes(value, length) 185 | } 186 | 187 | Pickle.prototype.setPayloadSize = function (payloadSize) { 188 | return this.header.writeUInt32LE(payloadSize, 0) 189 | } 190 | 191 | Pickle.prototype.getPayloadSize = function () { 192 | return this.header.readUInt32LE(0) 193 | } 194 | 195 | Pickle.prototype.writeBytes = function (data, length, method) { 196 | var dataLength = alignInt(length, SIZE_UINT32) 197 | var newSize = this.writeOffset + dataLength 198 | if (newSize > this.capacityAfterHeader) { 199 | this.resize(Math.max(this.capacityAfterHeader * 2, newSize)) 200 | } 201 | if (method != null) { 202 | method.call(this.header, data, this.headerSize + this.writeOffset) 203 | } else { 204 | this.header.write(data, this.headerSize + this.writeOffset, length) 205 | } 206 | var endOffset = this.headerSize + this.writeOffset + length 207 | this.header.fill(0, endOffset, endOffset + dataLength - length) 208 | this.setPayloadSize(newSize) 209 | this.writeOffset = newSize 210 | return true 211 | } 212 | 213 | Pickle.prototype.resize = function (newCapacity) { 214 | newCapacity = alignInt(newCapacity, PAYLOAD_UNIT) 215 | this.header = Buffer.concat([this.header, new Buffer(newCapacity)]) 216 | this.capacityAfterHeader = newCapacity 217 | } 218 | 219 | return Pickle 220 | })() 221 | 222 | module.exports = Pickle 223 | --------------------------------------------------------------------------------