import { Tag } from './constants'; import { AsPreserve, PreserveOn } from './symbols'; import { Encoder, Preservable } from './encoder'; import { Value } from './values'; import { GenericEmbedded } from './embedded'; const textEncoder = new TextEncoder(); const textDecoder = new TextDecoder(); export const IsPreservesBytes = Symbol.for('IsPreservesBytes'); export type BytesLike = Bytes | Uint8Array; export class Bytes implements Preservable { 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](): Value { 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) { encoder.state.emitbyte(Tag.ByteString); encoder.state.varint(this.length); encoder.state.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; 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; 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; } (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](); }; })();