diff options
Diffstat (limited to 'third_party/rust/neqo-transport/src/connection/tests/datagram.rs')
-rw-r--r-- | third_party/rust/neqo-transport/src/connection/tests/datagram.rs | 620 |
1 files changed, 620 insertions, 0 deletions
diff --git a/third_party/rust/neqo-transport/src/connection/tests/datagram.rs b/third_party/rust/neqo-transport/src/connection/tests/datagram.rs new file mode 100644 index 0000000000..5b7b8dc0b4 --- /dev/null +++ b/third_party/rust/neqo-transport/src/connection/tests/datagram.rs @@ -0,0 +1,620 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::{cell::RefCell, convert::TryFrom, rc::Rc}; + +use neqo_common::event::Provider; +use test_fixture::now; + +use super::{ + assert_error, connect_force_idle, default_client, default_server, new_client, new_server, + AT_LEAST_PTO, +}; +use crate::{ + events::{ConnectionEvent, OutgoingDatagramOutcome}, + frame::FRAME_TYPE_DATAGRAM, + packet::PacketBuilder, + quic_datagrams::MAX_QUIC_DATAGRAM, + send_stream::{RetransmissionPriority, TransmissionPriority}, + Connection, ConnectionError, ConnectionParameters, Error, StreamType, +}; + +const DATAGRAM_LEN_MTU: u64 = 1310; +const DATA_MTU: &[u8] = &[1; 1310]; +const DATA_BIGGER_THAN_MTU: &[u8] = &[0; 2620]; +const DATAGRAM_LEN_SMALLER_THAN_MTU: u64 = 1200; +const DATA_SMALLER_THAN_MTU: &[u8] = &[0; 1200]; +const DATA_SMALLER_THAN_MTU_2: &[u8] = &[0; 600]; +const OUTGOING_QUEUE: usize = 2; + +struct InsertDatagram<'a> { + data: &'a [u8], +} + +impl crate::connection::test_internal::FrameWriter for InsertDatagram<'_> { + fn write_frames(&mut self, builder: &mut PacketBuilder) { + builder.encode_varint(FRAME_TYPE_DATAGRAM); + builder.encode(self.data); + } +} + +#[test] +fn datagram_disabled_both() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + assert_eq!(client.max_datagram_size(), Err(Error::NotAvailable)); + assert_eq!(server.max_datagram_size(), Err(Error::NotAvailable)); + assert_eq!( + client.send_datagram(DATA_SMALLER_THAN_MTU, None), + Err(Error::TooMuchData) + ); + assert_eq!(server.stats().frame_tx.datagram, 0); + assert_eq!( + server.send_datagram(DATA_SMALLER_THAN_MTU, None), + Err(Error::TooMuchData) + ); + assert_eq!(server.stats().frame_tx.datagram, 0); +} + +#[test] +fn datagram_enabled_on_client() { + let mut client = + new_client(ConnectionParameters::default().datagram_size(DATAGRAM_LEN_SMALLER_THAN_MTU)); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + assert_eq!(client.max_datagram_size(), Err(Error::NotAvailable)); + assert_eq!( + server.max_datagram_size(), + Ok(DATAGRAM_LEN_SMALLER_THAN_MTU) + ); + assert_eq!( + client.send_datagram(DATA_SMALLER_THAN_MTU, Some(1)), + Err(Error::TooMuchData) + ); + let dgram_sent = server.stats().frame_tx.datagram; + assert_eq!(server.send_datagram(DATA_SMALLER_THAN_MTU, Some(1)), Ok(())); + let out = server.process_output(now()).dgram().unwrap(); + assert_eq!(server.stats().frame_tx.datagram, dgram_sent + 1); + + client.process_input(&out, now()); + assert!(matches!( + client.next_event().unwrap(), + ConnectionEvent::Datagram(data) if data == DATA_SMALLER_THAN_MTU + )); +} + +#[test] +fn datagram_enabled_on_server() { + let mut client = default_client(); + let mut server = + new_server(ConnectionParameters::default().datagram_size(DATAGRAM_LEN_SMALLER_THAN_MTU)); + connect_force_idle(&mut client, &mut server); + + assert_eq!( + client.max_datagram_size(), + Ok(DATAGRAM_LEN_SMALLER_THAN_MTU) + ); + assert_eq!(server.max_datagram_size(), Err(Error::NotAvailable)); + assert_eq!( + server.send_datagram(DATA_SMALLER_THAN_MTU, Some(1)), + Err(Error::TooMuchData) + ); + let dgram_sent = client.stats().frame_tx.datagram; + assert_eq!(client.send_datagram(DATA_SMALLER_THAN_MTU, Some(1)), Ok(())); + let out = client.process_output(now()).dgram().unwrap(); + assert_eq!(client.stats().frame_tx.datagram, dgram_sent + 1); + + server.process_input(&out, now()); + assert!(matches!( + server.next_event().unwrap(), + ConnectionEvent::Datagram(data) if data == DATA_SMALLER_THAN_MTU + )); +} + +fn connect_datagram() -> (Connection, Connection) { + let mut client = new_client( + ConnectionParameters::default() + .datagram_size(MAX_QUIC_DATAGRAM) + .outgoing_datagram_queue(OUTGOING_QUEUE), + ); + let mut server = new_server(ConnectionParameters::default().datagram_size(MAX_QUIC_DATAGRAM)); + connect_force_idle(&mut client, &mut server); + (client, server) +} + +#[test] +fn mtu_limit() { + let (client, server) = connect_datagram(); + + assert_eq!(client.max_datagram_size(), Ok(DATAGRAM_LEN_MTU)); + assert_eq!(server.max_datagram_size(), Ok(DATAGRAM_LEN_MTU)); +} + +#[test] +fn limit_data_size() { + let (mut client, mut server) = connect_datagram(); + + assert!(u64::try_from(DATA_BIGGER_THAN_MTU.len()).unwrap() > DATAGRAM_LEN_MTU); + // Datagram can be queued because they are smaller than allowed by the peer, + // but they cannot be sent. + assert_eq!(server.send_datagram(DATA_BIGGER_THAN_MTU, Some(1)), Ok(())); + + let dgram_dropped_s = server.stats().datagram_tx.dropped_too_big; + let dgram_sent_s = server.stats().frame_tx.datagram; + assert!(server.process_output(now()).dgram().is_none()); + assert_eq!( + server.stats().datagram_tx.dropped_too_big, + dgram_dropped_s + 1 + ); + assert_eq!(server.stats().frame_tx.datagram, dgram_sent_s); + assert!(matches!( + server.next_event().unwrap(), + ConnectionEvent::OutgoingDatagramOutcome { id, outcome } if id == 1 && outcome == OutgoingDatagramOutcome::DroppedTooBig + )); + + // The same test for the client side. + assert_eq!(client.send_datagram(DATA_BIGGER_THAN_MTU, Some(1)), Ok(())); + let dgram_sent_c = client.stats().frame_tx.datagram; + assert!(client.process_output(now()).dgram().is_none()); + assert_eq!(client.stats().frame_tx.datagram, dgram_sent_c); + assert!(matches!( + client.next_event().unwrap(), + ConnectionEvent::OutgoingDatagramOutcome { id, outcome } if id == 1 && outcome == OutgoingDatagramOutcome::DroppedTooBig + )); +} + +#[test] +fn after_dgram_dropped_continue_writing_frames() { + let (mut client, _) = connect_datagram(); + + assert!(u64::try_from(DATA_BIGGER_THAN_MTU.len()).unwrap() > DATAGRAM_LEN_MTU); + // Datagram can be queued because they are smaller than allowed by the peer, + // but they cannot be sent. + assert_eq!(client.send_datagram(DATA_BIGGER_THAN_MTU, Some(1)), Ok(())); + assert_eq!(client.send_datagram(DATA_SMALLER_THAN_MTU, Some(2)), Ok(())); + + let datagram_dropped = |e| { + matches!( + e, + ConnectionEvent::OutgoingDatagramOutcome { id, outcome } if id == 1 && outcome == OutgoingDatagramOutcome::DroppedTooBig) + }; + + let dgram_dropped_c = client.stats().datagram_tx.dropped_too_big; + let dgram_sent_c = client.stats().frame_tx.datagram; + + assert!(client.process_output(now()).dgram().is_some()); + assert_eq!(client.stats().frame_tx.datagram, dgram_sent_c + 1); + assert_eq!( + client.stats().datagram_tx.dropped_too_big, + dgram_dropped_c + 1 + ); + assert!(client.events().any(datagram_dropped)); +} + +#[test] +fn datagram_acked() { + let (mut client, mut server) = connect_datagram(); + + let dgram_sent = client.stats().frame_tx.datagram; + assert_eq!(client.send_datagram(DATA_SMALLER_THAN_MTU, Some(1)), Ok(())); + let out = client.process_output(now()).dgram(); + assert_eq!(client.stats().frame_tx.datagram, dgram_sent + 1); + + let dgram_received = server.stats().frame_rx.datagram; + server.process_input(&out.unwrap(), now()); + assert_eq!(server.stats().frame_rx.datagram, dgram_received + 1); + let now = now() + AT_LEAST_PTO; + // Ack should be sent + let ack_sent = server.stats().frame_tx.ack; + let out = server.process_output(now).dgram(); + assert_eq!(server.stats().frame_tx.ack, ack_sent + 1); + + assert!(matches!( + server.next_event().unwrap(), + ConnectionEvent::Datagram(data) if data == DATA_SMALLER_THAN_MTU + )); + + client.process_input(&out.unwrap(), now); + assert!(matches!( + client.next_event().unwrap(), + ConnectionEvent::OutgoingDatagramOutcome { id, outcome } if id == 1 && outcome == OutgoingDatagramOutcome::Acked + )); +} + +fn send_packet_and_get_server_event( + client: &mut Connection, + server: &mut Connection, +) -> ConnectionEvent { + let out = client.process_output(now()).dgram(); + server.process_input(&out.unwrap(), now()); + let mut events: Vec<_> = server + .events() + .filter_map(|evt| match evt { + ConnectionEvent::RecvStreamReadable { .. } | ConnectionEvent::Datagram { .. } => { + Some(evt) + } + _ => None, + }) + .collect(); + // We should only get one event - either RecvStreamReadable or Datagram. + assert_eq!(events.len(), 1); + events.remove(0) +} + +/// Write a datagram that is big enough to fill a packet, but then see that +/// normal priority stream data is sent first. +#[test] +fn datagram_after_stream_data() { + let (mut client, mut server) = connect_datagram(); + + // Write a datagram first. + let dgram_sent = client.stats().frame_tx.datagram; + assert_eq!(client.send_datagram(DATA_MTU, Some(1)), Ok(())); + + // Create a stream with normal priority and send some data. + let stream_id = client.stream_create(StreamType::BiDi).unwrap(); + client.stream_send(stream_id, &[6; 1200]).unwrap(); + + assert!( + matches!(send_packet_and_get_server_event(&mut client, &mut server), ConnectionEvent::RecvStreamReadable { stream_id: s } if s == stream_id) + ); + assert_eq!(client.stats().frame_tx.datagram, dgram_sent); + + if let ConnectionEvent::Datagram(data) = + &send_packet_and_get_server_event(&mut client, &mut server) + { + assert_eq!(data, DATA_MTU); + } else { + panic!(); + } + assert_eq!(client.stats().frame_tx.datagram, dgram_sent + 1); +} + +#[test] +fn datagram_before_stream_data() { + let (mut client, mut server) = connect_datagram(); + + // Create a stream with low priority and send some data before datagram. + let stream_id = client.stream_create(StreamType::BiDi).unwrap(); + client + .stream_priority( + stream_id, + TransmissionPriority::Low, + RetransmissionPriority::default(), + ) + .unwrap(); + client.stream_send(stream_id, &[6; 1200]).unwrap(); + + // Write a datagram. + let dgram_sent = client.stats().frame_tx.datagram; + assert_eq!(client.send_datagram(DATA_MTU, Some(1)), Ok(())); + + if let ConnectionEvent::Datagram(data) = + &send_packet_and_get_server_event(&mut client, &mut server) + { + assert_eq!(data, DATA_MTU); + } else { + panic!(); + } + assert_eq!(client.stats().frame_tx.datagram, dgram_sent + 1); + + assert!( + matches!(send_packet_and_get_server_event(&mut client, &mut server), ConnectionEvent::RecvStreamReadable { stream_id: s } if s == stream_id) + ); + assert_eq!(client.stats().frame_tx.datagram, dgram_sent + 1); +} + +#[test] +fn datagram_lost() { + let (mut client, _) = connect_datagram(); + + let dgram_sent = client.stats().frame_tx.datagram; + assert_eq!(client.send_datagram(DATA_SMALLER_THAN_MTU, Some(1)), Ok(())); + let _out = client.process_output(now()).dgram(); // This packet will be lost. + assert_eq!(client.stats().frame_tx.datagram, dgram_sent + 1); + + // Wait for PTO + let now = now() + AT_LEAST_PTO; + let dgram_sent2 = client.stats().frame_tx.datagram; + let pings_sent = client.stats().frame_tx.ping; + let dgram_lost = client.stats().datagram_tx.lost; + let out = client.process_output(now).dgram(); + assert!(out.is_some()); // PING probing + // Datagram is not sent again. + assert_eq!(client.stats().frame_tx.ping, pings_sent + 1); + assert_eq!(client.stats().frame_tx.datagram, dgram_sent2); + assert_eq!(client.stats().datagram_tx.lost, dgram_lost + 1); + + assert!(matches!( + client.next_event().unwrap(), + ConnectionEvent::OutgoingDatagramOutcome { id, outcome } if id == 1 && outcome == OutgoingDatagramOutcome::Lost + )); +} + +#[test] +fn datagram_sent_once() { + let (mut client, _) = connect_datagram(); + + let dgram_sent = client.stats().frame_tx.datagram; + assert_eq!(client.send_datagram(DATA_SMALLER_THAN_MTU, Some(1)), Ok(())); + let _out = client.process_output(now()).dgram(); + assert_eq!(client.stats().frame_tx.datagram, dgram_sent + 1); + + // Call process_output again should not send any new Datagram. + assert!(client.process_output(now()).dgram().is_none()); + assert_eq!(client.stats().frame_tx.datagram, dgram_sent + 1); +} + +#[test] +fn dgram_no_allowed() { + let mut client = default_client(); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + server.test_frame_writer = Some(Box::new(InsertDatagram { data: DATA_MTU })); + let out = server.process_output(now()).dgram().unwrap(); + server.test_frame_writer = None; + + client.process_input(&out, now()); + + assert_error( + &client, + &ConnectionError::Transport(Error::ProtocolViolation), + ); +} + +#[test] +#[allow(clippy::assertions_on_constants)] // this is a static assert, thanks +fn dgram_too_big() { + let mut client = + new_client(ConnectionParameters::default().datagram_size(DATAGRAM_LEN_SMALLER_THAN_MTU)); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + assert!(DATAGRAM_LEN_MTU > DATAGRAM_LEN_SMALLER_THAN_MTU); + server.test_frame_writer = Some(Box::new(InsertDatagram { data: DATA_MTU })); + let out = server.process_output(now()).dgram().unwrap(); + server.test_frame_writer = None; + + client.process_input(&out, now()); + + assert_error( + &client, + &ConnectionError::Transport(Error::ProtocolViolation), + ); +} + +#[test] +fn outgoing_datagram_queue_full() { + let (mut client, mut server) = connect_datagram(); + + let dgram_sent = client.stats().frame_tx.datagram; + assert_eq!(client.send_datagram(DATA_SMALLER_THAN_MTU, Some(1)), Ok(())); + assert_eq!( + client.send_datagram(DATA_SMALLER_THAN_MTU_2, Some(2)), + Ok(()) + ); + + // The outgoing datagram queue limit is 2, therefore the datagram with id 1 + // will be dropped after adding one more datagram. + let dgram_dropped = client.stats().datagram_tx.dropped_queue_full; + assert_eq!(client.send_datagram(DATA_MTU, Some(3)), Ok(())); + assert!(matches!( + client.next_event().unwrap(), + ConnectionEvent::OutgoingDatagramOutcome { id, outcome } if id == 1 && outcome == OutgoingDatagramOutcome::DroppedQueueFull + )); + assert_eq!( + client.stats().datagram_tx.dropped_queue_full, + dgram_dropped + 1 + ); + + // Send DATA_SMALLER_THAN_MTU_2 datagram + let out = client.process_output(now()).dgram(); + assert_eq!(client.stats().frame_tx.datagram, dgram_sent + 1); + server.process_input(&out.unwrap(), now()); + assert!(matches!( + server.next_event().unwrap(), + ConnectionEvent::Datagram(data) if data == DATA_SMALLER_THAN_MTU_2 + )); + + // Send DATA_SMALLER_THAN_MTU_2 datagram + let dgram_sent2 = client.stats().frame_tx.datagram; + let out = client.process_output(now()).dgram(); + assert_eq!(client.stats().frame_tx.datagram, dgram_sent2 + 1); + server.process_input(&out.unwrap(), now()); + assert!(matches!( + server.next_event().unwrap(), + ConnectionEvent::Datagram(data) if data == DATA_MTU + )); +} + +fn send_datagram(sender: &mut Connection, receiver: &mut Connection, data: &[u8]) { + let dgram_sent = sender.stats().frame_tx.datagram; + assert_eq!(sender.send_datagram(data, Some(1)), Ok(())); + let out = sender.process_output(now()).dgram().unwrap(); + assert_eq!(sender.stats().frame_tx.datagram, dgram_sent + 1); + + let dgram_received = receiver.stats().frame_rx.datagram; + receiver.process_input(&out, now()); + assert_eq!(receiver.stats().frame_rx.datagram, dgram_received + 1); +} + +#[test] +fn multiple_datagram_events() { + const DATA_SIZE: usize = 1200; + const MAX_QUEUE: usize = 3; + const FIRST_DATAGRAM: &[u8] = &[0; DATA_SIZE]; + const SECOND_DATAGRAM: &[u8] = &[1; DATA_SIZE]; + const THIRD_DATAGRAM: &[u8] = &[2; DATA_SIZE]; + const FOURTH_DATAGRAM: &[u8] = &[3; DATA_SIZE]; + + let mut client = new_client( + ConnectionParameters::default() + .datagram_size(u64::try_from(DATA_SIZE).unwrap()) + .incoming_datagram_queue(MAX_QUEUE), + ); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + send_datagram(&mut server, &mut client, FIRST_DATAGRAM); + send_datagram(&mut server, &mut client, SECOND_DATAGRAM); + send_datagram(&mut server, &mut client, THIRD_DATAGRAM); + + let mut datagrams = client.events().filter_map(|evt| { + if let ConnectionEvent::Datagram(d) = evt { + Some(d) + } else { + None + } + }); + assert_eq!(datagrams.next().unwrap(), FIRST_DATAGRAM); + assert_eq!(datagrams.next().unwrap(), SECOND_DATAGRAM); + assert_eq!(datagrams.next().unwrap(), THIRD_DATAGRAM); + assert!(datagrams.next().is_none()); + + // New events can be queued. + send_datagram(&mut server, &mut client, FOURTH_DATAGRAM); + let mut datagrams = client.events().filter_map(|evt| { + if let ConnectionEvent::Datagram(d) = evt { + Some(d) + } else { + None + } + }); + assert_eq!(datagrams.next().unwrap(), FOURTH_DATAGRAM); + assert!(datagrams.next().is_none()); +} + +#[test] +fn too_many_datagram_events() { + const DATA_SIZE: usize = 1200; + const MAX_QUEUE: usize = 2; + const FIRST_DATAGRAM: &[u8] = &[0; DATA_SIZE]; + const SECOND_DATAGRAM: &[u8] = &[1; DATA_SIZE]; + const THIRD_DATAGRAM: &[u8] = &[2; DATA_SIZE]; + const FOURTH_DATAGRAM: &[u8] = &[3; DATA_SIZE]; + + let mut client = new_client( + ConnectionParameters::default() + .datagram_size(u64::try_from(DATA_SIZE).unwrap()) + .incoming_datagram_queue(MAX_QUEUE), + ); + let mut server = default_server(); + connect_force_idle(&mut client, &mut server); + + send_datagram(&mut server, &mut client, FIRST_DATAGRAM); + send_datagram(&mut server, &mut client, SECOND_DATAGRAM); + send_datagram(&mut server, &mut client, THIRD_DATAGRAM); + + // Datagram with FIRST_DATAGRAM data will be dropped. + assert!(matches!( + client.next_event().unwrap(), + ConnectionEvent::Datagram(data) if data == SECOND_DATAGRAM + )); + assert!(matches!( + client.next_event().unwrap(), + ConnectionEvent::IncomingDatagramDropped + )); + assert!(matches!( + client.next_event().unwrap(), + ConnectionEvent::Datagram(data) if data == THIRD_DATAGRAM + )); + assert!(client.next_event().is_none()); + assert_eq!(client.stats().incoming_datagram_dropped, 1); + + // New events can be queued. + send_datagram(&mut server, &mut client, FOURTH_DATAGRAM); + assert!(matches!( + client.next_event().unwrap(), + ConnectionEvent::Datagram(data) if data == FOURTH_DATAGRAM + )); + assert!(client.next_event().is_none()); + assert_eq!(client.stats().incoming_datagram_dropped, 1); +} + +#[test] +fn multiple_quic_datagrams_in_one_packet() { + let (mut client, mut server) = connect_datagram(); + + let dgram_sent = client.stats().frame_tx.datagram; + // Enqueue 2 datagrams that can fit in a single packet. + assert_eq!( + client.send_datagram(DATA_SMALLER_THAN_MTU_2, Some(1)), + Ok(()) + ); + assert_eq!( + client.send_datagram(DATA_SMALLER_THAN_MTU_2, Some(2)), + Ok(()) + ); + + let out = client.process_output(now()).dgram(); + assert_eq!(client.stats().frame_tx.datagram, dgram_sent + 2); + server.process_input(&out.unwrap(), now()); + let datagram = |e: &_| matches!(e, ConnectionEvent::Datagram(..)); + assert_eq!(server.events().filter(datagram).count(), 2); +} + +/// Datagrams that are close to the capacity of the packet need special +/// handling. They need to use the packet-filling frame type and +/// they cannot allow other frames to follow. +#[test] +fn datagram_fill() { + struct PanickingFrameWriter {} + impl crate::connection::test_internal::FrameWriter for PanickingFrameWriter { + fn write_frames(&mut self, builder: &mut PacketBuilder) { + panic!( + "builder invoked with {} bytes remaining", + builder.remaining() + ); + } + } + struct TrackingFrameWriter { + called: Rc<RefCell<bool>>, + } + impl crate::connection::test_internal::FrameWriter for TrackingFrameWriter { + fn write_frames(&mut self, builder: &mut PacketBuilder) { + assert_eq!(builder.remaining(), 2); + *self.called.borrow_mut() = true; + } + } + + let (mut client, mut server) = connect_datagram(); + + // Work out how much space we have for a datagram. + let space = { + let p = client.paths.primary(); + let path = p.borrow(); + // Minimum overhead is connection ID length, 1 byte short header, 1 byte packet number, + // 1 byte for the DATAGRAM frame type, and 16 bytes for the AEAD. + path.mtu() - path.remote_cid().len() - 19 + }; + assert!(space >= 64); // Unlikely, but this test depends on the datagram being this large. + + // This should not be called. + client.test_frame_writer = Some(Box::new(PanickingFrameWriter {})); + + let buf = vec![9; space]; + // This will completely fill available space. + send_datagram(&mut client, &mut server, &buf); + // This will leave 1 byte free, but more frames won't be added in this space. + send_datagram(&mut client, &mut server, &buf[..buf.len() - 1]); + // This will leave 2 bytes free, which is enough space for a length field, + // but not enough space for another frame after that. + send_datagram(&mut client, &mut server, &buf[..buf.len() - 2]); + // Three bytes free will be space enough for a length frame, but not enough + // space left over for another frame (we need 2 bytes). + send_datagram(&mut client, &mut server, &buf[..buf.len() - 3]); + + // Four bytes free is enough space for another frame. + let called = Rc::new(RefCell::new(false)); + client.test_frame_writer = Some(Box::new(TrackingFrameWriter { + called: Rc::clone(&called), + })); + send_datagram(&mut client, &mut server, &buf[..buf.len() - 4]); + assert!(*called.borrow()); +} |