use crate::error::SessionBundleError;
use crate::types::{SessionBundleEnvelope, SessionTrustBundle, SESSION_BUNDLE_MEMBERS};
use aitp_core::{check_members, from_serde_error, CheckedJson};
use serde_json::Value;
const SESSION_BUNDLE_ENVELOPE_MEMBERS: &[&str] = &["session_bundle"];
pub fn parse_session_bundle_wire(
value: &CheckedJson,
) -> Result<SessionTrustBundle, SessionBundleError> {
let value: &Value = value.as_value();
if value.get("session_bundle").is_some() {
check_members(
"SessionBundleEnvelope",
value,
SESSION_BUNDLE_ENVELOPE_MEMBERS,
)
.map_err(|e| {
SessionBundleError::WireFormInvalid(format!(
"transport envelope: unknown field `{}` (RFC-AITP-0010 §3 places \
`signature` inside the signed body, never beside the wrapper)",
e.field
))
})?;
let body = value.get("session_bundle").expect("checked above");
check_members("SessionTrustBundle", body, SESSION_BUNDLE_MEMBERS)
.map_err(|e| SessionBundleError::UnknownField(e.field))?;
let env: SessionBundleEnvelope = serde_json::from_value(value.clone()).map_err(|e| {
if let Some(field) = from_serde_error(&e) {
SessionBundleError::UnknownField(field)
} else {
SessionBundleError::WireFormInvalid(format!("transport envelope: {e}"))
}
})?;
Ok(env.session_bundle)
} else {
check_members("SessionTrustBundle", value, SESSION_BUNDLE_MEMBERS)
.map_err(|e| SessionBundleError::UnknownField(e.field))?;
serde_json::from_value(value.clone()).map_err(|e| {
if let Some(field) = from_serde_error(&e) {
SessionBundleError::UnknownField(field)
} else {
SessionBundleError::WireFormInvalid(format!("bundle body: {e}"))
}
})
}
}
pub fn parse_session_bundle_envelope(
value: &CheckedJson,
) -> Result<SessionTrustBundle, SessionBundleError> {
if value.as_value().get("session_bundle").is_none() {
return Err(SessionBundleError::WireFormInvalid(
"missing `session_bundle` transport wrapper (RFC-AITP-0010 §4.3.1 \
rejects a bare body)"
.into(),
));
}
parse_session_bundle_wire(value)
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn checked(v: &serde_json::Value) -> aitp_core::CheckedJson {
v.to_string().parse().expect("valid JSON")
}
#[test]
fn envelope_parser_rejects_bare_body() {
match parse_session_bundle_envelope(&checked(&valid_body())) {
Err(SessionBundleError::WireFormInvalid(msg)) => {
assert!(msg.contains("session_bundle"), "got {msg}")
}
other => panic!("expected WireFormInvalid, got {other:?}"),
}
}
#[test]
fn envelope_parser_accepts_wrapped_body() {
let wire = json!({ "session_bundle": valid_body() });
assert!(parse_session_bundle_envelope(&checked(&wire)).is_ok());
}
fn valid_body() -> Value {
json!({
"version": "aitp/0.3",
"session_id": "00000000-0000-4000-8000-000000000000",
"coordinator": "aid:pubkey:O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik",
"issued_at": 1_700_000_000,
"expires_at": 1_700_010_000,
"participants": [],
"signature": "A".repeat(86),
})
}
#[test]
fn wrapped_with_signature_inside_parses() {
let wire = json!({ "session_bundle": valid_body() });
assert!(parse_session_bundle_wire(&checked(&wire)).is_ok());
}
#[test]
fn bare_body_parses() {
assert!(parse_session_bundle_wire(&checked(&valid_body())).is_ok());
}
#[test]
fn sibling_signature_is_rejected() {
let mut body = valid_body();
let signature = body.as_object_mut().unwrap().remove("signature").unwrap();
let wire = json!({ "session_bundle": body, "signature": signature });
assert!(matches!(
parse_session_bundle_wire(&checked(&wire)),
Err(SessionBundleError::WireFormInvalid(_))
));
}
#[test]
fn unexpected_sibling_key_is_rejected() {
let wire = json!({ "session_bundle": valid_body(), "extra": "nope" });
assert!(matches!(
parse_session_bundle_wire(&checked(&wire)),
Err(SessionBundleError::WireFormInvalid(_))
));
}
#[test]
fn bare_body_missing_signature_is_rejected() {
let mut body = valid_body();
body.as_object_mut().unwrap().remove("signature");
assert!(matches!(
parse_session_bundle_wire(&checked(&body)),
Err(SessionBundleError::WireFormInvalid(_))
));
}
#[test]
fn unknown_body_member_is_unknown_field() {
let mut body = valid_body();
body.as_object_mut()
.unwrap()
.insert("coordinator_note".into(), json!("primary-region"));
let wire = json!({ "session_bundle": body });
match parse_session_bundle_wire(&checked(&wire)) {
Err(SessionBundleError::UnknownField(field)) => {
assert_eq!(field, "coordinator_note")
}
other => panic!("expected UnknownField(\"coordinator_note\"), got {other:?}"),
}
}
#[test]
fn unknown_bare_body_member_is_unknown_field() {
let mut body = valid_body();
body.as_object_mut()
.unwrap()
.insert("rogue".into(), json!(1));
match parse_session_bundle_wire(&checked(&body)) {
Err(SessionBundleError::UnknownField(field)) => assert_eq!(field, "rogue"),
other => panic!("expected UnknownField(\"rogue\"), got {other:?}"),
}
}
#[test]
fn extensions_contents_are_never_treated_as_unknown_members() {
let mut body = valid_body();
body.as_object_mut()
.unwrap()
.insert("extensions".into(), json!({ "tee": { "platform": "sgx" } }));
let wire = json!({ "session_bundle": body });
assert!(parse_session_bundle_wire(&checked(&wire)).is_ok());
}
#[test]
fn unknown_participant_entry_member_is_unknown_field_via_from_serde_error() {
let mut body = valid_body();
body["participants"] = json!([{
"aid": "aid:pubkey:O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik",
"tct": "a.b.c",
"rogue": "nope",
}]);
let wire = json!({ "session_bundle": body });
match parse_session_bundle_wire(&checked(&wire)) {
Err(SessionBundleError::UnknownField(field)) => assert_eq!(field, "rogue"),
other => panic!("expected UnknownField(\"rogue\"), got {other:?}"),
}
}
#[test]
fn unknown_body_member_is_rejected_before_coordinator_key_resolution() {
let mut body = valid_body();
body["coordinator"] = json!("not-an-aid-and-not-even-attempted");
body.as_object_mut()
.unwrap()
.insert("rogue".into(), json!("nope"));
let wire = json!({ "session_bundle": body });
match parse_session_bundle_wire(&checked(&wire)) {
Err(SessionBundleError::UnknownField(field)) => assert_eq!(field, "rogue"),
other => panic!(
"expected the unknown-member check to short-circuit before `coordinator` is \
ever interpreted, got {other:?}"
),
}
}
}