import { Tag } from './constants'; import { GenericEmbedded } from './embedded'; import { Encoder, Preservable } from './encoder'; import { Value } from './values'; import { Writer, PreserveWritable } from './writer'; const textEncoder = new TextEncoder(); const textDecoder = new TextDecoder(); export const IsPreservesBytes = Symbol.for('IsPreservesBytes'); export type BytesLike = Bytes | Uint8Array; export class Bytes implements Preservable, PreserveWritable { 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); } } dataview(): DataView { return new DataView(this._view.buffer, this._view.byteOffset, this._view.byteLength); } 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); } __as_preserve__(): Value { return this; } static __from_preserve__(v: Value): undefined | Bytes { return Bytes.isBytes(v) ? v : void 0; } 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(''); } valueOf(): string { // Defined mostly in order to get lexicographic comparison to "work reliably" return this.toHex(); } __preserve_on__(encoder: Encoder) { encoder.state.emitbyte(Tag.ByteString); encoder.state.varint(this.length); encoder.state.emitbytes(this._view); } __preserve_text_on__(w: Writer) { w.state.writeBytes(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; } export function dataview(b: Bytes | DataView): DataView { return (b instanceof DataView) ? b : b.dataview(); } // 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](); }; })();