use std::{collections::HashMap, sync::Arc, time::Duration};
use tokio::sync::mpsc;
use crate::{
MessageDirection, SessionMessage,
errors::SessionError,
producer_buffer, receiver_buffer,
session::{
AppChannelSender, Common, CommonSession, GwChannelSender, Id, Info, Session, SessionConfig,
SessionConfigTrait, SessionDirection, State,
},
timer,
};
use producer_buffer::ProducerBuffer;
use receiver_buffer::ReceiverBuffer;
use agp_datapath::messages::{AgentType, utils::AgpHeaderFlags};
use agp_datapath::{
messages::Agent,
pubsub::proto::pubsub::v1::{AgpHeader, Message, SessionHeader, SessionHeaderType},
};
use tonic::{Status, async_trait};
use tracing::{debug, error, trace, warn};
const STREAM_BROADCAST: u32 = 50;
#[derive(Debug, Clone, PartialEq)]
pub struct StreamingConfiguration {
pub direction: SessionDirection,
pub topic: AgentType,
pub max_retries: u32,
pub timeout: std::time::Duration,
}
impl SessionConfigTrait for StreamingConfiguration {
fn replace(&mut self, session_config: &SessionConfig) -> Result<(), SessionError> {
match session_config {
SessionConfig::Streaming(config) => {
if self.direction != config.direction {
return Err(SessionError::ConfigurationError(format!(
"cannot change session direction from {:?} to {:?}",
self.direction, config.direction
)));
}
*self = config.clone();
Ok(())
}
_ => Err(SessionError::ConfigurationError(format!(
"invalid session config type: expected Streaming, got {:?}",
session_config
))),
}
}
}
impl Default for StreamingConfiguration {
fn default() -> Self {
StreamingConfiguration {
direction: SessionDirection::Receiver,
topic: AgentType::default(),
max_retries: 10,
timeout: std::time::Duration::from_millis(1000),
}
}
}
impl std::fmt::Display for StreamingConfiguration {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"StreamingConfiguration: max_retries: {}, timeout: {} ms",
self.max_retries,
self.timeout.as_millis(),
)
}
}
impl StreamingConfiguration {
pub fn new(
direction: SessionDirection,
topic: Option<AgentType>,
max_retries: Option<u32>,
timeout: Option<std::time::Duration>,
) -> Self {
StreamingConfiguration {
direction,
topic: topic.unwrap_or_default(),
max_retries: max_retries.unwrap_or(0),
timeout: timeout.unwrap_or(std::time::Duration::from_millis(0)),
}
}
}
struct RtxTimerObserver {
producer_name: Agent,
channel: mpsc::Sender<Result<(u32, bool, Agent), Status>>,
}
#[async_trait]
impl timer::TimerObserver for RtxTimerObserver {
async fn on_timeout(&self, timer_id: u32, timeouts: u32) {
trace!("timeout number {} for rtx {}, retry", timeouts, timer_id);
if self
.channel
.send(Ok((timer_id, true, self.producer_name.clone())))
.await
.is_err()
{
error!("error notifying the process loop - rtx timer");
}
}
async fn on_failure(&self, timer_id: u32, timeouts: u32) {
trace!(
"timeout number {} for rtx {}, stop retry",
timeouts, timer_id
);
if self
.channel
.send(Ok((timer_id, false, self.producer_name.clone())))
.await
.is_err()
{
error!("error notifying the process loop - rtx timer failure");
}
}
async fn on_stop(&self, timer_id: u32) {
trace!("timer for rtx {} cancelled", timer_id);
}
}
struct ProducerTimerObserver {
channel: mpsc::Sender<Result<(), Status>>,
}
#[async_trait]
impl timer::TimerObserver for ProducerTimerObserver {
async fn on_timeout(&self, _timer_id: u32, timeouts: u32) {
trace!(
"timeout number {} for producer timer, send beacon",
timeouts
);
if self.channel.send(Ok(())).await.is_err() {
error!("error notifying the process loop - producer timer");
}
}
async fn on_failure(&self, _timer_id: u32, _timeouts: u32) {
panic!("received on failure event on producer timer",);
}
async fn on_stop(&self, _timer_id: u32) {
trace!("producer timer cancelled");
}
}
struct ProducerState {
buffer: ProducerBuffer,
next_id: u32,
timer_observer: Arc<ProducerTimerObserver>,
timer: timer::Timer,
}
struct Receiver {
buffer: ReceiverBuffer,
timer_observer: Arc<RtxTimerObserver>,
rtx_map: HashMap<u32, Message>,
timers_map: HashMap<u32, timer::Timer>,
}
struct ReceiverState {
buffers: HashMap<Agent, Receiver>,
}
struct BidirectionalState {
receiver: ReceiverState,
producer: ProducerState,
}
enum Endpoint {
Producer(ProducerState),
Receiver(ReceiverState),
Bidirectional(BidirectionalState),
}
pub(crate) struct Streaming {
common: Common,
tx: mpsc::Sender<Result<(Message, MessageDirection), Status>>,
}
impl Streaming {
pub(crate) fn new(
id: Id,
session_config: StreamingConfiguration,
session_direction: SessionDirection,
agent: Agent,
tx_gw: GwChannelSender,
tx_app: AppChannelSender,
) -> Streaming {
let (tx, rx) = mpsc::channel(128);
let stream = Streaming {
common: Common::new(
id,
session_direction.clone(),
SessionConfig::Streaming(session_config.clone()),
agent,
tx_gw,
tx_app,
),
tx,
};
stream.process_message(rx, session_direction);
stream
}
fn process_message(
&self,
mut rx: mpsc::Receiver<Result<(Message, MessageDirection), Status>>,
session_direction: SessionDirection,
) {
let session_id = self.common.id();
let send_gw = self.common.tx_gw();
let send_app = self.common.tx_app();
let source = self.common.source().clone();
let (max_retries, timeout) = match self.common.session_config() {
SessionConfig::Streaming(streaming_configuration) => (
streaming_configuration.max_retries,
streaming_configuration.timeout,
),
_ => {
panic!("unable to parse streaming configuration");
}
};
let (rtx_timer_tx, mut rtx_timer_rx) = mpsc::channel(128);
let (prod_timer_tx, mut prod_timer_rx) = mpsc::channel(128);
let mut endpoint = match session_direction {
SessionDirection::Sender => {
let prod = ProducerState {
buffer: ProducerBuffer::with_capacity(500),
timer_observer: Arc::new(ProducerTimerObserver {
channel: prod_timer_tx,
}),
timer: timer::Timer::new(
1,
timer::TimerType::Exponential,
Duration::from_millis(1000),
Some(Duration::from_secs(30)),
None,
),
next_id: 0,
};
Endpoint::Producer(prod)
}
SessionDirection::Receiver => {
let recv = ReceiverState {
buffers: HashMap::new(),
};
Endpoint::Receiver(recv)
}
SessionDirection::Bidirectional => {
let producer = ProducerState {
buffer: ProducerBuffer::with_capacity(500),
timer_observer: Arc::new(ProducerTimerObserver {
channel: prod_timer_tx,
}),
timer: timer::Timer::new(
1,
timer::TimerType::Exponential,
Duration::from_millis(500),
Some(Duration::from_secs(30)),
None,
),
next_id: 0,
};
let receiver = ReceiverState {
buffers: HashMap::new(),
};
let state = BidirectionalState { receiver, producer };
Endpoint::Bidirectional(state)
}
};
let mut rtx_timer_rx_closed = false;
let mut prod_timer_rx_closed = false;
tokio::spawn(async move {
debug!("starting message processing on session {}", session_id);
loop {
tokio::select! {
next = rx.recv() => {
match next {
None => {
debug!("no more messages to process on session {}", session_id);
break;
}
Some(result) => {
debug!("got a message in process message");
if result.is_err() {
error!("error receiving a message on session {}, drop it", session_id);
continue;
}
let (msg, direction) = result.unwrap();
match &mut endpoint {
Endpoint::Producer(producer) => {
match direction {
MessageDirection::North => {
trace!("received message from the gataway on producer session {}", session_id);
match msg.get_session_header().header_type() {
SessionHeaderType::RtxRequest => {}
_ => {
error!("received invalid packet type on producer session {}: not RTX request", session_id);
continue;
}
};
process_incoming_rtx_request(msg, session_id, producer, &source, &send_gw).await;
}
MessageDirection::South => {
process_message_from_app(msg, session_id, producer, false, &send_gw, &send_app).await;
}
}
}
Endpoint::Receiver(receiver) => {
trace!("received message from the gataway on receiver session {}", session_id);
process_message_from_gw(msg, session_id, receiver, &source, max_retries, timeout, &rtx_timer_tx, &send_gw, &send_app).await;
}
Endpoint::Bidirectional(state) => {
match direction {
MessageDirection::North => {
trace!("received message from the gataway on bidirectional session {}", session_id);
match msg.get_session_header().header_type() {
SessionHeaderType::RtxRequest => {
process_incoming_rtx_request(msg, session_id, &state.producer, &source, &send_gw).await;
}
_ => {
process_message_from_gw(msg, session_id, &mut state.receiver, &source, max_retries, timeout, &rtx_timer_tx, &send_gw, &send_app).await;
}
}
}
MessageDirection::South => {
process_message_from_app(msg, session_id, &mut state.producer, true, &send_gw, &send_app).await;
}
};
}
}
}
}
}
next_rtx_timer = rtx_timer_rx.recv(), if !rtx_timer_rx_closed => {
match next_rtx_timer {
None => {
debug!("no more rtx timers to process");
rtx_timer_rx_closed = true;
},
Some(result) => {
if result.is_err() {
error!("error receiving an RTX timer, skip it");
continue;
}
let (msg_id, retry, producer_name) = result.unwrap();
match &mut endpoint {
Endpoint::Receiver(receiver) => {
if retry {
handle_rtx_timeout(receiver, &producer_name, msg_id, session_id, &send_gw).await;
} else {
handle_rtx_failure(receiver, &producer_name, msg_id, session_id, &send_app).await;
}
}
Endpoint::Producer(_) => {
error!("received rtx timer on a producer buffer");
continue;
}
Endpoint::Bidirectional(state) => {
if retry {
handle_rtx_timeout(&mut state.receiver, &producer_name, msg_id, session_id, &send_gw).await;
} else {
handle_rtx_failure(&mut state.receiver, &producer_name, msg_id, session_id, &send_app).await;
}
}
}
}
}
}
next_prod_timer = prod_timer_rx.recv(), if !prod_timer_rx_closed => {
match next_prod_timer {
None => {
debug!("no more prod timers to process");
prod_timer_rx_closed = true;
},
Some(result) => match result {
Ok(_) => {
match &mut endpoint {
Endpoint::Producer(producer) => {
let last_msg_id = producer.next_id - 1;
debug!("received producer timer, last packet = {}", last_msg_id);
send_beacon_msg(&source, producer.buffer.get_destination_name(), SessionHeaderType::BeaconStream, last_msg_id, session_id, &send_gw).await;
}
Endpoint::Bidirectional(state) => {
let last_msg_id = state.producer.next_id - 1;
debug!("received producer timer, last packet = {}", last_msg_id);
send_beacon_msg(&source, state.producer.buffer.get_destination_name(), SessionHeaderType::BeaconPubSub, last_msg_id, session_id, &send_gw).await;
}
_ => {
error!("received producer timer on a non producer buffer");
continue;
}
}
}
Err(_) => {
error!("error receiving a producer timer, skip it");
continue;
}
},
}
}
}
}
debug!(
"stopping message processing on streaming session {}",
session_id
);
});
}
}
async fn process_incoming_rtx_request(
msg: Message,
session_id: u32,
producer: &ProducerState,
source: &Agent,
send_gw: &mpsc::Sender<Result<Message, Status>>,
) {
let msg_rtx_id = msg.get_id();
trace!(
"received rtx for message {} on producer session {}",
msg_rtx_id, session_id
);
let pkt_src = msg.get_source();
let incoming_conn = msg.get_incoming_conn();
let rtx_pub = match producer.buffer.get(msg_rtx_id as usize) {
Some(packet) => {
trace!(
"packet {} exists in the producer buffer, create rtx reply",
msg_rtx_id
);
let payload = match packet.get_payload() {
Some(p) => p,
None => {
error!("unable to get the payload from the packet");
return;
}
};
let agp_header = Some(AgpHeader::new(
source,
pkt_src.agent_type(),
Some(pkt_src.agent_id()),
Some(
AgpHeaderFlags::default()
.with_forward_to(incoming_conn)
.with_fanout(1),
),
));
let session_header = Some(SessionHeader::new(
SessionHeaderType::RtxReply.into(),
session_id,
msg_rtx_id,
));
Message::new_publish_with_headers(agp_header, session_header, "", payload.blob.to_vec())
}
None => {
debug!(
"received an RTX messages for an old packet on session {}",
session_id
);
let flags = AgpHeaderFlags::default()
.with_forward_to(incoming_conn)
.with_error(true);
let agp_header = Some(AgpHeader::new(
source,
pkt_src.agent_type(),
Some(pkt_src.agent_id()),
Some(flags),
));
let session_header = Some(SessionHeader::new(
SessionHeaderType::RtxReply.into(),
session_id,
msg_rtx_id,
));
Message::new_publish_with_headers(agp_header, session_header, "", vec![])
}
};
trace!("send rtx reply for message {}", msg_rtx_id);
if send_gw.send(Ok(rtx_pub)).await.is_err() {
error!(
"error sending RTX packet to the gateway on session {}",
session_id
);
}
}
async fn process_message_from_app(
mut msg: Message,
session_id: u32,
producer: &mut ProducerState,
is_bidirectional: bool,
send_gw: &mpsc::Sender<Result<Message, Status>>,
send_app: &mpsc::Sender<Result<SessionMessage, SessionError>>,
) {
trace!("received message from the app on session {}", session_id);
if is_bidirectional {
msg.set_header_type(SessionHeaderType::PubSub);
} else {
msg.set_header_type(SessionHeaderType::Stream);
}
msg.set_message_id(producer.next_id);
msg.set_fanout(STREAM_BROADCAST);
trace!(
"add message {} to the producer buffer on session {}",
producer.next_id, session_id
);
if !producer.buffer.push(msg.clone()) {
warn!("cannot add packet to the local buffer");
}
trace!(
"send message {} to the producer buffer on session {}",
producer.next_id, session_id
);
producer.next_id += 1;
if send_gw.send(Ok(msg)).await.is_err() {
error!(
"error sending publication packet to the gateway on session {}",
session_id
);
send_app
.send(Err(SessionError::Processing(
"error sending message to the local gateway".to_string(),
)))
.await
.expect("error notifying app");
}
producer.timer.reset(producer.timer_observer.clone());
}
#[allow(clippy::too_many_arguments)]
async fn process_message_from_gw(
msg: Message,
session_id: u32,
receiver_state: &mut ReceiverState,
source: &Agent,
max_retries: u32,
timeout: Duration,
rtx_timer_tx: &mpsc::Sender<Result<(u32, bool, Agent), Status>>,
send_gw: &mpsc::Sender<Result<Message, Status>>,
send_app: &mpsc::Sender<Result<SessionMessage, SessionError>>,
) {
let producer_name = msg.get_source();
let producer_conn = msg.get_incoming_conn();
let receiver = match receiver_state.buffers.get_mut(&producer_name) {
Some(state) => state,
None => {
let state = Receiver {
buffer: ReceiverBuffer::default(),
timer_observer: Arc::new(RtxTimerObserver {
producer_name: producer_name.clone(),
channel: rtx_timer_tx.clone(),
}),
rtx_map: HashMap::new(),
timers_map: HashMap::new(),
};
receiver_state.buffers.insert(producer_name.clone(), state);
receiver_state
.buffers
.get_mut(&producer_name)
.expect("State should be present")
}
};
let mut recv = Vec::new();
let mut rtx = Vec::new();
let header_type = msg.get_header_type();
let msg_id = msg.get_id();
match header_type {
SessionHeaderType::Stream => {
(recv, rtx) = receiver.buffer.on_received_message(msg);
}
SessionHeaderType::PubSub => {
(recv, rtx) = receiver.buffer.on_received_message(msg);
}
SessionHeaderType::RtxReply => {
if msg.get_error().is_some() && msg.get_error().unwrap() {
recv = receiver.buffer.on_lost_message(msg_id);
} else {
(recv, rtx) = receiver.buffer.on_received_message(msg);
}
match receiver.timers_map.get_mut(&msg_id) {
Some(timer) => {
timer.stop();
receiver.timers_map.remove(&msg_id);
receiver.rtx_map.remove(&msg_id);
}
None => {
warn!("unable to find the timer associated to the received RTX reply");
receiver.rtx_map.remove(&msg_id);
}
}
}
SessionHeaderType::BeaconStream => {
debug!("received stream beacon for message {}", msg_id);
rtx = receiver.buffer.on_beacon_message(msg_id);
}
SessionHeaderType::BeaconPubSub => {
debug!("received pubsub beacon for message {}", msg_id);
rtx = receiver.buffer.on_beacon_message(msg_id);
}
_ => {
error!(
"received packet with invalid header type {} on session {}",
i32::from(header_type),
session_id
);
return;
}
}
if !recv.is_empty() {
send_message_to_app(recv, session_id, send_app).await;
}
for r in rtx {
debug!(
"packet loss detected on session {}, send RTX for id {}",
session_id, r
);
let agp_header = Some(AgpHeader::new(
source,
producer_name.agent_type(),
Some(producer_name.agent_id()),
Some(AgpHeaderFlags::default().with_forward_to(producer_conn)),
));
let session_header = Some(SessionHeader::new(
SessionHeaderType::RtxRequest.into(),
session_id,
r,
));
let rtx = Message::new_publish_with_headers(agp_header, session_header, "", vec![]);
let timer = timer::Timer::new(
r,
timer::TimerType::Constant,
timeout,
None,
Some(max_retries),
);
timer.start(receiver.timer_observer.clone());
receiver.rtx_map.insert(r, rtx.clone());
receiver.timers_map.insert(r, timer);
if send_gw.send(Ok(rtx)).await.is_err() {
error!("error sending RTX for id {} on session {}", r, session_id);
}
}
}
async fn handle_rtx_timeout(
receiver_state: &mut ReceiverState,
producer_name: &Agent,
msg_id: u32,
session_id: u32,
send_gw: &mpsc::Sender<Result<Message, Status>>,
) {
trace!(
"try to send rtx for packet {} on receiver session {}",
msg_id, session_id
);
let receiver = match receiver_state.buffers.get_mut(producer_name) {
Some(r) => r,
None => {
error!("received a timeout, but there is no state");
return;
}
};
let rtx = match receiver.rtx_map.get(&msg_id) {
Some(rtx) => rtx,
None => {
error!(
"rtx message does not exist in the map, skip retransmission and try to stop the timer"
);
let timer = match receiver.timers_map.get_mut(&msg_id) {
Some(t) => t,
None => {
error!("timer not found");
return;
}
};
timer.stop();
return;
}
};
if send_gw.send(Ok(rtx.clone())).await.is_err() {
error!(
"error sending RTX for id {} on session {}",
msg_id, session_id
);
}
}
async fn handle_rtx_failure(
receiver_state: &mut ReceiverState,
producer_name: &Agent,
msg_id: u32,
session_id: u32,
send_app: &mpsc::Sender<Result<SessionMessage, SessionError>>,
) {
trace!("packet {} lost, not retries left", msg_id);
let receiver = match receiver_state.buffers.get_mut(producer_name) {
Some(r) => r,
None => {
error!("received a timeout, but there is no state");
return;
}
};
receiver.rtx_map.remove(&msg_id);
receiver.timers_map.remove(&msg_id);
send_message_to_app(
receiver.buffer.on_lost_message(msg_id),
session_id,
send_app,
)
.await;
}
async fn send_beacon_msg(
source: &Agent,
topic: &AgentType,
beacon_type: SessionHeaderType,
last_msg_id: u32,
session_id: u32,
send_gw: &mpsc::Sender<Result<Message, Status>>,
) {
let agp_header = Some(AgpHeader::new(
source,
topic,
None,
Some(AgpHeaderFlags::default().with_fanout(STREAM_BROADCAST)),
));
let session_header = Some(SessionHeader::new(
beacon_type.into(),
session_id,
last_msg_id,
));
let msg = Message::new_publish_with_headers(agp_header, session_header, "", vec![]);
trace!("beacon to send {:?}", msg);
if send_gw.send(Ok(msg)).await.is_err() {
error!(
"error sending beacon msg to the gateway on session {}",
session_id
);
}
}
async fn send_message_to_app(
messages: Vec<Option<Message>>,
session_id: u32,
send_app: &mpsc::Sender<Result<SessionMessage, SessionError>>,
) {
for opt in messages {
match opt {
Some(m) => {
let info = Info::from(&m);
let session_msg = SessionMessage::new(m, info);
if send_app.send(Ok(session_msg)).await.is_err() {
error!(
"error sending packet to the gateway on session {}",
session_id
);
}
}
None => {
warn!("a message was definitely lost in session {}", session_id);
let _ = send_app
.send(Err(SessionError::MessageLost(session_id.to_string())))
.await;
}
}
}
}
#[async_trait]
impl Session for Streaming {
async fn on_message(
&self,
message: SessionMessage,
direction: MessageDirection,
) -> Result<(), SessionError> {
self.tx
.send(Ok((message.message, direction)))
.await
.map_err(|e| SessionError::Processing(e.to_string()))
}
}
delegate_common_behavior!(Streaming, common);
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::*;
use tokio::time;
use tracing_test::traced_test;
use agp_datapath::messages::AgentType;
#[tokio::test]
#[traced_test]
async fn test_stream_create() {
let (tx_gw, _) = tokio::sync::mpsc::channel(1);
let (tx_app, _) = tokio::sync::mpsc::channel(1);
let source = Agent::from_strings("cisco", "default", "local_agent", 0);
let session_config: StreamingConfiguration =
StreamingConfiguration::new(SessionDirection::Sender, None, None, None);
let session = Streaming::new(
0,
session_config.clone(),
SessionDirection::Sender,
source.clone(),
tx_gw.clone(),
tx_app.clone(),
);
assert_eq!(session.id(), 0);
assert_eq!(session.state(), &State::Active);
assert_eq!(
session.session_config(),
SessionConfig::Streaming(session_config.clone())
);
let session_config: StreamingConfiguration = StreamingConfiguration::new(
SessionDirection::Receiver,
None,
Some(10),
Some(Duration::from_millis(1000)),
);
let session = Streaming::new(
1,
session_config.clone(),
SessionDirection::Receiver,
source.clone(),
tx_gw,
tx_app,
);
assert_eq!(session.id(), 1);
assert_eq!(session.state(), &State::Active);
assert_eq!(
session.session_config(),
SessionConfig::Streaming(session_config)
);
}
#[tokio::test]
#[traced_test]
async fn test_stream_sender_and_receiver() {
let (tx_gw_sender, mut rx_gw_sender) = tokio::sync::mpsc::channel(1);
let (tx_app_sender, _rx_app_sender) = tokio::sync::mpsc::channel(1);
let (tx_gw_receiver, _rx_gw_receiver) = tokio::sync::mpsc::channel(1);
let (tx_app_receiver, mut rx_app_receiver) = tokio::sync::mpsc::channel(1);
let session_config_sender: StreamingConfiguration =
StreamingConfiguration::new(SessionDirection::Sender, None, None, None);
let session_config_receiver: StreamingConfiguration = StreamingConfiguration::new(
SessionDirection::Receiver,
None,
Some(5),
Some(Duration::from_millis(500)),
);
let sender = Streaming::new(
0,
session_config_sender,
SessionDirection::Sender,
Agent::from_strings("cisco", "default", "sender", 0),
tx_gw_sender,
tx_app_sender,
);
let receiver = Streaming::new(
0,
session_config_receiver,
SessionDirection::Receiver,
Agent::from_strings("cisco", "default", "receiver", 0),
tx_gw_receiver,
tx_app_receiver,
);
let mut message = Message::new_publish(
&Agent::from_strings("cisco", "default", "sender", 0),
&AgentType::from_strings("cisco", "default", "receiver"),
Some(0),
Some(AgpHeaderFlags::default().with_incoming_conn(123)), "msg",
vec![0x1, 0x2, 0x3, 0x4],
);
let header = message.get_session_header_mut();
header.session_id = 0;
let mut expected_msg = message.clone();
expected_msg.set_header_type(SessionHeaderType::Stream);
expected_msg.set_fanout(STREAM_BROADCAST);
let session_msg = SessionMessage::new(message.clone(), Info::new(0));
let res = sender
.on_message(session_msg.clone(), MessageDirection::South)
.await;
assert!(res.is_ok());
let msg = rx_gw_sender.recv().await.unwrap().unwrap();
assert_eq!(msg, expected_msg);
let session_msg = SessionMessage::new(msg, Info::new(0));
let res = receiver
.on_message(session_msg.clone(), MessageDirection::North)
.await;
assert!(res.is_ok());
let msg = rx_app_receiver.recv().await.unwrap().unwrap();
assert_eq!(msg.message, expected_msg);
assert_eq!(msg.info.id, 0);
}
#[tokio::test]
#[traced_test]
async fn test_stream_rtx_timeouts() {
let (tx_gw, mut rx_gw) = tokio::sync::mpsc::channel(1);
let (tx_app, mut rx_app) = tokio::sync::mpsc::channel(1);
let session_config: StreamingConfiguration = StreamingConfiguration::new(
SessionDirection::Receiver,
None,
Some(5),
Some(Duration::from_millis(500)),
);
let session = Streaming::new(
0,
session_config,
SessionDirection::Receiver,
Agent::from_strings("cisco", "default", "sender", 0),
tx_gw,
tx_app,
);
let mut message = Message::new_publish(
&Agent::from_strings("cisco", "default", "sender", 0),
&AgentType::from_strings("cisco", "default", "receiver"),
Some(0),
Some(AgpHeaderFlags::default().with_incoming_conn(123)), "msg",
vec![0x1, 0x2, 0x3, 0x4],
);
let header = message.get_session_header_mut();
header.header_type = SessionHeaderType::Stream.into();
let session_msg: SessionMessage = SessionMessage::new(message.clone(), Info::new(0));
let res = session
.on_message(session_msg.clone(), MessageDirection::North)
.await;
assert!(res.is_ok());
let msg = rx_app.recv().await.unwrap().unwrap();
assert_eq!(msg.message, session_msg.message);
assert_eq!(msg.info.id, 0);
let header = message.get_session_header_mut();
header.message_id = 2;
let session_msg = SessionMessage::new(message.clone(), Info::new(0));
let res = session
.on_message(session_msg.clone(), MessageDirection::North)
.await;
assert!(res.is_ok());
for _ in 0..6 {
let rtx_msg = rx_gw.recv().await.unwrap().unwrap();
let rtx_header = rtx_msg.get_session_header();
assert_eq!(rtx_header.session_id, 0);
assert_eq!(rtx_header.message_id, 1);
assert_eq!(
rtx_header.header_type,
i32::from(SessionHeaderType::RtxRequest)
);
}
time::sleep(Duration::from_millis(1000)).await;
let expected_msg = "packet 1 lost, not retries left";
assert!(logs_contain(expected_msg));
let expected_msg = "a message was definitely lost in session 0";
assert!(logs_contain(expected_msg));
}
#[tokio::test]
#[traced_test]
async fn test_stream_rtx_reception() {
let (tx_gw, mut rx_gw) = tokio::sync::mpsc::channel(8);
let (tx_app, _rx_app) = tokio::sync::mpsc::channel(8);
let session_config: StreamingConfiguration = StreamingConfiguration::new(
SessionDirection::Receiver,
None,
Some(5),
Some(Duration::from_millis(500)),
);
let session = Streaming::new(
120,
session_config,
SessionDirection::Sender,
Agent::from_strings("cisco", "default", "receiver", 0),
tx_gw,
tx_app,
);
let mut message = Message::new_publish(
&Agent::from_strings("cisco", "default", "sender", 0),
&AgentType::from_strings("cisco", "default", "receiver"),
Some(0),
None,
"",
vec![0x1, 0x2, 0x3, 0x4],
);
let header = message.get_session_header_mut();
header.session_id = 120;
let session_msg: SessionMessage = SessionMessage::new(message.clone(), Info::new(120));
for _ in 0..3 {
let res = session
.on_message(session_msg.clone(), MessageDirection::South)
.await;
assert!(res.is_ok());
}
for i in 0..3 {
let msg = rx_gw.recv().await.unwrap().unwrap();
let msg_header = msg.get_session_header();
assert_eq!(msg_header.session_id, 120);
assert_eq!(msg_header.message_id, i);
assert_eq!(msg_header.header_type, i32::from(SessionHeaderType::Stream));
}
let agp_header = Some(AgpHeader::new(
&Agent::from_strings("cisco", "default", "sender", 0),
&AgentType::from_strings("cisco", "default", "receiver"),
Some(0),
Some(
AgpHeaderFlags::default()
.with_forward_to(0)
.with_incoming_conn(123),
), ));
let session_header = Some(SessionHeader::new(
SessionHeaderType::RtxRequest.into(),
1,
2,
));
let rtx = Message::new_publish_with_headers(agp_header, session_header, "", vec![]);
let session_msg: SessionMessage = SessionMessage::new(rtx.clone(), Info::new(120));
let res = session
.on_message(session_msg.clone(), MessageDirection::North)
.await;
assert!(res.is_ok());
let msg = rx_gw.recv().await.unwrap().unwrap();
let msg_header = msg.get_session_header();
assert_eq!(msg_header.session_id, 120);
assert_eq!(msg_header.message_id, 2);
assert_eq!(
msg_header.header_type,
i32::from(SessionHeaderType::RtxReply)
);
assert_eq!(msg.get_payload().unwrap().blob, vec![0x1, 0x2, 0x3, 0x4]);
}
#[tokio::test]
#[traced_test]
async fn test_stream_e2e_with_losses() {
let (tx_gw_sender, mut rx_gw_sender) = tokio::sync::mpsc::channel(1);
let (tx_app_sender, _rx_app_sender) = tokio::sync::mpsc::channel(1);
let (tx_gw_receiver, mut rx_gw_receiver) = tokio::sync::mpsc::channel(1);
let (tx_app_receiver, mut rx_app_receiver) = tokio::sync::mpsc::channel(1);
let session_config_sender: StreamingConfiguration =
StreamingConfiguration::new(SessionDirection::Sender, None, None, None);
let session_config_receiver: StreamingConfiguration = StreamingConfiguration::new(
SessionDirection::Receiver,
None,
Some(5),
Some(Duration::from_millis(100)), );
let sender = Streaming::new(
0,
session_config_sender,
SessionDirection::Sender,
Agent::from_strings("cisco", "default", "sender", 0),
tx_gw_sender,
tx_app_sender,
);
let receiver = Streaming::new(
0,
session_config_receiver,
SessionDirection::Receiver,
Agent::from_strings("cisco", "default", "receiver", 0),
tx_gw_receiver,
tx_app_receiver,
);
let mut message = Message::new_publish(
&Agent::from_strings("cisco", "default", "sender", 0),
&AgentType::from_strings("cisco", "default", "receiver"),
Some(0),
Some(AgpHeaderFlags::default().with_incoming_conn(0)),
"msg",
vec![0x1, 0x2, 0x3, 0x4],
);
message.set_incoming_conn(Some(0));
let session_msg: SessionMessage = SessionMessage::new(message.clone(), Info::new(0));
for _ in 0..3 {
let res = sender
.on_message(session_msg.clone(), MessageDirection::South)
.await;
assert!(res.is_ok());
}
for i in 0..3 {
let mut msg = rx_gw_sender.recv().await.unwrap().unwrap();
let msg_header = msg.get_session_header();
assert_eq!(msg_header.session_id, 0);
assert_eq!(msg_header.message_id, i);
assert_eq!(msg_header.header_type, i32::from(SessionHeaderType::Stream));
if i != 1 {
msg.set_incoming_conn(Some(0));
let session_msg: SessionMessage = SessionMessage::new(msg.clone(), Info::new(0));
let res = receiver
.on_message(session_msg.clone(), MessageDirection::North)
.await;
assert!(res.is_ok());
}
}
let msg = rx_app_receiver.recv().await.unwrap().unwrap();
let msg_header = msg.message.get_session_header();
assert_eq!(msg_header.session_id, 0);
assert_eq!(msg_header.message_id, 0);
assert_eq!(msg_header.header_type, i32::from(SessionHeaderType::Stream));
assert_eq!(
msg.message.get_source(),
Agent::from_strings("cisco", "default", "sender", 0)
);
assert_eq!(
msg.message.get_name(),
(
AgentType::from_strings("cisco", "default", "receiver"),
Some(0)
)
);
let msg = rx_gw_receiver.recv().await.unwrap().unwrap();
let msg_header = msg.get_session_header();
assert_eq!(msg_header.session_id, 0);
assert_eq!(msg_header.message_id, 1);
assert_eq!(
msg_header.header_type,
i32::from(SessionHeaderType::RtxRequest)
);
assert_eq!(
msg.get_source(),
Agent::from_strings("cisco", "default", "receiver", 0)
);
assert_eq!(
msg.get_name(),
(
AgentType::from_strings("cisco", "default", "sender"),
Some(0)
)
);
let msg = rx_gw_receiver.recv().await.unwrap().unwrap();
let msg_header = msg.get_session_header();
assert_eq!(msg_header.session_id, 0);
assert_eq!(msg_header.message_id, 1);
assert_eq!(
msg_header.header_type,
i32::from(SessionHeaderType::RtxRequest)
);
assert_eq!(
msg.get_source(),
Agent::from_strings("cisco", "default", "receiver", 0)
);
assert_eq!(
msg.get_name(),
(
AgentType::from_strings("cisco", "default", "sender"),
Some(0)
)
);
let mut session_msg: SessionMessage = SessionMessage::new(msg.clone(), Info::new(0));
session_msg.message.set_incoming_conn(Some(0));
let res = sender
.on_message(session_msg.clone(), MessageDirection::North)
.await;
assert!(res.is_ok());
let msg = rx_gw_sender.recv().await.unwrap().unwrap();
let msg_header = msg.get_session_header();
assert_eq!(msg_header.session_id, 0);
assert_eq!(msg_header.message_id, 1);
assert_eq!(
msg_header.header_type,
i32::from(SessionHeaderType::RtxReply)
);
assert_eq!(
msg.get_source(),
Agent::from_strings("cisco", "default", "sender", 0)
);
assert_eq!(
msg.get_name(),
(
AgentType::from_strings("cisco", "default", "receiver"),
Some(0)
)
);
let mut session_msg: SessionMessage = SessionMessage::new(msg.clone(), Info::new(0));
session_msg.message.set_incoming_conn(Some(0));
let res = receiver
.on_message(session_msg.clone(), MessageDirection::North)
.await;
assert!(res.is_ok());
let msg = rx_app_receiver.recv().await.unwrap().unwrap();
let msg_header = msg.message.get_session_header();
assert_eq!(msg_header.session_id, 0);
assert_eq!(msg_header.message_id, 1);
assert_eq!(
msg_header.header_type,
i32::from(SessionHeaderType::RtxReply)
);
assert_eq!(
msg.message.get_source(),
Agent::from_strings("cisco", "default", "sender", 0)
);
assert_eq!(
msg.message.get_name(),
(
AgentType::from_strings("cisco", "default", "receiver"),
Some(0)
)
);
let msg = rx_app_receiver.recv().await.unwrap().unwrap();
let msg_header = msg.message.get_session_header();
assert_eq!(msg_header.session_id, 0);
assert_eq!(msg_header.message_id, 2);
assert_eq!(msg_header.header_type, i32::from(SessionHeaderType::Stream));
assert_eq!(
msg.message.get_source(),
Agent::from_strings("cisco", "default", "sender", 0)
);
assert_eq!(
msg.message.get_name(),
(
AgentType::from_strings("cisco", "default", "receiver"),
Some(0)
)
);
}
#[tokio::test]
#[traced_test]
async fn test_session_delete() {
let (tx_gw, _) = tokio::sync::mpsc::channel(1);
let (tx_app, _) = tokio::sync::mpsc::channel(1);
let source = Agent::from_strings("cisco", "default", "local_agent", 0);
let session_config: StreamingConfiguration =
StreamingConfiguration::new(SessionDirection::Sender, None, None, None);
{
let _session = Streaming::new(
0,
session_config.clone(),
SessionDirection::Sender,
source.clone(),
tx_gw.clone(),
tx_app.clone(),
);
}
time::sleep(Duration::from_millis(100)).await;
assert!(logs_contain(
"stopping message processing on streaming session 0"
));
}
}