Source code

Revision control

Copy as Markdown

Other Tools

use coremidi_sys::MIDIPacketList;
use coremidi_sys::{MIDIPacket, MIDIPacketNext, MIDITimeStamp};
use std::fmt;
use std::ops::{Deref, DerefMut};
use std::slice;
pub type Timestamp = u64;
const MAX_PACKET_DATA_LENGTH: usize = 0xffffusize;
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
pub mod alignment {
pub type Marker = [u32; 0]; // ensures 4-byte alignment (on ARM)
pub const NEEDS_ALIGNMENT: bool = true;
}
#[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))]
pub mod alignment {
pub type Marker = [u8; 0]; // unaligned
pub const NEEDS_ALIGNMENT: bool = false;
}
/// A collection of simultaneous MIDI events.
///
#[repr(C)]
pub struct Packet {
// NOTE: At runtime this type must only be used behind immutable references
// that point to valid instances of MIDIPacket (mutable references would allow mem::swap).
// This type must NOT implement `Copy`!
// On ARM, this must be 4-byte aligned.
inner: PacketInner,
_alignment_marker: alignment::Marker,
}
#[repr(packed)]
struct PacketInner {
timestamp: MIDITimeStamp,
length: u16,
data: [u8; 0], // zero-length, because we cannot make this type bigger without knowing how much data there actually is
}
impl Packet {
/// Get the packet timestamp.
///
pub fn timestamp(&self) -> Timestamp {
self.inner.timestamp as Timestamp
}
/// Get the packet data. This method just gives raw MIDI bytes. You would need another
/// library to decode them and work with higher level events.
///
///
/// The following example:
///
/// ```
/// let packet_list = &coremidi::PacketBuffer::new(0, &[0x90, 0x40, 0x7f]);
/// for packet in packet_list.iter() {
/// for byte in packet.data() {
/// print!(" {:x}", byte);
/// }
/// }
/// ```
///
/// will print:
///
/// ```text
/// 90 40 7f
/// ```
pub fn data(&self) -> &[u8] {
let data_ptr = self.inner.data.as_ptr();
let data_len = self.inner.length as usize;
unsafe { slice::from_raw_parts(data_ptr, data_len) }
}
}
impl fmt::Debug for Packet {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let result = write!(
f,
"Packet(ptr={:x}, ts={:016x}, data=[",
self as *const _ as usize,
self.timestamp() as u64
);
let result = self
.data()
.iter()
.enumerate()
.fold(result, |prev_result, (i, b)| match prev_result {
Err(err) => Err(err),
Ok(()) => {
let sep = if i > 0 { ", " } else { "" };
write!(f, "{}{:02x}", sep, b)
}
});
result.and_then(|_| write!(f, "])"))
}
}
impl fmt::Display for Packet {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let result = write!(f, "{:016x}:", self.timestamp());
self.data()
.iter()
.fold(result, |prev_result, b| match prev_result {
Err(err) => Err(err),
Ok(()) => write!(f, " {:02x}", b),
})
}
}
/// A [list of MIDI events](https://developer.apple.com/reference/coremidi/midipacketlist) being received from, or being sent to, one endpoint.
///
#[repr(C)]
pub struct PacketList {
// NOTE: This type must only exist in the form of immutable references
// pointing to valid instances of MIDIPacketList.
// This type must NOT implement `Copy`!
inner: PacketListInner,
_do_not_construct: alignment::Marker,
}
#[repr(packed)]
struct PacketListInner {
num_packets: u32,
data: [MIDIPacket; 0],
}
impl PacketList {
/// For internal usage only.
/// Requires this instance to actually point to a valid MIDIPacketList
pub(crate) unsafe fn as_ptr(&self) -> *mut MIDIPacketList {
self as *const PacketList as *mut PacketList as *mut MIDIPacketList
}
}
impl PacketList {
/// Check if the packet list is empty.
///
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Get the number of packets in the list.
///
pub fn len(&self) -> usize {
self.inner.num_packets as usize
}
/// Get an iterator for the packets in the list.
///
pub fn iter(&self) -> PacketListIterator {
PacketListIterator {
count: self.len(),
packet_ptr: std::ptr::addr_of!(self.inner.data) as *const MIDIPacket,
_phantom: ::std::marker::PhantomData::default(),
}
}
}
impl fmt::Debug for PacketList {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let result = write!(f, "PacketList(ptr={:x}, packets=[", unsafe {
self.as_ptr() as usize
});
self.iter()
.enumerate()
.fold(result, |prev_result, (i, packet)| match prev_result {
Err(err) => Err(err),
Ok(()) => {
let sep = if i != 0 { ", " } else { "" };
write!(f, "{}{:?}", sep, packet)
}
})
.and_then(|_| write!(f, "])"))
}
}
impl fmt::Display for PacketList {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let num_packets = self.inner.num_packets;
let result = write!(f, "PacketList(len={})", num_packets);
self.iter()
.fold(result, |prev_result, packet| match prev_result {
Err(err) => Err(err),
Ok(()) => write!(f, "\n {}", packet),
})
}
}
pub struct PacketListIterator<'a> {
count: usize,
packet_ptr: *const MIDIPacket,
_phantom: ::std::marker::PhantomData<&'a Packet>,
}
impl<'a> Iterator for PacketListIterator<'a> {
type Item = &'a Packet;
fn next(&mut self) -> Option<&'a Packet> {
if self.count > 0 {
let packet = unsafe { &*(self.packet_ptr as *const Packet) };
self.count -= 1;
self.packet_ptr = unsafe { MIDIPacketNext(self.packet_ptr) };
Some(packet)
} else {
None
}
}
}
const PACKET_LIST_HEADER_SIZE: usize = 4; // MIDIPacketList::numPackets: UInt32
const PACKET_HEADER_SIZE: usize = 8 + // MIDIPacket::timeStamp: MIDITimeStamp/UInt64
2; // MIDIPacket::length: UInt16
const INLINE_PACKET_BUFFER_SIZE: usize = 28; // must be divisible by 4
enum PacketBufferStorage {
/// Inline stores the data directy on the stack, if it is small enough.
/// NOTE: using u32 ensures correct alignment (required on ARM)
Inline([u32; INLINE_PACKET_BUFFER_SIZE / 4]),
/// External is used whenever the size of the data exceeds INLINE_PACKET_BUFFER_SIZE.
/// This means that the size of the contained vector is always greater than INLINE_PACKET_BUFFER_SIZE.
External(Vec<u32>),
}
impl PacketBufferStorage {
#[inline]
pub fn with_capacity(capacity: usize) -> PacketBufferStorage {
if capacity <= INLINE_PACKET_BUFFER_SIZE {
PacketBufferStorage::Inline([0; INLINE_PACKET_BUFFER_SIZE / 4])
} else {
let u32_len = ((capacity - 1) / 4) + 1;
let mut buffer = Vec::with_capacity(u32_len);
unsafe {
buffer.set_len(u32_len);
}
PacketBufferStorage::External(buffer)
}
}
#[inline]
fn capacity(&self) -> usize {
match *self {
PacketBufferStorage::Inline(ref inline) => inline.len() * 4,
PacketBufferStorage::External(ref vec) => vec.len() * 4,
}
}
#[inline]
fn get_slice(&self) -> &[u8] {
unsafe {
match *self {
PacketBufferStorage::Inline(ref inline) => {
slice::from_raw_parts(inline.as_ptr() as *const u8, inline.len() * 4)
}
PacketBufferStorage::External(ref vec) => {
slice::from_raw_parts(vec.as_ptr() as *const u8, vec.len() * 4)
}
}
}
}
#[inline]
fn get_slice_mut(&mut self) -> &mut [u8] {
unsafe {
match *self {
PacketBufferStorage::Inline(ref mut inline) => {
slice::from_raw_parts_mut(inline.as_mut_ptr() as *mut u8, inline.len() * 4)
}
PacketBufferStorage::External(ref mut vec) => {
slice::from_raw_parts_mut(vec.as_mut_ptr() as *mut u8, vec.len() * 4)
}
}
}
}
unsafe fn assign_packet(&mut self, packet_offset: usize, time: MIDITimeStamp, data: &[u8]) {
assert!(data.len() <= MAX_PACKET_DATA_LENGTH, "packet data too long"); // cannot store longer size in u16
if alignment::NEEDS_ALIGNMENT {
debug_assert!(packet_offset & 0b11 == 0);
}
let slice = self.get_slice_mut();
let ptr = slice[packet_offset..].as_mut_ptr() as *mut Packet;
(*ptr).inner.timestamp = time;
(*ptr).inner.length = data.len() as u16;
let packet_data_start = packet_offset + PACKET_HEADER_SIZE;
slice[packet_data_start..(packet_data_start + data.len())].copy_from_slice(data);
}
/// Requires that there is a valid Packet at `offset`, which has enough space for `data`
unsafe fn extend_packet(&mut self, packet_offset: usize, data: &[u8]) {
let slice = self.get_slice_mut();
let ptr = slice[packet_offset..].as_mut_ptr() as *mut Packet;
let packet_data_start = packet_offset + PACKET_HEADER_SIZE + (*ptr).inner.length as usize;
(*ptr).inner.length += data.len() as u16;
slice[packet_data_start..(packet_data_start + data.len())].copy_from_slice(data);
}
/// Call this only with larger length values (won't make the buffer smaller)
unsafe fn ensure_capacity(&mut self, capacity: usize) {
if capacity < INLINE_PACKET_BUFFER_SIZE || capacity < self.get_slice().len() {
return;
}
let vec_capacity = ((capacity - 1) / 4) + 1;
let vec: Option<Vec<u32>> = match *self {
PacketBufferStorage::Inline(ref inline) => {
let mut v = Vec::with_capacity(vec_capacity);
v.extend_from_slice(inline);
v.set_len(vec_capacity);
Some(v)
}
PacketBufferStorage::External(ref mut vec) => {
let current_len = vec.len();
vec.reserve(vec_capacity - current_len);
vec.set_len(vec_capacity);
None
}
};
// to prevent borrowcheck errors, this must come after the `match`
if let Some(v) = vec {
*self = PacketBufferStorage::External(v);
}
}
}
impl Deref for PacketBufferStorage {
type Target = PacketList;
#[inline]
fn deref(&self) -> &PacketList {
unsafe { &*(self.get_slice().as_ptr() as *const PacketList) }
}
}
impl DerefMut for PacketBufferStorage {
// NOTE: Mutable references `&mut PacketList` must not be exposed in the public API!
// The user could use mem::swap to modify the header without modifying the packets that follow.
#[inline]
fn deref_mut(&mut self) -> &mut PacketList {
unsafe { &mut *(self.get_slice_mut().as_mut_ptr() as *mut PacketList) }
}
}
/// A mutable `PacketList` builder.
///
/// A `PacketList` is an inmmutable reference to a [MIDIPacketList](https://developer.apple.com/reference/coremidi/midipacketlist) structure,
/// while a `PacketBuffer` is a mutable structure that allows to build a `PacketList` by adding packets.
/// It dereferences to a `PacketList`, so it can be used whenever a `PacketList` is needed.
///
pub struct PacketBuffer {
storage: PacketBufferStorage,
last_packet_offset: usize,
}
impl Deref for PacketBuffer {
type Target = PacketList;
#[inline]
fn deref(&self) -> &PacketList {
self.storage.deref()
}
}
impl PacketBuffer {
/// Create a `PacketBuffer` with a single packet containing the provided timestamp and data.
///
/// According to the official documentation for CoreMIDI, the timestamp represents
/// the time at which the events are to be played, where zero means "now".
/// The timestamp applies to the first MIDI byte in the packet.
///
/// Example on how to create a `PacketBuffer` with a single packet for a MIDI note on for C-5:
///
/// ```
/// let buffer = coremidi::PacketBuffer::new(0, &[0x90, 0x3c, 0x7f]);
/// assert_eq!(buffer.len(), 1)
/// ```
pub fn new(time: MIDITimeStamp, data: &[u8]) -> PacketBuffer {
let capacity = data.len() + PACKET_LIST_HEADER_SIZE + PACKET_HEADER_SIZE;
let mut storage = PacketBufferStorage::with_capacity(capacity);
storage.deref_mut().inner.num_packets = 1;
let last_packet_offset = PACKET_LIST_HEADER_SIZE;
unsafe {
storage.assign_packet(last_packet_offset, time, data);
}
PacketBuffer {
storage,
last_packet_offset,
}
}
/// Create an empty `PacketBuffer` with no packets.
///
/// Example on how to create an empty `PacketBuffer`
/// with a capacity for 128 bytes in total (including headers):
///
/// ```
/// let buffer = coremidi::PacketBuffer::with_capacity(128);
/// assert_eq!(buffer.len(), 0);
/// assert_eq!(buffer.capacity(), 128);
/// ```
pub fn with_capacity(capacity: usize) -> PacketBuffer {
let capacity = std::cmp::max(capacity, INLINE_PACKET_BUFFER_SIZE);
let mut storage = PacketBufferStorage::with_capacity(capacity);
storage.deref_mut().inner.num_packets = 0;
PacketBuffer {
storage,
last_packet_offset: PACKET_LIST_HEADER_SIZE,
}
}
/// Get underlying buffer capacity in bytes
pub fn capacity(&self) -> usize {
self.storage.capacity()
}
/// Add a new event containing the provided timestamp and data.
///
/// According to the official documentation for CoreMIDI, the timestamp represents
/// the time at which the events are to be played, where zero means "now".
/// The timestamp applies to the first MIDI byte in the packet.
///
/// An event must not have a timestamp that is smaller than that of a previous event
/// in the same `PacketList`
///
/// Example:
///
/// ```
/// let mut chord = coremidi::PacketBuffer::new(0, &[0x90, 0x3c, 0x7f]);
/// chord.push_data(0, &[0x90, 0x40, 0x7f]);
/// assert_eq!(chord.len(), 1);
/// let repr = format!("{}", &chord as &coremidi::PacketList);
/// assert_eq!(repr, "PacketList(len=1)\n 0000000000000000: 90 3c 7f 90 40 7f");
/// ```
pub fn push_data(&mut self, time: MIDITimeStamp, data: &[u8]) -> &mut Self {
let (can_merge, previous_data_len) = self.can_merge_into_last_packet(time, data);
if can_merge {
let new_packet_size = Self::packet_size(previous_data_len + data.len());
unsafe {
self.storage
.ensure_capacity(self.last_packet_offset + new_packet_size);
self.storage.extend_packet(self.last_packet_offset, data);
}
} else {
let packet_size = Self::packet_size(data.len());
let next_offset = self.next_packet_offset();
unsafe {
self.storage.ensure_capacity(next_offset + packet_size);
self.storage.assign_packet(next_offset, time, data);
}
self.packet_list_mut().num_packets += 1;
self.last_packet_offset = next_offset;
}
self
}
/// Clears the buffer, removing all packets.
/// Note that this method has no effect on the allocated capacity of the buffer.
pub fn clear(&mut self) {
self.packet_list_mut().num_packets = 0;
self.last_packet_offset = PACKET_LIST_HEADER_SIZE;
}
/// Checks whether the given tiemstamped data can be merged into the previous packet
fn can_merge_into_last_packet(&self, time: MIDITimeStamp, data: &[u8]) -> (bool, usize) {
if self.packet_list_is_empty() {
(false, 0)
} else {
let previous_packet = self.last_packet();
let previous_packet_data = previous_packet.data();
let previous_data_len = previous_packet_data.len();
let can_merge = previous_packet.timestamp() == time
&& Self::not_sysex(data)
&& Self::has_status_byte(data)
&& Self::not_sysex(previous_packet_data)
&& Self::has_status_byte(previous_packet_data)
&& previous_data_len + data.len() < MAX_PACKET_DATA_LENGTH;
(can_merge, previous_data_len)
}
}
#[inline]
fn last_packet(&self) -> &Packet {
assert!(self.packet_list().num_packets > 0);
let packets_slice = self.storage.get_slice();
let packet_slot = &packets_slice[self.last_packet_offset..];
unsafe { &*(packet_slot.as_ptr() as *const Packet) }
}
#[inline]
fn next_packet_offset(&self) -> usize {
if self.packet_list_is_empty() {
self.last_packet_offset
} else {
let data_len = self.last_packet().inner.length as usize;
let next_offset = self.last_packet_offset + Self::packet_size(data_len);
if alignment::NEEDS_ALIGNMENT {
(next_offset + 3) & !(3usize)
} else {
next_offset
}
}
}
#[inline]
fn not_sysex(data: &[u8]) -> bool {
data[0] != 0xF0
}
#[inline]
fn has_status_byte(data: &[u8]) -> bool {
data[0] & 0b10000000 != 0
}
#[inline]
fn packet_size(data_len: usize) -> usize {
PACKET_HEADER_SIZE + data_len
}
#[inline]
fn packet_list(&self) -> &PacketListInner {
&self.storage.deref().inner
}
#[inline]
fn packet_list_is_empty(&self) -> bool {
self.packet_list().num_packets == 0
}
#[inline]
fn packet_list_mut(&mut self) -> &mut PacketListInner {
&mut self.storage.deref_mut().inner
}
}
#[cfg(test)]
mod tests {
use super::*;
use coremidi_sys::{MIDIPacketList, MIDITimeStamp};
use std::mem;
#[test]
pub fn packet_struct_layout() {
let expected_align = if super::alignment::NEEDS_ALIGNMENT {
4
} else {
1
};
assert_eq!(expected_align, mem::align_of::<Packet>());
assert_eq!(expected_align, mem::align_of::<PacketList>());
let dummy_packet: Packet = unsafe { mem::zeroed() };
let ptr = &dummy_packet as *const _ as *const u8;
assert_eq!(
PACKET_HEADER_SIZE,
dummy_packet.inner.data.as_ptr() as usize - ptr as usize
);
let dummy_packet_list: PacketList = unsafe { mem::zeroed() };
let ptr = &dummy_packet_list as *const _ as *const u8;
assert_eq!(
PACKET_LIST_HEADER_SIZE,
std::ptr::addr_of!(dummy_packet_list.inner.data) as usize - ptr as usize
);
}
#[test]
pub fn single_packet_alloc_inline() {
let packet_buf = PacketBuffer::new(42, &[0x90u8, 0x40, 0x7f]);
if let PacketBufferStorage::External(_) = packet_buf.storage {
panic!("A single 3-byte message must not be allocated externally")
}
}
#[test]
fn packet_buffer_deref() {
let packet_buf = PacketBuffer::new(42, &[0x90u8, 0x40, 0x7f]);
let packet_list: &PacketList = &packet_buf;
assert_eq!(
unsafe { packet_list.as_ptr() as *const MIDIPacketList },
packet_buf.storage.get_slice().as_ptr() as *const _ as *const MIDIPacketList
);
}
#[test]
fn packet_list_length() {
let mut packet_buf = PacketBuffer::new(42, &[0x90u8, 0x40, 0x7f]);
packet_buf.push_data(43, &[0x91u8, 0x40, 0x7f]);
packet_buf.push_data(44, &[0x80u8, 0x40, 0x7f]);
packet_buf.push_data(45, &[0x81u8, 0x40, 0x7f]);
assert_eq!(packet_buf.len(), 4);
}
#[test]
fn packet_buffer_empty_with_capacity() {
let packet_buf = PacketBuffer::with_capacity(128);
assert_eq!(packet_buf.capacity(), 128);
assert_eq!(packet_buf.len(), 0);
}
#[test]
fn packet_buffer_with_capacity_zero() {
let packet_buf = PacketBuffer::with_capacity(0);
assert_eq!(packet_buf.capacity(), INLINE_PACKET_BUFFER_SIZE);
assert_eq!(packet_buf.len(), 0);
}
#[test]
fn packet_buffer_with_capacity() {
let mut packet_buf = PacketBuffer::with_capacity(128);
packet_buf.push_data(43, &[0x91u8, 0x40, 0x7f]);
packet_buf.push_data(44, &[0x80u8, 0x40, 0x7f]);
packet_buf.push_data(45, &[0x81u8, 0x40, 0x7f]);
assert_eq!(packet_buf.capacity(), 128);
assert_eq!(packet_buf.len(), 3);
}
#[test]
fn packet_buffer_clear() {
let mut packet_buf = PacketBuffer::new(42, &[0x90u8, 0x40, 0x7f]);
packet_buf.push_data(43, &[0x91u8, 0x40, 0x7f]);
packet_buf.push_data(44, &[0x80u8, 0x40, 0x7f]);
packet_buf.push_data(45, &[0x81u8, 0x40, 0x7f]);
assert_eq!(packet_buf.len(), 4);
packet_buf.clear();
assert_eq!(packet_buf.len(), 0);
}
#[test]
fn compare_equal_timestamps() {
// these messages should be merged into a single packet
unsafe {
compare_packet_list(vec![
(42, vec![0x90, 0x40, 0x7f]),
(42, vec![0x90, 0x41, 0x7f]),
(42, vec![0x90, 0x42, 0x7f]),
])
}
}
#[test]
fn compare_unequal_timestamps() {
unsafe {
compare_packet_list(vec![
(42, vec![0x90, 0x40, 0x7f]),
(43, vec![0x90, 0x40, 0x7f]),
(44, vec![0x90, 0x40, 0x7f]),
])
}
}
#[test]
fn compare_sysex() {
// the sysex must not be merged with the surrounding packets
unsafe {
compare_packet_list(vec![
(42, vec![0x90, 0x40, 0x7f]),
(42, vec![0xF0, 0x01, 0x01, 0x01, 0x01, 0x01, 0xF7]), // sysex
(42, vec![0x90, 0x41, 0x7f]),
])
}
}
#[test]
fn compare_sysex_split() {
// the sysex must not be merged with the surrounding packets
unsafe {
compare_packet_list(vec![
(42, vec![0x90, 0x40, 0x7f]),
(42, vec![0xF0, 0x01, 0x01, 0x01, 0x01]), // sysex part 1
(42, vec![0x01, 0xF7]), // sysex part 2
(42, vec![0x90, 0x41, 0x7f]),
])
}
}
#[test]
fn compare_sysex_split2() {
// the sysex must not be merged with the surrounding packets
unsafe {
compare_packet_list(vec![
(42, vec![0x90, 0x40, 0x7f]),
(42, vec![0xF0, 0x01, 0x01, 0x01, 0x01]), // sysex part 1
(42, vec![0x01, 0x01, 0x01]), // sysex part 2
(42, vec![0x01, 0xF7]), // sysex part 3
(42, vec![0x90, 0x41, 0x7f]),
])
}
}
#[test]
fn compare_sysex_malformed() {
// the sysex must not be merged with the surrounding packets
unsafe {
compare_packet_list(vec![
(42, vec![0x90, 0x40, 0x7f]),
(42, vec![0xF0, 0x01, 0x01, 0x01, 0x01]), // sysex part 1
(42, vec![0x01, 0x01, 0x01]), // sysex part 2
//(42, vec![0x01, 0xF7]), // sysex part 3 (missing)
(42, vec![0x90, 0x41, 0x7f]),
])
}
}
#[test]
fn compare_sysex_long() {
let mut sysex = vec![0xF0];
sysex.resize(301, 0x01);
sysex.push(0xF7);
unsafe {
compare_packet_list(vec![
(42, vec![0x90, 0x40, 0x7f]),
(43, vec![0x90, 0x41, 0x7f]),
(43, sysex),
])
}
}
/// Compares the results of building a PacketList using our PacketBuffer API
/// and the native API (MIDIPacketListAdd, etc).
unsafe fn compare_packet_list(packets: Vec<(MIDITimeStamp, Vec<u8>)>) {
use coremidi_sys::{MIDIPacketListAdd, MIDIPacketListInit};
// allocate a buffer on the stack for building the list using native methods
const BUFFER_SIZE: usize = 65536; // maximum allowed size
let buffer: &mut [u8] = &mut [0; BUFFER_SIZE];
let pkt_list_ptr = buffer.as_mut_ptr() as *mut MIDIPacketList;
// build the list
let mut pkt_ptr = MIDIPacketListInit(pkt_list_ptr);
for pkt in &packets {
pkt_ptr = MIDIPacketListAdd(
pkt_list_ptr,
BUFFER_SIZE as u64,
pkt_ptr,
pkt.0,
pkt.1.len() as u64,
pkt.1.as_ptr(),
);
assert!(!pkt_ptr.is_null());
}
let list_native = &*(pkt_list_ptr as *const _ as *const PacketList);
// build the PacketBuffer, containing the same packets
let mut packet_buf = PacketBuffer::new(packets[0].0, &packets[0].1);
for pkt in &packets[1..] {
packet_buf.push_data(pkt.0, &pkt.1);
}
// print buffer contents for debugging purposes
let packet_buf_slice = packet_buf.storage.get_slice();
println!(
"\nbuffer: {:?}",
packet_buf_slice
.iter()
.map(|b| format!("{:02X}", b))
.collect::<Vec<String>>()
.join(" ")
);
println!(
"\nnative: {:?}",
buffer[0..packet_buf_slice.len()]
.iter()
.map(|b| format!("{:02X}", b))
.collect::<Vec<String>>()
.join(" ")
);
let list: &PacketList = &packet_buf;
// check if the contents match
assert_eq!(
list_native.len(),
list.len(),
"PacketList lengths must match"
);
for (n, p) in list_native.iter().zip(list.iter()) {
assert_eq!(n.data(), p.data());
}
}
}