Skip to main content

sipx_ua/
flows.rs

1//! Several registrations for one device, one per flow (RFC 5626 §4.2).
2//!
3//! The reason to register more than once is stated in §4.2: "a UA MUST send a REGISTER request to
4//! each of the outbound proxies in the outbound-proxy-set", so that a proxy going away does not
5//! take the user's reachability with it. That only works if the flows are genuinely independent,
6//! which is a statement about *this* code rather than about the protocol: a set that reports one
7//! `Result` for the whole batch cannot help but let one failure stand for all of them.
8//!
9//! So there is no aggregate `Result` here. [`Flows::register`] returns an outcome per flow, and
10//! the failure of one is a fact about that flow — recorded, backed off according to §4.5, and
11//! retried on its own schedule.
12
13use std::time::Duration;
14
15use sipx_transport::{Handle, Target};
16
17use crate::agent::{Config, Flow, UserAgent};
18use crate::error::{Error, Result};
19use crate::outbound::{self, InstanceId, RegId};
20use crate::registrar::Lease;
21
22/// One flow's registration, and what has happened to it.
23#[derive(Debug)]
24struct Registered {
25    agent: UserAgent,
26    reg_id: RegId,
27    /// Consecutive failures, which is the exponent in §4.5's backoff.
28    failures: u32,
29    /// The lease from the last success, if the flow is up.
30    lease: Option<Lease>,
31}
32
33/// What one flow's registration attempt produced.
34#[derive(Debug)]
35pub struct Attempt {
36    /// Which flow.
37    pub reg_id: RegId,
38    /// Whether the registrar reported an Outbound registration (RFC 5626 §6).
39    pub flow_accepted: bool,
40    /// The lease, or why there is none.
41    pub outcome: Result<Lease>,
42    /// How long to wait before retrying, when this attempt failed (RFC 5626 §4.5).
43    ///
44    /// `None` on success. Computed with the *whole set* in view, because §4.5's base interval
45    /// depends on whether any flow is still up: 30 seconds when none is, 90 when one is.
46    pub retry_after: Option<Duration>,
47}
48
49/// Every flow registered for one device.
50///
51/// The instance ID is shared — it identifies the device, not the flow — and each flow gets its own
52/// `reg-id`, numbered from the order flows were added. §4.2 requires that numbering to be stable
53/// across reboots, which is why it comes from position rather than from an allocator.
54#[derive(Debug)]
55pub struct Flows {
56    instance: InstanceId,
57    flows: Vec<Registered>,
58}
59
60impl Flows {
61    /// An empty set for a device.
62    ///
63    /// The instance ID should be **loaded from storage, not generated here** on every start.
64    /// §4.1 requires it to be persistent, and a UA that mints a fresh one each time accumulates
65    /// dead bindings at the registrar and looks to it like a growing crowd of identical devices.
66    #[must_use]
67    pub fn for_instance(instance: InstanceId) -> Self {
68        Self {
69            instance,
70            flows: Vec::new(),
71        }
72    }
73
74    /// The device identity every flow in this set registers under.
75    #[must_use]
76    pub fn instance(&self) -> &InstanceId {
77        &self.instance
78    }
79
80    /// Add a flow to an outbound proxy, taking the next `reg-id`.
81    ///
82    /// Returns the `reg-id` assigned, which the caller should persist alongside the proxy it
83    /// belongs to: §4.2 wants the same number for the same flow after a restart.
84    ///
85    /// Fails only if the set has grown past `reg-id`'s range, which would take 2^31 proxies.
86    pub fn add(&mut self, endpoint: Handle, config: Config, target: Target) -> Result<RegId> {
87        let next = u32::try_from(self.flows.len())
88            .ok()
89            .and_then(|count| count.checked_add(1))
90            .and_then(RegId::new)
91            .ok_or(Error::TooManyFlows)?;
92        let mut config = config;
93        config.target = target;
94        let config = config.with_outbound(Flow {
95            instance: self.instance.clone(),
96            reg_id: next,
97        });
98        self.flows.push(Registered {
99            agent: UserAgent::new(endpoint, config),
100            reg_id: next,
101            failures: 0,
102            lease: None,
103        });
104        Ok(next)
105    }
106
107    /// How many flows are currently registered.
108    #[must_use]
109    pub fn active(&self) -> usize {
110        self.flows
111            .iter()
112            .filter(|flow| flow.lease.is_some())
113            .count()
114    }
115
116    /// Whether any flow is up.
117    ///
118    /// This is the question §4.5's backoff turns on, and the reason a set is worth having: a UA
119    /// with one working flow is reachable, and hurrying to re-establish the others only adds load
120    /// to a registrar that is plainly having a bad day.
121    #[must_use]
122    pub fn any_active(&self) -> bool {
123        self.active() > 0
124    }
125
126    /// The flows that are up, by `reg-id`.
127    #[must_use]
128    pub fn active_flows(&self) -> Vec<RegId> {
129        self.flows
130            .iter()
131            .filter(|flow| flow.lease.is_some())
132            .map(|flow| flow.reg_id)
133            .collect()
134    }
135
136    /// Register every flow, and report what each one did.
137    ///
138    /// **One flow's failure is not the set's failure**, which is the entire point. Every flow is
139    /// attempted regardless of what the others did, and each result is returned separately —
140    /// there is deliberately no `Result` wrapping the whole call for a caller to `?` on.
141    ///
142    /// Sequential rather than concurrent: the flows share a device and a set of credentials, and a
143    /// registrar that is going to challenge will challenge all of them. Registering in parallel
144    /// turns one nonce into a race, and §3.4.3's `nc` counting is per nonce.
145    pub async fn register(&mut self) -> Vec<Attempt> {
146        let mut attempts = Vec::with_capacity(self.flows.len());
147        for index in 0..self.flows.len() {
148            let (reg_id, outcome, flow_accepted) = {
149                let Some(flow) = self.flows.get_mut(index) else {
150                    continue;
151                };
152                let outcome = flow.agent.register().await;
153                if let Ok(lease) = &outcome {
154                    flow.lease = Some(*lease);
155                    flow.failures = 0;
156                } else {
157                    flow.lease = None;
158                    flow.failures = flow.failures.saturating_add(1);
159                }
160                (flow.reg_id, outcome, flow.agent.flow_accepted())
161            };
162            // Computed after the assignment above, so `any_active` reflects this attempt as well.
163            let retry_after = outcome.is_err().then(|| self.backoff_for(index));
164            attempts.push(Attempt {
165                reg_id,
166                flow_accepted,
167                outcome,
168                retry_after,
169            });
170        }
171        attempts
172    }
173
174    /// Keep every flow alive, and report which ones failed (RFC 5626 §4.4).
175    ///
176    /// A flow whose keep-alive fails is marked down and given a §4.5 retry delay; **the others are
177    /// pinged and judged regardless**. That is the criterion this whole module exists for: the
178    /// point of registering to several outbound proxies is that one of them going away is
179    /// survivable, and a keep-alive pass that stopped at the first failure would throw that away
180    /// at exactly the moment it mattered.
181    ///
182    /// Flows the registrar did not accept as Outbound are skipped: there is no flow, so there is
183    /// nothing to keep alive, and pinging would be traffic nothing at the far end cares about.
184    pub async fn keepalive(&mut self) -> Vec<Attempt> {
185        let mut attempts = Vec::new();
186        for index in 0..self.flows.len() {
187            let (reg_id, flow_accepted, failed) = {
188                let Some(flow) = self.flows.get_mut(index) else {
189                    continue;
190                };
191                if flow.lease.is_none() || !flow.agent.flow_accepted() {
192                    continue;
193                }
194                let result = flow.agent.keepalive().await;
195                let failed = result.err();
196                if failed.is_some() {
197                    flow.lease = None;
198                    flow.failures = flow.failures.saturating_add(1);
199                }
200                (flow.reg_id, true, failed)
201            };
202            let retry_after = failed.as_ref().map(|_| self.backoff_for(index));
203            let outcome = match failed {
204                Some(error) => Err(error),
205                None => self
206                    .flows
207                    .get(index)
208                    .and_then(|flow| flow.lease)
209                    .ok_or(Error::NoResponse),
210            };
211            attempts.push(Attempt {
212                reg_id,
213                flow_accepted,
214                outcome,
215                retry_after,
216            });
217        }
218        attempts
219    }
220
221    /// How long the flow at `index` should wait before its next attempt (RFC 5626 §4.5).
222    ///
223    /// The failure count has already been incremented by the caller, so one is taken off: §4.5's
224    /// exponent is the number of failures *before* this one, and starting at 2^1 would double the
225    /// very first wait.
226    fn backoff_for(&self, index: usize) -> Duration {
227        let failures = self
228            .flows
229            .get(index)
230            .map_or(1, |flow| flow.failures.saturating_sub(1));
231        outbound::recovery_delay(failures, self.any_active(), outbound::fraction())
232    }
233
234    /// The agent for one flow, for a caller that needs to send through it.
235    #[must_use]
236    pub fn flow(&self, reg_id: RegId) -> Option<&UserAgent> {
237        self.flows
238            .iter()
239            .find(|flow| flow.reg_id == reg_id)
240            .map(|flow| &flow.agent)
241    }
242
243    /// How long to wait before retrying a flow that has failed, per RFC 5626 §4.5.
244    ///
245    /// `None` for a flow that is up, or one this set does not have.
246    #[must_use]
247    pub fn retry_after(&self, reg_id: RegId) -> Option<Duration> {
248        let flow = self.flows.iter().find(|flow| flow.reg_id == reg_id)?;
249        (flow.lease.is_none() && flow.failures > 0).then(|| {
250            outbound::recovery_delay(
251                flow.failures.saturating_sub(1),
252                self.any_active(),
253                outbound::fraction(),
254            )
255        })
256    }
257}