71 lines
2.7 KiB
JavaScript
71 lines
2.7 KiB
JavaScript
|
|
"use strict";
|
||
|
|
|
||
|
|
const schema = require("./generated/plugin-contract.schema.json");
|
||
|
|
const { assertTerminalInterceptorFrameSchema } = require("./contractValidator.cjs");
|
||
|
|
|
||
|
|
const limits = schema.$defs.TerminalInterceptorLimits.const;
|
||
|
|
const TERMINAL_INTERCEPTOR_MAX_CHUNK_BYTES = limits.maxChunkBytes;
|
||
|
|
const TERMINAL_INTERCEPTOR_MAX_WINDOW_BYTES = limits.maxWindowBytes;
|
||
|
|
const arrayBufferByteLength = Object.getOwnPropertyDescriptor(
|
||
|
|
ArrayBuffer.prototype,
|
||
|
|
"byteLength",
|
||
|
|
)?.get;
|
||
|
|
|
||
|
|
function copyOwnDataRecord(value, label) {
|
||
|
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
||
|
|
throw new TypeError(`${label} must be a plain object`);
|
||
|
|
}
|
||
|
|
const copy = {};
|
||
|
|
for (const key of Object.keys(value)) {
|
||
|
|
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
||
|
|
if (!descriptor || !("value" in descriptor)) {
|
||
|
|
throw new TypeError(`${label} must contain data properties only`);
|
||
|
|
}
|
||
|
|
copy[key] = descriptor.value;
|
||
|
|
}
|
||
|
|
return copy;
|
||
|
|
}
|
||
|
|
|
||
|
|
function transferredByteLength(value) {
|
||
|
|
if (!arrayBufferByteLength) throw new TypeError("ArrayBuffer byteLength getter is unavailable");
|
||
|
|
try {
|
||
|
|
const byteLength = arrayBufferByteLength.call(value);
|
||
|
|
// The intrinsic getter reports zero for a detached zero-length buffer as
|
||
|
|
// well as for a valid empty buffer. Constructing a view distinguishes the
|
||
|
|
// two and also works for ArrayBuffers created in another realm.
|
||
|
|
new Uint8Array(value);
|
||
|
|
return byteLength;
|
||
|
|
} catch {
|
||
|
|
throw new TypeError("Terminal interceptor transfer requires a real, attached ArrayBuffer");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function createTerminalInterceptorEnvelope(frameValue, transfer) {
|
||
|
|
const frame = copyOwnDataRecord(frameValue, "Terminal interceptor frame");
|
||
|
|
assertTerminalInterceptorFrameSchema(frame);
|
||
|
|
const requiresTransfer = frame.type === "netcatty:terminal-interceptor:chunk"
|
||
|
|
|| (frame.type === "netcatty:terminal-interceptor:result" && frame.status === "ok");
|
||
|
|
if (!requiresTransfer) {
|
||
|
|
if (transfer !== undefined) {
|
||
|
|
throw new TypeError("This terminal interceptor frame must not include a transferred buffer");
|
||
|
|
}
|
||
|
|
return Object.freeze({ frame: Object.freeze(frame) });
|
||
|
|
}
|
||
|
|
if (transfer === undefined) {
|
||
|
|
throw new TypeError("This terminal interceptor frame requires a transferred ArrayBuffer");
|
||
|
|
}
|
||
|
|
const byteLength = transferredByteLength(transfer);
|
||
|
|
if (byteLength !== frame.byteLength) {
|
||
|
|
throw new RangeError(
|
||
|
|
`Terminal interceptor byteLength mismatch: declared ${frame.byteLength}, received ${byteLength}`,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
return Object.freeze({ frame: Object.freeze(frame), transfer });
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = {
|
||
|
|
TERMINAL_INTERCEPTOR_MAX_CHUNK_BYTES,
|
||
|
|
TERMINAL_INTERCEPTOR_MAX_WINDOW_BYTES,
|
||
|
|
createTerminalInterceptorEnvelope,
|
||
|
|
};
|