Skip to content

Commit abf99e6

Browse files
committed
Rewrote bencode.js
- Improved speed (see benchmark) - Moved anonymous functions out of `#encode()` and `#decode()` - `#decode()` now takes in a buffer (instead of a string) - `#decode()` got an additional parameter, to automatically convert bytestring to strings - Improved legibility (IMHO) - Removed deprecated and unused `require( 'sys' )`
1 parent 0c3ff65 commit abf99e6

File tree

1 file changed

+161
-145
lines changed

1 file changed

+161
-145
lines changed

bencode.js

Lines changed: 161 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -1,163 +1,179 @@
1-
/*
2-
* ----------------------------------------------------------------------------
3-
* node-bencode v0.1.0
4-
* <[email protected]> wrote this file. As long as you retain this notice
5-
* you can do whatever you want with this stuff. If we meet some day, and you
6-
* think this stuff is worth it, you can buy me a beer in return. Mark Schmale
7-
* ----------------------------------------------------------------------------
8-
*/
9-
var sys = require('sys');
101

112
/**
12-
* decodes a bencoded string
3+
* Encodes data in bencode.
4+
*
5+
* @param {Buffer|Array|String|Object|Number} data
6+
* @return {String}
137
*/
14-
exports.decode = function decode(str) {
15-
var arr = str.split('');
16-
17-
function integer(start) {
18-
var c = '', n = '', i = 0, mul = 1;
19-
var len = arr.length;
20-
if(arr[start] === '-') {
21-
mul = -1;
22-
start = start+1;
23-
}
24-
for(i=start;i<len;i++) {
25-
c = arr[i];
26-
if(c == parseInt(c)) {
27-
n = n + c;
28-
} else {
29-
break;
30-
}
31-
}
32-
return {next: i+1, ret: mul*parseInt(n)};
33-
}
34-
35-
function text(start) {
36-
var nfo = integer(start),
37-
start = nfo.next,
38-
len = nfo.ret
39-
i = 0
40-
full = '';
41-
42-
for(i=start;i<len+start;i++) {
43-
full = full + arr[i]
44-
}
45-
return {next: i, ret: full};
46-
}
47-
48-
function list(start) {
49-
var len = arr.length,
50-
i = start+1,
51-
list = [],
52-
tmp = {},
53-
lcnt = 0;
54-
while(i<len && arr[i] !== 'e') {
55-
tmp = next(i);
56-
i = tmp.next;
57-
list[lcnt] = tmp.ret;
58-
lcnt++;
59-
}
60-
return {next: i+1, ret: list};
61-
}
8+
function encode( data ) {
9+
10+
var out = ''
11+
12+
switch( typeof data ) {
13+
case 'string': out = encode.bytes( data )
14+
break
15+
case 'number': out = encode.number( data )
16+
break
17+
case 'object':
18+
out = data.constructor === Array
19+
? encode.list( data )
20+
: encode.dict( data )
21+
break
22+
}
23+
24+
return out
25+
26+
}
6227

63-
function dictionary(start) {
64-
var len = arr.length,
65-
i = start+1,
66-
list = {},
67-
tmp = {},
68-
isKey = true,
69-
key = '';
70-
while(i<len && arr[i] !== 'e') {
71-
tmp = next(i);
72-
i = tmp.next;
73-
if(isKey === true) {
74-
key = tmp.ret;
75-
} else {
76-
list[key] = tmp.ret;
77-
}
78-
isKey = !isKey;
79-
}
80-
return {next: i+1, ret: list};
81-
}
28+
encode.bytes = function( data ) {
29+
return data.length + ':' + data
30+
}
8231

32+
encode.number = function( data ) {
33+
return 'i' + data + 'e'
34+
}
8335

84-
function next(start) {
85-
var data = 0;
86-
start = start || 0;
87-
switch(arr[start]) {
88-
case 'i': // integer
89-
data = integer(start+1);
90-
break;
91-
case 'l': // liste
92-
data = list(start);
93-
break;
94-
case 'd': // dict
95-
data = dictionary(start);
96-
break;
97-
default: // string
98-
data = text(start);
99-
}
100-
return data;
101-
}
36+
encode.dict = function( data ) {
37+
38+
var dict = 'd'
39+
40+
for( var k in data ) {
41+
dict += encode( k ) + encode( data[k] )
42+
}
43+
44+
return dict + 'e'
45+
46+
}
10247

103-
return next(0).ret;
48+
encode.list = function( data ) {
49+
50+
var i = 0
51+
var c = data.length
52+
var lst = 'l'
53+
54+
for( ; i < c; i++ ) {
55+
lst += encode( data[i] )
56+
}
57+
58+
return lst + 'e'
59+
10460
}
10561

106-
exports.encode = function encode(data) {
107-
/* sys.puts(sys.inspect(typeof(data)));
108-
sys.puts(sys.inspect(is_array(data)));*/
109-
110-
function encode_string(data) {
111-
return data.length + ":" + data;
112-
}
62+
/**
63+
* Decodes bencoded data.
64+
*
65+
* @param {Buffer} data
66+
* @param {Boolean} toString
67+
* @return {Object|Array|Buffer|String|Number}
68+
*/
69+
function decode( data, toString ) {
70+
71+
if( !(this instanceof decode) ) {
72+
return new decode( data, toString )
73+
}
74+
75+
this.stringify = !!toString
76+
77+
this.data = data
78+
79+
return this.next()
80+
81+
}
11382

114-
function encode_integer(data) {
115-
return "i" + data + "e";
83+
decode.prototype = {
84+
85+
next: function() {
86+
87+
switch( this.data[0] ) {
88+
case 0x64: return this.dictionary()
89+
case 0x6C: return this.list()
90+
case 0x69: return this.integer()
91+
default: return this.bytes()
11692
}
117-
118-
function encode_list(data) {
119-
var max = data.length;
120-
var i = 0;
121-
var str = "l";
122-
for(i=0;i<max;i++) {
123-
str = str + encode(data[i]);
124-
}
125-
str = str + "e";
126-
return str;
93+
94+
},
95+
96+
find: function( needle ) {
97+
98+
var i = 0
99+
var length = this.data.length
100+
101+
for( ; i < length; i++ ) {
102+
if( this.data[i] === needle ) {
103+
return i
104+
}
127105
}
128-
129-
function encode_dict(data) {
130-
var str = "d";
131-
for(var key in data) {
132-
str = str + encode_string(key) + encode(data[key]);
133-
}
134-
str = str + "e";
135-
return str;
106+
107+
return -1
108+
109+
},
110+
111+
forward: function( index ) {
112+
this.data = this.data.slice(
113+
index, this.data.length
114+
)
115+
},
116+
117+
dictionary: function() {
118+
119+
this.forward( 1 )
120+
121+
var dict = {}
122+
123+
while( this.data[0] !== 0x65 ) {
124+
dict[ this.next() ] = this.next()
136125
}
126+
127+
this.forward( 1 )
128+
129+
return dict
130+
131+
},
137132

138-
/** helper **/
139-
function is_array(obj) {
140-
return obj.constructor == Array;
133+
list: function() {
134+
135+
this.forward( 1 )
136+
137+
var lst = []
138+
139+
while( this.data[0] !== 0x65 ) {
140+
lst.push( this.next() )
141141
}
142+
143+
this.forward( 1 )
144+
145+
return lst
146+
147+
},
142148

143-
var str = "";
144-
145-
switch(typeof(data)) {
146-
case 'object':
147-
if(is_array(data) === true) {
148-
str = encode_list(data);
149-
} else {
150-
str = encode_dict(data);
151-
}
152-
break;
153-
154-
case 'number':
155-
str = encode_integer(data);
156-
break;
149+
integer: function() {
150+
151+
var end = this.find( 0x65 )
152+
var number = this.data.slice( 1, end )
153+
154+
this.forward( end + 1 )
155+
156+
return +number
157+
158+
},
157159

158-
case 'string':
159-
str = encode_string(data);
160-
break;
161-
}
162-
return str;
160+
bytes: function() {
161+
162+
var sep = this.find( 0x3A )
163+
var length = +this.data.slice( 0, sep ).toString()
164+
var sepl = sep + 1 + length
165+
var bytes = this.data.slice( sep + 1, sepl )
166+
167+
this.forward( sepl )
168+
169+
return this.stringify
170+
? bytes.toString()
171+
: bytes
172+
173+
}
174+
163175
}
176+
177+
// Expose methods
178+
exports.encode = encode
179+
exports.decode = decode

0 commit comments

Comments
 (0)