macula_rust/station_link/
call.rs1use std::sync::{Arc, Weak};
8use std::time::Duration;
9
10use tokio::sync::oneshot;
11
12use crate::cbor::Value;
13use crate::frame::{self, RequestSpec, VerifiedRequest};
14
15use super::confidential::{reply_outcome, sealed_request, stated, CallSeal, Seal};
16use super::{now_ms, Inner, Link, LinkError};
17use crate::seal;
18
19pub const DEFAULT_CALL_TIMEOUT: Duration = Duration::from_secs(5);
21pub const MAX_CALL_TIMEOUT: Duration = Duration::from_secs(10 * 60);
23
24const LIVENESS_EVERY: Duration = Duration::from_secs(30);
25const LIVENESS_TIMEOUT: Duration = Duration::from_secs(30);
26
27#[derive(Debug, Clone, PartialEq)]
36pub struct Call {
37 pub realm: [u8; 32],
38 pub procedure: String,
39 pub target: [u8; 32],
40 pub payload: Value,
41 pub timeout: Duration,
42 pub token: Option<Vec<u8>>,
43 pub proofs: Vec<Vec<u8>>,
44 pub seal: Option<Seal>,
45}
46
47impl Default for Call {
48 fn default() -> Self {
49 Call {
50 realm: [0; 32],
51 procedure: String::new(),
52 target: [0; 32],
53 payload: Value::Map(Vec::new()),
54 timeout: Duration::ZERO,
55 token: None,
56 proofs: Vec::new(),
57 seal: None,
58 }
59 }
60}
61
62pub(super) struct Pending {
64 pub(super) request: VerifiedRequest,
65 pub(super) seal: Option<CallSeal>,
66 pub(super) outcome: oneshot::Sender<Result<Value, LinkError>>,
67}
68
69impl Link {
70 pub async fn call(&self, c: Call) -> Result<Value, LinkError> {
73 call(&self.inner, c).await
74 }
75}
76
77pub(super) async fn call(inner: &Arc<Inner>, c: Call) -> Result<Value, LinkError> {
78 let timeout = if c.timeout.is_zero() {
79 DEFAULT_CALL_TIMEOUT
80 } else {
81 c.timeout.min(MAX_CALL_TIMEOUT)
82 };
83 stated(&c.target, &inner.station.node_id, &c.seal)?;
84 let target = if c.target == [0; 32] {
85 inner.station.node_id
86 } else {
87 c.target
88 };
89 let mut request_id = [0u8; 16];
90 aws_lc_rs::rand::fill(&mut request_id).map_err(|_| LinkError::Io("no randomness".into()))?;
91 let deadline = (now_ms() + timeout.as_millis() as i64) as u64;
92 let (sealed, call_seal) = sealed_call(inner, &c, target, request_id, deadline)?;
93 let signed = frame::sign_call(
94 &RequestSpec {
95 request_id,
96 realm: c.realm,
97 procedure: c.procedure,
98 target,
99 deadline,
100 payload: c.payload,
101 sealed,
102 mode: None,
103 token: c.token,
104 proofs: c.proofs,
105 source_route: None,
106 retry_budget: None,
107 },
108 &inner.key,
109 )?;
110 let request = frame::verify_request(&signed, inner.profile)?;
111 let (outcome_tx, outcome) = oneshot::channel();
112 await_reply(
113 inner,
114 request_id,
115 Pending {
116 request,
117 seal: call_seal,
118 outcome: outcome_tx,
119 },
120 )?;
121 let forget = || {
122 inner.lock().pending.remove(&request_id);
123 };
124 if let Err(e) = inner.send_control(&signed).await {
125 forget();
126 return Err(e);
127 }
128 let answered = tokio::time::timeout(timeout, outcome).await;
129 forget();
130 match answered {
131 Ok(Ok(outcome)) => outcome,
132 Ok(Err(_)) => Err(inner.lock().ended.clone().unwrap_or(LinkError::Closed)),
133 Err(_) => Err(LinkError::CallTimeout),
134 }
135}
136
137fn sealed_call(
140 inner: &Inner,
141 c: &Call,
142 target: [u8; 32],
143 request_id: [u8; 16],
144 deadline: u64,
145) -> Result<(Option<frame::Sealed>, Option<CallSeal>), LinkError> {
146 match &c.seal {
147 Some(Seal::To(key)) => {
148 let (sealed, s) = sealed_request(
149 inner.profile,
150 key,
151 seal::FRAME_CALL,
152 c.realm,
153 &c.procedure,
154 inner.self_id,
155 target,
156 request_id,
157 deadline,
158 &c.payload,
159 )?;
160 Ok((Some(sealed), Some(s)))
161 }
162 _ => Ok((None, None)),
163 }
164}
165
166fn await_reply(inner: &Inner, request_id: [u8; 16], pending: Pending) -> Result<(), LinkError> {
169 let mut state = inner.lock();
170 if let Some(e) = &state.ended {
171 return Err(e.clone());
172 }
173 state.pending.insert(request_id, pending);
174 Ok(())
175}
176
177pub(super) fn replied(inner: &Arc<Inner>, v: &Value) {
180 let Ok((request_id, _)) = frame::claimed_reply_ids(v) else {
181 inner.count("malformed_reply");
182 return;
183 };
184 let (request, seal) = match inner.lock().pending.get(&request_id) {
185 Some(p) => (p.request.clone(), p.seal.clone()),
186 None => {
187 inner.count("unmatched_reply");
188 return;
189 }
190 };
191 let Some(outcome) = verified_outcome(inner, v, &request, seal.as_ref()) else {
192 inner.count("unverified_reply");
193 return;
194 };
195 if let Some(p) = inner.lock().pending.remove(&request_id) {
196 let _ = p.outcome.send(outcome);
197 }
198}
199
200fn verified_outcome(
201 inner: &Inner,
202 v: &Value,
203 request: &VerifiedRequest,
204 seal: Option<&CallSeal>,
205) -> Option<Result<Value, LinkError>> {
206 if v.get("reply").is_some() {
207 let reply = frame::verify_reply(v, request, inner.profile).ok()?;
208 return Some(reply_outcome(reply, request, seal));
209 }
210 let relayed =
211 frame::verify_relay_error(v, request, inner.profile, &inner.station.node_id).ok()?;
212 Some(Err(LinkError::Relay {
213 reported_by: relayed.reported_by,
214 code: relayed.code,
215 }))
216}
217
218pub(super) async fn probe(link: Weak<Inner>) {
224 let mut misses = 0;
225 loop {
226 let Some(mut done) = link.upgrade().map(|l| l.done_rx.clone()) else {
227 return;
228 };
229 tokio::select! {
230 _ = done.wait_for(|ended| *ended) => return,
231 _ = tokio::time::sleep(LIVENESS_EVERY) => {}
232 }
233 let Some(inner) = link.upgrade() else { return };
234 let outcome = probe_once(&inner).await;
235 misses = if matches!(outcome, Err(LinkError::CallTimeout)) {
236 misses + 1
237 } else {
238 0
239 };
240 if misses >= 2 {
241 inner.end(LinkError::LivenessLost);
242 return;
243 }
244 }
245}
246
247async fn probe_once(inner: &Inner) -> Result<(), LinkError> {
250 let mut nonce = [0u8; frame::LIVENESS_NONCE_SIZE];
251 aws_lc_rs::rand::fill(&mut nonce).map_err(|_| LinkError::Io("no randomness".into()))?;
252 let mut pongs = inner.pongs_rx.lock().await;
253 while pongs.try_recv().is_ok() {}
256 inner
257 .send_control(&frame::liveness_ping_frame(&nonce))
258 .await?;
259 let mut done = inner.done_rx.clone();
260 let answered = pong_of(&mut pongs, nonce);
261 tokio::select! {
262 _ = done.wait_for(|ended| *ended) => Err(LinkError::Closed),
263 outcome = tokio::time::timeout(LIVENESS_TIMEOUT, answered) => {
264 outcome.unwrap_or(Err(LinkError::CallTimeout))
265 }
266 }
267}
268
269async fn pong_of(
272 pongs: &mut tokio::sync::mpsc::Receiver<[u8; frame::LIVENESS_NONCE_SIZE]>,
273 nonce: [u8; frame::LIVENESS_NONCE_SIZE],
274) -> Result<(), LinkError> {
275 while let Some(pong) = pongs.recv().await {
276 if pong == nonce {
277 return Ok(());
278 }
279 }
280 Err(LinkError::Closed)
281}