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 Option<umsh_core::ChannelId>,
141 OwnedMacCommand,
142 )>::new()));
143 let pending_pfs_ref = pending_pfs.clone();
144 let pending_identity = Rc::new(RefCell::new(
145 Vec::<(LocalNode<M>, IdentityResponsePlan)>::new(),
146 ));
147 let pending_identity_ref = pending_identity.clone();
148 let pending_peer_repeaters = Rc::new(RefCell::new(Vec::<(
149 LocalNode<M>,
150 umsh_core::PublicKey,
151 Option<umsh_core::ChannelId>,
152 Vec<u8>,
153 )>::new()));
154 let pending_peer_repeaters_ref = pending_peer_repeaters.clone();
155 let dispatcher = self.dispatcher.clone();
156 let nodes = self.nodes.clone();
157 self.mac
158 .next_event(move |identity_id, event| {
159 dispatcher
160 .borrow_mut()
161 .dispatch_ticket_state(identity_id, &event);
162 let Some(node) = route_node(&nodes, identity_id) else {
163 return;
164 };
165 match event {
166 umsh_mac::MacEventRef::Received(packet) => {
167 let _ = node.dispatch_received_packet(&packet);
168 if packet.packet_type() == umsh_core::PacketType::Broadcast
169 && packet.payload().is_empty()
170 {
171 if let Some(from_hint) = packet.from_hint() {
172 node.dispatch_beacon(from_hint, packet.from_key());
173 }
174 } else if let Some(from) = packet.from_key() {
175 dispatch_payload_callbacks(
176 &node,
177 &packet,
178 from,
179 &pending_pfs_ref,
180 &pending_identity_ref,
181 &pending_peer_repeaters_ref,
182 now_ms,
183 );
184 }
185 }
186 umsh_mac::MacEventRef::AckReceived { peer, receipt } => {
187 node.dispatch_ack_received(
188 peer,
189 crate::SendToken::new(identity_id, receipt),
190 );
191 }
192 umsh_mac::MacEventRef::AckTimeout { peer, receipt } => {
193 node.dispatch_ack_timeout(
194 peer,
195 crate::SendToken::new(identity_id, receipt),
196 );
197 }
198 umsh_mac::MacEventRef::Transmitted { wire_bytes, .. } => {
199 node.dispatch_transmitted(wire_bytes);
200 }
201 umsh_mac::MacEventRef::Forwarded { .. } => {}
202 umsh_mac::MacEventRef::TxAbandoned { .. } => {}
206 }
207 })
208 .await
209 .map_err(HostError::Mac)?;
210
211 let queued: Vec<(
212 LocalIdentityId,
213 umsh_core::PublicKey,
214 Option<umsh_core::ChannelId>,
215 OwnedMacCommand,
216 )> = pending_pfs.borrow_mut().drain(..).collect();
217 for (identity_id, from, channel, command) in queued {
218 self.handle_pfs_command(identity_id, from, channel, command)
219 .await;
220 }
221
222 let identity_replies: Vec<(LocalNode<M>, IdentityResponsePlan)> =
223 pending_identity.borrow_mut().drain(..).collect();
224 for (node, plan) in identity_replies {
225 node.send_identity_response(plan).await;
226 }
227
228 let peer_repeater_requests: Vec<(
229 LocalNode<M>,
230 umsh_core::PublicKey,
231 Option<umsh_core::ChannelId>,
232 Vec<u8>,
233 )> = pending_peer_repeaters.borrow_mut().drain(..).collect();
234 for (node, from, channel, request) in peer_repeater_requests {
235 node.answer_peer_repeaters_request(from, channel, &request)
236 .await;
237 }
238
239 self.service_protocol_timeouts().await;
240
241 Ok(())
242 }
243
244 pub async fn service_protocol_timeouts(&mut self) {
251 let now_ms = self.mac.now_ms().await;
252 service_node_timeouts(&self.nodes, now_ms).await;
253 }
254
255 pub fn protocol_timeout_servicer(&self) -> ProtocolTimeoutServicer<M>
264 where
265 M: Clone,
266 {
267 ProtocolTimeoutServicer {
268 mac: self.mac.clone(),
269 nodes: self.nodes.clone(),
270 }
271 }
272
273 pub async fn run(&mut self) -> Result<(), HostError<M::RunError>> {
279 loop {
280 self.pump_once().await?;
281 }
282 }
283
284 async fn handle_pfs_command(
285 &mut self,
286 identity_id: LocalIdentityId,
287 from: umsh_core::PublicKey,
288 channel: Option<umsh_core::ChannelId>,
289 command: OwnedMacCommand,
290 ) {
291 let Some(node) = self.route_node(identity_id) else {
292 return;
293 };
294
295 match node
296 .handle_pfs_command(&from, channel, &command, &self.pfs_control_options)
297 .await
298 {
299 Ok(Some(PfsLifecycle::Established(peer))) => node.dispatch_pfs_established(peer),
300 Ok(Some(PfsLifecycle::Ended(peer))) => node.dispatch_pfs_ended(peer),
301 Ok(None) => {}
302 Err(err) => node.dispatch_pfs_failed(from, err.pfs_failure()),
306 }
307 }
308}
309
310pub struct ProtocolTimeoutServicer<M: MacBackend> {
313 mac: M,
314 nodes: Vec<(LocalIdentityId, LocalNode<M>)>,
315}
316
317impl<M: MacBackend> ProtocolTimeoutServicer<M> {
318 pub async fn service(&self) {
320 let now_ms = self.mac.now_ms().await;
321 service_node_timeouts(&self.nodes, now_ms).await;
322 }
323}
324
325async fn service_node_timeouts<M: MacBackend>(
326 nodes: &[(LocalIdentityId, LocalNode<M>)],
327 now_ms: u64,
328) {
329 #[cfg(feature = "software-crypto")]
330 for (_, node) in nodes {
331 if let Ok(expired) = node.expire_pfs_sessions().await {
332 for peer in expired {
333 node.dispatch_pfs_ended(peer);
334 }
335 }
336 for peer in node.expire_pfs_requests(now_ms) {
339 node.dispatch_pfs_failed(peer, crate::node::PfsFailure::Timeout);
340 }
341 }
342
343 for (_, node) in nodes {
344 node.expire_pings(now_ms);
345 }
346}
347
348fn route_node<M: MacBackend>(
349 nodes: &[(LocalIdentityId, LocalNode<M>)],
350 identity_id: LocalIdentityId,
351) -> Option<LocalNode<M>> {
352 nodes
353 .iter()
354 .find(|(id, _)| *id == identity_id)
355 .map(|(_, node)| node.clone())
356 .or_else(|| {
357 nodes
358 .iter()
359 .find(|(_, node)| node.owns_ephemeral_identity(identity_id))
360 .map(|(_, node)| node.clone())
361 })
362}
363
364fn dispatch_payload_callbacks<M: MacBackend>(
365 node: &LocalNode<M>,
366 packet: &ReceivedPacketRef<'_>,
367 from: umsh_core::PublicKey,
368 pending_pfs: &Rc<
369 RefCell<
370 Vec<(
371 LocalIdentityId,
372 umsh_core::PublicKey,
373 Option<umsh_core::ChannelId>,
374 OwnedMacCommand,
375 )>,
376 >,
377 >,
378 pending_identity: &Rc<RefCell<Vec<(LocalNode<M>, IdentityResponsePlan)>>>,
379 pending_peer_repeaters: &Rc<
380 RefCell<
381 Vec<(
382 LocalNode<M>,
383 umsh_core::PublicKey,
384 Option<umsh_core::ChannelId>,
385 Vec<u8>,
386 )>,
387 >,
388 >,
389 now_ms: u64,
390) {
391 if packet.payload_type() == PayloadType::NodeIdentity {
392 if let Ok(identity) = NodeIdentityPayload::from_bytes(packet.payload()) {
393 node.observe_peer_identity(from, &identity, now_ms);
396 node.dispatch_node_discovered(from, identity.name.as_deref());
397 }
398 return;
399 }
400
401 if packet.payload_type() == PayloadType::MacCommand {
402 if let Ok(command) = mac_command::parse(packet.payload()) {
403 let addressed_to_one_node = matches!(
409 packet.packet_family(),
410 umsh_mac::PacketFamily::Unicast | umsh_mac::PacketFamily::BlindUnicast
411 );
412 if !addressed_to_one_node
413 && !matches!(command, mac_command::MacCommand::IdentityRequest { .. })
414 {
415 return;
416 }
417 let reply_channel =
422 matches!(packet.packet_family(), umsh_mac::PacketFamily::BlindUnicast)
423 .then(|| packet.channel().map(|c| c.id()))
424 .flatten();
425 if let mac_command::MacCommand::EchoResponse { data } = command {
427 node.match_pong(
428 from,
429 data,
430 packet,
431 packet.received_at_ms().unwrap_or(now_ms),
432 );
433 }
434 if let mac_command::MacCommand::IdentityRequest { options } = command {
438 if let Some(plan) = node.evaluate_identity_request(
439 packet,
440 from,
441 options,
442 packet.received_at_ms().unwrap_or(now_ms),
443 ) {
444 pending_identity.borrow_mut().push((node.clone(), plan));
445 }
446 }
447 if let mac_command::MacCommand::PeerRepeatersRequest { options } = command
451 && node.peer_repeaters_responder_enabled()
452 {
453 pending_peer_repeaters.borrow_mut().push((
454 node.clone(),
455 from,
456 reply_channel,
457 Vec::from(options),
458 ));
459 }
460 let owned = OwnedMacCommand::from(command);
461 node.dispatch_mac_command(from, &owned);
462 if matches!(
463 owned,
464 OwnedMacCommand::PfsSessionRequest { .. }
465 | OwnedMacCommand::PfsSessionResponse { .. }
466 | OwnedMacCommand::EndPfsSession
467 ) {
468 pending_pfs
469 .borrow_mut()
470 .push((node.identity_id(), from, reply_channel, owned));
471 }
472 }
473 }
474}