Skip to main content

sipx_sip/transaction/
layer.rs

1//! The transaction layer: routing messages to transactions, and cleaning up after them.
2
3use std::collections::HashMap;
4
5use crate::message::{Message, Method, Request, Response};
6use crate::transaction::client::{ClientState, ClientTransaction};
7use crate::transaction::key::TransactionKey;
8use crate::transaction::server::{ServerState, ServerTransaction};
9use crate::transaction::timing::{Timer, Timers};
10use crate::transaction::{Output, Reliability, TuEvent};
11
12/// Where a message went.
13#[derive(Debug)]
14pub enum Dispatch {
15    /// It matched a transaction, which produced these outputs.
16    Matched {
17        /// The transaction it matched.
18        key: TransactionKey,
19        /// What the transaction wants done.
20        outputs: Vec<Output>,
21    },
22    /// It created a new server transaction.
23    Created {
24        /// The new transaction's key.
25        key: TransactionKey,
26        /// What the transaction wants done.
27        outputs: Vec<Output>,
28    },
29    /// It matched nothing.
30    ///
31    /// Passed up rather than dropped. An unmatched response may be a stray fork answer the
32    /// core has no business discarding silently, and an unmatched ACK for a 2xx is normal.
33    Unmatched(Box<Message>),
34}
35
36/// Holds the transactions in flight and routes messages to them.
37///
38/// Sans-IO like everything beneath it: the driver feeds messages and fired timers in, and
39/// performs the outputs.
40#[derive(Debug)]
41pub struct TransactionLayer {
42    client: HashMap<TransactionKey, ClientTransaction>,
43    server: HashMap<TransactionKey, ServerTransaction>,
44    timers: Timers,
45}
46
47impl TransactionLayer {
48    /// A layer with the given timer constants.
49    #[must_use]
50    pub fn new(timers: Timers) -> Self {
51        Self {
52            client: HashMap::new(),
53            server: HashMap::new(),
54            timers,
55        }
56    }
57
58    /// How many transactions are in flight, as (client, server).
59    ///
60    /// Exposed because a transaction store that leaks is a slow, quiet outage, and a test
61    /// that asserts on this is the cheapest way to notice.
62    #[must_use]
63    pub fn len(&self) -> (usize, usize) {
64        (self.client.len(), self.server.len())
65    }
66
67    /// Whether no transactions are in flight.
68    #[must_use]
69    pub fn is_empty(&self) -> bool {
70        self.client.is_empty() && self.server.is_empty()
71    }
72
73    /// Send a request, creating a client transaction for it.
74    pub fn send_request(
75        &mut self,
76        request: Request,
77        reliability: Reliability,
78    ) -> Option<(TransactionKey, Vec<Output>)> {
79        let key = TransactionKey::from_sent_request(&request)?;
80        let (tx, outputs) = ClientTransaction::new(request, reliability, self.timers);
81        self.client.insert(key.clone(), tx);
82        Some((key, outputs))
83    }
84
85    /// Route an incoming message.
86    pub fn receive(&mut self, message: Message, reliability: Reliability) -> Dispatch {
87        match message {
88            Message::Request(request) => self.receive_request(request, reliability),
89            Message::Response(response) => self.receive_response(response),
90        }
91    }
92
93    fn receive_request(&mut self, request: Request, reliability: Reliability) -> Dispatch {
94        let Some(key) = TransactionKey::from_request(&request) else {
95            return Dispatch::Unmatched(Box::new(Message::Request(request)));
96        };
97
98        if let Some(tx) = self.server.get_mut(&key) {
99            let outputs = tx.on_request(&request);
100            let terminated = tx.state().is_terminated();
101            if terminated {
102                self.server.remove(&key);
103            }
104            return Dispatch::Matched { key, outputs };
105        }
106
107        // An ACK that matches no transaction is an ACK for a 2xx whose transaction has already
108        // gone. That is ordinary, and it belongs to the transaction user.
109        if request.method == Method::Ack {
110            return Dispatch::Unmatched(Box::new(Message::Request(request)));
111        }
112
113        let (tx, outputs) = ServerTransaction::new(request, reliability, self.timers);
114        self.server.insert(key.clone(), tx);
115        Dispatch::Created { key, outputs }
116    }
117
118    fn receive_response(&mut self, response: Response) -> Dispatch {
119        let Some(key) = TransactionKey::from_response(&response) else {
120            return Dispatch::Unmatched(Box::new(Message::Response(response)));
121        };
122
123        let Some(tx) = self.client.get_mut(&key) else {
124            return Dispatch::Unmatched(Box::new(Message::Response(response)));
125        };
126
127        let outputs = tx.on_response(response);
128        if tx.state().is_terminated() {
129            self.client.remove(&key);
130        }
131        Dispatch::Matched { key, outputs }
132    }
133
134    /// Send a response from the transaction user.
135    pub fn send_response(&mut self, key: &TransactionKey, response: Response) -> Vec<Output> {
136        let Some(tx) = self.server.get_mut(key) else {
137            return Vec::new();
138        };
139        let outputs = tx.on_tu_response(response);
140        if tx.state().is_terminated() {
141            self.server.remove(key);
142        }
143        outputs
144    }
145
146    /// Abandon a server transaction the transaction user never answered.
147    ///
148    /// RFC 3261 §17.2 gives a server transaction in `Trying` no timer, because the model is
149    /// that the transaction user always responds. A stack exposed to a network needs the case
150    /// where it does not: an application that forgot a request, or one that is wedged, both
151    /// look like this, and a transaction held for the life of the process is a leak that grows
152    /// with traffic.
153    ///
154    /// Deliberately *not* a timer inside the transaction: that would change what the state
155    /// machine does, and the machine is right. This is the layer above admitting that its user
156    /// is fallible, and it is the driver — which owns the clock — that decides when.
157    ///
158    /// Returns whether there was one to abandon.
159    pub fn abandon(&mut self, key: &TransactionKey) -> bool {
160        self.server.remove(key).is_some()
161    }
162
163    /// A timer fired for a transaction.
164    pub fn on_timer(&mut self, key: &TransactionKey, timer: Timer) -> Vec<Output> {
165        if let Some(tx) = self.client.get_mut(key) {
166            let outputs = tx.on_timer(timer);
167            if tx.state().is_terminated() {
168                self.client.remove(key);
169            }
170            return outputs;
171        }
172        if let Some(tx) = self.server.get_mut(key) {
173            let outputs = tx.on_timer(timer);
174            if tx.state().is_terminated() {
175                self.server.remove(key);
176            }
177            return outputs;
178        }
179        Vec::new()
180    }
181
182    /// The transport failed for a transaction.
183    pub fn on_transport_error(&mut self, key: &TransactionKey) -> Vec<Output> {
184        if let Some(tx) = self.client.get_mut(key) {
185            let outputs = tx.on_transport_error();
186            self.client.remove(key);
187            return outputs;
188        }
189        if let Some(tx) = self.server.get_mut(key) {
190            let outputs = tx.on_transport_error();
191            self.server.remove(key);
192            return outputs;
193        }
194        Vec::new()
195    }
196
197    /// The request that created a server transaction.
198    ///
199    /// A driver needs this to build a response on the transaction's behalf — refusing an
200    /// overloaded endpoint with 503, for instance — without the application having been given
201    /// the request in the first place.
202    #[must_use]
203    pub fn server_request(&self, key: &TransactionKey) -> Option<&Request> {
204        self.server.get(key).map(ServerTransaction::request)
205    }
206
207    /// The request that created a client transaction.
208    ///
209    /// A transport driver needs this after an asynchronous connection attempt fails so it can
210    /// account for the exact method whose queued bytes never reached a socket.
211    #[must_use]
212    pub fn client_request(&self, key: &TransactionKey) -> Option<&Request> {
213        self.client.get(key).map(ClientTransaction::request)
214    }
215
216    /// The state of a client transaction, if it exists.
217    #[must_use]
218    pub fn client_state(&self, key: &TransactionKey) -> Option<ClientState> {
219        self.client.get(key).map(ClientTransaction::state)
220    }
221
222    /// The state of a server transaction, if it exists.
223    #[must_use]
224    pub fn server_state(&self, key: &TransactionKey) -> Option<ServerState> {
225        self.server.get(key).map(ServerTransaction::state)
226    }
227}
228
229/// Convenience: pull the transaction-user events out of a batch of outputs.
230#[must_use]
231pub fn tu_events(outputs: &[Output]) -> Vec<&TuEvent> {
232    outputs
233        .iter()
234        .filter_map(|o| match o {
235            Output::ToTu(event) => Some(event.as_ref()),
236            _ => None,
237        })
238        .collect()
239}
240
241/// Convenience: pull the messages to send out of a batch of outputs.
242#[must_use]
243pub fn sent_messages(outputs: &[Output]) -> Vec<&Message> {
244    outputs
245        .iter()
246        .filter_map(|o| match o {
247            Output::Send(message) => Some(message.as_ref()),
248            _ => None,
249        })
250        .collect()
251}