Skip to main content

aitp_session_bundle/
wire.rs

1//! Wire-form parsing for the Session Trust Bundle transport envelope
2//! (RFC-AITP-0010 §3).
3
4use crate::error::SessionBundleError;
5use crate::types::{SessionBundleEnvelope, SessionTrustBundle};
6use serde_json::Value;
7
8/// Parse a session bundle from its on-the-wire JSON.
9///
10/// Accepts the spec transport envelope `{"session_bundle": {<body incl.
11/// `signature`>}}` and, for internal callers that never wrap, a bare
12/// body. The wrapper is `additionalProperties: false`: any member
13/// besides `session_bundle` — notably the pre-erratum sibling
14/// `signature` — is rejected with
15/// [`SessionBundleError::WireFormInvalid`], which the error-code
16/// mapping surfaces as `SESSION_BUNDLE_INVALID`.
17pub fn parse_session_bundle_wire(value: &Value) -> Result<SessionTrustBundle, SessionBundleError> {
18    if value.get("session_bundle").is_some() {
19        let env: SessionBundleEnvelope = serde_json::from_value(value.clone()).map_err(|e| {
20            SessionBundleError::WireFormInvalid(format!(
21                "transport envelope: {e} (RFC-AITP-0010 §3 places `signature` \
22                 inside the signed body, never beside the wrapper)"
23            ))
24        })?;
25        Ok(env.session_bundle)
26    } else {
27        serde_json::from_value(value.clone())
28            .map_err(|e| SessionBundleError::WireFormInvalid(format!("bundle body: {e}")))
29    }
30}
31
32#[cfg(test)]
33mod tests {
34    use super::*;
35    use serde_json::json;
36
37    fn valid_body() -> Value {
38        json!({
39            "version": "aitp/0.2",
40            "session_id": "00000000-0000-4000-8000-000000000000",
41            "coordinator": "aid:pubkey:O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik",
42            "issued_at": 1_700_000_000,
43            "expires_at": 1_700_010_000,
44            "participants": [],
45            "signature": "A".repeat(86),
46        })
47    }
48
49    #[test]
50    fn wrapped_with_signature_inside_parses() {
51        let wire = json!({ "session_bundle": valid_body() });
52        assert!(parse_session_bundle_wire(&wire).is_ok());
53    }
54
55    #[test]
56    fn bare_body_parses() {
57        assert!(parse_session_bundle_wire(&valid_body()).is_ok());
58    }
59
60    #[test]
61    fn sibling_signature_is_rejected() {
62        let mut body = valid_body();
63        let signature = body.as_object_mut().unwrap().remove("signature").unwrap();
64        let wire = json!({ "session_bundle": body, "signature": signature });
65        assert!(matches!(
66            parse_session_bundle_wire(&wire),
67            Err(SessionBundleError::WireFormInvalid(_))
68        ));
69    }
70
71    #[test]
72    fn unexpected_sibling_key_is_rejected() {
73        let wire = json!({ "session_bundle": valid_body(), "extra": "nope" });
74        assert!(matches!(
75            parse_session_bundle_wire(&wire),
76            Err(SessionBundleError::WireFormInvalid(_))
77        ));
78    }
79
80    #[test]
81    fn bare_body_missing_signature_is_rejected() {
82        let mut body = valid_body();
83        body.as_object_mut().unwrap().remove("signature");
84        assert!(matches!(
85            parse_session_bundle_wire(&body),
86            Err(SessionBundleError::WireFormInvalid(_))
87        ));
88    }
89}