Skip to content

Commit c68988b

Browse files
authored
Merge pull request #89 from themasch/encoding-length
Add dedicated bencode.encodingLength() implementation
2 parents 9e4d840 + 2df7cd4 commit c68988b

File tree

5 files changed

+166
-16
lines changed

5 files changed

+166
-16
lines changed

lib/encode.js

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
var Buffer = require('safe-buffer').Buffer
2+
var { getType } = require('./util')
23

34
/**
45
* Encodes data in bencode.
@@ -25,22 +26,10 @@ function encode (data, buffer, offset) {
2526
encode.bytes = -1
2627
encode._floatConversionDetected = false
2728

28-
encode.getType = function (value) {
29-
if (Buffer.isBuffer(value)) return 'buffer'
30-
if (Array.isArray(value)) return 'array'
31-
if (ArrayBuffer.isView(value)) return 'arraybufferview'
32-
if (value instanceof Number) return 'number'
33-
if (value instanceof Boolean) return 'boolean'
34-
if (value instanceof Set) return 'set'
35-
if (value instanceof Map) return 'map'
36-
if (value instanceof ArrayBuffer) return 'arraybuffer'
37-
return typeof value
38-
}
39-
4029
encode._encode = function (buffers, data) {
4130
if (data == null) { return }
4231

43-
switch (encode.getType(data)) {
32+
switch (getType(data)) {
4433
case 'buffer': encode.buffer(buffers, data); break
4534
case 'object': encode.dict(buffers, data); break
4635
case 'map': encode.dictMap(buffers, data); break

lib/encoding-length.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
var { digitCount, getType } = require('./util')
2+
3+
function listLength (list) {
4+
var length = 1 + 1 // type marker + end-of-type marker
5+
6+
for (let value of list) {
7+
length += encodingLength(value)
8+
}
9+
10+
return length
11+
}
12+
13+
function mapLength (map) {
14+
var length = 1 + 1 // type marker + end-of-type marker
15+
16+
for (let [ key, value ] of map) {
17+
let keyLength = Buffer.byteLength(key)
18+
length += digitCount(keyLength) + 1 + keyLength
19+
length += encodingLength(value)
20+
}
21+
22+
return length
23+
}
24+
25+
function objectLength (value) {
26+
var length = 1 + 1 // type marker + end-of-type marker
27+
var keys = Object.keys(value)
28+
29+
for (var i = 0; i < keys.length; i++) {
30+
let keyLength = Buffer.byteLength(keys[i])
31+
length += digitCount(keyLength) + 1 + keyLength
32+
length += encodingLength(value[ keys[i] ])
33+
}
34+
35+
return length
36+
}
37+
38+
function stringLength (value) {
39+
var length = Buffer.byteLength(value)
40+
return digitCount(length) + 1 + length
41+
}
42+
43+
function arrayBufferLength (value) {
44+
var length = value.byteLength - value.byteOffset
45+
return digitCount(length) + 1 + length
46+
}
47+
48+
function encodingLength (value) {
49+
var length = 0
50+
51+
if (value == null) return length
52+
53+
var type = getType(value)
54+
55+
switch (type) {
56+
case 'buffer': return digitCount(value.length) + 1 + value.length
57+
case 'arraybufferview': return arrayBufferLength(value)
58+
case 'string': return stringLength(value)
59+
case 'array': case 'set': return listLength(value)
60+
case 'number': return 1 + digitCount(Math.floor(value)) + 1
61+
case 'bigint': return 1 + value.toString().length + 1
62+
case 'object': return objectLength(value)
63+
case 'map': return mapLength(value)
64+
default:
65+
throw new TypeError(`Unsupported value of type "${type}"`)
66+
}
67+
}
68+
69+
module.exports = encodingLength

lib/index.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,4 @@ bencode.decode = require('./decode')
99
* @param {Object|Array|Buffer|String|Number|Boolean} value
1010
* @return {Number} byteCount
1111
*/
12-
bencode.byteLength = bencode.encodingLength = function (value) {
13-
return bencode.encode(value).length
14-
}
12+
bencode.byteLength = bencode.encodingLength = require('./encoding-length')

lib/util.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
var util = module.exports
2+
3+
util.digitCount = function digitCount (value) {
4+
// Add a digit for negative numbers, as the sign will be prefixed
5+
var sign = value < 0 ? 1 : 0
6+
// Guard against negative numbers & zero going into log10(),
7+
// as that would return -Infinity
8+
value = Math.abs(Number(value || 1))
9+
return Math.floor(Math.log10(value)) + 1 + sign
10+
}
11+
12+
util.getType = function getType (value) {
13+
if (Buffer.isBuffer(value)) return 'buffer'
14+
if (ArrayBuffer.isView(value)) return 'arraybufferview'
15+
if (Array.isArray(value)) return 'array'
16+
if (value instanceof Number) return 'number'
17+
if (value instanceof Boolean) return 'boolean'
18+
if (value instanceof Set) return 'set'
19+
if (value instanceof Map) return 'map'
20+
if (value instanceof String) return 'string'
21+
if (value instanceof ArrayBuffer) return 'arraybuffer'
22+
return typeof value
23+
}

test/encoding-length.test.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
var fs = require('fs')
2+
var path = require('path')
3+
var test = require('tape').test
4+
var bencode = require('..')
5+
6+
var torrent = fs.readFileSync(
7+
path.join(__dirname, '..', 'benchmark', 'test.torrent')
8+
)
9+
10+
test('encoding-length', function (t) {
11+
t.test('torrent', function (t) {
12+
var value = bencode.decode(torrent)
13+
var length = bencode.encodingLength(value)
14+
t.plan(1)
15+
t.equal(length, torrent.length)
16+
})
17+
18+
t.test('returns correct length for empty dictionaries', function (t) {
19+
t.plan(2)
20+
t.equal(bencode.encodingLength({}), 2) // de
21+
t.equal(bencode.encodingLength(new Map()), 2) // de
22+
})
23+
24+
t.test('returns correct length for dictionaries', function (t) {
25+
t.plan(2)
26+
var obj = { a: 1, b: 'str', c: { de: 'f' } }
27+
var map = new Map([
28+
[ 'a', 1 ],
29+
[ 'b', 'str' ],
30+
[ 'c', { de: 'f' } ]
31+
])
32+
t.equal(bencode.encodingLength(obj), 28) // d1:ai1e1:b3:str1:cd2:de1:fee
33+
t.equal(bencode.encodingLength(map), 28) // d1:ai1e1:b3:str1:cd2:de1:fee
34+
})
35+
36+
t.test('returns correct length for empty lists', function (t) {
37+
t.plan(2)
38+
t.equal(bencode.encodingLength([]), 2) // le
39+
t.equal(bencode.encodingLength(new Set()), 2) // le
40+
})
41+
42+
t.test('returns correct length for lists', function (t) {
43+
t.plan(3)
44+
t.equal(bencode.encodingLength([1, 2, 3]), 11) // li1ei2ei3ee
45+
t.equal(bencode.encodingLength([1, 'string', [{ a: 1, b: 2 }]]), 29) // li1e6:stringld1:ai1e1:bi2eeee
46+
t.equal(bencode.encodingLength(new Set([1, 'string', [{ a: 1, b: 2 }]])), 29) // li1e6:stringld1:ai1e1:bi2eeee
47+
})
48+
49+
t.test('returns correct length for integers', function (t) {
50+
t.plan(2)
51+
t.equal(bencode.encodingLength(-0), 3) // i0e
52+
t.equal(bencode.encodingLength(-1), 4) // i-1e
53+
})
54+
55+
t.test('returns integer part length for floating point numbers', function (t) {
56+
t.plan(1)
57+
t.equal(bencode.encodingLength(100.25), 1 + 1 + 3)
58+
})
59+
60+
t.test('returns correct length for BigInts', function (t) {
61+
t.plan(1)
62+
// 2n ** 128n == 340282366920938463463374607431768211456
63+
t.equal(bencode.encodingLength(340282366920938463463374607431768211456), 1 + 1 + 39)
64+
})
65+
66+
t.test('returns zero for undefined or null values', function (t) {
67+
t.plan(2)
68+
t.equal(bencode.encodingLength(null), 0)
69+
t.equal(bencode.encodingLength(undefined), 0)
70+
})
71+
})

0 commit comments

Comments
 (0)