import { assert, assertArgument } from "./errors.js"; export type BytesLike = string | Uint8Array; function _getBytes(value: BytesLike, name?: string, copy?: boolean): Uint8Array { if (value instanceof Uint8Array) { if (copy) { return new Uint8Array(value); } return value; } if (typeof(value) === "string" && value.match(/^0x([0-9a-f][0-9a-f])*$/i)) { const result = new Uint8Array((value.length - 2) / 2); let offset = 2; for (let i = 0; i < result.length; i++) { result[i] = parseInt(value.substring(offset, offset + 2), 16); offset += 2; } return result; } assertArgument(false, "invalid BytesLike value", name || "value", value); } /** * Get a typed Uint8Array for %%value%%. If already a Uint8Array * the original %%value%% is returned; if a copy is required use * [[getBytesCopy]]. * * @see: getBytesCopy */ export function getBytes(value: BytesLike, name?: string): Uint8Array { return _getBytes(value, name, false); } /** * Get a typed Uint8Array for %%value%%, creating a copy if necessary * to prevent any modifications of the returned value from being * reflected elsewhere. * * @see: getBytes */ export function getBytesCopy(value: BytesLike, name?: string): Uint8Array { return _getBytes(value, name, true); } /** * Returns true if %%value%% is a valid [[HexString]], with additional * optional constraints depending on %%length%%. * * If %%length%% is //true//, then %%value%% must additionally be a valid * [[HexDataString]] (i.e. even length). * * If %%length%% is //a number//, then %%value%% must represent that many * bytes of data (e.g. ``0x1234`` is 2 bytes). */ export function isHexString(value: any, length?: number | boolean): value is `0x${ string }` { if (typeof(value) !== "string" || !value.match(/^0x[0-9A-Fa-f]*$/)) { return false } if (typeof(length) === "number" && value.length !== 2 + 2 * length) { return false; } if (length === true && (value.length % 2) !== 0) { return false; } return true; } /** * Returns true if %%value%% is a valid representation of arbitrary * data (i.e. a valid [[HexDataString]] or a Uint8Array). */ export function isBytesLike(value: any): value is BytesLike { return (isHexString(value, true) || (value instanceof Uint8Array)); } const HexCharacters: string = "0123456789abcdef"; /** * Returns a [[HexDataString]] representation of %%data%%. */ export function hexlify(data: BytesLike): string { const bytes = getBytes(data); let result = "0x"; for (let i = 0; i < bytes.length; i++) { const v = bytes[i]; result += HexCharacters[(v & 0xf0) >> 4] + HexCharacters[v & 0x0f]; } return result; } /** * Returns a [[HexDataString]] by concatenating all values * within %%data%%. */ export function concat(datas: ReadonlyArray): string { return "0x" + datas.map((d) => hexlify(d).substring(2)).join(""); } /** * Returns the length of %%data%%, in bytes. */ export function dataLength(data: BytesLike): number { if (isHexString(data, true)) { return (data.length - 2) / 2; } return getBytes(data).length; } /** * Returns a [[HexDataString]] by slicing %%data%% from the %%start%% * offset to the %%end%% offset. * * By default %%start%% is 0 and %%end%% is the length of %%data%%. */ export function dataSlice(data: BytesLike, start?: number, end?: number): string { const bytes = getBytes(data); if (end != null && end > bytes.length) { assert(false, "cannot slice beyond data bounds", "BUFFER_OVERRUN", { buffer: bytes, length: bytes.length, offset: end }); } return hexlify(bytes.slice((start == null) ? 0: start, (end == null) ? bytes.length: end)); } /** * Return the [[HexDataString]] result by stripping all **leading** ** zero bytes from %%data%%. */ export function stripZerosLeft(data: BytesLike): string { let bytes = hexlify(data).substring(2); while (bytes.substring(0, 2) == "00") { bytes = bytes.substring(2); } return "0x" + bytes; } function zeroPad(data: BytesLike, length: number, left: boolean): string { const bytes = getBytes(data); assert(length >= bytes.length, "padding exceeds data length", "BUFFER_OVERRUN", { buffer: new Uint8Array(bytes), length: length, offset: length + 1 }); const result = new Uint8Array(length); result.fill(0); if (left) { result.set(bytes, length - bytes.length); } else { result.set(bytes, 0); } return hexlify(result); } /** * Return the [[HexDataString]] of %%data%% padded on the **left** * to %%length%% bytes. */ export function zeroPadValue(data: BytesLike, length: number): string { return zeroPad(data, length, true); } /** * Return the [[HexDataString]] of %%data%% padded on the **right** * to %%length%% bytes. */ export function zeroPadBytes(data: BytesLike, length: number): string { return zeroPad(data, length, false); }