use core::net::SocketAddrV4;
use crate::{
address_from_octets, address_to_octets, BdtEntries, BvlcFunction, BvlcResultCode, BvllError,
BvllSink, FdtEntries, BVLC_TYPE_BIP, HEADER_LENGTH, MAX_BVLL_LENGTH,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BvllFrame<'a> {
pub function: BvlcFunction,
pub payload: &'a [u8],
}
impl<'a> BvllFrame<'a> {
pub fn decode(datagram: &'a [u8]) -> Result<Self, BvllError> {
let [bvlc_type, function, high, low, payload @ ..] = datagram else {
return Err(BvllError::TooShort {
length: datagram.len(),
});
};
if *bvlc_type != BVLC_TYPE_BIP {
return Err(BvllError::NotBip {
bvlc_type: *bvlc_type,
});
}
let declared = usize::from(u16::from_be_bytes([*high, *low]));
if declared != datagram.len() {
return Err(BvllError::LengthMismatch {
declared,
received: datagram.len(),
});
}
Ok(Self {
function: BvlcFunction::from_raw(*function),
payload,
})
}
pub fn message(self) -> Result<Bvll<'a>, BvllError> {
let function = self.function;
let wrong_length = BvllError::PayloadLength {
function,
length: self.payload.len(),
};
Ok(match (function, self.payload) {
(BvlcFunction::BVLC_RESULT, &[high, low]) => {
Bvll::BvlcResult(BvlcResultCode::from_raw(u16::from_be_bytes([high, low])))
}
(BvlcFunction::WRITE_BROADCAST_DISTRIBUTION_TABLE, table) => {
Bvll::WriteBroadcastDistributionTable(
BdtEntries::from_wire(table).map_err(|_| wrong_length)?,
)
}
(BvlcFunction::READ_BROADCAST_DISTRIBUTION_TABLE, []) => {
Bvll::ReadBroadcastDistributionTable
}
(BvlcFunction::READ_BROADCAST_DISTRIBUTION_TABLE_ACK, table) => {
Bvll::ReadBroadcastDistributionTableAck(
BdtEntries::from_wire(table).map_err(|_| wrong_length)?,
)
}
(BvlcFunction::FORWARDED_NPDU, &[a, b, c, d, high, low, ref npdu @ ..]) => {
Bvll::ForwardedNpdu {
origin: address_from_octets([a, b, c, d, high, low]),
npdu,
}
}
(BvlcFunction::REGISTER_FOREIGN_DEVICE, &[high, low]) => Bvll::RegisterForeignDevice {
ttl: u16::from_be_bytes([high, low]),
},
(BvlcFunction::READ_FOREIGN_DEVICE_TABLE, []) => Bvll::ReadForeignDeviceTable,
(BvlcFunction::READ_FOREIGN_DEVICE_TABLE_ACK, table) => {
Bvll::ReadForeignDeviceTableAck(
FdtEntries::from_wire(table).map_err(|_| wrong_length)?,
)
}
(BvlcFunction::DELETE_FOREIGN_DEVICE_TABLE_ENTRY, &[a, b, c, d, high, low]) => {
Bvll::DeleteForeignDeviceTableEntry(address_from_octets([a, b, c, d, high, low]))
}
(BvlcFunction::DISTRIBUTE_BROADCAST_TO_NETWORK, npdu) => {
Bvll::DistributeBroadcastToNetwork(npdu)
}
(BvlcFunction::ORIGINAL_UNICAST_NPDU, npdu) => Bvll::OriginalUnicastNpdu(npdu),
(BvlcFunction::ORIGINAL_BROADCAST_NPDU, npdu) => Bvll::OriginalBroadcastNpdu(npdu),
_ if function.is_defined() => return Err(wrong_length),
_ => {
return Err(BvllError::UnknownFunction {
function: function.to_raw(),
})
}
})
}
}
pub fn decode(datagram: &[u8]) -> Result<Bvll<'_>, BvllError> {
BvllFrame::decode(datagram)?.message()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Bvll<'a> {
BvlcResult(BvlcResultCode),
WriteBroadcastDistributionTable(BdtEntries<'a>),
ReadBroadcastDistributionTable,
ReadBroadcastDistributionTableAck(BdtEntries<'a>),
ForwardedNpdu {
origin: SocketAddrV4,
npdu: &'a [u8],
},
RegisterForeignDevice {
ttl: u16,
},
ReadForeignDeviceTable,
ReadForeignDeviceTableAck(FdtEntries<'a>),
DeleteForeignDeviceTableEntry(SocketAddrV4),
DistributeBroadcastToNetwork(&'a [u8]),
OriginalUnicastNpdu(&'a [u8]),
OriginalBroadcastNpdu(&'a [u8]),
}
impl<'a> Bvll<'a> {
#[must_use]
pub const fn function(&self) -> BvlcFunction {
match self {
Self::BvlcResult(_) => BvlcFunction::BVLC_RESULT,
Self::WriteBroadcastDistributionTable(_) => {
BvlcFunction::WRITE_BROADCAST_DISTRIBUTION_TABLE
}
Self::ReadBroadcastDistributionTable => BvlcFunction::READ_BROADCAST_DISTRIBUTION_TABLE,
Self::ReadBroadcastDistributionTableAck(_) => {
BvlcFunction::READ_BROADCAST_DISTRIBUTION_TABLE_ACK
}
Self::ForwardedNpdu { .. } => BvlcFunction::FORWARDED_NPDU,
Self::RegisterForeignDevice { .. } => BvlcFunction::REGISTER_FOREIGN_DEVICE,
Self::ReadForeignDeviceTable => BvlcFunction::READ_FOREIGN_DEVICE_TABLE,
Self::ReadForeignDeviceTableAck(_) => BvlcFunction::READ_FOREIGN_DEVICE_TABLE_ACK,
Self::DeleteForeignDeviceTableEntry(_) => {
BvlcFunction::DELETE_FOREIGN_DEVICE_TABLE_ENTRY
}
Self::DistributeBroadcastToNetwork(_) => BvlcFunction::DISTRIBUTE_BROADCAST_TO_NETWORK,
Self::OriginalUnicastNpdu(_) => BvlcFunction::ORIGINAL_UNICAST_NPDU,
Self::OriginalBroadcastNpdu(_) => BvlcFunction::ORIGINAL_BROADCAST_NPDU,
}
}
#[must_use]
pub const fn npdu(&self) -> Option<&'a [u8]> {
match *self {
Self::ForwardedNpdu { npdu, .. }
| Self::DistributeBroadcastToNetwork(npdu)
| Self::OriginalUnicastNpdu(npdu)
| Self::OriginalBroadcastNpdu(npdu) => Some(npdu),
_ => None,
}
}
#[must_use]
pub const fn origin(&self) -> Option<SocketAddrV4> {
match *self {
Self::ForwardedNpdu { origin, .. } => Some(origin),
_ => None,
}
}
const fn payload_length(&self) -> usize {
match self {
Self::BvlcResult(_) | Self::RegisterForeignDevice { .. } => 2,
Self::ReadBroadcastDistributionTable | Self::ReadForeignDeviceTable => 0,
Self::WriteBroadcastDistributionTable(table)
| Self::ReadBroadcastDistributionTableAck(table) => table.wire_length(),
Self::ReadForeignDeviceTableAck(table) => table.wire_length(),
Self::DeleteForeignDeviceTableEntry(_) => 6,
Self::ForwardedNpdu { npdu, .. } => 6 + npdu.len(),
Self::DistributeBroadcastToNetwork(npdu)
| Self::OriginalUnicastNpdu(npdu)
| Self::OriginalBroadcastNpdu(npdu) => npdu.len(),
}
}
pub fn encoded_len(&self) -> Result<usize, BvllError> {
if let Self::BvlcResult(code) = self {
if !code.is_defined() {
return Err(BvllError::UndefinedResultCode {
code: code.to_raw(),
});
}
}
let length = HEADER_LENGTH + self.payload_length();
if length > MAX_BVLL_LENGTH {
return Err(BvllError::TooLong { length });
}
Ok(length)
}
pub fn encode_into(&self, output: &mut [u8]) -> Result<usize, BvllError> {
let mut sink = output;
self.encode_to_sink(&mut sink)
}
pub fn encode_to_sink<S: BvllSink + ?Sized>(&self, sink: &mut S) -> Result<usize, BvllError> {
let length = self.encoded_len()?;
if let Some(available) = sink.remaining_capacity() {
if available < length {
return Err(BvllError::BufferExhausted {
needed: length,
available,
});
}
}
let [high, low] = (length as u16).to_be_bytes();
sink.write(&[BVLC_TYPE_BIP, self.function().to_raw(), high, low])?;
match self {
Self::BvlcResult(code) => sink.write(&code.to_raw().to_be_bytes())?,
Self::RegisterForeignDevice { ttl } => sink.write(&ttl.to_be_bytes())?,
Self::ReadBroadcastDistributionTable | Self::ReadForeignDeviceTable => {}
Self::WriteBroadcastDistributionTable(table)
| Self::ReadBroadcastDistributionTableAck(table) => table.write_to(sink)?,
Self::ReadForeignDeviceTableAck(table) => table.write_to(sink)?,
Self::DeleteForeignDeviceTableEntry(address) => {
sink.write(&address_to_octets(*address))?
}
Self::ForwardedNpdu { origin, npdu } => {
sink.write(&address_to_octets(*origin))?;
sink.write(npdu)?;
}
Self::DistributeBroadcastToNetwork(npdu)
| Self::OriginalUnicastNpdu(npdu)
| Self::OriginalBroadcastNpdu(npdu) => sink.write(npdu)?,
}
Ok(length)
}
}