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