diff --git a/Packet++/CMakeLists.txt b/Packet++/CMakeLists.txt index c4306ea1ff..059297bee0 100644 --- a/Packet++/CMakeLists.txt +++ b/Packet++/CMakeLists.txt @@ -46,6 +46,7 @@ add_library( src/RadiusLayer.cpp src/RawPacket.cpp src/S7CommLayer.cpp + src/SctpLayer.cpp src/SdpLayer.cpp src/SingleCommandTextProtocol.cpp src/SipLayer.cpp @@ -125,6 +126,7 @@ set( header/RadiusLayer.h header/RawPacket.h header/S7CommLayer.h + header/SctpLayer.h header/SdpLayer.h header/SingleCommandTextProtocol.h header/SipLayer.h diff --git a/Packet++/header/IPv4Layer.h b/Packet++/header/IPv4Layer.h index 20829b9f4e..699bc72283 100644 --- a/Packet++/header/IPv4Layer.h +++ b/Packet++/header/IPv4Layer.h @@ -97,6 +97,8 @@ namespace pcpp PACKETPP_IPPROTO_DSTOPTS = 60, /// VRRP protocol PACKETPP_IPPROTO_VRRP = 112, + /// SCTP (Stream Control Transmission Protocol) + PACKETPP_IPPROTO_SCTP = 132, /// Raw IP packets PACKETPP_IPPROTO_RAW = 255, /// Maximum value diff --git a/Packet++/header/ProtocolType.h b/Packet++/header/ProtocolType.h index 9e3c658f12..9351014b03 100644 --- a/Packet++/header/ProtocolType.h +++ b/Packet++/header/ProtocolType.h @@ -254,6 +254,9 @@ namespace pcpp /// FTP protocol family (FTPControl and FtpData protocols) const ProtocolTypeFamily FTP = 0x3c29; + /// SCTP (Stream Control Transmission Protocol) + const ProtocolType SCTP = 62; + /// @} /// An enum representing OSI model layers diff --git a/Packet++/header/SctpLayer.h b/Packet++/header/SctpLayer.h new file mode 100644 index 0000000000..fe85551dbf --- /dev/null +++ b/Packet++/header/SctpLayer.h @@ -0,0 +1,3587 @@ +#pragma once + +#include "Layer.h" +#include "IpAddress.h" +#include + +/// @file + +/// @namespace pcpp +/// @brief The main namespace for the PcapPlusPlus lib +namespace pcpp +{ + /// @struct sctphdr + /// Represents the SCTP common header (RFC 9260, Section 3) +#pragma pack(push, 1) + struct sctphdr + { + /// Source port number (16 bits) + uint16_t portSrc; + /// Destination port number (16 bits) + uint16_t portDst; + /// Verification tag (32 bits) + uint32_t verificationTag; + /// Checksum (32 bits) - CRC32c + uint32_t checksum; + }; +#pragma pack(pop) + static_assert(sizeof(sctphdr) == 12, "sctphdr size must be 12 bytes"); + + /// @struct sctp_chunk_hdr + /// Common header for all SCTP chunks (RFC 9260, Section 3.2) +#pragma pack(push, 1) + struct sctp_chunk_hdr + { + /// Chunk type (8 bits) + uint8_t type; + /// Chunk flags (8 bits) + uint8_t flags; + /// Chunk length (16 bits) - includes header, excludes padding + uint16_t length; + }; +#pragma pack(pop) + static_assert(sizeof(sctp_chunk_hdr) == 4, "sctp_chunk_hdr size must be 4 bytes"); + + /// @struct sctp_data_chunk + /// DATA chunk header (RFC 9260, Section 3.3.1) +#pragma pack(push, 1) + struct sctp_data_chunk + { + /// Chunk type = 0 + uint8_t type; + /// Chunk flags: E(0), B(1), U(2), I(3) + uint8_t flags; + /// Chunk length + uint16_t length; + /// Transmission Sequence Number + uint32_t tsn; + /// Stream Identifier + uint16_t streamId; + /// Stream Sequence Number + uint16_t streamSeqNum; + /// Payload Protocol Identifier + uint32_t ppid; + // User data follows + }; +#pragma pack(pop) + static_assert(sizeof(sctp_data_chunk) == 16, "sctp_data_chunk size must be 16 bytes"); + + /// @struct sctp_init_chunk + /// INIT chunk header (RFC 9260, Section 3.3.2) +#pragma pack(push, 1) + struct sctp_init_chunk + { + /// Chunk type = 1 + uint8_t type; + /// Chunk flags (reserved) + uint8_t flags; + /// Chunk length + uint16_t length; + /// Initiate Tag + uint32_t initiateTag; + /// Advertised Receiver Window Credit + uint32_t arwnd; + /// Number of Outbound Streams + uint16_t numOutboundStreams; + /// Number of Inbound Streams + uint16_t numInboundStreams; + /// Initial TSN + uint32_t initialTsn; + // Optional parameters follow + }; +#pragma pack(pop) + static_assert(sizeof(sctp_init_chunk) == 20, "sctp_init_chunk size must be 20 bytes"); + + /// @struct sctp_init_ack_chunk + /// INIT ACK chunk header (RFC 9260, Section 3.3.3) + /// Same structure as INIT chunk + using sctp_init_ack_chunk = sctp_init_chunk; + + /// @struct sctp_sack_chunk + /// SACK chunk header (RFC 9260, Section 3.3.4) +#pragma pack(push, 1) + struct sctp_sack_chunk + { + /// Chunk type = 3 + uint8_t type; + /// Chunk flags (reserved) + uint8_t flags; + /// Chunk length + uint16_t length; + /// Cumulative TSN Ack + uint32_t cumulativeTsnAck; + /// Advertised Receiver Window Credit + uint32_t arwnd; + /// Number of Gap Ack Blocks + uint16_t numGapBlocks; + /// Number of Duplicate TSNs + uint16_t numDupTsns; + // Gap Ack Blocks and Duplicate TSNs follow + }; +#pragma pack(pop) + static_assert(sizeof(sctp_sack_chunk) == 16, "sctp_sack_chunk size must be 16 bytes"); + + /// @struct sctp_gap_ack_block + /// Gap Ack Block structure for SACK chunk +#pragma pack(push, 1) + struct sctp_gap_ack_block + { + /// Gap Ack Block Start (offset from Cumulative TSN Ack) + uint16_t start; + /// Gap Ack Block End (offset from Cumulative TSN Ack) + uint16_t end; + }; +#pragma pack(pop) + static_assert(sizeof(sctp_gap_ack_block) == 4, "sctp_gap_ack_block size must be 4 bytes"); + + /// @struct sctp_heartbeat_chunk + /// HEARTBEAT chunk header (RFC 9260, Section 3.3.5) +#pragma pack(push, 1) + struct sctp_heartbeat_chunk + { + /// Chunk type = 4 + uint8_t type; + /// Chunk flags (reserved) + uint8_t flags; + /// Chunk length + uint16_t length; + // Heartbeat Information TLV follows + }; +#pragma pack(pop) + static_assert(sizeof(sctp_heartbeat_chunk) == 4, "sctp_heartbeat_chunk size must be 4 bytes"); + + /// @struct sctp_heartbeat_ack_chunk + /// HEARTBEAT ACK chunk header (RFC 9260, Section 3.3.6) + using sctp_heartbeat_ack_chunk = sctp_heartbeat_chunk; + + /// @struct sctp_abort_chunk + /// ABORT chunk header (RFC 9260, Section 3.3.7) +#pragma pack(push, 1) + struct sctp_abort_chunk + { + /// Chunk type = 6 + uint8_t type; + /// Chunk flags: T bit (0) + uint8_t flags; + /// Chunk length + uint16_t length; + // Zero or more Error Causes follow + }; +#pragma pack(pop) + static_assert(sizeof(sctp_abort_chunk) == 4, "sctp_abort_chunk size must be 4 bytes"); + + /// @struct sctp_shutdown_chunk + /// SHUTDOWN chunk header (RFC 9260, Section 3.3.8) +#pragma pack(push, 1) + struct sctp_shutdown_chunk + { + /// Chunk type = 7 + uint8_t type; + /// Chunk flags (reserved) + uint8_t flags; + /// Chunk length = 8 + uint16_t length; + /// Cumulative TSN Ack + uint32_t cumulativeTsnAck; + }; +#pragma pack(pop) + static_assert(sizeof(sctp_shutdown_chunk) == 8, "sctp_shutdown_chunk size must be 8 bytes"); + + /// @struct sctp_shutdown_ack_chunk + /// SHUTDOWN ACK chunk header (RFC 9260, Section 3.3.9) +#pragma pack(push, 1) + struct sctp_shutdown_ack_chunk + { + /// Chunk type = 8 + uint8_t type; + /// Chunk flags (reserved) + uint8_t flags; + /// Chunk length = 4 + uint16_t length; + }; +#pragma pack(pop) + static_assert(sizeof(sctp_shutdown_ack_chunk) == 4, "sctp_shutdown_ack_chunk size must be 4 bytes"); + + /// @struct sctp_error_chunk + /// ERROR chunk header (RFC 9260, Section 3.3.10) +#pragma pack(push, 1) + struct sctp_error_chunk + { + /// Chunk type = 9 + uint8_t type; + /// Chunk flags (reserved) + uint8_t flags; + /// Chunk length + uint16_t length; + // One or more Error Causes follow + }; +#pragma pack(pop) + static_assert(sizeof(sctp_error_chunk) == 4, "sctp_error_chunk size must be 4 bytes"); + + /// @struct sctp_cookie_echo_chunk + /// COOKIE ECHO chunk header (RFC 9260, Section 3.3.11) +#pragma pack(push, 1) + struct sctp_cookie_echo_chunk + { + /// Chunk type = 10 + uint8_t type; + /// Chunk flags (reserved) + uint8_t flags; + /// Chunk length + uint16_t length; + // Cookie follows (variable length) + }; +#pragma pack(pop) + static_assert(sizeof(sctp_cookie_echo_chunk) == 4, "sctp_cookie_echo_chunk size must be 4 bytes"); + + /// @struct sctp_cookie_ack_chunk + /// COOKIE ACK chunk header (RFC 9260, Section 3.3.12) +#pragma pack(push, 1) + struct sctp_cookie_ack_chunk + { + /// Chunk type = 11 + uint8_t type; + /// Chunk flags (reserved) + uint8_t flags; + /// Chunk length = 4 + uint16_t length; + }; +#pragma pack(pop) + static_assert(sizeof(sctp_cookie_ack_chunk) == 4, "sctp_cookie_ack_chunk size must be 4 bytes"); + + /// @struct sctp_ecne_chunk + /// ECNE chunk header (RFC 9260, Section 3.3.13) +#pragma pack(push, 1) + struct sctp_ecne_chunk + { + /// Chunk type = 12 + uint8_t type; + /// Chunk flags (reserved) + uint8_t flags; + /// Chunk length = 8 + uint16_t length; + /// Lowest TSN Number + uint32_t lowestTsn; + }; +#pragma pack(pop) + static_assert(sizeof(sctp_ecne_chunk) == 8, "sctp_ecne_chunk size must be 8 bytes"); + + /// @struct sctp_cwr_chunk + /// CWR chunk header (RFC 9260, Section 3.3.14) +#pragma pack(push, 1) + struct sctp_cwr_chunk + { + /// Chunk type = 13 + uint8_t type; + /// Chunk flags (reserved) + uint8_t flags; + /// Chunk length = 8 + uint16_t length; + /// Lowest TSN Number + uint32_t lowestTsn; + }; +#pragma pack(pop) + static_assert(sizeof(sctp_cwr_chunk) == 8, "sctp_cwr_chunk size must be 8 bytes"); + + /// @struct sctp_shutdown_complete_chunk + /// SHUTDOWN COMPLETE chunk header (RFC 9260, Section 3.3.15) +#pragma pack(push, 1) + struct sctp_shutdown_complete_chunk + { + /// Chunk type = 14 + uint8_t type; + /// Chunk flags: T bit (0) + uint8_t flags; + /// Chunk length = 4 + uint16_t length; + }; +#pragma pack(pop) + static_assert(sizeof(sctp_shutdown_complete_chunk) == 4, "sctp_shutdown_complete_chunk size must be 4 bytes"); + + /// @struct sctp_auth_chunk + /// AUTH chunk header (RFC 4895) +#pragma pack(push, 1) + struct sctp_auth_chunk + { + /// Chunk type = 15 + uint8_t type; + /// Chunk flags (reserved) + uint8_t flags; + /// Chunk length + uint16_t length; + /// Shared Key Identifier + uint16_t sharedKeyId; + /// HMAC Identifier + uint16_t hmacId; + // HMAC follows (variable length) + }; +#pragma pack(pop) + static_assert(sizeof(sctp_auth_chunk) == 8, "sctp_auth_chunk size must be 8 bytes"); + + /// @struct sctp_idata_chunk + /// I-DATA chunk header (RFC 8260) +#pragma pack(push, 1) + struct sctp_idata_chunk + { + /// Chunk type = 64 + uint8_t type; + /// Chunk flags: E(0), B(1), U(2), I(3) + uint8_t flags; + /// Chunk length + uint16_t length; + /// Transmission Sequence Number + uint32_t tsn; + /// Stream Identifier + uint16_t streamId; + /// Reserved + uint16_t reserved; + /// Message Identifier + uint32_t mid; + /// PPID (if B=1) or FSN (if B=0) + uint32_t ppidOrFsn; + // User data follows + }; +#pragma pack(pop) + static_assert(sizeof(sctp_idata_chunk) == 20, "sctp_idata_chunk size must be 20 bytes"); + + /// @struct sctp_asconf_ack_chunk + /// ASCONF-ACK chunk header (RFC 5061) +#pragma pack(push, 1) + struct sctp_asconf_ack_chunk + { + /// Chunk type = 128 (0x80) + uint8_t type; + /// Chunk flags (reserved) + uint8_t flags; + /// Chunk length + uint16_t length; + /// Serial Number + uint32_t serialNumber; + // ASCONF Parameters follow + }; +#pragma pack(pop) + static_assert(sizeof(sctp_asconf_ack_chunk) == 8, "sctp_asconf_ack_chunk size must be 8 bytes"); + + /// @struct sctp_reconfig_chunk + /// RE-CONFIG chunk header (RFC 6525) +#pragma pack(push, 1) + struct sctp_reconfig_chunk + { + /// Chunk type = 130 (0x82) + uint8_t type; + /// Chunk flags (reserved) + uint8_t flags; + /// Chunk length + uint16_t length; + // Re-configuration parameters follow + }; +#pragma pack(pop) + static_assert(sizeof(sctp_reconfig_chunk) == 4, "sctp_reconfig_chunk size must be 4 bytes"); + + /// @struct sctp_pad_chunk + /// PAD chunk header (RFC 4820) +#pragma pack(push, 1) + struct sctp_pad_chunk + { + /// Chunk type = 132 (0x84) + uint8_t type; + /// Chunk flags (reserved) + uint8_t flags; + /// Chunk length + uint16_t length; + // Padding data follows + }; +#pragma pack(pop) + static_assert(sizeof(sctp_pad_chunk) == 4, "sctp_pad_chunk size must be 4 bytes"); + + /// @struct sctp_nr_sack_chunk + /// NR-SACK (Non-Renegable SACK) chunk header (draft-natarajan-tsvwg-sctp-nrsack) + /// @note This chunk type is based on an IETF draft that was never published as an RFC. + /// It is registered with IANA (Type 16) but has limited deployment. Use with caution + /// in production environments. +#pragma pack(push, 1) + struct sctp_nr_sack_chunk + { + /// Chunk type = 16 (0x10) + uint8_t type; + /// Chunk flags: A bit (0x01) - all out-of-order blocks are non-renegable + uint8_t flags; + /// Chunk length + uint16_t length; + /// Cumulative TSN Ack + uint32_t cumulativeTsnAck; + /// Advertised Receiver Window Credit + uint32_t arwnd; + /// Number of Gap Ack Blocks + uint16_t numGapBlocks; + /// Number of NR Gap Ack Blocks + uint16_t numNrGapBlocks; + /// Number of Duplicate TSNs + uint16_t numDupTsns; + /// Reserved + uint16_t reserved; + // Gap Ack Blocks, NR Gap Ack Blocks, and Duplicate TSNs follow + }; +#pragma pack(pop) + static_assert(sizeof(sctp_nr_sack_chunk) == 20, "sctp_nr_sack_chunk size must be 20 bytes"); + + /// NR-SACK chunk flag bits + namespace SctpNrSackFlags + { + /// A bit - All out-of-order blocks are non-renegable + constexpr uint8_t ALL_NON_RENEGABLE = 0x01; + } // namespace SctpNrSackFlags + + /// @struct sctp_forward_tsn_chunk + /// FORWARD TSN chunk header (RFC 3758) +#pragma pack(push, 1) + struct sctp_forward_tsn_chunk + { + /// Chunk type = 192 (0xC0) + uint8_t type; + /// Chunk flags (reserved) + uint8_t flags; + /// Chunk length + uint16_t length; + /// New Cumulative TSN + uint32_t newCumulativeTsn; + // Stream/Sequence pairs follow + }; +#pragma pack(pop) + static_assert(sizeof(sctp_forward_tsn_chunk) == 8, "sctp_forward_tsn_chunk size must be 8 bytes"); + + /// @struct sctp_forward_tsn_stream + /// Stream/Sequence pair for FORWARD TSN chunk +#pragma pack(push, 1) + struct sctp_forward_tsn_stream + { + /// Stream Identifier + uint16_t streamId; + /// Stream Sequence + uint16_t streamSeq; + }; +#pragma pack(pop) + static_assert(sizeof(sctp_forward_tsn_stream) == 4, "sctp_forward_tsn_stream size must be 4 bytes"); + + /// @struct sctp_asconf_chunk + /// ASCONF chunk header (RFC 5061) +#pragma pack(push, 1) + struct sctp_asconf_chunk + { + /// Chunk type = 193 (0xC1) + uint8_t type; + /// Chunk flags (reserved) + uint8_t flags; + /// Chunk length + uint16_t length; + /// Serial Number + uint32_t serialNumber; + // Address Parameter followed by ASCONF Parameters + }; +#pragma pack(pop) + static_assert(sizeof(sctp_asconf_chunk) == 8, "sctp_asconf_chunk size must be 8 bytes"); + + /// @struct sctp_iforward_tsn_chunk + /// I-FORWARD-TSN chunk header (RFC 8260) +#pragma pack(push, 1) + struct sctp_iforward_tsn_chunk + { + /// Chunk type = 194 (0xC2) + uint8_t type; + /// Chunk flags (reserved) + uint8_t flags; + /// Chunk length + uint16_t length; + /// New Cumulative TSN + uint32_t newCumulativeTsn; + // Stream/MID/Unordered tuples follow + }; +#pragma pack(pop) + static_assert(sizeof(sctp_iforward_tsn_chunk) == 8, "sctp_iforward_tsn_chunk size must be 8 bytes"); + + /// @struct sctp_iforward_tsn_stream + /// Stream/MID tuple for I-FORWARD-TSN chunk (RFC 8260) + /// The reserved field contains 15 reserved bits and 1 U (Unordered) bit in the LSB +#pragma pack(push, 1) + struct sctp_iforward_tsn_stream + { + /// Stream Identifier + uint16_t streamId; + /// Reserved (15 bits) + U bit (1 bit in LSB position) + /// Use isUnordered() helper or check (reserved & 0x0001) after byte swap + uint16_t reserved; + /// Message Identifier + uint32_t mid; + + /// Check if the U (Unordered) bit is set + /// @param[in] reservedHostOrder The reserved field in host byte order + /// @return True if U bit is set (unordered message) + static bool isUnordered(uint16_t reservedHostOrder) + { + return (reservedHostOrder & 0x0001) != 0; + } + }; +#pragma pack(pop) + static_assert(sizeof(sctp_iforward_tsn_stream) == 8, "sctp_iforward_tsn_stream size must be 8 bytes"); + + /// @struct sctp_param_hdr + /// SCTP parameter header (TLV format) +#pragma pack(push, 1) + struct sctp_param_hdr + { + /// Parameter Type + uint16_t type; + /// Parameter Length (including header) + uint16_t length; + // Value follows + }; +#pragma pack(pop) + static_assert(sizeof(sctp_param_hdr) == 4, "sctp_param_hdr size must be 4 bytes"); + + /// @struct sctp_outgoing_ssn_reset_req + /// Outgoing SSN Reset Request Parameter (RFC 6525, Section 4.1) +#pragma pack(push, 1) + struct sctp_outgoing_ssn_reset_req + { + /// Parameter Type = 13 + uint16_t type; + /// Parameter Length = 16 + 2*N + uint16_t length; + /// Re-configuration Request Sequence Number + uint32_t reqSeqNum; + /// Re-configuration Response Sequence Number + uint32_t respSeqNum; + /// Sender's Last Assigned TSN + uint32_t lastTsn; + // Optional Stream Numbers follow (2 bytes each) + }; +#pragma pack(pop) + static_assert(sizeof(sctp_outgoing_ssn_reset_req) == 16, "sctp_outgoing_ssn_reset_req size must be 16 bytes"); + + /// @struct sctp_incoming_ssn_reset_req + /// Incoming SSN Reset Request Parameter (RFC 6525, Section 4.2) +#pragma pack(push, 1) + struct sctp_incoming_ssn_reset_req + { + /// Parameter Type = 14 + uint16_t type; + /// Parameter Length = 8 + 2*N + uint16_t length; + /// Re-configuration Request Sequence Number + uint32_t reqSeqNum; + // Optional Stream Numbers follow (2 bytes each) + }; +#pragma pack(pop) + static_assert(sizeof(sctp_incoming_ssn_reset_req) == 8, "sctp_incoming_ssn_reset_req size must be 8 bytes"); + + /// @struct sctp_ssn_tsn_reset_req + /// SSN/TSN Reset Request Parameter (RFC 6525, Section 4.3) +#pragma pack(push, 1) + struct sctp_ssn_tsn_reset_req + { + /// Parameter Type = 15 + uint16_t type; + /// Parameter Length = 8 + uint16_t length; + /// Re-configuration Request Sequence Number + uint32_t reqSeqNum; + }; +#pragma pack(pop) + static_assert(sizeof(sctp_ssn_tsn_reset_req) == 8, "sctp_ssn_tsn_reset_req size must be 8 bytes"); + + /// @struct sctp_reconfig_response + /// Re-configuration Response Parameter (RFC 6525, Section 4.4) +#pragma pack(push, 1) + struct sctp_reconfig_response + { + /// Parameter Type = 16 + uint16_t type; + /// Parameter Length = 12 or 20 + uint16_t length; + /// Re-configuration Response Sequence Number + uint32_t respSeqNum; + /// Result code + uint32_t result; + // Optional Sender's Next TSN and Receiver's Next TSN follow + }; +#pragma pack(pop) + static_assert(sizeof(sctp_reconfig_response) == 12, "sctp_reconfig_response size must be 12 bytes"); + + /// @struct sctp_add_streams_req + /// Add Outgoing/Incoming Streams Request Parameter (RFC 6525, Section 4.5/4.6) +#pragma pack(push, 1) + struct sctp_add_streams_req + { + /// Parameter Type = 17 or 18 + uint16_t type; + /// Parameter Length = 12 + uint16_t length; + /// Re-configuration Request Sequence Number + uint32_t reqSeqNum; + /// Number of new streams + uint16_t numNewStreams; + /// Reserved + uint16_t reserved; + }; +#pragma pack(pop) + static_assert(sizeof(sctp_add_streams_req) == 12, "sctp_add_streams_req size must be 12 bytes"); + + /// @struct sctp_asconf_param + /// ASCONF Parameter header (RFC 5061) - Add IP, Delete IP, Set Primary +#pragma pack(push, 1) + struct sctp_asconf_param + { + /// Parameter Type (0xC001, 0xC002, 0xC004) + uint16_t type; + /// Parameter Length + uint16_t length; + /// ASCONF-Request Correlation ID + uint32_t correlationId; + // Address Parameter follows + }; +#pragma pack(pop) + static_assert(sizeof(sctp_asconf_param) == 8, "sctp_asconf_param size must be 8 bytes"); + + /// @struct sctp_asconf_response + /// ASCONF Response Parameter header (RFC 5061) - Error Cause Indication, Success +#pragma pack(push, 1) + struct sctp_asconf_response + { + /// Parameter Type (0xC003 or 0xC005) + uint16_t type; + /// Parameter Length + uint16_t length; + /// ASCONF-Response Correlation ID + uint32_t correlationId; + // Error Cause(s) follow for Error Cause Indication (0xC003) + }; +#pragma pack(pop) + static_assert(sizeof(sctp_asconf_response) == 8, "sctp_asconf_response size must be 8 bytes"); + + /// @struct sctp_error_cause + /// SCTP error cause header +#pragma pack(push, 1) + struct sctp_error_cause + { + /// Cause Code + uint16_t code; + /// Cause Length (including header) + uint16_t length; + // Cause-specific information follows + }; +#pragma pack(pop) + static_assert(sizeof(sctp_error_cause) == 4, "sctp_error_cause size must be 4 bytes"); + + /// SCTP Chunk Types (IANA Registry) + enum class SctpChunkType : uint8_t + { + /// Payload Data (RFC 9260) + DATA = 0, + /// Initiation (RFC 9260) + INIT = 1, + /// Initiation Acknowledgement (RFC 9260) + INIT_ACK = 2, + /// Selective Acknowledgement (RFC 9260) + SACK = 3, + /// Heartbeat Request (RFC 9260) + HEARTBEAT = 4, + /// Heartbeat Acknowledgement (RFC 9260) + HEARTBEAT_ACK = 5, + /// Abort (RFC 9260) + ABORT = 6, + /// Shutdown (RFC 9260) + SHUTDOWN = 7, + /// Shutdown Acknowledgement (RFC 9260) + SHUTDOWN_ACK = 8, + /// Operation Error (RFC 9260) + /// @note Named SCTP_ERROR to avoid conflict with Windows ERROR macro + SCTP_ERROR = 9, + /// State Cookie (RFC 9260) + COOKIE_ECHO = 10, + /// Cookie Acknowledgement (RFC 9260) + COOKIE_ACK = 11, + /// Explicit Congestion Notification Echo (RFC 9260) + ECNE = 12, + /// Congestion Window Reduced (RFC 9260) + CWR = 13, + /// Shutdown Complete (RFC 9260) + SHUTDOWN_COMPLETE = 14, + /// Authentication Chunk (RFC 4895) + AUTH = 15, + /// NR-SACK - Non-Renegable SACK (draft-natarajan-tsvwg-sctp-nrsack, IANA registered) + /// @note Experimental - based on expired IETF draft, limited deployment + NR_SACK = 16, + /// I-DATA (RFC 8260) + I_DATA = 64, + /// Address Configuration Acknowledgment (RFC 5061) + ASCONF_ACK = 128, + /// Re-configuration Chunk (RFC 6525) + RE_CONFIG = 130, + /// Padding Chunk (RFC 4820) + PAD = 132, + /// Forward TSN (RFC 3758) + FORWARD_TSN = 192, + /// Address Configuration Change (RFC 5061) + ASCONF = 193, + /// I-FORWARD-TSN (RFC 8260) + I_FORWARD_TSN = 194, + /// Unknown chunk type + UNKNOWN = 255 + }; + + /// SCTP Parameter Types (IANA Registry) + enum class SctpParameterType : uint16_t + { + /// Heartbeat Info (RFC 9260) + HEARTBEAT_INFO = 1, + /// IPv4 Address (RFC 9260) + IPV4_ADDRESS = 5, + /// IPv6 Address (RFC 9260) + IPV6_ADDRESS = 6, + /// State Cookie (RFC 9260) + STATE_COOKIE = 7, + /// Unrecognized Parameter (RFC 9260) + UNRECOGNIZED_PARAM = 8, + /// Cookie Preservative (RFC 9260) + COOKIE_PRESERVATIVE = 9, + /// Host Name Address (RFC 9260) + HOST_NAME_ADDRESS = 11, + /// Supported Address Types (RFC 9260) + SUPPORTED_ADDRESS_TYPES = 12, + /// Outgoing SSN Reset Request (RFC 6525) + OUTGOING_SSN_RESET_REQ = 13, + /// Incoming SSN Reset Request (RFC 6525) + INCOMING_SSN_RESET_REQ = 14, + /// SSN/TSN Reset Request (RFC 6525) + SSN_TSN_RESET_REQ = 15, + /// Re-configuration Response (RFC 6525) + RECONFIG_RESPONSE = 16, + /// Add Outgoing Streams Request (RFC 6525) + ADD_OUTGOING_STREAMS_REQ = 17, + /// Add Incoming Streams Request (RFC 6525) + ADD_INCOMING_STREAMS_REQ = 18, + /// ECN Capable (RFC 9260) + ECN_CAPABLE = 0x8000, + /// Zero Checksum Acceptable (RFC 9653) + ZERO_CHECKSUM_ACCEPTABLE = 0x8001, + /// Random (RFC 4895) + RANDOM = 0x8002, + /// Chunk List (RFC 4895) + CHUNK_LIST = 0x8003, + /// Requested HMAC Algorithm (RFC 4895) + REQUESTED_HMAC_ALGO = 0x8004, + /// Padding (RFC 4820) + PADDING = 0x8005, + /// Supported Extensions (RFC 5061) + SUPPORTED_EXTENSIONS = 0x8008, + /// Forward TSN Supported (RFC 3758) + FORWARD_TSN_SUPPORTED = 0xC000, + /// Add IP Address (RFC 5061) + ADD_IP_ADDRESS = 0xC001, + /// Delete IP Address (RFC 5061) + DELETE_IP_ADDRESS = 0xC002, + /// Error Cause Indication (RFC 5061) + ERROR_CAUSE_INDICATION = 0xC003, + /// Set Primary Address (RFC 5061) + SET_PRIMARY_ADDRESS = 0xC004, + /// Success Indication (RFC 5061) + SUCCESS_INDICATION = 0xC005, + /// Adaptation Layer Indication (RFC 5061) + ADAPTATION_LAYER_INDICATION = 0xC006 + }; + + /// SCTP Error Cause Codes (IANA Registry) + enum class SctpErrorCauseCode : uint16_t + { + /// Invalid Stream Identifier + INVALID_STREAM_ID = 1, + /// Missing Mandatory Parameter + MISSING_MANDATORY_PARAM = 2, + /// Stale Cookie + STALE_COOKIE = 3, + /// Out of Resource + OUT_OF_RESOURCE = 4, + /// Unresolvable Address + UNRESOLVABLE_ADDRESS = 5, + /// Unrecognized Chunk Type + UNRECOGNIZED_CHUNK_TYPE = 6, + /// Invalid Mandatory Parameter + INVALID_MANDATORY_PARAM = 7, + /// Unrecognized Parameters + UNRECOGNIZED_PARAMS = 8, + /// No User Data + NO_USER_DATA = 9, + /// Cookie Received While Shutting Down + COOKIE_RECEIVED_WHILE_SHUTTING_DOWN = 10, + /// Restart Association with New Addresses + RESTART_WITH_NEW_ADDRESSES = 11, + /// User Initiated Abort + USER_INITIATED_ABORT = 12, + /// Protocol Violation + PROTOCOL_VIOLATION = 13, + /// Request to Delete Last Remaining IP (RFC 5061) + DELETE_LAST_IP = 160, + /// Operation Refused (RFC 5061) + OPERATION_REFUSED = 161, + /// Request to Delete Source IP (RFC 5061) + DELETE_SOURCE_IP = 162, + /// Association Aborted (RFC 5061) + ASSOCIATION_ABORTED = 163, + /// Request Refused (RFC 5061) + REQUEST_REFUSED = 164, + /// Unsupported HMAC Identifier (RFC 4895) + UNSUPPORTED_HMAC_ID = 261 + }; + + /// SCTP Payload Protocol Identifiers (IANA Registry) + enum class SctpPayloadProtocolId : uint32_t + { + /// Reserved + RESERVED = 0, + /// IUA (RFC 4233) + IUA = 1, + /// M2UA (RFC 3331) + M2UA = 2, + /// M3UA (RFC 4666) + M3UA = 3, + /// SUA (RFC 3868) + SUA = 4, + /// M2PA (RFC 4165) + M2PA = 5, + /// V5UA (RFC 3807) + V5UA = 6, + /// H.248 (ITU-T) + H248 = 7, + /// BICC/Q.2150.3 (ITU-T) + BICC = 8, + /// TALI (RFC 3094) + TALI = 9, + /// DUA (RFC 4129) + DUA = 10, + /// ASAP (RFC 5352) + ASAP = 11, + /// ENRP (RFC 5353) + ENRP = 12, + /// H.323 over SCTP + H323 = 13, + /// Q.IPC/Q.2150.3 (ITU-T) + QIPC = 14, + /// SIMCO + SIMCO = 15, + /// DDP Segment Chunk (RFC 5043) + DDP_SEGMENT = 16, + /// DDP Stream Session Control (RFC 5043) + DDP_STREAM = 17, + /// S1AP (3GPP TS 36.412) + S1AP = 18, + /// RUA (3GPP TS 25.468) + RUA = 19, + /// HNBAP (3GPP TS 25.469) + HNBAP = 20, + /// ForCES-HP (RFC 5811) + FORCES_HP = 21, + /// ForCES-MP (RFC 5811) + FORCES_MP = 22, + /// ForCES-LP (RFC 5811) + FORCES_LP = 23, + /// SBC-AP (3GPP TS 29.168) + SBC_AP = 24, + /// NBAP (3GPP TS 25.433) + NBAP = 25, + /// X2AP (3GPP TS 36.423) + X2AP = 27, + /// IRCP (Inter Router Capability Protocol) + IRCP = 28, + /// SABP (3GPP TS 25.419) + SABP = 29, + /// LCS-AP (3GPP TS 29.171) + LCS_AP = 30, + /// MPICH2 + MPICH2 = 31, + /// Fractal Generator Protocol + FGP = 32, + /// Ping Pong Protocol + PPP = 33, + /// CalcApp Protocol + CALCAPP = 34, + /// SSP (Simple Spreadsheet Protocol) + SSP = 35, + /// NPMP-CONTROL + NPMP_CONTROL = 36, + /// NPMP-DATA + NPMP_DATA = 37, + /// Echo + ECHO = 38, + /// Discard + DISCARD = 39, + /// Daytime + DAYTIME = 40, + /// Character Generator (CHARGEN) + CHARGEN = 41, + /// 3GPP RNA (Radio Network Layer Application) + RNA = 42, + /// M2AP (3GPP TS 36.443) + M2AP = 43, + /// M3AP (3GPP TS 36.444) + M3AP = 44, + /// SSH over SCTP + SSH = 45, + /// Diameter (RFC 6733) + DIAMETER = 46, + /// Diameter DTLS + DIAMETER_DTLS = 47, + /// R14P (BER encoded) + R14P_BER = 48, + /// R14P (GPB encoded) + R14P_GPB = 49, + /// WebRTC DCEP (RFC 8832) + WEBRTC_DCEP = 50, + /// WebRTC String (RFC 8831) + WEBRTC_STRING = 51, + /// WebRTC Binary Partial (RFC 8831) + WEBRTC_BINARY_PARTIAL = 52, + /// WebRTC Binary (RFC 8831) + WEBRTC_BINARY = 53, + /// WebRTC String Partial (RFC 8831) + WEBRTC_STRING_PARTIAL = 54, + /// WebRTC String Empty (RFC 8831) + WEBRTC_STRING_EMPTY = 56, + /// WebRTC Binary Empty (RFC 8831) + WEBRTC_BINARY_EMPTY = 57, + /// 3GPP NGAP (3GPP TS 38.413) + NGAP = 60, + /// 3GPP XnAP (3GPP TS 38.423) + XNAP = 61, + /// 3GPP F1AP (3GPP TS 38.473) + F1AP = 62, + /// HTTP/SCTP (experimental) + HTTP_SCTP = 63, + /// 3GPP E1AP (3GPP TS 38.463) + E1AP = 64, + /// 3GPP E2AP (O-RAN E2 interface, 3GPP TS 36.423) + E2AP = 65, + /// 3GPP E2AP over DTLS + E2AP_DTLS = 66, + /// 3GPP W1AP (3GPP TS 37.473) - non-DTLS variant + W1AP_NON_DTLS = 67, + /// 3GPP NRPPa (3GPP TS 38.455) + NRPPA = 68, + /// 3GPP NRPPa over DTLS + NRPPA_DTLS = 69, + /// 3GPP F1AP over DTLS + F1AP_DTLS = 70, + /// 3GPP E1AP over DTLS + E1AP_DTLS = 71, + /// 3GPP W1AP (3GPP TS 37.473) + W1AP = 72, + /// 3GPP NGAP over DTLS + NGAP_DTLS = 73, + /// 3GPP XnAP over DTLS + XNAP_DTLS = 74, + /// DTLS Chunk Key-Management Messages + DTLS_KEY_MGMT = 4242 + }; + + /// RE-CONFIG Response Result Codes (RFC 6525) + enum class SctpReconfigResult : uint32_t + { + /// Success - Nothing to do + SUCCESS_NOTHING_TO_DO = 0, + /// Success - Performed + SUCCESS_PERFORMED = 1, + /// Denied + DENIED = 2, + /// Error - Wrong SSN + ERROR_WRONG_SSN = 3, + /// Error - Request already in progress + ERROR_REQUEST_IN_PROGRESS = 4, + /// Error - Bad Sequence Number + ERROR_BAD_SEQUENCE_NUMBER = 5, + /// In progress + IN_PROGRESS = 6 + }; + + /// Zero Checksum Error Detection Method Identifiers (RFC 9653) + enum class SctpEdmid : uint32_t + { + /// Reserved + RESERVED = 0, + /// DTLS (RFC 9147) + DTLS = 1 + }; + + /// HMAC Identifiers (RFC 4895) + enum class SctpHmacIdentifier : uint16_t + { + /// Reserved + RESERVED = 0, + /// SHA-1 + SHA1 = 1, + /// SHA-256 + SHA256 = 3 + }; + + /// DATA chunk flag bits + namespace SctpDataChunkFlags + { + /// End fragment bit + constexpr uint8_t END_FRAGMENT = 0x01; + /// Beginning fragment bit + constexpr uint8_t BEGIN_FRAGMENT = 0x02; + /// Unordered bit + constexpr uint8_t UNORDERED = 0x04; + /// Immediate bit (RFC 7053) + constexpr uint8_t IMMEDIATE = 0x08; + } // namespace SctpDataChunkFlags + + /// ABORT and SHUTDOWN COMPLETE chunk flag bits + namespace SctpAbortFlags + { + /// T bit - Verification Tag handling + constexpr uint8_t T_BIT = 0x01; + } // namespace SctpAbortFlags + + /// Result of chunk bundling validation + enum class SctpBundlingStatus + { + /// Bundling is valid + VALID, + /// INIT chunk cannot be bundled with other chunks (RFC 9260) + INIT_BUNDLED, + /// INIT-ACK chunk cannot be bundled with other chunks (RFC 9260) + INIT_ACK_BUNDLED, + /// SHUTDOWN-COMPLETE chunk cannot be bundled with other chunks (RFC 9260) + SHUTDOWN_COMPLETE_BUNDLED, + /// INIT chunk requires verification tag to be zero (RFC 9260) + INIT_NONZERO_TAG + }; + + // Forward declarations + class SctpChunk; + class SctpLayer; + class SctpInitParameter; + + /// @class SctpChunk + /// A wrapper class for SCTP chunks. This class does not create or modify chunk records, + /// but rather serves as a wrapper and provides useful methods for retrieving data from them + class SctpChunk + { + public: + /// Construct from raw data pointer + /// @param[in] data Pointer to chunk data + explicit SctpChunk(uint8_t* data) : m_Data(reinterpret_cast(data)) + {} + + /// Default destructor + ~SctpChunk() = default; + + /// @return True if chunk is null/invalid + bool isNull() const + { + return m_Data == nullptr; + } + + /// @return True if chunk is not null + bool isNotNull() const + { + return m_Data != nullptr; + } + + /// @return Chunk type as enum + SctpChunkType getChunkType() const; + + /// @return Chunk type as raw uint8_t + uint8_t getChunkTypeAsInt() const; + + /// @return Chunk flags + uint8_t getFlags() const; + + /// @return Chunk length (as specified in header, not including padding) + uint16_t getLength() const; + + /// @return Total size including padding (aligned to 4 bytes) + size_t getTotalSize() const; + + /// @return Pointer to chunk value/payload (after the 4-byte header) + uint8_t* getValue() const; + + /// @return Size of chunk value (length - 4) + size_t getValueSize() const; + + /// @return Pointer to raw chunk data + uint8_t* getRecordBasePtr() const + { + return reinterpret_cast(m_Data); + } + + /// Get value at offset as specific type + /// @tparam T Type to retrieve + /// @param[in] offset Offset into value + /// @return Value at offset, or 0 if invalid + template T getValueAs(size_t offset = 0) const + { + if (m_Data == nullptr || offset + sizeof(T) > getValueSize()) + return T{}; + return *reinterpret_cast(getValue() + offset); + } + + // ==================== Utility Methods ==================== + + /// Check if a flag bit is set + /// @param[in] flagBit The flag bit to check + /// @return True if flag is set + bool isFlagSet(uint8_t flagBit) const; + + /// Get chunk type name as string + /// @return Chunk type name + std::string getChunkTypeName() const; + + private: + sctp_chunk_hdr* m_Data; + }; + + // ==================== Chunk View Classes ==================== + + /// @class SctpDataChunkView + /// Type-safe view for SCTP DATA chunks (RFC 9260 Section 3.3.1) + /// Provides type-checked access to DATA chunk fields without runtime overhead + class SctpDataChunkView + { + public: + /// Construct from generic chunk (caller should verify type) + /// @param[in] chunk The generic chunk to wrap + explicit SctpDataChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + /// Factory method - creates a view, use isValid() to check if chunk type matches + /// @param[in] chunk The generic chunk to wrap + /// @return A view that may or may not be valid depending on chunk type + static SctpDataChunkView fromChunk(SctpChunk chunk) + { + return SctpDataChunkView(chunk); + } + + /// @return True if this view points to a valid DATA chunk + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::DATA; + } + + /// @return The underlying generic chunk + SctpChunk getChunk() const + { + return m_Chunk; + } + + /// @return Chunk flags + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + + /// @return Chunk length + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + + /// @return Total size including padding + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + // ==================== DATA-specific Methods ==================== + + /// @return Transmission Sequence Number + uint32_t getTsn() const; + + /// @return Stream Identifier + uint16_t getStreamId() const; + + /// @return Stream Sequence Number + uint16_t getSequenceNumber() const; + + /// @return Payload Protocol Identifier (in host byte order) + /// @note RFC 9260 states PPID byte order is upper layer's responsibility, + /// but this library converts for API consistency + uint32_t getPpid() const; + + /// @return Pointer to user data payload + uint8_t* getUserData() const; + + /// @return Length of user data payload + size_t getUserDataLength() const; + + // ==================== Flag Accessors ==================== + + /// @return True if this is the beginning fragment (B bit set) + bool isBeginFragment() const; + + /// @return True if this is the ending fragment (E bit set) + bool isEndFragment() const; + + /// @return True if unordered delivery (U bit set) + bool isUnordered() const; + + /// @return True if immediate bit is set (I bit, RFC 7053) + bool isImmediate() const; + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpInitChunkView + /// Type-safe view for SCTP INIT chunks (RFC 9260 Section 3.3.2) + /// Provides type-checked access to INIT chunk fields without runtime overhead + class SctpInitChunkView + { + public: + /// Construct from generic chunk (caller should verify type) + /// @param[in] chunk The generic chunk to wrap + explicit SctpInitChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + /// Factory method - creates a view, use isValid() to check if chunk type matches + /// @param[in] chunk The generic chunk to wrap + /// @return A view that may or may not be valid depending on chunk type + static SctpInitChunkView fromChunk(SctpChunk chunk) + { + return SctpInitChunkView(chunk); + } + + /// @return True if this view points to a valid INIT chunk + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::INIT; + } + + /// @return The underlying generic chunk + SctpChunk getChunk() const + { + return m_Chunk; + } + + /// @return Chunk flags + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + + /// @return Chunk length + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + + /// @return Total size including padding + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + // ==================== INIT-specific Methods ==================== + + /// @return Initiate Tag + uint32_t getInitiateTag() const; + + /// @return Advertised Receiver Window Credit + uint32_t getArwnd() const; + + /// @return Number of Outbound Streams + uint16_t getNumOutboundStreams() const; + + /// @return Number of Inbound Streams + uint16_t getNumInboundStreams() const; + + /// @return Initial TSN + uint32_t getInitialTsn() const; + + /// @return Pointer to first parameter, or nullptr if none + uint8_t* getFirstParameter() const; + + /// @return Size of parameters section + size_t getParametersLength() const; + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpInitAckChunkView + /// Type-safe view for SCTP INIT-ACK chunks (RFC 9260 Section 3.3.3) + /// Same structure as INIT chunk + class SctpInitAckChunkView + { + public: + /// Construct from generic chunk (caller should verify type) + /// @param[in] chunk The generic chunk to wrap + explicit SctpInitAckChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + /// Factory method - creates a view, use isValid() to check if chunk type matches + /// @param[in] chunk The generic chunk to wrap + /// @return A view that may or may not be valid depending on chunk type + static SctpInitAckChunkView fromChunk(SctpChunk chunk) + { + return SctpInitAckChunkView(chunk); + } + + /// @return True if this view points to a valid INIT-ACK chunk + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::INIT_ACK; + } + + /// @return The underlying generic chunk + SctpChunk getChunk() const + { + return m_Chunk; + } + + /// @return Chunk flags + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + + /// @return Chunk length + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + + /// @return Total size including padding + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + // ==================== INIT-ACK-specific Methods ==================== + + /// @return Initiate Tag + uint32_t getInitiateTag() const; + + /// @return Advertised Receiver Window Credit + uint32_t getArwnd() const; + + /// @return Number of Outbound Streams + uint16_t getNumOutboundStreams() const; + + /// @return Number of Inbound Streams + uint16_t getNumInboundStreams() const; + + /// @return Initial TSN + uint32_t getInitialTsn() const; + + /// @return Pointer to first parameter, or nullptr if none + uint8_t* getFirstParameter() const; + + /// @return Size of parameters section + size_t getParametersLength() const; + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpSackChunkView + /// Type-safe view for SCTP SACK chunks (RFC 9260 Section 3.3.4) + /// Provides type-checked access to SACK chunk fields without runtime overhead + class SctpSackChunkView + { + public: + /// Construct from generic chunk (caller should verify type) + /// @param[in] chunk The generic chunk to wrap + explicit SctpSackChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + /// Factory method - creates a view, use isValid() to check if chunk type matches + /// @param[in] chunk The generic chunk to wrap + /// @return A view that may or may not be valid depending on chunk type + static SctpSackChunkView fromChunk(SctpChunk chunk) + { + return SctpSackChunkView(chunk); + } + + /// @return True if this view points to a valid SACK chunk + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::SACK; + } + + /// @return The underlying generic chunk + SctpChunk getChunk() const + { + return m_Chunk; + } + + /// @return Chunk flags + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + + /// @return Chunk length + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + + /// @return Total size including padding + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + // ==================== SACK-specific Methods ==================== + + /// @return Cumulative TSN Ack + uint32_t getCumulativeTsnAck() const; + + /// @return Advertised Receiver Window Credit + uint32_t getArwnd() const; + + /// @return Number of Gap Ack Blocks + uint16_t getNumGapBlocks() const; + + /// @return Number of Duplicate TSNs + uint16_t getNumDupTsns() const; + + /// Get Gap Ack Blocks + /// @return Vector of gap ack blocks (in host byte order) + std::vector getGapBlocks() const; + + /// Get Duplicate TSNs + /// @return Vector of duplicate TSNs (in host byte order) + std::vector getDupTsns() const; + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpHeartbeatChunkView + /// Type-safe view for SCTP HEARTBEAT chunks (RFC 9260 Section 3.3.5) + class SctpHeartbeatChunkView + { + public: + explicit SctpHeartbeatChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + static SctpHeartbeatChunkView fromChunk(SctpChunk chunk) + { + return SctpHeartbeatChunkView(chunk); + } + + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::HEARTBEAT; + } + + SctpChunk getChunk() const + { + return m_Chunk; + } + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + /// @return Pointer to Heartbeat Information TLV parameter + uint8_t* getInfo() const; + /// @return Size of Heartbeat Information (entire TLV) + size_t getInfoLength() const; + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpHeartbeatAckChunkView + /// Type-safe view for SCTP HEARTBEAT-ACK chunks (RFC 9260 Section 3.3.6) + class SctpHeartbeatAckChunkView + { + public: + explicit SctpHeartbeatAckChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + static SctpHeartbeatAckChunkView fromChunk(SctpChunk chunk) + { + return SctpHeartbeatAckChunkView(chunk); + } + + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::HEARTBEAT_ACK; + } + + SctpChunk getChunk() const + { + return m_Chunk; + } + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + /// @return Pointer to Heartbeat Information TLV parameter + uint8_t* getInfo() const; + /// @return Size of Heartbeat Information (entire TLV) + size_t getInfoLength() const; + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpAbortChunkView + /// Type-safe view for SCTP ABORT chunks (RFC 9260 Section 3.3.7) + class SctpAbortChunkView + { + public: + explicit SctpAbortChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + static SctpAbortChunkView fromChunk(SctpChunk chunk) + { + return SctpAbortChunkView(chunk); + } + + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::ABORT; + } + + SctpChunk getChunk() const + { + return m_Chunk; + } + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + /// @return True if T bit is set (Verification Tag reflected) + bool isTBitSet() const; + /// @return Pointer to first error cause, or nullptr if none + uint8_t* getFirstErrorCause() const; + /// @return Size of error causes section + size_t getErrorCausesLength() const; + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpErrorChunkView + /// Type-safe view for SCTP ERROR chunks (RFC 9260 Section 3.3.10) + class SctpErrorChunkView + { + public: + explicit SctpErrorChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + static SctpErrorChunkView fromChunk(SctpChunk chunk) + { + return SctpErrorChunkView(chunk); + } + + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::SCTP_ERROR; + } + + SctpChunk getChunk() const + { + return m_Chunk; + } + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + /// @return Pointer to first error cause, or nullptr if none + uint8_t* getFirstCause() const; + /// @return Size of error causes section + size_t getCausesLength() const; + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpShutdownChunkView + /// Type-safe view for SCTP SHUTDOWN chunks (RFC 9260 Section 3.3.8) + class SctpShutdownChunkView + { + public: + explicit SctpShutdownChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + static SctpShutdownChunkView fromChunk(SctpChunk chunk) + { + return SctpShutdownChunkView(chunk); + } + + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::SHUTDOWN; + } + + SctpChunk getChunk() const + { + return m_Chunk; + } + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + /// @return Cumulative TSN Ack + uint32_t getCumulativeTsnAck() const; + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpShutdownAckChunkView + /// Type-safe view for SCTP SHUTDOWN-ACK chunks (RFC 9260 Section 3.3.9) + class SctpShutdownAckChunkView + { + public: + explicit SctpShutdownAckChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + static SctpShutdownAckChunkView fromChunk(SctpChunk chunk) + { + return SctpShutdownAckChunkView(chunk); + } + + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::SHUTDOWN_ACK; + } + + SctpChunk getChunk() const + { + return m_Chunk; + } + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpShutdownCompleteChunkView + /// Type-safe view for SCTP SHUTDOWN-COMPLETE chunks (RFC 9260 Section 3.3.15) + class SctpShutdownCompleteChunkView + { + public: + explicit SctpShutdownCompleteChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + static SctpShutdownCompleteChunkView fromChunk(SctpChunk chunk) + { + return SctpShutdownCompleteChunkView(chunk); + } + + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::SHUTDOWN_COMPLETE; + } + + SctpChunk getChunk() const + { + return m_Chunk; + } + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + /// @return True if T bit is set (Verification Tag reflected) + bool isTBitSet() const; + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpCookieEchoChunkView + /// Type-safe view for SCTP COOKIE-ECHO chunks (RFC 9260 Section 3.3.11) + class SctpCookieEchoChunkView + { + public: + explicit SctpCookieEchoChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + static SctpCookieEchoChunkView fromChunk(SctpChunk chunk) + { + return SctpCookieEchoChunkView(chunk); + } + + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::COOKIE_ECHO; + } + + SctpChunk getChunk() const + { + return m_Chunk; + } + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + /// @return Pointer to cookie data + uint8_t* getCookie() const; + /// @return Size of cookie data + size_t getCookieLength() const; + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpCookieAckChunkView + /// Type-safe view for SCTP COOKIE-ACK chunks (RFC 9260 Section 3.3.12) + class SctpCookieAckChunkView + { + public: + explicit SctpCookieAckChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + static SctpCookieAckChunkView fromChunk(SctpChunk chunk) + { + return SctpCookieAckChunkView(chunk); + } + + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::COOKIE_ACK; + } + + SctpChunk getChunk() const + { + return m_Chunk; + } + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpEcneChunkView + /// Type-safe view for SCTP ECNE chunks (RFC 9260 Section 3.3.13) + class SctpEcneChunkView + { + public: + explicit SctpEcneChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + static SctpEcneChunkView fromChunk(SctpChunk chunk) + { + return SctpEcneChunkView(chunk); + } + + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::ECNE; + } + + SctpChunk getChunk() const + { + return m_Chunk; + } + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + /// @return Lowest TSN Number + uint32_t getLowestTsn() const; + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpCwrChunkView + /// Type-safe view for SCTP CWR chunks (RFC 9260 Section 3.3.14) + class SctpCwrChunkView + { + public: + explicit SctpCwrChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + static SctpCwrChunkView fromChunk(SctpChunk chunk) + { + return SctpCwrChunkView(chunk); + } + + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::CWR; + } + + SctpChunk getChunk() const + { + return m_Chunk; + } + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + /// @return Lowest TSN Number + uint32_t getLowestTsn() const; + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpAuthChunkView + /// Type-safe view for SCTP AUTH chunks (RFC 4895) + class SctpAuthChunkView + { + public: + explicit SctpAuthChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + static SctpAuthChunkView fromChunk(SctpChunk chunk) + { + return SctpAuthChunkView(chunk); + } + + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::AUTH; + } + + SctpChunk getChunk() const + { + return m_Chunk; + } + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + /// @return Shared Key Identifier + uint16_t getSharedKeyId() const; + /// @return HMAC Identifier + uint16_t getHmacId() const; + /// @return Pointer to HMAC data + uint8_t* getHmacData() const; + /// @return Length of HMAC data + size_t getHmacLength() const; + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpForwardTsnChunkView + /// Type-safe view for SCTP FORWARD-TSN chunks (RFC 3758) + class SctpForwardTsnChunkView + { + public: + explicit SctpForwardTsnChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + static SctpForwardTsnChunkView fromChunk(SctpChunk chunk) + { + return SctpForwardTsnChunkView(chunk); + } + + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::FORWARD_TSN; + } + + SctpChunk getChunk() const + { + return m_Chunk; + } + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + /// @return New Cumulative TSN + uint32_t getNewCumulativeTsn() const; + /// @return Number of streams in the chunk + size_t getStreamCount() const; + /// @return Vector of stream information + std::vector getStreams() const; + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpIDataChunkView + /// Type-safe view for SCTP I-DATA chunks (RFC 8260) + class SctpIDataChunkView + { + public: + explicit SctpIDataChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + static SctpIDataChunkView fromChunk(SctpChunk chunk) + { + return SctpIDataChunkView(chunk); + } + + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::I_DATA; + } + + SctpChunk getChunk() const + { + return m_Chunk; + } + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + /// @return Transmission Sequence Number + uint32_t getTsn() const; + /// @return Stream Identifier + uint16_t getStreamId() const; + /// @return Reserved field + uint16_t getReserved() const; + /// @return Message Identifier + uint32_t getMessageId() const; + /// @return PPID (if B bit set) or FSN (if B bit not set) + uint32_t getPpidOrFsn() const; + /// @return Pointer to user data payload + uint8_t* getUserData() const; + /// @return Length of user data payload + size_t getUserDataLength() const; + + // Flag accessors + /// @return True if this is the beginning fragment (B bit set) + bool isBeginFragment() const; + /// @return True if this is the ending fragment (E bit set) + bool isEndFragment() const; + /// @return True if unordered delivery (U bit set) + bool isUnordered() const; + /// @return True if immediate bit is set (I bit) + bool isImmediate() const; + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpIForwardTsnChunkView + /// Type-safe view for SCTP I-FORWARD-TSN chunks (RFC 8260) + class SctpIForwardTsnChunkView + { + public: + explicit SctpIForwardTsnChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + static SctpIForwardTsnChunkView fromChunk(SctpChunk chunk) + { + return SctpIForwardTsnChunkView(chunk); + } + + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::I_FORWARD_TSN; + } + + SctpChunk getChunk() const + { + return m_Chunk; + } + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + /// @return New Cumulative TSN + uint32_t getNewCumulativeTsn() const; + /// @return Number of streams in the chunk + size_t getStreamCount() const; + /// @return Vector of stream information + std::vector getStreams() const; + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpAsconfChunkView + /// Type-safe view for SCTP ASCONF chunks (RFC 5061) + class SctpAsconfChunkView + { + public: + explicit SctpAsconfChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + static SctpAsconfChunkView fromChunk(SctpChunk chunk) + { + return SctpAsconfChunkView(chunk); + } + + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::ASCONF; + } + + SctpChunk getChunk() const + { + return m_Chunk; + } + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + /// @return Serial Number + uint32_t getSerialNumber() const; + /// @return Pointer to first parameter, or nullptr if none + uint8_t* getFirstParameter() const; + /// @return Size of parameters section + size_t getParametersLength() const; + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpAsconfAckChunkView + /// Type-safe view for SCTP ASCONF-ACK chunks (RFC 5061) + class SctpAsconfAckChunkView + { + public: + explicit SctpAsconfAckChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + static SctpAsconfAckChunkView fromChunk(SctpChunk chunk) + { + return SctpAsconfAckChunkView(chunk); + } + + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::ASCONF_ACK; + } + + SctpChunk getChunk() const + { + return m_Chunk; + } + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + /// @return Serial Number + uint32_t getSerialNumber() const; + /// @return Pointer to first parameter, or nullptr if none + uint8_t* getFirstParameter() const; + /// @return Size of parameters section + size_t getParametersLength() const; + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpReconfigChunkView + /// Type-safe view for SCTP RE-CONFIG chunks (RFC 6525) + class SctpReconfigChunkView + { + public: + explicit SctpReconfigChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + static SctpReconfigChunkView fromChunk(SctpChunk chunk) + { + return SctpReconfigChunkView(chunk); + } + + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::RE_CONFIG; + } + + SctpChunk getChunk() const + { + return m_Chunk; + } + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + /// @return Pointer to first parameter, or nullptr if none + uint8_t* getFirstParameter() const; + /// @return Size of parameters section + size_t getParametersLength() const; + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpPadChunkView + /// Type-safe view for SCTP PAD chunks (RFC 4820) + class SctpPadChunkView + { + public: + explicit SctpPadChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + static SctpPadChunkView fromChunk(SctpChunk chunk) + { + return SctpPadChunkView(chunk); + } + + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::PAD; + } + + SctpChunk getChunk() const + { + return m_Chunk; + } + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + /// @return Padding data pointer + uint8_t* getPaddingData() const; + /// @return Length of padding data + size_t getPaddingLength() const; + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpNrSackChunkView + /// Type-safe view for SCTP NR-SACK chunks (RFC 6675) + class SctpNrSackChunkView + { + public: + explicit SctpNrSackChunkView(SctpChunk chunk) : m_Chunk(chunk) + {} + + static SctpNrSackChunkView fromChunk(SctpChunk chunk) + { + return SctpNrSackChunkView(chunk); + } + + bool isValid() const + { + return m_Chunk.isNotNull() && m_Chunk.getChunkType() == SctpChunkType::NR_SACK; + } + + SctpChunk getChunk() const + { + return m_Chunk; + } + uint8_t getFlags() const + { + return m_Chunk.getFlags(); + } + uint16_t getLength() const + { + return m_Chunk.getLength(); + } + size_t getTotalSize() const + { + return m_Chunk.getTotalSize(); + } + + /// @return Cumulative TSN Ack + uint32_t getCumulativeTsnAck() const; + /// @return Advertised Receiver Window Credit + uint32_t getArwnd() const; + /// @return Number of Gap Ack Blocks + uint16_t getNumGapBlocks() const; + /// @return Number of NR Gap Ack Blocks + uint16_t getNumNrGapBlocks() const; + /// @return Number of Duplicate TSNs + uint16_t getNumDupTsns() const; + /// @return True if All Non-Renegable flag (A bit) is set + bool isAllNonRenegable() const; + /// @return Vector of gap ack blocks (in host byte order) + std::vector getGapBlocks() const; + /// @return Vector of NR gap ack blocks (in host byte order) + std::vector getNrGapBlocks() const; + /// @return Vector of duplicate TSNs (in host byte order) + std::vector getDupTsns() const; + + private: + SctpChunk m_Chunk; + }; + + /// @class SctpLayer + /// Represents an SCTP (Stream Control Transmission Protocol) layer + class SctpLayer : public Layer + { + public: + /// A constructor that creates the layer from an existing packet raw data + /// @param[in] data A pointer to the raw data (will be casted to @ref sctphdr) + /// @param[in] dataLen Size of the data in bytes + /// @param[in] prevLayer A pointer to the previous layer + /// @param[in] packet A pointer to the Packet instance where layer will be stored in + SctpLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet); + + /// A constructor that allocates a new SCTP header with zero chunks + /// @param[in] srcPort Source port + /// @param[in] dstPort Destination port + /// @param[in] tag Verification tag (default 0) + SctpLayer(uint16_t srcPort, uint16_t dstPort, uint32_t tag = 0); + + /// Default destructor + ~SctpLayer() override = default; + + /// Get a pointer to the SCTP header + /// @return A pointer to the @ref sctphdr + sctphdr* getSctpHeader() const + { + return reinterpret_cast(m_Data); + } + + /// @return SCTP source port + uint16_t getSrcPort() const; + + /// @return SCTP destination port + uint16_t getDstPort() const; + + /// @return Verification tag + uint32_t getVerificationTag() const; + + /// Set source port + /// @param[in] port Source port value + void setSrcPort(uint16_t port); + + /// Set destination port + /// @param[in] port Destination port value + void setDstPort(uint16_t port); + + /// Set verification tag + /// @param[in] tag Verification tag value + void setVerificationTag(uint32_t tag); + + // ==================== Chunk Management ==================== + + /// @return Number of chunks in this SCTP packet + size_t getChunkCount() const; + + /// Get the first chunk in the packet + /// @return SctpChunk object or null chunk if no chunks + SctpChunk getFirstChunk() const; + + /// Get next chunk after the given chunk + /// @param[in] chunk Current chunk + /// @return Next SctpChunk or null chunk if no more chunks + SctpChunk getNextChunk(const SctpChunk& chunk) const; + + /// Get chunk by type + /// @param[in] chunkType The chunk type to search for + /// @return First chunk of specified type or null chunk if not found + SctpChunk getChunk(SctpChunkType chunkType) const; + + // ==================== Typed Chunk Getters ==================== + + /// Get DATA chunk as typed view + /// @return Valid view if DATA chunk exists, invalid view otherwise + SctpDataChunkView getDataChunk() const + { + return SctpDataChunkView::fromChunk(getChunk(SctpChunkType::DATA)); + } + + /// Get INIT chunk as typed view + /// @return Valid view if INIT chunk exists, invalid view otherwise + SctpInitChunkView getInitChunk() const + { + return SctpInitChunkView::fromChunk(getChunk(SctpChunkType::INIT)); + } + + /// Get INIT-ACK chunk as typed view + /// @return Valid view if INIT-ACK chunk exists, invalid view otherwise + SctpInitAckChunkView getInitAckChunk() const + { + return SctpInitAckChunkView::fromChunk(getChunk(SctpChunkType::INIT_ACK)); + } + + /// Get SACK chunk as typed view + /// @return Valid view if SACK chunk exists, invalid view otherwise + SctpSackChunkView getSackChunk() const + { + return SctpSackChunkView::fromChunk(getChunk(SctpChunkType::SACK)); + } + + /// Get HEARTBEAT chunk as typed view + /// @return Valid view if HEARTBEAT chunk exists, invalid view otherwise + SctpHeartbeatChunkView getHeartbeatChunk() const + { + return SctpHeartbeatChunkView::fromChunk(getChunk(SctpChunkType::HEARTBEAT)); + } + + /// Get HEARTBEAT-ACK chunk as typed view + /// @return Valid view if HEARTBEAT-ACK chunk exists, invalid view otherwise + SctpHeartbeatAckChunkView getHeartbeatAckChunk() const + { + return SctpHeartbeatAckChunkView::fromChunk(getChunk(SctpChunkType::HEARTBEAT_ACK)); + } + + /// Get ABORT chunk as typed view + /// @return Valid view if ABORT chunk exists, invalid view otherwise + SctpAbortChunkView getAbortChunk() const + { + return SctpAbortChunkView::fromChunk(getChunk(SctpChunkType::ABORT)); + } + + /// Get SHUTDOWN chunk as typed view + /// @return Valid view if SHUTDOWN chunk exists, invalid view otherwise + SctpShutdownChunkView getShutdownChunk() const + { + return SctpShutdownChunkView::fromChunk(getChunk(SctpChunkType::SHUTDOWN)); + } + + /// Get SHUTDOWN-ACK chunk as typed view + /// @return Valid view if SHUTDOWN-ACK chunk exists, invalid view otherwise + SctpShutdownAckChunkView getShutdownAckChunk() const + { + return SctpShutdownAckChunkView::fromChunk(getChunk(SctpChunkType::SHUTDOWN_ACK)); + } + + /// Get SHUTDOWN-COMPLETE chunk as typed view + /// @return Valid view if SHUTDOWN-COMPLETE chunk exists, invalid view otherwise + SctpShutdownCompleteChunkView getShutdownCompleteChunk() const + { + return SctpShutdownCompleteChunkView::fromChunk(getChunk(SctpChunkType::SHUTDOWN_COMPLETE)); + } + + /// Get ERROR chunk as typed view + /// @return Valid view if ERROR chunk exists, invalid view otherwise + SctpErrorChunkView getErrorChunk() const + { + return SctpErrorChunkView::fromChunk(getChunk(SctpChunkType::SCTP_ERROR)); + } + + /// Get COOKIE-ECHO chunk as typed view + /// @return Valid view if COOKIE-ECHO chunk exists, invalid view otherwise + SctpCookieEchoChunkView getCookieEchoChunk() const + { + return SctpCookieEchoChunkView::fromChunk(getChunk(SctpChunkType::COOKIE_ECHO)); + } + + /// Get COOKIE-ACK chunk as typed view + /// @return Valid view if COOKIE-ACK chunk exists, invalid view otherwise + SctpCookieAckChunkView getCookieAckChunk() const + { + return SctpCookieAckChunkView::fromChunk(getChunk(SctpChunkType::COOKIE_ACK)); + } + + /// Get ECNE chunk as typed view + /// @return Valid view if ECNE chunk exists, invalid view otherwise + SctpEcneChunkView getEcneChunk() const + { + return SctpEcneChunkView::fromChunk(getChunk(SctpChunkType::ECNE)); + } + + /// Get CWR chunk as typed view + /// @return Valid view if CWR chunk exists, invalid view otherwise + SctpCwrChunkView getCwrChunk() const + { + return SctpCwrChunkView::fromChunk(getChunk(SctpChunkType::CWR)); + } + + /// Get AUTH chunk as typed view + /// @return Valid view if AUTH chunk exists, invalid view otherwise + SctpAuthChunkView getAuthChunk() const + { + return SctpAuthChunkView::fromChunk(getChunk(SctpChunkType::AUTH)); + } + + /// Get FORWARD-TSN chunk as typed view + /// @return Valid view if FORWARD-TSN chunk exists, invalid view otherwise + SctpForwardTsnChunkView getForwardTsnChunk() const + { + return SctpForwardTsnChunkView::fromChunk(getChunk(SctpChunkType::FORWARD_TSN)); + } + + /// Get I-DATA chunk as typed view + /// @return Valid view if I-DATA chunk exists, invalid view otherwise + SctpIDataChunkView getIDataChunk() const + { + return SctpIDataChunkView::fromChunk(getChunk(SctpChunkType::I_DATA)); + } + + /// Get I-FORWARD-TSN chunk as typed view + /// @return Valid view if I-FORWARD-TSN chunk exists, invalid view otherwise + SctpIForwardTsnChunkView getIForwardTsnChunk() const + { + return SctpIForwardTsnChunkView::fromChunk(getChunk(SctpChunkType::I_FORWARD_TSN)); + } + + /// Get ASCONF chunk as typed view + /// @return Valid view if ASCONF chunk exists, invalid view otherwise + SctpAsconfChunkView getAsconfChunk() const + { + return SctpAsconfChunkView::fromChunk(getChunk(SctpChunkType::ASCONF)); + } + + /// Get ASCONF-ACK chunk as typed view + /// @return Valid view if ASCONF-ACK chunk exists, invalid view otherwise + SctpAsconfAckChunkView getAsconfAckChunk() const + { + return SctpAsconfAckChunkView::fromChunk(getChunk(SctpChunkType::ASCONF_ACK)); + } + + /// Get RE-CONFIG chunk as typed view + /// @return Valid view if RE-CONFIG chunk exists, invalid view otherwise + SctpReconfigChunkView getReconfigChunk() const + { + return SctpReconfigChunkView::fromChunk(getChunk(SctpChunkType::RE_CONFIG)); + } + + /// Get PAD chunk as typed view + /// @return Valid view if PAD chunk exists, invalid view otherwise + SctpPadChunkView getPadChunk() const + { + return SctpPadChunkView::fromChunk(getChunk(SctpChunkType::PAD)); + } + + /// Get NR-SACK chunk as typed view + /// @return Valid view if NR-SACK chunk exists, invalid view otherwise + SctpNrSackChunkView getNrSackChunk() const + { + return SctpNrSackChunkView::fromChunk(getChunk(SctpChunkType::NR_SACK)); + } + + // ==================== Checksum ==================== + + /// Calculate CRC32c checksum + /// @param[in] writeResultToPacket If true, writes result to packet + /// @return Calculated checksum value + uint32_t calculateChecksum(bool writeResultToPacket); + + /// Verify the checksum of the current packet + /// @return True if checksum is valid + bool isChecksumValid() const; + + // ==================== Validation ==================== + + /// Validate SCTP data + /// @param[in] data Pointer to data + /// @param[in] dataLen Length of data + /// @return True if data is valid SCTP packet + static bool isDataValid(const uint8_t* data, size_t dataLen); + + // ==================== Layer Interface ==================== + + /// Currently sets PayloadLayer as the next layer + void parseNextLayer() override; + + /// @return Header length (total SCTP packet length including all chunks) + size_t getHeaderLen() const override + { + return m_DataLen; + } + + /// Compute calculated fields (checksum) + void computeCalculateFields() override; + + /// @return String representation of the layer + std::string toString() const override; + + /// @return OSI model layer (Transport Layer) + OsiModelLayer getOsiModelLayer() const override + { + return OsiModelTransportLayer; + } + + // ==================== Chunk Creation Methods ==================== + + /// Add a DATA chunk to the SCTP packet + /// @param[in] tsn Transmission Sequence Number + /// @param[in] streamId Stream Identifier + /// @param[in] streamSeq Stream Sequence Number + /// @param[in] ppid Payload Protocol Identifier (in host byte order, will be converted to network byte order) + /// @param[in] userData Pointer to user data + /// @param[in] userDataLen Length of user data + /// @param[in] beginFragment True if this is the beginning fragment (B bit) + /// @param[in] endFragment True if this is the ending fragment (E bit) + /// @param[in] unordered True for unordered delivery (U bit) + /// @param[in] immediate True for immediate bit (I bit, RFC 7053) + /// @return True if chunk was added successfully + /// @note RFC 9260 states that PPID byte order conversion is the upper layer's responsibility. + /// For API consistency, this method converts PPID from host to network byte order. + bool addDataChunk(uint32_t tsn, uint16_t streamId, uint16_t streamSeq, uint32_t ppid, const uint8_t* userData, + size_t userDataLen, bool beginFragment = true, bool endFragment = true, + bool unordered = false, bool immediate = false); + + /// Add an INIT chunk to the SCTP packet + /// @param[in] initiateTag Initiate Tag + /// @param[in] arwnd Advertised Receiver Window Credit + /// @param[in] numOutboundStreams Number of Outbound Streams + /// @param[in] numInboundStreams Number of Inbound Streams + /// @param[in] initialTsn Initial TSN + /// @param[in] parameters Optional parameters data (can be nullptr) + /// @param[in] parametersLen Length of parameters data + /// @return True if chunk was added successfully + bool addInitChunk(uint32_t initiateTag, uint32_t arwnd, uint16_t numOutboundStreams, uint16_t numInboundStreams, + uint32_t initialTsn, const uint8_t* parameters = nullptr, size_t parametersLen = 0); + + /// Add an INIT-ACK chunk to the SCTP packet + /// @param[in] initiateTag Initiate Tag + /// @param[in] arwnd Advertised Receiver Window Credit + /// @param[in] numOutboundStreams Number of Outbound Streams + /// @param[in] numInboundStreams Number of Inbound Streams + /// @param[in] initialTsn Initial TSN + /// @param[in] parameters Optional parameters data (can be nullptr) + /// @param[in] parametersLen Length of parameters data + /// @return True if chunk was added successfully + bool addInitAckChunk(uint32_t initiateTag, uint32_t arwnd, uint16_t numOutboundStreams, + uint16_t numInboundStreams, uint32_t initialTsn, const uint8_t* parameters = nullptr, + size_t parametersLen = 0); + + /// Add a SACK chunk to the SCTP packet + /// @param[in] cumulativeTsnAck Cumulative TSN Ack + /// @param[in] arwnd Advertised Receiver Window Credit + /// @param[in] gapBlocks Vector of Gap Ack Blocks (in host byte order) + /// @param[in] dupTsns Vector of Duplicate TSNs (in host byte order) + /// @return True if chunk was added successfully + bool addSackChunk(uint32_t cumulativeTsnAck, uint32_t arwnd, + const std::vector& gapBlocks = {}, + const std::vector& dupTsns = {}); + + /// Add an NR-SACK (Non-Renegable SACK) chunk to the SCTP packet + /// @param[in] cumulativeTsnAck Cumulative TSN Ack + /// @param[in] arwnd Advertised Receiver Window Credit + /// @param[in] gapBlocks Vector of Gap Ack Blocks (in host byte order) + /// @param[in] nrGapBlocks Vector of NR (Non-Renegable) Gap Ack Blocks (in host byte order) + /// @param[in] dupTsns Vector of Duplicate TSNs (in host byte order) + /// @param[in] allNonRenegable Set A bit (all out-of-order blocks are non-renegable) + /// @return True if chunk was added successfully + bool addNrSackChunk(uint32_t cumulativeTsnAck, uint32_t arwnd, + const std::vector& gapBlocks = {}, + const std::vector& nrGapBlocks = {}, + const std::vector& dupTsns = {}, bool allNonRenegable = false); + + /// Add a HEARTBEAT chunk to the SCTP packet + /// @param[in] heartbeatInfo Pointer to heartbeat info data + /// @param[in] heartbeatInfoLen Length of heartbeat info data + /// @return True if chunk was added successfully + bool addHeartbeatChunk(const uint8_t* heartbeatInfo, size_t heartbeatInfoLen); + + /// Add a HEARTBEAT-ACK chunk to the SCTP packet + /// @param[in] heartbeatInfo Pointer to heartbeat info data (copy from HEARTBEAT) + /// @param[in] heartbeatInfoLen Length of heartbeat info data + /// @return True if chunk was added successfully + bool addHeartbeatAckChunk(const uint8_t* heartbeatInfo, size_t heartbeatInfoLen); + + /// Add a SHUTDOWN chunk to the SCTP packet + /// @param[in] cumulativeTsnAck Cumulative TSN Ack + /// @return True if chunk was added successfully + bool addShutdownChunk(uint32_t cumulativeTsnAck); + + /// Add a SHUTDOWN-ACK chunk to the SCTP packet + /// @return True if chunk was added successfully + bool addShutdownAckChunk(); + + /// Add a SHUTDOWN-COMPLETE chunk to the SCTP packet + /// @param[in] tBit T bit value (Verification Tag handling) + /// @return True if chunk was added successfully + bool addShutdownCompleteChunk(bool tBit = false); + + /// Add an ABORT chunk to the SCTP packet + /// @param[in] tBit T bit value (Verification Tag handling) + /// @param[in] errorCauses Optional error causes data + /// @param[in] errorCausesLen Length of error causes data + /// @return True if chunk was added successfully + bool addAbortChunk(bool tBit = false, const uint8_t* errorCauses = nullptr, size_t errorCausesLen = 0); + + /// Add a COOKIE-ECHO chunk to the SCTP packet + /// @param[in] cookie Pointer to cookie data + /// @param[in] cookieLen Length of cookie data + /// @return True if chunk was added successfully + bool addCookieEchoChunk(const uint8_t* cookie, size_t cookieLen); + + /// Add a COOKIE-ACK chunk to the SCTP packet + /// @return True if chunk was added successfully + bool addCookieAckChunk(); + + /// Add an ERROR chunk to the SCTP packet + /// @param[in] errorCauses Pointer to error causes data + /// @param[in] errorCausesLen Length of error causes data + /// @return True if chunk was added successfully + bool addErrorChunk(const uint8_t* errorCauses, size_t errorCausesLen); + + /// Add an ECNE (Explicit Congestion Notification Echo) chunk to the SCTP packet + /// @param[in] lowestTsn Lowest TSN Number triggering the ECN + /// @return True if chunk was added successfully + bool addEcneChunk(uint32_t lowestTsn); + + /// Add a CWR (Congestion Window Reduced) chunk to the SCTP packet + /// @param[in] lowestTsn Lowest TSN Number acknowledged by ECNE + /// @return True if chunk was added successfully + bool addCwrChunk(uint32_t lowestTsn); + + /// Add a FORWARD-TSN chunk to the SCTP packet (RFC 3758) + /// @param[in] newCumulativeTsn New Cumulative TSN + /// @param[in] streams Vector of stream/sequence pairs to skip + /// @return True if chunk was added successfully + bool addForwardTsnChunk(uint32_t newCumulativeTsn, const std::vector& streams = {}); + + /// Add an I-DATA chunk to the SCTP packet (RFC 8260) + /// @param[in] tsn Transmission Sequence Number + /// @param[in] streamId Stream Identifier + /// @param[in] mid Message Identifier + /// @param[in] ppidOrFsn PPID if B=1, FSN if B=0 + /// @param[in] userData Pointer to user data + /// @param[in] userDataLen Length of user data + /// @param[in] beginFragment True if this is the beginning fragment (B bit) + /// @param[in] endFragment True if this is the ending fragment (E bit) + /// @param[in] unordered True for unordered delivery (U bit) + /// @param[in] immediate True for immediate bit (I bit) + /// @return True if chunk was added successfully + bool addIDataChunk(uint32_t tsn, uint16_t streamId, uint32_t mid, uint32_t ppidOrFsn, const uint8_t* userData, + size_t userDataLen, bool beginFragment = true, bool endFragment = true, + bool unordered = false, bool immediate = false); + + /// Add an I-FORWARD-TSN chunk to the SCTP packet (RFC 8260) + /// @param[in] newCumulativeTsn New Cumulative TSN + /// @param[in] streams Vector of stream/MID tuples to skip + /// @return True if chunk was added successfully + bool addIForwardTsnChunk(uint32_t newCumulativeTsn, const std::vector& streams = {}); + + /// Add a PAD chunk to the SCTP packet (RFC 4820) + /// @param[in] paddingLen Length of padding data (will be filled with zeros) + /// @return True if chunk was added successfully + bool addPadChunk(size_t paddingLen); + + /// Add an AUTH chunk to the SCTP packet (RFC 4895) + /// @param[in] sharedKeyId Shared Key Identifier + /// @param[in] hmacId HMAC Identifier (use SctpHmacIdentifier values) + /// @param[in] hmac Pointer to HMAC data (pre-computed) + /// @param[in] hmacLen Length of HMAC data + /// @return True if chunk was added successfully + /// @note This method adds a pre-computed HMAC. The library does not compute HMACs. + /// Per RFC 4895, the HMAC must be 20 bytes for SHA-1 or 32 bytes for SHA-256. + bool addAuthChunk(uint16_t sharedKeyId, uint16_t hmacId, const uint8_t* hmac, size_t hmacLen); + + /// Add an ASCONF chunk to the SCTP packet (RFC 5061) + /// @param[in] serialNumber Sequence number for this ASCONF + /// @param[in] addressParam Address parameter (IPv4 or IPv6) identifying the sender + /// @param[in] addressParamLen Length of address parameter + /// @param[in] asconfParams ASCONF parameters data (Add IP, Delete IP, Set Primary) + /// @param[in] asconfParamsLen Length of ASCONF parameters + /// @return True if chunk was added successfully + bool addAsconfChunk(uint32_t serialNumber, const uint8_t* addressParam, size_t addressParamLen, + const uint8_t* asconfParams = nullptr, size_t asconfParamsLen = 0); + + /// Add an ASCONF-ACK chunk to the SCTP packet (RFC 5061) + /// @param[in] serialNumber Sequence number from corresponding ASCONF + /// @param[in] responseParams Response parameters data (Success or Error Cause Indication) + /// @param[in] responseParamsLen Length of response parameters + /// @return True if chunk was added successfully + bool addAsconfAckChunk(uint32_t serialNumber, const uint8_t* responseParams = nullptr, + size_t responseParamsLen = 0); + + /// Add a RE-CONFIG chunk to the SCTP packet (RFC 6525) + /// @param[in] parameters Re-configuration parameters data + /// @param[in] parametersLen Length of parameters + /// @return True if chunk was added successfully + /// @note Use parameter builder helpers or construct parameters manually + bool addReconfigChunk(const uint8_t* parameters, size_t parametersLen); + + // ==================== Validation Methods ==================== + + /// Validate the current packet for RFC 9260 bundling rules + /// @return SctpBundlingStatus indicating validation result + /// @note Per RFC 9260: INIT, INIT-ACK, and SHUTDOWN-COMPLETE MUST NOT be bundled + /// with any other chunk. INIT chunk packets MUST have verification tag = 0. + SctpBundlingStatus validateBundling() const; + + /// Check if a specific chunk type can be added without violating bundling rules + /// @param[in] chunkType The chunk type to check + /// @return True if the chunk can be added without violating RFC 9260 bundling rules + bool canAddChunk(SctpChunkType chunkType) const; + + /// Check if the SCTP packet contains a Host Name Address parameter + /// @return True if a deprecated Host Name Address parameter is found + /// @note Per RFC 9260, Host Name Address is deprecated and should be rejected + bool containsHostNameAddress() const; + + private: + void initLayer(); + uint8_t* getChunksBasePtr() const; + size_t getChunksDataLen() const; + bool addChunk(const uint8_t* chunkData, size_t chunkLen); + }; + + // ==================== INIT Parameter Iterator ==================== + + /// @class SctpInitParameter + /// A wrapper class for SCTP INIT/INIT-ACK parameters (TLV format) + class SctpInitParameter + { + public: + /// Construct from raw data pointer + /// @param[in] data Pointer to parameter data + /// @param[in] maxLen Maximum available length for this parameter + explicit SctpInitParameter(uint8_t* data, size_t maxLen = SIZE_MAX) + : m_Data(reinterpret_cast(data)), m_MaxLen(maxLen) + {} + + /// @return True if parameter is null/invalid + bool isNull() const + { + return m_Data == nullptr; + } + + /// @return True if parameter is not null + bool isNotNull() const + { + return m_Data != nullptr; + } + + /// @return Parameter type as enum + SctpParameterType getType() const; + + /// @return Parameter type as raw uint16_t + uint16_t getTypeAsInt() const; + + /// @return Parameter length (as specified in header, including header) + uint16_t getLength() const; + + /// @return Total size including padding (aligned to 4 bytes) + size_t getTotalSize() const; + + /// @return Pointer to parameter value (after the 4-byte header) + uint8_t* getValue() const; + + /// @return Size of parameter value (length - 4) + size_t getValueSize() const; + + /// @return Pointer to raw parameter data + uint8_t* getRecordBasePtr() const + { + return reinterpret_cast(m_Data); + } + + // ==================== Parameter-specific Methods ==================== + + /// Get IPv4 address from IPv4 Address parameter + /// @return IPv4 address, or IPv4Address::Zero if not applicable + IPv4Address getIPv4Address() const; + + /// Get IPv6 address from IPv6 Address parameter + /// @return IPv6 address, or IPv6Address::Zero if not applicable + IPv6Address getIPv6Address() const; + + /// Get supported address types from Supported Address Types parameter + /// @return Vector of supported address type values + std::vector getSupportedAddressTypes() const; + + /// Get State Cookie data from State Cookie parameter + /// @return Pointer to cookie data, or nullptr if not a State Cookie parameter + uint8_t* getStateCookie() const; + + /// Get State Cookie length from State Cookie parameter + /// @return Length of cookie data, or 0 if not a State Cookie parameter + size_t getStateCookieLength() const; + + /// Get Cookie Preservative suggested lifespan increment + /// @return Suggested Cookie Lifespan Increment in microseconds, or 0 if not applicable + uint32_t getCookiePreservativeIncrement() const; + + /// Get Random data from Random parameter (RFC 4895) + /// @return Pointer to random data, or nullptr if not a Random parameter + uint8_t* getRandomData() const; + + /// Get Random data length from Random parameter (RFC 4895) + /// @return Length of random data, or 0 if not a Random parameter + size_t getRandomDataLength() const; + + /// Get chunk types from Chunk List parameter (RFC 4895) + /// @return Vector of chunk type values + std::vector getChunkList() const; + + /// Get HMAC algorithms from Requested HMAC Algorithm parameter (RFC 4895) + /// @return Vector of HMAC identifier values + std::vector getRequestedHmacAlgorithms() const; + + /// Get supported chunk types from Supported Extensions parameter + /// @return Vector of chunk type values + std::vector getSupportedExtensions() const; + + /// Check if this is a deprecated Host Name Address parameter + /// @return True if this is a Host Name Address (which should be rejected per RFC 9260) + bool isHostNameAddress() const; + + /// Get Zero Checksum Acceptable EDMID value (RFC 9653) + /// @return Error Detection Method Identifier, or 0 if not a Zero Checksum parameter + uint32_t getZeroChecksumEdmid() const; + + /// Get Unrecognized Parameter value (RFC 9260) + /// @return Pointer to the unrecognized parameter data, or nullptr if not applicable + uint8_t* getUnrecognizedParameter() const; + + /// Get Unrecognized Parameter length (RFC 9260) + /// @return Length of unrecognized parameter data, or 0 if not applicable + size_t getUnrecognizedParameterLength() const; + + /// Get Adaptation Layer Indication value (RFC 5061) + /// @return Adaptation Layer Indication value, or 0 if not applicable + uint32_t getAdaptationLayerIndication() const; + + /// Get parameter type name as string + /// @return Parameter type name + std::string getTypeName() const; + + private: + sctp_param_hdr* m_Data; + size_t m_MaxLen; + }; + + /// @class SctpInitParameterIterator + /// Iterator for INIT/INIT-ACK parameters + class SctpInitParameterIterator + { + public: + /// Construct iterator from INIT/INIT-ACK chunk + /// @param[in] chunk The INIT or INIT-ACK chunk to iterate + explicit SctpInitParameterIterator(const SctpChunk& chunk); + + /// @return Current parameter + SctpInitParameter getParameter() const; + + /// Move to next parameter + /// @return Reference to this iterator + SctpInitParameterIterator& next(); + + /// @return True if current parameter is valid + bool isValid() const; + + /// Reset iterator to first parameter + void reset(); + + private: + uint8_t* m_ParamsBase; + size_t m_ParamsLen; + size_t m_CurrentOffset; + }; + + // ==================== Error Cause Classes ==================== + + /// @class SctpErrorCause + /// A wrapper class for SCTP error causes in ABORT/ERROR chunks + class SctpErrorCause + { + public: + /// Construct from raw data pointer + /// @param[in] data Pointer to error cause data + /// @param[in] maxLen Maximum available length for this error cause + explicit SctpErrorCause(uint8_t* data, size_t maxLen = SIZE_MAX) + : m_Data(reinterpret_cast(data)), m_MaxLen(maxLen) + {} + + /// @return True if error cause is null/invalid + bool isNull() const + { + return m_Data == nullptr; + } + + /// @return True if error cause is not null + bool isNotNull() const + { + return m_Data != nullptr; + } + + /// @return Error cause code as enum + SctpErrorCauseCode getCode() const; + + /// @return Error cause code as raw uint16_t + uint16_t getCodeAsInt() const; + + /// @return Error cause length (as specified in header, including header) + uint16_t getLength() const; + + /// @return Total size including padding (aligned to 4 bytes) + size_t getTotalSize() const; + + /// @return Pointer to error cause data (after the 4-byte header) + uint8_t* getData() const; + + /// @return Size of error cause data (length - 4) + size_t getDataSize() const; + + /// @return Pointer to raw error cause data + uint8_t* getRecordBasePtr() const + { + return reinterpret_cast(m_Data); + } + + /// Get error cause code name as string + /// @return Error cause code name + std::string getCodeName() const; + + // ==================== Cause-specific Methods ==================== + + /// Get Invalid Stream Identifier from cause data + /// @return Stream Identifier, or 0 if not applicable + uint16_t getInvalidStreamId() const; + + /// Get Stale Cookie staleness value + /// @return Measure of Staleness in microseconds, or 0 if not applicable + uint32_t getStaleCookieStaleness() const; + + /// Get TSN from No User Data cause + /// @return TSN value, or 0 if not applicable + uint32_t getNoUserDataTsn() const; + + /// Get Missing Mandatory Parameter types (RFC 9260) + /// @return Vector of missing parameter type values + std::vector getMissingMandatoryParams() const; + + /// Get Unrecognized Chunk from cause data (RFC 9260) + /// @return Pointer to the unrecognized chunk, or nullptr if not applicable + uint8_t* getUnrecognizedChunk() const; + + /// Get Unrecognized Chunk length (RFC 9260) + /// @return Length of unrecognized chunk data, or 0 if not applicable + size_t getUnrecognizedChunkLength() const; + + /// Get Unrecognized Parameters from cause data (RFC 9260) + /// @return Pointer to unrecognized parameters, or nullptr if not applicable + uint8_t* getUnrecognizedParameters() const; + + /// Get Unrecognized Parameters length (RFC 9260) + /// @return Length of unrecognized parameters data, or 0 if not applicable + size_t getUnrecognizedParametersLength() const; + + /// Get Unresolvable Address from cause data (RFC 9260) + /// @return Pointer to the address parameter, or nullptr if not applicable + uint8_t* getUnresolvableAddress() const; + + /// Get Unresolvable Address length (RFC 9260) + /// @return Length of address parameter, or 0 if not applicable + size_t getUnresolvableAddressLength() const; + + /// Get New Addresses from Restart with New Addresses cause (RFC 9260) + /// @return Pointer to new address list, or nullptr if not applicable + uint8_t* getRestartNewAddresses() const; + + /// Get New Addresses length from Restart with New Addresses cause (RFC 9260) + /// @return Length of new address list, or 0 if not applicable + size_t getRestartNewAddressesLength() const; + + private: + sctp_error_cause* m_Data; + size_t m_MaxLen; + }; + + /// @class SctpErrorCauseIterator + /// Iterator for error causes in ABORT/ERROR chunks + class SctpErrorCauseIterator + { + public: + /// Construct iterator from ABORT or ERROR chunk + /// @param[in] chunk The ABORT or ERROR chunk to iterate + explicit SctpErrorCauseIterator(const SctpChunk& chunk); + + /// @return Current error cause + SctpErrorCause getErrorCause() const; + + /// Move to next error cause + /// @return Reference to this iterator + SctpErrorCauseIterator& next(); + + /// @return True if current error cause is valid + bool isValid() const; + + /// Reset iterator to first error cause + void reset(); + + private: + uint8_t* m_CausesBase; + size_t m_CausesLen; + size_t m_CurrentOffset; + }; + + // ==================== RE-CONFIG Parameter Classes ==================== + + /// @class SctpReconfigParameter + /// A wrapper class for SCTP RE-CONFIG parameters (RFC 6525) + class SctpReconfigParameter + { + public: + /// Construct from raw data pointer + /// @param[in] data Pointer to parameter data + /// @param[in] maxLen Maximum available length for this parameter + explicit SctpReconfigParameter(uint8_t* data, size_t maxLen = SIZE_MAX) + : m_Data(reinterpret_cast(data)), m_MaxLen(maxLen) + {} + + /// @return True if parameter is null/invalid + bool isNull() const + { + return m_Data == nullptr; + } + + /// @return True if parameter is not null + bool isNotNull() const + { + return m_Data != nullptr; + } + + /// @return Parameter type as enum + SctpParameterType getType() const; + + /// @return Parameter type as raw uint16_t + uint16_t getTypeAsInt() const; + + /// @return Parameter length (as specified in header, including header) + uint16_t getLength() const; + + /// @return Total size including padding (aligned to 4 bytes) + size_t getTotalSize() const; + + /// @return Pointer to raw parameter data + uint8_t* getRecordBasePtr() const + { + return reinterpret_cast(m_Data); + } + + // ==================== Outgoing SSN Reset Request (Type 13) ==================== + + /// Get Re-configuration Request Sequence Number + /// @return Request sequence number, or 0 if not applicable + uint32_t getOutgoingReqSeqNum() const; + + /// Get Re-configuration Response Sequence Number (Outgoing SSN Reset Request only) + /// @return Response sequence number, or 0 if not applicable + uint32_t getOutgoingRespSeqNum() const; + + /// Get Sender's Last Assigned TSN (Outgoing SSN Reset Request only) + /// @return Last assigned TSN, or 0 if not applicable + uint32_t getOutgoingLastTsn() const; + + /// Get stream numbers from Outgoing/Incoming SSN Reset Request + /// @return Vector of stream IDs to reset + std::vector getResetStreamNumbers() const; + + // ==================== Incoming SSN Reset Request (Type 14) ==================== + + /// Get Re-configuration Request Sequence Number + /// @return Request sequence number, or 0 if not applicable + uint32_t getIncomingReqSeqNum() const; + + // ==================== SSN/TSN Reset Request (Type 15) ==================== + + /// Get Re-configuration Request Sequence Number + /// @return Request sequence number, or 0 if not applicable + uint32_t getSsnTsnResetReqSeqNum() const; + + // ==================== Re-configuration Response (Type 16) ==================== + + /// Get Re-configuration Response Sequence Number + /// @return Response sequence number, or 0 if not applicable + uint32_t getReconfigRespSeqNum() const; + + /// Get Result code from Re-configuration Response + /// @return Result code as SctpReconfigResult enum + SctpReconfigResult getReconfigResult() const; + + /// Get Sender's Next TSN from Re-configuration Response (optional field) + /// @return Sender's Next TSN, or 0 if not present + uint32_t getReconfigSenderNextTsn() const; + + /// Get Receiver's Next TSN from Re-configuration Response (optional field) + /// @return Receiver's Next TSN, or 0 if not present + uint32_t getReconfigReceiverNextTsn() const; + + /// Check if optional TSN fields are present in Re-configuration Response + /// @return True if Sender's/Receiver's Next TSN fields are present + bool hasReconfigOptionalTsn() const; + + // ==================== Add Streams Request (Type 17/18) ==================== + + /// Get Re-configuration Request Sequence Number + /// @return Request sequence number, or 0 if not applicable + uint32_t getAddStreamsReqSeqNum() const; + + /// Get number of new streams from Add Streams Request + /// @return Number of new streams, or 0 if not applicable + uint16_t getAddStreamsCount() const; + + /// Get parameter type name as string + /// @return Parameter type name + std::string getTypeName() const; + + private: + sctp_param_hdr* m_Data; + size_t m_MaxLen; + }; + + /// @class SctpReconfigParameterIterator + /// Iterator for RE-CONFIG chunk parameters (RFC 6525) + class SctpReconfigParameterIterator + { + public: + /// Construct iterator from RE-CONFIG chunk + /// @param[in] chunk The RE-CONFIG chunk to iterate + explicit SctpReconfigParameterIterator(const SctpChunk& chunk); + + /// @return Current parameter + SctpReconfigParameter getParameter() const; + + /// Move to next parameter + /// @return Reference to this iterator + SctpReconfigParameterIterator& next(); + + /// @return True if current parameter is valid + bool isValid() const; + + /// Reset iterator to first parameter + void reset(); + + private: + uint8_t* m_ParamsBase; + size_t m_ParamsLen; + size_t m_CurrentOffset; + }; + + // ==================== ASCONF Parameter Classes ==================== + + /// @class SctpAsconfParameter + /// A wrapper class for SCTP ASCONF/ASCONF-ACK parameters (RFC 5061) + class SctpAsconfParameter + { + public: + /// Construct from raw data pointer + /// @param[in] data Pointer to parameter data + /// @param[in] maxLen Maximum available length for this parameter + explicit SctpAsconfParameter(uint8_t* data, size_t maxLen = SIZE_MAX) + : m_Data(reinterpret_cast(data)), m_MaxLen(maxLen) + {} + + /// @return True if parameter is null/invalid + bool isNull() const + { + return m_Data == nullptr; + } + + /// @return True if parameter is not null + bool isNotNull() const + { + return m_Data != nullptr; + } + + /// @return Parameter type as enum + SctpParameterType getType() const; + + /// @return Parameter type as raw uint16_t + uint16_t getTypeAsInt() const; + + /// @return Parameter length (as specified in header, including header) + uint16_t getLength() const; + + /// @return Total size including padding (aligned to 4 bytes) + size_t getTotalSize() const; + + /// @return Pointer to raw parameter data + uint8_t* getRecordBasePtr() const + { + return reinterpret_cast(m_Data); + } + + // ==================== Request Parameters (Add/Delete/Set Primary) ==================== + + /// Get ASCONF-Request Correlation ID + /// @return Correlation ID for matching request/response + uint32_t getCorrelationId() const; + + /// Get Address Parameter from Add/Delete/Set Primary request + /// @return Pointer to address parameter (IPv4 or IPv6 TLV), or nullptr if not applicable + uint8_t* getAddressParameter() const; + + /// Get Address Parameter length + /// @return Length of address parameter, or 0 if not applicable + size_t getAddressParameterLength() const; + + /// Get IPv4 address from request (if address is IPv4) + /// @return IPv4 address, or IPv4Address::Zero if not IPv4 + IPv4Address getIPv4Address() const; + + /// Get IPv6 address from request (if address is IPv6) + /// @return IPv6 address, or IPv6Address::Zero if not IPv6 + IPv6Address getIPv6Address() const; + + // ==================== Response Parameters (Success/Error) ==================== + + /// Get ASCONF-Response Correlation ID + /// @return Correlation ID copied from request + uint32_t getResponseCorrelationId() const; + + /// Get Error Causes from Error Cause Indication response + /// @return Pointer to error causes, or nullptr if not error response + uint8_t* getErrorCauses() const; + + /// Get Error Causes length + /// @return Length of error causes, or 0 if not error response + size_t getErrorCausesLength() const; + + /// Get parameter type name as string + /// @return Parameter type name + std::string getTypeName() const; + + private: + sctp_param_hdr* m_Data; + size_t m_MaxLen; + }; + + /// @class SctpAsconfParameterIterator + /// Iterator for ASCONF/ASCONF-ACK chunk parameters (RFC 5061) + class SctpAsconfParameterIterator + { + public: + /// Construct iterator from ASCONF or ASCONF-ACK chunk + /// @param[in] chunk The ASCONF or ASCONF-ACK chunk to iterate + /// @param[in] skipAddressParam For ASCONF chunks, skip the mandatory Address Parameter + explicit SctpAsconfParameterIterator(const SctpChunk& chunk, bool skipAddressParam = true); + + /// @return Current parameter + SctpAsconfParameter getParameter() const; + + /// Move to next parameter + /// @return Reference to this iterator + SctpAsconfParameterIterator& next(); + + /// @return True if current parameter is valid + bool isValid() const; + + /// Reset iterator to first parameter + void reset(); + + private: + uint8_t* m_ParamsBase; + size_t m_ParamsLen; + size_t m_CurrentOffset; + size_t m_InitialOffset; + }; + + // ==================== CRC32c Functions ==================== + + /// Calculate CRC32c checksum for SCTP packet + /// @param[in] data Pointer to SCTP packet data + /// @param[in] length Length of data + /// @return CRC32c checksum value + uint32_t calculateSctpCrc32c(const uint8_t* data, size_t length); + + // ==================== HMAC Functions (RFC 4895) ==================== + + /// HMAC output sizes + namespace SctpHmacSize + { + /// SHA-1 HMAC output size in bytes + constexpr size_t SHA1 = 20; + /// SHA-256 HMAC output size in bytes + constexpr size_t SHA256 = 32; + } // namespace SctpHmacSize + + /// Calculate HMAC-SHA1 for SCTP AUTH chunk + /// @param[in] key Pointer to shared key data + /// @param[in] keyLen Length of shared key + /// @param[in] data Pointer to data to authenticate (SCTP packet from AUTH chunk HMAC field to end) + /// @param[in] dataLen Length of data + /// @param[out] hmacOut Output buffer for HMAC (must be at least 20 bytes) + /// @return True if HMAC was calculated successfully + bool calculateSctpHmacSha1(const uint8_t* key, size_t keyLen, const uint8_t* data, size_t dataLen, + uint8_t* hmacOut); + + /// Calculate HMAC-SHA256 for SCTP AUTH chunk + /// @param[in] key Pointer to shared key data + /// @param[in] keyLen Length of shared key + /// @param[in] data Pointer to data to authenticate (SCTP packet from AUTH chunk HMAC field to end) + /// @param[in] dataLen Length of data + /// @param[out] hmacOut Output buffer for HMAC (must be at least 32 bytes) + /// @return True if HMAC was calculated successfully + bool calculateSctpHmacSha256(const uint8_t* key, size_t keyLen, const uint8_t* data, size_t dataLen, + uint8_t* hmacOut); + + /// Verify HMAC for SCTP AUTH chunk + /// @param[in] hmacId HMAC algorithm identifier (1=SHA-1, 3=SHA-256) + /// @param[in] key Pointer to shared key data + /// @param[in] keyLen Length of shared key + /// @param[in] data Pointer to data that was authenticated + /// @param[in] dataLen Length of data + /// @param[in] expectedHmac Pointer to expected HMAC value + /// @param[in] expectedHmacLen Length of expected HMAC + /// @return True if HMAC matches expected value + bool verifySctpHmac(uint16_t hmacId, const uint8_t* key, size_t keyLen, const uint8_t* data, size_t dataLen, + const uint8_t* expectedHmac, size_t expectedHmacLen); + + /// Compute HMAC for SCTP AUTH chunk per RFC 4895 + /// This is a convenience function that handles the AUTH chunk HMAC computation procedure: + /// 1. Finds the AUTH chunk in the packet + /// 2. Zeros the HMAC field in a copy + /// 3. Computes HMAC over: AUTH chunk (with zeroed HMAC) + all chunks after AUTH + /// @param[in] sctpLayer The SCTP layer containing an AUTH chunk + /// @param[in] key Pointer to shared key data (association shared key per RFC 4895) + /// @param[in] keyLen Length of shared key + /// @param[out] hmacOut Output buffer for HMAC (must be at least 32 bytes for SHA-256) + /// @param[out] hmacOutLen On success, set to actual HMAC length (20 for SHA-1, 32 for SHA-256) + /// @return True if HMAC was computed successfully, false if no AUTH chunk or invalid parameters + /// @note Per RFC 4895, the HMAC is computed over the AUTH chunk with its HMAC field set to zero, + /// followed by all chunks placed after the AUTH chunk in the SCTP packet. + bool computeSctpAuthHmac(const SctpLayer& sctpLayer, const uint8_t* key, size_t keyLen, uint8_t* hmacOut, + size_t* hmacOutLen); + + /// Verify AUTH chunk HMAC in an SCTP packet per RFC 4895 + /// This is a convenience function that: + /// 1. Finds the AUTH chunk in the packet + /// 2. Computes the expected HMAC using the same procedure as computeSctpAuthHmac + /// 3. Compares with the HMAC in the AUTH chunk using constant-time comparison + /// @param[in] sctpLayer The SCTP layer containing an AUTH chunk to verify + /// @param[in] key Pointer to shared key data (association shared key per RFC 4895) + /// @param[in] keyLen Length of shared key + /// @return True if AUTH chunk HMAC is valid, false otherwise + bool verifySctpAuthChunk(const SctpLayer& sctpLayer, const uint8_t* key, size_t keyLen); + + // ==================== Chunk Action Bits (RFC 9260) ==================== + + /// Chunk type action bits namespace (RFC 9260 Section 3.2) + /// The upper 2 bits of the chunk type specify how to handle unrecognized chunks + namespace SctpChunkActionBits + { + /// Mask for extracting action bits from chunk type + constexpr uint8_t ACTION_MASK = 0xC0; + /// Stop processing and report in ERROR chunk (bits = 00) + constexpr uint8_t STOP_AND_REPORT = 0x00; + /// Stop processing, do not report (bits = 01) + constexpr uint8_t STOP_NO_REPORT = 0x40; + /// Skip this chunk and continue, report in ERROR chunk (bits = 10) + constexpr uint8_t SKIP_AND_REPORT = 0x80; + /// Skip this chunk and continue, do not report (bits = 11) + constexpr uint8_t SKIP_NO_REPORT = 0xC0; + } // namespace SctpChunkActionBits + + /// Get action bits from a chunk type value (RFC 9260 Section 3.2) + /// @param[in] chunkType The chunk type byte + /// @return Action bits (upper 2 bits, one of SctpChunkActionBits values) + inline uint8_t getSctpChunkActionBits(uint8_t chunkType) + { + return chunkType & SctpChunkActionBits::ACTION_MASK; + } + + /// Check if unrecognized chunk should cause processing to stop (RFC 9260) + /// @param[in] chunkType The chunk type byte + /// @return True if processing should stop (action bits = 00 or 01) + inline bool shouldStopOnUnrecognizedChunk(uint8_t chunkType) + { + return (chunkType & SctpChunkActionBits::ACTION_MASK) < SctpChunkActionBits::SKIP_AND_REPORT; + } + + /// Check if unrecognized chunk should be reported in ERROR (RFC 9260) + /// @param[in] chunkType The chunk type byte + /// @return True if chunk should be reported (action bits = 00 or 10) + inline bool shouldReportUnrecognizedChunk(uint8_t chunkType) + { + uint8_t action = chunkType & SctpChunkActionBits::ACTION_MASK; + return action == SctpChunkActionBits::STOP_AND_REPORT || action == SctpChunkActionBits::SKIP_AND_REPORT; + } + + // ==================== Parameter Action Bits (RFC 9260) ==================== + + /// Parameter type action bits namespace (RFC 9260 Section 3.2.1) + /// The upper 2 bits of the parameter type specify how to handle unrecognized parameters + namespace SctpParamActionBits + { + /// Mask for extracting action bits from parameter type + constexpr uint16_t ACTION_MASK = 0xC000; + /// Stop processing chunk, report in ERROR/Unrecognized Parameter (bits = 00) + constexpr uint16_t STOP_AND_REPORT = 0x0000; + /// Stop processing chunk, do not report (bits = 01) + constexpr uint16_t STOP_NO_REPORT = 0x4000; + /// Skip this parameter and continue, report (bits = 10) + constexpr uint16_t SKIP_AND_REPORT = 0x8000; + /// Skip this parameter and continue, do not report (bits = 11) + constexpr uint16_t SKIP_NO_REPORT = 0xC000; + } // namespace SctpParamActionBits + + /// Get action bits from a parameter type value (RFC 9260 Section 3.2.1) + /// @param[in] paramType The parameter type (host byte order) + /// @return Action bits (upper 2 bits, one of SctpParamActionBits values) + inline uint16_t getSctpParamActionBits(uint16_t paramType) + { + return paramType & SctpParamActionBits::ACTION_MASK; + } + + /// Check if unrecognized parameter should cause chunk processing to stop (RFC 9260) + /// @param[in] paramType The parameter type (host byte order) + /// @return True if chunk processing should stop (action bits = 00 or 01) + inline bool shouldStopOnUnrecognizedParam(uint16_t paramType) + { + return (paramType & SctpParamActionBits::ACTION_MASK) < SctpParamActionBits::SKIP_AND_REPORT; + } + + /// Check if unrecognized parameter should be reported (RFC 9260) + /// @param[in] paramType The parameter type (host byte order) + /// @return True if parameter should be reported (action bits = 00 or 10) + inline bool shouldReportUnrecognizedParam(uint16_t paramType) + { + uint16_t action = paramType & SctpParamActionBits::ACTION_MASK; + return action == SctpParamActionBits::STOP_AND_REPORT || action == SctpParamActionBits::SKIP_AND_REPORT; + } + +} // namespace pcpp diff --git a/Packet++/src/IPv4Layer.cpp b/Packet++/src/IPv4Layer.cpp index 4a09134b96..bddb7031fb 100644 --- a/Packet++/src/IPv4Layer.cpp +++ b/Packet++/src/IPv4Layer.cpp @@ -5,6 +5,7 @@ #include "PayloadLayer.h" #include "UdpLayer.h" #include "TcpLayer.h" +#include "SctpLayer.h" #include "IcmpLayer.h" #include "GreLayer.h" #include "IgmpLayer.h" @@ -271,6 +272,9 @@ namespace pcpp case PACKETPP_IPPROTO_TCP: tryConstructNextLayerWithFallback(payload, payloadLen, m_Packet); break; + case PACKETPP_IPPROTO_SCTP: + tryConstructNextLayerWithFallback(payload, payloadLen, m_Packet); + break; case PACKETPP_IPPROTO_ICMP: tryConstructNextLayerWithFallback(payload, payloadLen, m_Packet); break; @@ -385,6 +389,9 @@ namespace pcpp case UDP: ipHdr->protocol = PACKETPP_IPPROTO_UDP; break; + case SCTP: + ipHdr->protocol = PACKETPP_IPPROTO_SCTP; + break; case ICMP: ipHdr->protocol = PACKETPP_IPPROTO_ICMP; break; diff --git a/Packet++/src/IPv6Layer.cpp b/Packet++/src/IPv6Layer.cpp index fb17d09051..6bd2d48703 100644 --- a/Packet++/src/IPv6Layer.cpp +++ b/Packet++/src/IPv6Layer.cpp @@ -6,6 +6,7 @@ #include "PayloadLayer.h" #include "UdpLayer.h" #include "TcpLayer.h" +#include "SctpLayer.h" #include "GreLayer.h" #include "IPSecLayer.h" #include "IcmpV6Layer.h" @@ -231,6 +232,11 @@ namespace pcpp ? static_cast(new TcpLayer(payload, payloadLen, this, m_Packet)) : static_cast(new PayloadLayer(payload, payloadLen, this, m_Packet)); break; + case PACKETPP_IPPROTO_SCTP: + m_NextLayer = SctpLayer::isDataValid(payload, payloadLen) + ? static_cast(new SctpLayer(payload, payloadLen, this, m_Packet)) + : static_cast(new PayloadLayer(payload, payloadLen, this, m_Packet)); + break; case PACKETPP_IPPROTO_IPIP: { uint8_t ipVersion = *payload >> 4; @@ -300,6 +306,9 @@ namespace pcpp case UDP: nextHeader = PACKETPP_IPPROTO_UDP; break; + case SCTP: + nextHeader = PACKETPP_IPPROTO_SCTP; + break; case ICMP: nextHeader = PACKETPP_IPPROTO_ICMP; break; diff --git a/Packet++/src/SctpLayer.cpp b/Packet++/src/SctpLayer.cpp new file mode 100644 index 0000000000..4eb811a4ca --- /dev/null +++ b/Packet++/src/SctpLayer.cpp @@ -0,0 +1,3759 @@ +#define LOG_MODULE PacketLogModuleSctpLayer + +#include "EndianPortable.h" +#include "SctpLayer.h" +#include "PayloadLayer.h" +#include "Logger.h" +#include +#include + +namespace pcpp +{ + // ==================== CRC32c Implementation ==================== + // CRC32c (Castagnoli) polynomial: 0x1EDC6F41 + // This is the polynomial used by SCTP as defined in RFC 9260 + + namespace + { + // CRC32c lookup table (Castagnoli polynomial) + // Pre-computed for polynomial 0x1EDC6F41 (reflected) + constexpr uint32_t crc32cTable[256] = { + 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB, 0x8AD958CF, + 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24, 0x105EC76F, 0xE235446C, + 0xF165B798, 0x030E349B, 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384, 0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, + 0x89D76C54, 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B, 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, + 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35, 0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, 0x6DFE410E, + 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA, 0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD, + 0x1642AE59, 0xE4292D5A, 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, 0x7DA08661, 0x8FCB0562, 0x9C9BF696, + 0x6EF07595, 0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957, + 0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198, 0x5125DAD3, + 0xA34E59D0, 0xB01EAA24, 0x42752927, 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38, 0xDBFC821C, 0x2997011F, + 0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7, 0x61C69362, 0x93AD1061, 0x80FDE395, + 0x72966096, 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789, 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, + 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46, 0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, 0xB602C312, + 0x44694011, 0x5739B3E5, 0xA55230E6, 0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD, 0xCEB018DE, + 0xDDE0EB2A, 0x2F8B6829, 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, 0x456CAC67, 0xB7072F64, 0xA457DC90, + 0x563C5F93, 0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C, + 0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC, 0x1871A4D8, + 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033, 0xA24BB5A6, 0x502036A5, + 0x4370C551, 0xB11B4652, 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D, 0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, + 0x3BC21E9D, 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982, 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, + 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, 0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19, + 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED, 0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 0x0417B1DB, 0xF67C32D8, + 0xE52CC12C, 0x1747422F, 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, + 0x9D9E1AE0, 0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540, + 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F, 0xE330A81A, + 0x115B2B19, 0x020BD8ED, 0xF0605BEE, 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1, 0x69E9F0D5, 0x9B8273D6, + 0x88D28022, 0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E, 0xF36E6F75, 0x0105EC76, 0x12551F82, + 0xE03E9C81, 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E, 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, + 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351 + }; + + /// Calculate CRC32c using software lookup table + uint32_t calculateCrc32cSoftware(const uint8_t* data, size_t length) + { + uint32_t crc = 0xFFFFFFFF; + for (size_t i = 0; i < length; ++i) + { + crc = crc32cTable[(crc ^ data[i]) & 0xFF] ^ (crc >> 8); + } + return crc ^ 0xFFFFFFFF; + } + } // anonymous namespace + + uint32_t calculateSctpCrc32c(const uint8_t* data, size_t length) + { + return calculateCrc32cSoftware(data, length); + } + + // ==================== SctpChunk Implementation ==================== + + SctpChunkType SctpChunk::getChunkType() const + { + if (m_Data == nullptr) + return SctpChunkType::UNKNOWN; + + uint8_t type = m_Data->type; + switch (type) + { + case 0: + return SctpChunkType::DATA; + case 1: + return SctpChunkType::INIT; + case 2: + return SctpChunkType::INIT_ACK; + case 3: + return SctpChunkType::SACK; + case 4: + return SctpChunkType::HEARTBEAT; + case 5: + return SctpChunkType::HEARTBEAT_ACK; + case 6: + return SctpChunkType::ABORT; + case 7: + return SctpChunkType::SHUTDOWN; + case 8: + return SctpChunkType::SHUTDOWN_ACK; + case 9: + return SctpChunkType::SCTP_ERROR; + case 10: + return SctpChunkType::COOKIE_ECHO; + case 11: + return SctpChunkType::COOKIE_ACK; + case 12: + return SctpChunkType::ECNE; + case 13: + return SctpChunkType::CWR; + case 14: + return SctpChunkType::SHUTDOWN_COMPLETE; + case 15: + return SctpChunkType::AUTH; + case 16: + return SctpChunkType::NR_SACK; + case 64: + return SctpChunkType::I_DATA; + case 128: + return SctpChunkType::ASCONF_ACK; + case 130: + return SctpChunkType::RE_CONFIG; + case 132: + return SctpChunkType::PAD; + case 192: + return SctpChunkType::FORWARD_TSN; + case 193: + return SctpChunkType::ASCONF; + case 194: + return SctpChunkType::I_FORWARD_TSN; + default: + return SctpChunkType::UNKNOWN; + } + } + + uint8_t SctpChunk::getChunkTypeAsInt() const + { + if (m_Data == nullptr) + return 255; + return m_Data->type; + } + + uint8_t SctpChunk::getFlags() const + { + if (m_Data == nullptr) + return 0; + return m_Data->flags; + } + + uint16_t SctpChunk::getLength() const + { + if (m_Data == nullptr) + return 0; + return be16toh(m_Data->length); + } + + size_t SctpChunk::getTotalSize() const + { + uint16_t len = getLength(); + if (len == 0) + return 0; + // Pad to 4-byte boundary + return (len + 3) & ~3; + } + + uint8_t* SctpChunk::getValue() const + { + if (m_Data == nullptr) + return nullptr; + return reinterpret_cast(m_Data) + sizeof(sctp_chunk_hdr); + } + + size_t SctpChunk::getValueSize() const + { + uint16_t len = getLength(); + if (len <= sizeof(sctp_chunk_hdr)) + return 0; + return len - sizeof(sctp_chunk_hdr); + } + + bool SctpChunk::isFlagSet(uint8_t flagBit) const + { + return (getFlags() & flagBit) != 0; + } + + std::string SctpChunk::getChunkTypeName() const + { + switch (getChunkType()) + { + case SctpChunkType::DATA: + return "DATA"; + case SctpChunkType::INIT: + return "INIT"; + case SctpChunkType::INIT_ACK: + return "INIT-ACK"; + case SctpChunkType::SACK: + return "SACK"; + case SctpChunkType::HEARTBEAT: + return "HEARTBEAT"; + case SctpChunkType::HEARTBEAT_ACK: + return "HEARTBEAT-ACK"; + case SctpChunkType::ABORT: + return "ABORT"; + case SctpChunkType::SHUTDOWN: + return "SHUTDOWN"; + case SctpChunkType::SHUTDOWN_ACK: + return "SHUTDOWN-ACK"; + case SctpChunkType::SCTP_ERROR: + return "ERROR"; + case SctpChunkType::COOKIE_ECHO: + return "COOKIE-ECHO"; + case SctpChunkType::COOKIE_ACK: + return "COOKIE-ACK"; + case SctpChunkType::ECNE: + return "ECNE"; + case SctpChunkType::CWR: + return "CWR"; + case SctpChunkType::SHUTDOWN_COMPLETE: + return "SHUTDOWN-COMPLETE"; + case SctpChunkType::AUTH: + return "AUTH"; + case SctpChunkType::NR_SACK: + return "NR-SACK"; + case SctpChunkType::I_DATA: + return "I-DATA"; + case SctpChunkType::ASCONF_ACK: + return "ASCONF-ACK"; + case SctpChunkType::RE_CONFIG: + return "RE-CONFIG"; + case SctpChunkType::PAD: + return "PAD"; + case SctpChunkType::FORWARD_TSN: + return "FORWARD-TSN"; + case SctpChunkType::ASCONF: + return "ASCONF"; + case SctpChunkType::I_FORWARD_TSN: + return "I-FORWARD-TSN"; + default: + return "UNKNOWN"; + } + } + + // ==================== SctpDataChunkView Implementation ==================== + + uint32_t SctpDataChunkView::getTsn() const + { + if (!isValid()) + return 0; + auto* dataChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be32toh(dataChunk->tsn); + } + + uint16_t SctpDataChunkView::getStreamId() const + { + if (!isValid()) + return 0; + auto* dataChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be16toh(dataChunk->streamId); + } + + uint16_t SctpDataChunkView::getSequenceNumber() const + { + if (!isValid()) + return 0; + auto* dataChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be16toh(dataChunk->streamSeqNum); + } + + uint32_t SctpDataChunkView::getPpid() const + { + if (!isValid()) + return 0; + auto* dataChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be32toh(dataChunk->ppid); + } + + uint8_t* SctpDataChunkView::getUserData() const + { + if (!isValid()) + return nullptr; + return m_Chunk.getRecordBasePtr() + sizeof(sctp_data_chunk); + } + + size_t SctpDataChunkView::getUserDataLength() const + { + if (!isValid()) + return 0; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_data_chunk)) + return 0; + return len - sizeof(sctp_data_chunk); + } + + bool SctpDataChunkView::isBeginFragment() const + { + return m_Chunk.isFlagSet(SctpDataChunkFlags::BEGIN_FRAGMENT); + } + + bool SctpDataChunkView::isEndFragment() const + { + return m_Chunk.isFlagSet(SctpDataChunkFlags::END_FRAGMENT); + } + + bool SctpDataChunkView::isUnordered() const + { + return m_Chunk.isFlagSet(SctpDataChunkFlags::UNORDERED); + } + + bool SctpDataChunkView::isImmediate() const + { + return m_Chunk.isFlagSet(SctpDataChunkFlags::IMMEDIATE); + } + + // ==================== SctpInitChunkView Implementation ==================== + + uint32_t SctpInitChunkView::getInitiateTag() const + { + if (!isValid()) + return 0; + auto* initChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be32toh(initChunk->initiateTag); + } + + uint32_t SctpInitChunkView::getArwnd() const + { + if (!isValid()) + return 0; + auto* initChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be32toh(initChunk->arwnd); + } + + uint16_t SctpInitChunkView::getNumOutboundStreams() const + { + if (!isValid()) + return 0; + auto* initChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be16toh(initChunk->numOutboundStreams); + } + + uint16_t SctpInitChunkView::getNumInboundStreams() const + { + if (!isValid()) + return 0; + auto* initChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be16toh(initChunk->numInboundStreams); + } + + uint32_t SctpInitChunkView::getInitialTsn() const + { + if (!isValid()) + return 0; + auto* initChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be32toh(initChunk->initialTsn); + } + + uint8_t* SctpInitChunkView::getFirstParameter() const + { + if (!isValid()) + return nullptr; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_init_chunk)) + return nullptr; + return m_Chunk.getRecordBasePtr() + sizeof(sctp_init_chunk); + } + + size_t SctpInitChunkView::getParametersLength() const + { + if (!isValid()) + return 0; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_init_chunk)) + return 0; + return len - sizeof(sctp_init_chunk); + } + + // ==================== SctpInitAckChunkView Implementation ==================== + + uint32_t SctpInitAckChunkView::getInitiateTag() const + { + if (!isValid()) + return 0; + auto* initChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be32toh(initChunk->initiateTag); + } + + uint32_t SctpInitAckChunkView::getArwnd() const + { + if (!isValid()) + return 0; + auto* initChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be32toh(initChunk->arwnd); + } + + uint16_t SctpInitAckChunkView::getNumOutboundStreams() const + { + if (!isValid()) + return 0; + auto* initChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be16toh(initChunk->numOutboundStreams); + } + + uint16_t SctpInitAckChunkView::getNumInboundStreams() const + { + if (!isValid()) + return 0; + auto* initChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be16toh(initChunk->numInboundStreams); + } + + uint32_t SctpInitAckChunkView::getInitialTsn() const + { + if (!isValid()) + return 0; + auto* initChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be32toh(initChunk->initialTsn); + } + + uint8_t* SctpInitAckChunkView::getFirstParameter() const + { + if (!isValid()) + return nullptr; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_init_chunk)) + return nullptr; + return m_Chunk.getRecordBasePtr() + sizeof(sctp_init_chunk); + } + + size_t SctpInitAckChunkView::getParametersLength() const + { + if (!isValid()) + return 0; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_init_chunk)) + return 0; + return len - sizeof(sctp_init_chunk); + } + + // ==================== SctpSackChunkView Implementation ==================== + + uint32_t SctpSackChunkView::getCumulativeTsnAck() const + { + if (!isValid()) + return 0; + auto* sackChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be32toh(sackChunk->cumulativeTsnAck); + } + + uint32_t SctpSackChunkView::getArwnd() const + { + if (!isValid()) + return 0; + auto* sackChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be32toh(sackChunk->arwnd); + } + + uint16_t SctpSackChunkView::getNumGapBlocks() const + { + if (!isValid()) + return 0; + auto* sackChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be16toh(sackChunk->numGapBlocks); + } + + uint16_t SctpSackChunkView::getNumDupTsns() const + { + if (!isValid()) + return 0; + auto* sackChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be16toh(sackChunk->numDupTsns); + } + + std::vector SctpSackChunkView::getGapBlocks() const + { + std::vector result; + if (!isValid()) + return result; + + auto* sackChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + uint16_t numGapBlocks = be16toh(sackChunk->numGapBlocks); + + const uint8_t* gapBlocksPtr = m_Chunk.getRecordBasePtr() + sizeof(sctp_sack_chunk); + size_t availableLen = m_Chunk.getLength() - sizeof(sctp_sack_chunk); + + for (uint16_t i = 0; i < numGapBlocks && (i + 1) * sizeof(sctp_gap_ack_block) <= availableLen; ++i) + { + const auto* block = + reinterpret_cast(gapBlocksPtr + i * sizeof(sctp_gap_ack_block)); + sctp_gap_ack_block gapBlock; + gapBlock.start = be16toh(block->start); + gapBlock.end = be16toh(block->end); + result.push_back(gapBlock); + } + + return result; + } + + std::vector SctpSackChunkView::getDupTsns() const + { + std::vector result; + if (!isValid()) + return result; + + auto* sackChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + uint16_t numGapBlocks = be16toh(sackChunk->numGapBlocks); + uint16_t numDupTsns = be16toh(sackChunk->numDupTsns); + + size_t dupTsnsOffset = sizeof(sctp_sack_chunk) + numGapBlocks * sizeof(sctp_gap_ack_block); + const uint8_t* dupTsnsPtr = m_Chunk.getRecordBasePtr() + dupTsnsOffset; + size_t availableLen = m_Chunk.getLength() - dupTsnsOffset; + + for (uint16_t i = 0; i < numDupTsns && (i + 1) * sizeof(uint32_t) <= availableLen; ++i) + { + const auto* tsn = reinterpret_cast(dupTsnsPtr + i * sizeof(uint32_t)); + result.push_back(be32toh(*tsn)); + } + + return result; + } + + // ==================== SctpHeartbeatChunkView Implementation ==================== + + uint8_t* SctpHeartbeatChunkView::getInfo() const + { + if (!isValid()) + return nullptr; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_chunk_hdr)) + return nullptr; + return m_Chunk.getRecordBasePtr() + sizeof(sctp_chunk_hdr); + } + + size_t SctpHeartbeatChunkView::getInfoLength() const + { + if (!isValid()) + return 0; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_chunk_hdr)) + return 0; + return len - sizeof(sctp_chunk_hdr); + } + + // ==================== SctpHeartbeatAckChunkView Implementation ==================== + + uint8_t* SctpHeartbeatAckChunkView::getInfo() const + { + if (!isValid()) + return nullptr; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_chunk_hdr)) + return nullptr; + return m_Chunk.getRecordBasePtr() + sizeof(sctp_chunk_hdr); + } + + size_t SctpHeartbeatAckChunkView::getInfoLength() const + { + if (!isValid()) + return 0; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_chunk_hdr)) + return 0; + return len - sizeof(sctp_chunk_hdr); + } + + // ==================== SctpAbortChunkView Implementation ==================== + + bool SctpAbortChunkView::isTBitSet() const + { + if (!isValid()) + return false; + return (m_Chunk.getFlags() & 0x01) != 0; + } + + uint8_t* SctpAbortChunkView::getFirstErrorCause() const + { + if (!isValid()) + return nullptr; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_chunk_hdr)) + return nullptr; + return m_Chunk.getRecordBasePtr() + sizeof(sctp_chunk_hdr); + } + + size_t SctpAbortChunkView::getErrorCausesLength() const + { + if (!isValid()) + return 0; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_chunk_hdr)) + return 0; + return len - sizeof(sctp_chunk_hdr); + } + + // ==================== SctpErrorChunkView Implementation ==================== + + uint8_t* SctpErrorChunkView::getFirstCause() const + { + if (!isValid()) + return nullptr; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_chunk_hdr)) + return nullptr; + return m_Chunk.getRecordBasePtr() + sizeof(sctp_chunk_hdr); + } + + size_t SctpErrorChunkView::getCausesLength() const + { + if (!isValid()) + return 0; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_chunk_hdr)) + return 0; + return len - sizeof(sctp_chunk_hdr); + } + + // ==================== SctpShutdownChunkView Implementation ==================== + + uint32_t SctpShutdownChunkView::getCumulativeTsnAck() const + { + if (!isValid()) + return 0; + // SHUTDOWN chunk has 4-byte header + 4-byte cumulative TSN ack + auto* shutdownData = reinterpret_cast(m_Chunk.getRecordBasePtr() + sizeof(sctp_chunk_hdr)); + return be32toh(*shutdownData); + } + + // ==================== SctpShutdownCompleteChunkView Implementation ==================== + + bool SctpShutdownCompleteChunkView::isTBitSet() const + { + if (!isValid()) + return false; + return (m_Chunk.getFlags() & 0x01) != 0; + } + + // ==================== SctpCookieEchoChunkView Implementation ==================== + + uint8_t* SctpCookieEchoChunkView::getCookie() const + { + if (!isValid()) + return nullptr; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_chunk_hdr)) + return nullptr; + return m_Chunk.getRecordBasePtr() + sizeof(sctp_chunk_hdr); + } + + size_t SctpCookieEchoChunkView::getCookieLength() const + { + if (!isValid()) + return 0; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_chunk_hdr)) + return 0; + return len - sizeof(sctp_chunk_hdr); + } + + // ==================== SctpEcneChunkView Implementation ==================== + + uint32_t SctpEcneChunkView::getLowestTsn() const + { + if (!isValid()) + return 0; + // ECNE chunk has 4-byte header + 4-byte lowest TSN + auto* ecneData = reinterpret_cast(m_Chunk.getRecordBasePtr() + sizeof(sctp_chunk_hdr)); + return be32toh(*ecneData); + } + + // ==================== SctpCwrChunkView Implementation ==================== + + uint32_t SctpCwrChunkView::getLowestTsn() const + { + if (!isValid()) + return 0; + // CWR chunk has 4-byte header + 4-byte lowest TSN + auto* cwrData = reinterpret_cast(m_Chunk.getRecordBasePtr() + sizeof(sctp_chunk_hdr)); + return be32toh(*cwrData); + } + + // ==================== SctpAuthChunkView Implementation ==================== + + uint16_t SctpAuthChunkView::getSharedKeyId() const + { + if (!isValid()) + return 0; + auto* authChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be16toh(authChunk->sharedKeyId); + } + + uint16_t SctpAuthChunkView::getHmacId() const + { + if (!isValid()) + return 0; + auto* authChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be16toh(authChunk->hmacId); + } + + uint8_t* SctpAuthChunkView::getHmacData() const + { + if (!isValid()) + return nullptr; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_auth_chunk)) + return nullptr; + return m_Chunk.getRecordBasePtr() + sizeof(sctp_auth_chunk); + } + + size_t SctpAuthChunkView::getHmacLength() const + { + if (!isValid()) + return 0; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_auth_chunk)) + return 0; + return len - sizeof(sctp_auth_chunk); + } + + // ==================== SctpForwardTsnChunkView Implementation ==================== + + uint32_t SctpForwardTsnChunkView::getNewCumulativeTsn() const + { + if (!isValid()) + return 0; + auto* fwdChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be32toh(fwdChunk->newCumulativeTsn); + } + + size_t SctpForwardTsnChunkView::getStreamCount() const + { + if (!isValid()) + return 0; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_forward_tsn_chunk)) + return 0; + return (len - sizeof(sctp_forward_tsn_chunk)) / sizeof(sctp_forward_tsn_stream); + } + + std::vector SctpForwardTsnChunkView::getStreams() const + { + std::vector result; + if (!isValid()) + return result; + + size_t count = getStreamCount(); + const uint8_t* streamPtr = m_Chunk.getRecordBasePtr() + sizeof(sctp_forward_tsn_chunk); + + for (size_t i = 0; i < count; ++i) + { + const auto* entry = + reinterpret_cast(streamPtr + i * sizeof(sctp_forward_tsn_stream)); + sctp_forward_tsn_stream stream; + stream.streamId = be16toh(entry->streamId); + stream.streamSeq = be16toh(entry->streamSeq); + result.push_back(stream); + } + + return result; + } + + // ==================== SctpIDataChunkView Implementation ==================== + + uint32_t SctpIDataChunkView::getTsn() const + { + if (!isValid()) + return 0; + auto* idataChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be32toh(idataChunk->tsn); + } + + uint16_t SctpIDataChunkView::getStreamId() const + { + if (!isValid()) + return 0; + auto* idataChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be16toh(idataChunk->streamId); + } + + uint16_t SctpIDataChunkView::getReserved() const + { + if (!isValid()) + return 0; + auto* idataChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be16toh(idataChunk->reserved); + } + + uint32_t SctpIDataChunkView::getMessageId() const + { + if (!isValid()) + return 0; + auto* idataChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be32toh(idataChunk->mid); + } + + uint32_t SctpIDataChunkView::getPpidOrFsn() const + { + if (!isValid()) + return 0; + auto* idataChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be32toh(idataChunk->ppidOrFsn); + } + + uint8_t* SctpIDataChunkView::getUserData() const + { + if (!isValid()) + return nullptr; + return m_Chunk.getRecordBasePtr() + sizeof(sctp_idata_chunk); + } + + size_t SctpIDataChunkView::getUserDataLength() const + { + if (!isValid()) + return 0; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_idata_chunk)) + return 0; + return len - sizeof(sctp_idata_chunk); + } + + bool SctpIDataChunkView::isBeginFragment() const + { + return m_Chunk.isFlagSet(SctpDataChunkFlags::BEGIN_FRAGMENT); + } + + bool SctpIDataChunkView::isEndFragment() const + { + return m_Chunk.isFlagSet(SctpDataChunkFlags::END_FRAGMENT); + } + + bool SctpIDataChunkView::isUnordered() const + { + return m_Chunk.isFlagSet(SctpDataChunkFlags::UNORDERED); + } + + bool SctpIDataChunkView::isImmediate() const + { + return m_Chunk.isFlagSet(SctpDataChunkFlags::IMMEDIATE); + } + + // ==================== SctpIForwardTsnChunkView Implementation ==================== + + uint32_t SctpIForwardTsnChunkView::getNewCumulativeTsn() const + { + if (!isValid()) + return 0; + auto* ifwdChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be32toh(ifwdChunk->newCumulativeTsn); + } + + size_t SctpIForwardTsnChunkView::getStreamCount() const + { + if (!isValid()) + return 0; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_iforward_tsn_chunk)) + return 0; + return (len - sizeof(sctp_iforward_tsn_chunk)) / sizeof(sctp_iforward_tsn_stream); + } + + std::vector SctpIForwardTsnChunkView::getStreams() const + { + std::vector result; + if (!isValid()) + return result; + + size_t count = getStreamCount(); + const uint8_t* streamPtr = m_Chunk.getRecordBasePtr() + sizeof(sctp_iforward_tsn_chunk); + + for (size_t i = 0; i < count; ++i) + { + const auto* entry = + reinterpret_cast(streamPtr + i * sizeof(sctp_iforward_tsn_stream)); + sctp_iforward_tsn_stream stream; + stream.streamId = be16toh(entry->streamId); + stream.reserved = be16toh(entry->reserved); + stream.mid = be32toh(entry->mid); + result.push_back(stream); + } + + return result; + } + + // ==================== SctpAsconfChunkView Implementation ==================== + + uint32_t SctpAsconfChunkView::getSerialNumber() const + { + if (!isValid()) + return 0; + auto* asconfChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be32toh(asconfChunk->serialNumber); + } + + uint8_t* SctpAsconfChunkView::getFirstParameter() const + { + if (!isValid()) + return nullptr; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_asconf_chunk)) + return nullptr; + return m_Chunk.getRecordBasePtr() + sizeof(sctp_asconf_chunk); + } + + size_t SctpAsconfChunkView::getParametersLength() const + { + if (!isValid()) + return 0; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_asconf_chunk)) + return 0; + return len - sizeof(sctp_asconf_chunk); + } + + // ==================== SctpAsconfAckChunkView Implementation ==================== + + uint32_t SctpAsconfAckChunkView::getSerialNumber() const + { + if (!isValid()) + return 0; + auto* asconfAckChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be32toh(asconfAckChunk->serialNumber); + } + + uint8_t* SctpAsconfAckChunkView::getFirstParameter() const + { + if (!isValid()) + return nullptr; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_asconf_ack_chunk)) + return nullptr; + return m_Chunk.getRecordBasePtr() + sizeof(sctp_asconf_ack_chunk); + } + + size_t SctpAsconfAckChunkView::getParametersLength() const + { + if (!isValid()) + return 0; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_asconf_ack_chunk)) + return 0; + return len - sizeof(sctp_asconf_ack_chunk); + } + + // ==================== SctpReconfigChunkView Implementation ==================== + + uint8_t* SctpReconfigChunkView::getFirstParameter() const + { + if (!isValid()) + return nullptr; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_reconfig_chunk)) + return nullptr; + return m_Chunk.getRecordBasePtr() + sizeof(sctp_reconfig_chunk); + } + + size_t SctpReconfigChunkView::getParametersLength() const + { + if (!isValid()) + return 0; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_reconfig_chunk)) + return 0; + return len - sizeof(sctp_reconfig_chunk); + } + + // ==================== SctpPadChunkView Implementation ==================== + + uint8_t* SctpPadChunkView::getPaddingData() const + { + if (!isValid()) + return nullptr; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_pad_chunk)) + return nullptr; + return m_Chunk.getRecordBasePtr() + sizeof(sctp_pad_chunk); + } + + size_t SctpPadChunkView::getPaddingLength() const + { + if (!isValid()) + return 0; + uint16_t len = m_Chunk.getLength(); + if (len <= sizeof(sctp_pad_chunk)) + return 0; + return len - sizeof(sctp_pad_chunk); + } + + // ==================== SctpNrSackChunkView Implementation ==================== + + uint32_t SctpNrSackChunkView::getCumulativeTsnAck() const + { + if (!isValid()) + return 0; + auto* nrsackChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be32toh(nrsackChunk->cumulativeTsnAck); + } + + uint32_t SctpNrSackChunkView::getArwnd() const + { + if (!isValid()) + return 0; + auto* nrsackChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be32toh(nrsackChunk->arwnd); + } + + uint16_t SctpNrSackChunkView::getNumGapBlocks() const + { + if (!isValid()) + return 0; + auto* nrsackChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be16toh(nrsackChunk->numGapBlocks); + } + + uint16_t SctpNrSackChunkView::getNumNrGapBlocks() const + { + if (!isValid()) + return 0; + auto* nrsackChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be16toh(nrsackChunk->numNrGapBlocks); + } + + uint16_t SctpNrSackChunkView::getNumDupTsns() const + { + if (!isValid()) + return 0; + auto* nrsackChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + return be16toh(nrsackChunk->numDupTsns); + } + + bool SctpNrSackChunkView::isAllNonRenegable() const + { + if (!isValid()) + return false; + return (getFlags() & SctpNrSackFlags::ALL_NON_RENEGABLE) != 0; + } + + std::vector SctpNrSackChunkView::getGapBlocks() const + { + std::vector result; + if (!isValid()) + return result; + + auto* nrsackChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + uint16_t numGapBlocks = be16toh(nrsackChunk->numGapBlocks); + + const uint8_t* gapBlocksPtr = m_Chunk.getRecordBasePtr() + sizeof(sctp_nr_sack_chunk); + size_t availableLen = m_Chunk.getLength() - sizeof(sctp_nr_sack_chunk); + + for (uint16_t i = 0; i < numGapBlocks && (i + 1) * sizeof(sctp_gap_ack_block) <= availableLen; ++i) + { + const auto* block = + reinterpret_cast(gapBlocksPtr + i * sizeof(sctp_gap_ack_block)); + sctp_gap_ack_block gapBlock; + gapBlock.start = be16toh(block->start); + gapBlock.end = be16toh(block->end); + result.push_back(gapBlock); + } + + return result; + } + + std::vector SctpNrSackChunkView::getNrGapBlocks() const + { + std::vector result; + if (!isValid()) + return result; + + auto* nrsackChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + uint16_t numGapBlocks = be16toh(nrsackChunk->numGapBlocks); + uint16_t numNrGapBlocks = be16toh(nrsackChunk->numNrGapBlocks); + + size_t nrGapBlocksOffset = sizeof(sctp_nr_sack_chunk) + numGapBlocks * sizeof(sctp_gap_ack_block); + const uint8_t* nrGapBlocksPtr = m_Chunk.getRecordBasePtr() + nrGapBlocksOffset; + size_t availableLen = m_Chunk.getLength() - nrGapBlocksOffset; + + for (uint16_t i = 0; i < numNrGapBlocks && (i + 1) * sizeof(sctp_gap_ack_block) <= availableLen; ++i) + { + const auto* block = + reinterpret_cast(nrGapBlocksPtr + i * sizeof(sctp_gap_ack_block)); + sctp_gap_ack_block gapBlock; + gapBlock.start = be16toh(block->start); + gapBlock.end = be16toh(block->end); + result.push_back(gapBlock); + } + + return result; + } + + std::vector SctpNrSackChunkView::getDupTsns() const + { + std::vector result; + if (!isValid()) + return result; + + auto* nrsackChunk = reinterpret_cast(m_Chunk.getRecordBasePtr()); + uint16_t numGapBlocks = be16toh(nrsackChunk->numGapBlocks); + uint16_t numNrGapBlocks = be16toh(nrsackChunk->numNrGapBlocks); + uint16_t numDupTsns = be16toh(nrsackChunk->numDupTsns); + + size_t dupTsnsOffset = sizeof(sctp_nr_sack_chunk) + numGapBlocks * sizeof(sctp_gap_ack_block) + + numNrGapBlocks * sizeof(sctp_gap_ack_block); + const uint8_t* dupTsnsPtr = m_Chunk.getRecordBasePtr() + dupTsnsOffset; + size_t availableLen = m_Chunk.getLength() - dupTsnsOffset; + + for (uint16_t i = 0; i < numDupTsns && (i + 1) * sizeof(uint32_t) <= availableLen; ++i) + { + const auto* tsn = reinterpret_cast(dupTsnsPtr + i * sizeof(uint32_t)); + result.push_back(be32toh(*tsn)); + } + + return result; + } + + // ==================== SctpLayer Implementation ==================== + + SctpLayer::SctpLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet) + : Layer(data, dataLen, prevLayer, packet, SCTP) + {} + + SctpLayer::SctpLayer(uint16_t srcPort, uint16_t dstPort, uint32_t tag) + { + m_DataLen = sizeof(sctphdr); + m_Data = new uint8_t[m_DataLen]; + std::memset(m_Data, 0, m_DataLen); + m_Protocol = SCTP; + + sctphdr* hdr = getSctpHeader(); + hdr->portSrc = htobe16(srcPort); + hdr->portDst = htobe16(dstPort); + hdr->verificationTag = htobe32(tag); + hdr->checksum = 0; + } + + void SctpLayer::initLayer() + { + m_DataLen = sizeof(sctphdr); + m_Data = new uint8_t[m_DataLen]; + std::memset(m_Data, 0, m_DataLen); + m_Protocol = SCTP; + } + + uint16_t SctpLayer::getSrcPort() const + { + return be16toh(getSctpHeader()->portSrc); + } + + uint16_t SctpLayer::getDstPort() const + { + return be16toh(getSctpHeader()->portDst); + } + + uint32_t SctpLayer::getVerificationTag() const + { + return be32toh(getSctpHeader()->verificationTag); + } + + void SctpLayer::setSrcPort(uint16_t port) + { + getSctpHeader()->portSrc = htobe16(port); + } + + void SctpLayer::setDstPort(uint16_t port) + { + getSctpHeader()->portDst = htobe16(port); + } + + void SctpLayer::setVerificationTag(uint32_t tag) + { + getSctpHeader()->verificationTag = htobe32(tag); + } + + uint8_t* SctpLayer::getChunksBasePtr() const + { + return m_Data + sizeof(sctphdr); + } + + size_t SctpLayer::getChunksDataLen() const + { + if (m_DataLen <= sizeof(sctphdr)) + return 0; + return m_DataLen - sizeof(sctphdr); + } + + size_t SctpLayer::getChunkCount() const + { + size_t count = 0; + SctpChunk chunk = getFirstChunk(); + while (chunk.isNotNull()) + { + ++count; + chunk = getNextChunk(chunk); + } + return count; + } + + SctpChunk SctpLayer::getFirstChunk() const + { + size_t chunksLen = getChunksDataLen(); + if (chunksLen < sizeof(sctp_chunk_hdr)) + return SctpChunk(nullptr); + + uint8_t* chunksPtr = getChunksBasePtr(); + + // Validate chunk length + auto* chunkHdr = reinterpret_cast(chunksPtr); + uint16_t chunkLen = be16toh(chunkHdr->length); + if (chunkLen < sizeof(sctp_chunk_hdr) || chunkLen > chunksLen) + return SctpChunk(nullptr); + + return SctpChunk(chunksPtr); + } + + SctpChunk SctpLayer::getNextChunk(const SctpChunk& chunk) const + { + if (chunk.isNull()) + return SctpChunk(nullptr); + + uint8_t* chunksBase = getChunksBasePtr(); + size_t chunksLen = getChunksDataLen(); + + // Calculate offset of current chunk from start of chunks area + uint8_t* currentChunkPtr = chunk.getRecordBasePtr(); + if (currentChunkPtr < chunksBase) + return SctpChunk(nullptr); + + size_t currentOffset = currentChunkPtr - chunksBase; + size_t currentChunkTotalSize = chunk.getTotalSize(); + + if (currentChunkTotalSize == 0) + return SctpChunk(nullptr); + + size_t nextOffset = currentOffset + currentChunkTotalSize; + + // Check if there's room for another chunk header + if (nextOffset + sizeof(sctp_chunk_hdr) > chunksLen) + return SctpChunk(nullptr); + + uint8_t* nextChunkPtr = chunksBase + nextOffset; + + // Validate next chunk length + auto* nextChunkHdr = reinterpret_cast(nextChunkPtr); + uint16_t nextChunkLen = be16toh(nextChunkHdr->length); + if (nextChunkLen < sizeof(sctp_chunk_hdr) || nextOffset + nextChunkLen > chunksLen) + return SctpChunk(nullptr); + + return SctpChunk(nextChunkPtr); + } + + SctpChunk SctpLayer::getChunk(SctpChunkType chunkType) const + { + SctpChunk chunk = getFirstChunk(); + while (chunk.isNotNull()) + { + if (chunk.getChunkType() == chunkType) + return chunk; + chunk = getNextChunk(chunk); + } + return SctpChunk(nullptr); + } + + uint32_t SctpLayer::calculateChecksum(bool writeResultToPacket) + { + sctphdr* hdr = getSctpHeader(); + uint32_t originalChecksum = hdr->checksum; + + // Set checksum field to 0 for calculation + hdr->checksum = 0; + + // Calculate CRC32c over entire SCTP packet + uint32_t crc = calculateSctpCrc32c(m_Data, m_DataLen); + + if (writeResultToPacket) + { + // Per RFC 3309/9260, the checksum is stored in network byte order (big-endian) + hdr->checksum = htobe32(crc); + } + else + { + hdr->checksum = originalChecksum; + } + + return crc; + } + + bool SctpLayer::isChecksumValid() const + { + sctphdr* hdr = getSctpHeader(); + // Read the stored checksum and convert from network byte order + uint32_t storedChecksum = be32toh(hdr->checksum); + + // Temporarily set checksum to 0 for calculation + hdr->checksum = 0; + uint32_t calculatedCrc = calculateSctpCrc32c(m_Data, m_DataLen); + // Restore the original checksum value (in network byte order) + hdr->checksum = htobe32(storedChecksum); + + return storedChecksum == calculatedCrc; + } + + bool SctpLayer::isDataValid(const uint8_t* data, size_t dataLen) + { + // Minimum SCTP packet is just the common header (12 bytes) + if (data == nullptr || dataLen < sizeof(sctphdr)) + return false; + + const auto* hdr = reinterpret_cast(data); + + // Source and destination ports must not be 0 + if (hdr->portSrc == 0 || hdr->portDst == 0) + return false; + + // If there are chunks, validate at least the first chunk header + if (dataLen > sizeof(sctphdr)) + { + size_t chunksLen = dataLen - sizeof(sctphdr); + if (chunksLen < sizeof(sctp_chunk_hdr)) + return false; + + const auto* chunkHdr = reinterpret_cast(data + sizeof(sctphdr)); + uint16_t chunkLen = be16toh(chunkHdr->length); + + // Chunk length must be at least 4 (header size) and not exceed available data + if (chunkLen < sizeof(sctp_chunk_hdr) || chunkLen > chunksLen) + return false; + } + + return true; + } + + void SctpLayer::parseNextLayer() + { + // SCTP typically doesn't have a distinct next layer in the packet parsing sense + // The payload is contained within DATA chunks + // For now, we don't create a next layer - applications should use getChunk() to access data + } + + void SctpLayer::computeCalculateFields() + { + calculateChecksum(true); + } + + std::string SctpLayer::toString() const + { + std::ostringstream ss; + ss << "SCTP Layer, "; + ss << "Src port: " << getSrcPort(); + ss << ", Dst port: " << getDstPort(); + + size_t chunkCount = getChunkCount(); + if (chunkCount > 0) + { + ss << ", Chunks: " << chunkCount; + + // List chunk types + ss << " ["; + SctpChunk chunk = getFirstChunk(); + bool first = true; + while (chunk.isNotNull()) + { + if (!first) + ss << ", "; + ss << chunk.getChunkTypeName(); + first = false; + chunk = getNextChunk(chunk); + } + ss << "]"; + } + + return ss.str(); + } + + // ==================== Chunk Creation Methods ==================== + + bool SctpLayer::addChunk(const uint8_t* chunkData, size_t chunkLen) + { + if (chunkData == nullptr || chunkLen < sizeof(sctp_chunk_hdr)) + return false; + + // Calculate padded length (chunks must be 4-byte aligned) + size_t paddedLen = (chunkLen + 3) & ~3; + + // Extend the layer data + if (!extendLayer(m_DataLen, paddedLen)) + return false; + + // Copy chunk data + std::memcpy(m_Data + m_DataLen - paddedLen, chunkData, chunkLen); + + // Zero padding bytes + if (paddedLen > chunkLen) + { + std::memset(m_Data + m_DataLen - paddedLen + chunkLen, 0, paddedLen - chunkLen); + } + + return true; + } + + bool SctpLayer::addDataChunk(uint32_t tsn, uint16_t streamId, uint16_t streamSeq, uint32_t ppid, + const uint8_t* userData, size_t userDataLen, bool beginFragment, bool endFragment, + bool unordered, bool immediate) + { + if (userData == nullptr && userDataLen > 0) + return false; + + size_t chunkLen = sizeof(sctp_data_chunk) + userDataLen; + std::vector chunkData(chunkLen); + + auto* dataChunk = reinterpret_cast(chunkData.data()); + dataChunk->type = static_cast(SctpChunkType::DATA); + dataChunk->flags = 0; + if (endFragment) + dataChunk->flags |= SctpDataChunkFlags::END_FRAGMENT; + if (beginFragment) + dataChunk->flags |= SctpDataChunkFlags::BEGIN_FRAGMENT; + if (unordered) + dataChunk->flags |= SctpDataChunkFlags::UNORDERED; + if (immediate) + dataChunk->flags |= SctpDataChunkFlags::IMMEDIATE; + dataChunk->length = htobe16(static_cast(chunkLen)); + dataChunk->tsn = htobe32(tsn); + dataChunk->streamId = htobe16(streamId); + dataChunk->streamSeqNum = htobe16(streamSeq); + dataChunk->ppid = htobe32(ppid); + + if (userDataLen > 0) + { + std::memcpy(chunkData.data() + sizeof(sctp_data_chunk), userData, userDataLen); + } + + return addChunk(chunkData.data(), chunkLen); + } + + bool SctpLayer::addInitChunk(uint32_t initiateTag, uint32_t arwnd, uint16_t numOutboundStreams, + uint16_t numInboundStreams, uint32_t initialTsn, const uint8_t* parameters, + size_t parametersLen) + { + size_t chunkLen = sizeof(sctp_init_chunk) + parametersLen; + std::vector chunkData(chunkLen); + + auto* initChunk = reinterpret_cast(chunkData.data()); + initChunk->type = static_cast(SctpChunkType::INIT); + initChunk->flags = 0; + initChunk->length = htobe16(static_cast(chunkLen)); + initChunk->initiateTag = htobe32(initiateTag); + initChunk->arwnd = htobe32(arwnd); + initChunk->numOutboundStreams = htobe16(numOutboundStreams); + initChunk->numInboundStreams = htobe16(numInboundStreams); + initChunk->initialTsn = htobe32(initialTsn); + + if (parametersLen > 0 && parameters != nullptr) + { + std::memcpy(chunkData.data() + sizeof(sctp_init_chunk), parameters, parametersLen); + } + + return addChunk(chunkData.data(), chunkLen); + } + + bool SctpLayer::addInitAckChunk(uint32_t initiateTag, uint32_t arwnd, uint16_t numOutboundStreams, + uint16_t numInboundStreams, uint32_t initialTsn, const uint8_t* parameters, + size_t parametersLen) + { + size_t chunkLen = sizeof(sctp_init_chunk) + parametersLen; + std::vector chunkData(chunkLen); + + auto* initChunk = reinterpret_cast(chunkData.data()); + initChunk->type = static_cast(SctpChunkType::INIT_ACK); + initChunk->flags = 0; + initChunk->length = htobe16(static_cast(chunkLen)); + initChunk->initiateTag = htobe32(initiateTag); + initChunk->arwnd = htobe32(arwnd); + initChunk->numOutboundStreams = htobe16(numOutboundStreams); + initChunk->numInboundStreams = htobe16(numInboundStreams); + initChunk->initialTsn = htobe32(initialTsn); + + if (parametersLen > 0 && parameters != nullptr) + { + std::memcpy(chunkData.data() + sizeof(sctp_init_chunk), parameters, parametersLen); + } + + return addChunk(chunkData.data(), chunkLen); + } + + bool SctpLayer::addSackChunk(uint32_t cumulativeTsnAck, uint32_t arwnd, + const std::vector& gapBlocks, const std::vector& dupTsns) + { + size_t chunkLen = + sizeof(sctp_sack_chunk) + gapBlocks.size() * sizeof(sctp_gap_ack_block) + dupTsns.size() * sizeof(uint32_t); + std::vector chunkData(chunkLen); + + auto* sackChunk = reinterpret_cast(chunkData.data()); + sackChunk->type = static_cast(SctpChunkType::SACK); + sackChunk->flags = 0; + sackChunk->length = htobe16(static_cast(chunkLen)); + sackChunk->cumulativeTsnAck = htobe32(cumulativeTsnAck); + sackChunk->arwnd = htobe32(arwnd); + sackChunk->numGapBlocks = htobe16(static_cast(gapBlocks.size())); + sackChunk->numDupTsns = htobe16(static_cast(dupTsns.size())); + + // Add gap blocks + auto* gapBlocksPtr = reinterpret_cast(chunkData.data() + sizeof(sctp_sack_chunk)); + for (size_t i = 0; i < gapBlocks.size(); ++i) + { + gapBlocksPtr[i].start = htobe16(gapBlocks[i].start); + gapBlocksPtr[i].end = htobe16(gapBlocks[i].end); + } + + // Add duplicate TSNs + auto* dupTsnsPtr = reinterpret_cast(chunkData.data() + sizeof(sctp_sack_chunk) + + gapBlocks.size() * sizeof(sctp_gap_ack_block)); + for (size_t i = 0; i < dupTsns.size(); ++i) + { + dupTsnsPtr[i] = htobe32(dupTsns[i]); + } + + return addChunk(chunkData.data(), chunkLen); + } + + bool SctpLayer::addHeartbeatChunk(const uint8_t* heartbeatInfo, size_t heartbeatInfoLen) + { + if (heartbeatInfo == nullptr && heartbeatInfoLen > 0) + return false; + + size_t chunkLen = sizeof(sctp_heartbeat_chunk) + heartbeatInfoLen; + std::vector chunkData(chunkLen); + + auto* hbChunk = reinterpret_cast(chunkData.data()); + hbChunk->type = static_cast(SctpChunkType::HEARTBEAT); + hbChunk->flags = 0; + hbChunk->length = htobe16(static_cast(chunkLen)); + + if (heartbeatInfoLen > 0) + { + std::memcpy(chunkData.data() + sizeof(sctp_heartbeat_chunk), heartbeatInfo, heartbeatInfoLen); + } + + return addChunk(chunkData.data(), chunkLen); + } + + bool SctpLayer::addHeartbeatAckChunk(const uint8_t* heartbeatInfo, size_t heartbeatInfoLen) + { + if (heartbeatInfo == nullptr && heartbeatInfoLen > 0) + return false; + + size_t chunkLen = sizeof(sctp_heartbeat_chunk) + heartbeatInfoLen; + std::vector chunkData(chunkLen); + + auto* hbChunk = reinterpret_cast(chunkData.data()); + hbChunk->type = static_cast(SctpChunkType::HEARTBEAT_ACK); + hbChunk->flags = 0; + hbChunk->length = htobe16(static_cast(chunkLen)); + + if (heartbeatInfoLen > 0) + { + std::memcpy(chunkData.data() + sizeof(sctp_heartbeat_chunk), heartbeatInfo, heartbeatInfoLen); + } + + return addChunk(chunkData.data(), chunkLen); + } + + bool SctpLayer::addShutdownChunk(uint32_t cumulativeTsnAck) + { + sctp_shutdown_chunk shutdownChunk; + shutdownChunk.type = static_cast(SctpChunkType::SHUTDOWN); + shutdownChunk.flags = 0; + shutdownChunk.length = htobe16(sizeof(sctp_shutdown_chunk)); + shutdownChunk.cumulativeTsnAck = htobe32(cumulativeTsnAck); + + return addChunk(reinterpret_cast(&shutdownChunk), sizeof(shutdownChunk)); + } + + bool SctpLayer::addShutdownAckChunk() + { + sctp_shutdown_ack_chunk shutdownAckChunk; + shutdownAckChunk.type = static_cast(SctpChunkType::SHUTDOWN_ACK); + shutdownAckChunk.flags = 0; + shutdownAckChunk.length = htobe16(sizeof(sctp_shutdown_ack_chunk)); + + return addChunk(reinterpret_cast(&shutdownAckChunk), sizeof(shutdownAckChunk)); + } + + bool SctpLayer::addShutdownCompleteChunk(bool tBit) + { + sctp_shutdown_complete_chunk shutdownCompleteChunk; + shutdownCompleteChunk.type = static_cast(SctpChunkType::SHUTDOWN_COMPLETE); + shutdownCompleteChunk.flags = tBit ? SctpAbortFlags::T_BIT : 0; + shutdownCompleteChunk.length = htobe16(sizeof(sctp_shutdown_complete_chunk)); + + return addChunk(reinterpret_cast(&shutdownCompleteChunk), sizeof(shutdownCompleteChunk)); + } + + bool SctpLayer::addAbortChunk(bool tBit, const uint8_t* errorCauses, size_t errorCausesLen) + { + size_t chunkLen = sizeof(sctp_abort_chunk) + errorCausesLen; + std::vector chunkData(chunkLen); + + auto* abortChunk = reinterpret_cast(chunkData.data()); + abortChunk->type = static_cast(SctpChunkType::ABORT); + abortChunk->flags = tBit ? SctpAbortFlags::T_BIT : 0; + abortChunk->length = htobe16(static_cast(chunkLen)); + + if (errorCausesLen > 0 && errorCauses != nullptr) + { + std::memcpy(chunkData.data() + sizeof(sctp_abort_chunk), errorCauses, errorCausesLen); + } + + return addChunk(chunkData.data(), chunkLen); + } + + bool SctpLayer::addCookieEchoChunk(const uint8_t* cookie, size_t cookieLen) + { + if (cookie == nullptr || cookieLen == 0) + return false; + + size_t chunkLen = sizeof(sctp_cookie_echo_chunk) + cookieLen; + std::vector chunkData(chunkLen); + + auto* cookieChunk = reinterpret_cast(chunkData.data()); + cookieChunk->type = static_cast(SctpChunkType::COOKIE_ECHO); + cookieChunk->flags = 0; + cookieChunk->length = htobe16(static_cast(chunkLen)); + + std::memcpy(chunkData.data() + sizeof(sctp_cookie_echo_chunk), cookie, cookieLen); + + return addChunk(chunkData.data(), chunkLen); + } + + bool SctpLayer::addCookieAckChunk() + { + sctp_cookie_ack_chunk cookieAckChunk; + cookieAckChunk.type = static_cast(SctpChunkType::COOKIE_ACK); + cookieAckChunk.flags = 0; + cookieAckChunk.length = htobe16(sizeof(sctp_cookie_ack_chunk)); + + return addChunk(reinterpret_cast(&cookieAckChunk), sizeof(cookieAckChunk)); + } + + bool SctpLayer::addErrorChunk(const uint8_t* errorCauses, size_t errorCausesLen) + { + if (errorCauses == nullptr || errorCausesLen == 0) + return false; + + size_t chunkLen = sizeof(sctp_error_chunk) + errorCausesLen; + std::vector chunkData(chunkLen); + + auto* errorChunk = reinterpret_cast(chunkData.data()); + errorChunk->type = static_cast(SctpChunkType::SCTP_ERROR); + errorChunk->flags = 0; + errorChunk->length = htobe16(static_cast(chunkLen)); + + std::memcpy(chunkData.data() + sizeof(sctp_error_chunk), errorCauses, errorCausesLen); + + return addChunk(chunkData.data(), chunkLen); + } + + // ==================== SctpInitParameter Implementation ==================== + + SctpParameterType SctpInitParameter::getType() const + { + if (m_Data == nullptr) + return static_cast(0); + + uint16_t type = be16toh(m_Data->type); + return static_cast(type); + } + + uint16_t SctpInitParameter::getTypeAsInt() const + { + if (m_Data == nullptr) + return 0; + return be16toh(m_Data->type); + } + + uint16_t SctpInitParameter::getLength() const + { + if (m_Data == nullptr) + return 0; + if (m_MaxLen < sizeof(sctp_param_hdr)) + return 0; + return be16toh(m_Data->length); + } + + size_t SctpInitParameter::getTotalSize() const + { + uint16_t len = getLength(); + if (len == 0) + return 0; + // Pad to 4-byte boundary + size_t totalSize = (len + 3) & ~3; + // Validate against available buffer + if (totalSize > m_MaxLen) + return 0; + return totalSize; + } + + uint8_t* SctpInitParameter::getValue() const + { + if (m_Data == nullptr) + return nullptr; + if (m_MaxLen < sizeof(sctp_param_hdr)) + return nullptr; + return reinterpret_cast(m_Data) + sizeof(sctp_param_hdr); + } + + size_t SctpInitParameter::getValueSize() const + { + uint16_t len = getLength(); + if (len <= sizeof(sctp_param_hdr)) + return 0; + size_t valueSize = len - sizeof(sctp_param_hdr); + // Validate against available buffer + if (sizeof(sctp_param_hdr) + valueSize > m_MaxLen) + return 0; + return valueSize; + } + + IPv4Address SctpInitParameter::getIPv4Address() const + { + if (m_Data == nullptr || getType() != SctpParameterType::IPV4_ADDRESS) + return IPv4Address::Zero; + + if (getValueSize() < 4) + return IPv4Address::Zero; + + return IPv4Address(getValue()); + } + + IPv6Address SctpInitParameter::getIPv6Address() const + { + if (m_Data == nullptr || getType() != SctpParameterType::IPV6_ADDRESS) + return IPv6Address::Zero; + + if (getValueSize() < 16) + return IPv6Address::Zero; + + return IPv6Address(getValue()); + } + + std::vector SctpInitParameter::getSupportedAddressTypes() const + { + std::vector result; + if (m_Data == nullptr || getType() != SctpParameterType::SUPPORTED_ADDRESS_TYPES) + return result; + + size_t valueSize = getValueSize(); + const uint8_t* valuePtr = getValue(); + + for (size_t i = 0; i + 1 < valueSize; i += 2) + { + uint16_t addrType = be16toh(*reinterpret_cast(valuePtr + i)); + result.push_back(addrType); + } + + return result; + } + + uint8_t* SctpInitParameter::getStateCookie() const + { + if (m_Data == nullptr || getType() != SctpParameterType::STATE_COOKIE) + return nullptr; + return getValue(); + } + + size_t SctpInitParameter::getStateCookieLength() const + { + if (m_Data == nullptr || getType() != SctpParameterType::STATE_COOKIE) + return 0; + return getValueSize(); + } + + uint32_t SctpInitParameter::getCookiePreservativeIncrement() const + { + if (m_Data == nullptr || getType() != SctpParameterType::COOKIE_PRESERVATIVE) + return 0; + if (getValueSize() < 4) + return 0; + return be32toh(*reinterpret_cast(getValue())); + } + + uint8_t* SctpInitParameter::getRandomData() const + { + if (m_Data == nullptr || getType() != SctpParameterType::RANDOM) + return nullptr; + return getValue(); + } + + size_t SctpInitParameter::getRandomDataLength() const + { + if (m_Data == nullptr || getType() != SctpParameterType::RANDOM) + return 0; + return getValueSize(); + } + + std::vector SctpInitParameter::getChunkList() const + { + std::vector result; + if (m_Data == nullptr || getType() != SctpParameterType::CHUNK_LIST) + return result; + + size_t valueSize = getValueSize(); + const uint8_t* valuePtr = getValue(); + + for (size_t i = 0; i < valueSize; ++i) + { + result.push_back(valuePtr[i]); + } + + return result; + } + + std::vector SctpInitParameter::getRequestedHmacAlgorithms() const + { + std::vector result; + if (m_Data == nullptr || getType() != SctpParameterType::REQUESTED_HMAC_ALGO) + return result; + + size_t valueSize = getValueSize(); + const uint8_t* valuePtr = getValue(); + + for (size_t i = 0; i + 1 < valueSize; i += 2) + { + uint16_t hmacId = be16toh(*reinterpret_cast(valuePtr + i)); + result.push_back(hmacId); + } + + return result; + } + + std::vector SctpInitParameter::getSupportedExtensions() const + { + std::vector result; + if (m_Data == nullptr || getType() != SctpParameterType::SUPPORTED_EXTENSIONS) + return result; + + size_t valueSize = getValueSize(); + const uint8_t* valuePtr = getValue(); + + for (size_t i = 0; i < valueSize; ++i) + { + result.push_back(valuePtr[i]); + } + + return result; + } + + bool SctpInitParameter::isHostNameAddress() const + { + return m_Data != nullptr && getType() == SctpParameterType::HOST_NAME_ADDRESS; + } + + std::string SctpInitParameter::getTypeName() const + { + switch (getType()) + { + case SctpParameterType::HEARTBEAT_INFO: + return "Heartbeat Info"; + case SctpParameterType::IPV4_ADDRESS: + return "IPv4 Address"; + case SctpParameterType::IPV6_ADDRESS: + return "IPv6 Address"; + case SctpParameterType::STATE_COOKIE: + return "State Cookie"; + case SctpParameterType::UNRECOGNIZED_PARAM: + return "Unrecognized Parameter"; + case SctpParameterType::COOKIE_PRESERVATIVE: + return "Cookie Preservative"; + case SctpParameterType::HOST_NAME_ADDRESS: + return "Host Name Address (DEPRECATED)"; + case SctpParameterType::SUPPORTED_ADDRESS_TYPES: + return "Supported Address Types"; + case SctpParameterType::ECN_CAPABLE: + return "ECN Capable"; + case SctpParameterType::ZERO_CHECKSUM_ACCEPTABLE: + return "Zero Checksum Acceptable"; + case SctpParameterType::RANDOM: + return "Random"; + case SctpParameterType::CHUNK_LIST: + return "Chunk List"; + case SctpParameterType::REQUESTED_HMAC_ALGO: + return "Requested HMAC Algorithm"; + case SctpParameterType::PADDING: + return "Padding"; + case SctpParameterType::SUPPORTED_EXTENSIONS: + return "Supported Extensions"; + case SctpParameterType::FORWARD_TSN_SUPPORTED: + return "Forward TSN Supported"; + case SctpParameterType::ADD_IP_ADDRESS: + return "Add IP Address"; + case SctpParameterType::DELETE_IP_ADDRESS: + return "Delete IP Address"; + case SctpParameterType::ERROR_CAUSE_INDICATION: + return "Error Cause Indication"; + case SctpParameterType::SET_PRIMARY_ADDRESS: + return "Set Primary Address"; + case SctpParameterType::SUCCESS_INDICATION: + return "Success Indication"; + case SctpParameterType::ADAPTATION_LAYER_INDICATION: + return "Adaptation Layer Indication"; + default: + return "Unknown"; + } + } + + // ==================== SctpInitParameterIterator Implementation ==================== + + SctpInitParameterIterator::SctpInitParameterIterator(const SctpChunk& chunk) + : m_ParamsBase(nullptr), m_ParamsLen(0), m_CurrentOffset(0) + { + if (chunk.isNull()) + return; + + SctpChunkType type = chunk.getChunkType(); + if (type != SctpChunkType::INIT && type != SctpChunkType::INIT_ACK) + return; + + // Get parameters from INIT/INIT-ACK chunk + // Parameters start after the fixed 20-byte INIT header + uint16_t chunkLen = chunk.getLength(); + if (chunkLen <= sizeof(sctp_init_chunk)) + return; + + m_ParamsBase = chunk.getRecordBasePtr() + sizeof(sctp_init_chunk); + m_ParamsLen = chunkLen - sizeof(sctp_init_chunk); + } + + SctpInitParameter SctpInitParameterIterator::getParameter() const + { + if (!isValid()) + return SctpInitParameter(nullptr, 0); + + return SctpInitParameter(m_ParamsBase + m_CurrentOffset, m_ParamsLen - m_CurrentOffset); + } + + SctpInitParameterIterator& SctpInitParameterIterator::next() + { + if (!isValid()) + return *this; + + SctpInitParameter param = getParameter(); + size_t paramTotalSize = param.getTotalSize(); + + if (paramTotalSize == 0) + { + // Invalid parameter, mark as end + m_CurrentOffset = m_ParamsLen; + } + else + { + m_CurrentOffset += paramTotalSize; + } + + return *this; + } + + bool SctpInitParameterIterator::isValid() const + { + if (m_ParamsBase == nullptr || m_ParamsLen == 0) + return false; + + if (m_CurrentOffset + sizeof(sctp_param_hdr) > m_ParamsLen) + return false; + + // Validate parameter length + auto* paramHdr = reinterpret_cast(m_ParamsBase + m_CurrentOffset); + uint16_t paramLen = be16toh(paramHdr->length); + + if (paramLen < sizeof(sctp_param_hdr) || m_CurrentOffset + paramLen > m_ParamsLen) + return false; + + return true; + } + + void SctpInitParameterIterator::reset() + { + m_CurrentOffset = 0; + } + + // ==================== SctpErrorCause Implementation ==================== + + SctpErrorCauseCode SctpErrorCause::getCode() const + { + if (m_Data == nullptr) + return static_cast(0); + return static_cast(be16toh(m_Data->code)); + } + + uint16_t SctpErrorCause::getCodeAsInt() const + { + if (m_Data == nullptr) + return 0; + return be16toh(m_Data->code); + } + + uint16_t SctpErrorCause::getLength() const + { + if (m_Data == nullptr) + return 0; + if (m_MaxLen < sizeof(sctp_error_cause)) + return 0; + return be16toh(m_Data->length); + } + + size_t SctpErrorCause::getTotalSize() const + { + uint16_t len = getLength(); + if (len == 0) + return 0; + // Pad to 4-byte boundary + size_t totalSize = (len + 3) & ~3; + // Validate against available buffer + if (totalSize > m_MaxLen) + return 0; + return totalSize; + } + + uint8_t* SctpErrorCause::getData() const + { + if (m_Data == nullptr) + return nullptr; + if (m_MaxLen < sizeof(sctp_error_cause)) + return nullptr; + return reinterpret_cast(m_Data) + sizeof(sctp_error_cause); + } + + size_t SctpErrorCause::getDataSize() const + { + uint16_t len = getLength(); + if (len <= sizeof(sctp_error_cause)) + return 0; + size_t dataSize = len - sizeof(sctp_error_cause); + // Validate against available buffer + if (sizeof(sctp_error_cause) + dataSize > m_MaxLen) + return 0; + return dataSize; + } + + std::string SctpErrorCause::getCodeName() const + { + switch (getCode()) + { + case SctpErrorCauseCode::INVALID_STREAM_ID: + return "Invalid Stream Identifier"; + case SctpErrorCauseCode::MISSING_MANDATORY_PARAM: + return "Missing Mandatory Parameter"; + case SctpErrorCauseCode::STALE_COOKIE: + return "Stale Cookie"; + case SctpErrorCauseCode::OUT_OF_RESOURCE: + return "Out of Resource"; + case SctpErrorCauseCode::UNRESOLVABLE_ADDRESS: + return "Unresolvable Address"; + case SctpErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE: + return "Unrecognized Chunk Type"; + case SctpErrorCauseCode::INVALID_MANDATORY_PARAM: + return "Invalid Mandatory Parameter"; + case SctpErrorCauseCode::UNRECOGNIZED_PARAMS: + return "Unrecognized Parameters"; + case SctpErrorCauseCode::NO_USER_DATA: + return "No User Data"; + case SctpErrorCauseCode::COOKIE_RECEIVED_WHILE_SHUTTING_DOWN: + return "Cookie Received While Shutting Down"; + case SctpErrorCauseCode::RESTART_WITH_NEW_ADDRESSES: + return "Restart with New Addresses"; + case SctpErrorCauseCode::USER_INITIATED_ABORT: + return "User Initiated Abort"; + case SctpErrorCauseCode::PROTOCOL_VIOLATION: + return "Protocol Violation"; + case SctpErrorCauseCode::DELETE_LAST_IP: + return "Request to Delete Last Remaining IP"; + case SctpErrorCauseCode::OPERATION_REFUSED: + return "Operation Refused"; + case SctpErrorCauseCode::DELETE_SOURCE_IP: + return "Request to Delete Source IP"; + case SctpErrorCauseCode::ASSOCIATION_ABORTED: + return "Association Aborted"; + case SctpErrorCauseCode::REQUEST_REFUSED: + return "Request Refused"; + case SctpErrorCauseCode::UNSUPPORTED_HMAC_ID: + return "Unsupported HMAC Identifier"; + default: + return "Unknown"; + } + } + + uint16_t SctpErrorCause::getInvalidStreamId() const + { + if (m_Data == nullptr || getCode() != SctpErrorCauseCode::INVALID_STREAM_ID) + return 0; + if (getDataSize() < 2) + return 0; + return be16toh(*reinterpret_cast(getData())); + } + + uint32_t SctpErrorCause::getStaleCookieStaleness() const + { + if (m_Data == nullptr || getCode() != SctpErrorCauseCode::STALE_COOKIE) + return 0; + if (getDataSize() < 4) + return 0; + return be32toh(*reinterpret_cast(getData())); + } + + uint32_t SctpErrorCause::getNoUserDataTsn() const + { + if (m_Data == nullptr || getCode() != SctpErrorCauseCode::NO_USER_DATA) + return 0; + if (getDataSize() < 4) + return 0; + return be32toh(*reinterpret_cast(getData())); + } + + // ==================== SctpErrorCauseIterator Implementation ==================== + + SctpErrorCauseIterator::SctpErrorCauseIterator(const SctpChunk& chunk) + : m_CausesBase(nullptr), m_CausesLen(0), m_CurrentOffset(0) + { + if (chunk.isNull()) + return; + + SctpChunkType type = chunk.getChunkType(); + if (type != SctpChunkType::ABORT && type != SctpChunkType::SCTP_ERROR) + return; + + uint16_t chunkLen = chunk.getLength(); + if (chunkLen <= sizeof(sctp_chunk_hdr)) + return; + + m_CausesBase = chunk.getValue(); + m_CausesLen = chunkLen - sizeof(sctp_chunk_hdr); + } + + SctpErrorCause SctpErrorCauseIterator::getErrorCause() const + { + if (!isValid()) + return SctpErrorCause(nullptr, 0); + + return SctpErrorCause(m_CausesBase + m_CurrentOffset, m_CausesLen - m_CurrentOffset); + } + + SctpErrorCauseIterator& SctpErrorCauseIterator::next() + { + if (!isValid()) + return *this; + + SctpErrorCause cause = getErrorCause(); + size_t causeTotalSize = cause.getTotalSize(); + + if (causeTotalSize == 0) + { + m_CurrentOffset = m_CausesLen; + } + else + { + m_CurrentOffset += causeTotalSize; + } + + return *this; + } + + bool SctpErrorCauseIterator::isValid() const + { + if (m_CausesBase == nullptr || m_CausesLen == 0) + return false; + + if (m_CurrentOffset + sizeof(sctp_error_cause) > m_CausesLen) + return false; + + auto* causeHdr = reinterpret_cast(m_CausesBase + m_CurrentOffset); + uint16_t causeLen = be16toh(causeHdr->length); + + if (causeLen < sizeof(sctp_error_cause) || m_CurrentOffset + causeLen > m_CausesLen) + return false; + + return true; + } + + void SctpErrorCauseIterator::reset() + { + m_CurrentOffset = 0; + } + + // ==================== Additional Chunk Creation Methods ==================== + + bool SctpLayer::addEcneChunk(uint32_t lowestTsn) + { + sctp_ecne_chunk ecneChunk; + ecneChunk.type = static_cast(SctpChunkType::ECNE); + ecneChunk.flags = 0; + ecneChunk.length = htobe16(sizeof(sctp_ecne_chunk)); + ecneChunk.lowestTsn = htobe32(lowestTsn); + + return addChunk(reinterpret_cast(&ecneChunk), sizeof(ecneChunk)); + } + + bool SctpLayer::addCwrChunk(uint32_t lowestTsn) + { + sctp_cwr_chunk cwrChunk; + cwrChunk.type = static_cast(SctpChunkType::CWR); + cwrChunk.flags = 0; + cwrChunk.length = htobe16(sizeof(sctp_cwr_chunk)); + cwrChunk.lowestTsn = htobe32(lowestTsn); + + return addChunk(reinterpret_cast(&cwrChunk), sizeof(cwrChunk)); + } + + bool SctpLayer::addForwardTsnChunk(uint32_t newCumulativeTsn, const std::vector& streams) + { + size_t chunkLen = sizeof(sctp_forward_tsn_chunk) + streams.size() * sizeof(sctp_forward_tsn_stream); + std::vector chunkData(chunkLen); + + auto* fwdTsnChunk = reinterpret_cast(chunkData.data()); + fwdTsnChunk->type = static_cast(SctpChunkType::FORWARD_TSN); + fwdTsnChunk->flags = 0; + fwdTsnChunk->length = htobe16(static_cast(chunkLen)); + fwdTsnChunk->newCumulativeTsn = htobe32(newCumulativeTsn); + + auto* streamsPtr = + reinterpret_cast(chunkData.data() + sizeof(sctp_forward_tsn_chunk)); + for (size_t i = 0; i < streams.size(); ++i) + { + streamsPtr[i].streamId = htobe16(streams[i].streamId); + streamsPtr[i].streamSeq = htobe16(streams[i].streamSeq); + } + + return addChunk(chunkData.data(), chunkLen); + } + + bool SctpLayer::addIDataChunk(uint32_t tsn, uint16_t streamId, uint32_t mid, uint32_t ppidOrFsn, + const uint8_t* userData, size_t userDataLen, bool beginFragment, bool endFragment, + bool unordered, bool immediate) + { + if (userData == nullptr && userDataLen > 0) + return false; + + size_t chunkLen = sizeof(sctp_idata_chunk) + userDataLen; + std::vector chunkData(chunkLen); + + auto* idataChunk = reinterpret_cast(chunkData.data()); + idataChunk->type = static_cast(SctpChunkType::I_DATA); + idataChunk->flags = 0; + if (endFragment) + idataChunk->flags |= SctpDataChunkFlags::END_FRAGMENT; + if (beginFragment) + idataChunk->flags |= SctpDataChunkFlags::BEGIN_FRAGMENT; + if (unordered) + idataChunk->flags |= SctpDataChunkFlags::UNORDERED; + if (immediate) + idataChunk->flags |= SctpDataChunkFlags::IMMEDIATE; + idataChunk->length = htobe16(static_cast(chunkLen)); + idataChunk->tsn = htobe32(tsn); + idataChunk->streamId = htobe16(streamId); + idataChunk->reserved = 0; + idataChunk->mid = htobe32(mid); + idataChunk->ppidOrFsn = htobe32(ppidOrFsn); + + if (userDataLen > 0) + { + std::memcpy(chunkData.data() + sizeof(sctp_idata_chunk), userData, userDataLen); + } + + return addChunk(chunkData.data(), chunkLen); + } + + bool SctpLayer::addIForwardTsnChunk(uint32_t newCumulativeTsn, const std::vector& streams) + { + size_t chunkLen = sizeof(sctp_iforward_tsn_chunk) + streams.size() * sizeof(sctp_iforward_tsn_stream); + std::vector chunkData(chunkLen); + + auto* ifwdTsnChunk = reinterpret_cast(chunkData.data()); + ifwdTsnChunk->type = static_cast(SctpChunkType::I_FORWARD_TSN); + ifwdTsnChunk->flags = 0; + ifwdTsnChunk->length = htobe16(static_cast(chunkLen)); + ifwdTsnChunk->newCumulativeTsn = htobe32(newCumulativeTsn); + + auto* streamsPtr = + reinterpret_cast(chunkData.data() + sizeof(sctp_iforward_tsn_chunk)); + for (size_t i = 0; i < streams.size(); ++i) + { + streamsPtr[i].streamId = htobe16(streams[i].streamId); + streamsPtr[i].reserved = htobe16(streams[i].reserved); + streamsPtr[i].mid = htobe32(streams[i].mid); + } + + return addChunk(chunkData.data(), chunkLen); + } + + bool SctpLayer::addPadChunk(size_t paddingLen) + { + size_t chunkLen = sizeof(sctp_pad_chunk) + paddingLen; + std::vector chunkData(chunkLen, 0); + + auto* padChunk = reinterpret_cast(chunkData.data()); + padChunk->type = static_cast(SctpChunkType::PAD); + padChunk->flags = 0; + padChunk->length = htobe16(static_cast(chunkLen)); + + return addChunk(chunkData.data(), chunkLen); + } + + // ==================== Validation Methods ==================== + + SctpBundlingStatus SctpLayer::validateBundling() const + { + size_t chunkCount = getChunkCount(); + if (chunkCount == 0) + return SctpBundlingStatus::VALID; + + bool hasInit = false; + bool hasInitAck = false; + bool hasShutdownComplete = false; + + SctpChunk chunk = getFirstChunk(); + while (chunk.isNotNull()) + { + SctpChunkType type = chunk.getChunkType(); + if (type == SctpChunkType::INIT) + hasInit = true; + else if (type == SctpChunkType::INIT_ACK) + hasInitAck = true; + else if (type == SctpChunkType::SHUTDOWN_COMPLETE) + hasShutdownComplete = true; + + chunk = getNextChunk(chunk); + } + + // Per RFC 9260: INIT, INIT-ACK, and SHUTDOWN-COMPLETE MUST NOT be bundled + if (hasInit && chunkCount > 1) + return SctpBundlingStatus::INIT_BUNDLED; + + if (hasInitAck && chunkCount > 1) + return SctpBundlingStatus::INIT_ACK_BUNDLED; + + if (hasShutdownComplete && chunkCount > 1) + return SctpBundlingStatus::SHUTDOWN_COMPLETE_BUNDLED; + + // Per RFC 9260: INIT chunk packets MUST have verification tag = 0 + if (hasInit && getVerificationTag() != 0) + return SctpBundlingStatus::INIT_NONZERO_TAG; + + return SctpBundlingStatus::VALID; + } + + bool SctpLayer::canAddChunk(SctpChunkType chunkType) const + { + size_t chunkCount = getChunkCount(); + + // If packet is empty, any chunk can be added + if (chunkCount == 0) + return true; + + // Check what's already in the packet + SctpChunk chunk = getFirstChunk(); + while (chunk.isNotNull()) + { + SctpChunkType existingType = chunk.getChunkType(); + + // Cannot add anything to a packet with INIT, INIT-ACK, or SHUTDOWN-COMPLETE + if (existingType == SctpChunkType::INIT || existingType == SctpChunkType::INIT_ACK || + existingType == SctpChunkType::SHUTDOWN_COMPLETE) + { + return false; + } + + chunk = getNextChunk(chunk); + } + + // Cannot add INIT, INIT-ACK, or SHUTDOWN-COMPLETE to a packet with existing chunks + if (chunkType == SctpChunkType::INIT || chunkType == SctpChunkType::INIT_ACK || + chunkType == SctpChunkType::SHUTDOWN_COMPLETE) + { + return false; + } + + return true; + } + + bool SctpLayer::containsHostNameAddress() const + { + SctpChunk chunk = getFirstChunk(); + while (chunk.isNotNull()) + { + SctpChunkType type = chunk.getChunkType(); + if (type == SctpChunkType::INIT || type == SctpChunkType::INIT_ACK) + { + SctpInitParameterIterator iter(chunk); + while (iter.isValid()) + { + SctpInitParameter param = iter.getParameter(); + if (param.isHostNameAddress()) + return true; + iter.next(); + } + } + chunk = getNextChunk(chunk); + } + return false; + } + + // ==================== New Chunk Creation Methods (RFC 4895, 5061, 6525) ==================== + + bool SctpLayer::addAuthChunk(uint16_t sharedKeyId, uint16_t hmacId, const uint8_t* hmac, size_t hmacLen) + { + if (hmac == nullptr && hmacLen > 0) + return false; + + size_t chunkLen = sizeof(sctp_auth_chunk) + hmacLen; + std::vector chunkData(chunkLen); + + auto* authChunk = reinterpret_cast(chunkData.data()); + authChunk->type = static_cast(SctpChunkType::AUTH); + authChunk->flags = 0; + authChunk->length = htobe16(static_cast(chunkLen)); + authChunk->sharedKeyId = htobe16(sharedKeyId); + authChunk->hmacId = htobe16(hmacId); + + if (hmacLen > 0) + { + std::memcpy(chunkData.data() + sizeof(sctp_auth_chunk), hmac, hmacLen); + } + + return addChunk(chunkData.data(), chunkLen); + } + + bool SctpLayer::addAsconfChunk(uint32_t serialNumber, const uint8_t* addressParam, size_t addressParamLen, + const uint8_t* asconfParams, size_t asconfParamsLen) + { + if (addressParam == nullptr || addressParamLen < sizeof(sctp_param_hdr)) + return false; + + size_t chunkLen = sizeof(sctp_asconf_chunk) + addressParamLen + asconfParamsLen; + std::vector chunkData(chunkLen); + + auto* asconfChunk = reinterpret_cast(chunkData.data()); + asconfChunk->type = static_cast(SctpChunkType::ASCONF); + asconfChunk->flags = 0; + asconfChunk->length = htobe16(static_cast(chunkLen)); + asconfChunk->serialNumber = htobe32(serialNumber); + + // Copy address parameter (mandatory) + std::memcpy(chunkData.data() + sizeof(sctp_asconf_chunk), addressParam, addressParamLen); + + // Copy ASCONF parameters if provided + if (asconfParamsLen > 0 && asconfParams != nullptr) + { + std::memcpy(chunkData.data() + sizeof(sctp_asconf_chunk) + addressParamLen, asconfParams, asconfParamsLen); + } + + return addChunk(chunkData.data(), chunkLen); + } + + bool SctpLayer::addAsconfAckChunk(uint32_t serialNumber, const uint8_t* responseParams, size_t responseParamsLen) + { + size_t chunkLen = sizeof(sctp_asconf_ack_chunk) + responseParamsLen; + std::vector chunkData(chunkLen); + + auto* asconfAckChunk = reinterpret_cast(chunkData.data()); + asconfAckChunk->type = static_cast(SctpChunkType::ASCONF_ACK); + asconfAckChunk->flags = 0; + asconfAckChunk->length = htobe16(static_cast(chunkLen)); + asconfAckChunk->serialNumber = htobe32(serialNumber); + + if (responseParamsLen > 0 && responseParams != nullptr) + { + std::memcpy(chunkData.data() + sizeof(sctp_asconf_ack_chunk), responseParams, responseParamsLen); + } + + return addChunk(chunkData.data(), chunkLen); + } + + bool SctpLayer::addReconfigChunk(const uint8_t* parameters, size_t parametersLen) + { + if (parameters == nullptr || parametersLen < sizeof(sctp_param_hdr)) + return false; + + size_t chunkLen = sizeof(sctp_reconfig_chunk) + parametersLen; + std::vector chunkData(chunkLen); + + auto* reconfigChunk = reinterpret_cast(chunkData.data()); + reconfigChunk->type = static_cast(SctpChunkType::RE_CONFIG); + reconfigChunk->flags = 0; + reconfigChunk->length = htobe16(static_cast(chunkLen)); + + std::memcpy(chunkData.data() + sizeof(sctp_reconfig_chunk), parameters, parametersLen); + + return addChunk(chunkData.data(), chunkLen); + } + + // ==================== Additional SctpInitParameter Accessors ==================== + + uint32_t SctpInitParameter::getZeroChecksumEdmid() const + { + if (m_Data == nullptr || getType() != SctpParameterType::ZERO_CHECKSUM_ACCEPTABLE) + return 0; + if (getValueSize() < 4) + return 0; + return be32toh(*reinterpret_cast(getValue())); + } + + uint8_t* SctpInitParameter::getUnrecognizedParameter() const + { + if (m_Data == nullptr || getType() != SctpParameterType::UNRECOGNIZED_PARAM) + return nullptr; + return getValue(); + } + + size_t SctpInitParameter::getUnrecognizedParameterLength() const + { + if (m_Data == nullptr || getType() != SctpParameterType::UNRECOGNIZED_PARAM) + return 0; + return getValueSize(); + } + + uint32_t SctpInitParameter::getAdaptationLayerIndication() const + { + if (m_Data == nullptr || getType() != SctpParameterType::ADAPTATION_LAYER_INDICATION) + return 0; + if (getValueSize() < 4) + return 0; + return be32toh(*reinterpret_cast(getValue())); + } + + // ==================== Additional SctpErrorCause Accessors ==================== + + std::vector SctpErrorCause::getMissingMandatoryParams() const + { + std::vector result; + if (m_Data == nullptr || getCode() != SctpErrorCauseCode::MISSING_MANDATORY_PARAM) + return result; + + size_t dataSize = getDataSize(); + if (dataSize < 4) // Need at least number of params field + return result; + + uint8_t* data = getData(); + uint32_t numParams = be32toh(*reinterpret_cast(data)); + data += 4; + dataSize -= 4; + + for (uint32_t i = 0; i < numParams && dataSize >= 2; ++i) + { + result.push_back(be16toh(*reinterpret_cast(data))); + data += 2; + dataSize -= 2; + } + + return result; + } + + uint8_t* SctpErrorCause::getUnrecognizedChunk() const + { + if (m_Data == nullptr || getCode() != SctpErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE) + return nullptr; + return getData(); + } + + size_t SctpErrorCause::getUnrecognizedChunkLength() const + { + if (m_Data == nullptr || getCode() != SctpErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE) + return 0; + return getDataSize(); + } + + uint8_t* SctpErrorCause::getUnrecognizedParameters() const + { + if (m_Data == nullptr || getCode() != SctpErrorCauseCode::UNRECOGNIZED_PARAMS) + return nullptr; + return getData(); + } + + size_t SctpErrorCause::getUnrecognizedParametersLength() const + { + if (m_Data == nullptr || getCode() != SctpErrorCauseCode::UNRECOGNIZED_PARAMS) + return 0; + return getDataSize(); + } + + uint8_t* SctpErrorCause::getUnresolvableAddress() const + { + if (m_Data == nullptr || getCode() != SctpErrorCauseCode::UNRESOLVABLE_ADDRESS) + return nullptr; + return getData(); + } + + size_t SctpErrorCause::getUnresolvableAddressLength() const + { + if (m_Data == nullptr || getCode() != SctpErrorCauseCode::UNRESOLVABLE_ADDRESS) + return 0; + return getDataSize(); + } + + uint8_t* SctpErrorCause::getRestartNewAddresses() const + { + if (m_Data == nullptr || getCode() != SctpErrorCauseCode::RESTART_WITH_NEW_ADDRESSES) + return nullptr; + return getData(); + } + + size_t SctpErrorCause::getRestartNewAddressesLength() const + { + if (m_Data == nullptr || getCode() != SctpErrorCauseCode::RESTART_WITH_NEW_ADDRESSES) + return 0; + return getDataSize(); + } + + // ==================== SctpReconfigParameter Implementation ==================== + + SctpParameterType SctpReconfigParameter::getType() const + { + if (m_Data == nullptr) + return static_cast(0); + return static_cast(be16toh(m_Data->type)); + } + + uint16_t SctpReconfigParameter::getTypeAsInt() const + { + if (m_Data == nullptr) + return 0; + return be16toh(m_Data->type); + } + + uint16_t SctpReconfigParameter::getLength() const + { + if (m_Data == nullptr) + return 0; + if (m_MaxLen < sizeof(sctp_param_hdr)) + return 0; + return be16toh(m_Data->length); + } + + size_t SctpReconfigParameter::getTotalSize() const + { + uint16_t len = getLength(); + if (len == 0) + return 0; + // Pad to 4-byte boundary + size_t totalSize = (len + 3) & ~3; + // Validate against available buffer + if (totalSize > m_MaxLen) + return 0; + return totalSize; + } + + uint32_t SctpReconfigParameter::getOutgoingReqSeqNum() const + { + if (m_Data == nullptr || getType() != SctpParameterType::OUTGOING_SSN_RESET_REQ) + return 0; + if (getLength() < sizeof(sctp_outgoing_ssn_reset_req)) + return 0; + auto* req = reinterpret_cast(m_Data); + return be32toh(req->reqSeqNum); + } + + uint32_t SctpReconfigParameter::getOutgoingRespSeqNum() const + { + if (m_Data == nullptr || getType() != SctpParameterType::OUTGOING_SSN_RESET_REQ) + return 0; + if (getLength() < sizeof(sctp_outgoing_ssn_reset_req)) + return 0; + auto* req = reinterpret_cast(m_Data); + return be32toh(req->respSeqNum); + } + + uint32_t SctpReconfigParameter::getOutgoingLastTsn() const + { + if (m_Data == nullptr || getType() != SctpParameterType::OUTGOING_SSN_RESET_REQ) + return 0; + if (getLength() < sizeof(sctp_outgoing_ssn_reset_req)) + return 0; + auto* req = reinterpret_cast(m_Data); + return be32toh(req->lastTsn); + } + + std::vector SctpReconfigParameter::getResetStreamNumbers() const + { + std::vector result; + if (m_Data == nullptr) + return result; + + SctpParameterType type = getType(); + uint16_t len = getLength(); + size_t headerSize = 0; + + if (type == SctpParameterType::OUTGOING_SSN_RESET_REQ) + { + headerSize = sizeof(sctp_outgoing_ssn_reset_req); + } + else if (type == SctpParameterType::INCOMING_SSN_RESET_REQ) + { + headerSize = sizeof(sctp_incoming_ssn_reset_req); + } + else + { + return result; + } + + if (len <= headerSize) + return result; + + size_t streamBytesLen = len - headerSize; + size_t numStreams = streamBytesLen / 2; + auto* streamPtr = reinterpret_cast(reinterpret_cast(m_Data) + headerSize); + + for (size_t i = 0; i < numStreams; ++i) + { + result.push_back(be16toh(streamPtr[i])); + } + + return result; + } + + uint32_t SctpReconfigParameter::getIncomingReqSeqNum() const + { + if (m_Data == nullptr || getType() != SctpParameterType::INCOMING_SSN_RESET_REQ) + return 0; + if (getLength() < sizeof(sctp_incoming_ssn_reset_req)) + return 0; + auto* req = reinterpret_cast(m_Data); + return be32toh(req->reqSeqNum); + } + + uint32_t SctpReconfigParameter::getSsnTsnResetReqSeqNum() const + { + if (m_Data == nullptr || getType() != SctpParameterType::SSN_TSN_RESET_REQ) + return 0; + if (getLength() < sizeof(sctp_ssn_tsn_reset_req)) + return 0; + auto* req = reinterpret_cast(m_Data); + return be32toh(req->reqSeqNum); + } + + uint32_t SctpReconfigParameter::getReconfigRespSeqNum() const + { + if (m_Data == nullptr || getType() != SctpParameterType::RECONFIG_RESPONSE) + return 0; + if (getLength() < sizeof(sctp_reconfig_response)) + return 0; + auto* resp = reinterpret_cast(m_Data); + return be32toh(resp->respSeqNum); + } + + SctpReconfigResult SctpReconfigParameter::getReconfigResult() const + { + if (m_Data == nullptr || getType() != SctpParameterType::RECONFIG_RESPONSE) + return static_cast(0); + if (getLength() < sizeof(sctp_reconfig_response)) + return static_cast(0); + auto* resp = reinterpret_cast(m_Data); + return static_cast(be32toh(resp->result)); + } + + bool SctpReconfigParameter::hasReconfigOptionalTsn() const + { + if (m_Data == nullptr || getType() != SctpParameterType::RECONFIG_RESPONSE) + return false; + // Optional fields present if length is 20 (12 base + 4 sender TSN + 4 receiver TSN) + return getLength() >= 20; + } + + uint32_t SctpReconfigParameter::getReconfigSenderNextTsn() const + { + if (!hasReconfigOptionalTsn()) + return 0; + auto* resp = reinterpret_cast(m_Data); + // Sender's Next TSN is right after the base structure + auto* senderTsn = + reinterpret_cast(reinterpret_cast(resp) + sizeof(sctp_reconfig_response)); + return be32toh(*senderTsn); + } + + uint32_t SctpReconfigParameter::getReconfigReceiverNextTsn() const + { + if (!hasReconfigOptionalTsn()) + return 0; + auto* resp = reinterpret_cast(m_Data); + // Receiver's Next TSN is after sender's TSN + auto* receiverTsn = reinterpret_cast(reinterpret_cast(resp) + + sizeof(sctp_reconfig_response) + 4); + return be32toh(*receiverTsn); + } + + uint32_t SctpReconfigParameter::getAddStreamsReqSeqNum() const + { + SctpParameterType type = getType(); + if (m_Data == nullptr || (type != SctpParameterType::ADD_OUTGOING_STREAMS_REQ && + type != SctpParameterType::ADD_INCOMING_STREAMS_REQ)) + return 0; + if (getLength() < sizeof(sctp_add_streams_req)) + return 0; + auto* req = reinterpret_cast(m_Data); + return be32toh(req->reqSeqNum); + } + + uint16_t SctpReconfigParameter::getAddStreamsCount() const + { + SctpParameterType type = getType(); + if (m_Data == nullptr || (type != SctpParameterType::ADD_OUTGOING_STREAMS_REQ && + type != SctpParameterType::ADD_INCOMING_STREAMS_REQ)) + return 0; + if (getLength() < sizeof(sctp_add_streams_req)) + return 0; + auto* req = reinterpret_cast(m_Data); + return be16toh(req->numNewStreams); + } + + std::string SctpReconfigParameter::getTypeName() const + { + switch (getType()) + { + case SctpParameterType::OUTGOING_SSN_RESET_REQ: + return "Outgoing SSN Reset Request"; + case SctpParameterType::INCOMING_SSN_RESET_REQ: + return "Incoming SSN Reset Request"; + case SctpParameterType::SSN_TSN_RESET_REQ: + return "SSN/TSN Reset Request"; + case SctpParameterType::RECONFIG_RESPONSE: + return "Re-configuration Response"; + case SctpParameterType::ADD_OUTGOING_STREAMS_REQ: + return "Add Outgoing Streams Request"; + case SctpParameterType::ADD_INCOMING_STREAMS_REQ: + return "Add Incoming Streams Request"; + default: + return "Unknown"; + } + } + + // ==================== SctpReconfigParameterIterator Implementation ==================== + + SctpReconfigParameterIterator::SctpReconfigParameterIterator(const SctpChunk& chunk) + : m_ParamsBase(nullptr), m_ParamsLen(0), m_CurrentOffset(0) + { + if (chunk.isNull()) + return; + + if (chunk.getChunkType() != SctpChunkType::RE_CONFIG) + return; + + uint16_t chunkLen = chunk.getLength(); + if (chunkLen <= sizeof(sctp_reconfig_chunk)) + return; + + m_ParamsBase = chunk.getValue(); + m_ParamsLen = chunkLen - sizeof(sctp_chunk_hdr); + } + + SctpReconfigParameter SctpReconfigParameterIterator::getParameter() const + { + if (!isValid()) + return SctpReconfigParameter(nullptr, 0); + + return SctpReconfigParameter(m_ParamsBase + m_CurrentOffset, m_ParamsLen - m_CurrentOffset); + } + + SctpReconfigParameterIterator& SctpReconfigParameterIterator::next() + { + if (!isValid()) + return *this; + + SctpReconfigParameter param = getParameter(); + size_t paramTotalSize = param.getTotalSize(); + + if (paramTotalSize == 0) + { + m_CurrentOffset = m_ParamsLen; + } + else + { + m_CurrentOffset += paramTotalSize; + } + + return *this; + } + + bool SctpReconfigParameterIterator::isValid() const + { + if (m_ParamsBase == nullptr || m_ParamsLen == 0) + return false; + + if (m_CurrentOffset + sizeof(sctp_param_hdr) > m_ParamsLen) + return false; + + auto* paramHdr = reinterpret_cast(m_ParamsBase + m_CurrentOffset); + uint16_t paramLen = be16toh(paramHdr->length); + + if (paramLen < sizeof(sctp_param_hdr) || m_CurrentOffset + paramLen > m_ParamsLen) + return false; + + return true; + } + + void SctpReconfigParameterIterator::reset() + { + m_CurrentOffset = 0; + } + + // ==================== SctpAsconfParameter Implementation ==================== + + SctpParameterType SctpAsconfParameter::getType() const + { + if (m_Data == nullptr) + return static_cast(0); + return static_cast(be16toh(m_Data->type)); + } + + uint16_t SctpAsconfParameter::getTypeAsInt() const + { + if (m_Data == nullptr) + return 0; + return be16toh(m_Data->type); + } + + uint16_t SctpAsconfParameter::getLength() const + { + if (m_Data == nullptr) + return 0; + if (m_MaxLen < sizeof(sctp_param_hdr)) + return 0; + return be16toh(m_Data->length); + } + + size_t SctpAsconfParameter::getTotalSize() const + { + uint16_t len = getLength(); + if (len == 0) + return 0; + // Pad to 4-byte boundary + size_t totalSize = (len + 3) & ~3; + // Validate against available buffer + if (totalSize > m_MaxLen) + return 0; + return totalSize; + } + + uint32_t SctpAsconfParameter::getCorrelationId() const + { + SctpParameterType type = getType(); + if (m_Data == nullptr || + (type != SctpParameterType::ADD_IP_ADDRESS && type != SctpParameterType::DELETE_IP_ADDRESS && + type != SctpParameterType::SET_PRIMARY_ADDRESS)) + return 0; + if (getLength() < sizeof(sctp_asconf_param)) + return 0; + auto* param = reinterpret_cast(m_Data); + return be32toh(param->correlationId); + } + + uint8_t* SctpAsconfParameter::getAddressParameter() const + { + SctpParameterType type = getType(); + if (m_Data == nullptr || + (type != SctpParameterType::ADD_IP_ADDRESS && type != SctpParameterType::DELETE_IP_ADDRESS && + type != SctpParameterType::SET_PRIMARY_ADDRESS)) + return nullptr; + if (getLength() <= sizeof(sctp_asconf_param)) + return nullptr; + return reinterpret_cast(m_Data) + sizeof(sctp_asconf_param); + } + + size_t SctpAsconfParameter::getAddressParameterLength() const + { + SctpParameterType type = getType(); + if (m_Data == nullptr || + (type != SctpParameterType::ADD_IP_ADDRESS && type != SctpParameterType::DELETE_IP_ADDRESS && + type != SctpParameterType::SET_PRIMARY_ADDRESS)) + return 0; + uint16_t len = getLength(); + if (len <= sizeof(sctp_asconf_param)) + return 0; + return len - sizeof(sctp_asconf_param); + } + + IPv4Address SctpAsconfParameter::getIPv4Address() const + { + uint8_t* addrParam = getAddressParameter(); + if (addrParam == nullptr) + return IPv4Address::Zero; + + auto* paramHdr = reinterpret_cast(addrParam); + if (be16toh(paramHdr->type) != static_cast(SctpParameterType::IPV4_ADDRESS)) + return IPv4Address::Zero; + + uint16_t paramLen = be16toh(paramHdr->length); + if (paramLen < 8) // 4 byte header + 4 byte address + return IPv4Address::Zero; + + return IPv4Address(addrParam + sizeof(sctp_param_hdr)); + } + + IPv6Address SctpAsconfParameter::getIPv6Address() const + { + uint8_t* addrParam = getAddressParameter(); + if (addrParam == nullptr) + return IPv6Address::Zero; + + auto* paramHdr = reinterpret_cast(addrParam); + if (be16toh(paramHdr->type) != static_cast(SctpParameterType::IPV6_ADDRESS)) + return IPv6Address::Zero; + + uint16_t paramLen = be16toh(paramHdr->length); + if (paramLen < 20) // 4 byte header + 16 byte address + return IPv6Address::Zero; + + return IPv6Address(addrParam + sizeof(sctp_param_hdr)); + } + + uint32_t SctpAsconfParameter::getResponseCorrelationId() const + { + SctpParameterType type = getType(); + if (m_Data == nullptr || + (type != SctpParameterType::ERROR_CAUSE_INDICATION && type != SctpParameterType::SUCCESS_INDICATION)) + return 0; + if (getLength() < sizeof(sctp_asconf_response)) + return 0; + auto* resp = reinterpret_cast(m_Data); + return be32toh(resp->correlationId); + } + + uint8_t* SctpAsconfParameter::getErrorCauses() const + { + if (m_Data == nullptr || getType() != SctpParameterType::ERROR_CAUSE_INDICATION) + return nullptr; + if (getLength() <= sizeof(sctp_asconf_response)) + return nullptr; + return reinterpret_cast(m_Data) + sizeof(sctp_asconf_response); + } + + size_t SctpAsconfParameter::getErrorCausesLength() const + { + if (m_Data == nullptr || getType() != SctpParameterType::ERROR_CAUSE_INDICATION) + return 0; + uint16_t len = getLength(); + if (len <= sizeof(sctp_asconf_response)) + return 0; + return len - sizeof(sctp_asconf_response); + } + + std::string SctpAsconfParameter::getTypeName() const + { + switch (getType()) + { + case SctpParameterType::ADD_IP_ADDRESS: + return "Add IP Address"; + case SctpParameterType::DELETE_IP_ADDRESS: + return "Delete IP Address"; + case SctpParameterType::SET_PRIMARY_ADDRESS: + return "Set Primary Address"; + case SctpParameterType::ERROR_CAUSE_INDICATION: + return "Error Cause Indication"; + case SctpParameterType::SUCCESS_INDICATION: + return "Success Indication"; + default: + return "Unknown"; + } + } + + // ==================== SctpAsconfParameterIterator Implementation ==================== + + SctpAsconfParameterIterator::SctpAsconfParameterIterator(const SctpChunk& chunk, bool skipAddressParam) + : m_ParamsBase(nullptr), m_ParamsLen(0), m_CurrentOffset(0), m_InitialOffset(0) + { + if (chunk.isNull()) + return; + + SctpChunkType type = chunk.getChunkType(); + if (type != SctpChunkType::ASCONF && type != SctpChunkType::ASCONF_ACK) + return; + + uint16_t chunkLen = chunk.getLength(); + size_t headerSize = (type == SctpChunkType::ASCONF) ? sizeof(sctp_asconf_chunk) : sizeof(sctp_asconf_ack_chunk); + + if (chunkLen <= headerSize) + return; + + m_ParamsBase = chunk.getRecordBasePtr() + headerSize; + m_ParamsLen = chunkLen - headerSize; + + // For ASCONF chunks, optionally skip the mandatory Address Parameter + if (type == SctpChunkType::ASCONF && skipAddressParam && m_ParamsLen >= sizeof(sctp_param_hdr)) + { + auto* addrParamHdr = reinterpret_cast(m_ParamsBase); + uint16_t addrParamLen = be16toh(addrParamHdr->length); + size_t paddedLen = (addrParamLen + 3) & ~3; + if (paddedLen <= m_ParamsLen) + { + m_InitialOffset = paddedLen; + m_CurrentOffset = paddedLen; + } + } + } + + SctpAsconfParameter SctpAsconfParameterIterator::getParameter() const + { + if (!isValid()) + return SctpAsconfParameter(nullptr, 0); + + return SctpAsconfParameter(m_ParamsBase + m_CurrentOffset, m_ParamsLen - m_CurrentOffset); + } + + SctpAsconfParameterIterator& SctpAsconfParameterIterator::next() + { + if (!isValid()) + return *this; + + SctpAsconfParameter param = getParameter(); + size_t paramTotalSize = param.getTotalSize(); + + if (paramTotalSize == 0) + { + m_CurrentOffset = m_ParamsLen; + } + else + { + m_CurrentOffset += paramTotalSize; + } + + return *this; + } + + bool SctpAsconfParameterIterator::isValid() const + { + if (m_ParamsBase == nullptr || m_ParamsLen == 0) + return false; + + if (m_CurrentOffset + sizeof(sctp_param_hdr) > m_ParamsLen) + return false; + + auto* paramHdr = reinterpret_cast(m_ParamsBase + m_CurrentOffset); + uint16_t paramLen = be16toh(paramHdr->length); + + if (paramLen < sizeof(sctp_param_hdr) || m_CurrentOffset + paramLen > m_ParamsLen) + return false; + + return true; + } + + void SctpAsconfParameterIterator::reset() + { + m_CurrentOffset = m_InitialOffset; + } + + // ==================== NR-SACK Chunk Creation ==================== + + bool SctpLayer::addNrSackChunk(uint32_t cumulativeTsnAck, uint32_t arwnd, + const std::vector& gapBlocks, + const std::vector& nrGapBlocks, + const std::vector& dupTsns, bool allNonRenegable) + { + size_t chunkLen = sizeof(sctp_nr_sack_chunk) + (gapBlocks.size() * sizeof(sctp_gap_ack_block)) + + (nrGapBlocks.size() * sizeof(sctp_gap_ack_block)) + (dupTsns.size() * sizeof(uint32_t)); + + std::vector chunkData(chunkLen); + + auto* nrSackChunk = reinterpret_cast(chunkData.data()); + nrSackChunk->type = static_cast(SctpChunkType::NR_SACK); + nrSackChunk->flags = allNonRenegable ? SctpNrSackFlags::ALL_NON_RENEGABLE : 0; + nrSackChunk->length = htobe16(static_cast(chunkLen)); + nrSackChunk->cumulativeTsnAck = htobe32(cumulativeTsnAck); + nrSackChunk->arwnd = htobe32(arwnd); + nrSackChunk->numGapBlocks = htobe16(static_cast(gapBlocks.size())); + nrSackChunk->numNrGapBlocks = htobe16(static_cast(nrGapBlocks.size())); + nrSackChunk->numDupTsns = htobe16(static_cast(dupTsns.size())); + nrSackChunk->reserved = 0; + + size_t offset = sizeof(sctp_nr_sack_chunk); + + // Write Gap Ack Blocks + for (const auto& block : gapBlocks) + { + auto* blockPtr = reinterpret_cast(chunkData.data() + offset); + blockPtr->start = htobe16(block.start); + blockPtr->end = htobe16(block.end); + offset += sizeof(sctp_gap_ack_block); + } + + // Write NR Gap Ack Blocks + for (const auto& block : nrGapBlocks) + { + auto* blockPtr = reinterpret_cast(chunkData.data() + offset); + blockPtr->start = htobe16(block.start); + blockPtr->end = htobe16(block.end); + offset += sizeof(sctp_gap_ack_block); + } + + // Write Duplicate TSNs + for (uint32_t tsn : dupTsns) + { + *reinterpret_cast(chunkData.data() + offset) = htobe32(tsn); + offset += sizeof(uint32_t); + } + + return addChunk(chunkData.data(), chunkLen); + } + + // ==================== HMAC Functions (RFC 4895) ==================== + + // SHA-1 implementation constants + namespace + { + // SHA-1 context structure + struct SHA1Context + { + uint32_t state[5]; + uint32_t count[2]; + uint8_t buffer[64]; + }; + + constexpr uint32_t SHA1_K0 = 0x5A827999; + constexpr uint32_t SHA1_K1 = 0x6ED9EBA1; + constexpr uint32_t SHA1_K2 = 0x8F1BBCDC; + constexpr uint32_t SHA1_K3 = 0xCA62C1D6; + + inline uint32_t rotl32(uint32_t x, int n) + { + return (x << n) | (x >> (32 - n)); + } + + void sha1Transform(uint32_t state[5], const uint8_t buffer[64]) + { + uint32_t a, b, c, d, e, w[80]; + + for (int i = 0; i < 16; ++i) + { + w[i] = (static_cast(buffer[i * 4]) << 24) | (static_cast(buffer[i * 4 + 1]) << 16) | + (static_cast(buffer[i * 4 + 2]) << 8) | static_cast(buffer[i * 4 + 3]); + } + + for (int i = 16; i < 80; ++i) + { + w[i] = rotl32(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1); + } + + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + + for (int i = 0; i < 20; ++i) + { + uint32_t temp = rotl32(a, 5) + ((b & c) | (~b & d)) + e + w[i] + SHA1_K0; + e = d; + d = c; + c = rotl32(b, 30); + b = a; + a = temp; + } + + for (int i = 20; i < 40; ++i) + { + uint32_t temp = rotl32(a, 5) + (b ^ c ^ d) + e + w[i] + SHA1_K1; + e = d; + d = c; + c = rotl32(b, 30); + b = a; + a = temp; + } + + for (int i = 40; i < 60; ++i) + { + uint32_t temp = rotl32(a, 5) + ((b & c) | (b & d) | (c & d)) + e + w[i] + SHA1_K2; + e = d; + d = c; + c = rotl32(b, 30); + b = a; + a = temp; + } + + for (int i = 60; i < 80; ++i) + { + uint32_t temp = rotl32(a, 5) + (b ^ c ^ d) + e + w[i] + SHA1_K3; + e = d; + d = c; + c = rotl32(b, 30); + b = a; + a = temp; + } + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + } + + void sha1Init(SHA1Context* ctx) + { + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xEFCDAB89; + ctx->state[2] = 0x98BADCFE; + ctx->state[3] = 0x10325476; + ctx->state[4] = 0xC3D2E1F0; + ctx->count[0] = ctx->count[1] = 0; + } + + void sha1Update(SHA1Context* ctx, const uint8_t* data, size_t len) + { + size_t i, j; + j = (ctx->count[0] >> 3) & 63; + if ((ctx->count[0] += static_cast(len << 3)) < (len << 3)) + ctx->count[1]++; + ctx->count[1] += static_cast(len >> 29); + if ((j + len) > 63) + { + std::memcpy(&ctx->buffer[j], data, (i = 64 - j)); + sha1Transform(ctx->state, ctx->buffer); + for (; i + 63 < len; i += 64) + sha1Transform(ctx->state, &data[i]); + j = 0; + } + else + { + i = 0; + } + std::memcpy(&ctx->buffer[j], &data[i], len - i); + } + + void sha1Final(SHA1Context* ctx, uint8_t digest[20]) + { + uint8_t finalcount[8]; + for (int i = 0; i < 8; ++i) + finalcount[i] = static_cast((ctx->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); + sha1Update(ctx, reinterpret_cast("\200"), 1); + while ((ctx->count[0] & 504) != 448) + sha1Update(ctx, reinterpret_cast("\0"), 1); + sha1Update(ctx, finalcount, 8); + for (int i = 0; i < 20; ++i) + digest[i] = static_cast((ctx->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255); + } + + // SHA-256 implementation + struct SHA256Context + { + uint32_t state[8]; + uint64_t count; + uint8_t buffer[64]; + }; + + constexpr uint32_t SHA256_K[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + }; + + inline uint32_t rotr32(uint32_t x, int n) + { + return (x >> n) | (x << (32 - n)); + } + + inline uint32_t sha256Ch(uint32_t x, uint32_t y, uint32_t z) + { + return (x & y) ^ (~x & z); + } + inline uint32_t sha256Maj(uint32_t x, uint32_t y, uint32_t z) + { + return (x & y) ^ (x & z) ^ (y & z); + } + inline uint32_t sha256Sig0(uint32_t x) + { + return rotr32(x, 2) ^ rotr32(x, 13) ^ rotr32(x, 22); + } + inline uint32_t sha256Sig1(uint32_t x) + { + return rotr32(x, 6) ^ rotr32(x, 11) ^ rotr32(x, 25); + } + inline uint32_t sha256sig0(uint32_t x) + { + return rotr32(x, 7) ^ rotr32(x, 18) ^ (x >> 3); + } + inline uint32_t sha256sig1(uint32_t x) + { + return rotr32(x, 17) ^ rotr32(x, 19) ^ (x >> 10); + } + + void sha256Transform(uint32_t state[8], const uint8_t buffer[64]) + { + uint32_t a, b, c, d, e, f, g, h, w[64]; + + for (int i = 0; i < 16; ++i) + { + w[i] = (static_cast(buffer[i * 4]) << 24) | (static_cast(buffer[i * 4 + 1]) << 16) | + (static_cast(buffer[i * 4 + 2]) << 8) | static_cast(buffer[i * 4 + 3]); + } + + for (int i = 16; i < 64; ++i) + { + w[i] = sha256sig1(w[i - 2]) + w[i - 7] + sha256sig0(w[i - 15]) + w[i - 16]; + } + + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + f = state[5]; + g = state[6]; + h = state[7]; + + for (int i = 0; i < 64; ++i) + { + uint32_t t1 = h + sha256Sig1(e) + sha256Ch(e, f, g) + SHA256_K[i] + w[i]; + uint32_t t2 = sha256Sig0(a) + sha256Maj(a, b, c); + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + state[5] += f; + state[6] += g; + state[7] += h; + } + + void sha256Init(SHA256Context* ctx) + { + ctx->state[0] = 0x6a09e667; + ctx->state[1] = 0xbb67ae85; + ctx->state[2] = 0x3c6ef372; + ctx->state[3] = 0xa54ff53a; + ctx->state[4] = 0x510e527f; + ctx->state[5] = 0x9b05688c; + ctx->state[6] = 0x1f83d9ab; + ctx->state[7] = 0x5be0cd19; + ctx->count = 0; + } + + void sha256Update(SHA256Context* ctx, const uint8_t* data, size_t len) + { + size_t bufferPos = static_cast(ctx->count & 63); + ctx->count += len; + + while (len > 0) + { + size_t toCopy = (std::min)(len, static_cast(64 - bufferPos)); + std::memcpy(&ctx->buffer[bufferPos], data, toCopy); + bufferPos += toCopy; + data += toCopy; + len -= toCopy; + + if (bufferPos == 64) + { + sha256Transform(ctx->state, ctx->buffer); + bufferPos = 0; + } + } + } + + void sha256Final(SHA256Context* ctx, uint8_t digest[32]) + { + size_t bufferPos = static_cast(ctx->count & 63); + ctx->buffer[bufferPos++] = 0x80; + + if (bufferPos > 56) + { + std::memset(&ctx->buffer[bufferPos], 0, 64 - bufferPos); + sha256Transform(ctx->state, ctx->buffer); + bufferPos = 0; + } + + std::memset(&ctx->buffer[bufferPos], 0, 56 - bufferPos); + + uint64_t bitLen = ctx->count * 8; + for (int i = 0; i < 8; ++i) + ctx->buffer[56 + i] = static_cast(bitLen >> (56 - i * 8)); + + sha256Transform(ctx->state, ctx->buffer); + + for (int i = 0; i < 32; ++i) + digest[i] = static_cast(ctx->state[i >> 2] >> ((3 - (i & 3)) * 8)); + } + } // anonymous namespace + + bool calculateSctpHmacSha1(const uint8_t* key, size_t keyLen, const uint8_t* data, size_t dataLen, uint8_t* hmacOut) + { + if (key == nullptr || data == nullptr || hmacOut == nullptr) + return false; + + constexpr size_t BLOCK_SIZE = 64; + constexpr size_t HASH_SIZE = 20; + + uint8_t keyBlock[BLOCK_SIZE]; + std::memset(keyBlock, 0, BLOCK_SIZE); + + // If key is longer than block size, hash it + if (keyLen > BLOCK_SIZE) + { + SHA1Context ctx; + sha1Init(&ctx); + sha1Update(&ctx, key, keyLen); + sha1Final(&ctx, keyBlock); + } + else + { + std::memcpy(keyBlock, key, keyLen); + } + + // Create inner and outer padded keys + uint8_t innerPad[BLOCK_SIZE]; + uint8_t outerPad[BLOCK_SIZE]; + for (size_t i = 0; i < BLOCK_SIZE; ++i) + { + innerPad[i] = keyBlock[i] ^ 0x36; + outerPad[i] = keyBlock[i] ^ 0x5C; + } + + // Inner hash: H(K XOR ipad || message) + SHA1Context innerCtx; + sha1Init(&innerCtx); + sha1Update(&innerCtx, innerPad, BLOCK_SIZE); + sha1Update(&innerCtx, data, dataLen); + uint8_t innerHash[HASH_SIZE]; + sha1Final(&innerCtx, innerHash); + + // Outer hash: H(K XOR opad || inner_hash) + SHA1Context outerCtx; + sha1Init(&outerCtx); + sha1Update(&outerCtx, outerPad, BLOCK_SIZE); + sha1Update(&outerCtx, innerHash, HASH_SIZE); + sha1Final(&outerCtx, hmacOut); + + return true; + } + + bool calculateSctpHmacSha256(const uint8_t* key, size_t keyLen, const uint8_t* data, size_t dataLen, + uint8_t* hmacOut) + { + if (key == nullptr || data == nullptr || hmacOut == nullptr) + return false; + + constexpr size_t BLOCK_SIZE = 64; + constexpr size_t HASH_SIZE = 32; + + uint8_t keyBlock[BLOCK_SIZE]; + std::memset(keyBlock, 0, BLOCK_SIZE); + + // If key is longer than block size, hash it + if (keyLen > BLOCK_SIZE) + { + SHA256Context ctx; + sha256Init(&ctx); + sha256Update(&ctx, key, keyLen); + sha256Final(&ctx, keyBlock); + } + else + { + std::memcpy(keyBlock, key, keyLen); + } + + // Create inner and outer padded keys + uint8_t innerPad[BLOCK_SIZE]; + uint8_t outerPad[BLOCK_SIZE]; + for (size_t i = 0; i < BLOCK_SIZE; ++i) + { + innerPad[i] = keyBlock[i] ^ 0x36; + outerPad[i] = keyBlock[i] ^ 0x5C; + } + + // Inner hash: H(K XOR ipad || message) + SHA256Context innerCtx; + sha256Init(&innerCtx); + sha256Update(&innerCtx, innerPad, BLOCK_SIZE); + sha256Update(&innerCtx, data, dataLen); + uint8_t innerHash[HASH_SIZE]; + sha256Final(&innerCtx, innerHash); + + // Outer hash: H(K XOR opad || inner_hash) + SHA256Context outerCtx; + sha256Init(&outerCtx); + sha256Update(&outerCtx, outerPad, BLOCK_SIZE); + sha256Update(&outerCtx, innerHash, HASH_SIZE); + sha256Final(&outerCtx, hmacOut); + + return true; + } + + bool verifySctpHmac(uint16_t hmacId, const uint8_t* key, size_t keyLen, const uint8_t* data, size_t dataLen, + const uint8_t* expectedHmac, size_t expectedHmacLen) + { + if (key == nullptr || data == nullptr || expectedHmac == nullptr) + return false; + + uint8_t computedHmac[SctpHmacSize::SHA256] = { 0 }; // Use largest size, zero-initialized + size_t hmacSize = 0; + + switch (hmacId) + { + case static_cast(SctpHmacIdentifier::SHA1): + if (expectedHmacLen != SctpHmacSize::SHA1) + return false; + if (!calculateSctpHmacSha1(key, keyLen, data, dataLen, computedHmac)) + return false; + hmacSize = SctpHmacSize::SHA1; + break; + + case static_cast(SctpHmacIdentifier::SHA256): + if (expectedHmacLen != SctpHmacSize::SHA256) + return false; + if (!calculateSctpHmacSha256(key, keyLen, data, dataLen, computedHmac)) + return false; + hmacSize = SctpHmacSize::SHA256; + break; + + default: + return false; // Unsupported HMAC algorithm + } + + // Constant-time comparison to prevent timing attacks + uint8_t diff = 0; + for (size_t i = 0; i < hmacSize; ++i) + { + diff |= computedHmac[i] ^ expectedHmac[i]; + } + + return diff == 0; + } + + bool computeSctpAuthHmac(const SctpLayer& sctpLayer, const uint8_t* key, size_t keyLen, uint8_t* hmacOut, + size_t* hmacOutLen) + { + if (key == nullptr || hmacOut == nullptr || hmacOutLen == nullptr) + return false; + + // Find AUTH chunk + SctpChunk authChunk = const_cast(sctpLayer).getChunk(SctpChunkType::AUTH); + if (authChunk.isNull()) + return false; + + // Use Auth view to access chunk details + auto authView = SctpAuthChunkView::fromChunk(authChunk); + if (!authView.isValid()) + return false; + + // Get AUTH chunk details + uint16_t hmacId = authView.getHmacId(); + size_t authHmacLen = authView.getHmacLength(); + uint16_t authChunkLen = authChunk.getLength(); + + // Determine expected HMAC size based on algorithm + size_t expectedHmacSize; + switch (hmacId) + { + case static_cast(SctpHmacIdentifier::SHA1): + expectedHmacSize = SctpHmacSize::SHA1; + break; + case static_cast(SctpHmacIdentifier::SHA256): + expectedHmacSize = SctpHmacSize::SHA256; + break; + default: + return false; // Unsupported HMAC algorithm + } + + // Get pointers to build the data to authenticate + uint8_t* authChunkPtr = authChunk.getRecordBasePtr(); + uint8_t* layerData = const_cast(sctpLayer).getData(); + size_t layerLen = sctpLayer.getHeaderLen(); + + // Calculate offset of AUTH chunk from layer start + size_t authOffset = authChunkPtr - layerData; + + // AUTH chunk header is 8 bytes (type, flags, length, sharedKeyId, hmacId) + // HMAC field starts at offset 8 within AUTH chunk + constexpr size_t AUTH_HEADER_SIZE = 8; + + // Calculate total size of data to authenticate: + // AUTH chunk (with zeroed HMAC) + all chunks after AUTH + size_t authChunkTotalSize = authChunk.getTotalSize(); + size_t dataAfterAuth = layerLen - authOffset - authChunkTotalSize; + size_t totalDataLen = authChunkTotalSize + dataAfterAuth; + + // Allocate buffer for data with zeroed HMAC + std::vector dataToAuth(totalDataLen); + + // Copy AUTH chunk header (8 bytes) + std::memcpy(dataToAuth.data(), authChunkPtr, AUTH_HEADER_SIZE); + + // Zero out the HMAC field (comes after the 8-byte header) + std::memset(dataToAuth.data() + AUTH_HEADER_SIZE, 0, authHmacLen); + + // Copy any padding after HMAC in AUTH chunk + size_t postHmacOffset = AUTH_HEADER_SIZE + authHmacLen; + size_t paddingLen = authChunkTotalSize - authChunkLen; + if (paddingLen > 0 && postHmacOffset < authChunkTotalSize) + { + // There are padding bytes after HMAC + size_t authChunkRemainder = authChunkTotalSize - postHmacOffset; + std::memcpy(dataToAuth.data() + postHmacOffset, authChunkPtr + postHmacOffset, authChunkRemainder); + } + + // Copy all chunks after AUTH chunk + if (dataAfterAuth > 0) + { + std::memcpy(dataToAuth.data() + authChunkTotalSize, authChunkPtr + authChunkTotalSize, dataAfterAuth); + } + + // Compute HMAC + bool success = false; + switch (hmacId) + { + case static_cast(SctpHmacIdentifier::SHA1): + success = calculateSctpHmacSha1(key, keyLen, dataToAuth.data(), totalDataLen, hmacOut); + break; + case static_cast(SctpHmacIdentifier::SHA256): + success = calculateSctpHmacSha256(key, keyLen, dataToAuth.data(), totalDataLen, hmacOut); + break; + } + + if (success) + *hmacOutLen = expectedHmacSize; + + return success; + } + + bool verifySctpAuthChunk(const SctpLayer& sctpLayer, const uint8_t* key, size_t keyLen) + { + if (key == nullptr) + return false; + + // Find AUTH chunk + SctpChunk authChunk = const_cast(sctpLayer).getChunk(SctpChunkType::AUTH); + if (authChunk.isNull()) + return false; + + // Use Auth view to access chunk details + auto authView = SctpAuthChunkView::fromChunk(authChunk); + if (!authView.isValid()) + return false; + + // Get the HMAC from the AUTH chunk + const uint8_t* storedHmac = authView.getHmacData(); + size_t storedHmacLen = authView.getHmacLength(); + + if (storedHmac == nullptr || storedHmacLen == 0) + return false; + + // Compute expected HMAC + uint8_t computedHmac[SctpHmacSize::SHA256]; // Large enough for both SHA-1 and SHA-256 + size_t computedHmacLen = 0; + + if (!computeSctpAuthHmac(sctpLayer, key, keyLen, computedHmac, &computedHmacLen)) + return false; + + // Verify lengths match + if (computedHmacLen != storedHmacLen) + return false; + + // Constant-time comparison + uint8_t diff = 0; + for (size_t i = 0; i < computedHmacLen; ++i) + { + diff |= computedHmac[i] ^ storedHmac[i]; + } + + return diff == 0; + } + +} // namespace pcpp diff --git a/README.md b/README.md index 5e88c1f9b4..1395d1718b 100644 --- a/README.md +++ b/README.md @@ -253,42 +253,43 @@ PcapPlusPlus currently supports parsing, editing and creation of packets of the 27. COTP 28. GTP (v1 & v2) 29. IPSec AH & ESP - parsing only (no editing capabilities) -30. TCP -31. TPKT -32. UDP +30. SCTP +31. TCP +32. TPKT +33. UDP ### Session Layer (L5) -33. SDP -34. SIP +34. SDP +35. SIP ### Presentation Layer (L6) -35. SSL/TLS - parsing only (no editing capabilities) +36. SSL/TLS - parsing only (no editing capabilities) ### Application Layer (L7) -36. ASN.1 decoder and encoder -37. BGP (v4) -38. Cryptographic key decoders -39. DHCP -40. DHCPv6 -41. DNS -42. DoIP -43. FTP -44. HTTP headers (request & response) -45. LDAP -46. Modbus -47. NTP (v3, v4) -48. PEM decoder and encoder -49. Radius -50. S7 Communication (S7comm) -51. SMTP -52. SOME/IP -53. SSH - parsing only (no editing capabilities) -54. Telnet - parsing only (no editing capabilities) -55. X509 certificates - parsing only (no editing capabilities) -56. Generic payload +37. ASN.1 decoder and encoder +38. BGP (v4) +39. Cryptographic key decoders +40. DHCP +41. DHCPv6 +42. DNS +43. DoIP +44. FTP +45. HTTP headers (request & response) +46. LDAP +47. Modbus +48. NTP (v3, v4) +49. PEM decoder and encoder +50. Radius +51. S7 Communication (S7comm) +52. SMTP +53. SOME/IP +54. SSH - parsing only (no editing capabilities) +55. Telnet - parsing only (no editing capabilities) +56. X509 certificates - parsing only (no editing capabilities) +57. Generic payload ## DPDK And PF_RING Support diff --git a/Tests/Packet++Test/CMakeLists.txt b/Tests/Packet++Test/CMakeLists.txt index 8bcf59b253..cb97ea4d8a 100644 --- a/Tests/Packet++Test/CMakeLists.txt +++ b/Tests/Packet++Test/CMakeLists.txt @@ -33,6 +33,7 @@ add_executable( Tests/PPPoETests.cpp Tests/RadiusTests.cpp Tests/S7CommTests.cpp + Tests/SctpTests.cpp Tests/SipSdpTests.cpp Tests/Sll2Tests.cpp Tests/SllNullLoopbackTests.cpp diff --git a/Tests/Packet++Test/PacketExamples/SctpAllPackets.pcap b/Tests/Packet++Test/PacketExamples/SctpAllPackets.pcap new file mode 100644 index 0000000000..66b5c44dc0 Binary files /dev/null and b/Tests/Packet++Test/PacketExamples/SctpAllPackets.pcap differ diff --git a/Tests/Packet++Test/PacketExamples/SctpAuthPacket.dat b/Tests/Packet++Test/PacketExamples/SctpAuthPacket.dat new file mode 100644 index 0000000000..7754041c0f --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/SctpAuthPacket.dat @@ -0,0 +1 @@ +000c293e504f005056c00008080045000038000100004084f627c0a80164c0a8016504d20050abcdef01514056a90f00001800010003deadbeefcafebabe123456789abcdef0 \ No newline at end of file diff --git a/Tests/Packet++Test/PacketExamples/SctpCookieEchoPacket.dat b/Tests/Packet++Test/PacketExamples/SctpCookieEchoPacket.dat new file mode 100644 index 0000000000..4e506ee163 --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/SctpCookieEchoPacket.dat @@ -0,0 +1 @@ +000c293e504f005056c00008080045000030000100004084f62fc0a80164c0a8016504d20050abcdef01ece503140a000010534543524554434f4f4b4945 \ No newline at end of file diff --git a/Tests/Packet++Test/PacketExamples/SctpDataPacket.dat b/Tests/Packet++Test/PacketExamples/SctpDataPacket.dat new file mode 100644 index 0000000000..922379a8fd --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/SctpDataPacket.dat @@ -0,0 +1 @@ +000c293e504f005056c0000808004500003c000100004084f623c0a80164c0a8016504d20050abcdef0178be3fc50003001c0000000a000100050000002e48656c6c6f20576f726c6421 \ No newline at end of file diff --git a/Tests/Packet++Test/PacketExamples/SctpExamplePackets.pcap b/Tests/Packet++Test/PacketExamples/SctpExamplePackets.pcap new file mode 100644 index 0000000000..9fc71d08db Binary files /dev/null and b/Tests/Packet++Test/PacketExamples/SctpExamplePackets.pcap differ diff --git a/Tests/Packet++Test/PacketExamples/SctpForwardTsnPacket.dat b/Tests/Packet++Test/PacketExamples/SctpForwardTsnPacket.dat new file mode 100644 index 0000000000..0471a2b419 --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/SctpForwardTsnPacket.dat @@ -0,0 +1 @@ +000c293e504f005056c00008080045000030000100004084f62fc0a80164c0a8016504d20050abcdef01228d63b8c0000010000001000001000a00050014 \ No newline at end of file diff --git a/Tests/Packet++Test/PacketExamples/SctpHeartbeatPacket.dat b/Tests/Packet++Test/PacketExamples/SctpHeartbeatPacket.dat new file mode 100644 index 0000000000..d47bca1667 --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/SctpHeartbeatPacket.dat @@ -0,0 +1 @@ +000c293e504f005056c00008080045000030000100004084f62fc0a80164c0a8016504d20050abcdef014d9752f2040000100001000c4842494e464f2121 \ No newline at end of file diff --git a/Tests/Packet++Test/PacketExamples/SctpIDataPacket.dat b/Tests/Packet++Test/PacketExamples/SctpIDataPacket.dat new file mode 100644 index 0000000000..a1c94da0f9 --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/SctpIDataPacket.dat @@ -0,0 +1 @@ +000c293e504f005056c0000808004500003c000100004084f623c0a80164c0a8016504d20050abcdef0141e18b7f4003001c0000000a0003000000000005000000334944415441212121 \ No newline at end of file diff --git a/Tests/Packet++Test/PacketExamples/SctpIForwardTsnPacket.dat b/Tests/Packet++Test/PacketExamples/SctpIForwardTsnPacket.dat new file mode 100644 index 0000000000..50bc2183cc --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/SctpIForwardTsnPacket.dat @@ -0,0 +1 @@ +000c293e504f005056c00008080045000038000100004084f627c0a80164c0a8016504d20050abcdef016fce42eac200001800000200000200010000006400070000000000c8 \ No newline at end of file diff --git a/Tests/Packet++Test/PacketExamples/SctpIPv6Packet.dat b/Tests/Packet++Test/PacketExamples/SctpIPv6Packet.dat new file mode 100644 index 0000000000..8ebf7edb2e --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/SctpIPv6Packet.dat @@ -0,0 +1 @@ +000c293e504f005056c0000886dd600000000028844020010db800000000000000000000000120010db800000000000000000000000213881389deadbeef638cab720003001c00000001000000000000000048656c6c6f20495076362121 \ No newline at end of file diff --git a/Tests/Packet++Test/PacketExamples/SctpInitPacket.dat b/Tests/Packet++Test/PacketExamples/SctpInitPacket.dat new file mode 100644 index 0000000000..a43366af89 --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/SctpInitPacket.dat @@ -0,0 +1 @@ +000c293e504f005056c00008080045000034000100004084f62bc0a80164c0a8016500500051123456783c95e40d01000014aabbccdd00010000000a000a00000001 \ No newline at end of file diff --git a/Tests/Packet++Test/PacketExamples/SctpSackPacket.dat b/Tests/Packet++Test/PacketExamples/SctpSackPacket.dat new file mode 100644 index 0000000000..26d75493ce --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/SctpSackPacket.dat @@ -0,0 +1 @@ +000c293e504f005056c00008080045000038000100004084f627c0a80165c0a80164005004d2abcdef017cd8073203000018000000140000800000010001000500080000000f \ No newline at end of file diff --git a/Tests/Packet++Test/TestDefinition.h b/Tests/Packet++Test/TestDefinition.h index 9248e028d4..ea8ebeda98 100644 --- a/Tests/Packet++Test/TestDefinition.h +++ b/Tests/Packet++Test/TestDefinition.h @@ -340,3 +340,96 @@ PTF_TEST_CASE(PemDecodingTest); // Implemented in CryptoKeyTests.cpp PTF_TEST_CASE(CryptoKeyDecodingTest); PTF_TEST_CASE(CryptoKeyInvalidDataTest); + +// Implemented in SctpTests.cpp +PTF_TEST_CASE(SctpLayerParsingTest); +PTF_TEST_CASE(SctpLayerCreationTest); +PTF_TEST_CASE(SctpDataChunkParsingTest); +PTF_TEST_CASE(SctpSackChunkParsingTest); +PTF_TEST_CASE(SctpMultipleChunksTest); +PTF_TEST_CASE(SctpChecksumTest); +PTF_TEST_CASE(SctpValidationTest); +PTF_TEST_CASE(SctpChunkTypesTest); +PTF_TEST_CASE(SctpShutdownChunkTest); +PTF_TEST_CASE(SctpForwardTsnChunkTest); +PTF_TEST_CASE(SctpIForwardTsnChunkTest); +PTF_TEST_CASE(SctpHeartbeatChunkTest); +PTF_TEST_CASE(SctpCookieEchoChunkTest); +PTF_TEST_CASE(SctpAuthChunkTest); +PTF_TEST_CASE(SctpIDataChunkTest); +PTF_TEST_CASE(SctpEcneCwrChunkTest); +PTF_TEST_CASE(SctpCwrChunkTest); +PTF_TEST_CASE(SctpAbortChunkTest); +PTF_TEST_CASE(SctpErrorChunkTest); +PTF_TEST_CASE(SctpOverIPv6Test); +PTF_TEST_CASE(SctpAsconfChunkTest); +PTF_TEST_CASE(SctpAddDataChunkTest); +PTF_TEST_CASE(SctpAddInitChunkTest); +PTF_TEST_CASE(SctpAddInitAckChunkTest); +PTF_TEST_CASE(SctpAddSackChunkTest); +PTF_TEST_CASE(SctpAddHeartbeatChunkTest); +PTF_TEST_CASE(SctpAddShutdownChunksTest); +PTF_TEST_CASE(SctpAddAbortChunkTest); +PTF_TEST_CASE(SctpAddCookieChunksTest); +PTF_TEST_CASE(SctpAddErrorChunkTest); +PTF_TEST_CASE(SctpMultipleChunkCreationTest); +PTF_TEST_CASE(SctpInitParameterIteratorTest); +PTF_TEST_CASE(SctpInitParameterIPv6Test); +PTF_TEST_CASE(SctpChunkPaddingTest); +PTF_TEST_CASE(SctpBundlingValidationTest); +PTF_TEST_CASE(SctpAddEcneCwrChunkTest); +PTF_TEST_CASE(SctpAddForwardTsnChunkTest); +PTF_TEST_CASE(SctpAddIDataChunkTest); +PTF_TEST_CASE(SctpAddIForwardTsnChunkTest); +PTF_TEST_CASE(SctpAddPadChunkTest); +PTF_TEST_CASE(SctpErrorCauseIteratorTest); +PTF_TEST_CASE(SctpStateCookieParameterTest); +PTF_TEST_CASE(SctpHostNameAddressDetectionTest); +PTF_TEST_CASE(SctpAuthParametersTest); +PTF_TEST_CASE(SctpAddAuthChunkTest); +PTF_TEST_CASE(SctpAddAsconfChunkTest); +PTF_TEST_CASE(SctpAddAsconfAckChunkTest); +PTF_TEST_CASE(SctpAddReconfigChunkTest); +PTF_TEST_CASE(SctpReconfigParameterIteratorTest); +PTF_TEST_CASE(SctpReconfigResponseTest); +PTF_TEST_CASE(SctpAsconfParameterIteratorTest); +PTF_TEST_CASE(SctpZeroChecksumParameterTest); +PTF_TEST_CASE(SctpAdditionalErrorCauseAccessorsTest); +PTF_TEST_CASE(SctpUnrecognizedChunkErrorTest); +PTF_TEST_CASE(SctpExtendedPpidEnumsTest); +PTF_TEST_CASE(SctpReconfigResultEnumsTest); +PTF_TEST_CASE(SctpEdmidEnumsTest); +PTF_TEST_CASE(SctpNrSackChunkParsingTest); +PTF_TEST_CASE(SctpAddNrSackChunkTest); +PTF_TEST_CASE(SctpAddNrSackChunkMinimalTest); +PTF_TEST_CASE(SctpHmacSha1ComputationTest); +PTF_TEST_CASE(SctpHmacSha256ComputationTest); +PTF_TEST_CASE(SctpHmacVerificationTest); +PTF_TEST_CASE(SctpHmacSizeConstantsTest); +PTF_TEST_CASE(SctpExtendedPpidEnumsNewTest); +PTF_TEST_CASE(SctpNrSackChunkTypeNameTest); +PTF_TEST_CASE(SctpChunkActionBitsTest); +PTF_TEST_CASE(SctpParamActionBitsTest); +PTF_TEST_CASE(SctpComputeAuthHmacTest); +PTF_TEST_CASE(SctpComputeAuthHmacSha256Test); +PTF_TEST_CASE(SctpComputeAuthHmacNoAuthChunkTest); +PTF_TEST_CASE(SctpExtendedPpidEnumsNewTelecomTest); +PTF_TEST_CASE(SctpDataChunkViewTest); +PTF_TEST_CASE(SctpInitChunkViewTest); +PTF_TEST_CASE(SctpInitAckChunkViewTest); +PTF_TEST_CASE(SctpSackChunkViewTest); +PTF_TEST_CASE(SctpChunkViewTypeSafetyTest); +PTF_TEST_CASE(SctpHeartbeatChunkViewTest); +PTF_TEST_CASE(SctpCookieEchoChunkViewTest); +PTF_TEST_CASE(SctpAbortChunkViewTest); +PTF_TEST_CASE(SctpShutdownChunkViewTest); +PTF_TEST_CASE(SctpShutdownAckChunkViewTest); +PTF_TEST_CASE(SctpShutdownCompleteChunkViewTest); +PTF_TEST_CASE(SctpControlChunkViewTypeSafetyTest); +PTF_TEST_CASE(SctpAuthChunkViewTest); +PTF_TEST_CASE(SctpForwardTsnChunkViewTest); +PTF_TEST_CASE(SctpIDataChunkViewTest); +PTF_TEST_CASE(SctpIForwardTsnChunkViewTest); +PTF_TEST_CASE(SctpNrSackChunkViewTest); +PTF_TEST_CASE(SctpPadChunkViewTest); +PTF_TEST_CASE(SctpExtensionChunkViewTypeSafetyTest); diff --git a/Tests/Packet++Test/Tests/SctpTests.cpp b/Tests/Packet++Test/Tests/SctpTests.cpp new file mode 100644 index 0000000000..dd7887b04c --- /dev/null +++ b/Tests/Packet++Test/Tests/SctpTests.cpp @@ -0,0 +1,3190 @@ +#include "../TestDefinition.h" +#include "../Utils/TestUtils.h" +#include "EndianPortable.h" +#include "Packet.h" +#include "EthLayer.h" +#include "IPv4Layer.h" +#include "IPv6Layer.h" +#include "SctpLayer.h" +#include "PayloadLayer.h" +#include "SystemUtils.h" + +using pcpp_tests::utils::createPacketFromHexResource; + +PTF_TEST_CASE(SctpLayerParsingTest) +{ + // Parse SCTP INIT packet from hex resource file + auto rawPacket = createPacketFromHexResource("PacketExamples/SctpInitPacket.dat"); + + pcpp::Packet sctpPacket(rawPacket.get()); + + // Verify it's an SCTP packet + PTF_ASSERT_TRUE(sctpPacket.isPacketOfType(pcpp::IPv4)); + PTF_ASSERT_TRUE(sctpPacket.isPacketOfType(pcpp::SCTP)); + + // Get SCTP layer + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + + // Test common header fields + PTF_ASSERT_EQUAL(sctpLayer->getSrcPort(), 80); + PTF_ASSERT_EQUAL(sctpLayer->getDstPort(), 81); + PTF_ASSERT_EQUAL(sctpLayer->getVerificationTag(), 0x12345678); + + // Test checksum validation + PTF_ASSERT_TRUE(sctpLayer->isChecksumValid()); + + // Test chunk count + PTF_ASSERT_EQUAL(sctpLayer->getChunkCount(), 1); + + // Get INIT chunk + pcpp::SctpChunk initChunk = sctpLayer->getFirstChunk(); + PTF_ASSERT_TRUE(initChunk.isNotNull()); + PTF_ASSERT_EQUAL(initChunk.getChunkType(), pcpp::SctpChunkType::INIT, enumclass); + PTF_ASSERT_EQUAL(initChunk.getChunkTypeAsInt(), 1); + PTF_ASSERT_EQUAL(initChunk.getLength(), 20); + + // Use view for INIT chunk access + auto initView = pcpp::SctpInitChunkView::fromChunk(initChunk); + PTF_ASSERT_TRUE(initView.isValid()); + + // Test INIT chunk fields + PTF_ASSERT_EQUAL(initView.getInitiateTag(), 0xaabbccdd); + PTF_ASSERT_EQUAL(initView.getArwnd(), 65536); + PTF_ASSERT_EQUAL(initView.getNumOutboundStreams(), 10); + PTF_ASSERT_EQUAL(initView.getNumInboundStreams(), 10); + PTF_ASSERT_EQUAL(initView.getInitialTsn(), 1); + + // Test toString() + std::string layerStr = sctpLayer->toString(); + PTF_ASSERT_TRUE(layerStr.find("SCTP Layer") != std::string::npos); + PTF_ASSERT_TRUE(layerStr.find("Src port: 80") != std::string::npos); + PTF_ASSERT_TRUE(layerStr.find("Dst port: 81") != std::string::npos); +} + +PTF_TEST_CASE(SctpLayerCreationTest) +{ + // Create a new SCTP layer + pcpp::SctpLayer sctpLayer(12345, 80, 0xDEADBEEF); + + // Test basic fields + PTF_ASSERT_EQUAL(sctpLayer.getSrcPort(), 12345); + PTF_ASSERT_EQUAL(sctpLayer.getDstPort(), 80); + PTF_ASSERT_EQUAL(sctpLayer.getVerificationTag(), 0xDEADBEEF); + + // Test setters + sctpLayer.setSrcPort(54321); + sctpLayer.setDstPort(443); + sctpLayer.setVerificationTag(0x12345678); + + PTF_ASSERT_EQUAL(sctpLayer.getSrcPort(), 54321); + PTF_ASSERT_EQUAL(sctpLayer.getDstPort(), 443); + PTF_ASSERT_EQUAL(sctpLayer.getVerificationTag(), 0x12345678); + + // Test header access + pcpp::sctphdr* sctpHdr = sctpLayer.getSctpHeader(); + PTF_ASSERT_NOT_NULL(sctpHdr); + + // Test header fields in network byte order + PTF_ASSERT_EQUAL(sctpHdr->portSrc, htobe16(54321)); + PTF_ASSERT_EQUAL(sctpHdr->portDst, htobe16(443)); + PTF_ASSERT_EQUAL(sctpHdr->verificationTag, htobe32(0x12345678)); + + // Test OSI layer + PTF_ASSERT_EQUAL(sctpLayer.getOsiModelLayer(), pcpp::OsiModelTransportLayer, enum); + + // Test header length (should be 12 bytes for common header only) + PTF_ASSERT_EQUAL(sctpLayer.getHeaderLen(), 12); +} + +PTF_TEST_CASE(SctpDataChunkParsingTest) +{ + // Parse SCTP DATA packet from hex resource file + auto rawPacket = createPacketFromHexResource("PacketExamples/SctpDataPacket.dat"); + + pcpp::Packet sctpPacket(rawPacket.get()); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + + PTF_ASSERT_EQUAL(sctpLayer->getSrcPort(), 1234); + PTF_ASSERT_EQUAL(sctpLayer->getDstPort(), 80); + PTF_ASSERT_TRUE(sctpLayer->isChecksumValid()); + + // Get DATA chunk + pcpp::SctpChunk dataChunk = sctpLayer->getChunk(pcpp::SctpChunkType::DATA); + PTF_ASSERT_TRUE(dataChunk.isNotNull()); + PTF_ASSERT_EQUAL(dataChunk.getChunkType(), pcpp::SctpChunkType::DATA, enumclass); + + // Use view for DATA chunk access + auto dataView = pcpp::SctpDataChunkView::fromChunk(dataChunk); + PTF_ASSERT_TRUE(dataView.isValid()); + + // Test DATA chunk fields + PTF_ASSERT_EQUAL(dataView.getTsn(), 10); + PTF_ASSERT_EQUAL(dataView.getStreamId(), 1); + PTF_ASSERT_EQUAL(dataView.getSequenceNumber(), 5); + PTF_ASSERT_EQUAL(dataView.getPpid(), 46); + + // Test flags + PTF_ASSERT_TRUE(dataView.isBeginFragment()); + PTF_ASSERT_TRUE(dataView.isEndFragment()); + PTF_ASSERT_FALSE(dataView.isUnordered()); + PTF_ASSERT_FALSE(dataView.isImmediate()); + + // Test user data + PTF_ASSERT_EQUAL(dataView.getUserDataLength(), 12); + uint8_t* userData = dataView.getUserData(); + PTF_ASSERT_NOT_NULL(userData); + PTF_ASSERT_BUF_COMPARE(userData, "Hello World!", 12); + + // Test chunk type name + PTF_ASSERT_EQUAL(dataChunk.getChunkTypeName(), "DATA"); +} + +PTF_TEST_CASE(SctpSackChunkParsingTest) +{ + // Parse SCTP SACK packet from hex resource file + auto rawPacket = createPacketFromHexResource("PacketExamples/SctpSackPacket.dat"); + + pcpp::Packet sctpPacket(rawPacket.get()); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + + PTF_ASSERT_TRUE(sctpLayer->isChecksumValid()); + + // Get SACK chunk + pcpp::SctpChunk sackChunk = sctpLayer->getChunk(pcpp::SctpChunkType::SACK); + PTF_ASSERT_TRUE(sackChunk.isNotNull()); + PTF_ASSERT_EQUAL(sackChunk.getChunkType(), pcpp::SctpChunkType::SACK, enumclass); + + // Use view for SACK chunk access + auto sackView = pcpp::SctpSackChunkView::fromChunk(sackChunk); + PTF_ASSERT_TRUE(sackView.isValid()); + + // Test SACK chunk fields + PTF_ASSERT_EQUAL(sackView.getCumulativeTsnAck(), 20); + PTF_ASSERT_EQUAL(sackView.getArwnd(), 32768); + PTF_ASSERT_EQUAL(sackView.getNumGapBlocks(), 1); + PTF_ASSERT_EQUAL(sackView.getNumDupTsns(), 1); + + // Test gap blocks + std::vector gapBlocks = sackView.getGapBlocks(); + PTF_ASSERT_EQUAL(gapBlocks.size(), 1); + PTF_ASSERT_EQUAL(gapBlocks[0].start, 5); + PTF_ASSERT_EQUAL(gapBlocks[0].end, 8); + + // Test duplicate TSNs + std::vector dupTsns = sackView.getDupTsns(); + PTF_ASSERT_EQUAL(dupTsns.size(), 1); + PTF_ASSERT_EQUAL(dupTsns[0], 15); + + // Test chunk type name + PTF_ASSERT_EQUAL(sackChunk.getChunkTypeName(), "SACK"); +} + +PTF_TEST_CASE(SctpMultipleChunksTest) +{ + timeval time; + gettimeofday(&time, nullptr); + + // Create SCTP packet with multiple chunks (COOKIE-ACK + DATA) + // This test uses inline bytes since we need to test bundled chunks + uint8_t sctpMultiPacket[] = { + // Ethernet header (14 bytes) + 0x00, 0x0c, 0x29, 0x3e, 0x50, 0x4f, 0x00, 0x50, 0x56, 0xc0, 0x00, 0x08, 0x08, 0x00, + + // IPv4 header (20 bytes) + 0x45, 0x00, 0x00, 0x40, // Total Length: 64 + 0x00, 0x01, 0x00, 0x00, 0x40, 0x84, 0x00, 0x00, 0xc0, 0xa8, 0x01, 0x64, 0xc0, 0xa8, 0x01, 0x65, + + // SCTP common header (12 bytes) + 0x04, 0xd2, 0x00, 0x50, 0xab, 0xcd, 0xef, 0x01, 0x00, 0x00, 0x00, 0x00, + + // COOKIE-ACK chunk (4 bytes) + 0x0b, // Type: COOKIE-ACK + 0x00, // Flags + 0x00, 0x04, // Length: 4 + + // DATA chunk (20 bytes: 16 header + 4 data) + 0x00, // Type: DATA + 0x03, // Flags: B=1, E=1 + 0x00, 0x14, // Length: 20 + 0x00, 0x00, 0x00, 0x01, // TSN: 1 + 0x00, 0x00, // Stream ID: 0 + 0x00, 0x00, // Stream Seq: 0 + 0x00, 0x00, 0x00, 0x00, // PPID: 0 + 0x54, 0x45, 0x53, 0x54 // User data: "TEST" + }; + + // Calculate CRC32c checksum + size_t sctpOffset = 14 + 20; + size_t sctpLen = sizeof(sctpMultiPacket) - sctpOffset; + + sctpMultiPacket[sctpOffset + 8] = 0; + sctpMultiPacket[sctpOffset + 9] = 0; + sctpMultiPacket[sctpOffset + 10] = 0; + sctpMultiPacket[sctpOffset + 11] = 0; + + uint32_t crc = pcpp::calculateSctpCrc32c(sctpMultiPacket + sctpOffset, sctpLen); + // Store checksum in network byte order (big-endian) per RFC 3309/9260 + sctpMultiPacket[sctpOffset + 8] = (crc >> 24) & 0xFF; + sctpMultiPacket[sctpOffset + 9] = (crc >> 16) & 0xFF; + sctpMultiPacket[sctpOffset + 10] = (crc >> 8) & 0xFF; + sctpMultiPacket[sctpOffset + 11] = (crc >> 0) & 0xFF; + + pcpp::RawPacket rawPacket(sctpMultiPacket, sizeof(sctpMultiPacket), time, false); + pcpp::Packet sctpPacket(&rawPacket); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + + // Test chunk count + PTF_ASSERT_EQUAL(sctpLayer->getChunkCount(), 2); + + // Test first chunk (COOKIE-ACK) + pcpp::SctpChunk firstChunk = sctpLayer->getFirstChunk(); + PTF_ASSERT_TRUE(firstChunk.isNotNull()); + PTF_ASSERT_EQUAL(firstChunk.getChunkType(), pcpp::SctpChunkType::COOKIE_ACK, enumclass); + PTF_ASSERT_EQUAL(firstChunk.getLength(), 4); + + // Test second chunk (DATA) + pcpp::SctpChunk secondChunk = sctpLayer->getNextChunk(firstChunk); + PTF_ASSERT_TRUE(secondChunk.isNotNull()); + PTF_ASSERT_EQUAL(secondChunk.getChunkType(), pcpp::SctpChunkType::DATA, enumclass); + auto secondDataView = pcpp::SctpDataChunkView::fromChunk(secondChunk); + PTF_ASSERT_EQUAL(secondDataView.getTsn(), 1); + + // Test getting chunk by type + pcpp::SctpChunk cookieAckChunk = sctpLayer->getChunk(pcpp::SctpChunkType::COOKIE_ACK); + PTF_ASSERT_TRUE(cookieAckChunk.isNotNull()); + + pcpp::SctpChunk dataChunk = sctpLayer->getChunk(pcpp::SctpChunkType::DATA); + PTF_ASSERT_TRUE(dataChunk.isNotNull()); + + // Test non-existent chunk + pcpp::SctpChunk initChunk = sctpLayer->getChunk(pcpp::SctpChunkType::INIT); + PTF_ASSERT_TRUE(initChunk.isNull()); + + // Test no more chunks + pcpp::SctpChunk thirdChunk = sctpLayer->getNextChunk(secondChunk); + PTF_ASSERT_TRUE(thirdChunk.isNull()); +} + +PTF_TEST_CASE(SctpChecksumTest) +{ + // Parse SCTP INIT packet from hex resource file + auto rawPacket = createPacketFromHexResource("PacketExamples/SctpInitPacket.dat"); + + pcpp::Packet packet(rawPacket.get()); + + pcpp::SctpLayer* sctpLayer = packet.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + + // Verify checksum is valid + PTF_ASSERT_TRUE(sctpLayer->isChecksumValid()); + + // Calculate checksum and verify it matches + uint32_t calculatedCrc = sctpLayer->calculateChecksum(false); + // The stored checksum should match what we calculate + uint32_t storedCrc = be32toh(sctpLayer->getSctpHeader()->checksum); + PTF_ASSERT_EQUAL(calculatedCrc, storedCrc); + + // Test recalculating checksum + sctpLayer->getSctpHeader()->checksum = 0; // Clear checksum + sctpLayer->calculateChecksum(true); // Recalculate and write + PTF_ASSERT_TRUE(sctpLayer->isChecksumValid()); +} + +PTF_TEST_CASE(SctpValidationTest) +{ + // Test isDataValid with various inputs + + // Valid SCTP header only (12 bytes) + uint8_t validHeader[] = { + 0x00, 0x50, 0x00, 0x51, // Ports + 0x12, 0x34, 0x56, 0x78, // Tag + 0x00, 0x00, 0x00, 0x00 // Checksum + }; + PTF_ASSERT_TRUE(pcpp::SctpLayer::isDataValid(validHeader, sizeof(validHeader))); + + // Too short (less than 12 bytes) + uint8_t tooShort[] = { 0x00, 0x50, 0x00, 0x51, 0x12, 0x34 }; + PTF_ASSERT_FALSE(pcpp::SctpLayer::isDataValid(tooShort, sizeof(tooShort))); + + // Null pointer + PTF_ASSERT_FALSE(pcpp::SctpLayer::isDataValid(nullptr, 12)); + + // Zero source port (invalid) + uint8_t zeroSrcPort[] = { 0x00, 0x00, 0x00, 0x51, 0x12, 0x34, 0x56, 0x78, 0x00, 0x00, 0x00, 0x00 }; + PTF_ASSERT_FALSE(pcpp::SctpLayer::isDataValid(zeroSrcPort, sizeof(zeroSrcPort))); + + // Zero destination port (invalid) + uint8_t zeroDstPort[] = { 0x00, 0x50, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78, 0x00, 0x00, 0x00, 0x00 }; + PTF_ASSERT_FALSE(pcpp::SctpLayer::isDataValid(zeroDstPort, sizeof(zeroDstPort))); + + // Valid header with valid chunk + uint8_t validWithChunk[] = { 0x00, 0x50, 0x00, 0x51, 0x12, 0x34, 0x56, 0x78, 0x00, 0x00, 0x00, 0x00, + // SHUTDOWN-ACK chunk + 0x08, 0x00, 0x00, 0x04 }; + PTF_ASSERT_TRUE(pcpp::SctpLayer::isDataValid(validWithChunk, sizeof(validWithChunk))); + + // Valid header with chunk that has length too small + uint8_t invalidChunkLen[] = { 0x00, 0x50, 0x00, 0x51, 0x12, 0x34, 0x56, 0x78, 0x00, 0x00, 0x00, 0x00, + // Invalid chunk (length < 4) + 0x08, 0x00, 0x00, 0x02 }; + PTF_ASSERT_FALSE(pcpp::SctpLayer::isDataValid(invalidChunkLen, sizeof(invalidChunkLen))); +} + +PTF_TEST_CASE(SctpChunkTypesTest) +{ + // Test chunk type name conversion for all known chunk types + pcpp::sctp_chunk_hdr chunkHdr; + + // Test each chunk type + struct ChunkTypeTest + { + uint8_t type; + pcpp::SctpChunkType expectedType; + const char* expectedName; + }; + + constexpr ChunkTypeTest tests[] = { + { 0, pcpp::SctpChunkType::DATA, "DATA" }, + { 1, pcpp::SctpChunkType::INIT, "INIT" }, + { 2, pcpp::SctpChunkType::INIT_ACK, "INIT-ACK" }, + { 3, pcpp::SctpChunkType::SACK, "SACK" }, + { 4, pcpp::SctpChunkType::HEARTBEAT, "HEARTBEAT" }, + { 5, pcpp::SctpChunkType::HEARTBEAT_ACK, "HEARTBEAT-ACK" }, + { 6, pcpp::SctpChunkType::ABORT, "ABORT" }, + { 7, pcpp::SctpChunkType::SHUTDOWN, "SHUTDOWN" }, + { 8, pcpp::SctpChunkType::SHUTDOWN_ACK, "SHUTDOWN-ACK" }, + { 9, pcpp::SctpChunkType::SCTP_ERROR, "ERROR" }, + { 10, pcpp::SctpChunkType::COOKIE_ECHO, "COOKIE-ECHO" }, + { 11, pcpp::SctpChunkType::COOKIE_ACK, "COOKIE-ACK" }, + { 12, pcpp::SctpChunkType::ECNE, "ECNE" }, + { 13, pcpp::SctpChunkType::CWR, "CWR" }, + { 14, pcpp::SctpChunkType::SHUTDOWN_COMPLETE, "SHUTDOWN-COMPLETE" }, + { 15, pcpp::SctpChunkType::AUTH, "AUTH" }, + { 64, pcpp::SctpChunkType::I_DATA, "I-DATA" }, + { 128, pcpp::SctpChunkType::ASCONF_ACK, "ASCONF-ACK" }, + { 130, pcpp::SctpChunkType::RE_CONFIG, "RE-CONFIG" }, + { 132, pcpp::SctpChunkType::PAD, "PAD" }, + { 192, pcpp::SctpChunkType::FORWARD_TSN, "FORWARD-TSN" }, + { 193, pcpp::SctpChunkType::ASCONF, "ASCONF" }, + { 194, pcpp::SctpChunkType::I_FORWARD_TSN, "I-FORWARD-TSN" }, + { 200, pcpp::SctpChunkType::UNKNOWN, "UNKNOWN" } // Unknown type + }; + + for (const auto& test : tests) + { + chunkHdr.type = test.type; + chunkHdr.flags = 0; + chunkHdr.length = htobe16(4); + + pcpp::SctpChunk chunk(reinterpret_cast(&chunkHdr)); + PTF_ASSERT_EQUAL(chunk.getChunkType(), test.expectedType, enumclass); + PTF_ASSERT_EQUAL(chunk.getChunkTypeName(), test.expectedName); + } +} + +PTF_TEST_CASE(SctpShutdownChunkTest) +{ + timeval time; + gettimeofday(&time, nullptr); + + // Create SCTP packet with SHUTDOWN chunk + uint8_t sctpShutdownPacket[] = { + // Ethernet header + 0x00, 0x0c, 0x29, 0x3e, 0x50, 0x4f, 0x00, 0x50, 0x56, 0xc0, 0x00, 0x08, 0x08, 0x00, + + // IPv4 header + 0x45, 0x00, 0x00, 0x28, // Total Length: 40 + 0x00, 0x01, 0x00, 0x00, 0x40, 0x84, 0x00, 0x00, 0xc0, 0xa8, 0x01, 0x64, 0xc0, 0xa8, 0x01, 0x65, + + // SCTP header + SHUTDOWN chunk + 0x00, 0x50, 0x00, 0x51, 0x12, 0x34, 0x56, 0x78, 0x00, 0x00, 0x00, 0x00, + + // SHUTDOWN chunk (8 bytes) + 0x07, // Type: SHUTDOWN + 0x00, // Flags + 0x00, 0x08, // Length: 8 + 0x00, 0x00, 0x00, 0x64 // Cumulative TSN Ack: 100 + }; + + size_t sctpOffset = 14 + 20; + size_t sctpLen = sizeof(sctpShutdownPacket) - sctpOffset; + + sctpShutdownPacket[sctpOffset + 8] = 0; + sctpShutdownPacket[sctpOffset + 9] = 0; + sctpShutdownPacket[sctpOffset + 10] = 0; + sctpShutdownPacket[sctpOffset + 11] = 0; + + uint32_t crc = pcpp::calculateSctpCrc32c(sctpShutdownPacket + sctpOffset, sctpLen); + // Store checksum in network byte order (big-endian) per RFC 3309/9260 + sctpShutdownPacket[sctpOffset + 8] = (crc >> 24) & 0xFF; + sctpShutdownPacket[sctpOffset + 9] = (crc >> 16) & 0xFF; + sctpShutdownPacket[sctpOffset + 10] = (crc >> 8) & 0xFF; + sctpShutdownPacket[sctpOffset + 11] = (crc >> 0) & 0xFF; + + pcpp::RawPacket rawPacket(sctpShutdownPacket, sizeof(sctpShutdownPacket), time, false); + pcpp::Packet sctpPacket(&rawPacket); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + + PTF_ASSERT_TRUE(sctpLayer->isChecksumValid()); + + pcpp::SctpChunk shutdownChunk = sctpLayer->getChunk(pcpp::SctpChunkType::SHUTDOWN); + PTF_ASSERT_TRUE(shutdownChunk.isNotNull()); + auto shutdownView = pcpp::SctpShutdownChunkView::fromChunk(shutdownChunk); + PTF_ASSERT_TRUE(shutdownView.isValid()); + PTF_ASSERT_EQUAL(shutdownView.getCumulativeTsnAck(), 100); +} + +PTF_TEST_CASE(SctpForwardTsnChunkTest) +{ + // Parse FORWARD-TSN packet from hex resource file (RFC 3758) + auto rawPacket = createPacketFromHexResource("PacketExamples/SctpForwardTsnPacket.dat"); + pcpp::Packet sctpPacket(rawPacket.get()); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + PTF_ASSERT_TRUE(sctpLayer->isChecksumValid()); + + pcpp::SctpChunk fwdTsnChunk = sctpLayer->getChunk(pcpp::SctpChunkType::FORWARD_TSN); + PTF_ASSERT_TRUE(fwdTsnChunk.isNotNull()); + PTF_ASSERT_EQUAL(fwdTsnChunk.getChunkType(), pcpp::SctpChunkType::FORWARD_TSN, enumclass); + PTF_ASSERT_EQUAL(fwdTsnChunk.getChunkTypeName(), "FORWARD-TSN"); + + // Use view for FORWARD-TSN chunk access + auto fwdTsnView = pcpp::SctpForwardTsnChunkView::fromChunk(fwdTsnChunk); + PTF_ASSERT_TRUE(fwdTsnView.isValid()); + + // Test FORWARD-TSN fields + PTF_ASSERT_EQUAL(fwdTsnView.getNewCumulativeTsn(), 256); + PTF_ASSERT_EQUAL(fwdTsnView.getStreamCount(), 2); + + // Test stream entries - values are returned in host byte order + std::vector streams = fwdTsnView.getStreams(); + PTF_ASSERT_EQUAL(streams.size(), 2); + PTF_ASSERT_EQUAL(streams[0].streamId, 1); + PTF_ASSERT_EQUAL(streams[0].streamSeq, 10); + PTF_ASSERT_EQUAL(streams[1].streamId, 5); + PTF_ASSERT_EQUAL(streams[1].streamSeq, 20); +} + +PTF_TEST_CASE(SctpIForwardTsnChunkTest) +{ + // Parse I-FORWARD-TSN packet from hex resource file (RFC 8260) + auto rawPacket = createPacketFromHexResource("PacketExamples/SctpIForwardTsnPacket.dat"); + pcpp::Packet sctpPacket(rawPacket.get()); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + PTF_ASSERT_TRUE(sctpLayer->isChecksumValid()); + + pcpp::SctpChunk iFwdTsnChunk = sctpLayer->getChunk(pcpp::SctpChunkType::I_FORWARD_TSN); + PTF_ASSERT_TRUE(iFwdTsnChunk.isNotNull()); + PTF_ASSERT_EQUAL(iFwdTsnChunk.getChunkType(), pcpp::SctpChunkType::I_FORWARD_TSN, enumclass); + PTF_ASSERT_EQUAL(iFwdTsnChunk.getChunkTypeName(), "I-FORWARD-TSN"); + + // Use view for I-FORWARD-TSN chunk access + auto iFwdTsnView = pcpp::SctpIForwardTsnChunkView::fromChunk(iFwdTsnChunk); + PTF_ASSERT_TRUE(iFwdTsnView.isValid()); + + // Test I-FORWARD-TSN fields + PTF_ASSERT_EQUAL(iFwdTsnView.getNewCumulativeTsn(), 512); + PTF_ASSERT_EQUAL(iFwdTsnView.getStreamCount(), 2); + + // Test stream entries - values returned in host byte order + std::vector streams = iFwdTsnView.getStreams(); + PTF_ASSERT_EQUAL(streams.size(), 2); + PTF_ASSERT_EQUAL(streams[0].streamId, 2); + PTF_ASSERT_TRUE(pcpp::sctp_iforward_tsn_stream::isUnordered(streams[0].reserved)); // U flag = 1 + PTF_ASSERT_EQUAL(streams[0].mid, 100); + PTF_ASSERT_EQUAL(streams[1].streamId, 7); + PTF_ASSERT_FALSE(pcpp::sctp_iforward_tsn_stream::isUnordered(streams[1].reserved)); // U flag = 0 + PTF_ASSERT_EQUAL(streams[1].mid, 200); +} + +PTF_TEST_CASE(SctpHeartbeatChunkTest) +{ + // Parse HEARTBEAT packet from hex resource file + auto rawPacket = createPacketFromHexResource("PacketExamples/SctpHeartbeatPacket.dat"); + pcpp::Packet sctpPacket(rawPacket.get()); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + PTF_ASSERT_TRUE(sctpLayer->isChecksumValid()); + + pcpp::SctpChunk hbChunk = sctpLayer->getChunk(pcpp::SctpChunkType::HEARTBEAT); + PTF_ASSERT_TRUE(hbChunk.isNotNull()); + PTF_ASSERT_EQUAL(hbChunk.getChunkType(), pcpp::SctpChunkType::HEARTBEAT, enumclass); + PTF_ASSERT_EQUAL(hbChunk.getChunkTypeName(), "HEARTBEAT"); + + // Use view for HEARTBEAT chunk access + auto hbView = pcpp::SctpHeartbeatChunkView::fromChunk(hbChunk); + PTF_ASSERT_TRUE(hbView.isValid()); + + // Test HEARTBEAT info (returns the full TLV parameter: type + length + value) + PTF_ASSERT_EQUAL(hbView.getInfoLength(), 12); + uint8_t* hbInfo = hbView.getInfo(); + PTF_ASSERT_NOT_NULL(hbInfo); + // TLV: type=0x0001, length=0x000c, value="HBINFO!!" + uint8_t expectedTlv[] = { 0x00, 0x01, 0x00, 0x0c, 'H', 'B', 'I', 'N', 'F', 'O', '!', '!' }; + PTF_ASSERT_BUF_COMPARE(hbInfo, expectedTlv, 12); +} + +PTF_TEST_CASE(SctpCookieEchoChunkTest) +{ + // Parse COOKIE-ECHO packet from hex resource file + auto rawPacket = createPacketFromHexResource("PacketExamples/SctpCookieEchoPacket.dat"); + pcpp::Packet sctpPacket(rawPacket.get()); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + PTF_ASSERT_TRUE(sctpLayer->isChecksumValid()); + + pcpp::SctpChunk cookieChunk = sctpLayer->getChunk(pcpp::SctpChunkType::COOKIE_ECHO); + PTF_ASSERT_TRUE(cookieChunk.isNotNull()); + PTF_ASSERT_EQUAL(cookieChunk.getChunkType(), pcpp::SctpChunkType::COOKIE_ECHO, enumclass); + PTF_ASSERT_EQUAL(cookieChunk.getChunkTypeName(), "COOKIE-ECHO"); + + // Use view for COOKIE-ECHO chunk access + auto cookieView = pcpp::SctpCookieEchoChunkView::fromChunk(cookieChunk); + PTF_ASSERT_TRUE(cookieView.isValid()); + + // Test COOKIE-ECHO data + PTF_ASSERT_EQUAL(cookieView.getCookieLength(), 12); + uint8_t* cookieData = cookieView.getCookie(); + PTF_ASSERT_NOT_NULL(cookieData); + PTF_ASSERT_BUF_COMPARE(cookieData, "SECRETCOOKIE", 12); +} + +PTF_TEST_CASE(SctpAuthChunkTest) +{ + // Parse AUTH packet from hex resource file (RFC 4895) + auto rawPacket = createPacketFromHexResource("PacketExamples/SctpAuthPacket.dat"); + pcpp::Packet sctpPacket(rawPacket.get()); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + PTF_ASSERT_TRUE(sctpLayer->isChecksumValid()); + + pcpp::SctpChunk authChunk = sctpLayer->getChunk(pcpp::SctpChunkType::AUTH); + PTF_ASSERT_TRUE(authChunk.isNotNull()); + PTF_ASSERT_EQUAL(authChunk.getChunkType(), pcpp::SctpChunkType::AUTH, enumclass); + PTF_ASSERT_EQUAL(authChunk.getChunkTypeName(), "AUTH"); + + // Use view for AUTH chunk access + auto authView = pcpp::SctpAuthChunkView::fromChunk(authChunk); + PTF_ASSERT_TRUE(authView.isValid()); + + // Test AUTH chunk fields + PTF_ASSERT_EQUAL(authView.getSharedKeyId(), 1); + PTF_ASSERT_EQUAL(authView.getHmacId(), 3); // SHA-256 + + // Test HMAC data + PTF_ASSERT_EQUAL(authView.getHmacLength(), 16); + uint8_t* hmacData = authView.getHmacData(); + PTF_ASSERT_NOT_NULL(hmacData); + + uint8_t expectedHmac[] = { 0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe, + 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0 }; + PTF_ASSERT_BUF_COMPARE(hmacData, expectedHmac, 16); +} + +PTF_TEST_CASE(SctpIDataChunkTest) +{ + // Parse I-DATA packet from hex resource file (RFC 8260) + auto rawPacket = createPacketFromHexResource("PacketExamples/SctpIDataPacket.dat"); + pcpp::Packet sctpPacket(rawPacket.get()); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + PTF_ASSERT_TRUE(sctpLayer->isChecksumValid()); + + pcpp::SctpChunk idataChunk = sctpLayer->getChunk(pcpp::SctpChunkType::I_DATA); + PTF_ASSERT_TRUE(idataChunk.isNotNull()); + PTF_ASSERT_EQUAL(idataChunk.getChunkType(), pcpp::SctpChunkType::I_DATA, enumclass); + PTF_ASSERT_EQUAL(idataChunk.getChunkTypeName(), "I-DATA"); + + // Use view for I-DATA chunk access + auto idataView = pcpp::SctpIDataChunkView::fromChunk(idataChunk); + PTF_ASSERT_TRUE(idataView.isValid()); + + // Test I-DATA chunk fields + PTF_ASSERT_EQUAL(idataView.getTsn(), 10); + PTF_ASSERT_EQUAL(idataView.getStreamId(), 3); + PTF_ASSERT_EQUAL(idataView.getMessageId(), 5); + PTF_ASSERT_EQUAL(idataView.getPpidOrFsn(), 51); // PPID when B=1 + + // Test flags + PTF_ASSERT_TRUE(idataView.isBeginFragment()); + PTF_ASSERT_TRUE(idataView.isEndFragment()); + + // Test user data (I-DATA header is 20 bytes vs DATA's 16) + PTF_ASSERT_EQUAL(idataView.getUserDataLength(), 8); + uint8_t* userData = idataView.getUserData(); + PTF_ASSERT_NOT_NULL(userData); + PTF_ASSERT_BUF_COMPARE(userData, "IDATA!!!", 8); +} + +PTF_TEST_CASE(SctpEcneCwrChunkTest) +{ + timeval time; + gettimeofday(&time, nullptr); + + // Create SCTP packet with ECNE chunk + uint8_t sctpEcnePacket[] = { + // Ethernet header + 0x00, 0x0c, 0x29, 0x3e, 0x50, 0x4f, 0x00, 0x50, 0x56, 0xc0, 0x00, 0x08, 0x08, 0x00, + + // IPv4 header + 0x45, 0x00, 0x00, 0x28, // Total Length: 40 + 0x00, 0x01, 0x00, 0x00, 0x40, 0x84, 0x00, 0x00, 0xc0, 0xa8, 0x01, 0x64, 0xc0, 0xa8, 0x01, 0x65, + + // SCTP common header (12 bytes) + 0x04, 0xd2, 0x00, 0x50, 0xab, 0xcd, 0xef, 0x01, 0x00, 0x00, 0x00, 0x00, + + // ECNE chunk (8 bytes) + 0x0c, // Type: ECNE (12) + 0x00, // Flags + 0x00, 0x08, // Length: 8 + 0x00, 0x00, 0x01, 0x00 // Lowest TSN: 256 + }; + + size_t sctpOffset = 14 + 20; + size_t sctpLen = sizeof(sctpEcnePacket) - sctpOffset; + + uint32_t crc = pcpp::calculateSctpCrc32c(sctpEcnePacket + sctpOffset, sctpLen); + sctpEcnePacket[sctpOffset + 8] = (crc >> 24) & 0xFF; + sctpEcnePacket[sctpOffset + 9] = (crc >> 16) & 0xFF; + sctpEcnePacket[sctpOffset + 10] = (crc >> 8) & 0xFF; + sctpEcnePacket[sctpOffset + 11] = (crc >> 0) & 0xFF; + + pcpp::RawPacket rawPacket(sctpEcnePacket, sizeof(sctpEcnePacket), time, false); + pcpp::Packet sctpPacket(&rawPacket); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + PTF_ASSERT_TRUE(sctpLayer->isChecksumValid()); + + pcpp::SctpChunk ecneChunk = sctpLayer->getChunk(pcpp::SctpChunkType::ECNE); + PTF_ASSERT_TRUE(ecneChunk.isNotNull()); + PTF_ASSERT_EQUAL(ecneChunk.getChunkType(), pcpp::SctpChunkType::ECNE, enumclass); + PTF_ASSERT_EQUAL(ecneChunk.getChunkTypeName(), "ECNE"); + + auto ecneView = pcpp::SctpEcneChunkView::fromChunk(ecneChunk); + PTF_ASSERT_TRUE(ecneView.isValid()); + PTF_ASSERT_EQUAL(ecneView.getLowestTsn(), 256); +} + +PTF_TEST_CASE(SctpOverIPv6Test) +{ + // Parse SCTP over IPv6 packet from hex resource file + auto rawPacket = createPacketFromHexResource("PacketExamples/SctpIPv6Packet.dat"); + pcpp::Packet sctpPacket(rawPacket.get()); + + // Verify IPv6 layer + PTF_ASSERT_TRUE(sctpPacket.isPacketOfType(pcpp::IPv6)); + pcpp::IPv6Layer* ipv6Layer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(ipv6Layer); + PTF_ASSERT_EQUAL(ipv6Layer->getSrcIPAddress().toString(), "2001:db8::1"); + PTF_ASSERT_EQUAL(ipv6Layer->getDstIPAddress().toString(), "2001:db8::2"); + + // Verify SCTP layer + PTF_ASSERT_TRUE(sctpPacket.isPacketOfType(pcpp::SCTP)); + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + + // Test SCTP header fields + PTF_ASSERT_EQUAL(sctpLayer->getSrcPort(), 5000); + PTF_ASSERT_EQUAL(sctpLayer->getDstPort(), 5001); + PTF_ASSERT_EQUAL(sctpLayer->getVerificationTag(), 0xdeadbeef); + PTF_ASSERT_TRUE(sctpLayer->isChecksumValid()); + + // Test DATA chunk + pcpp::SctpChunk dataChunk = sctpLayer->getChunk(pcpp::SctpChunkType::DATA); + PTF_ASSERT_TRUE(dataChunk.isNotNull()); + auto dataView = pcpp::SctpDataChunkView::fromChunk(dataChunk); + PTF_ASSERT_TRUE(dataView.isValid()); + PTF_ASSERT_EQUAL(dataView.getTsn(), 1); + PTF_ASSERT_EQUAL(dataView.getUserDataLength(), 12); + PTF_ASSERT_BUF_COMPARE(dataView.getUserData(), "Hello IPv6!!", 12); +} + +PTF_TEST_CASE(SctpCwrChunkTest) +{ + timeval time; + gettimeofday(&time, nullptr); + + // Create SCTP packet with CWR chunk (RFC 9260 Section 3.3.14) + uint8_t sctpCwrPacket[] = { + // Ethernet header + 0x00, 0x0c, 0x29, 0x3e, 0x50, 0x4f, 0x00, 0x50, 0x56, 0xc0, 0x00, 0x08, 0x08, 0x00, + + // IPv4 header + 0x45, 0x00, 0x00, 0x28, // Total Length: 40 + 0x00, 0x01, 0x00, 0x00, 0x40, 0x84, 0x00, 0x00, 0xc0, 0xa8, 0x01, 0x64, 0xc0, 0xa8, 0x01, 0x65, + + // SCTP common header (12 bytes) + 0x04, 0xd2, 0x00, 0x50, 0xab, 0xcd, 0xef, 0x01, 0x00, 0x00, 0x00, 0x00, + + // CWR chunk (8 bytes) + 0x0d, // Type: CWR (13) + 0x00, // Flags + 0x00, 0x08, // Length: 8 + 0x00, 0x00, 0x02, 0x00 // Lowest TSN: 512 + }; + + size_t sctpOffset = 14 + 20; + size_t sctpLen = sizeof(sctpCwrPacket) - sctpOffset; + + uint32_t crc = pcpp::calculateSctpCrc32c(sctpCwrPacket + sctpOffset, sctpLen); + sctpCwrPacket[sctpOffset + 8] = (crc >> 24) & 0xFF; + sctpCwrPacket[sctpOffset + 9] = (crc >> 16) & 0xFF; + sctpCwrPacket[sctpOffset + 10] = (crc >> 8) & 0xFF; + sctpCwrPacket[sctpOffset + 11] = (crc >> 0) & 0xFF; + + pcpp::RawPacket rawPacket(sctpCwrPacket, sizeof(sctpCwrPacket), time, false); + pcpp::Packet sctpPacket(&rawPacket); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + PTF_ASSERT_TRUE(sctpLayer->isChecksumValid()); + + pcpp::SctpChunk cwrChunk = sctpLayer->getChunk(pcpp::SctpChunkType::CWR); + PTF_ASSERT_TRUE(cwrChunk.isNotNull()); + PTF_ASSERT_EQUAL(cwrChunk.getChunkType(), pcpp::SctpChunkType::CWR, enumclass); + PTF_ASSERT_EQUAL(cwrChunk.getChunkTypeName(), "CWR"); + + auto cwrView = pcpp::SctpCwrChunkView::fromChunk(cwrChunk); + PTF_ASSERT_TRUE(cwrView.isValid()); + PTF_ASSERT_EQUAL(cwrView.getLowestTsn(), 512); +} + +PTF_TEST_CASE(SctpAbortChunkTest) +{ + timeval time; + gettimeofday(&time, nullptr); + + // Create SCTP packet with ABORT chunk with T bit and error cause + uint8_t sctpAbortPacket[] = { + // Ethernet header + 0x00, 0x0c, 0x29, 0x3e, 0x50, 0x4f, 0x00, 0x50, 0x56, 0xc0, 0x00, 0x08, 0x08, 0x00, + + // IPv4 header + 0x45, 0x00, 0x00, 0x2c, // Total Length: 44 + 0x00, 0x01, 0x00, 0x00, 0x40, 0x84, 0x00, 0x00, 0xc0, 0xa8, 0x01, 0x64, 0xc0, 0xa8, 0x01, 0x65, + + // SCTP common header (12 bytes) + 0x04, 0xd2, 0x00, 0x50, 0xab, 0xcd, 0xef, 0x01, 0x00, 0x00, 0x00, 0x00, + + // ABORT chunk (12 bytes: 4 header + 8 error cause) + 0x06, // Type: ABORT (6) + 0x01, // Flags: T bit set + 0x00, 0x0c, // Length: 12 + // Error cause: User Initiated Abort (code=12, length=8) + 0x00, 0x0c, // Cause Code: 12 + 0x00, 0x08, // Cause Length: 8 + 0x41, 0x42, 0x43, 0x44 // Cause data: "ABCD" + }; + + size_t sctpOffset = 14 + 20; + size_t sctpLen = sizeof(sctpAbortPacket) - sctpOffset; + + uint32_t crc = pcpp::calculateSctpCrc32c(sctpAbortPacket + sctpOffset, sctpLen); + sctpAbortPacket[sctpOffset + 8] = (crc >> 24) & 0xFF; + sctpAbortPacket[sctpOffset + 9] = (crc >> 16) & 0xFF; + sctpAbortPacket[sctpOffset + 10] = (crc >> 8) & 0xFF; + sctpAbortPacket[sctpOffset + 11] = (crc >> 0) & 0xFF; + + pcpp::RawPacket rawPacket(sctpAbortPacket, sizeof(sctpAbortPacket), time, false); + pcpp::Packet sctpPacket(&rawPacket); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + PTF_ASSERT_TRUE(sctpLayer->isChecksumValid()); + + pcpp::SctpChunk abortChunk = sctpLayer->getChunk(pcpp::SctpChunkType::ABORT); + PTF_ASSERT_TRUE(abortChunk.isNotNull()); + PTF_ASSERT_EQUAL(abortChunk.getChunkType(), pcpp::SctpChunkType::ABORT, enumclass); + PTF_ASSERT_EQUAL(abortChunk.getChunkTypeName(), "ABORT"); + + auto abortView = pcpp::SctpAbortChunkView::fromChunk(abortChunk); + PTF_ASSERT_TRUE(abortView.isValid()); + + // Test T bit + PTF_ASSERT_TRUE(abortView.isTBitSet()); + + // Test error causes + PTF_ASSERT_EQUAL(abortView.getErrorCausesLength(), 8); + uint8_t* errorCause = abortView.getFirstErrorCause(); + PTF_ASSERT_NOT_NULL(errorCause); + + // Verify error cause header (code and length in network byte order) + auto* causeHdr = reinterpret_cast(errorCause); + PTF_ASSERT_EQUAL(be16toh(causeHdr->code), 12); // User Initiated Abort + PTF_ASSERT_EQUAL(be16toh(causeHdr->length), 8); +} + +PTF_TEST_CASE(SctpErrorChunkTest) +{ + timeval time; + gettimeofday(&time, nullptr); + + // Create SCTP packet with ERROR chunk + uint8_t sctpErrorPacket[] = { + // Ethernet header + 0x00, 0x0c, 0x29, 0x3e, 0x50, 0x4f, 0x00, 0x50, 0x56, 0xc0, 0x00, 0x08, 0x08, 0x00, + + // IPv4 header + 0x45, 0x00, 0x00, 0x2c, // Total Length: 44 + 0x00, 0x01, 0x00, 0x00, 0x40, 0x84, 0x00, 0x00, 0xc0, 0xa8, 0x01, 0x64, 0xc0, 0xa8, 0x01, 0x65, + + // SCTP common header (12 bytes) + 0x04, 0xd2, 0x00, 0x50, 0xab, 0xcd, 0xef, 0x01, 0x00, 0x00, 0x00, 0x00, + + // ERROR chunk (12 bytes: 4 header + 8 error cause) + 0x09, // Type: ERROR (9) + 0x00, // Flags + 0x00, 0x0c, // Length: 12 + // Error cause: Invalid Stream Identifier (code=1, length=8) + 0x00, 0x01, // Cause Code: 1 (Invalid Stream ID) + 0x00, 0x08, // Cause Length: 8 + 0x00, 0x05, // Stream Identifier: 5 + 0x00, 0x00 // Reserved + }; + + size_t sctpOffset = 14 + 20; + size_t sctpLen = sizeof(sctpErrorPacket) - sctpOffset; + + uint32_t crc = pcpp::calculateSctpCrc32c(sctpErrorPacket + sctpOffset, sctpLen); + sctpErrorPacket[sctpOffset + 8] = (crc >> 24) & 0xFF; + sctpErrorPacket[sctpOffset + 9] = (crc >> 16) & 0xFF; + sctpErrorPacket[sctpOffset + 10] = (crc >> 8) & 0xFF; + sctpErrorPacket[sctpOffset + 11] = (crc >> 0) & 0xFF; + + pcpp::RawPacket rawPacket(sctpErrorPacket, sizeof(sctpErrorPacket), time, false); + pcpp::Packet sctpPacket(&rawPacket); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + PTF_ASSERT_TRUE(sctpLayer->isChecksumValid()); + + pcpp::SctpChunk errorChunk = sctpLayer->getChunk(pcpp::SctpChunkType::SCTP_ERROR); + PTF_ASSERT_TRUE(errorChunk.isNotNull()); + PTF_ASSERT_EQUAL(errorChunk.getChunkType(), pcpp::SctpChunkType::SCTP_ERROR, enumclass); + PTF_ASSERT_EQUAL(errorChunk.getChunkTypeName(), "ERROR"); + + auto errorView = pcpp::SctpErrorChunkView::fromChunk(errorChunk); + PTF_ASSERT_TRUE(errorView.isValid()); + + // Test error causes + PTF_ASSERT_EQUAL(errorView.getCausesLength(), 8); + uint8_t* errorCause = errorView.getFirstCause(); + PTF_ASSERT_NOT_NULL(errorCause); + + // Verify error cause header + auto* causeHdr = reinterpret_cast(errorCause); + PTF_ASSERT_EQUAL(be16toh(causeHdr->code), 1); // Invalid Stream Identifier + PTF_ASSERT_EQUAL(be16toh(causeHdr->length), 8); +} + +PTF_TEST_CASE(SctpAsconfChunkTest) +{ + timeval time; + gettimeofday(&time, nullptr); + + // Create SCTP packet with ASCONF chunk (RFC 5061) + uint8_t sctpAsconfPacket[] = { // Ethernet header + 0x00, 0x0c, 0x29, 0x3e, 0x50, 0x4f, 0x00, 0x50, 0x56, 0xc0, 0x00, 0x08, 0x08, 0x00, + + // IPv4 header + 0x45, 0x00, 0x00, 0x30, // Total Length: 48 + 0x00, 0x01, 0x00, 0x00, 0x40, 0x84, 0x00, 0x00, 0xc0, 0xa8, 0x01, 0x64, 0xc0, 0xa8, + 0x01, 0x65, + + // SCTP common header (12 bytes) + 0x04, 0xd2, 0x00, 0x50, 0xab, 0xcd, 0xef, 0x01, 0x00, 0x00, 0x00, 0x00, + + // ASCONF chunk (12 bytes: 8 header + 4 placeholder data) + 0xc1, // Type: ASCONF (193) + 0x00, // Flags + 0x00, 0x0c, // Length: 12 + 0x00, 0x00, 0x12, 0x34, // Serial Number: 0x1234 + // Address Parameter placeholder (4 bytes) + 0x00, 0x00, 0x00, 0x00 + }; + + size_t sctpOffset = 14 + 20; + size_t sctpLen = sizeof(sctpAsconfPacket) - sctpOffset; + + uint32_t crc = pcpp::calculateSctpCrc32c(sctpAsconfPacket + sctpOffset, sctpLen); + sctpAsconfPacket[sctpOffset + 8] = (crc >> 24) & 0xFF; + sctpAsconfPacket[sctpOffset + 9] = (crc >> 16) & 0xFF; + sctpAsconfPacket[sctpOffset + 10] = (crc >> 8) & 0xFF; + sctpAsconfPacket[sctpOffset + 11] = (crc >> 0) & 0xFF; + + pcpp::RawPacket rawPacket(sctpAsconfPacket, sizeof(sctpAsconfPacket), time, false); + pcpp::Packet sctpPacket(&rawPacket); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + PTF_ASSERT_TRUE(sctpLayer->isChecksumValid()); + + pcpp::SctpChunk asconfChunk = sctpLayer->getChunk(pcpp::SctpChunkType::ASCONF); + PTF_ASSERT_TRUE(asconfChunk.isNotNull()); + PTF_ASSERT_EQUAL(asconfChunk.getChunkType(), pcpp::SctpChunkType::ASCONF, enumclass); + PTF_ASSERT_EQUAL(asconfChunk.getChunkTypeName(), "ASCONF"); + + auto asconfView = pcpp::SctpAsconfChunkView::fromChunk(asconfChunk); + PTF_ASSERT_TRUE(asconfView.isValid()); + PTF_ASSERT_EQUAL(asconfView.getSerialNumber(), 0x1234); +} + +// ==================== Chunk Creation Tests ==================== + +PTF_TEST_CASE(SctpAddDataChunkTest) +{ + // Create a new SCTP layer and add a DATA chunk + pcpp::SctpLayer sctpLayer(1234, 80, 0xDEADBEEF); + + const char* userData = "Hello SCTP!"; + size_t userDataLen = strlen(userData); + + // Add DATA chunk with all flags + PTF_ASSERT_TRUE(sctpLayer.addDataChunk(100, // TSN + 5, // Stream ID + 10, // Stream Sequence Number + 47, // PPID + reinterpret_cast(userData), userDataLen, + true, // Begin fragment + true, // End fragment + false, // Unordered + true // Immediate + )); + + // Verify chunk was added + PTF_ASSERT_EQUAL(sctpLayer.getChunkCount(), 1); + + pcpp::SctpChunk dataChunk = sctpLayer.getFirstChunk(); + PTF_ASSERT_TRUE(dataChunk.isNotNull()); + PTF_ASSERT_EQUAL(dataChunk.getChunkType(), pcpp::SctpChunkType::DATA, enumclass); + + auto dataView = pcpp::SctpDataChunkView::fromChunk(dataChunk); + PTF_ASSERT_TRUE(dataView.isValid()); + + // Verify DATA chunk fields + PTF_ASSERT_EQUAL(dataView.getTsn(), 100); + PTF_ASSERT_EQUAL(dataView.getStreamId(), 5); + PTF_ASSERT_EQUAL(dataView.getSequenceNumber(), 10); + PTF_ASSERT_EQUAL(dataView.getPpid(), 47); + + // Verify flags + PTF_ASSERT_TRUE(dataView.isBeginFragment()); + PTF_ASSERT_TRUE(dataView.isEndFragment()); + PTF_ASSERT_FALSE(dataView.isUnordered()); + PTF_ASSERT_TRUE(dataView.isImmediate()); + + // Verify user data + PTF_ASSERT_EQUAL(dataView.getUserDataLength(), userDataLen); + PTF_ASSERT_BUF_COMPARE(dataView.getUserData(), userData, userDataLen); +} + +PTF_TEST_CASE(SctpAddInitChunkTest) +{ + // Create a new SCTP layer and add an INIT chunk + pcpp::SctpLayer sctpLayer(5000, 5001, 0); + + PTF_ASSERT_TRUE(sctpLayer.addInitChunk(0xAABBCCDD, // Initiate Tag + 65536, // A-RWND + 10, // Outbound Streams + 10, // Inbound Streams + 1000 // Initial TSN + )); + + // Verify chunk was added + PTF_ASSERT_EQUAL(sctpLayer.getChunkCount(), 1); + + pcpp::SctpChunk initChunk = sctpLayer.getFirstChunk(); + PTF_ASSERT_TRUE(initChunk.isNotNull()); + PTF_ASSERT_EQUAL(initChunk.getChunkType(), pcpp::SctpChunkType::INIT, enumclass); + + auto initView = pcpp::SctpInitChunkView::fromChunk(initChunk); + PTF_ASSERT_TRUE(initView.isValid()); + + // Verify INIT chunk fields + PTF_ASSERT_EQUAL(initView.getInitiateTag(), 0xAABBCCDD); + PTF_ASSERT_EQUAL(initView.getArwnd(), 65536); + PTF_ASSERT_EQUAL(initView.getNumOutboundStreams(), 10); + PTF_ASSERT_EQUAL(initView.getNumInboundStreams(), 10); + PTF_ASSERT_EQUAL(initView.getInitialTsn(), 1000); +} + +PTF_TEST_CASE(SctpAddInitAckChunkTest) +{ + // Create a new SCTP layer and add an INIT-ACK chunk with parameters + pcpp::SctpLayer sctpLayer(5001, 5000, 0xAABBCCDD); + + // Create a simple State Cookie parameter (Type=7, Length=12, 8 bytes of cookie data) + uint8_t params[] = { + 0x00, 0x07, // Type: State Cookie (7) + 0x00, 0x0C, // Length: 12 (4 header + 8 data) + 0x43, 0x4F, 0x4F, 0x4B, // "COOK" + 0x49, 0x45, 0x21, 0x21 // "IE!!" + }; + + PTF_ASSERT_TRUE(sctpLayer.addInitAckChunk(0x11223344, // Initiate Tag + 32768, // A-RWND + 5, // Outbound Streams + 5, // Inbound Streams + 2000, // Initial TSN + params, sizeof(params))); + + // Verify chunk was added + PTF_ASSERT_EQUAL(sctpLayer.getChunkCount(), 1); + + pcpp::SctpChunk initAckChunk = sctpLayer.getFirstChunk(); + PTF_ASSERT_TRUE(initAckChunk.isNotNull()); + PTF_ASSERT_EQUAL(initAckChunk.getChunkType(), pcpp::SctpChunkType::INIT_ACK, enumclass); + + auto initAckView = pcpp::SctpInitAckChunkView::fromChunk(initAckChunk); + PTF_ASSERT_TRUE(initAckView.isValid()); + + // Verify INIT-ACK chunk fields + PTF_ASSERT_EQUAL(initAckView.getInitiateTag(), 0x11223344); + PTF_ASSERT_EQUAL(initAckView.getArwnd(), 32768); + PTF_ASSERT_EQUAL(initAckView.getNumOutboundStreams(), 5); + PTF_ASSERT_EQUAL(initAckView.getNumInboundStreams(), 5); + PTF_ASSERT_EQUAL(initAckView.getInitialTsn(), 2000); + + // Verify parameters were included + PTF_ASSERT_EQUAL(initAckView.getParametersLength(), sizeof(params)); +} + +PTF_TEST_CASE(SctpAddSackChunkTest) +{ + // Create a new SCTP layer and add a SACK chunk + pcpp::SctpLayer sctpLayer(80, 1234, 0x12345678); + + std::vector gapBlocks; + gapBlocks.push_back({ 3, 5 }); + gapBlocks.push_back({ 10, 15 }); + + std::vector dupTsns; + dupTsns.push_back(100); + dupTsns.push_back(200); + + PTF_ASSERT_TRUE(sctpLayer.addSackChunk(50, // Cumulative TSN Ack + 65535, // A-RWND + gapBlocks, dupTsns)); + + // Verify chunk was added + PTF_ASSERT_EQUAL(sctpLayer.getChunkCount(), 1); + + pcpp::SctpChunk sackChunk = sctpLayer.getFirstChunk(); + PTF_ASSERT_TRUE(sackChunk.isNotNull()); + PTF_ASSERT_EQUAL(sackChunk.getChunkType(), pcpp::SctpChunkType::SACK, enumclass); + + auto sackView = pcpp::SctpSackChunkView::fromChunk(sackChunk); + PTF_ASSERT_TRUE(sackView.isValid()); + + // Verify SACK chunk fields + PTF_ASSERT_EQUAL(sackView.getCumulativeTsnAck(), 50); + PTF_ASSERT_EQUAL(sackView.getArwnd(), 65535); + PTF_ASSERT_EQUAL(sackView.getNumGapBlocks(), 2); + PTF_ASSERT_EQUAL(sackView.getNumDupTsns(), 2); + + // Verify gap blocks + std::vector readGapBlocks = sackView.getGapBlocks(); + PTF_ASSERT_EQUAL(readGapBlocks.size(), 2); + PTF_ASSERT_EQUAL(readGapBlocks[0].start, 3); + PTF_ASSERT_EQUAL(readGapBlocks[0].end, 5); + PTF_ASSERT_EQUAL(readGapBlocks[1].start, 10); + PTF_ASSERT_EQUAL(readGapBlocks[1].end, 15); + + // Verify duplicate TSNs + std::vector readDupTsns = sackView.getDupTsns(); + PTF_ASSERT_EQUAL(readDupTsns.size(), 2); + PTF_ASSERT_EQUAL(readDupTsns[0], 100); + PTF_ASSERT_EQUAL(readDupTsns[1], 200); +} + +PTF_TEST_CASE(SctpAddHeartbeatChunkTest) +{ + // Create a new SCTP layer and add HEARTBEAT and HEARTBEAT-ACK chunks + pcpp::SctpLayer sctpLayer(1234, 5678, 0xABCDEF00); + + const char* hbInfo = "HB-INFO!"; + size_t hbInfoLen = strlen(hbInfo); + + PTF_ASSERT_TRUE(sctpLayer.addHeartbeatChunk(reinterpret_cast(hbInfo), hbInfoLen)); + + // Verify chunk was added + PTF_ASSERT_EQUAL(sctpLayer.getChunkCount(), 1); + + pcpp::SctpChunk hbChunk = sctpLayer.getFirstChunk(); + PTF_ASSERT_TRUE(hbChunk.isNotNull()); + PTF_ASSERT_EQUAL(hbChunk.getChunkType(), pcpp::SctpChunkType::HEARTBEAT, enumclass); + + // Verify heartbeat info using view + auto hbView = pcpp::SctpHeartbeatChunkView::fromChunk(hbChunk); + PTF_ASSERT_TRUE(hbView.isValid()); + PTF_ASSERT_EQUAL(hbView.getInfoLength(), hbInfoLen); + PTF_ASSERT_BUF_COMPARE(hbView.getInfo(), hbInfo, hbInfoLen); +} + +PTF_TEST_CASE(SctpAddShutdownChunksTest) +{ + // Test SHUTDOWN, SHUTDOWN-ACK, and SHUTDOWN-COMPLETE chunks + pcpp::SctpLayer sctpLayer(1234, 5678, 0x11111111); + + // Add SHUTDOWN chunk + PTF_ASSERT_TRUE(sctpLayer.addShutdownChunk(500)); + + pcpp::SctpChunk shutdownChunk = sctpLayer.getChunk(pcpp::SctpChunkType::SHUTDOWN); + PTF_ASSERT_TRUE(shutdownChunk.isNotNull()); + auto shutdownView = pcpp::SctpShutdownChunkView::fromChunk(shutdownChunk); + PTF_ASSERT_TRUE(shutdownView.isValid()); + PTF_ASSERT_EQUAL(shutdownView.getCumulativeTsnAck(), 500); + + // Create another layer for SHUTDOWN-ACK + pcpp::SctpLayer sctpLayer2(5678, 1234, 0x22222222); + PTF_ASSERT_TRUE(sctpLayer2.addShutdownAckChunk()); + + pcpp::SctpChunk shutdownAckChunk = sctpLayer2.getChunk(pcpp::SctpChunkType::SHUTDOWN_ACK); + PTF_ASSERT_TRUE(shutdownAckChunk.isNotNull()); + PTF_ASSERT_EQUAL(shutdownAckChunk.getChunkType(), pcpp::SctpChunkType::SHUTDOWN_ACK, enumclass); + + // Create another layer for SHUTDOWN-COMPLETE with T bit + pcpp::SctpLayer sctpLayer3(1234, 5678, 0x33333333); + PTF_ASSERT_TRUE(sctpLayer3.addShutdownCompleteChunk(true)); + + pcpp::SctpChunk shutdownCompleteChunk = sctpLayer3.getChunk(pcpp::SctpChunkType::SHUTDOWN_COMPLETE); + PTF_ASSERT_TRUE(shutdownCompleteChunk.isNotNull()); + // T bit is in the flags field - bit 0 indicates no TCB destroyed + PTF_ASSERT_EQUAL((shutdownCompleteChunk.getFlags() & 0x01), 0x01); +} + +PTF_TEST_CASE(SctpAddAbortChunkTest) +{ + // Create a new SCTP layer and add an ABORT chunk with error cause + pcpp::SctpLayer sctpLayer(1234, 5678, 0xDEADBEEF); + + // Error cause: User Initiated Abort (code=12) + uint8_t errorCause[] = { + 0x00, 0x0C, // Cause Code: 12 + 0x00, 0x08, // Cause Length: 8 + 0x42, 0x59, 0x45, 0x21 // "BYE!" + }; + + PTF_ASSERT_TRUE(sctpLayer.addAbortChunk(true, errorCause, sizeof(errorCause))); + + pcpp::SctpChunk abortChunk = sctpLayer.getChunk(pcpp::SctpChunkType::ABORT); + PTF_ASSERT_TRUE(abortChunk.isNotNull()); + auto abortView = pcpp::SctpAbortChunkView::fromChunk(abortChunk); + PTF_ASSERT_TRUE(abortView.isValid()); + PTF_ASSERT_TRUE(abortView.isTBitSet()); + PTF_ASSERT_EQUAL(abortView.getErrorCausesLength(), sizeof(errorCause)); +} + +PTF_TEST_CASE(SctpAddCookieChunksTest) +{ + // Test COOKIE-ECHO and COOKIE-ACK chunks + pcpp::SctpLayer sctpLayer(1234, 5678, 0xAABBCCDD); + + const char* cookie = "STATE_COOKIE_DATA"; + size_t cookieLen = strlen(cookie); + + PTF_ASSERT_TRUE(sctpLayer.addCookieEchoChunk(reinterpret_cast(cookie), cookieLen)); + + pcpp::SctpChunk cookieEchoChunk = sctpLayer.getChunk(pcpp::SctpChunkType::COOKIE_ECHO); + PTF_ASSERT_TRUE(cookieEchoChunk.isNotNull()); + auto cookieView = pcpp::SctpCookieEchoChunkView::fromChunk(cookieEchoChunk); + PTF_ASSERT_TRUE(cookieView.isValid()); + PTF_ASSERT_EQUAL(cookieView.getCookieLength(), cookieLen); + PTF_ASSERT_BUF_COMPARE(cookieView.getCookie(), cookie, cookieLen); + + // Create another layer for COOKIE-ACK + pcpp::SctpLayer sctpLayer2(5678, 1234, 0x11223344); + PTF_ASSERT_TRUE(sctpLayer2.addCookieAckChunk()); + + pcpp::SctpChunk cookieAckChunk = sctpLayer2.getChunk(pcpp::SctpChunkType::COOKIE_ACK); + PTF_ASSERT_TRUE(cookieAckChunk.isNotNull()); + PTF_ASSERT_EQUAL(cookieAckChunk.getChunkType(), pcpp::SctpChunkType::COOKIE_ACK, enumclass); +} + +PTF_TEST_CASE(SctpAddErrorChunkTest) +{ + // Create a new SCTP layer and add an ERROR chunk + pcpp::SctpLayer sctpLayer(1234, 5678, 0x12345678); + + // Error cause: Invalid Stream Identifier (code=1) + uint8_t errorCause[] = { + 0x00, 0x01, // Cause Code: 1 + 0x00, 0x08, // Cause Length: 8 + 0x00, 0x10, // Stream Identifier: 16 + 0x00, 0x00 // Reserved + }; + + PTF_ASSERT_TRUE(sctpLayer.addErrorChunk(errorCause, sizeof(errorCause))); + + pcpp::SctpChunk errorChunk = sctpLayer.getChunk(pcpp::SctpChunkType::SCTP_ERROR); + PTF_ASSERT_TRUE(errorChunk.isNotNull()); + auto errorView = pcpp::SctpErrorChunkView::fromChunk(errorChunk); + PTF_ASSERT_TRUE(errorView.isValid()); + PTF_ASSERT_EQUAL(errorView.getCausesLength(), sizeof(errorCause)); +} + +PTF_TEST_CASE(SctpMultipleChunkCreationTest) +{ + // Create a layer with multiple chunks (bundling) + pcpp::SctpLayer sctpLayer(1234, 5678, 0xFEDCBA98); + + // Add COOKIE-ACK followed by DATA + PTF_ASSERT_TRUE(sctpLayer.addCookieAckChunk()); + + const char* data = "First message"; + PTF_ASSERT_TRUE(sctpLayer.addDataChunk(1, 0, 0, 0, reinterpret_cast(data), strlen(data))); + + // Verify both chunks exist + PTF_ASSERT_EQUAL(sctpLayer.getChunkCount(), 2); + + pcpp::SctpChunk firstChunk = sctpLayer.getFirstChunk(); + PTF_ASSERT_EQUAL(firstChunk.getChunkType(), pcpp::SctpChunkType::COOKIE_ACK, enumclass); + + pcpp::SctpChunk secondChunk = sctpLayer.getNextChunk(firstChunk); + PTF_ASSERT_TRUE(secondChunk.isNotNull()); + PTF_ASSERT_EQUAL(secondChunk.getChunkType(), pcpp::SctpChunkType::DATA, enumclass); + auto dataView = pcpp::SctpDataChunkView::fromChunk(secondChunk); + PTF_ASSERT_TRUE(dataView.isValid()); + PTF_ASSERT_EQUAL(dataView.getTsn(), 1); +} + +// ==================== INIT Parameter Iterator Tests ==================== + +PTF_TEST_CASE(SctpInitParameterIteratorTest) +{ + // Create INIT chunk with multiple parameters + pcpp::SctpLayer sctpLayer(5000, 5001, 0); + + // Build parameters: + // 1. IPv4 Address (Type=5, Length=8, 4 bytes of IP) + // 2. Supported Address Types (Type=12, Length=8, 2 address types) + uint8_t params[] = { + // IPv4 Address Parameter + 0x00, 0x05, // Type: IPv4 Address (5) + 0x00, 0x08, // Length: 8 + 0xC0, 0xA8, 0x01, 0x01, // IP: 192.168.1.1 + + // Supported Address Types Parameter + 0x00, 0x0C, // Type: Supported Address Types (12) + 0x00, 0x08, // Length: 8 + 0x00, 0x05, // IPv4 (5) + 0x00, 0x06 // IPv6 (6) + }; + + PTF_ASSERT_TRUE(sctpLayer.addInitChunk(0xAABBCCDD, 65536, 10, 10, 1, params, sizeof(params))); + + pcpp::SctpChunk initChunk = sctpLayer.getFirstChunk(); + PTF_ASSERT_TRUE(initChunk.isNotNull()); + + // Create iterator + pcpp::SctpInitParameterIterator iter(initChunk); + PTF_ASSERT_TRUE(iter.isValid()); + + // First parameter: IPv4 Address + pcpp::SctpInitParameter param1 = iter.getParameter(); + PTF_ASSERT_TRUE(param1.isNotNull()); + PTF_ASSERT_EQUAL(param1.getType(), pcpp::SctpParameterType::IPV4_ADDRESS, enumclass); + PTF_ASSERT_EQUAL(param1.getLength(), 8); + PTF_ASSERT_EQUAL(param1.getTypeName(), "IPv4 Address"); + + pcpp::IPv4Address ipv4 = param1.getIPv4Address(); + PTF_ASSERT_EQUAL(ipv4.toString(), "192.168.1.1"); + + // Move to next parameter + iter.next(); + PTF_ASSERT_TRUE(iter.isValid()); + + // Second parameter: Supported Address Types + pcpp::SctpInitParameter param2 = iter.getParameter(); + PTF_ASSERT_TRUE(param2.isNotNull()); + PTF_ASSERT_EQUAL(param2.getType(), pcpp::SctpParameterType::SUPPORTED_ADDRESS_TYPES, enumclass); + PTF_ASSERT_EQUAL(param2.getTypeName(), "Supported Address Types"); + + std::vector addrTypes = param2.getSupportedAddressTypes(); + PTF_ASSERT_EQUAL(addrTypes.size(), 2); + PTF_ASSERT_EQUAL(addrTypes[0], 5); // IPv4 + PTF_ASSERT_EQUAL(addrTypes[1], 6); // IPv6 + + // No more parameters + iter.next(); + PTF_ASSERT_FALSE(iter.isValid()); +} + +PTF_TEST_CASE(SctpInitParameterIPv6Test) +{ + // Create INIT chunk with IPv6 address parameter + pcpp::SctpLayer sctpLayer(5000, 5001, 0); + + // IPv6 Address Parameter (Type=6, Length=20, 16 bytes of IP) + uint8_t params[] = { 0x00, 0x06, // Type: IPv6 Address (6) + 0x00, 0x14, // Length: 20 + 0x20, 0x01, 0x0d, 0xb8, // 2001:db8::1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; + + PTF_ASSERT_TRUE(sctpLayer.addInitChunk(0x11111111, 65536, 10, 10, 1, params, sizeof(params))); + + pcpp::SctpChunk initChunk = sctpLayer.getFirstChunk(); + pcpp::SctpInitParameterIterator iter(initChunk); + PTF_ASSERT_TRUE(iter.isValid()); + + pcpp::SctpInitParameter param = iter.getParameter(); + PTF_ASSERT_EQUAL(param.getType(), pcpp::SctpParameterType::IPV6_ADDRESS, enumclass); + PTF_ASSERT_EQUAL(param.getTypeName(), "IPv6 Address"); + + pcpp::IPv6Address ipv6 = param.getIPv6Address(); + PTF_ASSERT_EQUAL(ipv6.toString(), "2001:db8::1"); +} + +PTF_TEST_CASE(SctpChunkPaddingTest) +{ + // Test that chunks are properly padded to 4-byte boundaries + pcpp::SctpLayer sctpLayer(1234, 5678, 0x12345678); + + // Add DATA chunk with 5 bytes of data (needs 3 bytes padding) + const char* data5 = "ABCDE"; + PTF_ASSERT_TRUE(sctpLayer.addDataChunk(1, 0, 0, 0, reinterpret_cast(data5), 5)); + + // Add another DATA chunk after it to verify padding worked + const char* data4 = "WXYZ"; + PTF_ASSERT_TRUE(sctpLayer.addDataChunk(2, 0, 1, 0, reinterpret_cast(data4), 4)); + + // Verify both chunks can be read correctly + PTF_ASSERT_EQUAL(sctpLayer.getChunkCount(), 2); + + pcpp::SctpChunk chunk1 = sctpLayer.getFirstChunk(); + auto dataView1 = pcpp::SctpDataChunkView::fromChunk(chunk1); + PTF_ASSERT_TRUE(dataView1.isValid()); + PTF_ASSERT_EQUAL(dataView1.getTsn(), 1); + PTF_ASSERT_EQUAL(dataView1.getUserDataLength(), 5); + + pcpp::SctpChunk chunk2 = sctpLayer.getNextChunk(chunk1); + PTF_ASSERT_TRUE(chunk2.isNotNull()); + auto dataView2 = pcpp::SctpDataChunkView::fromChunk(chunk2); + PTF_ASSERT_TRUE(dataView2.isValid()); + PTF_ASSERT_EQUAL(dataView2.getTsn(), 2); + PTF_ASSERT_EQUAL(dataView2.getUserDataLength(), 4); +} + +// ==================== RFC 9260 Validation Tests ==================== + +PTF_TEST_CASE(SctpBundlingValidationTest) +{ + // Test bundling validation for INIT + pcpp::SctpLayer initLayer(5000, 5001, 0); + PTF_ASSERT_TRUE(initLayer.addInitChunk(0x12345678, 65536, 10, 10, 1)); + PTF_ASSERT_EQUAL(initLayer.validateBundling(), pcpp::SctpBundlingStatus::VALID, enumclass); + + // Test INIT with non-zero verification tag + pcpp::SctpLayer initLayerBadTag(5000, 5001, 0x12345678); + PTF_ASSERT_TRUE(initLayerBadTag.addInitChunk(0x12345678, 65536, 10, 10, 1)); + PTF_ASSERT_EQUAL(initLayerBadTag.validateBundling(), pcpp::SctpBundlingStatus::INIT_NONZERO_TAG, enumclass); + + // Test canAddChunk prevents bundling with INIT + PTF_ASSERT_FALSE(initLayer.canAddChunk(pcpp::SctpChunkType::DATA)); + PTF_ASSERT_FALSE(initLayer.canAddChunk(pcpp::SctpChunkType::SACK)); + + // Test DATA bundling is allowed + pcpp::SctpLayer dataLayer(1234, 80, 0xDEADBEEF); + const char* data = "test"; + PTF_ASSERT_TRUE(dataLayer.addDataChunk(1, 0, 0, 0, reinterpret_cast(data), 4)); + PTF_ASSERT_EQUAL(dataLayer.validateBundling(), pcpp::SctpBundlingStatus::VALID, enumclass); + PTF_ASSERT_TRUE(dataLayer.canAddChunk(pcpp::SctpChunkType::DATA)); + PTF_ASSERT_TRUE(dataLayer.canAddChunk(pcpp::SctpChunkType::SACK)); + PTF_ASSERT_FALSE(dataLayer.canAddChunk(pcpp::SctpChunkType::INIT)); +} + +PTF_TEST_CASE(SctpAddEcneCwrChunkTest) +{ + // Test ECNE chunk creation + pcpp::SctpLayer ecneLayer(1234, 5678, 0x12345678); + PTF_ASSERT_TRUE(ecneLayer.addEcneChunk(1000)); + + pcpp::SctpChunk ecneChunk = ecneLayer.getFirstChunk(); + PTF_ASSERT_TRUE(ecneChunk.isNotNull()); + PTF_ASSERT_EQUAL(ecneChunk.getChunkType(), pcpp::SctpChunkType::ECNE, enumclass); + auto ecneView = pcpp::SctpEcneChunkView::fromChunk(ecneChunk); + PTF_ASSERT_TRUE(ecneView.isValid()); + PTF_ASSERT_EQUAL(ecneView.getLowestTsn(), 1000); + + // Test CWR chunk creation + pcpp::SctpLayer cwrLayer(5678, 1234, 0x87654321); + PTF_ASSERT_TRUE(cwrLayer.addCwrChunk(1000)); + + pcpp::SctpChunk cwrChunk = cwrLayer.getFirstChunk(); + PTF_ASSERT_TRUE(cwrChunk.isNotNull()); + PTF_ASSERT_EQUAL(cwrChunk.getChunkType(), pcpp::SctpChunkType::CWR, enumclass); + auto cwrView = pcpp::SctpCwrChunkView::fromChunk(cwrChunk); + PTF_ASSERT_TRUE(cwrView.isValid()); + PTF_ASSERT_EQUAL(cwrView.getLowestTsn(), 1000); +} + +PTF_TEST_CASE(SctpAddForwardTsnChunkTest) +{ + // Test FORWARD-TSN chunk creation + pcpp::SctpLayer fwdLayer(1234, 5678, 0x12345678); + + std::vector streams; + streams.push_back({ 0, 5 }); + streams.push_back({ 1, 10 }); + + PTF_ASSERT_TRUE(fwdLayer.addForwardTsnChunk(100, streams)); + + pcpp::SctpChunk fwdChunk = fwdLayer.getFirstChunk(); + PTF_ASSERT_TRUE(fwdChunk.isNotNull()); + PTF_ASSERT_EQUAL(fwdChunk.getChunkType(), pcpp::SctpChunkType::FORWARD_TSN, enumclass); + + auto fwdView = pcpp::SctpForwardTsnChunkView::fromChunk(fwdChunk); + PTF_ASSERT_TRUE(fwdView.isValid()); + PTF_ASSERT_EQUAL(fwdView.getNewCumulativeTsn(), 100); + PTF_ASSERT_EQUAL(fwdView.getStreamCount(), 2); + + std::vector readStreams = fwdView.getStreams(); + PTF_ASSERT_EQUAL(readStreams.size(), 2); + PTF_ASSERT_EQUAL(readStreams[0].streamId, 0); + PTF_ASSERT_EQUAL(readStreams[0].streamSeq, 5); + PTF_ASSERT_EQUAL(readStreams[1].streamId, 1); + PTF_ASSERT_EQUAL(readStreams[1].streamSeq, 10); +} + +PTF_TEST_CASE(SctpAddIDataChunkTest) +{ + // Test I-DATA chunk creation (RFC 8260) + pcpp::SctpLayer idataLayer(1234, 5678, 0x12345678); + + const char* userData = "WebRTC data"; + PTF_ASSERT_TRUE(idataLayer.addIDataChunk(1, // TSN + 0, // Stream ID + 100, // Message ID + 51, // PPID (WebRTC String) + reinterpret_cast(userData), strlen(userData), true, true, + false, false)); + + pcpp::SctpChunk idataChunk = idataLayer.getFirstChunk(); + PTF_ASSERT_TRUE(idataChunk.isNotNull()); + PTF_ASSERT_EQUAL(idataChunk.getChunkType(), pcpp::SctpChunkType::I_DATA, enumclass); + + auto idataView = pcpp::SctpIDataChunkView::fromChunk(idataChunk); + PTF_ASSERT_TRUE(idataView.isValid()); + PTF_ASSERT_EQUAL(idataView.getTsn(), 1); + PTF_ASSERT_EQUAL(idataView.getStreamId(), 0); + PTF_ASSERT_EQUAL(idataView.getMessageId(), 100); + PTF_ASSERT_EQUAL(idataView.getPpidOrFsn(), 51); + PTF_ASSERT_TRUE(idataView.isBeginFragment()); + PTF_ASSERT_TRUE(idataView.isEndFragment()); +} + +PTF_TEST_CASE(SctpAddIForwardTsnChunkTest) +{ + // Test I-FORWARD-TSN chunk creation (RFC 8260) + pcpp::SctpLayer ifwdLayer(1234, 5678, 0x12345678); + + std::vector streams; + pcpp::sctp_iforward_tsn_stream s1 = { 0, 0, 50 }; // Stream 0, MID 50 + pcpp::sctp_iforward_tsn_stream s2 = { 1, 1, 25 }; // Stream 1, unordered, MID 25 + streams.push_back(s1); + streams.push_back(s2); + + PTF_ASSERT_TRUE(ifwdLayer.addIForwardTsnChunk(200, streams)); + + pcpp::SctpChunk ifwdChunk = ifwdLayer.getFirstChunk(); + PTF_ASSERT_TRUE(ifwdChunk.isNotNull()); + PTF_ASSERT_EQUAL(ifwdChunk.getChunkType(), pcpp::SctpChunkType::I_FORWARD_TSN, enumclass); + + auto ifwdView = pcpp::SctpIForwardTsnChunkView::fromChunk(ifwdChunk); + PTF_ASSERT_TRUE(ifwdView.isValid()); + PTF_ASSERT_EQUAL(ifwdView.getNewCumulativeTsn(), 200); + PTF_ASSERT_EQUAL(ifwdView.getStreamCount(), 2); +} + +PTF_TEST_CASE(SctpAddPadChunkTest) +{ + // Test PAD chunk creation (RFC 4820) + pcpp::SctpLayer padLayer(1234, 5678, 0x12345678); + + PTF_ASSERT_TRUE(padLayer.addPadChunk(100)); + + pcpp::SctpChunk padChunk = padLayer.getFirstChunk(); + PTF_ASSERT_TRUE(padChunk.isNotNull()); + PTF_ASSERT_EQUAL(padChunk.getChunkType(), pcpp::SctpChunkType::PAD, enumclass); + PTF_ASSERT_EQUAL(padChunk.getLength(), 104); // 4 header + 100 padding +} + +PTF_TEST_CASE(SctpErrorCauseIteratorTest) +{ + // Create ABORT chunk with multiple error causes + pcpp::SctpLayer abortLayer(1234, 5678, 0x12345678); + + // Build error causes: + // 1. Invalid Stream Identifier (code=1) + // 2. User Initiated Abort (code=12) + uint8_t errorCauses[] = { + // Invalid Stream Identifier + 0x00, 0x01, // Code: 1 + 0x00, 0x08, // Length: 8 + 0x00, 0x10, // Stream ID: 16 + 0x00, 0x00, // Reserved + + // User Initiated Abort + 0x00, 0x0C, // Code: 12 + 0x00, 0x08, // Length: 8 + 0x42, 0x59, 0x45, 0x21 // "BYE!" + }; + + PTF_ASSERT_TRUE(abortLayer.addAbortChunk(false, errorCauses, sizeof(errorCauses))); + + pcpp::SctpChunk abortChunk = abortLayer.getFirstChunk(); + pcpp::SctpErrorCauseIterator iter(abortChunk); + PTF_ASSERT_TRUE(iter.isValid()); + + // First cause: Invalid Stream Identifier + pcpp::SctpErrorCause cause1 = iter.getErrorCause(); + PTF_ASSERT_TRUE(cause1.isNotNull()); + PTF_ASSERT_EQUAL(cause1.getCode(), pcpp::SctpErrorCauseCode::INVALID_STREAM_ID, enumclass); + PTF_ASSERT_EQUAL(cause1.getCodeName(), "Invalid Stream Identifier"); + PTF_ASSERT_EQUAL(cause1.getInvalidStreamId(), 16); + + // Second cause: User Initiated Abort + iter.next(); + PTF_ASSERT_TRUE(iter.isValid()); + + pcpp::SctpErrorCause cause2 = iter.getErrorCause(); + PTF_ASSERT_EQUAL(cause2.getCode(), pcpp::SctpErrorCauseCode::USER_INITIATED_ABORT, enumclass); + + // No more causes + iter.next(); + PTF_ASSERT_FALSE(iter.isValid()); +} + +PTF_TEST_CASE(SctpStateCookieParameterTest) +{ + // Create INIT-ACK chunk with State Cookie parameter + pcpp::SctpLayer initAckLayer(5001, 5000, 0x12345678); + + // Build State Cookie parameter (Type=7) + uint8_t params[] = { + 0x00, 0x07, // Type: State Cookie (7) + 0x00, 0x10, // Length: 16 (4 header + 12 cookie) + 0x53, 0x54, 0x41, 0x54, // "STAT" + 0x45, 0x43, 0x4F, 0x4F, // "ECOO" + 0x4B, 0x49, 0x45, 0x21 // "KIE!" + }; + + PTF_ASSERT_TRUE(initAckLayer.addInitAckChunk(0xAABBCCDD, 65536, 10, 10, 1, params, sizeof(params))); + + pcpp::SctpChunk initAckChunk = initAckLayer.getFirstChunk(); + pcpp::SctpInitParameterIterator iter(initAckChunk); + PTF_ASSERT_TRUE(iter.isValid()); + + pcpp::SctpInitParameter param = iter.getParameter(); + PTF_ASSERT_EQUAL(param.getType(), pcpp::SctpParameterType::STATE_COOKIE, enumclass); + PTF_ASSERT_EQUAL(param.getTypeName(), "State Cookie"); + PTF_ASSERT_NOT_NULL(param.getStateCookie()); + PTF_ASSERT_EQUAL(param.getStateCookieLength(), 12); +} + +PTF_TEST_CASE(SctpHostNameAddressDetectionTest) +{ + // Create INIT chunk with deprecated Host Name Address parameter + pcpp::SctpLayer initLayer(5000, 5001, 0); + + // Host Name Address parameter (Type=11, deprecated per RFC 9260) + uint8_t params[] = { + 0x00, 0x0B, // Type: Host Name Address (11) + 0x00, 0x10, // Length: 16 + 0x74, 0x65, 0x73, 0x74, // "test" + 0x2E, 0x6C, 0x6F, 0x63, // ".loc" + 0x61, 0x6C, 0x00, 0x00 // "al\0\0" (null-terminated + padding) + }; + + PTF_ASSERT_TRUE(initLayer.addInitChunk(0x12345678, 65536, 10, 10, 1, params, sizeof(params))); + + // Verify containsHostNameAddress detects it + PTF_ASSERT_TRUE(initLayer.containsHostNameAddress()); + + // Verify parameter iterator identifies it as deprecated + pcpp::SctpChunk initChunk = initLayer.getFirstChunk(); + pcpp::SctpInitParameterIterator iter(initChunk); + PTF_ASSERT_TRUE(iter.isValid()); + + pcpp::SctpInitParameter param = iter.getParameter(); + PTF_ASSERT_TRUE(param.isHostNameAddress()); + PTF_ASSERT_EQUAL(param.getTypeName(), "Host Name Address (DEPRECATED)"); +} + +PTF_TEST_CASE(SctpAuthParametersTest) +{ + // Create INIT chunk with AUTH parameters (RFC 4895) + pcpp::SctpLayer initLayer(5000, 5001, 0); + + // Build parameters: + // 1. Random (32 bytes) + // 2. Chunk List + // 3. Requested HMAC Algorithm + uint8_t params[] = { + // Random parameter (Type=0x8002) + 0x80, 0x02, // Type: Random + 0x00, 0x24, // Length: 36 (4 + 32 bytes of random) + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, + + // Chunk List parameter (Type=0x8003) + 0x80, 0x03, // Type: Chunk List + 0x00, 0x06, // Length: 6 + 0x00, 0x0F, // DATA, AUTH chunks + 0x00, 0x00, // Padding + + // HMAC Algorithm parameter (Type=0x8004) + 0x80, 0x04, // Type: Requested HMAC Algorithm + 0x00, 0x06, // Length: 6 + 0x00, 0x01, // SHA-1 (1) + 0x00, 0x00 // Padding + }; + + PTF_ASSERT_TRUE(initLayer.addInitChunk(0x12345678, 65536, 10, 10, 1, params, sizeof(params))); + + pcpp::SctpChunk initChunk = initLayer.getFirstChunk(); + pcpp::SctpInitParameterIterator iter(initChunk); + + // First: Random + PTF_ASSERT_TRUE(iter.isValid()); + pcpp::SctpInitParameter randomParam = iter.getParameter(); + PTF_ASSERT_EQUAL(randomParam.getType(), pcpp::SctpParameterType::RANDOM, enumclass); + PTF_ASSERT_NOT_NULL(randomParam.getRandomData()); + PTF_ASSERT_EQUAL(randomParam.getRandomDataLength(), 32); + + // Second: Chunk List + iter.next(); + PTF_ASSERT_TRUE(iter.isValid()); + pcpp::SctpInitParameter chunkListParam = iter.getParameter(); + PTF_ASSERT_EQUAL(chunkListParam.getType(), pcpp::SctpParameterType::CHUNK_LIST, enumclass); + std::vector chunkList = chunkListParam.getChunkList(); + PTF_ASSERT_EQUAL(chunkList.size(), 2); + PTF_ASSERT_EQUAL(chunkList[0], 0); // DATA + PTF_ASSERT_EQUAL(chunkList[1], 15); // AUTH + + // Third: HMAC Algorithm + iter.next(); + PTF_ASSERT_TRUE(iter.isValid()); + pcpp::SctpInitParameter hmacParam = iter.getParameter(); + PTF_ASSERT_EQUAL(hmacParam.getType(), pcpp::SctpParameterType::REQUESTED_HMAC_ALGO, enumclass); + std::vector hmacAlgos = hmacParam.getRequestedHmacAlgorithms(); + PTF_ASSERT_EQUAL(hmacAlgos.size(), 1); + PTF_ASSERT_EQUAL(hmacAlgos[0], 1); // SHA-1 +} + +PTF_TEST_CASE(SctpAddAuthChunkTest) +{ + // Test AUTH chunk creation (RFC 4895) + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + // Create a dummy HMAC (20 bytes for SHA-1) + uint8_t hmac[20] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, + 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14 }; + + PTF_ASSERT_TRUE(sctpLayer.addAuthChunk(1, 1, hmac, sizeof(hmac))); + + // Verify chunk + pcpp::SctpChunk authChunk = sctpLayer.getFirstChunk(); + PTF_ASSERT_TRUE(authChunk.isNotNull()); + PTF_ASSERT_EQUAL(authChunk.getChunkType(), pcpp::SctpChunkType::AUTH, enumclass); + PTF_ASSERT_EQUAL(authChunk.getLength(), 28); // 8 byte header + 20 byte HMAC + + // Use view for AUTH chunk access + auto authView = pcpp::SctpAuthChunkView::fromChunk(authChunk); + PTF_ASSERT_TRUE(authView.isValid()); + + // Verify AUTH chunk fields + PTF_ASSERT_EQUAL(authView.getSharedKeyId(), 1); + PTF_ASSERT_EQUAL(authView.getHmacId(), 1); // SHA-1 + PTF_ASSERT_NOT_NULL(authView.getHmacData()); + PTF_ASSERT_EQUAL(authView.getHmacLength(), 20); + + // Verify HMAC content + const uint8_t* hmacData = authView.getHmacData(); + PTF_ASSERT_BUF_COMPARE(hmacData, hmac, 20); +} + +PTF_TEST_CASE(SctpAddAsconfChunkTest) +{ + // Test ASCONF chunk creation (RFC 5061) + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + // Build mandatory Address Parameter (IPv4) + uint8_t addressParam[] = { + 0x00, 0x05, // Type: IPv4 Address + 0x00, 0x08, // Length: 8 + 0xC0, 0xA8, 0x01, 0x01 // 192.168.1.1 + }; + + // Build ASCONF parameter (Add IP) + uint8_t asconfParam[] = { + 0xC0, 0x01, // Type: Add IP Address (0xC001) + 0x00, 0x10, // Length: 16 + 0x00, 0x00, 0x00, 0x01, // Correlation ID: 1 + 0x00, 0x05, // Type: IPv4 Address + 0x00, 0x08, // Length: 8 + 0xC0, 0xA8, 0x02, 0x01 // 192.168.2.1 + }; + + PTF_ASSERT_TRUE( + sctpLayer.addAsconfChunk(12345, addressParam, sizeof(addressParam), asconfParam, sizeof(asconfParam))); + + // Verify chunk + pcpp::SctpChunk asconfChunk = sctpLayer.getFirstChunk(); + PTF_ASSERT_TRUE(asconfChunk.isNotNull()); + PTF_ASSERT_EQUAL(asconfChunk.getChunkType(), pcpp::SctpChunkType::ASCONF, enumclass); + + // Use view for ASCONF chunk access + auto asconfView = pcpp::SctpAsconfChunkView::fromChunk(asconfChunk); + PTF_ASSERT_TRUE(asconfView.isValid()); + PTF_ASSERT_EQUAL(asconfView.getSerialNumber(), 12345); +} + +PTF_TEST_CASE(SctpAddAsconfAckChunkTest) +{ + // Test ASCONF-ACK chunk creation (RFC 5061) + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + // Build Success Indication response + uint8_t responseParam[] = { + 0xC0, 0x05, // Type: Success Indication (0xC005) + 0x00, 0x08, // Length: 8 + 0x00, 0x00, 0x00, 0x01 // Correlation ID: 1 + }; + + PTF_ASSERT_TRUE(sctpLayer.addAsconfAckChunk(12345, responseParam, sizeof(responseParam))); + + // Verify chunk + pcpp::SctpChunk asconfAckChunk = sctpLayer.getFirstChunk(); + PTF_ASSERT_TRUE(asconfAckChunk.isNotNull()); + PTF_ASSERT_EQUAL(asconfAckChunk.getChunkType(), pcpp::SctpChunkType::ASCONF_ACK, enumclass); + + // Use view for ASCONF-ACK chunk access + auto asconfAckView = pcpp::SctpAsconfAckChunkView::fromChunk(asconfAckChunk); + PTF_ASSERT_TRUE(asconfAckView.isValid()); + PTF_ASSERT_EQUAL(asconfAckView.getSerialNumber(), 12345); +} + +PTF_TEST_CASE(SctpAddReconfigChunkTest) +{ + // Test RE-CONFIG chunk creation (RFC 6525) + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + // Build Outgoing SSN Reset Request parameter + uint8_t reconfigParam[] = { + 0x00, 0x0D, // Type: Outgoing SSN Reset Request (13) + 0x00, 0x14, // Length: 20 + 0x00, 0x00, 0x00, 0x01, // Request Sequence Number: 1 + 0x00, 0x00, 0x00, 0x00, // Response Sequence Number: 0 + 0x00, 0x00, 0x00, 0x64, // Last TSN: 100 + 0x00, 0x00, // Stream 0 + 0x00, 0x01 // Stream 1 + }; + + PTF_ASSERT_TRUE(sctpLayer.addReconfigChunk(reconfigParam, sizeof(reconfigParam))); + + // Verify chunk + pcpp::SctpChunk reconfigChunk = sctpLayer.getFirstChunk(); + PTF_ASSERT_TRUE(reconfigChunk.isNotNull()); + PTF_ASSERT_EQUAL(reconfigChunk.getChunkType(), pcpp::SctpChunkType::RE_CONFIG, enumclass); +} + +PTF_TEST_CASE(SctpReconfigParameterIteratorTest) +{ + // Create RE-CONFIG chunk with parameters + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + // Build Outgoing SSN Reset Request with 2 streams + uint8_t reconfigParam[] = { + 0x00, 0x0D, // Type: Outgoing SSN Reset Request (13) + 0x00, 0x14, // Length: 20 + 0x00, 0x00, 0x00, 0x0A, // Request Sequence Number: 10 + 0x00, 0x00, 0x00, 0x05, // Response Sequence Number: 5 + 0x00, 0x00, 0x01, 0x00, // Last TSN: 256 + 0x00, 0x00, // Stream 0 + 0x00, 0x01 // Stream 1 + }; + + PTF_ASSERT_TRUE(sctpLayer.addReconfigChunk(reconfigParam, sizeof(reconfigParam))); + + // Test iterator + pcpp::SctpChunk reconfigChunk = sctpLayer.getFirstChunk(); + pcpp::SctpReconfigParameterIterator iter(reconfigChunk); + + PTF_ASSERT_TRUE(iter.isValid()); + pcpp::SctpReconfigParameter param = iter.getParameter(); + + PTF_ASSERT_EQUAL(param.getType(), pcpp::SctpParameterType::OUTGOING_SSN_RESET_REQ, enumclass); + PTF_ASSERT_EQUAL(param.getOutgoingReqSeqNum(), 10); + PTF_ASSERT_EQUAL(param.getOutgoingRespSeqNum(), 5); + PTF_ASSERT_EQUAL(param.getOutgoingLastTsn(), 256); + + std::vector streams = param.getResetStreamNumbers(); + PTF_ASSERT_EQUAL(streams.size(), 2); + PTF_ASSERT_EQUAL(streams[0], 0); + PTF_ASSERT_EQUAL(streams[1], 1); + + // No more parameters + iter.next(); + PTF_ASSERT_FALSE(iter.isValid()); +} + +PTF_TEST_CASE(SctpReconfigResponseTest) +{ + // Test RE-CONFIG Response parameter with optional TSN fields + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + // Build Re-configuration Response with optional TSNs + uint8_t reconfigParam[] = { + 0x00, 0x10, // Type: Re-configuration Response (16) + 0x00, 0x14, // Length: 20 (with optional fields) + 0x00, 0x00, 0x00, 0x0A, // Response Sequence Number: 10 + 0x00, 0x00, 0x00, 0x01, // Result: Success - Performed + 0x00, 0x00, 0x02, 0x00, // Sender's Next TSN: 512 + 0x00, 0x00, 0x03, 0x00 // Receiver's Next TSN: 768 + }; + + PTF_ASSERT_TRUE(sctpLayer.addReconfigChunk(reconfigParam, sizeof(reconfigParam))); + + pcpp::SctpChunk reconfigChunk = sctpLayer.getFirstChunk(); + pcpp::SctpReconfigParameterIterator iter(reconfigChunk); + + PTF_ASSERT_TRUE(iter.isValid()); + pcpp::SctpReconfigParameter param = iter.getParameter(); + + PTF_ASSERT_EQUAL(param.getType(), pcpp::SctpParameterType::RECONFIG_RESPONSE, enumclass); + PTF_ASSERT_EQUAL(param.getReconfigRespSeqNum(), 10); + PTF_ASSERT_EQUAL(param.getReconfigResult(), pcpp::SctpReconfigResult::SUCCESS_PERFORMED, enumclass); + PTF_ASSERT_TRUE(param.hasReconfigOptionalTsn()); + PTF_ASSERT_EQUAL(param.getReconfigSenderNextTsn(), 512); + PTF_ASSERT_EQUAL(param.getReconfigReceiverNextTsn(), 768); +} + +PTF_TEST_CASE(SctpAsconfParameterIteratorTest) +{ + // Create ASCONF chunk with parameters + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + // Build mandatory Address Parameter + Add IP parameter + uint8_t addressParam[] = { + 0x00, 0x05, // Type: IPv4 Address + 0x00, 0x08, // Length: 8 + 0xC0, 0xA8, 0x01, 0x01 // 192.168.1.1 + }; + + uint8_t asconfParam[] = { + 0xC0, 0x01, // Type: Add IP Address (0xC001) + 0x00, 0x10, // Length: 16 + 0x00, 0x00, 0x00, 0x42, // Correlation ID: 66 + 0x00, 0x05, // Type: IPv4 Address + 0x00, 0x08, // Length: 8 + 0xAC, 0x10, 0x00, 0x01 // 172.16.0.1 + }; + + PTF_ASSERT_TRUE( + sctpLayer.addAsconfChunk(999, addressParam, sizeof(addressParam), asconfParam, sizeof(asconfParam))); + + // Test iterator (skips Address Parameter by default) + pcpp::SctpChunk asconfChunk = sctpLayer.getFirstChunk(); + pcpp::SctpAsconfParameterIterator iter(asconfChunk); + + PTF_ASSERT_TRUE(iter.isValid()); + pcpp::SctpAsconfParameter param = iter.getParameter(); + + PTF_ASSERT_EQUAL(param.getType(), pcpp::SctpParameterType::ADD_IP_ADDRESS, enumclass); + PTF_ASSERT_EQUAL(param.getCorrelationId(), 66); + PTF_ASSERT_NOT_NULL(param.getAddressParameter()); + + // Test address extraction + pcpp::IPv4Address ipv4 = param.getIPv4Address(); + PTF_ASSERT_EQUAL(ipv4.toString(), "172.16.0.1"); + + // No more parameters + iter.next(); + PTF_ASSERT_FALSE(iter.isValid()); +} + +PTF_TEST_CASE(SctpZeroChecksumParameterTest) +{ + // Test Zero Checksum Acceptable parameter (RFC 9653) + pcpp::SctpLayer initLayer(5000, 5001, 0); + + // Build Zero Checksum Acceptable parameter + uint8_t params[] = { + 0x80, 0x01, // Type: Zero Checksum Acceptable (0x8001) + 0x00, 0x08, // Length: 8 + 0x00, 0x00, 0x00, 0x01 // EDMID: 1 (DTLS) + }; + + PTF_ASSERT_TRUE(initLayer.addInitChunk(0x12345678, 65536, 10, 10, 1, params, sizeof(params))); + + pcpp::SctpChunk initChunk = initLayer.getFirstChunk(); + pcpp::SctpInitParameterIterator iter(initChunk); + + PTF_ASSERT_TRUE(iter.isValid()); + pcpp::SctpInitParameter param = iter.getParameter(); + + PTF_ASSERT_EQUAL(param.getType(), pcpp::SctpParameterType::ZERO_CHECKSUM_ACCEPTABLE, enumclass); + PTF_ASSERT_EQUAL(param.getZeroChecksumEdmid(), 1); // DTLS +} + +PTF_TEST_CASE(SctpAdditionalErrorCauseAccessorsTest) +{ + // Test additional error cause accessors + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + // Build Missing Mandatory Parameter error cause + uint8_t missingParamCause[] = { + 0x00, 0x02, // Cause Code: Missing Mandatory Parameter + 0x00, 0x0C, // Length: 12 + 0x00, 0x00, 0x00, 0x02, // Number of missing params: 2 + 0x00, 0x05, // Type: IPv4 Address + 0x00, 0x07 // Type: State Cookie + }; + + PTF_ASSERT_TRUE(sctpLayer.addErrorChunk(missingParamCause, sizeof(missingParamCause))); + + pcpp::SctpChunk errorChunk = sctpLayer.getFirstChunk(); + pcpp::SctpErrorCauseIterator iter(errorChunk); + + PTF_ASSERT_TRUE(iter.isValid()); + pcpp::SctpErrorCause cause = iter.getErrorCause(); + + PTF_ASSERT_EQUAL(cause.getCode(), pcpp::SctpErrorCauseCode::MISSING_MANDATORY_PARAM, enumclass); + + std::vector missingParams = cause.getMissingMandatoryParams(); + PTF_ASSERT_EQUAL(missingParams.size(), 2); + PTF_ASSERT_EQUAL(missingParams[0], 5); // IPv4 Address + PTF_ASSERT_EQUAL(missingParams[1], 7); // State Cookie +} + +PTF_TEST_CASE(SctpUnrecognizedChunkErrorTest) +{ + // Test Unrecognized Chunk Type error cause + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + // Build Unrecognized Chunk Type error cause with embedded chunk + uint8_t unrecognizedChunkCause[] = { + 0x00, 0x06, // Cause Code: Unrecognized Chunk Type + 0x00, 0x0C, // Length: 12 + // Embedded unrecognized chunk + 0xFF, // Unknown chunk type + 0x00, // Flags + 0x00, 0x08, // Length + 0xDE, 0xAD, 0xBE, 0xEF // Data + }; + + PTF_ASSERT_TRUE(sctpLayer.addErrorChunk(unrecognizedChunkCause, sizeof(unrecognizedChunkCause))); + + pcpp::SctpChunk errorChunk = sctpLayer.getFirstChunk(); + pcpp::SctpErrorCauseIterator iter(errorChunk); + + PTF_ASSERT_TRUE(iter.isValid()); + pcpp::SctpErrorCause cause = iter.getErrorCause(); + + PTF_ASSERT_EQUAL(cause.getCode(), pcpp::SctpErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE, enumclass); + PTF_ASSERT_NOT_NULL(cause.getUnrecognizedChunk()); + PTF_ASSERT_EQUAL(cause.getUnrecognizedChunkLength(), 8); + + // Verify the unrecognized chunk content + const uint8_t* chunk = cause.getUnrecognizedChunk(); + PTF_ASSERT_EQUAL(chunk[0], 0xFF); // Unknown type +} + +PTF_TEST_CASE(SctpExtendedPpidEnumsTest) +{ + // Test extended PPID definitions + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::H248), 7); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::S1AP), 18); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::X2AP), 27); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::XNAP), 61); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::F1AP), 62); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::E1AP), 64); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::W1AP), 72); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::WEBRTC_BINARY_PARTIAL), 52); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::WEBRTC_STRING_PARTIAL), 54); +} + +PTF_TEST_CASE(SctpReconfigResultEnumsTest) +{ + // Test RE-CONFIG result code definitions (RFC 6525) + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpReconfigResult::SUCCESS_NOTHING_TO_DO), 0); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpReconfigResult::SUCCESS_PERFORMED), 1); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpReconfigResult::DENIED), 2); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpReconfigResult::ERROR_WRONG_SSN), 3); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpReconfigResult::ERROR_REQUEST_IN_PROGRESS), 4); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpReconfigResult::ERROR_BAD_SEQUENCE_NUMBER), 5); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpReconfigResult::IN_PROGRESS), 6); +} + +PTF_TEST_CASE(SctpEdmidEnumsTest) +{ + // Test EDMID definitions (RFC 9653) + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpEdmid::RESERVED), 0); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpEdmid::DTLS), 1); +} + +PTF_TEST_CASE(SctpNrSackChunkParsingTest) +{ + timeval time; + gettimeofday(&time, nullptr); + + // Create SCTP packet with NR-SACK chunk (Type 16) + // NR-SACK format: 20-byte header + gap blocks + NR gap blocks + dup TSNs + uint8_t sctpNrSackPacket[] = { // Ethernet header (14 bytes) + 0x00, 0x0c, 0x29, 0x3e, 0x50, 0x4f, 0x00, 0x50, 0x56, 0xc0, 0x00, 0x08, 0x08, 0x00, + + // IPv4 header (20 bytes) + 0x45, 0x00, 0x00, 0x48, // Total Length: 72 + 0x00, 0x01, 0x00, 0x00, 0x40, 0x84, 0x00, 0x00, 0xc0, 0xa8, 0x01, 0x64, 0xc0, 0xa8, + 0x01, 0x65, + + // SCTP common header (12 bytes) + 0x13, 0x88, 0x13, 0x89, // Ports: 5000 -> 5001 + 0xab, 0xcd, 0xef, 0x01, // Verification tag + 0x00, 0x00, 0x00, 0x00, // Checksum placeholder + + // NR-SACK chunk (32 bytes: 20 header + 4 gap + 4 nr-gap + 4 dup) + 0x10, // Type: NR-SACK (16) + 0x01, // Flags: A=1 (all non-renegable) + 0x00, 0x20, // Length: 32 + 0x00, 0x00, 0x00, 0x64, // Cumulative TSN Ack: 100 + 0x00, 0x01, 0x00, 0x00, // ARWND: 65536 + 0x00, 0x01, // Num Gap Blocks: 1 + 0x00, 0x01, // Num NR Gap Blocks: 1 + 0x00, 0x01, // Num Dup TSNs: 1 + 0x00, 0x00, // Reserved + // Gap Ack Block: start=5, end=8 + 0x00, 0x05, // Start: 5 + 0x00, 0x08, // End: 8 + // NR Gap Ack Block: start=10, end=12 + 0x00, 0x0A, // Start: 10 + 0x00, 0x0C, // End: 12 + // Duplicate TSN: 50 + 0x00, 0x00, 0x00, 0x32 + }; + + // Calculate CRC32c checksum + size_t sctpOffset = 14 + 20; + size_t sctpLen = sizeof(sctpNrSackPacket) - sctpOffset; + + uint32_t crc = pcpp::calculateSctpCrc32c(sctpNrSackPacket + sctpOffset, sctpLen); + sctpNrSackPacket[sctpOffset + 8] = (crc >> 24) & 0xFF; + sctpNrSackPacket[sctpOffset + 9] = (crc >> 16) & 0xFF; + sctpNrSackPacket[sctpOffset + 10] = (crc >> 8) & 0xFF; + sctpNrSackPacket[sctpOffset + 11] = (crc >> 0) & 0xFF; + + pcpp::RawPacket rawPacket(sctpNrSackPacket, sizeof(sctpNrSackPacket), time, false); + pcpp::Packet sctpPacket(&rawPacket); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + PTF_ASSERT_TRUE(sctpLayer->isChecksumValid()); + + // Get NR-SACK chunk + pcpp::SctpChunk nrSackChunk = sctpLayer->getChunk(pcpp::SctpChunkType::NR_SACK); + PTF_ASSERT_TRUE(nrSackChunk.isNotNull()); + PTF_ASSERT_EQUAL(nrSackChunk.getChunkType(), pcpp::SctpChunkType::NR_SACK, enumclass); + PTF_ASSERT_EQUAL(nrSackChunk.getChunkTypeAsInt(), 16); + + // Use view for NR-SACK access + auto nrSackView = pcpp::SctpNrSackChunkView::fromChunk(nrSackChunk); + PTF_ASSERT_TRUE(nrSackView.isValid()); + + // Test NR-SACK fields + PTF_ASSERT_EQUAL(nrSackView.getCumulativeTsnAck(), 100); + PTF_ASSERT_EQUAL(nrSackView.getArwnd(), 65536); + PTF_ASSERT_EQUAL(nrSackView.getNumGapBlocks(), 1); + PTF_ASSERT_EQUAL(nrSackView.getNumNrGapBlocks(), 1); + PTF_ASSERT_EQUAL(nrSackView.getNumDupTsns(), 1); + PTF_ASSERT_TRUE(nrSackView.isAllNonRenegable()); + + // Test gap blocks + std::vector gapBlocks = nrSackView.getGapBlocks(); + PTF_ASSERT_EQUAL(gapBlocks.size(), 1); + PTF_ASSERT_EQUAL(gapBlocks[0].start, 5); + PTF_ASSERT_EQUAL(gapBlocks[0].end, 8); + + // Test NR gap blocks + std::vector nrGapBlocks = nrSackView.getNrGapBlocks(); + PTF_ASSERT_EQUAL(nrGapBlocks.size(), 1); + PTF_ASSERT_EQUAL(nrGapBlocks[0].start, 10); + PTF_ASSERT_EQUAL(nrGapBlocks[0].end, 12); + + // Test duplicate TSNs + std::vector dupTsns = nrSackView.getDupTsns(); + PTF_ASSERT_EQUAL(dupTsns.size(), 1); + PTF_ASSERT_EQUAL(dupTsns[0], 50); +} + +PTF_TEST_CASE(SctpAddNrSackChunkTest) +{ + // Test NR-SACK chunk creation + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + // Create gap blocks + std::vector gapBlocks; + pcpp::sctp_gap_ack_block gap1 = { 3, 5 }; + pcpp::sctp_gap_ack_block gap2 = { 10, 15 }; + gapBlocks.push_back(gap1); + gapBlocks.push_back(gap2); + + // Create NR gap blocks + std::vector nrGapBlocks; + pcpp::sctp_gap_ack_block nrGap1 = { 20, 25 }; + nrGapBlocks.push_back(nrGap1); + + // Create duplicate TSNs + std::vector dupTsns; + dupTsns.push_back(1000); + dupTsns.push_back(2000); + + // Add NR-SACK chunk with all non-renegable flag + PTF_ASSERT_TRUE(sctpLayer.addNrSackChunk(500, 131072, gapBlocks, nrGapBlocks, dupTsns, true)); + + // Verify chunk + pcpp::SctpChunk nrSackChunk = sctpLayer.getFirstChunk(); + PTF_ASSERT_TRUE(nrSackChunk.isNotNull()); + PTF_ASSERT_EQUAL(nrSackChunk.getChunkType(), pcpp::SctpChunkType::NR_SACK, enumclass); + + // Use view for NR-SACK access + auto nrSackView = pcpp::SctpNrSackChunkView::fromChunk(nrSackChunk); + PTF_ASSERT_TRUE(nrSackView.isValid()); + + // Verify fields + PTF_ASSERT_EQUAL(nrSackView.getCumulativeTsnAck(), 500); + PTF_ASSERT_EQUAL(nrSackView.getArwnd(), 131072); + PTF_ASSERT_EQUAL(nrSackView.getNumGapBlocks(), 2); + PTF_ASSERT_EQUAL(nrSackView.getNumNrGapBlocks(), 1); + PTF_ASSERT_EQUAL(nrSackView.getNumDupTsns(), 2); + PTF_ASSERT_TRUE(nrSackView.isAllNonRenegable()); + + // Verify gap blocks + std::vector retrievedGaps = nrSackView.getGapBlocks(); + PTF_ASSERT_EQUAL(retrievedGaps.size(), 2); + PTF_ASSERT_EQUAL(retrievedGaps[0].start, 3); + PTF_ASSERT_EQUAL(retrievedGaps[0].end, 5); + PTF_ASSERT_EQUAL(retrievedGaps[1].start, 10); + PTF_ASSERT_EQUAL(retrievedGaps[1].end, 15); + + // Verify NR gap blocks + std::vector retrievedNrGaps = nrSackView.getNrGapBlocks(); + PTF_ASSERT_EQUAL(retrievedNrGaps.size(), 1); + PTF_ASSERT_EQUAL(retrievedNrGaps[0].start, 20); + PTF_ASSERT_EQUAL(retrievedNrGaps[0].end, 25); + + // Verify duplicate TSNs + std::vector retrievedDups = nrSackView.getDupTsns(); + PTF_ASSERT_EQUAL(retrievedDups.size(), 2); + PTF_ASSERT_EQUAL(retrievedDups[0], 1000); + PTF_ASSERT_EQUAL(retrievedDups[1], 2000); +} + +PTF_TEST_CASE(SctpAddNrSackChunkMinimalTest) +{ + // Test NR-SACK chunk creation with no optional fields + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + // Add minimal NR-SACK chunk + PTF_ASSERT_TRUE(sctpLayer.addNrSackChunk(100, 65536)); + + // Verify chunk + pcpp::SctpChunk nrSackChunk = sctpLayer.getFirstChunk(); + PTF_ASSERT_TRUE(nrSackChunk.isNotNull()); + PTF_ASSERT_EQUAL(nrSackChunk.getChunkType(), pcpp::SctpChunkType::NR_SACK, enumclass); + PTF_ASSERT_EQUAL(nrSackChunk.getLength(), 20); // Just header, no blocks + + // Use view for NR-SACK access + auto nrSackView = pcpp::SctpNrSackChunkView::fromChunk(nrSackChunk); + PTF_ASSERT_TRUE(nrSackView.isValid()); + + // Verify fields + PTF_ASSERT_EQUAL(nrSackView.getCumulativeTsnAck(), 100); + PTF_ASSERT_EQUAL(nrSackView.getArwnd(), 65536); + PTF_ASSERT_EQUAL(nrSackView.getNumGapBlocks(), 0); + PTF_ASSERT_EQUAL(nrSackView.getNumNrGapBlocks(), 0); + PTF_ASSERT_EQUAL(nrSackView.getNumDupTsns(), 0); + PTF_ASSERT_FALSE(nrSackView.isAllNonRenegable()); +} + +PTF_TEST_CASE(SctpHmacSha1ComputationTest) +{ + // Test HMAC-SHA1 computation with RFC 2202 test vectors + // Test Case 1: key = 0x0b repeated 20 times, data = "Hi There" + uint8_t key1[20]; + memset(key1, 0x0B, 20); + const uint8_t data1[] = "Hi There"; + uint8_t hmac1[20]; + + PTF_ASSERT_TRUE(pcpp::calculateSctpHmacSha1(key1, 20, data1, 8, hmac1)); + + // Expected HMAC-SHA1 from RFC 2202: 0xb617318655057264e28bc0b6fb378c8ef146be00 + uint8_t expected1[] = { 0xb6, 0x17, 0x31, 0x86, 0x55, 0x05, 0x72, 0x64, 0xe2, 0x8b, + 0xc0, 0xb6, 0xfb, 0x37, 0x8c, 0x8e, 0xf1, 0x46, 0xbe, 0x00 }; + PTF_ASSERT_BUF_COMPARE(hmac1, expected1, 20); + + // Test Case 2: key = "Jefe", data = "what do ya want for nothing?" + const uint8_t key2[] = "Jefe"; + const uint8_t data2[] = "what do ya want for nothing?"; + uint8_t hmac2[20]; + + PTF_ASSERT_TRUE(pcpp::calculateSctpHmacSha1(key2, 4, data2, 28, hmac2)); + + // Expected HMAC-SHA1: 0xeffcdf6ae5eb2fa2d27416d5f184df9c259a7c79 + uint8_t expected2[] = { 0xef, 0xfc, 0xdf, 0x6a, 0xe5, 0xeb, 0x2f, 0xa2, 0xd2, 0x74, + 0x16, 0xd5, 0xf1, 0x84, 0xdf, 0x9c, 0x25, 0x9a, 0x7c, 0x79 }; + PTF_ASSERT_BUF_COMPARE(hmac2, expected2, 20); +} + +PTF_TEST_CASE(SctpHmacSha256ComputationTest) +{ + // Test HMAC-SHA256 computation with RFC 4231 test vectors + // Test Case 1: key = 0x0b repeated 20 times, data = "Hi There" + uint8_t key1[20]; + memset(key1, 0x0B, 20); + const uint8_t data1[] = "Hi There"; + uint8_t hmac1[32]; + + PTF_ASSERT_TRUE(pcpp::calculateSctpHmacSha256(key1, 20, data1, 8, hmac1)); + + // Expected HMAC-SHA256 from RFC 4231 + uint8_t expected1[] = { 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, + 0xce, 0xaf, 0x0b, 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x00, 0xc9, 0x83, + 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, 0x2e, 0x32, 0xcf, 0xf7 }; + PTF_ASSERT_BUF_COMPARE(hmac1, expected1, 32); + + // Test Case 2: key = "Jefe", data = "what do ya want for nothing?" + const uint8_t key2[] = "Jefe"; + const uint8_t data2[] = "what do ya want for nothing?"; + uint8_t hmac2[32]; + + PTF_ASSERT_TRUE(pcpp::calculateSctpHmacSha256(key2, 4, data2, 28, hmac2)); + + // Expected HMAC-SHA256 from RFC 4231 + uint8_t expected2[] = { 0x5b, 0xdc, 0xc1, 0x46, 0xbf, 0x60, 0x75, 0x4e, 0x6a, 0x04, 0x24, + 0x26, 0x08, 0x95, 0x75, 0xc7, 0x5a, 0x00, 0x3f, 0x08, 0x9d, 0x27, + 0x39, 0x83, 0x9d, 0xec, 0x58, 0xb9, 0x64, 0xec, 0x38, 0x43 }; + PTF_ASSERT_BUF_COMPARE(hmac2, expected2, 32); +} + +PTF_TEST_CASE(SctpHmacVerificationTest) +{ + // Test HMAC verification for both SHA-1 and SHA-256 + const uint8_t key[] = "TestKey123"; + const uint8_t data[] = "Test data for SCTP AUTH chunk verification"; + + // Calculate and verify SHA-1 HMAC + uint8_t hmacSha1[20] = { 0 }; + PTF_ASSERT_TRUE(pcpp::calculateSctpHmacSha1(key, 10, data, 44, hmacSha1)); + + // Verify with correct HMAC (HMAC ID 1 = SHA-1) + PTF_ASSERT_TRUE(pcpp::verifySctpHmac(1, key, 10, data, 44, hmacSha1, 20)); + + // Verify with wrong HMAC + uint8_t wrongHmac[20] = { 0 }; + PTF_ASSERT_FALSE(pcpp::verifySctpHmac(1, key, 10, data, 44, wrongHmac, 20)); + + // Calculate and verify SHA-256 HMAC + uint8_t hmacSha256[32] = { 0 }; + PTF_ASSERT_TRUE(pcpp::calculateSctpHmacSha256(key, 10, data, 44, hmacSha256)); + + // Verify with correct HMAC (HMAC ID 3 = SHA-256) + PTF_ASSERT_TRUE(pcpp::verifySctpHmac(3, key, 10, data, 44, hmacSha256, 32)); + + // Verify with wrong HMAC (filled with 0xFF) + uint8_t wrongHmac256[32]; + memset(wrongHmac256, 0xFF, sizeof(wrongHmac256)); + PTF_ASSERT_FALSE(pcpp::verifySctpHmac(3, key, 10, data, 44, wrongHmac256, 32)); + + // Verify with unknown HMAC ID returns false + PTF_ASSERT_FALSE(pcpp::verifySctpHmac(99, key, 10, data, 44, hmacSha1, 20)); +} + +PTF_TEST_CASE(SctpHmacSizeConstantsTest) +{ + // Test HMAC size constants + PTF_ASSERT_EQUAL(pcpp::SctpHmacSize::SHA1, 20); + PTF_ASSERT_EQUAL(pcpp::SctpHmacSize::SHA256, 32); +} + +PTF_TEST_CASE(SctpExtendedPpidEnumsNewTest) +{ + // Test newly added PPID definitions from IANA registry + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::IRCP), 28); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::MPICH2), 31); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::FGP), 32); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::PPP), 33); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::CALCAPP), 34); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::SSP), 35); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::NPMP_CONTROL), 36); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::NPMP_DATA), 37); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::ECHO), 38); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::DISCARD), 39); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::DAYTIME), 40); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::CHARGEN), 41); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::RNA), 42); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::SSH), 45); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::DIAMETER_DTLS), 47); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::R14P_BER), 48); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::R14P_GPB), 49); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::WEBRTC_STRING_EMPTY), 56); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::WEBRTC_BINARY_EMPTY), 57); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::NRPPA), 68); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::NGAP_DTLS), 73); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::XNAP_DTLS), 74); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::DTLS_KEY_MGMT), 4242); +} + +PTF_TEST_CASE(SctpNrSackChunkTypeNameTest) +{ + // Test NR-SACK chunk type name + pcpp::sctp_chunk_hdr chunkHdr; + chunkHdr.type = 16; // NR-SACK + chunkHdr.flags = 0; + chunkHdr.length = htobe16(20); + + pcpp::SctpChunk chunk(reinterpret_cast(&chunkHdr)); + PTF_ASSERT_EQUAL(chunk.getChunkType(), pcpp::SctpChunkType::NR_SACK, enumclass); + PTF_ASSERT_EQUAL(chunk.getChunkTypeName(), "NR-SACK"); +} + +PTF_TEST_CASE(SctpChunkActionBitsTest) +{ + // Test chunk action bits (RFC 9260 Section 3.2) + + // Test action bit extraction + PTF_ASSERT_EQUAL(pcpp::getSctpChunkActionBits(0x00), pcpp::SctpChunkActionBits::STOP_AND_REPORT); + PTF_ASSERT_EQUAL(pcpp::getSctpChunkActionBits(0x0F), pcpp::SctpChunkActionBits::STOP_AND_REPORT); + PTF_ASSERT_EQUAL(pcpp::getSctpChunkActionBits(0x40), pcpp::SctpChunkActionBits::STOP_NO_REPORT); + PTF_ASSERT_EQUAL(pcpp::getSctpChunkActionBits(0x7F), pcpp::SctpChunkActionBits::STOP_NO_REPORT); + PTF_ASSERT_EQUAL(pcpp::getSctpChunkActionBits(0x80), pcpp::SctpChunkActionBits::SKIP_AND_REPORT); + PTF_ASSERT_EQUAL(pcpp::getSctpChunkActionBits(0xBF), pcpp::SctpChunkActionBits::SKIP_AND_REPORT); + PTF_ASSERT_EQUAL(pcpp::getSctpChunkActionBits(0xC0), pcpp::SctpChunkActionBits::SKIP_NO_REPORT); + PTF_ASSERT_EQUAL(pcpp::getSctpChunkActionBits(0xFF), pcpp::SctpChunkActionBits::SKIP_NO_REPORT); + + // Test known chunk types (should all be 00 - stop and report) + PTF_ASSERT_EQUAL(pcpp::getSctpChunkActionBits(0), pcpp::SctpChunkActionBits::STOP_AND_REPORT); // DATA + PTF_ASSERT_EQUAL(pcpp::getSctpChunkActionBits(1), pcpp::SctpChunkActionBits::STOP_AND_REPORT); // INIT + PTF_ASSERT_EQUAL(pcpp::getSctpChunkActionBits(15), pcpp::SctpChunkActionBits::STOP_AND_REPORT); // AUTH + + // Test shouldStopOnUnrecognizedChunk + PTF_ASSERT_TRUE(pcpp::shouldStopOnUnrecognizedChunk(0x00)); // 00 - stop + PTF_ASSERT_TRUE(pcpp::shouldStopOnUnrecognizedChunk(0x40)); // 01 - stop + PTF_ASSERT_FALSE(pcpp::shouldStopOnUnrecognizedChunk(0x80)); // 10 - skip + PTF_ASSERT_FALSE(pcpp::shouldStopOnUnrecognizedChunk(0xC0)); // 11 - skip + + // Test shouldReportUnrecognizedChunk + PTF_ASSERT_TRUE(pcpp::shouldReportUnrecognizedChunk(0x00)); // 00 - report + PTF_ASSERT_FALSE(pcpp::shouldReportUnrecognizedChunk(0x40)); // 01 - no report + PTF_ASSERT_TRUE(pcpp::shouldReportUnrecognizedChunk(0x80)); // 10 - report + PTF_ASSERT_FALSE(pcpp::shouldReportUnrecognizedChunk(0xC0)); // 11 - no report + + // Test with actual SCTP chunk types that use high bits + // I-DATA (64 = 0x40) has bits 01 - stop, no report + PTF_ASSERT_TRUE(pcpp::shouldStopOnUnrecognizedChunk(64)); + PTF_ASSERT_FALSE(pcpp::shouldReportUnrecognizedChunk(64)); + + // ASCONF-ACK (128 = 0x80) has bits 10 - skip, report + PTF_ASSERT_FALSE(pcpp::shouldStopOnUnrecognizedChunk(128)); + PTF_ASSERT_TRUE(pcpp::shouldReportUnrecognizedChunk(128)); + + // FORWARD-TSN (192 = 0xC0) has bits 11 - skip, no report + PTF_ASSERT_FALSE(pcpp::shouldStopOnUnrecognizedChunk(192)); + PTF_ASSERT_FALSE(pcpp::shouldReportUnrecognizedChunk(192)); +} + +PTF_TEST_CASE(SctpParamActionBitsTest) +{ + // Test parameter action bits (RFC 9260 Section 3.2.1) + + // Test action bit extraction + PTF_ASSERT_EQUAL(pcpp::getSctpParamActionBits(0x0000), pcpp::SctpParamActionBits::STOP_AND_REPORT); + PTF_ASSERT_EQUAL(pcpp::getSctpParamActionBits(0x0FFF), pcpp::SctpParamActionBits::STOP_AND_REPORT); + PTF_ASSERT_EQUAL(pcpp::getSctpParamActionBits(0x4000), pcpp::SctpParamActionBits::STOP_NO_REPORT); + PTF_ASSERT_EQUAL(pcpp::getSctpParamActionBits(0x7FFF), pcpp::SctpParamActionBits::STOP_NO_REPORT); + PTF_ASSERT_EQUAL(pcpp::getSctpParamActionBits(0x8000), pcpp::SctpParamActionBits::SKIP_AND_REPORT); + PTF_ASSERT_EQUAL(pcpp::getSctpParamActionBits(0xBFFF), pcpp::SctpParamActionBits::SKIP_AND_REPORT); + PTF_ASSERT_EQUAL(pcpp::getSctpParamActionBits(0xC000), pcpp::SctpParamActionBits::SKIP_NO_REPORT); + PTF_ASSERT_EQUAL(pcpp::getSctpParamActionBits(0xFFFF), pcpp::SctpParamActionBits::SKIP_NO_REPORT); + + // Test known parameter types + // IPv4 Address (5) - bits 00 + PTF_ASSERT_TRUE(pcpp::shouldStopOnUnrecognizedParam(5)); + PTF_ASSERT_TRUE(pcpp::shouldReportUnrecognizedParam(5)); + + // ECN Capable (0x8000) - bits 10 + PTF_ASSERT_FALSE(pcpp::shouldStopOnUnrecognizedParam(0x8000)); + PTF_ASSERT_TRUE(pcpp::shouldReportUnrecognizedParam(0x8000)); + + // Forward TSN Supported (0xC000) - bits 11 + PTF_ASSERT_FALSE(pcpp::shouldStopOnUnrecognizedParam(0xC000)); + PTF_ASSERT_FALSE(pcpp::shouldReportUnrecognizedParam(0xC000)); +} + +PTF_TEST_CASE(SctpComputeAuthHmacTest) +{ + // Create SCTP packet with AUTH chunk followed by DATA chunk + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + // Add AUTH chunk with SHA-1 HMAC (ID 1) + uint8_t dummyHmac[20] = { 0 }; // Will be replaced + PTF_ASSERT_TRUE(sctpLayer.addAuthChunk(0, 1, dummyHmac, 20)); + + // Add DATA chunk after AUTH + uint8_t userData[] = "Test data for AUTH"; + PTF_ASSERT_TRUE(sctpLayer.addDataChunk(1, 0, 0, 0, userData, sizeof(userData) - 1, true, true, false, false)); + + // Compute HMAC + const uint8_t key[] = "SharedSecretKey"; + uint8_t hmacOut[32]; + size_t hmacOutLen = 0; + + PTF_ASSERT_TRUE(pcpp::computeSctpAuthHmac(sctpLayer, key, 15, hmacOut, &hmacOutLen)); + PTF_ASSERT_EQUAL(hmacOutLen, 20); // SHA-1 produces 20 bytes + + // Verify HMAC is not all zeros (it was computed) + bool allZeros = true; + for (size_t i = 0; i < hmacOutLen; ++i) + { + if (hmacOut[i] != 0) + { + allZeros = false; + break; + } + } + PTF_ASSERT_FALSE(allZeros); +} + +PTF_TEST_CASE(SctpComputeAuthHmacSha256Test) +{ + // Create SCTP packet with AUTH chunk using SHA-256 + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + // Add AUTH chunk with SHA-256 HMAC (ID 3) + uint8_t dummyHmac[32] = { 0 }; + PTF_ASSERT_TRUE(sctpLayer.addAuthChunk(0, 3, dummyHmac, 32)); + + // Add DATA chunk after AUTH + uint8_t userData[] = "Test data"; + PTF_ASSERT_TRUE(sctpLayer.addDataChunk(1, 0, 0, 0, userData, sizeof(userData) - 1, true, true, false, false)); + + // Compute HMAC + const uint8_t key[] = "AnotherKey"; + uint8_t hmacOut[32]; + size_t hmacOutLen = 0; + + PTF_ASSERT_TRUE(pcpp::computeSctpAuthHmac(sctpLayer, key, 10, hmacOut, &hmacOutLen)); + PTF_ASSERT_EQUAL(hmacOutLen, 32); // SHA-256 produces 32 bytes +} + +PTF_TEST_CASE(SctpComputeAuthHmacNoAuthChunkTest) +{ + // Create SCTP packet without AUTH chunk + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + uint8_t userData[] = "Test data"; + PTF_ASSERT_TRUE(sctpLayer.addDataChunk(1, 0, 0, 0, userData, sizeof(userData) - 1, true, true, false, false)); + + // Should fail - no AUTH chunk + const uint8_t key[] = "Key"; + uint8_t hmacOut[32]; + size_t hmacOutLen = 0; + + PTF_ASSERT_FALSE(pcpp::computeSctpAuthHmac(sctpLayer, key, 3, hmacOut, &hmacOutLen)); +} + +PTF_TEST_CASE(SctpExtendedPpidEnumsNewTelecomTest) +{ + // Test newly added telecom PPID definitions + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::HTTP_SCTP), 63); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::E2AP), 65); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::E2AP_DTLS), 66); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::W1AP_NON_DTLS), 67); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::NRPPA_DTLS), 69); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::F1AP_DTLS), 70); + PTF_ASSERT_EQUAL(static_cast(pcpp::SctpPayloadProtocolId::E1AP_DTLS), 71); +} + +// ==================== Typed View API Tests ==================== + +PTF_TEST_CASE(SctpDataChunkViewTest) +{ + // Parse SCTP DATA packet from hex resource file + auto rawPacket = createPacketFromHexResource("PacketExamples/SctpDataPacket.dat"); + + pcpp::Packet sctpPacket(rawPacket.get()); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + + // Test using typed getter from SctpLayer + pcpp::SctpDataChunkView dataView = sctpLayer->getDataChunk(); + PTF_ASSERT_TRUE(dataView.isValid()); + + // Test DATA chunk fields via view + PTF_ASSERT_EQUAL(dataView.getTsn(), 10); + PTF_ASSERT_EQUAL(dataView.getStreamId(), 1); + PTF_ASSERT_EQUAL(dataView.getSequenceNumber(), 5); + PTF_ASSERT_EQUAL(dataView.getPpid(), 46); + + // Test flags via view + PTF_ASSERT_TRUE(dataView.isBeginFragment()); + PTF_ASSERT_TRUE(dataView.isEndFragment()); + PTF_ASSERT_FALSE(dataView.isUnordered()); + PTF_ASSERT_FALSE(dataView.isImmediate()); + + // Test user data via view + PTF_ASSERT_EQUAL(dataView.getUserDataLength(), 12); + uint8_t* userData = dataView.getUserData(); + PTF_ASSERT_NOT_NULL(userData); + PTF_ASSERT_BUF_COMPARE(userData, "Hello World!", 12); + + // Test fromChunk factory method + pcpp::SctpChunk genericChunk = sctpLayer->getChunk(pcpp::SctpChunkType::DATA); + pcpp::SctpDataChunkView dataView2 = pcpp::SctpDataChunkView::fromChunk(genericChunk); + PTF_ASSERT_TRUE(dataView2.isValid()); + PTF_ASSERT_EQUAL(dataView2.getTsn(), 10); + + // Test invalid view (wrong chunk type) + pcpp::SctpChunk nullChunk(nullptr); + pcpp::SctpDataChunkView invalidView = pcpp::SctpDataChunkView::fromChunk(nullChunk); + PTF_ASSERT_FALSE(invalidView.isValid()); + PTF_ASSERT_EQUAL(invalidView.getTsn(), 0); + PTF_ASSERT_NULL(invalidView.getUserData()); +} + +PTF_TEST_CASE(SctpInitChunkViewTest) +{ + // Parse SCTP INIT packet from hex resource file + auto rawPacket = createPacketFromHexResource("PacketExamples/SctpInitPacket.dat"); + + pcpp::Packet sctpPacket(rawPacket.get()); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + + // Test using typed getter from SctpLayer + pcpp::SctpInitChunkView initView = sctpLayer->getInitChunk(); + PTF_ASSERT_TRUE(initView.isValid()); + + // Test INIT chunk fields via view + PTF_ASSERT_EQUAL(initView.getInitiateTag(), 0xaabbccdd); + PTF_ASSERT_EQUAL(initView.getArwnd(), 65536); + PTF_ASSERT_EQUAL(initView.getNumOutboundStreams(), 10); + PTF_ASSERT_EQUAL(initView.getNumInboundStreams(), 10); + PTF_ASSERT_EQUAL(initView.getInitialTsn(), 1); + + // Test common methods via view + PTF_ASSERT_EQUAL(initView.getLength(), 20); + PTF_ASSERT_EQUAL(initView.getFlags(), 0); + + // Test getChunk() returns underlying chunk + pcpp::SctpChunk underlyingChunk = initView.getChunk(); + PTF_ASSERT_TRUE(underlyingChunk.isNotNull()); + PTF_ASSERT_EQUAL(underlyingChunk.getChunkType(), pcpp::SctpChunkType::INIT, enumclass); + + // Test invalid view + pcpp::SctpChunk nullChunk(nullptr); + pcpp::SctpInitChunkView invalidView = pcpp::SctpInitChunkView::fromChunk(nullChunk); + PTF_ASSERT_FALSE(invalidView.isValid()); + PTF_ASSERT_EQUAL(invalidView.getInitiateTag(), 0); +} + +PTF_TEST_CASE(SctpInitAckChunkViewTest) +{ + // Create SCTP packet with INIT-ACK chunk + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + uint8_t params[] = { 0x00, 0x05, 0x00, 0x08, 0xc0, 0xa8, 0x01, 0x01 }; // IPv4 address parameter + PTF_ASSERT_TRUE(sctpLayer.addInitAckChunk(0x11223344, 131072, 20, 15, 1000, params, sizeof(params))); + + // Test using typed getter + pcpp::SctpInitAckChunkView initAckView = sctpLayer.getInitAckChunk(); + PTF_ASSERT_TRUE(initAckView.isValid()); + + // Test INIT-ACK chunk fields via view + PTF_ASSERT_EQUAL(initAckView.getInitiateTag(), 0x11223344); + PTF_ASSERT_EQUAL(initAckView.getArwnd(), 131072); + PTF_ASSERT_EQUAL(initAckView.getNumOutboundStreams(), 20); + PTF_ASSERT_EQUAL(initAckView.getNumInboundStreams(), 15); + PTF_ASSERT_EQUAL(initAckView.getInitialTsn(), 1000); + + // Test parameters access + PTF_ASSERT_EQUAL(initAckView.getParametersLength(), sizeof(params)); + PTF_ASSERT_NOT_NULL(initAckView.getFirstParameter()); + + // Test that INIT view is invalid for INIT-ACK chunk + pcpp::SctpInitChunkView wrongView = sctpLayer.getInitChunk(); + PTF_ASSERT_FALSE(wrongView.isValid()); +} + +PTF_TEST_CASE(SctpSackChunkViewTest) +{ + // Parse SCTP SACK packet from hex resource file + auto rawPacket = createPacketFromHexResource("PacketExamples/SctpSackPacket.dat"); + + pcpp::Packet sctpPacket(rawPacket.get()); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + + // Test using typed getter from SctpLayer + pcpp::SctpSackChunkView sackView = sctpLayer->getSackChunk(); + PTF_ASSERT_TRUE(sackView.isValid()); + + // Test SACK chunk fields via view + PTF_ASSERT_EQUAL(sackView.getCumulativeTsnAck(), 20); + PTF_ASSERT_EQUAL(sackView.getArwnd(), 32768); + PTF_ASSERT_EQUAL(sackView.getNumGapBlocks(), 1); + PTF_ASSERT_EQUAL(sackView.getNumDupTsns(), 1); + + // Test gap blocks via view + std::vector gapBlocks = sackView.getGapBlocks(); + PTF_ASSERT_EQUAL(gapBlocks.size(), 1); + PTF_ASSERT_EQUAL(gapBlocks[0].start, 5); + PTF_ASSERT_EQUAL(gapBlocks[0].end, 8); + + // Test duplicate TSNs via view + std::vector dupTsns = sackView.getDupTsns(); + PTF_ASSERT_EQUAL(dupTsns.size(), 1); + PTF_ASSERT_EQUAL(dupTsns[0], 15); + + // Test invalid view + pcpp::SctpChunk nullChunk(nullptr); + pcpp::SctpSackChunkView invalidView = pcpp::SctpSackChunkView::fromChunk(nullChunk); + PTF_ASSERT_FALSE(invalidView.isValid()); + PTF_ASSERT_EQUAL(invalidView.getCumulativeTsnAck(), 0); + PTF_ASSERT_TRUE(invalidView.getGapBlocks().empty()); + PTF_ASSERT_TRUE(invalidView.getDupTsns().empty()); +} + +PTF_TEST_CASE(SctpChunkViewTypeSafetyTest) +{ + // Create SCTP packet with DATA chunk + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + uint8_t userData[] = "Test"; + PTF_ASSERT_TRUE(sctpLayer.addDataChunk(100, 1, 0, 46, userData, sizeof(userData) - 1, true, true, false, false)); + + // Get the DATA chunk + pcpp::SctpChunk dataChunk = sctpLayer.getChunk(pcpp::SctpChunkType::DATA); + PTF_ASSERT_TRUE(dataChunk.isNotNull()); + + // DATA view should be valid + pcpp::SctpDataChunkView dataView = pcpp::SctpDataChunkView::fromChunk(dataChunk); + PTF_ASSERT_TRUE(dataView.isValid()); + + // INIT view should be invalid for DATA chunk + pcpp::SctpInitChunkView initView = pcpp::SctpInitChunkView::fromChunk(dataChunk); + PTF_ASSERT_FALSE(initView.isValid()); + + // SACK view should be invalid for DATA chunk + pcpp::SctpSackChunkView sackView = pcpp::SctpSackChunkView::fromChunk(dataChunk); + PTF_ASSERT_FALSE(sackView.isValid()); + + // Typed getters should return invalid views for missing chunk types + PTF_ASSERT_FALSE(sctpLayer.getInitChunk().isValid()); + PTF_ASSERT_FALSE(sctpLayer.getInitAckChunk().isValid()); + PTF_ASSERT_FALSE(sctpLayer.getSackChunk().isValid()); +} + +PTF_TEST_CASE(SctpHeartbeatChunkViewTest) +{ + // Parse SCTP HEARTBEAT packet from hex resource file + auto rawPacket = createPacketFromHexResource("PacketExamples/SctpHeartbeatPacket.dat"); + + pcpp::Packet sctpPacket(rawPacket.get()); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + + // Test using typed getter from SctpLayer + pcpp::SctpHeartbeatChunkView hbView = sctpLayer->getHeartbeatChunk(); + PTF_ASSERT_TRUE(hbView.isValid()); + + // Test common methods via view + PTF_ASSERT_EQUAL(hbView.getLength(), 16); + PTF_ASSERT_EQUAL(hbView.getFlags(), 0); + + // Test heartbeat info access + PTF_ASSERT_TRUE(hbView.getInfoLength() > 0); + PTF_ASSERT_NOT_NULL(hbView.getInfo()); + + // Test getChunk() returns underlying chunk + pcpp::SctpChunk underlyingChunk = hbView.getChunk(); + PTF_ASSERT_TRUE(underlyingChunk.isNotNull()); + PTF_ASSERT_EQUAL(underlyingChunk.getChunkType(), pcpp::SctpChunkType::HEARTBEAT, enumclass); + + // Test invalid view + pcpp::SctpChunk nullChunk(nullptr); + pcpp::SctpHeartbeatChunkView invalidView = pcpp::SctpHeartbeatChunkView::fromChunk(nullChunk); + PTF_ASSERT_FALSE(invalidView.isValid()); + PTF_ASSERT_EQUAL(invalidView.getInfoLength(), 0); + PTF_ASSERT_NULL(invalidView.getInfo()); +} + +PTF_TEST_CASE(SctpCookieEchoChunkViewTest) +{ + // Parse SCTP COOKIE-ECHO packet from hex resource file + auto rawPacket = createPacketFromHexResource("PacketExamples/SctpCookieEchoPacket.dat"); + + pcpp::Packet sctpPacket(rawPacket.get()); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + + // Test using typed getter from SctpLayer + pcpp::SctpCookieEchoChunkView cookieView = sctpLayer->getCookieEchoChunk(); + PTF_ASSERT_TRUE(cookieView.isValid()); + + // Test cookie access + PTF_ASSERT_TRUE(cookieView.getCookieLength() > 0); + PTF_ASSERT_NOT_NULL(cookieView.getCookie()); + + // Test getChunk() returns underlying chunk + pcpp::SctpChunk underlyingChunk = cookieView.getChunk(); + PTF_ASSERT_TRUE(underlyingChunk.isNotNull()); + PTF_ASSERT_EQUAL(underlyingChunk.getChunkType(), pcpp::SctpChunkType::COOKIE_ECHO, enumclass); + + // Test invalid view + pcpp::SctpChunk nullChunk(nullptr); + pcpp::SctpCookieEchoChunkView invalidView = pcpp::SctpCookieEchoChunkView::fromChunk(nullChunk); + PTF_ASSERT_FALSE(invalidView.isValid()); + PTF_ASSERT_EQUAL(invalidView.getCookieLength(), 0); + PTF_ASSERT_NULL(invalidView.getCookie()); +} + +PTF_TEST_CASE(SctpAbortChunkViewTest) +{ + // Create SCTP packet with ABORT chunk + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + PTF_ASSERT_TRUE(sctpLayer.addAbortChunk(false)); + + // Test using typed getter from SctpLayer + pcpp::SctpAbortChunkView abortView = sctpLayer.getAbortChunk(); + PTF_ASSERT_TRUE(abortView.isValid()); + + // Test ABORT chunk fields via view + PTF_ASSERT_FALSE(abortView.isTBitSet()); + PTF_ASSERT_EQUAL(abortView.getErrorCausesLength(), 0); + + // Test getChunk() returns underlying chunk + pcpp::SctpChunk underlyingChunk = abortView.getChunk(); + PTF_ASSERT_TRUE(underlyingChunk.isNotNull()); + PTF_ASSERT_EQUAL(underlyingChunk.getChunkType(), pcpp::SctpChunkType::ABORT, enumclass); + + // Test with T bit set + pcpp::SctpLayer sctpLayer2(5000, 5001, 0xDEADBEEF); + PTF_ASSERT_TRUE(sctpLayer2.addAbortChunk(true)); + pcpp::SctpAbortChunkView abortView2 = sctpLayer2.getAbortChunk(); + PTF_ASSERT_TRUE(abortView2.isValid()); + PTF_ASSERT_TRUE(abortView2.isTBitSet()); + + // Test invalid view + pcpp::SctpChunk nullChunk(nullptr); + pcpp::SctpAbortChunkView invalidView = pcpp::SctpAbortChunkView::fromChunk(nullChunk); + PTF_ASSERT_FALSE(invalidView.isValid()); + PTF_ASSERT_FALSE(invalidView.isTBitSet()); +} + +PTF_TEST_CASE(SctpShutdownChunkViewTest) +{ + // Create SCTP packet with SHUTDOWN chunk + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + PTF_ASSERT_TRUE(sctpLayer.addShutdownChunk(12345678)); + + // Test using typed getter from SctpLayer + pcpp::SctpShutdownChunkView shutdownView = sctpLayer.getShutdownChunk(); + PTF_ASSERT_TRUE(shutdownView.isValid()); + + // Test SHUTDOWN chunk fields via view + PTF_ASSERT_EQUAL(shutdownView.getCumulativeTsnAck(), 12345678); + + // Test common methods via view + PTF_ASSERT_EQUAL(shutdownView.getLength(), 8); + PTF_ASSERT_EQUAL(shutdownView.getFlags(), 0); + + // Test getChunk() returns underlying chunk + pcpp::SctpChunk underlyingChunk = shutdownView.getChunk(); + PTF_ASSERT_TRUE(underlyingChunk.isNotNull()); + PTF_ASSERT_EQUAL(underlyingChunk.getChunkType(), pcpp::SctpChunkType::SHUTDOWN, enumclass); + + // Test invalid view + pcpp::SctpChunk nullChunk(nullptr); + pcpp::SctpShutdownChunkView invalidView = pcpp::SctpShutdownChunkView::fromChunk(nullChunk); + PTF_ASSERT_FALSE(invalidView.isValid()); + PTF_ASSERT_EQUAL(invalidView.getCumulativeTsnAck(), 0); +} + +PTF_TEST_CASE(SctpShutdownAckChunkViewTest) +{ + // Create SCTP packet with SHUTDOWN-ACK chunk + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + PTF_ASSERT_TRUE(sctpLayer.addShutdownAckChunk()); + + // Test using typed getter from SctpLayer + pcpp::SctpShutdownAckChunkView shutdownAckView = sctpLayer.getShutdownAckChunk(); + PTF_ASSERT_TRUE(shutdownAckView.isValid()); + + // Test common methods via view + PTF_ASSERT_EQUAL(shutdownAckView.getLength(), 4); + PTF_ASSERT_EQUAL(shutdownAckView.getFlags(), 0); + + // Test getChunk() returns underlying chunk + pcpp::SctpChunk underlyingChunk = shutdownAckView.getChunk(); + PTF_ASSERT_TRUE(underlyingChunk.isNotNull()); + PTF_ASSERT_EQUAL(underlyingChunk.getChunkType(), pcpp::SctpChunkType::SHUTDOWN_ACK, enumclass); + + // Test invalid view + pcpp::SctpChunk nullChunk(nullptr); + pcpp::SctpShutdownAckChunkView invalidView = pcpp::SctpShutdownAckChunkView::fromChunk(nullChunk); + PTF_ASSERT_FALSE(invalidView.isValid()); +} + +PTF_TEST_CASE(SctpShutdownCompleteChunkViewTest) +{ + // Create SCTP packet with SHUTDOWN-COMPLETE chunk + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + PTF_ASSERT_TRUE(sctpLayer.addShutdownCompleteChunk(false)); + + // Test using typed getter from SctpLayer + pcpp::SctpShutdownCompleteChunkView shutdownCompleteView = sctpLayer.getShutdownCompleteChunk(); + PTF_ASSERT_TRUE(shutdownCompleteView.isValid()); + + // Test SHUTDOWN-COMPLETE chunk fields via view + PTF_ASSERT_FALSE(shutdownCompleteView.isTBitSet()); + + // Test common methods via view + PTF_ASSERT_EQUAL(shutdownCompleteView.getLength(), 4); + + // Test getChunk() returns underlying chunk + pcpp::SctpChunk underlyingChunk = shutdownCompleteView.getChunk(); + PTF_ASSERT_TRUE(underlyingChunk.isNotNull()); + PTF_ASSERT_EQUAL(underlyingChunk.getChunkType(), pcpp::SctpChunkType::SHUTDOWN_COMPLETE, enumclass); + + // Test with T bit set + pcpp::SctpLayer sctpLayer2(5000, 5001, 0xDEADBEEF); + PTF_ASSERT_TRUE(sctpLayer2.addShutdownCompleteChunk(true)); + pcpp::SctpShutdownCompleteChunkView shutdownCompleteView2 = sctpLayer2.getShutdownCompleteChunk(); + PTF_ASSERT_TRUE(shutdownCompleteView2.isValid()); + PTF_ASSERT_TRUE(shutdownCompleteView2.isTBitSet()); + + // Test invalid view + pcpp::SctpChunk nullChunk(nullptr); + pcpp::SctpShutdownCompleteChunkView invalidView = pcpp::SctpShutdownCompleteChunkView::fromChunk(nullChunk); + PTF_ASSERT_FALSE(invalidView.isValid()); + PTF_ASSERT_FALSE(invalidView.isTBitSet()); +} + +PTF_TEST_CASE(SctpControlChunkViewTypeSafetyTest) +{ + // Create SCTP packet with HEARTBEAT chunk + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + PTF_ASSERT_TRUE(sctpLayer.addHeartbeatChunk(nullptr, 0)); + + // Get the HEARTBEAT chunk + pcpp::SctpChunk hbChunk = sctpLayer.getChunk(pcpp::SctpChunkType::HEARTBEAT); + PTF_ASSERT_TRUE(hbChunk.isNotNull()); + + // HEARTBEAT view should be valid + pcpp::SctpHeartbeatChunkView hbView = pcpp::SctpHeartbeatChunkView::fromChunk(hbChunk); + PTF_ASSERT_TRUE(hbView.isValid()); + + // Other control chunk views should be invalid for HEARTBEAT chunk + PTF_ASSERT_FALSE(pcpp::SctpHeartbeatAckChunkView::fromChunk(hbChunk).isValid()); + PTF_ASSERT_FALSE(pcpp::SctpAbortChunkView::fromChunk(hbChunk).isValid()); + PTF_ASSERT_FALSE(pcpp::SctpShutdownChunkView::fromChunk(hbChunk).isValid()); + PTF_ASSERT_FALSE(pcpp::SctpShutdownAckChunkView::fromChunk(hbChunk).isValid()); + PTF_ASSERT_FALSE(pcpp::SctpShutdownCompleteChunkView::fromChunk(hbChunk).isValid()); + PTF_ASSERT_FALSE(pcpp::SctpErrorChunkView::fromChunk(hbChunk).isValid()); + PTF_ASSERT_FALSE(pcpp::SctpCookieEchoChunkView::fromChunk(hbChunk).isValid()); + PTF_ASSERT_FALSE(pcpp::SctpCookieAckChunkView::fromChunk(hbChunk).isValid()); + PTF_ASSERT_FALSE(pcpp::SctpEcneChunkView::fromChunk(hbChunk).isValid()); + PTF_ASSERT_FALSE(pcpp::SctpCwrChunkView::fromChunk(hbChunk).isValid()); + + // Typed getters should return invalid views for missing chunk types + PTF_ASSERT_FALSE(sctpLayer.getHeartbeatAckChunk().isValid()); + PTF_ASSERT_FALSE(sctpLayer.getAbortChunk().isValid()); + PTF_ASSERT_FALSE(sctpLayer.getShutdownChunk().isValid()); + PTF_ASSERT_FALSE(sctpLayer.getShutdownAckChunk().isValid()); + PTF_ASSERT_FALSE(sctpLayer.getShutdownCompleteChunk().isValid()); + PTF_ASSERT_FALSE(sctpLayer.getErrorChunk().isValid()); + PTF_ASSERT_FALSE(sctpLayer.getCookieEchoChunk().isValid()); + PTF_ASSERT_FALSE(sctpLayer.getCookieAckChunk().isValid()); + PTF_ASSERT_FALSE(sctpLayer.getEcneChunk().isValid()); + PTF_ASSERT_FALSE(sctpLayer.getCwrChunk().isValid()); +} + +PTF_TEST_CASE(SctpAuthChunkViewTest) +{ + // Parse SCTP AUTH packet from hex resource file + auto rawPacket = createPacketFromHexResource("PacketExamples/SctpAuthPacket.dat"); + + pcpp::Packet sctpPacket(rawPacket.get()); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + + // Test using typed getter from SctpLayer + pcpp::SctpAuthChunkView authView = sctpLayer->getAuthChunk(); + PTF_ASSERT_TRUE(authView.isValid()); + + // Test AUTH chunk fields via view + PTF_ASSERT_TRUE(authView.getSharedKeyId() == 0 || authView.getSharedKeyId() > 0); + PTF_ASSERT_TRUE(authView.getHmacId() == 1 || authView.getHmacId() == 3); // SHA-1 or SHA-256 + PTF_ASSERT_TRUE(authView.getHmacLength() > 0); + PTF_ASSERT_NOT_NULL(authView.getHmacData()); + + // Test getChunk() returns underlying chunk + pcpp::SctpChunk underlyingChunk = authView.getChunk(); + PTF_ASSERT_TRUE(underlyingChunk.isNotNull()); + PTF_ASSERT_EQUAL(underlyingChunk.getChunkType(), pcpp::SctpChunkType::AUTH, enumclass); + + // Test invalid view + pcpp::SctpChunk nullChunk(nullptr); + pcpp::SctpAuthChunkView invalidView = pcpp::SctpAuthChunkView::fromChunk(nullChunk); + PTF_ASSERT_FALSE(invalidView.isValid()); + PTF_ASSERT_EQUAL(invalidView.getSharedKeyId(), 0); + PTF_ASSERT_EQUAL(invalidView.getHmacLength(), 0); + PTF_ASSERT_NULL(invalidView.getHmacData()); +} + +PTF_TEST_CASE(SctpForwardTsnChunkViewTest) +{ + // Parse SCTP FORWARD-TSN packet from hex resource file + auto rawPacket = createPacketFromHexResource("PacketExamples/SctpForwardTsnPacket.dat"); + + pcpp::Packet sctpPacket(rawPacket.get()); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + + // Test using typed getter from SctpLayer + pcpp::SctpForwardTsnChunkView fwdView = sctpLayer->getForwardTsnChunk(); + PTF_ASSERT_TRUE(fwdView.isValid()); + + // Test FORWARD-TSN chunk fields via view + PTF_ASSERT_TRUE(fwdView.getNewCumulativeTsn() > 0); + + // Test getChunk() returns underlying chunk + pcpp::SctpChunk underlyingChunk = fwdView.getChunk(); + PTF_ASSERT_TRUE(underlyingChunk.isNotNull()); + PTF_ASSERT_EQUAL(underlyingChunk.getChunkType(), pcpp::SctpChunkType::FORWARD_TSN, enumclass); + + // Test invalid view + pcpp::SctpChunk nullChunk(nullptr); + pcpp::SctpForwardTsnChunkView invalidView = pcpp::SctpForwardTsnChunkView::fromChunk(nullChunk); + PTF_ASSERT_FALSE(invalidView.isValid()); + PTF_ASSERT_EQUAL(invalidView.getNewCumulativeTsn(), 0); + PTF_ASSERT_EQUAL(invalidView.getStreamCount(), 0); + PTF_ASSERT_TRUE(invalidView.getStreams().empty()); +} + +PTF_TEST_CASE(SctpIDataChunkViewTest) +{ + // Parse SCTP I-DATA packet from hex resource file + auto rawPacket = createPacketFromHexResource("PacketExamples/SctpIDataPacket.dat"); + + pcpp::Packet sctpPacket(rawPacket.get()); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + + // Test using typed getter from SctpLayer + pcpp::SctpIDataChunkView idataView = sctpLayer->getIDataChunk(); + PTF_ASSERT_TRUE(idataView.isValid()); + + // Test I-DATA chunk fields via view + PTF_ASSERT_TRUE(idataView.getTsn() > 0); + PTF_ASSERT_TRUE(idataView.getUserDataLength() > 0); + PTF_ASSERT_NOT_NULL(idataView.getUserData()); + + // Test getChunk() returns underlying chunk + pcpp::SctpChunk underlyingChunk = idataView.getChunk(); + PTF_ASSERT_TRUE(underlyingChunk.isNotNull()); + PTF_ASSERT_EQUAL(underlyingChunk.getChunkType(), pcpp::SctpChunkType::I_DATA, enumclass); + + // Test invalid view + pcpp::SctpChunk nullChunk(nullptr); + pcpp::SctpIDataChunkView invalidView = pcpp::SctpIDataChunkView::fromChunk(nullChunk); + PTF_ASSERT_FALSE(invalidView.isValid()); + PTF_ASSERT_EQUAL(invalidView.getTsn(), 0); + PTF_ASSERT_EQUAL(invalidView.getMessageId(), 0); + PTF_ASSERT_NULL(invalidView.getUserData()); +} + +PTF_TEST_CASE(SctpIForwardTsnChunkViewTest) +{ + // Parse SCTP I-FORWARD-TSN packet from hex resource file + auto rawPacket = createPacketFromHexResource("PacketExamples/SctpIForwardTsnPacket.dat"); + + pcpp::Packet sctpPacket(rawPacket.get()); + + pcpp::SctpLayer* sctpLayer = sctpPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sctpLayer); + + // Test using typed getter from SctpLayer + pcpp::SctpIForwardTsnChunkView ifwdView = sctpLayer->getIForwardTsnChunk(); + PTF_ASSERT_TRUE(ifwdView.isValid()); + + // Test I-FORWARD-TSN chunk fields via view + PTF_ASSERT_TRUE(ifwdView.getNewCumulativeTsn() > 0); + + // Test getChunk() returns underlying chunk + pcpp::SctpChunk underlyingChunk = ifwdView.getChunk(); + PTF_ASSERT_TRUE(underlyingChunk.isNotNull()); + PTF_ASSERT_EQUAL(underlyingChunk.getChunkType(), pcpp::SctpChunkType::I_FORWARD_TSN, enumclass); + + // Test invalid view + pcpp::SctpChunk nullChunk(nullptr); + pcpp::SctpIForwardTsnChunkView invalidView = pcpp::SctpIForwardTsnChunkView::fromChunk(nullChunk); + PTF_ASSERT_FALSE(invalidView.isValid()); + PTF_ASSERT_EQUAL(invalidView.getNewCumulativeTsn(), 0); + PTF_ASSERT_EQUAL(invalidView.getStreamCount(), 0); + PTF_ASSERT_TRUE(invalidView.getStreams().empty()); +} + +PTF_TEST_CASE(SctpNrSackChunkViewTest) +{ + // Create SCTP packet with NR-SACK chunk + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + std::vector gapBlocks = { + { 3, 5 } + }; + std::vector nrGapBlocks = { + { 10, 12 } + }; + std::vector dupTsns = { 100 }; + + PTF_ASSERT_TRUE(sctpLayer.addNrSackChunk(1000, 65535, gapBlocks, nrGapBlocks, dupTsns)); + + // Test using typed getter from SctpLayer + pcpp::SctpNrSackChunkView nrsackView = sctpLayer.getNrSackChunk(); + PTF_ASSERT_TRUE(nrsackView.isValid()); + + // Test NR-SACK chunk fields via view + PTF_ASSERT_EQUAL(nrsackView.getCumulativeTsnAck(), 1000); + PTF_ASSERT_EQUAL(nrsackView.getArwnd(), 65535); + PTF_ASSERT_EQUAL(nrsackView.getNumGapBlocks(), 1); + PTF_ASSERT_EQUAL(nrsackView.getNumNrGapBlocks(), 1); + PTF_ASSERT_EQUAL(nrsackView.getNumDupTsns(), 1); + + // Test gap blocks via view + std::vector retrievedGapBlocks = nrsackView.getGapBlocks(); + PTF_ASSERT_EQUAL(retrievedGapBlocks.size(), 1); + PTF_ASSERT_EQUAL(retrievedGapBlocks[0].start, 3); + PTF_ASSERT_EQUAL(retrievedGapBlocks[0].end, 5); + + // Test NR gap blocks via view + std::vector retrievedNrGapBlocks = nrsackView.getNrGapBlocks(); + PTF_ASSERT_EQUAL(retrievedNrGapBlocks.size(), 1); + PTF_ASSERT_EQUAL(retrievedNrGapBlocks[0].start, 10); + PTF_ASSERT_EQUAL(retrievedNrGapBlocks[0].end, 12); + + // Test duplicate TSNs via view + std::vector retrievedDupTsns = nrsackView.getDupTsns(); + PTF_ASSERT_EQUAL(retrievedDupTsns.size(), 1); + PTF_ASSERT_EQUAL(retrievedDupTsns[0], 100); + + // Test getChunk() returns underlying chunk + pcpp::SctpChunk underlyingChunk = nrsackView.getChunk(); + PTF_ASSERT_TRUE(underlyingChunk.isNotNull()); + PTF_ASSERT_EQUAL(underlyingChunk.getChunkType(), pcpp::SctpChunkType::NR_SACK, enumclass); + + // Test invalid view + pcpp::SctpChunk nullChunk(nullptr); + pcpp::SctpNrSackChunkView invalidView = pcpp::SctpNrSackChunkView::fromChunk(nullChunk); + PTF_ASSERT_FALSE(invalidView.isValid()); + PTF_ASSERT_EQUAL(invalidView.getCumulativeTsnAck(), 0); + PTF_ASSERT_TRUE(invalidView.getGapBlocks().empty()); + PTF_ASSERT_TRUE(invalidView.getNrGapBlocks().empty()); + PTF_ASSERT_TRUE(invalidView.getDupTsns().empty()); +} + +PTF_TEST_CASE(SctpPadChunkViewTest) +{ + // Create SCTP packet with PAD chunk + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + PTF_ASSERT_TRUE(sctpLayer.addPadChunk(16)); + + // Test using typed getter from SctpLayer + pcpp::SctpPadChunkView padView = sctpLayer.getPadChunk(); + PTF_ASSERT_TRUE(padView.isValid()); + + // Test PAD chunk fields via view + PTF_ASSERT_EQUAL(padView.getPaddingLength(), 16); + PTF_ASSERT_NOT_NULL(padView.getPaddingData()); + + // Test getChunk() returns underlying chunk + pcpp::SctpChunk underlyingChunk = padView.getChunk(); + PTF_ASSERT_TRUE(underlyingChunk.isNotNull()); + PTF_ASSERT_EQUAL(underlyingChunk.getChunkType(), pcpp::SctpChunkType::PAD, enumclass); + + // Test invalid view + pcpp::SctpChunk nullChunk(nullptr); + pcpp::SctpPadChunkView invalidView = pcpp::SctpPadChunkView::fromChunk(nullChunk); + PTF_ASSERT_FALSE(invalidView.isValid()); + PTF_ASSERT_EQUAL(invalidView.getPaddingLength(), 0); + PTF_ASSERT_NULL(invalidView.getPaddingData()); +} + +PTF_TEST_CASE(SctpExtensionChunkViewTypeSafetyTest) +{ + // Create SCTP packet with FORWARD-TSN chunk + pcpp::SctpLayer sctpLayer(5000, 5001, 0xDEADBEEF); + + std::vector streams; + PTF_ASSERT_TRUE(sctpLayer.addForwardTsnChunk(1000, streams)); + + // Get the FORWARD-TSN chunk + pcpp::SctpChunk fwdChunk = sctpLayer.getChunk(pcpp::SctpChunkType::FORWARD_TSN); + PTF_ASSERT_TRUE(fwdChunk.isNotNull()); + + // FORWARD-TSN view should be valid + pcpp::SctpForwardTsnChunkView fwdView = pcpp::SctpForwardTsnChunkView::fromChunk(fwdChunk); + PTF_ASSERT_TRUE(fwdView.isValid()); + + // Other extension chunk views should be invalid for FORWARD-TSN chunk + PTF_ASSERT_FALSE(pcpp::SctpAuthChunkView::fromChunk(fwdChunk).isValid()); + PTF_ASSERT_FALSE(pcpp::SctpIDataChunkView::fromChunk(fwdChunk).isValid()); + PTF_ASSERT_FALSE(pcpp::SctpIForwardTsnChunkView::fromChunk(fwdChunk).isValid()); + PTF_ASSERT_FALSE(pcpp::SctpAsconfChunkView::fromChunk(fwdChunk).isValid()); + PTF_ASSERT_FALSE(pcpp::SctpAsconfAckChunkView::fromChunk(fwdChunk).isValid()); + PTF_ASSERT_FALSE(pcpp::SctpReconfigChunkView::fromChunk(fwdChunk).isValid()); + PTF_ASSERT_FALSE(pcpp::SctpPadChunkView::fromChunk(fwdChunk).isValid()); + PTF_ASSERT_FALSE(pcpp::SctpNrSackChunkView::fromChunk(fwdChunk).isValid()); + + // Typed getters should return invalid views for missing chunk types + PTF_ASSERT_FALSE(sctpLayer.getAuthChunk().isValid()); + PTF_ASSERT_FALSE(sctpLayer.getIDataChunk().isValid()); + PTF_ASSERT_FALSE(sctpLayer.getIForwardTsnChunk().isValid()); + PTF_ASSERT_FALSE(sctpLayer.getAsconfChunk().isValid()); + PTF_ASSERT_FALSE(sctpLayer.getAsconfAckChunk().isValid()); + PTF_ASSERT_FALSE(sctpLayer.getReconfigChunk().isValid()); + PTF_ASSERT_FALSE(sctpLayer.getPadChunk().isValid()); + PTF_ASSERT_FALSE(sctpLayer.getNrSackChunk().isValid()); +} diff --git a/Tests/Packet++Test/main.cpp b/Tests/Packet++Test/main.cpp index 5a6fd6cf1e..03d61f240d 100644 --- a/Tests/Packet++Test/main.cpp +++ b/Tests/Packet++Test/main.cpp @@ -409,5 +409,97 @@ int main(int argc, char* argv[]) PTF_RUN_TEST(CryptoKeyDecodingTest, "crypto"); PTF_RUN_TEST(CryptoKeyInvalidDataTest, "crypto"); + PTF_RUN_TEST(SctpLayerParsingTest, "sctp"); + PTF_RUN_TEST(SctpLayerCreationTest, "sctp"); + PTF_RUN_TEST(SctpDataChunkParsingTest, "sctp"); + PTF_RUN_TEST(SctpSackChunkParsingTest, "sctp"); + PTF_RUN_TEST(SctpMultipleChunksTest, "sctp"); + PTF_RUN_TEST(SctpChecksumTest, "sctp"); + PTF_RUN_TEST(SctpValidationTest, "sctp"); + PTF_RUN_TEST(SctpChunkTypesTest, "sctp"); + PTF_RUN_TEST(SctpShutdownChunkTest, "sctp"); + PTF_RUN_TEST(SctpForwardTsnChunkTest, "sctp"); + PTF_RUN_TEST(SctpIForwardTsnChunkTest, "sctp"); + PTF_RUN_TEST(SctpHeartbeatChunkTest, "sctp"); + PTF_RUN_TEST(SctpCookieEchoChunkTest, "sctp"); + PTF_RUN_TEST(SctpAuthChunkTest, "sctp"); + PTF_RUN_TEST(SctpIDataChunkTest, "sctp"); + PTF_RUN_TEST(SctpEcneCwrChunkTest, "sctp"); + PTF_RUN_TEST(SctpCwrChunkTest, "sctp"); + PTF_RUN_TEST(SctpAbortChunkTest, "sctp"); + PTF_RUN_TEST(SctpErrorChunkTest, "sctp"); + PTF_RUN_TEST(SctpOverIPv6Test, "sctp"); + PTF_RUN_TEST(SctpAsconfChunkTest, "sctp"); + PTF_RUN_TEST(SctpAddDataChunkTest, "sctp"); + PTF_RUN_TEST(SctpAddInitChunkTest, "sctp"); + PTF_RUN_TEST(SctpAddInitAckChunkTest, "sctp"); + PTF_RUN_TEST(SctpAddSackChunkTest, "sctp"); + PTF_RUN_TEST(SctpAddHeartbeatChunkTest, "sctp"); + PTF_RUN_TEST(SctpAddShutdownChunksTest, "sctp"); + PTF_RUN_TEST(SctpAddAbortChunkTest, "sctp"); + PTF_RUN_TEST(SctpAddCookieChunksTest, "sctp"); + PTF_RUN_TEST(SctpAddErrorChunkTest, "sctp"); + PTF_RUN_TEST(SctpMultipleChunkCreationTest, "sctp"); + PTF_RUN_TEST(SctpInitParameterIteratorTest, "sctp"); + PTF_RUN_TEST(SctpInitParameterIPv6Test, "sctp"); + PTF_RUN_TEST(SctpChunkPaddingTest, "sctp"); + PTF_RUN_TEST(SctpBundlingValidationTest, "sctp"); + PTF_RUN_TEST(SctpAddEcneCwrChunkTest, "sctp"); + PTF_RUN_TEST(SctpAddForwardTsnChunkTest, "sctp"); + PTF_RUN_TEST(SctpAddIDataChunkTest, "sctp"); + PTF_RUN_TEST(SctpAddIForwardTsnChunkTest, "sctp"); + PTF_RUN_TEST(SctpAddPadChunkTest, "sctp"); + PTF_RUN_TEST(SctpErrorCauseIteratorTest, "sctp"); + PTF_RUN_TEST(SctpStateCookieParameterTest, "sctp"); + PTF_RUN_TEST(SctpHostNameAddressDetectionTest, "sctp"); + PTF_RUN_TEST(SctpAuthParametersTest, "sctp"); + PTF_RUN_TEST(SctpAddAuthChunkTest, "sctp"); + PTF_RUN_TEST(SctpAddAsconfChunkTest, "sctp"); + PTF_RUN_TEST(SctpAddAsconfAckChunkTest, "sctp"); + PTF_RUN_TEST(SctpAddReconfigChunkTest, "sctp"); + PTF_RUN_TEST(SctpReconfigParameterIteratorTest, "sctp"); + PTF_RUN_TEST(SctpReconfigResponseTest, "sctp"); + PTF_RUN_TEST(SctpAsconfParameterIteratorTest, "sctp"); + PTF_RUN_TEST(SctpZeroChecksumParameterTest, "sctp"); + PTF_RUN_TEST(SctpAdditionalErrorCauseAccessorsTest, "sctp"); + PTF_RUN_TEST(SctpUnrecognizedChunkErrorTest, "sctp"); + PTF_RUN_TEST(SctpExtendedPpidEnumsTest, "sctp"); + PTF_RUN_TEST(SctpReconfigResultEnumsTest, "sctp"); + PTF_RUN_TEST(SctpEdmidEnumsTest, "sctp"); + PTF_RUN_TEST(SctpNrSackChunkParsingTest, "sctp"); + PTF_RUN_TEST(SctpAddNrSackChunkTest, "sctp"); + PTF_RUN_TEST(SctpAddNrSackChunkMinimalTest, "sctp"); + PTF_RUN_TEST(SctpHmacSha1ComputationTest, "sctp"); + PTF_RUN_TEST(SctpHmacSha256ComputationTest, "sctp"); + PTF_RUN_TEST(SctpHmacVerificationTest, "sctp"); + PTF_RUN_TEST(SctpHmacSizeConstantsTest, "sctp"); + PTF_RUN_TEST(SctpExtendedPpidEnumsNewTest, "sctp"); + PTF_RUN_TEST(SctpNrSackChunkTypeNameTest, "sctp"); + PTF_RUN_TEST(SctpChunkActionBitsTest, "sctp"); + PTF_RUN_TEST(SctpParamActionBitsTest, "sctp"); + PTF_RUN_TEST(SctpComputeAuthHmacTest, "sctp"); + PTF_RUN_TEST(SctpComputeAuthHmacSha256Test, "sctp"); + PTF_RUN_TEST(SctpComputeAuthHmacNoAuthChunkTest, "sctp"); + PTF_RUN_TEST(SctpExtendedPpidEnumsNewTelecomTest, "sctp"); + PTF_RUN_TEST(SctpDataChunkViewTest, "sctp"); + PTF_RUN_TEST(SctpInitChunkViewTest, "sctp"); + PTF_RUN_TEST(SctpInitAckChunkViewTest, "sctp"); + PTF_RUN_TEST(SctpSackChunkViewTest, "sctp"); + PTF_RUN_TEST(SctpChunkViewTypeSafetyTest, "sctp"); + PTF_RUN_TEST(SctpHeartbeatChunkViewTest, "sctp"); + PTF_RUN_TEST(SctpCookieEchoChunkViewTest, "sctp"); + PTF_RUN_TEST(SctpAbortChunkViewTest, "sctp"); + PTF_RUN_TEST(SctpShutdownChunkViewTest, "sctp"); + PTF_RUN_TEST(SctpShutdownAckChunkViewTest, "sctp"); + PTF_RUN_TEST(SctpShutdownCompleteChunkViewTest, "sctp"); + PTF_RUN_TEST(SctpControlChunkViewTypeSafetyTest, "sctp"); + PTF_RUN_TEST(SctpAuthChunkViewTest, "sctp"); + PTF_RUN_TEST(SctpForwardTsnChunkViewTest, "sctp"); + PTF_RUN_TEST(SctpIDataChunkViewTest, "sctp"); + PTF_RUN_TEST(SctpIForwardTsnChunkViewTest, "sctp"); + PTF_RUN_TEST(SctpNrSackChunkViewTest, "sctp"); + PTF_RUN_TEST(SctpPadChunkViewTest, "sctp"); + PTF_RUN_TEST(SctpExtensionChunkViewTypeSafetyTest, "sctp"); + PTF_END_RUNNING_TESTS; } diff --git a/Tests/Pcap++Test/Tests/FilterTests.cpp b/Tests/Pcap++Test/Tests/FilterTests.cpp index 9e6948de4c..2f84236636 100644 --- a/Tests/Pcap++Test/Tests/FilterTests.cpp +++ b/Tests/Pcap++Test/Tests/FilterTests.cpp @@ -46,11 +46,16 @@ PTF_TEST_CASE(TestPcapFiltersLive) DeviceTeardown devTeardown(liveDev); pcpp::RawPacketVector capturedPackets; + // Use the actual IP address from the device for filtering and assertions. + // The configured IP is used to find the interface, but the actual interface IP + // may differ (e.g., in CI environments with dynamic IP assignment). + pcpp::IPv4Address deviceIp = liveDev->getIPv4Address(); + //----------- // IP filter //----------- PTF_PRINT_VERBOSE("Testing IPFilter"); - std::string filterAddrAsString(PcapTestGlobalArgs.ipToSendReceivePackets); + std::string filterAddrAsString = deviceIp.toString(); pcpp::IPFilter ipFilter(filterAddrAsString, pcpp::DST); ipFilter.parseToString(filterAsString); PTF_ASSERT_TRUE(liveDev->setFilter(ipFilter)); @@ -67,7 +72,7 @@ PTF_TEST_CASE(TestPcapFiltersLive) pcpp::Packet packet(*iter); PTF_ASSERT_TRUE(packet.isPacketOfType(pcpp::IPv4)); pcpp::IPv4Layer* ipv4Layer = packet.getLayerOfType(); - PTF_ASSERT_EQUAL(ipv4Layer->getDstIPAddress(), ipToSearch); + PTF_ASSERT_EQUAL(ipv4Layer->getDstIPAddress(), deviceIp); } capturedPackets.clear(); @@ -120,7 +125,7 @@ PTF_TEST_CASE(TestPcapFiltersLive) pcpp::TcpLayer* tcpLayer = packet.getLayerOfType(); pcpp::IPv4Layer* ip4Layer = packet.getLayerOfType(); PTF_ASSERT_EQUAL(tcpLayer->getSrcPort(), 80); - PTF_ASSERT_EQUAL(ip4Layer->getDstIPAddress(), ipToSearch); + PTF_ASSERT_EQUAL(ip4Layer->getDstIPAddress(), deviceIp); } capturedPackets.clear(); @@ -153,14 +158,14 @@ PTF_TEST_CASE(TestPcapFiltersLive) pcpp::IPv4Layer* ip4Layer = packet.getLayerOfType(); if (ip4Layer != nullptr) { - srcIpMatch = ip4Layer->getSrcIPAddress() == ipToSearch; + srcIpMatch = ip4Layer->getSrcIPAddress() == deviceIp; } PTF_ASSERT_TRUE(srcIpMatch || srcPortMatch); } else if (packet.isPacketOfType(pcpp::IPv4)) { pcpp::IPv4Layer* ip4Layer = packet.getLayerOfType(); - PTF_ASSERT_EQUAL(ip4Layer->getSrcIPAddress(), ipToSearch); + PTF_ASSERT_EQUAL(ip4Layer->getSrcIPAddress(), deviceIp); } // else packet isn't of type IP or TCP } @@ -187,7 +192,7 @@ PTF_TEST_CASE(TestPcapFiltersLive) if (packet.isPacketOfType(pcpp::IPv4)) { pcpp::IPv4Layer* ipv4Layer = packet.getLayerOfType(); - PTF_ASSERT_NOT_EQUAL(ipv4Layer->getSrcIPAddress(), ipToSearch); + PTF_ASSERT_NOT_EQUAL(ipv4Layer->getSrcIPAddress(), deviceIp); } } capturedPackets.clear();