chore: remove unused bdecode logic as it is now in libsession util

pull/2620/head
Audric Ackermann 2 years ago
parent 3c58f9c1e4
commit 37e335097e

@ -1,268 +0,0 @@
import { from_string, to_string } from 'libsodium-wrappers-sumo';
import { isArray, isEmpty, isNumber, isPlainObject, isString, toNumber } from 'lodash';
import { StringUtils } from '.';
const e = 'e'; // end of whatever was before
const l = 'l'; // list of values
const i = 'i'; // start of integer
const d = 'd'; // start of dictionary
const colon = ':';
const eCode = e.charCodeAt(0); // end of whatever was before
const lCode = l.charCodeAt(0); // list of values
const iCode = i.charCodeAt(0); // start of integer
const dCode = d.charCodeAt(0); // start of dictionary
const colonCode = colon.charCodeAt(0);
interface BencodeDictType {
[key: string]: BencodeElementType;
}
type BencodeArrayType = Array<BencodeElementType>;
type BencodeElementType = number | string | BencodeDictType | BencodeArrayType;
const NUMBERS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
export class BDecode {
private readonly content: Uint8Array;
private currentParsingIndex = 0;
private readonly parsedContent: BencodeElementType;
constructor(content: Uint8Array | string) {
this.content = isString(content) ? from_string(content) : content;
this.parsedContent = this.parseContent();
}
public getParsedContent() {
return this.parsedContent;
}
/**
* Decode an int from a byte array starting with charCode of `i` and ending with charCode `e`
*/
private parseInt(): number {
if (this.currentParsingIndex >= this.content.length) {
throw new Error('parseInt: out of bounds');
}
if (this.content[this.currentParsingIndex] !== iCode) {
throw new Error('parseInt: not the start of an int');
}
this.currentParsingIndex++; // drop `i`
const startIntStr = this.currentParsingIndex; // save the start of the int
const nextEndSeparator = this.content.indexOf(eCode, this.currentParsingIndex);
if (nextEndSeparator === -1) {
throw new Error('parseInt: not an int to be parsed here: no end separator');
}
const parsed = toNumber(to_string(this.content.slice(startIntStr, nextEndSeparator)));
if (!isFinite(parsed)) {
throw new Error(`parseInt: could not parse number ${parsed}`);
}
this.currentParsingIndex = nextEndSeparator;
this.currentParsingIndex++; // drop the 'e'
return parsed;
}
private parseList(): BencodeArrayType {
const parsed: BencodeArrayType = [];
if (this.currentParsingIndex >= this.content.length) {
throw new Error('parseList: out of bounds');
}
if (this.content[this.currentParsingIndex] !== lCode) {
throw new Error('parseList: not the start of a list');
}
this.currentParsingIndex++; // drop `l`
while (
this.currentParsingIndex < this.content.length &&
this.content[this.currentParsingIndex] !== eCode
) {
parsed.push(this.parseBlock());
}
this.currentParsingIndex++; // drop the 'e'
return parsed;
}
private parseDict() {
const parsed: BencodeDictType = {};
if (this.currentParsingIndex >= this.content.length) {
throw new Error('parseDict: out of bounds');
}
if (this.content[this.currentParsingIndex] !== dCode) {
throw new Error('parseDict: not the start of a dict');
}
this.currentParsingIndex++; // drop `d`
while (
this.currentParsingIndex < this.content.length &&
this.content[this.currentParsingIndex] !== eCode
) {
const key = this.parseString();
const value = this.parseBlock();
parsed[key] = value;
}
this.currentParsingIndex++; // drop the 'e'
return parsed;
}
/**
* Decode a string element from iterator assumed to have structure `length:data`
*/
private parseString(): string {
if (this.currentParsingIndex >= this.content.length) {
throw new Error('parseString: out of bounds');
}
// this.currentParsingIndex++;
const separatorIndex = this.content.indexOf(colonCode, this.currentParsingIndex);
if (separatorIndex === -1) {
throw new Error('parseString: cannot parse string without separator');
}
const strLength = toNumber(
to_string(this.content.slice(this.currentParsingIndex, separatorIndex))
);
if (!isFinite(strLength)) {
throw new Error('parseString: cannot parse string without length');
}
if (strLength === 0) {
return '';
}
if (strLength > this.content.length - separatorIndex - 1) {
throw new Error(
'parseString: length is too long considering what we have left on this string'
);
}
const strContent = this.content.slice(separatorIndex + 1, separatorIndex + 1 + strLength);
this.currentParsingIndex = separatorIndex + 1 + strLength;
return StringUtils.decode(strContent, 'utf8');
}
private parseContent() {
return this.parseBlock();
}
private parseBlock() {
let parsed: BencodeElementType;
if (this.content.length < this.currentParsingIndex) {
throw new Error('Out of bounds');
}
if (this.content[this.currentParsingIndex] === lCode) {
parsed = this.parseList();
} else if (this.content[this.currentParsingIndex] === dCode) {
parsed = this.parseDict();
} else if (this.content[this.currentParsingIndex] === iCode) {
parsed = this.parseInt();
} else if (NUMBERS.some(num => this.content[this.currentParsingIndex] === num.charCodeAt(0))) {
parsed = this.parseString();
} else {
throw new Error(
`parseBlock: Could not parse charCode at ${this.currentParsingIndex}: ${
this.content[this.currentParsingIndex]
}. Length: ${this.content.length}`
);
}
return parsed;
}
}
export class BEncode {
private readonly input: BencodeElementType;
private readonly bencodedContent: Uint8Array;
constructor(content: BencodeElementType) {
this.input = content;
this.bencodedContent = this.encodeContent();
}
public getBencodedContent() {
return this.bencodedContent;
}
private encodeItem(item: BencodeElementType): Uint8Array {
if (isNumber(item) && isFinite(item)) {
return from_string(`i${item}e`);
}
if (isNumber(item)) {
throw new Error('encodeItem not finite number');
}
if (isString(item)) {
const content = new Uint8Array(StringUtils.encode(item, 'utf8'));
const contentLengthLength = `${content.length}`.length;
const toReturn = new Uint8Array(content.length + 1 + contentLengthLength);
toReturn.set(from_string(`${content.length}`));
toReturn.set([colonCode], contentLengthLength);
toReturn.set(content, contentLengthLength + 1);
return toReturn;
}
if (isArray(item)) {
let content = new Uint8Array();
for (let index = 0; index < item.length; index++) {
const encodedItem = this.encodeItem(item[index]);
const encodedItemLength = encodedItem.length;
const existingContentLength = content.length;
const newContent = new Uint8Array(existingContentLength + encodedItemLength);
newContent.set(content);
newContent.set(encodedItem, content.length);
content = newContent;
}
const toReturn = new Uint8Array(content.length + 2);
toReturn.set([lCode]);
toReturn.set(content, 1);
toReturn.set([eCode], content.length + 1);
return toReturn;
}
if (isPlainObject(item)) {
// bencoded objects keys must be sorted lexicographically
const sortedKeys = Object.keys(item).sort();
let content = new Uint8Array();
sortedKeys.forEach(key => {
const value = item[key];
const encodedKey = this.encodeItem(key);
const encodedValue = this.encodeItem(value);
const newContent = new Uint8Array(content.length + encodedKey.length + encodedValue.length);
newContent.set(content);
newContent.set(encodedKey, content.length);
newContent.set(encodedValue, content.length + encodedKey.length);
content = newContent;
});
const toReturn = new Uint8Array(content.length + 2);
toReturn.set([dCode]);
toReturn.set(content, 1);
toReturn.set([eCode], content.length + 1);
return toReturn;
}
throw new Error(`encodeItem: unknown type to encode ${typeof item}`);
}
private encodeContent(): Uint8Array {
if (!this.input || (isEmpty(this.input) && !isNumber(this.input))) {
return new Uint8Array();
}
return this.encodeItem(this.input);
}
}

@ -1,246 +0,0 @@
// tslint:disable: no-implicit-dependencies max-func-body-length no-unused-expression
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import { from_string } from 'libsodium-wrappers-sumo';
import { StringUtils } from '../../../../session/utils';
import { BDecode, BEncode } from '../../../../session/utils/Bencoding';
chai.use(chaiAsPromised as any);
const { expect } = chai;
describe('Bencoding: BDecode Utils', () => {
describe('From a string', () => {
describe('parseInt', () => {
it('parse 12', () => {
expect(new BDecode('i12e').getParsedContent()).to.equal(12);
});
it('parse 0', () => {
expect(new BDecode('ie').getParsedContent()).to.equal(0);
});
it('parse 12232332', () => {
expect(new BDecode('i12232332e').getParsedContent()).to.equal(12232332);
});
it('parse 12232332 even if extra characters', () => {
expect(new BDecode('i12232332eoverflow.d').getParsedContent()).to.equal(12232332);
});
it('throws invalid start', () => {
expect(() => new BDecode('d12232332e').getParsedContent()).to.throw();
});
it('throws invalid end', () => {
expect(() => new BDecode('i12232332d').getParsedContent()).to.throw();
});
it('throws invalid integer', () => {
expect(() => new BDecode('i1223233qw2e').getParsedContent()).to.throw();
});
});
describe('parseString', () => {
it('parse short string ', () => {
expect(new BDecode('1:a').getParsedContent()).to.equal('a');
});
it('parse string with emojis ', () => {
expect(new BDecode('8:🎃🥸').getParsedContent()).to.equal('🎃🥸');
expect(new BDecode('26:❤️‍🔥❤️‍🔥').getParsedContent()).to.equal('❤️‍🔥❤️‍🔥');
});
it('parse non ascii string', () => {
expect(new BDecode('48:転キハ連月ざれ地周りを報最こもろ').getParsedContent()).to.equal(
'転キハ連月ざれ地周りを報最こもろ'
);
});
it('parse longer string', () => {
expect(
new BDecode(
"320:Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic"
).getParsedContent()
).to.equal(
"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic"
);
});
it('throw if no separator ', () => {
expect(() => new BDecode('1aa').getParsedContent()).to.throw();
});
it('throw if first part is not a number ', () => {
expect(() => new BDecode('1a1:aa').getParsedContent()).to.throw();
});
it('throw if length brings us way out of range ', () => {
expect(() => new BDecode('322:aa').getParsedContent()).to.throw();
});
it('throw if length brings us just out of range ', () => {
expect(() => new BDecode('3:aa').getParsedContent()).to.throw();
});
});
});
describe('parseDict', () => {
it('parse single entry dict with string `d3:bar4:spame`', () => {
expect(new BDecode('d3:bar4:spame').getParsedContent()).to.deep.equal({ bar: 'spam' });
});
it('parse single entry dict with multiple strings `d3:bar4:spam5:barre2:twe`', () => {
expect(new BDecode('d3:bar4:spam5:barre2:twe').getParsedContent()).to.deep.equal({
bar: 'spam',
barre: 'tw',
});
});
it('parse multiple entries dict and int', () => {
expect(new BDecode('d3:bar4:spam3:fooi42ee').getParsedContent()).to.deep.equal({
bar: 'spam',
foo: 42,
});
});
it('parse multiple entries with ints', () => {
expect(new BDecode('d3:bari999e3:fooi42ee').getParsedContent()).to.deep.equal({
bar: 999,
foo: 42,
});
});
it('parse single entry with emoji', () => {
expect(new BDecode('d3:bar8:🎃🥸e').getParsedContent()).to.deep.equal({
bar: '🎃🥸',
});
});
});
describe('parseList', () => {
it('parse single entry', () => {
expect(new BDecode('l4:spame').getParsedContent()).to.deep.equal(['spam']);
});
it('parse multiple entries ', () => {
expect(new BDecode('l4:spam2:spe').getParsedContent()).to.deep.equal(['spam', 'sp']);
});
it('parse multiple entries witrh int and strings ', () => {
expect(new BDecode('l4:spam2:spi42e2:42e').getParsedContent()).to.deep.equal([
'spam',
'sp',
42,
'42',
]);
});
it('parse list with dict included ', () => {
expect(new BDecode('ld3:bari999e3:fooi42eee').getParsedContent()).to.deep.equal([
{
bar: 999,
foo: 42,
},
]);
});
it('parse list with mulitple dict included ', () => {
expect(
new BDecode('ld3:bari999e3:fooi42eed3:rabi111e3:offi2312eee').getParsedContent()
).to.deep.equal([
{
bar: 999,
foo: 42,
},
{
rab: 111,
off: 2312,
},
]);
});
it('parse dict with list included ', () => {
expect(new BDecode('d2:dili42ei24e4:key7ee').getParsedContent()).to.deep.equal({
di: [42, 24, 'key7'],
});
});
it('parse dict with multiple lists included ', () => {
expect(new BDecode('d2:dili42ei24e4:key7e4:secol4:key7ee').getParsedContent()).to.deep.equal({
di: [42, 24, 'key7'],
seco: ['key7'],
});
});
});
});
describe('Bencoding: BEncode Utils', () => {
it('encode single string', () => {
expect(new BEncode('abcdef').getBencodedContent()).to.deep.equal(
new Uint8Array(StringUtils.encode('6:abcdef', 'utf8'))
);
});
it('encode single string emoji', () => {
expect(new BEncode('🎃🥸').getBencodedContent()).to.deep.equal(
new Uint8Array(StringUtils.encode('8:🎃🥸', 'utf8'))
);
});
it('encode single int', () => {
expect(new BEncode(12).getBencodedContent()).to.deep.equal(from_string('i12e'));
});
it('encode array with one int', () => {
expect(new BEncode([12]).getBencodedContent()).to.deep.equal(from_string('li12ee'));
});
it('encode array with multiple int', () => {
expect(new BEncode([12, 34, 5678]).getBencodedContent()).to.deep.equal(
from_string('li12ei34ei5678ee')
);
});
it('encode array with different types', () => {
expect(new BEncode([12, '34', 5678]).getBencodedContent()).to.deep.equal(
from_string('li12e2:34i5678ee')
);
});
it('encode dict with one item', () => {
expect(new BEncode({ dict: '123' }).getBencodedContent()).to.deep.equal(
from_string('d4:dict3:123e')
);
});
it('encode dict with several items', () => {
expect(new BEncode({ dict: '123', dict2: '1234' }).getBencodedContent()).to.deep.equal(
from_string('d4:dict3:1235:dict24:1234e')
);
});
it('encode dict with several items with arrays', () => {
expect(new BEncode({ dict1: [1, 2, 3], dict2: [4, 5, 6] }).getBencodedContent()).to.deep.equal(
from_string('d5:dict1li1ei2ei3ee5:dict2li4ei5ei6eee')
);
});
it('encode dict with several items but sort them', () => {
expect(new BEncode({ dict2: 'second', dict1: 'first' }).getBencodedContent()).to.deep.equal(
from_string('d5:dict15:first5:dict26:seconde')
);
});
it('encode dict with array with dict', () => {
expect(new BEncode({ dict: [{ a: 'b', c: 'd' }] }).getBencodedContent()).to.deep.equal(
from_string('d4:dictld1:a1:b1:c1:deee')
);
});
it('encode dict with array with dict with emojis', () => {
expect(new BEncode({ dict: [{ a: 'b', c: '🎃🥸' }] }).getBencodedContent()).to.deep.equal(
from_string('d4:dictld1:a1:b1:c8:🎃🥸eee')
);
});
});
Loading…
Cancel
Save