#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use zerodds_wire::{
Endianness, FixedDigits, ParameterRole, PidFlagPolicy, PlCdr1Framing, PlCdr1Reading, Reader,
WireError, Writer,
};
#[test]
fn a_long_after_a_char_sits_at_four_not_at_one() {
let mut w = Writer::little_endian();
w.write_char('a').expect("'a' is one byte");
w.write_i32(1);
assert_eq!(w.as_bytes(), &[0x61, 0, 0, 0, 1, 0, 0, 0]);
}
#[test]
fn a_double_aligns_to_eight() {
let mut w = Writer::little_endian();
w.write_u8(0xFF);
w.write_f64(1.0);
assert_eq!(w.as_bytes().len(), 16, "{:?}", w.as_bytes());
assert_eq!(&w.as_bytes()[..8], &[0xFF, 0, 0, 0, 0, 0, 0, 0]);
}
#[test]
fn a_reader_skips_exactly_the_padding_the_writer_wrote() {
let mut w = Writer::little_endian();
w.write_char('a').expect("'a' is one byte");
w.write_i32(-1);
let bytes = w.finish();
let mut r = Reader::new(&bytes, Endianness::Little);
assert_eq!(r.read_char().expect("char"), 'a');
assert_eq!(r.read_i32().expect("long"), -1);
assert_eq!(r.remaining(), 0, "padding was left over");
}
#[test]
fn the_two_byte_orders_write_the_same_value_differently() {
let mut little = Writer::little_endian();
little.write_u32(0x0102_0304);
assert_eq!(little.as_bytes(), &[0x04, 0x03, 0x02, 0x01]);
let mut big = Writer::big_endian();
big.write_u32(0x0102_0304);
assert_eq!(big.as_bytes(), &[0x01, 0x02, 0x03, 0x04]);
}
#[test]
fn a_string_length_counts_its_terminator() {
let mut w = Writer::little_endian();
w.write_string("ab");
assert_eq!(w.as_bytes(), &[3, 0, 0, 0, b'a', b'b', 0]);
}
#[test]
fn an_empty_string_is_a_length_of_one_and_a_nul() {
let mut w = Writer::little_endian();
w.write_string("");
assert_eq!(w.as_bytes(), &[1, 0, 0, 0, 0]);
}
#[test]
fn a_string_round_trips_with_its_terminator_consumed() {
let mut w = Writer::little_endian();
w.write_string("hello");
w.write_u8(0xAB);
let bytes = w.finish();
let mut r = Reader::new(&bytes, Endianness::Little);
assert_eq!(r.read_string().expect("string"), "hello");
assert_eq!(
r.read_u8().expect("marker"),
0xAB,
"the NUL was not consumed"
);
}
#[test]
fn a_value_longer_than_its_string_bound_is_refused_and_not_truncated() {
let mut w = Writer::little_endian();
assert!(
w.write_string_bounded("äöü", 3).is_err(),
"six octets went into a string<3> and nothing was said"
);
let mut w = Writer::little_endian();
w.write_string_bounded("äöü", 6)
.expect("six octets fit six");
let bytes = w.finish();
let mut r = Reader::new(&bytes, Endianness::Little);
assert_eq!(r.read_string().expect("the value"), "äöü");
}
#[test]
fn a_dheader_holds_the_length_of_what_followed_it() {
let mut w = Writer::little_endian();
let header = w.write_dheader();
w.write_i32(7);
w.write_i32(8);
w.finish_dheader(header);
let bytes = w.finish();
assert_eq!(&bytes[..4], &[8, 0, 0, 0], "the DHEADER is not 8");
let mut r = Reader::new(&bytes, Endianness::Little);
assert_eq!(r.read_dheader().expect("dheader"), 8);
}
#[test]
fn an_appending_writer_writes_what_a_fresh_one_appended_would() {
fn body(w: &mut Writer) {
let header = w.write_dheader();
w.write_u8(1);
w.write_u64(0x0102_0304_0506_0708);
w.write_string("ab");
w.write_u16(9);
w.finish_dheader(header);
}
for (endianness, max_align) in [(Endianness::Little, 4), (Endianness::Big, 8)] {
let prefix = vec![0xEE; 3];
let mut fresh = Writer::with_max_align(endianness, max_align);
body(&mut fresh);
let mut expected = prefix.clone();
expected.extend_from_slice(fresh.as_bytes());
let mut appending = Writer::appending(endianness, max_align, prefix);
body(&mut appending);
assert_eq!(
appending.finish(),
expected,
"{endianness:?}, MAXALIGN {max_align}"
);
}
}
#[test]
fn an_emheader_packs_its_id_code_and_flag_into_one_word() {
let mut w = Writer::little_endian();
let header = w
.write_emheader(3, 4, true)
.expect("a header the plan chose");
w.write_i32(9);
w.finish_emheader(header)
.expect("the member is the length its code declares");
let bytes = w.finish();
let mut r = Reader::new(&bytes, Endianness::Little);
let read = r.read_emheader().expect("emheader");
assert_eq!(read.member_id, 3);
assert_eq!(read.length_code, 4);
assert!(read.must_understand);
assert_eq!(
read.next_int,
Some(4),
"LC4 carries a NEXTINT of the length"
);
}
#[test]
fn an_emheader_member_id_past_28_bits_is_refused() {
let mut w = Writer::little_endian();
w.write_emheader(0x0FFF_FFFF, 2, false)
.expect("the largest id the 28 bits hold");
for id in [0x1000_0000_u32, 0x8000_0001, u32::MAX] {
let error = w
.write_emheader(id, 2, true)
.expect_err("an id past 28 bits");
assert!(
matches!(error, WireError::OutOfRange { .. }),
"{id:#x}: {error:?}"
);
}
}
#[test]
fn a_length_code_below_four_carries_no_next_int() {
let mut w = Writer::little_endian();
let header = w
.write_emheader(1, 2, false)
.expect("a header the plan chose");
w.write_i32(5);
w.finish_emheader(header)
.expect("the member is the length its code declares");
let bytes = w.finish();
assert_eq!(bytes.len(), 8, "a NEXTINT was written for LC2: {bytes:?}");
}
const SHORT_FORM: PlCdr1Framing = PlCdr1Framing {
pid_flags: PidFlagPolicy::Bare,
highest_short_id: 0x3f00,
pad_declared_length: false,
pad_declared_optional_length: false,
member_must_understand: true,
require_extended: false,
pad_after_value: false,
};
#[test]
fn a_parameter_length_excludes_the_padding_that_follows_it() {
let mut w = Writer::little_endian().with_pl_cdr1_framing(SHORT_FORM);
let pid = w
.write_pid(5, ParameterRole::Member, false)
.expect("a header the plan chose");
w.write_u8(0xAA);
w.finish_pid(pid);
let bytes = w.finish();
let mut r = Reader::new(&bytes, Endianness::Little).with_pl_cdr1_framing(SHORT_FORM);
let read = r.read_pid().expect("pid");
assert_eq!(read.pid, 5);
assert_eq!(read.length, 1, "the padding was counted into the length");
assert_eq!(
bytes.len(),
5,
"a trailing pad was written under short-true-length"
);
}
const ROUNDED: PlCdr1Framing = PlCdr1Framing {
pad_declared_length: true,
pad_declared_optional_length: true,
..PlCdr1Framing::DEFAULT
};
#[test]
fn a_parameter_length_covers_the_padding_where_the_framing_says_so() {
let mut w = Writer::little_endian().with_pl_cdr1_framing(ROUNDED);
let pid = w
.write_pid(5, ParameterRole::Member, false)
.expect("a header the plan chose");
w.write_u8(0xAA);
w.finish_pid(pid);
let bytes = w.finish();
assert_eq!(bytes.len(), 8);
let mut r = Reader::new(&bytes, Endianness::Little).with_pl_cdr1_framing(SHORT_FORM);
let header = r.read_pid().expect("the header");
assert_eq!(header.length, 4);
let _ = r.read_u8().expect("the value");
match r.finish_pid(header) {
Err(WireError::WrongFraming {
fits: Some(fits), ..
}) => assert!(
fits.contains("short-rounded-length"),
"the error does not name the framing that fits: {fits}"
),
other => panic!("a padded length read as unpadded came back {other:?}"),
}
}
#[test]
fn the_default_parameter_length_is_the_exact_length_of_the_member() {
let mut w = Writer::little_endian();
let first = w
.write_pid(5, ParameterRole::Member, false)
.expect("a header the plan chose");
w.write_u8(0xAA);
w.finish_pid(first);
let second = w
.write_pid(6, ParameterRole::Member, false)
.expect("a header the plan chose");
w.write_u16(0xBBCC);
w.finish_pid(second);
w.write_sentinel();
let bytes = w.finish();
assert_eq!(
bytes,
[
0x05, 0x00, 0x01, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x06, 0x00, 0x02, 0x00, 0xCC, 0xBB, 0x00, 0x00, 0x02, 0x7f, 0x00, 0x00, ],
"the next parameter ID starts on a four-byte offset"
);
let mut r = Reader::new(&bytes, Endianness::Little);
let header = r.read_pid().expect("first header");
assert_eq!((header.pid, header.length), (5, 1));
assert_eq!(r.read_u8().expect("value"), 0xAA);
r.finish_pid(header)
.expect("the exact length is the default");
let header = r.read_pid().expect("second header");
assert_eq!((header.pid, header.length), (6, 2));
assert_eq!(r.read_u16().expect("value"), 0xBBCC);
r.finish_pid(header)
.expect("the exact length is the default");
assert!(r.read_pid().expect("sentinel").end);
}
#[test]
fn a_default_reader_refuses_the_rounded_length_and_names_the_setting() {
let mut w = Writer::little_endian().with_pl_cdr1_framing(ROUNDED);
let pid = w
.write_pid(5, ParameterRole::Member, false)
.expect("a header the plan chose");
w.write_u8(0xAA);
w.finish_pid(pid);
let bytes = w.finish();
assert_eq!(&bytes[..4], &[0x05, 0x00, 0x04, 0x00]);
let mut r = Reader::new(&bytes, Endianness::Little);
let header = r.read_pid().expect("the header");
let _ = r.read_u8().expect("the value");
match r.finish_pid(header) {
Err(WireError::WrongFraming {
fits: Some(fits), ..
}) => assert!(fits.contains("short-rounded-length"), "{fits}"),
other => panic!("the rounded length was taken by the strict reader: {other:?}"),
}
}
#[test]
fn a_tolerant_reader_takes_any_flags_on_the_sentinel() {
for pid in [0x3f02u16, 0x7f02, 0xbf02, 0xff02] {
let [lo, hi] = pid.to_le_bytes();
let bytes = [lo, hi, 0x00, 0x00];
let strict_expects = Reader::new(&bytes, Endianness::Little)
.with_pl_cdr1_framing(PlCdr1Framing::DEFAULT)
.read_pid();
assert_eq!(
strict_expects.is_ok(),
pid == 0x7f02,
"the strict reader's verdict on {pid:#06x}: {strict_expects:?}"
);
let mut tolerant =
Reader::new(&bytes, Endianness::Little).with_pl_cdr1_reading(PlCdr1Reading::Tolerant);
assert!(
tolerant.read_pid().expect("a tolerant reader").end,
"{pid:#06x} did not end the list"
);
let mut bare = Reader::new(&bytes, Endianness::Little).with_pl_cdr1_framing(SHORT_FORM);
assert_eq!(bare.read_pid().is_ok(), pid == 0x3f02, "{pid:#06x}");
}
}
#[test]
fn a_tolerant_reader_takes_any_flags_on_the_extended_marker() {
for marker in [0x3f01u16, 0x7f01, 0xbf01, 0xff01] {
let [lo, hi] = marker.to_le_bytes();
let bytes = [
lo, hi, 0x08, 0x00, 0x09, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, ];
let strict = Reader::new(&bytes, Endianness::Little).read_pid();
assert_eq!(
strict.is_ok(),
marker == 0x7f01,
"the strict reader's verdict on {marker:#06x}: {strict:?}"
);
let mut tolerant =
Reader::new(&bytes, Endianness::Little).with_pl_cdr1_reading(PlCdr1Reading::Tolerant);
let header = tolerant.read_pid().expect("a tolerant reader");
assert_eq!((header.pid, header.length), (9, 4), "{marker:#06x}");
assert_eq!(tolerant.read_u32().expect("value"), 42);
tolerant.finish_pid(header).expect("the block ends");
}
}
#[test]
fn a_tolerant_reader_takes_the_short_form_where_the_extended_one_is_expected() {
let always_extended = PlCdr1Framing {
require_extended: true,
..PlCdr1Framing::DEFAULT
};
let short = [
0x01, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x02, 0x7f, 0x00, 0x00, ];
let strict = Reader::new(&short, Endianness::Little)
.with_pl_cdr1_framing(always_extended)
.read_pid();
assert!(
matches!(strict, Err(WireError::WrongFraming { .. })),
"{strict:?}"
);
let mut tolerant = Reader::new(&short, Endianness::Little)
.with_pl_cdr1_framing(always_extended)
.with_pl_cdr1_reading(PlCdr1Reading::Tolerant);
let header = tolerant.read_pid().expect("the short form");
assert_eq!((header.pid, header.length), (1, 4));
assert_eq!(tolerant.read_i32().expect("value"), 7);
tolerant.finish_pid(header).expect("the block ends");
assert!(tolerant.read_pid().expect("sentinel").end);
let extended = [
0x01, 0x7f, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, ];
let mut r =
Reader::new(&extended, Endianness::Little).with_pl_cdr1_reading(PlCdr1Reading::Tolerant);
let header = r.read_pid().expect("the extended form");
assert_eq!((header.pid, header.length), (1, 4));
}
#[test]
fn a_tolerant_reader_takes_either_length_convention_and_no_third() {
let mut w = Writer::little_endian().with_pl_cdr1_framing(ROUNDED);
let pid = w
.write_pid(5, ParameterRole::Member, false)
.expect("a header the plan chose");
w.write_u8(0xAA);
w.finish_pid(pid);
let rounded = w.finish();
let mut r =
Reader::new(&rounded, Endianness::Little).with_pl_cdr1_reading(PlCdr1Reading::Tolerant);
let header = r.read_pid().expect("the header");
assert_eq!(header.length, 4);
assert_eq!(r.read_u8().expect("value"), 0xAA);
r.finish_pid(header).expect("the rounded length");
let mut exact = rounded.clone();
exact[2] = 1;
let mut r = Reader::new(&exact, Endianness::Little)
.with_pl_cdr1_framing(ROUNDED)
.with_pl_cdr1_reading(PlCdr1Reading::Tolerant);
let header = r.read_pid().expect("the header");
assert_eq!(r.read_u8().expect("value"), 0xAA);
r.finish_pid(header).expect("the exact length");
let mut neither = rounded.clone();
neither[2] = 2;
let mut r =
Reader::new(&neither, Endianness::Little).with_pl_cdr1_reading(PlCdr1Reading::Tolerant);
let header = r.read_pid().expect("the header");
let _ = r.read_u8().expect("value");
assert!(matches!(
r.finish_pid(header),
Err(WireError::OutOfRange { .. })
));
}
#[test]
fn the_extended_parameter_form_carries_a_thirty_two_bit_id() {
let mut w = Writer::little_endian();
let pid = w
.write_pid_extended(0x0001_0002, ParameterRole::Member, false)
.expect("a header the plan chose");
w.write_i32(1);
w.finish_pid(pid);
let bytes = w.finish();
let mut r = Reader::new(&bytes, Endianness::Little);
let read = r.read_pid().expect("pid");
assert_eq!(read.pid, 0x0001_0002);
assert_eq!(read.length, 4);
}
#[test]
fn the_sentinel_ends_a_parameter_list() {
let mut w = Writer::little_endian();
w.write_sentinel();
let bytes = w.finish();
let mut r = Reader::new(&bytes, Endianness::Little);
let read = r.read_pid().expect("sentinel");
assert!(read.end, "the sentinel did not read as the list's end");
}
#[test]
fn a_sequence_writes_a_count_and_an_array_does_not() {
let values = [1i32, 2, 3];
let mut sequence = Writer::little_endian();
sequence
.write_sequence(&values, |w, v| {
w.write_i32(*v);
Ok(())
})
.expect("a long never refuses");
assert_eq!(sequence.as_bytes().len(), 16, "count plus three longs");
let mut array = Writer::little_endian();
array
.write_array(&values, |w, v| {
w.write_i32(*v);
Ok(())
})
.expect("a long never refuses");
assert_eq!(array.as_bytes().len(), 12, "an array wrote a count");
}
#[test]
fn a_sequence_round_trips() {
let mut w = Writer::little_endian();
w.write_sequence(&[7i32, 8, 9], |w, v| {
w.write_i32(*v);
Ok(())
})
.expect("a long never refuses");
let bytes = w.finish();
let mut r = Reader::new(&bytes, Endianness::Little);
let read = r.read_sequence(Reader::read_i32).expect("sequence");
assert_eq!(read, vec![7, 8, 9]);
}
#[test]
fn a_truncated_stream_is_an_error_not_a_zero() {
let mut r = Reader::new(&[1, 2], Endianness::Little);
assert!(matches!(r.read_i32(), Err(WireError::Truncated { .. })));
}
#[test]
fn a_length_larger_than_the_stream_is_refused_before_anything_is_allocated() {
let bytes = [0xFF, 0xFF, 0xFF, 0x7F];
let mut r = Reader::new(&bytes, Endianness::Little);
assert!(matches!(
r.read_string(),
Err(WireError::ImpossibleLength { .. })
));
let mut r = Reader::new(&bytes, Endianness::Little);
assert!(matches!(
r.read_sequence(Reader::read_i32),
Err(WireError::ImpossibleLength { .. })
));
}
#[test]
fn a_boolean_that_is_neither_zero_nor_one_is_refused() {
let mut r = Reader::new(&[2], Endianness::Little);
assert!(matches!(r.read_bool(), Err(WireError::OutOfRange { .. })));
}
#[test]
fn a_string_of_bytes_that_are_not_utf8_is_refused() {
let bytes = [3, 0, 0, 0, 0xFF, 0xFE, 0];
let mut r = Reader::new(&bytes, Endianness::Little);
assert!(matches!(r.read_string(), Err(WireError::NotUtf8 { .. })));
}
#[test]
fn a_short_holder_is_the_hash_zero_padded() {
let mut w = Writer::big_endian();
w.write_i32(1);
let hash = w.zero_padded();
assert_eq!(hash[..4], [0, 0, 0, 1]);
assert_eq!(hash[4..], [0u8; 12]);
}
#[test]
fn the_digest_matches_the_published_vectors() {
assert_eq!(
hex(&zerodds_wire::md5(b"abc")),
"900150983cd24fb0d6963f7d28e17f72"
);
assert_eq!(
hex(&zerodds_wire::md5(b"")),
"d41d8cd98f00b204e9800998ecf8427e"
);
for (input, digest) in [
(&b"a"[..], "0cc175b9c0f1b6a831c399e269772661"),
(b"message digest", "f96b697d7cb7938d525a2f31aaf161d0"),
(
b"abcdefghijklmnopqrstuvwxyz",
"c3fcd3d76192e4007dfb496cca67e13b",
),
(
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
"d174ab98d277d9f5a5611c2c9f419d9f",
),
(
b"12345678901234567890123456789012345678901234567890123456789012345678901234567890",
"57edf4a22be3c955ac49da2e2107b67a",
),
] {
assert_eq!(hex(&zerodds_wire::md5(input)), digest, "{input:?}");
}
}
#[test]
fn the_digest_is_the_same_at_every_padding_boundary() {
for length in [0usize, 1, 55, 56, 57, 63, 64, 65, 119, 120, 128] {
let input: Vec<u8> = (0..length).map(|i| (i * 7 + 3) as u8).collect();
assert_eq!(
zerodds_wire::md5(&input),
reference_md5(&input),
"length {length}"
);
}
}
fn reference_md5(input: &[u8]) -> [u8; 16] {
let s: [u32; 64] = [
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5,
9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10,
15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
];
let k: Vec<u32> = (0..64)
.map(|i| ((f64::from(i + 1)).sin().abs() * 4_294_967_296.0) as u32)
.collect();
let mut message = input.to_vec();
message.push(0x80);
while message.len() % 64 != 56 {
message.push(0);
}
message.extend_from_slice(&((input.len() as u64) * 8).to_le_bytes());
let mut h = [0x6745_2301_u32, 0xefcd_ab89, 0x98ba_dcfe, 0x1032_5476];
for chunk in message.chunks_exact(64) {
let m: Vec<u32> = chunk
.chunks_exact(4)
.map(|w| u32::from_le_bytes([w[0], w[1], w[2], w[3]]))
.collect();
let (mut a, mut b, mut c, mut d) = (h[0], h[1], h[2], h[3]);
for i in 0..64 {
let (f, g) = match i / 16 {
0 => ((b & c) | (!b & d), i),
1 => ((d & b) | (!d & c), (5 * i + 1) % 16),
2 => (b ^ c ^ d, (3 * i + 5) % 16),
_ => (c ^ (b | !d), (7 * i) % 16),
};
let sum = a.wrapping_add(f).wrapping_add(k[i]).wrapping_add(m[g]);
a = d;
d = c;
c = b;
b = b.wrapping_add(sum.rotate_left(s[i]));
}
h[0] = h[0].wrapping_add(a);
h[1] = h[1].wrapping_add(b);
h[2] = h[2].wrapping_add(c);
h[3] = h[3].wrapping_add(d);
}
let mut out = [0u8; 16];
for (i, word) in h.iter().enumerate() {
out[i * 4..i * 4 + 4].copy_from_slice(&word.to_le_bytes());
}
out
}
fn hex(bytes: &[u8; 16]) -> String {
let mut out = String::new();
for byte in bytes {
out.push_str(&format!("{byte:02x}"));
}
out
}
#[test]
fn a_length_code_of_five_leaves_its_next_int_for_the_member() {
let mut w = Writer::little_endian();
let header = w
.write_emheader(2, 5, false)
.expect("a header the plan chose");
w.write_u32(3);
w.write_u8(7);
w.write_u8(8);
w.write_u8(9);
w.finish_emheader(header)
.expect("the member is the length its code declares");
let bytes = w.finish();
assert_eq!(bytes.len(), 11, "a placeholder was reserved: {bytes:?}");
let mut r = Reader::new(&bytes, Endianness::Little);
let read = r.read_emheader().expect("emheader");
assert_eq!(read.length_code, 5);
assert_eq!(read.next_int, Some(3), "the NEXTINT was not seen");
assert_eq!(read.extent, 7, "four bytes of length plus three of body");
assert_eq!(r.position(), 4, "the NEXTINT was consumed");
assert_eq!(
r.read_sequence(Reader::read_u8).expect("the member"),
[7, 8, 9]
);
}
#[test]
fn an_emheader_next_int_that_cannot_be_true_is_refused() {
let bytes = [0x01, 0x00, 0x00, 0x40, 0xFF, 0xFF, 0xFF, 0xFF];
let mut r = Reader::new(&bytes, Endianness::Little);
match r.read_emheader() {
Err(WireError::ImpossibleLength { at, claimed, .. }) => {
assert_eq!(at, 4);
assert_eq!(claimed, 0xFFFF_FFFF);
}
other => panic!("a NEXTINT of all ones was accepted: {other:?}"),
}
}
#[test]
fn a_delimited_sequence_cannot_run_past_its_delimiter() {
let mut w = Writer::little_endian();
w.write_u32(8);
w.write_u32(1);
w.write_u32(0x1111_1111);
w.write_u32(0x2222_2222);
let mut bytes = w.finish();
bytes[4] = 2;
let mut r = Reader::new(&bytes, Endianness::Little);
match r.read_delimited_sequence(Reader::read_u32) {
Err(WireError::Truncated { .. }) => {}
other => panic!("the sequence read past its delimiter: {other:?}"),
}
}
#[test]
fn a_delimited_sequence_that_stops_short_of_its_delimiter_is_refused() {
let mut w = Writer::little_endian();
w.write_u32(12); w.write_u32(1); w.write_u32(0x1111_1111);
w.write_u32(0x2222_2222);
let bytes = w.finish();
let mut r = Reader::new(&bytes, Endianness::Little);
match r.read_delimited_sequence(Reader::read_u32) {
Err(WireError::OutOfRange { at, .. }) => assert_eq!(at, 12),
other => panic!("four bytes inside the delimiter went unread: {other:?}"),
}
}
#[test]
fn a_string_whose_terminator_is_not_a_nul_is_refused() {
let bytes = [4, 0, 0, 0, b'a', b'b', b'c', b'X'];
let mut r = Reader::new(&bytes, Endianness::Little);
match r.read_string() {
Err(WireError::OutOfRange { at, .. }) => assert_eq!(at, 7),
other => panic!("a string ran on past its terminator: {other:?}"),
}
}
#[test]
fn a_string_with_a_nul_inside_it_is_refused() {
let bytes = [4, 0, 0, 0, b'a', 0, b'c', 0];
let mut r = Reader::new(&bytes, Endianness::Little);
match r.read_string() {
Err(WireError::OutOfRange { at, .. }) => assert_eq!(at, 5),
other => panic!("a string with an interior NUL was accepted: {other:?}"),
}
}
#[test]
fn a_parameter_length_that_cannot_be_true_is_refused() {
let bytes = [5, 0, 0xFF, 0xFF, 1, 2, 3, 4];
let mut r = Reader::new(&bytes, Endianness::Little).with_pl_cdr1_framing(SHORT_FORM);
match r.read_pid() {
Err(WireError::ImpossibleLength { at, claimed, .. }) => {
assert_eq!(at, 2);
assert_eq!(claimed, 0xFFFF);
}
other => panic!("a parameter length of all ones was accepted: {other:?}"),
}
}
#[test]
fn a_flagged_sentinel_and_a_flagged_extended_header_are_recognised() {
let bytes = [
0x01, 0x7f, 0x08, 0x00, 0x09, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x02, 0x7f, 0x00, 0x00, ];
let mut r = Reader::new(&bytes, Endianness::Little);
let header = r.read_pid().expect("the extended header");
assert_eq!(header.pid, 9, "the flag was read as part of the id");
assert_eq!(header.length, 4);
assert!(!header.must_understand);
assert_eq!(r.read_u32().expect("the value"), 42);
r.finish_pid(header).expect("the block ends");
assert!(
r.read_pid().expect("the sentinel").end,
"a flagged sentinel was read as a parameter"
);
}
#[test]
fn flag_two_of_an_extended_header_is_the_members_must_understand() {
let mut w = Writer::little_endian();
let pid = w
.write_pid_extended(1, ParameterRole::Member, true)
.expect("a header the plan chose");
w.write_i32(7);
w.finish_pid(pid);
assert_eq!(
w.as_bytes(),
&[
0x01, 0x7f, 0x08, 0x00, 0x01, 0x00, 0x00, 0x40, 0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, ]
);
let bytes = w.finish();
let mut r = Reader::new(&bytes, Endianness::Little);
let header = r.read_pid().expect("the extended header");
assert_eq!(header.pid, 1, "the flag bits were read as part of the id");
assert!(header.must_understand);
}
#[test]
fn a_parameter_value_counts_alignment_from_its_own_block() {
let mut w = Writer::new(Endianness::Little).with_pl_cdr1_framing(SHORT_FORM);
let pid = w
.write_pid(1, ParameterRole::Member, false)
.expect("a header the plan chose");
w.write_f64(1.5);
w.finish_pid(pid);
let bytes = w.finish();
assert_eq!(bytes.len(), 12, "padding inside the block: {bytes:?}");
let mut r = Reader::new(&bytes, Endianness::Little).with_pl_cdr1_framing(SHORT_FORM);
let header = r.read_pid().expect("the header");
assert_eq!(header.length, 8);
assert_eq!(
r.read_f64().expect("the value"),
1.5,
"the double was read from the wrong offset"
);
r.finish_pid(header).expect("the block ends");
assert_eq!(r.remaining(), 0);
}
#[test]
fn padding_the_stream_does_not_have_is_a_named_error() {
let bytes = [b'a'];
let mut r = Reader::new(&bytes, Endianness::Little);
assert_eq!(r.read_char().expect("char"), 'a');
match r.read_i32() {
Err(WireError::Truncated { at, needed, .. }) => {
assert_eq!(at, 1, "the offset must be where the padding began");
assert_eq!(needed, 3);
}
other => panic!("{other:?}"),
}
}
#[test]
fn a_short_length_code_replaces_the_next_int_when_the_member_fits_one() {
for (length, code, body) in [
(1usize, 0u8, vec![0xABu8]),
(2, 1, vec![0xAB, 0xCD]),
(4, 2, vec![1, 0, 0, 0]),
(8, 3, vec![1, 0, 0, 0, 2, 0, 0, 0]),
] {
let mut w = Writer::with_max_align(Endianness::Little, 4).with_short_length_codes(true);
let header = w
.write_emheader(1, 4, false)
.expect("a header the plan chose");
for byte in &body {
w.write_u8(*byte);
}
w.finish_emheader(header)
.expect("the member is the length its code declares");
let bytes = w.finish();
assert_eq!(
bytes.len(),
4 + length,
"the NEXTINT survived a {length}-byte member: {bytes:?}"
);
let mut r = Reader::with_max_align(&bytes, Endianness::Little, 4);
let read = r.read_emheader().expect("emheader");
assert_eq!(read.length_code, code, "{length} bytes");
assert_eq!(read.member_id, 1);
assert_eq!(read.extent, length);
}
}
#[test]
fn a_member_that_fits_no_short_code_keeps_its_next_int() {
let mut w = Writer::with_max_align(Endianness::Little, 4).with_short_length_codes(true);
let header = w
.write_emheader(1, 4, false)
.expect("a header the plan chose");
for _ in 0..3 {
w.write_u8(7);
}
w.finish_emheader(header)
.expect("the member is the length its code declares");
let bytes = w.finish();
assert_eq!(bytes.len(), 11, "{bytes:?}");
let mut r = Reader::with_max_align(&bytes, Endianness::Little, 4);
let read = r.read_emheader().expect("emheader");
assert_eq!(read.length_code, 4);
assert_eq!(read.next_int, Some(3));
}
#[test]
fn the_default_writer_leaves_an_lc4_header_alone() {
let mut w = Writer::with_max_align(Endianness::Little, 4);
let header = w
.write_emheader(1, 4, false)
.expect("a header the plan chose");
w.write_i32(5);
w.finish_emheader(header)
.expect("the member is the length its code declares");
assert_eq!(w.finish(), [1, 0, 0, 0x40, 4, 0, 0, 0, 5, 0, 0, 0]);
}
#[test]
fn shortening_a_header_keeps_its_id_and_its_flag_in_both_orders() {
for endianness in [Endianness::Little, Endianness::Big] {
let mut w = Writer::with_max_align(endianness, 4).with_short_length_codes(true);
let header = w
.write_emheader(0x0ABCDEF, 4, true)
.expect("a header the plan chose");
w.write_i32(5);
w.finish_emheader(header)
.expect("the member is the length its code declares");
let bytes = w.finish();
assert_eq!(bytes.len(), 8, "{endianness:?}: {bytes:?}");
let mut r = Reader::with_max_align(&bytes, endianness, 4);
let read = r.read_emheader().expect("emheader");
assert_eq!(read.member_id, 0x0ABCDEF, "{endianness:?}");
assert_eq!(read.length_code, 2, "{endianness:?}");
assert!(read.must_understand, "{endianness:?}");
}
}
#[test]
fn a_short_parameter_header_becomes_extended_when_the_value_overruns_it() {
let mut w = Writer::with_max_align(Endianness::Little, 8).with_pl_cdr1_framing(SHORT_FORM);
let header = w
.write_pid(1, ParameterRole::Member, false)
.expect("a header the plan chose");
let body = vec![0xABu8; 0x1_0000];
for byte in &body {
w.write_u8(*byte);
}
w.finish_pid(header);
let bytes = w.finish();
assert_eq!(&bytes[..4], &[0x01, 0x3f, 0x08, 0x00], "the marker");
assert_eq!(&bytes[4..8], &[0x01, 0, 0, 0], "the member id");
assert_eq!(&bytes[8..12], &[0x00, 0x00, 0x01, 0x00], "the length");
assert_eq!(bytes.len(), 12 + body.len());
assert_eq!(&bytes[12..14], &[0xAB, 0xAB], "the value moved intact");
let mut r =
Reader::with_max_align(&bytes, Endianness::Little, 8).with_pl_cdr1_framing(SHORT_FORM);
let read = r.read_pid().expect("a parameter header");
assert_eq!(read.pid, 1);
assert_eq!(read.length, body.len());
}
#[test]
fn a_value_that_fits_sixteen_bits_keeps_the_short_header() {
let mut w = Writer::with_max_align(Endianness::Little, 8).with_pl_cdr1_framing(SHORT_FORM);
let header = w
.write_pid(1, ParameterRole::Member, false)
.expect("a header the plan chose");
for _ in 0..0xFFFF {
w.write_u8(0xAB);
}
w.finish_pid(header);
let bytes = w.finish();
assert_eq!(&bytes[..4], &[0x01, 0x00, 0xff, 0xff], "the short header");
assert_eq!(bytes.len(), 4 + 0xFFFF);
}
#[test]
fn a_promoted_header_moves_the_must_understand_bit_to_flag_two() {
let mut w = Writer::with_max_align(Endianness::Little, 8);
let header = w
.write_pid(1, ParameterRole::Member, true)
.expect("a header the plan chose");
for _ in 0..0x1_0000 {
w.write_u8(0);
}
w.finish_pid(header);
let bytes = w.finish();
assert_eq!(&bytes[..4], &[0x01, 0x7f, 0x08, 0x00]);
assert_eq!(&bytes[4..8], &[0x01, 0x00, 0x00, 0x40]);
}
#[test]
fn a_fixed_is_bcd_nibbles_high_first_with_the_sign_last() {
fn bytes(text: &str, digits: u8, scale: u8, policy: FixedDigits) -> Vec<u8> {
let mut w = Writer::new(Endianness::Little);
w.write_fixed(text, digits, scale, policy)
.expect("a decimal that fits");
w.finish()
}
assert_eq!(
bytes("123.45", 5, 2, FixedDigits::Declared),
vec![0x12, 0x34, 0x5c]
);
assert_eq!(bytes("-1.5", 3, 1, FixedDigits::Declared), vec![0x01, 0x5d]);
assert_eq!(bytes("0", 3, 1, FixedDigits::Declared), vec![0x00, 0x0c]);
assert_eq!(bytes("-0.0", 3, 1, FixedDigits::Declared), vec![0x00, 0x0c]);
assert_eq!(
bytes("1", 5, 2, FixedDigits::Declared),
vec![0x00, 0x10, 0x0c]
);
assert_eq!(
bytes("1", 5, 2, FixedDigits::ValuePrecision),
vec![0x10, 0x0c]
);
}
#[test]
fn a_fixed_that_does_not_fit_is_refused() {
fn refuse(text: &str, digits: u8, scale: u8) -> bool {
Writer::new(Endianness::Little)
.write_fixed(text, digits, scale, FixedDigits::Declared)
.is_err()
}
assert!(refuse("1234", 5, 2), "the integer half holds three digits");
assert!(refuse("1.234", 5, 2), "the fraction holds two");
assert!(refuse("", 5, 2), "no digits at all");
assert!(refuse("12a.4", 5, 2), "not a decimal");
assert!(refuse("1.0", 0, 0), "a fixed with no digits holds nothing");
assert!(refuse("1.0", 32, 2), "IDL rule (42) caps the form at 31");
assert!(refuse("1.0", 2, 3), "a scale wider than the type");
assert!(!refuse("999.99", 5, 2));
assert!(!refuse("1.0", 31, 2));
}
#[test]
fn a_fixed_reads_back_as_the_one_spelling_its_declaration_admits() {
for (digits, scale, written, canonical) in [
(5u8, 2u8, "123.45", "123.45"),
(5, 2, "1", "1.00"),
(5, 2, "0001.5", "1.50"),
(3, 1, "-1.5", "-1.5"),
(3, 1, "0", "0.0"),
(3, 1, "-0.0", "0.0"),
(5, 0, "12345", "12345"),
(5, 5, ".12345", "0.12345"),
(
31,
2,
"12345678901234567890123456789.99",
"12345678901234567890123456789.99",
),
(1, 0, "9", "9"),
] {
for policy in [FixedDigits::Declared, FixedDigits::ValuePrecision] {
let mut w = Writer::new(Endianness::Little);
w.write_fixed(written, digits, scale, policy)
.unwrap_or_else(|error| panic!("{written} under fixed<{digits},{scale}>: {error}"));
let bytes = w.finish();
let mut r = Reader::new(&bytes, Endianness::Little);
let read = r
.read_fixed(digits, scale, policy)
.unwrap_or_else(|error| panic!("{written} {policy:?}: {error}"));
assert_eq!(
read, canonical,
"{written} under fixed<{digits},{scale}> {policy:?} came back as `{read}`"
);
assert_eq!(
r.remaining(),
0,
"{written} {policy:?}: the reader left {} octets of the field behind",
r.remaining()
);
let mut again = Writer::new(Endianness::Little);
again
.write_fixed(&read, digits, scale, policy)
.expect("the read-back writes");
assert_eq!(
again.finish(),
bytes,
"{written} {policy:?}: the read-back writes other octets"
);
}
}
}
#[test]
fn a_fixed_the_reader_cannot_make_a_value_of_is_refused() {
let read = |bytes: &[u8], digits, scale, policy| {
Reader::new(bytes, Endianness::Little)
.read_fixed(digits, scale, policy)
.is_err()
};
assert!(read(&[0x1a, 0x2c], 3, 1, FixedDigits::Declared));
assert!(read(&[0x12], 5, 2, FixedDigits::Declared));
assert!(read(&[0x11; 20], 5, 2, FixedDigits::ValuePrecision));
assert!(read(&[0x12, 0x3c], 0, 0, FixedDigits::Declared));
assert!(read(&[0x12, 0x3c], 32, 2, FixedDigits::Declared));
assert!(read(&[0x12, 0x3c], 2, 3, FixedDigits::Declared));
}
#[test]
fn a_fixed_reader_refuses_value_precision_beyond_the_declared_bound() {
for (bytes, digits, scale) in [(&[0x12, 0x3c][..], 1, 0), (&[0x12, 0x3d][..], 2, 1)] {
let mut r = Reader::new(bytes, Endianness::Little);
assert!(
r.read_fixed(digits, scale, FixedDigits::ValuePrecision)
.is_err()
);
}
}
#[test]
fn a_fixed_reader_stops_before_a_seventeenth_octet() {
let mut bytes = [0u8; 17];
bytes[16] = 0x1c;
let mut r = Reader::new(&bytes, Endianness::Little);
assert!(r.read_fixed(31, 0, FixedDigits::ValuePrecision).is_err());
assert_eq!(r.position(), 16);
assert_eq!(r.read_u8().unwrap(), 0x1c);
}
#[test]
fn value_string_digits_matches_ace_recorded_values() {
fn bytes(text: &str, digits: u8, scale: u8, policy: FixedDigits) -> Vec<u8> {
let mut w = Writer::new(Endianness::Little);
w.write_fixed(text, digits, scale, policy)
.expect("a decimal that fits");
w.finish()
}
assert_eq!(
bytes("123.45", 5, 2, FixedDigits::ValueStringDigits),
vec![0x12, 0x34, 0x5c],
"fixed<5,2> \"123.45\" from ACE: 12345c"
);
assert_eq!(
bytes("-123.45", 5, 2, FixedDigits::ValueStringDigits),
vec![0x12, 0x34, 0x5d],
"fixed<5,2> \"-123.45\" from ACE: 12345d"
);
assert_eq!(
bytes("12.34", 4, 2, FixedDigits::ValueStringDigits),
vec![0x01, 0x23, 0x4c],
"fixed<4,2> \"12.34\" from ACE: 01234c"
);
assert_eq!(
bytes("7", 1, 0, FixedDigits::ValueStringDigits),
vec![0x7c],
"fixed<1,0> \"7\" from ACE: 7c"
);
assert_eq!(
bytes("0.12345", 5, 5, FixedDigits::ValueStringDigits),
vec![0x00, 0x12, 0x34, 0x5c],
"fixed<5,5> \"0.12345\" from ACE: 0012345c"
);
assert_eq!(
bytes("0.00", 5, 2, FixedDigits::ValueStringDigits),
vec![0x00, 0x0c],
"fixed<5,2> \"0.00\" from ACE: 000c"
);
let thirty_one_nines = "9".repeat(31);
let mut expected = vec![0x99; 15];
expected.push(0x9c); assert_eq!(
bytes(&thirty_one_nines, 31, 0, FixedDigits::ValueStringDigits),
expected,
"fixed<31,0> 31 nines from ACE"
);
}
#[test]
fn value_string_digits_reads_back() {
let mut w = Writer::new(Endianness::Little);
w.write_fixed("0.12345", 5, 5, FixedDigits::ValueStringDigits)
.expect("write");
let bytes = w.finish();
assert_eq!(bytes, vec![0x00, 0x12, 0x34, 0x5c]);
let mut r = Reader::new(&bytes, Endianness::Little);
let read = r
.read_fixed(5, 5, FixedDigits::ValueStringDigits)
.expect("read");
assert_eq!(read, "0.12345");
}
#[test]
fn value_string_digits_past_31_digits_as_written_is_refused() {
for text in [
"00000000000000000000000000000001".to_string(),
format!("{}1", "0".repeat(300)),
] {
let mut w = Writer::new(Endianness::Little);
assert!(
w.write_fixed(&text, 31, 0, FixedDigits::ValueStringDigits)
.is_err(),
"{} digits as written",
text.len()
);
}
let mut w = Writer::new(Endianness::Little);
w.write_fixed(&"9".repeat(31), 31, 0, FixedDigits::ValueStringDigits)
.expect("thirty-one nines are the widest field");
assert_eq!(w.finish().len(), 16);
}
#[test]
fn value_string_digits_counts_the_scale_not_the_fraction_as_written() {
let mut w = Writer::new(Endianness::Little);
w.write_fixed("7", 5, 2, FixedDigits::ValueStringDigits)
.expect("write");
let bytes = w.finish();
assert_eq!(bytes, vec![0x70, 0x0c]);
let mut r = Reader::new(&bytes, Endianness::Little);
assert_eq!(
r.read_fixed(5, 2, FixedDigits::ValueStringDigits)
.expect("read"),
"7.00"
);
}
#[test]
fn a_continuing_writer_aligns_to_the_start_of_the_prefix() {
let prefix = vec![1, 2, 3, 4];
let mut continuing = Writer::continuing(Endianness::Little, 8, prefix.clone());
continuing.write_u64(0x1122_3344_5566_7788);
let bytes = continuing.finish();
assert_eq!(bytes.len(), 16, "four octets of padding, then eight");
assert_eq!(bytes[..4], [1, 2, 3, 4]);
assert_eq!(bytes[4..8], [0, 0, 0, 0]);
let mut appending = Writer::appending(Endianness::Little, 8, prefix);
appending.write_u64(0x1122_3344_5566_7788);
assert_eq!(
appending.finish().len(),
12,
"counted from the end of the prefix"
);
}
#[test]
fn a_bitmask_read_keeps_only_the_declared_bits() {
let mut r = Reader::new(&[0b0000_1111], Endianness::Little);
assert_eq!(r.read_bitmask(8, 0b0000_0101).expect("read"), 0b0000_0101);
let mut r = Reader::new(&[0xFF, 0xFF], Endianness::Little);
assert_eq!(r.read_bitmask(16, 0x8001).expect("read"), 0x8001);
let mut r = Reader::new(&[0xFF; 4], Endianness::Big);
assert_eq!(r.read_bitmask(32, 0x0000_0010).expect("read"), 0x10);
let mut r = Reader::new(&[0xFF; 8], Endianness::Little);
assert_eq!(
r.read_bitmask(64, 0x8000_0000_0000_0000).expect("read"),
0x8000_0000_0000_0000
);
}
#[test]
fn a_dheader_length_of_4_gib_or_more_is_refused() {
use zerodds_wire::{WireError, Writer, writer::checked_dheader_length};
assert_eq!(checked_dheader_length(0, 0).unwrap(), 0);
assert_eq!(
checked_dheader_length(u32::MAX as usize, 0).unwrap(),
u32::MAX
);
#[cfg(target_pointer_width = "64")]
{
let error = checked_dheader_length(u32::MAX as usize + 1, 12).unwrap_err();
assert!(
matches!(error, WireError::OutOfRange { at: 12, .. }),
"{error:?}"
);
}
let mut w = Writer::little_endian();
let header = w.write_dheader();
w.write_u32(7);
w.try_finish_dheader(header).unwrap();
assert_eq!(w.finish(), [4, 0, 0, 0, 7, 0, 0, 0]);
}
#[test]
fn the_ordered_map_reader_refuses_a_stream_that_repeats_a_key() {
let mut w = Writer::little_endian();
w.write_u32(2);
for value in [10i32, 20] {
w.write_i32(1);
w.write_i32(value);
}
let bytes = w.finish();
let mut r = Reader::new(&bytes, Endianness::Little);
let refused = r.read_map(|r| r.read_i32(), |r| r.read_i32());
assert!(
matches!(refused, Err(WireError::OutOfRange { .. })),
"{refused:?}"
);
let mut r = Reader::new(&bytes, Endianness::Little);
let kept = r
.read_map_entries(|r| r.read_i32(), |r| r.read_i32())
.expect("the entries form keeps both");
assert_eq!(kept.len(), 2);
}