/* * Copyright (C) 2026 Mateusz Gruszczynski @linuxiarz.pl * Source-Available Code / Dual-Licensed. * * Free for non-commercial and evaluation use under terms of BSL/GPLv3. * Commercial or production use requires a valid paid license. * See LICENSE file in repository root for details. */ use serde::{Deserialize, Serialize}; use std::{ collections::{HashMap, VecDeque}, error::Error, fmt, }; pub const MAX_OPERATION_COMPONENTS: usize = 4096; const MAX_OPERATION_OWNER_SPANS: usize = 8192; const MAX_OPERATION_INSERT_BYTES: usize = 2_000_000; const MAX_OWNER_LENGTH: usize = 120; const MAX_OPERATION_HISTORY: usize = 512; const MAX_OPERATION_HISTORY_BYTES: usize = 8 * 1024 * 1024; const AUTHORSHIP_VERSION: u8 = 2; const OWNER_COLOR_SEPARATOR: char = '\u{001f}'; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct OwnerSpan { pub start: usize, pub end: usize, pub owner: String, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum OperationComponent { Retain { count: usize, }, Delete { count: usize, }, Insert { text: String, #[serde(default)] owners: Vec, }, } #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] pub struct TextOperation { #[serde(default)] pub components: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct OwnerReplacement { pub owner: String, pub replacement: String, } #[derive(Debug, Clone, Serialize)] pub struct AppliedOperation { pub base_revision_id: i64, pub revision_id: i64, pub client_id: String, pub update_id: u64, pub operation: TextOperation, pub owner_replacements: Vec, } #[derive(Debug)] pub struct CollaborativeDocument { pub content: String, pub owner_map: String, pub revision_id: i64, history: VecDeque, history_bytes: usize, acknowledged_updates: HashMap, } impl CollaborativeDocument { pub fn new(content: String, owner_map: String, revision_id: i64) -> Self { Self { content, owner_map, revision_id, history: VecDeque::new(), history_bytes: 0, acknowledged_updates: HashMap::new(), } } pub fn transform_from( &self, base_revision_id: i64, operation: &TextOperation, client_id: &str, update_id: u64, ) -> Result { let mut transformed = normalize_operation(operation)?; if base_revision_id == self.revision_id { return Ok(transformed); } let Some(start) = self .history .iter() .position(|entry| entry.base_revision_id == base_revision_id) else { return Err(OperationError::RevisionUnavailable); }; let mut expected_revision = base_revision_id; for applied in self.history.iter().skip(start) { if applied.base_revision_id != expected_revision { return Err(OperationError::RevisionUnavailable); } let incoming_has_priority = operation_key_before(client_id, update_id, &applied.client_id, applied.update_id); transformed = transform_operation(&transformed, &applied.operation, incoming_has_priority)?; expected_revision = applied.revision_id; if expected_revision == self.revision_id { return Ok(transformed); } } Err(OperationError::RevisionUnavailable) } pub fn acknowledge(&mut self, client_id: &str, update_id: u64) { self.acknowledged_updates .entry(client_id.to_owned()) .and_modify(|acknowledged| *acknowledged = (*acknowledged).max(update_id)) .or_insert(update_id); } pub fn has_applied_update(&self, client_id: &str, update_id: u64) -> bool { self.acknowledged_updates .get(client_id) .is_some_and(|acknowledged| update_id <= *acknowledged) } pub fn acknowledged_updates(&self, client_id: &str) -> Vec { self.acknowledged_updates .get(client_id) .copied() .into_iter() .collect() } pub fn operations_after(&self, revision_id: i64) -> Option> { if revision_id == self.revision_id { return Some(Vec::new()); } let start = self .history .iter() .position(|entry| entry.base_revision_id == revision_id)?; let mut expected_revision = revision_id; let mut operations = Vec::new(); for applied in self.history.iter().skip(start) { if applied.base_revision_id != expected_revision { return None; } operations.push(applied.clone()); expected_revision = applied.revision_id; if expected_revision == self.revision_id { return Some(operations); } } None } pub fn record(&mut self, operation: AppliedOperation) { self.acknowledge(&operation.client_id, operation.update_id); self.history_bytes = self .history_bytes .saturating_add(applied_operation_size(&operation)); self.history.push_back(operation); while self.history.len() > MAX_OPERATION_HISTORY || self.history_bytes > MAX_OPERATION_HISTORY_BYTES { let Some(removed) = self.history.pop_front() else { break; }; self.history_bytes = self .history_bytes .saturating_sub(applied_operation_size(&removed)); } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum ComponentKind { Retain, Delete, Insert, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum OperationError { InvalidComponent, InvalidUtf16Boundary, LengthMismatch, TooManyComponents, RevisionUnavailable, Serialization, } impl fmt::Display for OperationError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { let message = match self { Self::InvalidComponent => "invalid text operation component", Self::InvalidUtf16Boundary => "text operation splits a UTF-16 character", Self::LengthMismatch => "text operation length does not match the document", Self::TooManyComponents => "text operation contains too many components", Self::RevisionUnavailable => "the base revision is no longer available", Self::Serialization => "invalid authorship metadata", }; formatter.write_str(message) } } impl Error for OperationError {} fn applied_operation_size(operation: &AppliedOperation) -> usize { let components = operation .operation .components .iter() .map(|component| match component { OperationComponent::Retain { .. } | OperationComponent::Delete { .. } => 24, OperationComponent::Insert { text, owners } => { 32usize.saturating_add(text.len()).saturating_add( owners .iter() .map(|span| 24usize.saturating_add(span.owner.len())) .sum::(), ) } }) .sum::(); components .saturating_add(operation.client_id.len()) .saturating_add( operation .owner_replacements .iter() .map(|replacement| replacement.owner.len() + replacement.replacement.len() + 16) .sum::(), ) } fn operation_key_before( left_client_id: &str, left_update_id: u64, right_client_id: &str, right_update_id: u64, ) -> bool { left_client_id < right_client_id || (left_client_id == right_client_id && left_update_id < right_update_id) } fn component_kind(component: &OperationComponent) -> ComponentKind { match component { OperationComponent::Retain { .. } => ComponentKind::Retain, OperationComponent::Delete { .. } => ComponentKind::Delete, OperationComponent::Insert { .. } => ComponentKind::Insert, } } fn component_length(component: &OperationComponent) -> usize { match component { OperationComponent::Retain { count } | OperationComponent::Delete { count } => *count, OperationComponent::Insert { text, .. } => text.encode_utf16().count(), } } fn normalize_owner_spans(spans: &[OwnerSpan], length: usize) -> Vec { let mut sorted = spans .iter() .filter_map(|span| { let start = span.start.min(length); let end = span.end.min(length).max(start); if span.owner.is_empty() || end <= start { None } else { Some(OwnerSpan { start, end, owner: span.owner.clone(), }) } }) .collect::>(); sorted.sort_by_key(|span| (span.start, span.end)); let mut result: Vec = Vec::new(); for mut span in sorted { if let Some(previous) = result.last_mut() { if previous.owner == span.owner && span.start <= previous.end { previous.end = previous.end.max(span.end); continue; } if span.start < previous.end { span.start = previous.end; } } if span.end > span.start { result.push(span); } } result } fn slice_owner_spans(spans: &[OwnerSpan], start: usize, length: usize) -> Vec { let end = start.saturating_add(length); let sliced = spans .iter() .filter_map(|span| { let overlap_start = start.max(span.start); let overlap_end = end.min(span.end); (overlap_end > overlap_start).then(|| OwnerSpan { start: overlap_start - start, end: overlap_end - start, owner: span.owner.clone(), }) }) .collect::>(); normalize_owner_spans(&sliced, length) } fn shift_owner_spans(spans: &[OwnerSpan], offset: usize) -> Vec { spans .iter() .map(|span| OwnerSpan { start: span.start + offset, end: span.end + offset, owner: span.owner.clone(), }) .collect() } fn append_component( components: &mut Vec, component: OperationComponent, ) -> Result<(), OperationError> { match component { OperationComponent::Retain { count } => { if count == 0 { return Ok(()); } if let Some(OperationComponent::Retain { count: previous }) = components.last_mut() { *previous = previous .checked_add(count) .ok_or(OperationError::InvalidComponent)?; } else { components.push(OperationComponent::Retain { count }); } } OperationComponent::Delete { count } => { if count == 0 { return Ok(()); } if let Some(OperationComponent::Delete { count: previous }) = components.last_mut() { *previous = previous .checked_add(count) .ok_or(OperationError::InvalidComponent)?; } else { components.push(OperationComponent::Delete { count }); } } OperationComponent::Insert { text, owners } => { let length = text.encode_utf16().count(); if length == 0 { return Ok(()); } let owners = normalize_owner_spans(&owners, length); if let Some(OperationComponent::Insert { text: previous_text, owners: previous_owners, }) = components.last_mut() { let offset = previous_text.encode_utf16().count(); previous_text.push_str(&text); previous_owners.extend(shift_owner_spans(&owners, offset)); *previous_owners = normalize_owner_spans(previous_owners, previous_text.encode_utf16().count()); } else { components.push(OperationComponent::Insert { text, owners }); } } } if components.len() > MAX_OPERATION_COMPONENTS { return Err(OperationError::TooManyComponents); } Ok(()) } pub fn normalize_operation(operation: &TextOperation) -> Result { if operation.components.len() > MAX_OPERATION_COMPONENTS { return Err(OperationError::TooManyComponents); } let mut inserted_bytes = 0usize; let mut owner_span_count = 0usize; let mut components = Vec::with_capacity(operation.components.len()); for component in &operation.components { if let OperationComponent::Insert { text, owners } = component { inserted_bytes = inserted_bytes .checked_add(text.len()) .ok_or(OperationError::InvalidComponent)?; owner_span_count = owner_span_count .checked_add(owners.len()) .ok_or(OperationError::InvalidComponent)?; let text_length = text.encode_utf16().count(); if inserted_bytes > MAX_OPERATION_INSERT_BYTES || owner_span_count > MAX_OPERATION_OWNER_SPANS || owners.iter().any(|span| { span.start > span.end || span.end > text_length || utf16_byte_index(text, span.start).is_err() || utf16_byte_index(text, span.end).is_err() || span.owner.chars().count() > MAX_OWNER_LENGTH || span.owner.chars().any(|character| { character.is_control() && character != OWNER_COLOR_SEPARATOR }) }) { return Err(OperationError::InvalidComponent); } } append_component(&mut components, component.clone())?; } Ok(TextOperation { components }) } pub fn operation_base_length(operation: &TextOperation) -> Result { normalize_operation(operation)? .components .iter() .try_fold(0usize, |length, component| { let component_length = match component { OperationComponent::Retain { count } | OperationComponent::Delete { count } => { *count } OperationComponent::Insert { .. } => 0, }; length .checked_add(component_length) .ok_or(OperationError::InvalidComponent) }) } pub fn operation_from_edit( previous_content: &str, next_content: &str, next_owner_map: &str, ) -> TextOperation { let mut previous_prefix_bytes = 0usize; let mut next_prefix_bytes = 0usize; for (previous, next) in previous_content.chars().zip(next_content.chars()) { if previous != next { break; } previous_prefix_bytes += previous.len_utf8(); next_prefix_bytes += next.len_utf8(); } let previous_remainder = &previous_content[previous_prefix_bytes..]; let next_remainder = &next_content[next_prefix_bytes..]; let mut previous_suffix_bytes = 0usize; let mut next_suffix_bytes = 0usize; for (previous, next) in previous_remainder .chars() .rev() .zip(next_remainder.chars().rev()) { if previous != next { break; } previous_suffix_bytes += previous.len_utf8(); next_suffix_bytes += next.len_utf8(); } let previous_middle_end = previous_content.len() - previous_suffix_bytes; let next_middle_end = next_content.len() - next_suffix_bytes; let previous_prefix = &previous_content[..previous_prefix_bytes]; let previous_middle = &previous_content[previous_prefix_bytes..previous_middle_end]; let next_middle = &next_content[next_prefix_bytes..next_middle_end]; let suffix = &previous_content[previous_middle_end..]; let prefix_length = previous_prefix.encode_utf16().count(); let deleted_length = previous_middle.encode_utf16().count(); let inserted_length = next_middle.encode_utf16().count(); let suffix_length = suffix.encode_utf16().count(); let next_authorship = parse_authorship(next_content, next_owner_map); let inserted_owners = slice_owner_spans(&next_authorship.spans, prefix_length, inserted_length); let mut components = Vec::new(); if prefix_length > 0 { components.push(OperationComponent::Retain { count: prefix_length, }); } if deleted_length > 0 { components.push(OperationComponent::Delete { count: deleted_length, }); } if !next_middle.is_empty() { components.push(OperationComponent::Insert { text: next_middle.to_owned(), owners: inserted_owners, }); } if suffix_length > 0 { components.push(OperationComponent::Retain { count: suffix_length, }); } TextOperation { components } } pub fn replace_operation( base_length: usize, content: String, owners: Vec, ) -> TextOperation { let mut components = Vec::new(); if base_length > 0 { components.push(OperationComponent::Delete { count: base_length }); } if !content.is_empty() { components.push(OperationComponent::Insert { text: content, owners, }); } TextOperation { components } } struct OperationCursor { components: Vec, index: usize, offset: usize, } impl OperationCursor { fn new(operation: &TextOperation) -> Result { Ok(Self { components: normalize_operation(operation)?.components, index: 0, offset: 0, }) } fn current(&self) -> Option<&OperationComponent> { self.components.get(self.index) } fn kind(&self) -> Option { self.current().map(component_kind) } fn remaining(&self) -> usize { self.current() .map(|component| component_length(component).saturating_sub(self.offset)) .unwrap_or(0) } fn take(&mut self, count: usize) -> Result { let component = self .current() .cloned() .ok_or(OperationError::InvalidComponent)?; if count == 0 || count > self.remaining() { return Err(OperationError::InvalidComponent); } let component_length = component_length(&component); let part = match component { OperationComponent::Retain { .. } => OperationComponent::Retain { count }, OperationComponent::Delete { .. } => OperationComponent::Delete { count }, OperationComponent::Insert { text, owners } => OperationComponent::Insert { text: slice_utf16(&text, self.offset, count)?.to_owned(), owners: slice_owner_spans(&owners, self.offset, count), }, }; self.offset += count; if self.offset == component_length { self.index += 1; self.offset = 0; } Ok(part) } fn take_remaining(&mut self) -> Result { let count = self.remaining(); self.take(count) } } pub fn transform_operation( left_operation: &TextOperation, right_operation: &TextOperation, left_before_right: bool, ) -> Result { if operation_base_length(left_operation)? != operation_base_length(right_operation)? { return Err(OperationError::LengthMismatch); } let mut left = OperationCursor::new(left_operation)?; let mut right = OperationCursor::new(right_operation)?; let mut left_prime = Vec::new(); while left.current().is_some() || right.current().is_some() { if left.kind() == Some(ComponentKind::Insert) && (right.kind() != Some(ComponentKind::Insert) || left_before_right) { append_component(&mut left_prime, left.take_remaining()?)?; continue; } if right.kind() == Some(ComponentKind::Insert) { let count = right.remaining(); right.take_remaining()?; append_component(&mut left_prime, OperationComponent::Retain { count })?; continue; } let (Some(left_kind), Some(right_kind)) = (left.kind(), right.kind()) else { return Err(OperationError::InvalidComponent); }; let count = left.remaining().min(right.remaining()); match (left_kind, right_kind) { (ComponentKind::Retain, ComponentKind::Retain) => { append_component(&mut left_prime, OperationComponent::Retain { count })?; } (ComponentKind::Delete, ComponentKind::Retain) => { append_component(&mut left_prime, OperationComponent::Delete { count })?; } (ComponentKind::Retain, ComponentKind::Delete) | (ComponentKind::Delete, ComponentKind::Delete) => {} _ => return Err(OperationError::InvalidComponent), } left.take(count)?; right.take(count)?; } Ok(TextOperation { components: left_prime, }) } fn utf16_byte_index(value: &str, offset: usize) -> Result { if offset == 0 { return Ok(0); } let mut current = 0usize; for (byte_index, character) in value.char_indices() { if current == offset { return Ok(byte_index); } current += character.len_utf16(); if current > offset { return Err(OperationError::InvalidUtf16Boundary); } } if current == offset { Ok(value.len()) } else { Err(OperationError::LengthMismatch) } } fn slice_utf16(value: &str, start: usize, length: usize) -> Result<&str, OperationError> { let start_byte = utf16_byte_index(value, start)?; let end_byte = utf16_byte_index(value, start.saturating_add(length))?; value .get(start_byte..end_byte) .ok_or(OperationError::InvalidUtf16Boundary) } #[derive(Debug, Clone, Serialize, Deserialize)] struct AuthorshipModel { #[serde(default = "authorship_version")] version: u8, #[serde(default)] spans: Vec, } fn authorship_version() -> u8 { AUTHORSHIP_VERSION } #[derive(Debug, Deserialize)] #[serde(untagged)] enum RawAuthorship { Model(AuthorshipModel), LineOwners(Vec), } fn parse_authorship(content: &str, raw: &str) -> AuthorshipModel { let length = content.encode_utf16().count(); match serde_json::from_str::(raw) { Ok(RawAuthorship::Model(model)) if model.version == AUTHORSHIP_VERSION => AuthorshipModel { version: AUTHORSHIP_VERSION, spans: normalize_owner_spans(&model.spans, length), }, Ok(RawAuthorship::LineOwners(owners)) => { let lines = content.split('\n').collect::>(); let mut offset = 0usize; let mut spans = Vec::new(); for (index, line) in lines.iter().enumerate() { let line_length = line.encode_utf16().count() + usize::from(index + 1 < lines.len()); let owner = owners.get(index).cloned().unwrap_or_default(); if !owner.is_empty() && line_length > 0 { spans.push(OwnerSpan { start: offset, end: offset + line_length, owner, }); } offset += line_length; } AuthorshipModel { version: AUTHORSHIP_VERSION, spans: normalize_owner_spans(&spans, length), } } _ => AuthorshipModel { version: AUTHORSHIP_VERSION, spans: Vec::new(), }, } } fn copy_retained_spans( target: &mut Vec, spans: &[OwnerSpan], source_start: usize, length: usize, output_start: usize, ) { let source_end = source_start + length; for span in spans { let start = source_start.max(span.start); let end = source_end.min(span.end); if end > start { target.push(OwnerSpan { start: output_start + start - source_start, end: output_start + end - source_start, owner: span.owner.clone(), }); } } } pub fn apply_operation_to_document( content: &str, owner_map: &str, operation: &TextOperation, owner_replacements: &[OwnerReplacement], ) -> Result<(String, String), OperationError> { let operation = normalize_operation(operation)?; let content_length = content.encode_utf16().count(); if operation_base_length(&operation)? != content_length { return Err(OperationError::LengthMismatch); } let source_model = parse_authorship(content, owner_map); let mut output_spans = Vec::new(); let mut source_offset = 0usize; let mut output_offset = 0usize; let mut output_content = String::new(); for component in &operation.components { match component { OperationComponent::Retain { count } => { output_content.push_str(slice_utf16(content, source_offset, *count)?); copy_retained_spans( &mut output_spans, &source_model.spans, source_offset, *count, output_offset, ); source_offset += *count; output_offset += *count; } OperationComponent::Delete { count } => { source_offset += *count; } OperationComponent::Insert { text, owners } => { output_content.push_str(text); output_spans.extend(shift_owner_spans(owners, output_offset)); output_offset += text.encode_utf16().count(); } } } if source_offset != content_length { return Err(OperationError::LengthMismatch); } for span in &mut output_spans { let identity = span .owner .split(OWNER_COLOR_SEPARATOR) .next() .unwrap_or_default(); if let Some(replacement) = owner_replacements.iter().find(|replacement| { replacement.owner == identity && !replacement.replacement.is_empty() }) { span.owner.clone_from(&replacement.replacement); } } let model = AuthorshipModel { version: AUTHORSHIP_VERSION, spans: normalize_owner_spans(&output_spans, output_offset), }; let owner_map = serde_json::to_string(&model).map_err(|_| OperationError::Serialization)?; Ok((output_content, owner_map)) } pub fn owner_spans_from_map(content: &str, owner_map: &str) -> Vec { parse_authorship(content, owner_map).spans } #[cfg(test)] #[path = "tests/collab.rs"] mod tests;