Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 101 additions & 1 deletion assembly/buffer/index.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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<usize>(str);
let byteCount = changetype<BLOCK>(changetype<usize>(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<u16>(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<ArrayBuffer>(__alloc(0, idof<ArrayBuffer>()));

// long path: loop over each enociding pair and perform the conversion
let ptr = changetype<usize>(str);
let byteEnd = changetype<BLOCK>(changetype<usize>(str) - BLOCK_OVERHEAD).rtSize + ptr;
let result = __alloc(bufferLength, idof<ArrayBuffer>());
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<u8>(result + (i >> 1), <u8>(outChar & 0xFF));
ptr += 4;
} else {
b = load<u32>(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<ArrayBuffer>(result);
}

/** Creates a string from a given ArrayBuffer that is decoded into hex format. */
export function decode(buff: ArrayBuffer): string {
return decodeUnsafe(changetype<usize>(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<String>());
let i = <usize>0;
let inputByteLength = <usize>length + ptr;

// loop over each byte and store a `u32` for each one
while (ptr < inputByteLength) {
store<u32>(result + i, charsFromByte(<u32>load<u8>(ptr)));
i += 4;
ptr++;
}

return changetype<string>(result);
}
}
}
11 changes: 11 additions & 0 deletions assembly/node.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
14 changes: 14 additions & 0 deletions tests/buffer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,18 @@ describe("buffer", () => {
// newBuff.writeUInt16BE(0);
// }).toThrow();
});

test("#Hex.encode", () => {
let actual = "000102030405060708090a0b0c0d0e0f102030405060708090a0b0c0d0e0f0";
let exampleBuffer = create<Buffer>([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<ArrayBuffer>(encoded).toStrictEqual(exampleBuffer.buffer);
});

test("#Hex.decode", () => {
let expected = "000102030405060708090a0b0c0d0e0f102030405060708090a0b0c0d0e0f0";
let exampleBuffer = create<Buffer>([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<string>(decoded).toStrictEqual(expected);
});
});
4 changes: 1 addition & 3 deletions tests/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down