import { TruncatedArchiveError } from '../errors.ts'; const fromCharCode = String.fromCharCode; export const textEncoder = new TextEncoder(); export const textDecoder = new TextDecoder(); export function concat(arrays: readonly Uint8Array[], size?: number): Uint8Array { let written = 0; // deno-lint-ignore prefer-const let len = arrays.length; let idx: number; if (size === undefined) { for (idx = size = 0; idx < len; idx++) { const chunk = arrays[idx]; size += chunk.length; } } const buffer = new Uint8Array(size); for (idx = 0; idx < len; idx++) { const chunk = arrays[idx]; buffer.set(chunk, written); written += chunk.length; } return buffer; } export async function read( stream: ReadableStream, size: number, ): Promise> { // fast path: a byte-type stream lets us request bytes straight into a // pre-allocated buffer with no producer-side memcpy. user-supplied // non-byte sources don't support BYOB; fall back to the default-reader // copy loop when getReader throws. // // the BYOB reader setup (and the TypeError throw when the stream isn't // byte-typed) carries ~µs of fixed overhead. for tiny reads — header // probes, signature checks, etc. — that swamps the per-byte savings, so // only attempt BYOB once the request is large enough to amortize the // setup. let byob: ReadableStreamBYOBReader | undefined; if (size >= 4096) { try { byob = stream.getReader({ mode: 'byob' }); } catch { // fall through to default-reader path below } } if (byob !== undefined) { let buffer = new ArrayBuffer(size); let pos = 0; let drained = false; try { while (pos < size) { const view = new Uint8Array(buffer, pos, size - pos); const { done, value } = await byob.read(view); if (done) { drained = true; break; } buffer = value.buffer as ArrayBuffer; pos += value.byteLength; } } finally { if (!drained) { await byob.cancel(); } else { byob.releaseLock(); } } if (pos < size) { throw new TruncatedArchiveError(`unexpected end of stream: expected ${size} bytes, got ${pos}`); } return new Uint8Array(buffer, 0, size); } const buffer = new Uint8Array(size); const reader = stream.getReader(); let pos = 0; let drained = false; try { while (pos < size) { const { done, value } = await reader.read(); if (done) { drained = true; break; } const length = value.length; const room = size - pos; if (length <= room) { buffer.set(value, pos); pos += length; } else { buffer.set(value.subarray(0, room), pos); pos = size; } } } finally { if (!drained) { await reader.cancel(); } else { reader.releaseLock(); } } if (pos < size) { throw new TruncatedArchiveError(`unexpected end of stream: expected ${size} bytes, got ${pos}`); } return buffer; } export const decodeUtf8From = (from: Uint8Array, offset?: number, length?: number): string => { let buffer: Uint8Array; if (offset === undefined) { buffer = from; } else if (length === undefined) { buffer = from.subarray(offset); } else { buffer = from.subarray(offset, offset + length); } const end = buffer.length; if (end > 24) { return textDecoder.decode(buffer); } { let str = ''; let idx = 0; for (; idx + 3 < end; idx += 4) { const a = buffer[idx]; const b = buffer[idx + 1]; const c = buffer[idx + 2]; const d = buffer[idx + 3]; if ((a | b | c | d) & 0x80) { return str + textDecoder.decode(buffer.subarray(idx)); } str += fromCharCode(a, b, c, d); } for (; idx < end; idx++) { const x = buffer[idx]; if (x & 0x80) { return str + textDecoder.decode(buffer.subarray(idx)); } str += fromCharCode(x); } return str; } }; /** * reads exactly `length` bytes from `reader` at `offset`. * * @param reader the random-access source * @param offset starting byte position within the reader * @param length number of bytes to read * @returns the requested bytes as a fresh `Uint8Array` * @throws if the reader returns fewer than `length` bytes */ export const readBytes = async ( reader: { read(offset: number, length?: number): Promise>> }, offset: number, length: number, ): Promise> => { return await read(await reader.read(offset, length), length); };