diff --git a/.github/actions/build-warduino/action.yml b/.github/actions/build-warduino/action.yml index a0355a1..12ee810 100644 --- a/.github/actions/build-warduino/action.yml +++ b/.github/actions/build-warduino/action.yml @@ -3,7 +3,7 @@ description: Check out and build a WARDuino CLI version inputs: version: - description: WARDuino version tag without the optional v prefix, latest, or dirty + description: WARDuino version tag (with or without the v prefix), branch, or latest required: false default: latest @@ -22,8 +22,8 @@ runs: | sort --version-sort \ | tail --lines=1) test -n "$VERSION" - elif [ "$WARDUINO_VERSION" = "dirty" ]; then - VERSION=main + elif git ls-remote --exit-code --heads https://github.com/TOPLLab/WARDuino.git "$WARDUINO_VERSION" > /dev/null; then + VERSION="$WARDUINO_VERSION" else VERSION="${WARDUINO_VERSION#v}" VERSION="v$VERSION" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0b15a6e..f93af79 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,7 +46,7 @@ jobs: - name: Build WARDuino CLI uses: ./.github/actions/build-warduino with: - version: 0.8.1 + version: nanopb - name: Upload built tools uses: actions/upload-artifact@v4 @@ -62,6 +62,7 @@ jobs: steps: - uses: actions/checkout@v4 + - run: npm i - name: Prebuild files diff --git a/justfile b/justfile new file mode 100644 index 0000000..4323b85 --- /dev/null +++ b/justfile @@ -0,0 +1,8 @@ +lint: + npx eslint src/**/* --config .eslint.config.mjs + +format: + npx eslint src/**/* --config .eslint.config.mjs --fix + +protocol: + temp_dir=$(mktemp -d) && trap 'rm -rf "$temp_dir"' EXIT && curl --fail --silent --show-error --location https://raw.githubusercontent.com/TOPLLab/WARDuino/7805f0bc87e79c55c151788a279f2ecb13e0fa7a/src/Debug/debug.proto --output "$temp_dir/debug.proto" && protoc --proto_path="$temp_dir" --plugin=protoc-gen-ts_proto=node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=src/protocol/vendor --ts_proto_opt=env=node,forceLong=bigint,useOptionals=messages,outputEncodeMethods=true,outputJsonMethods=false "$temp_dir/debug.proto" diff --git a/package-lock.json b/package-lock.json index 25a2103..d86c192 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "ink-testing-library": "^2.1.0", "mqtt": "^5.15.1", "serialport": "^13.0.0", + "ts-proto": "^2.12.1", "typescript-eslint": "^8.60.1" } }, @@ -337,6 +338,13 @@ "node": ">=18" } }, + "node_modules/@bufbuild/protobuf": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.14.0.tgz", + "integrity": "sha512-C3UGsiCwSprE2NKIIFA3hCDlpXTMCAXRZuEVp88L1GY36Y41+rYL5fryE+nOFhp4p4JPQvdV8PQ4DWgHgeTE+w==", + "dev": true, + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -2109,6 +2117,19 @@ ], "license": "CC-BY-4.0" }, + "node_modules/case-anything": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.13.tgz", + "integrity": "sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/cbor2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/cbor2/-/cbor2-2.3.0.tgz", @@ -2594,6 +2615,29 @@ "node": ">=0.3.1" } }, + "node_modules/dprint-node": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/dprint-node/-/dprint-node-1.0.8.tgz", + "integrity": "sha512-iVKnUtYfGrYcW1ZAlfR/F59cUVL8QIhWoBJoSjkkdua/dkWIgjZfiLMeTjiB06X0ZLkQ0M2C1VbUj/CxkIf1zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^1.0.3" + } + }, + "node_modules/dprint-node/node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.368", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.368.tgz", @@ -5418,6 +5462,42 @@ } } }, + "node_modules/ts-poet": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ts-poet/-/ts-poet-6.12.0.tgz", + "integrity": "sha512-xo+iRNMWqyvXpFTaOAvLPA5QAWO6TZrSUs5s4Odaya3epqofBu/fMLHEWl8jPmjhA0s9sgj9sNvF1BmaQlmQkA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dprint-node": "^1.0.8" + } + }, + "node_modules/ts-proto": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-2.12.1.tgz", + "integrity": "sha512-IEFvmib22yVlXbagL/UXcfliCPilgqXs3J0/ajDhUslfezMRhhhZvwgc63W0ZrKxCj+8jMHxvrN6G13A5UGFVg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bufbuild/protobuf": "^2.10.2", + "case-anything": "^2.1.13", + "ts-poet": "^6.12.0", + "ts-proto-descriptors": "2.1.0" + }, + "bin": { + "protoc-gen-ts_proto": "protoc-gen-ts_proto" + } + }, + "node_modules/ts-proto-descriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-proto-descriptors/-/ts-proto-descriptors-2.1.0.tgz", + "integrity": "sha512-S5EZYEQ6L9KLFfjSRpZWDIXDV/W7tAj8uW7pLsihIxyr62EAVSiKuVPwE8iWnr849Bqa53enex1jhDUcpgquzA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bufbuild/protobuf": "^2.0.0" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", diff --git a/package.json b/package.json index 4ca6c45..d4f5756 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "test:prebuild": "npm run build && npm run build:tests", "test": "npm run test:all", "test:all": "npm run test:prebuild && npm run test:ava && npm run test:integration && npm run test:end-to-end", + "protocol:generate": "just protocol", "test:ava": "ava", "test:integration": "npx ts-node ./tests/integration/precision.ts", "test:end-to-end": "npx ts-node ./tests/end-to-end/end2end.test.ts", @@ -76,6 +77,7 @@ "ink-testing-library": "^2.1.0", "mqtt": "^5.15.1", "serialport": "^13.0.0", + "ts-proto": "^2.12.1", "typescript-eslint": "^8.60.1" }, "ava": { diff --git a/src/framework/Testee.ts b/src/framework/Testee.ts index c990214..bb90f07 100644 --- a/src/framework/Testee.ts +++ b/src/framework/Testee.ts @@ -319,15 +319,19 @@ export class Testee { // TODO unified with testbed interface recover: (t: Testee) => Promise, retries: number = 0): Promise { let result: object | void = undefined; - let error; - while (0 <= retries && result === undefined) { - result = await attempt(testee, step, map).catch(async (err) => { + let error: unknown; + let succeeded = false; + while (0 <= retries && !succeeded) { + try { + result = await attempt(testee, step, map); + succeeded = true; + } catch (err) { error = err; await recover(testee); - }); - retries--; + retries--; + } } - return (result === undefined) ? Promise.reject(error) : result; + return succeeded ? result! : Promise.reject(error); } public skipall(): Testee { diff --git a/src/framework/scenario/Actions.ts b/src/framework/scenario/Actions.ts index 268b2aa..793a3b3 100644 --- a/src/framework/scenario/Actions.ts +++ b/src/framework/scenario/Actions.ts @@ -3,7 +3,8 @@ import {setTimeout} from 'timers/promises'; import {Testee} from '../Testee'; import {TestbedEvents} from '../../testbeds/Testbed'; import {Breakpoint} from '../../debug/Breakpoint'; -import {breakpointHitParser} from '../../messaging/Parsers'; +import {DebugFrame} from '../../protocol/frame'; +import {HitBreakpoint, NotificationType} from '../../protocol/vendor/debug'; export interface Dictionary { [index: string]: any; @@ -32,16 +33,12 @@ export function awaitBreakpoint(): Action { return { act: (testee: Testee) => { return new Promise>((resolve) => { - function breakpointListener(message: string) { - // check breakpoint hit message - try { - const breakpoint = breakpointHitParser(message); - // on success: remove listener + resolve - testee.bed()?.removeListener(TestbedEvents.OnMessage, breakpointListener); - resolve(assertable(breakpoint)); - } catch { - // breakpoint not hit yet - } + function breakpointListener(frame: DebugFrame) { + if (frame.type !== NotificationType.NOTIFICATION_HIT_BREAKPOINT) return; + const location = HitBreakpoint.decode(frame.payload).location; + if (location === undefined) return; + testee.bed()?.removeListener(TestbedEvents.OnMessage, breakpointListener); + resolve(assertable(new Breakpoint(location.programCounter, 0))); } // await breakpoint hit diff --git a/src/index.ts b/src/index.ts index a250307..ac2ee75 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,5 +16,7 @@ export * from './testbeds/TestbedSpecification'; export * from './debug/Breakpoint'; export * from './reporter/index'; export * from './debug/WARDuino'; +export * from './protocol/frame'; +export * as DebugProtocol from './protocol/vendor/debug'; export const latch = Framework.getImplementation(); diff --git a/src/messaging/Message.ts b/src/messaging/Message.ts index 9a2c85f..00f3440 100644 --- a/src/messaging/Message.ts +++ b/src/messaging/Message.ts @@ -1,17 +1,31 @@ import {WARDuino} from '../debug/WARDuino'; -import {ackParser, breakpointParser, invokeParser, stateParser} from './Parsers'; import {Breakpoint} from '../debug/Breakpoint'; import {WASM} from '../sourcemap/Wasm'; -import {write} from 'ieee754'; import {SourceMap} from '../sourcemap/SourceMap'; -import {readFileSync} from 'fs'; +import {readFileSync} from "fs"; +import {remoteFunctionResultParser} from "./Parsers"; import {CompileOutput, CompilerFactory} from '../manage/Compiler'; import {WABT} from '../util/env'; -import {encodeSLEB128, encodeULEB128} from '@thi.ng/leb128'; -import Interrupt = WARDuino.Interrupt; -import State = WARDuino.State; -import Value = WASM.Value; +import WasmValue = WASM.Value; import Type = WASM.Type; +import { + Breakpoint as ProtocolBreakpoint, + CallbackMapping, + Checkpoint, + Command, + Event as ProtocolEvent, + EventsQueue, + FunctionMessage, + HitBreakpoint, + Inspect as ProtocolInspect, + Locals, + ModuleUpdate, + NotificationType, + OperationResult, + RemoteFunctionCall, + Snapshot, + Value as ProtocolValue +} from '../protocol/vendor/debug'; // An acknowledgement returned by the debugger export interface Ack { @@ -20,11 +34,39 @@ export interface Ack { export type Exception = Ack; +const emptyNotification = (payload: Uint8Array): void => { + if (payload.length !== 0) throw Error('Expected an empty notification payload.'); +}; + +export const notificationParsers = { + [NotificationType.NOTIFICATION_CONTINUED]: emptyNotification, + [NotificationType.NOTIFICATION_HALTED]: emptyNotification, + [NotificationType.NOTIFICATION_PAUSED]: emptyNotification, + [NotificationType.NOTIFICATION_STEPPED]: emptyNotification, + [NotificationType.NOTIFICATION_HIT_BREAKPOINT]: HitBreakpoint.decode, + [NotificationType.NOTIFICATION_NEW_EVENT]: emptyNotification, + [NotificationType.NOTIFICATION_FUNCTION_DUMP]: FunctionMessage.decode, + [NotificationType.NOTIFICATION_LOCALS_DUMP]: Locals.decode, + [NotificationType.NOTIFICATION_SNAPSHOT]: Snapshot.decode, + [NotificationType.NOTIFICATION_EVENTS_DUMP]: EventsQueue.decode, + [NotificationType.NOTIFICATION_CALLBACKS_DUMP]: CallbackMapping.decode, + [NotificationType.NOTIFICATION_CHANGE_AFFECTED]: emptyNotification, + [NotificationType.NOTIFICATION_MALFORMED]: emptyNotification, + [NotificationType.NOTIFICATION_UNKNOWN_COMMAND]: emptyNotification, + [NotificationType.NOTIFICATION_OPERATION_RESULT]: OperationResult.decode, + [NotificationType.NOTIFICATION_REMOTE_FUNCTION_RESULT]: remoteFunctionResultParser, + [NotificationType.NOTIFICATION_CHECKPOINT]: Checkpoint.decode +} as const; + +export type Notification = keyof typeof notificationParsers; +export type NotificationResult = ReturnType<(typeof notificationParsers)[N]>; + // A request represents a debug message and its parser export interface Request { - type: Interrupt, // type of the debug message (pause, run, step, ...) - payload?: (map: SourceMap.Mapping) => string, // optional payload of the debug message - parser: (input: string) => R // the parser for the response to the debug message + type: Command, + notification: NotificationType, + payload?: (map: SourceMap.Mapping) => Uint8Array, + parser: (payload: Uint8Array) => R } /* eslint-disable @typescript-eslint/no-namespace */ @@ -32,198 +74,189 @@ export namespace Message { import Inspect = WARDuino.Inspect; import Float = WASM.Float; import isFloat = WASM.isFloat; - export const run: Request = { - type: Interrupt.run, - parser: (line: string) => { - return ackParser(line, 'GO'); - } + + export const run: Request = { + type: Command.COMMAND_RUN, + notification: NotificationType.NOTIFICATION_CONTINUED, + parser: notificationParsers[NotificationType.NOTIFICATION_CONTINUED] }; - export const halt: Request = { - type: Interrupt.halt, - parser: (line: string) => { - return ackParser(line, 'STOP'); - } + export const halt: Request = { + type: Command.COMMAND_HALT, + notification: NotificationType.NOTIFICATION_HALTED, + parser: notificationParsers[NotificationType.NOTIFICATION_HALTED] }; - export const pause: Request = { - type: Interrupt.pause, - parser: (line: string) => { - return ackParser(line, 'PAUSE'); - } + export const pause: Request = { + type: Command.COMMAND_PAUSE, + notification: NotificationType.NOTIFICATION_PAUSED, + parser: notificationParsers[NotificationType.NOTIFICATION_PAUSED] }; - export const step: Request = { - type: Interrupt.step, - parser: (line: string) => { - return ackParser(line, 'STEP'); - } + export const step: Request = { + type: Command.COMMAND_STEP, + notification: NotificationType.NOTIFICATION_STEPPED, + parser: notificationParsers[NotificationType.NOTIFICATION_STEPPED] }; - export const stepOver: Request = { - type: Interrupt.stepOver, - parser: (line: string): Ack => { - try { - return ackParser(line, 'STEP'); - } catch (_err) { - return ackParser(line, 'AT '); - } - } + export const stepOver: Request = { + type: Command.COMMAND_STEP_OVER, + notification: NotificationType.NOTIFICATION_STEPPED, + parser: notificationParsers[NotificationType.NOTIFICATION_STEPPED] }; - export function addBreakpoint(payload: Breakpoint): Request { + export function addBreakpoint(payload: Breakpoint): Request { return { - type: Interrupt.addBreakpoint, - payload: () => payload.toString(), - parser: breakpointParser + type: Command.COMMAND_ADD_BREAKPOINT, + notification: NotificationType.NOTIFICATION_OPERATION_RESULT, + payload: () => ProtocolBreakpoint.encode({ + location: {moduleIndex: 0, programCounter: payload.id} + }).finish(), + parser: notificationParsers[NotificationType.NOTIFICATION_OPERATION_RESULT] }; } - export function removeBreakpoint(payload: Breakpoint): Request { + export function removeBreakpoint(payload: Breakpoint): Request { return { - type: Interrupt.removeBreakpoint, - payload: () => payload.toString(), - parser: breakpointParser + type: Command.COMMAND_REMOVE_BREAKPOINT, + notification: NotificationType.NOTIFICATION_OPERATION_RESULT, + payload: () => ProtocolBreakpoint.encode({ + location: {moduleIndex: 0, programCounter: payload.id} + }).finish(), + parser: notificationParsers[NotificationType.NOTIFICATION_OPERATION_RESULT] }; } - export function inspect(fields: Inspect[]): Request { + export function inspect(fields: Inspect[]): Request { return { - type: Interrupt.inspect, - payload: () => fields.length.toString(16).padStart(4, '0') + fields.join('') - , - parser: stateParser + type: Command.COMMAND_INSPECT, + notification: NotificationType.NOTIFICATION_SNAPSHOT, + payload: () => ProtocolInspect.encode({ + state: Buffer.from(fields.map(field => Number.parseInt(field, 16))) + }).finish(), + parser: notificationParsers[NotificationType.NOTIFICATION_SNAPSHOT] } } - export const dump: Request = { - type: Interrupt.dump, - parser: stateParser + export const dump: Request = { + type: Command.COMMAND_DUMP, + notification: NotificationType.NOTIFICATION_FUNCTION_DUMP, + parser: notificationParsers[NotificationType.NOTIFICATION_FUNCTION_DUMP] }; - export const dumpLocals: Request = { - type: Interrupt.dumpLocals, - parser: stateParser + export const dumpLocals: Request = { + type: Command.COMMAND_DUMP_LOCALS, + notification: NotificationType.NOTIFICATION_LOCALS_DUMP, + parser: notificationParsers[NotificationType.NOTIFICATION_LOCALS_DUMP] }; - export const dumpAll: Request = { - type: Interrupt.dumpAll, - parser: stateParser + export const reset: Request = { + type: Command.COMMAND_RESET, + notification: NotificationType.NOTIFICATION_OPERATION_RESULT, + parser: notificationParsers[NotificationType.NOTIFICATION_OPERATION_RESULT] }; - export const reset: Request = { - type: Interrupt.reset, - parser: (line: string) => { - return ackParser(line, 'RESET'); - } - }; - - export const updateFunction: Request = { - type: Interrupt.updateFunction, - parser: (line: string) => { - return ackParser(line, 'CHANGE function'); - } + export const updateFunction: Request = { + type: Command.COMMAND_UPDATE_FUNCTION, + notification: NotificationType.NOTIFICATION_OPERATION_RESULT, + parser: notificationParsers[NotificationType.NOTIFICATION_OPERATION_RESULT] } - export const updateLocal: Request = { - type: Interrupt.updateLocal, - parser: (line: string) => { - return ackParser(line, 'CHANGE local'); - } + export const updateLocal: Request = { + type: Command.COMMAND_UPDATE_LOCAL, + notification: NotificationType.NOTIFICATION_OPERATION_RESULT, + parser: notificationParsers[NotificationType.NOTIFICATION_OPERATION_RESULT] } - export async function uploadFile(program: string): Promise> { + export async function uploadFile(program: string): Promise> { const compiled: CompileOutput = await new CompilerFactory(WABT).pickCompiler(program).compile(program); return updateModule(compiled.file); } - export function updateModule(wasm: string): Request { - function payload(binary: Buffer): string { - const w = new Uint8Array(binary); - const sizeHex = Buffer.from(encodeULEB128(w.length)).toString('hex'); - const sizeBuffer = Buffer.allocUnsafe(4); - sizeBuffer.writeUint32BE(w.length); - const wasmHex = Buffer.from(w).toString('hex'); - return sizeHex + wasmHex; - } - + export function updateModule(wasm: string): Request { return { - type: Interrupt.updateModule, - payload: () => payload(readFileSync(wasm)), - parser: (line: string) => { - return ackParser(line, 'CHANGE Module'); - } + type: Command.COMMAND_UPDATE_MODULE, + notification: NotificationType.NOTIFICATION_OPERATION_RESULT, + payload: () => ModuleUpdate.encode({wasm: readFileSync(wasm)}).finish(), + parser: notificationParsers[NotificationType.NOTIFICATION_OPERATION_RESULT] } } - export function pushEvent(topic: string, payload: string): Request { + export function pushEvent(topic: string, payload: string): Request { return { - type: Interrupt.pushEvent, - payload: () => `{topic: '${topic}', payload: '${payload}'}`, - parser: (line: string) => { - return ackParser(line, 'Interrupt: 73'); - } + type: Command.COMMAND_PUSH_EVENT, + notification: NotificationType.NOTIFICATION_NEW_EVENT, + payload: () => ProtocolEvent.encode({topic, payload: Buffer.from(payload)}).finish(), + parser: notificationParsers[NotificationType.NOTIFICATION_NEW_EVENT] } } - export function invoke(func: string, args: Value[]): Request | Exception> { + export function invoke(func: string, args: WasmValue[]): Request | Exception> { function fidx(map: SourceMap.Mapping, func: string): number { - const fidx: number | void = map.functions.find((closure: SourceMap.Closure) => closure.name === func)?.index; - if (fidx === undefined) { + const index: number | void = map.functions.find((closure: SourceMap.Closure) => closure.name === func)?.index; + if (index === undefined) { throw Error(`Sourcemap: index of ${func} not found.`); } - return fidx!; + return index; } - function convert(args: Value[]) { - let payload: string = ''; - args.forEach((arg: Value) => { - if (isFloat(arg.type)) { - const buff = Buffer.alloc(arg.type === Float.f32 ? 4 : 8); - write(buff, Number(arg.value), 0, true, arg.type === Float.f32 ? 23 : 52, buff.length); // todo fix precision loss - payload += buff.toString('hex'); - } else if (arg.type === WASM.Integer.i32 || arg.type === WASM.Integer.i64) { - payload += Buffer.from(encodeSLEB128((arg.value as WASM.WasmInt).toBigInt())).toString('hex'); - } else if (arg.type === WASM.Integer.u32 || arg.type === WASM.Integer.u64) { - payload += Buffer.from(encodeULEB128((arg.value as WASM.WasmInt).toBigInt())).toString('hex'); - } else { - throw Error(`Cannot invoke a function with a ${arg.type} argument.`); + function convert(arg: WasmValue, index: number): ProtocolValue { + if (arg.type === WASM.Integer.i32 || arg.type === WASM.Integer.u32) { + return {i32Bits: Number(BigInt.asUintN(32, (arg.value as WASM.WasmInt).toBigInt())), index}; + } + if (arg.type === WASM.Integer.i64 || arg.type === WASM.Integer.u64) { + return {i64Bits: BigInt.asUintN(64, (arg.value as WASM.WasmInt).toBigInt()), index}; + } + if (isFloat(arg.type)) { + const buffer = Buffer.allocUnsafe(arg.type === Float.f32 ? 4 : 8); + if (arg.type === Float.f32) { + buffer.writeFloatLE(Number(arg.value)); + return {f32Bits: buffer.readUInt32LE(), index}; } - }); - return payload; + buffer.writeDoubleLE(Number(arg.value)); + return {f64Bits: buffer.readBigUInt64LE(), index}; + } + throw Error(`Cannot invoke a function with a ${arg.type} argument.`); } return { - type: Interrupt.invoke, - payload: (map: SourceMap.Mapping) => `${Buffer.from(encodeULEB128(fidx(map, func))).toString('hex')}${convert(args)}`, - parser: invokeParser + type: Command.COMMAND_INVOKE, + notification: NotificationType.NOTIFICATION_REMOTE_FUNCTION_RESULT, + payload: (map: SourceMap.Mapping) => RemoteFunctionCall.encode({ + functionIndex: fidx(map, func), + arguments: args.map(convert) + }).finish(), + parser: notificationParsers[NotificationType.NOTIFICATION_REMOTE_FUNCTION_RESULT] } } - export const snapshot: Request = { - type: Interrupt.snapshot, - parser: stateParser + export const snapshot: Request = { + type: Command.COMMAND_SNAPSHOT, + notification: NotificationType.NOTIFICATION_SNAPSHOT, + parser: notificationParsers[NotificationType.NOTIFICATION_SNAPSHOT] } - export const dumpAllEvents: Request = { - type: Interrupt.dumpAllEvents, - parser: stateParser + export const dumpAllEvents: Request = { + type: Command.COMMAND_DUMP_EVENTS, + notification: NotificationType.NOTIFICATION_EVENTS_DUMP, + parser: notificationParsers[NotificationType.NOTIFICATION_EVENTS_DUMP] } - export const dumpEvents: Request = { - type: Interrupt.dumpEvents, - parser: stateParser + export const dumpEvents: Request = { + type: Command.COMMAND_DUMP_EVENTS, + notification: NotificationType.NOTIFICATION_EVENTS_DUMP, + parser: notificationParsers[NotificationType.NOTIFICATION_EVENTS_DUMP] } - export const dumpCallbackmapping: Request = { - type: Interrupt.dumpCallbackmapping, - parser: stateParser + export const dumpCallbackmapping: Request = { + type: Command.COMMAND_DUMP_CALLBACKS, + notification: NotificationType.NOTIFICATION_CALLBACKS_DUMP, + parser: notificationParsers[NotificationType.NOTIFICATION_CALLBACKS_DUMP] } - export const proxifyRequest: Request = { - type: Interrupt.proxify, - parser: (line: string) => { - return ackParser(line, 'PROXIED'); - } + export const proxifyRequest: Request = { + type: Command.COMMAND_PROXIFY, + notification: NotificationType.NOTIFICATION_OPERATION_RESULT, + parser: notificationParsers[NotificationType.NOTIFICATION_OPERATION_RESULT] }; } diff --git a/src/messaging/Parsers.ts b/src/messaging/Parsers.ts index 6635bd3..78dfedc 100644 --- a/src/messaging/Parsers.ts +++ b/src/messaging/Parsers.ts @@ -9,6 +9,7 @@ import nothing = WASM.nothing; import Type = WASM.Type; import WasmInt = WASM.WasmInt; import ieee754 from "ieee754"; +import {RemoteFunctionResult, Value as ProtocolValue} from "../protocol/vendor/debug"; export function identityParser(text: string) { return stripEnd(text); @@ -29,6 +30,57 @@ export function invokeParser(text: string): WASM.Value | Exception { return stacking(stack)[stack.length - 1]; } +/** Convert a protobuf remote-function response into the Latch WASM value contract. */ +export function remoteFunctionResultParser(payload: Uint8Array): WASM.Value | Exception { + return remoteFunctionResult(RemoteFunctionResult.decode(payload)); +} + +export function remoteFunctionResult(result: RemoteFunctionResult): WASM.Value | Exception { + if (!result.success) { + return {text: result.error.toString("utf8") || "Remote function invocation failed."}; + } + if (result.results.length === 0) { + return nothing; + } + return protocolValue(result.results[result.results.length - 1]); +} + +function protocolValue(value: ProtocolValue): WASM.Value { + const fields = [ + value.i32Bits === undefined ? undefined : "i32", + value.i64Bits === undefined ? undefined : "i64", + value.f32Bits === undefined ? undefined : "f32", + value.f64Bits === undefined ? undefined : "f64" + ].filter((field): field is string => field !== undefined); + + if (fields.length !== 1 || value.raw !== undefined) { + throw Error("Remote function result contains no supported WASM value."); + } + + switch (fields[0]) { + case "i32": + return {value: WasmInt.finite(signed(BigInt(value.i32Bits!), 32)), type: WASM.Integer.i32}; + case "i64": + return {value: WasmInt.finite(signed(value.i64Bits!, 64)), type: WASM.Integer.i64}; + case "f32": + return {value: floatFromBits(value.f32Bits!, 4), type: WASM.Float.f32}; + case "f64": + return {value: floatFromBits(value.f64Bits!, 8), type: WASM.Float.f64}; + default: + throw Error("Remote function result contains no supported WASM value."); + } +} + +function floatFromBits(bits: number | bigint, bytes: 4 | 8): number { + const buffer = Buffer.allocUnsafe(bytes); + if (bytes === 4) { + buffer.writeUInt32LE(Number(bits)); + return buffer.readFloatLE(); + } + buffer.writeBigUInt64LE(BigInt(bits)); + return buffer.readDoubleLE(); +} + function exception(text: string): boolean { return text.length > 1 && text.toLowerCase().includes('exception') && text.trim()[0] !== '{'; } diff --git a/src/protocol/frame.ts b/src/protocol/frame.ts new file mode 100644 index 0000000..0b4ff0e --- /dev/null +++ b/src/protocol/frame.ts @@ -0,0 +1,67 @@ +export interface DebugFrame { + type: T; + payload: Uint8Array; +} + +const MAX_VARINT_BYTES = 5; +const MAX_PAYLOAD_SIZE = 0x7fffffff; + +export function encodeFrame(frame: DebugFrame): Buffer { + if (!Number.isInteger(frame.type) || frame.type < 0 || frame.type > 0xff) { + throw new Error("Debug frame type must fit in one byte."); + } + return Buffer.concat([Buffer.from([frame.type]), encodeLength(frame.payload.length), Buffer.from(frame.payload)]); +} + +export function encodeLength(length: number): Buffer { + if (!Number.isInteger(length) || length < 0 || length > MAX_PAYLOAD_SIZE) { + throw new Error("Invalid debug frame payload length."); + } + + const bytes: number[] = []; + do { + let byte = length & 0x7f; + length >>>= 7; + if (length > 0) byte |= 0x80; + bytes.push(byte); + } while (length > 0); + return Buffer.from(bytes); +} + +export class DebugFrameDecoder { + private buffer = Buffer.alloc(0); + + push(chunk: Uint8Array): DebugFrame[] { + this.buffer = Buffer.concat([this.buffer, Buffer.from(chunk)]); + const frames: DebugFrame[] = []; + let offset = 0; + + while (offset < this.buffer.length) { + const length = this.readLength(offset + 1); + if (length === undefined) break; + const start = offset + 1 + length.bytes; + const end = start + length.value; + if (end > this.buffer.length) break; + frames.push({type: this.buffer[offset], payload: this.buffer.subarray(start, end)}); + offset = end; + } + + this.buffer = this.buffer.subarray(offset); + return frames; + } + + private readLength(offset: number): {value: number; bytes: number} | undefined { + let value = 0; + for (let index = 0; index < MAX_VARINT_BYTES; index++) { + const position = offset + index; + if (position >= this.buffer.length) return undefined; + const byte = this.buffer[position]; + value |= (byte & 0x7f) << (index * 7); + if ((byte & 0x80) === 0) { + if (value > MAX_PAYLOAD_SIZE) throw new Error("Debug frame payload is too large."); + return {value, bytes: index + 1}; + } + } + throw new Error("Invalid debug frame length varint."); + } +} diff --git a/src/protocol/vendor/debug.ts b/src/protocol/vendor/debug.ts new file mode 100644 index 0000000..920f000 --- /dev/null +++ b/src/protocol/vendor/debug.ts @@ -0,0 +1,2708 @@ +// Code generated by protoc-gen-ts_proto. DO NOT EDIT. +// versions: +// protoc-gen-ts_proto v2.12.1 +// protoc v7.35.1 +// source: debug.proto + +/* eslint-disable */ +import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; + +export const protobufPackage = "debug"; + +/** + * Frontend -> WARDuino. + * The receiver selects the payload schema from the command byte. + */ +export enum Command { + /** COMMAND_RUN - no payload */ + COMMAND_RUN = 0, + /** COMMAND_HALT - no payload */ + COMMAND_HALT = 1, + /** COMMAND_PAUSE - no payload */ + COMMAND_PAUSE = 2, + /** COMMAND_STEP - no payload */ + COMMAND_STEP = 3, + /** COMMAND_STEP_OVER - no payload */ + COMMAND_STEP_OVER = 4, + /** COMMAND_ADD_BREAKPOINT - Breakpoint */ + COMMAND_ADD_BREAKPOINT = 5, + /** COMMAND_REMOVE_BREAKPOINT - Breakpoint */ + COMMAND_REMOVE_BREAKPOINT = 6, + /** COMMAND_DUMP - no payload */ + COMMAND_DUMP = 7, + /** COMMAND_DUMP_LOCALS - no payload */ + COMMAND_DUMP_LOCALS = 8, + /** COMMAND_SNAPSHOT - no payload */ + COMMAND_SNAPSHOT = 9, + /** COMMAND_DUMP_EVENTS - Range */ + COMMAND_DUMP_EVENTS = 10, + /** COMMAND_DUMP_CALLBACKS - no payload */ + COMMAND_DUMP_CALLBACKS = 11, + /** COMMAND_UPDATE_FUNCTION - Function */ + COMMAND_UPDATE_FUNCTION = 12, + /** COMMAND_UPDATE_LOCAL - ValueUpdate */ + COMMAND_UPDATE_LOCAL = 13, + /** COMMAND_UPDATE_CALLBACKS - CallbackMapping */ + COMMAND_UPDATE_CALLBACKS = 14, + /** COMMAND_UPDATE_MODULE - ModuleUpdate */ + COMMAND_UPDATE_MODULE = 26, + /** COMMAND_UPDATE_GLOBAL - ValueUpdate */ + COMMAND_UPDATE_GLOBAL = 27, + /** COMMAND_UPDATE_STACK - ValueUpdate */ + COMMAND_UPDATE_STACK = 28, + /** COMMAND_LOAD_SNAPSHOT - Snapshot */ + COMMAND_LOAD_SNAPSHOT = 15, + /** COMMAND_PROXIFY - no payload */ + COMMAND_PROXIFY = 16, + /** COMMAND_ADD_PROXY - FunctionRef */ + COMMAND_ADD_PROXY = 17, + /** COMMAND_REMOVE_PROXY - FunctionRef */ + COMMAND_REMOVE_PROXY = 18, + /** COMMAND_PROXY_CALL - RemoteFunctionCall */ + COMMAND_PROXY_CALL = 19, + /** COMMAND_POP_EVENT - no payload */ + COMMAND_POP_EVENT = 20, + /** COMMAND_PUSH_EVENT - Event */ + COMMAND_PUSH_EVENT = 21, + /** COMMAND_CONTINUE_FOR - ContinueFor */ + COMMAND_CONTINUE_FOR = 22, + /** COMMAND_INSPECT - Inspect */ + COMMAND_INSPECT = 23, + /** COMMAND_RESET - no payload */ + COMMAND_RESET = 24, + /** COMMAND_INVOKE - RemoteFunctionCall */ + COMMAND_INVOKE = 25, + /** COMMAND_SET_SNAPSHOT_POLICY - SnapshotPolicyConfig */ + COMMAND_SET_SNAPSHOT_POLICY = 29, + /** COMMAND_SET_OVERRIDE - Override */ + COMMAND_SET_OVERRIDE = 30, + /** COMMAND_REMOVE_OVERRIDE - Override */ + COMMAND_REMOVE_OVERRIDE = 31, + UNRECOGNIZED = -1, +} + +/** + * WARDuino -> frontend. + * The receiver selects the payload schema from the notification byte. + */ +export enum NotificationType { + /** NOTIFICATION_CONTINUED - no payload */ + NOTIFICATION_CONTINUED = 0, + /** NOTIFICATION_HALTED - no payload */ + NOTIFICATION_HALTED = 1, + /** NOTIFICATION_PAUSED - no payload */ + NOTIFICATION_PAUSED = 2, + /** NOTIFICATION_STEPPED - no payload */ + NOTIFICATION_STEPPED = 3, + /** NOTIFICATION_HIT_BREAKPOINT - HitBreakpoint */ + NOTIFICATION_HIT_BREAKPOINT = 4, + /** NOTIFICATION_NEW_EVENT - NewEvent (zero-length payload) */ + NOTIFICATION_NEW_EVENT = 5, + /** NOTIFICATION_FUNCTION_DUMP - Function */ + NOTIFICATION_FUNCTION_DUMP = 6, + /** NOTIFICATION_LOCALS_DUMP - Locals */ + NOTIFICATION_LOCALS_DUMP = 7, + /** NOTIFICATION_SNAPSHOT - Snapshot */ + NOTIFICATION_SNAPSHOT = 8, + /** NOTIFICATION_EVENTS_DUMP - EventsQueue */ + NOTIFICATION_EVENTS_DUMP = 9, + /** NOTIFICATION_CALLBACKS_DUMP - CallbackMapping */ + NOTIFICATION_CALLBACKS_DUMP = 10, + /** NOTIFICATION_CHANGE_AFFECTED - no payload */ + NOTIFICATION_CHANGE_AFFECTED = 11, + /** NOTIFICATION_MALFORMED - no payload */ + NOTIFICATION_MALFORMED = 12, + /** NOTIFICATION_UNKNOWN_COMMAND - no payload */ + NOTIFICATION_UNKNOWN_COMMAND = 13, + /** NOTIFICATION_OPERATION_RESULT - OperationResult */ + NOTIFICATION_OPERATION_RESULT = 14, + /** NOTIFICATION_REMOTE_FUNCTION_RESULT - RemoteFunctionResult */ + NOTIFICATION_REMOTE_FUNCTION_RESULT = 15, + /** NOTIFICATION_CHECKPOINT - Checkpoint */ + NOTIFICATION_CHECKPOINT = 16, + UNRECOGNIZED = -1, +} + +export enum State { + STATE_WARDUINO_RUN = 0, + STATE_WARDUINO_PAUSE = 1, + STATE_WARDUINO_STEP = 2, + STATE_PROXY_RUN = 3, + STATE_PROXY_HALT = 4, + UNRECOGNIZED = -1, +} + +export enum SnapshotPolicy { + SNAPSHOT_POLICY_NONE = 0, + SNAPSHOT_POLICY_EVERY_INSTRUCTION = 1, + SNAPSHOT_POLICY_CHECKPOINTING = 2, + UNRECOGNIZED = -1, +} + +/** A virtual program address. */ +export interface CodeLocation { + moduleIndex: number; + programCounter: number; +} + +export interface Breakpoint { + location?: CodeLocation | undefined; +} + +export interface HitBreakpoint { + location?: CodeLocation | undefined; +} + +/** + * The notification type carries all information for this event. The empty + * message exists for host-side reflection, but no protobuf bytes are sent. + */ +export interface NewEvent { +} + +export interface ContinueFor { + count: number; +} + +/** + * Execution-state selectors understood by the VM. Keeping them as bytes lets + * the protocol add selectors without changing this schema. + */ +export interface Inspect { + state: Buffer; +} + +export interface FunctionRef { + functionIndex: number; +} + +export interface ValueUpdate { + index: number; + value?: Value | undefined; +} + +export interface Snapshot { + programCounter: number; + state: State; + breakpoints: number[]; + functions: FunctionMessage[]; + callstack: CallstackEntry[]; + locals?: Locals | undefined; + queue?: EventsQueue | undefined; + callbacks?: CallbackMapping | undefined; + globals: Value[]; + stack: Value[]; + table?: TableState | undefined; + memory?: MemoryState | undefined; + branchTable: number[]; + io: IOState[]; + overrides: Override[]; + heapUsed: number; +} + +export interface FunctionMessage { + functionIndex: number; + range?: Range | undefined; + locals?: Locals | undefined; + instructions: Buffer; +} + +export interface RemoteFunctionCall { + functionIndex: number; + arguments: Value[]; +} + +export interface CallstackEntry { + type: number; + functionIndex: number; + stackPointer: number; + framePointer: number; + start: number; + returnAddress: number; +} + +export interface Locals { + values: Value[]; +} + +/** + * The oneof tag is the value type, so a separate type enum and a decimal + * string are unnecessary. Fixed-width fields preserve WebAssembly bits and + * are fast to construct on the MCU. + */ +export interface Value { + i32Bits?: number | undefined; + i64Bits?: bigint | undefined; + f32Bits?: number | undefined; + f64Bits?: bigint | undefined; + raw?: Buffer | undefined; + index: number; +} + +export interface CallbackMapping { + entries: CallbackEntry[]; +} + +export interface CallbackEntry { + topic: string; + tableIndexes: number[]; +} + +export interface EventsQueue { + /** Total events in the queue; this can exceed the returned slice length. */ + totalCount: number; + events: Event[]; + range?: Range | undefined; +} + +export interface Event { + topic: string; + payload: Buffer; +} + +export interface Range { + start: number; + end: number; +} + +export interface ModuleUpdate { + wasm: Buffer; +} + +export interface IndexedValues { + values: Value[]; +} + +export interface SnapshotPolicyConfig { + policy: SnapshotPolicy; + interval: number; + minimumReturnCount: number; + selectedState: Buffer; +} + +export interface Override { + primitiveName: string; + argumentWords: number[]; + result: number; +} + +export interface OperationResult { + command: Command; + success: boolean; +} + +export interface RemoteFunctionResult { + success: boolean; + results: Value[]; + error: Buffer; +} + +export interface Checkpoint { + instructionCount: number; + hasPrimitiveCall: boolean; + primitiveFunctionIndex: number; + arguments: Value[]; + results: Value[]; + snapshot?: Snapshot | undefined; +} + +export interface TableState { + initial: number; + maximum: number; + entries: number[]; +} + +export interface MemoryState { + initial: number; + maximum: number; + pages: number; + bytes: Buffer; +} + +export interface IOState { + key: string; + output: boolean; + value: number; +} + +function createBaseCodeLocation(): CodeLocation { + return { moduleIndex: 0, programCounter: 0 }; +} + +export const CodeLocation: MessageFns = { + encode(message: CodeLocation, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.moduleIndex !== 0) { + writer.uint32(8).uint32(message.moduleIndex); + } + if (message.programCounter !== 0) { + writer.uint32(16).uint32(message.programCounter); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): CodeLocation { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseCodeLocation(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.moduleIndex = reader.uint32(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.programCounter = reader.uint32(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): CodeLocation { + return CodeLocation.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): CodeLocation { + const message = createBaseCodeLocation(); + message.moduleIndex = object.moduleIndex ?? 0; + message.programCounter = object.programCounter ?? 0; + return message; + }, +}; + +function createBaseBreakpoint(): Breakpoint { + return { location: undefined }; +} + +export const Breakpoint: MessageFns = { + encode(message: Breakpoint, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.location !== undefined) { + CodeLocation.encode(message.location, writer.uint32(10).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Breakpoint { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseBreakpoint(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.location = CodeLocation.decode(reader, reader.uint32()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): Breakpoint { + return Breakpoint.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Breakpoint { + const message = createBaseBreakpoint(); + message.location = (object.location !== undefined && object.location !== null) + ? CodeLocation.fromPartial(object.location) + : undefined; + return message; + }, +}; + +function createBaseHitBreakpoint(): HitBreakpoint { + return { location: undefined }; +} + +export const HitBreakpoint: MessageFns = { + encode(message: HitBreakpoint, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.location !== undefined) { + CodeLocation.encode(message.location, writer.uint32(10).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): HitBreakpoint { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseHitBreakpoint(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.location = CodeLocation.decode(reader, reader.uint32()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): HitBreakpoint { + return HitBreakpoint.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): HitBreakpoint { + const message = createBaseHitBreakpoint(); + message.location = (object.location !== undefined && object.location !== null) + ? CodeLocation.fromPartial(object.location) + : undefined; + return message; + }, +}; + +function createBaseNewEvent(): NewEvent { + return {}; +} + +export const NewEvent: MessageFns = { + encode(_: NewEvent, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): NewEvent { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseNewEvent(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): NewEvent { + return NewEvent.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(_: I): NewEvent { + const message = createBaseNewEvent(); + return message; + }, +}; + +function createBaseContinueFor(): ContinueFor { + return { count: 0 }; +} + +export const ContinueFor: MessageFns = { + encode(message: ContinueFor, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.count !== 0) { + writer.uint32(8).uint32(message.count); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): ContinueFor { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseContinueFor(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.count = reader.uint32(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): ContinueFor { + return ContinueFor.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): ContinueFor { + const message = createBaseContinueFor(); + message.count = object.count ?? 0; + return message; + }, +}; + +function createBaseInspect(): Inspect { + return { state: Buffer.alloc(0) }; +} + +export const Inspect: MessageFns = { + encode(message: Inspect, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.state.length !== 0) { + writer.uint32(10).bytes(message.state); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Inspect { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseInspect(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.state = Buffer.from(reader.bytes()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): Inspect { + return Inspect.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Inspect { + const message = createBaseInspect(); + message.state = object.state ?? Buffer.alloc(0); + return message; + }, +}; + +function createBaseFunctionRef(): FunctionRef { + return { functionIndex: 0 }; +} + +export const FunctionRef: MessageFns = { + encode(message: FunctionRef, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.functionIndex !== 0) { + writer.uint32(8).uint32(message.functionIndex); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): FunctionRef { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseFunctionRef(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.functionIndex = reader.uint32(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): FunctionRef { + return FunctionRef.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): FunctionRef { + const message = createBaseFunctionRef(); + message.functionIndex = object.functionIndex ?? 0; + return message; + }, +}; + +function createBaseValueUpdate(): ValueUpdate { + return { index: 0, value: undefined }; +} + +export const ValueUpdate: MessageFns = { + encode(message: ValueUpdate, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.index !== 0) { + writer.uint32(8).uint32(message.index); + } + if (message.value !== undefined) { + Value.encode(message.value, writer.uint32(18).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): ValueUpdate { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseValueUpdate(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.index = reader.uint32(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.value = Value.decode(reader, reader.uint32()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): ValueUpdate { + return ValueUpdate.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): ValueUpdate { + const message = createBaseValueUpdate(); + message.index = object.index ?? 0; + message.value = (object.value !== undefined && object.value !== null) ? Value.fromPartial(object.value) : undefined; + return message; + }, +}; + +function createBaseSnapshot(): Snapshot { + return { + programCounter: 0, + state: 0, + breakpoints: [], + functions: [], + callstack: [], + locals: undefined, + queue: undefined, + callbacks: undefined, + globals: [], + stack: [], + table: undefined, + memory: undefined, + branchTable: [], + io: [], + overrides: [], + heapUsed: 0, + }; +} + +export const Snapshot: MessageFns = { + encode(message: Snapshot, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.programCounter !== 0) { + writer.uint32(8).uint32(message.programCounter); + } + if (message.state !== 0) { + writer.uint32(16).int32(message.state); + } + writer.uint32(26).fork(); + for (const v of message.breakpoints) { + writer.uint32(v); + } + writer.join(); + for (const v of message.functions) { + FunctionMessage.encode(v!, writer.uint32(34).fork()).join(); + } + for (const v of message.callstack) { + CallstackEntry.encode(v!, writer.uint32(42).fork()).join(); + } + if (message.locals !== undefined) { + Locals.encode(message.locals, writer.uint32(50).fork()).join(); + } + if (message.queue !== undefined) { + EventsQueue.encode(message.queue, writer.uint32(58).fork()).join(); + } + if (message.callbacks !== undefined) { + CallbackMapping.encode(message.callbacks, writer.uint32(66).fork()).join(); + } + for (const v of message.globals) { + Value.encode(v!, writer.uint32(74).fork()).join(); + } + for (const v of message.stack) { + Value.encode(v!, writer.uint32(82).fork()).join(); + } + if (message.table !== undefined) { + TableState.encode(message.table, writer.uint32(90).fork()).join(); + } + if (message.memory !== undefined) { + MemoryState.encode(message.memory, writer.uint32(98).fork()).join(); + } + writer.uint32(106).fork(); + for (const v of message.branchTable) { + writer.uint32(v); + } + writer.join(); + for (const v of message.io) { + IOState.encode(v!, writer.uint32(114).fork()).join(); + } + for (const v of message.overrides) { + Override.encode(v!, writer.uint32(122).fork()).join(); + } + if (message.heapUsed !== 0) { + writer.uint32(128).uint32(message.heapUsed); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Snapshot { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseSnapshot(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.programCounter = reader.uint32(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.state = reader.int32() as any; + continue; + } + case 3: { + if (tag === 24) { + message.breakpoints.push(reader.uint32()); + + continue; + } + + if (tag === 26) { + const end2 = reader.uint32() + reader.pos; + while (reader.pos < end2) { + message.breakpoints.push(reader.uint32()); + } + + continue; + } + + break; + } + case 4: { + if (tag !== 34) { + break; + } + + message.functions.push(FunctionMessage.decode(reader, reader.uint32())); + continue; + } + case 5: { + if (tag !== 42) { + break; + } + + message.callstack.push(CallstackEntry.decode(reader, reader.uint32())); + continue; + } + case 6: { + if (tag !== 50) { + break; + } + + message.locals = Locals.decode(reader, reader.uint32()); + continue; + } + case 7: { + if (tag !== 58) { + break; + } + + message.queue = EventsQueue.decode(reader, reader.uint32()); + continue; + } + case 8: { + if (tag !== 66) { + break; + } + + message.callbacks = CallbackMapping.decode(reader, reader.uint32()); + continue; + } + case 9: { + if (tag !== 74) { + break; + } + + message.globals.push(Value.decode(reader, reader.uint32())); + continue; + } + case 10: { + if (tag !== 82) { + break; + } + + message.stack.push(Value.decode(reader, reader.uint32())); + continue; + } + case 11: { + if (tag !== 90) { + break; + } + + message.table = TableState.decode(reader, reader.uint32()); + continue; + } + case 12: { + if (tag !== 98) { + break; + } + + message.memory = MemoryState.decode(reader, reader.uint32()); + continue; + } + case 13: { + if (tag === 104) { + message.branchTable.push(reader.uint32()); + + continue; + } + + if (tag === 106) { + const end2 = reader.uint32() + reader.pos; + while (reader.pos < end2) { + message.branchTable.push(reader.uint32()); + } + + continue; + } + + break; + } + case 14: { + if (tag !== 114) { + break; + } + + message.io.push(IOState.decode(reader, reader.uint32())); + continue; + } + case 15: { + if (tag !== 122) { + break; + } + + message.overrides.push(Override.decode(reader, reader.uint32())); + continue; + } + case 16: { + if (tag !== 128) { + break; + } + + message.heapUsed = reader.uint32(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): Snapshot { + return Snapshot.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Snapshot { + const message = createBaseSnapshot(); + message.programCounter = object.programCounter ?? 0; + message.state = object.state ?? 0; + message.breakpoints = object.breakpoints?.map((e) => e) || []; + message.functions = object.functions?.map((e) => FunctionMessage.fromPartial(e)) || []; + message.callstack = object.callstack?.map((e) => CallstackEntry.fromPartial(e)) || []; + message.locals = (object.locals !== undefined && object.locals !== null) + ? Locals.fromPartial(object.locals) + : undefined; + message.queue = (object.queue !== undefined && object.queue !== null) + ? EventsQueue.fromPartial(object.queue) + : undefined; + message.callbacks = (object.callbacks !== undefined && object.callbacks !== null) + ? CallbackMapping.fromPartial(object.callbacks) + : undefined; + message.globals = object.globals?.map((e) => Value.fromPartial(e)) || []; + message.stack = object.stack?.map((e) => Value.fromPartial(e)) || []; + message.table = (object.table !== undefined && object.table !== null) + ? TableState.fromPartial(object.table) + : undefined; + message.memory = (object.memory !== undefined && object.memory !== null) + ? MemoryState.fromPartial(object.memory) + : undefined; + message.branchTable = object.branchTable?.map((e) => e) || []; + message.io = object.io?.map((e) => IOState.fromPartial(e)) || []; + message.overrides = object.overrides?.map((e) => Override.fromPartial(e)) || []; + message.heapUsed = object.heapUsed ?? 0; + return message; + }, +}; + +function createBaseFunctionMessage(): FunctionMessage { + return { functionIndex: 0, range: undefined, locals: undefined, instructions: Buffer.alloc(0) }; +} + +export const FunctionMessage: MessageFns = { + encode(message: FunctionMessage, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.functionIndex !== 0) { + writer.uint32(8).uint32(message.functionIndex); + } + if (message.range !== undefined) { + Range.encode(message.range, writer.uint32(18).fork()).join(); + } + if (message.locals !== undefined) { + Locals.encode(message.locals, writer.uint32(26).fork()).join(); + } + if (message.instructions.length !== 0) { + writer.uint32(34).bytes(message.instructions); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): FunctionMessage { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseFunctionMessage(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.functionIndex = reader.uint32(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.range = Range.decode(reader, reader.uint32()); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.locals = Locals.decode(reader, reader.uint32()); + continue; + } + case 4: { + if (tag !== 34) { + break; + } + + message.instructions = Buffer.from(reader.bytes()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): FunctionMessage { + return FunctionMessage.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): FunctionMessage { + const message = createBaseFunctionMessage(); + message.functionIndex = object.functionIndex ?? 0; + message.range = (object.range !== undefined && object.range !== null) ? Range.fromPartial(object.range) : undefined; + message.locals = (object.locals !== undefined && object.locals !== null) + ? Locals.fromPartial(object.locals) + : undefined; + message.instructions = object.instructions ?? Buffer.alloc(0); + return message; + }, +}; + +function createBaseRemoteFunctionCall(): RemoteFunctionCall { + return { functionIndex: 0, arguments: [] }; +} + +export const RemoteFunctionCall: MessageFns = { + encode(message: RemoteFunctionCall, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.functionIndex !== 0) { + writer.uint32(8).uint32(message.functionIndex); + } + for (const v of message.arguments) { + Value.encode(v!, writer.uint32(18).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): RemoteFunctionCall { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseRemoteFunctionCall(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.functionIndex = reader.uint32(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.arguments.push(Value.decode(reader, reader.uint32())); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): RemoteFunctionCall { + return RemoteFunctionCall.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): RemoteFunctionCall { + const message = createBaseRemoteFunctionCall(); + message.functionIndex = object.functionIndex ?? 0; + message.arguments = object.arguments?.map((e) => Value.fromPartial(e)) || []; + return message; + }, +}; + +function createBaseCallstackEntry(): CallstackEntry { + return { type: 0, functionIndex: 0, stackPointer: 0, framePointer: 0, start: 0, returnAddress: 0 }; +} + +export const CallstackEntry: MessageFns = { + encode(message: CallstackEntry, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.type !== 0) { + writer.uint32(8).uint32(message.type); + } + if (message.functionIndex !== 0) { + writer.uint32(16).uint32(message.functionIndex); + } + if (message.stackPointer !== 0) { + writer.uint32(24).uint32(message.stackPointer); + } + if (message.framePointer !== 0) { + writer.uint32(32).uint32(message.framePointer); + } + if (message.start !== 0) { + writer.uint32(40).uint32(message.start); + } + if (message.returnAddress !== 0) { + writer.uint32(48).uint32(message.returnAddress); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): CallstackEntry { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseCallstackEntry(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.type = reader.uint32(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.functionIndex = reader.uint32(); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.stackPointer = reader.uint32(); + continue; + } + case 4: { + if (tag !== 32) { + break; + } + + message.framePointer = reader.uint32(); + continue; + } + case 5: { + if (tag !== 40) { + break; + } + + message.start = reader.uint32(); + continue; + } + case 6: { + if (tag !== 48) { + break; + } + + message.returnAddress = reader.uint32(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): CallstackEntry { + return CallstackEntry.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): CallstackEntry { + const message = createBaseCallstackEntry(); + message.type = object.type ?? 0; + message.functionIndex = object.functionIndex ?? 0; + message.stackPointer = object.stackPointer ?? 0; + message.framePointer = object.framePointer ?? 0; + message.start = object.start ?? 0; + message.returnAddress = object.returnAddress ?? 0; + return message; + }, +}; + +function createBaseLocals(): Locals { + return { values: [] }; +} + +export const Locals: MessageFns = { + encode(message: Locals, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + for (const v of message.values) { + Value.encode(v!, writer.uint32(10).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Locals { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseLocals(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.values.push(Value.decode(reader, reader.uint32())); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): Locals { + return Locals.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Locals { + const message = createBaseLocals(); + message.values = object.values?.map((e) => Value.fromPartial(e)) || []; + return message; + }, +}; + +function createBaseValue(): Value { + return { i32Bits: undefined, i64Bits: undefined, f32Bits: undefined, f64Bits: undefined, raw: undefined, index: 0 }; +} + +export const Value: MessageFns = { + encode(message: Value, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.i32Bits !== undefined) { + writer.uint32(13).fixed32(message.i32Bits); + } + if (message.i64Bits !== undefined) { + if (BigInt.asUintN(64, message.i64Bits) !== message.i64Bits) { + throw new globalThis.Error("value provided for field message.i64Bits of type fixed64 too large"); + } + writer.uint32(17).fixed64(message.i64Bits); + } + if (message.f32Bits !== undefined) { + writer.uint32(29).fixed32(message.f32Bits); + } + if (message.f64Bits !== undefined) { + if (BigInt.asUintN(64, message.f64Bits) !== message.f64Bits) { + throw new globalThis.Error("value provided for field message.f64Bits of type fixed64 too large"); + } + writer.uint32(33).fixed64(message.f64Bits); + } + if (message.raw !== undefined) { + writer.uint32(42).bytes(message.raw); + } + if (message.index !== 0) { + writer.uint32(48).uint32(message.index); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Value { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseValue(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 13) { + break; + } + + message.i32Bits = reader.fixed32(); + continue; + } + case 2: { + if (tag !== 17) { + break; + } + + message.i64Bits = reader.fixed64() as bigint; + continue; + } + case 3: { + if (tag !== 29) { + break; + } + + message.f32Bits = reader.fixed32(); + continue; + } + case 4: { + if (tag !== 33) { + break; + } + + message.f64Bits = reader.fixed64() as bigint; + continue; + } + case 5: { + if (tag !== 42) { + break; + } + + message.raw = Buffer.from(reader.bytes()); + continue; + } + case 6: { + if (tag !== 48) { + break; + } + + message.index = reader.uint32(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): Value { + return Value.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Value { + const message = createBaseValue(); + message.i32Bits = object.i32Bits ?? undefined; + message.i64Bits = (object.i64Bits !== undefined && object.i64Bits !== null) ? BigInt(object.i64Bits) : undefined; + message.f32Bits = object.f32Bits ?? undefined; + message.f64Bits = (object.f64Bits !== undefined && object.f64Bits !== null) ? BigInt(object.f64Bits) : undefined; + message.raw = object.raw ?? undefined; + message.index = object.index ?? 0; + return message; + }, +}; + +function createBaseCallbackMapping(): CallbackMapping { + return { entries: [] }; +} + +export const CallbackMapping: MessageFns = { + encode(message: CallbackMapping, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + for (const v of message.entries) { + CallbackEntry.encode(v!, writer.uint32(10).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): CallbackMapping { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseCallbackMapping(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.entries.push(CallbackEntry.decode(reader, reader.uint32())); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): CallbackMapping { + return CallbackMapping.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): CallbackMapping { + const message = createBaseCallbackMapping(); + message.entries = object.entries?.map((e) => CallbackEntry.fromPartial(e)) || []; + return message; + }, +}; + +function createBaseCallbackEntry(): CallbackEntry { + return { topic: "", tableIndexes: [] }; +} + +export const CallbackEntry: MessageFns = { + encode(message: CallbackEntry, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.topic !== "") { + writer.uint32(10).string(message.topic); + } + writer.uint32(18).fork(); + for (const v of message.tableIndexes) { + writer.uint32(v); + } + writer.join(); + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): CallbackEntry { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseCallbackEntry(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.topic = reader.string(); + continue; + } + case 2: { + if (tag === 16) { + message.tableIndexes.push(reader.uint32()); + + continue; + } + + if (tag === 18) { + const end2 = reader.uint32() + reader.pos; + while (reader.pos < end2) { + message.tableIndexes.push(reader.uint32()); + } + + continue; + } + + break; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): CallbackEntry { + return CallbackEntry.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): CallbackEntry { + const message = createBaseCallbackEntry(); + message.topic = object.topic ?? ""; + message.tableIndexes = object.tableIndexes?.map((e) => e) || []; + return message; + }, +}; + +function createBaseEventsQueue(): EventsQueue { + return { totalCount: 0, events: [], range: undefined }; +} + +export const EventsQueue: MessageFns = { + encode(message: EventsQueue, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.totalCount !== 0) { + writer.uint32(8).uint32(message.totalCount); + } + for (const v of message.events) { + Event.encode(v!, writer.uint32(18).fork()).join(); + } + if (message.range !== undefined) { + Range.encode(message.range, writer.uint32(26).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): EventsQueue { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseEventsQueue(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.totalCount = reader.uint32(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.events.push(Event.decode(reader, reader.uint32())); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.range = Range.decode(reader, reader.uint32()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): EventsQueue { + return EventsQueue.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): EventsQueue { + const message = createBaseEventsQueue(); + message.totalCount = object.totalCount ?? 0; + message.events = object.events?.map((e) => Event.fromPartial(e)) || []; + message.range = (object.range !== undefined && object.range !== null) ? Range.fromPartial(object.range) : undefined; + return message; + }, +}; + +function createBaseEvent(): Event { + return { topic: "", payload: Buffer.alloc(0) }; +} + +export const Event: MessageFns = { + encode(message: Event, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.topic !== "") { + writer.uint32(10).string(message.topic); + } + if (message.payload.length !== 0) { + writer.uint32(18).bytes(message.payload); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Event { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseEvent(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.topic = reader.string(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.payload = Buffer.from(reader.bytes()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): Event { + return Event.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Event { + const message = createBaseEvent(); + message.topic = object.topic ?? ""; + message.payload = object.payload ?? Buffer.alloc(0); + return message; + }, +}; + +function createBaseRange(): Range { + return { start: 0, end: 0 }; +} + +export const Range: MessageFns = { + encode(message: Range, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.start !== 0) { + writer.uint32(8).uint32(message.start); + } + if (message.end !== 0) { + writer.uint32(16).uint32(message.end); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Range { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseRange(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.start = reader.uint32(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.end = reader.uint32(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): Range { + return Range.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Range { + const message = createBaseRange(); + message.start = object.start ?? 0; + message.end = object.end ?? 0; + return message; + }, +}; + +function createBaseModuleUpdate(): ModuleUpdate { + return { wasm: Buffer.alloc(0) }; +} + +export const ModuleUpdate: MessageFns = { + encode(message: ModuleUpdate, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.wasm.length !== 0) { + writer.uint32(10).bytes(message.wasm); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): ModuleUpdate { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseModuleUpdate(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.wasm = Buffer.from(reader.bytes()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): ModuleUpdate { + return ModuleUpdate.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): ModuleUpdate { + const message = createBaseModuleUpdate(); + message.wasm = object.wasm ?? Buffer.alloc(0); + return message; + }, +}; + +function createBaseIndexedValues(): IndexedValues { + return { values: [] }; +} + +export const IndexedValues: MessageFns = { + encode(message: IndexedValues, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + for (const v of message.values) { + Value.encode(v!, writer.uint32(10).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): IndexedValues { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseIndexedValues(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.values.push(Value.decode(reader, reader.uint32())); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): IndexedValues { + return IndexedValues.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): IndexedValues { + const message = createBaseIndexedValues(); + message.values = object.values?.map((e) => Value.fromPartial(e)) || []; + return message; + }, +}; + +function createBaseSnapshotPolicyConfig(): SnapshotPolicyConfig { + return { policy: 0, interval: 0, minimumReturnCount: 0, selectedState: Buffer.alloc(0) }; +} + +export const SnapshotPolicyConfig: MessageFns = { + encode(message: SnapshotPolicyConfig, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.policy !== 0) { + writer.uint32(8).int32(message.policy); + } + if (message.interval !== 0) { + writer.uint32(16).uint32(message.interval); + } + if (message.minimumReturnCount !== 0) { + writer.uint32(24).uint32(message.minimumReturnCount); + } + if (message.selectedState.length !== 0) { + writer.uint32(34).bytes(message.selectedState); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): SnapshotPolicyConfig { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseSnapshotPolicyConfig(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.policy = reader.int32() as any; + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.interval = reader.uint32(); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.minimumReturnCount = reader.uint32(); + continue; + } + case 4: { + if (tag !== 34) { + break; + } + + message.selectedState = Buffer.from(reader.bytes()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): SnapshotPolicyConfig { + return SnapshotPolicyConfig.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): SnapshotPolicyConfig { + const message = createBaseSnapshotPolicyConfig(); + message.policy = object.policy ?? 0; + message.interval = object.interval ?? 0; + message.minimumReturnCount = object.minimumReturnCount ?? 0; + message.selectedState = object.selectedState ?? Buffer.alloc(0); + return message; + }, +}; + +function createBaseOverride(): Override { + return { primitiveName: "", argumentWords: [], result: 0 }; +} + +export const Override: MessageFns = { + encode(message: Override, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.primitiveName !== "") { + writer.uint32(10).string(message.primitiveName); + } + writer.uint32(18).fork(); + for (const v of message.argumentWords) { + writer.fixed32(v); + } + writer.join(); + if (message.result !== 0) { + writer.uint32(29).fixed32(message.result); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Override { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseOverride(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.primitiveName = reader.string(); + continue; + } + case 2: { + if (tag === 21) { + message.argumentWords.push(reader.fixed32()); + + continue; + } + + if (tag === 18) { + const end2 = reader.uint32() + reader.pos; + while (reader.pos < end2) { + message.argumentWords.push(reader.fixed32()); + } + + continue; + } + + break; + } + case 3: { + if (tag !== 29) { + break; + } + + message.result = reader.fixed32(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): Override { + return Override.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Override { + const message = createBaseOverride(); + message.primitiveName = object.primitiveName ?? ""; + message.argumentWords = object.argumentWords?.map((e) => e) || []; + message.result = object.result ?? 0; + return message; + }, +}; + +function createBaseOperationResult(): OperationResult { + return { command: 0, success: false }; +} + +export const OperationResult: MessageFns = { + encode(message: OperationResult, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.command !== 0) { + writer.uint32(8).int32(message.command); + } + if (message.success !== false) { + writer.uint32(16).bool(message.success); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): OperationResult { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseOperationResult(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.command = reader.int32() as any; + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.success = reader.bool(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): OperationResult { + return OperationResult.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): OperationResult { + const message = createBaseOperationResult(); + message.command = object.command ?? 0; + message.success = object.success ?? false; + return message; + }, +}; + +function createBaseRemoteFunctionResult(): RemoteFunctionResult { + return { success: false, results: [], error: Buffer.alloc(0) }; +} + +export const RemoteFunctionResult: MessageFns = { + encode(message: RemoteFunctionResult, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.success !== false) { + writer.uint32(8).bool(message.success); + } + for (const v of message.results) { + Value.encode(v!, writer.uint32(18).fork()).join(); + } + if (message.error.length !== 0) { + writer.uint32(26).bytes(message.error); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): RemoteFunctionResult { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseRemoteFunctionResult(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.success = reader.bool(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.results.push(Value.decode(reader, reader.uint32())); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.error = Buffer.from(reader.bytes()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): RemoteFunctionResult { + return RemoteFunctionResult.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): RemoteFunctionResult { + const message = createBaseRemoteFunctionResult(); + message.success = object.success ?? false; + message.results = object.results?.map((e) => Value.fromPartial(e)) || []; + message.error = object.error ?? Buffer.alloc(0); + return message; + }, +}; + +function createBaseCheckpoint(): Checkpoint { + return { + instructionCount: 0, + hasPrimitiveCall: false, + primitiveFunctionIndex: 0, + arguments: [], + results: [], + snapshot: undefined, + }; +} + +export const Checkpoint: MessageFns = { + encode(message: Checkpoint, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.instructionCount !== 0) { + writer.uint32(8).uint32(message.instructionCount); + } + if (message.hasPrimitiveCall !== false) { + writer.uint32(16).bool(message.hasPrimitiveCall); + } + if (message.primitiveFunctionIndex !== 0) { + writer.uint32(24).uint32(message.primitiveFunctionIndex); + } + for (const v of message.arguments) { + Value.encode(v!, writer.uint32(34).fork()).join(); + } + for (const v of message.results) { + Value.encode(v!, writer.uint32(42).fork()).join(); + } + if (message.snapshot !== undefined) { + Snapshot.encode(message.snapshot, writer.uint32(50).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Checkpoint { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseCheckpoint(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.instructionCount = reader.uint32(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.hasPrimitiveCall = reader.bool(); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.primitiveFunctionIndex = reader.uint32(); + continue; + } + case 4: { + if (tag !== 34) { + break; + } + + message.arguments.push(Value.decode(reader, reader.uint32())); + continue; + } + case 5: { + if (tag !== 42) { + break; + } + + message.results.push(Value.decode(reader, reader.uint32())); + continue; + } + case 6: { + if (tag !== 50) { + break; + } + + message.snapshot = Snapshot.decode(reader, reader.uint32()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): Checkpoint { + return Checkpoint.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Checkpoint { + const message = createBaseCheckpoint(); + message.instructionCount = object.instructionCount ?? 0; + message.hasPrimitiveCall = object.hasPrimitiveCall ?? false; + message.primitiveFunctionIndex = object.primitiveFunctionIndex ?? 0; + message.arguments = object.arguments?.map((e) => Value.fromPartial(e)) || []; + message.results = object.results?.map((e) => Value.fromPartial(e)) || []; + message.snapshot = (object.snapshot !== undefined && object.snapshot !== null) + ? Snapshot.fromPartial(object.snapshot) + : undefined; + return message; + }, +}; + +function createBaseTableState(): TableState { + return { initial: 0, maximum: 0, entries: [] }; +} + +export const TableState: MessageFns = { + encode(message: TableState, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.initial !== 0) { + writer.uint32(8).uint32(message.initial); + } + if (message.maximum !== 0) { + writer.uint32(16).uint32(message.maximum); + } + writer.uint32(26).fork(); + for (const v of message.entries) { + writer.uint32(v); + } + writer.join(); + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): TableState { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseTableState(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.initial = reader.uint32(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.maximum = reader.uint32(); + continue; + } + case 3: { + if (tag === 24) { + message.entries.push(reader.uint32()); + + continue; + } + + if (tag === 26) { + const end2 = reader.uint32() + reader.pos; + while (reader.pos < end2) { + message.entries.push(reader.uint32()); + } + + continue; + } + + break; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): TableState { + return TableState.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): TableState { + const message = createBaseTableState(); + message.initial = object.initial ?? 0; + message.maximum = object.maximum ?? 0; + message.entries = object.entries?.map((e) => e) || []; + return message; + }, +}; + +function createBaseMemoryState(): MemoryState { + return { initial: 0, maximum: 0, pages: 0, bytes: Buffer.alloc(0) }; +} + +export const MemoryState: MessageFns = { + encode(message: MemoryState, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.initial !== 0) { + writer.uint32(8).uint32(message.initial); + } + if (message.maximum !== 0) { + writer.uint32(16).uint32(message.maximum); + } + if (message.pages !== 0) { + writer.uint32(24).uint32(message.pages); + } + if (message.bytes.length !== 0) { + writer.uint32(34).bytes(message.bytes); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): MemoryState { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseMemoryState(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.initial = reader.uint32(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.maximum = reader.uint32(); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.pages = reader.uint32(); + continue; + } + case 4: { + if (tag !== 34) { + break; + } + + message.bytes = Buffer.from(reader.bytes()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): MemoryState { + return MemoryState.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): MemoryState { + const message = createBaseMemoryState(); + message.initial = object.initial ?? 0; + message.maximum = object.maximum ?? 0; + message.pages = object.pages ?? 0; + message.bytes = object.bytes ?? Buffer.alloc(0); + return message; + }, +}; + +function createBaseIOState(): IOState { + return { key: "", output: false, value: 0 }; +} + +export const IOState: MessageFns = { + encode(message: IOState, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.key !== "") { + writer.uint32(10).string(message.key); + } + if (message.output !== false) { + writer.uint32(16).bool(message.output); + } + if (message.value !== 0) { + writer.uint32(24).sint32(message.value); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): IOState { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0; + if (previousRecursionDepth >= 100) { + throw new globalThis.Error("protobuf decode recursion limit exceeded"); + } + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1; + try { + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseIOState(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.key = reader.string(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.output = reader.bool(); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.value = reader.sint32(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + } finally { + (reader as any).__tsProtoDecodeDepth = previousRecursionDepth; + } + }, + + create, I>>(base?: I): IOState { + return IOState.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): IOState { + const message = createBaseIOState(); + message.key = object.key ?? ""; + message.output = object.output ?? false; + message.value = object.value ?? 0; + return message; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | bigint | undefined; + +export type DeepPartial = T extends bigint ? string | number | bigint + : T extends Builtin ? T : T extends globalThis.Array ? globalThis.Array> + : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin ? P + : P & { [K in keyof P]: Exact } & { [K in Exclude>]: never }; + +export interface MessageFns { + encode(message: T, writer?: BinaryWriter): BinaryWriter; + decode(input: BinaryReader | Uint8Array, length?: number): T; + create, I>>(base?: I): T; + fromPartial, I>>(object: I): T; +} diff --git a/src/testbeds/Emulator.ts b/src/testbeds/Emulator.ts index 24782e3..95eb35b 100644 --- a/src/testbeds/Emulator.ts +++ b/src/testbeds/Emulator.ts @@ -3,7 +3,6 @@ import {SubProcess} from '../bridge/SubProcess'; import {ProxySpecification} from './TestbedSpecification'; import * as net from 'node:net'; import {Socket} from 'node:net'; -import {MessageQueue} from '../messaging/MessageQueue'; import {Meta, TestbedEvents} from './Testbed'; import {EMULATOR} from "../util/env"; import {execFileAsync} from "../util/util"; @@ -49,23 +48,17 @@ export class Emulator extends Platform { export class DummyProxy extends Emulator { dummy: net.Server; - protected forwarding: MessageQueue; - private supervisor?: Socket; constructor(connection: SubProcess, specification: ProxySpecification) { super(connection); - this.forwarding = new MessageQueue('\n'); - this.dummy = new net.Server(); this.dummy.on('connection', (connection) => { this.supervisor = connection; connection.on('error', (error: Error) => this.failPending(error)); - connection.on('data', (data) => { - this.connection.channel.write(data.toString()); - }); + connection.on('data', (data) => this.connection.channel.write(data)); this.emit(TestbedEvents.Ready); }); this.dummy.on('error', (error: Error) => this.failPending(error)); @@ -76,17 +69,9 @@ export class DummyProxy extends Emulator { this.listenForErrors(); this.connection.channel.on('data', (data: Buffer) => { if (this.waitingForMessages()) { - this.messages.push(data.toString()); - this.process(); + this.receive(data); } else { - this.forwarding.push(data.toString()); - while (this.forwarding.hasCompleteMessage()) { - const message = this.forwarding.pop() - if (!message?.includes('Interrupt')) { - this.supervisor!.write(message!.toString()); - } - } - + this.supervisor?.write(data); } }); } diff --git a/src/testbeds/Platform.ts b/src/testbeds/Platform.ts index 2040c1c..28a4e0d 100644 --- a/src/testbeds/Platform.ts +++ b/src/testbeds/Platform.ts @@ -1,19 +1,21 @@ import {Testbed, TestbedEvents} from './Testbed'; import {EventEmitter} from 'events'; import {Request} from '../messaging/Message'; -import {MessageQueue} from '../messaging/MessageQueue'; import {Connection} from '../bridge/Connection'; import {SourceMap} from '../sourcemap/SourceMap'; +import {DebugFrame, DebugFrameDecoder, encodeFrame} from '../protocol/frame'; +import {NotificationType, OperationResult} from '../protocol/vendor/debug'; type PromiseResolver = (value: R | PromiseLike) => void; type PromiseRejector = (reason?: unknown) => void; +type PendingRequest = [Request, PromiseResolver, PromiseRejector]; export abstract class Platform extends EventEmitter implements Testbed { abstract connection: Connection; - protected requests: [Request, PromiseResolver, PromiseRejector][]; + protected requests: PendingRequest[]; - protected messages: MessageQueue; + private readonly decoder: DebugFrameDecoder; // Name of platform public abstract readonly name: string; @@ -24,7 +26,7 @@ export abstract class Platform extends EventEmitter implements Testbed { protected constructor() { super(); this.requests = []; - this.messages = new MessageQueue('\n'); + this.decoder = new DebugFrameDecoder(); } meta(): Promise { @@ -33,13 +35,20 @@ export abstract class Platform extends EventEmitter implements Testbed { // listen on duplex channel protected listen(): void { - this.connection.channel.on('data', (data: Buffer) => { - this.messages.push(data.toString()); - this.process(); - }); + this.connection.channel.on('data', (data: Buffer) => this.receive(data)); this.listenForErrors(); } + protected receive(data: Uint8Array): void { + try { + for (const frame of this.decoder.push(data)) { + this.process(frame); + } + } catch (error) { + this.failPending(error); + } + } + protected listenForErrors(): void { this.connection.channel.on('error', (error: Error) => this.failPending(error)); } @@ -49,47 +58,33 @@ export abstract class Platform extends EventEmitter implements Testbed { this.connection.channel.removeAllListeners('data'); } - // process messages in queue - protected process(): void { - // until no complete messages are left - for (const message of this.messages) { - const index: number = this.search(message); // search request + protected process(frame: DebugFrame): void { + const index = this.requests.findIndex(([request]) => this.matches(request, frame)); + this.emit(TestbedEvents.OnMessage, frame); - if (0 <= index && index < this.requests.length) { - // messaging and resolve - const [candidate, resolver] = this.requests[index]; - resolver(candidate.parser(message)); - this.emit(TestbedEvents.OnMessage, message); + if (index === -1) return; - this.requests.splice(index, 1); // delete resolved request - } + const [request, resolve, reject] = this.requests.splice(index, 1)[0]; + try { + resolve(request.parser(frame.payload)); + } catch (error) { + reject(error); } } - protected failPending(error: Error): void { + private matches(request: Request, frame: DebugFrame): boolean { + if (request.notification !== frame.type) return false; + if (frame.type !== NotificationType.NOTIFICATION_OPERATION_RESULT) return true; + return OperationResult.decode(frame.payload).command === request.type; + } + + protected failPending(error: unknown): void { const pending = this.requests.splice(0); for (const [, , reject] of pending) { reject(error); } } - // search for oldest request matching message - private search(message: string): number { - let index: number = 0; - while (index < this.requests.length) { - const [candidate] = this.requests[index]; - try { - // try candidate parser - candidate.parser(message); - return index; - } catch { - // failure: try next request - index++; - } - } - return -1; - } - // kill connection public kill(): Promise { this.connection.channel.destroy(); @@ -98,18 +93,21 @@ export abstract class Platform extends EventEmitter implements Testbed { // send request over duplex channel public sendRequest(map: SourceMap.Mapping, request: Request): Promise { - const message = `${request.type}${request.payload?.(map) ?? ''}\n`; - this.emit(TestbedEvents.Send, message); + const frame = encodeFrame({ + type: request.type, + payload: request.payload?.(map) ?? new Uint8Array() + }); + this.emit(TestbedEvents.Send, frame); return new Promise((resolve, reject) => { const resolver: PromiseResolver = value => resolve(value as R); - const pending: [Request, PromiseResolver, PromiseRejector] = [request as Request, resolver, reject]; + const pending: PendingRequest = [request as Request, resolver, reject]; this.requests.push(pending); - this.connection.channel.write(message, (err: Error | null | undefined) => { - if (err !== null && err !== undefined) { + this.connection.channel.write(frame, (error: Error | null | undefined) => { + if (error !== null && error !== undefined) { const index = this.requests.indexOf(pending); if (index !== -1) { this.requests.splice(index, 1); - reject(err); + reject(error); } } }); diff --git a/src/testbeds/Testbed.ts b/src/testbeds/Testbed.ts index 7ad5674..b49a8fb 100644 --- a/src/testbeds/Testbed.ts +++ b/src/testbeds/Testbed.ts @@ -3,6 +3,7 @@ import {EventEmitter} from 'events'; import {Connection} from '../bridge/Connection'; import {SourceMap} from '../sourcemap/SourceMap'; import {Breakpoint} from '../debug/Breakpoint'; +import {DebugFrame} from '../protocol/frame'; export enum TestbedEvents { OnMessage = 'message', @@ -27,7 +28,7 @@ export declare interface Testbed extends EventEmitter { kill(): Promise; - on(event: TestbedEvents.OnMessage, listener: (message: string) => void): this; + on(event: TestbedEvents.OnMessage, listener: (message: DebugFrame) => void): this; on(event: TestbedEvents.OnBreakpointHit, listener: (message: Breakpoint) => void): this; @@ -35,7 +36,7 @@ export declare interface Testbed extends EventEmitter { on(event: TestbedEvents.Ready, listener: (data: string) => void): this; - on(event: TestbedEvents.Send, listener: (data: string) => void): this; + on(event: TestbedEvents.Send, listener: (data: Buffer) => void): this; meta(): Promise; } diff --git a/src/testbeds/TestbedFactory.ts b/src/testbeds/TestbedFactory.ts index fa0c31b..3605f4d 100644 --- a/src/testbeds/TestbedFactory.ts +++ b/src/testbeds/TestbedFactory.ts @@ -42,7 +42,7 @@ export class TestbedFactory { default: return Promise.reject('Platform not implemented.'); } - testbed.on(TestbedEvents.Send, (message: string) => this.listener?.(Buffer.from(message))); + testbed.on(TestbedEvents.Send, (message: Buffer) => this.listener?.(message)); return testbed; } } diff --git a/tests/unit/interface.test.ts b/tests/unit/interface.test.ts index e590df7..551162d 100644 --- a/tests/unit/interface.test.ts +++ b/tests/unit/interface.test.ts @@ -1,21 +1,67 @@ import test from 'ava'; -import {PassThrough} from 'node:stream'; +import {Duplex} from 'node:stream'; import {SubProcess} from '../../src/bridge/SubProcess'; import {Message} from '../../src/messaging/Message'; import {SourceMap} from '../../src/sourcemap/SourceMap'; import {Platform} from '../../src/testbeds/Platform'; +import {DebugFrameDecoder, encodeFrame} from '../../src/protocol/frame'; +import { + Command, + Inspect, + NotificationType, + OperationResult +} from '../../src/protocol/vendor/debug'; +import {WARDuino} from "../../src/debug/WARDuino"; +import {Testee} from "../../src/framework/Testee"; +import {EmulatorSpecification} from "../../src/testbeds/TestbedSpecification"; + +class TestChannel extends Duplex { + readonly sent: Buffer[] = []; + + _read(): void { + // Test data is supplied explicitly through receive(). + } + + _write(chunk: Buffer, _encoding: BufferEncoding, callback: (error?: Error | null) => void): void { + this.sent.push(Buffer.from(chunk)); + callback(); + } + + receive(data: Uint8Array): void { + this.push(Buffer.from(data)); + } +} class TestPlatform extends Platform { readonly name = 'test'; connection: SubProcess; - constructor(channel: PassThrough) { + constructor(channel: TestChannel) { super(); this.connection = new SubProcess(channel); this.listen(); } } +const tick = () => new Promise(resolve => setImmediate(resolve)); + +test("[testee] successful void requests are not retried", async t => { + const testee = new Testee("test", new EmulatorSpecification(0), 0, 0); + const recoverable = (testee as unknown as {recoverable: (...args: any[]) => Promise}).recoverable; + let attempts = 0; + let recoveries = 0; + + const result = await recoverable( + testee, Message.run, new SourceMap.Mapping(), + async () => { attempts++; }, + async () => { recoveries++; }, 1 + ); + + t.is(result, undefined); + t.is(attempts, 1); + t.is(recoveries, 0); +}); + // file is currently excluded from tests test('[warduino] start emulator', t => { @@ -23,7 +69,7 @@ test('[warduino] start emulator', t => { }); test('[platform] rejects outstanding requests when the connection closes with an error', async t => { - const channel = new PassThrough(); + const channel = new TestChannel(); const platform = new TestPlatform(channel); const expected = new Error('read ECONNRESET'); const request = platform.sendRequest(new SourceMap.Mapping(), Message.run); @@ -33,6 +79,72 @@ test('[platform] rejects outstanding requests when the connection closes with an t.is(await t.throwsAsync(request), expected); }); +test('[platform] sends a framed protobuf command payload', async t => { + const channel = new TestChannel(); + const platform = new TestPlatform(channel); + const request = platform.sendRequest(new SourceMap.Mapping(), Message.inspect([ + WARDuino.Inspect.counter, + WARDuino.Inspect.io + ])); + const [frame] = new DebugFrameDecoder().push(channel.sent[0]); + + t.is(frame.type, Command.COMMAND_INSPECT); + t.deepEqual(Inspect.decode(frame.payload).state, Buffer.from([0x01, 0x0b])); + + const expected = new Error('test complete'); + channel.emit('error', expected); + t.is(await t.throwsAsync(request), expected); +}); + +test('[platform] resolves only the expected notification type', async t => { + const channel = new TestChannel(); + const platform = new TestPlatform(channel); + let resolved = false; + const request = platform.sendRequest(new SourceMap.Mapping(), Message.run).then(() => { + resolved = true; + }); + + channel.receive(encodeFrame({ + type: NotificationType.NOTIFICATION_PAUSED, + payload: new Uint8Array() + })); + await tick(); + t.false(resolved); + + channel.receive(encodeFrame({ + type: NotificationType.NOTIFICATION_CONTINUED, + payload: new Uint8Array() + })); + await request; + t.true(resolved); +}); + +test('[platform] matches operation results to their original command', async t => { + const channel = new TestChannel(); + const platform = new TestPlatform(channel); + let resolved = false; + const request = platform.sendRequest(new SourceMap.Mapping(), Message.reset).then(result => { + resolved = true; + return result; + }); + + channel.receive(encodeFrame({ + type: NotificationType.NOTIFICATION_OPERATION_RESULT, + payload: OperationResult.encode({command: Command.COMMAND_UPDATE_LOCAL, success: true}).finish() + })); + await tick(); + t.false(resolved); + + channel.receive(encodeFrame({ + type: NotificationType.NOTIFICATION_OPERATION_RESULT, + payload: OperationResult.encode({command: Command.COMMAND_RESET, success: true}).finish() + })); + const result = await request; + t.true(resolved); + t.is(result.command, Command.COMMAND_RESET); + t.true(result.success); +}); + test('[warduino] start oop testbed', t => { t.pass(); }); diff --git a/tests/unit/messaging.test.ts b/tests/unit/messaging.test.ts index 6134f39..b12d9d6 100644 --- a/tests/unit/messaging.test.ts +++ b/tests/unit/messaging.test.ts @@ -3,6 +3,7 @@ import {MessageQueue} from '../../src/messaging/MessageQueue'; import {Message} from '../../src/messaging/Message'; import {WASM} from '../../src/sourcemap/Wasm'; import {SourceMap} from '../../src/sourcemap/SourceMap'; +import {RemoteFunctionCall, RemoteFunctionResult} from "../../src/protocol/vendor/debug"; const alphanumerical = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'.split(''); @@ -18,10 +19,30 @@ test('[Message.invoke] : encode 64-bit integer arguments without precision loss' WASM.i64(-1n) ]); - t.is(request.payload!(mapping), '008080808080808080807f7f'); + const payload = RemoteFunctionCall.decode(request.payload!(mapping)); + t.is(payload.functionIndex, 0); + t.deepEqual(payload.arguments.map(({i64Bits, index}) => ({i64Bits, index})), [ + {i64Bits: 0x8000000000000000n, index: 0}, + {i64Bits: 0xffffffffffffffffn, index: 1} + ]); +}); + +test("[Message.invoke] : decodes an i64 reply to the legacy value contract", t => { + const request = Message.invoke("unused", []); + const result = request.parser(RemoteFunctionResult.encode({ + success: true, + results: [{i64Bits: 0n, index: 0}], + error: Buffer.alloc(0) + }).finish()); + + t.false("text" in result); + if ("text" in result) return; + t.is(result.type, WASM.Integer.i64); + t.is((result.value as WASM.WasmInt).toBigInt(), 0n); + t.false("success" in result); }); -test('[MessageQueue] : test EOM detection', t => { +test("[MessageQueue] : test EOM detection", t => { const fuzzer = fuzzy(alphanumerical); const newline = new MessageQueue('\n'); newline.push(fuzzer(9)); @@ -94,3 +115,20 @@ const fuzzy = (characters: string[]): (n: number) => string => { return result; }; } +import {Command, ContinueFor} from "../../src/protocol/vendor/debug"; +import {DebugFrameDecoder, encodeFrame} from "../../src/protocol/frame"; + +test("[debug protocol] encodes WARDuino CONTINUE_FOR fixture", t => { + const payload = ContinueFor.encode({count: 5}).finish(); + const frame = encodeFrame({type: Command.COMMAND_CONTINUE_FOR, payload}); + t.deepEqual(frame, Buffer.from([0x16, 0x02, 0x08, 0x05])); +}); + +test("[debug protocol] decodes fragmented WARDuino frames", t => { + const decoder = new DebugFrameDecoder(); + t.deepEqual(decoder.push(Buffer.from([0x16, 0x02])), []); + t.deepEqual(decoder.push(Buffer.from([0x08, 0x05])), [{ + type: Command.COMMAND_CONTINUE_FOR, + payload: Buffer.from([0x08, 0x05]) + }]); +}); diff --git a/tests/unit/parsing.test.ts b/tests/unit/parsing.test.ts index 3b2e6fd..9c63948 100644 --- a/tests/unit/parsing.test.ts +++ b/tests/unit/parsing.test.ts @@ -1,6 +1,7 @@ import test from 'ava'; -import {invokeParser, signed} from "../../src/messaging/Parsers"; -import {Exception, Expected, Kind, Step, WARDuino, WASM} from "../../src"; +import {invokeParser, remoteFunctionResultParser, signed} from "../../src/messaging/Parsers"; +import {Exception, Expected, Kind, Message, Step, WASM} from "../../src"; +import {RemoteFunctionResult} from "../../src/protocol/vendor/debug"; import {Verifier} from "../../src/framework/Verifier"; import Type = WASM.Type; import WasmInt = WASM.WasmInt; @@ -112,12 +113,44 @@ test('[invoke parser] : f32 decimal bit pattern with alpha hex equivalent', t => t.is(result.value, -8.881785255792436e-16); }); -test('[verifier] : numeric mismatch is reported as failure, not missing field', t => { +test("[protobuf invoke result] : decodes signed i64 boundaries", t => { + for (const [bits, expected] of [[0n, 0n], [0xffffffffffffffffn, -1n], [0x8000000000000000n, -9223372036854775808n]]) { + const result = remoteFunctionResultParser(RemoteFunctionResult.encode({success: true, results: [{i64Bits: bits, index: 0}], error: Buffer.alloc(0)}).finish()); + t.false("text" in result); + if ("text" in result) return; + t.is(result.type, WASM.Integer.i64); + t.is((result.value as WasmInt).toBigInt(), expected); + } +}); + +test("[protobuf invoke result] : decodes IEEE-754 float bits", t => { + const f32 = remoteFunctionResultParser(RemoteFunctionResult.encode({success: true, results: [{f32Bits: 0x3fc00000, index: 0}], error: Buffer.alloc(0)}).finish()); + const f64 = remoteFunctionResultParser(RemoteFunctionResult.encode({success: true, results: [{f64Bits: 0x4004000000000000n, index: 0}], error: Buffer.alloc(0)}).finish()); + + t.false("text" in f32); + t.false("text" in f64); + if ("text" in f32 || "text" in f64) return; + t.deepEqual(f32, {type: WASM.Float.f32, value: 1.5}); + t.deepEqual(f64, {type: WASM.Float.f64, value: 2.5}); +}); + +test("[protobuf invoke result] : maps void, malformed, and failed responses", t => { + const voidResult = remoteFunctionResultParser(RemoteFunctionResult.encode({success: true, results: [], error: Buffer.alloc(0)}).finish()); + t.deepEqual(voidResult, WASM.nothing); + + const failure = remoteFunctionResultParser(RemoteFunctionResult.encode({success: false, results: [], error: Buffer.from("division by zero")}).finish()); + t.deepEqual(failure, {text: "division by zero"}); + + const malformed = RemoteFunctionResult.encode({success: true, results: [{index: 0}], error: Buffer.alloc(0)}).finish(); + t.throws(() => remoteFunctionResultParser(malformed), {message: /no supported WASM value/}); +}); + +test("[verifier] : numeric mismatch is reported as failure, not missing field", t => { const step: Step = { title: 'CHECK: numeric mismatch', instruction: { kind: Kind.Request, - value: {type: WARDuino.Interrupt.invoke, payload: () => '', parser: invokeParser} + value: Message.invoke('unused', []) }, expected: [{'value': {kind: 'primitive', value: -8.881785255792436e-16} as Expected}] };