use std::collections::HashMap;
use parking_lot::RwLock as SyncRwLock;
use rand::Rng;
use tokio::sync::RwLock as AsyncRwLock;
use tracing::warn;
use crate::errors::SessionError;
use crate::fire_and_forget::FireAndForgetConfiguration;
use crate::request_response::{RequestResponse, RequestResponseConfiguration};
use crate::session::{
AppChannelSender, GwChannelSender, Id, Info, MessageDirection, SESSION_RANGE, Session,
SessionConfig, SessionConfigTrait, SessionDirection, SessionMessage, SessionType,
};
use crate::streaming::{self, StreamingConfiguration};
use crate::{fire_and_forget, session};
use agp_datapath::messages::encoder::Agent;
use agp_datapath::pubsub::proto::pubsub::v1::SessionHeaderType;
pub(crate) struct SessionLayer {
pool: AsyncRwLock<HashMap<Id, Box<dyn Session + Send + Sync>>>,
agent_name: Agent,
conn_id: u64,
tx_gw: GwChannelSender,
tx_app: AppChannelSender,
default_ff_conf: SyncRwLock<FireAndForgetConfiguration>,
default_rr_conf: SyncRwLock<RequestResponseConfiguration>,
default_stream_conf: SyncRwLock<StreamingConfiguration>,
}
impl std::fmt::Debug for SessionLayer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SessionPool")
}
}
impl SessionLayer {
pub(crate) fn new(
agent_name: &Agent,
conn_id: u64,
tx_gw: GwChannelSender,
tx_app: AppChannelSender,
) -> SessionLayer {
SessionLayer {
pool: AsyncRwLock::new(HashMap::new()),
agent_name: agent_name.clone(),
conn_id,
tx_gw,
tx_app,
default_ff_conf: SyncRwLock::new(FireAndForgetConfiguration::default()),
default_rr_conf: SyncRwLock::new(RequestResponseConfiguration::default()),
default_stream_conf: SyncRwLock::new(StreamingConfiguration::default()),
}
}
pub(crate) fn tx_gw(&self) -> GwChannelSender {
self.tx_gw.clone()
}
pub(crate) fn tx_app(&self) -> AppChannelSender {
self.tx_app.clone()
}
pub(crate) fn conn_id(&self) -> u64 {
self.conn_id
}
pub(crate) fn agent_name(&self) -> &Agent {
&self.agent_name
}
pub(crate) async fn create_session(
&self,
session_config: SessionConfig,
id: Option<Id>,
) -> Result<Info, SessionError> {
let mut pool = self.pool.write().await;
let mut id = match id {
Some(id) => {
if !SESSION_RANGE.contains(&id) {
return Err(SessionError::InvalidSessionId(id.to_string()));
}
if pool.contains_key(&id) {
return Err(SessionError::SessionIdAlreadyUsed(id.to_string()));
}
id
}
None => {
loop {
let id = rand::rng().random_range(SESSION_RANGE);
if !pool.contains_key(&id) {
break id;
}
}
}
};
let session: Box<(dyn Session + Send + Sync + 'static)> = match session_config {
SessionConfig::FireAndForget(conf) => Box::new(fire_and_forget::FireAndForget::new(
id,
conf,
SessionDirection::Bidirectional,
self.agent_name().clone(),
self.tx_gw.clone(),
self.tx_app.clone(),
)),
SessionConfig::RequestResponse(conf) => Box::new(RequestResponse::new(
id,
conf,
SessionDirection::Bidirectional,
self.agent_name().clone(),
self.tx_gw.clone(),
self.tx_app.clone(),
)),
SessionConfig::Streaming(conf) => {
let direction = conf.direction.clone();
if direction == SessionDirection::Bidirectional {
id = (agp_datapath::messages::encoder::calculate_hash(&conf.topic)
% (u32::MAX as u64)) as u32;
}
Box::new(streaming::Streaming::new(
id,
conf,
direction,
self.agent_name().clone(),
self.tx_gw.clone(),
self.tx_app.clone(),
))
}
};
let ret = pool.insert(id, session);
if ret.is_some() {
panic!("session already exists: {}", ret.is_some());
}
Ok(Info::new(id))
}
pub(crate) async fn remove_session(&self, id: Id) -> bool {
let mut pool = self.pool.write().await;
pool.remove(&id).is_some()
}
pub(crate) async fn handle_message(
&self,
message: SessionMessage,
direction: MessageDirection,
) -> Result<(), SessionError> {
if let Err(e) = message.message.validate() {
return Err(SessionError::ValidationError(e.to_string()));
}
if !message.message.is_publish() {
return Err(SessionError::ValidationError(
"message is not a publish".to_string(),
));
}
match direction {
MessageDirection::North => self.handle_message_from_gateway(message, direction).await,
MessageDirection::South => self.handle_message_from_app(message, direction).await,
}
}
async fn handle_message_from_app(
&self,
mut message: SessionMessage,
direction: MessageDirection,
) -> Result<(), SessionError> {
if let Some(session) = self.pool.read().await.get(&message.info.id) {
let header = message.message.get_session_header_mut();
header.session_id = message.info.id;
return session.on_message(message, direction).await;
}
Err(SessionError::SessionNotFound(message.info.id.to_string()))
}
async fn handle_message_from_gateway(
&self,
message: SessionMessage,
direction: MessageDirection,
) -> Result<(), SessionError> {
let (id, session_type) = {
let header = message.message.get_session_header();
let session_type = match SessionHeaderType::try_from(header.header_type) {
Ok(session_type) => session_type,
Err(e) => {
return Err(SessionError::ValidationError(format!(
"session type is not valid: {}",
e
)));
}
};
let id = header.session_id;
(id, session_type)
};
if let Some(session) = self.pool.read().await.get(&id) {
let ret = session.on_message(message, direction).await;
return ret;
}
let new_session_id = match session_type {
SessionHeaderType::Fnf => {
let conf = self.default_ff_conf.read().clone();
self.create_session(SessionConfig::FireAndForget(conf), Some(id))
.await?
}
SessionHeaderType::Request => {
let conf = self.default_rr_conf.read().clone();
self.create_session(SessionConfig::RequestResponse(conf), Some(id))
.await?
}
SessionHeaderType::Stream => {
let conf = self.default_stream_conf.read().clone();
self.create_session(session::SessionConfig::Streaming(conf), Some(id))
.await?
}
SessionHeaderType::PubSub => {
warn!("received pub/sub message with unknown session id");
return Err(SessionError::SessionUnknown(
session_type.as_str_name().to_string(),
));
}
SessionHeaderType::BeaconStream => {
let conf = self.default_stream_conf.read().clone();
self.create_session(session::SessionConfig::Streaming(conf), Some(id))
.await?
}
SessionHeaderType::BeaconPubSub => {
warn!("received beacon pub/sub message with unknown session id");
return Err(SessionError::SessionUnknown(
session_type.as_str_name().to_string(),
));
}
_ => {
return Err(SessionError::SessionUnknown(
session_type.as_str_name().to_string(),
));
}
};
debug_assert!(new_session_id.id == id);
if let Some(session) = self.pool.read().await.get(&new_session_id.id) {
return session.on_message(message, direction).await;
}
panic!("session not found: {}", "test");
}
pub(crate) async fn set_session_config(
&self,
session_config: &SessionConfig,
session_id: Option<Id>,
) -> Result<(), SessionError> {
let session_id = match session_id {
Some(id) => id,
None => {
match &session_config {
SessionConfig::FireAndForget(_) => {
return self.default_ff_conf.write().replace(session_config);
}
SessionConfig::RequestResponse(_) => {
return self.default_rr_conf.write().replace(session_config);
}
SessionConfig::Streaming(_) => {
return self.default_stream_conf.write().replace(session_config);
}
}
}
};
let mut pool = self.pool.write().await;
if let Some(session) = pool.get_mut(&session_id) {
return session.set_session_config(session_config);
}
Err(SessionError::SessionNotFound(session_id.to_string()))
}
pub(crate) async fn get_session_config(
&self,
session_id: Id,
) -> Result<SessionConfig, SessionError> {
let pool = self.pool.read().await;
if let Some(session) = pool.get(&session_id) {
return Ok(session.session_config());
}
Err(SessionError::SessionNotFound(session_id.to_string()))
}
pub(crate) async fn get_default_session_config(
&self,
session_type: SessionType,
) -> Result<SessionConfig, SessionError> {
match session_type {
SessionType::FireAndForget => Ok(SessionConfig::FireAndForget(
self.default_ff_conf.read().clone(),
)),
SessionType::RequestResponse => Ok(SessionConfig::RequestResponse(
self.default_rr_conf.read().clone(),
)),
SessionType::Streaming => Ok(SessionConfig::Streaming(
self.default_stream_conf.read().clone(),
)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fire_and_forget::FireAndForgetConfiguration;
use agp_datapath::{
messages::{Agent, AgentType},
pubsub::ProtoMessage,
};
fn create_session_layer() -> SessionLayer {
let (tx_gw, _) = tokio::sync::mpsc::channel(128);
let (tx_app, _) = tokio::sync::mpsc::channel(128);
let agent = Agent::from_strings("org", "ns", "type", 0);
SessionLayer::new(&agent, 0, tx_gw, tx_app)
}
#[tokio::test]
async fn test_create_session_layer() {
let session_layer = create_session_layer();
assert!(session_layer.pool.read().await.is_empty());
}
#[tokio::test]
async fn test_remove_session() {
let (tx_gw, _) = tokio::sync::mpsc::channel(1);
let (tx_app, _) = tokio::sync::mpsc::channel(1);
let agent = Agent::from_strings("org", "ns", "type", 0);
let session_layer = SessionLayer::new(&agent, 0, tx_gw.clone(), tx_app.clone());
let session_config = FireAndForgetConfiguration {};
let ret = session_layer
.create_session(SessionConfig::FireAndForget(session_config), Some(1))
.await;
assert!(ret.is_ok());
let res = session_layer.remove_session(1).await;
assert!(res);
}
#[tokio::test]
async fn test_create_session() {
let (tx_gw, _) = tokio::sync::mpsc::channel(1);
let (tx_app, _) = tokio::sync::mpsc::channel(1);
let agent = Agent::from_strings("org", "ns", "type", 0);
let session_layer = SessionLayer::new(&agent, 0, tx_gw.clone(), tx_app.clone());
let res = session_layer
.create_session(
SessionConfig::FireAndForget(FireAndForgetConfiguration {}),
None,
)
.await;
assert!(res.is_ok());
}
#[tokio::test]
async fn test_delete_session() {
let (tx_gw, _) = tokio::sync::mpsc::channel(1);
let (tx_app, _) = tokio::sync::mpsc::channel(1);
let agent = Agent::from_strings("org", "ns", "type", 0);
let session_layer = SessionLayer::new(&agent, 0, tx_gw.clone(), tx_app.clone());
let res = session_layer
.create_session(
SessionConfig::FireAndForget(FireAndForgetConfiguration {}),
Some(1),
)
.await;
assert!(res.is_ok());
let res = session_layer.remove_session(1).await;
assert!(res);
let res = session_layer.remove_session(1).await;
assert!(!res);
}
#[tokio::test]
async fn test_handle_message() {
let (tx_gw, _) = tokio::sync::mpsc::channel(1);
let (tx_app, mut rx_app) = tokio::sync::mpsc::channel(1);
let agent = Agent::from_strings("org", "ns", "type", 0);
let session_layer = SessionLayer::new(&agent, 0, tx_gw.clone(), tx_app.clone());
let session_config = FireAndForgetConfiguration {};
let res = session_layer
.create_session(SessionConfig::FireAndForget(session_config), Some(1))
.await;
assert!(res.is_ok());
let mut message = ProtoMessage::new_publish(
&Agent::from_strings("cisco", "default", "local_agent", 0),
&AgentType::from_strings("cisco", "default", "remote_agent"),
Some(0),
None,
"msg",
vec![0x1, 0x2, 0x3, 0x4],
);
let header = message.get_session_header_mut();
header.session_id = 1;
header.header_type = i32::from(SessionHeaderType::Fnf);
let res = session_layer
.handle_message(
SessionMessage::from(message.clone()),
MessageDirection::North,
)
.await;
assert!(res.is_ok());
let msg = rx_app
.recv()
.await
.expect("no message received")
.expect("error");
assert_eq!(msg.message, message);
assert_eq!(msg.info.id, 1);
}
}