diff --git a/bignum/index.js b/bignum/index.js new file mode 100644 index 0000000..db1002d --- /dev/null +++ b/bignum/index.js @@ -0,0 +1,18 @@ +var BigNumber = require('bignumber.js') +var bencode = module.exports + +bencode.encode = require('../lib/encode') +bencode.decode = require('../lib/decode') + +bencode.encode.number = function (buffers, data) { + buffers.push(new Buffer('i' + data.toString() + 'e')) +} + +bencode.decode.integer = function () { + var end = bencode.decode.find(0x65) + var number = bencode.decode.data.toString('ascii', bencode.decode.position + 1, end) + + bencode.decode.position += end + 1 - bencode.decode.position + + return new BigNumber(number, 10) +} diff --git a/lib/decode.js b/lib/decode.js deleted file mode 100644 index e51289b..0000000 --- a/lib/decode.js +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Decodes bencoded data. - * - * @param {Buffer} data - * @param {Number} start (optional) - * @param {Number} end (optional) - * @param {String} encoding (optional) - * @return {Object|Array|Buffer|String|Number} - */ -function decode (data, start, end, encoding) { - if (typeof start !== 'number' && encoding == null) { - encoding = start - start = undefined - } - - if (typeof end !== 'number' && encoding == null) { - encoding = end - end = undefined - } - - decode.position = 0 - decode.encoding = encoding || null - - decode.data = !(Buffer.isBuffer(data)) - ? new Buffer(data) - : data.slice(start, end) - - decode.bytes = decode.data.length - - return decode.next() -} - -decode.bytes = 0 -decode.position = 0 -decode.data = null -decode.encoding = null - -decode.next = function () { - switch (decode.data[decode.position]) { - case 0x64: - return decode.dictionary() - case 0x6C: - return decode.list() - case 0x69: - return decode.integer() - default: - return decode.buffer() - } -} - -decode.find = function (chr) { - var i = decode.position - var c = decode.data.length - var d = decode.data - - while (i < c) { - if (d[i] === chr) return i - i++ - } - - throw new Error( - 'Invalid data: Missing delimiter "' + - String.fromCharCode(chr) + '" [0x' + - chr.toString(16) + ']' - ) -} - -decode.dictionary = function () { - decode.position++ - - var dict = {} - - while (decode.data[decode.position] !== 0x65) { - dict[decode.buffer()] = decode.next() - } - - decode.position++ - - return dict -} - -decode.list = function () { - decode.position++ - - var lst = [] - - while (decode.data[decode.position] !== 0x65) { - lst.push(decode.next()) - } - - decode.position++ - - return lst -} - -decode.integer = function () { - var end = decode.find(0x65) - var number = decode.data.toString('ascii', decode.position + 1, end) - - decode.position += end + 1 - decode.position - - return parseInt(number, 10) -} - -decode.buffer = function () { - var sep = decode.find(0x3A) - var length = parseInt(decode.data.toString('ascii', decode.position, sep), 10) - var end = ++sep + length - - decode.position = end - - return decode.encoding - ? decode.data.toString(decode.encoding, sep, end) - : decode.data.slice(sep, end) -} - -module.exports = decode diff --git a/lib/decoder.js b/lib/decoder.js new file mode 100644 index 0000000..7e45ce3 --- /dev/null +++ b/lib/decoder.js @@ -0,0 +1,128 @@ +/** + * Decoder + * @return {Decoder} + */ +function Decoder () { + if (!(this instanceof Decoder)) { + return new Decoder() + } + + this.bytes = 0 + this.position = 0 + this.data = null + this.encoding = null +} + +/** + * Decoder prototype + * @type {Object} + */ +Decoder.prototype = { + + constructor: Decoder, + + use: function (type, fn) { + this[type] = fn + }, + + decode: function (data, start, end, encoding) { + if (typeof start !== 'number' && encoding == null) { + encoding = start + start = undefined + } + + if (typeof end !== 'number' && encoding == null) { + encoding = end + end = undefined + } + + this.position = 0 + this.encoding = encoding || null + + this.data = !(Buffer.isBuffer(data)) + ? new Buffer(data) + : data.slice(start, end) + + this.bytes = this.data.length + + return this.next() + }, + + next: function () { + switch (this.data[this.position]) { + case 0x64: return this.object() + case 0x6C: return this.array() + case 0x69: return this.number() + default: return this.buffer() + } + }, + + find: function (chr) { + var i = this.position + var c = this.data.length + var d = this.data + + while (i < c) { + if (d[i] === chr) return i + i++ + } + + throw new Error( + 'Invalid data: Missing delimiter "' + String.fromCharCode(chr) + + '" [0x' + chr.toString(16) + ']' + ) + }, + + object: function () { + this.position++ + + var dict = {} + + while (this.data[this.position] !== 0x65) { + dict[this.buffer()] = this.next() + } + + this.position++ + + return dict + }, + + array: function () { + this.position++ + + var lst = [] + + while (this.data[this.position] !== 0x65) { + lst.push(this.next()) + } + + this.position++ + + return lst + }, + + number: function () { + var end = this.find(0x65) + var number = this.data.toString('ascii', this.position + 1, end) + + this.position += end + 1 - this.position + + return parseInt(number, 10) + }, + + buffer: function () { + var sep = this.find(0x3A) + var length = parseInt(this.data.toString('ascii', this.position, sep), 10) + var end = ++sep + length + + this.position = end + + return this.encoding + ? this.data.toString(this.encoding, sep, end) + : this.data.slice(sep, end) + } + +} + +// Exports +module.exports = Decoder diff --git a/lib/encode.js b/lib/encode.js deleted file mode 100644 index c3311cb..0000000 --- a/lib/encode.js +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Encodes data in bencode. - * - * @param {Buffer|Array|String|Object|Number|Boolean} data - * @return {Buffer} - */ -function encode (data, buffer, offset) { - var buffers = [] - var result = null - - encode._encode(buffers, data) - result = Buffer.concat(buffers) - encode.bytes = result.length - - if (Buffer.isBuffer(buffer)) { - result.copy(buffer, offset) - return buffer - } - - return result -} - -encode.bytes = -1 -encode._floatConversionDetected = false - -encode._encode = function (buffers, data) { - if (Buffer.isBuffer(data)) { - buffers.push(new Buffer(data.length + ':')) - buffers.push(data) - return - } - - switch (typeof data) { - case 'string': - encode.buffer(buffers, data) - break - case 'number': - encode.number(buffers, data) - break - case 'object': - data.constructor === Array - ? encode.list(buffers, data) - : encode.dict(buffers, data) - break - case 'boolean': - encode.number(buffers, data ? 1 : 0) - break - } -} - -var buffE = new Buffer('e') -var buffD = new Buffer('d') -var buffL = new Buffer('l') - -encode.buffer = function (buffers, data) { - buffers.push(new Buffer(Buffer.byteLength(data) + ':' + data)) -} - -encode.number = function (buffers, data) { - var maxLo = 0x80000000 - var hi = (data / maxLo) << 0 - var lo = (data % maxLo) << 0 - var val = hi * maxLo + lo - - buffers.push(new Buffer('i' + val + 'e')) - - if (val !== data && !encode._floatConversionDetected) { - encode._floatConversionDetected = true - console.warn( - 'WARNING: Possible data corruption detected with value "' + data + '":', - 'Bencoding only defines support for integers, value was converted to "' + val + '"' - ) - console.trace() - } -} - -encode.dict = function (buffers, data) { - buffers.push(buffD) - - var j = 0 - var k - // fix for issue #13 - sorted dicts - var keys = Object.keys(data).sort() - var kl = keys.length - - for (; j < kl; j++) { - k = keys[j] - encode.buffer(buffers, k) - encode._encode(buffers, data[k]) - } - - buffers.push(buffE) -} - -encode.list = function (buffers, data) { - var i = 0 - var c = data.length - buffers.push(buffL) - - for (; i < c; i++) { - encode._encode(buffers, data[i]) - } - - buffers.push(buffE) -} - -module.exports = encode diff --git a/lib/encoder.js b/lib/encoder.js new file mode 100644 index 0000000..837ad55 --- /dev/null +++ b/lib/encoder.js @@ -0,0 +1,138 @@ +/** + * Encoder + * @return {Encoder} + */ +function Encoder () { + if (!(this instanceof Encoder)) { + return new Encoder() + } + + this.bytes = -1 + this._types = Encoder.types.slice() + this._floatConversionDetected = false +} + +Encoder.types = [ + function isString (value) { return typeof value === 'string' ? 'string' : void 0 }, + function isNumber (value) { return typeof value === 'number' ? 'number' : void 0 }, + function isBoolean (value) { return typeof value === 'boolean' ? 'boolean' : void 0 }, + function isBuffer (value) { return Buffer.isBuffer(value) ? 'buffer' : void 0 }, + function isArray (value) { return Array.isArray(value) ? 'array' : void 0 }, + function isObject (value) { return typeof value === 'object' ? 'object' : void 0 } +] + +var buffE = new Buffer('e') +var buffD = new Buffer('d') +var buffL = new Buffer('l') + +/** + * Encoder prototype + * @type {Object} + */ +Encoder.prototype = { + + constructor: Encoder, + + use: function (type, fn, check) { + this[type] = fn + this._types.unshift(function (value) { + return check(value) ? type : void 0 + }) + }, + + encode: function (data, buffer, offset) { + var buffers = [] + var result = null + + this._encode(buffers, data) + result = Buffer.concat(buffers) + this.bytes = result.length + + if (Buffer.isBuffer(buffer)) { + result.copy(buffer, offset) + return buffer + } + + return result + }, + + _typeof: function (value) { + var type + var len = this._types.length + + for (var i = 0; i < len; i++) { + type = this._types[i](value) + if (type) return type + } + }, + + _encode: function (buffers, data) { + var type = this._typeof(data) + this[type](buffers, data) + }, + + buffer: function (buffers, data) { + buffers.push(new Buffer(data.length + ':')) + buffers.push(data) + }, + + string: function (buffers, data) { + buffers.push(new Buffer(Buffer.byteLength(data) + ':' + data)) + }, + + boolean: function (buffers, data) { + this.number(buffers, data ? 1 : 0) + }, + + number: function (buffers, data) { + var maxLo = 0x80000000 + var hi = (data / maxLo) << 0 + var lo = (data % maxLo) << 0 + var val = hi * maxLo + lo + + buffers.push(new Buffer('i' + val + 'e')) + + if (val !== data && !this._floatConversionDetected) { + this._floatConversionDetected = true + console.warn( + 'WARNING: Possible data corruption detected with value "' + data + '":', + 'Bencoding only defines support for integers, value was converted to "' + val + '"' + ) + console.trace() + } + }, + + object: function (buffers, data) { + buffers.push(buffD) + + var j = 0 + var k + // fix for issue #13 - sorted dicts + var keys = Object.keys(data).sort() + var kl = keys.length + + for (; j < kl; j++) { + k = keys[j] + this.string(buffers, k) + this._encode(buffers, data[k]) + } + + buffers.push(buffE) + }, + + array: function (buffers, data) { + var i = 0 + var c = data.length + buffers.push(buffL) + + for (; i < c; i++) { + this._encode(buffers, data[i]) + } + + buffers.push(buffE) + } + +} + +// Exports +module.exports = Encoder diff --git a/lib/index.js b/lib/index.js index 1a0f428..99ea94c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,7 +1,33 @@ -var bencode = module.exports +/** + * @constructor + * @return {Bencode} + */ +function Bencode () { + if (!(this instanceof Bencode)) { + return new Bencode() + } + + this.encoder = new Bencode.Encoder() + this.decoder = new Bencode.Decoder() +} + +Bencode.Encoder = require('./encoder') +Bencode.Decoder = require('./decoder') -bencode.encode = require('./encode') -bencode.decode = require('./decode') +var encoder = new Bencode.Encoder() +var decoder = new Bencode.Decoder() + +Bencode.encode = function (data, buffer, offset) { + var result = encoder.encode(data, buffer, offset) + Bencode.encode.bytes = encoder.bytes + return result +} + +Bencode.decode = function (data, start, end, encoding) { + var result = decoder.decode(data, start, end, encoding) + Bencode.decode.bytes = decoder.bytes + return result +} /** * Determines the amount of bytes @@ -9,6 +35,45 @@ bencode.decode = require('./decode') * @param {Object|Array|Buffer|String|Number|Boolean} value * @return {Number} byteCount */ -bencode.byteLength = bencode.encodingLength = function (value) { - return bencode.encode(value).length +Bencode.byteLength = Bencode.encodingLength = function (value) { + return Bencode.encode(value).length } + +/** + * Bencode prototype + * @type {Object} + */ +Bencode.prototype = { + + constructor: Bencode, + + encode: function (data, buffer, offset) { + var result = this.encoder.encode(data, buffer, offset) + this.encode.bytes = this.encoder.bytes + return result + }, + + decode: function (data, start, end, encoding) { + var result = this.decoder.decode(data, start, end, encoding) + this.decode.bytes = this.decoder.bytes + return result + }, + + use: function (customType, type, fns) { + this.encoder.use(customType, fns.encode, fns.check) + this.decoder.use(type, fns.decode) + return this + }, + + byteLength: function (value) { + return this.encode(value).length + }, + + encodingLength: function (value) { + return this.byteLength(value) + } + +} + +// Exports +module.exports = Bencode diff --git a/package.json b/package.json index 720fe50..bf499d7 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,11 @@ "url": "https://jhermsmeier.de/" } ], + "dependencies": {}, "devDependencies": { "bencoding": "latest", + "bignumber.js": "latest", + "bn.js": "latest", "bncode": "latest", "dht-bencode": "latest", "dht.js": "latest",