/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/ */ import { UniffiInternalError } from "./errors.ts"; export type UniffiByteArray = Uint8Array; export class RustBuffer { private readOffset: number = 0; private writeOffset: number = 0; private capacity: number; public arrayBuffer: ArrayBuffer; private constructor(arrayBuffer: ArrayBuffer) { this.arrayBuffer = arrayBuffer; this.capacity = arrayBuffer.byteLength; } /** * Allocate a JS-owned buffer of the given capacity. Used by tests; not by * the runtime path, which calls `RustBuffer.fromUint8Array(allocator(n))` * with a runtime-supplied allocator. */ static withCapacity(capacity: number): RustBuffer { const buf = new ArrayBuffer(capacity); return new RustBuffer(buf); } static empty(): RustBuffer { return this.withCapacity(0); } static fromArrayBuffer(buf: ArrayBuffer): RustBuffer { return new RustBuffer(buf); } /** * Construct a RustBuffer that reads/writes directly out of a * `WebAssembly.Memory` backing buffer, with `readOffset`/`writeOffset` * pre-positioned at `dataPtr`. Allows record lift/lower to skip the * intermediate `Uint8Array` copy. The caller is responsible for the * buffer remaining attached for the duration of any read/write. */ static fromWasmMemory( memoryBuffer: ArrayBuffer, dataPtr: number, len: number, ): RustBuffer { const buf = new RustBuffer(memoryBuffer); // The reader/writer offset machinery uses absolute offsets into the // backing buffer; treat `dataPtr` as the start of the slice and // `dataPtr + len` as its end. buf.readOffset = dataPtr; buf.writeOffset = dataPtr; buf.capacity = dataPtr + len; return buf; } /** * Construct a `RustBuffer` over a `Uint8Array` view. Cursors track absolute * offsets into the backing `ArrayBuffer`, so `byteOffset` and `byteLength` * delimit the readable/writable region. Generalises `fromWasmMemory`. */ static fromUint8Array(view: UniffiByteArray): RustBuffer { const buf = new RustBuffer(view.buffer as ArrayBuffer); buf.readOffset = view.byteOffset; buf.writeOffset = view.byteOffset; buf.capacity = view.byteOffset + view.byteLength; return buf; } get length(): number { return this.arrayBuffer.byteLength; } get byteArray(): UniffiByteArray { return new Uint8Array(this.arrayBuffer); } readArrayBuffer(numBytes: number): ArrayBuffer { const start = this.readOffset; const end = this.checkOverflow(start, numBytes); const value = this.arrayBuffer.slice(start, end); this.readOffset = end; return value; } readByteArray(numBytes: number): UniffiByteArray { const start = this.readOffset; const end = this.checkOverflow(start, numBytes); const value = new Uint8Array(this.arrayBuffer, start, numBytes); this.readOffset = end; return value; } writeArrayBuffer(buffer: ArrayBufferLike) { const start = this.writeOffset; const end = this.checkOverflow(start, buffer.byteLength); const src = new Uint8Array(buffer); const dest = new Uint8Array(this.arrayBuffer, start); dest.set(src); this.writeOffset = end; } writeByteArray(src: UniffiByteArray) { const start = this.writeOffset; const end = this.checkOverflow(start, src.byteLength); const dest = new Uint8Array(this.arrayBuffer, start); dest.set(src); this.writeOffset = end; } /** * Advance the read cursor by `numBytes` and return the starting offset. * Used to construct a typed-array view directly over the backing store. */ advanceReadOffset(numBytes: number): number { const start = this.readOffset; this.readOffset = this.checkOverflow(start, numBytes); return start; } /** * Advance the write cursor by `numBytes` and return the starting offset. * Used to construct a typed-array view directly over the backing store. */ advanceWriteOffset(numBytes: number): number { const start = this.writeOffset; this.writeOffset = this.checkOverflow(start, numBytes); return start; } /** * Set the write cursor to an absolute position. Used to backfill after an * in-place encode reports the actual number of bytes written. */ setWriteOffset(offset: number): void { if (offset > this.capacity) { throw new UniffiInternalError.BufferOverflow(); } this.writeOffset = offset; } protected checkOverflow(start: number, numBytes: number): number { const end = start + numBytes; if (this.capacity < end) { throw new UniffiInternalError.BufferOverflow(); } return end; } }