A firmware family's wire protocol, expressed as pure functions and data.
Every member is pure: no I/O, no state, no time or randomness. Request
builders return a Request that the orchestrator hands to fetch;
parsers take the response text or headers. Because of that, every claim about
a firmware's protocol is a string-in/string-out unit test needing no device.
Authoring a dialect
Dialects are authored by spreading an existing dialect and overriding only
what differs. The interface deliberately has no optional members: optionality
would force dialect.x ?? fallback at every call site, which is the pile of
conditionals this design exists to avoid. Spread composition supplies the
defaults instead.
Adding a member to this interface later stays source-compatible for spread-authored dialects, and is breaking only for dialects implemented from scratch — so spreading is the sanctioned style.
Author a dialect by spreading an existing one
Author a dialect by spreading an existing one
import { assertEquals } from "@std/assert"; import type { Dialect } from "./dialect.ts"; import { gdprJson } from "./gdprJson.ts"; const vx800v: Dialect = { ...gdprJson, id: "vx800v", commandRequest: (baseUrl, envelope, session) => new Request(new URL("cgi_gdpr", baseUrl), { method: "POST", headers: { TokenID: session.tokenId }, body: `sign=${envelope.sign}\r\ndata=${envelope.data}\r\n`, }), }; assertEquals(vx800v.id, "vx800v"); assertEquals(vx800v.defaultUsername, gdprJson.defaultUsername); const request = vx800v.commandRequest( "http://192.168.1.1", { data: "ZGF0YQ==", sign: "00ff" }, { sessionId: "sid", tokenId: "tok", authTimes: 1 }, ); assertEquals(request.url, "http://192.168.1.1/cgi_gdpr"); assertEquals(request.headers.get("TokenID"), "tok");
id: string
Stable identifier, carried on the authentication result for diagnostics.
defaultUsername: string
Username used when the caller supplies none.
infoRequest(baseUrl: string): Request
Builds the request for the login page, whose inline script carries
authTimes and friends.
parseInfo(html: string): RouterInfo
Scrapes router variables out of the login page.
publicKeyRequest(baseUrl: string): Request
Builds the request for the RSA public key and sequence base.
parsePublicKey(text: string): PublicKeyInfo
Extracts RSA parameters from the public key response.
busyRequest(baseUrl: string): Request
Builds the request that reports whether a session is already established.
parseBusy(text: string): BusyStatus
Extracts login and busy flags from the busy response.
encodeLogin(credentials: Credentials): string
Encodes credentials into the plaintext login payload, pre-encryption.
loginRequest(baseUrl: string,envelope: Envelope): Request
Builds the login request. Owns where the envelope rides — query string, request body, or anywhere else the firmware expects it.
parseSessionId(headers: Headers): string | null
Extracts the session id from the login response headers.
tokenRequest(baseUrl: string,session: Pick<SessionContext, "sessionId" | "authTimes">): Request
Builds the request for the authenticated page carrying the security token.
parseTokenId(html: string): string
Extracts the security token from the authenticated page.
encodeCommands(actions: readonly Action[]): readonly CommandBatch[]
Splits actions into round trips and serializes each. Owns the operation vocabulary, stack defaults, and batching.
Builds a command request for one batch's envelope.
decodeCommand(text: string,batch: CommandBatch): DecodedBatch
Decodes a decrypted command response into results aligned with the batch.