From 7424e65ddf489d6f8ecfea09c4cd5f412d06d664 Mon Sep 17 00:00:00 2001 From: Antoine PLASKOWSKI Date: Thu, 14 Jul 2022 07:00:07 +0200 Subject: [PATCH] 0.0.0 --- .gitignore | 4 + Cargo.toml | 49 ++++ license.md | 11 + readme.md | 1 + rustfmt.toml | 18 ++ src/ether_type.rs | 155 ++++++++++++ src/ethernet.rs | 143 +++++++++++ src/ip_addr.rs | 596 +++++++++++++++++++++++++++++++++++++++++++++ src/ip_protocol.rs | 339 ++++++++++++++++++++++++++ src/ipv4.rs | 315 ++++++++++++++++++++++++ src/ipv6.rs | 219 +++++++++++++++++ src/lib.rs | 232 ++++++++++++++++++ src/tcp.rs | 581 +++++++++++++++++++++++++++++++++++++++++++ src/udp.rs | 87 +++++++ 14 files changed, 2750 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 license.md create mode 100644 readme.md create mode 100755 rustfmt.toml create mode 100755 src/ether_type.rs create mode 100644 src/ethernet.rs create mode 100644 src/ip_addr.rs create mode 100644 src/ip_protocol.rs create mode 100644 src/ipv4.rs create mode 100644 src/ipv6.rs create mode 100644 src/lib.rs create mode 100644 src/tcp.rs create mode 100644 src/udp.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b0b57d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +target/ +Cargo.lock +.DS_Store +.vscode diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3cfe935 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "binator_network" +authors = ["Stargateur"] +version = "0.0.0" +description = "binator network" +license = "Zlib" +repository = "https://github.com/binator/network" +readme = "readme.md" +keywords = ["parsing", "combinator", "network"] +categories = ["parsing"] +edition = "2021" + +include = [ + "readme.md", + "license.md", + "Cargo.toml", + "rustfmt.toml", + "src/**/*.rs", +] + +[dependencies] +binator_core = "0.0.2" +binator_utils = "0.0.0" +binator_base = "0.0.0" +binator_context = "0.0.0" +binator_number = "0.0.0" +serde = { version = "1.0", optional = true, features = ["derive"] } +const_format = { version = "0.2", features = ["const_generics"] } +paste = "1" + +[dependencies.tracing] +version = "0.1" +optional = true + +[dev-dependencies] +pretty_assertions = "1" +derive-new = "0.5" +derive_more = "0.99" + +binator_core = { version = "0.0.2", features = ["tracing"]} +binator_utils = { version = "0.0.0", features = ["tracing"]} +binator_base = { version = "0.0.0", features = ["tracing"]} +binator_context = { version = "0.0.0", features = ["tracing"]} +binator_number = { version = "0.0.0", features = ["tracing"]} + +tracing = "0.1" +tracing-subscriber = {version = "0.3", features = ["env-filter", "fmt"]} +env_logger = "0.9" +test-log = { version = "0.2.10", features = ["trace"] } \ No newline at end of file diff --git a/license.md b/license.md new file mode 100644 index 0000000..7df444e --- /dev/null +++ b/license.md @@ -0,0 +1,11 @@ +[zlib License](https://choosealicense.com/licenses/zlib/) + +(C) 2022-2024 Stargateur + +This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..2c14420 --- /dev/null +++ b/readme.md @@ -0,0 +1 @@ +Part of this work have been inspired by [pktparse](https://crates.io/crates/pktparse) that is the nom version. It was used to test binator but still provide some basic network parsing. \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100755 index 0000000..b712aa4 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,18 @@ +condense_wildcard_suffixes = true +fn_params_layout = "Compressed" +format_code_in_doc_comments = true +format_macro_matchers = true +format_strings = true +hex_literal_case = "Upper" +imports_layout = "Vertical" +merge_derives = false +imports_granularity = "Crate" +newline_style = "Unix" +normalize_comments = true +reorder_impl_items = true +group_imports = "StdExternalCrate" +tab_spaces = 2 +use_field_init_shorthand = true +version = "Two" +use_try_shorthand = true +wrap_comments = true diff --git a/src/ether_type.rs b/src/ether_type.rs new file mode 100755 index 0000000..ef9accd --- /dev/null +++ b/src/ether_type.rs @@ -0,0 +1,155 @@ +use binator_base::octet; +use binator_core::{ + Contexting, + CoreAtom, + Parse, + Parsed, + Streaming, +}; +use binator_utils::{ + Utils, + UtilsAtom, +}; + +use crate::struct_variants; + +struct_variants! { + EtherType, ether_type, u16: + /// 802.3 Min data length + LANMIN => 0x002E, + /// 802.3 Max data length + LANMAX => 0x05DC, + /// Internet Protocol version 4 (IPv4) + IPV4 => 0x0800, + /// Address Resolution Protocol (ARP) + ARP => 0x0806, + /// Wake-on-LAN + WOL => 0x0842, + /// IETF TRILL Protocol + TRILL => 0x22F3, + /// DECnet Phase IV + DECNET => 0x6003, + /// Reverse Address Resolution Protocol + RARP => 0x8035, + /// AppleTalk (Ethertalk) + APPLE_TALK => 0x809B, + /// AppleTalk Address Resolution Protocol (AARP) + AARP => 0x80F3, + /// VLAN-tagged frame (IEEE 802.1Q) and Shortest Path Bridging IEEE 802.1aq + VLAN => 0x8100, + /// IPX + IPX => 0x8137, + /// QNX Qnet + QNET => 0x8204, + /// Internet Protocol Version 6 (IPv6) + IPV6 => 0x86DD, + /// Ethernet flow control + FLOW_CONTROL => 0x8808, + /// CobraNet + COBRA_NET => 0x8819, + /// MPLS unicast + MPLS_UNI => 0x8847, + /// MPLS multicast + MPLS_MUTLI => 0x8848, + /// PPPoE Discovery Stage + PPPOE_DISCOVERY => 0x8863, + /// PPPoE Session Stage + PPPOE_SESSION => 0x8864, + /// HomePlug 1.0 MME + HOME_PLUG => 0x887B, + /// EAP over LAN (IEEE 802.1X) + EAPOL => 0x888E, + /// PROFINET Protocol + PROFINET => 0x8892, + /// HyperSCSI (SCSI over Ethernet) + HYPER_SCSI => 0x889A, + /// ATA over Ethernet + ATAOE => 0x88A2, + /// EtherCAT Protocol + ETHER_CAT => 0x88A4, + /// Provider Bridging (IEEE 802.1ad) & Shortest Path Bridging IEEE 802.1aq + QINQ => 0x88A8, + /// Ethernet Powerlink + POWER_LINK => 0x88AB, + /// GOOSE (Generic Object Oriented Substation event) + GOOSE => 0x88B8, + /// GSE (Generic Substation Events) Management Services + GSE => 0x88B9, + /// Link Layer Discovery Protocol (LLDP) + LLDP => 0x88CC, + /// SERCOS III + SERCOS => 0x88CD, + /// HomePlug AV MME + HOME_PLUG_AV => 0x88E1, + /// Media Redundancy Protocol (IEC62439-2) + MRP => 0x88E3, + /// MAC security (IEEE 802.1AE) + MAC_SEC => 0x88E5, + /// Provider Backbone Bridges (PBB) (IEEE 802.1ah) + PBB => 0x88E7, + /// Precision Time Protocol (PTP) over Ethernet (IEEE 1588) + PTP => 0x88F7, + /// Parallel Redundancy Protocol (PRP) + PRP => 0x88FB, + /// IEEE 802.1ag Connectivity Fault Management (CFM) Protocol / ITU-T Recommendation Y.1731 (OAM) + CFM => 0x8902, + /// Fibre Channel over Ethernet (FCoE) + FCOE => 0x8906, + /// FCoE Initialization Protocol + FCOEI => 0x8914, + /// RDMA over Converged Ethernet (RoCE) + ROCE => 0x8915, + /// TTEthernet Protocol Control Frame (TTE) + TTE => 0x891D, + /// High-availability Seamless Redundancy (HSR) + HSR => 0x892F, + /// Ethernet Configuration Testing Protocol + CTP => 0x9000, + /// VLAN-tagged (IEEE 802.1Q) frame with double tagging + VLAN_DOUBLE => 0x9100, + /// Veritas Low Latency Transport (LLT) + LLT => 0xCAFE, +} + +pub(crate) fn ether_type(stream: Stream) -> Parsed +where + Stream: Clone + Eq, + Stream: Streaming, + Context: Contexting>, + Context: Contexting>, + Stream::Item: Into, +{ + octet + .fill() + .map(u16::from_be_bytes) + .map(EtherType::new) + .parse(stream) +} + +#[cfg(test)] +mod tests { + use binator_context::Ignore; + use binator_core::Parsed; + + use super::EtherType; + + #[test] + fn ether_type() { + let tests = [ + ([0x08, 0x00], EtherType::IPV4), + ([0x08, 0x06], EtherType::ARP), + ([0x86, 0xDD], EtherType::IPV6), + ([0x81, 0x00], EtherType::VLAN), + ]; + + for (stream, expected) in tests { + assert_eq!( + super::ether_type::<_, Ignore>(&stream[..]), + Parsed::Success { + token: expected, + stream: &[][..], + } + ); + } + } +} diff --git a/src/ethernet.rs b/src/ethernet.rs new file mode 100644 index 0000000..1443eae --- /dev/null +++ b/src/ethernet.rs @@ -0,0 +1,143 @@ +//! Handles parsing of Ethernet headers + +use binator_base::octet; +use binator_core::{ + Contexting, + CoreAtom, + Parse, + Parsed, + Streaming, + Success, +}; +use binator_utils::{ + Utils, + UtilsAtom, +}; + +use crate::ether_type::{ + ether_type, + EtherType, +}; + +/// EthernetFrame +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct EthernetFrame { + /// MAC destination + pub destination: [u8; 6], + /// MAC source + pub source: [u8; 6], + /// EtherType used + pub ether_type: EtherType, + /// TCI + pub tci: Option, +} + +/// Parser that return a ethernet frame on success +/// +pub fn ethernet_frame(stream: Stream) -> Parsed +where + Stream: Clone, + Stream: Eq, + Stream: Streaming, + Stream::Item: Into, + Context: Contexting>, + Context: Contexting>, + Context: Contexting>, +{ + let Success { + token: destination, + stream, + } = octet.fill().parse(stream)?; + let Success { + token: source, + stream, + } = octet.fill().parse(stream)?; + let Success { + token: (ether_type, tci), + stream, + } = ether_type + .and_then(|tmp_ether_type| { + move |stream: Stream| { + if tmp_ether_type == EtherType::VLAN { + let Success { token: tci, stream } = + octet.fill().map(u16::from_be_bytes).parse(stream)?; + let Success { + token: ether_type, + stream, + } = ether_type.parse(stream)?; + + Parsed::Success { + token: (ether_type, Some(tci)), + stream, + } + } else { + Parsed::Success { + token: (tmp_ether_type, None), + stream, + } + } + } + }) + .parse(stream)?; + + Parsed::Success { + token: EthernetFrame { + destination, + source, + ether_type, + tci, + }, + stream, + } +} + +#[cfg(test)] +mod tests { + use binator_context::Ignore; + use binator_core::Parsed; + + use super::{ + EtherType, + EthernetFrame, + }; + + #[test] + fn ethernet_frame() { + let tests = [ + ( + &[ + 0x00, 0x23, 0x54, 0x07, 0x93, 0x6C, 0x00, 0x1B, 0x21, 0x0F, 0x91, 0x9B, 0x08, 0x00, + ][..], + EthernetFrame { + destination: [0x00, 0x23, 0x54, 0x07, 0x93, 0x6C], + source: [0x00, 0x1B, 0x21, 0x0F, 0x91, 0x9B], + ether_type: EtherType::IPV4, + tci: None, + }, + ), + ( + &[ + 0x00, 0x23, 0x54, 0x07, 0x93, 0x6C, 0x00, 0x1B, 0x21, 0x0F, 0x91, 0x9B, 0x81, 0x00, 0x04, + 0xD2, 0x08, 0x00, + ][..], + EthernetFrame { + destination: [0x00, 0x23, 0x54, 0x07, 0x93, 0x6C], + source: [0x00, 0x1B, 0x21, 0x0F, 0x91, 0x9B], + ether_type: EtherType::IPV4, + tci: Some(1234), + }, + ), + ]; + + for (stream, expected) in tests { + assert_eq!( + super::ethernet_frame::<_, Ignore>(stream), + Parsed::Success { + token: expected, + stream: b"".as_slice(), + } + ); + } + } +} diff --git a/src/ip_addr.rs b/src/ip_addr.rs new file mode 100644 index 0000000..cc07498 --- /dev/null +++ b/src/ip_addr.rs @@ -0,0 +1,596 @@ +use core::fmt::{ + self, + Display, + Formatter, +}; +use std::net::{ + Ipv4Addr, + Ipv6Addr, +}; + +use binator_base::*; +use binator_core::*; +use binator_number::*; +use binator_utils::*; + +/// Atom of ip_addr parser +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum IpAddrAtom { + /// When value in IPv4 would overflow an octet (u8) + NotAnOctet, + /// When value in IPv4 have leading zero + LeadingZero, +} + +impl Display for IpAddrAtom { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::NotAnOctet => write!(f, "IpAddr: NotAnOctet"), + Self::LeadingZero => write!(f, "IpAddr: LeadingZero"), + } + } +} + +/// Meta trait for ip_addr combinator +pub trait IpAddrParse = where + Stream: Streaming, + ::Item: Into + Clone, + ::Item: PartialEq<::Item>, + Context: Contexting>, + Context: Contexting>, + Context: Contexting>, + Context: Contexting>, + Context: Contexting>, + Context: Contexting, + Context: Contexting>; + +/// IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet +pub fn ipv4_address(stream: Stream) -> Parsed +where + (): IpAddrParse, +{ + ( + dec_octet, + is(b'.'), + dec_octet, + is(b'.'), + dec_octet, + is(b'.'), + dec_octet, + ) + .map(|(a, _, b, _, c, _, d)| Ipv4Addr::new(a, b, c, d)) + .parse(stream) +} + +// dec-octet = DIGIT ; 0-9 +// / %x31-39 DIGIT ; 10-99 +// / "1" 2DIGIT ; 100-199 +// / "2" %x30-34 DIGIT ; 200-249 +// / "25" %x30-35 ; 250-255 +fn dec_octet(stream: Stream) -> Parsed +where + (): IpAddrParse, +{ + [ + dec_octet_0, + dec_octet_1, + dec_octet_2, + dec_octet_3, + dec_octet_4, + ] + .parse(stream) +} + +fn dec_octet_0(stream: Stream) -> Parsed +where + (): IpAddrParse, +{ + (is(b'2'), is(b'5'), to_digit) + .try_map(|(_, _, c)| { + 250u8 + .checked_add(u8::from(c)) + .ok_or_else(|| Context::new(IpAddrAtom::NotAnOctet)) + }) + .parse(stream) +} + +fn dec_octet_1(stream: Stream) -> Parsed +where + (): IpAddrParse, +{ + (is(b'2'), to_digit, to_digit) + .try_map(|(_, b, c)| { + 200u8 + .checked_add(u8::from(b) * 10 + u8::from(c)) + .ok_or_else(|| Context::new(IpAddrAtom::NotAnOctet)) + }) + .parse(stream) +} + +fn dec_octet_2(stream: Stream) -> Parsed +where + (): IpAddrParse, +{ + (is(b'1'), to_digit, to_digit) + .try_map(|(_, b, c)| { + 100u8 + .checked_add(u8::from(b) * 10 + u8::from(c)) + .ok_or_else(|| Context::new(IpAddrAtom::NotAnOctet)) + }) + .parse(stream) +} + +fn dec_octet_3(stream: Stream) -> Parsed +where + (): IpAddrParse, +{ + (to_digit, to_digit) + .try_map(|(a, b)| { + let a = u8::from(a); + let b = u8::from(b); + if a == 0 { + Err(Context::new(IpAddrAtom::LeadingZero)) + } else { + Ok(a * 10 + b) + } + }) + .parse(stream) +} + +fn dec_octet_4(stream: Stream) -> Parsed +where + (): IpAddrParse, +{ + to_digit.map(u8::from).parse(stream) +} + +/// Ipv6Reference +#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash, PartialOrd, Ord)] +pub struct Ipv6Reference { + /// ipv6 + pub ipv6: Ipv6Addr, +} + +impl Display for Ipv6Reference { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "[{}]", self.ipv6) + } +} + +/// IPv6reference = "[" IPv6address "]" +pub fn ipv6_reference(stream: Stream) -> Parsed +where + (): IpAddrParse, +{ + (is(b'['), ipv6_address, is(b']')) + .map(|(_, ipv6, _)| Ipv6Reference { ipv6 }) + .parse(stream) +} + +#[allow(rustdoc::private_intra_doc_links)] +/// IPv6address = 6( h16 ":" ) ls32 +/// / "::" 5( h16 ":" ) ls32 +/// / [ h16 ] "::" 4( h16 ":" ) ls32 +/// / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 +/// / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 +/// / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 +/// / [ *4( h16 ":" ) h16 ] "::" ls32 +/// / [ *5( h16 ":" ) h16 ] "::" h16 +/// / [ *6( h16 ":" ) h16 ] "::" +pub fn ipv6_address(stream: Stream) -> Parsed +where + (): IpAddrParse, +{ + [ + ipv6_address_0, + ipv6_address_1, + ipv6_address_2, + ipv6_address_3, + ipv6_address_4, + ipv6_address_5, + ipv6_address_6, + ipv6_address_7, + ipv6_address_8, + ] + .parse(stream) +} + +fn ipv6_address_0(stream: Stream) -> Parsed +where + (): IpAddrParse, +{ + ( + h16_colon, h16_colon, h16_colon, h16_colon, h16_colon, h16_colon, ls32, + ) + .map(|(a, b, c, d, e, f, (g, h))| Ipv6Addr::new(a, b, c, d, e, f, g, h)) + .parse(stream) +} + +fn ipv6_address_1(stream: Stream) -> Parsed +where + (): IpAddrParse, +{ + ( + double_colon, + h16_colon, + h16_colon, + h16_colon, + h16_colon, + h16_colon, + ls32, + ) + .map(|(_, b, c, d, e, f, (g, h))| Ipv6Addr::new(0, b, c, d, e, f, g, h)) + .parse(stream) +} + +fn ipv6_address_2(stream: Stream) -> Parsed +where + (): IpAddrParse, +{ + ( + h16.opt(), + double_colon, + h16_colon, + h16_colon, + h16_colon, + h16_colon, + ls32, + ) + .map(|(a, _, c, d, e, f, (g, h))| Ipv6Addr::new(a.unwrap_or(0), 0, c, d, e, f, g, h)) + .parse(stream) +} + +fn ipv6_address_3(stream: Stream) -> Parsed +where + (): IpAddrParse, +{ + ( + h16.opt(), + colon_h16.opt(), + double_colon, + h16_colon, + h16_colon, + h16_colon, + ls32, + ) + .map(|(a, b, _, d, e, f, (g, h))| { + Ipv6Addr::new(a.unwrap_or(0), b.unwrap_or(0), 0, d, e, f, g, h) + }) + .parse(stream) +} + +fn ipv6_address_4(stream: Stream) -> Parsed +where + (): IpAddrParse, +{ + ( + h16.opt(), + colon_h16.opt(), + colon_h16.opt(), + double_colon, + h16_colon, + h16_colon, + ls32, + ) + .map(|(a, b, c, _, e, f, (g, h))| { + Ipv6Addr::new( + a.unwrap_or(0), + b.unwrap_or(0), + c.unwrap_or(0), + 0, + e, + f, + g, + h, + ) + }) + .parse(stream) +} + +fn ipv6_address_5(stream: Stream) -> Parsed +where + (): IpAddrParse, +{ + ( + h16.opt(), + colon_h16.opt(), + colon_h16.opt(), + colon_h16.opt(), + double_colon, + h16_colon, + ls32, + ) + .map(|(a, b, c, d, _, f, (g, h))| { + Ipv6Addr::new( + a.unwrap_or(0), + b.unwrap_or(0), + c.unwrap_or(0), + d.unwrap_or(0), + 0, + f, + g, + h, + ) + }) + .parse(stream) +} + +fn ipv6_address_6(stream: Stream) -> Parsed +where + (): IpAddrParse, +{ + ( + h16.opt(), + colon_h16.opt(), + colon_h16.opt(), + colon_h16.opt(), + colon_h16.opt(), + double_colon, + ls32, + ) + .map(|(a, b, c, d, e, _, (g, h))| { + Ipv6Addr::new( + a.unwrap_or(0), + b.unwrap_or(0), + c.unwrap_or(0), + d.unwrap_or(0), + e.unwrap_or(0), + 0, + g, + h, + ) + }) + .parse(stream) +} + +fn ipv6_address_7(stream: Stream) -> Parsed +where + (): IpAddrParse, +{ + ( + h16.opt(), + colon_h16.opt(), + colon_h16.opt(), + colon_h16.opt(), + colon_h16.opt(), + colon_h16.opt(), + double_colon, + h16, + ) + .map(|(a, b, c, d, e, f, _, h)| { + Ipv6Addr::new( + a.unwrap_or(0), + b.unwrap_or(0), + c.unwrap_or(0), + d.unwrap_or(0), + e.unwrap_or(0), + f.unwrap_or(0), + 0, + h, + ) + }) + .parse(stream) +} + +fn ipv6_address_8(stream: Stream) -> Parsed +where + (): IpAddrParse, +{ + ( + h16.opt(), + colon_h16.opt(), + colon_h16.opt(), + colon_h16.opt(), + colon_h16.opt(), + colon_h16.opt(), + colon_h16.opt(), + double_colon, + ) + .map(|(a, b, c, d, e, f, g, _)| { + Ipv6Addr::new( + a.unwrap_or(0), + b.unwrap_or(0), + c.unwrap_or(0), + d.unwrap_or(0), + e.unwrap_or(0), + f.unwrap_or(0), + g.unwrap_or(0), + 0, + ) + }) + .parse(stream) +} + +// custom rule +// h16colon = h16 ":" +fn h16_colon(stream: Stream) -> Parsed +where + (): IpAddrParse, +{ + h16.and_drop(is(b':')).parse(stream) +} + +fn colon_h16(stream: Stream) -> Parsed +where + (): IpAddrParse, +{ + is(b':').drop_and(h16).parse(stream) +} + +fn double_colon(stream: Stream) -> Parsed<(), Stream, Context> +where + (): IpAddrParse, +{ + is(b':').and(is(b':')).drop().parse(stream) +} + +// // ls32 = ( h16 ":" h16 ) / IPv4address +fn ls32(stream: Stream) -> Parsed<(u16, u16), Stream, Context> +where + (): IpAddrParse, +{ + h16 + .and(colon_h16) + .or(ipv4_address.map(|ipv4| { + let [a, b, c, d] = ipv4.octets(); + ((a as u16) << 8 | (b as u16), (c as u16) << 8 | (d as u16)) + })) + .parse(stream) +} + +// h16 = 1*4HEXDIG +fn h16(stream: Stream) -> Parsed +where + (): IpAddrParse, +{ + uint_radix(1..4, Radix::HEX).parse(stream) +} + +#[cfg(test)] +mod tests { + use core::{ + fmt::Debug, + str::FromStr, + }; + + use binator_base::*; + use binator_context::Tree; + use binator_core::*; + use derive_more::{ + Display, + From, + }; + + use super::*; + + #[derive(Display, Debug, Clone, PartialEq, From)] + enum FromAtom::Error> { + Any(CoreAtom), + Is(BaseAtom), + Utils(UtilsAtom), + U8Radix(IntRadixAtom), + U16Radix(IntRadixAtom), + IpAddr(IpAddrAtom), + } + + type HandleAtom = Tree>; + + #[test] + fn test_ipv4_address() { + // 012.15.65.12 0x12.222.012.111 + + let ipv4s = ["", "m", "127.0.0.1", "256.256.256.256", "255.255.255.255"]; + + for ipv4 in ipv4s.iter() { + println!("test: {}", ipv4); + match ( + ipv4_address::<_, HandleAtom<_>>(ipv4.as_bytes()), + Ipv4Addr::from_str(ipv4), + ) { + (Parsed::Success { token, stream }, Ok(expected)) => { + assert_eq!((token, stream), (expected, b"".as_slice())) + } + (Parsed::Failure(_) | Parsed::Error(_), Err(_)) => {} // no need compare error + (Parsed::Success { token, stream }, Err(e)) => { + panic!("We ok on {:?} {:?} but std error on {}", token, stream, e) + } + (Parsed::Failure(failure) | Parsed::Error(failure), Ok(expected)) => { + panic!("We error on {:?} but std ok on {}", failure, expected) + } + } + } + } + + #[test] + fn test_h16() { + let h16s = [ + "2001", "DB8", "0", "8", "800", "200C", "417A", "FF01", "101", "1", "13", "68", "3", "FFFF", + "129", "144", "52", "38", "0DB8", "CD30", "123", "4567", "89AB", "CDEF", + ]; + + for h in &h16s { + println!("test: {}", h); + match ( + h16::<_, HandleAtom<_>>(h.as_bytes()), + u16::from_str_radix(h, 16), + ) { + (Parsed::Success { token, stream }, Ok(expected)) => { + assert_eq!((token, stream), (expected, b"".as_slice())) + } + (Parsed::Failure(_) | Parsed::Error(_), Err(_)) => {} // no need compare error + (Parsed::Success { token, stream }, Err(e)) => { + panic!("We ok on {:?} {:?} but std error on {}", token, stream, e) + } + (Parsed::Failure(failure) | Parsed::Error(failure), Ok(expected)) => { + panic!("We error on {:?} but std ok on {}", failure, expected) + } + } + } + } + + #[test] + fn test_ipv6_address() { + use std::str::FromStr; + + let ipv6s = [ + "2001:DB8:0:0:8:800:200C:417A", + "2001:DB8::8:800:200C:417A", + "FF01:0:0:0:0:0:0:101", + "FF01::101", + "0:0:0:0:0:0:0:1", + "::1", + "0:0:0:0:0:0:0:0", + "::", + "0:0:0:0:0:0:13.1.68.3", + "::13.1.68.3", + "0:0:0:0:0:FFFF:129.144.52.38", + "::FFFF:129.144.52.38", + "2001:0DB8:0000:CD30:0000:0000:0000:0000", + "2001:0DB8::CD30:0:0:0:0", + "2001:0DB8:0:CD30::", + "2001:0DB8:0:CD30:123:4567:89AB:CDEF", + "2001:db8:0:0:1:0:0:1", + "2001:0db8:0:0:1:0:0:1", + "2001:db8::1:0:0:1", + "2001:db8::0:1:0:0:1", + "2001:0db8::1:0:0:1", + "2001:db8:0:0:1::1", + "2001:db8:0000:0:1::1", + "2001:DB8:0:0:1::1", + "2001:db8:aaaa:bbbb:cccc:dddd:eeee:0001", + "2001:db8:aaaa:bbbb:cccc:dddd:eeee:001", + "2001:db8:aaaa:bbbb:cccc:dddd:eeee:01", + "2001:db8:aaaa:bbbb:cccc:dddd:eeee:1", + "2001:db8:aaaa:bbbb:cccc:dddd::1", + "2001:db8:aaaa:bbbb:cccc:dddd:0:1", + "2001:db8:0:0:0::1", + "2001:db8:0:0::1", + "2001:db8:0::1", + "2001:db8::1", + "2001:db8::aaaa:0:0:1", + "2001:db8:0:0:aaaa::1", + "2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa", + "2001:db8:aaaa:bbbb:cccc:dddd:eeee:AAAA", + "2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa", + ]; + + for &ipv6 in ipv6s.iter() { + println!("test: {}", ipv6); + match ( + ipv6_address::<_, HandleAtom<_>>(ipv6.as_bytes()), + Ipv6Addr::from_str(ipv6), + ) { + (Parsed::Success { token, stream }, Ok(expected)) => { + assert_eq!((token, stream), (expected, "".as_bytes())) + } + (Parsed::Failure(_) | Parsed::Error(_), Err(_)) => {} // no need compare error + (Parsed::Success { token, stream }, Err(e)) => { + panic!("We ok on {:?} {:?} but std error on {}", token, stream, e) + } + (Parsed::Failure(failure) | Parsed::Error(failure), Ok(expected)) => { + panic!("We error on {:?} but std ok on {}", failure, expected) + } + } + } + } +} diff --git a/src/ip_protocol.rs b/src/ip_protocol.rs new file mode 100644 index 0000000..db5bb76 --- /dev/null +++ b/src/ip_protocol.rs @@ -0,0 +1,339 @@ +//! Handles parsing of Internet Protocol fields (shared between ipv4 and ipv6) + +use binator_base::octet; +use binator_core::{ + Contexting, + CoreAtom, + Parse, + Parsed, + Streaming, +}; +use binator_utils::Utils; + +use crate::struct_variants; + +struct_variants! { + IPProtocol, protocol, u8: + /// IPv6 Hop-by-Hop Option + HOPOPT => 0x00, + /// Internet Control Message Protocol + ICMP => 0x01, + /// Internet Group Management Protocol + IGMP => 0x02, + /// Gateway-to-Gateway Protocol + GGP => 0x03, + /// IP in IP + IP_IN_IP => 0x04, + /// Internet Stream Protocol + ST => 0x05, + /// Transmission Control Protocol + TCP => 0x06, + /// Core-based trees + CBT => 0x07, + /// Exterior Gateway Protocol + EGP => 0x08, + /// Interior Gateway Protocol + IGP => 0x09, + /// BBN RCC Monitoring + BBN_RCC_MON => 0x0A, + /// Network Voice Protocol + NVP_II => 0x0B, + /// Xerox PUP + PUP => 0x0C, + /// ARGUS + ARGUS => 0x0D, + /// EMCON + EMCON => 0x0E, + /// Cross Net Debugger + XNET => 0x0F, + /// Chaos + CHAOS => 0x10, + /// User Datagram Protocol + UDP => 0x11, + /// Multiplexing + MUX => 0x12, + /// DCN Measurement Subsystems + DCN_MEAS => 0x13, + /// Host Monitoring Protocol + HMP => 0x14, + /// Packet Radio Measurement + PRM => 0x15, + /// XEROX NS IDP + XNS_IDP => 0x16, + /// Trunk-1 + TRUNK_1 => 0x17, + /// Trunk-2 + TRUNK_2 => 0x18, + /// Leaf-1 + LEAF_1 => 0x19, + /// Leaf-2 + LEAF_2 => 0x1A, + /// Reliable Data Protocol + RDP => 0x1B, + /// Internet Reliable Transaction Protocol + IRTP => 0x1C, + /// ISO Transport Protocol Class 4 + ISO_TP4 => 0x1D, + /// Bulk Data Transfer Protocol + NETBLT => 0x1E, + /// MFE Network Services Protocol + MFE_NSP => 0x1F, + /// MERIT Internodal Protocol + MERIT_INP => 0x20, + /// Datagram Congestion Control Protocol + DCCP => 0x21, + /// Third Party Connect Protocol + PC3 => 0x22, + /// Inter-Domain Policy Routing Protocol + IDPR => 0x23, + /// Xpress Transport Protocol + XTP => 0x24, + /// Datagram Delivery Protocol + DDP => 0x25, + /// IDPR Control Message Transport Protocol + IDPR_CMTP => 0x26, + /// TP++ Transport Protocol + TP => 0x27, + /// IL Transport Protocol + IL => 0x28, + /// IPv6 Encapsulation + IPV6 => 0x29, + /// Source Demand Routing Protocol + SDRP => 0x2A, + /// Routing Header for IPv6 + IPV6_ROUTE => 0x2B, + /// Fragment Header for IPv6 + IPV6_FRAG => 0x2C, + /// Inter-Domain Routing Protocol + IDRP => 0x2D, + /// Resource Reservation Protocol + RSVP => 0x2E, + /// Generic Routing Encapsulation + GRE => 0x2F, + /// Dynamic Source Routing Protocol + DSR => 0x30, + /// Burroughs Network Architecture + BNA => 0x31, + /// Encapsulating Security Payload + ESP => 0x32, + /// Authentication Header + AH => 0x33, + /// Integrated Net Layer Security Protocol + I_NLSP => 0x34, + /// SwIPe + SWIPE => 0x35, + /// NBMA Address Resolution Protocol + NARP => 0x36, + /// IP Mobility (Min Encap) + MOBILE => 0x37, + /// Transport Layer Security Protocol (using Kryptonet key management) + TLSP => 0x38, + /// Simple Key-Management for Internet Protocol + SKIP => 0x39, + /// ICMP for IPv6 + ICMP_6 => 0x3A, + /// No Next Header for IPv6 + NO_NXT_6 => 0x3B, + /// Destination Options for IPv6 + OPTS_6 => 0x3C, + /// Any host internal protocol + AHIP => 0x3D, + /// CFTP + CFTP => 0x3E, + /// Any local network + ALN => 0x3F, + /// SATNET and Backroom EXPAK + SAT_EXPAK => 0x40, + /// Kryptolan + KRYPTOLAN => 0x41, + /// MIT Remote Virtual Disk Protocol + RVD => 0x42, + /// Internet Pluribus Packet Core + IPPC => 0x43, + /// Any distributed file system + ADFS => 0x44, + /// SATNET Monitoring + SAT_MON => 0x45, + /// VISA Protocol + VISA => 0x46, + /// Internet Packet Core Utility + IPCU => 0x47, + /// Computer Protocol Network Executive + CPNX => 0x48, + /// Computer Protocol Heart Beat + CPHB => 0x49, + /// Wang Span Network + WSN => 0x4A, + /// Packet Video Protocol + PVP => 0x4B, + /// Backroom SATNET Monitoring + BR_SAT_MON => 0x4C, + /// SUN ND PROTOCOL-Temporary + SUN_ND => 0x4D, + /// WIDEBAND Monitoring + WB_MON => 0x4E, + /// WIDEBAND EXPAK + WB_EXPAK => 0x4F, + /// International Organization for Standardization Internet Protocol + ISO_IP => 0x50, + /// Versatile Message Transaction Protocol + VMTP => 0x51, + /// Secure Versatile Message Transaction Protocol + SECURE_VMTP => 0x52, + /// VINES + VINES => 0x53, + /// TTP or Internet Protocol Traffic Manager + TTP_OR_IPTM => 0x54, + /// NSFNET-IGP + NSFNET_IGP => 0x55, + /// Dissimilar Gateway Protocol + DGP => 0x56, + /// TCF + TCF => 0x57, + /// EIGRP + EIGRP => 0x58, + /// Open Shortest Path First + OSPF => 0x59, + /// Sprite RPC Protocol + SPRITE_RPC => 0x5A, + /// Locus Address Resolution Protocol + LARP => 0x5B, + /// Multicast Transport Protocol + MTP => 0x5C, + /// AX.25 + AX25 => 0x5D, + /// KA9Q NOS compatible IP over IP tunneling + OS => 0x5E, + /// Mobile Internetworking Control Protocol + MICP => 0x5F, + /// Semaphore Communications Sec. Pro + SCC_SP => 0x60, + /// Ethernet-within-IP Encapsulation + ETHERIP => 0x61, + /// Encapsulation Header + ENCAP => 0x62, + /// Any private encryption scheme + APES => 0x63, + /// GMTP + GMTP => 0x64, + /// Ipsilon Flow Management Protocol + IFMP => 0x65, + /// PNNI over IP + PNNI => 0x66, + /// Protocol Independent Multicast + PIM => 0x67, + /// IBM's ARIS (Aggregate Route IP Switching) Protocol + ARIS => 0x68, + /// SCPS (Space Communications Protocol Standards) + SCPS => 0x69, + /// QNX + QNX => 0x6A, + /// Active Networks + AN => 0x6B, + /// IP Payload Compression Protocol + IP_COMP => 0x6C, + /// Sitara Networks Protocol + SNP => 0x6D, + /// Compaq Peer Protocol + COMPAQ_PEER => 0x6E, + /// IPX in IP + IPX_IN_IP => 0x6F, + /// Virtual Router Redundancy Protocol, Common Address Redundancy Protocol (not IANA assigned) + VRRP => 0x70, + /// PGM Reliable Transport Protocol + PGM => 0x71, + /// Any 0-hop protocol + AHP => 0x72, + /// Layer Two Tunneling Protocol Version 3 + L2TP => 0x73, + /// D-II Data Exchange (DDX) + DDX => 0x74, + /// Interactive Agent Transfer Protocol + IATP => 0x75, + /// Schedule Transfer Protocol + STP => 0x76, + /// SpectraLink Radio Protocol + SRP => 0x77, + /// Universal Transport Interface Protocol + UTI => 0x78, + /// Simple Message Protocol + SMP => 0x79, + /// Simple Multicast Protocol + SM => 0x7A, + /// Performance Transparency Protocol + PTP => 0x7B, + /// IS-IS over IPv4 Intermediate System to Intermediate System (IS-IS) Protocol over IPv4 + IS_IS => 0x7C, + /// Flexible Intra-AS Routing Environment + FIRE => 0x7D, + /// Combat Radio Transport Protocol + CRTP => 0x7E, + /// Combat Radio User Datagram + CRUDP => 0x7F, + /// Service-Specific Connection-Oriented Protocol in a Multilink and Connectionless Environment + SSCOPMCE => 0x80, + /// IPLT + IPLT => 0x81, + /// Secure Packet Shield + SPS => 0x82, + /// Private IP Encapsulation within IP + PIPE => 0x83, + /// Stream Control Transmission Protocol + SCTP => 0x84, + /// Fibre Channel + FC => 0x85, + /// Reservation Protocol (RSVP) End-to-End Ignore + RSVP_IGNORE => 0x86, + /// Mobility Extension Header for IPv6 + MOBILITY_6 => 0x87, + /// Lightweight User Datagram Protocol + UDP_LITE => 0x88, + /// Multiprotocol Label Switching Encapsulated in IP + MPLS_IN_IP => 0x89, + /// MANET Protocols + MANET => 0x8A, + /// Host Identity Protocol + HIP => 0x8B, + /// Site Multihoming by IPv6 Intermediation + SHIM_6 => 0x8C, + /// Wrapped Encapsulating Security Payload + WESP => 0x8D, + /// Robust Header Compression + ROHC => 0x8E, +} + +pub(crate) fn ip_protocol(stream: Stream) -> Parsed +where + Stream: Streaming, + Context: Contexting>, + Stream::Item: Into, +{ + octet.map(IPProtocol::new).parse(stream) +} + +#[cfg(test)] +mod tests { + use binator_context::Ignore; + use binator_core::Parsed; + + use super::IPProtocol; + + #[test] + fn ip_protocol() { + let tests = [ + ([1], IPProtocol::ICMP), + ([6], IPProtocol::TCP), + ([17], IPProtocol::UDP), + ]; + + for (stream, expected) in tests { + assert_eq!( + super::ip_protocol::<_, Ignore>(&stream[..]), + Parsed::Success { + stream: &[][..], + token: expected + } + ); + } + } +} diff --git a/src/ipv4.rs b/src/ipv4.rs new file mode 100644 index 0000000..0e727f8 --- /dev/null +++ b/src/ipv4.rs @@ -0,0 +1,315 @@ +//! Handles parsing of IPv4 headers + +use std::{ + fmt::{ + Display, + Formatter, + }, + net::Ipv4Addr, +}; + +use binator_base::{ + any, + nbit, + octet, + NBit, +}; +use binator_core::{ + Acc, + Contexting, + CoreAtom, + Parse, + Parsed, + Streaming, + Success, +}; +use binator_utils::{ + Utils, + UtilsAtom, +}; + +use crate::ip_protocol::{ + self, + IPProtocol, +}; + +/// +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct IPv4Header { + /// The first header field in an IP packet is the four-bit version field. For + /// IPv4, this is always equal to 4. + pub version: u8, + /// The IPv4 header is variable in size due to the optional 14th field + /// (options). The IHL field contains the size of the IPv4 header; + /// it has 4 bits that specify the number of 32-bit words in the header. + /// The minimum value for this field is 5, which indicates a length of 5 × 32 + /// bits = 160 bits = 20 bytes. As a 4-bit field, the maximum value is 15; + /// this means that the maximum size of the IPv4 header is 15 × 32 bits = 480 + /// bits = 60 bytes. + pub ihl: u8, + /// Originally defined as the type of service (ToS), + /// this field specifies differentiated services (DiffServ) per RFC 2474. + /// Real-time data streaming makes use of the DSCP field. + /// An example is Voice over IP (VoIP), which is used for interactive voice + /// services. + pub tos: u8, + /// This 16-bit field defines the entire packet size in bytes, including + /// header and data. The minimum size is 20 bytes (header without data) and + /// the maximum is 65,535 bytes. All hosts are required to be able to + /// reassemble datagrams of size up to 576 bytes, but most modern hosts handle + /// much larger packets. Links may impose further restrictions on the packet + /// size, in which case datagrams must be fragmented. Fragmentation in IPv4 is + /// performed in either the sending host or in routers. Reassembly is + /// performed at the receiving host. + pub length: u16, + /// This field is an identification field and is primarily used for uniquely + /// identifying the group of fragments of a single IP datagram. Some + /// experimental work has suggested using the ID field for other purposes, + /// such as for adding packet-tracing information to help trace datagrams with + /// spoofed source addresses, but RFC 6864 now prohibits any such use. + pub id: u16, + /// A three-bit field follows and is used to control or identify + /// fragments. They are (in order, from most significant to least + /// significant): + /// + /// bit 0: Reserved; must be zero. + /// bit 1: Don't Fragment (DF) + /// bit 2: More Fragments (MF) + /// + /// If the DF flag is set, and fragmentation is required to route the packet, + /// then the packet is dropped. This can be used when sending packets to a + /// host that does not have resources to perform reassembly of fragments. It + /// can also be used for path MTU discovery, either automatically by the host + /// IP software, or manually using diagnostic tools such as ping or + /// traceroute. For unfragmented packets, the MF flag is cleared. For + /// fragmented packets, all fragments except the last have the MF flag set. + /// The last fragment has a non-zero Fragment Offset field, differentiating it + /// from an unfragmented packet. + pub flags: u8, + /// This field specifies the offset of a particular fragment relative to the + /// beginning of the original unfragmented IP datagram. The fragmentation + /// offset value for the first fragment is always 0. The field is 13 bits + /// wide, so that the offset can be from 0 to 8191 (from (20 –1) to (213 – + /// 1)). Fragments are specified in units of 8 bytes, which is why fragment + /// length must be a multiple of 8. Therefore, the 13-bit field allows a + /// maximum offset of (213 – 1) × 8 = 65,528 bytes, with the header length + /// included (65,528 + 20 = 65,548 bytes), supporting fragmentation of packets + /// exceeding the maximum IP length of 65,535 bytes. + pub fragment_offset: u16, + /// An eight-bit time to live field limits a datagram's lifetime to + /// prevent network failure in the event of a routing loop. It is specified in + /// seconds, but time intervals less than 1 second are rounded up to 1. In + /// practice, the field is used as a hop count—when the datagram arrives at a + /// router, the router decrements the TTL field by one. When the TTL field + /// hits zero, the router discards the packet and typically sends an ICMP time + /// exceeded message to the sender. The program traceroute sends messages + /// with adjusted TTL values and uses these ICMP time exceeded messages to + /// identify the routers traversed by packets from the source to the + /// destination. + pub ttl: u8, + /// This field defines the protocol used in the data portion of the IP + /// datagram. IANA maintains a list of IP protocol numbers as directed by RFC + /// 790. + pub protocol: IPProtocol, + /// The 16-bit IPv4 header checksum field is used for error-checking of + /// the header. When a packet arrives at a router, the router calculates the + /// checksum of the header and compares it to the checksum field. If the + /// values do not match, the router discards the packet. Errors in the data + /// field must be handled by the encapsulated protocol. Both UDP and TCP have + /// separate checksums that apply to their data. When a packet arrives at a + /// router, the router decreases the TTL field in the header. Consequently, + /// the router must calculate a new header checksum. The checksum field is + /// the 16 bit one's complement of the one's complement sum of all 16 bit + /// words in the header. For purposes of computing the checksum, the value of + /// the checksum field is zero. + pub chksum: u16, + /// This 32-bit field is the IPv4 address of the sender of the packet. Note + /// that this address may be changed in transit by a network address + /// translation device. + pub source_addr: Ipv4Addr, + /// This 32-bit field is the IPv4 address of the receiver of the packet. As + /// with the source address, this may be changed in transit by a network + /// address translation device. + pub dest_addr: Ipv4Addr, + /// The options field is not often used. Packets containing some options may + /// be considered as dangerous by some routers and be blocked. Note that + /// the value in the IHL field must include enough extra 32-bit words to hold + /// all the options plus any padding needed to ensure that the header contains + /// an integer number of 32-bit words. If IHL is greater than 5 (i.e., it is + /// from 6 to 15) it means that the options field is present and must be + /// considered. The list of options may be terminated with an EOOL (End of + /// Options List, 0x00) option; this is only necessary if the end of the + /// options would not otherwise coincide with the end of the header. + pub options: Span, +} + +/// Ipv4 failure cause +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Ipv4Atom { + /// When version is not 4 + Version(u8), + /// When IHL is less than 5 + IHL(u8), +} + +impl Display for Ipv4Atom { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Ipv4Atom::Version(version) => { + write!(f, "Ipv4Context: Version field is not 4 found {}", version) + } + Ipv4Atom::IHL(ihl) => { + write!(f, "Ipv4Context: IHL field is less than 5 found {}", ihl) + } + } + } +} + +/// Parse ipv4 header. +pub fn ipv4_header( + stream: Stream, +) -> Parsed, Stream, Context> +where + Stream: Eq, + Stream: Streaming, + Stream::Item: Into, + Context: Contexting>, + Context: Contexting>, + Context: Contexting>, + Context: Contexting>, + Context: Contexting, +{ + let Success { + token: (version, ihl), + stream, + } = nbit(NBit::FOUR) + .try_map(|(version, ihl)| { + if version != 4 { + Err(Context::new(Ipv4Atom::Version(version))) + } else if ihl < 5 { + Err(Context::new(Ipv4Atom::IHL(ihl))) + } else { + Ok((version, ihl)) + } + }) + .parse(stream)?; + + let Success { token: tos, stream } = octet.parse(stream)?; + + let Success { + token: length, + stream, + } = octet.fill().map(u16::from_be_bytes).parse(stream)?; + + let Success { token: id, stream } = octet.fill().map(u16::from_be_bytes).parse(stream)?; + + let Success { + token: (flags, fragment_offset), + stream, + } = nbit(NBit::FIVE) + .and(octet) + .map(|((flags, fragment_offset_0), fragment_offset_1)| { + ( + flags, + u16::from_be_bytes([fragment_offset_0, fragment_offset_1]), + ) + }) + .parse(stream)?; + + let Success { token: ttl, stream } = octet.parse(stream)?; + + let Success { + token: protocol, + stream, + } = ip_protocol::ip_protocol.parse(stream)?; + + let Success { + token: chksum, + stream, + } = octet.fill().map(u16::from_be_bytes).parse(stream)?; + + let Success { + token: source_addr, + stream, + } = octet.fill().map(Ipv4Addr::from).parse(stream)?; + + let Success { + token: dest_addr, + stream, + } = octet.fill().map(Ipv4Addr::from).parse(stream)?; + + let Success { + token: Success { + stream: options, .. + }, + stream, + } = any + .drop() + .fold_bounds(usize::from(ihl - 5) * 4, || (), Acc::acc) + .span() + .parse(stream)?; + + Parsed::Success { + token: IPv4Header { + version, + ihl, + tos, + length, + id, + flags, + fragment_offset, + ttl, + protocol, + chksum, + source_addr, + dest_addr, + options, + }, + stream, + } +} + +#[cfg(test)] +mod tests { + use std::net::Ipv4Addr; + + use binator_context::Ignore; + use binator_core::Parsed; + + use super::{ + IPProtocol, + IPv4Header, + }; + + #[test] + fn ipv4_header() { + let data = [ + 0x45, 0x00, 0x05, 0xDC, 0x1A, 0xE6, 0x20, 0x00, 0x40, 0x01, 0x22, 0xED, 0x0A, 0x0A, 0x01, + 0x87, 0x0A, 0x0A, 0x01, 0xB4, + ]; + + let expectation = IPv4Header { + version: 4, + ihl: 5, + tos: 0, + length: 1500, + id: 0x1AE6, + flags: 0x01, + fragment_offset: 0, + ttl: 64, + protocol: IPProtocol::ICMP, + chksum: 0x22ED, + source_addr: Ipv4Addr::new(10, 10, 1, 135), + dest_addr: Ipv4Addr::new(10, 10, 1, 180), + options: "".as_bytes(), + }; + assert_eq!( + Parsed::Success { + token: expectation, + stream: "".as_bytes(), + }, + super::ipv4_header::<_, Ignore>(data.as_slice()) + ); + } +} diff --git a/src/ipv6.rs b/src/ipv6.rs new file mode 100644 index 0000000..443de79 --- /dev/null +++ b/src/ipv6.rs @@ -0,0 +1,219 @@ +//! Handles parsing of IPv6 headers + +use std::{ + fmt::{ + Display, + Formatter, + }, + net::Ipv6Addr, +}; + +use binator_base::{ + nbit, + octet, + NBit, +}; +use binator_core::{ + Contexting, + CoreAtom, + Parse, + Parsed, + Streaming, + Success, +}; +use binator_utils::{ + Utils, + UtilsAtom, +}; + +use crate::ip_protocol::{ + self, + IPProtocol, +}; + +/// +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct IPv6Header { + /// The constant 6 (bit sequence 0110). + pub version: u8, + /// Differentiated services is a computer networking architecture + /// that specifies a mechanism for classifying and managing network traffic + /// and providing quality of service (QoS) on modern IP networks. + pub ds: u8, + /// Explicit Congestion Notification (ECN); priority values subdivide into + /// ranges: traffic where the source provides congestion control and + /// non-congestion control traffic. + pub ecn: u8, + /// A high-entropy identifier of a flow of packets between a source and + /// destination. A flow is a group of packets, e.g., a TCP session or a media + /// stream. The special flow label 0 means the packet does not belong to any + /// flow (using this scheme). An older scheme identifies flow by source + /// address and port, destination address and port, protocol (value of the + /// last Next Header field). It has further been suggested that the flow + /// label be used to help detect spoofed packets. + pub flow_label: u32, + /// The size of the payload in octets, including any extension headers. The + /// length is set to zero when a Hop-by-Hop extension header carries a Jumbo + /// Payload option. + pub length: u16, + /// Specifies the type of the next header. This field usually specifies the + /// transport layer protocol used by a packet's payload. When extension + /// headers are present in the packet this field indicates which extension + /// header follows. The values are shared with those used for the IPv4 + /// protocol field, as both fields have the same function. + pub next_header: IPProtocol, + /// Replaces the time to live field in IPv4. This value is decremented by one + /// at each forwarding node and the packet is discarded if it becomes 0. + /// However, the destination node should process the packet normally even if + /// received with a hop limit of 0. + pub hop_limit: u8, + /// The unicast IPv6 address of the sending node. + pub source_addr: Ipv6Addr, + /// The IPv6 unicast or multicast address of the destination node(s). + pub dest_addr: Ipv6Addr, +} + +/// Aom produced by ipv6_header parser +pub enum Ipv6Atom { + /// When version is not 6 + 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) + } + } + } +} + +/// Parse IPv6 header +#[cfg_attr( + feature = "tracing", + tracing::instrument(level = "trace", skip_all, ret(Display)) +)] +pub fn ipv6_header(stream: Stream) -> Parsed +where + Stream: Clone, + Stream: Eq, + Stream: Streaming, + Stream::Item: Into, + Context: Contexting>, + Context: Contexting>, + Context: Contexting>, + Context: Contexting, +{ + 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; + use binator_core::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(), + } + ); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0ec61d2 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,232 @@ +#![doc = include_str!("../readme.md")] +// #![cfg_attr(not(test), no_std)] +#![feature(trait_alias)] +// #![feature(generic_const_exprs)] +#![warn(missing_docs)] +#![deny(clippy::default_numeric_fallback)] + +mod ether_type; +pub use ether_type::*; +mod ethernet; +pub use ethernet::*; +mod ip_addr; +pub use ip_addr::*; +mod ip_protocol; +pub use ip_protocol::*; +mod ipv4; +pub use ipv4::*; +mod ipv6; +pub use ipv6::*; +mod tcp; +pub use tcp::*; +mod udp; +pub use udp::*; + +macro_rules! pascal_name { + ($name:ident) => { + const_format::map_ascii_case!(const_format::Case::Pascal, core::stringify!($name)) + }; +} + +macro_rules! display_variants { + ($struct_name:ident, $field_name:ident: $($variant_name:ident,)*) => { + impl core::fmt::Display for $struct_name { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + $(&Self::$variant_name => write!(f, "{}: {}", $crate::pascal_name!($variant_name), self.$field_name),)* + _ => write!(f, "Unknown: {}", self.$field_name()), + } + } + } + }; +} + +macro_rules! decl_variants { + ($($(#[$docs:meta])* $variant_name:ident => $variant_value:expr,)*) => { + $($(#[$docs])* pub const $variant_name: Self = Self::new($variant_value);)* + }; +} + +macro_rules! struct_variants { + ($struct_name:ident, $field_name:ident, $field_type:ty: + $($(#[$variant_docs:meta])* $variant_name:ident => $variant_value:expr,)* + ) => { + #[doc=stringify!($struct_name)] + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[repr(transparent)] + pub struct $struct_name { + $field_name: $field_type, + } + + paste::paste! { + #[allow(non_camel_case_types)] + #[allow(dead_code)] + #[allow(clippy::upper_case_acronyms)] + enum [] { + $($variant_name = $variant_value,)* + } + } + + impl $struct_name { + $crate::decl_variants!{$($(#[$variant_docs])* $variant_name => $variant_value,)*} + + /// Return $struct_name from $field_type + pub const fn new($field_name: $field_type) -> Self { + Self { $field_name } + } + + /// Return $field_type + pub const fn $field_name(&self) -> $field_type { + self.$field_name + } + } + + impl From<$field_type> for $struct_name { + fn from($field_name: $field_type) -> Self { + Self::new($field_name) + } + } + + impl From<$struct_name> for $field_type { + fn from(this: $struct_name) -> Self { + this.$field_name + } + } + + impl core::str::FromStr for $struct_name { + type Err = (); + fn from_str(s: &str) -> Result { + match s { + $(core::stringify!($crate::pascal_name!($variant_name)) => Ok(Self::$variant_name),)* + _ => Err(()), + } + } + } + + $crate::display_variants!{ + $struct_name, $field_name: + $($variant_name,)* + } + }; +} + +pub(crate) use decl_variants; +pub(crate) use display_variants; +pub(crate) use pascal_name; +pub(crate) use struct_variants; + +#[cfg(test)] +mod tests { + use core::fmt::Debug; + + use binator_base::{ + all, + BaseAtom, + }; + use binator_context::Tree; + use binator_core::{ + CoreAtom, + Parse, + Streaming, + Success, + }; + use binator_number::IntRadixAtom; + use binator_utils::UtilsAtom; + use derive_more::{ + Display, + From, + }; + use pretty_assertions::assert_eq; + use test_log::test; + + use crate::{ + ipv4_header, + tcp_header, + tcp_options, + Ipv4Atom, + TcpAtom, + TcpOption, + }; + + #[derive(Display, Debug, Clone, PartialEq, From)] + enum FromAtom< + Stream: Streaming + Debug, + Item: 'static = ::Item, + Error = ::Error, + > { + Core(CoreAtom), + Utils(UtilsAtom), + Base(BaseAtom), + U8Radix(IntRadixAtom), + U16Radix(IntRadixAtom), + Tcp(TcpAtom), + Ipv4(Ipv4Atom), + } + + type HandleAtom = Tree>; + + #[test] + fn parse_tcp_packet() { + let bytes = [ + 0x45, 0x00, 0x00, 0x38, 0x76, 0xF4, 0x40, 0x00, 0x40, 0x06, 0x80, 0xD9, 0xC0, 0xA8, 0x00, + 0x6C, 0xD0, 0x61, 0xB1, 0x7C, 0xB0, 0xC2, 0x00, 0x50, 0xB0, 0xEE, 0x32, 0xA6, 0x04, 0x39, + 0xAE, 0xE6, 0x50, 0x18, 0x00, 0xE5, 0x76, 0x92, 0x00, 0x00, 0x47, 0x45, 0x54, 0x20, 0x2F, + 0x69, 0x6E, 0x64, 0x65, 0x78, 0x2E, 0x68, 0x74, 0x6D, 0x6C, 0x0A, + ]; + + let Success { + token: (_ipv4_header, tcp_header, data), + stream: _, + } = (ipv4_header::<_, HandleAtom<_>>, tcp_header, all) + .parse(bytes.as_slice()) + .unwrap(); + + assert_eq!(tcp_header.source_port, 45250); + assert_eq!(tcp_header.dest_port, 80); + assert_eq!(data, b"GET /index.html\x0a"); + } + + #[test] + fn parse_tcp_packet_with_options() { + let bytes = [ + 0x45, 0x20, 0x00, 0x34, 0x78, 0xD6, 0x40, 0x00, 0x35, 0x06, 0x7E, 0x77, 0x45, 0xA4, 0x10, + 0x00, 0xC0, 0xA8, 0x38, 0x0A, 0x00, 0x50, 0xC2, 0x27, 0x48, 0xF3, 0x02, 0xC2, 0x61, 0xD3, + 0x16, 0xA8, 0x80, 0x12, 0xFF, 0xFF, 0x9B, 0x80, 0x00, 0x00, 0x02, 0x04, 0x05, 0x3A, 0x01, + 0x03, 0x03, 0x04, 0x04, 0x02, 0x00, 0x00, + ]; + + let Success { + token: (_ipv4_header, tcp_header), + stream, + } = (ipv4_header::<_, HandleAtom<_>>, tcp_header) + .parse(bytes.as_slice()) + .unwrap(); + + assert_eq!(tcp_header.source_port, 80); + assert_eq!(tcp_header.dest_port, 49703); + + assert_eq!(stream, b""); + + let Success { + token: options, + stream, + } = tcp_options::<_, HandleAtom<_>> + .parse(tcp_header.options) + .unwrap(); + + // println!("{ipv4_header:#?}"); + // println!("{tcp_header:#?}"); + // println!("{options:#?}"); + + assert_eq!(options[0], TcpOption::MaximumSegmentSize(1338)); + assert_eq!(options[1], TcpOption::Noop); + assert_eq!(options[2], TcpOption::WindowScale(4)); + assert_eq!(options[3], TcpOption::SackPermitted); + assert_eq!(options[4], TcpOption::EndOfOption); + assert_eq!(options[5], TcpOption::EndOfOption); + + assert_eq!(options.len(), 6); + assert_eq!(stream, b""); + } +} diff --git a/src/tcp.rs b/src/tcp.rs new file mode 100644 index 0000000..e5d196a --- /dev/null +++ b/src/tcp.rs @@ -0,0 +1,581 @@ +//! Handles parsing of TCP headers + +use std::fmt::{ + Debug, + Display, + Formatter, +}; + +use binator_base::{ + any, + is, + octet, + BaseAtom, +}; +use binator_core::{ + Acc, + Contexting, + CoreAtom, + Parse, + Parsed, + Streaming, + Success, +}; +use binator_number::{ + u16_be, + u32_be, + IntRadixAtom, +}; +use binator_utils::{ + Utils, + UtilsAtom, +}; + +/// Meta trait for tcp combinator +pub trait TcpParse = where + Stream: Streaming + Clone + Eq, + ::Item: Into + Clone, + ::Item: PartialEq<::Item>, + Context: Contexting>, + Context: Contexting>, + Context: Contexting>, + // Context: Context>, + Context: Contexting>, + Context: Contexting, + u8: Into<::Item>; + +/// Contains TCP flags +#[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! { + /// Return true if option is set + pub const fn [](&self) -> bool { + self.raw & 1 << $pos != 0 + } + + /// Set option to bool value + pub fn [](&mut self, state: bool) -> bool { + if state { + self.raw |= 1 << $pos; + } + else { + self.raw &= !(1 << $pos); + } + state + } + })* + + /// Return data offset + pub const fn get_data_offset(&self) -> u8 { + (self.raw >> 12) as u8 + } + + /// Set data offset value, error if 4 < N < 16 is not true + pub fn set_data_offset(&mut self, n: usize) -> Result { + if n > 4 && n < 16 { + self.raw &= u16::MAX >> 4; // reset to 0 + self.raw |= (n as u16) << 12; // set to n + 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.[]()))* + .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 for TcpFlags { + fn from(raw: u16) -> Self { + Self { raw } + } +} + +/// TcpHeader +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TcpHeader { + /// Identifies the sending port. + pub source_port: u16, + /// Identifies the receiving port. + pub dest_port: u16, + /// Has a dual role: + /// * If the SYN flag is set (1), then this is the initial sequence number. + /// The sequence number of the actual first data byte and the acknowledged + /// number in the corresponding ACK are then this sequence number plus 1. + /// * If the SYN flag is clear (0), then this is the accumulated sequence + /// number of the first data byte of this segment for the current session. + pub sequence_no: u32, + /// If the ACK flag is set then the value of this field is the next sequence + /// number that the sender of the ACK is expecting. This acknowledges receipt + /// of all prior bytes (if any). The first ACK sent by each end acknowledges + /// the other end's initial sequence number itself, but no data. + pub ack_no: u32, + /// Contains 8 1-bit flags (control bits) + pub flags: TcpFlags, + /// The size of the receive window, which specifies the number of window size + /// units that the sender of this segment is currently willing to + /// receive. (See § Flow control and § Window scaling.) + pub window: u16, + /// The 16-bit checksum field is used for error-checking of the TCP header, + /// the payload and an IP pseudo-header. The pseudo-header consists of the + /// source IP address, the destination IP address, the protocol number for the + /// TCP protocol (6) and the length of the TCP headers and payload (in bytes). + pub checksum: u16, + /// If the URG flag is set, then this 16-bit field is an offset from the + /// sequence number indicating the last urgent data byte. + pub urgent_pointer: u16, + /// Options use tcp_options with the Span to parse Options to a Vec + // TODO could be custom type that impl iterator + pub options: Span, +} + +impl TcpHeader {} + +/// Atom produced by TCP +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TcpAtom { + /// When Data off Set is less than 5 + DataOffSet, + /// When Maximum len option size not 4 + MssLen, + /// When Maximum len option size not 3 + WindowScaleLen, + /// When Maximum len option size not 3 + SackPermittedLen, + /// When SackLen size length is invalid + SackLen(u8), + /// When Maximum len option size not 10 + 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: Stream) -> Parsed +where + (): TcpParse, +{ + u16_be + .map(TcpFlags::from) + .try_map(|flags| { + if flags.get_data_offset() >= 5 { + Ok(flags) + } else { + Err(Contexting::new(TcpAtom::DataOffSet)) + } + }) + .parse(stream) +} + +/// Parse tcp header +#[cfg_attr( + feature = "tracing", + tracing::instrument(level = "trace", skip_all, ret(Display)) +)] +pub fn tcp_header( + stream: Stream, +) -> Parsed::Span>, Stream, Context> +where + (): TcpParse, +{ + 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, + }, + } +} + +/// Sack +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Sack { + /// Sack with 2 u32 + A([u32; 2]), + /// Sack with 4 u32 + B([u32; 4]), + /// Sack with 6 u32 + C([u32; 6]), + /// Sack with 8 u32 + D([u32; 8]), +} + +/// Tcp Option +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum TcpOption { + /// End of Option + EndOfOption, + /// No Operation + Noop, + /// The largest amount of data, specified in bytes, that TCP is willing to + /// receive in a single segment + MaximumSegmentSize(u16), + /// The window scale value represents the number of bits to left-shift the + /// 16-bit window size field when interpreting it + WindowScale(u8), + /// Selective acknowledgments is permited or not. + SackPermitted, + /// Sack data + Sack(Sack), + /// Timestamps of paquet + Timestamps((u32, u32)), + /// Unknown option + Unknown((u8, Span)), +} + +#[cfg_attr( + feature = "tracing", + tracing::instrument(level = "trace", skip_all, ret(Display)) +)] +fn noop( + stream: Stream, +) -> Parsed::Span>, Stream, Context> +where + (): TcpParse, +{ + Parsed::Success { + token: TcpOption::Noop, + stream, + } +} + +#[cfg_attr( + feature = "tracing", + tracing::instrument(level = "trace", skip_all, ret(Display)) +)] +fn mss( + stream: Stream, +) -> Parsed::Span>, Stream, Context> +where + (): TcpParse, +{ + 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: Stream, +) -> Parsed::Span>, Stream, Context> +where + (): TcpParse, +{ + 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: Stream, +) -> Parsed::Span>, Stream, Context> +where + (): TcpParse, +{ + 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: Stream, +) -> Parsed::Span>, Stream, Context> +where + (): TcpParse, +{ + 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: Stream, +) -> Parsed::Span>, Stream, Context> +where + (): TcpParse, +{ + is(10) + .add_atom(|| TcpAtom::TimestampsLen) + .drop_and((u32_be, u32_be)) + .map(TcpOption::Timestamps) + .parse(stream) +} + +struct Unknown { + op: u8, +} + +fn unknown( + op: u8, +) -> impl Parse::Span>> +where + (): TcpParse, +{ + Unknown { op } +} + +impl Parse for Unknown +where + (): TcpParse, +{ + type Token = TcpOption<::Span>; + + #[cfg_attr( + feature = "tracing", + tracing::instrument(level = "trace", name = "unknown", skip_all, ret(Display)) + )] + fn parse( + &mut self, stream: Stream, + ) -> Parsed::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: Stream, +) -> Parsed::Span>, Stream, Context> +where + (): TcpParse, +{ + 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) +} + +/// Parse tcp option this can be used on the Stream Span. +#[cfg_attr( + feature = "tracing", + tracing::instrument(level = "trace", skip_all, ret(Display)) +)] +pub fn tcp_options( + stream: Stream, +) -> Parsed::Span>>, Stream, Context> +where + (): TcpParse, +{ + tcp_option.fold_bounds(.., Vec::new, Acc::acc).parse(stream) +} + +#[cfg(test)] +mod tests { + use core::fmt::Debug; + + use binator_base::BaseAtom; + use binator_context::Tree; + use binator_core::{ + CoreAtom, + Parse, + Parsed, + Streaming, + }; + use binator_number::IntRadixAtom; + use binator_utils::UtilsAtom; + use derive_more::{ + Display, + From, + }; + use pretty_assertions::assert_eq; + use test_log::test; + + use crate::{ + tcp_header, + TcpAtom, + TcpFlags, + TcpHeader, + }; + + // use super::*; + + #[derive(Display, Debug, Clone, PartialEq, From)] + enum FromAtom< + Stream: Streaming + Debug, + Item: 'static = ::Item, + Error = ::Error, + > { + Core(CoreAtom), + Utils(UtilsAtom), + Base(BaseAtom), + U8Radix(IntRadixAtom), + U16Radix(IntRadixAtom), + Tcp(TcpAtom), + } + + type HandleAtom = Tree>; + + #[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); + } +} diff --git a/src/udp.rs b/src/udp.rs new file mode 100644 index 0000000..337f30e --- /dev/null +++ b/src/udp.rs @@ -0,0 +1,87 @@ +//! Handles parsing of UDP header + +use binator_core::{ + Contexting, + CoreAtom, + Parse, + Parsed, + Streaming, + Success, +}; +use binator_number::u16_be; +use binator_utils::UtilsAtom; + +/// Data of a UDP Header +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct UdpHeader { + /// This field identifies the sender's port, when used, and should be assumed + /// to be the port to reply to if needed. If not used, it should be zero. If + /// the source host is the client, the port number is likely to be an + /// ephemeral port. If the source host is the server, the port number is + /// likely to be a well-known port number from 0 to 1023. + pub source_port: u16, + /// This field identifies the receiver's port and is required. Similar to + /// source port number, if the client is the destination host then the port + /// number will likely be an ephemeral port number and if the destination host + /// is the server then the port number will likely be a well-known port + /// number. + pub dest_port: u16, + /// This field specifies the length in bytes of the UDP header and UDP data. + pub length: u16, + /// The checksum field may be used for error-checking of the header and data. + /// This field is optional in IPv4, and mandatory in most cases in IPv6. The + /// field carries all-zeros if unused. + pub checksum: u16, +} + +/// UDP header parser +pub fn udp_header(stream: Stream) -> Parsed +where + Stream: Streaming, + Stream: Eq, + Stream::Item: Into, + Context: Contexting>, + Context: Contexting>, +{ + let Success { + token: (source_port, dest_port, length, checksum), + stream, + } = (u16_be, u16_be, u16_be, u16_be).parse(stream)?; + + Parsed::Success { + token: UdpHeader { + source_port, + dest_port, + length, + checksum, + }, + stream, + } +} + +#[cfg(test)] +mod tests { + use binator_context::Ignore; + use binator_core::Parsed; + + use super::UdpHeader; + + #[test] + fn udp_header_works() { + let bytes = [0x00, 0x12, 0x11, 0x11, 0x00, 0x1B, 0x21, 0x0F]; + let expectation = UdpHeader { + source_port: 0x12, + dest_port: 0x1111, + length: 0x1B, + checksum: 0x210F, + }; + assert_eq!( + super::udp_header::<_, Ignore>(&bytes[..]), + Parsed::Success { + token: expectation, + stream: &[][..] + } + ); + } +}