feat: add test protocol
This commit is contained in:
@@ -72,3 +72,185 @@ pub fn decode_float16(lo: u8, hi: u8) -> Result<f32, DecodingError> {
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// ------ STRING TESTS ------
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encode_ascii_exact_length() {
|
||||||
|
let result = encode_ascii("hello", 5, b' ');
|
||||||
|
assert_eq!(result.unwrap(), b"hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encode_ascii_pads_short_string() {
|
||||||
|
let result = encode_ascii("hi", 5, b'\0');
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap(),
|
||||||
|
vec![b'h', b'i', 0, 0, 0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encode_ascii_rejects_zero_length() {
|
||||||
|
let result = encode_ascii("hello", 0, b' ');
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().to_string(),
|
||||||
|
"Invalid string length: 0"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encode_ascii_rejects_non_ascii() {
|
||||||
|
let result = encode_ascii("héllo", 10, b' ');
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().to_string(),
|
||||||
|
"String must contain ASCII characters only"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encode_ascii_rejects_too_long_string() {
|
||||||
|
let result = encode_ascii("abcdef", 5, b' ');
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().to_string(),
|
||||||
|
"String too long: got 6, expected 5"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decode_ascii_without_strip_keeps_padding() {
|
||||||
|
let raw = b"abc ";
|
||||||
|
|
||||||
|
let result = decode_ascii(raw, false);
|
||||||
|
|
||||||
|
assert_eq!(result.unwrap(), "abc ");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decode_ascii_strip_removes_spaces() {
|
||||||
|
let raw = b"abc ";
|
||||||
|
|
||||||
|
let result = decode_ascii(raw, true);
|
||||||
|
|
||||||
|
assert_eq!(result.unwrap(), "abc");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decode_ascii_strip_removes_null_bytes() {
|
||||||
|
let raw = b"abc\0\0";
|
||||||
|
|
||||||
|
let result = decode_ascii(raw, true);
|
||||||
|
|
||||||
|
assert_eq!(result.unwrap(), "abc");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decode_ascii_rejects_non_ascii() {
|
||||||
|
let raw = vec![0xff, 0xfe];
|
||||||
|
|
||||||
|
let result = decode_ascii(&raw, false);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().to_string(),
|
||||||
|
"Invalid ASCII data"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ------ FLOAT TESTS ------
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decode_float16_zero() {
|
||||||
|
// IEEE754 half precision: 0x0000
|
||||||
|
let result = decode_float16(0x00, 0x00);
|
||||||
|
|
||||||
|
assert_eq!(result.unwrap(), 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decode_float16_negative_zero() {
|
||||||
|
// IEEE754 half precision: 0x8000
|
||||||
|
let result = decode_float16(0x00, 0x80);
|
||||||
|
|
||||||
|
assert_eq!(result.unwrap(), -0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decode_float16_one() {
|
||||||
|
// IEEE754 half precision: 1.0 = 0x3c00
|
||||||
|
let result = decode_float16(0x00, 0x3c);
|
||||||
|
|
||||||
|
assert_eq!(result.unwrap(), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decode_float16_negative_one() {
|
||||||
|
// IEEE754 half precision: -1.0 = 0xbc00
|
||||||
|
let result = decode_float16(0x00, 0xbc);
|
||||||
|
|
||||||
|
assert_eq!(result.unwrap(), -1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decode_float16_fraction() {
|
||||||
|
// IEEE754 half precision: 0.5 = 0x3800
|
||||||
|
let result = decode_float16(0x00, 0x38);
|
||||||
|
|
||||||
|
assert_eq!(result.unwrap(), 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decode_float16_subnormal() {
|
||||||
|
// Smallest positive subnormal half float = 0x0001
|
||||||
|
let result = decode_float16(0x01, 0x00);
|
||||||
|
|
||||||
|
let value = result.unwrap();
|
||||||
|
|
||||||
|
assert!(value > 0.0);
|
||||||
|
assert!(value < 0.0001);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decode_float16_positive_infinity() {
|
||||||
|
// +inf = 0x7c00
|
||||||
|
let result = decode_float16(0x00, 0x7c);
|
||||||
|
|
||||||
|
assert_eq!(result.unwrap(), f32::INFINITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decode_float16_negative_infinity() {
|
||||||
|
// -inf = 0xfc00
|
||||||
|
let result = decode_float16(0x00, 0xfc);
|
||||||
|
|
||||||
|
assert_eq!(result.unwrap(), f32::NEG_INFINITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decode_float16_nan() {
|
||||||
|
// NaN = exponent all ones + non-zero mantissa
|
||||||
|
let result = decode_float16(0x01, 0x7c);
|
||||||
|
|
||||||
|
assert!(result.unwrap().is_nan());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decode_float16_large_number() {
|
||||||
|
// 65504 = largest finite IEEE754 half float = 0x7bff
|
||||||
|
let result = decode_float16(0xff, 0x7b);
|
||||||
|
|
||||||
|
assert_eq!(result.unwrap(), 65504.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
+294
-18
@@ -193,42 +193,318 @@ pub fn map_response(frame: &Frame) -> Result<DeviceResponseType, ProtocolError>
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
fn frame(command: u8, payload: Vec<u8>) -> Frame {
|
||||||
|
Frame { command, payload }
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn acknowledgement() {
|
fn acknowledgement() {
|
||||||
let frame = Frame { command: 0x01, payload: vec![] };
|
let result = map_response(&frame(0x01, vec![])).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map_response(&frame).unwrap(),
|
result,
|
||||||
DeviceResponseType::Acknowledgement(Acknowledgement { success: true })
|
DeviceResponseType::Acknowledgement(Acknowledgement {
|
||||||
|
success: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn not_implemented() {
|
||||||
|
let result = map_response(&frame(0x02, vec![])).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
DeviceResponseType::NotImplemented(NotImplemented)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn handshake_ack() {
|
||||||
|
let result = map_response(&frame(0x10, vec![0x01])).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
DeviceResponseType::HandshakeAck(HandshakeAck {
|
||||||
|
ack: Some(1)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn handshake_ack_invalid_payload_length() {
|
||||||
|
let result = map_response(&frame(0x10, vec![1, 2]));
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn device_name() {
|
||||||
|
let result = map_response(&frame(0x13, b"TestDevice".to_vec()))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
DeviceResponseType::DeviceInfo(DeviceInfo {
|
||||||
|
name: "TestDevice".into()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn device_name_missing_payload() {
|
||||||
|
assert!(
|
||||||
|
map_response(&frame(0x13, vec![]))
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn device_name_invalid_ascii() {
|
||||||
|
assert!(
|
||||||
|
map_response(&frame(0x13, vec![0xff]))
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn current_preset() {
|
||||||
|
let result = map_response(&frame(0x14, vec![5]))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
DeviceResponseType::CurrentPreset(CurrentPreset {
|
||||||
|
index: 5
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn current_preset_wrong_length_errors() {
|
fn current_preset_wrong_length_errors() {
|
||||||
let frame = Frame { command: 0x14, payload: vec![1, 2] };
|
assert!(
|
||||||
assert!(map_response(&frame).is_err());
|
map_response(&frame(0x14, vec![1, 2]))
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unknown_command_falls_through() {
|
fn modified_preset() {
|
||||||
let frame = Frame { command: 0x99, payload: vec![0xAA] };
|
let payload = vec![0xff; 20];
|
||||||
match map_response(&frame).unwrap() {
|
|
||||||
DeviceResponseType::UnknownResponse(r) => {
|
let result = map_response(&frame(0x22, payload))
|
||||||
assert_eq!(r.cmd, "0x99");
|
.unwrap();
|
||||||
assert_eq!(r.raw, vec![0xAA]);
|
|
||||||
|
match result {
|
||||||
|
DeviceResponseType::ModifiedPreset(p) => {
|
||||||
|
assert_eq!(p.modified.len(), 20);
|
||||||
|
assert!(p.modified.iter().all(|v| *v));
|
||||||
}
|
}
|
||||||
other => panic!("expected UnknownResponse, got {:?}", other),
|
other => panic!("unexpected {:?}", other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn modified_preset_invalid_length() {
|
||||||
|
assert!(
|
||||||
|
map_response(&frame(0x22, vec![0; 19]))
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn config_chunk() {
|
||||||
|
let result = map_response(&frame(
|
||||||
|
0x24,
|
||||||
|
vec![3, 0xaa, 0xbb],
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
DeviceResponseType::ConfigChunk(ConfigChunk {
|
||||||
|
sub_index: 3,
|
||||||
|
data: vec![0xaa, 0xbb],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn config_chunk_empty_payload_defaults_to_zero() {
|
fn config_chunk_empty_payload_defaults_to_zero() {
|
||||||
let frame = Frame { command: 0x24, payload: vec![] };
|
let result = map_response(&frame(0x24, vec![]))
|
||||||
match map_response(&frame).unwrap() {
|
.unwrap();
|
||||||
DeviceResponseType::ConfigChunk(c) => {
|
|
||||||
assert_eq!(c.sub_index, 0);
|
assert_eq!(
|
||||||
assert_eq!(c.data, Vec::<u8>::new());
|
result,
|
||||||
|
DeviceResponseType::ConfigChunk(ConfigChunk {
|
||||||
|
sub_index: 0,
|
||||||
|
data: vec![],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn config_chunk_invalid_index() {
|
||||||
|
assert!(
|
||||||
|
map_response(&frame(0x24, vec![0x1d]))
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preset_name() {
|
||||||
|
let result = map_response(&frame(
|
||||||
|
0x29,
|
||||||
|
vec![2, b'A', b'm', b'p'],
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
DeviceResponseType::PresetName(PresetName {
|
||||||
|
index: 2,
|
||||||
|
name: "Amp".into(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preset_name_missing_index() {
|
||||||
|
assert!(
|
||||||
|
map_response(&frame(0x29, vec![]))
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preset_name_invalid_ascii() {
|
||||||
|
assert!(
|
||||||
|
map_response(&frame(0x29, vec![1, 0xff]))
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn device_flags_locked() {
|
||||||
|
let mut payload = vec![0; 6];
|
||||||
|
payload[5] = 1;
|
||||||
|
|
||||||
|
let result = map_response(&frame(0x2c, payload))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
DeviceResponseType::DeviceFlags(DeviceFlags {
|
||||||
|
is_locked: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn device_flags_invalid_length() {
|
||||||
|
assert!(
|
||||||
|
map_response(&frame(0x2c, vec![0; 5]))
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn device_flags_invalid_lock_flag() {
|
||||||
|
let mut payload = vec![0; 6];
|
||||||
|
payload[5] = 2;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
map_response(&frame(0x2c, payload))
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn authentication_success() {
|
||||||
|
let result = map_response(&frame(
|
||||||
|
0x2d,
|
||||||
|
vec![0, 1],
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
DeviceResponseType::AuthenticationResult(
|
||||||
|
AuthenticationResult {
|
||||||
|
success: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn authentication_missing_result() {
|
||||||
|
assert!(
|
||||||
|
map_response(&frame(0x2d, vec![0]))
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn authentication_invalid_flag() {
|
||||||
|
assert!(
|
||||||
|
map_response(&frame(0x2d, vec![0, 2]))
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn meters_invalid_length() {
|
||||||
|
assert!(
|
||||||
|
map_response(&frame(0x40, vec![0; 10]))
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn meters_decode() {
|
||||||
|
// 38 bytes required.
|
||||||
|
// First 36 bytes are meter values, remaining 2 bytes ignored.
|
||||||
|
let mut payload = vec![0; 38];
|
||||||
|
|
||||||
|
// half float 1.0 = 0x3c00
|
||||||
|
for i in (0..36).step_by(3) {
|
||||||
|
payload[i] = 0x00;
|
||||||
|
payload[i + 1] = 0x3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = map_response(&frame(0x40, payload))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match result {
|
||||||
|
DeviceResponseType::Meters(m) => {
|
||||||
|
assert_eq!(m.levels.len(), 12);
|
||||||
|
|
||||||
|
for level in m.levels {
|
||||||
|
assert!(level.is_finite());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
other => panic!("expected ConfigChunk, got {:?}", other),
|
other => panic!("unexpected {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unknown_command_falls_through() {
|
||||||
|
let result = map_response(&frame(
|
||||||
|
0x99,
|
||||||
|
vec![0xaa],
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match result {
|
||||||
|
DeviceResponseType::UnknownResponse(r) => {
|
||||||
|
assert_eq!(r.cmd, "0x99");
|
||||||
|
assert_eq!(r.raw, vec![0xaa]);
|
||||||
|
}
|
||||||
|
other => panic!(
|
||||||
|
"expected UnknownResponse, got {:?}",
|
||||||
|
other
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -273,6 +273,7 @@ pub fn build_peq_band(
|
|||||||
validate_raw_frequency(raw_freq)?;
|
validate_raw_frequency(raw_freq)?;
|
||||||
validate_raw_peq_q(raw_q)?;
|
validate_raw_peq_q(raw_q)?;
|
||||||
validate_raw_peq_filter(raw_filter_type)?;
|
validate_raw_peq_filter(raw_filter_type)?;
|
||||||
|
validate_raw_bool(bypass)?;
|
||||||
|
|
||||||
let gain = split_u16(raw_gain);
|
let gain = split_u16(raw_gain);
|
||||||
let freq = split_u16(raw_freq);
|
let freq = split_u16(raw_freq);
|
||||||
@@ -321,6 +322,7 @@ pub fn build_mute(
|
|||||||
)->Result<Vec<u8>,ValidationError>{
|
)->Result<Vec<u8>,ValidationError>{
|
||||||
|
|
||||||
validate_raw_channel(channel)?;
|
validate_raw_channel(channel)?;
|
||||||
|
validate_raw_bool(muted)?;
|
||||||
|
|
||||||
Ok(build_frame(
|
Ok(build_frame(
|
||||||
0x35,
|
0x35,
|
||||||
@@ -338,6 +340,7 @@ pub fn build_inverted_gain(
|
|||||||
)->Result<Vec<u8>,ValidationError>{
|
)->Result<Vec<u8>,ValidationError>{
|
||||||
|
|
||||||
validate_raw_channel(channel)?;
|
validate_raw_channel(channel)?;
|
||||||
|
validate_raw_bool(inverted)?;
|
||||||
|
|
||||||
Ok(build_frame(
|
Ok(build_frame(
|
||||||
0x36,
|
0x36,
|
||||||
@@ -427,6 +430,7 @@ pub fn build_bypass_peq(
|
|||||||
) -> Result<Vec<u8>, ValidationError> {
|
) -> Result<Vec<u8>, ValidationError> {
|
||||||
|
|
||||||
validate_raw_channel(channel)?;
|
validate_raw_channel(channel)?;
|
||||||
|
validate_raw_bool(bypass)?;
|
||||||
|
|
||||||
Ok(build_frame(
|
Ok(build_frame(
|
||||||
0x3C,
|
0x3C,
|
||||||
@@ -587,6 +591,7 @@ pub fn build_set_geq_bypass(
|
|||||||
) -> Result<Vec<u8>, ValidationError> {
|
) -> Result<Vec<u8>, ValidationError> {
|
||||||
|
|
||||||
validate_raw_input_channel(raw_channel)?;
|
validate_raw_input_channel(raw_channel)?;
|
||||||
|
validate_raw_bool(bypass)?;
|
||||||
|
|
||||||
Ok(build_frame(
|
Ok(build_frame(
|
||||||
0x49,
|
0x49,
|
||||||
@@ -596,3 +601,455 @@ pub fn build_set_geq_bypass(
|
|||||||
],
|
],
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use super::super::constants::*;
|
||||||
|
|
||||||
|
fn assert_frame(
|
||||||
|
frame: Vec<u8>,
|
||||||
|
expected_command: u8,
|
||||||
|
expected_payload: &[u8],
|
||||||
|
) {
|
||||||
|
assert_eq!(frame[5], expected_command);
|
||||||
|
assert_eq!(&frame[6..frame.len() - 3], expected_payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_split_u16() {
|
||||||
|
assert_eq!(split_u16(0x1234), [0x34, 0x12]);
|
||||||
|
assert_eq!(split_u16(0x0000), [0x00, 0x00]);
|
||||||
|
assert_eq!(split_u16(0xffff), [0xff, 0xff]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simple_commands() {
|
||||||
|
assert_frame(
|
||||||
|
build_handshake(),
|
||||||
|
0x10,
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_disconnect(),
|
||||||
|
0x11,
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_acknowledgement(),
|
||||||
|
0x12,
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_request_device_name(),
|
||||||
|
0x13,
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_get_current_preset(),
|
||||||
|
0x14,
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_preset_modification_status(),
|
||||||
|
0x22,
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_factory_reset(),
|
||||||
|
0x23,
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_get_device_flags(),
|
||||||
|
0x2c,
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_get_meter_levels(),
|
||||||
|
0x40,
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_preset_commands() {
|
||||||
|
assert_frame(
|
||||||
|
build_recall(5).unwrap(),
|
||||||
|
0x20,
|
||||||
|
&[5],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_store_current_configuration(3).unwrap(),
|
||||||
|
0x21,
|
||||||
|
&[3],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_get_preset_name(2).unwrap(),
|
||||||
|
0x29,
|
||||||
|
&[2],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_config_chunk(1).unwrap(),
|
||||||
|
0x27,
|
||||||
|
&[1],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_store_current_preset_name(b"TEST ").unwrap(),
|
||||||
|
0x26,
|
||||||
|
b"TEST ",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_device_lock_commands() {
|
||||||
|
assert_frame(
|
||||||
|
build_unlock_device(b"1234").unwrap(),
|
||||||
|
0x2d,
|
||||||
|
b"1234",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_lock_device(b"1234").unwrap(),
|
||||||
|
0x2f,
|
||||||
|
b"1234",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_set_delay_unit() {
|
||||||
|
assert_frame(
|
||||||
|
build_set_delay_unit(1).unwrap(),
|
||||||
|
0x15,
|
||||||
|
&[1],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_copy_channel() {
|
||||||
|
assert_frame(
|
||||||
|
build_copy_channel(1, 3).unwrap(),
|
||||||
|
0x2a,
|
||||||
|
&[1, 3],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_compressor_frame() {
|
||||||
|
assert_frame(
|
||||||
|
build_set_compressor(
|
||||||
|
5,
|
||||||
|
1,
|
||||||
|
300,
|
||||||
|
400,
|
||||||
|
2,
|
||||||
|
11,
|
||||||
|
).unwrap(),
|
||||||
|
0x30,
|
||||||
|
&[
|
||||||
|
5,
|
||||||
|
1, 0,
|
||||||
|
44, 1,
|
||||||
|
144, 1,
|
||||||
|
2, 0,
|
||||||
|
11, 0,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_filters() {
|
||||||
|
let freq = *FREQUENCY_RAW_RANGE.end();
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_low_pass(
|
||||||
|
*CHANNELS_RANGE.start(),
|
||||||
|
freq,
|
||||||
|
*CROSSOVER_FILTER_RAW_RANGE.start(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
0x31,
|
||||||
|
&[
|
||||||
|
*CHANNELS_RANGE.start(),
|
||||||
|
(freq & 0xff) as u8,
|
||||||
|
(freq >> 8) as u8,
|
||||||
|
*CROSSOVER_FILTER_RAW_RANGE.start(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_high_pass(
|
||||||
|
*CHANNELS_RANGE.end(),
|
||||||
|
freq,
|
||||||
|
*CROSSOVER_FILTER_RAW_RANGE.end(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
0x32,
|
||||||
|
&[
|
||||||
|
*CHANNELS_RANGE.end(),
|
||||||
|
(freq & 0xff) as u8,
|
||||||
|
(freq >> 8) as u8,
|
||||||
|
*CROSSOVER_FILTER_RAW_RANGE.end(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_peq_band() {
|
||||||
|
let gain = *EQ_GAIN_RAW_RANGE.end();
|
||||||
|
let freq = *FREQUENCY_RAW_RANGE.end();
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_peq_band(
|
||||||
|
*INPUT_CHANNELS.start(),
|
||||||
|
*PEQ_INPUT_BAND_RANGE.end(),
|
||||||
|
gain,
|
||||||
|
freq,
|
||||||
|
*PEQ_Q_RAW_RANGE.end(),
|
||||||
|
*PEQ_FILTER_RAW_RANGE.end(),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
0x33,
|
||||||
|
&[
|
||||||
|
*INPUT_CHANNELS.start(),
|
||||||
|
*PEQ_INPUT_BAND_RANGE.end(),
|
||||||
|
(gain & 0xff) as u8,
|
||||||
|
(gain >> 8) as u8,
|
||||||
|
(freq & 0xff) as u8,
|
||||||
|
(freq >> 8) as u8,
|
||||||
|
*PEQ_Q_RAW_RANGE.end(),
|
||||||
|
*PEQ_FILTER_RAW_RANGE.end(),
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gain_mute_invert() {
|
||||||
|
let gain = *CHANNEL_GAIN_RAW_RANGE.end();
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_gain(*CHANNELS_RANGE.end(), gain).unwrap(),
|
||||||
|
0x34,
|
||||||
|
&[
|
||||||
|
*CHANNELS_RANGE.end(),
|
||||||
|
(gain & 0xff) as u8,
|
||||||
|
(gain >> 8) as u8,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_mute(*CHANNELS_RANGE.start(), 1).unwrap(),
|
||||||
|
0x35,
|
||||||
|
&[*CHANNELS_RANGE.start(), 1],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_inverted_gain(*CHANNELS_RANGE.end(), 1).unwrap(),
|
||||||
|
0x36,
|
||||||
|
&[*CHANNELS_RANGE.end(), 1],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delay_and_input() {
|
||||||
|
assert_frame(
|
||||||
|
build_set_delay(
|
||||||
|
*CHANNELS_RANGE.end(),
|
||||||
|
*DELAY_RAW_RANGE.end(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
0x38,
|
||||||
|
&[
|
||||||
|
*CHANNELS_RANGE.end(),
|
||||||
|
0x00,
|
||||||
|
*DELAY_RAW_RANGE.end(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_set_input_source(
|
||||||
|
*INPUT_SOURCE_TYPE_RAW_RANGE.end(),
|
||||||
|
*DISCRETE_FREQUENCY_RAW_RANGE.end(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
0x39,
|
||||||
|
&[
|
||||||
|
*INPUT_SOURCE_TYPE_RAW_RANGE.end(),
|
||||||
|
*DISCRETE_FREQUENCY_RAW_RANGE.end(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_routing_commands() {
|
||||||
|
assert_frame(
|
||||||
|
build_matrix_routing(
|
||||||
|
*OUTPUT_CHANNELS.start(),
|
||||||
|
MATRIX_INPUT_A_MASK | MATRIX_INPUT_C_MASK,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
0x3A,
|
||||||
|
&[
|
||||||
|
*OUTPUT_CHANNELS.start(),
|
||||||
|
MATRIX_INPUT_A_MASK | MATRIX_INPUT_C_MASK,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
let source = *INPUT_CHANNELS.start();
|
||||||
|
let mask = input_link_mask(source).unwrap();
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_link_channel(source, mask).unwrap(),
|
||||||
|
0x3B,
|
||||||
|
&[source, mask],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_bypass_peq(*CHANNELS_RANGE.end(), 1).unwrap(),
|
||||||
|
0x3C,
|
||||||
|
&[*CHANNELS_RANGE.end(), 1],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_channel_name() {
|
||||||
|
let name = [b'A'; CHANNEL_NAME_LENGTH];
|
||||||
|
|
||||||
|
let mut expected = vec![*CHANNELS_RANGE.start()];
|
||||||
|
expected.extend_from_slice(&name);
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_store_channel_name(
|
||||||
|
*CHANNELS_RANGE.start(),
|
||||||
|
&name,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
0x3D,
|
||||||
|
&expected,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gate_and_limiter() {
|
||||||
|
let threshold = *GATE_THRESHOLD_RAW_RANGE.end();
|
||||||
|
let attack = *ATTACK_RAW_RANGE.end();
|
||||||
|
let hold = *HOLD_RAW_RANGE.end();
|
||||||
|
let release = *RELEASE_RAW_RANGE.end();
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_set_gate(
|
||||||
|
*INPUT_CHANNELS.end(),
|
||||||
|
threshold,
|
||||||
|
attack,
|
||||||
|
hold,
|
||||||
|
release,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
0x3E,
|
||||||
|
&[
|
||||||
|
*INPUT_CHANNELS.end(),
|
||||||
|
(attack & 0xff) as u8,
|
||||||
|
(attack >> 8) as u8,
|
||||||
|
(release & 0xff) as u8,
|
||||||
|
(release >> 8) as u8,
|
||||||
|
(hold & 0xff) as u8,
|
||||||
|
(hold >> 8) as u8,
|
||||||
|
(threshold & 0xff) as u8,
|
||||||
|
(threshold >> 8) as u8,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
let threshold = *THRESHOLD_RAW_RANGE.end();
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_set_limiter(
|
||||||
|
*OUTPUT_CHANNELS.end(),
|
||||||
|
threshold,
|
||||||
|
attack,
|
||||||
|
release,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
0x3F,
|
||||||
|
&[
|
||||||
|
*OUTPUT_CHANNELS.end(),
|
||||||
|
(attack & 0xff) as u8,
|
||||||
|
(attack >> 8) as u8,
|
||||||
|
(release & 0xff) as u8,
|
||||||
|
(release >> 8) as u8,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
(threshold & 0xff) as u8,
|
||||||
|
(threshold >> 8) as u8,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_matrix_gain() {
|
||||||
|
let gain = *MATRIX_GAIN_RAW_RANGE.end();
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_matrix_gain(
|
||||||
|
*INPUT_CHANNELS.end(),
|
||||||
|
*OUTPUT_CHANNELS.end(),
|
||||||
|
gain,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
0x41,
|
||||||
|
&[
|
||||||
|
*OUTPUT_CHANNELS.end(),
|
||||||
|
*INPUT_CHANNELS.end(),
|
||||||
|
(gain & 0xff) as u8,
|
||||||
|
(gain >> 8) as u8,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_geq_commands() {
|
||||||
|
let gain = *EQ_GAIN_RAW_RANGE.end();
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_set_geq_band(
|
||||||
|
*INPUT_CHANNELS.start(),
|
||||||
|
*DISCRETE_FREQUENCY_RAW_RANGE.end(),
|
||||||
|
gain,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
0x48,
|
||||||
|
&[
|
||||||
|
*INPUT_CHANNELS.start(),
|
||||||
|
*DISCRETE_FREQUENCY_RAW_RANGE.end(),
|
||||||
|
(gain & 0xff) as u8,
|
||||||
|
(gain >> 8) as u8,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_frame(
|
||||||
|
build_set_geq_bypass(
|
||||||
|
*INPUT_CHANNELS.end(),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
0x49,
|
||||||
|
&[
|
||||||
|
*INPUT_CHANNELS.end(),
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -101,19 +101,81 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn build_then_extract_round_trips() {
|
fn test_checksum() {
|
||||||
let cmd = 0x34;
|
let payload = [0x00, 0x01, 0x03, 0x10, 0x20, 0x30];
|
||||||
let args = [0x01, 0x02, 0x03];
|
|
||||||
let frame = build_frame(cmd, &args);
|
|
||||||
|
|
||||||
let decoded = extract_frame(&frame).unwrap();
|
// 0x01 ^ 0x00 ^ 0x01 ^ 0x03 ^ 0x10 ^ 0x20 ^ 0x30
|
||||||
assert_eq!(decoded.command, cmd);
|
assert_eq!(checksum(&payload), 0x03);
|
||||||
assert_eq!(decoded.payload, vec![0x01, 0x02, 0x03]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn too_short_is_an_error() {
|
fn test_build_frame() {
|
||||||
let err = extract_frame(&[0x10, 0x02, 0x01]).unwrap_err();
|
let frame = build_frame(0x05, &[0xAA, 0xBB]);
|
||||||
assert!(err.0.contains("too short"));
|
|
||||||
|
let expected_without_checksum = vec![
|
||||||
|
0x10, 0x02, // header
|
||||||
|
0x00, 0x01, 0x03, // device + version + length
|
||||||
|
0x05, 0xAA, 0xBB, // command + args
|
||||||
|
0x10, 0x03, // footer
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(&frame[..frame.len() - 1], expected_without_checksum);
|
||||||
|
|
||||||
|
let expected_checksum = checksum(&frame[2..frame.len() - 3]);
|
||||||
|
assert_eq!(frame[frame.len() - 1], expected_checksum);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_frame_invalid_header() {
|
||||||
|
let mut frame = build_frame(0x01, &[]);
|
||||||
|
frame[0] = 0x11;
|
||||||
|
|
||||||
|
let result = extract_frame(&frame);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_frame_invalid_checksum() {
|
||||||
|
let mut frame = build_frame(0x01, &[]);
|
||||||
|
let last = frame.len() - 1;
|
||||||
|
|
||||||
|
frame[last] ^= 0xFF;
|
||||||
|
|
||||||
|
let result = extract_frame(&frame);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_frame_not_response() {
|
||||||
|
let mut frame = build_frame(0x01, &[]);
|
||||||
|
|
||||||
|
// data[2] == 0 means request frame according to your parser
|
||||||
|
frame[2] = 0x00;
|
||||||
|
|
||||||
|
let result = extract_frame(&frame);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_frame_invalid_device_id() {
|
||||||
|
let mut frame = build_frame(0x01, &[]);
|
||||||
|
|
||||||
|
frame[3] = 0xFF;
|
||||||
|
|
||||||
|
let result = extract_frame(&frame);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_frame_too_short() {
|
||||||
|
let data = vec![0x10, 0x02, 0x01];
|
||||||
|
|
||||||
|
let result = extract_frame(&data);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -313,3 +313,495 @@ pub fn validate_raw_matrix_gain(raw_gain: u16) -> Result<(), ValidationError> {
|
|||||||
|| format!("Invalid matrix gain: {:#06x}", raw_gain),
|
|| format!("Invalid matrix gain: {:#06x}", raw_gain),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn validate_raw_bool(value: u8) -> Result<(), ValidationError> {
|
||||||
|
validate_range(
|
||||||
|
value,
|
||||||
|
0..=1,
|
||||||
|
|| format!("Invalid bool value: {:#04x}", value),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// ---- validate_raw_delay_unit: 0x00..=0x03 ----
|
||||||
|
#[test]
|
||||||
|
fn delay_unit_accepts_low_and_high_bounds() {
|
||||||
|
assert!(validate_raw_delay_unit(0x00).is_ok());
|
||||||
|
assert!(validate_raw_delay_unit(0x03).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delay_unit_rejects_just_above_max() {
|
||||||
|
assert!(validate_raw_delay_unit(0x04).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_preset_index: 0..=20 ----
|
||||||
|
#[test]
|
||||||
|
fn preset_index_accepts_bounds() {
|
||||||
|
assert!(validate_raw_preset_index(0).is_ok());
|
||||||
|
assert!(validate_raw_preset_index(PRESET_INDEX_MAX).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preset_index_rejects_above_max() {
|
||||||
|
assert!(validate_raw_preset_index(PRESET_INDEX_MAX + 1).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_store_preset_index: 1..=20 (0 reserved, unlike get/plain) ----
|
||||||
|
#[test]
|
||||||
|
fn store_preset_index_rejects_zero() {
|
||||||
|
assert!(validate_raw_store_preset_index(0).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn store_preset_index_accepts_one_through_max() {
|
||||||
|
assert!(validate_raw_store_preset_index(1).is_ok());
|
||||||
|
assert!(validate_raw_store_preset_index(PRESET_INDEX_MAX).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn store_preset_index_rejects_above_max() {
|
||||||
|
assert!(validate_raw_store_preset_index(PRESET_INDEX_MAX + 1).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_get_preset_index: 0..20 (max itself excluded) ----
|
||||||
|
#[test]
|
||||||
|
fn get_preset_index_accepts_zero_and_just_below_max() {
|
||||||
|
assert!(validate_raw_get_preset_index(0).is_ok());
|
||||||
|
assert!(validate_raw_get_preset_index(PRESET_INDEX_MAX - 1).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_preset_index_rejects_max_itself() {
|
||||||
|
// Unlike validate_raw_preset_index, this range is exclusive of PRESET_INDEX_MAX.
|
||||||
|
assert!(validate_raw_get_preset_index(PRESET_INDEX_MAX).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_preset_name: 14 ascii bytes, space-padded ----
|
||||||
|
#[test]
|
||||||
|
fn preset_name_accepts_correctly_padded_ascii() {
|
||||||
|
let mut name = b"MyPreset".to_vec();
|
||||||
|
name.resize(PRESET_NAME_LENGTH, PRESET_NAME_PADDING);
|
||||||
|
assert!(validate_raw_preset_name(&name).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preset_name_rejects_wrong_length() {
|
||||||
|
let name = b"TooShort".to_vec();
|
||||||
|
assert!(validate_raw_preset_name(&name).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preset_name_rejects_non_ascii_byte() {
|
||||||
|
let mut name = b"Bad".to_vec();
|
||||||
|
name.push(0x01); // control char, not printable ascii, not the space padding byte
|
||||||
|
name.resize(PRESET_NAME_LENGTH, PRESET_NAME_PADDING);
|
||||||
|
assert!(validate_raw_preset_name(&name).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_config_chunk_index: 0..=0x1C ----
|
||||||
|
#[test]
|
||||||
|
fn config_chunk_index_accepts_bounds() {
|
||||||
|
assert!(validate_raw_config_chunk_index(0x00).is_ok());
|
||||||
|
assert!(validate_raw_config_chunk_index(CONFIG_CHUNK_INDEX_MAX).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn config_chunk_index_rejects_above_max() {
|
||||||
|
assert!(validate_raw_config_chunk_index(CONFIG_CHUNK_INDEX_MAX + 1).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_channel: 0..=0x0B ----
|
||||||
|
#[test]
|
||||||
|
fn channel_accepts_full_range() {
|
||||||
|
assert!(validate_raw_channel(0x00).is_ok());
|
||||||
|
assert!(validate_raw_channel(0x0B).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn channel_rejects_above_max() {
|
||||||
|
assert!(validate_raw_channel(0x0C).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_input_channel: 0..=0x03 ----
|
||||||
|
#[test]
|
||||||
|
fn input_channel_accepts_its_own_range() {
|
||||||
|
assert!(validate_raw_input_channel(0x00).is_ok());
|
||||||
|
assert!(validate_raw_input_channel(0x03).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn input_channel_rejects_an_output_channel() {
|
||||||
|
assert!(validate_raw_input_channel(0x04).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_output_channel: 0x04..=0x0B ----
|
||||||
|
#[test]
|
||||||
|
fn output_channel_accepts_its_own_range() {
|
||||||
|
assert!(validate_raw_output_channel(0x04).is_ok());
|
||||||
|
assert!(validate_raw_output_channel(0x0B).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn output_channel_rejects_an_input_channel() {
|
||||||
|
assert!(validate_raw_output_channel(0x03).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_copy_channels ----
|
||||||
|
#[test]
|
||||||
|
fn copy_channels_allows_input_to_input() {
|
||||||
|
assert!(validate_raw_copy_channels(0x00, 0x01).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn copy_channels_allows_output_to_output() {
|
||||||
|
assert!(validate_raw_copy_channels(0x04, 0x05).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn copy_channels_rejects_same_source_and_destination() {
|
||||||
|
assert!(validate_raw_copy_channels(0x01, 0x01).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn copy_channels_rejects_mixed_input_and_output() {
|
||||||
|
assert!(validate_raw_copy_channels(0x01, 0x05).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn copy_channels_rejects_out_of_range_source() {
|
||||||
|
assert!(validate_raw_copy_channels(0x0C, 0x01).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn copy_channels_rejects_out_of_range_destination() {
|
||||||
|
assert!(validate_raw_copy_channels(0x01, 0x0C).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_password: 4 bytes, null-padded ----
|
||||||
|
#[test]
|
||||||
|
fn password_accepts_null_padded_ascii() {
|
||||||
|
let mut pw = b"ab".to_vec();
|
||||||
|
pw.resize(PASSWORD_LENGTH, PASSWORD_PADDING);
|
||||||
|
assert!(validate_raw_password(&pw).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn password_rejects_wrong_length() {
|
||||||
|
assert!(validate_raw_password(b"abcde").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn password_rejects_non_ascii_non_padding_byte() {
|
||||||
|
let pw = vec![b'a', b'b', 0x01, 0x00];
|
||||||
|
assert!(validate_raw_password(&pw).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_compressor_ratio: 0..=0x000F ----
|
||||||
|
#[test]
|
||||||
|
fn compressor_ratio_accepts_bounds() {
|
||||||
|
assert!(validate_raw_compressor_ratio(0x0000).is_ok());
|
||||||
|
assert!(validate_raw_compressor_ratio(0x000F).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compressor_ratio_rejects_above_max() {
|
||||||
|
assert!(validate_raw_compressor_ratio(0x0010).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_attack: 0..=0x03E6 ----
|
||||||
|
#[test]
|
||||||
|
fn attack_accepts_bounds() {
|
||||||
|
assert!(validate_raw_attack(0x0000).is_ok());
|
||||||
|
assert!(validate_raw_attack(0x03E6).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn attack_rejects_above_max() {
|
||||||
|
assert!(validate_raw_attack(0x03E7).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_release: 0x0009..=0x0BB7 ----
|
||||||
|
#[test]
|
||||||
|
fn release_rejects_below_min() {
|
||||||
|
assert!(validate_raw_release(0x0008).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn release_accepts_bounds() {
|
||||||
|
assert!(validate_raw_release(0x0009).is_ok());
|
||||||
|
assert!(validate_raw_release(0x0BB7).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn release_rejects_above_max() {
|
||||||
|
assert!(validate_raw_release(0x0BB8).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_compressor_knee: 0..=0x000C ----
|
||||||
|
#[test]
|
||||||
|
fn compressor_knee_accepts_bounds() {
|
||||||
|
assert!(validate_raw_compressor_knee(0x0000).is_ok());
|
||||||
|
assert!(validate_raw_compressor_knee(0x000C).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compressor_knee_rejects_above_max() {
|
||||||
|
assert!(validate_raw_compressor_knee(0x000D).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_threshold: 0..=0x00DC ----
|
||||||
|
#[test]
|
||||||
|
fn threshold_accepts_bounds() {
|
||||||
|
assert!(validate_raw_threshold(0x0000).is_ok());
|
||||||
|
assert!(validate_raw_threshold(0x00DC).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn threshold_rejects_above_max() {
|
||||||
|
assert!(validate_raw_threshold(0x00DD).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_frequency: 0..=0x012C ----
|
||||||
|
#[test]
|
||||||
|
fn frequency_accepts_bounds() {
|
||||||
|
assert!(validate_raw_frequency(0x0000).is_ok());
|
||||||
|
assert!(validate_raw_frequency(0x012C).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn frequency_rejects_above_max() {
|
||||||
|
assert!(validate_raw_frequency(0x012D).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_crossover_filter: 0..=0x14 ----
|
||||||
|
#[test]
|
||||||
|
fn crossover_filter_accepts_bounds() {
|
||||||
|
assert!(validate_raw_crossover_filter(0x00).is_ok());
|
||||||
|
assert!(validate_raw_crossover_filter(0x14).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn crossover_filter_rejects_above_max() {
|
||||||
|
assert!(validate_raw_crossover_filter(0x15).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_peq_band: depends on whether channel is input or output ----
|
||||||
|
#[test]
|
||||||
|
fn peq_band_accepts_max_band_for_input_channel() {
|
||||||
|
assert!(validate_raw_peq_band(0x00, 0x07).is_ok()); // input max band
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn peq_band_rejects_above_max_for_input_channel() {
|
||||||
|
assert!(validate_raw_peq_band(0x00, 0x08).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn peq_band_accepts_max_band_for_output_channel() {
|
||||||
|
assert!(validate_raw_peq_band(0x04, 0x08).is_ok()); // output max band (one higher than input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn peq_band_rejects_above_max_for_output_channel() {
|
||||||
|
assert!(validate_raw_peq_band(0x04, 0x09).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn peq_band_is_unchecked_for_out_of_range_channel() {
|
||||||
|
// Neither branch fires when the channel is outside both INPUT_CHANNELS and
|
||||||
|
// OUTPUT_CHANNELS, so the function falls through to Ok(()) regardless of band.
|
||||||
|
// This documents that behavior rather than asserting it's necessarily "correct".
|
||||||
|
assert!(validate_raw_peq_band(0x0C, 0xFF).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_peq_q: 0..=0x64 ----
|
||||||
|
#[test]
|
||||||
|
fn peq_q_accepts_bounds() {
|
||||||
|
assert!(validate_raw_peq_q(0x00).is_ok());
|
||||||
|
assert!(validate_raw_peq_q(0x64).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn peq_q_rejects_above_max() {
|
||||||
|
assert!(validate_raw_peq_q(0x65).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_peq_filter: 0..=0x08 ----
|
||||||
|
#[test]
|
||||||
|
fn peq_filter_accepts_bounds() {
|
||||||
|
assert!(validate_raw_peq_filter(0x00).is_ok());
|
||||||
|
assert!(validate_raw_peq_filter(0x08).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn peq_filter_rejects_above_max() {
|
||||||
|
assert!(validate_raw_peq_filter(0x09).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_eq_gain: 0..=0x00F0 ----
|
||||||
|
#[test]
|
||||||
|
fn eq_gain_accepts_bounds() {
|
||||||
|
assert!(validate_raw_eq_gain(0x0000).is_ok());
|
||||||
|
assert!(validate_raw_eq_gain(0x00F0).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eq_gain_rejects_above_max() {
|
||||||
|
assert!(validate_raw_eq_gain(0x00F1).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_gain: 0..=0x0190 ----
|
||||||
|
#[test]
|
||||||
|
fn gain_accepts_bounds() {
|
||||||
|
assert!(validate_raw_gain(0x0000).is_ok());
|
||||||
|
assert!(validate_raw_gain(0x0190).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gain_rejects_above_max() {
|
||||||
|
assert!(validate_raw_gain(0x0191).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_channel_delay: 0..=0xFF (the full u8 range) ----
|
||||||
|
#[test]
|
||||||
|
fn channel_delay_accepts_the_entire_u8_range() {
|
||||||
|
// The range spans all possible u8 values, so there is no invalid input to test;
|
||||||
|
// this asserts that fact rather than a boundary rejection.
|
||||||
|
assert!(validate_raw_channel_delay(0x00).is_ok());
|
||||||
|
assert!(validate_raw_channel_delay(0xFF).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_input_source: 0..=0x03 ----
|
||||||
|
#[test]
|
||||||
|
fn input_source_accepts_bounds() {
|
||||||
|
assert!(validate_raw_input_source(0x00).is_ok());
|
||||||
|
assert!(validate_raw_input_source(0x03).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn input_source_rejects_above_max() {
|
||||||
|
assert!(validate_raw_input_source(0x04).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_discrete_frequency: 0..=0x1E ----
|
||||||
|
#[test]
|
||||||
|
fn discrete_frequency_accepts_bounds() {
|
||||||
|
assert!(validate_raw_discrete_frequency(0x00).is_ok());
|
||||||
|
assert!(validate_raw_discrete_frequency(0x1E).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn discrete_frequency_rejects_above_max() {
|
||||||
|
assert!(validate_raw_discrete_frequency(0x1F).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_matrix_bitmask: 0..=0x0F ----
|
||||||
|
#[test]
|
||||||
|
fn matrix_bitmask_accepts_bounds() {
|
||||||
|
assert!(validate_raw_matrix_bitmask(0x00).is_ok());
|
||||||
|
assert!(validate_raw_matrix_bitmask(0x0F).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn matrix_bitmask_rejects_above_max() {
|
||||||
|
assert!(validate_raw_matrix_bitmask(0x10).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_link_channel ----
|
||||||
|
#[test]
|
||||||
|
fn link_channel_input_a_accepts_full_allowed_mask() {
|
||||||
|
// Channel 0x00 (A) allows linking to A|B|C|D = 0x0F per input_link_mask.
|
||||||
|
assert!(validate_raw_link_channel(0x00, 0x0F).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn link_channel_input_a_rejects_bit_outside_allowed_mask() {
|
||||||
|
assert!(validate_raw_link_channel(0x00, 0x10).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn link_channel_input_d_only_allows_its_own_bit() {
|
||||||
|
// Channel 0x03 (D) only allows mask 0x08 per input_link_mask.
|
||||||
|
assert!(validate_raw_link_channel(0x03, 0x08).is_ok());
|
||||||
|
assert!(validate_raw_link_channel(0x03, 0x04).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn link_channel_output_first_accepts_full_mask() {
|
||||||
|
// Channel 0x04 allows mask 0xFF per output_link_mask.
|
||||||
|
assert!(validate_raw_link_channel(0x04, 0xFF).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn link_channel_output_last_only_allows_its_own_bit() {
|
||||||
|
// Channel 0x0B allows only mask 0x80 per output_link_mask.
|
||||||
|
assert!(validate_raw_link_channel(0x0B, 0x80).is_ok());
|
||||||
|
assert!(validate_raw_link_channel(0x0B, 0x01).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn link_channel_rejects_out_of_range_source_channel() {
|
||||||
|
assert!(validate_raw_link_channel(0x0C, 0x01).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_channel_name: 8 ascii bytes, null-padded ----
|
||||||
|
#[test]
|
||||||
|
fn channel_name_accepts_correctly_padded_ascii() {
|
||||||
|
let mut name = b"Kick".to_vec();
|
||||||
|
name.resize(CHANNEL_NAME_LENGTH, CHANNEL_NAME_PADDING);
|
||||||
|
assert!(validate_raw_channel_name(&name).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn channel_name_rejects_wrong_length() {
|
||||||
|
assert!(validate_raw_channel_name(b"TooLongName").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn channel_name_rejects_non_ascii_byte() {
|
||||||
|
let mut name = b"Kick".to_vec();
|
||||||
|
name.push(0x01);
|
||||||
|
name.resize(CHANNEL_NAME_LENGTH, CHANNEL_NAME_PADDING);
|
||||||
|
assert!(validate_raw_channel_name(&name).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_gate_threshold: 0..=0x00B4 ----
|
||||||
|
#[test]
|
||||||
|
fn gate_threshold_accepts_bounds() {
|
||||||
|
assert!(validate_raw_gate_threshold(0x0000).is_ok());
|
||||||
|
assert!(validate_raw_gate_threshold(0x00B4).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gate_threshold_rejects_above_max() {
|
||||||
|
assert!(validate_raw_gate_threshold(0x00B5).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_hold: 0..=0x03E6 ----
|
||||||
|
#[test]
|
||||||
|
fn hold_accepts_bounds() {
|
||||||
|
assert!(validate_raw_hold(0x0000).is_ok());
|
||||||
|
assert!(validate_raw_hold(0x03E6).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hold_rejects_above_max() {
|
||||||
|
assert!(validate_raw_hold(0x03E7).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_raw_matrix_gain: 0..=0x0118 ----
|
||||||
|
#[test]
|
||||||
|
fn matrix_gain_accepts_bounds() {
|
||||||
|
assert!(validate_raw_matrix_gain(0x0000).is_ok());
|
||||||
|
assert!(validate_raw_matrix_gain(0x0118).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn matrix_gain_rejects_above_max() {
|
||||||
|
assert!(validate_raw_matrix_gain(0x0119).is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user