HH-415: restore binary payload byte semantics
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
+1
-1
@@ -19,7 +19,7 @@ The spike starts only loopback listeners and uses generated, one-day self-signed
|
||||
|
||||
Clark is launched directly with `--remote-debugging-address=127.0.0.1`. The runner waits on `/json/version`, discovers the startup page through `/json/list`, connects to its `webSocketDebuggerUrl`, and navigates to the already-listening loopback fixture before triggering test traffic. This avoids `clarkserve`'s current `0.0.0.0` default; a protected gateway and short-lived credentials remain required outside a local Spike.
|
||||
|
||||
Every JSONL line has `schema_version`, `session_id`, `target_id`, and `kind`. HTTP records use `request_id`; WebSocket records use the CDP `requestId` as `connection_id`. Kind-specific fields are nested under `request`, `response`, `headers`, `body`, or `error`. Query strings are removed, sensitive headers are replaced by `[REDACTED]`, and WebSocket payloads are never persisted. In schema `1.0`, `websocket_frame.payload_bytes` keeps its original meaning: the UTF-8 byte length of CDP `payloadData` (base64 text for opcode 2). Binary frames additionally expose `decoded_payload_bytes` for the decoded payload size, which is also used by the frame-size limit.
|
||||
Every JSONL line has `schema_version`, `session_id`, `target_id`, and `kind`. HTTP records use `request_id`; WebSocket records use the CDP `requestId` as `connection_id`. Kind-specific fields are nested under `request`, `response`, `headers`, `body`, or `error`. Query strings are removed, sensitive headers are replaced by `[REDACTED]`, and WebSocket payloads are never persisted. In schema `1.0`, `websocket_frame.payload_bytes` is the payload's original byte count (base64-decoded for opcode 2) and drives the frame-size limit. Binary frames additionally expose `encoded_payload_bytes` for the UTF-8 byte length of CDP's base64 `payloadData` text.
|
||||
|
||||
Response bodies are safe by default and never contain content. Their stable metadata is `{state,source,encoding,storage,reason,bytes}`: `source` is `network`, `memory_cache`, `disk_cache`, `prefetch_cache`, or `service_worker`; `encoding` is `utf8`, `base64`, or `unknown`; and `storage` remains `omitted`. Oversize and CDP-unavailable bodies keep `size_limit` and `unavailable` states. The local fixture fetches a cacheable response twice and an `application/octet-stream` response to assert cached and binary semantics.
|
||||
|
||||
|
||||
@@ -601,10 +601,10 @@ export function eventCollector(sessionId, targetId, {
|
||||
case "Network.webSocketFrameReceived": {
|
||||
const direction = method.endsWith("Sent") ? "sent" : "received";
|
||||
mark(`ws_${direction}`);
|
||||
const payloadBytes = Buffer.byteLength(params.response.payloadData);
|
||||
const decodedPayloadBytes = params.response.opcode === 2
|
||||
const encodedPayloadBytes = Buffer.byteLength(params.response.payloadData);
|
||||
const payloadBytes = params.response.opcode === 2
|
||||
? Buffer.from(params.response.payloadData, "base64").length
|
||||
: payloadBytes;
|
||||
: encodedPayloadBytes;
|
||||
let state = webSocketLimits.get(requestId);
|
||||
if (!state) {
|
||||
state = { window: null, windowEvents: 0, emitted: 0 };
|
||||
@@ -617,11 +617,11 @@ export function eventCollector(sessionId, targetId, {
|
||||
}
|
||||
state.windowEvents++;
|
||||
let dropReason;
|
||||
if (decodedPayloadBytes > maxWebSocketFrameBytes) dropReason = "frame_size_limit";
|
||||
if (payloadBytes > maxWebSocketFrameBytes) dropReason = "frame_size_limit";
|
||||
else if (state.windowEvents > maxWebSocketEventsPerSecond) dropReason = "rate_limit";
|
||||
else if (state.emitted >= maxWebSocketEventsPerConnection) dropReason = "event_limit";
|
||||
if (dropReason) {
|
||||
dropWebSocketFrame(requestId, dropReason, decodedPayloadBytes);
|
||||
dropWebSocketFrame(requestId, dropReason, payloadBytes);
|
||||
break;
|
||||
}
|
||||
state.emitted++;
|
||||
@@ -630,7 +630,7 @@ export function eventCollector(sessionId, targetId, {
|
||||
direction,
|
||||
opcode: params.response.opcode,
|
||||
payload_bytes: payloadBytes,
|
||||
...(params.response.opcode === 2 && { decoded_payload_bytes: decodedPayloadBytes }),
|
||||
...(params.response.opcode === 2 && { encoded_payload_bytes: encodedPayloadBytes }),
|
||||
payload: { state: "omitted", reason: "frame_policy" },
|
||||
});
|
||||
break;
|
||||
|
||||
@@ -76,8 +76,8 @@ test("WebSocket limits aggregate drops without payloads", () => {
|
||||
connection_id: "ws-binary",
|
||||
direction: "received",
|
||||
opcode: 2,
|
||||
payload_bytes: 4,
|
||||
decoded_payload_bytes: 2,
|
||||
payload_bytes: 2,
|
||||
encoded_payload_bytes: 4,
|
||||
payload: { state: "omitted", reason: "frame_policy" },
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user