1use sha2::{Digest, Sha384};
6
7use crate::cbor::Value;
8use crate::node_key::{node_id_of, NodeKey};
9use crate::profile::Profile;
10use crate::signed_object::{sign_object, verify_object};
11
12use super::sealed::{clear_or_sealed, sealed_field, Sealed, SealedContext};
13use super::{
14 bounded_text, check_payload, entry, fixed, has_fields, identity_signer, object_refusal,
15 protocol_uint, read_fields, received_frame, text_of, uint, FrameError, Rule, StreamMode,
16 MAX_PROCEDURE_BYTES, MAX_PROTOCOL_INT, PROTOCOL_VERSION, REQUEST_LABEL,
17};
18
19pub const MAX_PROOFS: usize = 8;
22pub const MAX_PROOFS_BYTES: usize = 256 * 1024;
23
24const CALL: &str = "call";
25const STREAM_OPEN: &str = "stream_open";
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum RequestType {
30 Call,
31 StreamOpen,
32}
33
34impl RequestType {
35 fn name(self) -> &'static str {
36 match self {
37 RequestType::Call => CALL,
38 RequestType::StreamOpen => STREAM_OPEN,
39 }
40 }
41}
42
43#[derive(Debug, Clone, PartialEq)]
50pub struct RequestSpec {
51 pub request_id: [u8; 16],
52 pub realm: [u8; 32],
53 pub procedure: String,
54 pub target: [u8; 32],
55 pub deadline: u64,
56 pub payload: Value,
57 pub sealed: Option<Sealed>,
58 pub mode: Option<StreamMode>,
59 pub token: Option<Vec<u8>>,
60 pub proofs: Vec<Vec<u8>>,
61 pub source_route: Option<Vec<u8>>,
62 pub retry_budget: Option<u64>,
63}
64
65#[derive(Debug, Clone, PartialEq)]
70pub struct VerifiedRequest {
71 pub frame_type: RequestType,
72 pub key: Vec<u8>,
73 pub request_hash: [u8; 48],
74 pub caller: [u8; 32],
75 pub request_id: [u8; 16],
76 pub realm: [u8; 32],
77 pub procedure: String,
78 pub target: [u8; 32],
79 pub deadline: u64,
80 pub payload: Value,
81 pub sealed: Option<Sealed>,
82 pub mode: Option<StreamMode>,
83 pub token: Option<Vec<u8>>,
84 pub proofs: Option<Vec<Vec<u8>>>,
85}
86
87pub fn sign_call(spec: &RequestSpec, key: &NodeKey) -> Result<Value, FrameError> {
94 sign_request(RequestType::Call, spec, key)
95}
96
97pub fn sign_stream_open(spec: &RequestSpec, key: &NodeKey) -> Result<Value, FrameError> {
100 sign_request(RequestType::StreamOpen, spec, key)
101}
102
103fn sign_request(
104 frame_type: RequestType,
105 spec: &RequestSpec,
106 key: &NodeKey,
107) -> Result<Value, FrameError> {
108 let proofs = check_request(frame_type, spec, key)?;
109 let fields = request_fields(frame_type, spec, key, proofs);
110 let request = sign_object(REQUEST_LABEL, &fields, key).map_err(object_refusal)?;
111 Ok(request_frame(frame_type, spec, request.to_value()))
112}
113
114fn check_request(
117 frame_type: RequestType,
118 spec: &RequestSpec,
119 key: &NodeKey,
120) -> Result<Value, FrameError> {
121 identity_signer(key)?;
122 bounded_text("procedure", spec.procedure.as_bytes(), MAX_PROCEDURE_BYTES)?;
123 match &spec.sealed {
124 Some(sealed) if !sealed.shaped(SealedContext::Request) => {
125 return Err(FrameError::SealedShape)
126 }
127 Some(_) => {}
128 None => check_payload(&spec.payload)?,
129 }
130 if spec.deadline >= MAX_PROTOCOL_INT || spec.retry_budget.is_some_and(|b| b >= MAX_PROTOCOL_INT)
131 {
132 return Err(FrameError::OutOfRange(
133 "a deadline or retry budget of 2^53 or more".into(),
134 ));
135 }
136 match (frame_type, spec.mode) {
137 (RequestType::Call, Some(_)) => {
138 return Err(FrameError::OutOfRange(
139 "a CALL carries no stream mode".into(),
140 ))
141 }
142 (RequestType::StreamOpen, None) => {
143 return Err(FrameError::OutOfRange(
144 "a STREAM_OPEN carries one of the three stream modes".into(),
145 ))
146 }
147 _ => {}
148 }
149 let proofs = proofs_value(&spec.proofs);
150 if !proofs_within_bound(&proofs) {
151 return Err(FrameError::ProofsOutOfBound);
152 }
153 Ok(proofs)
154}
155
156fn request_fields(
159 frame_type: RequestType,
160 spec: &RequestSpec,
161 key: &NodeKey,
162 proofs: Value,
163) -> Vec<(Value, Value)> {
164 let mut fields = vec![
165 entry("frame_type", Value::text(frame_type.name())),
166 entry("caller", Value::Bytes(key.key_id().to_vec())),
167 entry("request_id", Value::Bytes(spec.request_id.to_vec())),
168 entry("realm", Value::Bytes(spec.realm.to_vec())),
169 entry("procedure", Value::text(spec.procedure.clone())),
170 entry("target", Value::Bytes(spec.target.to_vec())),
171 entry("deadline", uint(spec.deadline)),
172 match &spec.sealed {
173 Some(sealed) => entry("sealed", sealed.value()),
174 None => entry("payload", spec.payload.clone()),
175 },
176 ];
177 if let Some(mode) = spec.mode {
178 fields.push(entry("mode", Value::text(mode.name())));
179 }
180 if let Some(token) = &spec.token {
181 fields.push(entry("token", Value::Bytes(token.clone())));
182 }
183 if !spec.proofs.is_empty() {
184 fields.push(entry("proofs", proofs));
185 }
186 fields
187}
188
189fn request_frame(frame_type: RequestType, spec: &RequestSpec, request: Value) -> Value {
192 let mut frame = vec![
193 entry("version", Value::Int(i128::from(PROTOCOL_VERSION))),
194 entry("frame_type", Value::text(frame_type.name())),
195 entry("request", request),
196 ];
197 if let Some(route) = &spec.source_route {
198 frame.push(entry("source_route", Value::Bytes(route.clone())));
199 }
200 if let Some(budget) = spec.retry_budget {
201 frame.push(entry("retry_budget", uint(budget)));
202 }
203 Value::Map(frame)
204}
205
206const STREAM_MODES: &[&str] = &["server_stream", "client_stream", "bidi"];
208
209pub(super) const REQUEST_ROUTES: &[(&str, Rule)] = &[
210 ("source_route", Rule::AnyBytes),
211 ("retry_budget", Rule::ProtocolUint),
212];
213
214pub fn verify_request(frame: &Value, profile: Profile) -> Result<VerifiedRequest, FrameError> {
220 let (frame_type, object) = received_frame(
221 frame,
222 "request",
223 Rule::CarriedObject,
224 REQUEST_ROUTES,
225 &[CALL, STREAM_OPEN],
226 )
227 .ok_or(FrameError::Malformed)?;
228 let frame_type = if frame_type == CALL {
229 RequestType::Call
230 } else {
231 RequestType::StreamOpen
232 };
233 let verified = verify_object(REQUEST_LABEL, &object, profile).map_err(object_refusal)?;
234 let fields =
235 read_fields(&verified.fields, &request_table(frame_type)).ok_or(FrameError::Malformed)?;
236 let has_mode = fields.contains_key("mode");
237 if !has_fields(
238 &fields,
239 &[
240 "frame_type",
241 "caller",
242 "request_id",
243 "realm",
244 "procedure",
245 "target",
246 "deadline",
247 ],
248 ) || !clear_or_sealed(&fields, "payload")
249 || has_mode != (frame_type == RequestType::StreamOpen)
250 {
251 return Err(FrameError::Malformed);
252 }
253 super::tbs_enum(&fields, "mode", STREAM_MODES)?;
254 super::signed_ext(&fields)?;
255 let request = VerifiedRequest {
256 frame_type,
257 request_hash: Sha384::digest(&verified.tbs).into(),
258 caller: fixed(&fields["caller"]),
259 request_id: fixed(&fields["request_id"]),
260 realm: fixed(&fields["realm"]),
261 procedure: text_of(&fields["procedure"]),
262 target: fixed(&fields["target"]),
263 deadline: protocol_uint(&fields["deadline"]).unwrap_or(0),
264 payload: fields.get("payload").cloned().unwrap_or(Value::Null),
265 sealed: sealed_field(&fields, SealedContext::Request),
266 mode: fields
267 .get("mode")
268 .and_then(|m| StreamMode::parse(&text_of(m))),
269 token: fields.get("token").map(super::bytes_of),
270 proofs: fields.get("proofs").map(|p| match p {
271 Value::List(items) => items.iter().map(super::bytes_of).collect(),
272 _ => Vec::new(),
273 }),
274 key: verified.key,
275 };
276 if request.caller != node_id_of(&request.key, profile) {
277 return Err(FrameError::KeyIdMismatch);
278 }
279 Ok(request)
280}
281
282fn request_table(frame_type: RequestType) -> Vec<(&'static str, Rule)> {
283 vec![
284 (
285 "frame_type",
286 Rule::TextIn(match frame_type {
287 RequestType::Call => &[CALL],
288 RequestType::StreamOpen => &[STREAM_OPEN],
289 }),
290 ),
291 ("alg", Rule::Any),
292 ("caller", Rule::BytesOf(32)),
293 ("request_id", Rule::BytesOf(16)),
294 ("realm", Rule::BytesOf(32)),
295 ("procedure", Rule::TextWithin(MAX_PROCEDURE_BYTES)),
296 ("target", Rule::BytesOf(32)),
297 ("deadline", Rule::ProtocolUint),
298 ("payload", Rule::Any),
299 ("sealed", Rule::Sealed(SealedContext::Request)),
300 ("mode", Rule::EnumText),
301 ("token", Rule::AnyBytes),
302 ("proofs", Rule::Proofs),
303 ("ext", Rule::Ext),
304 ]
305}
306
307pub fn request_fields_accepted(fields: &Value) -> bool {
311 read_fields(fields, &request_table(RequestType::Call)).is_some()
312}
313
314fn proofs_value(proofs: &[Vec<u8>]) -> Value {
315 Value::List(proofs.iter().map(|p| Value::Bytes(p.clone())).collect())
316}
317
318pub(super) fn proofs_within_bound(v: &Value) -> bool {
321 let Value::List(items) = v else {
322 return false;
323 };
324 if items.len() > MAX_PROOFS {
325 return false;
326 }
327 let mut seen = std::collections::HashSet::with_capacity(items.len());
328 let mut total = 0;
329 for item in items {
330 let Value::Bytes(b) = item else {
331 return false;
332 };
333 if !seen.insert(b.as_slice()) {
334 return false;
335 }
336 total += b.len();
337 }
338 total <= MAX_PROOFS_BYTES
339}