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}