use std::fmt::{
Debug,
Display,
Formatter,
};
use binator::{
base::{
any,
is,
octet,
primitive::{
u16_be,
u32_be,
},
BaseAtom,
IntRadixAtom,
},
utils::{
Acc,
Utils,
UtilsAtom,
},
Contexting,
CoreAtom,
Parse,
Parsed,
Streaming,
Success,
};
pub trait TcpParse<Stream, Context> = where
Stream: Streaming + Clone + Eq,
<Stream as Streaming>::Item: Into<u8> + Clone,
<Stream as Streaming>::Item: PartialEq<<Stream as Streaming>::Item>,
Context: Contexting<UtilsAtom<Stream>>,
Context: Contexting<BaseAtom<u8>>,
Context: Contexting<IntRadixAtom<u8>>,
Context: Contexting<CoreAtom<Stream>>,
Context: Contexting<TcpAtom>,
u8: Into<<Stream as Streaming>::Item>;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, PartialEq, Eq, Default)]
pub struct TcpFlags {
raw: u16,
}
macro_rules! tcp_flags {
($($name:ident => $pos:expr,)*) => {
impl TcpFlags {
$(paste::paste! {
pub const fn [<get_ $name>](&self) -> bool {
self.raw & 1 << $pos != 0
}
pub fn [<set_ $name>](&mut self, state: bool) -> bool {
if state {
self.raw |= 1 << $pos;
}
else {
self.raw &= !(1 << $pos);
}
state
}
})*
pub const fn get_data_offset(&self) -> u8 {
(self.raw >> 12) as u8
}
pub fn set_data_offset(&mut self, n: usize) -> Result<usize, usize> {
if n > 4 && n < 16 {
self.raw &= u16::MAX >> 4; self.raw |= (n as u16) << 12; Ok(n)
}
else {
Err(n)
}
}
}
paste::paste! {
impl Debug for TcpFlags {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TcpFlags")
.field("data_offset", &self.get_data_offset())
$(.field(stringify!($name), &self.[<get_ $name>]()))*
.finish()
}
}
}
};
}
tcp_flags! {
reserved_0 => 11,
reserved_1 => 10,
reserved_2 => 9,
ns => 8,
cwr => 7,
ece => 6,
urg => 5,
ack => 4,
psh => 3,
rst => 2,
syn => 1,
fin => 0,
}
impl From<u16> for TcpFlags {
fn from(raw: u16) -> Self {
Self { raw }
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TcpHeader<Span> {
pub source_port: u16,
pub dest_port: u16,
pub sequence_no: u32,
pub ack_no: u32,
pub flags: TcpFlags,
pub window: u16,
pub checksum: u16,
pub urgent_pointer: u16,
pub options: Span,
}
impl<Span> TcpHeader<Span> {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TcpAtom {
DataOffSet,
MssLen,
WindowScaleLen,
SackPermittedLen,
SackLen(u8),
TimestampsLen,
}
impl Display for TcpAtom {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
TcpAtom::DataOffSet => write!(f, "DataOffSet: data_offset is less than 5"),
TcpAtom::MssLen => {
write!(f, "MssLen: Maximun len size is not 4")
}
TcpAtom::WindowScaleLen => {
write!(f, "WindowScaleLen: Maximun len size is not 3")
}
TcpAtom::SackPermittedLen => {
write!(f, "SackPermittedLen: Maximun len size is not 3")
}
TcpAtom::SackLen(len) => {
write!(f, "SackLen: sack length is invalid found {}", len)
}
TcpAtom::TimestampsLen => {
write!(f, "TimestampsLen: Maximun len size is not 10")
}
}
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(level = "trace", skip_all, ret(Display))
)]
fn tcp_flags<Stream, Context>(stream: Stream) -> Parsed<TcpFlags, Stream, Context>
where
(): TcpParse<Stream, Context>,
{
u16_be
.map(TcpFlags::from)
.try_map(|flags| {
if flags.get_data_offset() >= 5 {
Ok(flags)
} else {
Err(Contexting::new(TcpAtom::DataOffSet))
}
})
.parse(stream)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(level = "trace", skip_all, ret(Display))
)]
pub fn tcp_header<Stream, Context>(
stream: Stream,
) -> Parsed<TcpHeader<<Stream as Streaming>::Span>, Stream, Context>
where
(): TcpParse<Stream, Context>,
{
let Success {
token: (source_port, dest_port, sequence_no, ack_no, flags, window, checksum, urgent_pointer),
stream,
} = (
u16_be, u16_be, u32_be, u32_be, tcp_flags, u16_be, u16_be, u16_be,
)
.parse(stream)?;
let Success {
token: options,
stream,
} = any
.drop()
.fold_bounds(
(usize::from(flags.get_data_offset()) - 5) * 4,
|| (),
Acc::acc,
)
.span()
.map(Success::into_stream)
.parse(stream)?;
Parsed::Success {
stream,
token: TcpHeader {
source_port,
dest_port,
sequence_no,
ack_no,
flags,
window,
checksum,
urgent_pointer,
options,
},
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Sack {
A([u32; 2]),
B([u32; 4]),
C([u32; 6]),
D([u32; 8]),
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TcpOption<Span> {
EndOfOption,
Noop,
MaximumSegmentSize(u16),
WindowScale(u8),
SackPermitted,
Sack(Sack),
Timestamps((u32, u32)),
Unknown((u8, Span)),
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(level = "trace", skip_all, ret(Display))
)]
fn noop<Stream, Context>(
stream: Stream,
) -> Parsed<TcpOption<<Stream as Streaming>::Span>, Stream, Context>
where
(): TcpParse<Stream, Context>,
{
Parsed::Success {
token: TcpOption::Noop,
stream,
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(level = "trace", skip_all, ret(Display))
)]
fn mss<Stream, Context>(
stream: Stream,
) -> Parsed<TcpOption<<Stream as Streaming>::Span>, Stream, Context>
where
(): TcpParse<Stream, Context>,
{
is(4)
.add_atom(|| TcpAtom::MssLen)
.drop_and(u16_be)
.map(TcpOption::MaximumSegmentSize)
.parse(stream)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(level = "trace", skip_all, ret(Display))
)]
fn window_scale<Stream, Context>(
stream: Stream,
) -> Parsed<TcpOption<<Stream as Streaming>::Span>, Stream, Context>
where
(): TcpParse<Stream, Context>,
{
is(3)
.add_atom(|| TcpAtom::WindowScaleLen)
.drop_and(octet)
.map(TcpOption::WindowScale)
.parse(stream)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(level = "trace", skip_all, ret(Display))
)]
fn sack_permitted<Stream, Context>(
stream: Stream,
) -> Parsed<TcpOption<<Stream as Streaming>::Span>, Stream, Context>
where
(): TcpParse<Stream, Context>,
{
is(2)
.add_atom(|| TcpAtom::SackPermittedLen)
.map(|_| TcpOption::SackPermitted)
.parse(stream)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(level = "trace", skip_all, ret(Display))
)]
fn sack<Stream, Context>(
stream: Stream,
) -> Parsed<TcpOption<<Stream as Streaming>::Span>, Stream, Context>
where
(): TcpParse<Stream, Context>,
{
octet
.and_then(|len| {
move |stream: Stream| match len {
10 => u32_be.fill().map(Sack::A).parse(stream),
18 => u32_be.fill().map(Sack::B).parse(stream),
26 => u32_be.fill().map(Sack::C).parse(stream),
34 => u32_be.fill().map(Sack::D).parse(stream),
len => Parsed::Failure(Context::new(TcpAtom::SackLen(len))),
}
})
.map(TcpOption::Sack)
.parse(stream)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(level = "trace", skip_all, ret(Display))
)]
fn tipestamps<Stream, Context>(
stream: Stream,
) -> Parsed<TcpOption<<Stream as Streaming>::Span>, Stream, Context>
where
(): TcpParse<Stream, Context>,
{
is(10)
.add_atom(|| TcpAtom::TimestampsLen)
.drop_and((u32_be, u32_be))
.map(TcpOption::Timestamps)
.parse(stream)
}
struct Unknown {
op: u8,
}
fn unknown<Stream, Context>(
op: u8,
) -> impl Parse<Stream, Context, Token = TcpOption<<Stream as Streaming>::Span>>
where
(): TcpParse<Stream, Context>,
{
Unknown { op }
}
impl<Stream, Context> Parse<Stream, Context> for Unknown
where
(): TcpParse<Stream, Context>,
{
type Token = TcpOption<<Stream as Streaming>::Span>;
#[cfg_attr(
feature = "tracing",
tracing::instrument(level = "trace", name = "unknown", skip_all, ret(Display))
)]
fn parse(
&mut self, stream: Stream,
) -> Parsed<TcpOption<<Stream as Streaming>::Span>, Stream, Context> {
octet
.and_then(|len| {
any
.drop()
.fold_bounds(usize::from(len), || (), Acc::acc)
.span()
})
.map(|span| TcpOption::Unknown((self.op, span.stream)))
.parse(stream)
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(level = "trace", skip_all, ret(Display))
)]
fn tcp_option<Stream, Context>(
stream: Stream,
) -> Parsed<TcpOption<<Stream as Streaming>::Span>, Stream, Context>
where
(): TcpParse<Stream, Context>,
{
octet
.and_then(|op| {
move |stream| match op {
0 => Parsed::Success {
token: TcpOption::EndOfOption,
stream,
},
1 => noop.parse(stream),
2 => mss.parse(stream),
3 => window_scale.parse(stream),
4 => sack_permitted.parse(stream),
5 => sack.parse(stream),
8 => tipestamps.parse(stream),
op => unknown(op).parse(stream),
}
})
.parse(stream)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(level = "trace", skip_all, ret(Display))
)]
pub fn tcp_options<Stream, Context>(
stream: Stream,
) -> Parsed<Vec<TcpOption<<Stream as Streaming>::Span>>, Stream, Context>
where
(): TcpParse<Stream, Context>,
{
tcp_option.fold_bounds(.., Vec::new, Acc::acc).parse(stream)
}
#[cfg(test)]
mod tests {
use core::fmt::Debug;
use binator::{
base::{
BaseAtom,
IntRadixAtom,
},
context::Tree,
utils::UtilsAtom,
CoreAtom,
Parse,
Parsed,
Streaming,
};
use derive_more::{
Display,
From,
};
use pretty_assertions::assert_eq;
use test_log::test;
use crate::{
tcp_header,
TcpAtom,
TcpFlags,
TcpHeader,
};
#[derive(Display, Debug, Clone, PartialEq, From)]
enum FromAtom<
Stream: Streaming + Debug,
Item: 'static = <Stream as Streaming>::Item,
Error = <Stream as Streaming>::Error,
> {
Core(CoreAtom<Stream, Error>),
Utils(UtilsAtom<Stream>),
Base(BaseAtom<Item>),
U8Radix(IntRadixAtom<u8>),
U16Radix(IntRadixAtom<u16>),
Tcp(TcpAtom),
}
type HandleAtom<Stream> = Tree<FromAtom<Stream>>;
#[test]
fn test_tcp_parse() {
let stream = [
0xC2, 0x1F, 0x00, 0x50, 0x0F, 0xD8, 0x7F, 0x4C, 0xEB, 0x2F, 0x05, 0xC8, 0x50, 0x18, 0x01,
0x00, 0x7C, 0x29, 0x00, 0x00,
];
let mut flags = TcpFlags::default();
flags.set_ack(true);
flags.set_psh(true);
flags.set_data_offset(5).unwrap();
let expect = TcpHeader {
source_port: 49695,
dest_port: 80,
sequence_no: 0x0FD87F4C,
ack_no: 0xEB2F05C8,
flags,
window: 256,
checksum: 0x7C29,
urgent_pointer: 0,
options: "".as_bytes(),
};
let result: Parsed<_, _, HandleAtom<_>> = tcp_header.parse(stream.as_slice());
let expected = Parsed::Success {
token: expect,
stream: "".as_bytes(),
};
assert_eq!(result, expected);
}
}