1use crate::cbor::Value;
10
11use super::{entry, fixed, has_fields, protocol_uint, read_fields, uint, Fields, Rule};
12
13pub const SEALED_SCHEME: u64 = 1;
15
16const KEM_CT_PURE_SIZE: usize = 1568;
17const KEM_CT_HYBRID_SIZE: usize = 1665;
18const SEALED_NONCE_SIZE: usize = 12;
19
20#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct Sealed {
23 pub key_id: [u8; 8],
24 pub kem_ct: Option<Vec<u8>>,
25 pub nonce: Option<Vec<u8>>,
26 pub ct: Vec<u8>,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum SealedContext {
32 Request,
33 Reply,
34 ProviderStream,
35 CallerStream,
36}
37
38impl Sealed {
39 pub(super) fn value(&self) -> Value {
41 let mut entries = vec![
42 entry("scheme", uint(SEALED_SCHEME)),
43 entry("key_id", Value::Bytes(self.key_id.to_vec())),
44 entry("ct", Value::Bytes(self.ct.clone())),
45 ];
46 if let Some(kem_ct) = &self.kem_ct {
47 entries.push(entry("kem_ct", Value::Bytes(kem_ct.clone())));
48 }
49 if let Some(nonce) = &self.nonce {
50 entries.push(entry("nonce", Value::Bytes(nonce.clone())));
51 }
52 Value::Map(entries)
53 }
54
55 pub(super) fn shaped(&self, context: SealedContext) -> bool {
58 if self
59 .nonce
60 .as_ref()
61 .is_some_and(|n| n.len() != SEALED_NONCE_SIZE)
62 {
63 return false;
64 }
65 match (context, &self.kem_ct, &self.nonce) {
66 (SealedContext::Request, Some(kem_ct), None) => {
67 kem_ct.len() == KEM_CT_PURE_SIZE || kem_ct.len() == KEM_CT_HYBRID_SIZE
68 }
69 (SealedContext::Reply | SealedContext::ProviderStream, None, Some(_)) => true,
70 (SealedContext::CallerStream, None, None) => true,
71 _ => false,
72 }
73 }
74}
75
76const SEALED_TABLE: &[(&str, Rule)] = &[
77 ("scheme", Rule::ProtocolUint),
78 ("key_id", Rule::BytesOf(8)),
79 ("kem_ct", Rule::AnyBytes),
80 ("nonce", Rule::BytesOf(SEALED_NONCE_SIZE)),
81 ("ct", Rule::AnyBytes),
82];
83
84pub(super) fn read_sealed(v: &Value, context: SealedContext) -> Option<Sealed> {
88 let fields = read_fields(v, SEALED_TABLE)?;
89 if !has_fields(&fields, &["scheme", "key_id", "ct"])
90 || protocol_uint(&fields["scheme"]) != Some(SEALED_SCHEME)
91 {
92 return None;
93 }
94 let bytes = |name: &str| match fields.get(name) {
95 Some(Value::Bytes(b)) => Some(b.clone()),
96 _ => None,
97 };
98 let sealed = Sealed {
99 key_id: fixed(&fields["key_id"]),
100 kem_ct: bytes("kem_ct"),
101 nonce: bytes("nonce"),
102 ct: bytes("ct")?,
103 };
104 sealed.shaped(context).then_some(sealed)
105}
106
107pub(super) fn clear_or_sealed(fields: &Fields, clear: &str) -> bool {
110 fields.contains_key(clear) != fields.contains_key("sealed")
111}
112
113pub(super) fn sealed_field(fields: &Fields, context: SealedContext) -> Option<Sealed> {
116 fields.get("sealed").and_then(|v| read_sealed(v, context))
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 fn sealed(kem_ct: Option<usize>, nonce: Option<usize>) -> Sealed {
124 Sealed {
125 key_id: [7; 8],
126 kem_ct: kem_ct.map(|n| vec![1; n]),
127 nonce: nonce.map(|n| vec![2; n]),
128 ct: vec![3; 20],
129 }
130 }
131
132 #[test]
133 fn each_frame_takes_only_its_own_sealed_shape() {
134 use SealedContext::*;
135 for (s, request, reply, provider_stream, caller_stream) in [
136 (sealed(Some(1568), None), true, false, false, false),
137 (sealed(Some(1665), None), true, false, false, false),
138 (sealed(Some(1567), None), false, false, false, false),
139 (sealed(None, Some(12)), false, true, true, false),
140 (sealed(None, Some(11)), false, false, false, false),
141 (sealed(None, None), false, false, false, true),
142 (sealed(Some(1568), Some(12)), false, false, false, false),
143 ] {
144 assert_eq!(s.shaped(Request), request, "{s:?}");
145 assert_eq!(s.shaped(Reply), reply, "{s:?}");
146 assert_eq!(s.shaped(ProviderStream), provider_stream, "{s:?}");
147 assert_eq!(s.shaped(CallerStream), caller_stream, "{s:?}");
148 assert_eq!(
149 read_sealed(&s.value(), Request).is_some(),
150 request,
151 "{s:?} read back"
152 );
153 }
154 }
155
156 #[test]
157 fn a_sealed_map_of_another_scheme_or_with_a_key_the_table_cannot_name_is_not_one() {
158 let Value::Map(mut other_scheme) = sealed(None, Some(12)).value() else {
159 unreachable!()
160 };
161 other_scheme[0] = entry("scheme", uint(2));
162 assert_eq!(
163 read_sealed(&Value::Map(other_scheme), SealedContext::Reply),
164 None
165 );
166 let Value::Map(mut extra) = sealed(None, Some(12)).value() else {
167 unreachable!()
168 };
169 extra.push(entry("payload", Value::Null));
170 assert_eq!(read_sealed(&Value::Map(extra), SealedContext::Reply), None);
171 }
172}