ping-openmls-sdk-core 0.1.4

Platform-agnostic OpenMLS-based messaging engine
//! Hybrid Logical Clock — Kulkarni et al. 2014.
//!
//! Used to order application messages within an MLS epoch when multiple devices send
//! concurrently. Epoch order is the primary axis; HLC orders within an epoch.

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Hlc {
    pub wall_ms: u64,
    pub logical: u32,
}

impl Hlc {
    pub const ZERO: Hlc = Hlc {
        wall_ms: 0,
        logical: 0,
    };

    /// Advance for a local event at wall-clock `now_ms`.
    pub fn tick(self, now_ms: u64) -> Hlc {
        let wall = now_ms.max(self.wall_ms);
        let logical = if wall == self.wall_ms {
            self.logical + 1
        } else {
            0
        };
        Hlc {
            wall_ms: wall,
            logical,
        }
    }

    /// Merge with a remote HLC observed at local wall-clock `now_ms`.
    pub fn merge(self, remote: Hlc, now_ms: u64) -> Hlc {
        let wall = now_ms.max(self.wall_ms).max(remote.wall_ms);
        let logical = match (wall == self.wall_ms, wall == remote.wall_ms) {
            (true, true) => self.logical.max(remote.logical) + 1,
            (true, false) => self.logical + 1,
            (false, true) => remote.logical + 1,
            (false, false) => 0,
        };
        Hlc {
            wall_ms: wall,
            logical,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn tick_monotonic_when_clock_stalls() {
        let a = Hlc::ZERO.tick(1000);
        let b = a.tick(1000);
        assert!(b > a);
        assert_eq!(b.wall_ms, 1000);
        assert_eq!(b.logical, 1);
    }

    #[test]
    fn merge_dominates_both_inputs() {
        let local = Hlc {
            wall_ms: 100,
            logical: 2,
        };
        let remote = Hlc {
            wall_ms: 200,
            logical: 5,
        };
        let merged = local.merge(remote, 150);
        assert!(merged > local);
        assert!(merged > remote);
    }
}