use std::marker::PhantomData;
use slim_auth::traits::{TokenProvider, Verifier};
use slim_datapath::api::{NameId, ProtoName};
use crate::{
Direction, SlimChannelSender,
common::{AppChannelSender, SessionMessage},
errors::SessionError,
session_config::SessionConfig,
session_controller::SessionController,
session_moderator::SessionModerator,
session_participant::SessionParticipant,
session_settings::SessionSettings,
subscription_manager::{SubscriptionManager, SubscriptionOps},
traits::MessageHandler,
};
pub struct NotReady;
pub struct Ready;
pub struct ForController;
pub struct ForParticipant;
pub struct ForModerator;
pub struct SessionBuilder<P, V, Target, State = NotReady, M = SubscriptionManager>
where
P: TokenProvider + Send + Sync + Clone + 'static,
V: Verifier + Send + Sync + Clone + 'static,
M: SubscriptionOps,
{
id: Option<u32>,
source: Option<ProtoName>,
destination: Option<ProtoName>,
control: Option<ProtoName>,
config: Option<SessionConfig>,
identity_provider: Option<P>,
identity_verifier: Option<V>,
slim_tx: Option<SlimChannelSender>,
app_tx: Option<AppChannelSender>,
tx_to_session_layer: Option<tokio::sync::mpsc::Sender<Result<SessionMessage, SessionError>>>,
graceful_shutdown_timeout: Option<std::time::Duration>,
direction: Direction,
subscription_manager: Option<M>,
service_id: Option<String>,
_target: PhantomData<Target>,
_state: PhantomData<State>,
}
impl<P, V, Target, M> SessionBuilder<P, V, Target, NotReady, M>
where
P: TokenProvider + Send + Sync + Clone + 'static,
V: Verifier + Send + Sync + Clone + 'static,
M: SubscriptionOps,
{
fn new() -> Self {
Self {
id: None,
source: None,
destination: None,
control: None,
config: None,
identity_provider: None,
identity_verifier: None,
slim_tx: None,
app_tx: None,
tx_to_session_layer: None,
graceful_shutdown_timeout: None,
direction: Direction::Bidirectional,
subscription_manager: None,
service_id: None,
_target: PhantomData,
_state: PhantomData,
}
}
pub fn with_id(mut self, id: u32) -> Self {
self.id = Some(id);
self
}
pub fn with_source(mut self, source: ProtoName) -> Self {
self.source = Some(source);
self
}
pub fn with_destination(mut self, destination: ProtoName) -> Self {
self.destination = Some(destination);
self
}
pub fn with_config(mut self, config: SessionConfig) -> Self {
self.config = Some(config);
self
}
pub fn with_identity_provider(mut self, identity_provider: P) -> Self {
self.identity_provider = Some(identity_provider);
self
}
pub fn with_identity_verifier(mut self, identity_verifier: V) -> Self {
self.identity_verifier = Some(identity_verifier);
self
}
pub fn with_slim_tx(mut self, slim_tx: SlimChannelSender) -> Self {
self.slim_tx = Some(slim_tx);
self
}
pub fn with_app_tx(mut self, app_tx: AppChannelSender) -> Self {
self.app_tx = Some(app_tx);
self
}
#[cfg(test)]
fn with_test_channels(mut self) -> Self {
let (slim_tx, _) = tokio::sync::mpsc::channel(10);
let (app_tx, _) = tokio::sync::mpsc::unbounded_channel();
self.slim_tx = Some(slim_tx);
self.app_tx = Some(app_tx);
self
}
pub fn with_tx_to_session_layer(
mut self,
tx_to_session_layer: tokio::sync::mpsc::Sender<Result<SessionMessage, SessionError>>,
) -> Self {
self.tx_to_session_layer = Some(tx_to_session_layer);
self
}
pub fn with_graceful_shutdown_timeout(mut self, timeout: std::time::Duration) -> Self {
self.graceful_shutdown_timeout = Some(timeout);
self
}
pub fn with_direction(mut self, direction: Direction) -> Self {
self.direction = direction;
self
}
pub fn with_subscription_manager<N: SubscriptionOps>(
self,
manager: N,
) -> SessionBuilder<P, V, Target, NotReady, N> {
SessionBuilder {
id: self.id,
source: self.source,
destination: self.destination,
control: self.control,
config: self.config,
identity_provider: self.identity_provider,
identity_verifier: self.identity_verifier,
slim_tx: self.slim_tx,
app_tx: self.app_tx,
tx_to_session_layer: self.tx_to_session_layer,
graceful_shutdown_timeout: self.graceful_shutdown_timeout,
direction: self.direction,
subscription_manager: Some(manager),
service_id: self.service_id,
_target: PhantomData,
_state: PhantomData,
}
}
pub fn with_service_id(mut self, service_id: String) -> Self {
self.service_id = Some(service_id);
self
}
pub fn ready(self) -> Result<SessionBuilder<P, V, Target, Ready, M>, SessionError> {
if self.id.is_none()
|| self.source.is_none()
|| self.destination.is_none()
|| self.config.is_none()
|| self.identity_provider.is_none()
|| self.identity_verifier.is_none()
|| self.slim_tx.is_none()
|| self.app_tx.is_none()
|| self.tx_to_session_layer.is_none()
{
return Err(SessionError::SessionBuilderIncomplete);
}
let config = self.config.as_ref().unwrap();
let destination = self.destination.as_ref().unwrap();
let (final_destination, control) = match config.session_type {
slim_datapath::api::ProtoSessionType::PointToPoint => {
(destination.clone(), destination.clone())
}
slim_datapath::api::ProtoSessionType::Multicast => {
let data_destination = destination.clone().with_id(NameId::DATA_CHANNEL_ID);
let control_destination = destination.clone().with_id(NameId::CONTROL_CHANNEL_ID);
(data_destination, control_destination)
}
_ => {
return Err(SessionError::SessionBuilderIncomplete);
}
};
Ok(SessionBuilder {
id: self.id,
source: self.source,
destination: Some(final_destination),
control: Some(control),
config: self.config,
identity_provider: self.identity_provider,
identity_verifier: self.identity_verifier,
slim_tx: self.slim_tx,
app_tx: self.app_tx,
tx_to_session_layer: self.tx_to_session_layer,
graceful_shutdown_timeout: self.graceful_shutdown_timeout,
direction: self.direction,
subscription_manager: self.subscription_manager,
service_id: self.service_id,
_target: PhantomData,
_state: PhantomData,
})
}
}
impl<P, V, M> SessionBuilder<P, V, ForController, NotReady, M>
where
P: TokenProvider + Send + Sync + Clone + 'static,
V: Verifier + Send + Sync + Clone + 'static,
M: SubscriptionOps,
{
pub fn for_controller() -> Self {
Self::new()
}
}
impl<P, V, M> SessionBuilder<P, V, ForParticipant, NotReady, M>
where
P: TokenProvider + Send + Sync + Clone + 'static,
V: Verifier + Send + Sync + Clone + 'static,
M: SubscriptionOps,
{
pub fn for_participant() -> Self {
Self::new()
}
}
impl<P, V, M> SessionBuilder<P, V, ForModerator, NotReady, M>
where
P: TokenProvider + Send + Sync + Clone + 'static,
V: Verifier + Send + Sync + Clone + 'static,
M: SubscriptionOps,
{
pub fn for_moderator() -> Self {
Self::new()
}
}
impl<P, V, M> SessionBuilder<P, V, ForController, Ready, M>
where
P: TokenProvider + Send + Sync + Clone + 'static,
V: Verifier + Send + Sync + Clone + 'static,
M: SubscriptionOps,
{
pub fn build(self) -> Result<SessionController, SessionError> {
let id = self.id.unwrap();
let source = self.source.clone().unwrap();
let destination = self.destination.clone().unwrap();
let config = self.config.clone().unwrap();
let role = if config.initiator {
"Moderator"
} else {
"Participant"
};
tracing::debug!(%role, "Building SessionController");
let session_controller = if config.initiator {
let (inner, tx, rx, settings) = self.build_session_stack(SessionModerator::new)?;
SessionController::from_parts(
id,
source,
destination,
config.clone(),
settings,
tx,
rx,
inner,
)
} else {
let (inner, tx, rx, settings) = self.build_session_stack(SessionParticipant::new)?;
SessionController::from_parts(id, source, destination, config, settings, tx, rx, inner)
};
Ok(session_controller)
}
fn build_session_stack<W>(
self,
wrapper_constructor: impl FnOnce(crate::session::Session, SessionSettings<P, V, M>) -> W,
) -> Result<
(
W,
tokio::sync::mpsc::Sender<SessionMessage>,
tokio::sync::mpsc::Receiver<SessionMessage>,
SessionSettings<P, V, M>,
),
SessionError,
>
where
W: MessageHandler,
{
let (tx_session, rx_session) = tokio::sync::mpsc::channel(256);
let inner = crate::session::Session::new(
self.id.unwrap(),
self.config.clone().unwrap(),
&self.source.clone().unwrap(),
tx_session.clone(),
self.direction,
);
let slim_tx = self.slim_tx.unwrap();
let app_tx = self.app_tx.unwrap();
let subscription_manager = self
.subscription_manager
.or_else(|| M::from_slim_tx(&slim_tx))
.expect("subscription_manager must be provided or M must implement from_slim_tx");
let settings = SessionSettings {
id: self.id.unwrap(),
source: self.source.unwrap(),
destination: self.destination.unwrap(),
control: self.control.unwrap(),
config: self.config.unwrap(),
direction: self.direction,
slim_tx,
app_tx,
tx_session: tx_session.clone(),
tx_to_session_layer: self.tx_to_session_layer.unwrap(),
identity_provider: self.identity_provider.unwrap(),
identity_verifier: self.identity_verifier.unwrap(),
graceful_shutdown_timeout: self.graceful_shutdown_timeout,
subscription_manager,
service_id: self.service_id.unwrap_or_default(),
};
let wrapper = wrapper_constructor(inner, settings.clone());
Ok((wrapper, tx_session, rx_session, settings))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
SessionError,
session_config::MlsSettings,
test_utils::{MockTokenProvider, MockVerifier},
};
use slim_datapath::api::ProtoSessionType;
use std::collections::HashMap;
use tokio::sync::mpsc;
fn create_test_config(initiator: bool) -> SessionConfig {
SessionConfig {
session_type: ProtoSessionType::PointToPoint,
max_retries: Some(3),
interval: Some(std::time::Duration::from_secs(1)),
mls_settings: None,
initiator,
metadata: HashMap::new(),
}
}
fn create_test_name(prefix: &str) -> ProtoName {
ProtoName::from_strings([prefix, "test", "name"]).with_id(1)
}
fn create_test_channels() -> (SlimChannelSender, AppChannelSender) {
let (slim_tx, _) = mpsc::channel(10);
let (app_tx, _) = mpsc::unbounded_channel();
(slim_tx, app_tx)
}
#[test]
fn test_builder_for_controller_creation() {
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller();
assert!(builder.id.is_none());
assert!(builder.source.is_none());
assert!(builder.destination.is_none());
}
#[test]
fn test_builder_for_participant_creation() {
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForParticipant, NotReady>::for_participant();
assert!(builder.id.is_none());
assert!(builder.source.is_none());
assert!(builder.destination.is_none());
}
#[test]
fn test_builder_for_moderator_creation() {
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForModerator, NotReady>::for_moderator();
assert!(builder.id.is_none());
assert!(builder.source.is_none());
assert!(builder.destination.is_none());
}
#[test]
fn test_builder_with_id() {
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(42);
assert_eq!(builder.id, Some(42));
}
#[test]
fn test_builder_with_source() {
let source = create_test_name("source");
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_source(source.clone());
assert_eq!(builder.source, Some(source));
}
#[test]
fn test_builder_with_destination() {
let destination = create_test_name("dest");
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_destination(destination.clone());
assert_eq!(builder.destination, Some(destination));
}
#[test]
fn test_builder_with_config() {
let config = create_test_config(true);
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_config(config.clone());
assert!(builder.config.is_some());
assert!(builder.config.unwrap().initiator);
}
#[test]
fn test_builder_with_identity_provider() {
let provider = MockTokenProvider;
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_identity_provider(provider);
assert!(builder.identity_provider.is_some());
}
#[test]
fn test_builder_with_identity_verifier() {
let verifier = MockVerifier;
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_identity_verifier(verifier);
assert!(builder.identity_verifier.is_some());
}
#[test]
fn test_builder_with_slim_tx_and_app_tx() {
let (slim_tx, app_tx) = create_test_channels();
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_slim_tx(slim_tx)
.with_app_tx(app_tx);
assert!(builder.slim_tx.is_some());
assert!(builder.app_tx.is_some());
}
#[test]
fn test_builder_with_tx_to_session_layer() {
let (tx, _) = mpsc::channel(10);
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_tx_to_session_layer(tx);
assert!(builder.tx_to_session_layer.is_some());
}
#[test]
fn test_builder_ready_with_all_fields() {
let (tx_to_session, _) = mpsc::channel(10);
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(1)
.with_source(create_test_name("source"))
.with_destination(create_test_name("dest"))
.with_config(create_test_config(true))
.with_identity_provider(MockTokenProvider)
.with_identity_verifier(MockVerifier)
.with_test_channels()
.with_tx_to_session_layer(tx_to_session);
let ready_result = builder.ready();
assert!(ready_result.is_ok());
}
#[test]
fn test_builder_ready_missing_id() {
let (tx_to_session, _) = mpsc::channel(10);
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_source(create_test_name("source"))
.with_destination(create_test_name("dest"))
.with_config(create_test_config(true))
.with_identity_provider(MockTokenProvider)
.with_identity_verifier(MockVerifier)
.with_test_channels()
.with_tx_to_session_layer(tx_to_session);
let ready_result = builder.ready();
assert!(ready_result.is_err_and(|e| matches!(e, SessionError::SessionBuilderIncomplete)));
}
#[test]
fn test_builder_ready_missing_source() {
let (tx_to_session, _) = mpsc::channel(10);
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(1)
.with_destination(create_test_name("dest"))
.with_config(create_test_config(true))
.with_identity_provider(MockTokenProvider)
.with_identity_verifier(MockVerifier)
.with_test_channels()
.with_tx_to_session_layer(tx_to_session);
let ready_result = builder.ready();
assert!(ready_result.is_err());
}
#[test]
fn test_builder_ready_missing_destination() {
let (tx_to_session, _) = mpsc::channel(10);
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(1)
.with_source(create_test_name("source"))
.with_config(create_test_config(true))
.with_identity_provider(MockTokenProvider)
.with_identity_verifier(MockVerifier)
.with_test_channels()
.with_tx_to_session_layer(tx_to_session);
let ready_result = builder.ready();
assert!(ready_result.is_err());
}
#[test]
fn test_builder_ready_missing_config() {
let (tx_to_session, _) = mpsc::channel(10);
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(1)
.with_source(create_test_name("source"))
.with_destination(create_test_name("dest"))
.with_identity_provider(MockTokenProvider)
.with_identity_verifier(MockVerifier)
.with_test_channels()
.with_tx_to_session_layer(tx_to_session);
let ready_result = builder.ready();
assert!(ready_result.is_err());
}
#[test]
fn test_builder_ready_missing_identity_provider() {
let (tx_to_session, _) = mpsc::channel(10);
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(1)
.with_source(create_test_name("source"))
.with_destination(create_test_name("dest"))
.with_config(create_test_config(true))
.with_identity_verifier(MockVerifier)
.with_test_channels()
.with_tx_to_session_layer(tx_to_session);
let ready_result = builder.ready();
assert!(ready_result.is_err());
}
#[test]
fn test_builder_ready_missing_identity_verifier() {
let (tx_to_session, _) = mpsc::channel(10);
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(1)
.with_source(create_test_name("source"))
.with_destination(create_test_name("dest"))
.with_config(create_test_config(true))
.with_identity_provider(MockTokenProvider)
.with_test_channels()
.with_tx_to_session_layer(tx_to_session);
let ready_result = builder.ready();
assert!(ready_result.is_err());
}
#[test]
fn test_builder_ready_missing_tx() {
let (tx_to_session, _) = mpsc::channel(10);
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(1)
.with_source(create_test_name("source"))
.with_destination(create_test_name("dest"))
.with_config(create_test_config(true))
.with_identity_provider(MockTokenProvider)
.with_identity_verifier(MockVerifier)
.with_tx_to_session_layer(tx_to_session);
let ready_result = builder.ready();
assert!(ready_result.is_err());
}
#[test]
fn test_builder_ready_missing_tx_to_session_layer() {
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(1)
.with_source(create_test_name("source"))
.with_destination(create_test_name("dest"))
.with_config(create_test_config(true))
.with_identity_provider(MockTokenProvider)
.with_identity_verifier(MockVerifier)
.with_test_channels();
let ready_result = builder.ready();
assert!(ready_result.is_err());
}
#[test]
fn test_builder_chaining() {
let (tx_to_session, _) = mpsc::channel(10);
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(123)
.with_source(create_test_name("src"))
.with_destination(create_test_name("dst"))
.with_config(create_test_config(false))
.with_identity_provider(MockTokenProvider)
.with_identity_verifier(MockVerifier)
.with_test_channels()
.with_tx_to_session_layer(tx_to_session);
assert_eq!(builder.id, Some(123));
assert!(builder.source.is_some());
assert!(builder.destination.is_some());
assert!(builder.config.is_some());
assert!(builder.identity_provider.is_some());
assert!(builder.identity_verifier.is_some());
assert!(builder.slim_tx.is_some());
assert!(builder.app_tx.is_some());
assert!(builder.tx_to_session_layer.is_some());
}
#[test]
fn test_builder_ready_state_transition() {
let (tx_to_session, _) = mpsc::channel(10);
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(1)
.with_source(create_test_name("source"))
.with_destination(create_test_name("dest"))
.with_config(create_test_config(true))
.with_identity_provider(MockTokenProvider)
.with_identity_verifier(MockVerifier)
.with_test_channels()
.with_tx_to_session_layer(tx_to_session);
let ready_builder = builder.ready().unwrap();
assert_eq!(ready_builder.id, Some(1));
assert!(ready_builder.source.is_some());
assert!(ready_builder.destination.is_some());
assert!(ready_builder.config.is_some());
assert!(ready_builder.identity_provider.is_some());
assert!(ready_builder.identity_verifier.is_some());
assert!(ready_builder.slim_tx.is_some());
assert!(ready_builder.app_tx.is_some());
assert!(ready_builder.tx_to_session_layer.is_some());
}
#[test]
fn test_builder_different_target_types() {
let _controller_builder = SessionBuilder::<
MockTokenProvider,
MockVerifier,
ForController,
NotReady,
>::for_controller();
let _participant_builder = SessionBuilder::<
MockTokenProvider,
MockVerifier,
ForParticipant,
NotReady,
>::for_participant();
let _moderator_builder = SessionBuilder::<
MockTokenProvider,
MockVerifier,
ForModerator,
NotReady,
>::for_moderator();
}
#[test]
fn test_builder_with_different_config_types() {
let config_initiator = create_test_config(true);
let config_participant = create_test_config(false);
let builder1 =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_config(config_initiator);
assert!(builder1.config.unwrap().initiator);
let builder2 =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_config(config_participant);
assert!(!builder2.config.unwrap().initiator);
}
#[test]
fn test_builder_with_multicast_session_config() {
let mut config = create_test_config(true);
config.session_type = ProtoSessionType::Multicast;
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_config(config);
assert_eq!(
builder.config.unwrap().session_type,
ProtoSessionType::Multicast
);
}
#[test]
fn test_builder_with_mls_enabled() {
let mut config = create_test_config(true);
config.mls_settings = Some(MlsSettings {
header_integrity_validation_percent: 100,
});
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_config(config);
assert!(builder.config.unwrap().mls_settings.is_some());
}
#[test]
fn test_builder_with_custom_retry_settings() {
let mut config = create_test_config(true);
config.max_retries = Some(10);
config.interval = Some(std::time::Duration::from_secs(5));
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_config(config.clone());
let stored_config = builder.config.unwrap();
assert_eq!(stored_config.max_retries, Some(10));
assert_eq!(
stored_config.interval,
Some(std::time::Duration::from_secs(5))
);
}
#[test]
fn test_builder_with_metadata() {
let mut config = create_test_config(true);
let mut metadata = HashMap::new();
metadata.insert("key1".to_string(), "value1".to_string());
metadata.insert("key2".to_string(), "value2".to_string());
config.metadata = metadata.clone();
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_config(config);
let stored_config = builder.config.unwrap();
assert_eq!(
stored_config.metadata.get("key1"),
Some(&"value1".to_string())
);
assert_eq!(
stored_config.metadata.get("key2"),
Some(&"value2".to_string())
);
}
#[test]
fn test_builder_overwrites_previous_values() {
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(1)
.with_id(2)
.with_id(3);
assert_eq!(builder.id, Some(3));
let source1 = create_test_name("first");
let source2 = create_test_name("second");
let builder = SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_source(source1)
.with_source(source2.clone());
assert_eq!(builder.source, Some(source2));
}
#[test]
fn test_builder_partial_configuration() {
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(42);
assert_eq!(builder.id, Some(42));
assert!(builder.source.is_none());
let dest = create_test_name("dest");
let (tx_to_session, _) = mpsc::channel(10);
let builder = builder
.with_source(create_test_name("source"))
.with_destination(dest.clone())
.with_config(create_test_config(true))
.with_identity_provider(MockTokenProvider)
.with_identity_verifier(MockVerifier)
.with_test_channels()
.with_tx_to_session_layer(tx_to_session);
assert!(builder.ready().is_ok());
}
#[test]
fn test_builder_ready_validation_comprehensive() {
let (tx_to_session, _) = mpsc::channel(10);
let mut builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller();
builder.id = Some(1);
builder.source = Some(create_test_name("source"));
builder.destination = Some(create_test_name("dest"));
builder.control = Some(create_test_name("dest"));
builder.config = Some(create_test_config(true));
builder.identity_provider = Some(MockTokenProvider);
builder.identity_verifier = Some(MockVerifier);
let (slim_tx, app_tx) = create_test_channels();
builder.slim_tx = Some(slim_tx);
builder.app_tx = Some(app_tx);
builder.tx_to_session_layer = Some(tx_to_session);
assert!(builder.ready().is_ok());
}
#[test]
fn test_builder_with_empty_metadata() {
let config = create_test_config(true);
assert!(config.metadata.is_empty());
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_config(config);
assert!(builder.config.unwrap().metadata.is_empty());
}
#[test]
fn test_builder_with_none_retries() {
let mut config = create_test_config(true);
config.max_retries = None;
config.interval = None;
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_config(config);
let stored = builder.config.unwrap();
assert_eq!(stored.max_retries, None);
assert_eq!(stored.interval, None);
}
#[test]
fn test_builder_with_zero_duration() {
let mut config = create_test_config(true);
config.interval = Some(std::time::Duration::from_secs(0));
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_config(config);
assert_eq!(
builder.config.unwrap().interval,
Some(std::time::Duration::from_secs(0))
);
}
#[test]
fn test_builder_with_large_id() {
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(u32::MAX);
assert_eq!(builder.id, Some(u32::MAX));
}
#[test]
fn test_builder_type_states() {
let not_ready =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller();
let not_ready = not_ready.with_id(1);
assert_eq!(not_ready.id, Some(1));
assert_eq!(
std::mem::size_of::<
SessionBuilder<MockTokenProvider, MockVerifier, ForController, NotReady>,
>(),
std::mem::size_of::<
SessionBuilder<MockTokenProvider, MockVerifier, ForController, Ready>,
>()
);
}
#[test]
fn test_builder_clone_safe_types() {
let provider1 = MockTokenProvider;
let provider2 = provider1.clone();
let builder1 =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_identity_provider(provider1);
let builder2 =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_identity_provider(provider2);
assert!(builder1.identity_provider.is_some());
assert!(builder2.identity_provider.is_some());
}
#[test]
fn test_builder_error_message_content() {
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(1);
let res = builder.ready();
assert!(res.is_err_and(|e| matches!(e, SessionError::SessionBuilderIncomplete)));
}
#[test]
fn test_builder_with_all_session_types() {
let config_p2p = SessionConfig {
session_type: ProtoSessionType::PointToPoint,
max_retries: None,
interval: None,
mls_settings: None,
initiator: true,
metadata: HashMap::new(),
};
let builder_p2p =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_config(config_p2p);
assert_eq!(
builder_p2p.config.unwrap().session_type,
ProtoSessionType::PointToPoint
);
let config_multicast = SessionConfig {
session_type: ProtoSessionType::Multicast,
max_retries: None,
interval: None,
mls_settings: None,
initiator: true,
metadata: HashMap::new(),
};
let builder_multicast = SessionBuilder::<
MockTokenProvider,
MockVerifier,
ForController,
NotReady,
>::for_controller()
.with_config(config_multicast);
assert_eq!(
builder_multicast.config.unwrap().session_type,
ProtoSessionType::Multicast
);
let config_unspecified = SessionConfig {
session_type: ProtoSessionType::Unspecified,
max_retries: None,
interval: None,
mls_settings: None,
initiator: false,
metadata: HashMap::new(),
};
let builder_unspecified = SessionBuilder::<
MockTokenProvider,
MockVerifier,
ForController,
NotReady,
>::for_controller()
.with_config(config_unspecified);
assert_eq!(
builder_unspecified.config.unwrap().session_type,
ProtoSessionType::Unspecified
);
}
#[tokio::test]
async fn test_builder_build_as_participant() {
let (tx_to_session, _rx_from_session) = mpsc::channel(10);
let config = create_test_config(false);
let dest = create_test_name("moderator");
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(1)
.with_source(create_test_name("participant"))
.with_destination(dest.clone())
.with_config(config)
.with_identity_provider(MockTokenProvider)
.with_identity_verifier(MockVerifier)
.with_test_channels()
.with_tx_to_session_layer(tx_to_session);
let controller = builder.ready().unwrap().build();
assert!(controller.is_ok());
let controller = controller.unwrap();
assert_eq!(controller.id(), 1);
assert!(!controller.is_initiator());
}
#[tokio::test]
async fn test_builder_build_as_moderator_p2p() {
let (tx_to_session, _rx_from_session) = mpsc::channel(10);
let (slim_tx, mut slim_rx) = mpsc::channel(10);
let (app_tx, _app_rx) = mpsc::unbounded_channel();
let mut config = create_test_config(true); config.session_type = ProtoSessionType::PointToPoint;
let dest = create_test_name("participant");
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(2)
.with_source(create_test_name("moderator"))
.with_destination(dest.clone())
.with_config(config)
.with_identity_provider(MockTokenProvider)
.with_identity_verifier(MockVerifier)
.with_slim_tx(slim_tx)
.with_app_tx(app_tx)
.with_tx_to_session_layer(tx_to_session);
let controller = builder.ready().unwrap().build();
assert!(controller.is_ok());
let controller = controller.unwrap();
assert_eq!(controller.id(), 2);
assert!(controller.is_initiator());
assert_eq!(controller.session_type(), ProtoSessionType::PointToPoint);
tokio::time::timeout(std::time::Duration::from_millis(100), slim_rx.recv())
.await
.ok();
}
#[tokio::test]
async fn test_builder_build_as_moderator_multicast() {
let (tx_to_session, _rx_from_session) = mpsc::channel(10);
let (slim_tx, mut slim_rx) = mpsc::channel(10);
let (app_tx, _app_rx) = mpsc::unbounded_channel();
let mut config = create_test_config(true); config.session_type = ProtoSessionType::Multicast;
let dest = create_test_name("group");
let data_channel = dest.with_id(NameId::DATA_CHANNEL_ID);
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(3)
.with_source(create_test_name("moderator"))
.with_destination(data_channel) .with_config(config)
.with_identity_provider(MockTokenProvider)
.with_identity_verifier(MockVerifier)
.with_slim_tx(slim_tx)
.with_app_tx(app_tx)
.with_tx_to_session_layer(tx_to_session);
let controller = builder.ready().unwrap().build();
assert!(controller.is_ok());
let controller = controller.unwrap();
assert_eq!(controller.id(), 3);
assert!(controller.is_initiator());
assert_eq!(controller.session_type(), ProtoSessionType::Multicast);
let result =
tokio::time::timeout(std::time::Duration::from_millis(50), slim_rx.recv()).await;
assert!(result.is_err() || result.unwrap().is_none());
}
#[tokio::test]
async fn test_builder_build_with_mls_disabled() {
let (tx_to_session, _) = mpsc::channel(10);
let mut config = create_test_config(false);
config.mls_settings = None;
let dest = create_test_name("dest");
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(4)
.with_source(create_test_name("source"))
.with_destination(dest.clone())
.with_config(config)
.with_identity_provider(MockTokenProvider)
.with_identity_verifier(MockVerifier)
.with_test_channels()
.with_tx_to_session_layer(tx_to_session);
let controller = builder.ready().unwrap().build();
assert!(controller.is_ok());
}
#[tokio::test]
async fn test_builder_build_with_custom_retry_settings() {
let (tx_to_session, _) = mpsc::channel(10);
let mut config = create_test_config(true);
config.max_retries = Some(5);
config.interval = Some(std::time::Duration::from_millis(500));
let dest = create_test_name("dest");
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(5)
.with_source(create_test_name("source"))
.with_destination(dest.clone())
.with_config(config.clone())
.with_identity_provider(MockTokenProvider)
.with_identity_verifier(MockVerifier)
.with_test_channels()
.with_tx_to_session_layer(tx_to_session);
let controller = builder.ready().unwrap().build();
assert!(controller.is_ok());
let controller = controller.unwrap();
let retrieved_config = controller.session_config();
assert_eq!(retrieved_config.max_retries, Some(5));
assert_eq!(
retrieved_config.interval,
Some(std::time::Duration::from_millis(500))
);
}
#[tokio::test]
async fn test_builder_build_with_metadata() {
let (tx_to_session, _) = mpsc::channel(10);
let mut config = create_test_config(false);
let mut metadata = HashMap::new();
metadata.insert("app_name".to_string(), "test_app".to_string());
metadata.insert("version".to_string(), "1.0".to_string());
config.metadata = metadata.clone();
let dest = create_test_name("dest");
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(6)
.with_source(create_test_name("source"))
.with_destination(dest.clone())
.with_config(config)
.with_identity_provider(MockTokenProvider)
.with_identity_verifier(MockVerifier)
.with_test_channels()
.with_tx_to_session_layer(tx_to_session);
let controller = builder.ready().unwrap().build();
assert!(controller.is_ok());
let controller = controller.unwrap();
let retrieved_metadata = controller.metadata();
assert_eq!(
retrieved_metadata.get("app_name"),
Some(&"test_app".to_string())
);
assert_eq!(retrieved_metadata.get("version"), Some(&"1.0".to_string()));
}
#[tokio::test]
async fn test_builder_build_verifies_session_source_and_destination() {
let (tx_to_session, _) = mpsc::channel(10);
let source = create_test_name("my_source");
let destination = create_test_name("my_dest");
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(7)
.with_source(source.clone())
.with_destination(destination.clone())
.with_config(create_test_config(false))
.with_identity_provider(MockTokenProvider)
.with_identity_verifier(MockVerifier)
.with_test_channels()
.with_tx_to_session_layer(tx_to_session);
let controller = builder.ready().unwrap().build();
assert!(controller.is_ok());
let controller = controller.unwrap();
assert_eq!(controller.source(), &source);
assert_eq!(controller.dst(), &destination);
}
#[tokio::test]
async fn test_builder_build_multiple_sessions_different_ids() {
let (tx_to_session1, _) = mpsc::channel(10);
let (tx_to_session2, _) = mpsc::channel(10);
let dest1 = create_test_name("dest1");
let builder1 =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(100)
.with_source(create_test_name("source1"))
.with_destination(dest1.clone())
.with_config(create_test_config(false))
.with_identity_provider(MockTokenProvider)
.with_identity_verifier(MockVerifier)
.with_test_channels()
.with_tx_to_session_layer(tx_to_session1);
let dest2 = create_test_name("dest2");
let builder2 =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(200)
.with_source(create_test_name("source2"))
.with_destination(dest2.clone())
.with_config(create_test_config(true))
.with_identity_provider(MockTokenProvider)
.with_identity_verifier(MockVerifier)
.with_test_channels()
.with_tx_to_session_layer(tx_to_session2);
let controller1 = builder1.ready().unwrap().build();
let controller2 = builder2.ready().unwrap().build();
assert!(controller1.is_ok());
assert!(controller2.is_ok());
let controller1 = controller1.unwrap();
let controller2 = controller2.unwrap();
assert_eq!(controller1.id(), 100);
assert_eq!(controller2.id(), 200);
assert_ne!(controller1.id(), controller2.id());
}
#[tokio::test]
async fn test_builder_build_with_unspecified_session_type() {
let (tx_to_session, _) = mpsc::channel(10);
let mut config = create_test_config(false);
config.session_type = ProtoSessionType::Unspecified;
let dest = create_test_name("dest");
let builder =
SessionBuilder::<MockTokenProvider, MockVerifier, ForController, NotReady>::for_controller()
.with_id(8)
.with_source(create_test_name("source"))
.with_destination(dest.clone())
.with_config(config)
.with_identity_provider(MockTokenProvider)
.with_identity_verifier(MockVerifier)
.with_test_channels()
.with_tx_to_session_layer(tx_to_session);
let ready_result = builder.ready();
assert!(ready_result.is_err());
assert!(ready_result.is_err_and(|e| matches!(e, SessionError::SessionBuilderIncomplete)));
}
#[test]
fn test_builder_multicast_forces_channel_ids() {
let (tx_to_session, _) = mpsc::channel(10);
let mut config = create_test_config(true);
config.session_type = ProtoSessionType::Multicast;
let dest = create_test_name("group").with_id(999);
let ready_builder = SessionBuilder::<
MockTokenProvider,
MockVerifier,
ForController,
NotReady,
>::for_controller()
.with_id(1)
.with_source(create_test_name("source"))
.with_destination(dest)
.with_config(config)
.with_identity_provider(MockTokenProvider)
.with_identity_verifier(MockVerifier)
.with_test_channels()
.with_tx_to_session_layer(tx_to_session)
.ready()
.unwrap();
assert_eq!(
ready_builder.destination.unwrap().id(),
NameId::DATA_CHANNEL_ID
);
assert_eq!(
ready_builder.control.unwrap().id(),
NameId::CONTROL_CHANNEL_ID
);
}
#[test]
fn test_builder_p2p_control_equals_destination() {
let (tx_to_session, _) = mpsc::channel(10);
let config = create_test_config(true);
let dest = create_test_name("remote").with_id(42);
let ready_builder = SessionBuilder::<
MockTokenProvider,
MockVerifier,
ForController,
NotReady,
>::for_controller()
.with_id(1)
.with_source(create_test_name("source"))
.with_destination(dest.clone())
.with_config(config)
.with_identity_provider(MockTokenProvider)
.with_identity_verifier(MockVerifier)
.with_test_channels()
.with_tx_to_session_layer(tx_to_session)
.ready()
.unwrap();
assert_eq!(ready_builder.destination.unwrap(), dest);
assert_eq!(ready_builder.control.unwrap(), dest);
}
}