1use alloc::rc::Rc;
2use alloc::vec::Vec;
3use core::cell::RefCell;
4
5use umsh_mac::{LocalIdentityId, SendOptions};
6
7use crate::dispatch::EventDispatcher;
8use crate::identity_responder::IdentityResponsePlan;
9use crate::mac::MacBackend;
10use crate::node::{LocalNode, LocalNodeState, NodeMembership, PfsLifecycle};
11use crate::receive::ReceivedPacketRef;
12use crate::{NodeIdentityPayload, OwnedMacCommand, mac_command};
13use umsh_core::PayloadType;
14
15#[derive(Debug)]
19pub enum HostError<E> {
20 Mac(E),
21}
22
23pub struct Host<M: MacBackend> {
47 mac: M,
48 dispatcher: Rc<RefCell<EventDispatcher>>,
49 nodes: Vec<(LocalIdentityId, LocalNode<M>)>,
50 pfs_control_options: SendOptions,
51}
52
53impl<M: MacBackend> Host<M> {
54 pub fn new(mac: M) -> Self {
56 Self {
57 mac,
58 dispatcher: Rc::new(RefCell::new(EventDispatcher::new())),
59 nodes: Vec::new(),
60 pfs_control_options: SendOptions::default()
61 .with_ack_requested(true)
62 .with_flood_hops(5),
63 }
64 }
65
66 pub fn mac(&self) -> M {
68 self.mac.clone()
69 }
70
71 pub fn pfs_control_options(&self) -> &SendOptions {
73 &self.pfs_control_options
74 }
75
76 pub fn set_pfs_control_options(&mut self, options: SendOptions) {
78 self.pfs_control_options = options;
79 }
80
81 pub fn add_node(&mut self, identity_id: LocalIdentityId) -> LocalNode<M> {
86 let membership = Rc::new(RefCell::new(NodeMembership::new()));
87 let state = Rc::new(RefCell::new(LocalNodeState::new()));
88 let node = LocalNode::new(
89 identity_id,
90 self.mac.clone(),
91 self.dispatcher.clone(),
92 membership,
93 state,
94 );
95 self.nodes.push((identity_id, node.clone()));
96 node
97 }
98
99 pub fn node(&self, identity_id: LocalIdentityId) -> Option<LocalNode<M>> {
101 self.nodes
102 .iter()
103 .find(|(id, _)| *id == identity_id)
104 .map(|(_, node)| node.clone())
105 }
106
107 fn route_node(&self, identity_id: LocalIdentityId) -> Option<LocalNode<M>> {
108 if let Some(node) = self.node(identity_id) {
109 return Some(node);
110 }
111
112 #[cfg(feature = "software-crypto")]
113 {
114 return self
115 .nodes
116 .iter()
117 .find(|(_, node)| node.owns_ephemeral_identity(identity_id))
118 .map(|(_, node)| node.clone());
119 }
120
121 #[cfg(not(feature = "software-crypto"))]
122 {
123 None
124 }
125 }
126
127 pub async fn pump_once(&mut self) -> Result<(), HostError<M::RunError>> {
136 let now_ms = self.mac.now_ms().await;
137 let pending_pfs = Rc::new(RefCell::new(Vec::<(
138 LocalIdentityId,
139 umsh_core::PublicKey,
140 OwnedMacCommand,
141 )>::new()));
142 let pending_pfs_ref = pending_pfs.clone();
143 let pending_identity = Rc::new(RefCell::new(
144 Vec::<(LocalNode<M>, IdentityResponsePlan)>::new(),
145 ));
146 let pending_identity_ref = pending_identity.clone();
147 let dispatcher = self.dispatcher.clone();
148 let nodes = self.nodes.clone();
149 self.mac
150 .next_event(move |identity_id, event| {
151 dispatcher
152 .borrow_mut()
153 .dispatch_ticket_state(identity_id, &event);
154 let Some(node) = route_node(&nodes, identity_id) else {
155 return;
156 };
157 match event {
158 umsh_mac::MacEventRef::Received(packet) => {
159 let _ = node.dispatch_received_packet(&packet);
160 if packet.packet_type() == umsh_core::PacketType::Broadcast
161 && packet.payload().is_empty()
162 {
163 if let Some(from_hint) = packet.from_hint() {
164 node.dispatch_beacon(from_hint, packet.from_key());
165 }
166 } else if let Some(from) = packet.from_key() {
167 dispatch_payload_callbacks(
168 &node,
169 &packet,
170 from,
171 &pending_pfs_ref,
172 &pending_identity_ref,
173 now_ms,
174 );
175 }
176 }
177 umsh_mac::MacEventRef::AckReceived { peer, receipt } => {
178 node.dispatch_ack_received(
179 peer,
180 crate::SendToken::new(identity_id, receipt),
181 );
182 }
183 umsh_mac::MacEventRef::AckTimeout { peer, receipt } => {
184 node.dispatch_ack_timeout(
185 peer,
186 crate::SendToken::new(identity_id, receipt),
187 );
188 }
189 umsh_mac::MacEventRef::Transmitted { wire_bytes, .. } => {
190 node.dispatch_transmitted(wire_bytes);
191 }
192 umsh_mac::MacEventRef::Forwarded { .. } => {}
193 umsh_mac::MacEventRef::TxAbandoned { .. } => {}
197 }
198 })
199 .await
200 .map_err(HostError::Mac)?;
201
202 let queued: Vec<(LocalIdentityId, umsh_core::PublicKey, OwnedMacCommand)> =
203 pending_pfs.borrow_mut().drain(..).collect();
204 for (identity_id, from, command) in queued {
205 self.handle_pfs_command(identity_id, from, command).await;
206 }
207
208 let identity_replies: Vec<(LocalNode<M>, IdentityResponsePlan)> =
209 pending_identity.borrow_mut().drain(..).collect();
210 for (node, plan) in identity_replies {
211 node.send_identity_response(plan).await;
212 }
213
214 self.service_protocol_timeouts().await;
215
216 Ok(())
217 }
218
219 pub async fn service_protocol_timeouts(&mut self) {
226 let now_ms = self.mac.now_ms().await;
227 service_node_timeouts(&self.nodes, now_ms).await;
228 }
229
230 pub fn protocol_timeout_servicer(&self) -> ProtocolTimeoutServicer<M>
239 where
240 M: Clone,
241 {
242 ProtocolTimeoutServicer {
243 mac: self.mac.clone(),
244 nodes: self.nodes.clone(),
245 }
246 }
247
248 pub async fn run(&mut self) -> Result<(), HostError<M::RunError>> {
254 loop {
255 self.pump_once().await?;
256 }
257 }
258
259 async fn handle_pfs_command(
260 &mut self,
261 identity_id: LocalIdentityId,
262 from: umsh_core::PublicKey,
263 command: OwnedMacCommand,
264 ) {
265 let Some(node) = self.route_node(identity_id) else {
266 return;
267 };
268
269 match node
270 .handle_pfs_command(&from, &command, &self.pfs_control_options)
271 .await
272 {
273 Ok(Some(PfsLifecycle::Established(peer))) => node.dispatch_pfs_established(peer),
274 Ok(Some(PfsLifecycle::Ended(peer))) => node.dispatch_pfs_ended(peer),
275 Ok(None) => {}
276 Err(err) => node.dispatch_pfs_failed(from, err.pfs_failure()),
280 }
281 }
282}
283
284pub struct ProtocolTimeoutServicer<M: MacBackend> {
287 mac: M,
288 nodes: Vec<(LocalIdentityId, LocalNode<M>)>,
289}
290
291impl<M: MacBackend> ProtocolTimeoutServicer<M> {
292 pub async fn service(&self) {
294 let now_ms = self.mac.now_ms().await;
295 service_node_timeouts(&self.nodes, now_ms).await;
296 }
297}
298
299async fn service_node_timeouts<M: MacBackend>(
300 nodes: &[(LocalIdentityId, LocalNode<M>)],
301 now_ms: u64,
302) {
303 #[cfg(feature = "software-crypto")]
304 for (_, node) in nodes {
305 if let Ok(expired) = node.expire_pfs_sessions().await {
306 for peer in expired {
307 node.dispatch_pfs_ended(peer);
308 }
309 }
310 for peer in node.expire_pfs_requests(now_ms) {
313 node.dispatch_pfs_failed(peer, crate::node::PfsFailure::Timeout);
314 }
315 }
316
317 for (_, node) in nodes {
318 node.expire_pings(now_ms);
319 }
320}
321
322fn route_node<M: MacBackend>(
323 nodes: &[(LocalIdentityId, LocalNode<M>)],
324 identity_id: LocalIdentityId,
325) -> Option<LocalNode<M>> {
326 nodes
327 .iter()
328 .find(|(id, _)| *id == identity_id)
329 .map(|(_, node)| node.clone())
330 .or_else(|| {
331 nodes
332 .iter()
333 .find(|(_, node)| node.owns_ephemeral_identity(identity_id))
334 .map(|(_, node)| node.clone())
335 })
336}
337
338fn dispatch_payload_callbacks<M: MacBackend>(
339 node: &LocalNode<M>,
340 packet: &ReceivedPacketRef<'_>,
341 from: umsh_core::PublicKey,
342 pending_pfs: &Rc<RefCell<Vec<(LocalIdentityId, umsh_core::PublicKey, OwnedMacCommand)>>>,
343 pending_identity: &Rc<RefCell<Vec<(LocalNode<M>, IdentityResponsePlan)>>>,
344 now_ms: u64,
345) {
346 if packet.payload_type() == PayloadType::NodeIdentity {
347 if let Ok(identity) = NodeIdentityPayload::from_bytes(packet.payload()) {
348 node.dispatch_node_discovered(from, identity.name.as_deref());
349 }
350 return;
351 }
352
353 if packet.payload_type() == PayloadType::MacCommand {
354 if let Ok(command) = mac_command::parse(packet.payload()) {
355 if packet.packet_family() == umsh_mac::PacketFamily::Broadcast
360 && !matches!(command, mac_command::MacCommand::IdentityRequest { .. })
361 {
362 return;
363 }
364 if let mac_command::MacCommand::EchoResponse { data } = command {
366 node.match_pong(
367 from,
368 data,
369 packet,
370 packet.received_at_ms().unwrap_or(now_ms),
371 );
372 }
373 if let mac_command::MacCommand::IdentityRequest { options } = command {
377 if let Some(plan) = node.evaluate_identity_request(packet, from, options) {
378 pending_identity.borrow_mut().push((node.clone(), plan));
379 }
380 }
381 let owned = OwnedMacCommand::from(command);
382 node.dispatch_mac_command(from, &owned);
383 if matches!(
384 owned,
385 OwnedMacCommand::PfsSessionRequest { .. }
386 | OwnedMacCommand::PfsSessionResponse { .. }
387 | OwnedMacCommand::EndPfsSession
388 ) {
389 pending_pfs
390 .borrow_mut()
391 .push((node.identity_id(), from, owned));
392 }
393 }
394 }
395}