diff --git a/assembly/buffer/index.ts b/assembly/buffer/index.ts index fb5d4ca..87cf104 100644 --- a/assembly/buffer/index.ts +++ b/assembly/buffer/index.ts @@ -1,4 +1,4 @@ -import { BLOCK_MAXSIZE } from "rt/common"; +import { BLOCK_MAXSIZE, BLOCK, BLOCK_OVERHEAD } from "rt/common"; import { E_INVALIDLENGTH, E_INDEXOUTOFRANGE } from "util/error"; import { Uint8Array } from "typedarray"; @@ -93,3 +93,103 @@ export class Buffer extends Uint8Array { return offset + 2; } } + +export namespace Buffer { + export namespace HEX { + /** Calculates the two char combination from the byte. */ + @inline export function charsFromByte(byte: u32): u32 { + let top = (byte >>> 4) & 0xF; + let bottom = (0xF & byte); + top += select(0x57, 0x30, top > 9); + bottom += select(0x57, 0x30, bottom > 9); + return (bottom << 16) | top; + } + + /** Calculates the byte length of the specified string when encoded as HEX. */ + export function byteLength(str: string): i32 { + let ptr = changetype(str); + let byteCount = changetype(changetype(str) - BLOCK_OVERHEAD).rtSize; + let length = byteCount >> 2; + // The string length must be even because the bytes come in pairs of characters two wide + if (byteCount & 0x3) return 0; // encoding fails and returns an empty ArrayBuffer + + byteCount += ptr; + while (ptr < byteCount) { + var char = load(ptr); + if ( ((char - 0x30) <= 0x9) + || ((char - 0x61) <= 0x5) + || ((char - 0x41) <= 0x5)) { + ptr += 2; + continue; + } else { + return 0; + } + } + return length; + } + + /** Creates an ArrayBuffer from a given string that is encoded in the HEX format. */ + export function encode(str: string): ArrayBuffer { + let bufferLength = byteLength(str); + // short path: string is not a valid hex string, return a new empty ArrayBuffer + if (bufferLength == 0) return changetype(__alloc(0, idof())); + + // long path: loop over each enociding pair and perform the conversion + let ptr = changetype(str); + let byteEnd = changetype(changetype(str) - BLOCK_OVERHEAD).rtSize + ptr; + let result = __alloc(bufferLength, idof()); + let b: u32 = 0; + let outChar = 0; + for (let i: usize = 0; ptr < byteEnd; i++) { + let odd = i & 1; + if (odd) { + outChar <<= 4; + b >>>= 16; + if ((b - 0x30) <= 9) { + outChar |= b - 0x30; + } else if ((b - 0x61) <= 0x5) { + outChar |= b - 0x57; + } else if (b - 0x41 <= 0x5) { + outChar |= b - 0x37; + } + store(result + (i >> 1), (outChar & 0xFF)); + ptr += 4; + } else { + b = load(ptr); + outChar <<= 4; + let c = b & 0xFF; + if ((c - 0x30) <= 9) { + outChar |= c - 0x30; + } else if ((c - 0x61) <= 0x5) { + outChar |= c - 0x57; + } else if (c - 0x41 <= 0x5) { + outChar |= c - 0x37; + } + } + } + return changetype(result); + } + + /** Creates a string from a given ArrayBuffer that is decoded into hex format. */ + export function decode(buff: ArrayBuffer): string { + return decodeUnsafe(changetype(buff), buff.byteLength); + } + + /** Decodes a chunk of memory to a utf16le encoded string in hex format. */ + @unsafe export function decodeUnsafe(ptr: usize, length: i32): string { + let stringByteLength = length << 2; // length * (2 bytes per char) * (2 chars per input byte) + let result = __alloc(stringByteLength, idof()); + let i = 0; + let inputByteLength = length + ptr; + + // loop over each byte and store a `u32` for each one + while (ptr < inputByteLength) { + store(result + i, charsFromByte(load(ptr))); + i += 4; + ptr++; + } + + return changetype(result); + } + } +} diff --git a/assembly/node.d.ts b/assembly/node.d.ts index 5beb381..16f3e81 100644 --- a/assembly/node.d.ts +++ b/assembly/node.d.ts @@ -30,3 +30,14 @@ declare class Buffer extends Uint8Array { /** Writes an inputted unsigned 16-bit integer at the designated offset, stored in Big Endian format */ writeUInt16BE(value: u16, offset?: i32): i32; } + +declare namespace Buffer { + export namespace HEX { + /** Creates an ArrayBuffer from a given string that is encoded in the hex format. */ + export function encode(str: string): ArrayBuffer; + /** Creates a string from a given ArrayBuffer that is decoded into hex format. */ + export function decode(buffer: ArrayBuffer): string; + /** Decodes a chunk of memory to a utf16le encoded string in hex format. */ + export function decodeUnsafe(ptr: usize, byteLength: i32): string; + } +} diff --git a/tests/buffer.spec.ts b/tests/buffer.spec.ts index ba4ff36..5e15eaa 100644 --- a/tests/buffer.spec.ts +++ b/tests/buffer.spec.ts @@ -213,4 +213,18 @@ describe("buffer", () => { // newBuff.writeUInt16BE(0); // }).toThrow(); }); + + test("#Hex.encode", () => { + let actual = "000102030405060708090a0b0c0d0e0f102030405060708090a0b0c0d0e0f0"; + let exampleBuffer = create([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0]); + let encoded = Buffer.HEX.encode(actual); + expect(encoded).toStrictEqual(exampleBuffer.buffer); + }); + + test("#Hex.decode", () => { + let expected = "000102030405060708090a0b0c0d0e0f102030405060708090a0b0c0d0e0f0"; + let exampleBuffer = create([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0]); + let decoded = Buffer.HEX.decode(exampleBuffer.buffer); + expect(decoded).toStrictEqual(expected); + }); }); diff --git a/tests/node.js b/tests/node.js index c652c1e..0b5a3d4 100644 --- a/tests/node.js +++ b/tests/node.js @@ -109,9 +109,7 @@ function runTest(file, type, binary, wat) { + "." + type + ".wat"; // should not block testing - fs.writeFile(watPath, wat, (err) => { - if (err) console.warn(err); - }); + fs.writeFileSync(watPath, wat); const context = new TestContext({ fileName: file,