mirror of
https://github.com/dolthub/dolt.git
synced 2026-02-13 03:09:06 -06:00
Split node & browser Uint8Array usage (#1811)
Split node & browser Uint8Array usage
This commit is contained in:
@@ -49,12 +49,10 @@
|
||||
"copy-flow-files-commonjs": "node build/copy-flow-files.js -d dist/commonjs/ src/"
|
||||
},
|
||||
"browser": {
|
||||
"./src/bytes.js": "./src/browser/bytes.js",
|
||||
"./dist/commonjs/bytes.js": "./dist/commonjs/browser/bytes.js",
|
||||
"./src/fetch.js": "./src/browser/fetch.js",
|
||||
"./dist/commonjs/fetch.js": "./dist/commonjs/browser/fetch.js",
|
||||
"./src/sha1.js": "./src/browser/sha1.js",
|
||||
"./dist/commonjs/sha1.js": "./dist/commonjs/browser/sha1.js",
|
||||
"./src/utf8.js": "./src/browser/utf8.js",
|
||||
"./dist/commonjs/utf8.js": "./dist/commonjs/browser/utf8.js",
|
||||
"./src/put-cache.js": "./src/browser/put-cache.js",
|
||||
"./dist/commonjs/put-cache.js": "./dist/commonjs/browser/put-cache.js"
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import SequenceChunker from './sequence-chunker.js';
|
||||
import type {BoundaryChecker, makeChunkFn} from './sequence-chunker.js';
|
||||
import {Kind} from './noms-kind.js';
|
||||
import type {EqualsFn} from './edit-distance.js';
|
||||
import Bytes from './bytes.js';
|
||||
|
||||
export default class Blob extends Collection<IndexedSequence> {
|
||||
constructor(bytes: Uint8Array) {
|
||||
@@ -76,7 +77,7 @@ export class BlobReader {
|
||||
const idx = cur.indexInChunk;
|
||||
if (idx > 0) {
|
||||
invariant(idx < arr.byteLength);
|
||||
arr = new Uint8Array(arr.buffer, arr.byteOffset + idx, arr.byteLength - idx);
|
||||
arr = Bytes.subarray(arr, idx, arr.byteLength);
|
||||
}
|
||||
|
||||
return cur.advanceChunk().then(() => {
|
||||
@@ -147,7 +148,7 @@ const blobPattern = ((1 << 11) | 0) - 1; // Avg Chunk Size: 2k
|
||||
|
||||
function newBlobLeafChunkFn(vr: ?ValueReader, vw: ?ValueWriter): makeChunkFn {
|
||||
return (items: Array<number>) => {
|
||||
const blobLeaf = new BlobLeafSequence(vr, new Uint8Array(items));
|
||||
const blobLeaf = new BlobLeafSequence(vr, Bytes.fromValues(items));
|
||||
const blob = Blob.fromSequence(blobLeaf);
|
||||
let mt;
|
||||
if (vw) {
|
||||
|
||||
125
js/src/browser/bytes.js
Normal file
125
js/src/browser/bytes.js
Normal file
@@ -0,0 +1,125 @@
|
||||
// @flow
|
||||
|
||||
// Copyright 2016 The Noms Authors. All rights reserved.
|
||||
// Licensed under the Apache License, version 2.0:
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
import {TextEncoder, TextDecoder} from './text-encoding.js';
|
||||
import Rusha from 'rusha';
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
const encoder = new TextEncoder();
|
||||
const r = new Rusha();
|
||||
const littleEndian = true;
|
||||
|
||||
export default class Bytes {
|
||||
static alloc(size: number): Uint8Array {
|
||||
return new Uint8Array(size);
|
||||
}
|
||||
|
||||
static fromValues(values: number[]): Uint8Array {
|
||||
return new Uint8Array(values);
|
||||
}
|
||||
|
||||
static fromString(s: string): Uint8Array {
|
||||
return encoder.encode(s);
|
||||
}
|
||||
|
||||
static toString(buff: Uint8Array): string {
|
||||
return decoder.decode(buff);
|
||||
}
|
||||
|
||||
static fromHexString(s: string): Uint8Array {
|
||||
const length = s.length / 2;
|
||||
const buff = new Uint8Array(length);
|
||||
for (let i = 0; i < length; i++) {
|
||||
const hc = asciiToBinary(s.charCodeAt(2 * i));
|
||||
const lc = asciiToBinary(s.charCodeAt(2 * i + 1));
|
||||
buff[i] = hc << 4 | lc;
|
||||
}
|
||||
return buff;
|
||||
}
|
||||
|
||||
static toHexString(buff: Uint8Array): string {
|
||||
const hex = new Array(buff.byteLength * 2);
|
||||
for (let i = 0; i < buff.length; i++) {
|
||||
hex[i] = byteToAscii[buff[i]];
|
||||
}
|
||||
return hex.join('');
|
||||
}
|
||||
|
||||
static grow(buff: Uint8Array, size: number): Uint8Array {
|
||||
const b = new Uint8Array(size);
|
||||
b.set(buff);
|
||||
return b;
|
||||
}
|
||||
|
||||
static copy(source: Uint8Array, target: Uint8Array, targetStart: number = 0) {
|
||||
target.set(source, targetStart);
|
||||
}
|
||||
|
||||
static slice(buff: Uint8Array, start: number, end: number): Uint8Array {
|
||||
return buff.slice(start, end);
|
||||
}
|
||||
|
||||
static subarray(buff: Uint8Array, start: number, end: number): Uint8Array {
|
||||
return buff.subarray(start, end);
|
||||
}
|
||||
|
||||
static readUtf8(buff: Uint8Array, start: number, end: number): string {
|
||||
return Bytes.toString(buff.subarray(start, end));
|
||||
}
|
||||
|
||||
static encodeUtf8(str: string, buff: Uint8Array, dv: DataView, offset: number): number {
|
||||
const strBuff = Bytes.fromString(str);
|
||||
const size = strBuff.byteLength;
|
||||
|
||||
dv.setUint32(offset, size, littleEndian);
|
||||
offset += 4;
|
||||
|
||||
buff.set(strBuff, offset);
|
||||
offset += size;
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
static compare(b1: Uint8Array, b2: Uint8Array): number {
|
||||
const b1Len = b1.byteLength;
|
||||
const b2Len = b2.byteLength;
|
||||
|
||||
for (let i = 0; i < b1Len && i < b2Len; i++) {
|
||||
if (b1[i] < b2[i]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (b1[i] > b2[i]) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (b1Len < b2Len) {
|
||||
return -1;
|
||||
}
|
||||
if (b1Len > b2Len) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static sha1(data: Uint8Array): Uint8Array {
|
||||
const ta = r.rawDigest(data);
|
||||
return new Uint8Array(ta.buffer, ta.byteOffset, ta.byteLength);
|
||||
}
|
||||
}
|
||||
|
||||
function asciiToBinary(cc: number): number {
|
||||
// This only accepts the char code for '0' - '9', 'a' - 'f'
|
||||
return cc - (cc <= 57 ? 48 : 87); // '9', '0', 'a' - 10
|
||||
}
|
||||
|
||||
// Precompute '00' to 'ff'.
|
||||
const byteToAscii = new Array(256);
|
||||
for (let i = 0; i < 256; i++) {
|
||||
byteToAscii[i] = (i < 0x10 ? '0' : '') + i.toString(16);
|
||||
}
|
||||
@@ -45,9 +45,9 @@ export function fetchText(url: string, options: FetchOptions = {}): Promise<stri
|
||||
return fetch(url, 'text', options);
|
||||
}
|
||||
|
||||
export function fetchArrayBuffer(url: string, options: FetchOptions = {}): Promise<ArrayBuffer> {
|
||||
export function fetchUint8Array(url: string, options: FetchOptions = {}): Promise<Uint8Array> {
|
||||
if (self.fetch) {
|
||||
return self.fetch(url, options).then(resp => resp.arrayBuffer());
|
||||
return self.fetch(url, options).then(resp => resp.arrayBuffer()).then(ar => new Uint8Array(ar));
|
||||
}
|
||||
|
||||
return fetch(url, 'arraybuffer', options);
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
// @flow
|
||||
|
||||
// Copyright 2016 The Noms Authors. All rights reserved.
|
||||
// Licensed under the Apache License, version 2.0:
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
// This is the browser version. The Node.js version is in ../sha1.js.
|
||||
|
||||
import Rusha from 'rusha';
|
||||
|
||||
const r = new Rusha();
|
||||
|
||||
export default function sha1(data: Uint8Array): Uint8Array {
|
||||
const ta = r.rawDigest(data);
|
||||
return new Uint8Array(ta.buffer, ta.byteOffset, ta.byteLength);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// @flow
|
||||
|
||||
// Copyright 2016 The Noms Authors. All rights reserved.
|
||||
// Licensed under the Apache License, version 2.0:
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
// This is the browser version. The Node.js version is in ../utf8.js.
|
||||
|
||||
import {TextEncoder, TextDecoder} from './text-encoding.js';
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
export function encode(s: string): Uint8Array {
|
||||
return encoder.encode(s);
|
||||
}
|
||||
|
||||
export function decode(data: Uint8Array): string {
|
||||
return decoder.decode(data);
|
||||
}
|
||||
@@ -7,11 +7,7 @@
|
||||
import {suite, test} from 'mocha';
|
||||
import {assert} from 'chai';
|
||||
import BuzHash from './buzhash.js';
|
||||
import {encode} from './utf8.js';
|
||||
|
||||
function bytes(s: string): Uint8Array {
|
||||
return encode(s);
|
||||
}
|
||||
import Bytes from './bytes.js';
|
||||
|
||||
const loremipsum1 = `Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
|
||||
Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et
|
||||
@@ -78,10 +74,12 @@ suite('BuzHash', () => {
|
||||
assert.isTrue(found);
|
||||
}
|
||||
|
||||
test(32, bytes('Aenean massa. Cum sociis natoque'), bytes(loremipsum1), 91);
|
||||
test(32,
|
||||
Bytes.fromString('Aenean massa. Cum sociis natoque'),
|
||||
Bytes.fromString(loremipsum1), 91);
|
||||
test(64,
|
||||
bytes('Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi'),
|
||||
bytes(loremipsum2),
|
||||
Bytes.fromString('Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi'),
|
||||
Bytes.fromString(loremipsum2),
|
||||
592);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
// This a javascript port of github.com/kch42/buzhash.
|
||||
|
||||
import Bytes from './bytes.js';
|
||||
|
||||
const bytehash: Array<number> = [
|
||||
0x12bd9527, 0xf4140cea, 0x987bd6e1, 0x79079850, 0xafbfd539, 0xd350ce0a,
|
||||
0x82973931, 0x9fc32b9c, 0x28003b88, 0xc30c13aa, 0x6b678c34, 0x5844ef1d,
|
||||
@@ -64,7 +66,7 @@ export default class BuzHash {
|
||||
this._n = n;
|
||||
this._bshiftn = n % 32;
|
||||
this._bshiftm = 32 - this._bshiftn;
|
||||
this._buf = new Uint8Array(n);
|
||||
this._buf = Bytes.alloc(n);
|
||||
this.reset();
|
||||
}
|
||||
|
||||
|
||||
184
js/src/bytes-test.js
Normal file
184
js/src/bytes-test.js
Normal file
@@ -0,0 +1,184 @@
|
||||
// @flow
|
||||
|
||||
// Copyright 2016 The Noms Authors. All rights reserved.
|
||||
// Licensed under the Apache License, version 2.0:
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
import {assert} from 'chai';
|
||||
import {suite, test} from 'mocha';
|
||||
|
||||
import {default as NodeBytes} from './bytes.js';
|
||||
import {default as BrowserBytes} from './browser/bytes.js';
|
||||
|
||||
function assertUint8Equal(ar1: Uint8Array, ar2: Uint8Array) {
|
||||
assert.equal(ar1.length, ar2.length);
|
||||
for (let i = 0; i < ar1.length; i++) {
|
||||
assert.equal(ar1[i], ar2[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function assertBytes(expect: number[], buff: Uint8Array) {
|
||||
const buffValues = [];
|
||||
for (let i = 0; i < buff.length; i++) {
|
||||
buffValues.push(buff[i]);
|
||||
}
|
||||
|
||||
assert.deepEqual(expect, buffValues);
|
||||
}
|
||||
|
||||
suite('Bytes', () => {
|
||||
test('alloc', () => {
|
||||
function test(size: number) {
|
||||
const n = NodeBytes.alloc(size);
|
||||
const b = BrowserBytes.alloc(size);
|
||||
assert.strictEqual(size, n.length);
|
||||
assert.strictEqual(size, b.length);
|
||||
}
|
||||
|
||||
test(0);
|
||||
test(10);
|
||||
test(2048);
|
||||
});
|
||||
|
||||
test('string', () => {
|
||||
function test(expect: number[], str: string) {
|
||||
assertBytes(expect, NodeBytes.fromString(str));
|
||||
assertBytes(expect, BrowserBytes.fromString(str));
|
||||
assert.strictEqual(str, NodeBytes.toString(NodeBytes.fromValues(expect)));
|
||||
assert.strictEqual(str, BrowserBytes.toString(BrowserBytes.fromValues(expect)));
|
||||
}
|
||||
|
||||
test([], '');
|
||||
test([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100], 'hello world');
|
||||
test([207, 128], '\u03c0');
|
||||
});
|
||||
|
||||
test('hex', () => {
|
||||
function test(expect: number[], hexString: string) {
|
||||
assertBytes(expect, NodeBytes.fromHexString(hexString));
|
||||
assertBytes(expect, BrowserBytes.fromHexString(hexString));
|
||||
assert.strictEqual(hexString, NodeBytes.toHexString(NodeBytes.fromValues(expect)));
|
||||
assert.strictEqual(hexString, BrowserBytes.toHexString(BrowserBytes.fromValues(expect)));
|
||||
}
|
||||
|
||||
test([], '');
|
||||
test([0x3e, 0x9a, 0x25, 0x9b], '3e9a259b');
|
||||
test([0x04, 0x03, 0x97, 0xca, 0x92, 0x1d, 0x02, 0x7d, 0xae, 0x6d,
|
||||
0xfc, 0x01, 0x02, 0xf2, 0x04, 0x46, 0x6f, 0xea, 0xd5, 0xc9],
|
||||
'040397ca921d027dae6dfc0102f204466fead5c9');
|
||||
});
|
||||
|
||||
test('grow', () => {
|
||||
function test(bytes: any) {
|
||||
const buff = bytes.alloc(4);
|
||||
buff[0] = 1;
|
||||
buff[1] = 2;
|
||||
buff[2] = 3;
|
||||
const b2 = bytes.grow(buff, 8);
|
||||
assert.strictEqual(1, b2[0]);
|
||||
assert.strictEqual(2, b2[1]);
|
||||
assert.strictEqual(3, b2[2]);
|
||||
assert.strictEqual(8, b2.length);
|
||||
}
|
||||
|
||||
test(NodeBytes);
|
||||
test(BrowserBytes);
|
||||
});
|
||||
|
||||
test('copy', () => {
|
||||
function test(bytes: any) {
|
||||
const buff = bytes.alloc(3);
|
||||
buff[0] = 1;
|
||||
buff[1] = 2;
|
||||
buff[2] = 3;
|
||||
const b2 = bytes.alloc(6);
|
||||
b2[0] = 10;
|
||||
b2[1] = 11;
|
||||
b2[2] = 12;
|
||||
bytes.copy(buff, b2, 3);
|
||||
assert.strictEqual(10, b2[0]);
|
||||
assert.strictEqual(11, b2[1]);
|
||||
assert.strictEqual(12, b2[2]);
|
||||
assert.strictEqual(1, b2[3]);
|
||||
assert.strictEqual(2, b2[4]);
|
||||
assert.strictEqual(3, b2[5]);
|
||||
}
|
||||
|
||||
test(NodeBytes);
|
||||
test(BrowserBytes);
|
||||
});
|
||||
|
||||
test('slice', () => {
|
||||
function test(bytes: any) {
|
||||
const buff = bytes.alloc(3);
|
||||
buff[0] = 1;
|
||||
buff[1] = 2;
|
||||
buff[2] = 3;
|
||||
|
||||
const b2 = bytes.slice(buff, 1, 3);
|
||||
buff[2] = 4;
|
||||
|
||||
assert.strictEqual(2, b2[0]);
|
||||
assert.strictEqual(3, b2[1]);
|
||||
}
|
||||
|
||||
test(NodeBytes);
|
||||
test(BrowserBytes);
|
||||
});
|
||||
|
||||
test('subarray', () => {
|
||||
function test(bytes: any) {
|
||||
const buff = bytes.alloc(3);
|
||||
buff[0] = 1;
|
||||
buff[1] = 2;
|
||||
buff[2] = 3;
|
||||
|
||||
const b2 = bytes.subarray(buff, 1, 3);
|
||||
buff[2] = 4;
|
||||
|
||||
assert.strictEqual(2, b2[0]);
|
||||
assert.strictEqual(4, b2[1]);
|
||||
}
|
||||
|
||||
test(NodeBytes);
|
||||
test(BrowserBytes);
|
||||
});
|
||||
|
||||
test('compare', () => {
|
||||
function test(bytes: any, expect: number, a1: number[], a2: number[]) {
|
||||
assert.strictEqual(expect, bytes.compare(bytes.fromValues(a1), bytes.fromValues(a2)));
|
||||
}
|
||||
|
||||
test(NodeBytes, 0, [], []);
|
||||
test(BrowserBytes, 0, [], []);
|
||||
|
||||
test(NodeBytes, 0, [1, 2, 3], [1, 2, 3]);
|
||||
test(BrowserBytes, 0, [1, 2, 3], [1, 2, 3]);
|
||||
|
||||
test(NodeBytes, -1, [1, 2], [1, 2, 3]);
|
||||
test(BrowserBytes, -1, [1, 2], [1, 2, 3]);
|
||||
|
||||
test(NodeBytes, 1, [1, 2, 3, 4], [1, 2, 3]);
|
||||
test(BrowserBytes, 1, [1, 2, 3, 4], [1, 2, 3]);
|
||||
|
||||
test(NodeBytes, 1, [2, 2, 3], [1, 2, 3]);
|
||||
test(BrowserBytes, 1, [2, 2, 3], [1, 2, 3]);
|
||||
|
||||
test(NodeBytes, -1, [0, 2, 3], [1, 2, 3]);
|
||||
test(BrowserBytes, -1, [0, 2, 3], [1, 2, 3]);
|
||||
});
|
||||
|
||||
test('sha1', () => {
|
||||
function test(arr: number[]) {
|
||||
// Node uses a Buffer, browser uses a Uint8Array
|
||||
const n = NodeBytes.sha1(NodeBytes.fromValues(arr));
|
||||
const b = BrowserBytes.sha1(BrowserBytes.fromValues(arr));
|
||||
assertUint8Equal(n, b);
|
||||
}
|
||||
|
||||
test([]);
|
||||
test([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
test([1, 2, 3, 4, 5]);
|
||||
test([2, 3, 4, 5, 10]);
|
||||
});
|
||||
});
|
||||
98
js/src/bytes.js
Normal file
98
js/src/bytes.js
Normal file
@@ -0,0 +1,98 @@
|
||||
// @flow
|
||||
|
||||
// Copyright 2016 The Noms Authors. All rights reserved.
|
||||
// Licensed under the Apache License, version 2.0:
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
import crypto from 'crypto';
|
||||
|
||||
// Note: Flow doesn't know that Buffer extends Uint8Array, thus all of the FlowIssues.
|
||||
export default class Bytes {
|
||||
|
||||
static alloc(size: number): Uint8Array {
|
||||
// $FlowIssue
|
||||
return Buffer.alloc(size);
|
||||
}
|
||||
|
||||
static fromValues(values: number[]): Uint8Array {
|
||||
// $FlowIssue
|
||||
return Buffer.from(values);
|
||||
}
|
||||
|
||||
static fromString(s: string): Uint8Array {
|
||||
// $FlowIssue
|
||||
return Buffer.from(s);
|
||||
}
|
||||
|
||||
static toString(buff: Uint8Array): string {
|
||||
return buff.toString();
|
||||
}
|
||||
|
||||
static fromHexString(str: string): Uint8Array {
|
||||
// $FlowIssue
|
||||
return Buffer.from(str, 'hex');
|
||||
}
|
||||
|
||||
static toHexString(buff: Uint8Array): string {
|
||||
return buff.toString('hex');
|
||||
}
|
||||
|
||||
static grow(buff: Uint8Array, size: number): Uint8Array {
|
||||
const b = Bytes.alloc(size);
|
||||
// $FlowIssue
|
||||
buff.copy(b);
|
||||
return b;
|
||||
}
|
||||
|
||||
static copy(source: Uint8Array, target: Uint8Array, targetStart: number = 0) {
|
||||
// $FlowIssue
|
||||
if (source instanceof Buffer) {
|
||||
// $FlowIssue
|
||||
source.copy(target, targetStart);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < source.length; i++) {
|
||||
target[targetStart++] = source[i];
|
||||
}
|
||||
}
|
||||
|
||||
static slice(buff: Uint8Array, start: number, end: number): Uint8Array {
|
||||
const v = Bytes.alloc(end - start);
|
||||
// $FlowIssue
|
||||
buff.copy(v, 0, start, end);
|
||||
return v;
|
||||
}
|
||||
|
||||
static subarray(buff: Uint8Array, start: number, end: number): Uint8Array {
|
||||
// $FlowIssue
|
||||
return Buffer.from(buff.buffer, buff.byteOffset + start, end - start);
|
||||
}
|
||||
|
||||
static readUtf8(buff: Uint8Array, start: number, end: number): string {
|
||||
return buff.toString('utf8', start, end);
|
||||
}
|
||||
|
||||
static encodeUtf8(str: string, buff: Uint8Array, dv: DataView, offset: number): number {
|
||||
const size = Buffer.byteLength(str);
|
||||
// $FlowIssue
|
||||
buff.writeUInt32LE(size, offset);
|
||||
offset += 4;
|
||||
|
||||
// $FlowIssue
|
||||
buff.write(str, offset);
|
||||
offset += size;
|
||||
return offset;
|
||||
}
|
||||
|
||||
static compare(b1: Uint8Array, b2: Uint8Array): number {
|
||||
// $FlowIssue
|
||||
return b1.compare(b2);
|
||||
}
|
||||
|
||||
static sha1(data: Uint8Array): Uint8Array {
|
||||
const hash = crypto.createHash('sha1');
|
||||
hash.update(data);
|
||||
return hash.digest();
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import Chunk from './chunk.js';
|
||||
import type Hash from './hash.js';
|
||||
import {deserialize, serialize} from './chunk-serializer.js';
|
||||
import type {ChunkStream} from './chunk-serializer.js';
|
||||
import Bytes from './bytes.js';
|
||||
|
||||
suite('ChunkSerializer', () => {
|
||||
|
||||
@@ -81,10 +82,10 @@ suite('ChunkSerializer', () => {
|
||||
test('large chunk', async () => {
|
||||
const expHints = [];
|
||||
const expChunks = [
|
||||
new Chunk(new Uint8Array(1024)),
|
||||
new Chunk(Bytes.alloc(1024)),
|
||||
Chunk.fromString('abc'),
|
||||
Chunk.fromString('def'),
|
||||
new Chunk(new Uint8Array(2048))];
|
||||
new Chunk(Bytes.alloc(2048))];
|
||||
|
||||
const pSerialized = serialize(new Set(expHints), createChunkStream(expChunks));
|
||||
const {hints, chunks} = deserialize(await pSerialized);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import Chunk from './chunk.js';
|
||||
import Hash from './hash.js';
|
||||
import {invariant} from './assert.js';
|
||||
import Bytes from './bytes.js';
|
||||
|
||||
const headerSize = 4; // uint32
|
||||
const bigEndian = false; // Passing false to DataView methods makes them use big-endian byte order.
|
||||
@@ -16,60 +17,57 @@ const chunkHeaderSize = sha1Size + chunkLengthSize;
|
||||
|
||||
export type ChunkStream = (cb: (chunk: Chunk) => void) => Promise<void>
|
||||
|
||||
export function serialize(hints: Set<Hash>, stream: ChunkStream): Promise<ArrayBuffer> {
|
||||
let buf = new ArrayBuffer(1024);
|
||||
const ensureCapacity = (needed: number) => {
|
||||
if (buf.byteLength >= needed) {
|
||||
export function serialize(hints: Set<Hash>, stream: ChunkStream): Promise<Uint8Array> {
|
||||
const hintsLength = serializedHintsLength(hints);
|
||||
let buf = Bytes.alloc(Math.max(hintsLength * 2, 2048));
|
||||
let dv = new DataView(buf.buffer);
|
||||
let offset = 0;
|
||||
|
||||
const ensureCapacity = (n: number) => {
|
||||
let length = buf.byteLength;
|
||||
if (offset + n <= length) {
|
||||
return;
|
||||
}
|
||||
let newLen = buf.byteLength;
|
||||
for (; newLen < needed; newLen *= 2)
|
||||
;
|
||||
const newBuf = new ArrayBuffer(newLen);
|
||||
new Uint8Array(newBuf).set(new Uint8Array(buf));
|
||||
buf = newBuf;
|
||||
|
||||
while (offset + n > length) {
|
||||
length *= 2;
|
||||
}
|
||||
buf = Bytes.grow(buf, length);
|
||||
dv = new DataView(buf.buffer);
|
||||
};
|
||||
|
||||
const hintsLength = serializedHintsLength(hints);
|
||||
if (buf.byteLength < hintsLength) {
|
||||
buf = new ArrayBuffer(hintsLength * 2); // Leave space for some chunks.
|
||||
}
|
||||
let offset = serializeHints(hints, buf);
|
||||
offset = serializeHints(hints, buf, dv);
|
||||
|
||||
return stream(chunk => {
|
||||
const chunkLength = serializedChunkLength(chunk);
|
||||
ensureCapacity(offset + chunkLength);
|
||||
offset = serializeChunk(chunk, buf, offset);
|
||||
}).then(() => buf.slice(0, offset));
|
||||
ensureCapacity(chunkLength);
|
||||
offset = serializeChunk(chunk, buf, dv, offset);
|
||||
}).then(() => Bytes.subarray(buf, 0, offset));
|
||||
}
|
||||
|
||||
function serializeChunk(chunk: Chunk, buffer: ArrayBuffer, offset: number): number {
|
||||
function serializeChunk(chunk: Chunk, buffer: Uint8Array, dv: DataView, offset: number): number {
|
||||
invariant(buffer.byteLength - offset >= serializedChunkLength(chunk),
|
||||
'Invalid chunk buffer');
|
||||
|
||||
const hashArray = new Uint8Array(buffer, offset, sha1Size);
|
||||
hashArray.set(chunk.hash.digest);
|
||||
Bytes.copy(chunk.hash.digest, buffer, offset);
|
||||
offset += sha1Size;
|
||||
|
||||
const chunkLength = chunk.data.length;
|
||||
const view = new DataView(buffer, offset, chunkLengthSize);
|
||||
view.setUint32(0, chunkLength, bigEndian); // Coerce number to uint32
|
||||
dv.setUint32(offset, chunkLength, bigEndian);
|
||||
offset += chunkLengthSize;
|
||||
|
||||
const dataArray = new Uint8Array(buffer, offset, chunkLength);
|
||||
dataArray.set(chunk.data);
|
||||
Bytes.copy(chunk.data, buffer, offset);
|
||||
offset += chunkLength;
|
||||
return offset;
|
||||
}
|
||||
|
||||
function serializeHints(hints: Set<Hash>, buffer: ArrayBuffer): number {
|
||||
function serializeHints(hints: Set<Hash>, buff: Uint8Array, dv: DataView): number {
|
||||
let offset = 0;
|
||||
const view = new DataView(buffer, offset, headerSize);
|
||||
view.setUint32(0, hints.size | 0, bigEndian); // Coerce number to uint32
|
||||
dv.setUint32(offset, hints.size, bigEndian);
|
||||
offset += headerSize;
|
||||
|
||||
hints.forEach(hash => {
|
||||
const hashArray = new Uint8Array(buffer, offset, sha1Size);
|
||||
hashArray.set(hash.digest);
|
||||
Bytes.copy(hash.digest, buff, offset);
|
||||
offset += sha1Size;
|
||||
});
|
||||
|
||||
@@ -84,54 +82,46 @@ function serializedChunkLength(chunk: Chunk): number {
|
||||
return chunkHeaderSize + chunk.data.length;
|
||||
}
|
||||
|
||||
export function deserialize(buffer: ArrayBuffer): {hints: Array<Hash>, chunks: Array<Chunk>} {
|
||||
const {hints, offset} = deserializeHints(buffer);
|
||||
return {hints: hints, chunks: deserializeChunks(buffer, offset)};
|
||||
export function deserialize(buff: Uint8Array): {hints: Array<Hash>, chunks: Array<Chunk>} {
|
||||
const dv = new DataView(buff.buffer, buff.byteOffset, buff.byteLength);
|
||||
const {hints, offset} = deserializeHints(buff, dv);
|
||||
return {hints: hints, chunks: deserializeChunks(buff, dv, offset)};
|
||||
}
|
||||
|
||||
function deserializeHints(buffer: ArrayBuffer): {hints: Array<Hash>, offset: number} {
|
||||
function deserializeHints(buff: Uint8Array, dv: DataView): {hints: Array<Hash>, offset: number} {
|
||||
const hints:Array<Hash> = [];
|
||||
|
||||
let offset = 0;
|
||||
const view = new DataView(buffer, offset, headerSize);
|
||||
const numHints = view.getUint32(0, bigEndian);
|
||||
const numHints = dv.getUint32(offset, bigEndian);
|
||||
offset += headerSize;
|
||||
|
||||
const totalLength = headerSize + (numHints * sha1Size);
|
||||
for (; offset < totalLength;) {
|
||||
invariant(buffer.byteLength - offset >= sha1Size, 'Invalid hint buffer');
|
||||
|
||||
// Make a slice/copy of the ArrayBuffer to prevent `buffer` from being gc'ed.
|
||||
const hashArray = new Uint8Array(buffer.slice(offset, offset + sha1Size));
|
||||
const hash = new Hash(hashArray);
|
||||
invariant(buff.byteLength - offset >= sha1Size * numHints, 'Invalid hint buffer');
|
||||
for (let i = 0; i < numHints; i++) {
|
||||
const hash = new Hash(Bytes.slice(buff, offset, offset + sha1Size)); // copy
|
||||
offset += sha1Size;
|
||||
|
||||
hints.push(hash);
|
||||
}
|
||||
|
||||
return {hints: hints, offset: offset};
|
||||
}
|
||||
|
||||
export function deserializeChunks(buffer: ArrayBuffer, offset: number = 0): Array<Chunk> {
|
||||
export function deserializeChunks(buff: Uint8Array, dv: DataView, offset: number = 0):
|
||||
Array<Chunk> {
|
||||
const chunks:Array<Chunk> = [];
|
||||
|
||||
const totalLenth = buffer.byteLength;
|
||||
for (; offset < totalLenth;) {
|
||||
invariant(buffer.byteLength - offset >= chunkHeaderSize, 'Invalid chunk buffer');
|
||||
const totalLength = buff.byteLength;
|
||||
for (; offset < totalLength;) {
|
||||
invariant(buff.byteLength - offset >= chunkHeaderSize, 'Invalid chunk buffer');
|
||||
|
||||
// No need to copy the data out since we are not holding on to the hash object.
|
||||
const hashArray = new Uint8Array(buffer, offset, sha1Size);
|
||||
const hash = new Hash(hashArray);
|
||||
const hash = new Hash(Bytes.subarray(buff, offset, offset + sha1Size));
|
||||
offset += sha1Size;
|
||||
|
||||
const view = new DataView(buffer, offset, chunkLengthSize);
|
||||
const chunkLength = view.getUint32(0, bigEndian);
|
||||
const chunkLength = dv.getUint32(offset, bigEndian);
|
||||
offset += chunkLengthSize;
|
||||
|
||||
invariant(offset + chunkLength <= totalLenth, 'Invalid chunk buffer');
|
||||
|
||||
// Make a copy of the data so that buffer can be collected.
|
||||
const chunk = new Chunk(new Uint8Array(buffer.slice(offset, offset + chunkLength)));
|
||||
invariant(offset + chunkLength <= totalLength, 'Invalid chunk buffer');
|
||||
const chunk = new Chunk(Bytes.slice(buff, offset, offset + chunkLength)); // copy
|
||||
|
||||
invariant(chunk.hash.equals(hash), 'Serialized hash !== computed hash');
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@ import {assert} from 'chai';
|
||||
import Chunk from './chunk.js';
|
||||
import Hash from './hash.js';
|
||||
import {notNull} from './assert.js';
|
||||
import Bytes from './bytes.js';
|
||||
|
||||
suite('Chunk', () => {
|
||||
test('construct', () => {
|
||||
const c = Chunk.fromString('abc');
|
||||
assert.strictEqual(c.toString(), 'abc');
|
||||
assert.isTrue(c.hash.equals(
|
||||
notNull(Hash.parse('sha1-a9993e364706816aba3e25717850c26c9cd0d89d'))));
|
||||
assert.isFalse(c.isEmpty());
|
||||
@@ -22,7 +22,6 @@ suite('Chunk', () => {
|
||||
test('construct with hash', () => {
|
||||
const hash = notNull(Hash.parse('sha1-0000000000000000000000000000000000000001'));
|
||||
const c = Chunk.fromString('abc', hash);
|
||||
assert.strictEqual(c.toString(), 'abc');
|
||||
assert.isTrue(c.hash.equals(
|
||||
notNull(Hash.parse('sha1-0000000000000000000000000000000000000001'))));
|
||||
assert.isFalse(c.isEmpty());
|
||||
@@ -34,7 +33,7 @@ suite('Chunk', () => {
|
||||
assert.isTrue(c.isEmpty());
|
||||
}
|
||||
|
||||
assertChunkIsEmpty(new Chunk());
|
||||
assertChunkIsEmpty(new Chunk(Bytes.alloc(0)));
|
||||
assertChunkIsEmpty(Chunk.fromString(''));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
import Hash from './hash.js';
|
||||
import {encode, decode} from './utf8.js';
|
||||
import Bytes from './bytes.js';
|
||||
|
||||
export default class Chunk {
|
||||
data: Uint8Array;
|
||||
_hash: ?Hash;
|
||||
|
||||
constructor(data: Uint8Array = new Uint8Array(0), hash: ?Hash) {
|
||||
constructor(data: Uint8Array, hash: ?Hash) {
|
||||
this.data = data;
|
||||
this._hash = hash;
|
||||
}
|
||||
@@ -25,14 +25,14 @@ export default class Chunk {
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return decode(this.data);
|
||||
return Bytes.toString(this.data);
|
||||
}
|
||||
|
||||
static fromString(s: string, hash: ?Hash): Chunk {
|
||||
return new Chunk(Bytes.fromString(s), hash);
|
||||
}
|
||||
|
||||
static emptyChunk: Chunk;
|
||||
|
||||
static fromString(s: string, hash: ?Hash): Chunk {
|
||||
return new Chunk(encode(s), hash);
|
||||
}
|
||||
}
|
||||
|
||||
export const emptyChunk = new Chunk();
|
||||
export const emptyChunk = new Chunk(Bytes.alloc(0));
|
||||
|
||||
@@ -8,12 +8,12 @@ import Chunk from './chunk.js';
|
||||
import Hash, {sha1Size} from './hash.js';
|
||||
import ValueDecoder from './value-decoder.js';
|
||||
import ValueEncoder from './value-encoder.js';
|
||||
import {encode, decode} from './utf8.js';
|
||||
import {invariant} from './assert.js';
|
||||
import {setEncodeValue} from './get-hash.js';
|
||||
import {setHash, ValueBase} from './value.js';
|
||||
import type Value from './value.js';
|
||||
import type {ValueReader, ValueWriter} from './value-store.js';
|
||||
import Bytes from './bytes.js';
|
||||
|
||||
export function encodeValue(v: Value, vw: ?ValueWriter): Chunk {
|
||||
const w = new BinaryNomsWriter();
|
||||
@@ -68,22 +68,20 @@ export interface NomsWriter {
|
||||
}
|
||||
|
||||
export class BinaryNomsReader {
|
||||
buff: ArrayBuffer;
|
||||
buff: Uint8Array;
|
||||
dv: DataView;
|
||||
offset: number;
|
||||
length: number;
|
||||
|
||||
constructor(data: Uint8Array) {
|
||||
this.buff = data.buffer;
|
||||
this.offset = data.byteOffset;
|
||||
this.length = data.byteLength;
|
||||
this.dv = new DataView(this.buff, this.offset, this.length);
|
||||
constructor(buff: Uint8Array) {
|
||||
this.buff = buff;
|
||||
this.dv = new DataView(buff.buffer, buff.byteOffset, buff.byteLength);
|
||||
this.offset = 0;
|
||||
}
|
||||
|
||||
readBytes(): Uint8Array {
|
||||
const size = this.readUint32();
|
||||
// Make a copy of the buffer to return
|
||||
const v = new Uint8Array(new Uint8Array(this.buff, this.offset, size));
|
||||
const v = Bytes.slice(this.buff, this.offset, this.offset + size);
|
||||
this.offset += size;
|
||||
return v;
|
||||
}
|
||||
@@ -122,54 +120,49 @@ export class BinaryNomsReader {
|
||||
|
||||
readString(): string {
|
||||
const size = this.readUint32();
|
||||
const v = new Uint8Array(this.buff, this.offset, size);
|
||||
const str = Bytes.readUtf8(this.buff, this.offset, this.offset + size);
|
||||
this.offset += size;
|
||||
return decode(v);
|
||||
return str;
|
||||
}
|
||||
|
||||
readHash(): Hash {
|
||||
// Make a copy of the data.
|
||||
const digest = new Uint8Array(this.buff.slice(this.offset, this.offset + sha1Size));
|
||||
const digest = Bytes.slice(this.buff, this.offset, this.offset + sha1Size);
|
||||
this.offset += sha1Size;
|
||||
return new Hash(digest);
|
||||
}
|
||||
}
|
||||
|
||||
const initialBufferSize = 2048;
|
||||
const initialBufferSize = 16;
|
||||
|
||||
export class BinaryNomsWriter {
|
||||
buff: ArrayBuffer;
|
||||
buff: Uint8Array;
|
||||
dv: DataView;
|
||||
offset: number;
|
||||
length: number;
|
||||
|
||||
constructor() {
|
||||
this.buff = new ArrayBuffer(initialBufferSize);
|
||||
this.dv = new DataView(this.buff, 0);
|
||||
this.buff = Bytes.alloc(initialBufferSize);
|
||||
this.dv = new DataView(this.buff.buffer, 0);
|
||||
this.offset = 0;
|
||||
this.length = this.buff.byteLength;
|
||||
}
|
||||
|
||||
get data(): Uint8Array {
|
||||
// Callers now owns the copied data.
|
||||
return new Uint8Array(new Uint8Array(this.buff, 0, this.offset));
|
||||
return Bytes.slice(this.buff, 0, this.offset);
|
||||
}
|
||||
|
||||
ensureCapacity(n: number): void {
|
||||
if (this.offset + n <= this.length) {
|
||||
let length = this.buff.byteLength;
|
||||
if (this.offset + n <= length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldData = new Uint8Array(this.buff);
|
||||
|
||||
while (this.offset + n > this.length) {
|
||||
this.length *= 2;
|
||||
while (this.offset + n > length) {
|
||||
length *= 2;
|
||||
}
|
||||
this.buff = new ArrayBuffer(this.length);
|
||||
this.dv = new DataView(this.buff, 0);
|
||||
|
||||
const a = new Uint8Array(this.buff);
|
||||
a.set(oldData);
|
||||
this.buff = Bytes.grow(this.buff, length);
|
||||
this.dv = new DataView(this.buff.buffer);
|
||||
}
|
||||
|
||||
writeBytes(v: Uint8Array): void {
|
||||
@@ -177,8 +170,7 @@ export class BinaryNomsWriter {
|
||||
this.writeUint32(size);
|
||||
|
||||
this.ensureCapacity(size);
|
||||
const a = new Uint8Array(this.buff, this.offset, size);
|
||||
a.set(v);
|
||||
Bytes.copy(v, this.buff, this.offset);
|
||||
this.offset += size;
|
||||
}
|
||||
|
||||
@@ -213,13 +205,14 @@ export class BinaryNomsWriter {
|
||||
}
|
||||
|
||||
writeString(v: string): void {
|
||||
this.writeBytes(encode(v));
|
||||
// TODO: This is a bummer. Ensure even the largest UTF8 string will fit.
|
||||
this.ensureCapacity(4 + v.length * 4);
|
||||
this.offset = Bytes.encodeUtf8(v, this.buff, this.dv, this.offset);
|
||||
}
|
||||
|
||||
writeHash(h: Hash): void {
|
||||
this.ensureCapacity(sha1Size);
|
||||
const a = new Uint8Array(this.buff, this.offset, sha1Size);
|
||||
a.set(h.digest);
|
||||
Bytes.copy(h.digest, this.buff, this.offset);
|
||||
this.offset += sha1Size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {suite, test} from 'mocha';
|
||||
import {assert} from 'chai';
|
||||
|
||||
import Blob from './blob.js';
|
||||
import Bytes from './bytes.js';
|
||||
import Hash from './hash.js';
|
||||
import List, {newListLeafSequence} from './list.js';
|
||||
import Map from './map.js';
|
||||
@@ -266,8 +267,8 @@ suite('Encoding', () => {
|
||||
|
||||
test('simple blob', () => {
|
||||
assertEncoding([
|
||||
uint8(BlobKind), false, new Uint8Array([0, 1]),
|
||||
], new Blob(new Uint8Array([0, 1])));
|
||||
uint8(BlobKind), false, Bytes.fromValues([0, 1]),
|
||||
], new Blob(Bytes.fromValues([0, 1])));
|
||||
});
|
||||
|
||||
test('list', () => {
|
||||
@@ -386,9 +387,9 @@ suite('Encoding', () => {
|
||||
|
||||
test('struct with blob', () => {
|
||||
assertEncoding([
|
||||
uint8(StructKind), 'S', uint32(1) /* len */, 'b', uint8(BlobKind), uint8(BlobKind), false, new Uint8Array([0, 1]),
|
||||
uint8(StructKind), 'S', uint32(1) /* len */, 'b', uint8(BlobKind), uint8(BlobKind), false, Bytes.fromValues([0, 1]),
|
||||
],
|
||||
newStruct('S', {b: new Blob(new Uint8Array([0, 1]))}));
|
||||
newStruct('S', {b: new Blob(Bytes.fromValues([0, 1]))}));
|
||||
});
|
||||
|
||||
test('compound list', () => {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import {request} from 'http';
|
||||
import {parse} from 'url';
|
||||
import Bytes from './bytes.js';
|
||||
|
||||
export type FetchOptions = {
|
||||
method?: string,
|
||||
@@ -14,7 +15,7 @@ export type FetchOptions = {
|
||||
withCredentials? : boolean,
|
||||
};
|
||||
|
||||
function fetch<T>(url: string, f: (buf: Buffer) => T, options: FetchOptions = {}): Promise<T> {
|
||||
function fetch(url: string, options: FetchOptions = {}): Promise<Uint8Array> {
|
||||
const opts: any = parse(url);
|
||||
opts.method = options.method || 'GET';
|
||||
if (options.headers) {
|
||||
@@ -26,12 +27,30 @@ function fetch<T>(url: string, f: (buf: Buffer) => T, options: FetchOptions = {}
|
||||
reject(res.statusCode);
|
||||
return;
|
||||
}
|
||||
let buf = new Buffer(0);
|
||||
res.on('data', (chunk: Buffer) => {
|
||||
buf = Buffer.concat([buf, chunk]);
|
||||
|
||||
let buf = Bytes.alloc(2048);
|
||||
let offset = 0;
|
||||
const ensureCapacity = (n: number) => {
|
||||
let length = buf.byteLength;
|
||||
if (offset + n <= length) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (offset + n > length) {
|
||||
length *= 2;
|
||||
}
|
||||
|
||||
buf = Bytes.grow(buf, length);
|
||||
};
|
||||
|
||||
res.on('data', (chunk: Uint8Array) => {
|
||||
const size = chunk.byteLength;
|
||||
ensureCapacity(size);
|
||||
Bytes.copy(chunk, buf, offset);
|
||||
offset += size;
|
||||
});
|
||||
res.on('end', () => {
|
||||
resolve(f(buf));
|
||||
resolve(Bytes.subarray(buf, 0, offset));
|
||||
});
|
||||
});
|
||||
req.on('error', err => {
|
||||
@@ -51,22 +70,13 @@ function fetch<T>(url: string, f: (buf: Buffer) => T, options: FetchOptions = {}
|
||||
});
|
||||
}
|
||||
|
||||
function bufferToArrayBuffer(buf: Buffer): ArrayBuffer {
|
||||
const ab = new ArrayBuffer(buf.length);
|
||||
const view = new Uint8Array(ab);
|
||||
for (let i = 0; i < buf.length; i++) {
|
||||
view[i] = buf[i];
|
||||
}
|
||||
return ab;
|
||||
}
|
||||
|
||||
function arrayBufferToBuffer(ab: ArrayBuffer): Buffer {
|
||||
// $FlowIssue: Node type declaration doesn't include ArrayBuffer.
|
||||
return new Buffer(ab);
|
||||
}
|
||||
|
||||
function bufferToString(buf: Buffer): string {
|
||||
return buf.toString();
|
||||
function bufferToString(buf: Uint8Array): string {
|
||||
return Bytes.readUtf8(buf, 0, buf.byteLength);
|
||||
}
|
||||
|
||||
function normalizeBody(opts: FetchOptions): FetchOptions {
|
||||
@@ -77,9 +87,9 @@ function normalizeBody(opts: FetchOptions): FetchOptions {
|
||||
}
|
||||
|
||||
export function fetchText(url: string, options: FetchOptions = {}): Promise<string> {
|
||||
return fetch(url, bufferToString, normalizeBody(options));
|
||||
return fetch(url, normalizeBody(options)).then(ar => bufferToString(ar));
|
||||
}
|
||||
|
||||
export function fetchArrayBuffer(url: string, options: FetchOptions = {}): Promise<ArrayBuffer> {
|
||||
return fetch(url, bufferToArrayBuffer, options);
|
||||
export function fetchUint8Array(url: string, options: FetchOptions = {}): Promise<Uint8Array> {
|
||||
return fetch(url, options);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
// Licensed under the Apache License, version 2.0:
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
import {assert} from 'chai';
|
||||
import {suite, test} from 'mocha';
|
||||
import Hash, {emptyHash} from './hash.js';
|
||||
import {encode} from './utf8.js';
|
||||
import {assert} from 'chai';
|
||||
import Bytes from './bytes.js';
|
||||
import {notNull} from './assert.js';
|
||||
import {suite, test} from 'mocha';
|
||||
|
||||
suite('Hash', () => {
|
||||
test('parse', () => {
|
||||
@@ -51,13 +51,13 @@ suite('Hash', () => {
|
||||
});
|
||||
|
||||
test('fromData', () => {
|
||||
const r = Hash.fromData(encode('abc'));
|
||||
const r = Hash.fromData(Bytes.fromString('abc'));
|
||||
|
||||
assert.strictEqual('sha1-a9993e364706816aba3e25717850c26c9cd0d89d', r.toString());
|
||||
});
|
||||
|
||||
test('isEmpty', () => {
|
||||
const digest = new Uint8Array(20);
|
||||
const digest = Bytes.alloc(20);
|
||||
let r = new Hash(digest);
|
||||
assert.isTrue(r.isEmpty());
|
||||
|
||||
|
||||
@@ -4,33 +4,11 @@
|
||||
// Licensed under the Apache License, version 2.0:
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
import sha1 from './sha1.js';
|
||||
import Bytes from './bytes.js';
|
||||
|
||||
export const sha1Size = 20;
|
||||
const pattern = /^sha1-[0-9a-f]{40}$/;
|
||||
|
||||
const sha1Prefix = 'sha1-';
|
||||
const sha1PrefixLength = sha1Prefix.length;
|
||||
|
||||
function uint8ArrayToSha1(a: Uint8Array): string {
|
||||
const sha1 = new Array(1 + sha1Size * 2);
|
||||
sha1[0] = [sha1Prefix];
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
sha1[i + 1] = byteToAscii[a[i]];
|
||||
}
|
||||
return sha1.join('');
|
||||
}
|
||||
|
||||
function sha1ToUint8Array(s: string): Uint8Array {
|
||||
const digest = new Uint8Array(sha1Size);
|
||||
for (let i = 0; i < sha1Size; i++) {
|
||||
const hc = asciiToBinary(s.charCodeAt(sha1PrefixLength + 2 * i));
|
||||
const lc = asciiToBinary(s.charCodeAt(sha1PrefixLength + 2 * i + 1));
|
||||
digest[i] = hc << 4 | lc;
|
||||
}
|
||||
return digest;
|
||||
}
|
||||
|
||||
export default class Hash {
|
||||
_digest: Uint8Array;
|
||||
|
||||
@@ -47,58 +25,31 @@ export default class Hash {
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
for (let i = 0; i < sha1Size; i++) {
|
||||
if (this._digest[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return this.equals(emptyHash);
|
||||
}
|
||||
|
||||
equals(other: Hash): boolean {
|
||||
for (let i = 0; i < sha1Size; i++) {
|
||||
if (this._digest[i] !== other._digest[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return this.compare(other) === 0;
|
||||
}
|
||||
|
||||
compare(other: Hash): number {
|
||||
for (let i = 0; i < sha1Size; i++) {
|
||||
const d = this._digest[i] - other._digest[i];
|
||||
if (d) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
return Bytes.compare(this._digest, other._digest);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return uint8ArrayToSha1(this._digest);
|
||||
return 'sha1-' + Bytes.toHexString(this._digest);
|
||||
}
|
||||
|
||||
static parse(s: string): ?Hash {
|
||||
if (pattern.test(s)) {
|
||||
return new Hash(sha1ToUint8Array(s));
|
||||
return new Hash(Bytes.fromHexString(s.substring(5)));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static fromData(data: Uint8Array): Hash {
|
||||
return new Hash(sha1(data));
|
||||
return new Hash(Bytes.sha1(data));
|
||||
}
|
||||
}
|
||||
|
||||
export const emptyHash = new Hash(new Uint8Array(sha1Size));
|
||||
|
||||
function asciiToBinary(cc: number): number {
|
||||
// This only accepts the char code for '0' - '9', 'a' - 'f'
|
||||
return cc - (cc <= 57 ? 48 : 87); // '9', '0', 'a' - 10
|
||||
}
|
||||
|
||||
// Precompute '00' to 'ff'.
|
||||
const byteToAscii = new Array(256);
|
||||
for (let i = 0; i < 256; i++) {
|
||||
byteToAscii[i] = (i < 0x10 ? '0' : '') + i.toString(16);
|
||||
}
|
||||
export const emptyHash = new Hash(Bytes.alloc(sha1Size));
|
||||
|
||||
@@ -11,7 +11,7 @@ import type {FetchOptions} from './fetch.js';
|
||||
import type {ChunkStream} from './chunk-serializer.js';
|
||||
import {serialize, deserializeChunks} from './chunk-serializer.js';
|
||||
import {emptyChunk} from './chunk.js';
|
||||
import {fetchArrayBuffer, fetchText} from './fetch.js';
|
||||
import {fetchUint8Array, fetchText} from './fetch.js';
|
||||
import {notNull} from './assert.js';
|
||||
|
||||
const HTTP_STATUS_CONFLICT = 409;
|
||||
@@ -81,9 +81,9 @@ export class Delegate {
|
||||
const hashStrs = Object.keys(reqs);
|
||||
const body = hashStrs.map(r => 'ref=' + r).join('&');
|
||||
const opts = Object.assign(this._readBatchOptions, {body: body});
|
||||
const buf = await fetchArrayBuffer(this._rpc.getRefs, opts);
|
||||
const buf = await fetchUint8Array(this._rpc.getRefs, opts);
|
||||
|
||||
const chunks = deserializeChunks(buf);
|
||||
const chunks = deserializeChunks(buf, new DataView(buf.buffer, buf.byteOffset, buf.byteLength));
|
||||
|
||||
// Return success
|
||||
chunks.forEach(chunk => {
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {tcoll as Collection} from 'tingodb';
|
||||
import fs from 'fs';
|
||||
import Chunk, {emptyChunk} from './chunk.js';
|
||||
import {invariant} from './assert.js';
|
||||
import Bytes from './bytes.js';
|
||||
|
||||
const __tingodb = tingodb();
|
||||
|
||||
@@ -57,8 +58,10 @@ export default class OrderedPutCache {
|
||||
return false;
|
||||
}
|
||||
this._chunkIndex.set(hash, -1);
|
||||
// TODO: Bug #1814.
|
||||
const data = Bytes.slice(c.data, 0, c.data.byteLength);
|
||||
const p = this._coll
|
||||
.then(coll => coll.insert({hash: hash, data: c.data}))
|
||||
.then(coll => coll.insert({hash: hash, data: data}))
|
||||
.then(itemId => this._chunkIndex.set(hash, itemId))
|
||||
.then(() => { this._appends.delete(p); });
|
||||
this._appends.add(p);
|
||||
@@ -206,7 +209,7 @@ class DbCollection {
|
||||
}
|
||||
|
||||
function recordToItem(record: DbRecord): ChunkItem {
|
||||
return {hash: record.hash, data: new Uint8Array(record.data.buffer)};
|
||||
return {hash: record.hash, data: record.data.buffer};
|
||||
}
|
||||
|
||||
function makeTempDir(): Promise<string> {
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
// @flow
|
||||
|
||||
// Copyright 2016 The Noms Authors. All rights reserved.
|
||||
// Licensed under the Apache License, version 2.0:
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
import {assert} from 'chai';
|
||||
import {suite, test} from 'mocha';
|
||||
|
||||
import sha1Node from './sha1.js';
|
||||
import sha1Browser from './browser/sha1.js';
|
||||
|
||||
suite('Sha1', () => {
|
||||
test('hex', () => {
|
||||
function assertSame(arr: Uint8Array) {
|
||||
// Node uses a Buffer, browser uses a Uint8Array
|
||||
const n = sha1Node(arr);
|
||||
const b = sha1Browser(arr);
|
||||
assert.equal(n.byteLength, b.byteLength);
|
||||
const n2 = new Uint8Array(n.buffer, n.byteOffset, n.byteLength);
|
||||
const b2 = new Uint8Array(b.buffer, b.byteOffset, b.byteLength);
|
||||
for (let i = 0; i < n2.length; i++) {
|
||||
assert.equal(n2[i], b2[i]);
|
||||
}
|
||||
}
|
||||
|
||||
assertSame(new Uint8Array(0));
|
||||
assertSame(new Uint8Array(42));
|
||||
|
||||
const arr = new Uint8Array([1, 2, 3, 4, 5]);
|
||||
assertSame(arr);
|
||||
assertSame(new Uint8Array(arr));
|
||||
assertSame(new Uint8Array(arr.buffer));
|
||||
assertSame(new Uint8Array(arr.buffer, 1, 2));
|
||||
});
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
// @flow
|
||||
|
||||
// Copyright 2016 The Noms Authors. All rights reserved.
|
||||
// Licensed under the Apache License, version 2.0:
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
// This is the Node.js version. The browser version is in ./browser/sha1.js.
|
||||
|
||||
import crypto from 'crypto';
|
||||
|
||||
export default function sha1(data: Uint8Array): Uint8Array {
|
||||
const hash = crypto.createHash('sha1');
|
||||
hash.update(data);
|
||||
return hash.digest();
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import {equals} from './compare.js';
|
||||
import {getTypeOfValue, makeStructType, findFieldIndex} from './type.js';
|
||||
import {invariant} from './assert.js';
|
||||
import {isPrimitive} from './primitives.js';
|
||||
import {encode as utf8Encode} from './utf8.js';
|
||||
import Bytes from './bytes.js';
|
||||
|
||||
type StructData = {[key: string]: Value};
|
||||
|
||||
@@ -271,7 +271,7 @@ export function escapeStructField(input: string): string {
|
||||
}
|
||||
|
||||
let out = escapeChar;
|
||||
utf8Encode(c).forEach(b => {
|
||||
Bytes.fromString(c).forEach(b => {
|
||||
const hex = b.toString(16).toUpperCase();
|
||||
if (hex.length === 1) {
|
||||
out += '0';
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
// @flow
|
||||
|
||||
// Copyright 2016 The Noms Authors. All rights reserved.
|
||||
// Licensed under the Apache License, version 2.0:
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
import {suite, test} from 'mocha';
|
||||
import {assert} from 'chai';
|
||||
|
||||
import {encode as encodeNative, decode as decodeNative} from './utf8.js';
|
||||
import {encode as encodeBrowser, decode as decodeBrowser} from './browser/utf8.js';
|
||||
|
||||
suite('Utf8', () => {
|
||||
test('encode', () => {
|
||||
function assertSame(s: string) {
|
||||
assert.deepEqual(encodeNative(s), encodeBrowser(s));
|
||||
}
|
||||
|
||||
assertSame('');
|
||||
assertSame('hello world');
|
||||
assertSame('\u03c0');
|
||||
});
|
||||
|
||||
test('decode', () => {
|
||||
function assertSame(data: Uint8Array) {
|
||||
assert.strictEqual(decodeNative(data), decodeBrowser(data));
|
||||
}
|
||||
|
||||
assertSame(new Uint8Array(0));
|
||||
assertSame(new Uint8Array([100, 101, 102]));
|
||||
assertSame(new Uint8Array([0x3c0]));
|
||||
});
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
// @flow
|
||||
|
||||
// Copyright 2016 The Noms Authors. All rights reserved.
|
||||
// Licensed under the Apache License, version 2.0:
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
// This is the Node.js version. The browser version is in ./browser/utf8.js.
|
||||
|
||||
// TODO: Use opaque type for the data. https://github.com/attic-labs/noms/issues/1082
|
||||
|
||||
export function encode(s: string): Uint8Array {
|
||||
const buf = new Buffer(s, 'utf8');
|
||||
// Note: it would be better not to copy the memory, and instead do:
|
||||
//
|
||||
// return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength)
|
||||
//
|
||||
// but Node5 seems to get confused converting between Buffer and Uint8Array while trying to use
|
||||
// the same underlying buffer. Node6 works fine, but it's not the LTS release yet.
|
||||
return new Uint8Array(buf);
|
||||
}
|
||||
|
||||
export function decode(data: Uint8Array): string {
|
||||
// Note: see comment above. For a similar reason, it would be better, but impossible, to do:
|
||||
//
|
||||
// return new Buffer(data.buffer, data.byteOffset, data.byteLength).toString('utf8');
|
||||
//
|
||||
// $FlowIssue: flow doesn't know this is a legit constructor.
|
||||
return new Buffer(data).toString('utf8');
|
||||
}
|
||||
@@ -4,6 +4,9 @@
|
||||
// Licensed under the Apache License, version 2.0:
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
import {suite, suiteSetup, suiteTeardown, test} from 'mocha';
|
||||
import {assert} from 'chai';
|
||||
|
||||
import {BatchStoreAdaptor} from './batch-store.js';
|
||||
import {createStructClass} from './struct.js';
|
||||
import Database from './database.js';
|
||||
@@ -19,10 +22,6 @@ import List from './list.js';
|
||||
import Map from './map.js';
|
||||
import NomsSet from './set.js'; // namespace collision with JS Set
|
||||
import walk from './walk.js';
|
||||
|
||||
import {suite, suiteSetup, suiteTeardown, test} from 'mocha';
|
||||
import {assert} from 'chai';
|
||||
|
||||
import type Value from './value.js';
|
||||
|
||||
suite('walk', () => {
|
||||
|
||||
Reference in New Issue
Block a user