use crate::endian::{read_u16_be, read_u32_be};
use crate::error::{CrafterError, Result};
use super::constants::{
MPTCP_SUBTYPE_TCPRST, TCP_EDO_HEADER_AND_SEGMENT_LEN, TCP_EDO_HEADER_LEN, TCP_EDO_REQUEST_LEN,
TCP_OPTION_ACCURATE_ECN_MIN_LEN, TCP_OPTION_ACCURATE_ECN_ORDER_0,
TCP_OPTION_ACCURATE_ECN_ORDER_1, TCP_OPTION_EDO, TCP_OPTION_EOL, TCP_OPTION_EXPERIMENTAL_1,
TCP_OPTION_EXPERIMENTAL_2, TCP_OPTION_EXPERIMENTAL_MIN_LEN, TCP_OPTION_FAST_OPEN,
TCP_OPTION_MD5_SIGNATURE, TCP_OPTION_MPTCP, TCP_OPTION_MSS, TCP_OPTION_NOP, TCP_OPTION_SACK,
TCP_OPTION_SACK_PERMITTED, TCP_OPTION_TCP_AUTHENTICATION,
TCP_OPTION_TCP_AUTHENTICATION_MIN_LEN, TCP_OPTION_TCP_ENO, TCP_OPTION_TCP_ENO_MIN_LEN,
TCP_OPTION_TIMESTAMP, TCP_OPTION_USER_TIMEOUT, TCP_OPTION_USER_TIMEOUT_LEN,
TCP_OPTION_WINDOW_SCALE, TCP_WINDOW_SCALE_MAX_SHIFT,
};
use super::sizing::TcpOptionBudget;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TcpOptionKindClass {
Assigned,
Experimental,
Unassigned,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TcpSackBlock {
pub left_edge: u32,
pub right_edge: u32,
}
impl TcpSackBlock {
pub const fn new(left_edge: u32, right_edge: u32) -> Self {
Self {
left_edge,
right_edge,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TcpExtendedDataOffset {
Request,
HeaderLength {
header_length: u16,
},
HeaderAndSegmentLength {
header_length: u16,
segment_length: u16,
},
}
impl TcpExtendedDataOffset {
pub const fn option_len(self) -> u8 {
match self {
Self::Request => TCP_EDO_REQUEST_LEN,
Self::HeaderLength { .. } => TCP_EDO_HEADER_LEN,
Self::HeaderAndSegmentLength { .. } => TCP_EDO_HEADER_AND_SEGMENT_LEN,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TcpOption {
EndOfList,
NoOperation,
MaximumSegmentSize(u16),
WindowScale(u8),
SackPermitted,
Sack(Vec<TcpSackBlock>),
Timestamp {
value: u32,
echo_reply: u32,
},
MultipathTcp {
subtype: u8,
data: Vec<u8>,
},
ExtendedDataOffset(TcpExtendedDataOffset),
FastOpen(Vec<u8>),
Experimental {
kind: u8,
experiment_id: u16,
data: Vec<u8>,
},
UserTimeout {
granularity: bool,
value: u16,
},
AuthenticationOption {
key_id: u8,
rnext_key_id: u8,
mac: Vec<u8>,
},
TcpEno {
suboptions: Vec<u8>,
},
AccurateEcn {
kind: u8,
data: Vec<u8>,
},
Generic {
kind: u8,
data: Vec<u8>,
},
}
impl TcpOption {
pub const fn end_of_list() -> Self {
Self::EndOfList
}
pub const fn no_operation() -> Self {
Self::NoOperation
}
pub const fn maximum_segment_size(mss: u16) -> Self {
Self::MaximumSegmentSize(mss)
}
pub const fn mss(mss: u16) -> Self {
Self::MaximumSegmentSize(mss)
}
pub const fn window_scale(shift: u8) -> Self {
Self::WindowScale(shift)
}
pub const fn sack_permitted() -> Self {
Self::SackPermitted
}
pub fn sack(blocks: impl Into<Vec<TcpSackBlock>>) -> Self {
Self::Sack(blocks.into())
}
pub const fn timestamp(value: u32, echo_reply: u32) -> Self {
Self::Timestamp { value, echo_reply }
}
pub fn multipath_tcp(subtype: u8, data: impl Into<Vec<u8>>) -> Self {
Self::MultipathTcp {
subtype,
data: data.into(),
}
}
pub const fn extended_data_offset_request() -> Self {
Self::ExtendedDataOffset(TcpExtendedDataOffset::Request)
}
pub const fn extended_data_offset(header_length: u16) -> Self {
Self::ExtendedDataOffset(TcpExtendedDataOffset::HeaderLength { header_length })
}
pub const fn extended_data_offset_ext(header_length: u16, segment_length: u16) -> Self {
Self::ExtendedDataOffset(TcpExtendedDataOffset::HeaderAndSegmentLength {
header_length,
segment_length,
})
}
pub fn fast_open(cookie: impl Into<Vec<u8>>) -> Self {
Self::FastOpen(cookie.into())
}
pub fn fast_open_cookie_request() -> Self {
Self::FastOpen(Vec::new())
}
pub fn experimental(kind: u8, experiment_id: u16, data: impl Into<Vec<u8>>) -> Self {
Self::Experimental {
kind,
experiment_id,
data: data.into(),
}
}
pub fn experimental_1(experiment_id: u16, data: impl Into<Vec<u8>>) -> Self {
Self::experimental(TCP_OPTION_EXPERIMENTAL_1, experiment_id, data)
}
pub fn experimental_2(experiment_id: u16, data: impl Into<Vec<u8>>) -> Self {
Self::experimental(TCP_OPTION_EXPERIMENTAL_2, experiment_id, data)
}
pub const fn user_timeout(granularity: bool, value: u16) -> Self {
Self::UserTimeout { granularity, value }
}
pub fn tcp_authentication(key_id: u8, rnext_key_id: u8, mac: impl Into<Vec<u8>>) -> Self {
Self::AuthenticationOption {
key_id,
rnext_key_id,
mac: mac.into(),
}
}
pub fn tcp_eno(suboptions: impl Into<Vec<u8>>) -> Self {
Self::TcpEno {
suboptions: suboptions.into(),
}
}
pub fn accurate_ecn(kind: u8, data: impl Into<Vec<u8>>) -> Self {
Self::AccurateEcn {
kind,
data: data.into(),
}
}
pub fn accurate_ecn_order_0(data: impl Into<Vec<u8>>) -> Self {
Self::accurate_ecn(TCP_OPTION_ACCURATE_ECN_ORDER_0, data)
}
pub fn accurate_ecn_order_1(data: impl Into<Vec<u8>>) -> Self {
Self::accurate_ecn(TCP_OPTION_ACCURATE_ECN_ORDER_1, data)
}
pub fn generic(kind: u8, data: impl Into<Vec<u8>>) -> Self {
Self::Generic {
kind,
data: data.into(),
}
}
pub const fn kind(&self) -> u8 {
match self {
Self::EndOfList => TCP_OPTION_EOL,
Self::NoOperation => TCP_OPTION_NOP,
Self::MaximumSegmentSize(_) => TCP_OPTION_MSS,
Self::WindowScale(_) => TCP_OPTION_WINDOW_SCALE,
Self::SackPermitted => TCP_OPTION_SACK_PERMITTED,
Self::Sack(_) => TCP_OPTION_SACK,
Self::Timestamp { .. } => TCP_OPTION_TIMESTAMP,
Self::MultipathTcp { .. } => TCP_OPTION_MPTCP,
Self::ExtendedDataOffset(_) => TCP_OPTION_EDO,
Self::FastOpen(_) => TCP_OPTION_FAST_OPEN,
Self::Experimental { kind, .. } => *kind,
Self::UserTimeout { .. } => TCP_OPTION_USER_TIMEOUT,
Self::AuthenticationOption { .. } => TCP_OPTION_TCP_AUTHENTICATION,
Self::TcpEno { .. } => TCP_OPTION_TCP_ENO,
Self::AccurateEcn { kind, .. } => *kind,
Self::Generic { kind, .. } => *kind,
}
}
pub const fn kind_class(&self) -> TcpOptionKindClass {
tcp_option_kind_class(self.kind())
}
pub const fn kind_name(&self) -> &'static str {
tcp_option_kind_name(self.kind())
}
pub const fn kind_is_assigned(&self) -> bool {
tcp_option_kind_is_assigned(self.kind())
}
pub const fn kind_is_experimental(&self) -> bool {
tcp_option_kind_is_experimental(self.kind())
}
pub const fn maximum_segment_size_value(&self) -> Option<u16> {
match self {
Self::MaximumSegmentSize(mss) => Some(*mss),
_ => None,
}
}
pub const fn window_scale_shift(&self) -> Option<u8> {
match self {
Self::WindowScale(shift) => Some(*shift),
_ => None,
}
}
pub const fn window_scale_shift_is_valid(&self) -> Option<bool> {
match self {
Self::WindowScale(shift) => Some(valid_window_scale(*shift)),
_ => None,
}
}
pub const fn is_sack_permitted(&self) -> bool {
matches!(self, Self::SackPermitted)
}
pub fn sack_blocks(&self) -> Option<&[TcpSackBlock]> {
match self {
Self::Sack(blocks) => Some(blocks),
_ => None,
}
}
pub fn sack_block_count(&self) -> Option<usize> {
self.sack_blocks().map(<[TcpSackBlock]>::len)
}
pub fn first_sack_block(&self) -> Option<TcpSackBlock> {
self.sack_blocks()
.and_then(|blocks| blocks.first().copied())
}
pub fn remaining_sack_blocks(&self) -> Option<&[TcpSackBlock]> {
self.sack_blocks()
.map(|blocks| blocks.get(1..).unwrap_or(&[]))
}
pub fn is_potential_dsack_first_block(&self, cumulative_ack: u32) -> Option<bool> {
let blocks = self.sack_blocks()?;
let first = blocks.first()?;
let below_cumulative_ack = serial_le(first.right_edge, cumulative_ack);
let subset_of_second = blocks.get(1).is_some_and(|second| {
serial_le(second.left_edge, first.left_edge)
&& serial_le(first.right_edge, second.right_edge)
});
Some(below_cumulative_ack || subset_of_second)
}
pub const fn timestamp_values(&self) -> Option<(u32, u32)> {
match self {
Self::Timestamp { value, echo_reply } => Some((*value, *echo_reply)),
_ => None,
}
}
pub const fn timestamp_value(&self) -> Option<u32> {
match self {
Self::Timestamp { value, .. } => Some(*value),
_ => None,
}
}
pub const fn timestamp_echo_reply(&self) -> Option<u32> {
match self {
Self::Timestamp { echo_reply, .. } => Some(*echo_reply),
_ => None,
}
}
pub const fn mptcp_subtype(&self) -> Option<u8> {
match self {
Self::MultipathTcp { subtype, .. } => Some(*subtype),
_ => None,
}
}
pub fn mptcp_data(&self) -> Option<&[u8]> {
match self {
Self::MultipathTcp { data, .. } => Some(data),
_ => None,
}
}
pub const fn is_multipath_tcp(&self) -> bool {
matches!(self, Self::MultipathTcp { .. })
}
pub fn mptcp_flags(&self) -> Option<u8> {
match self {
Self::MultipathTcp { data, .. } => data.first().map(|first| first & 0x0f),
_ => None,
}
}
pub fn mptcp_subtype_data(&self) -> Option<&[u8]> {
match self {
Self::MultipathTcp { data, .. } => {
Some(data.split_first().map_or(&[][..], |(_, rest)| rest))
}
_ => None,
}
}
pub fn mptcp_tcprst_reason(&self) -> Option<u8> {
match self {
Self::MultipathTcp { subtype, data } if *subtype == MPTCP_SUBTYPE_TCPRST => {
data.get(1).copied()
}
_ => None,
}
}
pub const fn extended_data_offset_value(&self) -> Option<TcpExtendedDataOffset> {
match self {
Self::ExtendedDataOffset(edo) => Some(*edo),
_ => None,
}
}
pub fn fast_open_cookie(&self) -> Option<&[u8]> {
match self {
Self::FastOpen(cookie) => Some(cookie),
_ => None,
}
}
pub fn is_fast_open_cookie_request(&self) -> bool {
matches!(self, Self::FastOpen(cookie) if cookie.is_empty())
}
pub const fn experiment_id(&self) -> Option<u16> {
match self {
Self::Experimental { experiment_id, .. } => Some(*experiment_id),
_ => None,
}
}
pub fn experiment_data(&self) -> Option<&[u8]> {
match self {
Self::Experimental { data, .. } => Some(data),
_ => None,
}
}
pub const fn is_experimental(&self) -> bool {
matches!(self, Self::Experimental { .. })
}
pub const fn user_timeout_value(&self) -> Option<(bool, u16)> {
match self {
Self::UserTimeout { granularity, value } => Some((*granularity, *value)),
_ => None,
}
}
pub fn tcp_authentication_value(&self) -> Option<(u8, u8, &[u8])> {
match self {
Self::AuthenticationOption {
key_id,
rnext_key_id,
mac,
} => Some((*key_id, *rnext_key_id, mac)),
_ => None,
}
}
pub const fn key_id(&self) -> Option<u8> {
match self {
Self::AuthenticationOption { key_id, .. } => Some(*key_id),
_ => None,
}
}
pub const fn rnext_key_id(&self) -> Option<u8> {
match self {
Self::AuthenticationOption { rnext_key_id, .. } => Some(*rnext_key_id),
_ => None,
}
}
pub fn authentication_mac(&self) -> Option<&[u8]> {
match self {
Self::AuthenticationOption { mac, .. } => Some(mac),
_ => None,
}
}
pub fn tcp_eno_suboptions(&self) -> Option<&[u8]> {
match self {
Self::TcpEno { suboptions } => Some(suboptions),
_ => None,
}
}
pub const fn accurate_ecn_order(&self) -> Option<u8> {
match self {
Self::AccurateEcn { kind, .. } => Some(*kind),
_ => None,
}
}
pub fn accurate_ecn_data(&self) -> Option<&[u8]> {
match self {
Self::AccurateEcn { data, .. } => Some(data),
_ => None,
}
}
pub fn accurate_ecn_value(&self) -> Option<(u8, &[u8])> {
match self {
Self::AccurateEcn { kind, data } => Some((*kind, data)),
_ => None,
}
}
pub const fn is_accurate_ecn(&self) -> bool {
matches!(self, Self::AccurateEcn { .. })
}
pub const fn generic_kind(&self) -> Option<u8> {
match self {
Self::Generic { kind, .. } => Some(*kind),
_ => None,
}
}
pub fn generic_data(&self) -> Option<&[u8]> {
match self {
Self::Generic { data, .. } => Some(data),
_ => None,
}
}
pub fn encoded_len(&self) -> usize {
match self {
Self::EndOfList | Self::NoOperation => 1,
Self::MaximumSegmentSize(_) => 4,
Self::WindowScale(_) => 3,
Self::SackPermitted => 2,
Self::Sack(blocks) => 2 + blocks.len() * 8,
Self::Timestamp { .. } => 10,
Self::MultipathTcp { data, .. } => 2 + data.len().max(1),
Self::ExtendedDataOffset(edo) => edo.option_len() as usize,
Self::FastOpen(cookie) => 2 + cookie.len(),
Self::Experimental { data, .. } => 4 + data.len(),
Self::UserTimeout { .. } => TCP_OPTION_USER_TIMEOUT_LEN as usize,
Self::AuthenticationOption { mac, .. } => {
TCP_OPTION_TCP_AUTHENTICATION_MIN_LEN as usize + mac.len()
}
Self::TcpEno { suboptions } => TCP_OPTION_TCP_ENO_MIN_LEN as usize + suboptions.len(),
Self::AccurateEcn { data, .. } => TCP_OPTION_ACCURATE_ECN_MIN_LEN as usize + data.len(),
Self::Generic { data, .. } => 2 + data.len(),
}
}
pub fn encode(&self) -> Result<Vec<u8>> {
let len = self.encoded_len();
if len > u8::MAX as usize {
return Err(CrafterError::invalid_field_value(
"tcp.option.length",
"TCP option length must fit in one byte",
));
}
let mut bytes = Vec::with_capacity(len);
match self {
Self::EndOfList => bytes.push(TCP_OPTION_EOL),
Self::NoOperation => bytes.push(TCP_OPTION_NOP),
Self::MaximumSegmentSize(mss) => {
bytes.extend_from_slice(&[TCP_OPTION_MSS, 4]);
bytes.extend_from_slice(&mss.to_be_bytes());
}
Self::WindowScale(shift) => {
bytes.extend_from_slice(&[TCP_OPTION_WINDOW_SCALE, 3, *shift]);
}
Self::SackPermitted => {
bytes.extend_from_slice(&[TCP_OPTION_SACK_PERMITTED, 2]);
}
Self::Sack(blocks) => {
bytes.extend_from_slice(&[TCP_OPTION_SACK, len as u8]);
for block in blocks {
bytes.extend_from_slice(&block.left_edge.to_be_bytes());
bytes.extend_from_slice(&block.right_edge.to_be_bytes());
}
}
Self::Timestamp { value, echo_reply } => {
bytes.extend_from_slice(&[TCP_OPTION_TIMESTAMP, 10]);
bytes.extend_from_slice(&value.to_be_bytes());
bytes.extend_from_slice(&echo_reply.to_be_bytes());
}
Self::MultipathTcp { subtype, data } => {
if *subtype > 0x0f {
return Err(CrafterError::invalid_field_value(
"tcp.option.mptcp.subtype",
"MPTCP subtype must fit in four bits",
));
}
bytes.extend_from_slice(&[TCP_OPTION_MPTCP, len as u8]);
if let Some((first, rest)) = data.split_first() {
bytes.push((subtype << 4) | (first & 0x0f));
bytes.extend_from_slice(rest);
} else {
bytes.push(subtype << 4);
}
}
Self::ExtendedDataOffset(edo) => {
bytes.extend_from_slice(&[TCP_OPTION_EDO, edo.option_len()]);
match edo {
TcpExtendedDataOffset::Request => {}
TcpExtendedDataOffset::HeaderLength { header_length } => {
bytes.extend_from_slice(&header_length.to_be_bytes());
}
TcpExtendedDataOffset::HeaderAndSegmentLength {
header_length,
segment_length,
} => {
bytes.extend_from_slice(&header_length.to_be_bytes());
bytes.extend_from_slice(&segment_length.to_be_bytes());
}
}
}
Self::FastOpen(cookie) => {
bytes.extend_from_slice(&[TCP_OPTION_FAST_OPEN, len as u8]);
bytes.extend_from_slice(cookie);
}
Self::Experimental {
kind,
experiment_id,
data,
} => {
if *kind == TCP_OPTION_EOL || *kind == TCP_OPTION_NOP {
return Err(CrafterError::invalid_field_value(
"tcp.option.experimental.kind",
"experimental option kind must carry a length byte",
));
}
bytes.extend_from_slice(&[*kind, len as u8]);
bytes.extend_from_slice(&experiment_id.to_be_bytes());
bytes.extend_from_slice(data);
}
Self::UserTimeout { granularity, value } => {
let field = ((*granularity as u16) << 15) | (value & 0x7fff);
bytes.extend_from_slice(&[TCP_OPTION_USER_TIMEOUT, TCP_OPTION_USER_TIMEOUT_LEN]);
bytes.extend_from_slice(&field.to_be_bytes());
}
Self::AuthenticationOption {
key_id,
rnext_key_id,
mac,
} => {
bytes.extend_from_slice(&[
TCP_OPTION_TCP_AUTHENTICATION,
len as u8,
*key_id,
*rnext_key_id,
]);
bytes.extend_from_slice(mac);
}
Self::TcpEno { suboptions } => {
bytes.extend_from_slice(&[TCP_OPTION_TCP_ENO, len as u8]);
bytes.extend_from_slice(suboptions);
}
Self::AccurateEcn { kind, data } => {
if *kind == TCP_OPTION_EOL || *kind == TCP_OPTION_NOP {
return Err(CrafterError::invalid_field_value(
"tcp.option.accurate_ecn.kind",
"AccECN option kind must carry a length byte",
));
}
bytes.extend_from_slice(&[*kind, len as u8]);
bytes.extend_from_slice(data);
}
Self::Generic { kind, data } => {
if *kind == TCP_OPTION_EOL || *kind == TCP_OPTION_NOP {
return Err(CrafterError::invalid_field_value(
"tcp.option.kind",
"EOL and NOP options do not carry a length byte",
));
}
bytes.extend_from_slice(&[*kind, len as u8]);
bytes.extend_from_slice(data);
}
}
Ok(bytes)
}
pub fn decode_all(bytes: &[u8]) -> Result<Vec<Self>> {
TcpOptionIter::new(bytes).collect()
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TcpSynOptions {
options: Vec<TcpOption>,
}
impl TcpSynOptions {
pub const fn new() -> Self {
Self {
options: Vec::new(),
}
}
pub fn mss(mut self, mss: u16) -> Self {
self.options.push(TcpOption::maximum_segment_size(mss));
self
}
pub fn window_scale(mut self, shift: u8) -> Self {
self.options.push(TcpOption::window_scale(shift));
self
}
pub fn sack_permitted(mut self) -> Self {
self.options.push(TcpOption::sack_permitted());
self
}
pub fn timestamp(mut self, tsval: u32, tsecr: u32) -> Self {
self.options.push(TcpOption::timestamp(tsval, tsecr));
self
}
pub fn fast_open_cookie_request(mut self) -> Self {
self.options.push(TcpOption::fast_open_cookie_request());
self
}
pub fn fast_open_cookie(mut self, cookie: impl Into<Vec<u8>>) -> Self {
self.options.push(TcpOption::fast_open(cookie));
self
}
pub fn multipath_tcp(mut self, subtype: u8, data: impl Into<Vec<u8>>) -> Self {
self.options.push(TcpOption::multipath_tcp(subtype, data));
self
}
pub fn accurate_ecn(mut self, kind: u8, data: impl Into<Vec<u8>>) -> Self {
self.options.push(TcpOption::accurate_ecn(kind, data));
self
}
pub fn option(mut self, option: TcpOption) -> Self {
self.options.push(option);
self
}
pub fn options(&self) -> &[TcpOption] {
&self.options
}
pub fn budget(&self) -> TcpOptionBudget {
TcpOptionBudget::for_options(&self.options)
}
pub fn fits(&self) -> bool {
self.budget().fits()
}
pub fn build(&self) -> Vec<TcpOption> {
self.options.clone()
}
pub fn build_bytes(&self) -> Result<Vec<u8>> {
let mut bytes = Vec::new();
for option in &self.options {
bytes.extend_from_slice(&option.encode()?);
}
Ok(bytes)
}
}
#[derive(Debug, Clone)]
pub struct TcpOptionIter<'a> {
bytes: &'a [u8],
offset: usize,
done: bool,
}
impl<'a> TcpOptionIter<'a> {
pub const fn new(bytes: &'a [u8]) -> Self {
Self {
bytes,
offset: 0,
done: false,
}
}
}
impl Iterator for TcpOptionIter<'_> {
type Item = Result<TcpOption>;
fn next(&mut self) -> Option<Self::Item> {
if self.done || self.offset >= self.bytes.len() {
return None;
}
let start = self.offset;
let kind = self.bytes[start];
match kind {
TCP_OPTION_EOL => {
self.done = true;
self.offset = self.bytes.len();
Some(Ok(TcpOption::EndOfList))
}
TCP_OPTION_NOP => {
self.offset += 1;
Some(Ok(TcpOption::NoOperation))
}
_ => {
if start + 2 > self.bytes.len() {
self.done = true;
return Some(Err(CrafterError::buffer_too_short(
"tcp option",
start + 2,
self.bytes.len(),
)));
}
let len = self.bytes[start + 1] as usize;
if len < 2 {
self.done = true;
return Some(Err(CrafterError::invalid_field_value(
"tcp.option.length",
"option length must be at least 2 bytes",
)));
}
if start + len > self.bytes.len() {
self.done = true;
return Some(Err(CrafterError::buffer_too_short(
"tcp option",
start + len,
self.bytes.len(),
)));
}
let option_bytes = &self.bytes[start..start + len];
self.offset += len;
Some(decode_tcp_option(option_bytes))
}
}
}
}
pub(crate) fn validate_tcp_options(options: &[u8]) -> Result<()> {
let mut offset = 0;
while offset < options.len() {
let kind = options[offset];
match kind {
TCP_OPTION_EOL => return Ok(()),
TCP_OPTION_NOP => {
offset += 1;
}
_ => {
if offset + 2 > options.len() {
return Err(CrafterError::buffer_too_short(
"tcp option",
offset + 2,
options.len(),
));
}
let len = options[offset + 1] as usize;
if len < 2 {
return Err(CrafterError::invalid_field_value(
"tcp.option.length",
"option length must be at least 2 bytes",
));
}
if offset + len > options.len() {
return Err(CrafterError::buffer_too_short(
"tcp option",
offset + len,
options.len(),
));
}
validate_tcp_option_shape(kind, len)?;
offset += len;
}
}
}
Ok(())
}
fn validate_tcp_option_shape(kind: u8, len: usize) -> Result<()> {
match kind {
TCP_OPTION_MSS => validate_tcp_option_len("tcp.option.mss", len, 4),
TCP_OPTION_WINDOW_SCALE => validate_tcp_option_len("tcp.option.window_scale", len, 3),
TCP_OPTION_SACK_PERMITTED => validate_tcp_option_len("tcp.option.sack_permitted", len, 2),
TCP_OPTION_SACK => validate_tcp_sack_shape(len),
TCP_OPTION_TIMESTAMP => validate_tcp_option_len("tcp.option.timestamp", len, 10),
TCP_OPTION_MPTCP => {
if len <= 2 {
Err(CrafterError::invalid_field_value(
"tcp.option.mptcp",
"MPTCP option must include a subtype byte",
))
} else {
Ok(())
}
}
TCP_OPTION_USER_TIMEOUT => validate_tcp_option_len(
"tcp.option.user_timeout",
len,
TCP_OPTION_USER_TIMEOUT_LEN as usize,
),
TCP_OPTION_TCP_AUTHENTICATION => validate_tcp_min_len(
"tcp.option.authentication.length",
len,
TCP_OPTION_TCP_AUTHENTICATION_MIN_LEN as usize,
"RFC 5925 TCP-AO option must be at least 4 bytes (kind, length, KeyID, RNextKeyID)",
),
TCP_OPTION_TCP_ENO => validate_tcp_min_len(
"tcp.option.eno.length",
len,
TCP_OPTION_TCP_ENO_MIN_LEN as usize,
"RFC 8547 TCP-ENO option must be at least 2 bytes (kind, length)",
),
TCP_OPTION_ACCURATE_ECN_ORDER_0 | TCP_OPTION_ACCURATE_ECN_ORDER_1 => validate_tcp_min_len(
"tcp.option.accurate_ecn.length",
len,
TCP_OPTION_ACCURATE_ECN_MIN_LEN as usize,
"RFC 9768 AccECN option must be at least 2 bytes (kind, length)",
),
TCP_OPTION_EDO => validate_tcp_edo_shape(len),
TCP_OPTION_EXPERIMENTAL_1 | TCP_OPTION_EXPERIMENTAL_2 => validate_tcp_min_len(
"tcp.option.experimental.length",
len,
TCP_OPTION_EXPERIMENTAL_MIN_LEN as usize,
"RFC 6994 experimental option must be at least 4 bytes (kind, length, 16-bit ExID)",
),
TCP_OPTION_FAST_OPEN | TCP_OPTION_MD5_SIGNATURE => Ok(()),
_ => Ok(()),
}
}
fn validate_tcp_min_len(
context: &'static str,
len: usize,
minimum: usize,
message: &'static str,
) -> Result<()> {
if len < minimum {
Err(CrafterError::invalid_field_value(context, message))
} else {
Ok(())
}
}
fn validate_tcp_sack_shape(len: usize) -> Result<()> {
if len < 10 || (len - 2) % 8 != 0 {
return Err(CrafterError::invalid_field_value(
"tcp.option.sack",
"SACK option payload must contain one or more 8-byte blocks",
));
}
Ok(())
}
fn validate_tcp_edo_shape(len: usize) -> Result<()> {
match len as u8 {
TCP_EDO_REQUEST_LEN | TCP_EDO_HEADER_LEN | TCP_EDO_HEADER_AND_SEGMENT_LEN => Ok(()),
_ => Err(CrafterError::invalid_field_value(
"tcp.option.edo.length",
"EDO length must be 2, 4, or 6 bytes",
)),
}
}
fn decode_tcp_option(bytes: &[u8]) -> Result<TcpOption> {
let kind = bytes[0];
let data = &bytes[2..];
match kind {
TCP_OPTION_MSS => {
validate_tcp_option_len("tcp.option.mss", bytes.len(), 4)?;
Ok(TcpOption::MaximumSegmentSize(read_u16_be(data)?))
}
TCP_OPTION_WINDOW_SCALE => {
validate_tcp_option_len("tcp.option.window_scale", bytes.len(), 3)?;
Ok(TcpOption::WindowScale(data[0]))
}
TCP_OPTION_SACK_PERMITTED => {
validate_tcp_option_len("tcp.option.sack_permitted", bytes.len(), 2)?;
Ok(TcpOption::SackPermitted)
}
TCP_OPTION_SACK => decode_tcp_sack_option(data, bytes.len()),
TCP_OPTION_TIMESTAMP => {
validate_tcp_option_len("tcp.option.timestamp", bytes.len(), 10)?;
Ok(TcpOption::Timestamp {
value: read_u32_be(&data[0..4])?,
echo_reply: read_u32_be(&data[4..8])?,
})
}
TCP_OPTION_MPTCP => {
if data.is_empty() {
return Err(CrafterError::invalid_field_value(
"tcp.option.mptcp",
"MPTCP option must include a subtype byte",
));
}
Ok(TcpOption::MultipathTcp {
subtype: data[0] >> 4,
data: data.to_vec(),
})
}
TCP_OPTION_USER_TIMEOUT => decode_tcp_user_timeout_option(data, bytes.len()),
TCP_OPTION_TCP_AUTHENTICATION => decode_tcp_authentication_option(data, bytes.len()),
TCP_OPTION_TCP_ENO => decode_tcp_eno_option(data, bytes.len()),
TCP_OPTION_ACCURATE_ECN_ORDER_0 | TCP_OPTION_ACCURATE_ECN_ORDER_1 => {
decode_tcp_accurate_ecn_option(kind, data, bytes.len())
}
TCP_OPTION_EDO => decode_tcp_edo_option(data, bytes.len()),
TCP_OPTION_FAST_OPEN => Ok(TcpOption::FastOpen(data.to_vec())),
TCP_OPTION_EXPERIMENTAL_1 | TCP_OPTION_EXPERIMENTAL_2 => {
decode_tcp_experimental_option(kind, data, bytes.len())
}
_ => Ok(TcpOption::Generic {
kind,
data: data.to_vec(),
}),
}
}
fn decode_tcp_experimental_option(kind: u8, data: &[u8], len: usize) -> Result<TcpOption> {
if (len as u8) < TCP_OPTION_EXPERIMENTAL_MIN_LEN {
return Err(CrafterError::invalid_field_value(
"tcp.option.experimental.length",
"RFC 6994 experimental option must be at least 4 bytes (kind, length, 16-bit ExID)",
));
}
Ok(TcpOption::Experimental {
kind,
experiment_id: read_u16_be(&data[0..2])?,
data: data[2..].to_vec(),
})
}
fn decode_tcp_user_timeout_option(data: &[u8], len: usize) -> Result<TcpOption> {
validate_tcp_option_len(
"tcp.option.user_timeout",
len,
TCP_OPTION_USER_TIMEOUT_LEN as usize,
)?;
let field = read_u16_be(&data[0..2])?;
Ok(TcpOption::UserTimeout {
granularity: field & 0x8000 != 0,
value: field & 0x7fff,
})
}
fn decode_tcp_authentication_option(data: &[u8], len: usize) -> Result<TcpOption> {
if (len as u8) < TCP_OPTION_TCP_AUTHENTICATION_MIN_LEN {
return Err(CrafterError::invalid_field_value(
"tcp.option.authentication.length",
"RFC 5925 TCP-AO option must be at least 4 bytes (kind, length, KeyID, RNextKeyID)",
));
}
Ok(TcpOption::AuthenticationOption {
key_id: data[0],
rnext_key_id: data[1],
mac: data[2..].to_vec(),
})
}
fn decode_tcp_eno_option(data: &[u8], len: usize) -> Result<TcpOption> {
if (len as u8) < TCP_OPTION_TCP_ENO_MIN_LEN {
return Err(CrafterError::invalid_field_value(
"tcp.option.eno.length",
"RFC 8547 TCP-ENO option must be at least 2 bytes (kind, length)",
));
}
Ok(TcpOption::TcpEno {
suboptions: data.to_vec(),
})
}
fn decode_tcp_accurate_ecn_option(kind: u8, data: &[u8], len: usize) -> Result<TcpOption> {
if (len as u8) < TCP_OPTION_ACCURATE_ECN_MIN_LEN {
return Err(CrafterError::invalid_field_value(
"tcp.option.accurate_ecn.length",
"RFC 9768 AccECN option must be at least 2 bytes (kind, length)",
));
}
Ok(TcpOption::AccurateEcn {
kind,
data: data.to_vec(),
})
}
fn serial_le(a: u32, b: u32) -> bool {
b.wrapping_sub(a) < 0x8000_0000
}
fn decode_tcp_sack_option(data: &[u8], len: usize) -> Result<TcpOption> {
if len < 10 || (len - 2) % 8 != 0 {
return Err(CrafterError::invalid_field_value(
"tcp.option.sack",
"SACK option payload must contain one or more 8-byte blocks",
));
}
let mut blocks = Vec::with_capacity((len - 2) / 8);
for chunk in data.chunks_exact(8) {
blocks.push(TcpSackBlock {
left_edge: read_u32_be(&chunk[0..4])?,
right_edge: read_u32_be(&chunk[4..8])?,
});
}
Ok(TcpOption::Sack(blocks))
}
fn decode_tcp_edo_option(data: &[u8], len: usize) -> Result<TcpOption> {
let edo = match len as u8 {
TCP_EDO_REQUEST_LEN => TcpExtendedDataOffset::Request,
TCP_EDO_HEADER_LEN => TcpExtendedDataOffset::HeaderLength {
header_length: read_u16_be(&data[0..2])?,
},
TCP_EDO_HEADER_AND_SEGMENT_LEN => TcpExtendedDataOffset::HeaderAndSegmentLength {
header_length: read_u16_be(&data[0..2])?,
segment_length: read_u16_be(&data[2..4])?,
},
_ => {
return Err(CrafterError::invalid_field_value(
"tcp.option.edo.length",
"EDO length must be 2, 4, or 6 bytes",
))
}
};
Ok(TcpOption::ExtendedDataOffset(edo))
}
fn validate_tcp_option_len(field: &'static str, actual: usize, expected: usize) -> Result<()> {
if actual != expected {
return Err(CrafterError::invalid_field_value(
field,
"TCP option has an invalid fixed length",
));
}
Ok(())
}
pub const fn tcp_option_kind_class(kind: u8) -> TcpOptionKindClass {
match kind {
TCP_OPTION_EXPERIMENTAL_1 | TCP_OPTION_EXPERIMENTAL_2 => TcpOptionKindClass::Experimental,
TCP_OPTION_EOL
| TCP_OPTION_NOP
| TCP_OPTION_MSS
| TCP_OPTION_WINDOW_SCALE
| TCP_OPTION_SACK_PERMITTED
| TCP_OPTION_SACK
| 6
| 7
| TCP_OPTION_TIMESTAMP
| 9
| 10
| 11
| 12
| 13
| 14
| 15
| 18
| TCP_OPTION_MD5_SIGNATURE
| 27
| TCP_OPTION_USER_TIMEOUT
| TCP_OPTION_TCP_AUTHENTICATION
| TCP_OPTION_MPTCP
| TCP_OPTION_FAST_OPEN
| TCP_OPTION_TCP_ENO
| TCP_OPTION_ACCURATE_ECN_ORDER_0
| TCP_OPTION_ACCURATE_ECN_ORDER_1 => TcpOptionKindClass::Assigned,
_ => TcpOptionKindClass::Unassigned,
}
}
pub const fn tcp_option_kind_name(kind: u8) -> &'static str {
match kind {
TCP_OPTION_EOL => "EOL",
TCP_OPTION_NOP => "NOP",
TCP_OPTION_MSS => "MSS",
TCP_OPTION_WINDOW_SCALE => "WScale",
TCP_OPTION_SACK_PERMITTED => "SAckOK",
TCP_OPTION_SACK => "SAck",
TCP_OPTION_TIMESTAMP => "TS",
TCP_OPTION_MD5_SIGNATURE => "Md5",
TCP_OPTION_USER_TIMEOUT => "UTO",
TCP_OPTION_TCP_AUTHENTICATION => "AO",
TCP_OPTION_MPTCP => "MPTCP",
TCP_OPTION_FAST_OPEN => "FastOpen",
TCP_OPTION_TCP_ENO => "ENO",
TCP_OPTION_ACCURATE_ECN_ORDER_0 | TCP_OPTION_ACCURATE_ECN_ORDER_1 => "AccECN",
TCP_OPTION_EDO => "EDO",
TCP_OPTION_EXPERIMENTAL_1 | TCP_OPTION_EXPERIMENTAL_2 => "Exp",
_ => "opt",
}
}
pub const fn tcp_option_kind_is_assigned(kind: u8) -> bool {
matches!(
tcp_option_kind_class(kind),
TcpOptionKindClass::Assigned | TcpOptionKindClass::Experimental
)
}
pub const fn tcp_option_kind_is_experimental(kind: u8) -> bool {
matches!(
tcp_option_kind_class(kind),
TcpOptionKindClass::Experimental
)
}
pub const fn valid_window_scale(shift: u8) -> bool {
shift <= TCP_WINDOW_SCALE_MAX_SHIFT
}