use std::{fmt::Debug, ops::Deref};
use crate::{
address::{addr::ScionAddr, host_addr::UnknownAddressTypeError, socket_addr::ScionSocketAddr},
core::{
convert::{TryFromModel, TryFromView},
encode::{InvalidStructureError, WireEncode},
macros::impl_from,
model::Model,
view::{View, ViewConversionError},
},
dataplane_path::model::DpPath,
header::{
model::{AddressHeader, CommonHeader, ScionPacketHeader},
view::ScionHeaderView,
},
packet::{
classify::{ClassifiedPacket, ClassifyError},
view::{ScionPacketView, ScionRawPacketView, ScionScmpPacketView, ScionUdpPacketView},
},
payload::{
ProtocolNumber, encode::PayloadEncode, scmp::model::ScmpMessage, udp::model::UdpDatagram,
},
};
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct ScionPacket<T: PayloadEncode> {
pub header: ScionPacketHeader,
pub payload: T,
}
impl<T: PayloadEncode> ScionPacket<T> {
#[inline]
pub fn into_raw(self) -> ScionRawPacket {
let header_size = self.header.required_size();
let payload_size = self.payload.required_size(header_size);
let mut buf = vec![0u8; payload_size];
let size = self
.payload
.try_encode(&mut buf[..], &self.header.address, header_size)
.expect("Buffer size must be sufficient based on required_size");
debug_assert_eq!(
size, payload_size,
"Encoded payload size must match required_size calculation"
);
ScionPacket {
header: self.header,
payload: buf,
}
}
#[inline]
pub fn src_scion_addr(&self) -> Result<ScionAddr, UnknownAddressTypeError> {
let host = self.header.address.src_host_addr.scion_host_addr()?;
Ok(ScionAddr::new(self.header.address.src_ia, host))
}
#[inline]
pub fn set_src_scion_addr(&mut self, addr: ScionAddr) {
self.header.address.src_ia = addr.isd_asn();
self.header.address.src_host_addr = addr.host().into();
}
#[inline]
pub fn dst_scion_addr(&self) -> Result<ScionAddr, UnknownAddressTypeError> {
let host = self.header.address.dst_host_addr.scion_host_addr()?;
Ok(ScionAddr::new(self.header.address.dst_ia, host))
}
#[inline]
pub fn set_dst_scion_addr(&mut self, addr: ScionAddr) {
self.header.address.dst_ia = addr.isd_asn();
self.header.address.dst_host_addr = addr.host().into();
}
}
impl<T: PayloadEncode> WireEncode for ScionPacket<T> {
#[inline]
fn required_size(&self) -> usize {
self.header.required_size() + self.payload.required_size(self.header.required_size())
}
#[inline]
fn wire_valid(&self) -> Result<(), InvalidStructureError> {
self.header.wire_valid()?;
self.payload.wire_valid()?;
Ok(())
}
#[inline]
unsafe fn encode_unchecked(&self, buf: &mut [u8]) -> usize {
let header_size = self.header.required_size();
let payload_size = self.payload.required_size(header_size);
unsafe {
{
let header_buf = buf.get_unchecked_mut(0..header_size);
self.header
.encode_unchecked(header_buf, payload_size as u16);
}
let payload_buf = buf.get_unchecked_mut(header_size..(header_size + payload_size));
self.payload
.encode_unchecked(payload_buf, &self.header.address, header_size);
}
self.required_size()
}
}
pub type ScionRawPacket = ScionPacket<Vec<u8>>;
impl ScionRawPacket {
#[inline]
pub fn new(
src: ScionAddr,
dst: ScionAddr,
path: DpPath,
next_header: ProtocolNumber,
payload: Vec<u8>,
) -> Self {
Self {
header: ScionPacketHeader {
common: CommonHeader {
traffic_class: 0,
flow_id: 0,
next_header,
},
address: AddressHeader::new(src, dst),
path,
},
payload,
}
}
#[inline]
pub fn try_classify(self) -> Result<ClassifiedPacket, ClassifyError> {
match self.header.common.next_header {
ProtocolNumber::Scmp => {
ScionScmpPacket::try_from_raw(self)
.map_err(ClassifyError::MalformedScmp)
.map(ClassifiedPacket::Scmp)
}
ProtocolNumber::Udp => {
ScionUdpPacket::try_from_raw(self)
.map_err(ClassifyError::MalformedUdp)
.map(ClassifiedPacket::Udp)
}
_ => Ok(ClassifiedPacket::Other(self.to_owned())),
}
}
#[inline]
pub fn try_into_scmp(self) -> Result<ScionScmpPacket, ViewConversionError> {
ScionScmpPacket::try_from_raw(self)
}
#[inline]
pub fn try_into_udp(self) -> Result<ScionUdpPacket, ViewConversionError> {
ScionUdpPacket::try_from_raw(self)
}
}
impl Debug for ScionRawPacket {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ScionPacket")
.field("header", &self.header)
.field("payload", &format_args!("{} bytes", self.payload.len()))
.finish()
}
}
impl Model for ScionRawPacket {
type ViewType = ScionRawPacketView;
}
impl TryFromModel for ScionRawPacketView {
type ModelType = ScionRawPacket;
}
impl TryFromView for ScionRawPacket {
type ViewType = ScionPacketView;
#[inline]
fn try_from_view(view: &Self::ViewType) -> Result<Self, ViewConversionError> {
ScionRawPacketRef::try_from_view(view).map(|packet_ref| packet_ref.to_owned())
}
}
impl_from!(ScionPacket<ScmpMessage>, ScionRawPacket, |scmp_packet| {
scmp_packet.into_raw()
});
impl_from!(ScionPacket<UdpDatagram>, ScionRawPacket, |udp_packet| {
udp_packet.into_raw()
});
pub type ScionRawPacketRef<'a> = ScionPacket<&'a [u8]>;
impl<'a> ScionPacket<&'a [u8]> {
#[inline]
pub fn try_from_view(view: &'a ScionPacketView) -> Result<Self, ViewConversionError> {
Ok(Self {
header: ScionPacketHeader::try_from_view(view.header())?,
payload: view.payload(),
})
}
#[inline]
pub fn try_from_slice(buf: &'a [u8]) -> Result<(Self, &'a [u8]), ViewConversionError> {
let payload_size = ScionHeaderView::try_from_slice(buf)?.0.payload_len();
let (header, rest) = ScionPacketHeader::try_from_slice(buf)?;
let (payload, rest) = rest.split_at_checked(payload_size as usize).ok_or(
ViewConversionError::BufferTooSmall {
at: "Payload",
required: payload_size as usize,
actual: rest.len(),
},
)?;
Ok((ScionPacket { header, payload }, rest))
}
}
impl Debug for ScionPacket<&[u8]> {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ScionRawPacketRef")
.field("header", &self.header)
.field("payload", &format_args!("{} bytes", self.payload.len()))
.finish()
}
}
impl<'a, RawT: PayloadEncode + Deref<Target = [u8]>> ScionPacket<RawT> {
#[inline]
pub fn to_owned(self) -> ScionRawPacket {
ScionPacket {
header: self.header,
payload: self.payload.to_vec(),
}
}
#[inline]
pub fn to_ref(&'a self) -> ScionRawPacketRef<'a> {
ScionPacket {
header: self.header.clone(),
payload: self.payload.deref(),
}
}
}
pub type ScionScmpPacket = ScionPacket<ScmpMessage>;
impl ScionScmpPacket {
#[inline]
pub fn new(src: ScionAddr, dst: ScionAddr, path: DpPath, payload: ScmpMessage) -> Self {
Self {
header: ScionPacketHeader {
common: CommonHeader {
traffic_class: 0,
flow_id: 0,
next_header: ProtocolNumber::Scmp,
},
address: AddressHeader::new(src, dst),
path,
},
payload,
}
}
#[inline]
pub fn try_from_raw(packet: ScionRawPacket) -> Result<Self, ViewConversionError> {
if packet.header.common.next_header != ProtocolNumber::Scmp {
return Err(ViewConversionError::Other("next header not SCMP"));
}
let payload = packet.payload;
let (scmp_message, _rest) = ScmpMessage::try_from_slice(&payload)?;
Ok(Self {
header: packet.header,
payload: scmp_message,
})
}
}
impl Debug for ScionScmpPacket {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ScionScmpPacket")
.field("header", &self.header)
.field("payload", &self.payload)
.finish()
}
}
impl Model for ScionScmpPacket {
type ViewType = ScionScmpPacketView;
}
impl TryFromModel for ScionScmpPacketView {
type ModelType = ScionScmpPacket;
}
impl TryFromView for ScionScmpPacket {
type ViewType = ScionScmpPacketView;
#[inline]
fn try_from_view(view: &Self::ViewType) -> Result<Self, ViewConversionError> {
let header = ScionPacketHeader::try_from_view(view.header())?;
let payload = ScmpMessage::from_view(&view.scmp().message());
Ok(Self { header, payload })
}
}
impl TryFrom<ScionRawPacket> for ScionScmpPacket {
type Error = ViewConversionError;
#[inline]
fn try_from(raw_packet: ScionRawPacket) -> Result<Self, Self::Error> {
Self::try_from_raw(raw_packet)
}
}
pub type ScionUdpPacket = ScionPacket<UdpDatagram>;
impl ScionUdpPacket {
#[inline]
pub fn new(src: ScionSocketAddr, dst: ScionSocketAddr, path: DpPath, payload: Vec<u8>) -> Self {
Self {
header: ScionPacketHeader {
common: CommonHeader {
traffic_class: 0,
flow_id: 0,
next_header: ProtocolNumber::Udp,
},
address: AddressHeader::new(src.scion_addr(), dst.scion_addr()),
path,
},
payload: UdpDatagram::new(src.port(), dst.port(), payload),
}
}
#[inline]
pub fn new_from_parts(address: AddressHeader, path: DpPath, payload: UdpDatagram) -> Self {
let header = ScionPacketHeader {
common: CommonHeader {
traffic_class: 0,
flow_id: 0,
next_header: ProtocolNumber::Udp,
},
address,
path,
};
Self { header, payload }
}
#[inline]
pub fn try_from_raw(packet: ScionRawPacket) -> Result<Self, ViewConversionError> {
if packet.header.common.next_header != ProtocolNumber::Udp {
return Err(ViewConversionError::Other("next header not UDP"));
}
let payload = packet.payload;
let (udp_datagram, _rest) = UdpDatagram::try_from_slice(&payload)?;
Ok(Self {
header: packet.header,
payload: udp_datagram,
})
}
}
impl Debug for ScionUdpPacket {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ScionUdpPacket")
.field("header", &self.header)
.field("payload", &self.payload)
.finish()
}
}
impl Model for ScionUdpPacket {
type ViewType = ScionUdpPacketView;
}
impl TryFromModel for ScionUdpPacketView {
type ModelType = ScionUdpPacket;
}
impl TryFromView for ScionUdpPacket {
type ViewType = ScionUdpPacketView;
#[inline]
fn try_from_view(view: &Self::ViewType) -> Result<Self, ViewConversionError> {
let header = ScionPacketHeader::try_from_view(view.header())?;
let payload = UdpDatagram::from_view(view.udp());
Ok(Self { header, payload })
}
}
impl TryFrom<ScionRawPacket> for ScionUdpPacket {
type Error = ViewConversionError;
#[inline]
fn try_from(packet: ScionRawPacket) -> Result<Self, Self::Error> {
Self::try_from_raw(packet)
}
}
impl ScionUdpPacket {
#[inline]
pub fn src_socket_addr(&self) -> Result<ScionSocketAddr, UnknownAddressTypeError> {
let src_port = self.payload.as_ref().src_port;
let isd_asn = self.header.address.src_ia;
let scion_addr = self.header.address.src_host_addr.scion_host_addr()?;
Ok(ScionSocketAddr::new(isd_asn, scion_addr, src_port))
}
#[inline]
pub fn set_src_socket_addr(&mut self, socket_addr: ScionSocketAddr) {
self.header.address.src_ia = socket_addr.isd_asn();
self.header.address.src_host_addr = socket_addr.host().into();
self.payload.as_mut().src_port = socket_addr.port();
}
#[inline]
pub fn dst_socket_addr(&self) -> Result<ScionSocketAddr, UnknownAddressTypeError> {
let dst_port = self.payload.as_ref().dst_port;
let isd_asn = self.header.address.dst_ia;
let scion_addr = self.header.address.dst_host_addr.scion_host_addr()?;
Ok(ScionSocketAddr::new(isd_asn, scion_addr, dst_port))
}
#[inline]
pub fn set_dst_socket_addr(&mut self, socket_addr: ScionSocketAddr) {
self.header.address.dst_ia = socket_addr.isd_asn();
self.header.address.dst_host_addr = socket_addr.host().into();
self.payload.as_mut().dst_port = socket_addr.port();
}
}
#[cfg(feature = "proptest")]
pub mod ptest {
use ::proptest::prelude::*;
use proptest::collection;
use super::*;
#[derive(Debug, Clone, Default)]
pub struct ArbitraryScionRawPacketParams {
pub header: <ScionPacketHeader as Arbitrary>::Parameters,
}
impl Arbitrary for ScionRawPacket {
type Parameters = ArbitraryScionRawPacketParams;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
(
ScionPacketHeader::arbitrary_with(params.header),
collection::vec(any::<u8>(), 0..2048),
)
.prop_map(|(header, payload)| ScionPacket { header, payload })
.boxed()
}
}
#[derive(Debug, Clone, Default)]
pub struct ArbitraryScionUdpPacketParams {
pub header: <ScionPacketHeader as Arbitrary>::Parameters,
}
impl Arbitrary for ScionUdpPacket {
type Parameters = ArbitraryScionUdpPacketParams;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
(
ScionPacketHeader::arbitrary_with(params.header),
any::<UdpDatagram>(),
)
.prop_map(|(mut header, payload)| {
header.common.next_header = ProtocolNumber::Udp;
ScionPacket { header, payload }
})
.boxed()
}
}
#[derive(Debug, Clone, Default)]
pub struct ArbitraryScionScmpPacketParams {
pub header: <ScionPacketHeader as Arbitrary>::Parameters,
pub scmp_message: <ScmpMessage as Arbitrary>::Parameters,
}
impl Arbitrary for ScionScmpPacket {
type Parameters = ArbitraryScionScmpPacketParams;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
(
ScionPacketHeader::arbitrary_with(params.header),
ScmpMessage::arbitrary_with(params.scmp_message),
)
.prop_map(|(mut header, payload)| {
header.common.next_header = ProtocolNumber::Scmp;
ScionPacket { header, payload }
})
.boxed()
}
}
}