diff options
Diffstat (limited to 'src/document/sync.rs')
| -rw-r--r-- | src/document/sync.rs | 49 |
1 files changed, 41 insertions, 8 deletions
diff --git a/src/document/sync.rs b/src/document/sync.rs index dab43b4..8cb740b 100644 --- a/src/document/sync.rs +++ b/src/document/sync.rs @@ -10,7 +10,6 @@ use std::collections::HashSet; use std::sync::atomic::{AtomicU64, Ordering}; use std::time::Duration; -use automerge::AutomergeError; use automerge::sync::{self, SyncDoc}; use tokio::time::Instant; use tracing::debug; @@ -302,23 +301,29 @@ impl SyncSession { /// Applies a sync message from the peer to `doc`. Doesn't settle the awaited /// answer: the caller measures the exchange first (`reply`). #[cfg(test)] - pub(crate) fn receive( - &mut self, - doc: &mut Document, - message: Received, - ) -> Result<(), AutomergeError> { + pub(crate) fn receive(&mut self, doc: &mut Document, message: Received) -> anyhow::Result<()> { self.receive_logged(doc, message, &mut automerge::PatchLog::inactive()) } /// Applies a sync message from the peer to `doc`, what it changes recorded in `log` /// (`Replica::logged`). Doesn't settle the awaited answer: the caller measures the - /// exchange first (`reply`). + /// exchange first (`reply`). A message carrying a change that names an author though + /// it isn't its actor's first is refused whole, before Automerge sees it + /// (`changes::misplaced_author`). pub(crate) fn receive_logged( &mut self, doc: &mut Document, message: Received, log: &mut automerge::PatchLog, - ) -> Result<(), AutomergeError> { + ) -> anyhow::Result<()> { + if let Some(misplaced) = message + .0 + .changes + .iter() + .find_map(crate::document::changes::misplaced_author) + { + anyhow::bail!("Refused a sync message: {misplaced}"); + } self.delta_offered = false; self.before_receive = Some(self.peer_view()); let said = message.0.heads.clone(); @@ -1909,6 +1914,34 @@ mod tests { assert_eq!(theirs.get_heads(), ours.get_heads(), "both in one message"); } + /// A change naming an author though it isn't its actor's first is refused before + /// Automerge sees it (`changes::misplaced_author`), and the document stays as it was. + #[test] + fn a_message_with_a_misplaced_author_is_refused() { + use crate::document::testing::{change, with_author_footer}; + use automerge::{ActorId, ROOT, transaction::Transactable}; + let mut writer = Document::new().with_actor(ActorId::from(b"writer".to_vec())); + change(&mut writer, |doc| doc.put(ROOT, "x", 1).unwrap()); + change(&mut writer, |doc| doc.put(ROOT, "x", 2).unwrap()); + let changes = writer.get_changes(&[]); + let first = changes[0].raw_bytes().to_vec(); + let crafted = with_author_footer(changes[1].raw_bytes(), b"mallory"); + let (mut ours, mut theirs) = (Document::new(), Document::new()); + let (mut a, _) = in_step(&mut ours, &mut theirs); + a.receive( + &mut ours, + Received::pushed(Vec::new(), vec![first]).unwrap(), + ) + .unwrap(); + let heads = ours.get_heads(); + let refused = a.receive( + &mut ours, + Received::pushed(Vec::new(), vec![crafted]).unwrap(), + ); + assert!(refused.is_err()); + assert_eq!(ours.get_heads(), heads); + } + /// Pushed changes (`push`) do what the sync message they stand for would: the peer /// applies them, is level with us, and has nothing to answer; nothing goes twice. #[test] |
