pub const SYSEX_START: u8 = 0xF0;
pub const SYSEX_EOX: u8 = 0xF7;
pub const SYSEX_HEADER: [u8; 4] = [0x7D, 0x46, 0x50, 0x01];
pub const MAX_PAYLOAD_SIZE: usize = 512;
pub const MAX_PLAIN_SIZE: usize = MAX_PAYLOAD_SIZE + 2;
pub const MAX_PACKED_SIZE: usize = packed_len(MAX_PLAIN_SIZE);
pub const MAX_SYSEX_FRAME: usize = 1 + SYSEX_HEADER.len() + MAX_PACKED_SIZE + 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SysexError {
InvalidByte,
BufferTooSmall,
Truncated,
}
pub const fn packed_len(n: usize) -> usize {
n + n.div_ceil(7)
}
pub fn pack_7bit(src: &[u8], dst: &mut [u8]) -> Result<usize, SysexError> {
if dst.len() < packed_len(src.len()) {
return Err(SysexError::BufferTooSmall);
}
let mut written = 0;
for group in src.chunks(7) {
let msb_idx = written;
dst[msb_idx] = 0;
written += 1;
for (i, &byte) in group.iter().enumerate() {
dst[msb_idx] |= (byte >> 7) << i;
dst[written] = byte & 0x7F;
written += 1;
}
}
Ok(written)
}
pub fn unpack_7bit(src: &[u8], dst: &mut [u8]) -> Result<usize, SysexError> {
let mut written = 0;
for group in src.chunks(8) {
if group.len() < 2 {
return Err(SysexError::Truncated);
}
let msb = group[0];
for (i, &byte) in group[1..].iter().enumerate() {
if byte & 0x80 != 0 || msb & 0x80 != 0 {
return Err(SysexError::InvalidByte);
}
if written >= dst.len() {
return Err(SysexError::BufferTooSmall);
}
dst[written] = byte | (((msb >> i) & 1) << 7);
written += 1;
}
}
Ok(written)
}
#[cfg(test)]
mod tests {
use super::*;
fn roundtrip(plain: &[u8]) {
let mut packed = [0u8; packed_len(MAX_PLAIN_SIZE)];
let packed_size = pack_7bit(plain, &mut packed).unwrap();
assert_eq!(packed_size, packed_len(plain.len()));
assert!(packed[..packed_size].iter().all(|b| b & 0x80 == 0));
let mut unpacked = [0u8; MAX_PLAIN_SIZE];
let plain_size = unpack_7bit(&packed[..packed_size], &mut unpacked).unwrap();
assert_eq!(&unpacked[..plain_size], plain);
}
#[test]
fn empty_roundtrip() {
roundtrip(&[]);
}
#[test]
fn small_roundtrips() {
roundtrip(&[0x00]);
roundtrip(&[0xFF]);
roundtrip(&[0x80, 0x7F, 0x00, 0xFF, 0x01, 0xFE, 0xAA]);
roundtrip(&[0x80, 0x7F, 0x00, 0xFF, 0x01, 0xFE, 0xAA, 0x55]);
}
#[test]
fn max_size_roundtrip() {
let mut plain = [0u8; MAX_PLAIN_SIZE];
for (i, byte) in plain.iter_mut().enumerate() {
*byte = (i % 256) as u8;
}
roundtrip(&plain);
}
#[test]
fn unpack_rejects_high_bit() {
let mut dst = [0u8; 8];
assert_eq!(
unpack_7bit(&[0x00, 0x80], &mut dst),
Err(SysexError::InvalidByte)
);
}
#[test]
fn unpack_rejects_lone_msb_byte() {
let mut dst = [0u8; 8];
assert_eq!(unpack_7bit(&[0x01], &mut dst), Err(SysexError::Truncated));
}
#[test]
fn frame_size_consts() {
assert_eq!(MAX_PACKED_SIZE, 588);
assert_eq!(MAX_SYSEX_FRAME, 594);
}
}