use crate::WireError;
use crate::codec::{WireDecode, WireEncode, read_u8, read_u32_le};
use crate::primitives::identifier::WireName;
use bytes::{BufMut, Bytes, BytesMut};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OptionDescriptor {
pub key: WireName,
pub kind: u8,
pub default_value: Bytes,
pub description: String,
}
impl WireEncode for OptionDescriptor {
fn encoded_size(&self) -> usize {
self.key.encoded_size() + 1 + 4 + self.default_value.len() + 4 + self.description.len()
}
fn encode(&self, buf: &mut BytesMut) {
self.key.encode(buf);
buf.put_u8(self.kind);
#[allow(clippy::cast_possible_truncation)]
buf.put_u32_le(self.default_value.len() as u32);
buf.put_slice(&self.default_value);
#[allow(clippy::cast_possible_truncation)]
buf.put_u32_le(self.description.len() as u32);
buf.put_slice(self.description.as_bytes());
}
}
impl WireDecode for OptionDescriptor {
fn decode(buf: &[u8]) -> Result<(Self, usize), WireError> {
let (key, mut pos) = WireName::decode(buf)?;
let kind = read_u8(buf, pos)?;
pos += 1;
let default_len = read_u32_le(buf, pos)? as usize;
pos += 4;
let default_end = pos
.checked_add(default_len)
.ok_or_else(|| WireError::UnexpectedEof {
offset: pos,
need: default_len,
have: buf.len().saturating_sub(pos),
})?;
if buf.len() < default_end {
return Err(WireError::UnexpectedEof {
offset: pos,
need: default_len,
have: buf.len().saturating_sub(pos),
});
}
let default_value = Bytes::copy_from_slice(&buf[pos..default_end]);
pos = default_end;
let description_len = read_u32_le(buf, pos)? as usize;
pos += 4;
let description_end =
pos.checked_add(description_len)
.ok_or_else(|| WireError::UnexpectedEof {
offset: pos,
need: description_len,
have: buf.len().saturating_sub(pos),
})?;
if buf.len() < description_end {
return Err(WireError::UnexpectedEof {
offset: pos,
need: description_len,
have: buf.len().saturating_sub(pos),
});
}
let description = String::from_utf8_lossy(&buf[pos..description_end]).into_owned();
pos = description_end;
Ok((
Self {
key,
kind,
default_value,
description,
},
pos,
))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DescribeOptionsResponse {
pub entries: Vec<OptionDescriptor>,
}
impl WireEncode for DescribeOptionsResponse {
fn encoded_size(&self) -> usize {
4 + self
.entries
.iter()
.map(WireEncode::encoded_size)
.sum::<usize>()
}
fn encode(&self, buf: &mut BytesMut) {
#[allow(clippy::cast_possible_truncation)]
buf.put_u32_le(self.entries.len() as u32);
for entry in &self.entries {
entry.encode(buf);
}
}
}
const MIN_DESCRIPTOR_SIZE: usize = 2 + 1 + 4 + 4;
impl WireDecode for DescribeOptionsResponse {
fn decode(buf: &[u8]) -> Result<(Self, usize), WireError> {
let count = read_u32_le(buf, 0)? as usize;
let mut pos = 4;
let mut entries = Vec::with_capacity(crate::codec::bounded_capacity(
count,
buf.len().saturating_sub(pos),
MIN_DESCRIPTOR_SIZE,
));
for _ in 0..count {
let (entry, consumed) = OptionDescriptor::decode(&buf[pos..])?;
pos += consumed;
entries.push(entry);
}
Ok((Self { entries }, pos))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample() -> DescribeOptionsResponse {
DescribeOptionsResponse {
entries: vec![
OptionDescriptor {
key: WireName::new("partitions_count").unwrap(),
kind: 11,
default_value: Bytes::copy_from_slice(&1u32.to_le_bytes()),
description: "Number of partitions to create".to_string(),
},
OptionDescriptor {
key: WireName::new("max_topic_size").unwrap(),
kind: 12,
default_value: Bytes::new(),
description: String::new(),
},
],
}
}
#[test]
fn roundtrip() {
let resp = sample();
let bytes = resp.to_bytes();
let (decoded, consumed) = DescribeOptionsResponse::decode(&bytes).unwrap();
assert_eq!(consumed, bytes.len());
assert_eq!(decoded, resp);
}
#[test]
fn roundtrip_empty() {
let resp = DescribeOptionsResponse { entries: vec![] };
let bytes = resp.to_bytes();
assert_eq!(bytes.len(), 4);
let (decoded, consumed) = DescribeOptionsResponse::decode(&bytes).unwrap();
assert_eq!(consumed, bytes.len());
assert_eq!(decoded, resp);
}
#[test]
fn truncated_returns_error() {
let bytes = sample().to_bytes();
for i in 5..bytes.len() {
assert!(
DescribeOptionsResponse::decode(&bytes[..i]).is_err(),
"expected error for truncation at byte {i}"
);
}
}
}