dynamixel2/
bus.rs

1use serial2::SerialPort;
2use std::path::Path;
3use std::time::{Duration, Instant};
4
5use crate::bytestuff;
6use crate::checksum::calculate_checksum;
7use crate::endian::{read_u16_le, read_u32_le, read_u8_le, write_u16_le};
8use crate::{ReadError, TransferError, WriteError};
9
10const HEADER_PREFIX: [u8; 4] = [0xFF, 0xFF, 0xFD, 0x00];
11const HEADER_SIZE: usize = 8;
12const STATUS_HEADER_SIZE: usize = 9;
13
14/// Dynamixel Protocol 2 communication bus.
15pub struct Bus<ReadBuffer, WriteBuffer> {
16	/// The underlying stream (normally a serial port).
17	serial_port: SerialPort,
18
19	/// The baud rate of the serial port, if known.
20	baud_rate: u32,
21
22	/// The buffer for reading incoming messages.
23	read_buffer: ReadBuffer,
24
25	/// The total number of valid bytes in the read buffer.
26	read_len: usize,
27
28	/// The number of leading bytes in the read buffer that have already been used.
29	used_bytes: usize,
30
31	/// The buffer for outgoing messages.
32	write_buffer: WriteBuffer,
33}
34
35impl<ReadBuffer, WriteBuffer> std::fmt::Debug for Bus<ReadBuffer, WriteBuffer> {
36	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37		#[derive(Debug)]
38		#[allow(dead_code)] // Dead code analysis ignores derive debug impls, but that is the whole point of this struct.
39		enum Raw {
40			#[cfg(unix)]
41			Fd(std::os::unix::io::RawFd),
42			#[cfg(windows)]
43			Handle(std::os::windows::io::RawHandle),
44		}
45
46		#[cfg(unix)]
47		let raw = {
48			use std::os::unix::io::AsRawFd;
49			Raw::Fd(self.serial_port.as_raw_fd())
50		};
51		#[cfg(windows)]
52		let raw = {
53			use std::os::windows::io::AsRawHandle;
54			Raw::Handle(self.serial_port.as_raw_handle())
55		};
56
57		f.debug_struct("Bus")
58			.field("serial_port", &raw)
59			.field("baud_rate", &self.baud_rate)
60			.finish_non_exhaustive()
61	}
62}
63
64impl Bus<Vec<u8>, Vec<u8>> {
65	/// Open a serial port with the given baud rate.
66	///
67	/// This will allocate a new read and write buffer of 128 bytes each.
68	/// Use [`Self::open_with_buffers()`] if you want to use a custom buffers.
69	pub fn open(path: impl AsRef<Path>, baud_rate: u32) -> std::io::Result<Self> {
70		let port = SerialPort::open(path, baud_rate)?;
71		Ok(Self::with_buffers_and_baud_rate(port, vec![0; 128], vec![0; 128], baud_rate))
72	}
73
74	/// Create a new bus for an open serial port.
75	///
76	/// The serial port must already be configured in raw mode with the correct baud rate,
77	/// character size (8), parity (disabled) and stop bits (1).
78	///
79	/// This will allocate a new read and write buffer of 128 bytes each.
80	/// Use [`Self::with_buffers()`] if you want to use a custom buffers.
81	pub fn new(serial_port: SerialPort) -> Result<Self, crate::InitializeError> {
82		Self::with_buffers(serial_port, vec![0; 128], vec![0; 128])
83	}
84}
85
86impl<ReadBuffer, WriteBuffer> Bus<ReadBuffer, WriteBuffer>
87where
88	ReadBuffer: AsRef<[u8]> + AsMut<[u8]>,
89	WriteBuffer: AsRef<[u8]> + AsMut<[u8]>,
90{
91	/// Open a serial port with the given baud rate.
92	///
93	/// This will allocate a new read and write buffer of 128 bytes each.
94	pub fn open_with_buffers(
95		path: impl AsRef<Path>,
96		baud_rate: u32,
97		read_buffer: ReadBuffer,
98		write_buffer: WriteBuffer,
99	) -> std::io::Result<Self> {
100		let port = SerialPort::open(path, baud_rate)?;
101		Ok(Self::with_buffers_and_baud_rate(port, read_buffer, write_buffer, baud_rate))
102	}
103
104	/// Create a new bus using pre-allocated buffers.
105	///
106	/// The serial port must already be configured in raw mode with the correct baud rate,
107	/// character size (8), parity (disabled) and stop bits (1).
108	pub fn with_buffers(serial_port: SerialPort, read_buffer: ReadBuffer, write_buffer: WriteBuffer) -> Result<Self, crate::InitializeError> {
109		let baud_rate = serial_port.get_configuration()
110			.map_err(crate::InitializeError::GetConfiguration)?
111			.get_baud_rate()
112			.map_err(crate::InitializeError::GetBaudRate)?;
113
114		Ok(Self::with_buffers_and_baud_rate(serial_port, read_buffer, write_buffer, baud_rate))
115	}
116
117	/// Create a new bus using pre-allocated buffers.
118	fn with_buffers_and_baud_rate(serial_port: SerialPort, read_buffer: ReadBuffer, mut write_buffer: WriteBuffer, baud_rate: u32) -> Self {
119		// Pre-fill write buffer with the header prefix.
120		// TODO: return Err instead of panicing.
121		assert!(write_buffer.as_mut().len() >= HEADER_SIZE + 2);
122		write_buffer.as_mut()[..4].copy_from_slice(&HEADER_PREFIX);
123
124		Self {
125			serial_port,
126			baud_rate,
127			read_buffer,
128			read_len: 0,
129			used_bytes: 0,
130			write_buffer,
131		}
132	}
133
134	/// Get a reference to the underlying [`SerialPort`].
135	///
136	/// Note that performing any read or write with the [`SerialPort`] bypasses the read/write buffer of the bus,
137	/// and may disrupt the communication with the motors.
138	/// In general, it should be safe to read and write to the bus manually in between instructions,
139	/// if the response from the motors has already been received.
140	pub fn serial_port(&self) -> &SerialPort {
141		&self.serial_port
142	}
143
144	/// Consume this bus object to get ownership of the serial port.
145	///
146	/// This discards any data in internal the read buffer of the bus object.
147	/// This is normally not a problem, since all data in the read buffer is also discarded when transmitting a new command.
148	pub fn into_serial_port(self) -> SerialPort {
149		self.serial_port
150	}
151
152	/// Get the baud rate of the bus.
153	pub fn baud_rate(&self) -> u32 {
154		self.baud_rate
155	}
156
157	/// Set the baud rate of the underlying serial port.
158	pub fn set_baud_rate(&mut self, baud_rate: u32) -> Result<(), std::io::Error> {
159		let mut settings = self.serial_port.get_configuration()?;
160		settings.set_baud_rate(baud_rate)?;
161		self.serial_port.set_configuration(&settings)?;
162		self.baud_rate = baud_rate;
163		Ok(())
164	}
165
166	/// Write a raw instruction to a stream, and read a single raw response.
167	///
168	/// This function also checks that the packet ID of the status response matches the one from the instruction.
169	///
170	/// This is not suitable for broadcast instructions.
171	/// For broadcast instructions, each motor sends an individual response or no response is send at all.
172	/// Instead, use [`Self::write_instruction`] and [`Self::read_status_response`].
173	pub fn transfer_single<F>(
174		&mut self,
175		packet_id: u8,
176		instruction_id: u8,
177		parameter_count: usize,
178		expected_response_parameters: u16,
179		encode_parameters: F,
180	) -> Result<StatusPacket<'_>, TransferError>
181	where
182		F: FnOnce(&mut [u8]),
183	{
184		self.write_instruction(packet_id, instruction_id, parameter_count, encode_parameters)?;
185		let response = self.read_status_response(expected_response_parameters)?;
186		crate::error::InvalidPacketId::check(response.packet_id(), packet_id).map_err(crate::ReadError::from)?;
187		Ok(response)
188	}
189
190	/// Write an instruction message to the bus.
191	pub fn write_instruction<F>(
192		&mut self,
193		packet_id: u8,
194		instruction_id: u8,
195		parameter_count: usize,
196		encode_parameters: F,
197	) -> Result<(), WriteError>
198	where
199		F: FnOnce(&mut [u8]),
200	{
201		let buffer = self.write_buffer.as_mut();
202
203		// Check if the buffer can hold the unstuffed message.
204		crate::error::BufferTooSmallError::check(HEADER_SIZE + parameter_count + 2, buffer.len())?;
205
206		// Add the header, with a placeholder for the length field.
207		buffer[4] = packet_id;
208		buffer[5] = 0;
209		buffer[6] = 0;
210		buffer[7] = instruction_id;
211		encode_parameters(&mut buffer[HEADER_SIZE..][..parameter_count]);
212
213		// Perform bitstuffing on the body.
214		// The header never needs stuffing.
215		let stuffed_body_len = bytestuff::stuff_inplace(&mut buffer[HEADER_SIZE..], parameter_count)?;
216
217		write_u16_le(&mut buffer[5..], stuffed_body_len as u16 + 3);
218
219		// Add checksum.
220		let checksum_index = HEADER_SIZE + stuffed_body_len;
221		let checksum = calculate_checksum(0, &buffer[..checksum_index]);
222		write_u16_le(&mut buffer[checksum_index..], checksum);
223
224		// Throw away old data in the read buffer and the kernel read buffer.
225		// We don't do this when reading a reply, because we might receive multiple replies for one instruction,
226		// and read() can potentially read more than one reply per syscall.
227		self.read_len = 0;
228		self.used_bytes = 0;
229		self.serial_port.discard_input_buffer().map_err(WriteError::DiscardBuffer)?;
230
231		// Send message.
232		let stuffed_message = &buffer[..checksum_index + 2];
233		trace!("sending instruction: {:02X?}", stuffed_message);
234		self.serial_port.write_all(stuffed_message).map_err(WriteError::Write)?;
235		Ok(())
236	}
237
238	/// Read a raw status response from the bus with the given deadline.
239	pub fn read_status_response_deadline(&mut self, deadline: Instant) -> Result<StatusPacket, ReadError> {
240		// Check that the read buffer is large enough to hold atleast a status packet header.
241		crate::error::BufferTooSmallError::check(STATUS_HEADER_SIZE, self.read_buffer.as_mut().len())?;
242
243		let stuffed_message_len = loop {
244			self.remove_garbage();
245
246			// The call to remove_garbage() removes all leading bytes that don't match a status header.
247			// So if there's enough bytes left, it's a status header.
248			if self.read_len > STATUS_HEADER_SIZE {
249				let read_buffer = &self.read_buffer.as_mut()[..self.read_len];
250				let body_len = read_buffer[5] as usize + read_buffer[6] as usize * 256;
251				let body_len = body_len - 2; // Length includes instruction and error fields, which is already included in STATUS_HEADER_SIZE too.
252
253				// Check if the read buffer is large enough for the entire message.
254				// We don't have to remove the read bytes, because `write_instruction()` already clears the read buffer.
255				crate::error::BufferTooSmallError::check(STATUS_HEADER_SIZE + body_len, self.read_buffer.as_mut().len())?;
256
257				if self.read_len >= STATUS_HEADER_SIZE + body_len {
258					break STATUS_HEADER_SIZE + body_len;
259				}
260			}
261
262			let timeout = match deadline.checked_duration_since(Instant::now()) {
263				Some(x) => x,
264				None => {
265					trace!(
266						"timeout reading status response, data in buffer: {:02X?}",
267						&self.read_buffer.as_ref()[..self.read_len]
268					);
269					return Err(std::io::ErrorKind::TimedOut.into());
270				},
271			};
272
273			// Try to read more data into the buffer.
274			self.serial_port.set_read_timeout(timeout).ok();
275			let new_data = self.serial_port.read(&mut self.read_buffer.as_mut()[self.read_len..])?;
276			if new_data == 0 {
277				continue;
278			}
279
280			self.read_len += new_data;
281		};
282
283		let buffer = self.read_buffer.as_mut();
284		let parameters_end = stuffed_message_len - 2;
285		trace!("read packet: {:02X?}", &buffer[..parameters_end]);
286
287		let checksum_message = read_u16_le(&buffer[parameters_end..]);
288		let checksum_computed = calculate_checksum(0, &buffer[..parameters_end]);
289		if checksum_message != checksum_computed {
290			self.consume_read_bytes(stuffed_message_len);
291			return Err(crate::InvalidChecksum {
292				message: checksum_message,
293				computed: checksum_computed,
294			}
295			.into());
296		}
297
298		// Mark the whole message as "used_bytes", so that the next call to `remove_garbage()` removes it.
299		self.used_bytes += stuffed_message_len;
300
301		// Remove byte-stuffing from the parameters.
302		let parameter_count = bytestuff::unstuff_inplace(&mut buffer[STATUS_HEADER_SIZE..parameters_end]);
303
304		// Wrap the data in a `StatusPacket`.
305		let response = StatusPacket {
306			data: &self.read_buffer.as_ref()[..STATUS_HEADER_SIZE + parameter_count],
307		};
308
309		crate::InvalidInstruction::check(response.instruction_id(), crate::instructions::instruction_id::STATUS)?;
310		crate::MotorError::check(response.error())?;
311		Ok(response)
312	}
313
314	/// Read a raw status response with an automatically calculated timeout.
315	///
316	/// The read timeout is determined by the expected number of response parameters and the baud rate of the bus.
317	pub fn read_status_response(&mut self, expected_parameters: u16) -> Result<StatusPacket, ReadError> {
318		// Offical SDK adds a flat 34 milliseconds, so lets just mimick that.
319		let message_size = STATUS_HEADER_SIZE as u32 + u32::from(expected_parameters) + 2;
320		let timeout = message_transfer_time(message_size, self.baud_rate) + Duration::from_millis(34);
321		self.read_status_response_deadline(Instant::now() + timeout)
322	}
323}
324
325/// Calculate the required time to transfer a message of a given size.
326///
327/// The size must include any headers and footers of the message.
328pub(crate) fn message_transfer_time(message_size: u32, baud_rate: u32) -> Duration {
329	let baud_rate = u64::from(baud_rate);
330	let bits = u64::from(message_size) * 10; // each byte is 1 start bit, 8 data bits and 1 stop bit.
331	let secs = bits / baud_rate;
332	let subsec_bits = bits % baud_rate;
333	let nanos = (subsec_bits * 1_000_000_000).div_ceil(baud_rate);
334	Duration::new(secs, nanos as u32)
335}
336
337impl<ReadBuffer, WriteBuffer> Bus<ReadBuffer, WriteBuffer>
338where
339	ReadBuffer: AsRef<[u8]> + AsMut<[u8]>,
340	WriteBuffer: AsRef<[u8]> + AsMut<[u8]>,
341{
342	/// Remove leading garbage data from the read buffer.
343	fn remove_garbage(&mut self) {
344		let read_buffer = self.read_buffer.as_mut();
345		let garbage_len = find_header(&read_buffer[..self.read_len][self.used_bytes..]);
346		if garbage_len > 0 {
347			debug!("skipping {} bytes of leading garbage.", garbage_len);
348			trace!("skipped garbage: {:02X?}", &read_buffer[..garbage_len]);
349		}
350		self.consume_read_bytes(self.used_bytes + garbage_len);
351		debug_assert_eq!(self.used_bytes, 0);
352	}
353
354	fn consume_read_bytes(&mut self, len: usize) {
355		debug_assert!(len <= self.read_len);
356		self.read_buffer.as_mut().copy_within(len..self.read_len, 0);
357		// Decrease both used_bytes and read_len together.
358		// Some consumed bytes may be garbage instead of used bytes though.
359		// So we use `saturating_sub` for `used_bytes` to cap the result at 0.
360		self.used_bytes = self.used_bytes.saturating_sub(len);
361		self.read_len -= len;
362	}
363}
364
365/// A status response that is currently in the read buffer of a bus.
366///
367/// When dropped, the response data is removed from the read buffer.
368#[derive(Debug)]
369pub struct StatusPacket<'a> {
370	/// Message data (with byte-stuffing already undone).
371	data: &'a [u8],
372}
373
374impl<'a> StatusPacket<'a> {
375	/// Get the raw bytes of the message.
376	///
377	/// This includes the message header and the parameters.
378	/// It does not include the CRC or byte-stuffing.
379	pub fn as_bytes(&self) -> &[u8] {
380		self.data
381	}
382
383	/// The packet ID of the response.
384	pub fn packet_id(&self) -> u8 {
385		self.as_bytes()[4]
386	}
387
388	/// The instruction ID of the response.
389	pub fn instruction_id(&self) -> u8 {
390		self.as_bytes()[7]
391	}
392
393	/// The error field of the response.
394	pub fn error(&self) -> u8 {
395		self.as_bytes()[8]
396	}
397
398	/// The error number of the status packet.
399	///
400	/// This is the lower 7 bits of the error field.
401	pub fn error_number(&self) -> u8 {
402		self.error() & !0x80
403	}
404
405	/// The alert bit from the error field of the response.
406	///
407	/// This is the 8th bit of the error field.
408	///
409	/// If this bit is set, you can normally check the "Hardware Error" register for more details.
410	/// Consult the manual of your motor for more information.
411	pub fn alert(&self) -> bool {
412		self.error() & 0x80 != 0
413	}
414
415	/// The parameters of the response.
416	pub fn parameters(&self) -> &[u8] {
417		&self.data[STATUS_HEADER_SIZE..]
418	}
419}
420
421/// Find the potential starting position of a header.
422///
423/// This will return the first possible position of the header prefix.
424/// Note that if the buffer ends with a partial header prefix,
425/// the start position of the partial header prefix is returned.
426fn find_header(buffer: &[u8]) -> usize {
427	for i in 0..buffer.len() {
428		let possible_prefix = HEADER_PREFIX.len().min(buffer.len() - i);
429		if buffer[i..].starts_with(&HEADER_PREFIX[..possible_prefix]) {
430			return i;
431		}
432	}
433
434	buffer.len()
435}
436
437/// A response from a motor.
438///
439/// Note that the `Eq` and `PartialEq` compare all fields of the struct,
440/// including the `motor_id` and `alert`.
441#[derive(Debug, Clone, Eq, PartialEq)]
442pub struct Response<T> {
443	/// The motor that sent the response.
444	pub motor_id: u8,
445
446	/// The alert bit from the response message.
447	///
448	/// If this bit is set, you can normally check the "Hardware Error" register for more details.
449	/// Consult the manual of your motor for more information.
450	pub alert: bool,
451
452	/// The data from the motor.
453	pub data: T,
454}
455
456impl<'a> TryFrom<StatusPacket<'a>> for Response<()> {
457	type Error = crate::InvalidParameterCount;
458
459	fn try_from(status_packet: StatusPacket<'a>) -> Result<Self, Self::Error> {
460		crate::InvalidParameterCount::check(status_packet.parameters().len(), 0)?;
461		Ok(Self {
462			motor_id: status_packet.packet_id(),
463			alert: status_packet.alert(),
464			data: (),
465		})
466	}
467}
468
469impl<'a, 'b> From<&'b StatusPacket<'a>> for Response<&'b [u8]> {
470	fn from(status_packet: &'b StatusPacket<'a>) -> Self {
471		Self {
472			motor_id: status_packet.packet_id(),
473			alert: status_packet.alert(),
474			data: status_packet.parameters(),
475		}
476	}
477}
478
479impl<'a> From<StatusPacket<'a>> for Response<Vec<u8>> {
480	fn from(status_packet: StatusPacket<'a>) -> Self {
481		Self {
482			motor_id: status_packet.packet_id(),
483			alert: status_packet.alert(),
484			data: status_packet.parameters().to_owned(),
485		}
486	}
487}
488
489impl<'a> TryFrom<StatusPacket<'a>> for Response<u8> {
490	type Error = crate::InvalidParameterCount;
491
492	fn try_from(status_packet: StatusPacket<'a>) -> Result<Self, Self::Error> {
493		crate::InvalidParameterCount::check(status_packet.parameters().len(), 1)?;
494		Ok(Self {
495			motor_id: status_packet.packet_id(),
496			alert: status_packet.alert(),
497			data: read_u8_le(status_packet.parameters()),
498		})
499	}
500}
501
502impl<'a> TryFrom<StatusPacket<'a>> for Response<u16> {
503	type Error = crate::InvalidParameterCount;
504
505	fn try_from(status_packet: StatusPacket<'a>) -> Result<Self, Self::Error> {
506		crate::InvalidParameterCount::check(status_packet.parameters().len(), 2)?;
507		Ok(Self {
508			motor_id: status_packet.packet_id(),
509			alert: status_packet.alert(),
510			data: read_u16_le(status_packet.parameters()),
511		})
512	}
513}
514
515impl<'a> TryFrom<StatusPacket<'a>> for Response<u32> {
516	type Error = crate::InvalidParameterCount;
517
518	fn try_from(status_packet: StatusPacket<'a>) -> Result<Self, Self::Error> {
519		crate::InvalidParameterCount::check(status_packet.parameters().len(), 4)?;
520		Ok(Self {
521			motor_id: status_packet.packet_id(),
522			alert: status_packet.alert(),
523			data: read_u32_le(status_packet.parameters()),
524		})
525	}
526}
527
528#[cfg(test)]
529mod test {
530	use super::*;
531	use assert2::assert;
532
533	#[test]
534	fn test_find_garbage_end() {
535		assert!(find_header(&[0xFF]) == 0);
536		assert!(find_header(&[0xFF, 0xFF]) == 0);
537		assert!(find_header(&[0xFF, 0xFF, 0xFD]) == 0);
538		assert!(find_header(&[0xFF, 0xFF, 0xFD, 0x00]) == 0);
539		assert!(find_header(&[0xFF, 0xFF, 0xFD, 0x00, 9]) == 0);
540
541		assert!(find_header(&[0, 1, 2, 3, 4, 0xFF]) == 5);
542		assert!(find_header(&[0, 1, 2, 3, 4, 0xFF, 0xFF]) == 5);
543		assert!(find_header(&[0, 1, 2, 3, 4, 0xFF, 0xFF, 0xFD]) == 5);
544		assert!(find_header(&[0, 1, 2, 3, 4, 0xFF, 0xFF, 0xFD, 0x00]) == 5);
545		assert!(find_header(&[0, 1, 2, 3, 4, 0xFF, 0xFF, 0xFD, 0x00, 9]) == 5);
546
547		assert!(find_header(&[0xFF, 1]) == 2);
548		assert!(find_header(&[0, 1, 2, 3, 4, 0xFF, 6]) == 7);
549	}
550
551	#[test]
552	fn test_message_transfer_time() {
553		// Try a bunch of values to ensure we dealt with overflow correctly.
554		assert!(message_transfer_time(100, 1_000) == Duration::from_secs(1));
555		assert!(message_transfer_time(1_000, 10_000) == Duration::from_secs(1));
556		assert!(message_transfer_time(1_000, 1_000_000) == Duration::from_millis(10));
557		assert!(message_transfer_time(1_000, 10_000_000) == Duration::from_millis(1));
558		assert!(message_transfer_time(1_000, 100_000_000) == Duration::from_micros(100));
559		assert!(message_transfer_time(1_000, 1_000_000_000) == Duration::from_micros(10));
560		assert!(message_transfer_time(1_000, 2_000_000_000) == Duration::from_micros(5));
561		assert!(message_transfer_time(1_000, 4_000_000_000) == Duration::from_nanos(2500));
562		assert!(message_transfer_time(10_000, 4_000_000_000) == Duration::from_micros(25));
563		assert!(message_transfer_time(1_000_000, 4_000_000_000) == Duration::from_micros(2500));
564		assert!(message_transfer_time(10_000_000, 4_000_000_000) == Duration::from_millis(25));
565		assert!(message_transfer_time(100_000_000, 4_000_000_000) == Duration::from_millis(250));
566		assert!(message_transfer_time(1_000_000_000, 4_000_000_000) == Duration::from_millis(2500));
567		assert!(message_transfer_time(2_000_000_000, 4_000_000_000) == Duration::from_secs(5));
568		assert!(message_transfer_time(4_000_000_000, 4_000_000_000) == Duration::from_secs(10));
569		assert!(message_transfer_time(4_000_000_000, 2_000_000_000) == Duration::from_secs(20));
570		assert!(message_transfer_time(4_000_000_000, 1_000_000_000) == Duration::from_secs(40));
571		assert!(message_transfer_time(4_000_000_000, 100_000_000) == Duration::from_secs(400));
572		assert!(message_transfer_time(4_000_000_000, 10_000_000) == Duration::from_secs(4_000));
573		assert!(message_transfer_time(4_000_000_000, 1_000_000) == Duration::from_secs(40_000));
574		assert!(message_transfer_time(4_000_000_000, 100_000) == Duration::from_secs(400_000));
575		assert!(message_transfer_time(4_000_000_000, 10_000) == Duration::from_secs(4_000_000));
576		assert!(message_transfer_time(4_000_000_000, 1_000) == Duration::from_secs(40_000_000));
577		assert!(message_transfer_time(4_000_000_000, 100) == Duration::from_secs(400_000_000));
578		assert!(message_transfer_time(4_000_000_000, 10) == Duration::from_secs(4_000_000_000));
579		assert!(message_transfer_time(4_000_000_000, 1) == Duration::from_secs(40_000_000_000));
580
581		assert!(message_transfer_time(43, 1) == Duration::from_secs(430));
582		assert!(message_transfer_time(43, 10) == Duration::from_secs(43));
583		assert!(message_transfer_time(43, 2) == Duration::from_secs(215));
584		assert!(message_transfer_time(43, 20) == Duration::from_millis(21_500));
585		assert!(message_transfer_time(43, 200) == Duration::from_millis(2_150));
586		assert!(message_transfer_time(43, 2_000_000) == Duration::from_micros(215));
587		assert!(message_transfer_time(43, 2_000_000_000) == Duration::from_nanos(215));
588		assert!(message_transfer_time(43, 4_000_000_000) == Duration::from_nanos(108)); // rounded up
589		assert!(message_transfer_time(3, 4_000_000_000) == Duration::from_nanos(8)); // rounded up
590		assert!(message_transfer_time(5, 4_000_000_000) == Duration::from_nanos(13)); // rounded up
591
592		let lots = u32::MAX - 1; // Use MAX - 1 because MAX is not cleanly divisible by 2.
593		assert!(message_transfer_time(lots, 1) == Duration::from_secs(u64::from(lots) * 10));
594		assert!(message_transfer_time(lots, lots) == Duration::from_secs(10));
595		assert!(message_transfer_time(lots / 2, lots) == Duration::from_secs(5));
596		assert!(message_transfer_time(lots, lots / 2) == Duration::from_secs(20));
597	}
598}