Тема: JavaScript: Bencode - формат хранения и передачи данных BitTorrent
на досуге написал небольшой скрипт разбора и упаковки данных в Bencode - формат, используемый p2p системой распространения файлов BitTorrent для хранения и передачи свободно структурированных данных. Когда начинал писать были какие-то мысли на счет его использования. Однако со временем актуальность потерялась, а код остался. Некоторое время спустя возникло желание завершить написанное и поделиться с общественностью. Возможно кому-нибудь пригодится. Насколько помню, проблема упирается в корректную трансляцию порции бинарных данных. Полностью решить не удалось.
.
Ссылки
Подробно о формате данных можно почитать по следующим ссылкам:
http://ru.wikipedia.org/wiki/Bencode - русская википедия
http://en.wikipedia.org/wiki/Bencode - английская википедия
http://wiki.theory.org/BitTorrentSpecification - спецификация от разработчиков
На этих страницах можно найти и другие реализации с использованием различных языков программирования (также и JScript/JavaScript).
.
Описание библиотеки
Bencode.stringify(value)
Конвертирует объект в строку в формате bencode.
Bencode.parse(value)
Разбирает bencode строку в объект JavaScript. В случае некорректно сформированной строки бросает исключение RangeError. Это самая основная функция для разбора данных торрент-файла.
Bencode.PATH_DELIMITER = '\\';
Разделитель путей, по умолчанию. Это значение может быть изменено до вызова Bencode.torrentInfo().
Bencode.torrentInfo(value, key)
Рассматривает входной объект как торрент-объект и возвращает информацию, заданную параметром key. Если входное значение - строка, то она первоначально будет разобрана в торрент-объект.
Есть несколько специально заданных ключей для упрощения доступа к данных:
'name'
название торрента
'piece length'
количество байт в каждом фрагменте
'pieces'
строка, состоящая из 20-ибайтых SHA1-кодированных хешей. До сих пор эта информация у меня не вызывает доверия в силу того, что JScript не умеет корректно работать с байтовыми строками без привязки к кодировке. Возможно в NodeJS этих проблем не будет. Я не проверял.
'creation date'
дата создания торрент-файла. Эта информация конвертируется в формат типа Date.
'announce-list'
упрощенный список (массив) анонсов (в отличие от используемого для внутренних целей списка списков строк)
'file-names'
'file-sizes'
всегда (даже если файл один) список имен и список размеров всех файлов в торренте
.
Исходный код
Полная и актуальная версия файла находится по адресу http://code.google.com/p/jsxt/source/br … Bencode.js
var Bencode = Bencode || {};
(function()
{
var toString = Object.prototype.toString;
var stringify = function(value)
{
var typeOf = toString.call(value);
if ( typeOf == '[object Number]' ) {
return 'i' + parseInt(value) + 'e';
}
if ( typeOf == '[object String]' ) {
return value.length + ':' + value.toString();
}
if ( typeOf == '[object Date]' ) {
return 'i' + Math.floor(value.getTime() / 1000) + 'e';
}
var result = [];
if ( typeOf == '[object Array]' ) {
for (var i = 0; i < value.length; i++) {
result.push(stringify(value[i]));
}
return 'l' + result.join('') + 'e';
}
for (var p in value) {
if ( ! value.hasOwnProperty(p) ) {
continue;
}
result.push(stringify(String(p)) + stringify(value[p]));
}
return 'd' + result.join('') + 'e';
};
var i = 0;
var text;
var parser = function()
{
var c = text.charAt(i);
var err;
switch (c) {
case 'i':
err = 'integer';
var matches = text.slice(i).match(/^i(-?\d+)e/);
if ( ! matches ) {
break;
}
i += matches.lastIndex;
return Number(matches[1]);
case 'l':
err = 'list';
i++;
var result = [];
while ( i < text.length && text.charAt(i) != 'e' ) {
result.push(parser());
}
if ( text.charAt(i) != 'e' ) {
break;
}
i++;
return result;
case 'd':
err = 'dictionary';
i++;
var result = {};
while ( i < text.length && text.charAt(i) != 'e' ) {
var k = parser();
var v = parser();
result[k] = v;
}
if ( text.charAt(i) != 'e' ) {
break;
}
i++;
return result;
default:
err = 'string';
var matches = text.slice(i).match(/^(\d+):/);
if ( ! matches ) {
break;
}
var len = Number(matches[1]);
var a = i + matches.lastIndex;
var b = a + len;
var result = text.slice(a, b);
if ( result.length != len ) {
break;
}
i = b;
return result;
}
// Here is abnormal ending
throw new RangeError('Bencode.parse: Illegal ' + err + ' at ' + i + ' (0x' + i.toString(16).toUpperCase() + ')');
};
var torrentInfo = function(value, key)
{
switch ( key ) {
case 'name':
case 'piece length':
case 'pieces':
return value.info[key];
case 'creation date':
// convert date to the standard Date object
return new Date(value[key] * 1000);
case 'announce-list':
var input = value['announce-list'];
var result = [];
for (var i = 0; i < input.length; i++) {
// convert a list of lists of strings to
// the simple list of strings
result.push.apply(result, input[i]);
}
return result;
case 'file-names':
var input = value.info.files;
if ( ! input ) {
// torrent contains the single file
return [value.info.name];
}
var result = [];
for (var i = 0; i < input.length; i++) {
result.push(input[i].path.join(Bencode.PATH_DELIMITER));
}
return result;
case 'file-sizes':
var input = value.info.files;
if ( ! input ) {
// torrent contains the single file
return [value.info.length];
}
var result = [];
for (var i = 0; i < input.length; i++) {
result.push(input[i].length);
}
return result;
default:
return value[key];
}
};
Bencode.PATH_DELIMITER = '\\';
Bencode.stringify = stringify;
Bencode.parse = function(value)
{
i = 0;
text = (value || '').toString();
return parser();
};
Bencode.torrentInfo = function(value, key)
{
if ( toString.call(value) == '[object String]' ) {
value = Bencode.parse(value);
}
if ( ! value || ! value.info ) {
return;
}
return torrentInfo(value, key);
};
})();.
Пример использования
В качестве функции чтения торрент-файла использовалось стороннее решение http://demon.tw/my-work/javascript-bencode.html. Насколько я разобрался, это самый корректный из известных мне способов прочитать бинарные данные в JScript.
// http://demon.tw/my-work/javascript-bencode.html
function read(path) {
var cp1252Chars = [/\u20AC/g,/\u201A/g,/\u0192/g,/\u201E/g,/\u2026/g,/\u2020/g,/\u2021/g,/\u02C6/g,/\u2030/g,/\u0160/g,/\u2039/g,/\u0152/g,/\u017D/g,/\u2018/g,/\u2019/g,/\u201C/g,/\u201D/g,/\u2022/g,/\u2013/g,/\u2014/g,/\u02DC/g,/\u2122/g,/\u0161/g,/\u203A/g,/\u0153/g,/\u017E/g,/\u0178/g];
var latin1Chars = ["\u0080","\u0082","\u0083","\u0084","\u0085","\u0086","\u0087","\u0088","\u0089","\u008A","\u008B","\u008C","\u008E","\u0091","\u0092","\u0093","\u0094","\u0095","\u0096","\u0097","\u0098","\u0099","\u009A","\u009B","\u009C","\u009E","\u009F"];
var binstream = new ActiveXObject("ADODB.Stream");
binstream.Type = 2;
binstream.Charset = "iso-8859-1";
binstream.Open();
binstream.LoadFromFile(path);
var s = binstream.ReadText();
for (var i = 0; i < 27; i++)
s = s.replace(cp1252Chars[i], latin1Chars[i]);
return s;
}
var filename = 'foo.torrent';
var text = read(filename);
var torrent = Bencode.parse(text);
WScript.Echo( Bencode.torrentInfo(torrent, 'name') );
WScript.Echo( Bencode.torrentInfo(torrent, 'creation date') );
WScript.Echo( Bencode.torrentInfo(torrent, 'file-names') );
