use super::{Bip6Address, Bip6Vmac, Bvlc6Function, Bvlc6ResultCode, Bvll6Error, BVLC_TYPE_BIP6};
use crate::{BvllSink, PayloadShape, HEADER_LENGTH, MAX_BVLL_LENGTH};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Bvll6Frame<'a> {
pub function: Bvlc6Function,
pub payload: &'a [u8],
}
impl<'a> Bvll6Frame<'a> {
pub fn decode(datagram: &'a [u8]) -> Result<Self, Bvll6Error> {
let [bvlc_type, function, high, low, payload @ ..] = datagram else {
return Err(Bvll6Error::TooShort {
length: datagram.len(),
});
};
if *bvlc_type != BVLC_TYPE_BIP6 {
return Err(Bvll6Error::NotBip6 {
bvlc_type: *bvlc_type,
});
}
let declared = usize::from(u16::from_be_bytes([*high, *low]));
if declared != datagram.len() {
return Err(Bvll6Error::LengthMismatch {
declared,
received: datagram.len(),
});
}
Ok(Self {
function: Bvlc6Function::from_raw(*function),
payload,
})
}
pub fn message(self) -> Result<Bvll6<'a>, Bvll6Error> {
let function = self.function;
match read(function, self.payload) {
Some(message) => Ok(message),
None if function.is_defined() => Err(Bvll6Error::PayloadLength {
function,
length: self.payload.len(),
}),
None => Err(Bvll6Error::UnknownFunction {
function: function.to_raw(),
}),
}
}
}
struct Fields<'a>(&'a [u8]);
impl<'a> Fields<'a> {
fn take<const N: usize>(&mut self) -> Option<[u8; N]> {
let (field, rest) = self.0.split_first_chunk::<N>()?;
self.0 = rest;
Some(*field)
}
fn vmac(&mut self) -> Option<Bip6Vmac> {
self.take().map(Bip6Vmac::new)
}
fn address(&mut self) -> Option<Bip6Address> {
self.take().map(Bip6Address::from_octets)
}
fn u16(&mut self) -> Option<u16> {
self.take().map(u16::from_be_bytes)
}
fn npdu(self) -> &'a [u8] {
self.0
}
fn end<T>(self, message: T) -> Option<T> {
self.0.is_empty().then_some(message)
}
}
fn read(function: Bvlc6Function, payload: &[u8]) -> Option<Bvll6<'_>> {
let mut fields = Fields(payload);
let source = fields.vmac()?;
let message = match function {
Bvlc6Function::BVLC_RESULT => Bvll6::BvlcResult {
source,
code: Bvlc6ResultCode::from_raw(fields.u16()?),
},
Bvlc6Function::ORIGINAL_UNICAST_NPDU => {
let destination = fields.vmac()?;
return Some(Bvll6::OriginalUnicastNpdu {
source,
destination,
npdu: fields.npdu(),
});
}
Bvlc6Function::ORIGINAL_BROADCAST_NPDU => {
return Some(Bvll6::OriginalBroadcastNpdu {
source,
npdu: fields.npdu(),
})
}
Bvlc6Function::ADDRESS_RESOLUTION => Bvll6::AddressResolution {
source,
target: fields.vmac()?,
},
Bvlc6Function::FORWARDED_ADDRESS_RESOLUTION => {
let target = fields.vmac()?;
Bvll6::ForwardedAddressResolution {
original_source: source,
target,
original_address: fields.address()?,
}
}
Bvlc6Function::ADDRESS_RESOLUTION_ACK => Bvll6::AddressResolutionAck {
source,
destination: fields.vmac()?,
},
Bvlc6Function::VIRTUAL_ADDRESS_RESOLUTION => Bvll6::VirtualAddressResolution { source },
Bvlc6Function::VIRTUAL_ADDRESS_RESOLUTION_ACK => Bvll6::VirtualAddressResolutionAck {
source,
destination: fields.vmac()?,
},
Bvlc6Function::FORWARDED_NPDU => {
let original_address = fields.address()?;
return Some(Bvll6::ForwardedNpdu {
original_source: source,
original_address,
npdu: fields.npdu(),
});
}
Bvlc6Function::REGISTER_FOREIGN_DEVICE => Bvll6::RegisterForeignDevice {
source,
ttl: fields.u16()?,
},
Bvlc6Function::DELETE_FOREIGN_DEVICE_TABLE_ENTRY => Bvll6::DeleteForeignDeviceTableEntry {
source,
entry: fields.address()?,
},
Bvlc6Function::DISTRIBUTE_BROADCAST_TO_NETWORK => {
return Some(Bvll6::DistributeBroadcastToNetwork {
original_source: source,
npdu: fields.npdu(),
})
}
_ => return None,
};
fields.end(message)
}
pub fn decode(datagram: &[u8]) -> Result<Bvll6<'_>, Bvll6Error> {
Bvll6Frame::decode(datagram)?.message()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Bvll6<'a> {
BvlcResult {
source: Bip6Vmac,
code: Bvlc6ResultCode,
},
OriginalUnicastNpdu {
source: Bip6Vmac,
destination: Bip6Vmac,
npdu: &'a [u8],
},
OriginalBroadcastNpdu {
source: Bip6Vmac,
npdu: &'a [u8],
},
AddressResolution {
source: Bip6Vmac,
target: Bip6Vmac,
},
ForwardedAddressResolution {
original_source: Bip6Vmac,
target: Bip6Vmac,
original_address: Bip6Address,
},
AddressResolutionAck {
source: Bip6Vmac,
destination: Bip6Vmac,
},
VirtualAddressResolution {
source: Bip6Vmac,
},
VirtualAddressResolutionAck {
source: Bip6Vmac,
destination: Bip6Vmac,
},
ForwardedNpdu {
original_source: Bip6Vmac,
original_address: Bip6Address,
npdu: &'a [u8],
},
RegisterForeignDevice {
source: Bip6Vmac,
ttl: u16,
},
DeleteForeignDeviceTableEntry {
source: Bip6Vmac,
entry: Bip6Address,
},
DistributeBroadcastToNetwork {
original_source: Bip6Vmac,
npdu: &'a [u8],
},
}
impl<'a> Bvll6<'a> {
#[must_use]
pub const fn function(&self) -> Bvlc6Function {
match self {
Self::BvlcResult { .. } => Bvlc6Function::BVLC_RESULT,
Self::OriginalUnicastNpdu { .. } => Bvlc6Function::ORIGINAL_UNICAST_NPDU,
Self::OriginalBroadcastNpdu { .. } => Bvlc6Function::ORIGINAL_BROADCAST_NPDU,
Self::AddressResolution { .. } => Bvlc6Function::ADDRESS_RESOLUTION,
Self::ForwardedAddressResolution { .. } => Bvlc6Function::FORWARDED_ADDRESS_RESOLUTION,
Self::AddressResolutionAck { .. } => Bvlc6Function::ADDRESS_RESOLUTION_ACK,
Self::VirtualAddressResolution { .. } => Bvlc6Function::VIRTUAL_ADDRESS_RESOLUTION,
Self::VirtualAddressResolutionAck { .. } => {
Bvlc6Function::VIRTUAL_ADDRESS_RESOLUTION_ACK
}
Self::ForwardedNpdu { .. } => Bvlc6Function::FORWARDED_NPDU,
Self::RegisterForeignDevice { .. } => Bvlc6Function::REGISTER_FOREIGN_DEVICE,
Self::DeleteForeignDeviceTableEntry { .. } => {
Bvlc6Function::DELETE_FOREIGN_DEVICE_TABLE_ENTRY
}
Self::DistributeBroadcastToNetwork { .. } => {
Bvlc6Function::DISTRIBUTE_BROADCAST_TO_NETWORK
}
}
}
#[must_use]
pub const fn source_vmac(&self) -> Bip6Vmac {
match *self {
Self::BvlcResult { source, .. }
| Self::OriginalUnicastNpdu { source, .. }
| Self::OriginalBroadcastNpdu { source, .. }
| Self::AddressResolution { source, .. }
| Self::AddressResolutionAck { source, .. }
| Self::VirtualAddressResolution { source }
| Self::VirtualAddressResolutionAck { source, .. }
| Self::RegisterForeignDevice { source, .. }
| Self::DeleteForeignDeviceTableEntry { source, .. }
| Self::ForwardedAddressResolution {
original_source: source,
..
}
| Self::ForwardedNpdu {
original_source: source,
..
}
| Self::DistributeBroadcastToNetwork {
original_source: source,
..
} => source,
}
}
#[must_use]
pub const fn destination_vmac(&self) -> Option<Bip6Vmac> {
match *self {
Self::OriginalUnicastNpdu { destination, .. }
| Self::AddressResolutionAck { destination, .. }
| Self::VirtualAddressResolutionAck { destination, .. } => Some(destination),
_ => None,
}
}
#[must_use]
pub const fn npdu(&self) -> Option<&'a [u8]> {
match *self {
Self::OriginalUnicastNpdu { npdu, .. }
| Self::OriginalBroadcastNpdu { npdu, .. }
| Self::ForwardedNpdu { npdu, .. }
| Self::DistributeBroadcastToNetwork { npdu, .. } => Some(npdu),
_ => None,
}
}
#[must_use]
pub const fn origin(&self) -> Option<Bip6Address> {
match *self {
Self::ForwardedNpdu {
original_address, ..
}
| Self::ForwardedAddressResolution {
original_address, ..
} => Some(original_address),
_ => None,
}
}
const fn payload_length(&self) -> usize {
let fixed = match self.function().payload_shape() {
Some(PayloadShape::Fixed(length) | PayloadShape::Npdu { prefix: length }) => length,
Some(PayloadShape::Table { .. }) | None => 0,
};
match self.npdu() {
Some(npdu) => fixed + npdu.len(),
None => fixed,
}
}
pub fn encoded_len(&self) -> Result<usize, Bvll6Error> {
if let Self::BvlcResult { code, .. } = self {
if !code.is_defined() {
return Err(Bvll6Error::UndefinedResultCode {
code: code.to_raw(),
});
}
}
let length = HEADER_LENGTH + self.payload_length();
if length > MAX_BVLL_LENGTH {
return Err(Bvll6Error::TooLong { length });
}
Ok(length)
}
pub fn encode_into(&self, output: &mut [u8]) -> Result<usize, Bvll6Error> {
let mut sink = output;
self.encode_to_sink(&mut sink)
}
pub fn encode_to_sink<S: BvllSink + ?Sized>(&self, sink: &mut S) -> Result<usize, Bvll6Error> {
let length = self.encoded_len()?;
if let Some(available) = sink.remaining_capacity() {
if available < length {
return Err(Bvll6Error::BufferExhausted {
needed: length,
available,
});
}
}
let [high, low] = (length as u16).to_be_bytes();
let [v0, v1, v2] = self.source_vmac().octets();
sink.write(&[
BVLC_TYPE_BIP6,
self.function().to_raw(),
high,
low,
v0,
v1,
v2,
])?;
match *self {
Self::VirtualAddressResolution { .. } => {}
Self::BvlcResult { code, .. } => sink.write(&code.to_raw().to_be_bytes())?,
Self::RegisterForeignDevice { ttl, .. } => sink.write(&ttl.to_be_bytes())?,
Self::AddressResolution { target: second, .. }
| Self::AddressResolutionAck {
destination: second,
..
}
| Self::VirtualAddressResolutionAck {
destination: second,
..
} => sink.write(&second.octets())?,
Self::ForwardedAddressResolution {
target,
original_address,
..
} => {
sink.write(&target.octets())?;
sink.write(&original_address.to_octets())?;
}
Self::DeleteForeignDeviceTableEntry { entry, .. } => sink.write(&entry.to_octets())?,
Self::OriginalUnicastNpdu {
destination, npdu, ..
} => {
sink.write(&destination.octets())?;
sink.write(npdu)?;
}
Self::ForwardedNpdu {
original_address,
npdu,
..
} => {
sink.write(&original_address.to_octets())?;
sink.write(npdu)?;
}
Self::OriginalBroadcastNpdu { npdu, .. }
| Self::DistributeBroadcastToNetwork { npdu, .. } => sink.write(npdu)?,
}
Ok(length)
}
}