use nom::{IResult, Needed, Err, be_u8};
use nom::Endianness::Big;
const END_OF_OPTIONS: u8 = 0;
const NO_OP: u8 = 1;
const MSS: u8 = 2;
const WINDOW_SCALE: u8 = 3;
const SACK_PERMITTED: u8 = 4;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "derive", derive(Serialize, Deserialize))]
pub enum TcpOption {
EndOfOptions,
NoOperation,
MaximumSegmentSize(MaximumSegmentSize),
WindowScale(WindowScale),
SackPermitted,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "derive", derive(Serialize, Deserialize))]
pub struct MaximumSegmentSize {
pub mss: u16,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "derive", derive(Serialize, Deserialize))]
pub struct WindowScale {
pub scaling: u8,
}
#[cfg_attr(feature = "derive", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Eq, Default)]
pub struct TcpHeader {
pub source_port: u16,
pub dest_port: u16,
pub sequence_no: u32,
pub ack_no: u32,
pub data_offset: u8,
pub reserved: u8,
pub flag_urg: bool,
pub flag_ack: bool,
pub flag_psh: bool,
pub flag_rst: bool,
pub flag_syn: bool,
pub flag_fin: bool,
pub window: u16,
pub checksum: u16,
pub urgent_pointer: u16,
pub options: Option<Vec<TcpOption>>,
}
named!(dataof_res_flags<&[u8], (u8, u8, u8)>,
bits!(tuple!(
take_bits!(u8, 4),
take_bits!(u8, 6),
take_bits!(u8, 6))));
named!(tcp_parse<&[u8], TcpHeader>,
do_parse!(src: u16!(Big) >>
dst: u16!(Big) >>
seq: u32!(Big) >>
ack: u32!(Big) >>
dataof_res_flags : dataof_res_flags >>
window : u16!(Big) >>
checksum : u16!(Big) >>
urgent_ptr : u16!(Big) >>
({
TcpHeader {
source_port: src,
dest_port : dst,
sequence_no : seq,
ack_no : ack,
data_offset : dataof_res_flags.0,
reserved : dataof_res_flags.1,
flag_urg : dataof_res_flags.2 & 0b10_0000 == 0b10_0000,
flag_ack : dataof_res_flags.2 & 0b01_0000 == 0b01_0000,
flag_psh : dataof_res_flags.2 & 0b00_1000 == 0b00_1000,
flag_rst : dataof_res_flags.2 & 0b00_0100 == 0b00_0100,
flag_syn : dataof_res_flags.2 & 0b00_0010 == 0b00_0010,
flag_fin : dataof_res_flags.2 & 0b00_0001 == 0b00_0001,
window : window,
checksum : checksum,
urgent_pointer : urgent_ptr,
options : None
}})));
named!(tcp_parse_option<&[u8], TcpOption>,
switch!(be_u8,
END_OF_OPTIONS => do_parse!(take!(0) >>
(TcpOption::EndOfOptions))
| NO_OP => do_parse!(take!(0) >>
(TcpOption::NoOperation))
| MSS => do_parse!(_len: be_u8 >>
mss: u16!(Big) >>
(TcpOption::MaximumSegmentSize(MaximumSegmentSize{mss: mss})))
| WINDOW_SCALE => do_parse!(_len: be_u8 >>
scaling: be_u8 >>
(TcpOption::WindowScale(WindowScale{scaling: scaling})))
| SACK_PERMITTED => do_parse!(_len: be_u8 >>
(TcpOption::SackPermitted))
));
fn tcp_parse_options(i: &[u8]) -> IResult<&[u8], Vec<TcpOption>> {
let mut left = i;
let mut options: Vec<TcpOption> = vec![];
loop {
match tcp_parse_option(left) {
Ok((l, opt)) => {
left = l;
options.push(opt);
if let TcpOption::EndOfOptions = opt {
break;
}
},
Err(e) => return Err(e),
}
}
Ok((left, options))
}
pub fn parse_tcp_header(i: &[u8]) -> IResult<&[u8], TcpHeader> {
match tcp_parse(i) {
Ok((left, mut tcp_header)) => {
if tcp_header.data_offset > 5 {
let options_length = ((tcp_header.data_offset - 5) * 4) as usize;
if options_length <= left.len() {
if let Ok((_, options)) = tcp_parse_options(&left[0..options_length]) {
tcp_header.options = Some(options);
return Ok((&left[options_length..], tcp_header));
}
Ok((&left[options_length..], tcp_header))
} else {
Err(Err::Incomplete(Needed::Size(options_length - left.len())))
}
} else {
Ok((left, tcp_header))
}
}
e => e,
}
}
#[cfg(test)]
mod tests {
use super::*;
const EMPTY_SLICE: &'static [u8] = &[];
#[test]
fn test_tcp_parse() {
let bytes = [0xc2, 0x1f,
0x00, 0x50,
0x0f, 0xd8, 0x7f, 0x4c,
0xeb, 0x2f, 0x05, 0xc8,
0x50, 0x18, 0x01, 0x00,
0x7c, 0x29,
0x00, 0x00 ];
let expectation = TcpHeader {
source_port: 49695,
dest_port: 80,
sequence_no: 0x0fd87f4c,
ack_no: 0xeb2f05c8,
data_offset: 5,
reserved: 0,
flag_urg: false,
flag_ack: true,
flag_psh: true,
flag_rst: false,
flag_syn: false,
flag_fin: false,
window: 256,
checksum: 0x7c29,
urgent_pointer: 0,
options: None,
};
assert_eq!(parse_tcp_header(&bytes), Ok((EMPTY_SLICE, expectation)));
}
}