2021-02-17 15:52:01 +00:00
|
|
|
import { Tag } from './constants';
|
|
|
|
import { AsPreserve, PreserveOn } from './symbols';
|
2021-03-02 21:54:42 +00:00
|
|
|
import { Encoder, Preservable } from './encoder';
|
2021-02-17 15:52:01 +00:00
|
|
|
import { DefaultPointer, Value } from './values';
|
|
|
|
|
|
|
|
const textEncoder = new TextEncoder();
|
|
|
|
const textDecoder = new TextDecoder();
|
|
|
|
|
|
|
|
export const IsPreservesBytes = Symbol.for('IsPreservesBytes');
|
|
|
|
|
|
|
|
export type BytesLike = Bytes | Uint8Array;
|
|
|
|
|
|
|
|
export class Bytes implements Preservable<never> {
|
|
|
|
readonly _view: Uint8Array;
|
|
|
|
|
|
|
|
constructor(maybeByteIterable: any = new Uint8Array()) {
|
|
|
|
if (Bytes.isBytes(maybeByteIterable)) {
|
|
|
|
this._view = maybeByteIterable._view;
|
|
|
|
} else if (ArrayBuffer.isView(maybeByteIterable)) {
|
|
|
|
this._view = new Uint8Array(maybeByteIterable.buffer,
|
|
|
|
maybeByteIterable.byteOffset,
|
|
|
|
maybeByteIterable.byteLength);
|
|
|
|
} else if (maybeByteIterable instanceof ArrayBuffer) {
|
|
|
|
this._view = new Uint8Array(maybeByteIterable);
|
|
|
|
} else if (typeof maybeByteIterable === 'string') {
|
|
|
|
this._view = textEncoder.encode(maybeByteIterable);
|
|
|
|
} else if (typeof maybeByteIterable === 'number') {
|
|
|
|
this._view = new Uint8Array(maybeByteIterable);
|
|
|
|
} else if (typeof maybeByteIterable.length === 'number') {
|
|
|
|
this._view = Uint8Array.from(maybeByteIterable);
|
|
|
|
} else {
|
|
|
|
throw new TypeError("Attempt to initialize Bytes from unsupported value: " +
|
|
|
|
maybeByteIterable);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
get length(): number {
|
|
|
|
return this._view.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
static from(x: any): Bytes {
|
|
|
|
return new Bytes(x);
|
|
|
|
}
|
|
|
|
|
|
|
|
static of(...bytes: number[]): Bytes {
|
|
|
|
return new Bytes(Uint8Array.of(...bytes));
|
|
|
|
}
|
|
|
|
|
|
|
|
static fromHex(s: string): Bytes {
|
|
|
|
if (s.length & 1) throw new Error("Cannot decode odd-length hexadecimal string");
|
|
|
|
const len = s.length >> 1;
|
|
|
|
const result = new Bytes(len);
|
|
|
|
for (let i = 0; i < len; i++) {
|
|
|
|
result._view[i] =
|
|
|
|
(unhexDigit(s.charCodeAt(i << 1)) << 4) | unhexDigit(s.charCodeAt((i << 1) + 1));
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static fromIO(io: string | BytesLike): string | Bytes {
|
|
|
|
if (typeof io === 'string') return io;
|
|
|
|
if (Bytes.isBytes(io)) return io;
|
|
|
|
return new Bytes(io);
|
|
|
|
}
|
|
|
|
|
|
|
|
static toIO(b : string | BytesLike): string | Uint8Array {
|
|
|
|
if (typeof b === 'string') return b;
|
|
|
|
if (Bytes.isBytes(b)) return b._view;
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
|
|
|
|
static concat = function (bss: BytesLike[]): Bytes {
|
|
|
|
let len = 0;
|
|
|
|
for (let i = 0; i < bss.length; i++) { len += underlying(bss[i]).length; }
|
|
|
|
|
|
|
|
const result = new Bytes(len);
|
|
|
|
let index = 0;
|
|
|
|
for (let i = 0; i < bss.length; i++) {
|
|
|
|
const bs = underlying(bss[i]);
|
|
|
|
result._view.set(bs, index);
|
|
|
|
index += bs.length;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
get(index: number): number {
|
|
|
|
return this._view[index];
|
|
|
|
}
|
|
|
|
|
|
|
|
equals(other: any): boolean {
|
|
|
|
if (!Bytes.isBytes(other)) return false;
|
|
|
|
if (other.length !== this.length) return false;
|
|
|
|
const va = this._view;
|
|
|
|
const vb = other._view;
|
|
|
|
for (let i = 0; i < va.length; i++) {
|
|
|
|
if (va[i] !== vb[i]) return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
hashCode(): number {
|
|
|
|
// Immutable.js uses this function for strings.
|
|
|
|
const v = this._view;
|
|
|
|
let hash = 0;
|
|
|
|
for (let i = 0; i < v.length; i++) {
|
|
|
|
hash = ((31 * hash) + v[i]) | 0;
|
|
|
|
}
|
|
|
|
return hash;
|
|
|
|
}
|
|
|
|
|
|
|
|
static compare(a: Bytes, b: Bytes): number {
|
|
|
|
if (a < b) return -1;
|
|
|
|
if (b < a) return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static decodeUtf8(bs: Bytes | Uint8Array): string {
|
|
|
|
return textDecoder.decode(underlying(bs));
|
|
|
|
}
|
|
|
|
|
|
|
|
fromUtf8(): string {
|
|
|
|
return textDecoder.decode(this._view);
|
|
|
|
}
|
|
|
|
|
|
|
|
toString(): string {
|
|
|
|
return this.asPreservesText();
|
|
|
|
}
|
|
|
|
|
|
|
|
[AsPreserve]<T extends object = DefaultPointer>(): Value<T> {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
asPreservesText(): string {
|
|
|
|
return '#"' + this.__asciify() + '"';
|
|
|
|
}
|
|
|
|
|
|
|
|
__asciify(): string {
|
|
|
|
const pieces = [];
|
|
|
|
const v = this._view;
|
|
|
|
for (let i = 0; i < v.length; i++) {
|
|
|
|
const b = v[i];
|
|
|
|
if (b === 92 || b === 34) {
|
|
|
|
pieces.push('\\' + String.fromCharCode(b));
|
|
|
|
} else if (b >= 32 && b <= 126) {
|
|
|
|
pieces.push(String.fromCharCode(b));
|
|
|
|
} else {
|
|
|
|
pieces.push('\\x' + hexDigit(b >> 4) + hexDigit(b & 15));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return pieces.join('');
|
|
|
|
}
|
|
|
|
|
|
|
|
toHex(): string {
|
|
|
|
var nibbles = [];
|
|
|
|
for (let i = 0; i < this.length; i++) {
|
|
|
|
nibbles.push(hexDigit(this._view[i] >> 4));
|
|
|
|
nibbles.push(hexDigit(this._view[i] & 15));
|
|
|
|
}
|
|
|
|
return nibbles.join('');
|
|
|
|
}
|
|
|
|
|
|
|
|
[PreserveOn](encoder: Encoder<never>) {
|
|
|
|
encoder.emitbyte(Tag.ByteString);
|
|
|
|
encoder.varint(this.length);
|
|
|
|
encoder.emitbytes(this._view);
|
|
|
|
}
|
|
|
|
|
|
|
|
get [IsPreservesBytes](): boolean {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static isBytes(x: any): x is Bytes {
|
|
|
|
return !!x?.[IsPreservesBytes];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function hexDigit(n: number): string {
|
|
|
|
return '0123456789abcdef'[n];
|
|
|
|
}
|
|
|
|
|
|
|
|
export function unhexDigit(asciiCode: number) {
|
|
|
|
if (asciiCode >= 48 && asciiCode <= 57) return asciiCode - 48;
|
|
|
|
if (asciiCode >= 97 && asciiCode <= 102) return asciiCode - 97 + 10;
|
|
|
|
if (asciiCode >= 65 && asciiCode <= 70) return asciiCode - 65 + 10;
|
|
|
|
throw new Error("Invalid hex digit: " + String.fromCharCode(asciiCode));
|
|
|
|
}
|
|
|
|
|
|
|
|
export function underlying(b: Bytes | Uint8Array): Uint8Array {
|
|
|
|
return (b instanceof Uint8Array) ? b : b._view;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Uint8Array / TypedArray methods
|
|
|
|
|
|
|
|
export interface Bytes {
|
|
|
|
entries(): IterableIterator<[number, number]>;
|
|
|
|
every(predicate: (value: number, index: number, array: Uint8Array) => unknown,
|
|
|
|
thisArg?: any): boolean;
|
|
|
|
find(predicate: (value: number, index: number, obj: Uint8Array) => boolean,
|
|
|
|
thisArg?: any): number;
|
|
|
|
findIndex(predicate: (value: number, index: number, obj: Uint8Array) => boolean,
|
|
|
|
thisArg?: any): number;
|
|
|
|
forEach(callbackfn: (value: number, index: number, array: Uint8Array) => void,
|
|
|
|
thisArg?: any): void;
|
|
|
|
includes(searchElement: number, fromIndex?: number): boolean;
|
|
|
|
indexOf(searchElement: number, fromIndex?: number): number;
|
|
|
|
join(separator?: string): string;
|
|
|
|
keys(): IterableIterator<number>;
|
|
|
|
lastIndexOf(searchElement: number, fromIndex?: number): number;
|
|
|
|
reduce(callbackfn: (previousValue: number,
|
|
|
|
currentValue: number,
|
|
|
|
currentIndex: number,
|
|
|
|
array: Uint8Array) => number,
|
|
|
|
initialValue?: number): number;
|
|
|
|
reduceRight(callbackfn: (previousValue: number,
|
|
|
|
currentValue: number,
|
|
|
|
currentIndex: number,
|
|
|
|
array: Uint8Array) => number,
|
|
|
|
initialValue?: number): number;
|
|
|
|
some(predicate: (value: number, index: number, array: Uint8Array) => unknown,
|
|
|
|
thisArg?: any): boolean;
|
|
|
|
toLocaleString(): string;
|
|
|
|
values(): IterableIterator<number>;
|
|
|
|
|
|
|
|
filter(predicate: (value: number, index: number, array: Uint8Array) => any,
|
|
|
|
thisArg?: any): Bytes;
|
|
|
|
map(callbackfn: (value: number, index: number, array: Uint8Array) => number,
|
|
|
|
thisArg?: any): Bytes;
|
|
|
|
slice(start?: number, end?: number): Bytes;
|
|
|
|
subarray(begin?: number, end?: number): Bytes;
|
|
|
|
|
|
|
|
reverse(): Bytes;
|
|
|
|
sort(compareFn?: (a: number, b: number) => number): Bytes;
|
|
|
|
|
|
|
|
[Symbol.iterator](): IterableIterator<number>;
|
|
|
|
}
|
|
|
|
|
|
|
|
(function () {
|
|
|
|
for (const k of `entries every find findIndex forEach includes indexOf join
|
|
|
|
keys lastIndexOf reduce reduceRight some toLocaleString values`.split(/\s+/))
|
|
|
|
{
|
|
|
|
(Bytes as any).prototype[k] =
|
|
|
|
function (...args: any[]) { return this._view[k](...args); };
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const k of `filter map slice subarray`.split(/\s+/))
|
|
|
|
{
|
|
|
|
(Bytes as any).prototype[k] =
|
|
|
|
function (...args: any[]) { return new Bytes(this._view[k](...args)); };
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const k of `reverse sort`.split(/\s+/))
|
|
|
|
{
|
|
|
|
(Bytes as any).prototype[k] =
|
|
|
|
function (...args: any[]) { return new Bytes(this._view.slice()[k](...args)); };
|
|
|
|
}
|
|
|
|
|
|
|
|
Bytes.prototype[Symbol.iterator] = function () { return this._view[Symbol.iterator](); };
|
|
|
|
})();
|