function charLengthInBytes(code: number): number { if ((code & 0xffffff80) === 0) { return 1; } else if ((code & 0xfffff800) === 0) { return 2; } else if ((code & 0xffff0000) === 0) { return 3; } else { return 4; } } export function stringLengthInBytes(value: string): number { let result = 0; for (let i = 0; i < value.length; i++) { const code = value.charCodeAt(i); // high surrogate if (code >= 0xd800 && code <= 0xdbff) { if ((i + 1) < value.length) { const extra = value.charCodeAt(i + 1); // low surrogate if ((extra & 0xfc00) === 0xdc00) { i++; result += charLengthInBytes(((code & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000); } } } else { result += charLengthInBytes(code); } } return result; } function writeCharacter(buffer: Uint8Array | Buffer, offset: number, code: number): number { const length = charLengthInBytes(code); switch (length) { case 1: buffer[offset] = code; break; case 2: buffer[offset] = ((code >> 6) & 0x1f) | 0xc0; buffer[offset + 1] = (code & 0x3f) | 0x80; break; case 3: buffer[offset] = ((code >> 12) & 0x0f) | 0xe0; buffer[offset + 1] = ((code >> 6) & 0x3f) | 0x80; buffer[offset + 2] = (code & 0x3f) | 0x80; break; default: buffer[offset] = ((code >> 18) & 0x07) | 0xf0; buffer[offset + 1] = ((code >> 12) & 0x3f) | 0x80; buffer[offset + 2] = ((code >> 6) & 0x3f) | 0x80; buffer[offset + 3] = (code & 0x3f) | 0x80; break; } return length; } export function encodeStringTo(buffer: Uint8Array | Buffer, offset: number, value: string): number { for (let i = 0; i < value.length; i++) { const code = value.charCodeAt(i); // high surrogate if (code >= 0xd800 && code <= 0xdbff) { if ((i + 1) < value.length) { const extra = value.charCodeAt(i + 1); // low surrogate if ((extra & 0xfc00) === 0xdc00) { i++; const fullCode = ((code & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000; offset += writeCharacter(buffer, offset, fullCode); } } } else { offset += writeCharacter(buffer, offset, code); } } return offset; } export function encodeString(value: string): Uint8Array { if (value.length > 1000 && typeof TextEncoder !== 'undefined') { return (new TextEncoder()).encode(value); } const buffer = new Uint8Array(stringLengthInBytes(value)); encodeStringTo(buffer, 0, value); return buffer; } function continuationByte(buffer: Uint8Array, index: number): number { if (index >= buffer.length) { throw Error('Invalid byte index'); } const continuationByte = buffer[index]; if ((continuationByte & 0xC0) === 0x80) { return continuationByte & 0x3F; } else { throw Error('Invalid continuation byte'); } } export function decodeString(value: Uint8Array): string { if (value.byteLength > 1000 && typeof TextDecoder !== 'undefined') { return (new TextDecoder()).decode(value); } let result: string[] = []; for (let i = 0; i < value.length;) { const byte1 = value[i++]; let code: number; if ((byte1 & 0x80) === 0) { code = byte1; } else if ((byte1 & 0xe0) === 0xc0) { const byte2 = continuationByte(value, i++); code = ((byte1 & 0x1f) << 6) | byte2; if (code < 0x80) { throw Error('Invalid continuation byte'); } } else if ((byte1 & 0xf0) === 0xe0) { const byte2 = continuationByte(value, i++); const byte3 = continuationByte(value, i++); code = ((byte1 & 0x0f) << 12) | (byte2 << 6) | byte3; if (code < 0x0800) { throw Error('Invalid continuation byte'); } if (code >= 0xd800 && code <= 0xdfff) { throw Error(`Lone surrogate U+${code.toString(16).toUpperCase()} is not a scalar value`); } } else if ((byte1 & 0xf8) === 0xf0) { const byte2 = continuationByte(value, i++); const byte3 = continuationByte(value, i++); const byte4 = continuationByte(value, i++); code = ((byte1 & 0x0f) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4; if (code < 0x010000 || code > 0x10ffff) { throw Error('Invalid continuation byte'); } } else { throw Error('Invalid UTF-8 detected'); } if (code > 0xffff) { code -= 0x10000; result.push(String.fromCharCode(code >>> 10 & 0x3ff | 0xd800)); code = 0xdc00 | code & 0x3ff; } result.push(String.fromCharCode(code)); } return result.join(''); }