├── .gitattributes ├── .npmignore ├── base64.wasm ├── zz.toml ├── example.js ├── wapm.toml ├── .github └── workflows │ └── test.yml ├── tests ├── test.js └── wasm.js ├── package.json ├── LICENSE ├── Makefile ├── README.md ├── .gitignore ├── index.js ├── src └── lib.zz └── base64.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.zz linguist-language=C++ 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | tests/ 2 | target/ 3 | node_modules/ 4 | 5 | Makefile 6 | example.js 7 | -------------------------------------------------------------------------------- /base64.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/little-core-labs/base64-wasm/HEAD/base64.wasm -------------------------------------------------------------------------------- /zz.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | version = "0.2.1" 3 | name = "base64" 4 | cincludes = [] 5 | cobjects = [] 6 | pkgconfig = [] 7 | 8 | lflags = [] 9 | cflags = [] 10 | 11 | [variants] 12 | default = [] 13 | 14 | [dependencies] 15 | 16 | [repos] 17 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | const b64 = require('./') 2 | 3 | b64.ready((err) => { 4 | if (err) { throw err } 5 | const message = Buffer.from('hello world') 6 | const encoded = b64.encode(message) 7 | 8 | console.log(encoded.toString()) // aGVsbG8gd29ybGQ= 9 | 10 | const decoded = b64.decode(encoded) 11 | 12 | console.log(decoded.toString()) // hello world 13 | }) 14 | -------------------------------------------------------------------------------- /wapm.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jwerle/base64-wasm" 3 | version = "0.2.1" 4 | description = "A base64 implementation written in ZZ compiled to WASM" 5 | license = "MIT" 6 | repository = "little-core-labs/base64-wasm" 7 | readme = "README.md" 8 | homepage = "https://github.com/little-core-labs/base64-wasm" 9 | 10 | [[module]] 11 | name = "base64" 12 | source = "base64.wasm" 13 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [12.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: npm install, build, and test 21 | run: | 22 | npm i 23 | npm test 24 | env: 25 | CI: true 26 | -------------------------------------------------------------------------------- /tests/test.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto') 2 | const test = require('tape') 3 | const b64 = require('../') 4 | 5 | test('encode', (t) => { 6 | const message = crypto.randomBytes(40 * 1024) 7 | t.equal(message.toString('base64'), b64.encode(message).toString()) 8 | t.end() 9 | }) 10 | 11 | test('decode', (t) => { 12 | const message = crypto.randomBytes(40 * 1024) 13 | t.equal( 14 | message.toString('hex'), 15 | b64.decode(Buffer.from(message.toString('base64'))).toString('hex') 16 | ) 17 | t.end() 18 | }) 19 | 20 | test('ready', (t) => { 21 | b64.ready((err) => { 22 | t.error(err) 23 | t.end() 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "base64-wasm", 3 | "version": "0.2.1", 4 | "description": "A base64 implementation compiled to WASM format", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "test": "nyc -x tests -x base64.js tape tests/*.js" 11 | }, 12 | "keywords": [ 13 | "base64", 14 | "wasm", 15 | "codec" 16 | ], 17 | "author": "Joseph Werle ", 18 | "license": "MIT", 19 | "devDependencies": { 20 | "nyc": "^15.0.1", 21 | "tape": "^5.0.0", 22 | "wasm2js": "^0.2.0" 23 | }, 24 | "dependencies": { 25 | "nanoassert": "^2.0.0" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/little-core-labs/base64-wasm.git" 30 | }, 31 | "bugs": { 32 | "url": "https://github.com/little-core-labs/base64-wasm/issues" 33 | }, 34 | "homepage": "https://github.com/little-core-labs/base64-wasm#readme" 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Little Core Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CLANG ?= /opt/wasi-sdk/bin/clang 2 | 3 | WASMOPT ?= $(shell which wasm-opt) 4 | WASM2JS ?= $(shell pwd)/node_modules/.bin/wasm2js 5 | 6 | ifeq ($(CC),cc) 7 | CC = $(CLANG) 8 | endif 9 | 10 | ifeq ($(CC),gcc) 11 | CC = $(CLANG) 12 | endif 13 | 14 | ZZ ?= $(shell which zz) 15 | SRC = $(wildcard src/*.zz) 16 | TARGET = base64.wasm 17 | 18 | CFLAGS += --target=wasm32-unknown-unknown # WASM clang target 19 | CFLAGS += -nostdlib # disable stdlib 20 | 21 | LDFLAGS += -Wl,--export=__heap_base 22 | LDFLAGS += -Wl,--export=base64_encode 23 | LDFLAGS += -Wl,--export=base64_decode 24 | LDFLAGS += -Wl,--export=base64_encoding_length 25 | LDFLAGS += -Wl,--export=base64_decoding_length 26 | #LDFLAGS += -Wl,--export-dynamic # export all dynamic symbols, like functions 27 | #LDFLAGS += -Wl,--export-all # export all symbols so we get access to __heap_base, etc 28 | LDFLAGS += -Wl,--import-memory # import memory from runtime 29 | LDFLAGS += -Wl,--no-entry # don't look for a _start function 30 | LDFLAGS += -nostartfiles # no startup files 31 | 32 | ZZFLAGS += --release 33 | 34 | export CC 35 | export CFLAGS 36 | export LDFLAGS 37 | 38 | .PHONY: target 39 | 40 | build: $(TARGET) base64.js 41 | 42 | $(TARGET): $(SRC) 43 | $(ZZ) build $(ZZFLAGS) 44 | cp target/release/lib/libbase64.so $@ 45 | ifneq ($(WASMOPT),) 46 | $(WASMOPT) -Oz $@ -o $@ 47 | endif 48 | 49 | base64.js: $(TARGET) 50 | $(WASM2JS) $(TARGET) -o $@ 51 | 52 | clean: 53 | $(ZZ) clean 54 | $(RM) base64.js $(TARGET) 55 | 56 | test: base64.js 57 | npm t 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | base64-wasm 2 | =========== 3 | 4 | > A Base64 implementation for WebAssembly (WASM) written in [ZZ][zz] that 5 | > implements an [_Abstract Encoding_][abstract-encoding] interface. 6 | 7 | ## Installation 8 | 9 | ### Node 10 | 11 | ```sh 12 | $ npm install base64-wasm 13 | ``` 14 | 15 | ### WAPM 16 | 17 | ```sh 18 | $ wapm install jwerle/base64-wasm 19 | ``` 20 | 21 | ## Usage 22 | 23 | ```js 24 | const b64 = require('base64-wasm') 25 | 26 | // wait for module to be ready if loading in a browser environment 27 | b64.ready((err) => { 28 | const message = Buffer.from('hello world') 29 | const encoded = b64.encode(message) 30 | 31 | console.log(encoded.toString()) // aGVsbG8gd29ybGQ= 32 | 33 | const decoded = b64.decode(encoded) 34 | 35 | console.log(decoded.toString()) // hello world 36 | }) 37 | ``` 38 | 39 | ## API 40 | 41 | ### `buffer = encode(input[, buffer[, offset]])` 42 | 43 | Base64 encode an `input` optionally into `buffer` at an optionally 44 | specified `offset`. 45 | 46 | 47 | ### `buffer = decode(input[, buffer[, offset]])` 48 | 49 | Base64 decode an `input` optionally into `buffer` at an optionally 50 | specified `offset`. 51 | 52 | ### `promise = ready(callback)` 53 | 54 | Returns a promise that resolves or rejects when the WebAssembly exports 55 | are loading. In some cases, this may happen synchronously when this 56 | module is loaded. 57 | 58 | ```js 59 | await b64.ready() 60 | ``` 61 | 62 | ## Limits 63 | 64 | ### Initial Memory 65 | 66 | By default, this module allocates 2 pages of memory for the WebAssembly module. 67 | That is `2 * 64 * 1024` bytes. 68 | 69 | ### Maximum Memory 70 | 71 | This module allows at most 256 pages of memory. That is `256 * 64 * 72 | 1024` bytes. 73 | 74 | ## License 75 | 76 | MIT 77 | 78 | [zz]: https://github.com/zetzit/zz 79 | [abstract-encoding]: https://github.com/mafintosh/abstract-encoding 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | target/ 106 | 107 | wapm_packages -------------------------------------------------------------------------------- /tests/wasm.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto') 2 | const path = require('path') 3 | const test = require('tape') 4 | const fs = require('fs') 5 | 6 | function zzwasm(name, opts, callback) { 7 | const filename = path.resolve(__dirname, `../${name}.wasm`) 8 | const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 }) 9 | 10 | if ('string' === typeof process.env.NODE_OPTIONS) { 11 | if (process.env.NODE_OPTIONS.includes('--experimental-wasi-unstable-preview1')) { 12 | opts.wasi = true 13 | } 14 | } 15 | 16 | let wasi = null 17 | 18 | if (opts.wasi) { 19 | const { WASI } = require('wasi') 20 | wasi = new WASI() 21 | } 22 | 23 | fs.readFile(filename, onread) 24 | 25 | function onread(err, buffer) { 26 | if (err) { return callback(err) } 27 | 28 | const imports = { env: { memory } } 29 | 30 | if (opts.wasi) { 31 | imports.wasi_snapshot_preview1 = wasi.wasiImport 32 | } 33 | 34 | WebAssembly.instantiate(buffer, imports).then(onwasm, onerror) 35 | } 36 | 37 | function onwasm(wasm) { 38 | const { instance } = wasm 39 | 40 | if (opts.wasi) { 41 | wasi.start(instance) 42 | } 43 | 44 | callback(null, instance, wasm.instance.exports.memory || memory) 45 | } 46 | 47 | function onerror(err) { 48 | callback(err) 49 | } 50 | } 51 | 52 | test('loads WASM module', (t) => { 53 | zzwasm('base64', { wasi: false }, (err, mod, heap) => { 54 | t.error(err) 55 | t.end() 56 | }) 57 | }) 58 | 59 | test('encode()', (t) => { 60 | zzwasm('base64', { wasi: false }, (err, mod, memory) => { 61 | t.error(err) 62 | 63 | const { base64_encode } = mod.exports 64 | const heap = Buffer.from(memory.buffer) // pointer to containter memory (heap) 65 | 66 | const pointer = mod.exports.__heap_base // pointer to head 67 | const output = pointer 68 | //const input = Buffer.from("hello") 69 | const input = crypto.randomBytes(32) 70 | 71 | const outputLength = mod.exports.base64_encoding_length(input.length) 72 | 73 | // put `input` on heap after space reserved for output 74 | input.copy(heap, pointer + outputLength) 75 | 76 | t.equal( 77 | outputLength, 78 | base64_encode(output, pointer + outputLength, input.length)) 79 | 80 | const encoded = heap.slice(pointer + 0, pointer + outputLength) 81 | 82 | t.ok(0 === Buffer.compare(Buffer.from(input.toString('base64')), encoded)) 83 | 84 | t.end() 85 | }) 86 | }) 87 | 88 | test('decode()', (t) => { 89 | zzwasm('base64', { wasi: false }, (err, mod, memory) => { 90 | t.error(err) 91 | 92 | const { base64_encode, base64_decode } = mod.exports 93 | const heap = Buffer.from(memory.buffer) // pointer to containter memory (heap) 94 | 95 | const pointer = mod.exports.__heap_base // pointer to head 96 | const output = pointer 97 | const input = crypto.randomBytes(32) 98 | 99 | const outputLength = mod.exports.base64_encoding_length(input.length) 100 | 101 | // put `input` on heap after space reserved for output 102 | input.copy(heap, pointer + outputLength) 103 | 104 | t.equal( 105 | outputLength, 106 | base64_encode(output, pointer + outputLength, input.length)) 107 | 108 | const encoded = heap.slice(pointer + 0, pointer + outputLength) 109 | 110 | t.ok(0 === Buffer.compare(Buffer.from(input.toString('base64')), encoded)) 111 | 112 | t.equal( 113 | input.length, 114 | base64_decode(pointer + encoded.length, output, encoded.length)) 115 | 116 | const decoded = heap.slice(pointer + encoded.length, pointer+encoded.length + input.length) 117 | 118 | t.ok(0 === Buffer.compare(input, decoded)) 119 | t.end() 120 | }) 121 | }) 122 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const createBase64 = require('./base64') 2 | const assert = require('nanoassert') 3 | 4 | const WASM_NOT_LOADED_ERR = 'base64-wasm has not loaded yet.' 5 | const BYTES_PER_PAGE = 64 * 1024 6 | const MAX_PAGES = 256 7 | 8 | const memory = new WebAssembly.Memory({ initial: 2, maximum: MAX_PAGES }) 9 | const wasm = createBase64({ imports: { env: { memory }}}) 10 | 11 | const promise = new Promise((resolve, reject) => { 12 | wasm.onload((err) => { 13 | // istanbul ignore next 14 | if (err) { return reject(err) } 15 | resolve() 16 | }) 17 | }) 18 | 19 | function pointer(offset) { 20 | // pointer to heap head 21 | return wasm.exports.__heap_base + (offset || 0) 22 | } 23 | 24 | function grow(size) { 25 | const needed = Math.ceil(Math.abs(size - memory.buffer.byteLength) / BYTES_PER_PAGE) 26 | memory.grow(Math.max(0, needed)) 27 | } 28 | 29 | function sync(size) { 30 | const pages = memory.buffer.byteLength / BYTES_PER_PAGE 31 | const needed = Math.floor((memory.buffer.byteLength + size) / BYTES_PER_PAGE) 32 | 33 | if (size && needed > pages) { 34 | grow(size) 35 | } 36 | 37 | // pointer to containter memory (heap) 38 | return Buffer.from(memory.buffer) 39 | } 40 | 41 | function toBuffer(buffer, size, offset) { 42 | // istanbul ignore next 43 | if (!Buffer.isBuffer(buffer)) { 44 | return Buffer.alloc(size) 45 | } else { 46 | // istanbul ignore next 47 | return buffer.slice(offset || 0) 48 | } 49 | } 50 | 51 | async function ready(callback) { 52 | if ('function' === typeof callback) { 53 | try { 54 | await promise 55 | } catch (err) { 56 | // istanbul ignore next 57 | return void callback(err) 58 | } 59 | 60 | callback(null) 61 | } 62 | return promise 63 | } 64 | 65 | function encode(input, output, offset) { 66 | assert(wasm.exports, WASM_NOT_LOADED_ERR) 67 | 68 | // coerece to buffer 69 | input = Buffer.from(input) 70 | 71 | // output size 72 | const size = encodingLength(input) 73 | 74 | // sync and grow memory if needed and get a pointer to heap 75 | const heap = sync(input.length + size) 76 | 77 | // heap pointer 78 | const ptr = pointer() 79 | 80 | // get buffer at offset or a new one 81 | output = toBuffer(output, size, offset) 82 | 83 | // put `input` on heap after space reserved for output 84 | input.copy(heap, ptr + size) 85 | 86 | // encode input at `pointer + size` into `pointer` 87 | wasm.exports.base64_encode(ptr, ptr + size, input.length) 88 | 89 | // copy `pointer` heap value into output buffer 90 | heap.slice(ptr, ptr + size).copy(output) 91 | return output 92 | } 93 | 94 | function decode(input, output, offset) { 95 | assert(wasm.exports, WASM_NOT_LOADED_ERR) 96 | 97 | // coerece to buffer 98 | input = Buffer.from(input) 99 | 100 | // output size 101 | const size = decodingLength(input) 102 | 103 | // sync and grow memory if needed and get a pointer to heap 104 | const heap = sync(input.length + size) 105 | 106 | // heap pointer 107 | const ptr = pointer() 108 | 109 | // get buffer at offset or a new one 110 | output = toBuffer(output, size, offset) 111 | 112 | // put `input` on heap after space reserved for output 113 | input.copy(heap, ptr + size) 114 | 115 | // encode input at `ptr + size` into `ptr` 116 | wasm.exports.base64_decode(ptr, ptr + size, input.length) 117 | 118 | // copy `ptr` heap value into output buffer 119 | heap.slice(ptr + 0, ptr + size).copy(output) 120 | return output 121 | } 122 | 123 | function encodingLength(input) { 124 | assert(wasm.exports, WASM_NOT_LOADED_ERR) 125 | return wasm.exports.base64_encoding_length(input.length) 126 | } 127 | 128 | function decodingLength(input) { 129 | assert(wasm.exports, WASM_NOT_LOADED_ERR) 130 | 131 | // heap pointer 132 | const heap = sync(0) 133 | const ptr = pointer() 134 | 135 | input = Buffer.from(input) 136 | input.copy(heap, ptr) 137 | return wasm.exports.base64_decoding_length(input.length, ptr) 138 | } 139 | 140 | module.exports = { 141 | encodingLength, 142 | decodingLength, 143 | encode, 144 | decode, 145 | ready, 146 | } 147 | -------------------------------------------------------------------------------- /src/lib.zz: -------------------------------------------------------------------------------- 1 | static u8 table[] = { 2 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 3 | 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 4 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 5 | 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 6 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 7 | 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 8 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', 9 | '4', '5', '6', '7', '8', '9', '+', '/' 10 | }; 11 | 12 | export fn encode_block(u8 mut *out, u8 *bytes) 13 | where safe(out) 14 | where safe(bytes) 15 | where 4 == len(out) 16 | where 3 == len(bytes) 17 | { 18 | out[0] = (bytes[0] & 0xfc) >> 2; 19 | out[1] = ((bytes[0] & 0x03) << 4) + ((bytes[1] & 0xf0) >> 4); 20 | out[2] = ((bytes[1] & 0x0f) << 2) + ((bytes[2] & 0xc0) >> 6); 21 | out[3] = bytes[2] & 0x3f; 22 | } 23 | 24 | export fn decode_block(u8 mut *out, u8 mut *bytes) 25 | where safe(out) 26 | where safe(bytes) 27 | where 3 == len(out) 28 | where 4 == len(bytes) 29 | { 30 | for (usize mut j = 0; j < 4; ++j) { 31 | for (usize mut l = 0; l < 64; ++l) { 32 | if bytes[j] == table[l] { 33 | bytes[j] = (u8) l; 34 | break; 35 | } 36 | } 37 | } 38 | 39 | out[0] = (bytes[0] << 2) + ((bytes[1] & 0x30) >> 4); 40 | out[1] = ((bytes[1] & 0xf) << 4) + ((bytes[2] & 0x3c) >> 2); 41 | out[2] = ((bytes[2] & 0x3) << 6) + bytes[3]; 42 | } 43 | 44 | export fn encode(u8 mut *out, u8 *bytes, usize length) -> usize 45 | where safe(out) 46 | where safe(bytes) 47 | where len(bytes) >= length 48 | { 49 | usize mut pending = length; 50 | usize mut counter = 0; 51 | usize mut offset = 0; 52 | usize size = encoding_length(length); 53 | 54 | u8 mut buf[4]; 55 | u8 mut tmp[3]; 56 | 57 | static_attest(len(out) >= size); 58 | zeros(out, size); 59 | 60 | while 0 != pending-- { 61 | usize k = length - pending - 1; 62 | 63 | static_attest(len(bytes) > k); 64 | static_attest(len(tmp) > counter); 65 | 66 | tmp[counter] = bytes[k]; 67 | 68 | counter++; 69 | 70 | if 3 == counter { 71 | encode_block(buf, tmp); 72 | 73 | for (counter = 0; counter < 4; ++counter) { 74 | let x = (usize) buf[counter]; 75 | static_attest(len(table) > x); 76 | static_attest(len(out) > offset); 77 | out[offset] = table[x]; 78 | offset++; 79 | } 80 | 81 | counter = 0; 82 | } 83 | } 84 | 85 | if counter > 0 { 86 | for (usize mut j = counter; j < 3; ++j) { 87 | tmp[j] = 0; 88 | } 89 | 90 | encode_block(buf, tmp); 91 | 92 | for (usize mut j = 0; (j < counter + 1); ++j) { 93 | let x = (usize) buf[j]; 94 | static_attest(len(table) > x); 95 | static_attest(len(out) > offset); 96 | out[offset] = table[x]; 97 | offset++; 98 | } 99 | 100 | while counter++ < 3 { 101 | static_attest(len(out) > offset); 102 | out[offset] = (u8) '='; // padding 103 | offset++; 104 | } 105 | } 106 | 107 | return size; 108 | } 109 | 110 | export fn decode(u8 mut *out, u8 *bytes, usize length) -> usize 111 | where safe(out) 112 | where safe(bytes) 113 | where len(bytes) >= length 114 | where length >= 4 115 | { 116 | usize mut pending = length; 117 | usize mut counter = 0; 118 | usize mut offset = 0; 119 | usize mut index = 0; 120 | 121 | u8 mut buf[3]; 122 | u8 mut tmp[4]; 123 | 124 | while 0 != pending-- { 125 | static_attest(len(bytes) > index); 126 | 127 | // break on first padding character 128 | if (u8) '=' == bytes[index] { 129 | break; 130 | } 131 | 132 | // break if byte not found in codec table 133 | if -1 == table_index(bytes[index]) { 134 | break; 135 | } 136 | 137 | tmp[counter] = bytes[index]; 138 | 139 | counter++; 140 | index++; 141 | 142 | if 4 == counter { 143 | decode_block(buf, tmp); 144 | 145 | for (usize mut j = 0; j < 3; ++j) { 146 | static_attest(len(out) > offset); 147 | out[offset] = buf[j]; 148 | offset++; 149 | } 150 | 151 | counter = 0; 152 | } 153 | } 154 | 155 | if counter > 0 { 156 | for (usize mut j = counter; j < 4; ++j) { 157 | tmp[j] = 0; 158 | } 159 | 160 | decode_block(buf, tmp); 161 | 162 | for (usize mut j = 0; j < counter - 1; ++j) { 163 | static_attest(len(out) > offset); 164 | out[offset] = buf[j]; 165 | offset++; 166 | } 167 | } 168 | 169 | return offset; 170 | } 171 | 172 | export fn encoding_length(usize length) -> usize { 173 | // calculates the size in bytes of the encoded byte length 174 | return ((3 + (4 * length / 3)) & ~3); 175 | } 176 | 177 | export fn decoding_length(usize length, u8 *bytes) -> usize 178 | where length >= 4 179 | where safe(bytes) 180 | where len(bytes) >= length 181 | { 182 | usize mut padding = 0; 183 | if (u8) '=' == bytes[length - 1] { padding++; } 184 | if (u8) '=' == bytes[length - 2] { padding++; } 185 | // calculates the size in bytes of the decoded byte length 186 | return (3 * (length / 4)) - padding; 187 | } 188 | 189 | fn table_index(u8 byte) -> int { 190 | for (usize mut j = 0; j < static(len(table)); ++j) { 191 | if byte == table[j] { 192 | return (int) j; 193 | } 194 | } 195 | 196 | return -1; 197 | } 198 | 199 | fn zeros(u8 mut *bytes, usize count) 200 | where safe(bytes) 201 | where len(bytes) >= count 202 | { 203 | for (usize mut i = 0; i < count; ++i) { 204 | bytes[i] = 0; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /base64.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = loadWebAssembly 3 | 4 | loadWebAssembly.supported = typeof WebAssembly !== 'undefined' 5 | 6 | function loadWebAssembly (opts) { 7 | if (!loadWebAssembly.supported) return null 8 | 9 | var imp = opts && opts.imports 10 | var wasm = toUint8Array('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') 11 | var ready = null 12 | 13 | var mod = { 14 | buffer: wasm, 15 | memory: null, 16 | exports: null, 17 | realloc: realloc, 18 | onload: onload 19 | } 20 | 21 | onload(function () {}) 22 | 23 | return mod 24 | 25 | function realloc (size) { 26 | mod.exports.memory.grow(Math.max(0, Math.ceil(Math.abs(size - mod.memory.length) / 65536))) 27 | mod.memory = new Uint8Array(mod.exports.memory.buffer) 28 | } 29 | 30 | function onload (cb) { 31 | if (mod.exports) return cb() 32 | 33 | if (ready) { 34 | ready.then(cb.bind(null, null)).catch(cb) 35 | return 36 | } 37 | 38 | try { 39 | if (opts && opts.async) throw new Error('async') 40 | setup({instance: new WebAssembly.Instance(new WebAssembly.Module(wasm), imp)}) 41 | } catch (err) { 42 | ready = WebAssembly.instantiate(wasm, imp).then(setup) 43 | } 44 | 45 | onload(cb) 46 | } 47 | 48 | function setup (w) { 49 | mod.exports = w.instance.exports 50 | mod.memory = mod.exports.memory && mod.exports.memory.buffer && new Uint8Array(mod.exports.memory.buffer) 51 | } 52 | } 53 | 54 | function toUint8Array (s) { 55 | if (typeof atob === 'function') return new Uint8Array(atob(s).split('').map(charCodeAt)) 56 | return (require('buf' + 'fer').Buffer).from(s, 'base64') 57 | } 58 | 59 | function charCodeAt (c) { 60 | return c.charCodeAt(0) 61 | } 62 | --------------------------------------------------------------------------------