use super::*;
use std::io;
use std::net::SocketAddr;
type Result<T> = std::result::Result<T, util::Error>;
impl From<Error> for util::Error {
fn from(e: Error) -> Self {
util::Error::from_std(e)
}
}
struct DumbConn;
#[async_trait]
impl Conn for DumbConn {
async fn connect(&self, _addr: SocketAddr) -> Result<()> {
Err(io::Error::new(io::ErrorKind::Other, "Not applicable").into())
}
async fn recv(&self, _b: &mut [u8]) -> Result<usize> {
Ok(0)
}
async fn recv_from(&self, _buf: &mut [u8]) -> Result<(usize, SocketAddr)> {
Err(io::Error::new(io::ErrorKind::Other, "Not applicable").into())
}
async fn send(&self, _b: &[u8]) -> Result<usize> {
Ok(0)
}
async fn send_to(&self, _buf: &[u8], _target: SocketAddr) -> Result<usize> {
Err(io::Error::new(io::ErrorKind::Other, "Not applicable").into())
}
async fn local_addr(&self) -> Result<SocketAddr> {
Err(io::Error::new(io::ErrorKind::AddrNotAvailable, "Addr Not Available").into())
}
async fn remote_addr(&self) -> Option<SocketAddr> {
None
}
async fn close(&self) -> Result<()> {
Ok(())
}
}
fn create_association_internal(config: Config) -> AssociationInternal {
let (close_loop_ch_tx, _close_loop_ch_rx) = broadcast::channel(1);
let (accept_ch_tx, _accept_ch_rx) = mpsc::channel(1);
let (handshake_completed_ch_tx, _handshake_completed_ch_rx) = mpsc::channel(1);
let (awake_write_loop_ch_tx, _awake_write_loop_ch_rx) = mpsc::channel(1);
AssociationInternal::new(
config,
close_loop_ch_tx,
accept_ch_tx,
handshake_completed_ch_tx,
Arc::new(awake_write_loop_ch_tx),
)
}
#[test]
fn test_create_forward_tsn_forward_one_abandoned() -> Result<()> {
let mut a = AssociationInternal::default();
a.cumulative_tsn_ack_point = 9;
a.advanced_peer_tsn_ack_point = 10;
a.inflight_queue.push_no_check(ChunkPayloadData {
beginning_fragment: true,
ending_fragment: true,
tsn: 10,
stream_identifier: 1,
stream_sequence_number: 2,
user_data: Bytes::from_static(b"ABC"),
nsent: 1,
abandoned: Arc::new(AtomicBool::new(true)),
..Default::default()
});
let fwdtsn = a.create_forward_tsn();
assert_eq!(10, fwdtsn.new_cumulative_tsn, "should be able to serialize");
assert_eq!(1, fwdtsn.streams.len(), "there should be one stream");
assert_eq!(1, fwdtsn.streams[0].identifier, "si should be 1");
assert_eq!(2, fwdtsn.streams[0].sequence, "ssn should be 2");
Ok(())
}
#[test]
fn test_create_forward_tsn_forward_two_abandoned_with_the_same_si() -> Result<()> {
let mut a = AssociationInternal::default();
a.cumulative_tsn_ack_point = 9;
a.advanced_peer_tsn_ack_point = 12;
a.inflight_queue.push_no_check(ChunkPayloadData {
beginning_fragment: true,
ending_fragment: true,
tsn: 10,
stream_identifier: 1,
stream_sequence_number: 2,
user_data: Bytes::from_static(b"ABC"),
nsent: 1,
abandoned: Arc::new(AtomicBool::new(true)),
..Default::default()
});
a.inflight_queue.push_no_check(ChunkPayloadData {
beginning_fragment: true,
ending_fragment: true,
tsn: 11,
stream_identifier: 1,
stream_sequence_number: 3,
user_data: Bytes::from_static(b"DEF"),
nsent: 1,
abandoned: Arc::new(AtomicBool::new(true)),
..Default::default()
});
a.inflight_queue.push_no_check(ChunkPayloadData {
beginning_fragment: true,
ending_fragment: true,
tsn: 12,
stream_identifier: 2,
stream_sequence_number: 1,
user_data: Bytes::from_static(b"123"),
nsent: 1,
abandoned: Arc::new(AtomicBool::new(true)),
..Default::default()
});
let fwdtsn = a.create_forward_tsn();
assert_eq!(12, fwdtsn.new_cumulative_tsn, "should be able to serialize");
assert_eq!(2, fwdtsn.streams.len(), "there should be two stream");
let mut si1ok = false;
let mut si2ok = false;
for s in &fwdtsn.streams {
match s.identifier {
1 => {
assert_eq!(3, s.sequence, "ssn should be 3");
si1ok = true;
}
2 => {
assert_eq!(1, s.sequence, "ssn should be 1");
si2ok = true;
}
_ => assert!(false, "unexpected stream indentifier"),
}
}
assert!(si1ok, "si=1 should be present");
assert!(si2ok, "si=2 should be present");
Ok(())
}
#[tokio::test]
async fn test_handle_forward_tsn_forward_3unreceived_chunks() -> Result<()> {
let mut a = AssociationInternal::default();
a.use_forward_tsn = true;
let prev_tsn = a.peer_last_tsn;
let fwdtsn = ChunkForwardTsn {
new_cumulative_tsn: a.peer_last_tsn + 3,
streams: vec![ChunkForwardTsnStream {
identifier: 0,
sequence: 0,
}],
};
let p = a.handle_forward_tsn(&fwdtsn).await?;
let delayed_ack_triggered = a.delayed_ack_triggered;
let immediate_ack_triggered = a.immediate_ack_triggered;
assert_eq!(
a.peer_last_tsn,
prev_tsn + 3,
"peerLastTSN should advance by 3 "
);
assert!(delayed_ack_triggered, "delayed sack should be triggered");
assert!(
!immediate_ack_triggered,
"immediate sack should NOT be triggered"
);
assert!(p.is_empty(), "should return empty");
Ok(())
}
#[tokio::test]
async fn test_handle_forward_tsn_forward_1for1_missing() -> Result<()> {
let mut a = AssociationInternal::default();
a.use_forward_tsn = true;
let prev_tsn = a.peer_last_tsn;
a.payload_queue.push(
ChunkPayloadData {
beginning_fragment: true,
ending_fragment: true,
tsn: a.peer_last_tsn + 2,
stream_identifier: 0,
stream_sequence_number: 1,
user_data: Bytes::from_static(b"ABC"),
..Default::default()
},
a.peer_last_tsn,
);
let fwdtsn = ChunkForwardTsn {
new_cumulative_tsn: a.peer_last_tsn + 1,
streams: vec![ChunkForwardTsnStream {
identifier: 0,
sequence: 1,
}],
};
let p = a.handle_forward_tsn(&fwdtsn).await?;
let delayed_ack_triggered = a.delayed_ack_triggered;
let immediate_ack_triggered = a.immediate_ack_triggered;
assert_eq!(
a.peer_last_tsn,
prev_tsn + 2,
"peerLastTSN should advance by 2"
);
assert!(delayed_ack_triggered, "delayed sack should be triggered");
assert!(
!immediate_ack_triggered,
"immediate sack should NOT be triggered"
);
assert!(p.is_empty(), "should return empty");
Ok(())
}
#[tokio::test]
async fn test_handle_forward_tsn_forward_1for2_missing() -> Result<()> {
let mut a = AssociationInternal::default();
a.use_forward_tsn = true;
let prev_tsn = a.peer_last_tsn;
a.payload_queue.push(
ChunkPayloadData {
beginning_fragment: true,
ending_fragment: true,
tsn: a.peer_last_tsn + 3,
stream_identifier: 0,
stream_sequence_number: 1,
user_data: Bytes::from_static(b"ABC"),
..Default::default()
},
a.peer_last_tsn,
);
let fwdtsn = ChunkForwardTsn {
new_cumulative_tsn: a.peer_last_tsn + 1,
streams: vec![ChunkForwardTsnStream {
identifier: 0,
sequence: 1,
}],
};
let p = a.handle_forward_tsn(&fwdtsn).await?;
let immediate_ack_triggered = a.immediate_ack_triggered;
assert_eq!(
a.peer_last_tsn,
prev_tsn + 1,
"peerLastTSN should advance by 1"
);
assert!(
immediate_ack_triggered,
"immediate sack should be triggered"
);
assert!(p.is_empty(), "should return empty");
Ok(())
}
#[tokio::test]
async fn test_handle_forward_tsn_dup_forward_tsn_chunk_should_generate_sack() -> Result<()> {
let mut a = AssociationInternal::default();
a.use_forward_tsn = true;
let prev_tsn = a.peer_last_tsn;
let fwdtsn = ChunkForwardTsn {
new_cumulative_tsn: a.peer_last_tsn,
streams: vec![ChunkForwardTsnStream {
identifier: 0,
sequence: 1,
}],
};
let p = a.handle_forward_tsn(&fwdtsn).await?;
let ack_state = a.ack_state;
assert_eq!(a.peer_last_tsn, prev_tsn, "peerLastTSN should not advance");
assert_eq!(AckState::Immediate, ack_state, "sack should be requested");
assert!(p.is_empty(), "should return empty");
Ok(())
}
#[tokio::test]
async fn test_assoc_create_new_stream() -> Result<()> {
let (accept_ch_tx, _accept_ch_rx) = mpsc::channel(ACCEPT_CH_SIZE);
let mut a = AssociationInternal {
accept_ch_tx: Some(accept_ch_tx),
..Default::default()
};
for i in 0..ACCEPT_CH_SIZE {
let s = a.create_stream(i as u16, true);
if let Some(s) = s {
let result = a.streams.get(&s.stream_identifier);
assert!(result.is_some(), "should be in a.streams map");
} else {
assert!(false, "{} should success", i);
}
}
let new_si = ACCEPT_CH_SIZE as u16;
let s = a.create_stream(new_si, true);
assert!(s.is_none(), "should be none");
let result = a.streams.get(&new_si);
assert!(result.is_none(), "should NOT be in a.streams map");
let to_be_ignored = ChunkPayloadData {
beginning_fragment: true,
ending_fragment: true,
tsn: a.peer_last_tsn + 1,
stream_identifier: new_si,
user_data: Bytes::from_static(b"ABC"),
..Default::default()
};
let p = a.handle_data(&to_be_ignored).await?;
assert!(p.is_empty(), "should return empty");
Ok(())
}
async fn handle_init_test(name: &str, initial_state: AssociationState, expect_err: bool) {
let mut a = create_association_internal(Config {
net_conn: Arc::new(DumbConn {}),
max_receive_buffer_size: 0,
max_message_size: 0,
name: "client".to_owned(),
});
a.set_state(initial_state);
let pkt = Packet {
source_port: 5001,
destination_port: 5002,
..Default::default()
};
let mut init = ChunkInit {
initial_tsn: 1234,
num_outbound_streams: 1001,
num_inbound_streams: 1002,
initiate_tag: 5678,
advertised_receiver_window_credit: 512 * 1024,
..Default::default()
};
init.set_supported_extensions();
let result = a.handle_init(&pkt, &init).await;
if expect_err {
assert!(result.is_err(), "{} should fail", name);
return;
} else {
assert!(result.is_ok(), "{} should be ok", name);
}
assert_eq!(
if init.initial_tsn == 0 {
u32::MAX
} else {
init.initial_tsn - 1
},
a.peer_last_tsn,
"{} should match",
name
);
assert_eq!(1001, a.my_max_num_outbound_streams, "{} should match", name);
assert_eq!(1002, a.my_max_num_inbound_streams, "{} should match", name);
assert_eq!(5678, a.peer_verification_tag, "{} should match", name);
assert_eq!(pkt.source_port, a.destination_port, "{} should match", name);
assert_eq!(pkt.destination_port, a.source_port, "{} should match", name);
assert!(a.use_forward_tsn, "{} should be set to true", name);
}
#[tokio::test]
async fn test_assoc_handle_init() -> Result<()> {
handle_init_test("normal", AssociationState::Closed, false).await;
handle_init_test(
"unexpected state established",
AssociationState::Established,
true,
)
.await;
handle_init_test(
"unexpected state shutdownAckSent",
AssociationState::ShutdownAckSent,
true,
)
.await;
handle_init_test(
"unexpected state shutdownPending",
AssociationState::ShutdownPending,
true,
)
.await;
handle_init_test(
"unexpected state shutdownReceived",
AssociationState::ShutdownReceived,
true,
)
.await;
handle_init_test(
"unexpected state shutdownSent",
AssociationState::ShutdownSent,
true,
)
.await;
Ok(())
}
#[tokio::test]
async fn test_assoc_max_message_size_default() -> Result<()> {
let mut a = create_association_internal(Config {
net_conn: Arc::new(DumbConn {}),
max_receive_buffer_size: 0,
max_message_size: 0,
name: "client".to_owned(),
});
assert_eq!(
65536,
a.max_message_size.load(Ordering::SeqCst),
"should match"
);
let stream = a.create_stream(1, false);
assert!(stream.is_some(), "should succeed");
if let Some(s) = stream {
let p = Bytes::from(vec![0u8; 65537]);
let ppi = PayloadProtocolIdentifier::from(s.default_payload_type.load(Ordering::SeqCst));
if let Err(err) = s.write_sctp(&p.slice(..65536), ppi).await {
assert_ne!(
Error::ErrOutboundPacketTooLarge,
err,
"should be not Error::ErrOutboundPacketTooLarge"
);
} else {
assert!(false, "should be error");
}
if let Err(err) = s.write_sctp(&p.slice(..65537), ppi).await {
assert_eq!(
Error::ErrOutboundPacketTooLarge,
err,
"should be Error::ErrOutboundPacketTooLarge"
);
} else {
assert!(false, "should be error");
}
}
Ok(())
}
#[tokio::test]
async fn test_assoc_max_message_size_explicit() -> Result<()> {
let mut a = create_association_internal(Config {
net_conn: Arc::new(DumbConn {}),
max_receive_buffer_size: 0,
max_message_size: 30000,
name: "client".to_owned(),
});
assert_eq!(
30000,
a.max_message_size.load(Ordering::SeqCst),
"should match"
);
let stream = a.create_stream(1, false);
assert!(stream.is_some(), "should succeed");
if let Some(s) = stream {
let p = Bytes::from(vec![0u8; 30001]);
let ppi = PayloadProtocolIdentifier::from(s.default_payload_type.load(Ordering::SeqCst));
if let Err(err) = s.write_sctp(&p.slice(..30000), ppi).await {
assert_ne!(
Error::ErrOutboundPacketTooLarge,
err,
"should be not Error::ErrOutboundPacketTooLarge"
);
} else {
assert!(false, "should be error");
}
if let Err(err) = s.write_sctp(&p.slice(..30001), ppi).await {
assert_eq!(
Error::ErrOutboundPacketTooLarge,
err,
"should be Error::ErrOutboundPacketTooLarge"
);
} else {
assert!(false, "should be error");
}
}
Ok(())
}