pub const RESULT_COUNT_LEN: usize = 4;
pub const RESULT_ENTRY_LEN: usize = 8;
pub const REJECTION_SECTION_LEN: usize = RESULT_COUNT_LEN + RESULT_ENTRY_LEN;
fn read_u32(reply_body: &[u8], offset: usize) -> Option<u32> {
reply_body
.get(offset..offset + 4)
.and_then(|bytes| bytes.try_into().ok())
.map(u32::from_le_bytes)
}
#[must_use]
pub fn result_code(reply_body: &[u8]) -> Option<u32> {
let section_len = result_section_len(reply_body)?;
if section_len == RESULT_COUNT_LEN {
Some(0)
} else {
read_u32(reply_body, RESULT_COUNT_LEN + 4)
}
}
#[must_use]
pub fn result_section_len(reply_body: &[u8]) -> Option<usize> {
let count = usize::try_from(read_u32(reply_body, 0)?).ok()?;
let len = count
.checked_mul(RESULT_ENTRY_LEN)?
.checked_add(RESULT_COUNT_LEN)?;
(reply_body.len() >= len).then_some(len)
}
pub fn write_rejection_section(dst: &mut [u8], code: u32) {
let (count, entry) = dst[..REJECTION_SECTION_LEN].split_at_mut(RESULT_COUNT_LEN);
count.copy_from_slice(&1u32.to_le_bytes());
let (index, result) = entry.split_at_mut(size_of::<u32>());
index.copy_from_slice(&0u32.to_le_bytes());
result.copy_from_slice(&code.to_le_bytes());
}
#[cfg(test)]
mod tests {
use super::*;
fn success_body(payload: &[u8]) -> Vec<u8> {
let mut body = 0u32.to_le_bytes().to_vec();
body.extend_from_slice(payload);
body
}
fn rejection_body(code: u32) -> Vec<u8> {
let mut body = 1u32.to_le_bytes().to_vec();
body.extend_from_slice(&0u32.to_le_bytes());
body.extend_from_slice(&code.to_le_bytes());
body
}
#[test]
fn success_decodes_to_zero_and_payload_starts_after_count() {
let body = success_body(b"payload");
assert_eq!(result_code(&body), Some(0));
assert_eq!(result_section_len(&body), Some(RESULT_COUNT_LEN));
assert_eq!(&body[RESULT_COUNT_LEN..], b"payload");
}
#[test]
fn rejection_decodes_to_first_entry_result() {
let body = rejection_body(1009);
assert_eq!(result_code(&body), Some(1009));
assert_eq!(
result_section_len(&body),
Some(RESULT_COUNT_LEN + RESULT_ENTRY_LEN),
);
}
#[test]
fn written_rejection_section_is_the_frame_the_decoders_read() {
let mut section = [0u8; REJECTION_SECTION_LEN];
write_rejection_section(&mut section, 1009);
assert_eq!(section.as_slice(), rejection_body(1009).as_slice());
assert_eq!(result_code(§ion), Some(1009));
assert_eq!(result_section_len(§ion), Some(REJECTION_SECTION_LEN));
}
#[test]
fn malformed_body_is_none_never_zero() {
assert_eq!(result_code(&[]), None);
assert_eq!(result_section_len(&[]), None);
assert_eq!(result_code(&1u32.to_le_bytes()), None); assert_eq!(result_section_len(&1u32.to_le_bytes()), None);
assert_eq!(result_code(&[1, 0, 0, 0, 0, 0, 0, 0]), None); assert_eq!(result_section_len(&[1, 0, 0, 0, 0, 0, 0, 0]), None);
}
#[test]
fn given_truncated_result_entries_when_decoding_should_not_return_the_first_error() {
let mut body = rejection_body(1009);
body[..RESULT_COUNT_LEN].copy_from_slice(&2u32.to_le_bytes());
for missing_tail in [0, RESULT_ENTRY_LEN - 1] {
let mut truncated = body.clone();
truncated.extend(std::iter::repeat_n(0, missing_tail));
assert_eq!(result_code(&truncated), None);
assert_eq!(result_section_len(&truncated), None);
}
body.extend_from_slice(&1u32.to_le_bytes());
body.extend_from_slice(&1010u32.to_le_bytes());
assert_eq!(result_code(&body), Some(1009));
assert_eq!(
result_section_len(&body),
Some(RESULT_COUNT_LEN + 2 * RESULT_ENTRY_LEN)
);
}
#[test]
fn given_maximum_result_count_when_body_is_short_should_reject_without_overflow() {
let mut body = rejection_body(1009);
body[..RESULT_COUNT_LEN].copy_from_slice(&u32::MAX.to_le_bytes());
assert_eq!(result_code(&body), None);
assert_eq!(result_section_len(&body), None);
}
}