aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/document/sync.rs42
-rw-r--r--src/swarm/sync.rs21
2 files changed, 38 insertions, 25 deletions
diff --git a/src/document/sync.rs b/src/document/sync.rs
index a46e059..4ae41ba 100644
--- a/src/document/sync.rs
+++ b/src/document/sync.rs
@@ -59,19 +59,27 @@ impl Received {
/// unknown, they give way to the heads we share (`known_last_sync`), as for an
/// overtaken message.
///
- /// No heads stand for the tips of the changes (`SyncSession::push`). Changes that
- /// don't decode leave the heads out; applying them fails as it would have anyway.
- pub(crate) fn pushed(heads: Vec<crate::document::ChangeHash>, changes: Vec<Vec<u8>>) -> Self {
+ /// No heads stand for the tips of the changes (`SyncSession::push`). None without
+ /// changes, or without heads where a change doesn't decode: no heads would say the
+ /// sender lost all it had, and we'd send it everything again.
+ pub(crate) fn pushed(
+ heads: Vec<crate::document::ChangeHash>,
+ changes: Vec<Vec<u8>>,
+ ) -> Option<Self> {
+ if changes.is_empty() {
+ return None;
+ }
let heads = if heads.is_empty() {
- let decoded: Vec<automerge::Change> = changes
+ let decoded = changes
.iter()
- .filter_map(|bytes| automerge::Change::try_from(bytes.as_slice()).ok())
- .collect();
+ .map(|bytes| automerge::Change::try_from(bytes.as_slice()))
+ .collect::<Result<Vec<_>, _>>()
+ .ok()?;
tips(decoded.iter().map(|change| (change.hash(), change.deps())))
} else {
heads
};
- Self(sync::Message {
+ Some(Self(sync::Message {
heads: heads.clone(),
need: Vec::new(),
have: vec![sync::Have {
@@ -81,7 +89,7 @@ impl Received {
changes: changes.into(),
flags: None,
version: sync::MessageVersion::V2,
- })
+ }))
}
/// The sender's heads, as it says.
@@ -1499,7 +1507,7 @@ mod tests {
assert!(a.push(&ours, 1 << 20).is_none(), "sent already");
let before = theirs.get_heads();
- let message = Received::pushed(heads, changes);
+ let message = Received::pushed(heads, changes).expect("decodes");
assert_eq!(message.heads(), ours.get_heads(), "found in the changes");
b.receive(&mut theirs, message).unwrap();
b.came_from_peer(&theirs, &before);
@@ -1530,8 +1538,11 @@ mod tests {
a.send_succeeded(a.id());
let before = theirs.get_heads();
- b.receive(&mut theirs, Received::pushed(heads, changes))
- .unwrap();
+ b.receive(
+ &mut theirs,
+ Received::pushed(heads, changes).expect("decodes"),
+ )
+ .unwrap();
b.came_from_peer(&theirs, &before);
assert!(!b.answered_as_delivered(&theirs, &theirs.get_heads()));
let answer = b.generate(&theirs, now, bytes).expect("an answer");
@@ -1542,6 +1553,15 @@ mod tests {
assert_eq!(ours.get_heads(), theirs.get_heads());
}
+ /// A push without changes, or without heads and changes that don't decode, is no
+ /// message: no heads would say the sender lost all it had.
+ #[test]
+ fn broken_pushes_are_refused() {
+ let hash = crate::document::ChangeHash([1; 32]);
+ assert!(Received::pushed(vec![hash], Vec::new()).is_none());
+ assert!(Received::pushed(Vec::new(), vec![vec![1, 2, 3]]).is_none());
+ }
+
/// Tips are the changes none of the others depend on, sorted as heads are.
#[test]
fn tips_are_what_nothing_depends_on() {
diff --git a/src/swarm/sync.rs b/src/swarm/sync.rs
index 0f607e7..aea6a0b 100644
--- a/src/swarm/sync.rs
+++ b/src/swarm/sync.rs
@@ -471,8 +471,7 @@ pub(crate) async fn handle_sync(
let writer = envelope.writer;
let delivered = envelope.delivered;
// Changes pushed along a tree (`SyncBody::Changes`): their heads and bytes, made
- // into the Automerge message that would have brought them once the document's lock
- // is held (`Received::pushed`).
+ // into the Automerge message that would have brought them (`Received::pushed`).
let (payload, deflated, pushed) = match envelope.body {
transport::SyncBody::Message(payload) => (payload, false, None),
transport::SyncBody::Deflated(payload) => (payload, true, None),
@@ -537,12 +536,13 @@ pub(crate) async fn handle_sync(
} else {
message
};
- let (payload_len, decoded) = match &pushed {
- Some((_, _, len)) => (*len, None),
- None => (
- message.0.len(),
- Some(crate::document::sync::decode(&message)?),
+ let (payload_len, message) = match pushed {
+ Some((heads, changes, len)) => (
+ len,
+ crate::document::sync::Received::pushed(heads, changes)
+ .context("Pushed changes that don't decode")?,
),
+ None => (message.0.len(), crate::document::sync::decode(&message)?),
};
stats.sync_received(payload_len);
let mut replica = doc.blocking_write();
@@ -552,13 +552,6 @@ pub(crate) async fn handle_sync(
if replica.epoch != epoch {
return Ok(None);
}
- let message = match decoded {
- Some(message) => message,
- None => {
- let (heads, changes, _) = pushed.expect("pushed or decoded");
- crate::document::sync::Received::pushed(heads, changes)
- }
- };
let carried = message.carries_changes();
let sizes = if carried { message.sizes() } else { (0, 0) };
if carried {