1use std::future::Future;
14use std::sync::Arc;
15use std::time::Duration;
16
17use tokio::time::Instant;
18
19use crate::cbor::Value;
20use crate::frame::StreamMode;
21use crate::record::{self, RecordType, Trust, Verified};
22use crate::station_link::{self, Link, LinkError, Stream, DEFAULT_CALL_TIMEOUT};
23use crate::transport::Target;
24
25use super::{Pool, PoolError, PoolInner};
26
27const MIN_CANDIDATE_SHARE: Duration = Duration::from_secs(1);
29
30#[derive(Debug, Clone, PartialEq)]
35pub struct Call {
36 pub realm: [u8; 32],
37 pub procedure: String,
38 pub provider: [u8; 32],
39 pub payload: Value,
40 pub timeout: Duration,
41 pub token: Option<Vec<u8>>,
42 pub proofs: Vec<Vec<u8>>,
43}
44
45impl Default for Call {
46 fn default() -> Self {
47 Call {
48 realm: [0; 32],
49 procedure: String::new(),
50 provider: [0; 32],
51 payload: Value::Map(Vec::new()),
52 timeout: Duration::ZERO,
53 token: None,
54 proofs: Vec::new(),
55 }
56 }
57}
58
59#[derive(Debug, Clone, PartialEq)]
64pub struct StreamCall {
65 pub realm: [u8; 32],
66 pub procedure: String,
67 pub provider: [u8; 32],
68 pub mode: StreamMode,
69 pub payload: Value,
70 pub deadline: Duration,
71 pub token: Option<Vec<u8>>,
72 pub proofs: Vec<Vec<u8>>,
73}
74
75impl Default for StreamCall {
76 fn default() -> Self {
77 StreamCall {
78 realm: [0; 32],
79 procedure: String::new(),
80 provider: [0; 32],
81 mode: StreamMode::ServerStream,
82 payload: Value::Map(Vec::new()),
83 deadline: Duration::ZERO,
84 token: None,
85 proofs: Vec::new(),
86 }
87 }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
92pub struct Provider {
93 pub node: [u8; 32],
94 pub station: [u8; 32],
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub(super) struct Candidate {
100 provider: Provider,
101 expires_at: u64,
102 created_at: u64,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, Hash)]
106pub(super) struct ResolvedKey {
107 realm: [u8; 32],
108 procedure: String,
109 provider: [u8; 32],
110}
111
112impl Pool {
113 pub async fn call(&self, c: Call) -> Result<Value, PoolError> {
118 let inner = &self.inner;
119 let realm_key = inner.realm_key_for(&c.realm, &c.procedure)?;
120 let timeout = if c.timeout.is_zero() {
121 DEFAULT_CALL_TIMEOUT
122 } else {
123 c.timeout
124 };
125 let deadline = Instant::now() + timeout;
126 let key = ResolvedKey {
127 realm: c.realm,
128 procedure: c.procedure.clone(),
129 provider: c.provider,
130 };
131 let candidates = bounded(deadline, inner.candidates(&key, realm_key)).await?;
132 let mut tried = Vec::new();
133 let count = candidates.len();
134 for (i, cand) in candidates.into_iter().enumerate() {
135 let share = candidate_share(deadline, count - i);
136 let outcome = bounded(share, inner.call_at(&cand, &c, share)).await;
137 match outcome {
138 Ok(v) => {
139 inner.remember(key, cand);
140 return Ok(v);
141 }
142 Err(e @ PoolError::Link(LinkError::Provider { .. })) => {
143 inner.remember(key, cand);
144 return Err(e);
145 }
146 Err(e) => {
147 inner.forget(&key);
148 tried.push((cand.provider, e));
149 if Instant::now() >= deadline {
150 break;
151 }
152 }
153 }
154 }
155 Err(PoolError::NoProvider(tried))
156 }
157
158 pub async fn providers(
162 &self,
163 realm: &[u8; 32],
164 procedure: &str,
165 ) -> Result<Vec<Provider>, PoolError> {
166 let realm_key = self.inner.realm_key_for(realm, procedure)?;
167 let key = ResolvedKey {
168 realm: *realm,
169 procedure: procedure.to_string(),
170 provider: [0; 32],
171 };
172 let found = self.inner.resolve(&key, realm_key).await?;
173 Ok(found.into_iter().map(|c| c.provider).collect())
174 }
175
176 pub async fn open_stream(&self, c: StreamCall) -> Result<Stream, PoolError> {
180 let inner = &self.inner;
181 let realm_key = inner.realm_key_for(&c.realm, &c.procedure)?;
182 let deadline = Instant::now() + DEFAULT_CALL_TIMEOUT;
183 let key = ResolvedKey {
184 realm: c.realm,
185 procedure: c.procedure.clone(),
186 provider: c.provider,
187 };
188 let candidates = bounded(deadline, inner.candidates(&key, realm_key)).await?;
189 let mut tried = Vec::new();
190 let count = candidates.len();
191 for (i, cand) in candidates.into_iter().enumerate() {
192 let share = candidate_share(deadline, count - i);
193 match bounded(share, inner.open_at(&cand, &c, share)).await {
194 Ok(stream) => {
195 inner.remember(key, cand);
196 return Ok(stream);
197 }
198 Err(e) => {
199 inner.forget(&key);
200 tried.push((cand.provider, e));
201 if Instant::now() >= deadline {
202 break;
203 }
204 }
205 }
206 }
207 Err(PoolError::NoProvider(tried))
208 }
209
210 pub async fn station_target(&self, station: &[u8; 32]) -> Result<Target, PoolError> {
213 self.inner.station_target(station).await
214 }
215
216 pub async fn link_to(&self, station: &[u8; 32]) -> Result<Link, PoolError> {
221 let deadline = Instant::now() + DEFAULT_CALL_TIMEOUT;
222 self.inner.link_to(station, deadline).await
223 }
224
225 pub async fn find_record(&self, key: &[u8; 32]) -> Result<Verified, PoolError> {
227 self.inner
228 .first_answer(|l| async move { l.find_record(key).await })
229 .await
230 }
231
232 pub async fn find_records(&self, key: &[u8; 32]) -> Result<(Vec<Verified>, usize), PoolError> {
235 self.inner
236 .first_answer(|l| async move { l.find_records(key).await })
237 .await
238 }
239
240 pub async fn find_records_by_type(
243 &self,
244 t: RecordType,
245 ) -> Result<(Vec<Verified>, usize), PoolError> {
246 self.inner
247 .first_answer(|l| async move { l.find_records_by_type(t).await })
248 .await
249 }
250
251 pub async fn put_record(&self, wire: &[u8]) -> Result<(), PoolError> {
253 self.inner
254 .first_answer(|l| async move { l.put_record(wire).await })
255 .await
256 }
257}
258
259impl PoolInner {
260 fn remember(&self, key: ResolvedKey, cand: Candidate) {
261 self.lock().remember.insert(key, cand);
262 }
263
264 fn forget(&self, key: &ResolvedKey) {
265 self.lock().remember.remove(key);
266 }
267
268 async fn candidates(
271 &self,
272 key: &ResolvedKey,
273 realm_key: Option<Vec<u8>>,
274 ) -> Result<Vec<Candidate>, PoolError> {
275 let remembered = self.lock().remember.get(key).copied();
276 if let Some(cand) = remembered {
277 if cand.expires_at as i64 > now_ms() && self.linked_to(&cand.provider.station).is_some()
278 {
279 return Ok(vec![cand]);
280 }
281 }
282 self.resolve(key, realm_key).await
283 }
284
285 async fn resolve(
288 &self,
289 key: &ResolvedKey,
290 realm_key: Option<Vec<u8>>,
291 ) -> Result<Vec<Candidate>, PoolError> {
292 let slot = record::procedure_key(&key.realm, &key.procedure);
293 let (found, _) = self
294 .first_answer(|l| async move { l.find_records(&slot).await })
295 .await?;
296 let now = now_ms();
297 let trust = Trust {
298 profile: self.opts.identity.profile(),
299 realm_key,
300 };
301 let mut out: Vec<Candidate> = found
302 .iter()
303 .filter(|v| v.record().record_type == RecordType::PROCEDURE_ADVERTISEMENT)
304 .filter_map(|v| {
305 let ad = record::read_procedure_advertisement(v.record()).ok()?;
306 let wanted = ad.realm_id == key.realm
307 && ad.procedure == key.procedure
308 && (key.provider == [0; 32] || ad.advertiser_node == key.provider);
309 if !wanted || record::verify_authorization(v, &trust, now).is_err() {
310 return None;
311 }
312 Some(Candidate {
313 provider: Provider {
314 node: ad.advertiser_node,
315 station: ad.serving_station,
316 },
317 expires_at: v.record().expires_at,
318 created_at: v.record().created_at,
319 })
320 })
321 .collect();
322 if out.is_empty() {
323 return Err(PoolError::NoProvider(Vec::new()));
324 }
325 out.sort_by_key(|c| std::cmp::Reverse(c.created_at));
326 Ok(out)
327 }
328
329 async fn call_at(
330 self: &Arc<Self>,
331 cand: &Candidate,
332 c: &Call,
333 share: Instant,
334 ) -> Result<Value, PoolError> {
335 let link = self.link_to(&cand.provider.station, share).await?;
336 let left = share.saturating_duration_since(Instant::now());
337 Ok(link
338 .call(station_link::Call {
339 realm: c.realm,
340 procedure: c.procedure.clone(),
341 target: cand.provider.node,
342 payload: c.payload.clone(),
343 timeout: left.max(Duration::from_millis(1)),
344 token: c.token.clone(),
345 proofs: c.proofs.clone(),
346 })
347 .await?)
348 }
349
350 async fn open_at(
351 self: &Arc<Self>,
352 cand: &Candidate,
353 c: &StreamCall,
354 share: Instant,
355 ) -> Result<Stream, PoolError> {
356 let link = self.link_to(&cand.provider.station, share).await?;
357 Ok(link
358 .open_stream(station_link::StreamCall {
359 realm: c.realm,
360 procedure: c.procedure.clone(),
361 target: cand.provider.node,
362 mode: c.mode,
363 payload: c.payload.clone(),
364 deadline: c.deadline,
365 token: c.token.clone(),
366 proofs: c.proofs.clone(),
367 })
368 .await?)
369 }
370
371 fn linked_to(&self, station: &[u8; 32]) -> Option<Link> {
373 self.links()
374 .into_iter()
375 .find(|l| l.station_node_id() == *station)
376 }
377
378 pub(super) async fn link_to(
379 self: &Arc<Self>,
380 station: &[u8; 32],
381 deadline: Instant,
382 ) -> Result<Link, PoolError> {
383 if let Some(link) = self.linked_to(station) {
384 return Ok(link);
385 }
386 let (existing, direct) = {
387 let state = self.lock();
388 if state.closed {
389 return Err(PoolError::Closed);
390 }
391 let existing = state
392 .members
393 .iter()
394 .find(|m| m.target.expected_node_id == *station)
395 .cloned();
396 (existing, state.members.iter().filter(|m| m.direct).count())
397 };
398 let fresh = existing.is_none();
399 let member = match existing {
400 Some(m) => m,
401 None => {
402 if direct >= self.opts.max_direct_links {
403 return Err(PoolError::DirectLinksFull);
404 }
405 let target = bounded(deadline, self.station_target(station)).await?;
406 self.start_member(target, true)
407 }
408 };
409 if let Some(link) = member.await_up(deadline, fresh).await {
410 return Ok(link);
411 }
412 if fresh {
413 self.drop_member(&member);
414 }
415 Err(PoolError::StationNotReached {
416 station: *station,
417 cause: member.last_error(),
418 })
419 }
420
421 async fn station_target(&self, station: &[u8; 32]) -> Result<Target, PoolError> {
422 let slot = record::station_endpoint_key(station);
423 let verified = self
424 .first_answer(|l| async move { l.find_record(&slot).await })
425 .await
426 .map_err(|e| match e {
427 PoolError::Link(link) => PoolError::NoStationEndpoint(Some(link)),
428 other => other,
429 })?;
430 let r = verified.record();
431 let signer = r.signed.as_ref().map(|s| s.key_id);
432 let endpoint = record::read_station_endpoint(r)
433 .map_err(|e| PoolError::NoStationEndpoint(Some(e.into())))?;
434 match (signer, endpoint.host_advertised.first()) {
435 (Some(signer), Some(host)) if signer == *station && endpoint.quic_port != 0 => {
436 Ok(Target {
437 host: host.clone(),
438 port: endpoint.quic_port,
439 profile: self.opts.identity.profile(),
440 expected_node_id: *station,
441 })
442 }
443 _ => Err(PoolError::NoStationEndpoint(None)),
444 }
445 }
446
447 pub(super) async fn first_answer<'a, T, F, Fut>(&self, ask: F) -> Result<T, PoolError>
451 where
452 F: Fn(Link) -> Fut,
453 Fut: Future<Output = Result<T, LinkError>> + 'a,
454 {
455 let links = self.links();
456 if links.is_empty() {
457 return Err(PoolError::NoLink(Vec::new()));
458 }
459 let mut errors = Vec::new();
460 for link in links {
461 match ask(link).await {
462 Err(e) if unreachable(&e) => errors.push(e),
463 answered => return answered.map_err(PoolError::Link),
464 }
465 }
466 Err(PoolError::NoLink(errors))
467 }
468}
469
470fn unreachable(e: &LinkError) -> bool {
473 matches!(
474 e,
475 LinkError::CallTimeout
476 | LinkError::Closed
477 | LinkError::LivenessLost
478 | LinkError::Io(_)
479 | LinkError::Goodbye(_)
480 | LinkError::StatusExpired
481 | LinkError::BindingExpired
482 )
483}
484
485fn candidate_share(deadline: Instant, left: usize) -> Instant {
489 let now = Instant::now();
490 let remaining = deadline.saturating_duration_since(now);
491 (now + (remaining / left.max(1) as u32).max(MIN_CANDIDATE_SHARE)).min(deadline)
492}
493
494async fn bounded<T>(
496 deadline: Instant,
497 work: impl Future<Output = Result<T, PoolError>>,
498) -> Result<T, PoolError> {
499 tokio::time::timeout_at(deadline, work)
500 .await
501 .unwrap_or(Err(PoolError::Link(LinkError::CallTimeout)))
502}
503
504fn now_ms() -> i64 {
505 crate::uuid_v7::now_ms() as i64
506}