From eba7038bd241518c835736cb58d16b771577a934 Mon Sep 17 00:00:00 2001 From: Alex Good <alex@memoryandthought.me> Date: Thu, 1 Sep 2022 15:38:19 +0100 Subject: [PATCH] Allow for empty head indices when decoding doc The compressed document format includes at the end of the document chunk the indicies of the heads of the document. Older versions of the javascript implementation do not include these indicies so we allow them to be omitted when decoding. Whilst we're here add some tracing::trace logs to make it easier to understand where parsing is failing. --- automerge/src/automerge.rs | 7 +++++++ automerge/src/storage/chunk.rs | 1 + automerge/src/storage/document.rs | 28 +++++++++++++++++----------- automerge/src/storage/load.rs | 1 + 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/automerge/src/automerge.rs b/automerge/src/automerge.rs index 6c0cd6dd..f48fac6b 100644 --- a/automerge/src/automerge.rs +++ b/automerge/src/automerge.rs @@ -591,13 +591,16 @@ impl Automerge { } /// Load a document. + #[tracing::instrument(skip(data, options), err)] pub fn load_with<Obs: OpObserver>( data: &[u8], mut options: ApplyOptions<'_, Obs>, ) -> Result<Self, AutomergeError> { if data.is_empty() { + tracing::trace!("no data, initializing empty document"); return Ok(Self::new()); } + tracing::trace!("loading first chunk"); let (remaining, first_chunk) = storage::Chunk::parse(storage::parse::Input::new(data)) .map_err(|e| load::Error::Parse(Box::new(e)))?; if !first_chunk.checksum_valid() { @@ -607,6 +610,7 @@ impl Automerge { let mut am = match first_chunk { storage::Chunk::Document(d) => { + tracing::trace!("first chunk is document chunk, inflating"); let storage::load::Reconstructed { max_op, result: op_set, @@ -643,6 +647,7 @@ impl Automerge { } } storage::Chunk::Change(stored_change) => { + tracing::trace!("first chunk is change chunk, applying"); let change = Change::new_from_unverified(stored_change.into_owned(), None) .map_err(|e| load::Error::InvalidChangeColumns(Box::new(e)))?; let mut am = Self::new(); @@ -650,6 +655,7 @@ impl Automerge { am } storage::Chunk::CompressedChange(stored_change, compressed) => { + tracing::trace!("first chunk is compressed change, decompressing and applying"); let change = Change::new_from_unverified( stored_change.into_owned(), Some(compressed.into_owned()), @@ -660,6 +666,7 @@ impl Automerge { am } }; + tracing::trace!("first chunk loaded, loading remaining chunks"); match load::load_changes(remaining.reset()) { load::LoadedChanges::Complete(c) => { for change in c { diff --git a/automerge/src/storage/chunk.rs b/automerge/src/storage/chunk.rs index ad64e804..821c2c55 100644 --- a/automerge/src/storage/chunk.rs +++ b/automerge/src/storage/chunk.rs @@ -56,6 +56,7 @@ impl<'a> Chunk<'a> { first: chunk_input, remaining, } = i.split(header.data_bytes().len()); + tracing::trace!(?header, "parsed chunk header"); let chunk = match header.chunk_type { ChunkType::Change => { let (remaining, change) = diff --git a/automerge/src/storage/document.rs b/automerge/src/storage/document.rs index b9923b7a..500fbe85 100644 --- a/automerge/src/storage/document.rs +++ b/automerge/src/storage/document.rs @@ -135,17 +135,23 @@ impl<'a> Document<'a> { let (i, parse::RangeOf { range: ops, .. }) = parse::range_of(|i| parse::take_n(ops_meta.total_column_len(), i), i)?; - // parse the suffix - let ( - i, - parse::RangeOf { - range: suffix, - value: head_indices, - }, - ) = parse::range_of( - |i| parse::apply_n(heads.len(), parse::leb128_u64::<ParseError>)(i), - i, - )?; + // parse the suffix, which may be empty if this document was produced by an older version + // of the JS automerge implementation + let (i, suffix, head_indices) = if i.is_empty() { + (i, 0..0, Vec::new()) + } else { + let ( + i, + parse::RangeOf { + range: suffix, + value: head_indices, + }, + ) = parse::range_of( + |i| parse::apply_n(heads.len(), parse::leb128_u64::<ParseError>)(i), + i, + )?; + (i, suffix, head_indices) + }; let compression::Decompressed { change_bytes, diff --git a/automerge/src/storage/load.rs b/automerge/src/storage/load.rs index 75732d7c..fe2e8429 100644 --- a/automerge/src/storage/load.rs +++ b/automerge/src/storage/load.rs @@ -80,6 +80,7 @@ fn load_next_change<'a>( } match chunk { storage::Chunk::Document(d) => { + tracing::trace!("loading document chunk"); let Reconstructed { changes: new_changes, ..