1use std::future::Future;
22use std::pin::Pin;
23use std::sync::{Arc, Mutex, Weak};
24use std::time::Duration;
25
26use tokio::sync::watch;
27
28use crate::cbor::{self, Value};
29use crate::frame::{self, StreamMode, VerifiedRequest};
30use crate::node_key::NodeKey;
31use crate::record::{
32 self, Authorization, ProcedureAdvertisementOptions, Reason, Record, RecordError,
33 TombstoneOptions, Trust,
34};
35use crate::seal;
36use crate::ucan::Policy;
37
38use super::admission::Verdict;
39use super::authorize::authorize;
40use super::confidential::{
41 clear_allowed, opened_request, CallSeal, Confidentiality, CODE_SEALED_REFUSED,
42 CODE_SEALED_REQUIRED,
43};
44use super::framing::MAX_FRAME_BYTES;
45use super::stream::StreamHandler;
46use super::{now_ms, Inner, Link, LinkError};
47
48const CODE_HANDLER_ERROR: &str = "handler_error";
52const CODE_HANDLER_CRASHED: &str = "temporary_relay_failure";
53const CODE_UNKNOWN_PROCEDURE: &str = "unknown_next_peer";
54pub(super) const CODE_REQUEST_COPY: &str = "request_copy";
55const CODE_PAYLOAD_TOO_LARGE: &str = "payload_too_large";
56const CODE_UNSENDABLE: &str = "unknown_error";
57const CODE_UNAVAILABLE: &str = "unavailable";
60
61const MAX_DETAIL_BYTES: usize = 256;
63
64const MAX_ADVERTISEMENT_TTL: Duration =
66 Duration::from_millis(super::confidential::MAX_ADVERTISEMENT_TTL_MS as u64);
67const REFRESH_RETRY: Duration = Duration::from_secs(10);
70
71pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send + 'static>>;
73
74pub type Handler = Arc<dyn Fn(Request) -> BoxFuture<Result<Value, String>> + Send + Sync>;
78
79pub fn handler<F, Fut>(f: F) -> Handler
81where
82 F: Fn(Request) -> Fut + Send + Sync + 'static,
83 Fut: Future<Output = Result<Value, String>> + Send + 'static,
84{
85 Arc::new(move |r| Box::pin(f(r)))
86}
87
88#[derive(Debug, Clone, PartialEq)]
92pub struct Request {
93 pub caller: [u8; 32],
94 pub realm: [u8; 32],
95 pub procedure: String,
96 pub payload: Value,
97 pub token: Option<Vec<u8>>,
98 pub proofs: Option<Vec<Vec<u8>>>,
99 pub deadline_ms: u64,
100 pub sealed: bool,
103}
104
105#[derive(Clone)]
110pub struct Offer {
111 pub realm: [u8; 32],
112 pub procedure: String,
113 pub handler: Option<Handler>,
114 pub stream: Option<StreamOffer>,
115 pub realm_key: Option<Vec<u8>>,
116 pub confidential: Confidentiality,
120 pub keyed_since_ms: Option<i64>,
125 pub policy: Option<Policy>,
129}
130
131impl Offer {
132 pub fn unary(realm: [u8; 32], procedure: &str, handler: Handler) -> Offer {
134 Offer {
135 realm,
136 procedure: procedure.to_string(),
137 handler: Some(handler),
138 stream: None,
139 realm_key: None,
140 confidential: Confidentiality::Preferred,
141 keyed_since_ms: None,
142 policy: None,
143 }
144 }
145
146 pub fn stream(
148 realm: [u8; 32],
149 procedure: &str,
150 mode: StreamMode,
151 handler: StreamHandler,
152 ) -> Offer {
153 Offer {
154 realm,
155 procedure: procedure.to_string(),
156 handler: None,
157 stream: Some(StreamOffer { mode, handler }),
158 realm_key: None,
159 confidential: Confidentiality::Preferred,
160 keyed_since_ms: None,
161 policy: None,
162 }
163 }
164}
165
166#[derive(Clone)]
170pub struct StreamOffer {
171 pub mode: StreamMode,
172 pub handler: StreamHandler,
173}
174
175pub(super) type ServedEntry = Arc<ServedInner>;
177
178pub(super) struct ServedInner {
179 link: Weak<Inner>,
180 key: ([u8; 32], String),
181 pub(super) offer: Offer,
182 latest: Mutex<Record>,
183 err: Mutex<Option<LinkError>>,
184 done_tx: watch::Sender<bool>,
185}
186
187#[derive(Clone)]
191pub struct Served {
192 inner: Arc<ServedInner>,
193}
194
195impl Link {
196 pub async fn serve(&self, mut o: Offer) -> Result<Served, LinkError> {
207 let own = record::in_own_namespace(&o.procedure);
208 if o.handler.is_some() == o.stream.is_some()
209 || (!own && o.realm_key.is_none())
210 || o.policy.as_ref().is_some_and(|p| !p.valid())
211 {
212 return Err(LinkError::InvalidOffer);
213 }
214 if !matches!(record::procedure_org(&o.procedure), Ok(Some(_))) {
215 return Err(LinkError::NoOrg);
216 }
217 let can_name_key = self.inner.kem_advertise && self.inner.keyring.is_some();
218 if o.confidential == Confidentiality::Required && !can_name_key {
219 return Err(LinkError::KemAdvertiseDisabled);
220 }
221 if self.inner.keyed(&o) && o.keyed_since_ms.is_none() {
222 o.keyed_since_ms = Some(now_ms());
223 }
224 let (advertisement, wire) = self.advertisement(&o, MAX_ADVERTISEMENT_TTL).await?;
225 let key = (o.realm, o.procedure.clone());
226 let (done_tx, _) = watch::channel(false);
227 let served = Arc::new(ServedInner {
228 link: Arc::downgrade(&self.inner),
229 key: key.clone(),
230 offer: o,
231 latest: Mutex::new(advertisement),
232 err: Mutex::new(None),
233 done_tx,
234 });
235 route_calls(&self.inner, key, served.clone())?;
236 if let Err(e) = self.announce(&wire).await {
237 served.end(e.clone());
238 return Err(e);
239 }
240 tokio::spawn(renew(served.clone()));
241 Ok(Served { inner: served })
242 }
243
244 async fn advertisement(
248 &self,
249 o: &Offer,
250 max_ttl: Duration,
251 ) -> Result<(Record, Vec<u8>), LinkError> {
252 let inner = &self.inner;
253 let max_ttl_ms = max_ttl.as_millis() as u64;
254 let kem_key = inner.kem_key(o);
256 let opts = if record::in_own_namespace(&o.procedure) {
257 ProcedureAdvertisementOptions {
258 authorization: Authorization::None,
259 kem_key,
260 ttl_ms: max_ttl_ms,
261 }
262 } else {
263 self.org_advertisement_options(o, max_ttl_ms, kem_key)
264 .await?
265 };
266 let unsigned = record::new_procedure_advertisement(
267 &inner.self_id,
268 &o.realm,
269 &o.procedure,
270 &[inner.station.node_id],
271 &opts,
272 )?;
273 let signed = record::sign(&unsigned, &inner.key)?;
276 let wire = record::encode(&signed)?;
277 let now = now_ms();
278 let verified = record::verify(&wire, inner.profile, now)?;
279 record::verify_authorization(
280 &verified,
281 &Trust {
282 profile: inner.profile,
283 realm_key: o.realm_key.clone(),
284 },
285 now,
286 )?;
287 Ok((signed, wire))
288 }
289
290 async fn org_advertisement_options(
294 &self,
295 o: &Offer,
296 max_ttl_ms: u64,
297 kem_key: Option<Vec<u8>>,
298 ) -> Result<ProcedureAdvertisementOptions, LinkError> {
299 let inner = &self.inner;
300 let org = record::procedure_org(&o.procedure)?.ok_or(LinkError::NoOrg)?;
301 let directory = self
302 .find_record(&record::org_directory_key(&o.realm, org))
303 .await?;
304 let named = record::read_org_directory(directory.record())?;
305 let delegation = self
306 .find_record(&record::procedure_delegation_key(
307 &named.org_key,
308 &inner.self_id,
309 ))
310 .await?;
311 let now = now_ms();
312 let ttl = (max_ttl_ms as i64)
313 .min(directory.record().expires_at as i64 - now)
314 .min(delegation.record().expires_at as i64 - now);
315 if ttl <= 0 {
316 return Err(RecordError::AuthorizationOutlived.into());
317 }
318 Ok(ProcedureAdvertisementOptions {
319 authorization: Authorization::Delegation {
320 org_directory: record::encode(directory.record())?,
321 procedure_delegation: record::encode(delegation.record())?,
322 },
323 kem_key,
324 ttl_ms: ttl as u64,
325 })
326 }
327
328 async fn announce(&self, wire: &[u8]) -> Result<(), LinkError> {
332 self.inner
333 .send_control(&frame::advertise_frame(wire))
334 .await?;
335 self.put_record(wire).await
336 }
337}
338
339fn route_calls(
342 inner: &Inner,
343 key: ([u8; 32], String),
344 served: Arc<ServedInner>,
345) -> Result<(), LinkError> {
346 let mut state = inner.lock();
347 if let Some(e) = &state.ended {
348 return Err(e.clone());
349 }
350 if state.served.contains_key(&key) {
351 return Err(LinkError::AlreadyServed);
352 }
353 state.served.insert(key, served);
354 Ok(())
355}
356
357impl Served {
358 pub async fn stop(&self) -> Result<(), LinkError> {
363 if self.inner.is_done() {
364 return Ok(());
365 }
366 self.inner.end(LinkError::Stopped);
367 let Some(inner) = self.inner.link.upgrade() else {
368 return Ok(());
369 };
370 let latest = self
371 .inner
372 .latest
373 .lock()
374 .unwrap_or_else(|p| p.into_inner())
375 .clone();
376 let tombstone =
377 record::new_tombstone(&latest, Reason::Shutdown, &TombstoneOptions::default())?;
378 let wire = record::encode(&record::sign(&tombstone, &inner.key)?)?;
379 inner.send_control(&frame::unadvertise_frame(&wire)).await?;
380 Link { inner }.put_record(&wire).await
381 }
382
383 pub async fn done(&self) -> LinkError {
385 let mut done = self.inner.done_tx.subscribe();
386 let _ = done.wait_for(|ended| *ended).await;
387 self.error().unwrap_or(LinkError::Stopped)
388 }
389
390 pub fn error(&self) -> Option<LinkError> {
394 self.inner
395 .err
396 .lock()
397 .unwrap_or_else(|p| p.into_inner())
398 .clone()
399 }
400}
401
402impl ServedInner {
403 fn is_done(&self) -> bool {
404 *self.done_tx.borrow()
405 }
406
407 pub(super) fn end(self: &Arc<Self>, err: LinkError) {
410 if !self.hold_err(err) {
411 return;
412 }
413 self.unroute();
414 let _ = self.done_tx.send_replace(true);
415 }
416
417 fn hold_err(&self, err: LinkError) -> bool {
420 let mut held = self.err.lock().unwrap_or_else(|p| p.into_inner());
421 if held.is_some() {
422 return false;
423 }
424 *held = Some(err);
425 true
426 }
427
428 fn unroute(self: &Arc<Self>) {
431 let Some(inner) = self.link.upgrade() else {
432 return;
433 };
434 let mut state = inner.lock();
435 if state
436 .served
437 .get(&self.key)
438 .is_some_and(|s| Arc::ptr_eq(s, self))
439 {
440 state.served.remove(&self.key);
441 }
442 }
443}
444
445pub(super) fn advertise_refused(
449 inner: &Inner,
450 key: &([u8; 32], String),
451 record_version: &[u8; 16],
452 refused: LinkError,
453) {
454 let Some(served) = inner.lock().served.get(key).cloned() else {
455 return;
456 };
457 let current = served
458 .latest
459 .lock()
460 .unwrap_or_else(|p| p.into_inner())
461 .version;
462 if ¤t == record_version {
463 served.end(refused);
464 }
465}
466
467async fn renew(served: Arc<ServedInner>) {
472 let mut current = served
473 .latest
474 .lock()
475 .unwrap_or_else(|p| p.into_inner())
476 .clone();
477 let mut wait = half_life(¤t);
478 let mut last_err: Option<LinkError> = None;
479 let mut stopped = served.done_tx.subscribe();
480 loop {
481 let Some(mut link_done) = served.link.upgrade().map(|l| l.done_rx.clone()) else {
482 return;
483 };
484 tokio::select! {
485 _ = stopped.wait_for(|ended| *ended) => return,
486 _ = link_done.wait_for(|ended| *ended) => {
487 let err = served.link.upgrade().and_then(|l| l.lock().ended.clone()).unwrap_or(LinkError::Closed);
488 served.end(err);
489 return;
490 }
491 _ = tokio::time::sleep(wait) => {}
492 }
493 if now_ms() >= current.expires_at as i64 {
494 served.end(last_err.unwrap_or(LinkError::Stopped));
495 return;
496 }
497 let Some(inner) = served.link.upgrade() else {
498 return;
499 };
500 let link = Link { inner };
501 let renewed = async {
502 let (advertisement, wire) = link
503 .advertisement(&served.offer, MAX_ADVERTISEMENT_TTL)
504 .await?;
505 link.announce(&wire).await?;
506 Ok::<_, LinkError>(advertisement)
507 }
508 .await;
509 match renewed {
510 Ok(advertisement) => {
511 *served.latest.lock().unwrap_or_else(|p| p.into_inner()) = advertisement.clone();
512 wait = half_life(&advertisement);
513 current = advertisement;
514 }
515 Err(e) => {
516 last_err = Some(e);
517 let left = (current.expires_at as i64 - now_ms()).max(0) as u64;
518 wait = REFRESH_RETRY.min(Duration::from_millis(left));
519 }
520 }
521 }
522}
523
524fn half_life(r: &Record) -> Duration {
525 Duration::from_millis(r.expires_at.saturating_sub(r.created_at) / 2)
526}
527
528pub(super) fn called(inner: &Arc<Inner>, v: &Value) {
533 let Ok(request) = frame::verify_request(v, inner.profile) else {
534 inner.count("unverified_call");
535 return;
536 };
537 if request.target != inner.self_id {
538 inner.count("call_for_another_node");
539 return;
540 }
541 let inner = inner.clone();
542 match inner.admission.admit(&request, &inner.share, now_ms()) {
543 Verdict::Refused(code) => refuse_call(inner, &request, code),
544 Verdict::Copy(None) => refuse_call(inner, &request, CODE_REQUEST_COPY),
545 Verdict::Copy(Some(stored)) => {
546 tokio::spawn(async move {
547 let _ = inner.control.write(&stored, MAX_FRAME_BYTES).await;
548 });
549 }
550 Verdict::New => {
551 tokio::spawn(answer(inner, request));
552 }
553 }
554}
555
556fn refuse_call(inner: Arc<Inner>, request: &VerifiedRequest, code: &'static str) {
559 let Some(reply) = provider_error(&inner.signer(), request, code, None) else {
560 inner.count("unsignable_reply");
561 return;
562 };
563 tokio::spawn(async move { send_reply(&inner, reply).await });
564}
565
566async fn answer(inner: Arc<Inner>, request: VerifiedRequest) {
569 let offer = inner
570 .lock()
571 .served
572 .get(&(request.realm, request.procedure.clone()))
573 .map(|s| s.offer.clone());
574 let Some(reply) = reply(&inner, &request, offer).await else {
575 inner.count("unsignable_reply");
576 return;
577 };
578 let Ok(encoded) = cbor::encode(&reply) else {
579 inner.count("unencodable_reply");
580 return;
581 };
582 inner.admission.store(&request, encoded.clone());
583 let _ = inner.control.write(&encoded, MAX_FRAME_BYTES).await;
584}
585
586async fn reply(inner: &Inner, request: &VerifiedRequest, offer: Option<Offer>) -> Option<Value> {
593 let (payload, sealing) = match &request.sealed {
594 Some(_) => match opened_request(inner.keyring.as_deref(), request) {
595 Ok((payload, sealing)) => (payload, Some(sealing)),
596 Err(detail) => {
597 return provider_error(&inner.signer(), request, CODE_SEALED_REFUSED, Some(&detail))
598 }
599 },
600 None => {
601 let refused = offer
602 .as_ref()
603 .is_some_and(|o| !clear_allowed(o.confidential, inner.keyed_since(o), now_ms()));
604 if refused {
605 return provider_error(&inner.signer(), request, CODE_SEALED_REQUIRED, None);
606 }
607 (request.payload.clone(), None)
608 }
609 };
610 let outcome = match gated(inner.profile, offer, request) {
611 Ok(handler) => handled(handler, request, without_caller(payload), sealing.is_some()).await,
612 Err(code) => Outcome::Refused(code, None),
613 };
614 answered(&inner.signer(), request, sealing.as_ref(), outcome)
615}
616
617fn gated(
621 profile: crate::profile::Profile,
622 offer: Option<Offer>,
623 request: &VerifiedRequest,
624) -> Result<Handler, &'static str> {
625 let Some(Offer {
626 handler: Some(handler),
627 policy,
628 ..
629 }) = offer
630 else {
631 return Err(CODE_UNKNOWN_PROCEDURE);
632 };
633 authorize(profile, policy.as_ref(), request).map_or(Ok(handler), Err)
634}
635
636pub(super) fn without_caller(payload: Value) -> Value {
641 match payload {
642 Value::Map(_) => payload.without(&["caller"]),
643 other => other,
644 }
645}
646
647enum Outcome {
650 Result(Value),
651 Refused(&'static str, Option<String>),
652}
653
654async fn handled(
658 handler: Handler,
659 request: &VerifiedRequest,
660 payload: Value,
661 sealed: bool,
662) -> Outcome {
663 let running = tokio::spawn(handler(Request {
664 caller: request.caller,
665 realm: request.realm,
666 procedure: request.procedure.clone(),
667 payload,
668 token: request.token.clone(),
669 proofs: request.proofs.clone(),
670 deadline_ms: request.deadline,
671 sealed,
672 }));
673 let abort = running.abort_handle();
674 let left = (request.deadline as i64 - now_ms()).max(0) as u64;
675 match tokio::time::timeout(Duration::from_millis(left), running).await {
676 Err(_) => {
677 abort.abort();
678 Outcome::Refused(
679 CODE_HANDLER_ERROR,
680 Some("the request's deadline passed".into()),
681 )
682 }
683 Ok(Err(_panicked)) => Outcome::Refused(CODE_HANDLER_CRASHED, None),
684 Ok(Ok(Err(refusal))) => Outcome::Refused(
685 CODE_HANDLER_ERROR,
686 Some(bounded_detail(&refusal).to_string()),
687 ),
688 Ok(Ok(Ok(payload))) => Outcome::Result(payload),
689 }
690}
691
692pub(super) struct Signer<'a> {
695 pub(super) key: &'a NodeKey,
696 pub(super) self_id: [u8; 32],
697}
698
699impl Inner {
700 fn signer(&self) -> Signer<'_> {
702 Signer {
703 key: &self.key,
704 self_id: self.self_id,
705 }
706 }
707}
708
709fn clear_answer(signer: &Signer, request: &VerifiedRequest, outcome: Outcome) -> Option<Value> {
711 match outcome {
712 Outcome::Refused(code, detail) => provider_error(signer, request, code, detail.as_deref()),
713 Outcome::Result(payload) => clear_result(signer, request, &payload),
714 }
715}
716
717fn clear_result(signer: &Signer, request: &VerifiedRequest, payload: &Value) -> Option<Value> {
720 match frame::sign_result(request, payload, None, signer.key) {
721 Ok(signed) => Some(signed),
722 Err(_) if cbor::encode(payload).is_ok_and(|e| e.len() > frame::MAX_FRAME_BYTES) => {
723 provider_error(signer, request, CODE_PAYLOAD_TOO_LARGE, None)
724 }
725 Err(_) => provider_error(signer, request, CODE_UNSENDABLE, None),
726 }
727}
728
729fn answered(
734 signer: &Signer,
735 request: &VerifiedRequest,
736 sealing: Option<&CallSeal>,
737 outcome: Outcome,
738) -> Option<Value> {
739 let Some(sealing) = sealing else {
740 return clear_answer(signer, request, outcome);
741 };
742 let payload = match outcome {
743 Outcome::Refused(code, detail) => {
744 return sealed_error(signer, request, sealing, code, detail.as_deref())
745 }
746 Outcome::Result(payload) => payload,
747 };
748 let plain = frame::check_payload(&payload)
749 .ok()
750 .and_then(|_| cbor::encode(&payload).ok());
751 let Some(plain) = plain else {
752 return sealed_error(signer, request, sealing, CODE_UNSENDABLE, None);
753 };
754 let signed = sealing
755 .sealed_answer(
756 seal::FRAME_RESULT,
757 &plain,
758 &request.request_hash,
759 &signer.self_id,
760 )
761 .and_then(|sealed| {
762 frame::sign_sealed_result(request, &sealed, None, signer.key).map_err(LinkError::from)
763 });
764 match sealed_result_refusal(signed) {
765 Ok(reply) => Some(reply),
766 Err(Refusal::Sealed(code)) => sealed_error(signer, request, sealing, code, None),
767 Err(Refusal::Clear(code)) => provider_error(signer, request, code, None),
768 }
769}
770
771#[derive(Debug, PartialEq)]
774enum Refusal {
775 Sealed(&'static str),
776 Clear(&'static str),
777}
778
779fn sealed_result_refusal(signed: Result<Value, LinkError>) -> Result<Value, Refusal> {
784 match signed {
785 Ok(reply) if cbor::encode(&reply).is_ok_and(|e| e.len() <= frame::MAX_FRAME_BYTES) => {
786 Ok(reply)
787 }
788 Ok(_) => Err(Refusal::Sealed(CODE_PAYLOAD_TOO_LARGE)),
789 Err(LinkError::Io(_)) => Err(Refusal::Clear(CODE_UNAVAILABLE)),
790 Err(_) => Err(Refusal::Sealed(CODE_UNSENDABLE)),
791 }
792}
793
794fn sealed_error(
799 signer: &Signer,
800 request: &VerifiedRequest,
801 sealing: &CallSeal,
802 code: &str,
803 detail: Option<&str>,
804) -> Option<Value> {
805 let plain = seal::error_plain(code, detail.unwrap_or(""));
806 sealing
807 .sealed_answer(
808 seal::FRAME_ERROR,
809 &plain,
810 &request.request_hash,
811 &signer.self_id,
812 )
813 .ok()
814 .and_then(|sealed| {
815 frame::sign_sealed_provider_error(request, &sealed, None, signer.key).ok()
816 })
817 .or_else(|| provider_error(signer, request, CODE_UNAVAILABLE, None))
818}
819
820impl Inner {
821 pub(super) fn keyed(&self, o: &Offer) -> bool {
823 self.kem_advertise && self.keyring.is_some() && o.confidential != Confidentiality::Off
824 }
825
826 pub(super) fn keyed_since(&self, o: &Offer) -> Option<i64> {
829 o.keyed_since_ms.filter(|_| self.keyed(o))
830 }
831
832 fn kem_key(&self, o: &Offer) -> Option<Vec<u8>> {
834 let keyring = self.keyring.as_ref().filter(|_| self.keyed(o))?;
835 Some(keyring.current().public_key().carried().to_vec())
836 }
837}
838
839fn provider_error(
843 signer: &Signer,
844 request: &VerifiedRequest,
845 code: &str,
846 detail: Option<&str>,
847) -> Option<Value> {
848 frame::sign_provider_error(request, code, detail, None, signer.key).ok()
849}
850
851async fn send_reply(inner: &Inner, reply: Value) {
852 let _ = inner.send_control(&reply).await;
853}
854
855pub(super) fn bounded_detail(text: &str) -> &str {
857 if text.len() <= MAX_DETAIL_BYTES {
858 return text;
859 }
860 let mut cut = MAX_DETAIL_BYTES;
861 while !text.is_char_boundary(cut) {
862 cut -= 1;
863 }
864 &text[..cut]
865}
866
867#[cfg(test)]
868mod tests {
869 use super::*;
874 use crate::frame::{FrameError, RequestType};
875 use crate::node_key::NodeKey;
876 use crate::profile::Profile;
877 use crate::seal::Keyring;
878 use crate::station_link::confidential::sealed_request;
879
880 const CALLER: [u8; 32] = [1; 32];
881
882 fn sealed_call(keyring: &Keyring, target: [u8; 32]) -> (VerifiedRequest, CallSeal) {
885 let to = keyring.current().public_key().carried().to_vec();
886 let (sealed, _) = sealed_request(
887 keyring.profile(),
888 &to,
889 seal::FRAME_CALL,
890 [3; 32],
891 "~ring",
892 CALLER,
893 target,
894 [4; 16],
895 1_790_000_000_000,
896 &Value::text("secret"),
897 )
898 .unwrap();
899 let request = VerifiedRequest {
900 frame_type: RequestType::Call,
901 key: Vec::new(),
902 request_hash: [5; 48],
903 caller: CALLER,
904 request_id: [4; 16],
905 realm: [3; 32],
906 procedure: "~ring".into(),
907 target,
908 deadline: 1_790_000_000_000,
909 payload: Value::Null,
910 sealed: Some(sealed),
911 mode: None,
912 token: None,
913 proofs: None,
914 };
915 let (_, sealing) = opened_request(Some(keyring), &request).unwrap();
916 (request, sealing)
917 }
918
919 #[test]
920 fn only_a_result_over_the_frame_cap_is_payload_too_large() {
921 let oversize = Value::Bytes(vec![0; frame::MAX_FRAME_BYTES + 1]);
922 assert_eq!(
923 sealed_result_refusal(Ok(oversize)),
924 Err(Refusal::Sealed(CODE_PAYLOAD_TOO_LARGE))
925 );
926 assert_eq!(
927 sealed_result_refusal(Err(LinkError::Io("no randomness".into()))),
928 Err(Refusal::Clear(CODE_UNAVAILABLE))
929 );
930 assert_eq!(
931 sealed_result_refusal(Err(LinkError::Frame(FrameError::Unsignable))),
932 Err(Refusal::Sealed(CODE_UNSENDABLE))
933 );
934 assert_eq!(
935 sealed_result_refusal(Ok(Value::text("fits"))),
936 Ok(Value::text("fits"))
937 );
938 }
939
940 #[test]
941 fn a_sealed_answer_that_does_not_sign_does_not_panic() {
942 let profile = Profile::PqPure;
943 let keyring = Keyring::system(profile).unwrap();
944 let key = NodeKey::generate_identity(profile, 0).unwrap();
945 let signer = Signer {
946 key: &key,
947 self_id: key.key_id(),
948 };
949 let (request, sealing) = sealed_call(&keyring, [2; 32]);
952 let refused = Outcome::Refused(CODE_HANDLER_ERROR, Some("no".into()));
953 assert_eq!(answered(&signer, &request, Some(&sealing), refused), None);
954 let result = Outcome::Result(Value::text("answer"));
955 assert_eq!(answered(&signer, &request, Some(&sealing), result), None);
956 let (request, sealing) = sealed_call(&keyring, key.key_id());
958 let result = Outcome::Result(Value::text("answer"));
959 let reply = answered(&signer, &request, Some(&sealing), result).unwrap();
960 assert!(reply.get("reply").is_some(), "{reply:?}");
961 }
962}