{"record":{"id":"a99bd8ad5a89255f","repo":"unicity-aos/aos-ce","slug":"received-malformed-ipc-payload-from-socket","errorCode":null,"errorMessage":"Received malformed IPC payload from socket","messagePattern":"Received malformed IPC payload from socket","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"capsules/capsule-cli/src/lib.rs","lineNumber":553,"sourceCode":"/// Parse an incoming client message, apply the per-connection binding state\n/// machine ([`decide_ingress`]), and forward it to the IPC bus if the binding\n/// allows it and the topic passes the ingress allowlist.\n///\n/// `current_binding` is the connection's principal so far (`None` until the\n/// first usable message binds it). Returns an [`IngressOutcome`] carrying the\n/// newly-bound principal (only on the binding message) and the conversation\n/// session observed on this message, both of which the caller folds onto the\n/// connection. A dropped/malformed message yields an empty outcome.\nfn handle_ingress(bytes: &[u8], current_binding: Option<&str>) -> IngressOutcome {\n    let empty = IngressOutcome {\n        newly_bound: None,\n        session_id: None,\n    };\n\n    let msg = match serde_json::from_slice::<serde_json::Value>(bytes) {\n        Ok(v) => v,\n        Err(_) => {\n            log::warn(\"Received malformed IPC payload from socket\");\n            return empty;\n        }\n    };\n\n    let message_principal = msg.get(\"principal\").and_then(|p| p.as_str());\n\n    // Resolve the binding decision first — a conflicting or malformed\n    // principal is dropped before any forward, and never mutates the binding.\n    let (forward_as, newly_bound) = match decide_ingress(current_binding, message_principal) {\n        IngressDecision::Bind(p) => (p.clone(), Some(p)),\n        IngressDecision::ForwardAs(p) => (p, None),\n        IngressDecision::Drop { reason } => {\n            match reason {\n                DropReason::InvalidPrincipal(p) => log::warn(format!(\n                    \"Dropped ingress message: malformed principal {p:?}; connection stays unbound\"\n                )),\n                DropReason::PrincipalConflict { bound, claimed } => log::warn(format!(\n                    \"Dropped ingress message: connection bound to {bound:?} but message claimed {claimed:?}\"","sourceCodeStart":535,"sourceCodeEnd":571,"githubUrl":"https://github.com/unicity-aos/aos-ce/blob/f6f22024fb1e8d122f28a1b4a9f75aee448ae839/capsules/capsule-cli/src/lib.rs#L535-L571","documentation":"This warning is logged in `handle_ingress` (capsule-cli) when an incoming IPC payload on the socket is not valid JSON — serde_json::from_slice fails. The capsule ignores the message and returns an empty response rather than panicking, so the sender gets no indication beyond this log line. It indicates a protocol mismatch between the IPC client and the capsule.","triggerScenarios":"A client writes raw bytes, partial JSON, binary data, or a non-JSON protocol frame (e.g. length-prefixed body with header bytes included) to the capsule's IPC socket, so serde_json cannot parse the slice.","commonSituations":"Hand-testing the socket with curl/netcat sending non-JSON; client framing mismatch (extra length prefix or NUL terminator); truncated write from a crashed client; sending protobuf/MessagePack instead of JSON; encoding issues.","solutions":["Log/dump the offending bytes at the client and confirm the payload is complete, valid JSON (test with `jq`).","Ensure the client sends exactly one JSON value per message with the framing the capsule expects — no length prefixes, NUL terminators, or header bytes.","Fix truncation: verify the client flushes/closes the write side so the full payload is delivered before the capsule parses.","Confirm client and capsule agree on JSON encoding (UTF-8, serde_json::Value object with fields like `principal`)."],"exampleFix":"// before: sending extra framing\nsocket.write_all(format!(\"{}\\n\", len).as_bytes())?;\nsocket.write_all(&payload)?;\n// after: send the JSON document only\nsocket.write_all(&serde_json::to_vec(&msg)?)?;","handlingStrategy":"try-catch","validationCode":"// Client-side: validate before writing to the IPC socket\nlet payload = serde_json::to_vec(&msg).expect(\"serializable\");\nassert!(serde_json::from_slice::<serde_json::Value>(&payload).is_ok(), \"payload must be one valid JSON value\");\nsocket.write_all(&payload)?;","typeGuard":null,"tryCatchPattern":"let msg = match serde_json::from_slice::<serde_json::Value>(bytes) {\n    Ok(v) => v,\n    Err(e) => {\n        log::warn!(\"malformed IPC payload: {e} ({} bytes)\", bytes.len());\n        return empty;\n    }\n};","preventionTips":["Send exactly one UTF-8 JSON value per IPC message; never prepend length prefixes or NUL terminators unless the protocol defines them.","Test the socket path with a reference client (jq-valid payload) before integrating custom code.","Flush and close the write side so the capsule never parses a truncated body.","Log a hex dump of rejected payloads at debug level to diagnose framing mismatches quickly."],"tags":["ipc","json","protocol","serialization"],"backgroundTag":"json-parse-error","analyzedSha":"f6f22024fb1e8d122f28a1b4a9f75aee448ae839","analyzedAt":"2026-09-13T03:04:44.565Z","contentChangedAt":"2026-09-13T03:04:44.565Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}