use super::*;
type Encode = fn(&mut &mut [u8], &[u8]) -> Result<usize, SinkFull>;
fn text(payload: &[u8]) -> &str {
core::str::from_utf8(payload).expect("the cases use ASCII")
}
const ENCODERS: [(&str, Encode); 8] = [
("app CharacterString", |s, p| {
encode_app_character_string_to_sink(s, text(p))
}),
("ctx 3 CharacterString", |s, p| {
encode_ctx_character_string_to_sink(s, 3, text(p))
}),
("CharacterString contents", |s, p| {
encode_character_string_to_sink(s, text(p))
}),
("app OCTET STRING", |s, p| {
encode_app_octet_string_to_sink(s, p)
}),
("ctx 20 OCTET STRING", |s, p| {
encode_ctx_octet_string_to_sink(s, 20, p)
}),
("app BIT STRING", |s, p| {
encode_app_bit_string_to_sink(s, 3, p)
}),
("ctx 1 BIT STRING", |s, p| {
encode_ctx_bit_string_to_sink(s, 1, 3, p)
}),
("BIT STRING contents", |s, p| {
encode_bit_string_to_sink(s, 3, p)
}),
];
const GOLDENS: [&[u8]; 8] = [
&[0x73, 0x00, b'a', b'b'],
&[0x3B, 0x00, b'a', b'b'],
&[0x00, b'a', b'b'],
&[0x62, b'a', b'b'],
&[0xFA, 0x14, b'a', b'b'],
&[0x83, 0x03, b'a', b'b'],
&[0x1B, 0x03, b'a', b'b'],
&[0x03, b'a', b'b'],
];
#[test]
fn each_encoder_writes_its_tabulated_octets() {
for ((name, encode), golden) in ENCODERS.into_iter().zip(GOLDENS) {
let mut storage = [0xA5; 8];
let mut tail = &mut storage[..];
assert_eq!(encode(&mut tail, b"ab"), Ok(golden.len()), "{name}");
assert_eq!(tail.len(), 8 - golden.len(), "{name}");
assert_eq!(&storage[..golden.len()], golden, "{name}");
}
}
#[test]
fn a_longer_value_takes_the_extended_length_form() {
let payload = [b'x'; 252];
let mut storage = [0; 300];
let mut tail = &mut storage[..];
let written = encode_app_character_string_to_sink(&mut tail, text(&payload)).unwrap();
assert_eq!(written, 2 + 253);
assert_eq!(storage[..3], [0x75, 0xFD, 0x00]);
assert_eq!(storage[3..255], payload);
}
#[test]
fn a_value_that_does_not_fit_leaves_the_sink_unchanged() {
for ((name, encode), golden) in ENCODERS.into_iter().zip(GOLDENS) {
for room in 0..golden.len() {
let mut storage = [0xA5; 8];
let mut tail = &mut storage[..room];
assert_eq!(
encode(&mut tail, b"ab"),
Err(SinkFull {
needed: golden.len(),
available: room
}),
"{name}, {room} octets of room"
);
assert_eq!(tail.len(), room, "{name}");
assert_eq!(storage, [0xA5; 8], "{name}");
}
}
}
#[cfg(feature = "alloc")]
#[test]
fn the_sink_encoders_match_the_bytes_mut_encoders() {
use bytes::BytesMut;
use crate::primitives::{
encode_app_bit_string, encode_app_character_string, encode_app_octet_string,
encode_bit_string, encode_character_string, encode_ctx_bit_string,
encode_ctx_character_string, encode_ctx_octet_string,
};
type Hosted = fn(&mut BytesMut, &[u8]);
let hosted: [Hosted; 8] = [
|b, p| encode_app_character_string(b, text(p)).unwrap(),
|b, p| encode_ctx_character_string(b, 3, text(p)).unwrap(),
|b, p| encode_character_string(b, text(p)),
encode_app_octet_string,
|b, p| encode_ctx_octet_string(b, 20, p),
|b, p| encode_app_bit_string(b, 3, p),
|b, p| encode_ctx_bit_string(b, 1, 3, p),
|b, p| encode_bit_string(b, 3, p),
];
let payload = std::vec![b'z'; 70_000];
for length in [
0, 1, 3, 4, 5, 252, 253, 254, 255, 65_534, 65_535, 65_536, 70_000,
] {
let payload = &payload[..length];
for ((name, encode), hosted) in ENCODERS.into_iter().zip(hosted) {
let mut expected = BytesMut::new();
hosted(&mut expected, payload);
let mut storage = std::vec![0; expected.len()];
let mut tail = &mut storage[..];
assert_eq!(encode(&mut tail, payload), Ok(expected.len()), "{name}");
assert!(tail.is_empty(), "{name}");
assert_eq!(storage, expected[..], "{name}, {length} octets");
}
}
}
#[test]
fn a_length_past_32_bits_is_refused() {
let mut storage = [0xA5; 8];
let tail = &mut storage[..];
assert_eq!(header_length(&tail, u32::MAX as usize), Ok(u32::MAX));
if let Some(over) = (u32::MAX as usize).checked_add(1) {
assert_eq!(
header_length(&tail, over),
Err(SinkFull {
needed: over + MAX_HEADER,
available: 8
})
);
assert_eq!(
header_length(&tail, usize::MAX),
Err(SinkFull {
needed: usize::MAX,
available: 8
})
);
#[cfg(feature = "alloc")]
assert_eq!(
header_length(&std::vec::Vec::<u8>::new(), over),
Err(SinkFull {
needed: over + MAX_HEADER,
available: u32::MAX as usize
})
);
}
let mut header = [0; MAX_HEADER];
let mut header_tail = &mut header[..];
assert_eq!(
encode_tag_to_sink(&mut header_tail, 254, TagClass::Context, u32::MAX),
Ok(MAX_HEADER)
);
}