use std::{
fmt::{
Display,
Formatter,
},
net::Ipv6Addr,
};
use binator::{
base::{
nbit,
octet,
NBit,
},
utils::{
Utils,
UtilsAtom,
},
Contexting,
CoreAtom,
Parse,
Parsed,
Streaming,
Success,
};
use crate::ip_protocol::{
self,
IPProtocol,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct IPv6Header {
pub version: u8,
pub ds: u8,
pub ecn: u8,
pub flow_label: u32,
pub length: u16,
pub next_header: IPProtocol,
pub hop_limit: u8,
pub source_addr: Ipv6Addr,
pub dest_addr: Ipv6Addr,
}
pub enum Ipv6Atom {
Version(u8),
}
impl Display for Ipv6Atom {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Version(version) => {
write!(f, "Ipv4Context: Version field is not 6 found {}", version)
}
}
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(level = "trace", skip_all, ret(Display))
)]
pub fn ipv6_header<Stream, Context>(stream: Stream) -> Parsed<IPv6Header, Stream, Context>
where
Stream: Clone,
Stream: Eq,
Stream: Streaming,
Stream::Item: Into<u8>,
Context: Contexting<CoreAtom<Stream>>,
Context: Contexting<UtilsAtom<Stream>>,
Context: Contexting<UtilsAtom<Stream>>,
Context: Contexting<Ipv6Atom>,
{
let Success {
token: (version, tc_0),
stream,
} = nbit(NBit::FOUR)
.try_map(|(version, tc_0)| {
if version == 6 {
Ok((version, tc_0))
} else {
Err(Context::new(Ipv6Atom::Version(version)))
}
})
.parse(stream)?;
let Success {
token: (tc_1, flow_label_0),
stream,
} = nbit(NBit::FOUR).parse(stream)?;
let Success {
token: flow_label,
stream,
} = octet
.and(octet)
.map(|(flow_label_1, flow_label_2)| {
u32::from_be_bytes([0, flow_label_0, flow_label_1, flow_label_2])
})
.parse(stream)?;
let Success {
token: length,
stream,
} = octet.fill().map(u16::from_be_bytes).parse(stream)?;
let Success {
token: next_header,
stream,
} = ip_protocol::ip_protocol.parse(stream)?;
let Success {
token: hop_limit,
stream,
} = octet.parse(stream)?;
let Success {
token: source_addr,
stream,
} = octet.fill().map(Ipv6Addr::from).parse(stream)?;
let Success {
token: dest_addr,
stream,
} = octet.fill().map(Ipv6Addr::from).parse(stream)?;
Parsed::Success {
token: IPv6Header {
version,
ds: (tc_0 << 2) + (tc_1 >> 2),
ecn: tc_1 & 0b11,
flow_label,
length,
next_header,
hop_limit,
source_addr,
dest_addr,
},
stream,
}
}
#[cfg(test)]
mod tests {
use std::net::Ipv6Addr;
use binator::{
context::Ignore,
Parsed,
};
use pretty_assertions::assert_eq;
use super::{
IPProtocol,
IPv6Header,
};
#[test]
fn ipv6_header() {
let bytes = [
0x60, 0x20, 0x01, 0xFF, 0x05, 0x78, 0x3A, 0x05, 0x20, 0x01, 0x0D, 0xB8, 0x5C, 0xF8, 0x1A,
0xA8, 0x24, 0x81, 0x61, 0xE6, 0x5A, 0xC6, 0x03, 0xE0, 0x20, 0x01, 0x0D, 0xB8, 0x78, 0x90,
0x2A, 0xE9, 0x90, 0x8F, 0xA9, 0xF4, 0x2F, 0x4A, 0x9B, 0x80,
];
let expectation = IPv6Header {
version: 6,
ds: 0,
ecn: 2,
flow_label: 511,
length: 1400,
next_header: IPProtocol::ICMP_6,
hop_limit: 5,
source_addr: Ipv6Addr::new(0x2001, 0xDB8, 0x5CF8, 0x1AA8, 0x2481, 0x61E6, 0x5AC6, 0x3E0),
dest_addr: Ipv6Addr::new(
0x2001, 0xDB8, 0x7890, 0x2AE9, 0x908F, 0xA9F4, 0x2F4A, 0x9B80,
),
};
assert_eq!(
super::ipv6_header::<_, Ignore>(&bytes[..]),
Parsed::Success {
token: expectation,
stream: "".as_bytes(),
}
);
}
}