use std::net::SocketAddr;
use tokio::sync::watch;
use crate::connection::ConnectionState;
use crate::proxy_protocol::ProxyProtocolMetadata;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct SessionId(u64);
impl SessionId {
pub(crate) const fn from_session_owner(owner_id: u64) -> Self {
Self(owner_id)
}
pub(crate) const fn owner_id(self) -> u64 {
self.0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub struct ProxyInfoSnapshot {
source: SocketAddr,
destination: SocketAddr,
}
impl ProxyInfoSnapshot {
fn from_metadata(metadata: &ProxyProtocolMetadata) -> Self {
Self {
source: metadata.source,
destination: metadata.destination,
}
}
#[must_use]
pub const fn source(&self) -> SocketAddr {
self.source
}
#[must_use]
pub const fn destination(&self) -> SocketAddr {
self.destination
}
}
#[derive(Clone)]
pub struct SessionStateView {
state_rx: watch::Receiver<ConnectionState>,
closed_rx: watch::Receiver<bool>,
}
impl SessionStateView {
pub(crate) fn from_receivers(state_rx: watch::Receiver<ConnectionState>, closed_rx: watch::Receiver<bool>) -> Self {
Self { state_rx, closed_rx }
}
pub(crate) fn same_canonical_owner(&self, other: &Self) -> bool {
self.state_rx.same_channel(&other.state_rx) && self.closed_rx.same_channel(&other.closed_rx)
}
#[must_use]
pub fn is_healthy(&self) -> bool {
!self.is_closed() && *self.state_rx.borrow() == ConnectionState::Healthy
}
#[must_use]
pub fn is_closed(&self) -> bool {
self.publishers_dropped() || *self.closed_rx.borrow() || *self.state_rx.borrow() == ConnectionState::Closed
}
pub async fn closed(&self) {
let mut state_rx = self.state_rx.clone();
let mut closed_rx = self.closed_rx.clone();
while !self.is_closed() {
tokio::select! {
changed = state_rx.changed() => {
if changed.is_err() {
return;
}
}
changed = closed_rx.changed() => {
if changed.is_err() {
return;
}
}
}
}
}
fn publishers_dropped(&self) -> bool {
self.state_rx.has_changed().is_err() || self.closed_rx.has_changed().is_err()
}
}
#[derive(Clone)]
#[non_exhaustive]
pub enum SessionView {
Network {
id: SessionId,
local_addr: SocketAddr,
remote_addr: SocketAddr,
transport_peer_addr: SocketAddr,
proxy: Option<ProxyInfoSnapshot>,
state: SessionStateView,
},
Embedded {
id: SessionId,
state: SessionStateView,
},
}
impl SessionView {
pub(crate) fn network(
session_id: u64,
local_addr: SocketAddr,
remote_addr: SocketAddr,
transport_peer_addr: SocketAddr,
proxy_protocol: Option<&ProxyProtocolMetadata>,
state_rx: watch::Receiver<ConnectionState>,
closed_rx: watch::Receiver<bool>,
) -> Self {
Self::Network {
id: SessionId::from_session_owner(session_id),
local_addr,
remote_addr,
transport_peer_addr,
proxy: proxy_protocol.map(ProxyInfoSnapshot::from_metadata),
state: SessionStateView::from_receivers(state_rx, closed_rx),
}
}
#[must_use]
pub const fn id(&self) -> SessionId {
match self {
Self::Network { id, .. } | Self::Embedded { id, .. } => *id,
}
}
#[must_use]
pub const fn state(&self) -> &SessionStateView {
match self {
Self::Network { state, .. } | Self::Embedded { state, .. } => state,
}
}
}
pub(crate) struct EmbeddedSessionRecord {
view: SessionView,
state_tx: watch::Sender<ConnectionState>,
closed_tx: watch::Sender<bool>,
}
impl EmbeddedSessionRecord {
pub(crate) fn new(session_id: u64) -> Self {
let (state_tx, state_rx) = watch::channel(ConnectionState::Healthy);
let (closed_tx, closed_rx) = watch::channel(false);
Self {
view: SessionView::Embedded {
id: SessionId::from_session_owner(session_id),
state: SessionStateView::from_receivers(state_rx, closed_rx),
},
state_tx,
closed_tx,
}
}
pub(crate) fn view(&self) -> SessionView {
self.view.clone()
}
pub(crate) fn close(&self) {
let _ = self.state_tx.send(ConnectionState::Closed);
let _ = self.closed_tx.send(true);
}
}
impl Drop for EmbeddedSessionRecord {
fn drop(&mut self) {
self.close();
}
}
#[cfg(test)]
mod tests {
use std::net::SocketAddr;
use super::*;
fn address(value: &str) -> SocketAddr {
value.parse().expect("test socket address must parse")
}
#[tokio::test]
async fn cloned_state_views_observe_the_same_close_transition() {
let (state_tx, state_rx) = watch::channel(ConnectionState::Healthy);
let (closed_tx, closed_rx) = watch::channel(false);
let first = SessionStateView::from_receivers(state_rx, closed_rx);
let second = first.clone();
assert!(first.is_healthy());
assert!(!first.is_closed());
state_tx
.send(ConnectionState::Degraded)
.expect("test state receiver must remain subscribed");
assert!(!first.is_healthy());
assert!(!first.is_closed());
closed_tx
.send(true)
.expect("test close receiver must remain subscribed");
first.closed().await;
second.closed().await;
assert!(first.is_closed());
assert!(second.is_closed());
}
#[tokio::test]
async fn cloned_state_views_treat_publisher_loss_as_closed() {
let (state_tx, state_rx) = watch::channel(ConnectionState::Healthy);
let (closed_tx, closed_rx) = watch::channel(false);
let first = SessionStateView::from_receivers(state_rx, closed_rx);
let second = first.clone();
drop(state_tx);
drop(closed_tx);
first.closed().await;
second.closed().await;
assert!(first.is_closed());
assert!(second.is_closed());
assert!(!first.is_healthy());
assert!(!second.is_healthy());
}
#[tokio::test]
async fn embedded_views_have_no_socket_addresses_and_close_with_their_record() {
let record = EmbeddedSessionRecord::new(41);
let view = record.view();
let SessionView::Embedded { id, state } = &view else {
panic!("embedded record must create an embedded session view");
};
assert_eq!(*id, view.id());
assert!(state.is_healthy());
drop(record);
let SessionView::Embedded { state, .. } = &view else {
panic!("embedded session view must retain its variant");
};
state.closed().await;
assert!(state.is_closed());
}
#[test]
fn network_view_keeps_proxy_source_distinct_from_the_transport_peer() {
let transport_peer = address("203.0.113.9:31000");
let metadata = ProxyProtocolMetadata {
transport_peer,
source: address("198.51.100.44:43123"),
destination: address("192.0.2.10:10911"),
tlvs: Default::default(),
};
let (_state_tx, state_rx) = watch::channel(ConnectionState::Healthy);
let (_closed_tx, closed_rx) = watch::channel(false);
let view = SessionView::network(
77,
metadata.destination,
metadata.source,
transport_peer,
Some(&metadata),
state_rx,
closed_rx,
);
let SessionView::Network {
id,
local_addr,
remote_addr,
transport_peer_addr,
proxy,
state,
} = view
else {
panic!("network constructor must create a network session view");
};
let proxy = proxy.expect("trusted proxy metadata must have a snapshot");
assert_eq!(local_addr, metadata.destination);
assert_eq!(remote_addr, metadata.source);
assert_eq!(transport_peer_addr, transport_peer);
assert_ne!(remote_addr, transport_peer_addr);
assert_eq!(proxy.source(), metadata.source);
assert_eq!(proxy.destination(), metadata.destination);
assert!(state.is_healthy());
assert_eq!(id, SessionId::from_session_owner(77));
}
}