├── fuzz ├── .gitignore ├── artifacts │ ├── param │ │ ├── crash-216833e417069f431d0617fb4e9f8abe6c9a6c1d │ │ └── crash-fb1b644bc0d365ce2dc3c3ff77cd3c4cd8da528d │ └── packet │ │ ├── crash-16cad30042bc4791bd62c630a780add5d1220779 │ │ ├── crash-8b9b318a6b66ea23232a4e2aec91deeeca470af8 │ │ ├── crash-8d90dfc8fc34fa06f161f69617ee8f48dec434cd │ │ ├── crash-b836a20af7f8af85423dbe80565465b16bb7a16f │ │ └── crash-f940d9879efc88872145955bae11ca6ad6a4c044 ├── fuzz_targets │ ├── packet.rs │ └── param.rs └── Cargo.toml ├── doc └── webrtc.rs.png ├── src ├── timer │ ├── mod.rs │ ├── ack_timer.rs │ └── rtx_timer.rs ├── queue │ ├── control_queue.rs │ ├── mod.rs │ ├── pending_queue.rs │ ├── payload_queue.rs │ └── reassembly_queue.rs ├── lib.rs ├── param │ ├── param_random.rs │ ├── param_heartbeat_info.rs │ ├── param_forward_tsn_supported.rs │ ├── param_header.rs │ ├── param_state_cookie.rs │ ├── param_supported_extensions.rs │ ├── param_chunk_list.rs │ ├── param_unknown.rs │ ├── param_unrecognized.rs │ ├── param_requested_hmac_algorithm.rs │ ├── mod.rs │ ├── param_reconfig_response.rs │ ├── param_outgoing_reset_request.rs │ ├── param_type.rs │ └── param_test.rs ├── chunk │ ├── mod.rs │ ├── chunk_unknown.rs │ ├── chunk_cookie_ack.rs │ ├── chunk_shutdown_ack.rs │ ├── chunk_shutdown_complete.rs │ ├── chunk_cookie_echo.rs │ ├── chunk_shutdown.rs │ ├── chunk_abort.rs │ ├── chunk_error.rs │ ├── chunk_heartbeat.rs │ ├── chunk_type.rs │ ├── chunk_header.rs │ ├── chunk_heartbeat_ack.rs │ ├── chunk_reconfig.rs │ ├── chunk_forward_tsn.rs │ ├── chunk_selective_ack.rs │ └── chunk_payload_data.rs ├── fuzz_artifact_test.rs ├── association │ └── association_stats.rs ├── error_cause.rs ├── stream │ └── stream_test.rs ├── error.rs └── util.rs ├── README.md ├── .github ├── actions-rs │ └── grcov.yml └── workflows │ ├── cargo.yml │ └── grcov.yml ├── .gitignore ├── codecov.yml ├── Cargo.toml ├── LICENSE-MIT ├── examples ├── pong.rs └── ping.rs └── LICENSE-APACHE /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | corpus 3 | artifacts 4 | -------------------------------------------------------------------------------- /fuzz/artifacts/param/crash-216833e417069f431d0617fb4e9f8abe6c9a6c1d: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /doc/webrtc.rs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webrtc-rs/sctp/HEAD/doc/webrtc.rs.png -------------------------------------------------------------------------------- /src/timer/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod timer_test; 3 | 4 | pub(crate) mod ack_timer; 5 | pub(crate) mod rtx_timer; 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crate moved 2 | 3 | As of the 23rd of August 2022 this crate has been migrated to the [`webrtc-rs/webrtc`](http://github.com/webrtc-rs/webrtc/) monorepo. 4 | -------------------------------------------------------------------------------- /src/queue/control_queue.rs: -------------------------------------------------------------------------------- 1 | use crate::packet::Packet; 2 | 3 | use std::collections::VecDeque; 4 | 5 | /// control queue 6 | pub(crate) type ControlQueue = VecDeque; 7 | -------------------------------------------------------------------------------- /fuzz/artifacts/packet/crash-16cad30042bc4791bd62c630a780add5d1220779: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webrtc-rs/sctp/HEAD/fuzz/artifacts/packet/crash-16cad30042bc4791bd62c630a780add5d1220779 -------------------------------------------------------------------------------- /fuzz/artifacts/packet/crash-8b9b318a6b66ea23232a4e2aec91deeeca470af8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webrtc-rs/sctp/HEAD/fuzz/artifacts/packet/crash-8b9b318a6b66ea23232a4e2aec91deeeca470af8 -------------------------------------------------------------------------------- /fuzz/artifacts/packet/crash-8d90dfc8fc34fa06f161f69617ee8f48dec434cd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webrtc-rs/sctp/HEAD/fuzz/artifacts/packet/crash-8d90dfc8fc34fa06f161f69617ee8f48dec434cd -------------------------------------------------------------------------------- /fuzz/artifacts/packet/crash-b836a20af7f8af85423dbe80565465b16bb7a16f: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webrtc-rs/sctp/HEAD/fuzz/artifacts/packet/crash-b836a20af7f8af85423dbe80565465b16bb7a16f -------------------------------------------------------------------------------- /fuzz/artifacts/packet/crash-f940d9879efc88872145955bae11ca6ad6a4c044: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webrtc-rs/sctp/HEAD/fuzz/artifacts/packet/crash-f940d9879efc88872145955bae11ca6ad6a4c044 -------------------------------------------------------------------------------- /fuzz/artifacts/param/crash-fb1b644bc0d365ce2dc3c3ff77cd3c4cd8da528d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webrtc-rs/sctp/HEAD/fuzz/artifacts/param/crash-fb1b644bc0d365ce2dc3c3ff77cd3c4cd8da528d -------------------------------------------------------------------------------- /src/queue/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod queue_test; 3 | 4 | pub(crate) mod control_queue; 5 | pub(crate) mod payload_queue; 6 | pub(crate) mod pending_queue; 7 | pub(crate) mod reassembly_queue; 8 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/packet.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | 4 | use webrtc_sctp::packet::Packet; 5 | use bytes::Bytes; 6 | 7 | fuzz_target!(|data: &[u8]| { 8 | let bytes = Bytes::from(data.to_vec()); 9 | Packet::unmarshal(&bytes); 10 | }); 11 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/param.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | 4 | use webrtc_sctp::param::build_param; 5 | use bytes::Bytes; 6 | 7 | fuzz_target!(|data: &[u8]| { 8 | let bytes = Bytes::from(data.to_vec()); 9 | build_param(&bytes); 10 | }); 11 | -------------------------------------------------------------------------------- /.github/actions-rs/grcov.yml: -------------------------------------------------------------------------------- 1 | branch: true 2 | ignore-not-existing: true 3 | llvm: true 4 | filter: covered 5 | output-type: lcov 6 | output-path: ./lcov.info 7 | source-dir: . 8 | ignore: 9 | - "/*" 10 | - "C:/*" 11 | - "../*" 12 | excl-line: "#\\[derive\\(" 13 | excl-start: "mod tests \\{" 14 | excl-br-line: "#\\[derive\\(" 15 | excl-br-start: "mod tests \\{" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | /.idea/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(rust_2018_idioms)] 2 | #![allow(dead_code)] 3 | 4 | pub mod association; 5 | pub mod chunk; 6 | mod error; 7 | pub mod error_cause; 8 | pub mod packet; 9 | pub mod param; 10 | pub(crate) mod queue; 11 | pub mod stream; 12 | pub(crate) mod timer; 13 | pub(crate) mod util; 14 | 15 | pub use error::Error; 16 | 17 | #[cfg(test)] 18 | mod fuzz_artifact_test; 19 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: yes 3 | max_report_age: off 4 | token: 9ec6d495-dfa7-4250-afeb-dbf342009340 5 | 6 | coverage: 7 | precision: 2 8 | round: down 9 | range: 50..90 10 | status: 11 | project: 12 | default: 13 | enabled: no 14 | threshold: 0.2 15 | if_not_found: success 16 | patch: 17 | default: 18 | enabled: no 19 | if_not_found: success 20 | changes: 21 | default: 22 | enabled: no 23 | if_not_found: success 24 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "webrtc-sctp-fuzz" 3 | version = "0.0.0" 4 | authors = ["Automatically generated"] 5 | publish = false 6 | edition = "2018" 7 | 8 | [package.metadata] 9 | cargo-fuzz = true 10 | 11 | [dependencies] 12 | libfuzzer-sys = "0.4" 13 | bytes = "*" 14 | 15 | [dependencies.webrtc-sctp] 16 | path = ".." 17 | 18 | # Prevent this from interfering with workspaces 19 | [workspace] 20 | members = ["."] 21 | 22 | [[bin]] 23 | name = "packet" 24 | path = "fuzz_targets/packet.rs" 25 | test = false 26 | doc = false 27 | 28 | [[bin]] 29 | name = "param" 30 | path = "fuzz_targets/param.rs" 31 | test = false 32 | doc = false 33 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "webrtc-sctp" 3 | version = "0.6.0" 4 | authors = ["Rain Liu "] 5 | edition = "2018" 6 | description = "A pure Rust implementation of SCTP" 7 | license = "MIT/Apache-2.0" 8 | documentation = "https://docs.rs/webrtc-sctp" 9 | homepage = "https://webrtc.rs" 10 | repository = "https://github.com/webrtc-rs/sctp" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | util = { package = "webrtc-util", version = "0.5.4", default-features = false, features = ["conn"] } 16 | tokio = { version = "1.19", features = ["full"] } 17 | bytes = "1" 18 | rand = "0.8.5" 19 | crc = "3.0" 20 | async-trait = "0.1.56" 21 | log = "0.4" 22 | thiserror = "1.0" 23 | 24 | [dev-dependencies] 25 | tokio-test = "0.4.0" # must match the min version of the `tokio` crate above 26 | lazy_static = "1.4.0" 27 | env_logger = "0.9.0" 28 | chrono = "0.4.19" 29 | clap = "3.2.6" 30 | 31 | [[example]] 32 | name = "ping" 33 | path = "examples/ping.rs" 34 | bench = false 35 | 36 | [[example]] 37 | name = "pong" 38 | path = "examples/pong.rs" 39 | bench = false 40 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 WebRTC.rs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/param/param_random.rs: -------------------------------------------------------------------------------- 1 | use super::{param_header::*, param_type::*, *}; 2 | 3 | use bytes::{Bytes, BytesMut}; 4 | 5 | #[derive(Default, Debug, Clone, PartialEq)] 6 | pub(crate) struct ParamRandom { 7 | pub(crate) random_data: Bytes, 8 | } 9 | 10 | impl fmt::Display for ParamRandom { 11 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 12 | write!(f, "{} {:?}", self.header(), self.random_data) 13 | } 14 | } 15 | 16 | impl Param for ParamRandom { 17 | fn header(&self) -> ParamHeader { 18 | ParamHeader { 19 | typ: ParamType::Random, 20 | value_length: self.value_length() as u16, 21 | } 22 | } 23 | 24 | fn unmarshal(raw: &Bytes) -> Result { 25 | let header = ParamHeader::unmarshal(raw)?; 26 | let random_data = 27 | raw.slice(PARAM_HEADER_LENGTH..PARAM_HEADER_LENGTH + header.value_length()); 28 | Ok(ParamRandom { random_data }) 29 | } 30 | 31 | fn marshal_to(&self, buf: &mut BytesMut) -> Result { 32 | self.header().marshal_to(buf)?; 33 | buf.extend(self.random_data.clone()); 34 | Ok(buf.len()) 35 | } 36 | 37 | fn value_length(&self) -> usize { 38 | self.random_data.len() 39 | } 40 | 41 | fn clone_to(&self) -> Box { 42 | Box::new(self.clone()) 43 | } 44 | 45 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 46 | self 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/chunk/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod chunk_test; 3 | 4 | pub(crate) mod chunk_abort; 5 | pub(crate) mod chunk_cookie_ack; 6 | pub(crate) mod chunk_cookie_echo; 7 | pub(crate) mod chunk_error; 8 | pub(crate) mod chunk_forward_tsn; 9 | pub(crate) mod chunk_header; 10 | pub(crate) mod chunk_heartbeat; 11 | pub(crate) mod chunk_heartbeat_ack; 12 | pub(crate) mod chunk_init; 13 | pub mod chunk_payload_data; 14 | pub(crate) mod chunk_reconfig; 15 | pub(crate) mod chunk_selective_ack; 16 | pub(crate) mod chunk_shutdown; 17 | pub(crate) mod chunk_shutdown_ack; 18 | pub(crate) mod chunk_shutdown_complete; 19 | pub(crate) mod chunk_type; 20 | pub(crate) mod chunk_unknown; 21 | 22 | use crate::error::{Error, Result}; 23 | use chunk_header::*; 24 | 25 | use bytes::{Bytes, BytesMut}; 26 | use std::marker::Sized; 27 | use std::{any::Any, fmt}; 28 | 29 | pub(crate) trait Chunk: fmt::Display + fmt::Debug { 30 | fn header(&self) -> ChunkHeader; 31 | fn unmarshal(raw: &Bytes) -> Result 32 | where 33 | Self: Sized; 34 | fn marshal_to(&self, buf: &mut BytesMut) -> Result; 35 | fn check(&self) -> Result<()>; 36 | fn value_length(&self) -> usize; 37 | fn as_any(&self) -> &(dyn Any + Send + Sync); 38 | 39 | fn marshal(&self) -> Result { 40 | let capacity = CHUNK_HEADER_SIZE + self.value_length(); 41 | let mut buf = BytesMut::with_capacity(capacity); 42 | self.marshal_to(&mut buf)?; 43 | Ok(buf.freeze()) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/chunk/chunk_unknown.rs: -------------------------------------------------------------------------------- 1 | use crate::chunk::chunk_header::{ChunkHeader, CHUNK_HEADER_SIZE}; 2 | use crate::chunk::Chunk; 3 | use bytes::{Bytes, BytesMut}; 4 | use std::any::Any; 5 | use std::fmt::{Debug, Display, Formatter}; 6 | 7 | #[derive(Clone, Debug)] 8 | pub struct ChunkUnknown { 9 | hdr: ChunkHeader, 10 | value: Bytes, 11 | } 12 | 13 | impl Display for ChunkUnknown { 14 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 15 | write!(f, "ChunkUnknown( {} {:?} )", self.hdr, self.value) 16 | } 17 | } 18 | 19 | impl Chunk for ChunkUnknown { 20 | fn header(&self) -> ChunkHeader { 21 | self.hdr.clone() 22 | } 23 | 24 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 25 | self 26 | } 27 | 28 | fn check(&self) -> crate::error::Result<()> { 29 | Ok(()) 30 | } 31 | 32 | fn value_length(&self) -> usize { 33 | self.value.len() 34 | } 35 | 36 | fn marshal_to(&self, buf: &mut BytesMut) -> crate::error::Result { 37 | self.header().marshal_to(buf)?; 38 | buf.extend(&self.value); 39 | Ok(buf.len()) 40 | } 41 | 42 | fn unmarshal(raw: &Bytes) -> crate::error::Result 43 | where 44 | Self: Sized, 45 | { 46 | let header = ChunkHeader::unmarshal(raw)?; 47 | let len = header.value_length(); 48 | Ok(Self { 49 | hdr: header, 50 | value: raw.slice(CHUNK_HEADER_SIZE..CHUNK_HEADER_SIZE + len), 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/param/param_heartbeat_info.rs: -------------------------------------------------------------------------------- 1 | use super::{param_header::*, param_type::*, *}; 2 | 3 | use bytes::{Bytes, BytesMut}; 4 | 5 | #[derive(Default, Debug, Clone, PartialEq)] 6 | pub(crate) struct ParamHeartbeatInfo { 7 | pub(crate) heartbeat_information: Bytes, 8 | } 9 | 10 | impl fmt::Display for ParamHeartbeatInfo { 11 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 12 | write!(f, "{} {:?}", self.header(), self.heartbeat_information) 13 | } 14 | } 15 | 16 | impl Param for ParamHeartbeatInfo { 17 | fn header(&self) -> ParamHeader { 18 | ParamHeader { 19 | typ: ParamType::HeartbeatInfo, 20 | value_length: self.value_length() as u16, 21 | } 22 | } 23 | 24 | fn unmarshal(raw: &Bytes) -> Result { 25 | let header = ParamHeader::unmarshal(raw)?; 26 | let heartbeat_information = 27 | raw.slice(PARAM_HEADER_LENGTH..PARAM_HEADER_LENGTH + header.value_length()); 28 | Ok(ParamHeartbeatInfo { 29 | heartbeat_information, 30 | }) 31 | } 32 | 33 | fn marshal_to(&self, buf: &mut BytesMut) -> Result { 34 | self.header().marshal_to(buf)?; 35 | buf.extend(self.heartbeat_information.clone()); 36 | Ok(buf.len()) 37 | } 38 | 39 | fn value_length(&self) -> usize { 40 | self.heartbeat_information.len() 41 | } 42 | 43 | fn clone_to(&self) -> Box { 44 | Box::new(self.clone()) 45 | } 46 | 47 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 48 | self 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/fuzz_artifact_test.rs: -------------------------------------------------------------------------------- 1 | //! # What are these tests? 2 | //! 3 | //! These tests ensure that regressions in the unmarshaling code are caught. 4 | //! 5 | //! They check all artifacts of the fuzzer that crashed this lib, and make sure they no longer crash the library. 6 | //! 7 | //! The content of the files is mostly garbage, but it triggers "intersting" behaviour in the unmarshaling code. 8 | //! So if your change fails one of these tests you probably made an error somwhere. 9 | //! 10 | //! Sadly these tests cannot really tell you where your error is specifically outside the standard backtrace rust will provide to you. Sorry. 11 | 12 | use bytes::Bytes; 13 | 14 | #[test] 15 | fn param_crash_artifacts() { 16 | for artifact in std::fs::read_dir("fuzz/artifacts/param").unwrap() { 17 | let artifact = artifact.unwrap(); 18 | if artifact 19 | .file_name() 20 | .into_string() 21 | .unwrap() 22 | .starts_with("crash-") 23 | { 24 | let artifact = std::fs::read(artifact.path()).unwrap(); 25 | crate::param::build_param(&Bytes::from(artifact)).ok(); 26 | } 27 | } 28 | } 29 | 30 | #[test] 31 | fn packet_crash_artifacts() { 32 | for artifact in std::fs::read_dir("fuzz/artifacts/packet").unwrap() { 33 | let artifact = artifact.unwrap(); 34 | if artifact 35 | .file_name() 36 | .into_string() 37 | .unwrap() 38 | .starts_with("crash-") 39 | { 40 | let artifact = std::fs::read(artifact.path()).unwrap(); 41 | crate::packet::Packet::unmarshal(&Bytes::from(artifact)).ok(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/param/param_forward_tsn_supported.rs: -------------------------------------------------------------------------------- 1 | use super::{param_header::*, param_type::*, *}; 2 | 3 | use bytes::{Bytes, BytesMut}; 4 | 5 | /// At the initialization of the association, the sender of the INIT or 6 | /// INIT ACK chunk MAY include this OPTIONAL parameter to inform its peer 7 | /// that it is able to support the Forward TSN chunk 8 | /// 9 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 10 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 11 | ///| Parameter Type = 49152 | Parameter Length = 4 | 12 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 13 | #[derive(Default, Debug, Clone, PartialEq)] 14 | pub(crate) struct ParamForwardTsnSupported; 15 | 16 | impl fmt::Display for ParamForwardTsnSupported { 17 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 18 | write!(f, "{}", self.header()) 19 | } 20 | } 21 | 22 | impl Param for ParamForwardTsnSupported { 23 | fn header(&self) -> ParamHeader { 24 | ParamHeader { 25 | typ: ParamType::ForwardTsnSupp, 26 | value_length: self.value_length() as u16, 27 | } 28 | } 29 | 30 | fn unmarshal(raw: &Bytes) -> Result { 31 | let _ = ParamHeader::unmarshal(raw)?; 32 | Ok(ParamForwardTsnSupported {}) 33 | } 34 | 35 | fn marshal_to(&self, buf: &mut BytesMut) -> Result { 36 | self.header().marshal_to(buf)?; 37 | Ok(buf.len()) 38 | } 39 | 40 | fn value_length(&self) -> usize { 41 | 0 42 | } 43 | 44 | fn clone_to(&self) -> Box { 45 | Box::new(self.clone()) 46 | } 47 | 48 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 49 | self 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/chunk/chunk_cookie_ack.rs: -------------------------------------------------------------------------------- 1 | use super::{chunk_header::*, chunk_type::*, *}; 2 | 3 | use bytes::{Bytes, BytesMut}; 4 | use std::fmt; 5 | 6 | /// chunkCookieAck represents an SCTP Chunk of type chunkCookieAck 7 | /// 8 | /// 0 1 2 3 9 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 10 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 11 | /// | Type = 11 |Chunk Flags | Length = 4 | 12 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 13 | #[derive(Debug, Clone)] 14 | pub(crate) struct ChunkCookieAck; 15 | 16 | /// makes ChunkCookieAck printable 17 | impl fmt::Display for ChunkCookieAck { 18 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 19 | write!(f, "{}", self.header()) 20 | } 21 | } 22 | 23 | impl Chunk for ChunkCookieAck { 24 | fn header(&self) -> ChunkHeader { 25 | ChunkHeader { 26 | typ: CT_COOKIE_ACK, 27 | flags: 0, 28 | value_length: self.value_length() as u16, 29 | } 30 | } 31 | 32 | fn unmarshal(raw: &Bytes) -> Result { 33 | let header = ChunkHeader::unmarshal(raw)?; 34 | 35 | if header.typ != CT_COOKIE_ACK { 36 | return Err(Error::ErrChunkTypeNotCookieAck); 37 | } 38 | 39 | Ok(ChunkCookieAck {}) 40 | } 41 | 42 | fn marshal_to(&self, buf: &mut BytesMut) -> Result { 43 | self.header().marshal_to(buf)?; 44 | Ok(buf.len()) 45 | } 46 | 47 | fn check(&self) -> Result<()> { 48 | Ok(()) 49 | } 50 | 51 | fn value_length(&self) -> usize { 52 | 0 53 | } 54 | 55 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 56 | self 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/chunk/chunk_shutdown_ack.rs: -------------------------------------------------------------------------------- 1 | use super::{chunk_header::*, chunk_type::*, *}; 2 | 3 | use bytes::{Bytes, BytesMut}; 4 | use std::fmt; 5 | 6 | ///chunkShutdownAck represents an SCTP Chunk of type chunkShutdownAck 7 | /// 8 | ///0 1 2 3 9 | ///0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 10 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 11 | ///| Type = 8 | Chunk Flags | Length = 4 | 12 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 13 | #[derive(Default, Debug, Clone)] 14 | pub(crate) struct ChunkShutdownAck; 15 | 16 | /// makes chunkShutdownAck printable 17 | impl fmt::Display for ChunkShutdownAck { 18 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 19 | write!(f, "{}", self.header()) 20 | } 21 | } 22 | 23 | impl Chunk for ChunkShutdownAck { 24 | fn header(&self) -> ChunkHeader { 25 | ChunkHeader { 26 | typ: CT_SHUTDOWN_ACK, 27 | flags: 0, 28 | value_length: self.value_length() as u16, 29 | } 30 | } 31 | 32 | fn unmarshal(raw: &Bytes) -> Result { 33 | let header = ChunkHeader::unmarshal(raw)?; 34 | 35 | if header.typ != CT_SHUTDOWN_ACK { 36 | return Err(Error::ErrChunkTypeNotShutdownAck); 37 | } 38 | 39 | Ok(ChunkShutdownAck {}) 40 | } 41 | 42 | fn marshal_to(&self, writer: &mut BytesMut) -> Result { 43 | self.header().marshal_to(writer)?; 44 | Ok(writer.len()) 45 | } 46 | 47 | fn check(&self) -> Result<()> { 48 | Ok(()) 49 | } 50 | 51 | fn value_length(&self) -> usize { 52 | 0 53 | } 54 | 55 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 56 | self 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/param/param_header.rs: -------------------------------------------------------------------------------- 1 | use super::{param_type::*, *}; 2 | 3 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 4 | use std::fmt; 5 | 6 | #[derive(Debug, Clone, PartialEq)] 7 | pub(crate) struct ParamHeader { 8 | pub(crate) typ: ParamType, 9 | pub(crate) value_length: u16, 10 | } 11 | 12 | pub(crate) const PARAM_HEADER_LENGTH: usize = 4; 13 | 14 | /// String makes paramHeader printable 15 | impl fmt::Display for ParamHeader { 16 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 17 | write!(f, "{}", self.typ) 18 | } 19 | } 20 | 21 | impl Param for ParamHeader { 22 | fn header(&self) -> ParamHeader { 23 | self.clone() 24 | } 25 | 26 | fn unmarshal(raw: &Bytes) -> Result { 27 | if raw.len() < PARAM_HEADER_LENGTH { 28 | return Err(Error::ErrParamHeaderTooShort); 29 | } 30 | 31 | let reader = &mut raw.clone(); 32 | 33 | let typ: ParamType = reader.get_u16().into(); 34 | 35 | let len = reader.get_u16() as usize; 36 | if len < PARAM_HEADER_LENGTH || raw.len() < len { 37 | return Err(Error::ErrParamHeaderTooShort); 38 | } 39 | 40 | Ok(ParamHeader { 41 | typ, 42 | value_length: (len - PARAM_HEADER_LENGTH) as u16, 43 | }) 44 | } 45 | 46 | fn marshal_to(&self, writer: &mut BytesMut) -> Result { 47 | writer.put_u16(self.typ.into()); 48 | writer.put_u16(self.value_length + PARAM_HEADER_LENGTH as u16); 49 | Ok(writer.len()) 50 | } 51 | 52 | fn value_length(&self) -> usize { 53 | self.value_length as usize 54 | } 55 | 56 | fn clone_to(&self) -> Box { 57 | Box::new(self.clone()) 58 | } 59 | 60 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 61 | self 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/param/param_state_cookie.rs: -------------------------------------------------------------------------------- 1 | use super::{param_header::*, param_type::*, *}; 2 | 3 | use bytes::{Bytes, BytesMut}; 4 | use rand::Rng; 5 | use std::fmt; 6 | 7 | #[derive(Default, Debug, Clone, PartialEq)] 8 | pub(crate) struct ParamStateCookie { 9 | pub(crate) cookie: Bytes, 10 | } 11 | 12 | /// String makes paramStateCookie printable 13 | impl fmt::Display for ParamStateCookie { 14 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 15 | write!(f, "{}: {:?}", self.header(), self.cookie) 16 | } 17 | } 18 | 19 | impl Param for ParamStateCookie { 20 | fn header(&self) -> ParamHeader { 21 | ParamHeader { 22 | typ: ParamType::StateCookie, 23 | value_length: self.value_length() as u16, 24 | } 25 | } 26 | 27 | fn unmarshal(raw: &Bytes) -> Result { 28 | let header = ParamHeader::unmarshal(raw)?; 29 | let cookie = raw.slice(PARAM_HEADER_LENGTH..PARAM_HEADER_LENGTH + header.value_length()); 30 | Ok(ParamStateCookie { cookie }) 31 | } 32 | 33 | fn marshal_to(&self, buf: &mut BytesMut) -> Result { 34 | self.header().marshal_to(buf)?; 35 | buf.extend(self.cookie.clone()); 36 | Ok(buf.len()) 37 | } 38 | 39 | fn value_length(&self) -> usize { 40 | self.cookie.len() 41 | } 42 | 43 | fn clone_to(&self) -> Box { 44 | Box::new(self.clone()) 45 | } 46 | 47 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 48 | self 49 | } 50 | } 51 | 52 | impl ParamStateCookie { 53 | pub(crate) fn new() -> Self { 54 | let mut cookie = BytesMut::new(); 55 | cookie.resize(32, 0); 56 | rand::thread_rng().fill(cookie.as_mut()); 57 | 58 | ParamStateCookie { 59 | cookie: cookie.freeze(), 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/chunk/chunk_shutdown_complete.rs: -------------------------------------------------------------------------------- 1 | use super::{chunk_header::*, chunk_type::*, *}; 2 | 3 | use bytes::{Bytes, BytesMut}; 4 | use std::fmt; 5 | 6 | ///chunkShutdownComplete represents an SCTP Chunk of type chunkShutdownComplete 7 | /// 8 | ///0 1 2 3 9 | ///0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 10 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 11 | ///| Type = 14 |Reserved |T| Length = 4 | 12 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 13 | #[derive(Default, Debug, Clone)] 14 | pub(crate) struct ChunkShutdownComplete; 15 | 16 | /// makes chunkShutdownComplete printable 17 | impl fmt::Display for ChunkShutdownComplete { 18 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 19 | write!(f, "{}", self.header()) 20 | } 21 | } 22 | 23 | impl Chunk for ChunkShutdownComplete { 24 | fn header(&self) -> ChunkHeader { 25 | ChunkHeader { 26 | typ: CT_SHUTDOWN_COMPLETE, 27 | flags: 0, 28 | value_length: self.value_length() as u16, 29 | } 30 | } 31 | 32 | fn unmarshal(raw: &Bytes) -> Result { 33 | let header = ChunkHeader::unmarshal(raw)?; 34 | 35 | if header.typ != CT_SHUTDOWN_COMPLETE { 36 | return Err(Error::ErrChunkTypeNotShutdownComplete); 37 | } 38 | 39 | Ok(ChunkShutdownComplete {}) 40 | } 41 | 42 | fn marshal_to(&self, writer: &mut BytesMut) -> Result { 43 | self.header().marshal_to(writer)?; 44 | Ok(writer.len()) 45 | } 46 | 47 | fn check(&self) -> Result<()> { 48 | Ok(()) 49 | } 50 | 51 | fn value_length(&self) -> usize { 52 | 0 53 | } 54 | 55 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 56 | self 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/association/association_stats.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicU64, Ordering}; 2 | 3 | #[derive(Default, Debug)] 4 | pub(crate) struct AssociationStats { 5 | n_datas: AtomicU64, 6 | n_sacks: AtomicU64, 7 | n_t3timeouts: AtomicU64, 8 | n_ack_timeouts: AtomicU64, 9 | n_fast_retrans: AtomicU64, 10 | } 11 | 12 | impl AssociationStats { 13 | pub(crate) fn inc_datas(&self) { 14 | self.n_datas.fetch_add(1, Ordering::SeqCst); 15 | } 16 | 17 | pub(crate) fn get_num_datas(&self) -> u64 { 18 | self.n_datas.load(Ordering::SeqCst) 19 | } 20 | 21 | pub(crate) fn inc_sacks(&self) { 22 | self.n_sacks.fetch_add(1, Ordering::SeqCst); 23 | } 24 | 25 | pub(crate) fn get_num_sacks(&self) -> u64 { 26 | self.n_sacks.load(Ordering::SeqCst) 27 | } 28 | 29 | pub(crate) fn inc_t3timeouts(&self) { 30 | self.n_t3timeouts.fetch_add(1, Ordering::SeqCst); 31 | } 32 | 33 | pub(crate) fn get_num_t3timeouts(&self) -> u64 { 34 | self.n_t3timeouts.load(Ordering::SeqCst) 35 | } 36 | 37 | pub(crate) fn inc_ack_timeouts(&self) { 38 | self.n_ack_timeouts.fetch_add(1, Ordering::SeqCst); 39 | } 40 | 41 | pub(crate) fn get_num_ack_timeouts(&self) -> u64 { 42 | self.n_ack_timeouts.load(Ordering::SeqCst) 43 | } 44 | 45 | pub(crate) fn inc_fast_retrans(&self) { 46 | self.n_fast_retrans.fetch_add(1, Ordering::SeqCst); 47 | } 48 | 49 | pub(crate) fn get_num_fast_retrans(&self) -> u64 { 50 | self.n_fast_retrans.load(Ordering::SeqCst) 51 | } 52 | 53 | pub(crate) fn reset(&self) { 54 | self.n_datas.store(0, Ordering::SeqCst); 55 | self.n_sacks.store(0, Ordering::SeqCst); 56 | self.n_t3timeouts.store(0, Ordering::SeqCst); 57 | self.n_ack_timeouts.store(0, Ordering::SeqCst); 58 | self.n_fast_retrans.store(0, Ordering::SeqCst); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/param/param_supported_extensions.rs: -------------------------------------------------------------------------------- 1 | use super::{param_header::*, param_type::*, *}; 2 | use crate::chunk::chunk_type::*; 3 | 4 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 5 | 6 | #[derive(Default, Debug, Clone, PartialEq)] 7 | pub(crate) struct ParamSupportedExtensions { 8 | pub(crate) chunk_types: Vec, 9 | } 10 | 11 | impl fmt::Display for ParamSupportedExtensions { 12 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 13 | write!( 14 | f, 15 | "{} {}", 16 | self.header(), 17 | self.chunk_types 18 | .iter() 19 | .map(|ct| ct.to_string()) 20 | .collect::>() 21 | .join(" "), 22 | ) 23 | } 24 | } 25 | 26 | impl Param for ParamSupportedExtensions { 27 | fn header(&self) -> ParamHeader { 28 | ParamHeader { 29 | typ: ParamType::SupportedExt, 30 | value_length: self.value_length() as u16, 31 | } 32 | } 33 | 34 | fn unmarshal(raw: &Bytes) -> Result { 35 | let header = ParamHeader::unmarshal(raw)?; 36 | 37 | let reader = 38 | &mut raw.slice(PARAM_HEADER_LENGTH..PARAM_HEADER_LENGTH + header.value_length()); 39 | 40 | let mut chunk_types = vec![]; 41 | while reader.has_remaining() { 42 | chunk_types.push(ChunkType(reader.get_u8())); 43 | } 44 | 45 | Ok(ParamSupportedExtensions { chunk_types }) 46 | } 47 | 48 | fn marshal_to(&self, buf: &mut BytesMut) -> Result { 49 | self.header().marshal_to(buf)?; 50 | for ct in &self.chunk_types { 51 | buf.put_u8(ct.0); 52 | } 53 | Ok(buf.len()) 54 | } 55 | 56 | fn value_length(&self) -> usize { 57 | self.chunk_types.len() 58 | } 59 | 60 | fn clone_to(&self) -> Box { 61 | Box::new(self.clone()) 62 | } 63 | 64 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 65 | self 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/param/param_chunk_list.rs: -------------------------------------------------------------------------------- 1 | use super::{param_header::*, param_type::*, *}; 2 | use crate::chunk::chunk_type::*; 3 | 4 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 5 | 6 | #[derive(Default, Debug, Clone, PartialEq)] 7 | pub(crate) struct ParamChunkList { 8 | pub(crate) chunk_types: Vec, 9 | } 10 | 11 | impl fmt::Display for ParamChunkList { 12 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 13 | write!( 14 | f, 15 | "{} {}", 16 | self.header(), 17 | self.chunk_types 18 | .iter() 19 | .map(|ct| ct.to_string()) 20 | .collect::>() 21 | .join(" ") 22 | ) 23 | } 24 | } 25 | 26 | impl Param for ParamChunkList { 27 | fn header(&self) -> ParamHeader { 28 | ParamHeader { 29 | typ: ParamType::ChunkList, 30 | value_length: self.value_length() as u16, 31 | } 32 | } 33 | 34 | fn unmarshal(raw: &Bytes) -> Result { 35 | let header = ParamHeader::unmarshal(raw)?; 36 | 37 | if header.typ != ParamType::ChunkList { 38 | return Err(Error::ErrParamTypeUnexpected); 39 | } 40 | 41 | let reader = 42 | &mut raw.slice(PARAM_HEADER_LENGTH..PARAM_HEADER_LENGTH + header.value_length()); 43 | 44 | let mut chunk_types = vec![]; 45 | while reader.has_remaining() { 46 | chunk_types.push(ChunkType(reader.get_u8())); 47 | } 48 | 49 | Ok(ParamChunkList { chunk_types }) 50 | } 51 | 52 | fn marshal_to(&self, buf: &mut BytesMut) -> Result { 53 | self.header().marshal_to(buf)?; 54 | for ct in &self.chunk_types { 55 | buf.put_u8(ct.0); 56 | } 57 | Ok(buf.len()) 58 | } 59 | 60 | fn value_length(&self) -> usize { 61 | self.chunk_types.len() 62 | } 63 | 64 | fn clone_to(&self) -> Box { 65 | Box::new(self.clone()) 66 | } 67 | 68 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 69 | self 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/param/param_unknown.rs: -------------------------------------------------------------------------------- 1 | use crate::param::param_header::ParamHeader; 2 | use crate::param::param_header::PARAM_HEADER_LENGTH; 3 | use crate::param::param_type::ParamType; 4 | use crate::param::Param; 5 | use bytes::{Bytes, BytesMut}; 6 | use std::any::Any; 7 | use std::fmt::{Debug, Display, Formatter}; 8 | 9 | /// This type is meant to represent ANY parameter for un/remarshaling purposes, where we do not have a more specific type for it. 10 | /// This means we do not really understand the semantics of the param but can represent it. 11 | /// 12 | /// This is useful for usage in e.g.`ParamUnrecognized` where we want to report some unrecognized params back to the sender. 13 | #[derive(Clone, Debug, PartialEq, Eq)] 14 | pub struct ParamUnknown { 15 | typ: u16, 16 | value: Bytes, 17 | } 18 | 19 | impl Display for ParamUnknown { 20 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 21 | write!(f, "ParamUnknown( {} {:?} )", self.header(), self.value) 22 | } 23 | } 24 | 25 | impl Param for ParamUnknown { 26 | fn header(&self) -> ParamHeader { 27 | ParamHeader { 28 | typ: ParamType::Unknown { 29 | param_type: self.typ, 30 | }, 31 | value_length: self.value.len() as u16, 32 | } 33 | } 34 | 35 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 36 | self 37 | } 38 | 39 | fn unmarshal(raw: &Bytes) -> crate::error::Result 40 | where 41 | Self: Sized, 42 | { 43 | let header = ParamHeader::unmarshal(raw)?; 44 | let value = raw.slice(PARAM_HEADER_LENGTH..PARAM_HEADER_LENGTH + header.value_length()); 45 | Ok(Self { 46 | typ: header.typ.into(), 47 | value, 48 | }) 49 | } 50 | 51 | fn marshal_to(&self, buf: &mut BytesMut) -> crate::error::Result { 52 | self.header().marshal_to(buf)?; 53 | buf.extend(self.value.clone()); 54 | Ok(buf.len()) 55 | } 56 | 57 | fn value_length(&self) -> usize { 58 | self.value.len() 59 | } 60 | 61 | fn clone_to(&self) -> Box { 62 | Box::new(self.clone()) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/param/param_unrecognized.rs: -------------------------------------------------------------------------------- 1 | use crate::param::param_header::PARAM_HEADER_LENGTH; 2 | use crate::param::param_type::ParamType; 3 | use crate::param::ParamHeader; 4 | use crate::param::{build_param, Param}; 5 | use bytes::{Bytes, BytesMut}; 6 | use std::any::Any; 7 | use std::fmt::{Debug, Display, Formatter}; 8 | 9 | /// This is the parameter type used to report unrecognized parameters in e.g. init chunks back to the sender in the init ack. 10 | /// The contained param is likely to be a `ParamUnknown` but might be something more specific. 11 | #[derive(Clone, Debug)] 12 | pub struct ParamUnrecognized { 13 | param: Box, 14 | } 15 | 16 | impl ParamUnrecognized { 17 | pub(crate) fn wrap(param: Box) -> Self { 18 | Self { param } 19 | } 20 | } 21 | 22 | impl Display for ParamUnrecognized { 23 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 24 | f.write_str("UnrecognizedParam")?; 25 | Display::fmt(&self.param, f) 26 | } 27 | } 28 | 29 | impl Param for ParamUnrecognized { 30 | fn header(&self) -> ParamHeader { 31 | ParamHeader { 32 | typ: ParamType::UnrecognizedParam, 33 | value_length: self.value_length() as u16, 34 | } 35 | } 36 | 37 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 38 | self 39 | } 40 | 41 | fn unmarshal(raw: &Bytes) -> crate::error::Result 42 | where 43 | Self: Sized, 44 | { 45 | let header = ParamHeader::unmarshal(raw)?; 46 | let raw_param = raw.slice(PARAM_HEADER_LENGTH..PARAM_HEADER_LENGTH + header.value_length()); 47 | let param = build_param(&raw_param)?; 48 | Ok(Self { param }) 49 | } 50 | 51 | fn marshal_to(&self, buf: &mut BytesMut) -> crate::error::Result { 52 | self.header().marshal_to(buf)?; 53 | self.param.marshal_to(buf)?; 54 | Ok(buf.len()) 55 | } 56 | 57 | fn value_length(&self) -> usize { 58 | self.param.value_length() + PARAM_HEADER_LENGTH 59 | } 60 | 61 | fn clone_to(&self) -> Box { 62 | Box::new(self.clone()) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/chunk/chunk_cookie_echo.rs: -------------------------------------------------------------------------------- 1 | use super::{chunk_header::*, chunk_type::*, *}; 2 | 3 | use bytes::{Bytes, BytesMut}; 4 | use std::fmt; 5 | 6 | /// CookieEcho represents an SCTP Chunk of type CookieEcho 7 | /// 8 | /// 0 1 2 3 9 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 10 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 11 | /// | Type = 10 |Chunk Flags | Length | 12 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 13 | /// | Cookie | 14 | /// | | 15 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 16 | #[derive(Default, Debug, Clone)] 17 | pub(crate) struct ChunkCookieEcho { 18 | pub(crate) cookie: Bytes, 19 | } 20 | 21 | /// makes ChunkCookieEcho printable 22 | impl fmt::Display for ChunkCookieEcho { 23 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 24 | write!(f, "{}", self.header()) 25 | } 26 | } 27 | 28 | impl Chunk for ChunkCookieEcho { 29 | fn header(&self) -> ChunkHeader { 30 | ChunkHeader { 31 | typ: CT_COOKIE_ECHO, 32 | flags: 0, 33 | value_length: self.value_length() as u16, 34 | } 35 | } 36 | 37 | fn unmarshal(raw: &Bytes) -> Result { 38 | let header = ChunkHeader::unmarshal(raw)?; 39 | 40 | if header.typ != CT_COOKIE_ECHO { 41 | return Err(Error::ErrChunkTypeNotCookieEcho); 42 | } 43 | 44 | let cookie = raw.slice(CHUNK_HEADER_SIZE..CHUNK_HEADER_SIZE + header.value_length()); 45 | Ok(ChunkCookieEcho { cookie }) 46 | } 47 | 48 | fn marshal_to(&self, buf: &mut BytesMut) -> Result { 49 | self.header().marshal_to(buf)?; 50 | buf.extend(self.cookie.clone()); 51 | Ok(buf.len()) 52 | } 53 | 54 | fn check(&self) -> Result<()> { 55 | Ok(()) 56 | } 57 | 58 | fn value_length(&self) -> usize { 59 | self.cookie.len() 60 | } 61 | 62 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 63 | self 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/chunk/chunk_shutdown.rs: -------------------------------------------------------------------------------- 1 | use super::{chunk_header::*, chunk_type::*, *}; 2 | 3 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 4 | use std::fmt; 5 | 6 | ///chunkShutdown represents an SCTP Chunk of type chunkShutdown 7 | /// 8 | ///0 1 2 3 9 | ///0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 10 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 11 | ///| Type = 7 | Chunk Flags | Length = 8 | 12 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 13 | ///| Cumulative TSN Ack | 14 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 15 | #[derive(Default, Debug, Clone)] 16 | pub(crate) struct ChunkShutdown { 17 | pub(crate) cumulative_tsn_ack: u32, 18 | } 19 | 20 | pub(crate) const CUMULATIVE_TSN_ACK_LENGTH: usize = 4; 21 | 22 | /// makes chunkShutdown printable 23 | impl fmt::Display for ChunkShutdown { 24 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 25 | write!(f, "{}", self.header()) 26 | } 27 | } 28 | 29 | impl Chunk for ChunkShutdown { 30 | fn header(&self) -> ChunkHeader { 31 | ChunkHeader { 32 | typ: CT_SHUTDOWN, 33 | flags: 0, 34 | value_length: self.value_length() as u16, 35 | } 36 | } 37 | 38 | fn unmarshal(raw: &Bytes) -> Result { 39 | let header = ChunkHeader::unmarshal(raw)?; 40 | 41 | if header.typ != CT_SHUTDOWN { 42 | return Err(Error::ErrChunkTypeNotShutdown); 43 | } 44 | 45 | if raw.len() != CHUNK_HEADER_SIZE + CUMULATIVE_TSN_ACK_LENGTH { 46 | return Err(Error::ErrInvalidChunkSize); 47 | } 48 | 49 | let reader = &mut raw.slice(CHUNK_HEADER_SIZE..CHUNK_HEADER_SIZE + header.value_length()); 50 | 51 | let cumulative_tsn_ack = reader.get_u32(); 52 | 53 | Ok(ChunkShutdown { cumulative_tsn_ack }) 54 | } 55 | 56 | fn marshal_to(&self, writer: &mut BytesMut) -> Result { 57 | self.header().marshal_to(writer)?; 58 | writer.put_u32(self.cumulative_tsn_ack); 59 | Ok(writer.len()) 60 | } 61 | 62 | fn check(&self) -> Result<()> { 63 | Ok(()) 64 | } 65 | 66 | fn value_length(&self) -> usize { 67 | CUMULATIVE_TSN_ACK_LENGTH 68 | } 69 | 70 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 71 | self 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/timer/ack_timer.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use std::sync::Weak; 3 | use tokio::sync::{mpsc, Mutex}; 4 | use tokio::time::Duration; 5 | 6 | pub(crate) const ACK_INTERVAL: Duration = Duration::from_millis(200); 7 | 8 | /// ackTimerObserver is the inteface to an ack timer observer. 9 | #[async_trait] 10 | pub(crate) trait AckTimerObserver { 11 | async fn on_ack_timeout(&mut self); 12 | } 13 | 14 | /// ackTimer provides the retnransmission timer conforms with RFC 4960 Sec 6.3.1 15 | #[derive(Default, Debug)] 16 | pub(crate) struct AckTimer { 17 | pub(crate) timeout_observer: Weak>, 18 | pub(crate) interval: Duration, 19 | pub(crate) close_tx: Option>, 20 | } 21 | 22 | impl AckTimer { 23 | /// newAckTimer creates a new acknowledgement timer used to enable delayed ack. 24 | pub(crate) fn new(timeout_observer: Weak>, interval: Duration) -> Self { 25 | AckTimer { 26 | timeout_observer, 27 | interval, 28 | close_tx: None, 29 | } 30 | } 31 | 32 | /// start starts the timer. 33 | pub(crate) fn start(&mut self) -> bool { 34 | // this timer is already closed 35 | if self.close_tx.is_some() { 36 | return false; 37 | } 38 | 39 | let (close_tx, mut close_rx) = mpsc::channel(1); 40 | let interval = self.interval; 41 | let timeout_observer = self.timeout_observer.clone(); 42 | 43 | tokio::spawn(async move { 44 | let timer = tokio::time::sleep(interval); 45 | tokio::pin!(timer); 46 | 47 | tokio::select! { 48 | _ = timer.as_mut() => { 49 | if let Some(observer) = timeout_observer.upgrade(){ 50 | let mut observer = observer.lock().await; 51 | observer.on_ack_timeout().await; 52 | } 53 | } 54 | _ = close_rx.recv() => {}, 55 | } 56 | }); 57 | 58 | self.close_tx = Some(close_tx); 59 | true 60 | } 61 | 62 | /// stops the timer. this is similar to stop() but subsequent start() call 63 | /// will fail (the timer is no longer usable) 64 | pub(crate) fn stop(&mut self) { 65 | self.close_tx.take(); 66 | } 67 | 68 | /// isRunning tests if the timer is running. 69 | /// Debug purpose only 70 | pub(crate) fn is_running(&self) -> bool { 71 | self.close_tx.is_some() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /.github/workflows/cargo.yml: -------------------------------------------------------------------------------- 1 | name: cargo 2 | 3 | on: 4 | push: 5 | branches: [ main, proto ] 6 | pull_request: 7 | branches: [ main, proto ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | check_and_test: 14 | name: Check and test 15 | strategy: 16 | matrix: 17 | os: ['ubuntu-latest', 'macos-latest', 'windows-latest'] 18 | toolchain: 19 | - 1.57.0 # min supported version (https://github.com/webrtc-rs/webrtc/#toolchain) 20 | - stable 21 | runs-on: ${{ matrix.os }} 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Cache cargo registry 25 | uses: actions/cache@v3 26 | with: 27 | path: ~/.cargo/registry 28 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} 29 | - uses: actions-rs/toolchain@v1 30 | with: 31 | toolchain: ${{ matrix.toolchain }} 32 | profile: minimal 33 | override: true 34 | - uses: actions-rs/cargo@v1 35 | with: 36 | command: check 37 | - uses: actions-rs/cargo@v1 38 | with: 39 | command: test 40 | 41 | rustfmt_and_clippy: 42 | name: Check rustfmt style and run clippy 43 | runs-on: ubuntu-latest 44 | steps: 45 | - uses: actions/checkout@v3 46 | - uses: actions-rs/toolchain@v1 47 | with: 48 | toolchain: stable 49 | profile: minimal 50 | components: clippy, rustfmt 51 | override: true 52 | - name: Cache cargo registry 53 | uses: actions/cache@v3 54 | with: 55 | path: ~/.cargo/registry 56 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} 57 | - name: Run clippy 58 | uses: actions-rs/cargo@v1 59 | with: 60 | command: clippy 61 | args: -- -D warnings 62 | - name: Check formating 63 | uses: actions-rs/cargo@v1 64 | with: 65 | command: fmt 66 | args: --all -- --check 67 | 68 | minimal_versions: 69 | name: Compile and test with minimal versions 70 | strategy: 71 | matrix: 72 | os: ['ubuntu-latest', 'macos-latest', 'windows-latest'] 73 | runs-on: ${{ matrix.os }} 74 | steps: 75 | - uses: actions/checkout@v3 76 | - name: Install latest nightly 77 | uses: actions-rs/toolchain@v1 78 | with: 79 | toolchain: nightly 80 | override: true 81 | - uses: taiki-e/install-action@cargo-hack 82 | - uses: taiki-e/install-action@cargo-minimal-versions 83 | - run: cargo minimal-versions check --workspace --all-features --ignore-private -v 84 | - run: cargo minimal-versions build --workspace --all-features --ignore-private -v 85 | - run: cargo minimal-versions test --workspace --all-features -v 86 | -------------------------------------------------------------------------------- /.github/workflows/grcov.yml: -------------------------------------------------------------------------------- 1 | name: coverage 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | grcov: 14 | name: Coverage 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | os: 19 | - ubuntu-latest 20 | toolchain: 21 | - nightly 22 | cargo_flags: 23 | - "--all-features" 24 | steps: 25 | - name: Checkout source code 26 | uses: actions/checkout@v2 27 | 28 | - name: Install Rust 29 | uses: actions-rs/toolchain@v1 30 | with: 31 | profile: minimal 32 | toolchain: ${{ matrix.toolchain }} 33 | override: true 34 | 35 | - name: Install grcov 36 | uses: actions-rs/install@v0.1 37 | with: 38 | crate: grcov 39 | version: latest 40 | use-tool-cache: true 41 | 42 | - name: Test 43 | uses: actions-rs/cargo@v1 44 | with: 45 | command: test 46 | args: --all --no-fail-fast ${{ matrix.cargo_flags }} 47 | env: 48 | CARGO_INCREMENTAL: "0" 49 | RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort -Cdebug-assertions=off' 50 | RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort -Cdebug-assertions=off' 51 | 52 | - name: Generate coverage data 53 | id: grcov 54 | # uses: actions-rs/grcov@v0.1 55 | run: | 56 | grcov target/debug/ \ 57 | --branch \ 58 | --llvm \ 59 | --source-dir . \ 60 | --output-path lcov.info \ 61 | --ignore='/**' \ 62 | --ignore='C:/**' \ 63 | --ignore='../**' \ 64 | --ignore-not-existing \ 65 | --excl-line "#\\[derive\\(" \ 66 | --excl-br-line "#\\[derive\\(" \ 67 | --excl-start "#\\[cfg\\(test\\)\\]" \ 68 | --excl-br-start "#\\[cfg\\(test\\)\\]" \ 69 | --commit-sha ${{ github.sha }} \ 70 | --service-job-id ${{ github.job }} \ 71 | --service-name "GitHub Actions" \ 72 | --service-number ${{ github.run_id }} 73 | - name: Upload coverage as artifact 74 | uses: actions/upload-artifact@v2 75 | with: 76 | name: lcov.info 77 | # path: ${{ steps.grcov.outputs.report }} 78 | path: lcov.info 79 | 80 | - name: Upload coverage to codecov.io 81 | uses: codecov/codecov-action@v1 82 | with: 83 | # file: ${{ steps.grcov.outputs.report }} 84 | file: lcov.info 85 | fail_ci_if_error: true 86 | -------------------------------------------------------------------------------- /src/chunk/chunk_abort.rs: -------------------------------------------------------------------------------- 1 | use super::{chunk_header::*, chunk_type::*, *}; 2 | use crate::error_cause::*; 3 | 4 | use bytes::{Bytes, BytesMut}; 5 | use std::fmt; 6 | 7 | ///Abort represents an SCTP Chunk of type ABORT 8 | /// 9 | ///The ABORT chunk is sent to the peer of an association to close the 10 | ///association. The ABORT chunk may contain Cause Parameters to inform 11 | ///the receiver about the reason of the abort. DATA chunks MUST NOT be 12 | ///bundled with ABORT. Control chunks (except for INIT, INIT ACK, and 13 | ///SHUTDOWN COMPLETE) MAY be bundled with an ABORT, but they MUST be 14 | ///placed before the ABORT in the SCTP packet or they will be ignored by 15 | ///the receiver. 16 | /// 17 | /// 0 1 2 3 18 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 19 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 20 | ///| Type = 6 |Reserved |T| Length | 21 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 22 | ///| | 23 | ///| zero or more Error Causes | 24 | ///| | 25 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 26 | #[derive(Default, Debug, Clone)] 27 | pub(crate) struct ChunkAbort { 28 | pub(crate) error_causes: Vec, 29 | } 30 | 31 | /// String makes chunkAbort printable 32 | impl fmt::Display for ChunkAbort { 33 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 34 | let mut res = vec![self.header().to_string()]; 35 | 36 | for cause in &self.error_causes { 37 | res.push(format!(" - {}", cause)); 38 | } 39 | 40 | write!(f, "{}", res.join("\n")) 41 | } 42 | } 43 | 44 | impl Chunk for ChunkAbort { 45 | fn header(&self) -> ChunkHeader { 46 | ChunkHeader { 47 | typ: CT_ABORT, 48 | flags: 0, 49 | value_length: self.value_length() as u16, 50 | } 51 | } 52 | 53 | fn unmarshal(raw: &Bytes) -> Result { 54 | let header = ChunkHeader::unmarshal(raw)?; 55 | 56 | if header.typ != CT_ABORT { 57 | return Err(Error::ErrChunkTypeNotAbort); 58 | } 59 | 60 | let mut error_causes = vec![]; 61 | let mut offset = CHUNK_HEADER_SIZE; 62 | while offset + 4 <= raw.len() { 63 | let e = ErrorCause::unmarshal( 64 | &raw.slice(offset..CHUNK_HEADER_SIZE + header.value_length()), 65 | )?; 66 | offset += e.length(); 67 | error_causes.push(e); 68 | } 69 | 70 | Ok(ChunkAbort { error_causes }) 71 | } 72 | 73 | fn marshal_to(&self, buf: &mut BytesMut) -> Result { 74 | self.header().marshal_to(buf)?; 75 | for ec in &self.error_causes { 76 | buf.extend(ec.marshal()); 77 | } 78 | Ok(buf.len()) 79 | } 80 | 81 | fn check(&self) -> Result<()> { 82 | Ok(()) 83 | } 84 | 85 | fn value_length(&self) -> usize { 86 | self.error_causes 87 | .iter() 88 | .fold(0, |length, ec| length + ec.length()) 89 | } 90 | 91 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 92 | self 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/chunk/chunk_error.rs: -------------------------------------------------------------------------------- 1 | use super::{chunk_header::*, chunk_type::*, *}; 2 | use crate::error_cause::*; 3 | 4 | use bytes::{Bytes, BytesMut}; 5 | use std::fmt; 6 | 7 | ///Operation Error (ERROR) (9) 8 | /// 9 | ///An endpoint sends this chunk to its peer endpoint to notify it of 10 | ///certain error conditions. It contains one or more error causes. An 11 | ///Operation Error is not considered fatal in and of itself, but may be 12 | ///used with an ERROR chunk to report a fatal condition. It has the 13 | ///following parameters: 14 | /// 0 1 2 3 15 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 16 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 17 | ///| Type = 9 | Chunk Flags | Length | 18 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 19 | ///| | 20 | ///| one or more Error Causes | 21 | ///| | 22 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 23 | ///Chunk Flags: 8 bits 24 | /// Set to 0 on transmit and ignored on receipt. 25 | ///Length: 16 bits (unsigned integer) 26 | /// Set to the size of the chunk in bytes, including the chunk header 27 | /// and all the Error Cause fields present. 28 | #[derive(Default, Debug, Clone)] 29 | pub(crate) struct ChunkError { 30 | pub(crate) error_causes: Vec, 31 | } 32 | 33 | /// makes ChunkError printable 34 | impl fmt::Display for ChunkError { 35 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 36 | let mut res = vec![self.header().to_string()]; 37 | 38 | for cause in &self.error_causes { 39 | res.push(format!(" - {}", cause)); 40 | } 41 | 42 | write!(f, "{}", res.join("\n")) 43 | } 44 | } 45 | 46 | impl Chunk for ChunkError { 47 | fn header(&self) -> ChunkHeader { 48 | ChunkHeader { 49 | typ: CT_ERROR, 50 | flags: 0, 51 | value_length: self.value_length() as u16, 52 | } 53 | } 54 | 55 | fn unmarshal(raw: &Bytes) -> Result { 56 | let header = ChunkHeader::unmarshal(raw)?; 57 | 58 | if header.typ != CT_ERROR { 59 | return Err(Error::ErrChunkTypeNotCtError); 60 | } 61 | 62 | let mut error_causes = vec![]; 63 | let mut offset = CHUNK_HEADER_SIZE; 64 | while offset + 4 <= raw.len() { 65 | let e = ErrorCause::unmarshal( 66 | &raw.slice(offset..CHUNK_HEADER_SIZE + header.value_length()), 67 | )?; 68 | offset += e.length(); 69 | error_causes.push(e); 70 | } 71 | 72 | Ok(ChunkError { error_causes }) 73 | } 74 | 75 | fn marshal_to(&self, buf: &mut BytesMut) -> Result { 76 | self.header().marshal_to(buf)?; 77 | for ec in &self.error_causes { 78 | buf.extend(ec.marshal()); 79 | } 80 | Ok(buf.len()) 81 | } 82 | 83 | fn check(&self) -> Result<()> { 84 | Ok(()) 85 | } 86 | 87 | fn value_length(&self) -> usize { 88 | self.error_causes 89 | .iter() 90 | .fold(0, |length, ec| length + ec.length()) 91 | } 92 | 93 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 94 | self 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/chunk/chunk_heartbeat.rs: -------------------------------------------------------------------------------- 1 | use super::{chunk_header::*, chunk_type::*, *}; 2 | use crate::param::{param_header::*, param_type::*, *}; 3 | 4 | use bytes::{Bytes, BytesMut}; 5 | use std::fmt; 6 | 7 | ///chunkHeartbeat represents an SCTP Chunk of type HEARTBEAT 8 | /// 9 | ///An endpoint should send this chunk to its peer endpoint to probe the 10 | ///reachability of a particular destination transport address defined in 11 | ///the present association. 12 | /// 13 | ///The parameter field contains the Heartbeat Information, which is a 14 | ///variable-length opaque data structure understood only by the sender. 15 | /// 16 | /// 17 | /// 0 1 2 3 18 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 19 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 20 | ///| Type = 4 | Chunk Flags | Heartbeat Length | 21 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 22 | ///| | 23 | ///| Heartbeat Information TLV (Variable-Length) | 24 | ///| | 25 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 26 | /// 27 | ///Defined as a variable-length parameter using the format described 28 | ///in Section 3.2.1, i.e.: 29 | /// 30 | ///Variable Parameters Status Type Value 31 | ///------------------------------------------------------------- 32 | ///heartbeat Info Mandatory 1 33 | #[derive(Default, Debug)] 34 | pub(crate) struct ChunkHeartbeat { 35 | pub(crate) params: Vec>, 36 | } 37 | 38 | /// makes ChunkHeartbeat printable 39 | impl fmt::Display for ChunkHeartbeat { 40 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 41 | write!(f, "{}", self.header()) 42 | } 43 | } 44 | 45 | impl Chunk for ChunkHeartbeat { 46 | fn header(&self) -> ChunkHeader { 47 | ChunkHeader { 48 | typ: CT_HEARTBEAT, 49 | flags: 0, 50 | value_length: self.value_length() as u16, 51 | } 52 | } 53 | 54 | fn unmarshal(raw: &Bytes) -> Result { 55 | let header = ChunkHeader::unmarshal(raw)?; 56 | 57 | if header.typ != CT_HEARTBEAT { 58 | return Err(Error::ErrChunkTypeNotHeartbeat); 59 | } 60 | 61 | if raw.len() <= CHUNK_HEADER_SIZE { 62 | return Err(Error::ErrHeartbeatNotLongEnoughInfo); 63 | } 64 | 65 | let p = 66 | build_param(&raw.slice(CHUNK_HEADER_SIZE..CHUNK_HEADER_SIZE + header.value_length()))?; 67 | if p.header().typ != ParamType::HeartbeatInfo { 68 | return Err(Error::ErrHeartbeatParam); 69 | } 70 | let params = vec![p]; 71 | 72 | Ok(ChunkHeartbeat { params }) 73 | } 74 | 75 | fn marshal_to(&self, buf: &mut BytesMut) -> Result { 76 | self.header().marshal_to(buf)?; 77 | for p in &self.params { 78 | buf.extend(p.marshal()?); 79 | } 80 | Ok(buf.len()) 81 | } 82 | 83 | fn check(&self) -> Result<()> { 84 | Ok(()) 85 | } 86 | 87 | fn value_length(&self) -> usize { 88 | self.params.iter().fold(0, |length, p| { 89 | length + PARAM_HEADER_LENGTH + p.value_length() 90 | }) 91 | } 92 | 93 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 94 | self 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/chunk/chunk_type.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | // chunkType is an enum for SCTP Chunk Type field 4 | // This field identifies the type of information contained in the 5 | // Chunk Value field. 6 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] 7 | pub(crate) struct ChunkType(pub(crate) u8); 8 | 9 | pub(crate) const CT_PAYLOAD_DATA: ChunkType = ChunkType(0); 10 | pub(crate) const CT_INIT: ChunkType = ChunkType(1); 11 | pub(crate) const CT_INIT_ACK: ChunkType = ChunkType(2); 12 | pub(crate) const CT_SACK: ChunkType = ChunkType(3); 13 | pub(crate) const CT_HEARTBEAT: ChunkType = ChunkType(4); 14 | pub(crate) const CT_HEARTBEAT_ACK: ChunkType = ChunkType(5); 15 | pub(crate) const CT_ABORT: ChunkType = ChunkType(6); 16 | pub(crate) const CT_SHUTDOWN: ChunkType = ChunkType(7); 17 | pub(crate) const CT_SHUTDOWN_ACK: ChunkType = ChunkType(8); 18 | pub(crate) const CT_ERROR: ChunkType = ChunkType(9); 19 | pub(crate) const CT_COOKIE_ECHO: ChunkType = ChunkType(10); 20 | pub(crate) const CT_COOKIE_ACK: ChunkType = ChunkType(11); 21 | pub(crate) const CT_CWR: ChunkType = ChunkType(13); 22 | pub(crate) const CT_SHUTDOWN_COMPLETE: ChunkType = ChunkType(14); 23 | pub(crate) const CT_RECONFIG: ChunkType = ChunkType(130); 24 | pub(crate) const CT_FORWARD_TSN: ChunkType = ChunkType(192); 25 | 26 | impl fmt::Display for ChunkType { 27 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 28 | let others = format!("Unknown ChunkType: {}", self.0); 29 | let s = match *self { 30 | CT_PAYLOAD_DATA => "DATA", 31 | CT_INIT => "INIT", 32 | CT_INIT_ACK => "INIT-ACK", 33 | CT_SACK => "SACK", 34 | CT_HEARTBEAT => "HEARTBEAT", 35 | CT_HEARTBEAT_ACK => "HEARTBEAT-ACK", 36 | CT_ABORT => "ABORT", 37 | CT_SHUTDOWN => "SHUTDOWN", 38 | CT_SHUTDOWN_ACK => "SHUTDOWN-ACK", 39 | CT_ERROR => "ERROR", 40 | CT_COOKIE_ECHO => "COOKIE-ECHO", 41 | CT_COOKIE_ACK => "COOKIE-ACK", 42 | CT_CWR => "ECNE", // Explicit Congestion Notification Echo 43 | CT_SHUTDOWN_COMPLETE => "SHUTDOWN-COMPLETE", 44 | CT_RECONFIG => "RECONFIG", // Re-configuration 45 | CT_FORWARD_TSN => "FORWARD-TSN", 46 | _ => others.as_str(), 47 | }; 48 | write!(f, "{}", s) 49 | } 50 | } 51 | 52 | #[cfg(test)] 53 | mod test { 54 | use super::*; 55 | 56 | #[test] 57 | fn test_chunk_type_string() { 58 | let tests = vec![ 59 | (CT_PAYLOAD_DATA, "DATA"), 60 | (CT_INIT, "INIT"), 61 | (CT_INIT_ACK, "INIT-ACK"), 62 | (CT_SACK, "SACK"), 63 | (CT_HEARTBEAT, "HEARTBEAT"), 64 | (CT_HEARTBEAT_ACK, "HEARTBEAT-ACK"), 65 | (CT_ABORT, "ABORT"), 66 | (CT_SHUTDOWN, "SHUTDOWN"), 67 | (CT_SHUTDOWN_ACK, "SHUTDOWN-ACK"), 68 | (CT_ERROR, "ERROR"), 69 | (CT_COOKIE_ECHO, "COOKIE-ECHO"), 70 | (CT_COOKIE_ACK, "COOKIE-ACK"), 71 | (CT_CWR, "ECNE"), 72 | (CT_SHUTDOWN_COMPLETE, "SHUTDOWN-COMPLETE"), 73 | (CT_RECONFIG, "RECONFIG"), 74 | (CT_FORWARD_TSN, "FORWARD-TSN"), 75 | (ChunkType(255), "Unknown ChunkType: 255"), 76 | ]; 77 | 78 | for (ct, expected) in tests { 79 | assert_eq!( 80 | ct.to_string(), 81 | expected, 82 | "failed to stringify chunkType {}, expected {}", 83 | ct, 84 | expected 85 | ); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/param/param_requested_hmac_algorithm.rs: -------------------------------------------------------------------------------- 1 | use super::{param_header::*, param_type::*, *}; 2 | 3 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 4 | use std::fmt; 5 | 6 | #[derive(Debug, Copy, Clone, PartialEq)] 7 | #[repr(C)] 8 | pub(crate) enum HmacAlgorithm { 9 | HmacResv1 = 0, 10 | HmacSha128 = 1, 11 | HmacResv2 = 2, 12 | HmacSha256 = 3, 13 | Unknown, 14 | } 15 | 16 | impl fmt::Display for HmacAlgorithm { 17 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 18 | let s = match *self { 19 | HmacAlgorithm::HmacResv1 => "HMAC Reserved (0x00)", 20 | HmacAlgorithm::HmacSha128 => "HMAC SHA-128", 21 | HmacAlgorithm::HmacResv2 => "HMAC Reserved (0x02)", 22 | HmacAlgorithm::HmacSha256 => "HMAC SHA-256", 23 | _ => "Unknown HMAC Algorithm", 24 | }; 25 | write!(f, "{}", s) 26 | } 27 | } 28 | 29 | impl From for HmacAlgorithm { 30 | fn from(v: u16) -> HmacAlgorithm { 31 | match v { 32 | 0 => HmacAlgorithm::HmacResv1, 33 | 1 => HmacAlgorithm::HmacSha128, 34 | 2 => HmacAlgorithm::HmacResv2, 35 | 3 => HmacAlgorithm::HmacSha256, 36 | _ => HmacAlgorithm::Unknown, 37 | } 38 | } 39 | } 40 | 41 | #[derive(Default, Debug, Clone, PartialEq)] 42 | pub(crate) struct ParamRequestedHmacAlgorithm { 43 | pub(crate) available_algorithms: Vec, 44 | } 45 | 46 | impl fmt::Display for ParamRequestedHmacAlgorithm { 47 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 48 | write!( 49 | f, 50 | "{} {}", 51 | self.header(), 52 | self.available_algorithms 53 | .iter() 54 | .map(|ct| ct.to_string()) 55 | .collect::>() 56 | .join(" "), 57 | ) 58 | } 59 | } 60 | 61 | impl Param for ParamRequestedHmacAlgorithm { 62 | fn header(&self) -> ParamHeader { 63 | ParamHeader { 64 | typ: ParamType::ReqHmacAlgo, 65 | value_length: self.value_length() as u16, 66 | } 67 | } 68 | 69 | fn unmarshal(raw: &Bytes) -> Result { 70 | let header = ParamHeader::unmarshal(raw)?; 71 | 72 | let reader = 73 | &mut raw.slice(PARAM_HEADER_LENGTH..PARAM_HEADER_LENGTH + header.value_length()); 74 | 75 | let mut available_algorithms = vec![]; 76 | let mut offset = 0; 77 | while offset + 1 < header.value_length() { 78 | let a: HmacAlgorithm = reader.get_u16().into(); 79 | if a == HmacAlgorithm::HmacSha128 || a == HmacAlgorithm::HmacSha256 { 80 | available_algorithms.push(a); 81 | } else { 82 | return Err(Error::ErrInvalidAlgorithmType); 83 | } 84 | 85 | offset += 2; 86 | } 87 | 88 | Ok(ParamRequestedHmacAlgorithm { 89 | available_algorithms, 90 | }) 91 | } 92 | 93 | fn marshal_to(&self, buf: &mut BytesMut) -> Result { 94 | self.header().marshal_to(buf)?; 95 | for a in &self.available_algorithms { 96 | buf.put_u16(*a as u16); 97 | } 98 | Ok(buf.len()) 99 | } 100 | 101 | fn value_length(&self) -> usize { 102 | 2 * self.available_algorithms.len() 103 | } 104 | 105 | fn clone_to(&self) -> Box { 106 | Box::new(self.clone()) 107 | } 108 | 109 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 110 | self 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/param/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod param_test; 3 | 4 | pub(crate) mod param_chunk_list; 5 | pub(crate) mod param_forward_tsn_supported; 6 | pub(crate) mod param_header; 7 | pub(crate) mod param_heartbeat_info; 8 | pub(crate) mod param_outgoing_reset_request; 9 | pub(crate) mod param_random; 10 | pub(crate) mod param_reconfig_response; 11 | pub(crate) mod param_requested_hmac_algorithm; 12 | pub(crate) mod param_state_cookie; 13 | pub(crate) mod param_supported_extensions; 14 | pub(crate) mod param_type; 15 | pub(crate) mod param_unknown; 16 | pub(crate) mod param_unrecognized; 17 | 18 | use crate::error::{Error, Result}; 19 | use crate::param::{ 20 | param_chunk_list::ParamChunkList, param_forward_tsn_supported::ParamForwardTsnSupported, 21 | param_heartbeat_info::ParamHeartbeatInfo, 22 | param_outgoing_reset_request::ParamOutgoingResetRequest, param_random::ParamRandom, 23 | param_reconfig_response::ParamReconfigResponse, 24 | param_requested_hmac_algorithm::ParamRequestedHmacAlgorithm, 25 | param_state_cookie::ParamStateCookie, param_supported_extensions::ParamSupportedExtensions, 26 | }; 27 | use param_header::*; 28 | use param_type::*; 29 | 30 | use crate::param::param_unknown::ParamUnknown; 31 | use bytes::{Buf, Bytes, BytesMut}; 32 | use std::{any::Any, fmt}; 33 | 34 | pub(crate) trait Param: fmt::Display + fmt::Debug { 35 | fn header(&self) -> ParamHeader; 36 | fn unmarshal(raw: &Bytes) -> Result 37 | where 38 | Self: Sized; 39 | fn marshal_to(&self, buf: &mut BytesMut) -> Result; 40 | fn value_length(&self) -> usize; 41 | fn clone_to(&self) -> Box; 42 | fn as_any(&self) -> &(dyn Any + Send + Sync); 43 | 44 | fn marshal(&self) -> Result { 45 | let capacity = PARAM_HEADER_LENGTH + self.value_length(); 46 | let mut buf = BytesMut::with_capacity(capacity); 47 | self.marshal_to(&mut buf)?; 48 | Ok(buf.freeze()) 49 | } 50 | } 51 | 52 | impl Clone for Box { 53 | fn clone(&self) -> Box { 54 | self.clone_to() 55 | } 56 | } 57 | 58 | pub(crate) fn build_param(raw_param: &Bytes) -> Result> { 59 | if raw_param.len() < PARAM_HEADER_LENGTH { 60 | return Err(Error::ErrParamHeaderTooShort); 61 | } 62 | let reader = &mut raw_param.slice(..2); 63 | let raw_type = reader.get_u16(); 64 | match raw_type.into() { 65 | ParamType::ForwardTsnSupp => Ok(Box::new(ParamForwardTsnSupported::unmarshal(raw_param)?)), 66 | ParamType::SupportedExt => Ok(Box::new(ParamSupportedExtensions::unmarshal(raw_param)?)), 67 | ParamType::Random => Ok(Box::new(ParamRandom::unmarshal(raw_param)?)), 68 | ParamType::ReqHmacAlgo => Ok(Box::new(ParamRequestedHmacAlgorithm::unmarshal(raw_param)?)), 69 | ParamType::ChunkList => Ok(Box::new(ParamChunkList::unmarshal(raw_param)?)), 70 | ParamType::StateCookie => Ok(Box::new(ParamStateCookie::unmarshal(raw_param)?)), 71 | ParamType::HeartbeatInfo => Ok(Box::new(ParamHeartbeatInfo::unmarshal(raw_param)?)), 72 | ParamType::OutSsnResetReq => Ok(Box::new(ParamOutgoingResetRequest::unmarshal(raw_param)?)), 73 | ParamType::ReconfigResp => Ok(Box::new(ParamReconfigResponse::unmarshal(raw_param)?)), 74 | _ => { 75 | // According to RFC https://datatracker.ietf.org/doc/html/rfc4960#section-3.2.1 76 | let stop_processing = ((raw_type >> 15) & 0x01) == 0; 77 | if stop_processing { 78 | Err(Error::ErrParamTypeUnhandled { typ: raw_type }) 79 | } else { 80 | // We still might need to report this param as unrecognized. 81 | // This depends on the context though. 82 | Ok(Box::new(ParamUnknown::unmarshal(raw_param)?)) 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /examples/pong.rs: -------------------------------------------------------------------------------- 1 | use webrtc_sctp::association::*; 2 | use webrtc_sctp::stream::*; 3 | use webrtc_sctp::Error; 4 | 5 | use bytes::Bytes; 6 | use clap::{App, AppSettings, Arg}; 7 | use std::net::Shutdown; 8 | use std::sync::Arc; 9 | use std::time::Duration; 10 | use tokio::net::UdpSocket; 11 | use tokio::signal; 12 | use tokio::sync::mpsc; 13 | use util::{conn::conn_disconnected_packet::DisconnectedPacketConn, Conn}; 14 | 15 | // RUST_LOG=trace cargo run --color=always --package webrtc-sctp --example pong -- --host 0.0.0.0:5678 16 | 17 | #[tokio::main] 18 | async fn main() -> Result<(), Error> { 19 | /*env_logger::Builder::new() 20 | .format(|buf, record| { 21 | writeln!( 22 | buf, 23 | "{}:{} [{}] {} - {}", 24 | record.file().unwrap_or("unknown"), 25 | record.line().unwrap_or(0), 26 | record.level(), 27 | chrono::Local::now().format("%H:%M:%S.%6f"), 28 | record.args() 29 | ) 30 | }) 31 | .filter(None, log::LevelFilter::Trace) 32 | .init();*/ 33 | 34 | let mut app = App::new("SCTP Pong") 35 | .version("0.1.0") 36 | .author("Rain Liu ") 37 | .about("An example of SCTP Server") 38 | .setting(AppSettings::DeriveDisplayOrder) 39 | .setting(AppSettings::SubcommandsNegateReqs) 40 | .arg( 41 | Arg::with_name("FULLHELP") 42 | .help("Prints more detailed help information") 43 | .long("fullhelp"), 44 | ) 45 | .arg( 46 | Arg::with_name("host") 47 | .required_unless("FULLHELP") 48 | .takes_value(true) 49 | .long("host") 50 | .help("SCTP host name."), 51 | ); 52 | 53 | let matches = app.clone().get_matches(); 54 | 55 | if matches.is_present("FULLHELP") { 56 | app.print_long_help().unwrap(); 57 | std::process::exit(0); 58 | } 59 | 60 | let host = matches.value_of("host").unwrap(); 61 | let conn = DisconnectedPacketConn::new(Arc::new(UdpSocket::bind(host).await.unwrap())); 62 | println!("listening {}...", conn.local_addr().await.unwrap()); 63 | 64 | let config = Config { 65 | net_conn: Arc::new(conn), 66 | max_receive_buffer_size: 0, 67 | max_message_size: 0, 68 | name: "server".to_owned(), 69 | }; 70 | let a = Association::server(config).await?; 71 | println!("created a server"); 72 | 73 | let stream = a.accept_stream().await.unwrap(); 74 | println!("accepted a stream"); 75 | 76 | // set unordered = true and 10ms treshold for dropping packets 77 | stream.set_reliability_params(true, ReliabilityType::Timed, 10); 78 | 79 | let (done_tx, mut done_rx) = mpsc::channel::<()>(1); 80 | let stream2 = Arc::clone(&stream); 81 | tokio::spawn(async move { 82 | let mut buff = vec![0u8; 1024]; 83 | while let Ok(n) = stream2.read(&mut buff).await { 84 | let ping_msg = String::from_utf8(buff[..n].to_vec()).unwrap(); 85 | println!("received: {}", ping_msg); 86 | 87 | let pong_msg = format!("pong [{}]", ping_msg); 88 | println!("sent: {}", pong_msg); 89 | stream2.write(&Bytes::from(pong_msg)).await?; 90 | 91 | tokio::time::sleep(Duration::from_secs(1)).await; 92 | } 93 | println!("finished ping-pong"); 94 | drop(done_tx); 95 | 96 | Result::<(), Error>::Ok(()) 97 | }); 98 | 99 | println!("Waiting for Ctrl-C..."); 100 | signal::ctrl_c().await.expect("failed to listen for event"); 101 | println!("Closing stream and association..."); 102 | 103 | stream.shutdown(Shutdown::Both).await?; 104 | a.close().await?; 105 | 106 | let _ = done_rx.recv().await; 107 | 108 | Ok(()) 109 | } 110 | -------------------------------------------------------------------------------- /examples/ping.rs: -------------------------------------------------------------------------------- 1 | use webrtc_sctp::association::*; 2 | use webrtc_sctp::chunk::chunk_payload_data::PayloadProtocolIdentifier; 3 | use webrtc_sctp::stream::*; 4 | use webrtc_sctp::Error; 5 | 6 | use bytes::Bytes; 7 | use clap::{App, AppSettings, Arg}; 8 | use std::net::Shutdown; 9 | use std::sync::Arc; 10 | use tokio::net::UdpSocket; 11 | use tokio::signal; 12 | use tokio::sync::mpsc; 13 | 14 | // RUST_LOG=trace cargo run --color=always --package webrtc-sctp --example ping -- --server 0.0.0.0:5678 15 | 16 | #[tokio::main] 17 | async fn main() -> Result<(), Error> { 18 | /*env_logger::Builder::new() 19 | .format(|buf, record| { 20 | writeln!( 21 | buf, 22 | "{}:{} [{}] {} - {}", 23 | record.file().unwrap_or("unknown"), 24 | record.line().unwrap_or(0), 25 | record.level(), 26 | chrono::Local::now().format("%H:%M:%S.%6f"), 27 | record.args() 28 | ) 29 | }) 30 | .filter(None, log::LevelFilter::Trace) 31 | .init();*/ 32 | 33 | let mut app = App::new("SCTP Ping") 34 | .version("0.1.0") 35 | .author("Rain Liu ") 36 | .about("An example of SCTP Client") 37 | .setting(AppSettings::DeriveDisplayOrder) 38 | .setting(AppSettings::SubcommandsNegateReqs) 39 | .arg( 40 | Arg::with_name("FULLHELP") 41 | .help("Prints more detailed help information") 42 | .long("fullhelp"), 43 | ) 44 | .arg( 45 | Arg::with_name("server") 46 | .required_unless("FULLHELP") 47 | .takes_value(true) 48 | .long("server") 49 | .help("SCTP Server name."), 50 | ); 51 | 52 | let matches = app.clone().get_matches(); 53 | 54 | if matches.is_present("FULLHELP") { 55 | app.print_long_help().unwrap(); 56 | std::process::exit(0); 57 | } 58 | 59 | let server = matches.value_of("server").unwrap(); 60 | 61 | let conn = Arc::new(UdpSocket::bind("0.0.0.0:0").await.unwrap()); 62 | conn.connect(server).await.unwrap(); 63 | println!("connecting {}..", server); 64 | 65 | let config = Config { 66 | net_conn: conn, 67 | max_receive_buffer_size: 0, 68 | max_message_size: 0, 69 | name: "client".to_owned(), 70 | }; 71 | let a = Association::client(config).await?; 72 | println!("created a client"); 73 | 74 | let stream = a.open_stream(0, PayloadProtocolIdentifier::String).await?; 75 | println!("opened a stream"); 76 | 77 | // set unordered = true and 10ms treshold for dropping packets 78 | stream.set_reliability_params(true, ReliabilityType::Timed, 10); 79 | 80 | let stream_tx = Arc::clone(&stream); 81 | tokio::spawn(async move { 82 | let mut ping_seq_num = 0; 83 | while ping_seq_num < 10 { 84 | let ping_msg = format!("ping {}", ping_seq_num); 85 | println!("sent: {}", ping_msg); 86 | stream_tx.write(&Bytes::from(ping_msg)).await?; 87 | 88 | ping_seq_num += 1; 89 | } 90 | 91 | println!("finished send ping"); 92 | Result::<(), Error>::Ok(()) 93 | }); 94 | 95 | let (done_tx, mut done_rx) = mpsc::channel::<()>(1); 96 | let stream_rx = Arc::clone(&stream); 97 | tokio::spawn(async move { 98 | let mut buff = vec![0u8; 1024]; 99 | while let Ok(n) = stream_rx.read(&mut buff).await { 100 | let pong_msg = String::from_utf8(buff[..n].to_vec()).unwrap(); 101 | println!("received: {}", pong_msg); 102 | } 103 | 104 | println!("finished recv pong"); 105 | drop(done_tx); 106 | }); 107 | 108 | println!("Waiting for Ctrl-C..."); 109 | signal::ctrl_c().await.expect("failed to listen for event"); 110 | println!("Closing stream and association..."); 111 | 112 | stream.shutdown(Shutdown::Both).await?; 113 | a.close().await?; 114 | 115 | let _ = done_rx.recv().await; 116 | 117 | Ok(()) 118 | } 119 | -------------------------------------------------------------------------------- /src/chunk/chunk_header.rs: -------------------------------------------------------------------------------- 1 | use super::{chunk_type::*, *}; 2 | 3 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 4 | use std::fmt; 5 | 6 | ///chunkHeader represents a SCTP Chunk header, defined in https://tools.ietf.org/html/rfc4960#section-3.2 7 | ///The figure below illustrates the field format for the chunks to be 8 | ///transmitted in the SCTP packet. Each chunk is formatted with a Chunk 9 | ///Type field, a chunk-specific Flag field, a Chunk Length field, and a 10 | ///Value field. 11 | /// 12 | /// 0 1 2 3 13 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 14 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 15 | ///| Chunk Type | Chunk Flags | Chunk Length | 16 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 17 | ///| | 18 | ///| Chunk Value | 19 | ///| | 20 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 21 | #[derive(Debug, Clone)] 22 | pub(crate) struct ChunkHeader { 23 | pub(crate) typ: ChunkType, 24 | pub(crate) flags: u8, 25 | pub(crate) value_length: u16, 26 | } 27 | 28 | pub(crate) const CHUNK_HEADER_SIZE: usize = 4; 29 | 30 | /// makes ChunkHeader printable 31 | impl fmt::Display for ChunkHeader { 32 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 33 | write!(f, "{}", self.typ) 34 | } 35 | } 36 | 37 | impl Chunk for ChunkHeader { 38 | fn header(&self) -> ChunkHeader { 39 | self.clone() 40 | } 41 | 42 | fn unmarshal(raw: &Bytes) -> Result { 43 | if raw.len() < CHUNK_HEADER_SIZE { 44 | return Err(Error::ErrChunkHeaderTooSmall); 45 | } 46 | 47 | let reader = &mut raw.clone(); 48 | 49 | let typ = ChunkType(reader.get_u8()); 50 | let flags = reader.get_u8(); 51 | let length = reader.get_u16(); 52 | 53 | if length < CHUNK_HEADER_SIZE as u16 { 54 | return Err(Error::ErrChunkHeaderInvalidLength); 55 | } 56 | if (length as usize) > raw.len() { 57 | return Err(Error::ErrChunkHeaderInvalidLength); 58 | } 59 | 60 | // Length includes Chunk header 61 | let value_length = length as isize - CHUNK_HEADER_SIZE as isize; 62 | 63 | let length_after_value = raw.len() as isize - length as isize; 64 | if length_after_value < 0 { 65 | return Err(Error::ErrChunkHeaderNotEnoughSpace); 66 | } else if length_after_value < 4 { 67 | // https://tools.ietf.org/html/rfc4960#section-3.2 68 | // The Chunk Length field does not count any chunk PADDING. 69 | // Chunks (including Type, Length, and Value fields) are padded out 70 | // by the sender with all zero bytes to be a multiple of 4 bytes 71 | // long. This PADDING MUST NOT be more than 3 bytes in total. The 72 | // Chunk Length value does not include terminating PADDING of the 73 | // chunk. However, it does include PADDING of any variable-length 74 | // parameter except the last parameter in the chunk. The receiver 75 | // MUST ignore the PADDING. 76 | for i in (1..=length_after_value).rev() { 77 | let padding_offset = CHUNK_HEADER_SIZE + (value_length + i - 1) as usize; 78 | if raw[padding_offset] != 0 { 79 | return Err(Error::ErrChunkHeaderPaddingNonZero); 80 | } 81 | } 82 | } 83 | 84 | Ok(ChunkHeader { 85 | typ, 86 | flags, 87 | value_length: length - CHUNK_HEADER_SIZE as u16, 88 | }) 89 | } 90 | 91 | fn marshal_to(&self, writer: &mut BytesMut) -> Result { 92 | writer.put_u8(self.typ.0); 93 | writer.put_u8(self.flags); 94 | writer.put_u16(self.value_length + CHUNK_HEADER_SIZE as u16); 95 | Ok(writer.len()) 96 | } 97 | 98 | fn check(&self) -> Result<()> { 99 | Ok(()) 100 | } 101 | 102 | fn value_length(&self) -> usize { 103 | self.value_length as usize 104 | } 105 | 106 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 107 | self 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/queue/pending_queue.rs: -------------------------------------------------------------------------------- 1 | use crate::chunk::chunk_payload_data::ChunkPayloadData; 2 | 3 | use std::collections::VecDeque; 4 | use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; 5 | use tokio::sync::Mutex; 6 | 7 | /// pendingBaseQueue 8 | pub(crate) type PendingBaseQueue = VecDeque; 9 | 10 | // TODO: benchmark performance between multiple Atomic+Mutex vs one Mutex 11 | 12 | /// pendingQueue 13 | #[derive(Debug, Default)] 14 | pub(crate) struct PendingQueue { 15 | unordered_queue: Mutex, 16 | ordered_queue: Mutex, 17 | queue_len: AtomicUsize, 18 | n_bytes: AtomicUsize, 19 | selected: AtomicBool, 20 | unordered_is_selected: AtomicBool, 21 | } 22 | 23 | impl PendingQueue { 24 | pub(crate) fn new() -> Self { 25 | PendingQueue::default() 26 | } 27 | 28 | pub(crate) async fn push(&self, c: ChunkPayloadData) { 29 | self.n_bytes.fetch_add(c.user_data.len(), Ordering::SeqCst); 30 | if c.unordered { 31 | let mut unordered_queue = self.unordered_queue.lock().await; 32 | unordered_queue.push_back(c); 33 | } else { 34 | let mut ordered_queue = self.ordered_queue.lock().await; 35 | ordered_queue.push_back(c); 36 | } 37 | self.queue_len.fetch_add(1, Ordering::SeqCst); 38 | } 39 | 40 | pub(crate) async fn peek(&self) -> Option { 41 | if self.selected.load(Ordering::SeqCst) { 42 | if self.unordered_is_selected.load(Ordering::SeqCst) { 43 | let unordered_queue = self.unordered_queue.lock().await; 44 | return unordered_queue.get(0).cloned(); 45 | } else { 46 | let ordered_queue = self.ordered_queue.lock().await; 47 | return ordered_queue.get(0).cloned(); 48 | } 49 | } 50 | 51 | let c = { 52 | let unordered_queue = self.unordered_queue.lock().await; 53 | unordered_queue.get(0).cloned() 54 | }; 55 | 56 | if c.is_some() { 57 | return c; 58 | } 59 | 60 | let ordered_queue = self.ordered_queue.lock().await; 61 | ordered_queue.get(0).cloned() 62 | } 63 | 64 | pub(crate) async fn pop( 65 | &self, 66 | beginning_fragment: bool, 67 | unordered: bool, 68 | ) -> Option { 69 | let popped = if self.selected.load(Ordering::SeqCst) { 70 | let popped = if self.unordered_is_selected.load(Ordering::SeqCst) { 71 | let mut unordered_queue = self.unordered_queue.lock().await; 72 | unordered_queue.pop_front() 73 | } else { 74 | let mut ordered_queue = self.ordered_queue.lock().await; 75 | ordered_queue.pop_front() 76 | }; 77 | if let Some(p) = &popped { 78 | if p.ending_fragment { 79 | self.selected.store(false, Ordering::SeqCst); 80 | } 81 | } 82 | popped 83 | } else { 84 | if !beginning_fragment { 85 | return None; 86 | } 87 | if unordered { 88 | let popped = { 89 | let mut unordered_queue = self.unordered_queue.lock().await; 90 | unordered_queue.pop_front() 91 | }; 92 | if let Some(p) = &popped { 93 | if !p.ending_fragment { 94 | self.selected.store(true, Ordering::SeqCst); 95 | self.unordered_is_selected.store(true, Ordering::SeqCst); 96 | } 97 | } 98 | popped 99 | } else { 100 | let popped = { 101 | let mut ordered_queue = self.ordered_queue.lock().await; 102 | ordered_queue.pop_front() 103 | }; 104 | if let Some(p) = &popped { 105 | if !p.ending_fragment { 106 | self.selected.store(true, Ordering::SeqCst); 107 | self.unordered_is_selected.store(false, Ordering::SeqCst); 108 | } 109 | } 110 | popped 111 | } 112 | }; 113 | 114 | if let Some(p) = &popped { 115 | self.n_bytes.fetch_sub(p.user_data.len(), Ordering::SeqCst); 116 | self.queue_len.fetch_sub(1, Ordering::SeqCst); 117 | } 118 | 119 | popped 120 | } 121 | 122 | pub(crate) fn get_num_bytes(&self) -> usize { 123 | self.n_bytes.load(Ordering::SeqCst) 124 | } 125 | 126 | pub(crate) fn len(&self) -> usize { 127 | self.queue_len.load(Ordering::SeqCst) 128 | } 129 | 130 | pub(crate) fn is_empty(&self) -> bool { 131 | self.len() == 0 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/chunk/chunk_heartbeat_ack.rs: -------------------------------------------------------------------------------- 1 | use super::{chunk_header::*, chunk_type::*, *}; 2 | use crate::param::param_type::ParamType; 3 | use crate::param::{param_header::*, *}; 4 | use crate::util::get_padding_size; 5 | 6 | use bytes::{Bytes, BytesMut}; 7 | use std::fmt; 8 | 9 | ///chunkHeartbeatAck represents an SCTP Chunk of type HEARTBEAT ACK 10 | /// 11 | ///An endpoint should send this chunk to its peer endpoint as a response 12 | ///to a HEARTBEAT chunk (see Section 8.3). A HEARTBEAT ACK is always 13 | ///sent to the source IP address of the IP datagram containing the 14 | ///HEARTBEAT chunk to which this ack is responding. 15 | /// 16 | ///The parameter field contains a variable-length opaque data structure. 17 | /// 18 | /// 0 1 2 3 19 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 20 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 21 | ///| Type = 5 | Chunk Flags | Heartbeat Ack Length | 22 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 23 | ///| | 24 | ///| Heartbeat Information TLV (Variable-Length) | 25 | ///| | 26 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 27 | /// 28 | /// 29 | ///Defined as a variable-length parameter using the format described 30 | ///in Section 3.2.1, i.e.: 31 | /// 32 | ///Variable Parameters Status Type Value 33 | ///------------------------------------------------------------- 34 | ///Heartbeat Info Mandatory 1 35 | #[derive(Default, Debug)] 36 | pub(crate) struct ChunkHeartbeatAck { 37 | pub(crate) params: Vec>, 38 | } 39 | 40 | /// makes ChunkHeartbeatAck printable 41 | impl fmt::Display for ChunkHeartbeatAck { 42 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 43 | write!(f, "{}", self.header()) 44 | } 45 | } 46 | 47 | impl Chunk for ChunkHeartbeatAck { 48 | fn header(&self) -> ChunkHeader { 49 | ChunkHeader { 50 | typ: CT_HEARTBEAT_ACK, 51 | flags: 0, 52 | value_length: self.value_length() as u16, 53 | } 54 | } 55 | 56 | fn unmarshal(raw: &Bytes) -> Result { 57 | let header = ChunkHeader::unmarshal(raw)?; 58 | 59 | if header.typ != CT_HEARTBEAT_ACK { 60 | return Err(Error::ErrChunkTypeNotHeartbeatAck); 61 | } 62 | 63 | if raw.len() <= CHUNK_HEADER_SIZE { 64 | return Err(Error::ErrHeartbeatNotLongEnoughInfo); 65 | } 66 | 67 | let p = 68 | build_param(&raw.slice(CHUNK_HEADER_SIZE..CHUNK_HEADER_SIZE + header.value_length()))?; 69 | if p.header().typ != ParamType::HeartbeatInfo { 70 | return Err(Error::ErrHeartbeatParam); 71 | } 72 | let params = vec![p]; 73 | 74 | Ok(ChunkHeartbeatAck { params }) 75 | } 76 | 77 | fn marshal_to(&self, buf: &mut BytesMut) -> Result { 78 | if self.params.len() != 1 { 79 | return Err(Error::ErrHeartbeatAckParams); 80 | } 81 | if self.params[0].header().typ != ParamType::HeartbeatInfo { 82 | return Err(Error::ErrHeartbeatAckNotHeartbeatInfo); 83 | } 84 | 85 | self.header().marshal_to(buf)?; 86 | for (idx, p) in self.params.iter().enumerate() { 87 | let pp = p.marshal()?; 88 | let pp_len = pp.len(); 89 | buf.extend(pp); 90 | 91 | // Chunks (including Type, Length, and Value fields) are padded out 92 | // by the sender with all zero bytes to be a multiple of 4 bytes 93 | // long. This PADDING MUST NOT be more than 3 bytes in total. The 94 | // Chunk Length value does not include terminating PADDING of the 95 | // chunk. *However, it does include PADDING of any variable-length 96 | // parameter except the last parameter in the chunk.* The receiver 97 | // MUST ignore the PADDING. 98 | if idx != self.params.len() - 1 { 99 | let cnt = get_padding_size(pp_len); 100 | buf.extend(vec![0u8; cnt]); 101 | } 102 | } 103 | Ok(buf.len()) 104 | } 105 | 106 | fn check(&self) -> Result<()> { 107 | Ok(()) 108 | } 109 | 110 | fn value_length(&self) -> usize { 111 | let mut l = 0; 112 | for (idx, p) in self.params.iter().enumerate() { 113 | let p_len = PARAM_HEADER_LENGTH + p.value_length(); 114 | l += p_len; 115 | if idx != self.params.len() - 1 { 116 | l += get_padding_size(p_len); 117 | } 118 | } 119 | l 120 | } 121 | 122 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 123 | self 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/chunk/chunk_reconfig.rs: -------------------------------------------------------------------------------- 1 | use super::{chunk_header::*, chunk_type::*, *}; 2 | use crate::param::{param_header::*, *}; 3 | use crate::util::get_padding_size; 4 | 5 | use bytes::{Bytes, BytesMut}; 6 | use std::fmt; 7 | 8 | ///https://tools.ietf.org/html/rfc6525#section-3.1 9 | ///chunkReconfig represents an SCTP Chunk used to reconfigure streams. 10 | /// 11 | /// 0 1 2 3 12 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 13 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 14 | ///| Type = 130 | Chunk Flags | Chunk Length | 15 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 16 | ///| | 17 | ///| Re-configuration Parameter | 18 | ///| | 19 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 20 | ///| | 21 | ///| Re-configuration Parameter (optional) | 22 | ///| | 23 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 24 | #[derive(Default, Debug)] 25 | pub(crate) struct ChunkReconfig { 26 | pub(crate) param_a: Option>, 27 | pub(crate) param_b: Option>, 28 | } 29 | 30 | impl Clone for ChunkReconfig { 31 | fn clone(&self) -> Self { 32 | ChunkReconfig { 33 | param_a: self.param_a.as_ref().cloned(), 34 | param_b: self.param_b.as_ref().cloned(), 35 | } 36 | } 37 | } 38 | 39 | /// makes chunkReconfig printable 40 | impl fmt::Display for ChunkReconfig { 41 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 42 | let mut res = String::new(); 43 | if let Some(param_a) = &self.param_a { 44 | res += format!("Param A:\n {}", param_a).as_str(); 45 | } 46 | if let Some(param_b) = &self.param_b { 47 | res += format!("Param B:\n {}", param_b).as_str() 48 | } 49 | write!(f, "{}", res) 50 | } 51 | } 52 | 53 | impl Chunk for ChunkReconfig { 54 | fn header(&self) -> ChunkHeader { 55 | ChunkHeader { 56 | typ: CT_RECONFIG, 57 | flags: 0, 58 | value_length: self.value_length() as u16, 59 | } 60 | } 61 | 62 | fn unmarshal(raw: &Bytes) -> Result { 63 | let header = ChunkHeader::unmarshal(raw)?; 64 | 65 | if header.typ != CT_RECONFIG { 66 | return Err(Error::ErrChunkTypeNotReconfig); 67 | } 68 | 69 | let param_a = 70 | build_param(&raw.slice(CHUNK_HEADER_SIZE..CHUNK_HEADER_SIZE + header.value_length()))?; 71 | 72 | let padding = get_padding_size(PARAM_HEADER_LENGTH + param_a.value_length()); 73 | let offset = CHUNK_HEADER_SIZE + PARAM_HEADER_LENGTH + param_a.value_length() + padding; 74 | let param_b = if CHUNK_HEADER_SIZE + header.value_length() > offset { 75 | Some(build_param( 76 | &raw.slice(offset..CHUNK_HEADER_SIZE + header.value_length()), 77 | )?) 78 | } else { 79 | None 80 | }; 81 | 82 | Ok(ChunkReconfig { 83 | param_a: Some(param_a), 84 | param_b, 85 | }) 86 | } 87 | 88 | fn marshal_to(&self, writer: &mut BytesMut) -> Result { 89 | self.header().marshal_to(writer)?; 90 | 91 | let param_a_value_length = if let Some(param_a) = &self.param_a { 92 | writer.extend(param_a.marshal()?); 93 | param_a.value_length() 94 | } else { 95 | return Err(Error::ErrChunkReconfigInvalidParamA); 96 | }; 97 | 98 | if let Some(param_b) = &self.param_b { 99 | // Pad param A 100 | let padding = get_padding_size(PARAM_HEADER_LENGTH + param_a_value_length); 101 | writer.extend(vec![0u8; padding]); 102 | writer.extend(param_b.marshal()?); 103 | } 104 | Ok(writer.len()) 105 | } 106 | 107 | fn check(&self) -> Result<()> { 108 | Ok(()) 109 | } 110 | 111 | fn value_length(&self) -> usize { 112 | let mut l = PARAM_HEADER_LENGTH; 113 | let param_a_value_length = if let Some(param_a) = &self.param_a { 114 | l += param_a.value_length(); 115 | param_a.value_length() 116 | } else { 117 | 0 118 | }; 119 | if let Some(param_b) = &self.param_b { 120 | let padding = get_padding_size(PARAM_HEADER_LENGTH + param_a_value_length); 121 | l += PARAM_HEADER_LENGTH + param_b.value_length() + padding; 122 | } 123 | l 124 | } 125 | 126 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 127 | self 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/param/param_reconfig_response.rs: -------------------------------------------------------------------------------- 1 | use super::{param_header::*, param_type::*, *}; 2 | 3 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 4 | use std::fmt; 5 | 6 | #[derive(Debug, Copy, Clone, PartialEq)] 7 | #[repr(C)] 8 | pub(crate) enum ReconfigResult { 9 | SuccessNop = 0, 10 | SuccessPerformed = 1, 11 | Denied = 2, 12 | ErrorWrongSsn = 3, 13 | ErrorRequestAlreadyInProgress = 4, 14 | ErrorBadSequenceNumber = 5, 15 | InProgress = 6, 16 | Unknown, 17 | } 18 | 19 | impl Default for ReconfigResult { 20 | fn default() -> Self { 21 | ReconfigResult::Unknown 22 | } 23 | } 24 | 25 | impl fmt::Display for ReconfigResult { 26 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 27 | let s = match *self { 28 | ReconfigResult::SuccessNop => "0: Success - Nothing to do", 29 | ReconfigResult::SuccessPerformed => "1: Success - Performed", 30 | ReconfigResult::Denied => "2: Denied", 31 | ReconfigResult::ErrorWrongSsn => "3: Error - Wrong SSN", 32 | ReconfigResult::ErrorRequestAlreadyInProgress => { 33 | "4: Error - Request already in progress" 34 | } 35 | ReconfigResult::ErrorBadSequenceNumber => "5: Error - Bad Sequence Number", 36 | ReconfigResult::InProgress => "6: In progress", 37 | _ => "Unknown ReconfigResult", 38 | }; 39 | write!(f, "{}", s) 40 | } 41 | } 42 | 43 | impl From for ReconfigResult { 44 | fn from(v: u32) -> ReconfigResult { 45 | match v { 46 | 0 => ReconfigResult::SuccessNop, 47 | 1 => ReconfigResult::SuccessPerformed, 48 | 2 => ReconfigResult::Denied, 49 | 3 => ReconfigResult::ErrorWrongSsn, 50 | 4 => ReconfigResult::ErrorRequestAlreadyInProgress, 51 | 5 => ReconfigResult::ErrorBadSequenceNumber, 52 | 6 => ReconfigResult::InProgress, 53 | _ => ReconfigResult::Unknown, 54 | } 55 | } 56 | } 57 | 58 | ///This parameter is used by the receiver of a Re-configuration Request 59 | ///Parameter to respond to the request. 60 | /// 61 | ///0 1 2 3 62 | ///0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 63 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 64 | ///| Parameter Type = 16 | Parameter Length | 65 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 66 | ///| Re-configuration Response Sequence Number | 67 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 68 | ///| Result | 69 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 70 | ///| Sender's Next TSN (optional) | 71 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 72 | ///| Receiver's Next TSN (optional) | 73 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 74 | #[derive(Default, Debug, Clone, PartialEq)] 75 | pub(crate) struct ParamReconfigResponse { 76 | /// This value is copied from the request parameter and is used by the 77 | /// receiver of the Re-configuration Response Parameter to tie the 78 | /// response to the request. 79 | pub(crate) reconfig_response_sequence_number: u32, 80 | /// This value describes the result of the processing of the request. 81 | pub(crate) result: ReconfigResult, 82 | } 83 | 84 | impl fmt::Display for ParamReconfigResponse { 85 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 86 | write!( 87 | f, 88 | "{} {} {}", 89 | self.header(), 90 | self.reconfig_response_sequence_number, 91 | self.result 92 | ) 93 | } 94 | } 95 | 96 | impl Param for ParamReconfigResponse { 97 | fn header(&self) -> ParamHeader { 98 | ParamHeader { 99 | typ: ParamType::ReconfigResp, 100 | value_length: self.value_length() as u16, 101 | } 102 | } 103 | 104 | fn unmarshal(raw: &Bytes) -> Result { 105 | let header = ParamHeader::unmarshal(raw)?; 106 | 107 | // validity of value_length is checked in ParamHeader::unmarshal 108 | if header.value_length < 8 { 109 | return Err(Error::ErrReconfigRespParamTooShort); 110 | } 111 | 112 | let reader = 113 | &mut raw.slice(PARAM_HEADER_LENGTH..PARAM_HEADER_LENGTH + header.value_length()); 114 | 115 | let reconfig_response_sequence_number = reader.get_u32(); 116 | let result = reader.get_u32().into(); 117 | 118 | Ok(ParamReconfigResponse { 119 | reconfig_response_sequence_number, 120 | result, 121 | }) 122 | } 123 | 124 | fn marshal_to(&self, buf: &mut BytesMut) -> Result { 125 | self.header().marshal_to(buf)?; 126 | buf.put_u32(self.reconfig_response_sequence_number); 127 | buf.put_u32(self.result as u32); 128 | Ok(buf.len()) 129 | } 130 | 131 | fn value_length(&self) -> usize { 132 | 8 133 | } 134 | 135 | fn clone_to(&self) -> Box { 136 | Box::new(self.clone()) 137 | } 138 | 139 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 140 | self 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/param/param_outgoing_reset_request.rs: -------------------------------------------------------------------------------- 1 | use super::{param_header::*, param_type::*, *}; 2 | 3 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 4 | 5 | pub(crate) const PARAM_OUTGOING_RESET_REQUEST_STREAM_IDENTIFIERS_OFFSET: usize = 12; 6 | 7 | ///This parameter is used by the sender to request the reset of some or 8 | ///all outgoing streams. 9 | /// 0 1 2 3 10 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 11 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 12 | ///| Parameter Type = 13 | Parameter Length = 16 + 2 * N | 13 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 14 | ///| Re-configuration Request Sequence Number | 15 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 16 | ///| Re-configuration Response Sequence Number | 17 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 18 | ///| Sender's Last Assigned TSN | 19 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 20 | ///| Stream Number 1 (optional) | Stream Number 2 (optional) | 21 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 22 | ///| ...... | 23 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 24 | ///| Stream Number N-1 (optional) | Stream Number N (optional) | 25 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 26 | #[derive(Default, Debug, Clone, PartialEq)] 27 | pub(crate) struct ParamOutgoingResetRequest { 28 | /// reconfig_request_sequence_number is used to identify the request. It is a monotonically 29 | /// increasing number that is initialized to the same value as the 30 | /// initial TSN. It is increased by 1 whenever sending a new Re- 31 | /// configuration Request Parameter. 32 | pub(crate) reconfig_request_sequence_number: u32, 33 | /// When this Outgoing SSN Reset Request Parameter is sent in response 34 | /// to an Incoming SSN Reset Request Parameter, this parameter is also 35 | /// an implicit response to the incoming request. This field then 36 | /// holds the Re-configuration Request Sequence Number of the incoming 37 | /// request. In other cases, it holds the next expected 38 | /// Re-configuration Request Sequence Number minus 1. 39 | pub(crate) reconfig_response_sequence_number: u32, 40 | /// This value holds the next TSN minus 1 -- in other words, the last 41 | /// TSN that this sender assigned. 42 | pub(crate) sender_last_tsn: u32, 43 | /// This optional field, if included, is used to indicate specific 44 | /// streams that are to be reset. If no streams are listed, then all 45 | /// streams are to be reset. 46 | pub(crate) stream_identifiers: Vec, 47 | } 48 | 49 | impl fmt::Display for ParamOutgoingResetRequest { 50 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 51 | write!( 52 | f, 53 | "{} {} {} {} {:?}", 54 | self.header(), 55 | self.reconfig_request_sequence_number, 56 | self.reconfig_request_sequence_number, 57 | self.reconfig_response_sequence_number, 58 | self.stream_identifiers 59 | ) 60 | } 61 | } 62 | 63 | impl Param for ParamOutgoingResetRequest { 64 | fn header(&self) -> ParamHeader { 65 | ParamHeader { 66 | typ: ParamType::OutSsnResetReq, 67 | value_length: self.value_length() as u16, 68 | } 69 | } 70 | 71 | fn unmarshal(raw: &Bytes) -> Result { 72 | let header = ParamHeader::unmarshal(raw)?; 73 | 74 | // validity of value_length is checked in ParamHeader::unmarshal 75 | if header.value_length() < PARAM_OUTGOING_RESET_REQUEST_STREAM_IDENTIFIERS_OFFSET { 76 | return Err(Error::ErrSsnResetRequestParamTooShort); 77 | } 78 | 79 | let reader = 80 | &mut raw.slice(PARAM_HEADER_LENGTH..PARAM_HEADER_LENGTH + header.value_length()); 81 | let reconfig_request_sequence_number = reader.get_u32(); 82 | let reconfig_response_sequence_number = reader.get_u32(); 83 | let sender_last_tsn = reader.get_u32(); 84 | 85 | let lim = 86 | (header.value_length() - PARAM_OUTGOING_RESET_REQUEST_STREAM_IDENTIFIERS_OFFSET) / 2; 87 | let mut stream_identifiers = vec![]; 88 | for _ in 0..lim { 89 | stream_identifiers.push(reader.get_u16()); 90 | } 91 | 92 | Ok(ParamOutgoingResetRequest { 93 | reconfig_request_sequence_number, 94 | reconfig_response_sequence_number, 95 | sender_last_tsn, 96 | stream_identifiers, 97 | }) 98 | } 99 | 100 | fn marshal_to(&self, buf: &mut BytesMut) -> Result { 101 | self.header().marshal_to(buf)?; 102 | buf.put_u32(self.reconfig_request_sequence_number); 103 | buf.put_u32(self.reconfig_response_sequence_number); 104 | buf.put_u32(self.sender_last_tsn); 105 | for sid in &self.stream_identifiers { 106 | buf.put_u16(*sid); 107 | } 108 | Ok(buf.len()) 109 | } 110 | 111 | fn value_length(&self) -> usize { 112 | PARAM_OUTGOING_RESET_REQUEST_STREAM_IDENTIFIERS_OFFSET + self.stream_identifiers.len() * 2 113 | } 114 | 115 | fn clone_to(&self) -> Box { 116 | Box::new(self.clone()) 117 | } 118 | 119 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 120 | self 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/queue/payload_queue.rs: -------------------------------------------------------------------------------- 1 | use crate::chunk::chunk_payload_data::ChunkPayloadData; 2 | use crate::chunk::chunk_selective_ack::GapAckBlock; 3 | use crate::util::*; 4 | 5 | use std::collections::HashMap; 6 | use std::sync::atomic::{AtomicUsize, Ordering}; 7 | use std::sync::Arc; 8 | 9 | #[derive(Default, Debug)] 10 | pub(crate) struct PayloadQueue { 11 | pub(crate) length: Arc, 12 | pub(crate) chunk_map: HashMap, 13 | pub(crate) sorted: Vec, 14 | pub(crate) dup_tsn: Vec, 15 | pub(crate) n_bytes: usize, 16 | } 17 | 18 | impl PayloadQueue { 19 | pub(crate) fn new(length: Arc) -> Self { 20 | length.store(0, Ordering::SeqCst); 21 | PayloadQueue { 22 | length, 23 | ..Default::default() 24 | } 25 | } 26 | 27 | pub(crate) fn update_sorted_keys(&mut self) { 28 | self.sorted.sort_by(|a, b| { 29 | if sna32lt(*a, *b) { 30 | std::cmp::Ordering::Less 31 | } else { 32 | std::cmp::Ordering::Greater 33 | } 34 | }); 35 | } 36 | 37 | pub(crate) fn can_push(&self, p: &ChunkPayloadData, cumulative_tsn: u32) -> bool { 38 | !(self.chunk_map.contains_key(&p.tsn) || sna32lte(p.tsn, cumulative_tsn)) 39 | } 40 | 41 | pub(crate) fn push_no_check(&mut self, p: ChunkPayloadData) { 42 | self.n_bytes += p.user_data.len(); 43 | self.sorted.push(p.tsn); 44 | self.chunk_map.insert(p.tsn, p); 45 | self.length.fetch_add(1, Ordering::SeqCst); 46 | self.update_sorted_keys(); 47 | } 48 | 49 | /// push pushes a payload data. If the payload data is already in our queue or 50 | /// older than our cumulative_tsn marker, it will be recored as duplications, 51 | /// which can later be retrieved using popDuplicates. 52 | pub(crate) fn push(&mut self, p: ChunkPayloadData, cumulative_tsn: u32) -> bool { 53 | let ok = self.chunk_map.contains_key(&p.tsn); 54 | if ok || sna32lte(p.tsn, cumulative_tsn) { 55 | // Found the packet, log in dups 56 | self.dup_tsn.push(p.tsn); 57 | return false; 58 | } 59 | 60 | self.n_bytes += p.user_data.len(); 61 | self.sorted.push(p.tsn); 62 | self.chunk_map.insert(p.tsn, p); 63 | self.length.fetch_add(1, Ordering::SeqCst); 64 | self.update_sorted_keys(); 65 | 66 | true 67 | } 68 | 69 | /// pop pops only if the oldest chunk's TSN matches the given TSN. 70 | pub(crate) fn pop(&mut self, tsn: u32) -> Option { 71 | if !self.sorted.is_empty() && tsn == self.sorted[0] { 72 | self.sorted.remove(0); 73 | if let Some(c) = self.chunk_map.remove(&tsn) { 74 | self.length.fetch_sub(1, Ordering::SeqCst); 75 | self.n_bytes -= c.user_data.len(); 76 | return Some(c); 77 | } 78 | } 79 | 80 | None 81 | } 82 | 83 | /// get returns reference to chunkPayloadData with the given TSN value. 84 | pub(crate) fn get(&self, tsn: u32) -> Option<&ChunkPayloadData> { 85 | self.chunk_map.get(&tsn) 86 | } 87 | pub(crate) fn get_mut(&mut self, tsn: u32) -> Option<&mut ChunkPayloadData> { 88 | self.chunk_map.get_mut(&tsn) 89 | } 90 | 91 | /// popDuplicates returns an array of TSN values that were found duplicate. 92 | pub(crate) fn pop_duplicates(&mut self) -> Vec { 93 | self.dup_tsn.drain(..).collect() 94 | } 95 | 96 | pub(crate) fn get_gap_ack_blocks(&self, cumulative_tsn: u32) -> Vec { 97 | if self.chunk_map.is_empty() { 98 | return vec![]; 99 | } 100 | 101 | let mut b = GapAckBlock::default(); 102 | let mut gap_ack_blocks = vec![]; 103 | for (i, tsn) in self.sorted.iter().enumerate() { 104 | let diff = if *tsn >= cumulative_tsn { 105 | (*tsn - cumulative_tsn) as u16 106 | } else { 107 | 0 108 | }; 109 | 110 | if i == 0 { 111 | b.start = diff; 112 | b.end = b.start; 113 | } else if b.end + 1 == diff { 114 | b.end += 1; 115 | } else { 116 | gap_ack_blocks.push(b); 117 | 118 | b.start = diff; 119 | b.end = diff; 120 | } 121 | } 122 | 123 | gap_ack_blocks.push(b); 124 | 125 | gap_ack_blocks 126 | } 127 | 128 | pub(crate) fn get_gap_ack_blocks_string(&self, cumulative_tsn: u32) -> String { 129 | let mut s = format!("cumTSN={}", cumulative_tsn); 130 | for b in self.get_gap_ack_blocks(cumulative_tsn) { 131 | s += format!(",{}-{}", b.start, b.end).as_str(); 132 | } 133 | s 134 | } 135 | 136 | pub(crate) fn mark_as_acked(&mut self, tsn: u32) -> usize { 137 | let n_bytes_acked = if let Some(c) = self.chunk_map.get_mut(&tsn) { 138 | c.acked = true; 139 | c.retransmit = false; 140 | let n = c.user_data.len(); 141 | self.n_bytes -= n; 142 | c.user_data.clear(); 143 | n 144 | } else { 145 | 0 146 | }; 147 | 148 | n_bytes_acked 149 | } 150 | 151 | pub(crate) fn get_last_tsn_received(&self) -> Option<&u32> { 152 | self.sorted.last() 153 | } 154 | 155 | pub(crate) fn mark_all_to_retrasmit(&mut self) { 156 | for c in self.chunk_map.values_mut() { 157 | if c.acked || c.abandoned() { 158 | continue; 159 | } 160 | c.retransmit = true; 161 | } 162 | } 163 | 164 | pub(crate) fn get_num_bytes(&self) -> usize { 165 | self.n_bytes 166 | } 167 | 168 | pub(crate) fn len(&self) -> usize { 169 | assert_eq!(self.chunk_map.len(), self.length.load(Ordering::SeqCst)); 170 | self.chunk_map.len() 171 | } 172 | 173 | pub(crate) fn is_empty(&self) -> bool { 174 | self.len() == 0 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/error_cause.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, Result}; 2 | 3 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 4 | use std::fmt; 5 | 6 | /// errorCauseCode is a cause code that appears in either a ERROR or ABORT chunk 7 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] 8 | pub(crate) struct ErrorCauseCode(pub(crate) u16); 9 | 10 | pub(crate) const INVALID_STREAM_IDENTIFIER: ErrorCauseCode = ErrorCauseCode(1); 11 | pub(crate) const MISSING_MANDATORY_PARAMETER: ErrorCauseCode = ErrorCauseCode(2); 12 | pub(crate) const STALE_COOKIE_ERROR: ErrorCauseCode = ErrorCauseCode(3); 13 | pub(crate) const OUT_OF_RESOURCE: ErrorCauseCode = ErrorCauseCode(4); 14 | pub(crate) const UNRESOLVABLE_ADDRESS: ErrorCauseCode = ErrorCauseCode(5); 15 | pub(crate) const UNRECOGNIZED_CHUNK_TYPE: ErrorCauseCode = ErrorCauseCode(6); 16 | pub(crate) const INVALID_MANDATORY_PARAMETER: ErrorCauseCode = ErrorCauseCode(7); 17 | pub(crate) const UNRECOGNIZED_PARAMETERS: ErrorCauseCode = ErrorCauseCode(8); 18 | pub(crate) const NO_USER_DATA: ErrorCauseCode = ErrorCauseCode(9); 19 | pub(crate) const COOKIE_RECEIVED_WHILE_SHUTTING_DOWN: ErrorCauseCode = ErrorCauseCode(10); 20 | pub(crate) const RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES: ErrorCauseCode = ErrorCauseCode(11); 21 | pub(crate) const USER_INITIATED_ABORT: ErrorCauseCode = ErrorCauseCode(12); 22 | pub(crate) const PROTOCOL_VIOLATION: ErrorCauseCode = ErrorCauseCode(13); 23 | 24 | impl fmt::Display for ErrorCauseCode { 25 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 26 | let others = format!("Unknown CauseCode: {}", self.0); 27 | let s = match *self { 28 | INVALID_STREAM_IDENTIFIER => "Invalid Stream Identifier", 29 | MISSING_MANDATORY_PARAMETER => "Missing Mandatory Parameter", 30 | STALE_COOKIE_ERROR => "Stale Cookie Error", 31 | OUT_OF_RESOURCE => "Out Of Resource", 32 | UNRESOLVABLE_ADDRESS => "Unresolvable IP", 33 | UNRECOGNIZED_CHUNK_TYPE => "Unrecognized Chunk Type", 34 | INVALID_MANDATORY_PARAMETER => "Invalid Mandatory Parameter", 35 | UNRECOGNIZED_PARAMETERS => "Unrecognized Parameters", 36 | NO_USER_DATA => "No User Data", 37 | COOKIE_RECEIVED_WHILE_SHUTTING_DOWN => "Cookie Received While Shutting Down", 38 | RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES => { 39 | "Restart Of An Association With New Addresses" 40 | } 41 | USER_INITIATED_ABORT => "User Initiated Abort", 42 | PROTOCOL_VIOLATION => "Protocol Violation", 43 | _ => others.as_str(), 44 | }; 45 | write!(f, "{}", s) 46 | } 47 | } 48 | 49 | /// ErrorCauseHeader represents the shared header that is shared by all error causes 50 | #[derive(Debug, Clone, Default)] 51 | pub(crate) struct ErrorCause { 52 | pub(crate) code: ErrorCauseCode, 53 | pub(crate) raw: Bytes, 54 | } 55 | 56 | /// ErrorCauseInvalidMandatoryParameter represents an SCTP error cause 57 | pub(crate) type ErrorCauseInvalidMandatoryParameter = ErrorCause; 58 | 59 | /// ErrorCauseUnrecognizedChunkType represents an SCTP error cause 60 | pub(crate) type ErrorCauseUnrecognizedChunkType = ErrorCause; 61 | 62 | /// 63 | /// This error cause MAY be included in ABORT chunks that are sent 64 | /// because an SCTP endpoint detects a protocol violation of the peer 65 | /// that is not covered by the error causes described in Section 3.3.10.1 66 | /// to Section 3.3.10.12. An implementation MAY provide additional 67 | /// information specifying what kind of protocol violation has been 68 | /// detected. 69 | /// 0 1 2 3 70 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 71 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 72 | /// | Cause Code=13 | Cause Length=Variable | 73 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 74 | /// / Additional Information / 75 | /// \ \ 76 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 77 | /// 78 | pub(crate) type ErrorCauseProtocolViolation = ErrorCause; 79 | 80 | pub(crate) const ERROR_CAUSE_HEADER_LENGTH: usize = 4; 81 | 82 | /// makes ErrorCauseHeader printable 83 | impl fmt::Display for ErrorCause { 84 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 85 | write!(f, "{}", self.code) 86 | } 87 | } 88 | 89 | impl ErrorCause { 90 | pub(crate) fn unmarshal(buf: &Bytes) -> Result { 91 | if buf.len() < ERROR_CAUSE_HEADER_LENGTH { 92 | return Err(Error::ErrErrorCauseTooSmall); 93 | } 94 | 95 | let reader = &mut buf.clone(); 96 | 97 | let code = ErrorCauseCode(reader.get_u16()); 98 | let len = reader.get_u16(); 99 | 100 | if len < ERROR_CAUSE_HEADER_LENGTH as u16 { 101 | return Err(Error::ErrErrorCauseTooSmall); 102 | } 103 | if buf.len() < len as usize { 104 | return Err(Error::ErrErrorCauseTooSmall); 105 | } 106 | 107 | let value_length = len as usize - ERROR_CAUSE_HEADER_LENGTH; 108 | 109 | let raw = buf.slice(ERROR_CAUSE_HEADER_LENGTH..ERROR_CAUSE_HEADER_LENGTH + value_length); 110 | 111 | Ok(ErrorCause { code, raw }) 112 | } 113 | 114 | pub(crate) fn marshal(&self) -> Bytes { 115 | let mut buf = BytesMut::with_capacity(self.length()); 116 | let _ = self.marshal_to(&mut buf); 117 | buf.freeze() 118 | } 119 | 120 | pub(crate) fn marshal_to(&self, writer: &mut BytesMut) -> usize { 121 | let len = self.raw.len() + ERROR_CAUSE_HEADER_LENGTH; 122 | writer.put_u16(self.code.0); 123 | writer.put_u16(len as u16); 124 | writer.extend(self.raw.clone()); 125 | writer.len() 126 | } 127 | 128 | pub(crate) fn length(&self) -> usize { 129 | self.raw.len() + ERROR_CAUSE_HEADER_LENGTH 130 | } 131 | 132 | pub(crate) fn error_cause_code(&self) -> ErrorCauseCode { 133 | self.code 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/chunk/chunk_forward_tsn.rs: -------------------------------------------------------------------------------- 1 | use super::{chunk_header::*, chunk_type::*, *}; 2 | 3 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 4 | use std::fmt; 5 | 6 | ///This chunk shall be used by the data sender to inform the data 7 | ///receiver to adjust its cumulative received TSN point forward because 8 | ///some missing TSNs are associated with data chunks that SHOULD NOT be 9 | ///transmitted or retransmitted by the sender. 10 | /// 0 1 2 3 11 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 12 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 13 | ///| Type = 192 | Flags = 0x00 | Length = Variable | 14 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 15 | ///| New Cumulative TSN | 16 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 17 | ///| Stream-1 | Stream Sequence-1 | 18 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 19 | ///| | 20 | ///| | 21 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 22 | ///| Stream-N | Stream Sequence-N | 23 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 24 | #[derive(Default, Debug, Clone)] 25 | pub(crate) struct ChunkForwardTsn { 26 | /// This indicates the new cumulative TSN to the data receiver. Upon 27 | /// the reception of this value, the data receiver MUST consider 28 | /// any missing TSNs earlier than or equal to this value as received, 29 | /// and stop reporting them as gaps in any subsequent SACKs. 30 | pub(crate) new_cumulative_tsn: u32, 31 | pub(crate) streams: Vec, 32 | } 33 | 34 | pub(crate) const NEW_CUMULATIVE_TSN_LENGTH: usize = 4; 35 | pub(crate) const FORWARD_TSN_STREAM_LENGTH: usize = 4; 36 | 37 | /// makes ChunkForwardTsn printable 38 | impl fmt::Display for ChunkForwardTsn { 39 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 40 | let mut res = vec![self.header().to_string()]; 41 | res.push(format!("New Cumulative TSN: {}", self.new_cumulative_tsn)); 42 | for s in &self.streams { 43 | res.push(format!(" - si={}, ssn={}", s.identifier, s.sequence)); 44 | } 45 | 46 | write!(f, "{}", res.join("\n")) 47 | } 48 | } 49 | 50 | impl Chunk for ChunkForwardTsn { 51 | fn header(&self) -> ChunkHeader { 52 | ChunkHeader { 53 | typ: CT_FORWARD_TSN, 54 | flags: 0, 55 | value_length: self.value_length() as u16, 56 | } 57 | } 58 | 59 | fn unmarshal(buf: &Bytes) -> Result { 60 | let header = ChunkHeader::unmarshal(buf)?; 61 | 62 | if header.typ != CT_FORWARD_TSN { 63 | return Err(Error::ErrChunkTypeNotForwardTsn); 64 | } 65 | 66 | let mut offset = CHUNK_HEADER_SIZE + NEW_CUMULATIVE_TSN_LENGTH; 67 | if buf.len() < offset { 68 | return Err(Error::ErrChunkTooShort); 69 | } 70 | 71 | let reader = &mut buf.slice(CHUNK_HEADER_SIZE..CHUNK_HEADER_SIZE + header.value_length()); 72 | let new_cumulative_tsn = reader.get_u32(); 73 | 74 | let mut streams = vec![]; 75 | let mut remaining = buf.len() - offset; 76 | while remaining > 0 { 77 | let s = ChunkForwardTsnStream::unmarshal( 78 | &buf.slice(offset..CHUNK_HEADER_SIZE + header.value_length()), 79 | )?; 80 | offset += s.value_length(); 81 | remaining -= s.value_length(); 82 | streams.push(s); 83 | } 84 | 85 | Ok(ChunkForwardTsn { 86 | new_cumulative_tsn, 87 | streams, 88 | }) 89 | } 90 | 91 | fn marshal_to(&self, writer: &mut BytesMut) -> Result { 92 | self.header().marshal_to(writer)?; 93 | 94 | writer.put_u32(self.new_cumulative_tsn); 95 | 96 | for s in &self.streams { 97 | writer.extend(s.marshal()?); 98 | } 99 | 100 | Ok(writer.len()) 101 | } 102 | 103 | fn check(&self) -> Result<()> { 104 | Ok(()) 105 | } 106 | 107 | fn value_length(&self) -> usize { 108 | NEW_CUMULATIVE_TSN_LENGTH + FORWARD_TSN_STREAM_LENGTH * self.streams.len() 109 | } 110 | 111 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 112 | self 113 | } 114 | } 115 | 116 | #[derive(Debug, Clone)] 117 | pub(crate) struct ChunkForwardTsnStream { 118 | /// This field holds a stream number that was skipped by this 119 | /// FWD-TSN. 120 | pub(crate) identifier: u16, 121 | 122 | /// This field holds the sequence number associated with the stream 123 | /// that was skipped. The stream sequence field holds the largest 124 | /// stream sequence number in this stream being skipped. The receiver 125 | /// of the FWD-TSN's can use the Stream-N and Stream Sequence-N fields 126 | /// to enable delivery of any stranded TSN's that remain on the stream 127 | /// re-ordering queues. This field MUST NOT report TSN's corresponding 128 | /// to DATA chunks that are marked as unordered. For ordered DATA 129 | /// chunks this field MUST be filled in. 130 | pub(crate) sequence: u16, 131 | } 132 | 133 | /// makes ChunkForwardTsnStream printable 134 | impl fmt::Display for ChunkForwardTsnStream { 135 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 136 | write!(f, "{}, {}", self.identifier, self.sequence) 137 | } 138 | } 139 | 140 | impl Chunk for ChunkForwardTsnStream { 141 | fn header(&self) -> ChunkHeader { 142 | ChunkHeader { 143 | typ: ChunkType(0), 144 | flags: 0, 145 | value_length: self.value_length() as u16, 146 | } 147 | } 148 | 149 | fn unmarshal(buf: &Bytes) -> Result { 150 | if buf.len() < FORWARD_TSN_STREAM_LENGTH { 151 | return Err(Error::ErrChunkTooShort); 152 | } 153 | 154 | let reader = &mut buf.clone(); 155 | let identifier = reader.get_u16(); 156 | let sequence = reader.get_u16(); 157 | 158 | Ok(ChunkForwardTsnStream { 159 | identifier, 160 | sequence, 161 | }) 162 | } 163 | 164 | fn marshal_to(&self, writer: &mut BytesMut) -> Result { 165 | writer.put_u16(self.identifier); 166 | writer.put_u16(self.sequence); 167 | Ok(writer.len()) 168 | } 169 | 170 | fn check(&self) -> Result<()> { 171 | Ok(()) 172 | } 173 | 174 | fn value_length(&self) -> usize { 175 | FORWARD_TSN_STREAM_LENGTH 176 | } 177 | 178 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 179 | self 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/chunk/chunk_selective_ack.rs: -------------------------------------------------------------------------------- 1 | use super::{chunk_header::*, chunk_type::*, *}; 2 | 3 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 4 | use std::fmt; 5 | 6 | ///chunkSelectiveAck represents an SCTP Chunk of type SACK 7 | /// 8 | ///This chunk is sent to the peer endpoint to acknowledge received DATA 9 | ///chunks and to inform the peer endpoint of gaps in the received 10 | ///subsequences of DATA chunks as represented by their TSNs. 11 | ///0 1 2 3 12 | ///0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 13 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 14 | ///| Type = 3 |Chunk Flags | Chunk Length | 15 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 16 | ///| Cumulative TSN Ack | 17 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 18 | ///| Advertised Receiver Window Credit (a_rwnd) | 19 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 20 | ///| Number of Gap Ack Blocks = N | Number of Duplicate TSNs = X | 21 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 22 | ///| Gap Ack Block #1 Start | Gap Ack Block #1 End | 23 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 24 | ///| | 25 | ///| ... | 26 | ///| | 27 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 28 | ///| Gap Ack Block #N Start | Gap Ack Block #N End | 29 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 30 | ///| Duplicate TSN 1 | 31 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 32 | ///| | 33 | ///| ... | 34 | ///| | 35 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 36 | ///| Duplicate TSN X | 37 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 38 | #[derive(Debug, Default, Copy, Clone)] 39 | pub(crate) struct GapAckBlock { 40 | pub(crate) start: u16, 41 | pub(crate) end: u16, 42 | } 43 | 44 | /// makes gapAckBlock printable 45 | impl fmt::Display for GapAckBlock { 46 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 47 | write!(f, "{} - {}", self.start, self.end) 48 | } 49 | } 50 | 51 | #[derive(Default, Debug)] 52 | pub(crate) struct ChunkSelectiveAck { 53 | pub(crate) cumulative_tsn_ack: u32, 54 | pub(crate) advertised_receiver_window_credit: u32, 55 | pub(crate) gap_ack_blocks: Vec, 56 | pub(crate) duplicate_tsn: Vec, 57 | } 58 | 59 | /// makes chunkSelectiveAck printable 60 | impl fmt::Display for ChunkSelectiveAck { 61 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 62 | let mut res = format!( 63 | "SACK cumTsnAck={} arwnd={} dupTsn={:?}", 64 | self.cumulative_tsn_ack, self.advertised_receiver_window_credit, self.duplicate_tsn 65 | ); 66 | 67 | for gap in &self.gap_ack_blocks { 68 | res += format!("\n gap ack: {}", gap).as_str(); 69 | } 70 | 71 | write!(f, "{}", res) 72 | } 73 | } 74 | 75 | pub(crate) const SELECTIVE_ACK_HEADER_SIZE: usize = 12; 76 | 77 | impl Chunk for ChunkSelectiveAck { 78 | fn header(&self) -> ChunkHeader { 79 | ChunkHeader { 80 | typ: CT_SACK, 81 | flags: 0, 82 | value_length: self.value_length() as u16, 83 | } 84 | } 85 | 86 | fn unmarshal(raw: &Bytes) -> Result { 87 | let header = ChunkHeader::unmarshal(raw)?; 88 | 89 | if header.typ != CT_SACK { 90 | return Err(Error::ErrChunkTypeNotSack); 91 | } 92 | 93 | // validity of value_length is checked in ChunkHeader::unmarshal 94 | if header.value_length() < SELECTIVE_ACK_HEADER_SIZE { 95 | return Err(Error::ErrSackSizeNotLargeEnoughInfo); 96 | } 97 | 98 | let reader = &mut raw.slice(CHUNK_HEADER_SIZE..CHUNK_HEADER_SIZE + header.value_length()); 99 | 100 | let cumulative_tsn_ack = reader.get_u32(); 101 | let advertised_receiver_window_credit = reader.get_u32(); 102 | let gap_ack_blocks_len = reader.get_u16() as usize; 103 | let duplicate_tsn_len = reader.get_u16() as usize; 104 | 105 | // Here we must account for case where the buffer contains another chunk 106 | // right after this one. Testing for equality would incorrectly fail the 107 | // parsing of this chunk and incorrectly close the transport. 108 | 109 | // validity of value_length is checked in ChunkHeader::unmarshal 110 | if header.value_length() 111 | < SELECTIVE_ACK_HEADER_SIZE + (4 * gap_ack_blocks_len + 4 * duplicate_tsn_len) 112 | { 113 | return Err(Error::ErrSackSizeNotLargeEnoughInfo); 114 | } 115 | 116 | let mut gap_ack_blocks = vec![]; 117 | let mut duplicate_tsn = vec![]; 118 | for _ in 0..gap_ack_blocks_len { 119 | let start = reader.get_u16(); 120 | let end = reader.get_u16(); 121 | gap_ack_blocks.push(GapAckBlock { start, end }); 122 | } 123 | for _ in 0..duplicate_tsn_len { 124 | duplicate_tsn.push(reader.get_u32()); 125 | } 126 | 127 | Ok(ChunkSelectiveAck { 128 | cumulative_tsn_ack, 129 | advertised_receiver_window_credit, 130 | gap_ack_blocks, 131 | duplicate_tsn, 132 | }) 133 | } 134 | 135 | fn marshal_to(&self, writer: &mut BytesMut) -> Result { 136 | self.header().marshal_to(writer)?; 137 | 138 | writer.put_u32(self.cumulative_tsn_ack); 139 | writer.put_u32(self.advertised_receiver_window_credit); 140 | writer.put_u16(self.gap_ack_blocks.len() as u16); 141 | writer.put_u16(self.duplicate_tsn.len() as u16); 142 | for g in &self.gap_ack_blocks { 143 | writer.put_u16(g.start); 144 | writer.put_u16(g.end); 145 | } 146 | for t in &self.duplicate_tsn { 147 | writer.put_u32(*t); 148 | } 149 | 150 | Ok(writer.len()) 151 | } 152 | 153 | fn check(&self) -> Result<()> { 154 | Ok(()) 155 | } 156 | 157 | fn value_length(&self) -> usize { 158 | SELECTIVE_ACK_HEADER_SIZE + self.gap_ack_blocks.len() * 4 + self.duplicate_tsn.len() * 4 159 | } 160 | 161 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 162 | self 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/param/param_type.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | /// paramType represents a SCTP INIT/INITACK parameter 4 | #[derive(Debug, Copy, Clone, PartialEq)] 5 | #[repr(C)] 6 | pub(crate) enum ParamType { 7 | HeartbeatInfo, 8 | /// Heartbeat Info [RFCRFC4960] 9 | Ipv4Addr, 10 | /// IPv4 IP [RFCRFC4960] 11 | Ipv6Addr, 12 | /// IPv6 IP [RFCRFC4960] 13 | StateCookie, 14 | /// State Cookie [RFCRFC4960] 15 | UnrecognizedParam, 16 | /// Unrecognized Parameters [RFCRFC4960] 17 | CookiePreservative, 18 | /// Cookie Preservative [RFCRFC4960] 19 | HostNameAddr, 20 | /// Host Name IP [RFCRFC4960] 21 | SupportedAddrTypes, 22 | /// Supported IP Types [RFCRFC4960] 23 | OutSsnResetReq, 24 | /// Outgoing SSN Reset Request Parameter [RFCRFC6525] 25 | IncSsnResetReq, 26 | /// Incoming SSN Reset Request Parameter [RFCRFC6525] 27 | SsnTsnResetReq, 28 | /// SSN/TSN Reset Request Parameter [RFCRFC6525] 29 | ReconfigResp, 30 | /// Re-configuration Response Parameter [RFCRFC6525] 31 | AddOutStreamsReq, 32 | /// Add Outgoing Streams Request Parameter [RFCRFC6525] 33 | AddIncStreamsReq, 34 | /// Add Incoming Streams Request Parameter [RFCRFC6525] 35 | Random, 36 | /// Random (0x8002) [RFCRFC4805] 37 | ChunkList, 38 | /// Chunk List (0x8003) [RFCRFC4895] 39 | ReqHmacAlgo, 40 | /// Requested HMAC Algorithm Parameter (0x8004) [RFCRFC4895] 41 | Padding, 42 | /// Padding (0x8005) 43 | SupportedExt, 44 | /// Supported Extensions (0x8008) [RFCRFC5061] 45 | ForwardTsnSupp, 46 | /// Forward TSN supported (0xC000) [RFCRFC3758] 47 | AddIpAddr, 48 | /// Add IP IP (0xC001) [RFCRFC5061] 49 | DelIpaddr, 50 | /// Delete IP IP (0xC002) [RFCRFC5061] 51 | ErrClauseInd, 52 | /// Error Cause Indication (0xC003) [RFCRFC5061] 53 | SetPriAddr, 54 | /// Set Primary IP (0xC004) [RFCRFC5061] 55 | SuccessInd, 56 | /// Success Indication (0xC005) [RFCRFC5061] 57 | AdaptLayerInd, 58 | /// Adaptation Layer Indication (0xC006) [RFCRFC5061] 59 | Unknown { 60 | param_type: u16, 61 | }, 62 | } 63 | 64 | impl fmt::Display for ParamType { 65 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 66 | let s = match *self { 67 | ParamType::HeartbeatInfo => "Heartbeat Info", 68 | ParamType::Ipv4Addr => "IPv4 IP", 69 | ParamType::Ipv6Addr => "IPv6 IP", 70 | ParamType::StateCookie => "State Cookie", 71 | ParamType::UnrecognizedParam => "Unrecognized Parameters", 72 | ParamType::CookiePreservative => "Cookie Preservative", 73 | ParamType::HostNameAddr => "Host Name IP", 74 | ParamType::SupportedAddrTypes => "Supported IP Types", 75 | ParamType::OutSsnResetReq => "Outgoing SSN Reset Request Parameter", 76 | ParamType::IncSsnResetReq => "Incoming SSN Reset Request Parameter", 77 | ParamType::SsnTsnResetReq => "SSN/TSN Reset Request Parameter", 78 | ParamType::ReconfigResp => "Re-configuration Response Parameter", 79 | ParamType::AddOutStreamsReq => "Add Outgoing Streams Request Parameter", 80 | ParamType::AddIncStreamsReq => "Add Incoming Streams Request Parameter", 81 | ParamType::Random => "Random", 82 | ParamType::ChunkList => "Chunk List", 83 | ParamType::ReqHmacAlgo => "Requested HMAC Algorithm Parameter", 84 | ParamType::Padding => "Padding", 85 | ParamType::SupportedExt => "Supported Extensions", 86 | ParamType::ForwardTsnSupp => "Forward TSN supported", 87 | ParamType::AddIpAddr => "Add IP IP", 88 | ParamType::DelIpaddr => "Delete IP IP", 89 | ParamType::ErrClauseInd => "Error Cause Indication", 90 | ParamType::SetPriAddr => "Set Primary IP", 91 | ParamType::SuccessInd => "Success Indication", 92 | ParamType::AdaptLayerInd => "Adaptation Layer Indication", 93 | _ => "Unknown ParamType", 94 | }; 95 | write!(f, "{}", s) 96 | } 97 | } 98 | 99 | impl From for ParamType { 100 | fn from(v: u16) -> ParamType { 101 | match v { 102 | 1 => ParamType::HeartbeatInfo, 103 | 5 => ParamType::Ipv4Addr, 104 | 6 => ParamType::Ipv6Addr, 105 | 7 => ParamType::StateCookie, 106 | 8 => ParamType::UnrecognizedParam, 107 | 9 => ParamType::CookiePreservative, 108 | 11 => ParamType::HostNameAddr, 109 | 12 => ParamType::SupportedAddrTypes, 110 | 13 => ParamType::OutSsnResetReq, 111 | 14 => ParamType::IncSsnResetReq, 112 | 15 => ParamType::SsnTsnResetReq, 113 | 16 => ParamType::ReconfigResp, 114 | 17 => ParamType::AddOutStreamsReq, 115 | 18 => ParamType::AddIncStreamsReq, 116 | 32770 => ParamType::Random, 117 | 32771 => ParamType::ChunkList, 118 | 32772 => ParamType::ReqHmacAlgo, 119 | 32773 => ParamType::Padding, 120 | 32776 => ParamType::SupportedExt, 121 | 49152 => ParamType::ForwardTsnSupp, 122 | 49153 => ParamType::AddIpAddr, 123 | 49154 => ParamType::DelIpaddr, 124 | 49155 => ParamType::ErrClauseInd, 125 | 49156 => ParamType::SetPriAddr, 126 | 49157 => ParamType::SuccessInd, 127 | 49158 => ParamType::AdaptLayerInd, 128 | unknown => ParamType::Unknown { 129 | param_type: unknown, 130 | }, 131 | } 132 | } 133 | } 134 | 135 | impl From for u16 { 136 | fn from(v: ParamType) -> u16 { 137 | match v { 138 | ParamType::HeartbeatInfo => 1, 139 | ParamType::Ipv4Addr => 5, 140 | ParamType::Ipv6Addr => 6, 141 | ParamType::StateCookie => 7, 142 | ParamType::UnrecognizedParam => 8, 143 | ParamType::CookiePreservative => 9, 144 | ParamType::HostNameAddr => 11, 145 | ParamType::SupportedAddrTypes => 12, 146 | ParamType::OutSsnResetReq => 13, 147 | ParamType::IncSsnResetReq => 14, 148 | ParamType::SsnTsnResetReq => 15, 149 | ParamType::ReconfigResp => 16, 150 | ParamType::AddOutStreamsReq => 17, 151 | ParamType::AddIncStreamsReq => 18, 152 | ParamType::Random => 32770, 153 | ParamType::ChunkList => 32771, 154 | ParamType::ReqHmacAlgo => 32772, 155 | ParamType::Padding => 32773, 156 | ParamType::SupportedExt => 32776, 157 | ParamType::ForwardTsnSupp => 49152, 158 | ParamType::AddIpAddr => 49153, 159 | ParamType::DelIpaddr => 49154, 160 | ParamType::ErrClauseInd => 49155, 161 | ParamType::SetPriAddr => 49156, 162 | ParamType::SuccessInd => 49157, 163 | ParamType::AdaptLayerInd => 49158, 164 | ParamType::Unknown { param_type, .. } => param_type, 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/stream/stream_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use std::sync::atomic::{AtomicU32, Ordering}; 3 | use std::sync::Arc; 4 | use tokio::io::AsyncReadExt; 5 | use tokio::io::AsyncWriteExt; 6 | 7 | #[test] 8 | fn test_stream_buffered_amount() -> Result<()> { 9 | let s = Stream::default(); 10 | 11 | assert_eq!(0, s.buffered_amount()); 12 | assert_eq!(0, s.buffered_amount_low_threshold()); 13 | 14 | s.buffered_amount.store(8192, Ordering::SeqCst); 15 | s.set_buffered_amount_low_threshold(2048); 16 | assert_eq!(8192, s.buffered_amount(), "unexpected bufferedAmount"); 17 | assert_eq!( 18 | 2048, 19 | s.buffered_amount_low_threshold(), 20 | "unexpected threshold" 21 | ); 22 | 23 | Ok(()) 24 | } 25 | 26 | #[tokio::test] 27 | async fn test_stream_amount_on_buffered_amount_low() -> Result<()> { 28 | let s = Stream::default(); 29 | 30 | s.buffered_amount.store(4096, Ordering::SeqCst); 31 | s.set_buffered_amount_low_threshold(2048); 32 | 33 | let n_cbs = Arc::new(AtomicU32::new(0)); 34 | let n_cbs2 = n_cbs.clone(); 35 | 36 | s.on_buffered_amount_low(Box::new(move || { 37 | n_cbs2.fetch_add(1, Ordering::SeqCst); 38 | Box::pin(async {}) 39 | })) 40 | .await; 41 | 42 | // Negative value should be ignored (by design) 43 | s.on_buffer_released(-32).await; // bufferedAmount = 3072 44 | assert_eq!(4096, s.buffered_amount(), "unexpected bufferedAmount"); 45 | assert_eq!(0, n_cbs.load(Ordering::SeqCst), "callback count mismatch"); 46 | 47 | // Above to above, no callback 48 | s.on_buffer_released(1024).await; // bufferedAmount = 3072 49 | assert_eq!(3072, s.buffered_amount(), "unexpected bufferedAmount"); 50 | assert_eq!(0, n_cbs.load(Ordering::SeqCst), "callback count mismatch"); 51 | 52 | // Above to equal, callback should be made 53 | s.on_buffer_released(1024).await; // bufferedAmount = 2048 54 | assert_eq!(2048, s.buffered_amount(), "unexpected bufferedAmount"); 55 | assert_eq!(1, n_cbs.load(Ordering::SeqCst), "callback count mismatch"); 56 | 57 | // Eaual to below, no callback 58 | s.on_buffer_released(1024).await; // bufferedAmount = 1024 59 | assert_eq!(1024, s.buffered_amount(), "unexpected bufferedAmount"); 60 | assert_eq!(1, n_cbs.load(Ordering::SeqCst), "callback count mismatch"); 61 | 62 | // Blow to below, no callback 63 | s.on_buffer_released(1024).await; // bufferedAmount = 0 64 | assert_eq!(0, s.buffered_amount(), "unexpected bufferedAmount"); 65 | assert_eq!(1, n_cbs.load(Ordering::SeqCst), "callback count mismatch"); 66 | 67 | // Capped at 0, no callback 68 | s.on_buffer_released(1024).await; // bufferedAmount = 0 69 | assert_eq!(0, s.buffered_amount(), "unexpected bufferedAmount"); 70 | assert_eq!(1, n_cbs.load(Ordering::SeqCst), "callback count mismatch"); 71 | 72 | Ok(()) 73 | } 74 | 75 | #[tokio::test] 76 | async fn test_stream() -> std::result::Result<(), io::Error> { 77 | let s = Stream::new( 78 | "test_poll_stream".to_owned(), 79 | 0, 80 | 4096, 81 | Arc::new(AtomicU32::new(4096)), 82 | Arc::new(AtomicU8::new(AssociationState::Established as u8)), 83 | None, 84 | Arc::new(PendingQueue::new()), 85 | ); 86 | 87 | // getters 88 | assert_eq!(0, s.stream_identifier()); 89 | assert_eq!(0, s.buffered_amount()); 90 | assert_eq!(0, s.buffered_amount_low_threshold()); 91 | assert_eq!(0, s.get_num_bytes_in_reassembly_queue().await); 92 | 93 | // setters 94 | s.set_default_payload_type(PayloadProtocolIdentifier::Binary); 95 | s.set_reliability_params(true, ReliabilityType::Reliable, 0); 96 | 97 | // write 98 | let n = s.write(&Bytes::from("Hello ")).await?; 99 | assert_eq!(6, n); 100 | assert_eq!(6, s.buffered_amount()); 101 | let n = s 102 | .write_sctp(&Bytes::from("world"), PayloadProtocolIdentifier::Binary) 103 | .await?; 104 | assert_eq!(5, n); 105 | assert_eq!(11, s.buffered_amount()); 106 | 107 | // async read 108 | // 1. pretend that we've received a chunk 109 | s.handle_data(ChunkPayloadData { 110 | unordered: true, 111 | beginning_fragment: true, 112 | ending_fragment: true, 113 | user_data: Bytes::from_static(&[0, 1, 2, 3, 4]), 114 | payload_type: PayloadProtocolIdentifier::Binary, 115 | ..Default::default() 116 | }) 117 | .await; 118 | // 2. read it 119 | let mut buf = [0; 5]; 120 | s.read(&mut buf).await?; 121 | assert_eq!(buf, [0, 1, 2, 3, 4]); 122 | 123 | // shutdown write 124 | s.shutdown(Shutdown::Write).await?; 125 | // write must fail 126 | assert!(s.write(&Bytes::from("error")).await.is_err()); 127 | // read should continue working 128 | s.handle_data(ChunkPayloadData { 129 | unordered: true, 130 | beginning_fragment: true, 131 | ending_fragment: true, 132 | user_data: Bytes::from_static(&[5, 6, 7, 8, 9]), 133 | payload_type: PayloadProtocolIdentifier::Binary, 134 | ..Default::default() 135 | }) 136 | .await; 137 | let mut buf = [0; 5]; 138 | s.read(&mut buf).await?; 139 | assert_eq!(buf, [5, 6, 7, 8, 9]); 140 | 141 | // shutdown read 142 | s.shutdown(Shutdown::Read).await?; 143 | // read must return 0 144 | assert_eq!(Ok(0), s.read(&mut buf).await); 145 | 146 | Ok(()) 147 | } 148 | 149 | #[tokio::test] 150 | async fn test_poll_stream() -> std::result::Result<(), io::Error> { 151 | let s = Arc::new(Stream::new( 152 | "test_poll_stream".to_owned(), 153 | 0, 154 | 4096, 155 | Arc::new(AtomicU32::new(4096)), 156 | Arc::new(AtomicU8::new(AssociationState::Established as u8)), 157 | None, 158 | Arc::new(PendingQueue::new()), 159 | )); 160 | let mut poll_stream = PollStream::new(s.clone()); 161 | 162 | // getters 163 | assert_eq!(0, poll_stream.stream_identifier()); 164 | assert_eq!(0, poll_stream.buffered_amount()); 165 | assert_eq!(0, poll_stream.buffered_amount_low_threshold()); 166 | assert_eq!(0, poll_stream.get_num_bytes_in_reassembly_queue().await); 167 | 168 | // async write 169 | let n = poll_stream.write(&[1, 2, 3]).await?; 170 | assert_eq!(3, n); 171 | poll_stream.flush().await?; 172 | assert_eq!(3, poll_stream.buffered_amount()); 173 | 174 | // async read 175 | // 1. pretend that we've received a chunk 176 | let sc = s.clone(); 177 | sc.handle_data(ChunkPayloadData { 178 | unordered: true, 179 | beginning_fragment: true, 180 | ending_fragment: true, 181 | user_data: Bytes::from_static(&[0, 1, 2, 3, 4]), 182 | payload_type: PayloadProtocolIdentifier::Binary, 183 | ..Default::default() 184 | }) 185 | .await; 186 | // 2. read it 187 | let mut buf = [0; 5]; 188 | poll_stream.read(&mut buf).await?; 189 | assert_eq!(buf, [0, 1, 2, 3, 4]); 190 | 191 | // shutdown write 192 | poll_stream.shutdown().await?; 193 | // write must fail 194 | assert!(poll_stream.write(&[1, 2, 3]).await.is_err()); 195 | // read should continue working 196 | sc.handle_data(ChunkPayloadData { 197 | unordered: true, 198 | beginning_fragment: true, 199 | ending_fragment: true, 200 | user_data: Bytes::from_static(&[5, 6, 7, 8, 9]), 201 | payload_type: PayloadProtocolIdentifier::Binary, 202 | ..Default::default() 203 | }) 204 | .await; 205 | let mut buf = [0; 5]; 206 | poll_stream.read(&mut buf).await?; 207 | assert_eq!(buf, [5, 6, 7, 8, 9]); 208 | 209 | // misc. 210 | let clone = poll_stream.clone(); 211 | assert_eq!(clone.stream_identifier(), poll_stream.stream_identifier()); 212 | 213 | Ok(()) 214 | } 215 | -------------------------------------------------------------------------------- /src/timer/rtx_timer.rs: -------------------------------------------------------------------------------- 1 | use crate::association::RtxTimerId; 2 | use async_trait::async_trait; 3 | use std::sync::{Arc, Weak}; 4 | use tokio::sync::{mpsc, Mutex}; 5 | use tokio::time::Duration; 6 | 7 | pub(crate) const RTO_INITIAL: u64 = 3000; // msec 8 | pub(crate) const RTO_MIN: u64 = 1000; // msec 9 | pub(crate) const RTO_MAX: u64 = 60000; // msec 10 | pub(crate) const RTO_ALPHA: u64 = 1; 11 | pub(crate) const RTO_BETA: u64 = 2; 12 | pub(crate) const RTO_BASE: u64 = 8; 13 | pub(crate) const MAX_INIT_RETRANS: usize = 8; 14 | pub(crate) const PATH_MAX_RETRANS: usize = 5; 15 | pub(crate) const NO_MAX_RETRANS: usize = 0; 16 | 17 | /// rtoManager manages Rtx timeout values. 18 | /// This is an implementation of RFC 4960 sec 6.3.1. 19 | #[derive(Default, Debug)] 20 | pub(crate) struct RtoManager { 21 | pub(crate) srtt: u64, 22 | pub(crate) rttvar: f64, 23 | pub(crate) rto: u64, 24 | pub(crate) no_update: bool, 25 | } 26 | 27 | impl RtoManager { 28 | /// newRTOManager creates a new rtoManager. 29 | pub(crate) fn new() -> Self { 30 | RtoManager { 31 | rto: RTO_INITIAL, 32 | ..Default::default() 33 | } 34 | } 35 | 36 | /// set_new_rtt takes a newly measured RTT then adjust the RTO in msec. 37 | pub(crate) fn set_new_rtt(&mut self, rtt: u64) -> u64 { 38 | if self.no_update { 39 | return self.srtt; 40 | } 41 | 42 | if self.srtt == 0 { 43 | // First measurement 44 | self.srtt = rtt; 45 | self.rttvar = rtt as f64 / 2.0; 46 | } else { 47 | // Subsequent rtt measurement 48 | self.rttvar = ((RTO_BASE - RTO_BETA) as f64 * self.rttvar 49 | + RTO_BETA as f64 * (self.srtt as i64 - rtt as i64).abs() as f64) 50 | / RTO_BASE as f64; 51 | self.srtt = ((RTO_BASE - RTO_ALPHA) * self.srtt + RTO_ALPHA * rtt) / RTO_BASE; 52 | } 53 | 54 | self.rto = std::cmp::min( 55 | std::cmp::max(self.srtt + (4.0 * self.rttvar) as u64, RTO_MIN), 56 | RTO_MAX, 57 | ); 58 | 59 | self.srtt 60 | } 61 | 62 | /// get_rto simply returns the current RTO in msec. 63 | pub(crate) fn get_rto(&self) -> u64 { 64 | self.rto 65 | } 66 | 67 | /// reset resets the RTO variables to the initial values. 68 | pub(crate) fn reset(&mut self) { 69 | if self.no_update { 70 | return; 71 | } 72 | 73 | self.srtt = 0; 74 | self.rttvar = 0.0; 75 | self.rto = RTO_INITIAL; 76 | } 77 | 78 | /// set RTO value for testing 79 | pub(crate) fn set_rto(&mut self, rto: u64, no_update: bool) { 80 | self.rto = rto; 81 | self.no_update = no_update; 82 | } 83 | } 84 | 85 | pub(crate) fn calculate_next_timeout(rto: u64, n_rtos: usize) -> u64 { 86 | // RFC 4096 sec 6.3.3. Handle T3-rtx Expiration 87 | // E2) For the destination address for which the timer expires, set RTO 88 | // <- RTO * 2 ("back off the timer"). The maximum value discussed 89 | // in rule C7 above (RTO.max) may be used to provide an upper bound 90 | // to this doubling operation. 91 | if n_rtos < 31 { 92 | std::cmp::min(rto << n_rtos, RTO_MAX) 93 | } else { 94 | RTO_MAX 95 | } 96 | } 97 | 98 | /// rtxTimerObserver is the inteface to a timer observer. 99 | /// NOTE: Observers MUST NOT call start() or stop() method on rtxTimer 100 | /// from within these callbacks. 101 | #[async_trait] 102 | pub(crate) trait RtxTimerObserver { 103 | async fn on_retransmission_timeout(&mut self, timer_id: RtxTimerId, n: usize); 104 | async fn on_retransmission_failure(&mut self, timer_id: RtxTimerId); 105 | } 106 | 107 | /// rtxTimer provides the retnransmission timer conforms with RFC 4960 Sec 6.3.1 108 | #[derive(Default, Debug)] 109 | pub(crate) struct RtxTimer { 110 | pub(crate) timeout_observer: Weak>, 111 | pub(crate) id: RtxTimerId, 112 | pub(crate) max_retrans: usize, 113 | pub(crate) close_tx: Arc>>>, 114 | } 115 | 116 | impl RtxTimer { 117 | /// newRTXTimer creates a new retransmission timer. 118 | /// if max_retrans is set to 0, it will keep retransmitting until stop() is called. 119 | /// (it will never make on_retransmission_failure() callback. 120 | pub(crate) fn new( 121 | timeout_observer: Weak>, 122 | id: RtxTimerId, 123 | max_retrans: usize, 124 | ) -> Self { 125 | RtxTimer { 126 | timeout_observer, 127 | id, 128 | max_retrans, 129 | close_tx: Arc::new(Mutex::new(None)), 130 | } 131 | } 132 | 133 | /// start starts the timer. 134 | pub(crate) async fn start(&self, rto: u64) -> bool { 135 | // Note: rto value is intentionally not capped by RTO.Min to allow 136 | // fast timeout for the tests. Non-test code should pass in the 137 | // rto generated by rtoManager get_rto() method which caps the 138 | // value at RTO.Min or at RTO.Max. 139 | 140 | // this timer is already closed 141 | let mut close_rx = { 142 | let mut close = self.close_tx.lock().await; 143 | if close.is_some() { 144 | return false; 145 | } 146 | 147 | let (close_tx, close_rx) = mpsc::channel(1); 148 | *close = Some(close_tx); 149 | close_rx 150 | }; 151 | 152 | let id = self.id; 153 | let max_retrans = self.max_retrans; 154 | let close_tx = Arc::clone(&self.close_tx); 155 | let timeout_observer = self.timeout_observer.clone(); 156 | 157 | tokio::spawn(async move { 158 | let mut n_rtos = 0; 159 | 160 | loop { 161 | let interval = calculate_next_timeout(rto, n_rtos); 162 | let timer = tokio::time::sleep(Duration::from_millis(interval)); 163 | tokio::pin!(timer); 164 | 165 | tokio::select! { 166 | _ = timer.as_mut() => { 167 | n_rtos+=1; 168 | 169 | let failure = { 170 | if let Some(observer) = timeout_observer.upgrade(){ 171 | let mut observer = observer.lock().await; 172 | if max_retrans == 0 || n_rtos <= max_retrans { 173 | observer.on_retransmission_timeout(id, n_rtos).await; 174 | false 175 | } else { 176 | observer.on_retransmission_failure(id).await; 177 | true 178 | } 179 | }else{ 180 | true 181 | } 182 | }; 183 | if failure { 184 | let mut close = close_tx.lock().await; 185 | *close = None; 186 | break; 187 | } 188 | } 189 | _ = close_rx.recv() => break, 190 | } 191 | } 192 | }); 193 | 194 | true 195 | } 196 | 197 | /// stop stops the timer. 198 | pub(crate) async fn stop(&self) { 199 | let mut close_tx = self.close_tx.lock().await; 200 | close_tx.take(); 201 | } 202 | 203 | /// isRunning tests if the timer is running. 204 | /// Debug purpose only 205 | pub(crate) async fn is_running(&self) -> bool { 206 | let close_tx = self.close_tx.lock().await; 207 | close_tx.is_some() 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/param/param_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /////////////////////////////////////////////////////////////////// 4 | //param_type_test 5 | /////////////////////////////////////////////////////////////////// 6 | use super::param_type::*; 7 | 8 | #[test] 9 | fn test_parse_param_type_success() -> Result<()> { 10 | let tests = vec![ 11 | (Bytes::from_static(&[0x0, 0x1]), ParamType::HeartbeatInfo), 12 | (Bytes::from_static(&[0x0, 0xd]), ParamType::OutSsnResetReq), 13 | ]; 14 | 15 | for (mut binary, expected) in tests { 16 | let pt: ParamType = binary.get_u16().into(); 17 | assert_eq!(expected, pt); 18 | } 19 | 20 | Ok(()) 21 | } 22 | 23 | /////////////////////////////////////////////////////////////////// 24 | //param_header_test 25 | /////////////////////////////////////////////////////////////////// 26 | use super::param_header::*; 27 | 28 | static PARAM_HEADER_BYTES: Bytes = Bytes::from_static(&[0x0, 0x1, 0x0, 0x4]); 29 | 30 | #[test] 31 | fn test_param_header_success() -> Result<()> { 32 | let tests = vec![( 33 | PARAM_HEADER_BYTES.clone(), 34 | ParamHeader { 35 | typ: ParamType::HeartbeatInfo, 36 | value_length: 0, 37 | }, 38 | )]; 39 | 40 | for (binary, parsed) in tests { 41 | let actual = ParamHeader::unmarshal(&binary)?; 42 | assert_eq!(parsed, actual); 43 | let b = actual.marshal()?; 44 | assert_eq!(binary, b); 45 | } 46 | 47 | Ok(()) 48 | } 49 | 50 | #[test] 51 | fn test_param_header_unmarshal_failure() -> Result<()> { 52 | let tests = vec![ 53 | ("header too short", PARAM_HEADER_BYTES.slice(..2)), 54 | // {"wrong param type", []byte{0x0, 0x0, 0x0, 0x4}}, // Not possible to fail parseParamType atm. 55 | ( 56 | "reported length below header length", 57 | Bytes::from_static(&[0x0, 0xd, 0x0, 0x3]), 58 | ), 59 | ("wrong reported length", CHUNK_RECONFIG_PARAM_A.slice(0..4)), 60 | ]; 61 | 62 | for (name, binary) in tests { 63 | let result = ParamHeader::unmarshal(&binary); 64 | assert!(result.is_err(), "expected unmarshal: {} to fail.", name); 65 | } 66 | 67 | Ok(()) 68 | } 69 | 70 | /////////////////////////////////////////////////////////////////// 71 | //param_forward_tsn_supported_test 72 | /////////////////////////////////////////////////////////////////// 73 | use super::param_forward_tsn_supported::*; 74 | 75 | static PARAM_FORWARD_TSN_SUPPORTED_BYTES: Bytes = Bytes::from_static(&[0xc0, 0x0, 0x0, 0x4]); 76 | 77 | #[test] 78 | fn test_param_forward_tsn_supported_success() -> Result<()> { 79 | let tests = vec![( 80 | PARAM_FORWARD_TSN_SUPPORTED_BYTES.clone(), 81 | ParamForwardTsnSupported {}, 82 | )]; 83 | 84 | for (binary, parsed) in tests { 85 | let actual = ParamForwardTsnSupported::unmarshal(&binary)?; 86 | assert_eq!(parsed, actual); 87 | let b = actual.marshal()?; 88 | assert_eq!(binary, b); 89 | } 90 | 91 | Ok(()) 92 | } 93 | 94 | #[test] 95 | fn test_param_forward_tsn_supported_failure() -> Result<()> { 96 | let tests = vec![("param too short", Bytes::from_static(&[0x0, 0xd, 0x0]))]; 97 | 98 | for (name, binary) in tests { 99 | let result = ParamForwardTsnSupported::unmarshal(&binary); 100 | assert!(result.is_err(), "expected unmarshal: {} to fail.", name); 101 | } 102 | 103 | Ok(()) 104 | } 105 | 106 | /////////////////////////////////////////////////////////////////// 107 | //param_outgoing_reset_request_test 108 | /////////////////////////////////////////////////////////////////// 109 | use super::param_outgoing_reset_request::*; 110 | 111 | static CHUNK_RECONFIG_PARAM_A: Bytes = Bytes::from_static(&[ 112 | 0x0, 0xd, 0x0, 0x16, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x3, 0x0, 0x4, 0x0, 113 | 0x5, 0x0, 0x6, 114 | ]); 115 | static CHUNK_RECONFIG_PARAM_B: Bytes = Bytes::from_static(&[ 116 | 0x0, 0xd, 0x0, 0x10, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x3, 117 | ]); 118 | 119 | #[test] 120 | fn test_param_outgoing_reset_request_success() -> Result<()> { 121 | let tests = vec![ 122 | ( 123 | CHUNK_RECONFIG_PARAM_A.clone(), 124 | ParamOutgoingResetRequest { 125 | reconfig_request_sequence_number: 1, 126 | reconfig_response_sequence_number: 2, 127 | sender_last_tsn: 3, 128 | stream_identifiers: vec![4, 5, 6], 129 | }, 130 | ), 131 | ( 132 | CHUNK_RECONFIG_PARAM_B.clone(), 133 | ParamOutgoingResetRequest { 134 | reconfig_request_sequence_number: 1, 135 | reconfig_response_sequence_number: 2, 136 | sender_last_tsn: 3, 137 | stream_identifiers: vec![], 138 | }, 139 | ), 140 | ]; 141 | 142 | for (binary, parsed) in tests { 143 | let actual = ParamOutgoingResetRequest::unmarshal(&binary)?; 144 | assert_eq!(parsed, actual); 145 | let b = actual.marshal()?; 146 | assert_eq!(binary, b); 147 | } 148 | 149 | Ok(()) 150 | } 151 | 152 | #[test] 153 | fn test_param_outgoing_reset_request_failure() -> Result<()> { 154 | let tests = vec![ 155 | ("packet too short", CHUNK_RECONFIG_PARAM_A.slice(..8)), 156 | ("param too short", Bytes::from_static(&[0x0, 0xd, 0x0, 0x4])), 157 | ]; 158 | 159 | for (name, binary) in tests { 160 | let result = ParamOutgoingResetRequest::unmarshal(&binary); 161 | assert!(result.is_err(), "expected unmarshal: {} to fail.", name); 162 | } 163 | 164 | Ok(()) 165 | } 166 | 167 | /////////////////////////////////////////////////////////////////// 168 | //param_reconfig_response_test 169 | /////////////////////////////////////////////////////////////////// 170 | use super::param_reconfig_response::*; 171 | use bytes::Buf; 172 | 173 | static CHUNK_RECONFIG_RESPONCE: Bytes = 174 | Bytes::from_static(&[0x0, 0x10, 0x0, 0xc, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1]); 175 | 176 | #[test] 177 | fn test_param_reconfig_response_success() -> Result<()> { 178 | let tests = vec![( 179 | CHUNK_RECONFIG_RESPONCE.clone(), 180 | ParamReconfigResponse { 181 | reconfig_response_sequence_number: 1, 182 | result: ReconfigResult::SuccessPerformed, 183 | }, 184 | )]; 185 | 186 | for (binary, parsed) in tests { 187 | let actual = ParamReconfigResponse::unmarshal(&binary)?; 188 | assert_eq!(parsed, actual); 189 | let b = actual.marshal()?; 190 | assert_eq!(binary, b); 191 | } 192 | 193 | Ok(()) 194 | } 195 | 196 | #[test] 197 | fn test_param_reconfig_response_failure() -> Result<()> { 198 | let tests = vec![ 199 | ("packet too short", CHUNK_RECONFIG_RESPONCE.slice(..8)), 200 | ( 201 | "param too short", 202 | Bytes::from_static(&[0x0, 0x10, 0x0, 0x4]), 203 | ), 204 | ]; 205 | 206 | for (name, binary) in tests { 207 | let result = ParamReconfigResponse::unmarshal(&binary); 208 | assert!(result.is_err(), "expected unmarshal: {} to fail.", name); 209 | } 210 | 211 | Ok(()) 212 | } 213 | 214 | #[test] 215 | fn test_reconfig_result_stringer() -> Result<()> { 216 | let tests = vec![ 217 | (ReconfigResult::SuccessNop, "0: Success - Nothing to do"), 218 | (ReconfigResult::SuccessPerformed, "1: Success - Performed"), 219 | (ReconfigResult::Denied, "2: Denied"), 220 | (ReconfigResult::ErrorWrongSsn, "3: Error - Wrong SSN"), 221 | ( 222 | ReconfigResult::ErrorRequestAlreadyInProgress, 223 | "4: Error - Request already in progress", 224 | ), 225 | ( 226 | ReconfigResult::ErrorBadSequenceNumber, 227 | "5: Error - Bad Sequence Number", 228 | ), 229 | (ReconfigResult::InProgress, "6: In progress"), 230 | ]; 231 | 232 | for (result, expected) in tests { 233 | let actual = result.to_string(); 234 | assert_eq!(expected, actual, "Test case {}", expected); 235 | } 236 | 237 | Ok(()) 238 | } 239 | 240 | /////////////////////////////////////////////////////////////////// 241 | //param_test 242 | /////////////////////////////////////////////////////////////////// 243 | 244 | #[test] 245 | fn test_build_param_success() -> Result<()> { 246 | let tests = vec![CHUNK_RECONFIG_PARAM_A.clone()]; 247 | 248 | for binary in tests { 249 | let p = build_param(&binary)?; 250 | let b = p.marshal()?; 251 | assert_eq!(binary, b); 252 | } 253 | 254 | Ok(()) 255 | } 256 | 257 | #[test] 258 | fn test_build_param_failure() -> Result<()> { 259 | let tests = vec![ 260 | ("invalid ParamType", Bytes::from_static(&[0x0, 0x0])), 261 | ("build failure", CHUNK_RECONFIG_PARAM_A.slice(..8)), 262 | ]; 263 | 264 | for (name, binary) in tests { 265 | let result = build_param(&binary); 266 | assert!(result.is_err(), "expected unmarshal: {} to fail.", name); 267 | } 268 | 269 | Ok(()) 270 | } 271 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use thiserror::Error; 3 | 4 | pub type Result = std::result::Result; 5 | 6 | #[derive(Debug, Error, PartialEq, Eq)] 7 | #[non_exhaustive] 8 | pub enum Error { 9 | #[error("raw is too small for a SCTP chunk")] 10 | ErrChunkHeaderTooSmall, 11 | #[error("not enough data left in SCTP packet to satisfy requested length")] 12 | ErrChunkHeaderNotEnoughSpace, 13 | #[error("chunk PADDING is non-zero at offset")] 14 | ErrChunkHeaderPaddingNonZero, 15 | #[error("chunk has invalid length")] 16 | ErrChunkHeaderInvalidLength, 17 | 18 | #[error("ChunkType is not of type ABORT")] 19 | ErrChunkTypeNotAbort, 20 | #[error("failed build Abort Chunk")] 21 | ErrBuildAbortChunkFailed, 22 | #[error("ChunkType is not of type COOKIEACK")] 23 | ErrChunkTypeNotCookieAck, 24 | #[error("ChunkType is not of type COOKIEECHO")] 25 | ErrChunkTypeNotCookieEcho, 26 | #[error("ChunkType is not of type ctError")] 27 | ErrChunkTypeNotCtError, 28 | #[error("failed build Error Chunk")] 29 | ErrBuildErrorChunkFailed, 30 | #[error("failed to marshal stream")] 31 | ErrMarshalStreamFailed, 32 | #[error("chunk too short")] 33 | ErrChunkTooShort, 34 | #[error("ChunkType is not of type ForwardTsn")] 35 | ErrChunkTypeNotForwardTsn, 36 | #[error("ChunkType is not of type HEARTBEAT")] 37 | ErrChunkTypeNotHeartbeat, 38 | #[error("ChunkType is not of type HEARTBEATACK")] 39 | ErrChunkTypeNotHeartbeatAck, 40 | #[error("heartbeat is not long enough to contain Heartbeat Info")] 41 | ErrHeartbeatNotLongEnoughInfo, 42 | #[error("failed to parse param type")] 43 | ErrParseParamTypeFailed, 44 | #[error("heartbeat should only have HEARTBEAT param")] 45 | ErrHeartbeatParam, 46 | #[error("failed unmarshalling param in Heartbeat Chunk")] 47 | ErrHeartbeatChunkUnmarshal, 48 | #[error("unimplemented")] 49 | ErrUnimplemented, 50 | #[error("heartbeat Ack must have one param")] 51 | ErrHeartbeatAckParams, 52 | #[error("heartbeat Ack must have one param, and it should be a HeartbeatInfo")] 53 | ErrHeartbeatAckNotHeartbeatInfo, 54 | #[error("unable to marshal parameter for Heartbeat Ack")] 55 | ErrHeartbeatAckMarshalParam, 56 | 57 | #[error("raw is too small for error cause")] 58 | ErrErrorCauseTooSmall, 59 | 60 | #[error("unhandled ParamType `{typ}`")] 61 | ErrParamTypeUnhandled { typ: u16 }, 62 | 63 | #[error("unexpected ParamType")] 64 | ErrParamTypeUnexpected, 65 | 66 | #[error("param header too short")] 67 | ErrParamHeaderTooShort, 68 | #[error("param self reported length is shorter than header length")] 69 | ErrParamHeaderSelfReportedLengthShorter, 70 | #[error("param self reported length is longer than header length")] 71 | ErrParamHeaderSelfReportedLengthLonger, 72 | #[error("failed to parse param type")] 73 | ErrParamHeaderParseFailed, 74 | 75 | #[error("packet to short")] 76 | ErrParamPacketTooShort, 77 | #[error("outgoing SSN reset request parameter too short")] 78 | ErrSsnResetRequestParamTooShort, 79 | #[error("reconfig response parameter too short")] 80 | ErrReconfigRespParamTooShort, 81 | #[error("invalid algorithm type")] 82 | ErrInvalidAlgorithmType, 83 | 84 | #[error("failed to parse param type")] 85 | ErrInitChunkParseParamTypeFailed, 86 | #[error("failed unmarshalling param in Init Chunk")] 87 | ErrInitChunkUnmarshalParam, 88 | #[error("unable to marshal parameter for INIT/INITACK")] 89 | ErrInitAckMarshalParam, 90 | 91 | #[error("ChunkType is not of type INIT")] 92 | ErrChunkTypeNotTypeInit, 93 | #[error("chunk Value isn't long enough for mandatory parameters exp")] 94 | ErrChunkValueNotLongEnough, 95 | #[error("ChunkType of type INIT flags must be all 0")] 96 | ErrChunkTypeInitFlagZero, 97 | #[error("failed to unmarshal INIT body")] 98 | ErrChunkTypeInitUnmarshalFailed, 99 | #[error("failed marshaling INIT common data")] 100 | ErrChunkTypeInitMarshalFailed, 101 | #[error("ChunkType of type INIT ACK InitiateTag must not be 0")] 102 | ErrChunkTypeInitInitateTagZero, 103 | #[error("INIT ACK inbound stream request must be > 0")] 104 | ErrInitInboundStreamRequestZero, 105 | #[error("INIT ACK outbound stream request must be > 0")] 106 | ErrInitOutboundStreamRequestZero, 107 | #[error("INIT ACK Advertised Receiver Window Credit (a_rwnd) must be >= 1500")] 108 | ErrInitAdvertisedReceiver1500, 109 | 110 | #[error("packet is smaller than the header size")] 111 | ErrChunkPayloadSmall, 112 | #[error("ChunkType is not of type PayloadData")] 113 | ErrChunkTypeNotPayloadData, 114 | #[error("ChunkType is not of type Reconfig")] 115 | ErrChunkTypeNotReconfig, 116 | #[error("ChunkReconfig has invalid ParamA")] 117 | ErrChunkReconfigInvalidParamA, 118 | 119 | #[error("failed to parse param type")] 120 | ErrChunkParseParamTypeFailed, 121 | #[error("unable to marshal parameter A for reconfig")] 122 | ErrChunkMarshalParamAReconfigFailed, 123 | #[error("unable to marshal parameter B for reconfig")] 124 | ErrChunkMarshalParamBReconfigFailed, 125 | 126 | #[error("ChunkType is not of type SACK")] 127 | ErrChunkTypeNotSack, 128 | #[error("SACK Chunk size is not large enough to contain header")] 129 | ErrSackSizeNotLargeEnoughInfo, 130 | 131 | #[error("invalid chunk size")] 132 | ErrInvalidChunkSize, 133 | #[error("ChunkType is not of type SHUTDOWN")] 134 | ErrChunkTypeNotShutdown, 135 | 136 | #[error("ChunkType is not of type SHUTDOWN-ACK")] 137 | ErrChunkTypeNotShutdownAck, 138 | #[error("ChunkType is not of type SHUTDOWN-COMPLETE")] 139 | ErrChunkTypeNotShutdownComplete, 140 | 141 | #[error("raw is smaller than the minimum length for a SCTP packet")] 142 | ErrPacketRawTooSmall, 143 | #[error("unable to parse SCTP chunk, not enough data for complete header")] 144 | ErrParseSctpChunkNotEnoughData, 145 | #[error("failed to unmarshal, contains unknown chunk type")] 146 | ErrUnmarshalUnknownChunkType, 147 | #[error("checksum mismatch theirs")] 148 | ErrChecksumMismatch, 149 | 150 | #[error("unexpected chunk popped (unordered)")] 151 | ErrUnexpectedChuckPoppedUnordered, 152 | #[error("unexpected chunk popped (ordered)")] 153 | ErrUnexpectedChuckPoppedOrdered, 154 | #[error("unexpected q state (should've been selected)")] 155 | ErrUnexpectedQState, 156 | #[error("try again")] 157 | ErrTryAgain, 158 | 159 | #[error("abort chunk, with following errors")] 160 | ErrChunk, 161 | #[error("shutdown called in non-Established state")] 162 | ErrShutdownNonEstablished, 163 | #[error("association closed before connecting")] 164 | ErrAssociationClosedBeforeConn, 165 | #[error("association init failed")] 166 | ErrAssociationInitFailed, 167 | #[error("association handshake closed")] 168 | ErrAssociationHandshakeClosed, 169 | #[error("silently discard")] 170 | ErrSilentlyDiscard, 171 | #[error("the init not stored to send")] 172 | ErrInitNotStoredToSend, 173 | #[error("cookieEcho not stored to send")] 174 | ErrCookieEchoNotStoredToSend, 175 | #[error("sctp packet must not have a source port of 0")] 176 | ErrSctpPacketSourcePortZero, 177 | #[error("sctp packet must not have a destination port of 0")] 178 | ErrSctpPacketDestinationPortZero, 179 | #[error("init chunk must not be bundled with any other chunk")] 180 | ErrInitChunkBundled, 181 | #[error("init chunk expects a verification tag of 0 on the packet when out-of-the-blue")] 182 | ErrInitChunkVerifyTagNotZero, 183 | #[error("todo: handle Init when in state")] 184 | ErrHandleInitState, 185 | #[error("no cookie in InitAck")] 186 | ErrInitAckNoCookie, 187 | #[error("there already exists a stream with identifier")] 188 | ErrStreamAlreadyExist, 189 | #[error("Failed to create a stream with identifier")] 190 | ErrStreamCreateFailed, 191 | #[error("unable to be popped from inflight queue TSN")] 192 | ErrInflightQueueTsnPop, 193 | #[error("requested non-existent TSN")] 194 | ErrTsnRequestNotExist, 195 | #[error("sending reset packet in non-Established state")] 196 | ErrResetPacketInStateNotExist, 197 | #[error("unexpected parameter type")] 198 | ErrParamterType, 199 | #[error("sending payload data in non-Established state")] 200 | ErrPayloadDataStateNotExist, 201 | #[error("unhandled chunk type")] 202 | ErrChunkTypeUnhandled, 203 | #[error("handshake failed (INIT ACK)")] 204 | ErrHandshakeInitAck, 205 | #[error("handshake failed (COOKIE ECHO)")] 206 | ErrHandshakeCookieEcho, 207 | 208 | #[error("outbound packet larger than maximum message size")] 209 | ErrOutboundPacketTooLarge, 210 | #[error("Stream closed")] 211 | ErrStreamClosed, 212 | #[error("Short buffer to be filled")] 213 | ErrShortBuffer, 214 | #[error("Io EOF")] 215 | ErrEof, 216 | #[error("Invalid SystemTime")] 217 | ErrInvalidSystemTime, 218 | #[error("Net Conn read error")] 219 | ErrNetConnReadError, 220 | #[error("Max Data Channel ID")] 221 | ErrMaxDataChannelID, 222 | 223 | #[error("{0}")] 224 | Other(String), 225 | } 226 | 227 | impl From for io::Error { 228 | fn from(error: Error) -> Self { 229 | match error { 230 | e @ Error::ErrEof => io::Error::new(io::ErrorKind::UnexpectedEof, e.to_string()), 231 | e @ Error::ErrStreamClosed => { 232 | io::Error::new(io::ErrorKind::ConnectionAborted, e.to_string()) 233 | } 234 | e => io::Error::new(io::ErrorKind::Other, e.to_string()), 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/chunk/chunk_payload_data.rs: -------------------------------------------------------------------------------- 1 | use super::{chunk_header::*, chunk_type::*, *}; 2 | 3 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 4 | use std::fmt; 5 | use std::sync::atomic::{AtomicBool, Ordering}; 6 | use std::sync::Arc; 7 | use std::time::SystemTime; 8 | 9 | pub(crate) const PAYLOAD_DATA_ENDING_FRAGMENT_BITMASK: u8 = 1; 10 | pub(crate) const PAYLOAD_DATA_BEGINING_FRAGMENT_BITMASK: u8 = 2; 11 | pub(crate) const PAYLOAD_DATA_UNORDERED_BITMASK: u8 = 4; 12 | pub(crate) const PAYLOAD_DATA_IMMEDIATE_SACK: u8 = 8; 13 | pub(crate) const PAYLOAD_DATA_HEADER_SIZE: usize = 12; 14 | 15 | /// PayloadProtocolIdentifier is an enum for DataChannel payload types 16 | /// PayloadProtocolIdentifier enums 17 | /// https://www.iana.org/assignments/sctp-parameters/sctp-parameters.xhtml#sctp-parameters-25 18 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 19 | #[repr(C)] 20 | pub enum PayloadProtocolIdentifier { 21 | Dcep = 50, 22 | String = 51, 23 | Binary = 53, 24 | StringEmpty = 56, 25 | BinaryEmpty = 57, 26 | Unknown, 27 | } 28 | 29 | impl Default for PayloadProtocolIdentifier { 30 | fn default() -> Self { 31 | PayloadProtocolIdentifier::Unknown 32 | } 33 | } 34 | 35 | impl fmt::Display for PayloadProtocolIdentifier { 36 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 37 | let s = match *self { 38 | PayloadProtocolIdentifier::Dcep => "WebRTC DCEP", 39 | PayloadProtocolIdentifier::String => "WebRTC String", 40 | PayloadProtocolIdentifier::Binary => "WebRTC Binary", 41 | PayloadProtocolIdentifier::StringEmpty => "WebRTC String (Empty)", 42 | PayloadProtocolIdentifier::BinaryEmpty => "WebRTC Binary (Empty)", 43 | _ => "Unknown Payload Protocol Identifier", 44 | }; 45 | write!(f, "{}", s) 46 | } 47 | } 48 | 49 | impl From for PayloadProtocolIdentifier { 50 | fn from(v: u32) -> PayloadProtocolIdentifier { 51 | match v { 52 | 50 => PayloadProtocolIdentifier::Dcep, 53 | 51 => PayloadProtocolIdentifier::String, 54 | 53 => PayloadProtocolIdentifier::Binary, 55 | 56 => PayloadProtocolIdentifier::StringEmpty, 56 | 57 => PayloadProtocolIdentifier::BinaryEmpty, 57 | _ => PayloadProtocolIdentifier::Unknown, 58 | } 59 | } 60 | } 61 | 62 | ///chunkPayloadData represents an SCTP Chunk of type DATA 63 | /// 64 | /// 0 1 2 3 65 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 66 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 67 | ///| Type = 0 | Reserved|U|B|E| Length | 68 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 69 | ///| TSN | 70 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 71 | ///| Stream Identifier S | Stream Sequence Number n | 72 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 73 | ///| Payload Protocol Identifier | 74 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 75 | ///| | 76 | ///| User Data (seq n of Stream S) | 77 | ///| | 78 | ///+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 79 | /// 80 | /// 81 | ///An unfragmented user message shall have both the B and E bits set to 82 | ///'1'. Setting both B and E bits to '0' indicates a middle fragment of 83 | ///a multi-fragment user message, as summarized in the following table: 84 | /// B E Description 85 | ///============================================================ 86 | ///| 1 0 | First piece of a fragmented user message | 87 | ///+----------------------------------------------------------+ 88 | ///| 0 0 | Middle piece of a fragmented user message | 89 | ///+----------------------------------------------------------+ 90 | ///| 0 1 | Last piece of a fragmented user message | 91 | ///+----------------------------------------------------------+ 92 | ///| 1 1 | Unfragmented message | 93 | ///============================================================ 94 | ///| Table 1: Fragment Description Flags | 95 | ///============================================================ 96 | #[derive(Debug, Clone)] 97 | pub struct ChunkPayloadData { 98 | pub(crate) unordered: bool, 99 | pub(crate) beginning_fragment: bool, 100 | pub(crate) ending_fragment: bool, 101 | pub(crate) immediate_sack: bool, 102 | 103 | pub(crate) tsn: u32, 104 | pub(crate) stream_identifier: u16, 105 | pub(crate) stream_sequence_number: u16, 106 | pub(crate) payload_type: PayloadProtocolIdentifier, 107 | pub(crate) user_data: Bytes, 108 | 109 | /// Whether this data chunk was acknowledged (received by peer) 110 | pub(crate) acked: bool, 111 | pub(crate) miss_indicator: u32, 112 | 113 | /// Partial-reliability parameters used only by sender 114 | pub(crate) since: SystemTime, 115 | /// number of transmission made for this chunk 116 | pub(crate) nsent: u32, 117 | 118 | /// valid only with the first fragment 119 | pub(crate) abandoned: Arc, 120 | /// valid only with the first fragment 121 | pub(crate) all_inflight: Arc, 122 | 123 | /// Retransmission flag set when T1-RTX timeout occurred and this 124 | /// chunk is still in the inflight queue 125 | pub(crate) retransmit: bool, 126 | } 127 | 128 | impl Default for ChunkPayloadData { 129 | fn default() -> Self { 130 | ChunkPayloadData { 131 | unordered: false, 132 | beginning_fragment: false, 133 | ending_fragment: false, 134 | immediate_sack: false, 135 | tsn: 0, 136 | stream_identifier: 0, 137 | stream_sequence_number: 0, 138 | payload_type: PayloadProtocolIdentifier::default(), 139 | user_data: Bytes::new(), 140 | acked: false, 141 | miss_indicator: 0, 142 | since: SystemTime::now(), 143 | nsent: 0, 144 | abandoned: Arc::new(AtomicBool::new(false)), 145 | all_inflight: Arc::new(AtomicBool::new(false)), 146 | retransmit: false, 147 | } 148 | } 149 | } 150 | 151 | /// makes chunkPayloadData printable 152 | impl fmt::Display for ChunkPayloadData { 153 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 154 | write!(f, "{}\n{}", self.header(), self.tsn) 155 | } 156 | } 157 | 158 | impl Chunk for ChunkPayloadData { 159 | fn header(&self) -> ChunkHeader { 160 | let mut flags: u8 = 0; 161 | if self.ending_fragment { 162 | flags = 1; 163 | } 164 | if self.beginning_fragment { 165 | flags |= 1 << 1; 166 | } 167 | if self.unordered { 168 | flags |= 1 << 2; 169 | } 170 | if self.immediate_sack { 171 | flags |= 1 << 3; 172 | } 173 | 174 | ChunkHeader { 175 | typ: CT_PAYLOAD_DATA, 176 | flags, 177 | value_length: self.value_length() as u16, 178 | } 179 | } 180 | 181 | fn unmarshal(raw: &Bytes) -> Result { 182 | let header = ChunkHeader::unmarshal(raw)?; 183 | 184 | if header.typ != CT_PAYLOAD_DATA { 185 | return Err(Error::ErrChunkTypeNotPayloadData); 186 | } 187 | 188 | let immediate_sack = (header.flags & PAYLOAD_DATA_IMMEDIATE_SACK) != 0; 189 | let unordered = (header.flags & PAYLOAD_DATA_UNORDERED_BITMASK) != 0; 190 | let beginning_fragment = (header.flags & PAYLOAD_DATA_BEGINING_FRAGMENT_BITMASK) != 0; 191 | let ending_fragment = (header.flags & PAYLOAD_DATA_ENDING_FRAGMENT_BITMASK) != 0; 192 | 193 | // validity of value_length is checked in ChunkHeader::unmarshal 194 | if header.value_length() < PAYLOAD_DATA_HEADER_SIZE { 195 | return Err(Error::ErrChunkPayloadSmall); 196 | } 197 | 198 | let reader = &mut raw.slice(CHUNK_HEADER_SIZE..CHUNK_HEADER_SIZE + header.value_length()); 199 | 200 | let tsn = reader.get_u32(); 201 | let stream_identifier = reader.get_u16(); 202 | let stream_sequence_number = reader.get_u16(); 203 | let payload_type: PayloadProtocolIdentifier = reader.get_u32().into(); 204 | let user_data = raw.slice( 205 | CHUNK_HEADER_SIZE + PAYLOAD_DATA_HEADER_SIZE..CHUNK_HEADER_SIZE + header.value_length(), 206 | ); 207 | 208 | Ok(ChunkPayloadData { 209 | unordered, 210 | beginning_fragment, 211 | ending_fragment, 212 | immediate_sack, 213 | 214 | tsn, 215 | stream_identifier, 216 | stream_sequence_number, 217 | payload_type, 218 | user_data, 219 | acked: false, 220 | miss_indicator: 0, 221 | since: SystemTime::now(), 222 | nsent: 0, 223 | abandoned: Arc::new(AtomicBool::new(false)), 224 | all_inflight: Arc::new(AtomicBool::new(false)), 225 | retransmit: false, 226 | }) 227 | } 228 | 229 | fn marshal_to(&self, writer: &mut BytesMut) -> Result { 230 | self.header().marshal_to(writer)?; 231 | 232 | writer.put_u32(self.tsn); 233 | writer.put_u16(self.stream_identifier); 234 | writer.put_u16(self.stream_sequence_number); 235 | writer.put_u32(self.payload_type as u32); 236 | writer.extend(self.user_data.clone()); 237 | 238 | Ok(writer.len()) 239 | } 240 | 241 | fn check(&self) -> Result<()> { 242 | Ok(()) 243 | } 244 | 245 | fn value_length(&self) -> usize { 246 | PAYLOAD_DATA_HEADER_SIZE + self.user_data.len() 247 | } 248 | 249 | fn as_any(&self) -> &(dyn Any + Send + Sync) { 250 | self 251 | } 252 | } 253 | 254 | impl ChunkPayloadData { 255 | pub(crate) fn abandoned(&self) -> bool { 256 | let (abandoned, all_inflight) = ( 257 | self.abandoned.load(Ordering::SeqCst), 258 | self.all_inflight.load(Ordering::SeqCst), 259 | ); 260 | 261 | abandoned && all_inflight 262 | } 263 | 264 | pub(crate) fn set_abandoned(&self, abandoned: bool) { 265 | self.abandoned.store(abandoned, Ordering::SeqCst); 266 | } 267 | 268 | pub(crate) fn set_all_inflight(&mut self) { 269 | if self.ending_fragment { 270 | self.all_inflight.store(true, Ordering::SeqCst); 271 | } 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | use crc::{Crc, CRC_32_ISCSI}; 3 | 4 | const PADDING_MULTIPLE: usize = 4; 5 | 6 | pub(crate) fn get_padding_size(len: usize) -> usize { 7 | (PADDING_MULTIPLE - (len % PADDING_MULTIPLE)) % PADDING_MULTIPLE 8 | } 9 | 10 | /// Allocate and zero this data once. 11 | /// We need to use it for the checksum and don't want to allocate/clear each time. 12 | pub(crate) static FOUR_ZEROES: Bytes = Bytes::from_static(&[0, 0, 0, 0]); 13 | 14 | /// Fastest way to do a crc32 without allocating. 15 | pub(crate) fn generate_packet_checksum(raw: &Bytes) -> u32 { 16 | let hasher = Crc::::new(&CRC_32_ISCSI); 17 | let mut digest = hasher.digest(); 18 | digest.update(&raw[0..8]); 19 | digest.update(&FOUR_ZEROES[..]); 20 | digest.update(&raw[12..]); 21 | digest.finalize() 22 | } 23 | 24 | /// Serial Number Arithmetic (RFC 1982) 25 | #[inline] 26 | pub(crate) fn sna32lt(i1: u32, i2: u32) -> bool { 27 | (i1 < i2 && i2 - i1 < 1 << 31) || (i1 > i2 && i1 - i2 > 1 << 31) 28 | } 29 | 30 | #[inline] 31 | pub(crate) fn sna32lte(i1: u32, i2: u32) -> bool { 32 | i1 == i2 || sna32lt(i1, i2) 33 | } 34 | 35 | #[inline] 36 | pub(crate) fn sna32gt(i1: u32, i2: u32) -> bool { 37 | (i1 < i2 && (i2 - i1) >= 1 << 31) || (i1 > i2 && (i1 - i2) <= 1 << 31) 38 | } 39 | 40 | #[inline] 41 | pub(crate) fn sna32gte(i1: u32, i2: u32) -> bool { 42 | i1 == i2 || sna32gt(i1, i2) 43 | } 44 | 45 | #[inline] 46 | pub(crate) fn sna32eq(i1: u32, i2: u32) -> bool { 47 | i1 == i2 48 | } 49 | 50 | #[inline] 51 | pub(crate) fn sna16lt(i1: u16, i2: u16) -> bool { 52 | (i1 < i2 && (i2 - i1) < 1 << 15) || (i1 > i2 && (i1 - i2) > 1 << 15) 53 | } 54 | 55 | #[inline] 56 | pub(crate) fn sna16lte(i1: u16, i2: u16) -> bool { 57 | i1 == i2 || sna16lt(i1, i2) 58 | } 59 | 60 | #[inline] 61 | pub(crate) fn sna16gt(i1: u16, i2: u16) -> bool { 62 | (i1 < i2 && (i2 - i1) >= 1 << 15) || (i1 > i2 && (i1 - i2) <= 1 << 15) 63 | } 64 | 65 | #[inline] 66 | pub(crate) fn sna16gte(i1: u16, i2: u16) -> bool { 67 | i1 == i2 || sna16gt(i1, i2) 68 | } 69 | 70 | #[inline] 71 | pub(crate) fn sna16eq(i1: u16, i2: u16) -> bool { 72 | i1 == i2 73 | } 74 | 75 | #[cfg(test)] 76 | mod test { 77 | use crate::error::Result; 78 | 79 | use super::*; 80 | 81 | const DIV: isize = 16; 82 | 83 | #[test] 84 | fn test_serial_number_arithmetic32bit() -> Result<()> { 85 | const SERIAL_BITS: u32 = 32; 86 | const INTERVAL: u32 = ((1u64 << (SERIAL_BITS as u64)) / (DIV as u64)) as u32; 87 | const MAX_FORWARD_DISTANCE: u32 = 1 << ((SERIAL_BITS - 1) - 1); 88 | const MAX_BACKWARD_DISTANCE: u32 = 1 << (SERIAL_BITS - 1); 89 | 90 | for i in 0..DIV as u32 { 91 | let s1 = i * INTERVAL; 92 | let s2f = s1.checked_add(MAX_FORWARD_DISTANCE); 93 | let s2b = s1.checked_add(MAX_BACKWARD_DISTANCE); 94 | 95 | if let (Some(s2f), Some(s2b)) = (s2f, s2b) { 96 | assert!( 97 | sna32lt(s1, s2f), 98 | "s1 < s2 should be true: s1={} s2={}", 99 | s1, 100 | s2f 101 | ); 102 | assert!( 103 | !sna32lt(s1, s2b), 104 | "s1 < s2 should be false: s1={} s2={}", 105 | s1, 106 | s2b 107 | ); 108 | 109 | assert!( 110 | !sna32gt(s1, s2f), 111 | "s1 > s2 should be false: s1={} s2={}", 112 | s1, 113 | s2f 114 | ); 115 | assert!( 116 | sna32gt(s1, s2b), 117 | "s1 > s2 should be true: s1={} s2={}", 118 | s1, 119 | s2b 120 | ); 121 | 122 | assert!( 123 | sna32lte(s1, s2f), 124 | "s1 <= s2 should be true: s1={} s2={}", 125 | s1, 126 | s2f 127 | ); 128 | assert!( 129 | !sna32lte(s1, s2b), 130 | "s1 <= s2 should be false: s1={} s2={}", 131 | s1, 132 | s2b 133 | ); 134 | 135 | assert!( 136 | !sna32gte(s1, s2f), 137 | "s1 >= s2 should be fales: s1={} s2={}", 138 | s1, 139 | s2f 140 | ); 141 | assert!( 142 | sna32gte(s1, s2b), 143 | "s1 >= s2 should be true: s1={} s2={}", 144 | s1, 145 | s2b 146 | ); 147 | 148 | assert!( 149 | sna32eq(s2b, s2b), 150 | "s2 == s2 should be true: s2={} s2={}", 151 | s2b, 152 | s2b 153 | ); 154 | assert!( 155 | sna32lte(s2b, s2b), 156 | "s2 == s2 should be true: s2={} s2={}", 157 | s2b, 158 | s2b 159 | ); 160 | assert!( 161 | sna32gte(s2b, s2b), 162 | "s2 == s2 should be true: s2={} s2={}", 163 | s2b, 164 | s2b 165 | ); 166 | } 167 | 168 | if let Some(s1add1) = s1.checked_add(1) { 169 | assert!( 170 | !sna32eq(s1, s1add1), 171 | "s1 == s1+1 should be false: s1={} s1+1={}", 172 | s1, 173 | s1add1 174 | ); 175 | } 176 | 177 | if let Some(s1sub1) = s1.checked_sub(1) { 178 | assert!( 179 | !sna32eq(s1, s1sub1), 180 | "s1 == s1-1 hould be false: s1={} s1-1={}", 181 | s1, 182 | s1sub1 183 | ); 184 | } 185 | 186 | assert!( 187 | sna32eq(s1, s1), 188 | "s1 == s1 should be true: s1={} s2={}", 189 | s1, 190 | s1 191 | ); 192 | assert!( 193 | sna32lte(s1, s1), 194 | "s1 == s1 should be true: s1={} s2={}", 195 | s1, 196 | s1 197 | ); 198 | 199 | assert!( 200 | sna32gte(s1, s1), 201 | "s1 == s1 should be true: s1={} s2={}", 202 | s1, 203 | s1 204 | ); 205 | } 206 | 207 | Ok(()) 208 | } 209 | 210 | #[test] 211 | fn test_serial_number_arithmetic16bit() -> Result<()> { 212 | const SERIAL_BITS: u16 = 16; 213 | const INTERVAL: u16 = ((1u64 << (SERIAL_BITS as u64)) / (DIV as u64)) as u16; 214 | const MAX_FORWARD_DISTANCE: u16 = 1 << ((SERIAL_BITS - 1) - 1); 215 | const MAX_BACKWARD_DISTANCE: u16 = 1 << (SERIAL_BITS - 1); 216 | 217 | for i in 0..DIV as u16 { 218 | let s1 = i * INTERVAL; 219 | let s2f = s1.checked_add(MAX_FORWARD_DISTANCE); 220 | let s2b = s1.checked_add(MAX_BACKWARD_DISTANCE); 221 | 222 | if let (Some(s2f), Some(s2b)) = (s2f, s2b) { 223 | assert!( 224 | sna16lt(s1, s2f), 225 | "s1 < s2 should be true: s1={} s2={}", 226 | s1, 227 | s2f 228 | ); 229 | assert!( 230 | !sna16lt(s1, s2b), 231 | "s1 < s2 should be false: s1={} s2={}", 232 | s1, 233 | s2b 234 | ); 235 | 236 | assert!( 237 | !sna16gt(s1, s2f), 238 | "s1 > s2 should be fales: s1={} s2={}", 239 | s1, 240 | s2f 241 | ); 242 | assert!( 243 | sna16gt(s1, s2b), 244 | "s1 > s2 should be true: s1={} s2={}", 245 | s1, 246 | s2b 247 | ); 248 | 249 | assert!( 250 | sna16lte(s1, s2f), 251 | "s1 <= s2 should be true: s1={} s2={}", 252 | s1, 253 | s2f 254 | ); 255 | assert!( 256 | !sna16lte(s1, s2b), 257 | "s1 <= s2 should be false: s1={} s2={}", 258 | s1, 259 | s2b 260 | ); 261 | 262 | assert!( 263 | !sna16gte(s1, s2f), 264 | "s1 >= s2 should be fales: s1={} s2={}", 265 | s1, 266 | s2f 267 | ); 268 | assert!( 269 | sna16gte(s1, s2b), 270 | "s1 >= s2 should be true: s1={} s2={}", 271 | s1, 272 | s2b 273 | ); 274 | 275 | assert!( 276 | sna16eq(s2b, s2b), 277 | "s2 == s2 should be true: s2={} s2={}", 278 | s2b, 279 | s2b 280 | ); 281 | assert!( 282 | sna16lte(s2b, s2b), 283 | "s2 == s2 should be true: s2={} s2={}", 284 | s2b, 285 | s2b 286 | ); 287 | assert!( 288 | sna16gte(s2b, s2b), 289 | "s2 == s2 should be true: s2={} s2={}", 290 | s2b, 291 | s2b 292 | ); 293 | } 294 | 295 | assert!( 296 | sna16eq(s1, s1), 297 | "s1 == s1 should be true: s1={} s2={}", 298 | s1, 299 | s1 300 | ); 301 | 302 | if let Some(s1add1) = s1.checked_add(1) { 303 | assert!( 304 | !sna16eq(s1, s1add1), 305 | "s1 == s1+1 should be false: s1={} s1+1={}", 306 | s1, 307 | s1add1 308 | ); 309 | } 310 | if let Some(s1sub1) = s1.checked_sub(1) { 311 | assert!( 312 | !sna16eq(s1, s1sub1), 313 | "s1 == s1-1 hould be false: s1={} s1-1={}", 314 | s1, 315 | s1sub1 316 | ); 317 | } 318 | 319 | assert!( 320 | sna16lte(s1, s1), 321 | "s1 == s1 should be true: s1={} s2={}", 322 | s1, 323 | s1 324 | ); 325 | assert!( 326 | sna16gte(s1, s1), 327 | "s1 == s1 should be true: s1={} s2={}", 328 | s1, 329 | s1 330 | ); 331 | } 332 | 333 | Ok(()) 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/queue/reassembly_queue.rs: -------------------------------------------------------------------------------- 1 | use crate::chunk::chunk_payload_data::{ChunkPayloadData, PayloadProtocolIdentifier}; 2 | use crate::util::*; 3 | 4 | use crate::error::{Error, Result}; 5 | 6 | use std::cmp::Ordering; 7 | 8 | fn sort_chunks_by_tsn(c: &mut [ChunkPayloadData]) { 9 | c.sort_by(|a, b| { 10 | if sna32lt(a.tsn, b.tsn) { 11 | Ordering::Less 12 | } else { 13 | Ordering::Greater 14 | } 15 | }); 16 | } 17 | 18 | fn sort_chunks_by_ssn(c: &mut [ChunkSet]) { 19 | c.sort_by(|a, b| { 20 | if sna16lt(a.ssn, b.ssn) { 21 | Ordering::Less 22 | } else { 23 | Ordering::Greater 24 | } 25 | }); 26 | } 27 | 28 | /// chunkSet is a set of chunks that share the same SSN 29 | #[derive(Debug, Clone)] 30 | pub(crate) struct ChunkSet { 31 | /// used only with the ordered chunks 32 | pub(crate) ssn: u16, 33 | pub(crate) ppi: PayloadProtocolIdentifier, 34 | pub(crate) chunks: Vec, 35 | } 36 | 37 | impl ChunkSet { 38 | pub(crate) fn new(ssn: u16, ppi: PayloadProtocolIdentifier) -> Self { 39 | ChunkSet { 40 | ssn, 41 | ppi, 42 | chunks: vec![], 43 | } 44 | } 45 | 46 | pub(crate) fn push(&mut self, chunk: ChunkPayloadData) -> bool { 47 | // check if dup 48 | for c in &self.chunks { 49 | if c.tsn == chunk.tsn { 50 | return false; 51 | } 52 | } 53 | 54 | // append and sort 55 | self.chunks.push(chunk); 56 | sort_chunks_by_tsn(&mut self.chunks); 57 | 58 | // Check if we now have a complete set 59 | self.is_complete() 60 | } 61 | 62 | pub(crate) fn is_complete(&self) -> bool { 63 | // Condition for complete set 64 | // 0. Has at least one chunk. 65 | // 1. Begins with beginningFragment set to true 66 | // 2. Ends with endingFragment set to true 67 | // 3. TSN monotinically increase by 1 from beginning to end 68 | 69 | // 0. 70 | let n_chunks = self.chunks.len(); 71 | if n_chunks == 0 { 72 | return false; 73 | } 74 | 75 | // 1. 76 | if !self.chunks[0].beginning_fragment { 77 | return false; 78 | } 79 | 80 | // 2. 81 | if !self.chunks[n_chunks - 1].ending_fragment { 82 | return false; 83 | } 84 | 85 | // 3. 86 | let mut last_tsn = 0u32; 87 | for (i, c) in self.chunks.iter().enumerate() { 88 | if i > 0 { 89 | // Fragments must have contiguous TSN 90 | // From RFC 4960 Section 3.3.1: 91 | // When a user message is fragmented into multiple chunks, the TSNs are 92 | // used by the receiver to reassemble the message. This means that the 93 | // TSNs for each fragment of a fragmented user message MUST be strictly 94 | // sequential. 95 | if c.tsn != last_tsn + 1 { 96 | // mid or end fragment is missing 97 | return false; 98 | } 99 | } 100 | 101 | last_tsn = c.tsn; 102 | } 103 | 104 | true 105 | } 106 | } 107 | 108 | #[derive(Default, Debug)] 109 | pub(crate) struct ReassemblyQueue { 110 | pub(crate) si: u16, 111 | pub(crate) next_ssn: u16, 112 | /// expected SSN for next ordered chunk 113 | pub(crate) ordered: Vec, 114 | pub(crate) unordered: Vec, 115 | pub(crate) unordered_chunks: Vec, 116 | pub(crate) n_bytes: usize, 117 | } 118 | 119 | impl ReassemblyQueue { 120 | /// From RFC 4960 Sec 6.5: 121 | /// The Stream Sequence Number in all the streams MUST start from 0 when 122 | /// the association is Established. Also, when the Stream Sequence 123 | /// Number reaches the value 65535 the next Stream Sequence Number MUST 124 | /// be set to 0. 125 | pub(crate) fn new(si: u16) -> Self { 126 | ReassemblyQueue { 127 | si, 128 | next_ssn: 0, // From RFC 4960 Sec 6.5: 129 | ordered: vec![], 130 | unordered: vec![], 131 | unordered_chunks: vec![], 132 | n_bytes: 0, 133 | } 134 | } 135 | 136 | pub(crate) fn push(&mut self, chunk: ChunkPayloadData) -> bool { 137 | if chunk.stream_identifier != self.si { 138 | return false; 139 | } 140 | 141 | if chunk.unordered { 142 | // First, insert into unordered_chunks array 143 | //atomic.AddUint64(&r.n_bytes, uint64(len(chunk.userData))) 144 | self.n_bytes += chunk.user_data.len(); 145 | self.unordered_chunks.push(chunk); 146 | sort_chunks_by_tsn(&mut self.unordered_chunks); 147 | 148 | // Scan unordered_chunks that are contiguous (in TSN) 149 | // If found, append the complete set to the unordered array 150 | if let Some(cset) = self.find_complete_unordered_chunk_set() { 151 | self.unordered.push(cset); 152 | return true; 153 | } 154 | 155 | false 156 | } else { 157 | // This is an ordered chunk 158 | if sna16lt(chunk.stream_sequence_number, self.next_ssn) { 159 | return false; 160 | } 161 | 162 | self.n_bytes += chunk.user_data.len(); 163 | 164 | // Check if a chunkSet with the SSN already exists 165 | for s in &mut self.ordered { 166 | if s.ssn == chunk.stream_sequence_number { 167 | return s.push(chunk); 168 | } 169 | } 170 | 171 | // If not found, create a new chunkSet 172 | let mut cset = ChunkSet::new(chunk.stream_sequence_number, chunk.payload_type); 173 | let unordered = chunk.unordered; 174 | let ok = cset.push(chunk); 175 | self.ordered.push(cset); 176 | if !unordered { 177 | sort_chunks_by_ssn(&mut self.ordered); 178 | } 179 | 180 | ok 181 | } 182 | } 183 | 184 | pub(crate) fn find_complete_unordered_chunk_set(&mut self) -> Option { 185 | let mut start_idx = -1isize; 186 | let mut n_chunks = 0usize; 187 | let mut last_tsn = 0u32; 188 | let mut found = false; 189 | 190 | for (i, c) in self.unordered_chunks.iter().enumerate() { 191 | // seek beigining 192 | if c.beginning_fragment { 193 | start_idx = i as isize; 194 | n_chunks = 1; 195 | last_tsn = c.tsn; 196 | 197 | if c.ending_fragment { 198 | found = true; 199 | break; 200 | } 201 | continue; 202 | } 203 | 204 | if start_idx < 0 { 205 | continue; 206 | } 207 | 208 | // Check if contiguous in TSN 209 | if c.tsn != last_tsn + 1 { 210 | start_idx = -1; 211 | continue; 212 | } 213 | 214 | last_tsn = c.tsn; 215 | n_chunks += 1; 216 | 217 | if c.ending_fragment { 218 | found = true; 219 | break; 220 | } 221 | } 222 | 223 | if !found { 224 | return None; 225 | } 226 | 227 | // Extract the range of chunks 228 | let chunks: Vec = self 229 | .unordered_chunks 230 | .drain(start_idx as usize..(start_idx as usize) + n_chunks) 231 | .collect(); 232 | 233 | let mut chunk_set = ChunkSet::new(0, chunks[0].payload_type); 234 | chunk_set.chunks = chunks; 235 | 236 | Some(chunk_set) 237 | } 238 | 239 | pub(crate) fn is_readable(&self) -> bool { 240 | // Check unordered first 241 | if !self.unordered.is_empty() { 242 | // The chunk sets in r.unordered should all be complete. 243 | return true; 244 | } 245 | 246 | // Check ordered sets 247 | if !self.ordered.is_empty() { 248 | let cset = &self.ordered[0]; 249 | if cset.is_complete() && sna16lte(cset.ssn, self.next_ssn) { 250 | return true; 251 | } 252 | } 253 | false 254 | } 255 | 256 | pub(crate) fn read(&mut self, buf: &mut [u8]) -> Result<(usize, PayloadProtocolIdentifier)> { 257 | // Check unordered first 258 | let cset = if !self.unordered.is_empty() { 259 | self.unordered.remove(0) 260 | } else if !self.ordered.is_empty() { 261 | // Now, check ordered 262 | let cset = &self.ordered[0]; 263 | if !cset.is_complete() { 264 | return Err(Error::ErrTryAgain); 265 | } 266 | if sna16gt(cset.ssn, self.next_ssn) { 267 | return Err(Error::ErrTryAgain); 268 | } 269 | if cset.ssn == self.next_ssn { 270 | self.next_ssn += 1; 271 | } 272 | self.ordered.remove(0) 273 | } else { 274 | return Err(Error::ErrTryAgain); 275 | }; 276 | 277 | // Concat all fragments into the buffer 278 | let mut n_written = 0; 279 | let mut err = None; 280 | for c in &cset.chunks { 281 | let to_copy = c.user_data.len(); 282 | self.subtract_num_bytes(to_copy); 283 | if err.is_none() { 284 | let n = std::cmp::min(to_copy, buf.len() - n_written); 285 | buf[n_written..n_written + n].copy_from_slice(&c.user_data[..n]); 286 | n_written += n; 287 | if n < to_copy { 288 | err = Some(Error::ErrShortBuffer); 289 | } 290 | } 291 | } 292 | 293 | if let Some(err) = err { 294 | Err(err) 295 | } else { 296 | Ok((n_written, cset.ppi)) 297 | } 298 | } 299 | 300 | /// Use last_ssn to locate a chunkSet then remove it if the set has 301 | /// not been complete 302 | pub(crate) fn forward_tsn_for_ordered(&mut self, last_ssn: u16) { 303 | let num_bytes = self 304 | .ordered 305 | .iter() 306 | .filter(|s| sna16lte(s.ssn, last_ssn) && !s.is_complete()) 307 | .fold(0, |n, s| { 308 | n + s.chunks.iter().fold(0, |acc, c| acc + c.user_data.len()) 309 | }); 310 | self.subtract_num_bytes(num_bytes); 311 | 312 | self.ordered 313 | .retain(|s| !sna16lte(s.ssn, last_ssn) || s.is_complete()); 314 | 315 | // Finally, forward next_ssn 316 | if sna16lte(self.next_ssn, last_ssn) { 317 | self.next_ssn = last_ssn + 1; 318 | } 319 | } 320 | 321 | /// Remove all fragments in the unordered sets that contains chunks 322 | /// equal to or older than `new_cumulative_tsn`. 323 | /// We know all sets in the r.unordered are complete ones. 324 | /// Just remove chunks that are equal to or older than new_cumulative_tsn 325 | /// from the unordered_chunks 326 | pub(crate) fn forward_tsn_for_unordered(&mut self, new_cumulative_tsn: u32) { 327 | let mut last_idx: isize = -1; 328 | for (i, c) in self.unordered_chunks.iter().enumerate() { 329 | if sna32gt(c.tsn, new_cumulative_tsn) { 330 | break; 331 | } 332 | last_idx = i as isize; 333 | } 334 | if last_idx >= 0 { 335 | for i in 0..(last_idx + 1) as usize { 336 | self.subtract_num_bytes(self.unordered_chunks[i].user_data.len()); 337 | } 338 | self.unordered_chunks.drain(..(last_idx + 1) as usize); 339 | } 340 | } 341 | 342 | pub(crate) fn subtract_num_bytes(&mut self, n_bytes: usize) { 343 | if self.n_bytes >= n_bytes { 344 | self.n_bytes -= n_bytes; 345 | } else { 346 | self.n_bytes = 0; 347 | } 348 | } 349 | 350 | pub(crate) fn get_num_bytes(&self) -> usize { 351 | self.n_bytes 352 | } 353 | } 354 | --------------------------------------------------------------------------------