238 lines
7.6 KiB
JavaScript
238 lines
7.6 KiB
JavaScript
import { promisify } from 'util';
|
|
import { inflate } from 'zlib';
|
|
import * as constants from './constants.js';
|
|
import { jidEncode, WAJIDDomains } from './jid-utils.js';
|
|
const inflatePromise = promisify(inflate);
|
|
export const decompressingIfRequired = async (buffer) => {
|
|
if (2 & buffer.readUInt8()) {
|
|
buffer = await inflatePromise(buffer.slice(1));
|
|
}
|
|
else {
|
|
// nodes with no compression have a 0x00 prefix, we remove that
|
|
buffer = buffer.slice(1);
|
|
}
|
|
return buffer;
|
|
};
|
|
export const decodeDecompressedBinaryNode = (buffer, opts, indexRef = { index: 0 }) => {
|
|
const { DOUBLE_BYTE_TOKENS, SINGLE_BYTE_TOKENS, TAGS } = opts;
|
|
const checkEOS = (length) => {
|
|
if (indexRef.index + length > buffer.length) {
|
|
throw new Error('end of stream');
|
|
}
|
|
};
|
|
const next = () => {
|
|
const value = buffer[indexRef.index];
|
|
indexRef.index += 1;
|
|
return value;
|
|
};
|
|
const readByte = () => {
|
|
checkEOS(1);
|
|
return next();
|
|
};
|
|
const readBytes = (n) => {
|
|
checkEOS(n);
|
|
const value = buffer.slice(indexRef.index, indexRef.index + n);
|
|
indexRef.index += n;
|
|
return value;
|
|
};
|
|
const readStringFromChars = (length) => {
|
|
return readBytes(length).toString('utf-8');
|
|
};
|
|
const readInt = (n, littleEndian = false) => {
|
|
checkEOS(n);
|
|
let val = 0;
|
|
for (let i = 0; i < n; i++) {
|
|
const shift = littleEndian ? i : n - 1 - i;
|
|
val |= next() << (shift * 8);
|
|
}
|
|
return val;
|
|
};
|
|
const readInt20 = () => {
|
|
checkEOS(3);
|
|
return ((next() & 15) << 16) + (next() << 8) + next();
|
|
};
|
|
const unpackHex = (value) => {
|
|
if (value >= 0 && value < 16) {
|
|
return value < 10 ? '0'.charCodeAt(0) + value : 'A'.charCodeAt(0) + value - 10;
|
|
}
|
|
throw new Error('invalid hex: ' + value);
|
|
};
|
|
const unpackNibble = (value) => {
|
|
if (value >= 0 && value <= 9) {
|
|
return '0'.charCodeAt(0) + value;
|
|
}
|
|
switch (value) {
|
|
case 10:
|
|
return '-'.charCodeAt(0);
|
|
case 11:
|
|
return '.'.charCodeAt(0);
|
|
case 15:
|
|
return '\0'.charCodeAt(0);
|
|
default:
|
|
throw new Error('invalid nibble: ' + value);
|
|
}
|
|
};
|
|
const unpackByte = (tag, value) => {
|
|
if (tag === TAGS.NIBBLE_8) {
|
|
return unpackNibble(value);
|
|
}
|
|
else if (tag === TAGS.HEX_8) {
|
|
return unpackHex(value);
|
|
}
|
|
else {
|
|
throw new Error('unknown tag: ' + tag);
|
|
}
|
|
};
|
|
const readPacked8 = (tag) => {
|
|
const startByte = readByte();
|
|
let value = '';
|
|
for (let i = 0; i < (startByte & 127); i++) {
|
|
const curByte = readByte();
|
|
value += String.fromCharCode(unpackByte(tag, (curByte & 0xf0) >> 4));
|
|
value += String.fromCharCode(unpackByte(tag, curByte & 0x0f));
|
|
}
|
|
if (startByte >> 7 !== 0) {
|
|
value = value.slice(0, -1);
|
|
}
|
|
return value;
|
|
};
|
|
const isListTag = (tag) => {
|
|
return tag === TAGS.LIST_EMPTY || tag === TAGS.LIST_8 || tag === TAGS.LIST_16;
|
|
};
|
|
const readListSize = (tag) => {
|
|
switch (tag) {
|
|
case TAGS.LIST_EMPTY:
|
|
return 0;
|
|
case TAGS.LIST_8:
|
|
return readByte();
|
|
case TAGS.LIST_16:
|
|
return readInt(2);
|
|
default:
|
|
throw new Error('invalid tag for list size: ' + tag);
|
|
}
|
|
};
|
|
const readJidPair = () => {
|
|
const i = readString(readByte());
|
|
const j = readString(readByte());
|
|
if (j) {
|
|
return (i || '') + '@' + j;
|
|
}
|
|
throw new Error('invalid jid pair: ' + i + ', ' + j);
|
|
};
|
|
const readAdJid = () => {
|
|
const rawDomainType = readByte();
|
|
const domainType = Number(rawDomainType);
|
|
const device = readByte();
|
|
const user = readString(readByte());
|
|
let server = 's.whatsapp.net'; // default whatsapp server
|
|
if (domainType === WAJIDDomains.LID) {
|
|
server = 'lid';
|
|
}
|
|
else if (domainType === WAJIDDomains.HOSTED) {
|
|
server = 'hosted';
|
|
}
|
|
else if (domainType === WAJIDDomains.HOSTED_LID) {
|
|
server = 'hosted.lid';
|
|
}
|
|
return jidEncode(user, server, device);
|
|
};
|
|
const readString = (tag) => {
|
|
if (tag >= 1 && tag < SINGLE_BYTE_TOKENS.length) {
|
|
return SINGLE_BYTE_TOKENS[tag] || '';
|
|
}
|
|
switch (tag) {
|
|
case TAGS.DICTIONARY_0:
|
|
case TAGS.DICTIONARY_1:
|
|
case TAGS.DICTIONARY_2:
|
|
case TAGS.DICTIONARY_3:
|
|
return getTokenDouble(tag - TAGS.DICTIONARY_0, readByte());
|
|
case TAGS.LIST_EMPTY:
|
|
return '';
|
|
case TAGS.BINARY_8:
|
|
return readStringFromChars(readByte());
|
|
case TAGS.BINARY_20:
|
|
return readStringFromChars(readInt20());
|
|
case TAGS.BINARY_32:
|
|
return readStringFromChars(readInt(4));
|
|
case TAGS.JID_PAIR:
|
|
return readJidPair();
|
|
case TAGS.AD_JID:
|
|
return readAdJid();
|
|
case TAGS.HEX_8:
|
|
case TAGS.NIBBLE_8:
|
|
return readPacked8(tag);
|
|
default:
|
|
throw new Error('invalid string with tag: ' + tag);
|
|
}
|
|
};
|
|
const readList = (tag) => {
|
|
const items = [];
|
|
const size = readListSize(tag);
|
|
for (let i = 0; i < size; i++) {
|
|
items.push(decodeDecompressedBinaryNode(buffer, opts, indexRef));
|
|
}
|
|
return items;
|
|
};
|
|
const getTokenDouble = (index1, index2) => {
|
|
const dict = DOUBLE_BYTE_TOKENS[index1];
|
|
if (!dict) {
|
|
throw new Error(`Invalid double token dict (${index1})`);
|
|
}
|
|
const value = dict[index2];
|
|
if (typeof value === 'undefined') {
|
|
throw new Error(`Invalid double token (${index2})`);
|
|
}
|
|
return value;
|
|
};
|
|
const listSize = readListSize(readByte());
|
|
const header = readString(readByte());
|
|
if (!listSize || !header.length) {
|
|
throw new Error('invalid node');
|
|
}
|
|
const attrs = {};
|
|
let data;
|
|
if (listSize === 0 || !header) {
|
|
throw new Error('invalid node');
|
|
}
|
|
// read the attributes in
|
|
const attributesLength = (listSize - 1) >> 1;
|
|
for (let i = 0; i < attributesLength; i++) {
|
|
const key = readString(readByte());
|
|
const value = readString(readByte());
|
|
attrs[key] = value;
|
|
}
|
|
if (listSize % 2 === 0) {
|
|
const tag = readByte();
|
|
if (isListTag(tag)) {
|
|
data = readList(tag);
|
|
}
|
|
else {
|
|
let decoded;
|
|
switch (tag) {
|
|
case TAGS.BINARY_8:
|
|
decoded = readBytes(readByte());
|
|
break;
|
|
case TAGS.BINARY_20:
|
|
decoded = readBytes(readInt20());
|
|
break;
|
|
case TAGS.BINARY_32:
|
|
decoded = readBytes(readInt(4));
|
|
break;
|
|
default:
|
|
decoded = readString(tag);
|
|
break;
|
|
}
|
|
data = decoded;
|
|
}
|
|
}
|
|
return {
|
|
tag: header,
|
|
attrs,
|
|
content: data
|
|
};
|
|
};
|
|
export const decodeBinaryNode = async (buff) => {
|
|
const decompBuff = await decompressingIfRequired(buff);
|
|
return decodeDecompressedBinaryNode(decompBuff, constants);
|
|
};
|
|
//# sourceMappingURL=decode.js.map
|