Skip to main content

macula_rust/station_link/
serve.rs

1//! Serving, as macula 12's provider does it. A procedure is served under an
2//! org namespace: the realm's org directory names the org's key, the org's
3//! procedure delegation names this node, both are found in the DHT, and the
4//! signed procedure_advertisement carries them to the station in an
5//! ADVERTISE, checked against the realm key before it goes out. Or it is
6//! served in this node's own namespace, `~<node_id>/<name>` (D25 item 6): its
7//! advertisement carries no authorization, its signature alone authorizes it,
8//! and no realm key or record in the DHT is needed. The station routes CALLs
9//! for the procedure to this link; each is admitted once per (caller,
10//! request_id) and answered with a RESULT or ERROR signed by this node.
11//! UNADVERTISE carries a tombstone of the advertisement.
12//!
13//! With `kem_advertise` on, a confidential procedure's advertisement names
14//! this node's KEM key, and a request sealed to it is opened and answered
15//! sealed (confidential.rs).
16//!
17//! A procedure is served open, or gated on a UCAN policy: a request whose
18//! token the policy does not authorize is refused unauthorized
19//! (authorize.rs).
20
21use 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
48/// The provider codes of a served procedure's ERRORs: a handler's own
49/// refusal, a handler that panicked, a procedure this link does not serve, a
50/// copy of a request still running, and a result the wire cannot carry.
51const 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";
57/// A sealed answer this node could not seal: a refusal from the closed set,
58/// which carries no application data.
59const CODE_UNAVAILABLE: &str = "unavailable";
60
61/// An ERROR's detail is at most 256 bytes, cut on a character boundary.
62const MAX_DETAIL_BYTES: usize = 256;
63
64/// macula's default and longest advertisement lifetime.
65const MAX_ADVERTISEMENT_TTL: Duration =
66    Duration::from_millis(super::confidential::MAX_ADVERTISEMENT_TTL_MS as u64);
67/// How soon a failed renewal is tried again, while the advertisement it
68/// replaces still lives.
69const REFRESH_RETRY: Duration = Duration::from_secs(10);
70
71/// A future a handler returns.
72pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send + 'static>>;
73
74/// Answers a [`Request`] with a result payload, or an error whose text the
75/// caller receives as a handler_error's detail. A handler still running at
76/// the request's deadline is dropped and answered handler_error.
77pub type Handler = Arc<dyn Fn(Request) -> BoxFuture<Result<Value, String>> + Send + Sync>;
78
79/// A [`Handler`] from an async closure.
80pub 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/// A CALL a served procedure answers: the caller (the key id its signature
89/// verified under), what it asked for, and its deadline in unix
90/// milliseconds.
91#[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    /// Whether the request came sealed end to end: `payload` is its opened
101    /// plaintext, and the answer goes back sealed.
102    pub sealed: bool,
103}
104
105/// A procedure to serve: its realm and name, exactly one of a unary handler
106/// and a stream offer, and, for an org procedure, the realm key the org
107/// directory must be signed with, as the realm's members pin it; a procedure
108/// in this node's own namespace needs none.
109#[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    /// How the procedure takes its requests: whether its advertisement names
117    /// this node's KEM key, with `kem_advertise` on, and whether it takes
118    /// clear ones.
119    pub confidential: Confidentiality,
120    /// When the procedure was first advertised naming the key (unix ms),
121    /// from which its keyless window runs; serving sets it to now when
122    /// `None`. A pool keeps it across the links it serves on, so no link
123    /// reopens the window.
124    pub keyed_since_ms: Option<i64>,
125    /// What the procedure requires of a request's UCAN: `None` serves any
126    /// caller (an open procedure ignores any token), and a [`Policy`] serves
127    /// only a request whose token it authorizes (authorize.rs).
128    pub policy: Option<Policy>,
129}
130
131impl Offer {
132    /// A unary procedure, with no realm key.
133    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    /// A streaming procedure of `mode`, with no realm key.
147    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/// A streaming procedure's mode and handler. The advertisement is the one a
167/// unary procedure sends, which names no mode: a STREAM_OPEN of another mode
168/// is refused mode_mismatch.
169#[derive(Clone)]
170pub struct StreamOffer {
171    pub mode: StreamMode,
172    pub handler: StreamHandler,
173}
174
175/// A procedure a link serves, as the link holds it.
176pub(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/// A procedure this link serves, until [`Served::stop`] or the link ends, or
188/// until its advertisement lapses because its authorization could not be
189/// found again. Cloning it shares the serving.
190#[derive(Clone)]
191pub struct Served {
192    inner: Arc<ServedInner>,
193}
194
195impl Link {
196    /// Advertises `o`'s procedure on the link and answers its CALLs until
197    /// stopped. For an org procedure it resolves the org directory and this
198    /// node's procedure delegation from the DHT; a procedure in this node's
199    /// own namespace needs neither, and another node's namespace is refused.
200    /// It signs the advertisement with this node's identity key, naming the
201    /// connected station as the serving station and living no longer than
202    /// the records it carries nor 5 minutes, and checks its authorization
203    /// (against the realm key for an org procedure) before sending it in an
204    /// ADVERTISE and putting it in the DHT. The advertisement is renewed at
205    /// half its lifetime.
206    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    /// `o`'s signed procedure_advertisement and its wire form, living at most
245    /// `max_ttl`: with the authorization resolved from the DHT for an org
246    /// procedure, with none in this node's own namespace.
247    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        // Each signing reads the keyring, so a renewal names a rotated key.
255        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        // Signed, then checked as a caller will check it before it is sent
274        // anywhere.
275        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    /// The options of an org procedure's advertisement: its org directory and
291    /// this node's procedure delegation, resolved from the DHT, and a lifetime
292    /// no longer than either of them nor `max_ttl_ms`.
293    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    /// Sends an advertisement to the station in an ADVERTISE, which routes
329    /// CALLs through it, and puts it in the DHT, where a caller resolving
330    /// the procedure finds it, as macula's advertise_direct does both.
331    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
339/// Routes the procedure's CALLs on the link to `served`, unless the link has
340/// ended or already serves the procedure.
341fn 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    /// Withdraws the advertisement with an UNADVERTISE carrying its
359    /// tombstone, signed by this node, puts the tombstone in the
360    /// advertisement's DHT slot, and stops answering the procedure's CALLs.
361    /// Stopping a procedure no longer served does nothing.
362    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    /// Waits until the procedure is no longer served, and says why.
384    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    /// Why the procedure is no longer served: [`LinkError::Stopped`] after
391    /// stop, the link's error when it ended, or the renewal's failure; `None`
392    /// while served.
393    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    /// Ends the serving once, with `err`: the link no longer routes the
408    /// procedure's CALLs to its handler.
409    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    /// Keeps `err` as why the serving ended, and whether it is the first;
418    /// a later one is not kept.
419    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    /// Stops the link routing the procedure's CALLs here, when it still
429    /// routes them to this serving and not a later one.
430    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
445/// Ends the serving of `key` with `refused` when the station refused the
446/// ADVERTISE of its current record, `record_version`; an ack for an older
447/// record is not this serving's.
448pub(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 &current == record_version {
463        served.end(refused);
464    }
465}
466
467/// Sends a fresh advertisement at half the current one's lifetime. A renewal
468/// that fails is tried again every [`REFRESH_RETRY`] while the current one
469/// lives; when it lapses unrenewed, the procedure is no longer served and its
470/// error says why.
471async 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(&current);
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
528/// Answers a CALL the station routed to this link. A request that does not
529/// verify, or targets another node, gets no reply and is counted. One that
530/// verifies is judged by the admission, then answered by its handler, or by
531/// an ERROR naming why not.
532pub(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
556/// Sends this node's ERROR for `request` with `code`, or counts it
557/// unsignable_reply when it does not sign.
558fn 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
566/// Serves a request and sends its signed reply, storing it for the
567/// request's copies.
568async 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
586/// The signed reply to `request`, served by `offer` (`None` when this link
587/// serves no such procedure), in macula 13's order: a sealed request opened
588/// first, or refused sealed_refused in the clear when it does not open; a
589/// clear one to a procedure past its keyless window refused sealed_required;
590/// then the procedure, its policy and its handler, answered sealed when the
591/// request was.
592async 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
617/// The handler that serves `request` under `offer`'s policy, or the code it
618/// is refused with: unknown_procedure when this link serves no such unary
619/// procedure, and the policy's refusal when its token does not authorize it.
620fn 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
636/// `payload` as a handler receives it: a map loses a text "caller" key, so
637/// the only caller a handler can learn is the verified one
638/// ([`Request::caller`]), as macula's `with_caller/2` and macula-go's
639/// `withoutCaller` arrange (macula-rust#13). Any other payload is untouched.
640pub(super) fn without_caller(payload: Value) -> Value {
641    match payload {
642        Value::Map(_) => payload.without(&["caller"]),
643        other => other,
644    }
645}
646
647/// What answers a request: a RESULT's payload, or an ERROR's code and
648/// detail.
649enum Outcome {
650    Result(Value),
651    Refused(&'static str, Option<String>),
652}
653
654/// The handler's answer to `request`, on `payload`: its result, its refusal
655/// as handler_error, a panic as temporary_relay_failure, or handler_error
656/// when it is still running at the deadline.
657async 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
692/// What signs this node's replies: its identity key and its node id, the
693/// replies' responded_by.
694pub(super) struct Signer<'a> {
695    pub(super) key: &'a NodeKey,
696    pub(super) self_id: [u8; 32],
697}
698
699impl Inner {
700    /// This node's [`Signer`].
701    fn signer(&self) -> Signer<'_> {
702        Signer {
703            key: &self.key,
704            self_id: self.self_id,
705        }
706    }
707}
708
709/// An outcome as the clear signed reply to a clear `request`.
710fn 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
717/// A result as the signed RESULT to a clear `request`, or payload_too_large
718/// or unknown_error when the wire cannot carry it.
719fn 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
729/// An outcome as the signed reply to `request`: clear to a clear request,
730/// sealed to a sealed one. A result the wire cannot carry is answered
731/// payload_too_large or unknown_error, as macula answers it. `None` when no
732/// reply signs: the request is left unanswered (macula-rust#14).
733fn 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/// How a sealed RESULT that cannot go is refused: sealed, or in the clear
772/// from the closed set.
773#[derive(Debug, PartialEq)]
774enum Refusal {
775    Sealed(&'static str),
776    Clear(&'static str),
777}
778
779/// A signed sealed RESULT, or how to refuse it: payload_too_large sealed when
780/// it is over the frame cap, unavailable in the clear when it could not be
781/// sealed (no randomness), unknown_error sealed when it did not sign
782/// (macula-rust#14).
783fn 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
794/// This node's ERROR for a sealed request, its code and detail sealed as
795/// cbor([code, detail]). One that cannot be sealed (no randomness) or does
796/// not sign is answered unavailable in the clear, from the closed set: never
797/// the code or detail in the clear (macula-rust#14).
798fn 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    /// Whether `o`'s advertisements name this node's KEM key.
822    pub(super) fn keyed(&self, o: &Offer) -> bool {
823        self.kem_advertise && self.keyring.is_some() && o.confidential != Confidentiality::Off
824    }
825
826    /// When `o` was first keyed, `None` while its advertisements name no
827    /// key.
828    pub(super) fn keyed_since(&self, o: &Offer) -> Option<i64> {
829        o.keyed_since_ms.filter(|_| self.keyed(o))
830    }
831
832    /// The key `o`'s advertisement names now, as carried, `None` for none.
833    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
839/// This node's signed ERROR for `request`, `None` when it does not sign: a
840/// request this key cannot answer, or a key that fails to sign. Either leaves
841/// the request unanswered, counted by the caller of this, never a panic.
842fn 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
855/// `text` cut to 256 bytes on a character boundary.
856pub(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    //! A provider's sealed answer on its error paths (macula-rust#14): a
870    //! RESULT is called payload_too_large only when it is over the frame cap,
871    //! and an answer that does not sign never panics.
872
873    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    /// A call sealed to `keyring` and targeting `target`, as its provider
883    /// verified it, and the provider's keys for the answer.
884    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        // Answered as this node, the request names another node as its
950        // target, so no reply to it signs.
951        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        // Targeting this node, the same answers sign, sealed.
957        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}