use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::collections::BTreeMap;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct VectorClock(pub BTreeMap<String, u64>);
impl VectorClock {
pub fn new() -> Self {
Self(BTreeMap::new())
}
pub fn increment(&mut self, peer_id: &str) {
let counter = self.0.entry(peer_id.to_string()).or_insert(0);
*counter += 1;
}
pub fn merge(&mut self, other: &VectorClock) {
for (peer, timestamp) in &other.0 {
let entry = self.0.entry(peer.clone()).or_insert(0);
*entry = (*entry).max(*timestamp);
}
}
pub fn compare(&self, other: &VectorClock) -> ClockOrdering {
let mut self_less = false;
let mut other_less = false;
let mut all_peers: std::collections::HashSet<&String> = self.0.keys().collect();
all_peers.extend(other.0.keys());
for peer in all_peers {
let self_val = self.0.get(peer).copied().unwrap_or(0);
let other_val = other.0.get(peer).copied().unwrap_or(0);
match self_val.cmp(&other_val) {
Ordering::Less => other_less = true,
Ordering::Greater => self_less = true,
Ordering::Equal => {}
}
}
match (self_less, other_less) {
(true, true) => ClockOrdering::Concurrent, (true, false) => ClockOrdering::After, (false, true) => ClockOrdering::Before, (false, false) => ClockOrdering::Equal, }
}
pub fn has_dependencies(&self, message_clock: &VectorClock) -> bool {
for (peer, timestamp) in &message_clock.0 {
let our_timestamp = self.0.get(peer).copied().unwrap_or(0);
if our_timestamp < timestamp.saturating_sub(1) {
return false; }
}
true
}
pub fn get_missing_ranges(&self, remote: &VectorClock) -> Vec<MissingRange> {
let mut missing = Vec::new();
for (peer_id, remote_ts) in &remote.0 {
let local_ts = self.0.get(peer_id).copied().unwrap_or(0);
if *remote_ts > local_ts {
missing.push(MissingRange {
peer_id: peer_id.clone(),
from_timestamp: local_ts + 1,
to_timestamp: *remote_ts,
});
}
}
missing
}
}
impl Default for VectorClock {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClockOrdering {
Before, After, Concurrent, Equal, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MissingRange {
pub peer_id: String,
pub from_timestamp: u64,
pub to_timestamp: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MessageMetadata {
pub id: String, pub entity_id: String, pub entity_type: EntityType, pub author_peer_id: String, pub vector_clock: VectorClock, pub lamport_clock: u64, pub timestamp: u64, pub previous_message_id: Option<String>, pub reply_to_id: Option<String>, }
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum EntityType {
Person,
Group,
Project,
Channel,
Organisation,
}
impl EntityType {
pub fn as_str(&self) -> &'static str {
match self {
EntityType::Person => "person",
EntityType::Group => "group",
EntityType::Project => "project",
EntityType::Channel => "channel",
EntityType::Organisation => "organisation",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CRDTMessage {
pub content: MessageContent,
pub metadata: MessageMetadata,
#[serde(skip_serializing_if = "Option::is_none")]
pub local_state: Option<LocalMessageState>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MessageContent {
pub text: String,
pub author: String, #[serde(skip_serializing_if = "Option::is_none")]
pub attachments: Option<Vec<Attachment>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Attachment {
pub attachment_type: AttachmentType,
pub url: String,
pub name: String,
pub size: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum AttachmentType {
File,
Image,
Video,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct LocalMessageState {
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<MessageStatus>,
#[serde(default)]
pub reactions: Vec<Reaction>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thread_count: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub latest_reply_by: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum MessageStatus {
Sent,
Delivered,
Read,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Reaction {
pub emoji: String,
pub count: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub user_reacted: Option<bool>,
pub peer_ids: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyncRequest {
pub entity_id: String,
pub entity_type: EntityType,
pub requester_peer_id: String,
pub vector_clock: VectorClock,
#[serde(skip_serializing_if = "Option::is_none")]
pub missing_message_ids: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyncResponse {
pub entity_id: String,
pub entity_type: EntityType,
pub messages: Vec<CRDTMessage>,
pub vector_clock: VectorClock,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EntitySyncState {
pub entity_id: String,
pub entity_type: EntityType,
pub vector_clock: VectorClock,
pub last_sync_time: u64,
pub message_count: usize,
pub missing_messages: Vec<String>,
pub out_of_order_messages: Vec<String>,
}
pub fn sort_messages_causally(messages: &mut [CRDTMessage]) {
messages.sort_by(|a, b| {
match a.metadata.vector_clock.compare(&b.metadata.vector_clock) {
ClockOrdering::Before => Ordering::Less,
ClockOrdering::After => Ordering::Greater,
ClockOrdering::Equal | ClockOrdering::Concurrent => {
match a.metadata.lamport_clock.cmp(&b.metadata.lamport_clock) {
Ordering::Equal => {
a.metadata.id.cmp(&b.metadata.id)
}
other => other,
}
}
}
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vector_clock_increment() {
let mut clock = VectorClock::new();
clock.increment("alice");
clock.increment("alice");
clock.increment("bob");
assert_eq!(clock.0.get("alice"), Some(&2));
assert_eq!(clock.0.get("bob"), Some(&1));
}
#[test]
fn test_vector_clock_comparison() {
let mut clock1 = VectorClock::new();
clock1.increment("alice");
clock1.increment("alice");
let mut clock2 = VectorClock::new();
clock2.increment("alice");
assert_eq!(clock1.compare(&clock2), ClockOrdering::After);
assert_eq!(clock2.compare(&clock1), ClockOrdering::Before);
let mut clock3 = VectorClock::new();
clock3.increment("bob");
assert_eq!(clock1.compare(&clock3), ClockOrdering::Concurrent);
}
#[test]
fn test_vector_clock_merge() {
let mut clock1 = VectorClock::new();
clock1.increment("alice");
clock1.increment("alice");
let mut clock2 = VectorClock::new();
clock2.increment("alice");
clock2.increment("bob");
clock1.merge(&clock2);
assert_eq!(clock1.0.get("alice"), Some(&2));
assert_eq!(clock1.0.get("bob"), Some(&1));
}
#[test]
fn test_has_dependencies() {
let mut local = VectorClock::new();
local.increment("alice");
local.increment("alice");
let mut message_clock = VectorClock::new();
message_clock.increment("alice");
message_clock.increment("alice");
message_clock.increment("alice");
assert!(local.has_dependencies(&message_clock));
message_clock.increment("alice");
message_clock.increment("alice");
assert!(!local.has_dependencies(&message_clock));
}
}