use std::sync::Arc;
use tokio::sync::{mpsc, watch};
use anyhow::Result;
use crate::InstanceId;
use super::{
SessionId,
messages::{BlockInfo, SessionMessage, SessionStateSnapshot},
state::{AttachmentState, ControlRole, SessionPhase},
transport::MessageTransport,
};
pub struct SessionEndpoint {
session_id: SessionId,
instance_id: InstanceId,
control_role: ControlRole,
attachment: AttachmentState,
phase: SessionPhase,
transport: Arc<MessageTransport>,
msg_rx: mpsc::Receiver<SessionMessage>,
state_tx: watch::Sender<SessionStateSnapshot>,
}
impl SessionEndpoint {
pub fn new(
session_id: SessionId,
instance_id: InstanceId,
transport: Arc<MessageTransport>,
msg_rx: mpsc::Receiver<SessionMessage>,
) -> Self {
let initial_state = SessionStateSnapshot {
phase: SessionPhase::default(),
control_role: ControlRole::default(),
g2_blocks: Vec::new(),
g3_pending: 0,
ready_layer_range: None,
};
let (state_tx, _) = watch::channel(initial_state);
Self {
session_id,
instance_id,
control_role: ControlRole::default(),
attachment: AttachmentState::default(),
phase: SessionPhase::default(),
transport,
msg_rx,
state_tx,
}
}
pub fn new_attached(
session_id: SessionId,
instance_id: InstanceId,
peer: InstanceId,
role: ControlRole,
phase: SessionPhase,
transport: Arc<MessageTransport>,
msg_rx: mpsc::Receiver<SessionMessage>,
) -> Self {
let initial_state = SessionStateSnapshot {
phase,
control_role: role,
g2_blocks: Vec::new(),
g3_pending: 0,
ready_layer_range: None,
};
let (state_tx, _) = watch::channel(initial_state);
Self {
session_id,
instance_id,
control_role: role,
attachment: AttachmentState::Attached { peer },
phase,
transport,
msg_rx,
state_tx,
}
}
pub fn session_id(&self) -> SessionId {
self.session_id
}
pub fn instance_id(&self) -> InstanceId {
self.instance_id
}
pub fn control_role(&self) -> ControlRole {
self.control_role
}
pub fn is_attached(&self) -> bool {
self.attachment.is_attached()
}
pub fn peer(&self) -> Option<InstanceId> {
self.attachment.peer()
}
pub fn phase(&self) -> SessionPhase {
self.phase
}
pub fn is_complete(&self) -> bool {
self.phase.is_terminal()
}
pub fn set_phase(&mut self, phase: SessionPhase) {
self.phase = phase;
}
pub fn set_control_role(&mut self, role: ControlRole) {
self.control_role = role;
}
pub fn accept_attachment(&mut self, peer: InstanceId, role: ControlRole) {
self.attachment = AttachmentState::Attached { peer };
self.control_role = role;
}
pub fn detach(&mut self) -> Option<InstanceId> {
let peer = self.attachment.peer();
self.attachment = AttachmentState::Unattached;
self.control_role = ControlRole::Neutral;
peer
}
pub fn yield_control(&mut self) -> Result<()> {
if self.control_role != ControlRole::Controller {
anyhow::bail!("Cannot yield control: not currently Controller");
}
self.control_role = ControlRole::Neutral;
Ok(())
}
pub fn acquire_control(&mut self) -> Result<()> {
if self.control_role == ControlRole::Controller {
return Ok(());
}
self.control_role = ControlRole::Controller;
Ok(())
}
pub fn peer_yielded_control(&mut self) {
if self.control_role == ControlRole::Controllee {
self.control_role = ControlRole::Neutral;
}
}
pub fn peer_acquired_control(&mut self) -> Result<()> {
if self.control_role == ControlRole::Controller {
anyhow::bail!("Cannot transition to Controllee: currently Controller");
}
self.control_role = ControlRole::Controllee;
Ok(())
}
pub async fn recv(&mut self) -> Option<SessionMessage> {
self.msg_rx.recv().await
}
pub fn try_recv(&mut self) -> Result<SessionMessage, mpsc::error::TryRecvError> {
self.msg_rx.try_recv()
}
pub async fn send_attach(&self, peer: InstanceId, as_role: ControlRole) -> Result<()> {
let msg = SessionMessage::Attach {
peer: self.instance_id,
session_id: self.session_id,
as_role,
};
self.send_to(peer, msg).await
}
pub async fn send_detach(&self) -> Result<()> {
let peer = self
.peer()
.ok_or_else(|| anyhow::anyhow!("Cannot detach: not attached"))?;
let msg = SessionMessage::Detach {
peer: self.instance_id,
session_id: self.session_id,
};
self.send_to(peer, msg).await
}
pub async fn send_yield_control(&self) -> Result<()> {
let peer = self
.peer()
.ok_or_else(|| anyhow::anyhow!("Cannot yield: not attached"))?;
let msg = SessionMessage::YieldControl {
peer: self.instance_id,
session_id: self.session_id,
};
self.send_to(peer, msg).await
}
pub async fn send_acquire_control(&self) -> Result<()> {
let peer = self
.peer()
.ok_or_else(|| anyhow::anyhow!("Cannot acquire: not attached"))?;
let msg = SessionMessage::AcquireControl {
peer: self.instance_id,
session_id: self.session_id,
};
self.send_to(peer, msg).await
}
pub async fn send_to(&self, peer: InstanceId, msg: SessionMessage) -> Result<()> {
self.transport.send_session(peer, msg).await
}
pub async fn send(&self, msg: SessionMessage) -> Result<()> {
let peer = self
.peer()
.ok_or_else(|| anyhow::anyhow!("Cannot send: not attached"))?;
self.send_to(peer, msg).await
}
pub fn publish_state(&self, g2_blocks: Vec<BlockInfo>, g3_pending: usize) {
let _ = self.state_tx.send(SessionStateSnapshot {
phase: self.phase,
control_role: self.control_role,
g2_blocks,
g3_pending,
ready_layer_range: None,
});
}
pub fn publish_state_with_layer_range(
&self,
g2_blocks: Vec<BlockInfo>,
g3_pending: usize,
layer_range: Option<std::ops::Range<usize>>,
) {
let _ = self.state_tx.send(SessionStateSnapshot {
phase: self.phase,
control_role: self.control_role,
g2_blocks,
g3_pending,
ready_layer_range: layer_range,
});
}
pub fn state_rx(&self) -> watch::Receiver<SessionStateSnapshot> {
self.state_tx.subscribe()
}
pub fn transport(&self) -> &Arc<MessageTransport> {
&self.transport
}
}
pub type SessionMessageTx = mpsc::Sender<SessionMessage>;
pub fn session_message_channel(
buffer: usize,
) -> (SessionMessageTx, mpsc::Receiver<SessionMessage>) {
mpsc::channel(buffer)
}
#[cfg(test)]
mod tests {
use super::*;
use dashmap::DashMap;
fn create_test_transport() -> Arc<MessageTransport> {
Arc::new(MessageTransport::local(
Arc::new(DashMap::new()),
Arc::new(DashMap::new()),
))
}
#[test]
fn test_endpoint_initial_state() {
let (_, rx) = mpsc::channel(32);
let transport = create_test_transport();
let session_id = SessionId::new_v4();
let instance_id = InstanceId::new_v4();
let endpoint = SessionEndpoint::new(session_id, instance_id, transport, rx);
assert_eq!(endpoint.session_id(), session_id);
assert_eq!(endpoint.instance_id(), instance_id);
assert_eq!(endpoint.control_role(), ControlRole::Neutral);
assert!(!endpoint.is_attached());
assert!(endpoint.peer().is_none());
assert_eq!(endpoint.phase(), SessionPhase::Searching);
assert!(!endpoint.is_complete());
}
#[test]
fn test_endpoint_attachment() {
let (_, rx) = mpsc::channel(32);
let transport = create_test_transport();
let session_id = SessionId::new_v4();
let instance_id = InstanceId::new_v4();
let peer_id = InstanceId::new_v4();
let mut endpoint = SessionEndpoint::new(session_id, instance_id, transport, rx);
endpoint.accept_attachment(peer_id, ControlRole::Controllee);
assert!(endpoint.is_attached());
assert_eq!(endpoint.peer(), Some(peer_id));
assert_eq!(endpoint.control_role(), ControlRole::Controllee);
let detached = endpoint.detach();
assert_eq!(detached, Some(peer_id));
assert!(!endpoint.is_attached());
assert_eq!(endpoint.control_role(), ControlRole::Neutral);
}
#[test]
fn test_endpoint_pre_attached() {
let (_, rx) = mpsc::channel(32);
let transport = create_test_transport();
let session_id = SessionId::new_v4();
let instance_id = InstanceId::new_v4();
let peer_id = InstanceId::new_v4();
let endpoint = SessionEndpoint::new_attached(
session_id,
instance_id,
peer_id,
ControlRole::Controller,
SessionPhase::Holding,
transport,
rx,
);
assert!(endpoint.is_attached());
assert_eq!(endpoint.peer(), Some(peer_id));
assert_eq!(endpoint.control_role(), ControlRole::Controller);
assert_eq!(endpoint.phase(), SessionPhase::Holding);
}
#[test]
fn test_control_transitions() {
let (_, rx) = mpsc::channel(32);
let transport = create_test_transport();
let session_id = SessionId::new_v4();
let instance_id = InstanceId::new_v4();
let peer_id = InstanceId::new_v4();
let mut endpoint = SessionEndpoint::new(session_id, instance_id, transport, rx);
endpoint.accept_attachment(peer_id, ControlRole::Controller);
assert!(endpoint.yield_control().is_ok());
assert_eq!(endpoint.control_role(), ControlRole::Neutral);
assert!(endpoint.yield_control().is_err());
assert!(endpoint.acquire_control().is_ok());
assert_eq!(endpoint.control_role(), ControlRole::Controller);
}
#[test]
fn test_phase_transitions() {
let (_, rx) = mpsc::channel(32);
let transport = create_test_transport();
let session_id = SessionId::new_v4();
let instance_id = InstanceId::new_v4();
let mut endpoint = SessionEndpoint::new(session_id, instance_id, transport, rx);
assert_eq!(endpoint.phase(), SessionPhase::Searching);
assert!(!endpoint.is_complete());
endpoint.set_phase(SessionPhase::Holding);
assert_eq!(endpoint.phase(), SessionPhase::Holding);
endpoint.set_phase(SessionPhase::Complete);
assert!(endpoint.is_complete());
}
#[tokio::test]
async fn test_state_publication() {
let (_, rx) = mpsc::channel(32);
let transport = create_test_transport();
let session_id = SessionId::new_v4();
let instance_id = InstanceId::new_v4();
let endpoint = SessionEndpoint::new(session_id, instance_id, transport, rx);
let mut state_rx = endpoint.state_rx();
let state = state_rx.borrow().clone();
assert_eq!(state.phase, SessionPhase::Searching);
assert_eq!(state.g2_blocks.len(), 0);
endpoint.publish_state(vec![], 5);
state_rx.changed().await.unwrap();
let state = state_rx.borrow().clone();
assert_eq!(state.g3_pending, 5);
}
}