Skip to content

Commit

Permalink
Merge branch 'first'
Browse files Browse the repository at this point in the history
  • Loading branch information
lpatiny committed Sep 2, 2021
2 parents 8ee2fc8 + c83bad9 commit c683160
Show file tree
Hide file tree
Showing 9 changed files with 253 additions and 9 deletions.
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# uint8-base64

[![NPM version][npm-image]][npm-url]
[![build status][ci-image]][ci-url]
[![Test coverage][codecov-image]][codecov-url]
[![npm download][download-image]][download-url]

You can find a lot of NPM libraries dealing with base64 encoding and decoding.

However we could not find one that would have as input AND output an Uint8Array. This library does exactly this.

This library is pretty fast and will convert over 500 Mb per second in nodejs as well as in the browser.

## Installation

`$ npm i uint8-base64`

## Usage

```js
import { encode } from 'uint8-base64';

const result = myModule(args);
// result is ...
```

## License

The code was largely inspired by: https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727

[MIT](./LICENSE)

[npm-image]: https://img.shields.io/npm/v/uint8-base64.svg
[npm-url]: https://www.npmjs.com/package/uint8-base64
[ci-image]: https://github.com/cheminfo/uint8-base64/workflows/Node.js%20CI/badge.svg?branch=main
[ci-url]: https://github.com/cheminfo/uint8-base64/actions?query=workflow%3A%22Node.js+CI%22
[codecov-image]: https://img.shields.io/codecov/c/github/cheminfo/uint8-base64.svg
[codecov-url]: https://codecov.io/gh/cheminfo/uint8-base64
[download-image]: https://img.shields.io/npm/dm/uint8-base64.svg
[download-url]: https://www.npmjs.com/package/uint8-base64
56 changes: 56 additions & 0 deletions benchmark/big.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
'use strict';

const { Buffer } = require('buffer');

const { decode, encode } = require('../lib/');

const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();

let string = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNIOQRSTUVWXYZ';
for (let i = 0; i < 20; i++) {
string += string;
}

const uint8 = textEncoder.encode(string);
const buffer = Buffer.from(string, 'utf8');

console.time('btoa');
const btoaString = btoa(string);
console.timeEnd('btoa');

console.time('atob');
const atobString = atob(btoaString);
console.timeEnd('atob');

console.time('buffer.toString');
const bufferToString = buffer.toString('base64');
console.timeEnd('buffer.toString');

console.time('Buffer.from');
const bufferFrom = Buffer.from(bufferToString, 'base64');
console.timeEnd('Buffer.from');

console.time('encode');
const bufferBase64 = encode(uint8);
console.timeEnd('encode');

console.time('decode');
const newBuffer = decode(bufferBase64);
console.timeEnd('decode');

const newString = textDecoder.decode(newBuffer);

console.log(
string.length,
uint8.length,
atobString.length,
bufferFrom.length,
bufferBase64.length,
bufferToString.length,
btoaString.length,
btoaString === textDecoder.decode(bufferBase64),
newString === string,
newString === atobString,
newString === textDecoder.decode(bufferFrom),
);
22 changes: 13 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "base64-tools",
"name": "uint8-base64",
"version": "0.0.0",
"description": "Encode and decode base64 from and to uint8 and arraybuffer",
"description": "Encode and decode base64 from and to Uint8Array",
"main": "./lib/index.js",
"module": "./lib-esm/index.js",
"types": "./lib/index.d.ts",
Expand Down Expand Up @@ -30,24 +30,28 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/cheminfo/base64-tools.git"
"url": "git+https://github.com/cheminfo/uint8-base64.git"
},
"bugs": {
"url": "https://github.com/cheminfo/base64-tools/issues"
"url": "https://github.com/cheminfo/uint8-base64/issues"
},
"homepage": "https://github.com/cheminfo/base64-tools#readme",
"homepage": "https://github.com/cheminfo/uint8-base64#readme",
"jest": {
"preset": "ts-jest",
"testEnvironment": "node"
"testEnvironment": "node",
"testPathIgnorePatterns": [
"node_modules",
"data.ts"
]
},
"devDependencies": {
"@types/jest": "^27.0.1",
"eslint": "^7.32.0",
"eslint-config-cheminfo-typescript": "^8.0.9",
"jest": "^27.0.6",
"jest": "^27.1.0",
"prettier": "^2.3.2",
"rimraf": "^3.0.2",
"ts-jest": "^27.0.3",
"typescript": "^4.3.5"
"ts-jest": "^27.0.5",
"typescript": "^4.4.2"
}
}
15 changes: 15 additions & 0 deletions src/__tests__/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const tests = [
// ['', ''],
// ['TWFu', 'Man'],
['QQ==', 'A'],
// ['SGVsbG8gd29ybGQ=', 'Hello world'],
//['SGVsbG8gd29ybGRzIQ==', 'Hello worlds!'],
];

export const allBytes = new Uint8Array(256);
for (let i = 0; i < 256; i++) {
allBytes[i] = i;
}

export const base64AllBytes =
'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==';
22 changes: 22 additions & 0 deletions src/__tests__/decode.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { decode } from '..';

import { tests, allBytes, base64AllBytes } from './data';

const textEncoder = new TextEncoder();

describe('decode', () => {
it.each(tests)('%s -> %s', (base64: string, binary: string) => {
const encodedBase64 = textEncoder.encode(base64);
const encodedBinary = textEncoder.encode(binary);
expect(Array.from(decode(encodedBase64))).toStrictEqual(
Array.from(encodedBinary),
);
});

it('All possibles values', () => {
const encodeBase64 = textEncoder.encode(base64AllBytes);
expect(Array.from(decode(encodeBase64))).toStrictEqual(
Array.from(allBytes),
);
});
});
22 changes: 22 additions & 0 deletions src/__tests__/encode.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { encode } from '..';

import { tests, allBytes, base64AllBytes } from './data';

const textEncoder = new TextEncoder();

describe('encode', () => {
it.each(tests)('%s -> %s', (base64: string, binary: string) => {
const encodedBinary = textEncoder.encode(binary);
const encodedBase64 = textEncoder.encode(base64);
expect(Array.from(encode(encodedBinary))).toStrictEqual(
Array.from(encodedBase64),
);
});

it('All possibles values', () => {
const encodedBase64 = textEncoder.encode(base64AllBytes);
expect(Array.from(encode(allBytes))).toStrictEqual(
Array.from(encodedBase64),
);
});
});
44 changes: 44 additions & 0 deletions src/decode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const base64codes = Uint8Array.from([
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255,
255, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 0, 255, 255,
255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 255, 255, 255, 255, 255, 255, 26, 27, 28, 29, 30, 31, 32,
33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
]);

/**
* Convert a Uint8Array containing a base64 encoded bytes to a Uint8Array containing decoded values
* @returns a Uint8Array containing the decoded bytes
*/

export function decode(
input: Uint8Array, //| ArrayBuffer,
): Uint8Array {
if (!ArrayBuffer.isView(input)) {
input = new Uint8Array(input);
}

if (input.length % 4 !== 0) {
throw new Error('Unable to parse base64 string.');
}

let output = new Uint8Array(3 * (input.length / 4));
if (input.length === 0) return output;

const missingOctets =
input[input.length - 2] === 61 ? 2 : input[input.length - 1] === 61 ? 1 : 0;

for (let i = 0, j = 0; i < input.length; i += 4, j += 3) {
const buffer =
(base64codes[input[i]] << 18) |
(base64codes[input[i + 1]] << 12) |
(base64codes[input[i + 2]] << 6) |
base64codes[input[i + 3]];
output[j] = buffer >> 16;
output[j + 1] = (buffer >> 8) & 0xff;
output[j + 2] = buffer & 0xff;
}
return output.subarray(0, output.length - missingOctets);
}
39 changes: 39 additions & 0 deletions src/encode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const base64codes = Uint8Array.from([
65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106,
107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121,
122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 47,
]);

/**
* Convert a Uint8Array containing bytes to a Uint8Array containing the base64 encoded values
* @returns a Uint8Array containing the encoded bytes
*/

export function encode(input: Uint8Array): Uint8Array {
const output = new Uint8Array(Math.ceil(input.length / 3) * 4);
let i, j;
for (i = 2, j = 0; i < input.length; i += 3, j += 4) {
output[j] = base64codes[input[i - 2] >> 2];
output[j + 1] =
base64codes[((input[i - 2] & 0x03) << 4) | (input[i - 1] >> 4)];
output[j + 2] = base64codes[((input[i - 1] & 0x0f) << 2) | (input[i] >> 6)];
output[j + 3] = base64codes[input[i] & 0x3f];
}
if (i === input.length + 1) {
// 1 octet yet to write
output[j] = base64codes[input[i - 2] >> 2];
output[j + 1] = base64codes[(input[i - 2] & 0x03) << 4];
output[j + 2] = 61;
output[j + 3] = 61;
}
if (i === input.length) {
// 2 octets yet to write
output[j] = base64codes[input[i - 2] >> 2];
output[j + 1] =
base64codes[((input[i - 2] & 0x03) << 4) | (input[i - 1] >> 4)];
output[j + 2] = base64codes[(input[i - 1] & 0x0f) << 2];
output[j + 3] = 61;
}
return output;
}
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './decode';
export * from './encode';

0 comments on commit c683160

Please sign in to comment.