#[derive(Debug)]
pub struct ConsensusSession {
client_id: u128,
session: Option<u64>,
request_counter: u64,
register_consumed: bool,
}
impl ConsensusSession {
#[must_use]
pub fn new() -> Self {
Self::with_client_id(generate_client_id())
}
#[must_use]
pub fn with_client_id(client_id: u128) -> Self {
Self {
client_id,
session: None,
request_counter: 1,
register_consumed: false,
}
}
#[must_use]
pub fn client_id(&self) -> u128 {
self.client_id
}
#[must_use]
pub fn session(&self) -> Option<u64> {
self.session
}
#[must_use]
pub fn is_bound(&self) -> bool {
self.session.is_some()
}
pub fn bind(&mut self, session: u64) {
assert!(
self.session.is_none(),
"session already bound (session={})",
self.session.unwrap()
);
assert!(session > 0, "session must be > 0");
self.session = Some(session);
}
pub fn register_request_id(&mut self) -> u64 {
assert!(
!self.register_consumed,
"register_request_id already called"
);
assert!(!self.is_bound(), "register_request_id called after bind");
self.register_consumed = true;
0
}
pub fn begin_register(&mut self) -> u64 {
if self.register_consumed || self.is_bound() {
*self = Self::new();
}
self.register_request_id()
}
pub fn next_request_id(&mut self) -> u64 {
assert!(self.is_bound(), "next_request_id called before bind");
let id = self.request_counter;
self.request_counter = self
.request_counter
.checked_add(1)
.expect("request counter overflow (u64::MAX requests on a single session)");
id
}
#[must_use]
pub fn current_request_id(&self) -> u64 {
self.request_counter
}
}
impl Default for ConsensusSession {
fn default() -> Self {
Self::new()
}
}
fn generate_client_id() -> u128 {
iggy_common::random_id::get_uuid()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_session_is_unbound() {
let session = ConsensusSession::new();
assert!(!session.is_bound());
assert!(session.session().is_none());
assert_ne!(session.client_id(), 0);
}
#[test]
fn client_id_is_unique() {
let s1 = ConsensusSession::new();
let s2 = ConsensusSession::new();
assert_ne!(s1.client_id(), s2.client_id());
}
#[test]
fn bind_sets_session() {
let mut session = ConsensusSession::with_client_id(42);
session.bind(100);
assert!(session.is_bound());
assert_eq!(session.session(), Some(100));
}
#[test]
fn register_request_id_returns_zero() {
let mut session = ConsensusSession::with_client_id(1);
assert_eq!(session.register_request_id(), 0);
}
#[test]
fn request_ids_are_monotonic_after_bind() {
let mut session = ConsensusSession::with_client_id(1);
let _ = session.register_request_id();
session.bind(10);
assert_eq!(session.next_request_id(), 1);
assert_eq!(session.next_request_id(), 2);
assert_eq!(session.next_request_id(), 3);
assert_eq!(session.current_request_id(), 4);
}
#[test]
#[should_panic(expected = "register_request_id already called")]
fn double_register_request_id_panics() {
let mut session = ConsensusSession::with_client_id(1);
let _ = session.register_request_id();
let _ = session.register_request_id();
}
#[test]
fn begin_register_on_fresh_session_keeps_client_id() {
let mut session = ConsensusSession::with_client_id(7);
assert_eq!(session.begin_register(), 0);
assert_eq!(session.client_id(), 7);
assert!(!session.is_bound());
}
#[test]
fn begin_register_re_arms_after_consume_without_bind() {
let mut session = ConsensusSession::with_client_id(1);
let _ = session.begin_register();
assert_eq!(session.begin_register(), 0);
assert!(!session.is_bound());
}
#[test]
fn begin_register_re_arms_after_bind() {
let mut session = ConsensusSession::with_client_id(1);
let _ = session.begin_register();
session.bind(42);
assert_eq!(session.begin_register(), 0);
assert!(!session.is_bound());
}
#[test]
#[should_panic(expected = "next_request_id called before bind")]
fn next_request_id_before_bind_panics() {
let mut session = ConsensusSession::with_client_id(1);
let _ = session.next_request_id();
}
#[test]
#[should_panic(expected = "already bound")]
fn double_bind_panics() {
let mut session = ConsensusSession::with_client_id(1);
session.bind(10);
session.bind(20);
}
#[test]
fn reconnect_uses_fresh_session() {
let s1 = ConsensusSession::new();
let s2 = ConsensusSession::new();
assert_ne!(s1.client_id(), s2.client_id());
assert!(!s1.is_bound());
assert!(!s2.is_bound());
}
#[test]
fn with_client_id_deterministic() {
let session = ConsensusSession::with_client_id(0xDEAD_BEEF);
assert_eq!(session.client_id(), 0xDEAD_BEEF);
}
}