1mod admission;
19mod authorize;
20mod call;
21mod confidential;
22mod dht;
23mod framing;
24mod pubsub;
25mod report;
26mod serve;
27mod stream;
28mod versions;
29
30pub use admission::{Admission, AdmissionLimits};
31pub use call::{Call, DEFAULT_CALL_TIMEOUT, MAX_CALL_TIMEOUT};
32pub use confidential::{
33 is_clear_refusal, Confidentiality, ConfidentialityError, ConfidentialityReason, Seal,
34};
35pub use pubsub::{Event, EventDedup, Publication, PublicationSeq, SignedPublication, Subscription};
36pub use report::{Report, ReportError};
37pub use serve::{handler, BoxFuture, Handler, Offer, Request, Served, StreamOffer};
38pub(crate) use stream::Reseal;
39pub use stream::{
40 stream_handler, Stream, StreamCall, StreamEvent, StreamHandler, DEFAULT_STREAM_DEADLINE,
41};
42pub use versions::handshake_counters;
43
44use std::collections::HashMap;
45use std::fmt;
46use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
47use std::sync::{Arc, Mutex, MutexGuard};
48use std::time::Duration;
49
50use tokio::sync::watch;
51
52use crate::cbor::{self, Value};
53use crate::frame::{self, Envelope, FrameError, Liveness};
54use crate::handshake::{self, ClientSession, Exporter, HandshakeError, Peer, Ranges, Station};
55use crate::node_key::NodeKey;
56use crate::profile::Profile;
57use crate::record::RecordError;
58use crate::seal::Keyring;
59use crate::statement_issuer::{
60 ConnectMaterial, IssuerError, StatementIssuer, StatementSubscription,
61};
62use crate::transport::{self, DialError, Target};
63
64use framing::{read_frame, FrameWriter, HANDSHAKE_FRAME_BYTES, MAX_FRAME_BYTES};
65
66pub const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(30);
68
69const CLOSE_LINGER: Duration = Duration::from_secs(1);
72
73const STATUS_GRACE_MS: i64 = 5 * 60 * 1000;
76
77#[derive(Debug, Clone, PartialEq, Eq)]
79pub enum LinkError {
80 InvalidConfig(String),
83 Dial(String),
85 Handshake(HandshakeError),
87 HandshakeTimeout,
89 Issuer(IssuerError),
91 Io(String),
93 FrameTooLarge(usize),
95 Frame(FrameError),
98 Record(RecordError),
100 StatusExpired,
102 BindingExpired,
104 Closed,
106 Goodbye(String),
108 LivenessLost,
110 CallTimeout,
112 Provider {
114 responded_by: [u8; 32],
115 code: String,
116 detail: Option<String>,
117 },
118 Relay { reported_by: [u8; 32], code: String },
120 RecordNotFound,
122 RecordRefused(String),
124 ReplyVersionUnsupported,
127 UnexpectedReply(String),
129 InvalidOffer,
132 NoOrg,
134 AlreadyServed,
136 KemAdvertiseDisabled,
139 Stopped,
141 RegisterRefused {
144 kind: frame::RegisterKind,
145 reason: String,
146 },
147 Stream {
150 code: String,
151 message: String,
152 relay: bool,
153 },
154 EndOfStream,
156 StreamClosed,
158 StreamOpenTooLarge(usize),
160 Confidentiality(ConfidentialityError),
163 SealedRefused { named: Option<[u8; 8]> },
166 ClearAnswerToSealed,
170 SealedFramesExhausted,
173}
174
175impl fmt::Display for LinkError {
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 match self {
178 LinkError::Provider {
179 code,
180 detail: Some(d),
181 ..
182 } => write!(f, "the provider answered {code}: {d}"),
183 LinkError::Provider { code, .. } => write!(f, "the provider answered {code}"),
184 LinkError::Relay { code, .. } => {
185 write!(f, "the station could not relay the call: {code}")
186 }
187 LinkError::Stream { code, message, .. } if !message.is_empty() => {
188 write!(f, "stream error {code}: {message}")
189 }
190 LinkError::Stream { code, .. } => write!(f, "stream error {code}"),
191 LinkError::Handshake(e) => write!(f, "handshake: {e}"),
192 LinkError::Frame(e) => write!(f, "frame: {e}"),
193 LinkError::Record(e) => write!(f, "record: {e}"),
194 LinkError::Issuer(e) => write!(f, "{e}"),
195 LinkError::Goodbye(reason) => write!(f, "the station said goodbye: {reason}"),
196 LinkError::RegisterRefused { kind, reason } => {
197 write!(f, "the station refused the {kind:?}: {reason}")
198 }
199 LinkError::Confidentiality(e) => write!(f, "{e}"),
200 LinkError::SealedRefused { named: Some(id) } => write!(
201 f,
202 "the provider could not open the request; it holds key {}",
203 id.iter().map(|b| format!("{b:02x}")).collect::<String>()
204 ),
205 LinkError::SealedRefused { named: None } => {
206 f.write_str("the provider opens no sealed payload")
207 }
208 LinkError::ClearAnswerToSealed => f.write_str("a clear answer to a sealed request"),
209 LinkError::KemAdvertiseDisabled => {
210 f.write_str("a required-confidential procedure needs kem_advertise on")
211 }
212 LinkError::SealedFramesExhausted => {
213 f.write_str("this sealed stream has sealed all the frames it may")
214 }
215 other => write!(f, "{other:?}"),
216 }
217 }
218}
219
220impl std::error::Error for LinkError {}
221
222impl From<FrameError> for LinkError {
223 fn from(e: FrameError) -> Self {
224 LinkError::Frame(e)
225 }
226}
227
228impl From<RecordError> for LinkError {
229 fn from(e: RecordError) -> Self {
230 LinkError::Record(e)
231 }
232}
233
234impl From<HandshakeError> for LinkError {
235 fn from(e: HandshakeError) -> Self {
236 LinkError::Handshake(e)
237 }
238}
239
240impl From<DialError> for LinkError {
241 fn from(e: DialError) -> Self {
242 LinkError::Dial(e.to_string())
243 }
244}
245
246pub struct Config {
252 pub target: Target,
253 pub identity: Arc<NodeKey>,
254 pub issuer: StatementIssuer,
255 pub member_endorsement: Vec<u8>,
256 pub publication_seq: Option<Arc<PublicationSeq>>,
257 pub admission: Option<Arc<Admission>>,
258 pub dedup: Option<Arc<EventDedup>>,
259 pub share: Option<String>,
262 pub keyring: Option<Arc<Keyring>>,
266 pub kem_advertise: bool,
272}
273
274impl Config {
275 pub fn new(target: Target, identity: Arc<NodeKey>, issuer: StatementIssuer) -> Config {
277 Config {
278 target,
279 identity,
280 issuer,
281 member_endorsement: Vec::new(),
282 publication_seq: None,
283 admission: None,
284 dedup: None,
285 share: None,
286 keyring: None,
287 kem_advertise: false,
288 }
289 }
290}
291
292#[derive(Clone)]
294pub struct Link {
295 inner: Arc<Inner>,
296}
297
298struct Inner {
299 serial: u64,
301 connection: quinn::Connection,
302 _endpoint: quinn::Endpoint,
303 control: FrameWriter,
304 profile: Profile,
305 key: Arc<NodeKey>,
306 self_id: [u8; 32],
307 station: Station,
308 station_capabilities: u64,
309 pongs: tokio::sync::mpsc::Sender<[u8; frame::LIVENESS_NONCE_SIZE]>,
311 pong_in_flight: AtomicBool,
314 pongs_rx: tokio::sync::Mutex<tokio::sync::mpsc::Receiver<[u8; frame::LIVENESS_NONCE_SIZE]>>,
315 status_deadline: AtomicI64,
316 publication_seq: Arc<PublicationSeq>,
317 admission: Arc<Admission>,
318 dedup: Arc<EventDedup>,
319 share: String,
320 keyring: Option<Arc<Keyring>>,
321 kem_advertise: bool,
322 state: Mutex<State>,
323 done_tx: watch::Sender<bool>,
324 done_rx: watch::Receiver<bool>,
325}
326
327struct State {
328 ended: Option<LinkError>,
329 closing: bool,
332 unrouted: HashMap<String, u64>,
333 pending: HashMap<[u8; 16], call::Pending>,
334 subs: HashMap<([u8; 32], String), Vec<pubsub::SubscriberSlot>>,
335 served: HashMap<([u8; 32], String), serve::ServedEntry>,
336 streams: Vec<std::sync::Weak<stream::StreamInner>>,
337}
338
339impl Link {
340 pub async fn dial(cfg: Config) -> Result<Link, LinkError> {
344 if cfg.identity.profile() != cfg.target.profile {
345 return Err(LinkError::InvalidConfig(
346 "the identity key is of another profile than the target's".into(),
347 ));
348 }
349 if let Some(admission) = &cfg.admission {
350 admission.limits().validate()?;
351 }
352 if cfg
353 .keyring
354 .as_ref()
355 .is_some_and(|k| k.profile() != cfg.target.profile)
356 {
357 return Err(LinkError::InvalidConfig(
358 "the keyring is of another profile than the target's".into(),
359 ));
360 }
361 if cfg.kem_advertise && cfg.keyring.is_none() {
362 return Err(LinkError::InvalidConfig(
363 "kem_advertise names a key: it needs a keyring".into(),
364 ));
365 }
366 dial_once(&cfg).await
367 }
368
369 pub fn handshake_version(&self) -> i64 {
371 self.inner.station.version
372 }
373
374 pub fn station_node_id(&self) -> [u8; 32] {
376 self.inner.station.node_id
377 }
378
379 pub fn serial(&self) -> u64 {
381 self.inner.serial
382 }
383
384 pub fn node_id(&self) -> [u8; 32] {
386 self.inner.self_id
387 }
388
389 pub fn station_capabilities(&self) -> u64 {
391 self.inner.station_capabilities
392 }
393
394 pub fn profile(&self) -> Profile {
396 self.inner.profile
397 }
398
399 pub fn error(&self) -> Option<LinkError> {
401 self.inner.lock().ended.clone()
402 }
403
404 pub async fn done(&self) -> LinkError {
406 let mut done = self.inner.done_rx.clone();
407 let _ = done.wait_for(|ended| *ended).await;
408 self.error().unwrap_or(LinkError::Closed)
409 }
410
411 pub fn unrouted(&self) -> HashMap<String, u64> {
414 self.inner.lock().unrouted.clone()
415 }
416
417 pub async fn close(&self, reason: &str) -> Result<(), LinkError> {
421 if !self.inner.mark_closing() {
422 return Ok(());
423 }
424 let goodbye = frame::goodbye_frame(reason, None)?;
425 let sent = self.inner.send_control(&goodbye).await;
426 if sent.is_ok() {
427 self.inner.control.finish().await;
428 let _ = tokio::time::timeout(CLOSE_LINGER, self.inner.connection.closed()).await;
429 }
430 self.inner.end(LinkError::Closed);
431 sent
432 }
433}
434
435async fn dial_once(cfg: &Config) -> Result<Link, LinkError> {
438 tokio::time::timeout(HANDSHAKE_TIMEOUT, async {
439 let dialed = transport::dial_target(&cfg.target).await?;
440 let connection = dialed.connection.clone();
441 let linked = handshaken(cfg, dialed).await;
442 if let Err(e) = &linked {
443 versions::count_refusal(e);
444 connection.close(0u32.into(), b"handshake failed");
445 }
446 linked
447 })
448 .await
449 .map_err(|_| LinkError::HandshakeTimeout)
450 .and_then(|linked| linked)
451}
452
453async fn handshaken(cfg: &Config, dialed: transport::Dialed) -> Result<Link, LinkError> {
458 let export = keying_exporter(dialed.connection.clone());
459 let export: &Exporter = &export;
460 let (send, mut recv) = dialed
461 .connection
462 .open_bi()
463 .await
464 .map_err(|e| LinkError::Io(format!("open the control stream: {e}")))?;
465 let control = FrameWriter::new(send);
466 control
467 .write(
468 &handshake::opener(&Ranges::supported(cfg.target.profile)),
469 HANDSHAKE_FRAME_BYTES,
470 )
471 .await?;
472 let challenge = read_frame(&mut recv, HANDSHAKE_FRAME_BYTES).await?;
473 let material = cfg.issuer.connect_material().map_err(LinkError::Issuer)?;
474 let (connect, station) = handshake::answer_challenge(
475 &challenge,
476 &client_session(cfg, &dialed.leaf, &material, export),
477 )?;
478 control.write(&connect, HANDSHAKE_FRAME_BYTES).await?;
479 let hello = read_frame(&mut recv, HANDSHAKE_FRAME_BYTES).await?;
480 let capabilities = handshake::read_hello(&hello, &station)?;
481 versions::completed(&station.node_id, station.version);
482 let self_id = cfg
483 .identity
484 .node_id()
485 .map_err(|e| LinkError::InvalidConfig(e.to_string()))?;
486 let statements = cfg
487 .issuer
488 .subscribe(&material.binding)
489 .map_err(LinkError::Issuer)?;
490 let inner = new_inner(cfg, dialed, control, station, capabilities, self_id);
491 spawn_link_tasks(&inner, statements, recv);
492 Ok(Link { inner })
493}
494
495fn keying_exporter(
498 connection: quinn::Connection,
499) -> impl Fn(&str, &[u8], usize) -> Option<Vec<u8>> + Send + Sync {
500 move |label: &str, context: &[u8], length: usize| {
501 let mut out = vec![0; length];
502 connection
503 .export_keying_material(&mut out, label.as_bytes(), context)
504 .ok()?;
505 Some(out)
506 }
507}
508
509fn client_session<'a>(
515 cfg: &Config,
516 leaf: &'a [u8],
517 material: &'a ConnectMaterial,
518 export: &'a Exporter,
519) -> ClientSession<'a> {
520 ClientSession {
521 profile: cfg.target.profile,
522 expected_node_id: cfg.target.expected_node_id,
523 leaf,
524 identity_key: cfg.identity.public_key(),
525 connect_key: &material.key,
526 connect_binding: &material.binding,
527 connect_status: &material.status,
528 capabilities: handshake::CAP_RELAY_OVERLOADED,
529 now_ms: now_ms(),
530 member_endorsement: cfg.member_endorsement.clone(),
531 ranges: Ranges::supported(cfg.target.profile),
532 floor: &versions::floor,
533 export,
534 }
535}
536
537fn new_inner(
540 cfg: &Config,
541 dialed: transport::Dialed,
542 control: FrameWriter,
543 station: Station,
544 capabilities: u64,
545 self_id: [u8; 32],
546) -> Arc<Inner> {
547 let (done_tx, done_rx) = watch::channel(false);
548 let share = cfg
549 .share
550 .clone()
551 .unwrap_or_else(|| format!("{}:{}", cfg.target.host, cfg.target.port));
552 let (pongs, pongs_rx) = tokio::sync::mpsc::channel(1);
553 static SERIALS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
554 Arc::new(Inner {
555 serial: SERIALS.fetch_add(1, Ordering::Relaxed),
556 connection: dialed.connection,
557 _endpoint: dialed.endpoint,
558 control,
559 profile: cfg.target.profile,
560 key: cfg.identity.clone(),
561 self_id,
562 status_deadline: AtomicI64::new(station.status_expires_at + STATUS_GRACE_MS),
563 station_capabilities: capabilities,
564 pongs,
565 pong_in_flight: AtomicBool::new(false),
566 pongs_rx: tokio::sync::Mutex::new(pongs_rx),
567 station,
568 publication_seq: cfg.publication_seq.clone().unwrap_or_default(),
569 admission: cfg
570 .admission
571 .clone()
572 .unwrap_or_else(|| Arc::new(Admission::new(AdmissionLimits::default()))),
573 dedup: cfg.dedup.clone().unwrap_or_default(),
574 share,
575 keyring: cfg.keyring.clone(),
576 kem_advertise: cfg.kem_advertise,
577 state: Mutex::new(State {
578 ended: None,
579 closing: false,
580 unrouted: HashMap::new(),
581 pending: HashMap::new(),
582 subs: HashMap::new(),
583 served: HashMap::new(),
584 streams: Vec::new(),
585 }),
586 done_tx,
587 done_rx,
588 })
589}
590
591fn spawn_link_tasks(
595 inner: &Arc<Inner>,
596 statements: StatementSubscription,
597 recv: quinn::RecvStream,
598) {
599 tokio::spawn(send_statements(Arc::downgrade(inner), statements));
600 tokio::spawn(read_control(inner.clone(), recv));
601 tokio::spawn(stream::accept_streams(Arc::downgrade(inner)));
602 tokio::spawn(call::probe(Arc::downgrade(inner)));
603 tokio::spawn(watch_expiries(Arc::downgrade(inner)));
604 tokio::spawn(watch_connection(Arc::downgrade(inner)));
605}
606
607impl Inner {
608 fn lock(&self) -> MutexGuard<'_, State> {
609 self.state
610 .lock()
611 .unwrap_or_else(|poisoned| poisoned.into_inner())
612 }
613
614 fn count(&self, what: &str) {
615 *self.lock().unrouted.entry(what.to_string()).or_default() += 1;
616 }
617
618 fn mark_closing(&self) -> bool {
621 let mut state = self.lock();
622 if state.ended.is_some() {
623 return false;
624 }
625 state.closing = true;
626 true
627 }
628
629 async fn send_control(&self, v: &Value) -> Result<(), LinkError> {
631 let encoded =
632 cbor::encode(v).map_err(|e| LinkError::Frame(FrameError::Payload(e.to_string())))?;
633 self.control.write(&encoded, MAX_FRAME_BYTES).await
634 }
635
636 fn end(&self, err: LinkError) {
639 let mut state = self.lock();
640 let Some(err) = state.mark_ended(err) else {
641 return;
642 };
643 let pending = std::mem::take(&mut state.pending);
644 let subs = std::mem::take(&mut state.subs);
645 let served = std::mem::take(&mut state.served);
646 let streams = std::mem::take(&mut state.streams);
647 drop(state);
648 for (_, p) in pending {
649 let _ = p.outcome.send(Err(err.clone()));
650 }
651 drop(subs);
652 for s in served.into_values() {
653 s.end(err.clone());
654 }
655 for s in streams.into_iter().filter_map(|w| w.upgrade()) {
656 stream::StreamInner::end(&s, Some(err.clone()));
657 }
658 self.connection.close(0u32.into(), b"link ended");
659 let _ = self.done_tx.send_replace(true);
660 }
661}
662
663impl State {
664 fn mark_ended(&mut self, err: LinkError) -> Option<LinkError> {
668 if self.ended.is_some() {
669 return None;
670 }
671 let err = if self.closing { LinkError::Closed } else { err };
672 self.ended = Some(err.clone());
673 Some(err)
674 }
675}
676
677async fn send_statements(link: std::sync::Weak<Inner>, mut statements: StatementSubscription) {
680 loop {
681 let Some(done) = link.upgrade().map(|l| l.done_rx.clone()) else {
682 return;
683 };
684 let mut done = done;
685 let statement = tokio::select! {
686 _ = done.wait_for(|ended| *ended) => return,
687 statement = statements.recv() => statement,
688 };
689 let (Some(statement), Some(inner)) = (statement, link.upgrade()) else {
690 return;
691 };
692 if let Err(e) = inner
693 .control
694 .write(&handshake::status_frame(&statement), MAX_FRAME_BYTES)
695 .await
696 {
697 inner.end(e);
698 return;
699 }
700 }
701}
702
703const CONTROL_FRAME_TYPES: &[&str] = &[
706 "status",
707 "liveness_ping",
708 "liveness_pong",
709 "event",
710 "result",
711 "error",
712 "call",
713 "goodbye",
714 "register_ack",
715];
716
717async fn read_control(inner: Arc<Inner>, mut recv: quinn::RecvStream) {
719 let mut done = inner.done_rx.clone();
720 loop {
721 let payload = tokio::select! {
722 _ = done.wait_for(|ended| *ended) => return,
723 payload = read_frame(&mut recv, MAX_FRAME_BYTES) => payload,
724 };
725 let outcome = match payload {
726 Ok(payload) => received(&inner, &payload),
727 Err(e) => Err(e),
728 };
729 if let Err(e) = outcome {
730 inner.end(e);
731 return;
732 }
733 }
734}
735
736fn control_frame(payload: &[u8]) -> Result<Option<Envelope>, FrameError> {
741 let v = cbor::decode(payload).map_err(|_| FrameError::Malformed)?;
742 let envelope = frame::read_envelope(&v)?;
743 if frame::unknown_frame_type(&envelope, CONTROL_FRAME_TYPES).is_some() {
744 return Ok(None);
745 }
746 if frame::is_control_frame(&envelope.frame_type) {
747 frame::read_control(&envelope.frame)?;
748 }
749 Ok(Some(envelope))
750}
751
752fn received(inner: &Arc<Inner>, payload: &[u8]) -> Result<(), LinkError> {
758 let envelope = match control_frame(payload) {
759 Ok(Some(envelope)) => envelope,
760 Ok(None) => {
761 inner.count("unknown_frame_type");
762 return Ok(());
763 }
764 Err(FrameError::ExtensionUnsupported(_)) => {
765 inner.count("extension_unsupported");
766 return Ok(());
767 }
768 Err(e) => return Err(LinkError::Frame(e)),
769 };
770 let v = envelope.frame;
771 match envelope.frame_type.as_str() {
772 "status" => {
773 let expires_at = handshake::read_status_frame(
774 &v,
775 &Peer {
776 profile: inner.profile,
777 identity_key: inner.station.identity_key.clone(),
778 binding: inner.station.tls_binding.clone(),
779 now_ms: now_ms(),
780 },
781 )?;
782 inner
783 .status_deadline
784 .store(expires_at + STATUS_GRACE_MS, Ordering::SeqCst);
785 }
786 "liveness_ping" | "liveness_pong" => {
787 if let Some((kind, nonce)) = frame::liveness_nonce(&v) {
788 return liveness(inner, kind, nonce);
789 }
790 }
791 "event" => pubsub::evented(inner, &v),
792 "result" | "error" => call::replied(inner, &v),
793 "call" => serve::called(inner, &v),
794 "register_ack" => registered(inner, &v)?,
795 "goodbye" => {
796 let reason = match v.get("reason") {
797 Some(Value::Text(r)) => r.clone(),
798 _ => String::new(),
799 };
800 return Err(LinkError::Goodbye(reason));
801 }
802 other => inner.count(other),
803 }
804 Ok(())
805}
806
807fn registered(inner: &Arc<Inner>, v: &Value) -> Result<(), LinkError> {
812 let Some(ack) = frame::read_register_ack(v)? else {
813 inner.count("register_ack_unknown_kind");
814 return Ok(());
815 };
816 let outcome = ack.refusal.as_deref().unwrap_or("accepted");
817 inner.count(&format!("register_ack_{:?}_{outcome}", ack.kind).to_lowercase());
818 let Some(reason) = ack.refusal.clone() else {
819 return Ok(());
820 };
821 let refused = LinkError::RegisterRefused {
822 kind: ack.kind,
823 reason,
824 };
825 match (ack.kind, &ack.answered) {
826 (frame::RegisterKind::Advertise, frame::Answered::Advertisement { record_version }) => {
827 serve::advertise_refused(
828 inner,
829 &(ack.realm, ack.name.clone()),
830 record_version,
831 refused,
832 )
833 }
834 (frame::RegisterKind::Subscribe, answered)
835 if subscribe_ack_is_ours(answered, &inner.self_id) =>
836 {
837 pubsub::subscribe_refused(inner, &(ack.realm, ack.name.clone()))
838 }
839 (frame::RegisterKind::Subscribe, _) => inner.count("register_ack_subscribe_not_ours"),
840 _ => {}
841 }
842 Ok(())
843}
844
845fn subscribe_ack_is_ours(answered: &frame::Answered, self_id: &[u8; 32]) -> bool {
848 matches!(answered, frame::Answered::Subscription { subscriber } if subscriber == self_id)
849}
850
851fn liveness(
854 inner: &Arc<Inner>,
855 kind: Liveness,
856 nonce: [u8; frame::LIVENESS_NONCE_SIZE],
857) -> Result<(), LinkError> {
858 match kind {
859 Liveness::Ping if !pong_slot(&inner.pong_in_flight) => {}
863 Liveness::Ping => {
864 tokio::spawn(send_pong(inner.clone(), nonce));
865 }
866 Liveness::Pong => {
867 let _ = inner.pongs.try_send(nonce);
868 }
869 }
870 Ok(())
871}
872
873async fn send_pong(inner: Arc<Inner>, nonce: [u8; frame::LIVENESS_NONCE_SIZE]) {
876 let sent = inner
877 .send_control(&frame::liveness_pong_frame(&nonce))
878 .await;
879 inner.pong_in_flight.store(false, Ordering::Release);
880 if let Err(e) = sent {
881 inner.end(e);
882 }
883}
884
885fn pong_slot(in_flight: &AtomicBool) -> bool {
887 !in_flight.swap(true, Ordering::AcqRel)
888}
889
890async fn watch_expiries(link: std::sync::Weak<Inner>) {
893 loop {
894 let Some(inner) = link.upgrade() else { return };
895 let now = now_ms();
896 if now >= inner.station.binding_not_after {
897 inner.end(LinkError::BindingExpired);
898 return;
899 }
900 let status_deadline = inner.status_deadline.load(Ordering::SeqCst);
901 if now >= status_deadline {
902 inner.end(LinkError::StatusExpired);
903 return;
904 }
905 let wait = (status_deadline.min(inner.station.binding_not_after) - now).clamp(1, 60_000);
906 let mut done = inner.done_rx.clone();
907 drop(inner);
908 tokio::select! {
909 _ = done.wait_for(|ended| *ended) => return,
910 _ = tokio::time::sleep(Duration::from_millis(wait as u64)) => {}
911 }
912 }
913}
914
915async fn watch_connection(link: std::sync::Weak<Inner>) {
917 let Some(connection) = link.upgrade().map(|l| l.connection.clone()) else {
918 return;
919 };
920 let cause = connection.closed().await;
921 if let Some(inner) = link.upgrade() {
922 inner.end(LinkError::Io(cause.to_string()));
923 }
924}
925
926fn now_ms() -> i64 {
927 crate::uuid_v7::now_ms() as i64
928}
929
930#[cfg(test)]
931mod tests {
932 use super::*;
933
934 #[test]
939 fn a_refused_subscribe_of_another_subscriber_is_not_ours() {
940 let me = [2u8; 32];
941 let ours =
942 |subscriber| subscribe_ack_is_ours(&frame::Answered::Subscription { subscriber }, &me);
943 assert!(ours(me));
944 assert!(!ours([1u8; 32]));
945 let advert = frame::Answered::Advertisement {
946 record_version: [0; 16],
947 };
948 assert!(!subscribe_ack_is_ours(&advert, &me));
949 }
950
951 #[test]
952 fn a_burst_of_pings_starts_one_pong_at_a_time() {
953 let in_flight = AtomicBool::new(false);
954 let taken = (0..1000).filter(|_| pong_slot(&in_flight)).count();
955 assert_eq!(taken, 1);
956 in_flight.store(false, Ordering::Release);
957 assert!(pong_slot(&in_flight), "free again once the pong is written");
958 }
959
960 #[test]
964 fn the_wire_1_0_envelope_vectors() {
965 let file = crate::wire_vectors::load("envelope");
966 for c in &file.cases {
967 let before = frame::wire_counters();
968 let (outcome, reason, quic_code) = envelope_verdict(c);
969 let after = frame::wire_counters();
970 assert_eq!((outcome, reason), (c.outcome(), c.reason()), "{}", c.id);
971 let code = c.expect_int("quic_code");
972 assert!(
973 code.is_none() || code == quic_code.map(i64::from),
974 "{}",
975 c.id
976 );
977 assert!(
978 counted(c.expect_text("count"), &before, &after),
979 "{}: not counted",
980 c.id
981 );
982 }
983 }
984
985 fn counted(name: &str, before: &frame::WireCounts, after: &frame::WireCounts) -> bool {
988 let ext = |w: &frame::WireCounts| w.ext_ignored.values().sum::<u64>();
989 match name {
990 "wire_unknown_frame_type" => after.unknown_frame_type > before.unknown_frame_type,
991 "wire_ext_ignored" => ext(after) > ext(before),
992 "wire_ext_refused" => {
993 let refused = |w: &frame::WireCounts| w.ext_refused.values().sum::<u64>();
994 refused(after) > refused(before)
995 }
996 _ => true,
997 }
998 }
999
1000 fn envelope_verdict(
1002 c: &crate::wire_vectors::Case,
1003 ) -> (&'static str, &'static str, Option<u32>) {
1004 if c.has("length_header") {
1005 let length = u32::from_be_bytes(c.hex("length_header").try_into().unwrap());
1006 return match framing::length_within(length as usize, MAX_FRAME_BYTES) {
1007 Ok(()) => ("accept", "", None),
1008 Err(_) => ("refuse", "frame_too_large", None),
1009 };
1010 }
1011 let payload = c.hex("frame");
1012 if c.text("position") == "dedicated_stream" {
1013 return match stream::unknown_on_stream(&payload) {
1014 Some(code) => ("abort_stream", "", Some(code)),
1015 None => ("accept", "", None),
1016 };
1017 }
1018 match control_frame(&payload) {
1019 Ok(Some(_)) => ("accept", "", None),
1020 Ok(None) => ("ignore", "", None),
1021 Err(FrameError::ExtensionUnsupported(_)) => ("refuse", "extension_unsupported", None),
1022 Err(_) => ("refuse", "malformed_frame", None),
1023 }
1024 }
1025}