#![allow(dead_code)]
use crate::error::{CrafterError, Result};
use crate::packet::Packet;
use crate::registry::ProtocolRegistry;
use super::checksum::{decoded_sctp_checksum_status, SctpChecksumStatus};
use super::chunk::decode_chunks;
use super::constants::{
SCTP_CHUNK_HEADER_LEN, SCTP_COMMON_HEADER_LEN, SCTP_UDP_ENCAPSULATION_PORT,
};
use super::packet::Sctp;
pub(crate) fn append_sctp_packet_with_registry(
registry: &ProtocolRegistry,
packet: Packet,
bytes: &[u8],
) -> Result<Packet> {
let decoded = decode_sctp_parts(bytes, registry.validates_checksums())?;
Ok(packet.push_sctp(decoded.sctp))
}
pub fn looks_like_sctp_payload(bytes: &[u8]) -> bool {
if bytes.len() < SCTP_COMMON_HEADER_LEN + SCTP_CHUNK_HEADER_LEN {
return false;
}
match decode_sctp_parts(bytes, false) {
Ok(decoded) => !decoded.sctp.is_chunks_empty(),
Err(_) => false,
}
}
pub fn looks_like_udp_encapsulated_sctp_payload(
source_port: u16,
destination_port: u16,
bytes: &[u8],
) -> bool {
(source_port == SCTP_UDP_ENCAPSULATION_PORT || destination_port == SCTP_UDP_ENCAPSULATION_PORT)
&& looks_like_sctp_payload(bytes)
}
pub fn looks_like_sctp_udp_encapsulation(
source_port: u16,
destination_port: u16,
bytes: &[u8],
) -> bool {
looks_like_udp_encapsulated_sctp_payload(source_port, destination_port, bytes)
}
struct DecodedSctpPacket {
sctp: Sctp,
}
fn decode_sctp_parts(bytes: &[u8], validate_checksum: bool) -> Result<DecodedSctpPacket> {
if bytes.len() < SCTP_COMMON_HEADER_LEN {
return Err(CrafterError::buffer_too_short(
"sctp common header",
SCTP_COMMON_HEADER_LEN,
bytes.len(),
));
}
let source_port = u16::from_be_bytes([bytes[0], bytes[1]]);
let destination_port = u16::from_be_bytes([bytes[2], bytes[3]]);
let verification_tag = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
let checksum = u32::from_be_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
let chunks = decode_chunks(&bytes[SCTP_COMMON_HEADER_LEN..])?;
let checksum_status = if validate_checksum {
decoded_sctp_checksum_status(bytes)?
} else {
SctpChecksumStatus::NotChecked
};
Ok(DecodedSctpPacket {
sctp: Sctp::from_decoded_parts_with_chunks_and_checksum_status(
source_port,
destination_port,
verification_tag,
checksum,
chunks,
checksum_status,
),
})
}
#[cfg(test)]
mod tests {
use crate::error::CrafterError;
use crate::packet::Packet;
use crate::registry::ProtocolRegistry;
use super::super::checksum::SctpChecksumStatus;
use super::super::chunk::{SctpChunk, SctpDataChunk};
use super::super::constants::{
SCTP_CHECKSUM_LEN, SCTP_CHECKSUM_OFFSET, SCTP_CHUNK_TYPE_DATA,
SCTP_CHUNK_TYPE_IETF_DEFINED_EXTENSION_4, SCTP_COMMON_HEADER_LEN,
};
use super::super::packet::Sctp;
use super::*;
fn read_checksum(bytes: &[u8]) -> u32 {
u32::from_be_bytes([
bytes[SCTP_CHECKSUM_OFFSET],
bytes[SCTP_CHECKSUM_OFFSET + 1],
bytes[SCTP_CHECKSUM_OFFSET + 2],
bytes[SCTP_CHECKSUM_OFFSET + 3],
])
}
#[test]
fn sctp_append_decoder_materializes_common_header_chunks_and_checksum_status() -> Result<()> {
let compiled = Packet::from_layer(
Sctp::new()
.sport(12_000)
.dport(12_001)
.vtag(0x1122_3344)
.chunk(SctpDataChunk::from_data(1, 2, 3, 4, b"payload".to_vec())),
)
.compile()?;
let decoded = append_sctp_packet_with_registry(
&ProtocolRegistry::new(),
Packet::new(),
compiled.as_bytes(),
)?;
let sctp = decoded.layer::<Sctp>().expect("decoded SCTP layer");
assert_eq!(sctp.source_port_value(), 12_000);
assert_eq!(sctp.destination_port_value(), 12_001);
assert_eq!(sctp.verification_tag_value(), 0x1122_3344);
assert_eq!(
sctp.checksum_value(),
Some(read_checksum(compiled.as_bytes()))
);
assert_eq!(sctp.checksum_status(), SctpChecksumStatus::Valid);
assert_eq!(sctp.chunk_count(), 1);
let SctpChunk::Data(data) = &sctp.chunks()[0] else {
panic!("expected DATA chunk");
};
assert_eq!(data.user_data()?, b"payload");
assert!(decoded.summary().contains("checksum_status=valid"));
Ok(())
}
#[test]
fn sctp_append_decoder_preserves_valid_unknown_chunks() -> Result<()> {
let compiled = Packet::from_layer(Sctp::new().chunk(SctpChunk::unknown(
SCTP_CHUNK_TYPE_IETF_DEFINED_EXTENSION_4,
0xa0,
[0xde, 0xad],
)))
.compile()?;
let decoded = append_sctp_packet_with_registry(
&ProtocolRegistry::new(),
Packet::new(),
compiled.as_bytes(),
)?;
let sctp = decoded.layer::<Sctp>().expect("decoded SCTP layer");
assert_eq!(sctp.chunk_count(), 1);
let SctpChunk::Unknown(unknown) = &sctp.chunks()[0] else {
panic!("expected UNKNOWN chunk");
};
assert_eq!(
unknown.chunk_type_value(),
SCTP_CHUNK_TYPE_IETF_DEFINED_EXTENSION_4
);
assert_eq!(unknown.flags(), 0xa0);
assert_eq!(unknown.value(), &[0xde, 0xad]);
assert_eq!(unknown.padding(), &[0x00, 0x00]);
Ok(())
}
#[test]
fn sctp_append_decoder_can_skip_checksum_validation() -> Result<()> {
let compiled = Packet::from_layer(Sctp::new().chunk(SctpDataChunk::from_data(
1,
2,
3,
4,
b"payload".to_vec(),
)))
.compile()?;
let registry = ProtocolRegistry::new().checksum_validation(false);
let decoded =
append_sctp_packet_with_registry(®istry, Packet::new(), compiled.as_bytes())?;
let sctp = decoded.layer::<Sctp>().expect("decoded SCTP layer");
assert_eq!(sctp.checksum_status(), SctpChecksumStatus::NotChecked);
assert!(!decoded.summary().contains("checksum_status="));
Ok(())
}
#[test]
fn sctp_append_decoder_reports_invalid_nonzero_checksum_without_dropping_layer() -> Result<()> {
let compiled = Packet::from_layer(Sctp::new()).compile()?;
let mut bytes = compiled.as_bytes().to_vec();
let mut bad_checksum = read_checksum(compiled.as_bytes()) ^ 0xffff_ffff;
if bad_checksum == 0 {
bad_checksum = 1;
}
bytes[SCTP_CHECKSUM_OFFSET..SCTP_CHECKSUM_OFFSET + SCTP_CHECKSUM_LEN]
.copy_from_slice(&bad_checksum.to_be_bytes());
let decoded =
append_sctp_packet_with_registry(&ProtocolRegistry::new(), Packet::new(), &bytes)?;
let sctp = decoded.layer::<Sctp>().expect("decoded SCTP layer");
assert_eq!(sctp.checksum_value(), Some(bad_checksum));
assert_eq!(sctp.checksum_status(), SctpChecksumStatus::Invalid);
Ok(())
}
#[test]
fn sctp_append_decoder_rejects_short_common_header() {
let short = [0u8; SCTP_COMMON_HEADER_LEN - 1];
let err = append_sctp_packet_with_registry(&ProtocolRegistry::new(), Packet::new(), &short)
.unwrap_err();
assert_eq!(
err,
CrafterError::BufferTooShort {
context: "sctp common header",
required: SCTP_COMMON_HEADER_LEN,
available: short.len(),
}
);
}
#[test]
fn sctp_udp_encap_shape_accepts_only_port_9899_structural_sctp() -> Result<()> {
let compiled = Packet::from_layer(Sctp::new().chunk(SctpDataChunk::from_data(
1,
2,
3,
4,
b"payload".to_vec(),
)))
.compile()?;
let sctp = compiled.as_bytes();
assert!(looks_like_sctp_payload(sctp));
assert!(looks_like_udp_encapsulated_sctp_payload(
SCTP_UDP_ENCAPSULATION_PORT,
49_152,
sctp
));
assert!(looks_like_sctp_udp_encapsulation(
49_152,
SCTP_UDP_ENCAPSULATION_PORT,
sctp
));
assert!(!looks_like_udp_encapsulated_sctp_payload(
49_152, 49_153, sctp
));
let header_only = Packet::from_layer(Sctp::new()).compile()?;
assert!(!looks_like_sctp_payload(header_only.as_bytes()));
assert!(!looks_like_udp_encapsulated_sctp_payload(
SCTP_UDP_ENCAPSULATION_PORT,
49_152,
header_only.as_bytes()
));
let unrelated = b"not an sctp packet";
assert!(!looks_like_udp_encapsulated_sctp_payload(
SCTP_UDP_ENCAPSULATION_PORT,
49_152,
unrelated
));
let mut truncated_chunk = vec![0u8; SCTP_COMMON_HEADER_LEN];
truncated_chunk.extend_from_slice(&[SCTP_CHUNK_TYPE_DATA, 0x00, 0x00]);
assert!(!looks_like_udp_encapsulated_sctp_payload(
SCTP_UDP_ENCAPSULATION_PORT,
49_152,
&truncated_chunk
));
Ok(())
}
}