1#![allow(async_fn_in_trait)]
2#![cfg_attr(not(feature = "std"), no_std)]
3
4#[cfg(not(feature = "alloc"))]
135compile_error!("umsh-node currently requires the alloc feature");
136
137extern crate alloc;
138
139mod app_error;
140mod app_payload;
141mod app_util;
142#[cfg(feature = "software-crypto")]
143mod channel;
144mod dispatch;
145mod host;
146mod identity;
147pub mod location;
148mod mac;
149pub mod mac_command;
150mod node;
151mod peer;
152#[cfg(feature = "software-crypto")]
153mod pfs;
154mod receive;
155mod ticket;
156mod transport;
157
158pub use app_error::{AppEncodeError, AppParseError};
159pub use app_payload::{
160 expect_payload_type, parse_mac_command_payload, parse_node_identity_payload, split_payload_type,
161};
162#[cfg(feature = "software-crypto")]
163pub use channel::Channel;
164pub use host::{Host, HostError};
165pub use identity::{NodeCapabilities, NodeIdentityPayload, NodeRole};
166pub use mac::{MacBackend, MacBackendError};
167pub use mac_command::OwnedMacCommand;
168pub use mac_command::{CommandId, MacCommand};
169#[cfg(feature = "software-crypto")]
170pub use node::BoundChannel;
171#[cfg(feature = "software-crypto")]
172pub use node::PfsStatus;
173pub use node::{LocalNode, NodeError, Subscription};
174pub use peer::PeerConnection;
175pub use receive::{ChannelInfoRef, PacketFamily, ReceivedPacketRef, RouteHops, RxMetadata, Snr};
176pub use ticket::{SendProgressTicket, SendToken};
177pub use transport::Transport;
178
179#[cfg(test)]
180mod tests {
181 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
182 use std::{
183 cell::RefCell,
184 collections::VecDeque,
185 future::Future,
186 num::NonZeroU8,
187 pin::pin,
188 rc::Rc,
189 task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
190 };
191 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
192 use umsh_core::{NodeHint, PublicKey};
193 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
194 use umsh_crypto::NodeIdentity;
195 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
196 use umsh_crypto::software::SoftwareIdentity;
197 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
198 use umsh_hal::Snr;
199 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
200 use umsh_mac::MacEventRef;
201 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
202 use umsh_mac::{CapacityError, LocalIdentityId, PeerId, SendError, SendOptions, SendReceipt};
203
204 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
205 use crate::ReceivedPacketRef;
206 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
207 use crate::{MacBackend, MacBackendError, OwnedMacCommand, SendToken};
208 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
209 use umsh_core::ChannelId;
210 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
211 use umsh_text::OwnedTextMessage;
212 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
213 #[test]
214 fn peer_receive_handlers_precede_node_receive_handlers() {
215 use crate::node::{LocalNode, LocalNodeState, NodeMembership};
216
217 let mac = FakeMac::new(Vec::new());
218 let dispatcher = Rc::new(RefCell::new(crate::dispatch::EventDispatcher::new()));
219 let membership = Rc::new(RefCell::new(NodeMembership::new()));
220 let state = Rc::new(RefCell::new(LocalNodeState::new()));
221 let node = LocalNode::new(LocalIdentityId(1), mac, dispatcher, membership, state);
222 let peer = PublicKey([0x41; 32]);
223 let peer_connection = block_on_ready(node.peer(peer)).unwrap();
224
225 let call_order = Rc::new(RefCell::new(Vec::new()));
226 let peer_call_order = call_order.clone();
227 let _peer_subscription = peer_connection.on_receive(move |_| {
228 peer_call_order.borrow_mut().push("peer");
229 true
230 });
231 let node_call_order = call_order.clone();
232 let _node_subscription = node.on_receive(move |_| {
233 node_call_order.borrow_mut().push("node");
234 true
235 });
236
237 assert!(node.dispatch_received_packet(&test_unicast_packet(peer, &[0x01, 0x02])));
238 assert_eq!(call_order.borrow().as_slice(), ["peer"]);
239 }
240
241 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
242 #[test]
243 fn receive_callbacks_can_observe_rx_metadata() {
244 use crate::node::{LocalNode, LocalNodeState, NodeMembership};
245
246 let mac = FakeMac::new(Vec::new());
247 let dispatcher = Rc::new(RefCell::new(crate::dispatch::EventDispatcher::new()));
248 let membership = Rc::new(RefCell::new(NodeMembership::new()));
249 let state = Rc::new(RefCell::new(LocalNodeState::new()));
250 let node = LocalNode::new(LocalIdentityId(1), mac, dispatcher, membership, state);
251 let peer = PublicKey([0x44; 32]);
252
253 let observed = Rc::new(RefCell::new(None));
254 let observed_for_callback = observed.clone();
255 let _subscription = node.on_receive(move |packet| {
256 *observed_for_callback.borrow_mut() = Some((
257 packet.rssi(),
258 packet.snr(),
259 packet.lqi(),
260 packet.received_at_ms(),
261 ));
262 true
263 });
264
265 let payload = encode_text_payload("metadata");
266 let packet = test_unicast_packet_with_rx(
267 peer,
268 &payload,
269 umsh_mac::RxMetadata::new(
270 Some(-73),
271 Some(Snr::from_centibels(123)),
272 NonZeroU8::new(200),
273 Some(123_456),
274 ),
275 );
276
277 assert!(node.dispatch_received_packet(&packet));
278 assert_eq!(
279 *observed.borrow(),
280 Some((
281 Some(-73),
282 Some(Snr::from_centibels(123)),
283 NonZeroU8::new(200),
284 Some(123_456),
285 ))
286 );
287 }
288
289 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
290 #[test]
291 fn subscription_guard_unregisters_on_drop() {
292 use crate::node::{LocalNode, LocalNodeState, NodeMembership};
293
294 let mac = FakeMac::new(Vec::new());
295 let dispatcher = Rc::new(RefCell::new(crate::dispatch::EventDispatcher::new()));
296 let membership = Rc::new(RefCell::new(NodeMembership::new()));
297 let state = Rc::new(RefCell::new(LocalNodeState::new()));
298 let node = LocalNode::new(LocalIdentityId(1), mac, dispatcher, membership, state);
299 let peer = PublicKey([0x33; 32]);
300
301 let hits = Rc::new(RefCell::new(0u32));
302 {
303 let hits = hits.clone();
304 let _subscription = node.on_receive(move |_| {
305 *hits.borrow_mut() += 1;
306 true
307 });
308 assert!(node.dispatch_received_packet(&test_unicast_packet(peer, &[0x01, 0x02])));
309 }
310
311 assert_eq!(*hits.borrow(), 1);
312 assert!(!node.dispatch_received_packet(&test_unicast_packet(peer, &[0x01, 0x02])));
313 assert_eq!(*hits.borrow(), 1);
314 }
315
316 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
317 #[test]
318 fn callbacks_observe_control_side_events_and_peer_ack_state() {
319 use crate::node::{LocalNode, LocalNodeState, NodeMembership};
320
321 let mac = FakeMac::new(Vec::new());
322 let dispatcher = Rc::new(RefCell::new(crate::dispatch::EventDispatcher::new()));
323 let membership = Rc::new(RefCell::new(NodeMembership::new()));
324 let state = Rc::new(RefCell::new(LocalNodeState::new()));
325 let node = LocalNode::new(LocalIdentityId(1), mac, dispatcher, membership, state);
326
327 let peer = PublicKey([0x42; 32]);
328 let peer_connection = block_on_ready(node.peer(peer)).unwrap();
329 let node_discovery = Rc::new(RefCell::new(Vec::new()));
330 let beacons = Rc::new(RefCell::new(Vec::new()));
331 let commands = Rc::new(RefCell::new(Vec::new()));
332 let peer_acks = Rc::new(RefCell::new(Vec::new()));
333 let peer_timeouts = Rc::new(RefCell::new(Vec::new()));
334
335 let discovery_log = node_discovery.clone();
336 let _discovered_subscription = node.on_node_discovered(move |key, name| {
337 discovery_log
338 .borrow_mut()
339 .push((key, name.map(str::to_string)));
340 });
341 let beacon_log = beacons.clone();
342 let _beacon_subscription = node.on_beacon(move |from_hint, from_key| {
343 beacon_log.borrow_mut().push((from_hint, from_key));
344 });
345 let command_log = commands.clone();
346 let _command_subscription = node.on_mac_command(move |from, command| {
347 command_log.borrow_mut().push((from, command.clone()));
348 });
349 let peer_ack_log = peer_acks.clone();
350 let _ack_subscription = peer_connection.on_ack_received(move |token| {
351 peer_ack_log.borrow_mut().push(token);
352 });
353 let peer_timeout_log = peer_timeouts.clone();
354 let _timeout_subscription = peer_connection.on_ack_timeout(move |token| {
355 peer_timeout_log.borrow_mut().push(token);
356 });
357
358 let token = SendToken::new(LocalIdentityId(1), SendReceipt(12));
359 let timeout_token = SendToken::new(LocalIdentityId(1), SendReceipt(13));
360 let hint = NodeHint([1, 2, 3]);
361 let command = OwnedMacCommand::EchoRequest {
362 data: vec![9, 8, 7],
363 };
364
365 node.dispatch_node_discovered(peer, Some("alice"));
366 node.dispatch_beacon(hint, Some(peer));
367 node.dispatch_mac_command(peer, &command);
368 node.dispatch_ack_received(peer, token);
369 node.dispatch_ack_timeout(peer, timeout_token);
370
371 assert_eq!(
372 node_discovery.borrow().as_slice(),
373 &[(peer, Some(String::from("alice")))]
374 );
375 assert_eq!(beacons.borrow().as_slice(), &[(hint, Some(peer))]);
376 assert_eq!(commands.borrow().as_slice(), &[(peer, command)]);
377 assert_eq!(peer_acks.borrow().as_slice(), &[token]);
378 assert_eq!(peer_timeouts.borrow().as_slice(), &[timeout_token]);
379 }
380
381 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
382 #[test]
383 fn pfs_routed_send_tracks_ack_against_ephemeral_identity() {
384 use crate::node::{LocalNode, LocalNodeState, NodeMembership};
385
386 let mac = FakeMac::new(vec![[7u8; 32], [9u8; 32]]);
387 let dispatcher = Rc::new(RefCell::new(crate::dispatch::EventDispatcher::new()));
388 let membership = Rc::new(RefCell::new(NodeMembership::new()));
389 let state = Rc::new(RefCell::new(LocalNodeState::new()));
390 let node = LocalNode::new(
391 LocalIdentityId(1),
392 mac.clone(),
393 dispatcher.clone(),
394 membership,
395 state,
396 );
397
398 let peer = PublicKey([0x55; 32]);
399 let peer_connection = block_on_ready(node.peer(peer)).unwrap();
400 let options = SendOptions::default().with_ack_requested(true);
401
402 block_on_ready(node.request_pfs(&peer, 60, &options)).unwrap();
403 let request = mac.take_unicasts().pop().expect("request send");
404 let request_command = parse_owned_mac_command(&request.payload);
405 let request_ephemeral = match request_command {
406 OwnedMacCommand::PfsSessionRequest { ephemeral_key, .. } => ephemeral_key,
407 other => panic!("unexpected request payload: {other:?}"),
408 };
409
410 block_on_ready(node.handle_pfs_command(
411 &peer,
412 &OwnedMacCommand::PfsSessionResponse {
413 ephemeral_key: PublicKey([0x44; 32]),
414 duration_minutes: 60,
415 },
416 &options,
417 ))
418 .unwrap();
419
420 let payload = encode_text_payload("hello over pfs");
421 let ticket = block_on_ready(peer_connection.send(&payload, &options)).unwrap();
422 let sent = mac.take_unicasts().pop().expect("pfs-routed send");
423 assert_eq!(sent.from, LocalIdentityId(10));
424 assert_eq!(sent.to, PublicKey([0x44; 32]));
425
426 let pairwise_from_pfs = PublicKey([0x44; 32]);
427 let _ = request_ephemeral; dispatcher.borrow_mut().dispatch_ticket_state(
429 sent.from,
430 &MacEventRef::AckReceived {
431 peer: pairwise_from_pfs,
432 receipt: SendReceipt(42),
433 },
434 );
435 assert!(ticket.was_acked());
436 assert!(ticket.is_finished());
437 }
438
439 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
440 #[test]
441 fn pfs_session_manager_request_and_teardown() {
442 use crate::pfs::PfsSessionManager;
443
444 let mac = FakeMac::new(vec![[3u8; 32]]);
445 let peer_long_term = PublicKey([0x55; 32]);
446 let options = SendOptions::default().with_ack_requested(true);
447
448 let mut pfs = PfsSessionManager::new();
449 block_on_ready(pfs.request_session(
450 &mac,
451 LocalIdentityId(1),
452 &peer_long_term,
453 60,
454 &options,
455 ))
456 .unwrap();
457
458 let sent = mac.take_unicasts();
459 assert_eq!(sent.len(), 1);
460 assert_eq!(sent[0].from, LocalIdentityId(1));
461 assert_eq!(sent[0].to, peer_long_term);
462 assert_eq!(
463 parse_owned_mac_command(&sent[0].payload),
464 OwnedMacCommand::PfsSessionRequest {
465 ephemeral_key: *SoftwareIdentity::from_secret_bytes(&[3u8; 32]).public_key(),
466 duration_minutes: 60,
467 }
468 );
469
470 assert!(
471 block_on_ready(pfs.end_session(
472 &mac,
473 LocalIdentityId(1),
474 &peer_long_term,
475 true,
476 &options,
477 ))
478 .unwrap()
479 );
480 let sent = mac.take_unicasts();
481 assert_eq!(sent.len(), 1);
482 assert_eq!(
483 parse_owned_mac_command(&sent[0].payload),
484 OwnedMacCommand::EndPfsSession
485 );
486 }
487
488 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
489 #[test]
490 fn pfs_end_session_errors_when_missing() {
491 use crate::pfs::PfsSessionManager;
492
493 let mac = FakeMac::new(Vec::new());
494 let options = SendOptions::default();
495 let mut pfs = PfsSessionManager::new();
496
497 let error = block_on_ready(pfs.end_session(
498 &mac,
499 LocalIdentityId(1),
500 &PublicKey([0x77; 32]),
501 true,
502 &options,
503 ))
504 .unwrap_err();
505 assert!(matches!(error, crate::NodeError::PfsSessionMissing));
506 }
507
508 #[cfg(feature = "unsafe-advanced")]
509 fn encode_text_payload(text: &str) -> Vec<u8> {
510 let message = OwnedTextMessage {
511 message_type: umsh_text::MessageType::Basic,
512 sender_handle: None,
513 sequence: None,
514 sequence_reset: false,
515 regarding: None,
516 editing: None,
517 bg_color: None,
518 text_color: None,
519 body: String::from(text),
520 };
521 let mut body = [0u8; 512];
522 let len = umsh_text::text_message::encode(&message.as_borrowed(), &mut body).unwrap();
523 let mut payload = Vec::with_capacity(len + 1);
524 payload.push(umsh_core::PayloadType::TextMessage as u8);
525 payload.extend_from_slice(&body[..len]);
526 payload
527 }
528
529 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
530 fn test_unicast_packet<'a>(from: PublicKey, payload: &'a [u8]) -> ReceivedPacketRef<'a> {
531 test_unicast_packet_with_rx(from, payload, umsh_mac::RxMetadata::default())
532 }
533
534 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
535 fn test_unicast_packet_with_rx<'a>(
536 from: PublicKey,
537 payload: &'a [u8],
538 rx: umsh_mac::RxMetadata,
539 ) -> ReceivedPacketRef<'a> {
540 let wire = Box::leak(payload.to_vec().into_boxed_slice());
541 let header = umsh_core::PacketHeader {
542 fcf: umsh_core::Fcf::new(umsh_core::PacketType::Unicast, false, false),
543 options_range: 0..0,
544 flood_hops: None,
545 dst: None,
546 channel: None,
547 ack_dst: None,
548 source: umsh_core::SourceAddrRef::Hint(from.hint()),
549 sec_info: None,
550 body_range: 0..wire.len(),
551 mic_range: wire.len()..wire.len(),
552 total_len: wire.len(),
553 };
554 ReceivedPacketRef::new(
555 wire,
556 wire,
557 header,
558 umsh_core::ParsedOptions::default(),
559 Some(from),
560 Some(from.hint()),
561 true,
562 None,
563 rx,
564 )
565 }
566
567 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
568 #[derive(Clone, Default)]
569 struct FakeMac {
570 state: Rc<RefCell<FakeMacState>>,
571 }
572
573 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
574 #[derive(Default)]
575 struct FakeMacState {
576 random_blocks: VecDeque<[u8; 32]>,
577 next_peer_id: u8,
578 next_ephemeral_id: u8,
579 now_ms: u64,
580 unicasts: Vec<SentUnicast>,
581 removed_ephemerals: Vec<LocalIdentityId>,
582 peers: Vec<(PublicKey, PeerId)>,
583 }
584
585 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
586 #[derive(Clone, Debug, PartialEq, Eq)]
587 struct SentUnicast {
588 from: LocalIdentityId,
589 to: PublicKey,
590 payload: Vec<u8>,
591 options: SendOptions,
592 }
593
594 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
595 impl FakeMac {
596 fn new(random_blocks: Vec<[u8; 32]>) -> Self {
597 Self {
598 state: Rc::new(RefCell::new(FakeMacState {
599 random_blocks: random_blocks.into(),
600 next_peer_id: 0,
601 next_ephemeral_id: 10,
602 now_ms: 1_000,
603 ..FakeMacState::default()
604 })),
605 }
606 }
607
608 fn take_unicasts(&self) -> Vec<SentUnicast> {
609 core::mem::take(&mut self.state.borrow_mut().unicasts)
610 }
611 }
612
613 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
614 impl MacBackend for FakeMac {
615 type SendError = SendError;
616 type CapacityError = CapacityError;
617
618 async fn add_peer(
619 &self,
620 key: PublicKey,
621 ) -> Result<PeerId, MacBackendError<Self::SendError, Self::CapacityError>> {
622 let mut state = self.state.borrow_mut();
623 if let Some((_, existing)) = state
624 .peers
625 .iter()
626 .find(|(existing_key, _)| *existing_key == key)
627 {
628 return Ok(*existing);
629 }
630 let peer_id = PeerId(state.next_peer_id);
631 state.next_peer_id = state.next_peer_id.wrapping_add(1);
632 state.peers.push((key, peer_id));
633 Ok(peer_id)
634 }
635
636 async fn add_private_channel(
637 &self,
638 _key: umsh_core::ChannelKey,
639 ) -> Result<(), MacBackendError<Self::SendError, Self::CapacityError>> {
640 Ok(())
641 }
642
643 async fn add_named_channel(
644 &self,
645 _name: &str,
646 ) -> Result<(), MacBackendError<Self::SendError, Self::CapacityError>> {
647 Ok(())
648 }
649
650 async fn send_broadcast(
651 &self,
652 _from: LocalIdentityId,
653 _payload: &[u8],
654 _options: &SendOptions,
655 ) -> Result<SendReceipt, MacBackendError<Self::SendError, Self::CapacityError>> {
656 Ok(SendReceipt(99))
657 }
658
659 async fn send_multicast(
660 &self,
661 _from: LocalIdentityId,
662 _channel: &ChannelId,
663 _payload: &[u8],
664 _options: &SendOptions,
665 ) -> Result<SendReceipt, MacBackendError<Self::SendError, Self::CapacityError>> {
666 Ok(SendReceipt(99))
667 }
668
669 async fn send_unicast(
670 &self,
671 from: LocalIdentityId,
672 dst: &PublicKey,
673 payload: &[u8],
674 options: &SendOptions,
675 ) -> Result<Option<SendReceipt>, MacBackendError<Self::SendError, Self::CapacityError>>
676 {
677 self.state.borrow_mut().unicasts.push(SentUnicast {
678 from,
679 to: *dst,
680 payload: payload.to_vec(),
681 options: options.clone(),
682 });
683 Ok(Some(SendReceipt(42)))
684 }
685
686 async fn send_blind_unicast(
687 &self,
688 from: LocalIdentityId,
689 dst: &PublicKey,
690 _channel: &ChannelId,
691 payload: &[u8],
692 options: &SendOptions,
693 ) -> Result<Option<SendReceipt>, MacBackendError<Self::SendError, Self::CapacityError>>
694 {
695 self.send_unicast(from, dst, payload, options).await
696 }
697
698 async fn fill_random(&self, dest: &mut [u8]) {
699 let mut state = self.state.borrow_mut();
700 let next = state.random_blocks.pop_front().expect("test rng exhausted");
701 dest.copy_from_slice(&next[..dest.len()]);
702 }
703
704 async fn now_ms(&self) -> u64 {
705 self.state.borrow().now_ms
706 }
707
708 async fn register_ephemeral(
709 &self,
710 _parent: LocalIdentityId,
711 _identity: SoftwareIdentity,
712 ) -> Result<LocalIdentityId, MacBackendError<Self::SendError, Self::CapacityError>>
713 {
714 let mut state = self.state.borrow_mut();
715 let id = LocalIdentityId(state.next_ephemeral_id);
716 state.next_ephemeral_id = state.next_ephemeral_id.wrapping_add(1);
717 Ok(id)
718 }
719
720 async fn remove_ephemeral(&self, id: LocalIdentityId) -> bool {
721 self.state.borrow_mut().removed_ephemerals.push(id);
722 true
723 }
724 }
725
726 #[cfg(all(feature = "software-crypto", feature = "unsafe-advanced"))]
727 fn parse_owned_mac_command(payload: &[u8]) -> OwnedMacCommand {
728 OwnedMacCommand::from(
729 crate::parse_mac_command_payload(umsh_core::PacketType::Unicast, payload).unwrap(),
730 )
731 }
732
733 #[cfg(feature = "unsafe-advanced")]
734 fn block_on_ready<F: Future>(future: F) -> F::Output {
735 fn raw_waker() -> RawWaker {
736 fn clone(_: *const ()) -> RawWaker {
737 raw_waker()
738 }
739 fn wake(_: *const ()) {}
740 fn wake_by_ref(_: *const ()) {}
741 fn drop(_: *const ()) {}
742
743 RawWaker::new(
744 core::ptr::null(),
745 &RawWakerVTable::new(clone, wake, wake_by_ref, drop),
746 )
747 }
748
749 let waker = unsafe { Waker::from_raw(raw_waker()) };
750 let mut context = Context::from_waker(&waker);
751 let mut future = pin!(future);
752 match future.as_mut().poll(&mut context) {
753 Poll::Ready(value) => value,
754 Poll::Pending => panic!("test future unexpectedly returned Poll::Pending"),
755 }
756 }
757}