import { randomBytes } from 'crypto'; import { Int64BE, Uint64BE } from 'int64-buffer'; import blake2b from 'blake2b'; import { TextEncoder } from 'text-encoding'; import { getCustomEncryption } from '../utils/constants'; import { be_ui8_array_to_i16, be_ui8_array_to_i32, be_ui8_array_to_ui16, be_ui8_array_to_ui32, i16_to_be_ui8_array, i32_to_be_ui8_array, i8_to_ui8_array, ui16_to_be_ui8_array, ui32_to_be_ui8_array, ui8_array_to_i8, ui8_array_to_ui8, ui8_to_ui8_array, } from '../utils/byte_order'; import { XChaCha20Poly1305 } from '@stablelib/xchacha20poly1305'; export default class packet { constructor(token) { this.token = token; } buffer = new Uint8Array(0); decode_offset = 0; token; append_buffer = (buffer) => { if (!(buffer instanceof Uint8Array)) { throw new Error('Buffer must be of type Uint8Array'); } this.buffer = this.buffer ? new Uint8Array([...this.buffer, ...buffer]) : buffer; }; clear = () => { this.buffer = null; this.decode_offset = 0; }; decode_buffer = (length) => { if (!this.buffer) { throw new Error('Invalid buffer'); } const ret = this.buffer.slice( this.decode_offset, this.decode_offset + length ); this.decode_offset += length; return Buffer.from(ret); }; decode_stat = () => { const mode = this.decode_ui16(); const nlink = this.decode_ui16(); const uid = this.decode_ui32(); const gid = this.decode_ui32(); const atime = this.decode_ui64(); const mtime = this.decode_ui64(); const ctime = this.decode_ui64(); const birth_time = this.decode_ui64(); const size = this.decode_ui64(); const blocks = this.decode_ui64(); const blksize = this.decode_ui32(); const flags = this.decode_ui32(); const directory = !!this.decode_ui8(); return { mode, nlink, uid, gid, atime, mtime, ctime, birth_time, size, blocks, blksize, flags, directory, }; }; decode_utf8 = () => { if (!this.buffer) { throw new Error('Invalid buffer'); } const startIndex = this.decode_offset; const endIndex = this.buffer.indexOf(0, startIndex); if (endIndex >= 0) { let ret = ''; for (let i = startIndex; i < endIndex; i++) { ret += String.fromCharCode(this.buffer[i]); } this.decode_offset = endIndex + 1; return ret; } throw new Error('String not found in buffer'); }; decode_i8 = () => { return ui8_array_to_i8(this.buffer, this.decode_offset++); }; decode_ui8 = () => { return ui8_array_to_ui8(this.buffer, this.decode_offset++); }; decode_i16 = () => { const ret = be_ui8_array_to_i16(this.buffer, this.decode_offset); this.decode_offset += 2; return ret; }; decode_ui16 = () => { const ret = be_ui8_array_to_ui16(this.buffer, this.decode_offset); this.decode_offset += 2; return ret; }; decode_i32 = () => { const ret = be_ui8_array_to_i32(this.buffer, this.decode_offset); this.decode_offset += 4; return ret; }; decode_ui32 = () => { const ret = be_ui8_array_to_ui32(this.buffer, this.decode_offset); this.decode_offset += 4; return ret; }; decode_i64 = () => { const ret = new Int64BE( this.buffer.slice(this.decode_offset, this.decode_offset + 8) ); this.decode_offset += 8; return ret.toString(10); }; decode_ui64 = () => { const ret = new Uint64BE( this.buffer.slice(this.decode_offset, this.decode_offset + 8) ); this.decode_offset += 8; return ret.toString(10); }; decrypt = async () => { try { let hash = blake2b(32); hash = hash.update(new TextEncoder().encode(this.token)); const key = Uint8Array.from(hash.digest()); const nonce = this.buffer.slice(0, 24); const mac = this.buffer.slice(24, 16 + 24); const customEncryption = getCustomEncryption(); if (customEncryption) { this.buffer = Buffer.from( await customEncryption.decrypt( Buffer.from(key).toString('base64'), Buffer.from(nonce).toString('base64'), Buffer.from(mac).toString('base64'), Buffer.from(this.buffer.slice(40)).toString('base64') ), 'base64' ); } else { const aad = ui32_to_be_ui8_array(this.buffer.length); this.buffer = new Uint8Array([ ...this.buffer.slice(nonce.length + mac.length), ...this.buffer.slice(nonce.length, nonce.length + mac.length), ]); const result = new XChaCha20Poly1305(key).open(nonce, this.buffer, aad); if (!result) { throw new Error('decryption failed'); } this.buffer = Buffer.from(result); } this.buffer = new Uint8Array(this.buffer); return this.buffer; } catch (e) { return Promise.reject(e); } }; encode_buffer = (buffer) => { this.append_buffer(new Uint8Array(buffer)); }; encode_i8 = (num) => { this.append_buffer(i8_to_ui8_array(num)); }; encode_top_i8 = (num) => { this.push_buffer(i8_to_ui8_array(num)); }; encode_u8 = (num) => { this.append_buffer(ui8_to_ui8_array(num)); }; encode_top_u8 = (num) => { this.push_buffer(ui8_to_ui8_array(num)); }; encode_i16 = (num) => { this.append_buffer(i16_to_be_ui8_array(num)); }; encode_top_i16 = (num) => { this.push_buffer(i16_to_be_ui8_array(num)); }; encode_ui16 = (num) => { this.append_buffer(ui16_to_be_ui8_array(num)); }; encode_top_ui16 = (num) => { this.push_buffer(ui16_to_be_ui8_array(num)); }; encode_i32 = (num) => { this.append_buffer(i32_to_be_ui8_array(num)); }; encode_top_i32 = (num) => { this.push_buffer(i32_to_be_ui8_array(num)); }; encode_ui32 = (num) => { this.append_buffer(ui32_to_be_ui8_array(num)); }; encode_top_ui32 = (num) => { this.push_buffer(ui32_to_be_ui8_array(num)); }; encode_i64 = (num) => { this.append_buffer(new Uint8Array(new Int64BE(num).toArray())); }; encode_top_i64 = (num) => { this.push_buffer(new Uint8Array(new Int64BE(num).toArray())); }; encode_ui64 = (num) => { this.append_buffer(new Uint8Array(new Uint64BE(num).toArray())); }; encode_top_ui64 = (num) => { this.push_buffer(new Uint8Array(new Uint64BE(num).toArray())); }; encode_utf8 = (str) => { if (!(typeof str === 'string' || str instanceof String)) { throw new Error('Value must be of type string'); } const buffer = new Uint8Array([...new TextEncoder().encode(str), 0]); this.append_buffer(buffer); }; encode_top_utf8 = (str) => { if (!(typeof str === 'string' || str instanceof String)) { throw new Error('Value must be of type string'); } const buffer = new Uint8Array([...new TextEncoder().encode(str), 0]); this.push_buffer(buffer); }; encrypt = async (nonce) => { try { let hash = blake2b(32); hash = hash.update(new TextEncoder().encode(this.token)); const key = Uint8Array.from(hash.digest()); if (!nonce) { nonce = Uint8Array.from(randomBytes(24)); } const customEncryption = getCustomEncryption(); if (customEncryption) { this.buffer = new Uint8Array( Buffer.from( await customEncryption.encrypt( Buffer.from(key).toString('base64'), Buffer.from(nonce).toString('base64'), Buffer.from(this.buffer).toString('base64') ), 'base64' ) ); } else { const aad = ui32_to_be_ui8_array(this.buffer.length + 40); this.buffer = new XChaCha20Poly1305(key).seal(nonce, this.buffer, aad); this.buffer = new Uint8Array([ ...this.buffer.slice(this.buffer.length - 16), ...this.buffer.slice(0, this.buffer.length - 16), ]); this.push_buffer(nonce); } return this.buffer; } catch (e) { return Promise.reject(e); } }; push_buffer = (buffer) => { if (!(buffer instanceof Uint8Array)) { throw new Error('Buffer must be of type Uint8Array'); } this.buffer = this.buffer ? new Uint8Array([...buffer, ...this.buffer]) : buffer; }; }