use stun_types::message::StunParseError;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct ChannelData<'a> {
id: u16,
data: &'a [u8],
}
impl<'a> ChannelData<'a> {
pub fn new(id: u16, data: &'a [u8]) -> Self {
Self { id, data }
}
pub fn id(&self) -> u16 {
self.id
}
pub fn data(&self) -> &[u8] {
self.data
}
pub fn parse(data: &'a [u8]) -> Result<Self, StunParseError> {
let (id, len) = Self::parse_header(data)?;
if len + 4 > data.len() {
return Err(stun_types::message::StunParseError::Truncated {
expected: 4 + len,
actual: data.len(),
});
}
Ok(ChannelData {
id,
data: &data[4..4 + len],
})
}
pub fn parse_header(data: &[u8]) -> Result<(u16, usize), StunParseError> {
if data.len() < 4 {
return Err(stun_types::message::StunParseError::Truncated {
expected: 4,
actual: data.len(),
});
}
let id = u16::from_be_bytes([data[0], data[1]]);
let len = u16::from_be_bytes([data[2], data[3]]) as usize;
if !(0x4000..=0xFFFE).contains(&id) {
return Err(stun_types::message::StunParseError::InvalidAttributeData);
}
Ok((id, len))
}
pub fn write_into_unchecked(self, dest: &mut [u8]) -> usize {
dest[..2].copy_from_slice(self.id.to_be_bytes().as_ref());
dest[2..4].copy_from_slice((self.data.len() as u16).to_be_bytes().as_ref());
dest[4..].copy_from_slice(self.data);
self.data.len() + 4
}
}
impl core::fmt::Display for ChannelData<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"ChannelData(id: {}, data of {} bytes)",
self.id,
self.data.len()
)
}
}
impl AsRef<[u8]> for ChannelData<'_> {
fn as_ref(&self) -> &[u8] {
self.data
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn channel_data_parse_invalid_id() {
let data = [0x00, 0x00, 0x00, 0x00];
assert!(matches!(
ChannelData::parse(&data),
Err(StunParseError::InvalidAttributeData)
));
}
#[test]
fn channel_data_parse_empty() {
let data = [0x40, 0x00, 0x00, 0x00];
let channel = ChannelData::parse(&data).unwrap();
assert_eq!(channel.data(), &[]);
}
#[test]
fn channel_data_parse_truncated_data() {
let data = [0x40, 0x00, 0x00, 0x01];
let Err(StunParseError::Truncated { expected, actual }) = ChannelData::parse(&data) else {
unreachable!();
};
assert_eq!(expected, 5);
assert_eq!(actual, 4);
assert_eq!(ChannelData::parse_header(&data).unwrap(), (0x4000, 1));
}
#[test]
fn channel_data_parse_truncated_header() {
let data = [0x40, 0x00, 0x00];
let Err(StunParseError::Truncated { expected, actual }) = ChannelData::parse(&data) else {
unreachable!();
};
assert_eq!(expected, 4);
assert_eq!(actual, 3);
}
static CHANNEL_SINGLE_BYTE: [u8; 5] = [0x40, 0x00, 0x00, 0x01, 0x42];
#[test]
fn channel_data_parse_success() {
let channel = ChannelData::parse(&CHANNEL_SINGLE_BYTE).unwrap();
assert_eq!(channel.data(), &[0x42]);
assert_eq!(channel.as_ref(), &[0x42]);
}
#[test]
fn channel_data_display() {
let channel = ChannelData::parse(&CHANNEL_SINGLE_BYTE).unwrap();
assert_eq!(
&alloc::format!("{channel}"),
"ChannelData(id: 16384, data of 1 bytes)"
);
}
}