use crate::chunk::Chunk;
use crate::chunk::chunk_abort::ChunkAbort;
use crate::chunk::chunk_cookie_ack::ChunkCookieAck;
use crate::chunk::chunk_cookie_echo::ChunkCookieEcho;
use crate::chunk::chunk_error::ChunkError;
use crate::chunk::chunk_forward_tsn::ChunkForwardTsn;
use crate::chunk::chunk_header::*;
use crate::chunk::chunk_heartbeat::ChunkHeartbeat;
use crate::chunk::chunk_init::ChunkInit;
use crate::chunk::chunk_payload_data::ChunkPayloadData;
use crate::chunk::chunk_reconfig::ChunkReconfig;
use crate::chunk::chunk_selective_ack::ChunkSelectiveAck;
use crate::chunk::chunk_shutdown::ChunkShutdown;
use crate::chunk::chunk_shutdown_ack::ChunkShutdownAck;
use crate::chunk::chunk_shutdown_complete::ChunkShutdownComplete;
use crate::chunk::chunk_type::*;
use crate::util::*;
use shared::error::{Error, Result};
use bytes::{Buf, BufMut, Bytes, BytesMut};
use std::fmt;
pub(crate) const PACKET_HEADER_SIZE: usize = 12;
#[derive(Default, Debug)]
pub(crate) struct CommonHeader {
pub(crate) source_port: u16,
pub(crate) destination_port: u16,
pub(crate) verification_tag: u32,
}
#[derive(Default, Debug)]
pub struct PartialDecode {
pub(crate) common_header: CommonHeader,
pub(crate) remaining: Bytes,
pub(crate) first_chunk_type: ChunkType,
pub(crate) initiate_tag: Option<u32>,
pub(crate) cookie: Option<Bytes>,
}
impl PartialDecode {
pub(crate) fn unmarshal(raw: &Bytes) -> Result<Self> {
if raw.len() < PACKET_HEADER_SIZE {
return Err(Error::ErrPacketRawTooSmall);
}
let reader = &mut raw.clone();
let source_port = reader.get_u16();
let destination_port = reader.get_u16();
let verification_tag = reader.get_u32();
let their_checksum = reader.get_u32_le();
let our_checksum = generate_packet_checksum(raw);
if their_checksum != our_checksum {
return Err(Error::ErrChecksumMismatch);
}
if reader.remaining() < CHUNK_HEADER_SIZE {
return Err(Error::ErrParseSctpChunkNotEnoughData);
}
let header = ChunkHeader::unmarshal(reader)?;
reader.advance(CHUNK_HEADER_SIZE);
let mut initiate_tag = None;
let mut cookie = None;
match header.typ {
CT_INIT | CT_INIT_ACK => {
initiate_tag = Some(reader.get_u32());
}
CT_COOKIE_ECHO => {
cookie = Some(raw.slice(
PACKET_HEADER_SIZE + CHUNK_HEADER_SIZE
..PACKET_HEADER_SIZE + CHUNK_HEADER_SIZE + header.value_length(),
));
}
_ => {}
}
Ok(PartialDecode {
common_header: CommonHeader {
source_port,
destination_port,
verification_tag,
},
remaining: raw.slice(PACKET_HEADER_SIZE..),
first_chunk_type: header.typ,
initiate_tag,
cookie,
})
}
pub(crate) fn finish(self) -> Result<Packet> {
let mut chunks = vec![];
let mut offset = 0;
loop {
if offset == self.remaining.len() {
break;
} else if offset + CHUNK_HEADER_SIZE > self.remaining.len() {
return Err(Error::ErrParseSctpChunkNotEnoughData);
}
let chunk_buf = slice_chunk(&self.remaining, offset)?;
let ct = ChunkType(self.remaining[offset]);
let c: Box<dyn Chunk> = match ct {
CT_INIT => Box::new(ChunkInit::unmarshal(&chunk_buf)?),
CT_INIT_ACK => Box::new(ChunkInit::unmarshal(&chunk_buf)?),
CT_ABORT => Box::new(ChunkAbort::unmarshal(&chunk_buf)?),
CT_COOKIE_ECHO => Box::new(ChunkCookieEcho::unmarshal(&chunk_buf)?),
CT_COOKIE_ACK => Box::new(ChunkCookieAck::unmarshal(&chunk_buf)?),
CT_HEARTBEAT => Box::new(ChunkHeartbeat::unmarshal(&chunk_buf)?),
CT_PAYLOAD_DATA => Box::new(ChunkPayloadData::unmarshal(&chunk_buf)?),
CT_SACK => Box::new(ChunkSelectiveAck::unmarshal(&chunk_buf)?),
CT_RECONFIG => Box::new(ChunkReconfig::unmarshal(&chunk_buf)?),
CT_FORWARD_TSN => Box::new(ChunkForwardTsn::unmarshal(&chunk_buf)?),
CT_ERROR => Box::new(ChunkError::unmarshal(&chunk_buf)?),
CT_SHUTDOWN => Box::new(ChunkShutdown::unmarshal(&chunk_buf)?),
CT_SHUTDOWN_ACK => Box::new(ChunkShutdownAck::unmarshal(&chunk_buf)?),
CT_SHUTDOWN_COMPLETE => Box::new(ChunkShutdownComplete::unmarshal(&chunk_buf)?),
_ => return Err(Error::ErrUnmarshalUnknownChunkType),
};
let chunk_value_padding = get_padding_size(c.value_length());
offset += CHUNK_HEADER_SIZE + c.value_length() + chunk_value_padding;
chunks.push(c);
}
Ok(Packet {
common_header: self.common_header,
chunks,
})
}
}
fn slice_chunk(raw: &Bytes, offset: usize) -> Result<Bytes> {
let chunk_length = u16::from_be_bytes([raw[offset + 2], raw[offset + 3]]) as usize;
if chunk_length < CHUNK_HEADER_SIZE {
return Err(Error::ErrChunkHeaderInvalidLength);
}
if offset + chunk_length > raw.len() {
return Err(Error::ErrChunkHeaderNotEnoughSpace);
}
Ok(raw.slice(offset..offset + chunk_length))
}
#[derive(Default, Debug)]
pub(crate) struct Packet {
pub(crate) common_header: CommonHeader,
pub(crate) chunks: Vec<Box<dyn Chunk>>,
}
impl fmt::Display for Packet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut res = format!(
"Packet:
source_port: {}
destination_port: {}
verification_tag: {}
",
self.common_header.source_port,
self.common_header.destination_port,
self.common_header.verification_tag,
);
for chunk in &self.chunks {
res += format!("Chunk: {}", chunk).as_str();
}
write!(f, "{}", res)
}
}
impl Packet {
pub(crate) fn unmarshal(raw: &Bytes) -> Result<Self> {
if raw.len() < PACKET_HEADER_SIZE {
return Err(Error::ErrPacketRawTooSmall);
}
let reader = &mut raw.clone();
let source_port = reader.get_u16();
let destination_port = reader.get_u16();
let verification_tag = reader.get_u32();
let their_checksum = reader.get_u32_le();
let our_checksum = generate_packet_checksum(raw);
if their_checksum != our_checksum {
return Err(Error::ErrChecksumMismatch);
}
let mut chunks = vec![];
let mut offset = PACKET_HEADER_SIZE;
loop {
if offset == raw.len() {
break;
} else if offset + CHUNK_HEADER_SIZE > raw.len() {
return Err(Error::ErrParseSctpChunkNotEnoughData);
}
let chunk_buf = slice_chunk(raw, offset)?;
let ct = ChunkType(raw[offset]);
let c: Box<dyn Chunk> = match ct {
CT_INIT => Box::new(ChunkInit::unmarshal(&chunk_buf)?),
CT_INIT_ACK => Box::new(ChunkInit::unmarshal(&chunk_buf)?),
CT_ABORT => Box::new(ChunkAbort::unmarshal(&chunk_buf)?),
CT_COOKIE_ECHO => Box::new(ChunkCookieEcho::unmarshal(&chunk_buf)?),
CT_COOKIE_ACK => Box::new(ChunkCookieAck::unmarshal(&chunk_buf)?),
CT_HEARTBEAT => Box::new(ChunkHeartbeat::unmarshal(&chunk_buf)?),
CT_PAYLOAD_DATA => Box::new(ChunkPayloadData::unmarshal(&chunk_buf)?),
CT_SACK => Box::new(ChunkSelectiveAck::unmarshal(&chunk_buf)?),
CT_RECONFIG => Box::new(ChunkReconfig::unmarshal(&chunk_buf)?),
CT_FORWARD_TSN => Box::new(ChunkForwardTsn::unmarshal(&chunk_buf)?),
CT_ERROR => Box::new(ChunkError::unmarshal(&chunk_buf)?),
CT_SHUTDOWN => Box::new(ChunkShutdown::unmarshal(&chunk_buf)?),
CT_SHUTDOWN_ACK => Box::new(ChunkShutdownAck::unmarshal(&chunk_buf)?),
CT_SHUTDOWN_COMPLETE => Box::new(ChunkShutdownComplete::unmarshal(&chunk_buf)?),
_ => return Err(Error::ErrUnmarshalUnknownChunkType),
};
let chunk_value_padding = get_padding_size(c.value_length());
offset += CHUNK_HEADER_SIZE + c.value_length() + chunk_value_padding;
chunks.push(c);
}
Ok(Packet {
common_header: CommonHeader {
source_port,
destination_port,
verification_tag,
},
chunks,
})
}
pub(crate) fn marshaled_len(&self) -> usize {
let body_len: usize = self
.chunks
.iter()
.map(|c| {
let n = CHUNK_HEADER_SIZE + c.value_length();
n + get_padding_size(n)
})
.sum();
PACKET_HEADER_SIZE + body_len
}
pub(crate) fn marshal_to(&self, writer: &mut BytesMut) -> Result<usize> {
writer.reserve(self.marshaled_len());
Self::write_framed(
&self.common_header,
self.chunks.iter().map(|c| &**c as &dyn Chunk),
writer,
)
}
pub(crate) fn marshal(&self) -> Result<Bytes> {
let mut buf = BytesMut::with_capacity(self.marshaled_len());
Self::write_framed(
&self.common_header,
self.chunks.iter().map(|c| &**c as &dyn Chunk),
&mut buf,
)
.map(|_| buf.freeze())
}
pub(crate) fn write_framed<'a>(
common_header: &CommonHeader,
chunks: impl Iterator<Item = &'a dyn Chunk>,
writer: &mut BytesMut,
) -> Result<usize> {
let start = writer.len();
writer.put_u16(common_header.source_port);
writer.put_u16(common_header.destination_port);
writer.put_u32(common_header.verification_tag);
let checksum_offset = writer.len();
writer.put_u32(0);
let chunks_start = writer.len();
for c in chunks {
c.marshal_to(writer)?;
let padding_needed = get_padding_size(writer.len() - chunks_start);
if padding_needed != 0 {
writer.put_bytes(0, padding_needed);
}
}
let checksum = crc32c::crc32c(&writer[start..]);
writer[checksum_offset..checksum_offset + 4].copy_from_slice(&checksum.to_le_bytes());
Ok(writer.len())
}
}
impl Packet {
pub(crate) fn check_packet(&self) -> Result<()> {
if self.common_header.source_port == 0 {
return Err(Error::ErrSctpPacketSourcePortZero);
}
if self.common_header.destination_port == 0 {
return Err(Error::ErrSctpPacketDestinationPortZero);
}
for c in &self.chunks {
if let Some(ci) = c.as_any().downcast_ref::<ChunkInit>()
&& !ci.is_ack
{
if self.chunks.len() != 1 {
return Err(Error::ErrInitChunkBundled);
}
if self.common_header.verification_tag != 0 {
return Err(Error::ErrInitChunkVerifyTagNotZero);
}
}
}
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_packet_unmarshal() -> Result<()> {
let result = Packet::unmarshal(&Bytes::new());
assert!(
result.is_err(),
"Unmarshal should fail when a packet is too small to be SCTP"
);
let header_only = Bytes::from_static(&[
0x13, 0x88, 0x13, 0x88, 0x00, 0x00, 0x00, 0x00, 0x06, 0xa9, 0x00, 0xe1,
]);
let pkt = Packet::unmarshal(&header_only)?;
assert_eq!(
pkt.common_header.source_port, 5000,
"Unmarshal passed for SCTP packet, but got incorrect source port exp: {} act: {}",
5000, pkt.common_header.source_port
);
assert_eq!(
pkt.common_header.destination_port, 5000,
"Unmarshal passed for SCTP packet, but got incorrect destination port exp: {} act: {}",
5000, pkt.common_header.destination_port
);
assert_eq!(
pkt.common_header.verification_tag, 0,
"Unmarshal passed for SCTP packet, but got incorrect verification tag exp: {} act: {}",
0, pkt.common_header.verification_tag
);
let raw_chunk = Bytes::from_static(&[
0x13, 0x88, 0x13, 0x88, 0x00, 0x00, 0x00, 0x00, 0x81, 0x46, 0x9d, 0xfc, 0x01, 0x00,
0x00, 0x56, 0x55, 0xb9, 0x64, 0xa5, 0x00, 0x02, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00,
0xe8, 0x6d, 0x10, 0x30, 0xc0, 0x00, 0x00, 0x04, 0x80, 0x08, 0x00, 0x09, 0xc0, 0x0f,
0xc1, 0x80, 0x82, 0x00, 0x00, 0x00, 0x80, 0x02, 0x00, 0x24, 0x9f, 0xeb, 0xbb, 0x5c,
0x50, 0xc9, 0xbf, 0x75, 0x9c, 0xb1, 0x2c, 0x57, 0x4f, 0xa4, 0x5a, 0x51, 0xba, 0x60,
0x17, 0x78, 0x27, 0x94, 0x5c, 0x31, 0xe6, 0x5d, 0x5b, 0x09, 0x47, 0xe2, 0x22, 0x06,
0x80, 0x04, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x80, 0x03, 0x00, 0x06, 0x80, 0xc1,
0x00, 0x00,
]);
Packet::unmarshal(&raw_chunk)?;
Ok(())
}
#[test]
fn test_packet_marshal() -> Result<()> {
let header_only = Bytes::from_static(&[
0x13, 0x88, 0x13, 0x88, 0x00, 0x00, 0x00, 0x00, 0x06, 0xa9, 0x00, 0xe1,
]);
let pkt = Packet::unmarshal(&header_only)?;
let header_only_marshaled = pkt.marshal()?;
assert_eq!(
header_only, header_only_marshaled,
"Unmarshal/Marshaled header only packet did not match \nheaderOnly: {:?} \nheader_only_marshaled {:?}",
header_only, header_only_marshaled
);
Ok(())
}
#[test]
fn test_marshal_data_chunks_matches_packet_marshal() -> Result<()> {
use crate::chunk::chunk_payload_data::PayloadProtocolIdentifier;
let mk_common = || CommonHeader {
source_port: 5000,
destination_port: 5000,
verification_tag: 0xdeadbeef,
};
let mk_chunk = |tsn: u32, ssn: u16, data: &'static [u8]| ChunkPayloadData {
stream_identifier: 1,
payload_type: PayloadProtocolIdentifier::Binary,
user_data: Bytes::from_static(data),
beginning_fragment: true,
ending_fragment: true,
tsn,
stream_sequence_number: ssn,
..Default::default()
};
let cases = [
vec![mk_chunk(1, 0, b"hello")],
vec![mk_chunk(1, 0, b"hello"), mk_chunk(2, 1, b"world!!")],
];
for chunks in cases {
let direct = {
let mut buf = BytesMut::new();
Packet::write_framed(
&mk_common(),
chunks.iter().map(|c| c as &dyn Chunk),
&mut buf,
)?;
buf.freeze()
};
let pkt = Packet {
common_header: mk_common(),
chunks: chunks
.iter()
.cloned()
.map(|c| Box::new(c) as Box<dyn Chunk>)
.collect(),
};
assert_eq!(
direct,
pkt.marshal()?,
"write_framed DATA path must equal Packet::marshal"
);
let mut buf = BytesMut::new();
pkt.marshal_to(&mut buf)?;
assert_eq!(
direct,
buf.freeze(),
"marshal_to must equal the write_framed DATA path"
);
}
Ok(())
}
#[test]
fn test_partial_decode_init_chunk() -> Result<()> {
let raw_pkt = Bytes::from_static(&[
0x13, 0x88, 0x13, 0x88, 0x00, 0x00, 0x00, 0x00, 0x81, 0x46, 0x9d, 0xfc, 0x01, 0x00,
0x00, 0x56, 0x55, 0xb9, 0x64, 0xa5, 0x00, 0x02, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00,
0xe8, 0x6d, 0x10, 0x30, 0xc0, 0x00, 0x00, 0x04, 0x80, 0x08, 0x00, 0x09, 0xc0, 0x0f,
0xc1, 0x80, 0x82, 0x00, 0x00, 0x00, 0x80, 0x02, 0x00, 0x24, 0x9f, 0xeb, 0xbb, 0x5c,
0x50, 0xc9, 0xbf, 0x75, 0x9c, 0xb1, 0x2c, 0x57, 0x4f, 0xa4, 0x5a, 0x51, 0xba, 0x60,
0x17, 0x78, 0x27, 0x94, 0x5c, 0x31, 0xe6, 0x5d, 0x5b, 0x09, 0x47, 0xe2, 0x22, 0x06,
0x80, 0x04, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x80, 0x03, 0x00, 0x06, 0x80, 0xc1,
0x00, 0x00,
]);
let pkt = PartialDecode::unmarshal(&raw_pkt)?;
assert_eq!(pkt.first_chunk_type, CT_INIT);
if let Some(initiate_tag) = pkt.initiate_tag {
assert_eq!(
initiate_tag, 1438213285,
"Unmarshal passed for SCTP packet, but got incorrect initiate tag exp: {} act: {}",
1438213285, initiate_tag
);
}
Ok(())
}
#[test]
fn test_partial_decode_init_ack() -> Result<()> {
let raw_pkt = Bytes::from_static(&[
0x13, 0x88, 0x13, 0x88, 0xce, 0x15, 0x79, 0xa2, 0x96, 0x19, 0xe8, 0xb2, 0x02, 0x00,
0x00, 0x1c, 0xeb, 0x81, 0x4e, 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00,
0x50, 0xdf, 0x90, 0xd9, 0x00, 0x07, 0x00, 0x08, 0x94, 0x06, 0x2f, 0x93,
]);
let pkt = PartialDecode::unmarshal(&raw_pkt)?;
assert_eq!(pkt.first_chunk_type, CT_INIT_ACK);
if let Some(initiate_tag) = pkt.initiate_tag {
assert_eq!(
initiate_tag, 3951119873u32,
"Unmarshal passed for SCTP packet, but got incorrect initiate tag exp: {} act: {}",
3951119873u32, initiate_tag
);
}
Ok(())
}
#[test]
fn test_unmarshal_variable_length_chunks_followed_by_other_chunks() -> Result<()> {
use crate::chunk::chunk_payload_data::PayloadProtocolIdentifier;
use crate::chunk::{ErrorCause, PROTOCOL_VIOLATION, UNRECOGNIZED_CHUNK_TYPE};
let original = Packet {
common_header: CommonHeader {
source_port: 5000,
destination_port: 5000,
verification_tag: 0xdeadbeef,
},
chunks: vec![
Box::new(ChunkForwardTsn {
new_cumulative_tsn: 3,
streams: vec![],
}),
Box::new(ChunkAbort {
error_causes: vec![ErrorCause {
code: PROTOCOL_VIOLATION,
..Default::default()
}],
}),
Box::new(ChunkError {
error_causes: vec![ErrorCause {
code: UNRECOGNIZED_CHUNK_TYPE,
raw: Bytes::from_static(&[0xc0, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03]),
}],
}),
Box::new(ChunkShutdown {
cumulative_tsn_ack: 0x12345678,
}),
Box::new(ChunkPayloadData {
unordered: true,
beginning_fragment: true,
ending_fragment: true,
tsn: 4,
stream_identifier: 1,
payload_type: PayloadProtocolIdentifier::Binary,
user_data: Bytes::from_static(&[0xaa, 0xbb, 0xcc, 0xdd]),
..Default::default()
}),
],
};
let raw = original.marshal()?;
let parsed = Packet::unmarshal(&raw)?;
assert_eq!(parsed.chunks.len(), 5);
let fwd = parsed.chunks[0]
.as_any()
.downcast_ref::<ChunkForwardTsn>()
.expect("chunks[0] should be FORWARD-TSN");
assert_eq!(fwd.new_cumulative_tsn, 3);
assert!(fwd.streams.is_empty());
let abort = parsed.chunks[1]
.as_any()
.downcast_ref::<ChunkAbort>()
.expect("chunks[1] should be ABORT");
assert_eq!(abort.error_causes.len(), 1);
assert_eq!(abort.error_causes[0].error_cause_code(), PROTOCOL_VIOLATION);
let err = parsed.chunks[2]
.as_any()
.downcast_ref::<ChunkError>()
.expect("chunks[2] should be ERROR");
assert_eq!(err.error_causes.len(), 1);
assert_eq!(
err.error_causes[0].error_cause_code(),
UNRECOGNIZED_CHUNK_TYPE
);
let shutdown = parsed.chunks[3]
.as_any()
.downcast_ref::<ChunkShutdown>()
.expect("chunks[3] should be SHUTDOWN");
assert_eq!(shutdown.cumulative_tsn_ack, 0x12345678);
let data = parsed.chunks[4]
.as_any()
.downcast_ref::<ChunkPayloadData>()
.expect("chunks[4] should be DATA");
assert_eq!(data.tsn, 4);
assert_eq!(data.stream_identifier, 1);
assert_eq!(data.payload_type, PayloadProtocolIdentifier::Binary);
assert_eq!(&data.user_data[..], &[0xaa, 0xbb, 0xcc, 0xdd]);
Ok(())
}
}