diff --git a/assembly/buffer/index.ts b/assembly/buffer/index.ts index 91be52d..19471bf 100644 --- a/assembly/buffer/index.ts +++ b/assembly/buffer/index.ts @@ -376,4 +376,135 @@ export namespace Buffer { return changetype(result); } } + + export namespace BASE64 { + // @ts-ignore: Decorators! + @inline + function charCodeFromByte(value: i32): u64 { + if (i32(value >= 0) & i32(value < 26)) return value + 0x41; + else if (i32(value >= 26) & i32(value < 52)) return value + 0x47; + else if (i32(value >= 52) & i32(value < 62)) return value - 0x4; + else return select(0x2B, 0x2F, value == 62); + } + + export function byteLength(input: string): i32 { + let length = input.length; + if (length & 0b11) return -1; + let char: u16; + let count = length - 2; + for (let i = 0; i < count; i++) { + char = load(changetype(input) + (i << alignof())); + if (i32(char >= 0x41) & i32(char <= 0x5A)) continue; + else if (i32(char >= 0x61) & i32(char <= 0x7A)) continue; + else if (i32(char >= 0x31) & i32(char <= 0x39)) continue; + else if(i32(char == 0x2B) | i32(char == 0x2F)) continue; + return -1; + } + + // check each of the last two chars + char = load(changetype(input) + (count << alignof()), 2); + + let hasPadding = false; + if (i32(char >= 0x41) & i32(char <= 0x5A) + | i32(char >= 0x61) & i32(char <= 0x7A) + | i32(char >= 0x31) & i32(char <= 0x39) + | i32(char == 0x2B) + | i32(char == 0x2F)) {} + else if (char == 0x3D) { hasPadding = true; } + else return -1; + + char = load(changetype(input) + (length << alignof()), 4); + if (hasPadding) { + if (char != 0x3D) return -1; + } else { + if ( (i32(char >= 0x41) & i32(char <= 0x5A)) + | (i32(char >= 0x61) & i32(char <= 0x7A)) + | (i32(char >= 0x31) & i32(char <= 0x39)) + | i32(char == 0x2B) + | i32(char == 0x2F) + | i32(char == 0x3D)) {} + else return -1; + } + + return (length * 3) >>> 2; + } + + export function stringLength(byteLength: i32): i32 { + return (Math.floor(byteLength / 3) << 2) + select(4, 0, byteLength % 3 != 0); + } + + export function encode(input: string): ArrayBuffer | null { + let outputLength = byteLength(input); + if (outputLength == -1) return null; + let output = __alloc(outputLength, idof()); + let stringLength = input.length; + let iterations = stringLength >>> 1; + for (let i = 0; i < iterations; i++) { + let inputPointer = changetype(input) + (i << 4); + let chars1 = load(inputPointer); + let chars2 = load(inputPointer, 8); + // TODO: set output1 and output2 + let output1: u32 = 0; + let output2: u16 = 0; + let outputPointer = output + i * 12; + store(outputPointer, output1); + store(outputPointer, output2, 4); + } + + // TODO: set the last 3 bytes with the last 4 characters + return changetype(output); + } + + export function decode(buffer: ArrayBuffer): string { + return decodeUnsafe(changetype(buffer), buffer.byteLength); + } + + export function decodeUnsafe(sourcePointer: usize, length: i32): string { + if (length == 0) return ""; + let iterations = Math.floor(length / 3); + let leftoverBytes = length % 3; + let stringLength = (iterations << 2) + select(4, 0, leftoverBytes != 0); + let output = __alloc(stringLength << 1, idof()); + + for (let i = 0; i < iterations; i++) { + let bytes = bswap(load(sourcePointer + i * 3)) >>> 8; + + let value1 = (bytes & 0b111111000000000000000000) >> 18; // first 6 bits + let value2 = (bytes & 0b111111000000000000) >> 12; // second 6 bits + let value3 = (bytes & 0b111111000000) >> 6 ; // third 6 bits + let value4 = (bytes & 0b111111) ; // fourth 6 bits + let chars: u64 = charCodeFromByte(value1) + | (charCodeFromByte(value2) << 16) + | (charCodeFromByte(value3) << 32) + | (charCodeFromByte(value4) << 48); + store(output + (i << alignof()), chars); + } + + // A is 0x61 + // = is 0x3D + if (leftoverBytes == 2) { + let bytes = bswap(load(sourcePointer + (iterations * 3))) << 2; + let value1 = (bytes & 0b111111000000000000) >> 12; // first 6 bits + let value2 = (bytes & 0b111111000000) >> 6 ; // second 6 bits + let value3 = (bytes & 0b111111) >> 0 ; // third 6 bits + let chars: u64 = charCodeFromByte(value1) + | (charCodeFromByte(value2) << 16) + | (charCodeFromByte(value3) << 32) + | 0x3D000000000000; + store(output + (iterations << alignof()), chars); + } else if (leftoverBytes == 1) { + let bytes = load(sourcePointer + (iterations * 3)); + trace("bytes", 1, bytes); + let value1 = (bytes & 0b11111100) >> 2; + let value2 = (bytes & 0b11) << 4; + let chars: u64 = charCodeFromByte(value1) + | (charCodeFromByte(value2) << 16) + | 0x3D003D00000000; + + store(output + (iterations << alignof()), chars); + } + + return changetype(output); + } + } } diff --git a/assembly/node.d.ts b/assembly/node.d.ts index 419384a..66cad20 100644 --- a/assembly/node.d.ts +++ b/assembly/node.d.ts @@ -94,4 +94,9 @@ declare namespace Buffer { /** Decodes a chunk of memory to a utf16le encoded string in hex format. */ export function decodeUnsafe(ptr: usize, byteLength: i32): string; } + + export namespace BASE64 { + /** Creates a string from a given ArrayBuffer that is decoded into base64 format. */ + export function decode(buffer: ArrayBuffer): string; + } } diff --git a/index.js b/index.js new file mode 100644 index 0000000..537925b --- /dev/null +++ b/index.js @@ -0,0 +1,7 @@ +const { randomFillSync } = require("crypto"); + +for (let i = 0; i < 10; i++) { + let buffer = randomFillSync(Buffer.allocUnsafe(Math.floor(Math.random() * 100))); + let result = buffer.toString("base64"); + console.log(Array.from(buffer), result); +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 05a35d2..1203f65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,20 +5,20 @@ "requires": true, "dependencies": { "@as-pect/assembly": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@as-pect/assembly/-/assembly-2.3.1.tgz", - "integrity": "sha512-KYBhyTEnaVcJjN/1EpzLhpbUHKT3pJjCPxm+Mdc7obnZ9EdVz6vN/lw+BQjeL4cUi1YLsnvgl8ftXcup5jVbQA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@as-pect/assembly/-/assembly-2.4.0.tgz", + "integrity": "sha512-EITjZbVIzoI7b61PZakfBh08LI3g7uB8tuk4e+IoRj5SuV8i0MyeLxQnzwu/uC6p9HZJYQE2BzqN6tQFIcDMWw==", "dev": true }, "@as-pect/core": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@as-pect/core/-/core-2.3.1.tgz", - "integrity": "sha512-iwd4MkGuO1wZqo9/sPlT567XYK0PkMLzBvwfkXOM2zq1wwuc5GZQrKoofgYorA40KI0edJW39djtOmPwIhx2vA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@as-pect/core/-/core-2.4.0.tgz", + "integrity": "sha512-pwWFSIF/7D13KXfO5p5kBMVrC19MwF+GUA+4jERvPNr/l5lVcx1Mk794TXMfIP4ytGXY09TM8L7Mw6wv0huwIg==", "dev": true, "requires": { - "@as-pect/assembly": "^2.3.1", + "@as-pect/assembly": "^2.4.0", "chalk": "^2.4.2", - "csv-stringify": "^5.3.0", + "csv-stringify": "^5.3.3", "long": "^4.0.0" } }, @@ -120,14 +120,11 @@ "dev": true }, "csv-stringify": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.3.0.tgz", - "integrity": "sha512-VMYPbE8zWz475smwqb9VbX9cj0y4J0PBl59UdcqzLkzXHZZ8dh4Rmbb0ZywsWEtUml4A96Hn7Q5MW9ppVghYzg==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.3.3.tgz", + "integrity": "sha512-q8Qj+/lN74LRmG7Mg0LauE5WcnJOD5MEGe1gI57IYJCB61KWuEbAFHm1uIPDkI26aqElyBB57SlE2GGwq2EY5A==", "dev": true, - "optional": true, - "requires": { - "lodash.get": "~4.4.2" - } + "optional": true }, "escape-string-regexp": { "version": "1.0.5", @@ -183,13 +180,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", - "dev": true, - "optional": true - }, "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", diff --git a/package.json b/package.json index ff4bc6b..2749784 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "url": "https://github.com/AssemblyScript/node/issues" }, "devDependencies": { - "@as-pect/core": "^2.3.1", + "@as-pect/core": "^2.4.0", "assemblyscript": "github:assemblyscript/assemblyscript", "glob": "^7.1.4", "wasi": "github:devsnek/node-wasi" diff --git a/tests/buffer.spec.ts b/tests/buffer.spec.ts index c508a08..e9f4457 100644 --- a/tests/buffer.spec.ts +++ b/tests/buffer.spec.ts @@ -570,7 +570,7 @@ describe("buffer", () => { // newBuff.swap64(); // }).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]); @@ -584,4 +584,41 @@ describe("buffer", () => { let decoded = Buffer.HEX.decode(exampleBuffer.buffer); expect(decoded).toStrictEqual(expected); }); + + test("#Base64.decode", () => { + let decoded = Buffer.BASE64.decode(create([1, 2, 3]).buffer); + expect(decoded).toBe("AQID"); + decoded = Buffer.BASE64.decode(create([1, 2, 3, 4]).buffer); + expect(decoded).toStrictEqual("AQIDBA=="); + decoded = Buffer.BASE64.decode(create([1, 2, 3, 4, 5]).buffer); + expect(decoded).toBe("AQIDBAU=", "target"); + decoded = Buffer.BASE64.decode(create([ + 150, 144, 40, 36, 82, 6, 240, 81, 182, 94, 22, + 137, 32, 212, 14, 6, 176, 169, 104, 91, 243, 241, + 62, 242, 156, 72, 51, 139, 140, 227, 109, 204, 147, + 130, 35, 104, 157, 139, 52, 201, 114, 154, 160, 230, + 185, 245, 198, 192, 21, 153, 33, 120, 133, 40, 140, + 215, 26, 233, 199, 245, 13, 211, 169, 183 + ]).buffer); + expect(decoded).toBe("lpAoJFIG8FG2XhaJINQOBrCpaFvz8T7ynEgzi4zjbcyTgiNonYs0yXKaoOa59cbAFZkheIUojNca6cf1DdOptw=="); + + decoded = Buffer.BASE64.decode(create([ + 189, 36, 9, 30, 61, 86, 186, 198, 247, 150, 23, 224, + 108, 178, 35, 223, 155, 242, 140, 171, 163, 130, 173, 126, + 60, 70, 83, 211, 113, 6, 137, 140, 166, 98, 94, 94, + 4, 110, 146, 2, 226, 51, 123, 226, 235, 4, 44, 31, + 113, 69, 169, 100, 97, 170, 239, 185, 119, 106, 112, 107, + 162, 44, 211, 166, 88, 225, 141, 38, 122, 82, 229, 153, + 115, 23, 108, 119, 38, 17, 228, 204, 52, 14, 27, 104, + 59, 64, 197, 109, 235, 206, 243, 196, 171, 179, 6, 62, + 106 + ]).buffer); + expect(decoded).toBe("vSQJHj1Wusb3lhfgbLIj35vyjKujgq1+PEZT03EGiYymYl5eBG6SAuIze+LrBCwfcUWpZGGq77l3anBroizTpljhjSZ6UuWZcxdsdyYR5Mw0DhtoO0DFbevO88SrswY+ag=="); + + decoded = Buffer.BASE64.decode(create([ + 141, 192, 94, 172, + 180, 140, 2, 59 + ]).buffer); + expect(decoded).toBe("jcBerLSMAjs="); + }); });