use bytes::{Buf, BufMut};
use crate::{
ProtocolError,
primitives::{
fixed::{get_i32, put_i32},
varint::{get_uvarint, put_uvarint, uvarint_len},
},
};
pub fn put_array_len<B: BufMut>(buf: &mut B, n: usize, flexible: bool) {
if flexible {
put_uvarint(buf, u32::try_from(n + 1).expect("array too large"));
} else {
put_i32(buf, i32::try_from(n).expect("array too large"));
}
}
pub fn put_nullable_array_len<B: BufMut>(buf: &mut B, len: Option<usize>, flexible: bool) {
match (flexible, len) {
(false, None) => put_i32(buf, -1),
(false, Some(n)) => put_i32(buf, i32::try_from(n).expect("array too large")),
(true, None) => put_uvarint(buf, 0),
(true, Some(n)) => put_uvarint(buf, u32::try_from(n + 1).expect("array too large")),
}
}
#[must_use]
pub fn array_len_prefix_len(n: usize, flexible: bool) -> usize {
if flexible {
uvarint_len(u32::try_from(n + 1).unwrap())
} else {
4
}
}
#[must_use]
pub fn nullable_array_len_prefix_len(len: Option<usize>, flexible: bool) -> usize {
match (flexible, len) {
(false, _) => 4,
(true, None) => uvarint_len(0),
(true, Some(n)) => uvarint_len(u32::try_from(n + 1).unwrap()),
}
}
pub fn get_array_len<B: Buf>(buf: &mut B, flexible: bool) -> Result<usize, ProtocolError> {
let n = if flexible {
let raw = get_uvarint(buf)?;
if raw == 0 {
return Err(ProtocolError::InvalidValue(
"non-nullable array was null (compact encoding)",
));
}
(raw - 1) as usize
} else {
let n = get_i32(buf)?;
if n < 0 {
return Err(ProtocolError::InvalidValue(
"non-nullable array had negative length",
));
}
usize::try_from(n).expect("n is non-negative")
};
if n > buf.remaining() {
return Err(ProtocolError::InvalidValue(
"array length exceeds remaining buffer",
));
}
Ok(n)
}
pub fn get_nullable_array_len<B: Buf>(
buf: &mut B,
flexible: bool,
) -> Result<Option<usize>, ProtocolError> {
let n = if flexible {
let raw = get_uvarint(buf)?;
if raw == 0 {
return Ok(None);
}
(raw - 1) as usize
} else {
let n = get_i32(buf)?;
if n < 0 {
return Ok(None);
}
usize::try_from(n).expect("n is non-negative")
};
if n > buf.remaining() {
return Err(ProtocolError::InvalidValue(
"array length exceeds remaining buffer",
));
}
Ok(Some(n))
}
#[cfg(test)]
mod tests {
use bytes::BytesMut;
use super::*;
#[test]
fn array_roundtrip_cases() {
for (_case, len, flexible, expected_prefix) in [
("non-flex empty", 0, false, &[0, 0, 0, 0][..]),
("non-flex three", 3, false, &[0, 0, 0, 3][..]),
("flex empty", 0, true, &[0x01][..]),
("flex three", 3, true, &[0x04][..]),
] {
let mut buf = BytesMut::new();
put_array_len(&mut buf, len, flexible);
assert2::assert!(&buf[..] == expected_prefix);
buf.extend_from_slice(&vec![0; len]);
let mut cur = &buf[..];
assert2::assert!((get_array_len(&mut cur, flexible).unwrap(), cur.len()) == (len, len));
}
}
#[test]
fn nullable_array_roundtrip_cases() {
for (_case, len, flexible, expected_prefix) in [
("non-flex null", None, false, &[0xFF, 0xFF, 0xFF, 0xFF][..]),
("non-flex some", Some(3), false, &[0, 0, 0, 3][..]),
("flex null", None, true, &[0x00][..]),
("flex some", Some(3), true, &[0x04][..]),
] {
let mut buf = BytesMut::new();
put_nullable_array_len(&mut buf, len, flexible);
assert2::assert!(&buf[..] == expected_prefix);
let payload_len = len.unwrap_or(0);
buf.extend_from_slice(&vec![0; payload_len]);
let mut cur = &buf[..];
assert2::assert!(
(
get_nullable_array_len(&mut cur, flexible).unwrap(),
cur.len()
) == (len, payload_len)
);
}
}
#[test]
fn array_len_prefix_len_non_flex() {
for (_case, len) in [("empty", 0), ("populated", 100)] {
assert2::assert!(array_len_prefix_len(len, false) == 4);
}
}
#[test]
fn array_len_prefix_len_flex() {
for (len, want) in [(0, 1), (126, 1), (127, 2)] {
assert2::assert!(array_len_prefix_len(len, true) == want);
}
}
#[test]
fn nullable_prefix_len_non_flex_always_4() {
for (_case, len) in [("null", None), ("some", Some(3))] {
assert2::assert!(nullable_array_len_prefix_len(len, false) == 4);
}
}
#[test]
fn nullable_prefix_len_flex_null_is_1() {
assert2::assert!(nullable_array_len_prefix_len(None, true) == 1);
}
#[test]
fn non_nullable_rejects_null_cases() {
for (_case, bytes, flexible) in [
("non-flex", &(-1i32).to_be_bytes()[..], false),
("flex", &[0x00][..], true),
] {
let mut cur = bytes;
assert2::assert!(matches!(
get_array_len(&mut cur, flexible),
Err(ProtocolError::InvalidValue(_))
));
}
}
#[test]
fn rejects_length_exceeding_remaining_cases() {
for (_case, nullable, flexible) in [
("non-nullable non-flex", false, false),
("non-nullable flex", false, true),
("nullable non-flex", true, false),
("nullable flex", true, true),
] {
let mut buf = BytesMut::new();
if nullable {
put_nullable_array_len(&mut buf, Some(2_000_000_000), flexible);
} else {
put_array_len(&mut buf, 2_000_000_000, flexible);
}
let mut cur = &buf[..];
let result = if nullable {
get_nullable_array_len(&mut cur, flexible).map(|value| value.unwrap_or(0))
} else {
get_array_len(&mut cur, flexible)
};
assert2::assert!(matches!(
result,
Err(ProtocolError::InvalidValue(
"array length exceeds remaining buffer"
))
));
}
}
#[test]
fn length_equal_to_remaining_is_accepted() {
for (_case, flexible) in [("non-flex", false), ("flex", true)] {
let mut buf = BytesMut::new();
put_array_len(&mut buf, 5, flexible);
buf.extend_from_slice(&[0u8; 5]);
let mut cur = &buf[..];
assert2::assert!(get_array_len(&mut cur, flexible).unwrap() == 5);
}
}
}