automerge-rb 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +175 -0
- data/ext/automerge_ext/automerge_ext.c +1805 -0
- data/ext/automerge_ext/extconf.rb +132 -0
- data/lib/automerge/version.rb +6 -0
- data/lib/automerge.rb +110 -0
- data/rust-toolchain.toml +4 -0
- data/vendor/automerge-rust/Cargo.lock +1909 -0
- data/vendor/automerge-rust/Cargo.toml +15 -0
- data/vendor/automerge-rust/automerge/Cargo.toml +78 -0
- data/vendor/automerge-rust/automerge/README.md +5 -0
- data/vendor/automerge-rust/automerge/benches/load_save.rs +102 -0
- data/vendor/automerge-rust/automerge/benches/map.rs +260 -0
- data/vendor/automerge-rust/automerge/benches/range.rs +37 -0
- data/vendor/automerge-rust/automerge/benches/sync.rs +95 -0
- data/vendor/automerge-rust/automerge/examples/README.md +7 -0
- data/vendor/automerge-rust/automerge/examples/quickstart.rs +57 -0
- data/vendor/automerge-rust/automerge/examples/watch.rs +94 -0
- data/vendor/automerge-rust/automerge/fuzz/Cargo.toml +29 -0
- data/vendor/automerge-rust/automerge/fuzz/fuzz_targets/load.rs +37 -0
- data/vendor/automerge-rust/automerge/src/autocommit.rs +1286 -0
- data/vendor/automerge-rust/automerge/src/automerge/current_state.rs +546 -0
- data/vendor/automerge-rust/automerge/src/automerge/tests.rs +2023 -0
- data/vendor/automerge-rust/automerge/src/automerge.rs +2071 -0
- data/vendor/automerge-rust/automerge/src/autoserde.rs +128 -0
- data/vendor/automerge-rust/automerge/src/change.rs +357 -0
- data/vendor/automerge-rust/automerge/src/change_graph.rs +1215 -0
- data/vendor/automerge-rust/automerge/src/change_queue.rs +46 -0
- data/vendor/automerge-rust/automerge/src/clock.rs +206 -0
- data/vendor/automerge-rust/automerge/src/columnar/column_range/boolean.rs +83 -0
- data/vendor/automerge-rust/automerge/src/columnar/column_range/delta.rs +148 -0
- data/vendor/automerge-rust/automerge/src/columnar/column_range/generic/group.rs +138 -0
- data/vendor/automerge-rust/automerge/src/columnar/column_range/generic/simple.rs +76 -0
- data/vendor/automerge-rust/automerge/src/columnar/column_range/generic.rs +93 -0
- data/vendor/automerge-rust/automerge/src/columnar/column_range/key.rs +272 -0
- data/vendor/automerge-rust/automerge/src/columnar/column_range/obj_id.rs +202 -0
- data/vendor/automerge-rust/automerge/src/columnar/column_range/opid_list.rs +329 -0
- data/vendor/automerge-rust/automerge/src/columnar/column_range/raw.rs +38 -0
- data/vendor/automerge-rust/automerge/src/columnar/column_range/rle.rs +216 -0
- data/vendor/automerge-rust/automerge/src/columnar/column_range/value.rs +547 -0
- data/vendor/automerge-rust/automerge/src/columnar/column_range.rs +17 -0
- data/vendor/automerge-rust/automerge/src/columnar/encoding/boolean.rs +197 -0
- data/vendor/automerge-rust/automerge/src/columnar/encoding/col_error.rs +88 -0
- data/vendor/automerge-rust/automerge/src/columnar/encoding/column_decoder.rs +133 -0
- data/vendor/automerge-rust/automerge/src/columnar/encoding/decodable_impls.rs +175 -0
- data/vendor/automerge-rust/automerge/src/columnar/encoding/delta.rs +96 -0
- data/vendor/automerge-rust/automerge/src/columnar/encoding/encodable_impls.rs +200 -0
- data/vendor/automerge-rust/automerge/src/columnar/encoding/leb128.rs +82 -0
- data/vendor/automerge-rust/automerge/src/columnar/encoding/properties.rs +178 -0
- data/vendor/automerge-rust/automerge/src/columnar/encoding/raw.rs +101 -0
- data/vendor/automerge-rust/automerge/src/columnar/encoding/rle.rs +239 -0
- data/vendor/automerge-rust/automerge/src/columnar/encoding.rs +67 -0
- data/vendor/automerge-rust/automerge/src/columnar/splice_error.rs +47 -0
- data/vendor/automerge-rust/automerge/src/columnar.rs +14 -0
- data/vendor/automerge-rust/automerge/src/convert.rs +112 -0
- data/vendor/automerge-rust/automerge/src/cursor.rs +296 -0
- data/vendor/automerge-rust/automerge/src/decoding.rs +475 -0
- data/vendor/automerge-rust/automerge/src/error.rs +159 -0
- data/vendor/automerge-rust/automerge/src/exid.rs +238 -0
- data/vendor/automerge-rust/automerge/src/hydrate/list.rs +140 -0
- data/vendor/automerge-rust/automerge/src/hydrate/map.rs +132 -0
- data/vendor/automerge-rust/automerge/src/hydrate/tests.rs +40 -0
- data/vendor/automerge-rust/automerge/src/hydrate/text.rs +89 -0
- data/vendor/automerge-rust/automerge/src/hydrate.rs +368 -0
- data/vendor/automerge-rust/automerge/src/indexed_cache.rs +113 -0
- data/vendor/automerge-rust/automerge/src/iter/doc.rs +603 -0
- data/vendor/automerge-rust/automerge/src/iter/keys.rs +93 -0
- data/vendor/automerge-rust/automerge/src/iter/list_range.rs +433 -0
- data/vendor/automerge-rust/automerge/src/iter/map_range.rs +316 -0
- data/vendor/automerge-rust/automerge/src/iter/spans.rs +601 -0
- data/vendor/automerge-rust/automerge/src/iter/tools.rs +427 -0
- data/vendor/automerge-rust/automerge/src/iter/values.rs +36 -0
- data/vendor/automerge-rust/automerge/src/iter.rs +25 -0
- data/vendor/automerge-rust/automerge/src/legacy/mod.rs +364 -0
- data/vendor/automerge-rust/automerge/src/legacy/serde_impls/actor_id.rs +25 -0
- data/vendor/automerge-rust/automerge/src/legacy/serde_impls/change_hash.rs +29 -0
- data/vendor/automerge-rust/automerge/src/legacy/serde_impls/element_id.rs +27 -0
- data/vendor/automerge-rust/automerge/src/legacy/serde_impls/mod.rs +31 -0
- data/vendor/automerge-rust/automerge/src/legacy/serde_impls/object_id.rs +36 -0
- data/vendor/automerge-rust/automerge/src/legacy/serde_impls/op.rs +668 -0
- data/vendor/automerge-rust/automerge/src/legacy/serde_impls/op_type.rs +26 -0
- data/vendor/automerge-rust/automerge/src/legacy/serde_impls/opid.rs +25 -0
- data/vendor/automerge-rust/automerge/src/legacy/serde_impls/scalar_value.rs +63 -0
- data/vendor/automerge-rust/automerge/src/legacy/utility_impls/element_id.rs +66 -0
- data/vendor/automerge-rust/automerge/src/legacy/utility_impls/key.rs +49 -0
- data/vendor/automerge-rust/automerge/src/legacy/utility_impls/mod.rs +4 -0
- data/vendor/automerge-rust/automerge/src/legacy/utility_impls/object_id.rs +74 -0
- data/vendor/automerge-rust/automerge/src/legacy/utility_impls/opid.rs +68 -0
- data/vendor/automerge-rust/automerge/src/lib.rs +315 -0
- data/vendor/automerge-rust/automerge/src/marks.rs +478 -0
- data/vendor/automerge-rust/automerge/src/op_set2/change/batch.rs +2002 -0
- data/vendor/automerge-rust/automerge/src/op_set2/change/collector.rs +974 -0
- data/vendor/automerge-rust/automerge/src/op_set2/change.rs +332 -0
- data/vendor/automerge-rust/automerge/src/op_set2/columns.rs +714 -0
- data/vendor/automerge-rust/automerge/src/op_set2/meta.rs +174 -0
- data/vendor/automerge-rust/automerge/src/op_set2/op.rs +1363 -0
- data/vendor/automerge-rust/automerge/src/op_set2/op_set/elems.rs +43 -0
- data/vendor/automerge-rust/automerge/src/op_set2/op_set/found_op.rs +60 -0
- data/vendor/automerge-rust/automerge/src/op_set2/op_set/index.rs +197 -0
- data/vendor/automerge-rust/automerge/src/op_set2/op_set/insert.rs +179 -0
- data/vendor/automerge-rust/automerge/src/op_set2/op_set/mark_index.rs +292 -0
- data/vendor/automerge-rust/automerge/src/op_set2/op_set/marks.rs +86 -0
- data/vendor/automerge-rust/automerge/src/op_set2/op_set/op_iter.rs +1295 -0
- data/vendor/automerge-rust/automerge/src/op_set2/op_set/op_query.rs +82 -0
- data/vendor/automerge-rust/automerge/src/op_set2/op_set/top_op.rs +50 -0
- data/vendor/automerge-rust/automerge/src/op_set2/op_set/visible.rs +290 -0
- data/vendor/automerge-rust/automerge/src/op_set2/op_set.rs +1793 -0
- data/vendor/automerge-rust/automerge/src/op_set2/parents.rs +133 -0
- data/vendor/automerge-rust/automerge/src/op_set2/skip_list.rs +714 -0
- data/vendor/automerge-rust/automerge/src/op_set2/types.rs +769 -0
- data/vendor/automerge-rust/automerge/src/op_set2.rs +18 -0
- data/vendor/automerge-rust/automerge/src/patches/patch.rs +95 -0
- data/vendor/automerge-rust/automerge/src/patches/patch_builder.rs +382 -0
- data/vendor/automerge-rust/automerge/src/patches/patch_log.rs +584 -0
- data/vendor/automerge-rust/automerge/src/patches.rs +7 -0
- data/vendor/automerge-rust/automerge/src/query/list_state.rs +230 -0
- data/vendor/automerge-rust/automerge/src/query/seek_mark.rs +142 -0
- data/vendor/automerge-rust/automerge/src/read.rs +326 -0
- data/vendor/automerge-rust/automerge/src/sequence_tree.rs +662 -0
- data/vendor/automerge-rust/automerge/src/storage/bundle/builder.rs +942 -0
- data/vendor/automerge-rust/automerge/src/storage/bundle/error.rs +42 -0
- data/vendor/automerge-rust/automerge/src/storage/bundle/meta.rs +23 -0
- data/vendor/automerge-rust/automerge/src/storage/bundle/storage.rs +146 -0
- data/vendor/automerge-rust/automerge/src/storage/bundle.rs +210 -0
- data/vendor/automerge-rust/automerge/src/storage/change/change_actors.rs +311 -0
- data/vendor/automerge-rust/automerge/src/storage/change/change_op_columns.rs +621 -0
- data/vendor/automerge-rust/automerge/src/storage/change/compressed.rs +51 -0
- data/vendor/automerge-rust/automerge/src/storage/change/op_with_change_actors.rs +1 -0
- data/vendor/automerge-rust/automerge/src/storage/change.rs +523 -0
- data/vendor/automerge-rust/automerge/src/storage/chunk.rs +312 -0
- data/vendor/automerge-rust/automerge/src/storage/columns/column.rs +42 -0
- data/vendor/automerge-rust/automerge/src/storage/columns/column_builder.rs +199 -0
- data/vendor/automerge-rust/automerge/src/storage/columns/column_specification.rs +340 -0
- data/vendor/automerge-rust/automerge/src/storage/columns/raw_column.rs +286 -0
- data/vendor/automerge-rust/automerge/src/storage/columns.rs +355 -0
- data/vendor/automerge-rust/automerge/src/storage/document/compression.rs +362 -0
- data/vendor/automerge-rust/automerge/src/storage/document.rs +411 -0
- data/vendor/automerge-rust/automerge/src/storage/load/change_collector.rs +15 -0
- data/vendor/automerge-rust/automerge/src/storage/load.rs +136 -0
- data/vendor/automerge-rust/automerge/src/storage/parse/leb128.rs +302 -0
- data/vendor/automerge-rust/automerge/src/storage/parse.rs +619 -0
- data/vendor/automerge-rust/automerge/src/storage/save/document.rs +27 -0
- data/vendor/automerge-rust/automerge/src/storage.rs +26 -0
- data/vendor/automerge-rust/automerge/src/sync/bloom.rs +161 -0
- data/vendor/automerge-rust/automerge/src/sync/message_builder.rs +118 -0
- data/vendor/automerge-rust/automerge/src/sync/state.rs +214 -0
- data/vendor/automerge-rust/automerge/src/sync/v1_compat_test/bloom.rs +162 -0
- data/vendor/automerge-rust/automerge/src/sync/v1_compat_test/mod.rs +625 -0
- data/vendor/automerge-rust/automerge/src/sync/v1_compat_test/state.rs +120 -0
- data/vendor/automerge-rust/automerge/src/sync.rs +2482 -0
- data/vendor/automerge-rust/automerge/src/text_diff/LICENSE +201 -0
- data/vendor/automerge-rust/automerge/src/text_diff/myers.rs +332 -0
- data/vendor/automerge-rust/automerge/src/text_diff/replace.rs +139 -0
- data/vendor/automerge-rust/automerge/src/text_diff/utils.rs +119 -0
- data/vendor/automerge-rust/automerge/src/text_diff.rs +515 -0
- data/vendor/automerge-rust/automerge/src/text_value.rs +276 -0
- data/vendor/automerge-rust/automerge/src/transaction/commit.rs +34 -0
- data/vendor/automerge-rust/automerge/src/transaction/inner.rs +1403 -0
- data/vendor/automerge-rust/automerge/src/transaction/manual_transaction.rs +147 -0
- data/vendor/automerge-rust/automerge/src/transaction/owned_transaction.rs +266 -0
- data/vendor/automerge-rust/automerge/src/transaction/result.rs +21 -0
- data/vendor/automerge-rust/automerge/src/transaction/transactable.rs +203 -0
- data/vendor/automerge-rust/automerge/src/transaction.rs +513 -0
- data/vendor/automerge-rust/automerge/src/types.rs +749 -0
- data/vendor/automerge-rust/automerge/src/validation.rs +29 -0
- data/vendor/automerge-rust/automerge/src/value.rs +763 -0
- data/vendor/automerge-rust/automerge/tests/batch_insert.rs +1034 -0
- data/vendor/automerge-rust/automerge/tests/block_tests.rs +887 -0
- data/vendor/automerge-rust/automerge/tests/convert_string_to_text.rs +72 -0
- data/vendor/automerge-rust/automerge/tests/diff_marks.rs +1508 -0
- data/vendor/automerge-rust/automerge/tests/fixtures/64bit_obj_id_change.automerge +0 -0
- data/vendor/automerge-rust/automerge/tests/fixtures/64bit_obj_id_doc.automerge +0 -0
- data/vendor/automerge-rust/automerge/tests/fixtures/counter_value_has_incorrect_meta.automerge +0 -0
- data/vendor/automerge-rust/automerge/tests/fixtures/counter_value_is_ok.automerge +0 -0
- data/vendor/automerge-rust/automerge/tests/fixtures/counter_value_is_overlong.automerge +0 -0
- data/vendor/automerge-rust/automerge/tests/fixtures/two_change_chunks.automerge +0 -0
- data/vendor/automerge-rust/automerge/tests/fixtures/two_change_chunks_compressed.automerge +0 -0
- data/vendor/automerge-rust/automerge/tests/fixtures/two_change_chunks_out_of_order.automerge +0 -0
- data/vendor/automerge-rust/automerge/tests/fuzz-crashers/action-is-48.automerge +0 -0
- data/vendor/automerge-rust/automerge/tests/fuzz-crashers/crash-da39a3ee5e6b4b0d3255bfef95601890afd80709 +0 -0
- data/vendor/automerge-rust/automerge/tests/fuzz-crashers/incorrect_max_op.automerge +0 -0
- data/vendor/automerge-rust/automerge/tests/fuzz-crashers/invalid_deflate_stream.automerge +0 -0
- data/vendor/automerge-rust/automerge/tests/fuzz-crashers/missing_actor.automerge +0 -0
- data/vendor/automerge-rust/automerge/tests/fuzz-crashers/overflow_in_length.automerge +0 -0
- data/vendor/automerge-rust/automerge/tests/fuzz-crashers/too_many_deps.automerge +0 -0
- data/vendor/automerge-rust/automerge/tests/fuzz-crashers/too_many_ops.automerge +0 -0
- data/vendor/automerge-rust/automerge/tests/test.rs +2668 -0
- data/vendor/automerge-rust/automerge/tests/test_mark_patches.rs +41 -0
- data/vendor/automerge-rust/automerge/tests/test_save_load_orphans.rs +84 -0
- data/vendor/automerge-rust/automerge/tests/text.rs +1098 -0
- data/vendor/automerge-rust/automerge/tests/text_encoding.rs +640 -0
- data/vendor/automerge-rust/automerge-c/CMakeLists.txt +439 -0
- data/vendor/automerge-rust/automerge-c/Cargo.toml +22 -0
- data/vendor/automerge-rust/automerge-c/README.md +233 -0
- data/vendor/automerge-rust/automerge-c/build.rs +21 -0
- data/vendor/automerge-rust/automerge-c/cbindgen.toml +48 -0
- data/vendor/automerge-rust/automerge-c/cmake/Cargo.toml.in +22 -0
- data/vendor/automerge-rust/automerge-c/cmake/automerge-c-config.cmake.in +99 -0
- data/vendor/automerge-rust/automerge-c/cmake/cbindgen.toml.in +48 -0
- data/vendor/automerge-rust/automerge-c/cmake/config.h.in +58 -0
- data/vendor/automerge-rust/automerge-c/cmake/enum-string-functions-gen.cmake +183 -0
- data/vendor/automerge-rust/automerge-c/cmake/file-regex-replace.cmake +33 -0
- data/vendor/automerge-rust/automerge-c/cmake/file-touch.cmake +35 -0
- data/vendor/automerge-rust/automerge-c/docs/CMakeLists.txt +39 -0
- data/vendor/automerge-rust/automerge-c/docs/img/brandmark.png +0 -0
- data/vendor/automerge-rust/automerge-c/examples/CMakeLists.txt +42 -0
- data/vendor/automerge-rust/automerge-c/examples/README.md +9 -0
- data/vendor/automerge-rust/automerge-c/examples/quickstart.c +131 -0
- data/vendor/automerge-rust/automerge-c/include/automerge-c/utils/result.h +30 -0
- data/vendor/automerge-rust/automerge-c/include/automerge-c/utils/stack.h +130 -0
- data/vendor/automerge-rust/automerge-c/include/automerge-c/utils/stack_callback_data.h +53 -0
- data/vendor/automerge-rust/automerge-c/include/automerge-c/utils/string.h +29 -0
- data/vendor/automerge-rust/automerge-c/src/actor_id.rs +193 -0
- data/vendor/automerge-rust/automerge-c/src/byte_span.rs +227 -0
- data/vendor/automerge-rust/automerge-c/src/change.rs +356 -0
- data/vendor/automerge-rust/automerge-c/src/cursor.rs +168 -0
- data/vendor/automerge-rust/automerge-c/src/doc/list.rs +636 -0
- data/vendor/automerge-rust/automerge-c/src/doc/map.rs +556 -0
- data/vendor/automerge-rust/automerge-c/src/doc/mark.rs +296 -0
- data/vendor/automerge-rust/automerge-c/src/doc/utils.rs +46 -0
- data/vendor/automerge-rust/automerge-c/src/doc.rs +1009 -0
- data/vendor/automerge-rust/automerge-c/src/index.rs +84 -0
- data/vendor/automerge-rust/automerge-c/src/item.rs +2177 -0
- data/vendor/automerge-rust/automerge-c/src/items.rs +401 -0
- data/vendor/automerge-rust/automerge-c/src/lib.rs +11 -0
- data/vendor/automerge-rust/automerge-c/src/obj.rs +216 -0
- data/vendor/automerge-rust/automerge-c/src/result.rs +653 -0
- data/vendor/automerge-rust/automerge-c/src/sync/have.rs +42 -0
- data/vendor/automerge-rust/automerge-c/src/sync/message.rs +146 -0
- data/vendor/automerge-rust/automerge-c/src/sync/state.rs +262 -0
- data/vendor/automerge-rust/automerge-c/src/sync.rs +7 -0
- data/vendor/automerge-rust/automerge-c/src/utils/result.c +33 -0
- data/vendor/automerge-rust/automerge-c/src/utils/stack.c +106 -0
- data/vendor/automerge-rust/automerge-c/src/utils/stack_callback_data.c +9 -0
- data/vendor/automerge-rust/automerge-c/src/utils/string.c +46 -0
- data/vendor/automerge-rust/automerge-c/test/CMakeLists.txt +67 -0
- data/vendor/automerge-rust/automerge-c/test/actor_id_tests.c +140 -0
- data/vendor/automerge-rust/automerge-c/test/base_state.c +17 -0
- data/vendor/automerge-rust/automerge-c/test/base_state.h +39 -0
- data/vendor/automerge-rust/automerge-c/test/byte_span_tests.c +119 -0
- data/vendor/automerge-rust/automerge-c/test/cmocka_utils.c +91 -0
- data/vendor/automerge-rust/automerge-c/test/cmocka_utils.h +42 -0
- data/vendor/automerge-rust/automerge-c/test/cursor_tests.c +263 -0
- data/vendor/automerge-rust/automerge-c/test/doc_state.c +27 -0
- data/vendor/automerge-rust/automerge-c/test/doc_state.h +17 -0
- data/vendor/automerge-rust/automerge-c/test/doc_tests.c +335 -0
- data/vendor/automerge-rust/automerge-c/test/enum_string_tests.c +148 -0
- data/vendor/automerge-rust/automerge-c/test/files/brave-ape-49.automerge +0 -0
- data/vendor/automerge-rust/automerge-c/test/item_tests.c +313 -0
- data/vendor/automerge-rust/automerge-c/test/list_tests.c +544 -0
- data/vendor/automerge-rust/automerge-c/test/macro_utils.c +38 -0
- data/vendor/automerge-rust/automerge-c/test/macro_utils.h +23 -0
- data/vendor/automerge-rust/automerge-c/test/main.c +33 -0
- data/vendor/automerge-rust/automerge-c/test/map_tests.c +1610 -0
- data/vendor/automerge-rust/automerge-c/test/mark_tests.c +124 -0
- data/vendor/automerge-rust/automerge-c/test/ported_wasm/basic_tests.c +1642 -0
- data/vendor/automerge-rust/automerge-c/test/ported_wasm/cursor_tests.c +108 -0
- data/vendor/automerge-rust/automerge-c/test/ported_wasm/suite.c +17 -0
- data/vendor/automerge-rust/automerge-c/test/ported_wasm/sync_tests.c +1280 -0
- data/vendor/automerge-rust/automerge-c/test/str_utils.c +15 -0
- data/vendor/automerge-rust/automerge-c/test/str_utils.h +17 -0
- data/vendor/automerge-rust/automerge-test/Cargo.toml +17 -0
- data/vendor/automerge-rust/automerge-test/README.md +3 -0
- data/vendor/automerge-rust/automerge-test/src/lib.rs +487 -0
- data/vendor/automerge-rust/hexane/CHANGELOG.md +34 -0
- data/vendor/automerge-rust/hexane/Cargo.toml +47 -0
- data/vendor/automerge-rust/hexane/README.md +292 -0
- data/vendor/automerge-rust/hexane/RESULTS.txt +20 -0
- data/vendor/automerge-rust/hexane/TODO +18 -0
- data/vendor/automerge-rust/hexane/benches/insert.rs +82 -0
- data/vendor/automerge-rust/hexane/benches/seek.rs +77 -0
- data/vendor/automerge-rust/hexane/benches/splice.rs +82 -0
- data/vendor/automerge-rust/hexane/src/aggregate.rs +288 -0
- data/vendor/automerge-rust/hexane/src/boolean.rs +478 -0
- data/vendor/automerge-rust/hexane/src/columndata.rs +2540 -0
- data/vendor/automerge-rust/hexane/src/cursor.rs +756 -0
- data/vendor/automerge-rust/hexane/src/delta.rs +793 -0
- data/vendor/automerge-rust/hexane/src/encoder.rs +639 -0
- data/vendor/automerge-rust/hexane/src/leb128.rs +82 -0
- data/vendor/automerge-rust/hexane/src/lib.rs +95 -0
- data/vendor/automerge-rust/hexane/src/pack.rs +325 -0
- data/vendor/automerge-rust/hexane/src/raw.rs +314 -0
- data/vendor/automerge-rust/hexane/src/rle.rs +928 -0
- data/vendor/automerge-rust/hexane/src/slab/tree.rs +1373 -0
- data/vendor/automerge-rust/hexane/src/slab/writer.rs +535 -0
- data/vendor/automerge-rust/hexane/src/slab.rs +224 -0
- data/vendor/automerge-rust/hexane/src/test.rs +108 -0
- data/vendor/bundle/ruby/3.3.0/bin/rake +29 -0
- data/vendor/bundle/ruby/3.3.0/bin/rake-compiler +29 -0
- data/vendor/bundle/ruby/3.3.0/bin/rake-compiler-dock +29 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/History.rdoc +1732 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/Manifest.txt +32 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/README.rdoc +845 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/Rakefile +97 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/design_rationale.rb +54 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/lib/hoe/minitest.rb +30 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/lib/minitest/assertions.rb +850 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/lib/minitest/autorun.rb +6 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/lib/minitest/benchmark.rb +452 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/lib/minitest/compress.rb +94 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/lib/minitest/error_on_warning.rb +11 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/lib/minitest/expectations.rb +321 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/lib/minitest/hell.rb +11 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/lib/minitest/manual_plugins.rb +16 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/lib/minitest/mock.rb +327 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/lib/minitest/parallel.rb +72 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/lib/minitest/pride.rb +4 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/lib/minitest/pride_plugin.rb +135 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/lib/minitest/spec.rb +353 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/lib/minitest/test.rb +238 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/lib/minitest/test_task.rb +324 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/lib/minitest/unit.rb +42 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/lib/minitest.rb +1250 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/test/minitest/metametameta.rb +150 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/test/minitest/test_minitest_assertions.rb +1677 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/test/minitest/test_minitest_benchmark.rb +137 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/test/minitest/test_minitest_mock.rb +1213 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/test/minitest/test_minitest_reporter.rb +437 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/test/minitest/test_minitest_spec.rb +1159 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/test/minitest/test_minitest_test.rb +1374 -0
- data/vendor/bundle/ruby/3.3.0/gems/minitest-5.27.0/test/minitest/test_minitest_test_task.rb +57 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/History.rdoc +2454 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/MIT-LICENSE +21 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/README.rdoc +155 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/doc/command_line_usage.rdoc +171 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/doc/example/Rakefile1 +38 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/doc/example/Rakefile2 +35 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/doc/example/a.c +6 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/doc/example/b.c +6 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/doc/example/main.c +11 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/doc/glossary.rdoc +42 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/doc/jamis.rb +592 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/doc/proto_rake.rdoc +127 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/doc/rake.1 +156 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/doc/rakefile.rdoc +635 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/doc/rational.rdoc +151 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/exe/rake +27 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/application.rb +847 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/backtrace.rb +25 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/clean.rb +78 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/cloneable.rb +17 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/cpu_counter.rb +122 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/default_loader.rb +15 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/dsl_definition.rb +196 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/early_time.rb +22 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/ext/core.rb +26 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/ext/string.rb +176 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/file_creation_task.rb +25 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/file_list.rb +435 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/file_task.rb +58 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/file_utils.rb +137 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/file_utils_ext.rb +135 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/invocation_chain.rb +57 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/invocation_exception_mixin.rb +17 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/late_time.rb +18 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/linked_list.rb +112 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/loaders/makefile.rb +54 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/multi_task.rb +14 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/name_space.rb +38 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/options.rb +31 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/packagetask.rb +222 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/phony.rb +16 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/private_reader.rb +21 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/promise.rb +100 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/pseudo_status.rb +30 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/rake_module.rb +67 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/rake_test_loader.rb +27 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/rule_recursion_overflow_error.rb +20 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/scope.rb +43 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/task.rb +434 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/task_argument_error.rb +8 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/task_arguments.rb +113 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/task_manager.rb +333 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/tasklib.rb +12 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/testtask.rb +192 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/thread_history_display.rb +49 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/thread_pool.rb +157 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/trace_output.rb +23 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/version.rb +10 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake/win32.rb +17 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/lib/rake.rb +69 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-13.4.2/rake.gemspec +102 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/Gemfile +8 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/History.md +695 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/LICENSE.txt +20 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/README.md +476 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/Rakefile +15 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/appveyor.yml +22 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/bin/rake-compiler +24 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/cucumber.yml +4 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/features/compile.feature +79 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/features/cross-compile.feature +23 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/features/cross-package-multi.feature +15 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/features/cross-package.feature +14 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/features/java-compile.feature +22 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/features/java-no-native-compile.feature +33 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/features/java-package.feature +24 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/features/package.feature +40 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/features/step_definitions/compilation.rb +70 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/features/step_definitions/cross_compilation.rb +27 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/features/step_definitions/execution.rb +52 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/features/step_definitions/folders.rb +32 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/features/step_definitions/gem.rb +46 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/features/step_definitions/java_compilation.rb +7 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/features/support/env.rb +10 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/features/support/file_template_helpers.rb +137 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/features/support/generator_helpers.rb +123 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/features/support/platform_extension_helpers.rb +27 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/lib/rake/baseextensiontask.rb +90 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/lib/rake/compiler_config.rb +38 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/lib/rake/extensioncompiler.rb +51 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/lib/rake/extensiontask.rb +589 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/lib/rake/javaextensiontask.rb +321 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/tasks/bin/cross-ruby.rake +189 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/tasks/bootstrap.rake +11 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/tasks/common.rake +10 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/tasks/cucumber.rake +23 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/tasks/gem.rake +15 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-1.3.1/tasks/rspec.rake +9 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/CHANGELOG.md +446 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/CONTRIBUTING.md +109 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/Dockerfile.jruby +79 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/Dockerfile.mri.erb +282 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/Gemfile +8 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/LICENSE.txt +22 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/README.md +447 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/Rakefile +246 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/bin/rake-compiler-dock +18 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/build/buildkitd.toml +2 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/build/gem_helper.rb +54 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/build/mk_i686.rb +18 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/build/mk_musl_cross.sh +37 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/build/mk_osxcross.sh +45 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/build/mk_pkg_config.sh +24 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/build/parallel_docker_build.rb +169 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/build/patches/rake-compiler-1.3.1/0004-Enable-build-of-static-libruby.patch +38 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/build/patches/rake-compiler-1.3.1/0005-build-miniruby-first.patch +16 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/build/patches/rake-compiler-1.3.1/0006-ruby-4-rubyspec-capiext.patch +16 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/build/rcd-env.sh +6 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/build/runas +7 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/build/sigfw.c +45 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/build/strip_wrapper_codesign +17 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/build/strip_wrapper_vbox +30 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/build/sudoers +1 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/lib/rake_compiler_dock/colors.rb +43 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/lib/rake_compiler_dock/docker_check.rb +356 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/lib/rake_compiler_dock/predefined_user_group.rb +5 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/lib/rake_compiler_dock/starter.rb +206 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/lib/rake_compiler_dock/version.rb +4 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/lib/rake_compiler_dock.rb +151 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/mingw64-ucrt/Dockerfile +66 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/mingw64-ucrt/README.md +14 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/mingw64-ucrt/binutils-mingw-w64-ignore-check-errors.patch +13 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/mingw64-ucrt/gcc-mingw-w64-only-c-c++.patch +13 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/mingw64-ucrt/mingw-w64-enable-ucrt.patch +22 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/rake-compiler-dock.gemspec +34 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/test/env/Dockerfile.alpine +17 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/test/env/Dockerfile.debian +24 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/test/fixtures/mig_test_rpc.defs +8 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/test/rcd_test/Gemfile +11 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/test/rcd_test/Rakefile +97 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/test/rcd_test/ext/java/RcdTestExtService.java +19 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/test/rcd_test/ext/java/RubyRcdTest.java +16 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/test/rcd_test/ext/mri/extconf.rb +111 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/test/rcd_test/ext/mri/rcd_test_ext.c +65 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/test/rcd_test/ext/mri/rcd_test_ext.h +11 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/test/rcd_test/lib/rcd_test.rb +6 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/test/rcd_test/rcd_test.gemspec +28 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/test/rcd_test/test/test_basic.rb +49 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/test/test_environment_variables.rb +108 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/test/test_mig.rb +18 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/test/test_parallel_docker_build.rb +95 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/test/test_rubygems_plugins.rb +12 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/test/test_starter.rb +158 -0
- data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.12.0/test/test_versions.rb +82 -0
- data/vendor/bundle/ruby/3.3.0/specifications/minitest-5.27.0.gemspec +32 -0
- data/vendor/bundle/ruby/3.3.0/specifications/rake-13.4.2.gemspec +26 -0
- data/vendor/bundle/ruby/3.3.0/specifications/rake-compiler-1.3.1.gemspec +33 -0
- data/vendor/bundle/ruby/3.3.0/specifications/rake-compiler-dock-1.12.0.gemspec +28 -0
- metadata +584 -0
|
@@ -0,0 +1,2482 @@
|
|
|
1
|
+
//! # Sync Protocol
|
|
2
|
+
//!
|
|
3
|
+
//! The sync protocol is based on this paper:
|
|
4
|
+
//! <https://arxiv.org/abs/2012.00472>, it assumes a reliable in-order stream
|
|
5
|
+
//! between two peers who are synchronizing a document.
|
|
6
|
+
//!
|
|
7
|
+
//! Each peer maintains a [`State`] for each peer they are synchronizing with.
|
|
8
|
+
//! This state tracks things like what the heads of the other peer are and
|
|
9
|
+
//! whether there are in-flight messages. Anything which implements [`SyncDoc`]
|
|
10
|
+
//! can take part in the sync protocol. The flow goes something like this:
|
|
11
|
+
//!
|
|
12
|
+
//! * The initiating peer creates an empty [`State`] and then calls
|
|
13
|
+
//! [`SyncDoc::generate_sync_message()`] to generate new sync message and sends
|
|
14
|
+
//! it to the receiving peer.
|
|
15
|
+
//! * The receiving peer receives a message from the initiator, creates a new
|
|
16
|
+
//! [`State`], and calls [`SyncDoc::receive_sync_message()`] on it's view of the
|
|
17
|
+
//! document
|
|
18
|
+
//! * The receiving peer then calls [`SyncDoc::generate_sync_message()`] to generate
|
|
19
|
+
//! a new sync message and send it back to the initiator
|
|
20
|
+
//! * From this point on each peer operates in a loop, receiving a sync message
|
|
21
|
+
//! from the other peer and then generating a new message to send back.
|
|
22
|
+
//!
|
|
23
|
+
//! ## Example
|
|
24
|
+
//!
|
|
25
|
+
//! ```
|
|
26
|
+
//! use automerge::{transaction::Transactable, sync::{self, SyncDoc}, ReadDoc};
|
|
27
|
+
//! # fn main() -> Result<(), automerge::AutomergeError> {
|
|
28
|
+
//! // Create a document on peer1
|
|
29
|
+
//! let mut peer1 = automerge::AutoCommit::new();
|
|
30
|
+
//! peer1.put(automerge::ROOT, "key", "value")?;
|
|
31
|
+
//!
|
|
32
|
+
//! // Create a state to track our sync with peer2
|
|
33
|
+
//! let mut peer1_state = sync::State::new();
|
|
34
|
+
//! // Generate the initial message to send to peer2, unwrap for brevity
|
|
35
|
+
//! let message1to2 = peer1.sync().generate_sync_message(&mut peer1_state).unwrap();
|
|
36
|
+
//!
|
|
37
|
+
//! // We receive the message on peer2. We don't have a document at all yet
|
|
38
|
+
//! // so we create one
|
|
39
|
+
//! let mut peer2 = automerge::AutoCommit::new();
|
|
40
|
+
//! // We don't have a state for peer1 (it's a new connection), so we create one
|
|
41
|
+
//! let mut peer2_state = sync::State::new();
|
|
42
|
+
//! // Now receive the message from peer 1
|
|
43
|
+
//! peer2.sync().receive_sync_message(&mut peer2_state, message1to2)?;
|
|
44
|
+
//!
|
|
45
|
+
//! // Now we loop, sending messages from one to two and two to one until
|
|
46
|
+
//! // neither has anything new to send
|
|
47
|
+
//!
|
|
48
|
+
//! loop {
|
|
49
|
+
//! let two_to_one = peer2.sync().generate_sync_message(&mut peer2_state);
|
|
50
|
+
//! if let Some(message) = two_to_one.as_ref() {
|
|
51
|
+
//! println!("two to one");
|
|
52
|
+
//! peer1.sync().receive_sync_message(&mut peer1_state, message.clone())?;
|
|
53
|
+
//! }
|
|
54
|
+
//! let one_to_two = peer1.sync().generate_sync_message(&mut peer1_state);
|
|
55
|
+
//! if let Some(message) = one_to_two.as_ref() {
|
|
56
|
+
//! println!("one to two");
|
|
57
|
+
//! peer2.sync().receive_sync_message(&mut peer2_state, message.clone())?;
|
|
58
|
+
//! }
|
|
59
|
+
//! if two_to_one.is_none() && one_to_two.is_none() {
|
|
60
|
+
//! break;
|
|
61
|
+
//! }
|
|
62
|
+
//! }
|
|
63
|
+
//!
|
|
64
|
+
//! assert_eq!(peer2.get(automerge::ROOT, "key")?.unwrap().0.to_str(), Some("value"));
|
|
65
|
+
//!
|
|
66
|
+
//! # Ok(())
|
|
67
|
+
//! # }
|
|
68
|
+
//! ```
|
|
69
|
+
|
|
70
|
+
use itertools::Itertools;
|
|
71
|
+
use serde::ser::SerializeMap;
|
|
72
|
+
use std::collections::{HashMap, HashSet};
|
|
73
|
+
|
|
74
|
+
use crate::{
|
|
75
|
+
patches::PatchLog,
|
|
76
|
+
storage::{parse, ReadChangeOpError},
|
|
77
|
+
Automerge, AutomergeError, ChangeHash, ReadDoc,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
mod bloom;
|
|
81
|
+
mod message_builder;
|
|
82
|
+
mod state;
|
|
83
|
+
use message_builder::MessageBuilder;
|
|
84
|
+
|
|
85
|
+
#[cfg(test)]
|
|
86
|
+
mod v1_compat_test;
|
|
87
|
+
|
|
88
|
+
pub use bloom::{BloomFilter, DecodeError as DecodeBloomError};
|
|
89
|
+
pub use state::DecodeError as DecodeStateError;
|
|
90
|
+
pub use state::{Have, State};
|
|
91
|
+
|
|
92
|
+
/// A document which can take part in the sync protocol
|
|
93
|
+
///
|
|
94
|
+
/// See the [module level documentation](crate::sync) for more details.
|
|
95
|
+
pub trait SyncDoc {
|
|
96
|
+
/// Generate a sync message for the remote peer represented by `sync_state`
|
|
97
|
+
///
|
|
98
|
+
/// If this returns [`None`] then there are no new messages to send, either because we are
|
|
99
|
+
/// waiting for an acknolwedgement of an in-flight message, or because the remote is up to
|
|
100
|
+
/// date.
|
|
101
|
+
///
|
|
102
|
+
/// * `sync_state` - The [`State`] for this document and the remote peer
|
|
103
|
+
/// * `message` - The [`Message`] to receive
|
|
104
|
+
/// * `patch_log` - A [`PatchLog`] which will be updated with any changes that are made to the current state of the document due to the received sync message
|
|
105
|
+
fn generate_sync_message(&self, sync_state: &mut State) -> Option<Message>;
|
|
106
|
+
|
|
107
|
+
/// Apply a received sync message to this document and `sync_state`
|
|
108
|
+
fn receive_sync_message(
|
|
109
|
+
&mut self,
|
|
110
|
+
sync_state: &mut State,
|
|
111
|
+
message: Message,
|
|
112
|
+
) -> Result<(), AutomergeError>;
|
|
113
|
+
|
|
114
|
+
/// Apply a received sync message to this document and `sync_state`, logging any changes that
|
|
115
|
+
/// are made to `patch_log`
|
|
116
|
+
///
|
|
117
|
+
/// If this returns [`None`] then there are no new messages to send, either because we are
|
|
118
|
+
/// waiting for an acknolwedgement of an in-flight message, or because the remote is up to
|
|
119
|
+
/// date.
|
|
120
|
+
///
|
|
121
|
+
/// # Arguments
|
|
122
|
+
///
|
|
123
|
+
/// * `sync_state` - The [`State`] for this document and the remote peer
|
|
124
|
+
/// * `message` - The [`Message`] to receive
|
|
125
|
+
/// * `patch_log` - A [`PatchLog`] which will be updated with any changes that are made to the current state of the document due to the received sync message
|
|
126
|
+
fn receive_sync_message_log_patches(
|
|
127
|
+
&mut self,
|
|
128
|
+
sync_state: &mut State,
|
|
129
|
+
message: Message,
|
|
130
|
+
patch_log: &mut PatchLog,
|
|
131
|
+
) -> Result<(), AutomergeError>;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const MESSAGE_TYPE_SYNC: u8 = 0x42; // first byte of a sync message, for identification
|
|
135
|
+
const MESSAGE_TYPE_SYNC_V2: u8 = 0x43; // first byte of a sync message, for identification
|
|
136
|
+
|
|
137
|
+
#[derive(Clone, Debug, PartialEq)]
|
|
138
|
+
pub enum MessageVersion {
|
|
139
|
+
V1,
|
|
140
|
+
V2,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
impl MessageVersion {
|
|
144
|
+
fn parse(input: parse::Input<'_>) -> parse::ParseResult<'_, Self, ReadMessageError> {
|
|
145
|
+
let (i, first_byte) = parse::take1(input)?;
|
|
146
|
+
match first_byte {
|
|
147
|
+
MESSAGE_TYPE_SYNC => Ok((i, Self::V1)),
|
|
148
|
+
MESSAGE_TYPE_SYNC_V2 => Ok((i, Self::V2)),
|
|
149
|
+
_ => Err(parse::ParseError::Error(ReadMessageError::WrongType {
|
|
150
|
+
expected_one_of: vec![MESSAGE_TYPE_SYNC, MESSAGE_TYPE_SYNC_V2],
|
|
151
|
+
found: first_byte,
|
|
152
|
+
})),
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
fn encode(&self) -> u8 {
|
|
157
|
+
match self {
|
|
158
|
+
Self::V1 => MESSAGE_TYPE_SYNC,
|
|
159
|
+
Self::V2 => MESSAGE_TYPE_SYNC_V2,
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
impl SyncDoc for Automerge {
|
|
165
|
+
fn generate_sync_message(&self, sync_state: &mut State) -> Option<Message> {
|
|
166
|
+
let our_heads = self.get_heads();
|
|
167
|
+
|
|
168
|
+
let our_need = if sync_state.read_only {
|
|
169
|
+
vec![]
|
|
170
|
+
} else {
|
|
171
|
+
self.get_missing_deps(sync_state.their_heads.as_ref().unwrap_or(&vec![]))
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
let their_heads_set = if let Some(ref heads) = sync_state.their_heads {
|
|
175
|
+
heads.iter().collect::<HashSet<_>>()
|
|
176
|
+
} else {
|
|
177
|
+
HashSet::new()
|
|
178
|
+
};
|
|
179
|
+
let our_have = if our_need.iter().all(|hash| their_heads_set.contains(hash)) {
|
|
180
|
+
vec![self.make_bloom_filter(sync_state.shared_heads.clone())]
|
|
181
|
+
} else {
|
|
182
|
+
Vec::new()
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
if let Some(ref their_have) = sync_state.their_have {
|
|
186
|
+
if let Some(first_have) = their_have.first().as_ref() {
|
|
187
|
+
if !first_have
|
|
188
|
+
.last_sync
|
|
189
|
+
.iter()
|
|
190
|
+
.all(|hash| self.has_change(hash))
|
|
191
|
+
{
|
|
192
|
+
return Some(Message::reset(our_heads));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let message_builder = if sync_state.is_peer_read_only() {
|
|
198
|
+
// The remote peer is read-only and will ignore incoming changes.
|
|
199
|
+
// Skip computing and sending changes to save bandwidth.
|
|
200
|
+
MessageBuilder::new(vec![], sync_state)
|
|
201
|
+
} else if let Some((their_have, their_need)) = sync_state.their() {
|
|
202
|
+
if sync_state.send_doc() {
|
|
203
|
+
let hashes = self.change_graph.get_hashes(&[]);
|
|
204
|
+
MessageBuilder::new_v2(self.save(), hashes)
|
|
205
|
+
} else {
|
|
206
|
+
let all_hashes = self
|
|
207
|
+
.get_hashes_to_send(their_have, their_need)
|
|
208
|
+
.expect("Should have only used hashes that are in the document");
|
|
209
|
+
// deduplicate the changes to send with those we have already sent and clone it now
|
|
210
|
+
let hashes: Vec<_> = all_hashes
|
|
211
|
+
.into_iter()
|
|
212
|
+
.filter(|hash| !sync_state.sent_hashes.contains(hash))
|
|
213
|
+
.collect();
|
|
214
|
+
if hashes.len() > self.change_graph.len() / 3 && sync_state.supports_v2_messages() {
|
|
215
|
+
// sending more than a 1/3 of the document? send everything
|
|
216
|
+
let all_hashes = self.change_graph.get_hashes(&[]);
|
|
217
|
+
MessageBuilder::new_v2(self.save(), all_hashes)
|
|
218
|
+
} else {
|
|
219
|
+
let changes = self.get_changes_by_hashes(hashes.iter().copied()).ok()?;
|
|
220
|
+
MessageBuilder::new(changes, sync_state)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
MessageBuilder::new(vec![], sync_state)
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
let heads_unchanged = sync_state.last_sent_heads == our_heads;
|
|
228
|
+
|
|
229
|
+
let heads_equal = sync_state.their_heads.as_ref() == Some(&our_heads);
|
|
230
|
+
|
|
231
|
+
if heads_unchanged && sync_state.have_responded {
|
|
232
|
+
if (heads_equal || sync_state.read_only) && message_builder.is_empty() {
|
|
233
|
+
return None;
|
|
234
|
+
}
|
|
235
|
+
if sync_state.in_flight {
|
|
236
|
+
return None;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
sync_state.have_responded = true;
|
|
241
|
+
sync_state.last_sent_heads.clone_from(&our_heads);
|
|
242
|
+
sync_state.sent_hashes.extend(message_builder.hashes());
|
|
243
|
+
|
|
244
|
+
let mut flags = MessageFlags::new();
|
|
245
|
+
flags.set(MessageFlags::SUPPORTS_SYNC_RESET);
|
|
246
|
+
if sync_state.read_only {
|
|
247
|
+
flags.set(MessageFlags::READ_ONLY);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// When switching from read-only to read-write, we need the remote to
|
|
251
|
+
// clear its sent_hashes so it resends changes we previously ignored.
|
|
252
|
+
// Peers that advertise Capability::SyncReset understand the SyncReset
|
|
253
|
+
// flag. Old peers don't, so we send empty heads to simulate losing all
|
|
254
|
+
// local state, which triggers the same sent_hashes clearing via the
|
|
255
|
+
// existing "peer lost all data" code path.
|
|
256
|
+
let heads_to_send = if sync_state.needs_reset {
|
|
257
|
+
sync_state.needs_reset = false;
|
|
258
|
+
if sync_state.peer_supports_sync_reset() {
|
|
259
|
+
flags.set(MessageFlags::SYNC_RESET);
|
|
260
|
+
our_heads.clone()
|
|
261
|
+
} else {
|
|
262
|
+
vec![]
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
our_heads.clone()
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
let sync_message = message_builder
|
|
269
|
+
.heads(heads_to_send)
|
|
270
|
+
.have(our_have)
|
|
271
|
+
.need(our_need)
|
|
272
|
+
.flags(Some(flags))
|
|
273
|
+
.build();
|
|
274
|
+
|
|
275
|
+
sync_state.in_flight = true;
|
|
276
|
+
Some(sync_message)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
fn receive_sync_message(
|
|
280
|
+
&mut self,
|
|
281
|
+
sync_state: &mut State,
|
|
282
|
+
message: Message,
|
|
283
|
+
) -> Result<(), AutomergeError> {
|
|
284
|
+
let mut patch_log = PatchLog::inactive();
|
|
285
|
+
self.receive_sync_message_inner(sync_state, message, &mut patch_log)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
fn receive_sync_message_log_patches(
|
|
289
|
+
&mut self,
|
|
290
|
+
sync_state: &mut State,
|
|
291
|
+
message: Message,
|
|
292
|
+
patch_log: &mut PatchLog,
|
|
293
|
+
) -> Result<(), AutomergeError> {
|
|
294
|
+
self.receive_sync_message_inner(sync_state, message, patch_log)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
impl Automerge {
|
|
299
|
+
#[inline(never)]
|
|
300
|
+
fn make_bloom_filter(&self, last_sync: Vec<ChangeHash>) -> Have {
|
|
301
|
+
let hashes = self.change_graph.get_hashes(&last_sync);
|
|
302
|
+
Have {
|
|
303
|
+
last_sync,
|
|
304
|
+
bloom: BloomFilter::from_hashes(hashes.iter()),
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
#[inline(never)]
|
|
309
|
+
fn get_hashes_to_send(
|
|
310
|
+
&self,
|
|
311
|
+
have: &[Have],
|
|
312
|
+
need: &[ChangeHash],
|
|
313
|
+
) -> Result<Vec<ChangeHash>, AutomergeError> {
|
|
314
|
+
if have.is_empty() {
|
|
315
|
+
Ok(need.to_vec())
|
|
316
|
+
} else {
|
|
317
|
+
let mut last_sync_hashes = HashSet::new();
|
|
318
|
+
let mut bloom_filters = Vec::with_capacity(have.len());
|
|
319
|
+
|
|
320
|
+
for h in have {
|
|
321
|
+
let Have { last_sync, bloom } = h;
|
|
322
|
+
last_sync_hashes.extend(last_sync);
|
|
323
|
+
bloom_filters.push(bloom);
|
|
324
|
+
}
|
|
325
|
+
let last_sync_hashes = last_sync_hashes.into_iter().copied().collect::<Vec<_>>();
|
|
326
|
+
|
|
327
|
+
let hashes = self.change_graph.get_hashes(&last_sync_hashes);
|
|
328
|
+
|
|
329
|
+
let mut change_hashes = HashSet::with_capacity(hashes.len());
|
|
330
|
+
let mut dependents: HashMap<ChangeHash, Vec<ChangeHash>> = HashMap::new();
|
|
331
|
+
let mut hashes_to_send = HashSet::new();
|
|
332
|
+
|
|
333
|
+
for hash in &*hashes {
|
|
334
|
+
change_hashes.insert(*hash);
|
|
335
|
+
|
|
336
|
+
for dep in self.change_graph.deps(hash) {
|
|
337
|
+
dependents.entry(dep).or_default().push(*hash);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if bloom_filters.iter().all(|bloom| !bloom.contains_hash(hash)) {
|
|
341
|
+
hashes_to_send.insert(*hash);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
let mut stack = hashes_to_send.iter().copied().collect::<Vec<_>>();
|
|
346
|
+
while let Some(hash) = stack.pop() {
|
|
347
|
+
if let Some(deps) = dependents.get(&hash) {
|
|
348
|
+
for dep in deps {
|
|
349
|
+
if hashes_to_send.insert(*dep) {
|
|
350
|
+
stack.push(*dep);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
let mut final_hashes = Vec::with_capacity(hashes_to_send.len() + need.len());
|
|
357
|
+
for hash in need {
|
|
358
|
+
if !hashes_to_send.contains(hash) {
|
|
359
|
+
final_hashes.push(*hash);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
for hash in &*hashes {
|
|
364
|
+
if hashes_to_send.contains(hash) {
|
|
365
|
+
final_hashes.push(*hash);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
Ok(final_hashes)
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
#[inline(never)]
|
|
373
|
+
pub(crate) fn receive_sync_message_inner(
|
|
374
|
+
&mut self,
|
|
375
|
+
sync_state: &mut State,
|
|
376
|
+
message: Message,
|
|
377
|
+
patch_log: &mut PatchLog,
|
|
378
|
+
) -> Result<(), AutomergeError> {
|
|
379
|
+
sync_state.in_flight = false;
|
|
380
|
+
let before_heads = self.get_heads();
|
|
381
|
+
|
|
382
|
+
let Message {
|
|
383
|
+
heads: message_heads,
|
|
384
|
+
changes: message_changes,
|
|
385
|
+
need: message_need,
|
|
386
|
+
have: message_have,
|
|
387
|
+
flags: message_flags,
|
|
388
|
+
..
|
|
389
|
+
} = message;
|
|
390
|
+
|
|
391
|
+
if let Some(flags) = message_flags {
|
|
392
|
+
// Any peer that sends the flags section supports V2 messages —
|
|
393
|
+
// the flags section was introduced alongside V2 support.
|
|
394
|
+
let mut caps = vec![Capability::MessageV2];
|
|
395
|
+
if flags.contains(MessageFlags::SUPPORTS_SYNC_RESET) {
|
|
396
|
+
caps.push(Capability::SyncReset);
|
|
397
|
+
}
|
|
398
|
+
sync_state.their_capabilities = Some(caps);
|
|
399
|
+
|
|
400
|
+
// Process transient per-message signals
|
|
401
|
+
if flags.contains(MessageFlags::SYNC_RESET) {
|
|
402
|
+
sync_state.sent_hashes.clear();
|
|
403
|
+
}
|
|
404
|
+
sync_state.peer_read_only = flags.contains(MessageFlags::READ_ONLY);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
let changes_is_empty = message_changes.is_empty();
|
|
408
|
+
if !changes_is_empty && !sync_state.read_only {
|
|
409
|
+
self.load_incremental_log_patches(&message_changes.join(), patch_log)?;
|
|
410
|
+
sync_state.shared_heads = advance_heads(
|
|
411
|
+
&before_heads.iter().collect(),
|
|
412
|
+
&self.get_heads().into_iter().collect(),
|
|
413
|
+
&sync_state.shared_heads,
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// trim down the sent hashes to those that we know they haven't seen
|
|
418
|
+
self.filter_changes(&message_heads, &mut sync_state.sent_hashes)?;
|
|
419
|
+
|
|
420
|
+
if changes_is_empty && message_heads == before_heads {
|
|
421
|
+
sync_state.last_sent_heads.clone_from(&message_heads);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
let known_heads = message_heads
|
|
425
|
+
.iter()
|
|
426
|
+
.filter(|head| self.has_change(head))
|
|
427
|
+
.collect::<Vec<_>>();
|
|
428
|
+
if known_heads.len() == message_heads.len() {
|
|
429
|
+
sync_state.shared_heads.clone_from(&message_heads);
|
|
430
|
+
// If the remote peer has lost all its data, reset our state to perform a full resync
|
|
431
|
+
if message_heads.is_empty() {
|
|
432
|
+
sync_state.last_sent_heads = Default::default();
|
|
433
|
+
sync_state.sent_hashes = Default::default();
|
|
434
|
+
}
|
|
435
|
+
} else {
|
|
436
|
+
sync_state.shared_heads = sync_state
|
|
437
|
+
.shared_heads
|
|
438
|
+
.iter()
|
|
439
|
+
.chain(known_heads)
|
|
440
|
+
.copied()
|
|
441
|
+
.unique()
|
|
442
|
+
.sorted()
|
|
443
|
+
.collect::<Vec<_>>();
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
sync_state.their_have = Some(message_have);
|
|
447
|
+
sync_state.their_heads = Some(message_heads);
|
|
448
|
+
sync_state.their_need = Some(message_need);
|
|
449
|
+
|
|
450
|
+
Ok(())
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
#[derive(Debug, thiserror::Error)]
|
|
455
|
+
pub enum ReadMessageError {
|
|
456
|
+
#[error("expected {expected_one_of:?} but found {found}")]
|
|
457
|
+
WrongType { expected_one_of: Vec<u8>, found: u8 },
|
|
458
|
+
#[error("{0}")]
|
|
459
|
+
Parse(String),
|
|
460
|
+
#[error(transparent)]
|
|
461
|
+
ReadChangeOps(#[from] ReadChangeOpError),
|
|
462
|
+
#[error("not enough input")]
|
|
463
|
+
NotEnoughInput,
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
impl From<parse::leb128::Error> for ReadMessageError {
|
|
467
|
+
fn from(e: parse::leb128::Error) -> Self {
|
|
468
|
+
ReadMessageError::Parse(e.to_string())
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
impl From<bloom::ParseError> for ReadMessageError {
|
|
473
|
+
fn from(e: bloom::ParseError) -> Self {
|
|
474
|
+
ReadMessageError::Parse(e.to_string())
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
impl From<crate::storage::change::ParseError> for ReadMessageError {
|
|
479
|
+
fn from(e: crate::storage::change::ParseError) -> Self {
|
|
480
|
+
ReadMessageError::Parse(format!("error parsing changes: {}", e))
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
impl From<ReadMessageError> for parse::ParseError<ReadMessageError> {
|
|
485
|
+
fn from(e: ReadMessageError) -> Self {
|
|
486
|
+
parse::ParseError::Error(e)
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
impl From<parse::ParseError<ReadMessageError>> for ReadMessageError {
|
|
491
|
+
fn from(p: parse::ParseError<ReadMessageError>) -> Self {
|
|
492
|
+
match p {
|
|
493
|
+
parse::ParseError::Error(e) => e,
|
|
494
|
+
parse::ParseError::Incomplete(..) => Self::NotEnoughInput,
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/// The sync message to be sent.
|
|
500
|
+
///
|
|
501
|
+
/// ## Notes about encoding
|
|
502
|
+
///
|
|
503
|
+
/// There are two versions of the sync message, V1 and V2. The V1 message is the original message
|
|
504
|
+
/// which automerge shipped with and V2 is an extension which allows for encoding the changes as
|
|
505
|
+
/// either a list of changes or as a compressed document format. This makes syncing up for the
|
|
506
|
+
/// first time faster.
|
|
507
|
+
///
|
|
508
|
+
/// Encoding this in a backwards compatible way is a bit tricky. The wire format of the v1 message
|
|
509
|
+
/// didn't allow for any forwards compatible changes. In order to accomodate this the first message
|
|
510
|
+
/// a peer sends is a V1 message with a length-prefixed `Vec<MessageFlag>` appended to it. For old
|
|
511
|
+
/// implementations this appended data is just ignored but new implementations read it and store
|
|
512
|
+
/// the advertised capabilities on the sync state. This allows new implementations to discover if
|
|
513
|
+
/// the remote peer supports the V2 message format and if so send a V2 message. The flags also
|
|
514
|
+
/// carry transient per-message signals such as read-only mode and sync reset.
|
|
515
|
+
#[derive(Clone, Debug, PartialEq)]
|
|
516
|
+
pub struct Message {
|
|
517
|
+
/// The heads of the sender.
|
|
518
|
+
pub heads: Vec<ChangeHash>,
|
|
519
|
+
/// The hashes of any changes that are being explicitly requested from the recipient.
|
|
520
|
+
pub need: Vec<ChangeHash>,
|
|
521
|
+
/// A summary of the changes that the sender already has.
|
|
522
|
+
pub have: Vec<Have>,
|
|
523
|
+
/// The changes for the recipient to apply.
|
|
524
|
+
///
|
|
525
|
+
/// This is a Vec of bytes which should be passed to `Automerge::load_incremental`. The reason
|
|
526
|
+
/// it is a `Vec<Vec<u8>>` and not a `Vec<u8>` is that the V1 message format is a sequence of
|
|
527
|
+
/// change chunks, each of which is length delimited. The V2 message format is a single length
|
|
528
|
+
/// delimited chunk but we nest it inside a Vec for backwards compatibility.
|
|
529
|
+
pub changes: ChunkList,
|
|
530
|
+
/// Per-message flags including capability advertisements and transient signals.
|
|
531
|
+
pub flags: Option<MessageFlags>,
|
|
532
|
+
/// What version to encode this message as
|
|
533
|
+
pub version: MessageVersion,
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/// An array of changes, each of which should be passed to [`Automerge::load_incremental()`]
|
|
537
|
+
#[derive(Clone, Debug, PartialEq)]
|
|
538
|
+
pub struct ChunkList(Vec<Vec<u8>>);
|
|
539
|
+
|
|
540
|
+
impl From<Vec<Vec<u8>>> for ChunkList {
|
|
541
|
+
fn from(v: Vec<Vec<u8>>) -> Self {
|
|
542
|
+
Self(v)
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
impl From<Vec<u8>> for ChunkList {
|
|
547
|
+
fn from(v: Vec<u8>) -> Self {
|
|
548
|
+
Self(vec![v])
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
impl ChunkList {
|
|
553
|
+
fn parse(i: parse::Input<'_>) -> parse::ParseResult<'_, Self, ReadMessageError> {
|
|
554
|
+
let change_parser = |i| {
|
|
555
|
+
let (i, bytes) = parse::length_prefixed_bytes(i)?;
|
|
556
|
+
Ok((i, bytes.to_vec()))
|
|
557
|
+
};
|
|
558
|
+
let (i, stored_changes) = parse::length_prefixed(change_parser)(i)?;
|
|
559
|
+
Ok((i, Self(stored_changes)))
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
pub fn empty() -> Self {
|
|
563
|
+
Self(Vec::new())
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
pub fn is_empty(&self) -> bool {
|
|
567
|
+
self.0.is_empty()
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
pub fn len(&self) -> usize {
|
|
571
|
+
self.0.len()
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
pub fn iter(&self) -> impl ExactSizeIterator<Item = &[u8]> {
|
|
575
|
+
self.0.iter().map(|v| v.as_slice())
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
pub(crate) fn join(&self) -> Vec<u8> {
|
|
579
|
+
let total: usize = self.0.iter().map(Vec::len).sum();
|
|
580
|
+
let mut result = Vec::with_capacity(total);
|
|
581
|
+
|
|
582
|
+
for v in &self.0 {
|
|
583
|
+
result.extend(v);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
result
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
impl serde::Serialize for Message {
|
|
591
|
+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
592
|
+
where
|
|
593
|
+
S: serde::Serializer,
|
|
594
|
+
{
|
|
595
|
+
let mut map = serializer.serialize_map(Some(4))?;
|
|
596
|
+
map.serialize_entry("heads", &self.heads)?;
|
|
597
|
+
map.serialize_entry("need", &self.need)?;
|
|
598
|
+
map.serialize_entry("have", &self.have)?;
|
|
599
|
+
map.serialize_entry("changes", &self.changes.0)?;
|
|
600
|
+
map.end()
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
fn parse_have(input: parse::Input<'_>) -> parse::ParseResult<'_, Have, ReadMessageError> {
|
|
605
|
+
let (i, last_sync) = parse::length_prefixed(parse::change_hash)(input)?;
|
|
606
|
+
let (i, bloom_bytes) = parse::length_prefixed_bytes(i)?;
|
|
607
|
+
let (_, bloom) = BloomFilter::parse(parse::Input::new(bloom_bytes)).map_err(|e| e.lift())?;
|
|
608
|
+
Ok((i, Have { last_sync, bloom }))
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
impl Message {
|
|
612
|
+
pub(crate) fn reset(our_heads: Vec<ChangeHash>) -> Message {
|
|
613
|
+
Message {
|
|
614
|
+
heads: our_heads,
|
|
615
|
+
need: Vec::new(),
|
|
616
|
+
have: vec![Have::default()],
|
|
617
|
+
changes: ChunkList::empty(),
|
|
618
|
+
flags: {
|
|
619
|
+
let mut f = MessageFlags::new();
|
|
620
|
+
f.set(MessageFlags::SUPPORTS_SYNC_RESET);
|
|
621
|
+
Some(f)
|
|
622
|
+
},
|
|
623
|
+
version: MessageVersion::V1,
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
pub fn decode(input: &[u8]) -> Result<Self, ReadMessageError> {
|
|
628
|
+
let input = parse::Input::new(input);
|
|
629
|
+
match Self::parse(input) {
|
|
630
|
+
Ok((_, msg)) => Ok(msg),
|
|
631
|
+
Err(parse::ParseError::Error(e)) => Err(e),
|
|
632
|
+
Err(parse::ParseError::Incomplete(_)) => Err(ReadMessageError::NotEnoughInput),
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
pub(crate) fn parse(input: parse::Input<'_>) -> parse::ParseResult<'_, Self, ReadMessageError> {
|
|
637
|
+
let (i, message_version) = MessageVersion::parse(input)?;
|
|
638
|
+
|
|
639
|
+
let (i, heads) = parse::length_prefixed(parse::change_hash)(i)?;
|
|
640
|
+
let (i, need) = parse::length_prefixed(parse::change_hash)(i)?;
|
|
641
|
+
let (i, have) = parse::length_prefixed(parse_have)(i)?;
|
|
642
|
+
|
|
643
|
+
let (i, changes) = ChunkList::parse(i)?;
|
|
644
|
+
let (i, flags) = if !i.is_empty() {
|
|
645
|
+
let (i, raw_bytes) = parse::length_prefixed_bytes(i)?;
|
|
646
|
+
(i, Some(MessageFlags::parse_bytes(raw_bytes)))
|
|
647
|
+
} else {
|
|
648
|
+
(i, None)
|
|
649
|
+
};
|
|
650
|
+
Ok((
|
|
651
|
+
i,
|
|
652
|
+
Message {
|
|
653
|
+
heads,
|
|
654
|
+
need,
|
|
655
|
+
have,
|
|
656
|
+
changes,
|
|
657
|
+
flags,
|
|
658
|
+
version: message_version,
|
|
659
|
+
},
|
|
660
|
+
))
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
pub fn encode(self) -> Vec<u8> {
|
|
664
|
+
let mut buf = vec![self.version.encode()];
|
|
665
|
+
|
|
666
|
+
encode_hashes(&mut buf, &self.heads);
|
|
667
|
+
encode_hashes(&mut buf, &self.need);
|
|
668
|
+
encode_many(&mut buf, self.have.iter(), |buf, h| {
|
|
669
|
+
encode_hashes(buf, &h.last_sync);
|
|
670
|
+
leb128::write::unsigned(buf, h.bloom.to_bytes().len() as u64).unwrap();
|
|
671
|
+
buf.extend(h.bloom.to_bytes());
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
encode_many(&mut buf, self.changes.iter(), |buf, change| {
|
|
675
|
+
leb128::write::unsigned(buf, change.len() as u64).unwrap();
|
|
676
|
+
buf.extend::<&[u8]>(change.as_ref())
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
if let Some(flags) = self.flags {
|
|
680
|
+
flags.encode(&mut buf);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
buf
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/// Per-message flags packed into a bitfield byte on the wire.
|
|
688
|
+
///
|
|
689
|
+
/// ## Wire encoding
|
|
690
|
+
///
|
|
691
|
+
/// Flags are encoded a little strangely, it's easiest to explain by explaining
|
|
692
|
+
/// the history. The first flag we added was to indicate that the other end
|
|
693
|
+
/// supported "v2" sync protocol messages - sync messages in which the entire
|
|
694
|
+
/// document state is sent in a single message. This flag was forward compatibly
|
|
695
|
+
/// encoded by taking advantage of the fact that the original parser would ignore
|
|
696
|
+
/// any bytes after the end of the sync message. We thus encoded new flags as
|
|
697
|
+
/// length prefixed set of flags at the end of the message. I.e. it looked
|
|
698
|
+
/// a bit like this:
|
|
699
|
+
///
|
|
700
|
+
/// | message length | < sync message > | number of flags | flags |
|
|
701
|
+
///
|
|
702
|
+
/// The idea was that this allows us to forward compatibly add new flags to the
|
|
703
|
+
/// protocol by just sending a longer list of flags. The problem with this
|
|
704
|
+
/// approach is that it usees a byte per flag, which becomes much more wasteful
|
|
705
|
+
/// as the number of flags grows. Thus, we now encode flags as bitfields. We
|
|
706
|
+
/// want to stay backwards compatible for implementations unaware of the bitfields
|
|
707
|
+
/// though. To do this we take advantage of the fact that there was only ever
|
|
708
|
+
/// one flag sent - the old `MessageV2` flag. This means that the only flags
|
|
709
|
+
/// ever sent were:
|
|
710
|
+
///
|
|
711
|
+
/// | 0x01 | 0x02 |
|
|
712
|
+
///
|
|
713
|
+
/// Where `0x01` is the length prefix byte and `0x02` is the old `MessageV2` flag.
|
|
714
|
+
/// To stay backwards compatible we encode the old `MessageV2` flag as a single byte
|
|
715
|
+
/// and then all the remaining flags as a byte with the high bit set. The old
|
|
716
|
+
/// code thus sees a bunch of unknown bytes after the old MessageV2 flag and
|
|
717
|
+
/// ignores them, the new code reads every bit and parses any byte with it's high
|
|
718
|
+
/// bit set as a bitfield of flags. This scheme allows us to forward compatibly
|
|
719
|
+
/// add as many flags as we need (by increasing the length prefix) without breaking
|
|
720
|
+
/// old implementations.
|
|
721
|
+
///
|
|
722
|
+
/// The final encoding then is
|
|
723
|
+
///
|
|
724
|
+
/// | length prefix | old MessageV2 flag | bitfield byte (0x80 set) |
|
|
725
|
+
///
|
|
726
|
+
/// When parsing messages from old implementations (all bytes < 0x80), the
|
|
727
|
+
/// old individual byte values are mapped to the corresponding flag bits.
|
|
728
|
+
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
|
729
|
+
pub struct MessageFlags(u8);
|
|
730
|
+
|
|
731
|
+
impl MessageFlags {
|
|
732
|
+
// V1/V2 message format support is not encoded in the bitfield — V2 is
|
|
733
|
+
// communicated via the legacy `0x02` byte that always precedes the
|
|
734
|
+
// bitfield, and V1 is the default. This leaves all 7 bitfield bits
|
|
735
|
+
// available for future flags.
|
|
736
|
+
|
|
737
|
+
/// Signals the remote peer should clear its `sent_hashes` and perform a
|
|
738
|
+
/// fresh sync. Used when switching from read-only to read-write mode.
|
|
739
|
+
pub const SYNC_RESET: u8 = 1 << 0;
|
|
740
|
+
/// Indicates the sender is in read-only mode and will not apply incoming
|
|
741
|
+
/// changes.
|
|
742
|
+
pub const READ_ONLY: u8 = 1 << 1;
|
|
743
|
+
/// Advertises that the sender understands the [`SYNC_RESET`](Self::SYNC_RESET)
|
|
744
|
+
/// flag and will clear `sent_hashes` when it receives one.
|
|
745
|
+
pub const SUPPORTS_SYNC_RESET: u8 = 1 << 2;
|
|
746
|
+
|
|
747
|
+
const BITFIELD_MARKER: u8 = 0x80;
|
|
748
|
+
/// The old MessageV2 byte, sent first for backwards compatibility.
|
|
749
|
+
const LEGACY_V2_BYTE: u8 = 0x02;
|
|
750
|
+
|
|
751
|
+
pub fn new() -> Self {
|
|
752
|
+
Self(0)
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
pub fn contains(self, flag: u8) -> bool {
|
|
756
|
+
self.0 & flag != 0
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
pub fn set(&mut self, flag: u8) {
|
|
760
|
+
self.0 |= flag;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
fn encode(&self, out: &mut Vec<u8>) {
|
|
764
|
+
// Two entries: 0x02 (old MessageV2 for backwards compat) + bitfield byte
|
|
765
|
+
leb128::write::unsigned(out, 2u64).unwrap();
|
|
766
|
+
out.push(Self::LEGACY_V2_BYTE);
|
|
767
|
+
out.push(Self::BITFIELD_MARKER | self.0);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
fn parse_bytes(bytes: &[u8]) -> Self {
|
|
771
|
+
let mut flags = Self::new();
|
|
772
|
+
for &byte in bytes {
|
|
773
|
+
if byte & Self::BITFIELD_MARKER != 0 {
|
|
774
|
+
// New-style bitfield byte: extract the flag bits
|
|
775
|
+
flags.0 |= byte & !Self::BITFIELD_MARKER;
|
|
776
|
+
}
|
|
777
|
+
// Old-style bytes (0x01, 0x02) are ignored — V1/V2 support
|
|
778
|
+
// is inferred from the presence of the flags section itself
|
|
779
|
+
}
|
|
780
|
+
flags
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/// Persistent peer capabilities, derived from [`MessageFlags`]s on incoming
|
|
785
|
+
/// messages. These describe what the remote peer supports.
|
|
786
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
787
|
+
pub enum Capability {
|
|
788
|
+
MessageV1,
|
|
789
|
+
MessageV2,
|
|
790
|
+
/// The peer understands the [`MessageFlags::SYNC_RESET`] flag and will
|
|
791
|
+
/// clear its `sent_hashes` when it receives one.
|
|
792
|
+
SyncReset,
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
fn encode_many<'a, I, It, F>(out: &mut Vec<u8>, data: I, f: F)
|
|
796
|
+
where
|
|
797
|
+
I: Iterator<Item = It> + ExactSizeIterator + 'a,
|
|
798
|
+
F: Fn(&mut Vec<u8>, It),
|
|
799
|
+
{
|
|
800
|
+
leb128::write::unsigned(out, data.len() as u64).unwrap();
|
|
801
|
+
for datum in data {
|
|
802
|
+
f(out, datum)
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
fn encode_hashes(buf: &mut Vec<u8>, hashes: &[ChangeHash]) {
|
|
807
|
+
debug_assert!(
|
|
808
|
+
hashes.windows(2).all(|h| h[0] <= h[1]),
|
|
809
|
+
"hashes were not sorted"
|
|
810
|
+
);
|
|
811
|
+
encode_many(buf, hashes.iter(), |buf, hash| buf.extend(hash.as_bytes()))
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
fn advance_heads(
|
|
815
|
+
my_old_heads: &HashSet<&ChangeHash>,
|
|
816
|
+
my_new_heads: &HashSet<ChangeHash>,
|
|
817
|
+
our_old_shared_heads: &[ChangeHash],
|
|
818
|
+
) -> Vec<ChangeHash> {
|
|
819
|
+
let new_heads = my_new_heads
|
|
820
|
+
.iter()
|
|
821
|
+
.filter(|head| !my_old_heads.contains(head))
|
|
822
|
+
.copied()
|
|
823
|
+
.collect::<Vec<_>>();
|
|
824
|
+
|
|
825
|
+
let common_heads = our_old_shared_heads
|
|
826
|
+
.iter()
|
|
827
|
+
.filter(|head| my_new_heads.contains(head))
|
|
828
|
+
.copied()
|
|
829
|
+
.collect::<Vec<_>>();
|
|
830
|
+
|
|
831
|
+
let mut advanced_heads = HashSet::with_capacity(new_heads.len() + common_heads.len());
|
|
832
|
+
for head in new_heads.into_iter().chain(common_heads) {
|
|
833
|
+
advanced_heads.insert(head);
|
|
834
|
+
}
|
|
835
|
+
let mut advanced_heads = advanced_heads.into_iter().collect::<Vec<_>>();
|
|
836
|
+
advanced_heads.sort();
|
|
837
|
+
advanced_heads
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
#[cfg(test)]
|
|
841
|
+
mod tests {
|
|
842
|
+
use super::*;
|
|
843
|
+
//use crate::change::gen::gen_change;
|
|
844
|
+
use crate::storage::parse::Input;
|
|
845
|
+
use crate::storage::Chunk;
|
|
846
|
+
use crate::transaction::Transactable;
|
|
847
|
+
//use crate::types::gen::gen_hash;
|
|
848
|
+
use crate::ActorId;
|
|
849
|
+
//use proptest::prelude::*;
|
|
850
|
+
|
|
851
|
+
/*
|
|
852
|
+
prop_compose! {
|
|
853
|
+
fn gen_bloom()(hashes in gen_sorted_hashes(0..10)) -> BloomFilter {
|
|
854
|
+
BloomFilter::from_hashes(hashes.into_iter())
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
prop_compose! {
|
|
859
|
+
fn gen_have()(bloom in gen_bloom(), last_sync in gen_sorted_hashes(0..10)) -> Have {
|
|
860
|
+
Have {
|
|
861
|
+
bloom,
|
|
862
|
+
last_sync,
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
fn gen_sorted_hashes(size: std::ops::Range<usize>) -> impl Strategy<Value = Vec<ChangeHash>> {
|
|
868
|
+
proptest::collection::vec(gen_hash(), size).prop_map(|mut h| {
|
|
869
|
+
h.sort();
|
|
870
|
+
h
|
|
871
|
+
})
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
prop_compose! {
|
|
875
|
+
fn gen_sync_message_v1()(
|
|
876
|
+
heads in gen_sorted_hashes(0..10),
|
|
877
|
+
need in gen_sorted_hashes(0..10),
|
|
878
|
+
have in proptest::collection::vec(gen_have(), 0..10),
|
|
879
|
+
changes in proptest::collection::vec(gen_change(), 0..10),
|
|
880
|
+
supported_capabilities in prop_oneof![
|
|
881
|
+
Just(None),
|
|
882
|
+
Just(Some(vec![Capability::MessageV1])),
|
|
883
|
+
Just(Some(vec![Capability::MessageV2])),
|
|
884
|
+
Just(Some(vec![Capability::MessageV1, Capability::MessageV2])),
|
|
885
|
+
],
|
|
886
|
+
) -> Message {
|
|
887
|
+
Message {
|
|
888
|
+
heads,
|
|
889
|
+
need,
|
|
890
|
+
have,
|
|
891
|
+
changes: changes.into_iter().map(|c| c.raw_bytes().to_vec()).collect::<Vec<Vec<u8>>>().into(),
|
|
892
|
+
supported_capabilities,
|
|
893
|
+
version: MessageVersion::V1,
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
*/
|
|
898
|
+
|
|
899
|
+
/*
|
|
900
|
+
prop_compose! {
|
|
901
|
+
fn gen_sync_message_v2()(
|
|
902
|
+
heads in gen_sorted_hashes(0..10),
|
|
903
|
+
need in gen_sorted_hashes(0..10),
|
|
904
|
+
have in proptest::collection::vec(gen_have(), 0..10),
|
|
905
|
+
raw in proptest::collection::vec(any::<u8>(), 0..100),
|
|
906
|
+
supported_capabilities in prop_oneof![
|
|
907
|
+
Just(None),
|
|
908
|
+
Just(Some(vec![Capability::MessageV1])),
|
|
909
|
+
Just(Some(vec![Capability::MessageV2])),
|
|
910
|
+
Just(Some(vec![Capability::MessageV1, Capability::MessageV2])),
|
|
911
|
+
],
|
|
912
|
+
) -> Message {
|
|
913
|
+
Message {
|
|
914
|
+
heads,
|
|
915
|
+
need,
|
|
916
|
+
have,
|
|
917
|
+
changes: ChunkList::from(raw),
|
|
918
|
+
supported_capabilities,
|
|
919
|
+
version: MessageVersion::V2,
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
*/
|
|
924
|
+
|
|
925
|
+
/*
|
|
926
|
+
fn gen_sync_message() -> impl Strategy<Value = Message> {
|
|
927
|
+
prop_oneof![gen_sync_message_v1(), gen_sync_message_v2(),].boxed()
|
|
928
|
+
}
|
|
929
|
+
*/
|
|
930
|
+
|
|
931
|
+
#[test]
|
|
932
|
+
fn encode_decode_empty_message() {
|
|
933
|
+
let msg = Message {
|
|
934
|
+
heads: vec![],
|
|
935
|
+
need: vec![],
|
|
936
|
+
have: vec![],
|
|
937
|
+
changes: ChunkList::empty(),
|
|
938
|
+
flags: None,
|
|
939
|
+
version: MessageVersion::V2,
|
|
940
|
+
};
|
|
941
|
+
let encoded = msg.encode();
|
|
942
|
+
Message::parse(Input::new(&encoded)).unwrap();
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/*
|
|
946
|
+
proptest! {
|
|
947
|
+
#[test]
|
|
948
|
+
fn encode_decode_message(msg in gen_sync_message()) {
|
|
949
|
+
let encoded = msg.clone().encode();
|
|
950
|
+
let (i, decoded) = Message::parse(Input::new(&encoded)).unwrap();
|
|
951
|
+
assert!(i.is_empty());
|
|
952
|
+
assert_eq!(msg, decoded);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
*/
|
|
956
|
+
|
|
957
|
+
#[test]
|
|
958
|
+
fn generate_sync_message_twice_does_nothing() {
|
|
959
|
+
let mut doc = crate::AutoCommit::new();
|
|
960
|
+
doc.put(crate::ROOT, "key", "value").unwrap();
|
|
961
|
+
let mut sync_state = State::new();
|
|
962
|
+
|
|
963
|
+
assert!(doc.sync().generate_sync_message(&mut sync_state).is_some());
|
|
964
|
+
assert!(doc.sync().generate_sync_message(&mut sync_state).is_none());
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
#[test]
|
|
968
|
+
fn first_response_is_some_even_if_no_changes() {
|
|
969
|
+
// The first time we generate a sync message for a given peer we should always send a
|
|
970
|
+
// response so that they know what our heads are, even if we are at the same heads as them
|
|
971
|
+
|
|
972
|
+
let mut doc1 = crate::AutoCommit::new();
|
|
973
|
+
doc1.put(crate::ROOT, "key", "value").unwrap();
|
|
974
|
+
let mut doc2 = doc1.fork();
|
|
975
|
+
|
|
976
|
+
let mut s1 = State::new();
|
|
977
|
+
let mut s2 = State::new();
|
|
978
|
+
|
|
979
|
+
let m1 = doc1
|
|
980
|
+
.sync()
|
|
981
|
+
.generate_sync_message(&mut s1)
|
|
982
|
+
.expect("message was none");
|
|
983
|
+
|
|
984
|
+
doc2.sync().receive_sync_message(&mut s2, m1).unwrap();
|
|
985
|
+
|
|
986
|
+
let _m2 = doc2
|
|
987
|
+
.sync()
|
|
988
|
+
.generate_sync_message(&mut s2)
|
|
989
|
+
.expect("response was none");
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
#[test]
|
|
993
|
+
fn should_not_reply_if_we_have_no_data_after_first_round() {
|
|
994
|
+
let mut doc1 = crate::AutoCommit::new();
|
|
995
|
+
let mut doc2 = crate::AutoCommit::new();
|
|
996
|
+
let mut s1 = State::new();
|
|
997
|
+
let mut s2 = State::new();
|
|
998
|
+
let m1 = doc1
|
|
999
|
+
.sync()
|
|
1000
|
+
.generate_sync_message(&mut s1)
|
|
1001
|
+
.expect("message was none");
|
|
1002
|
+
|
|
1003
|
+
doc2.sync().receive_sync_message(&mut s2, m1).unwrap();
|
|
1004
|
+
let _m2 = doc2
|
|
1005
|
+
.sync()
|
|
1006
|
+
.generate_sync_message(&mut s2)
|
|
1007
|
+
.expect("first round message was none");
|
|
1008
|
+
|
|
1009
|
+
let m1 = doc1.sync().generate_sync_message(&mut s1);
|
|
1010
|
+
assert!(m1.is_none());
|
|
1011
|
+
|
|
1012
|
+
let m2 = doc2.sync().generate_sync_message(&mut s2);
|
|
1013
|
+
assert!(m2.is_none());
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
#[test]
|
|
1017
|
+
fn should_allow_simultaneous_messages_during_synchronisation() {
|
|
1018
|
+
// create & synchronize two nodes
|
|
1019
|
+
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
1020
|
+
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
1021
|
+
let mut s1 = State::new();
|
|
1022
|
+
let mut s2 = State::new();
|
|
1023
|
+
|
|
1024
|
+
for i in 0..5 {
|
|
1025
|
+
doc1.put(&crate::ROOT, "x", i).unwrap();
|
|
1026
|
+
doc1.commit();
|
|
1027
|
+
doc2.put(&crate::ROOT, "y", i).unwrap();
|
|
1028
|
+
doc2.commit();
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
let head1 = doc1.get_heads()[0];
|
|
1032
|
+
let head2 = doc2.get_heads()[0];
|
|
1033
|
+
|
|
1034
|
+
//// both sides report what they have but have no shared peer state
|
|
1035
|
+
let msg1to2 = doc1
|
|
1036
|
+
.sync()
|
|
1037
|
+
.generate_sync_message(&mut s1)
|
|
1038
|
+
.expect("initial sync from 1 to 2 was None");
|
|
1039
|
+
let msg2to1 = doc2
|
|
1040
|
+
.sync()
|
|
1041
|
+
.generate_sync_message(&mut s2)
|
|
1042
|
+
.expect("initial sync message from 2 to 1 was None");
|
|
1043
|
+
let Message {
|
|
1044
|
+
changes: changes1to2,
|
|
1045
|
+
..
|
|
1046
|
+
} = &msg1to2;
|
|
1047
|
+
assert_eq!(changes1to2.len(), 0);
|
|
1048
|
+
assert_eq!(msg1to2.have[0].last_sync.len(), 0);
|
|
1049
|
+
let Message {
|
|
1050
|
+
changes: changes2to1,
|
|
1051
|
+
..
|
|
1052
|
+
} = &msg2to1;
|
|
1053
|
+
assert_eq!(changes2to1.len(), 0);
|
|
1054
|
+
assert_eq!(msg2to1.have[0].last_sync.len(), 0);
|
|
1055
|
+
|
|
1056
|
+
//// doc1 and doc2 receive that message and update sync state
|
|
1057
|
+
doc1.sync().receive_sync_message(&mut s1, msg2to1).unwrap();
|
|
1058
|
+
doc2.sync().receive_sync_message(&mut s2, msg1to2).unwrap();
|
|
1059
|
+
|
|
1060
|
+
//// now both reply with their local changes the other lacks
|
|
1061
|
+
//// (standard warning that 1% of the time this will result in a "need" message)
|
|
1062
|
+
let msg1to2 = doc1
|
|
1063
|
+
.sync()
|
|
1064
|
+
.generate_sync_message(&mut s1)
|
|
1065
|
+
.expect("first reply from 1 to 2 was None");
|
|
1066
|
+
let Message {
|
|
1067
|
+
changes: changes1to2,
|
|
1068
|
+
..
|
|
1069
|
+
} = &msg1to2;
|
|
1070
|
+
assert!(!changes1to2.is_empty());
|
|
1071
|
+
|
|
1072
|
+
let msg2to1 = doc2
|
|
1073
|
+
.sync()
|
|
1074
|
+
.generate_sync_message(&mut s2)
|
|
1075
|
+
.expect("first reply from 2 to 1 was None");
|
|
1076
|
+
let Message {
|
|
1077
|
+
changes: changes2to1,
|
|
1078
|
+
..
|
|
1079
|
+
} = &msg2to1;
|
|
1080
|
+
assert!(!changes2to1.is_empty());
|
|
1081
|
+
|
|
1082
|
+
//// both should now apply the changes
|
|
1083
|
+
doc1.sync().receive_sync_message(&mut s1, msg2to1).unwrap();
|
|
1084
|
+
assert_eq!(doc1.get_missing_deps(&[]), Vec::new());
|
|
1085
|
+
|
|
1086
|
+
doc2.sync().receive_sync_message(&mut s2, msg1to2).unwrap();
|
|
1087
|
+
assert_eq!(doc2.get_missing_deps(&[]), Vec::new());
|
|
1088
|
+
|
|
1089
|
+
//// The response acknowledges the changes received and sends no further changes
|
|
1090
|
+
let msg1to2 = doc1
|
|
1091
|
+
.sync()
|
|
1092
|
+
.generate_sync_message(&mut s1)
|
|
1093
|
+
.expect("second reply from 1 to 2 was None");
|
|
1094
|
+
let Message {
|
|
1095
|
+
changes: changes1to2,
|
|
1096
|
+
..
|
|
1097
|
+
} = &msg1to2;
|
|
1098
|
+
assert_eq!(changes1to2.len(), 0);
|
|
1099
|
+
let msg2to1 = doc2
|
|
1100
|
+
.sync()
|
|
1101
|
+
.generate_sync_message(&mut s2)
|
|
1102
|
+
.expect("second reply from 2 to 1 was None");
|
|
1103
|
+
let Message {
|
|
1104
|
+
changes: changes2to1,
|
|
1105
|
+
..
|
|
1106
|
+
} = &msg2to1;
|
|
1107
|
+
assert_eq!(changes2to1.len(), 0);
|
|
1108
|
+
|
|
1109
|
+
//// After receiving acknowledgements, their shared heads should be equal
|
|
1110
|
+
doc1.sync().receive_sync_message(&mut s1, msg2to1).unwrap();
|
|
1111
|
+
doc2.sync().receive_sync_message(&mut s2, msg1to2).unwrap();
|
|
1112
|
+
|
|
1113
|
+
assert_eq!(s1.shared_heads, s2.shared_heads);
|
|
1114
|
+
|
|
1115
|
+
//// We're in sync, no more messages required
|
|
1116
|
+
assert!(doc1.sync().generate_sync_message(&mut s1).is_none());
|
|
1117
|
+
assert!(doc2.sync().generate_sync_message(&mut s2).is_none());
|
|
1118
|
+
|
|
1119
|
+
//// If we make one more change and start another sync then its lastSync should be updated
|
|
1120
|
+
doc1.put(crate::ROOT, "x", 5).unwrap();
|
|
1121
|
+
doc1.commit();
|
|
1122
|
+
let msg1to2 = doc1
|
|
1123
|
+
.sync()
|
|
1124
|
+
.generate_sync_message(&mut s1)
|
|
1125
|
+
.expect("third reply from 1 to 2 was None");
|
|
1126
|
+
let mut expected_heads = vec![head1, head2];
|
|
1127
|
+
expected_heads.sort();
|
|
1128
|
+
let mut actual_heads = msg1to2.have[0].last_sync.clone();
|
|
1129
|
+
actual_heads.sort();
|
|
1130
|
+
assert_eq!(actual_heads, expected_heads);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
#[test]
|
|
1134
|
+
fn should_handle_false_positive_head() {
|
|
1135
|
+
// Scenario: ,-- n1
|
|
1136
|
+
// c0 <-- c1 <-- c2 <-- c3 <-- c4 <-- c5 <-- c6 <-- c7 <-- c8 <-- c9 <-+
|
|
1137
|
+
// `-- n2
|
|
1138
|
+
// where n2 is a false positive in the Bloom filter containing {n1}.
|
|
1139
|
+
// lastSync is c9.
|
|
1140
|
+
|
|
1141
|
+
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
1142
|
+
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
1143
|
+
let mut s1 = State::new();
|
|
1144
|
+
let mut s2 = State::new();
|
|
1145
|
+
|
|
1146
|
+
for i in 0..10 {
|
|
1147
|
+
doc1.put(crate::ROOT, "x", i).unwrap();
|
|
1148
|
+
doc1.commit();
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
1152
|
+
|
|
1153
|
+
// search for false positive; see comment above
|
|
1154
|
+
let mut i = 0;
|
|
1155
|
+
let (mut doc1, mut doc2) = loop {
|
|
1156
|
+
let mut doc1copy = doc1
|
|
1157
|
+
.clone()
|
|
1158
|
+
.with_actor(ActorId::try_from("01234567").unwrap());
|
|
1159
|
+
let val1 = format!("{} @ n1", i);
|
|
1160
|
+
doc1copy.put(crate::ROOT, "x", val1).unwrap();
|
|
1161
|
+
doc1copy.commit();
|
|
1162
|
+
|
|
1163
|
+
let mut doc2copy = doc1
|
|
1164
|
+
.clone()
|
|
1165
|
+
.with_actor(ActorId::try_from("89abcdef").unwrap());
|
|
1166
|
+
let val2 = format!("{} @ n2", i);
|
|
1167
|
+
doc2copy.put(crate::ROOT, "x", val2).unwrap();
|
|
1168
|
+
doc2copy.commit();
|
|
1169
|
+
|
|
1170
|
+
let n1_bloom = BloomFilter::from_hashes(doc1copy.get_heads().into_iter());
|
|
1171
|
+
if n1_bloom.contains_hash(&doc2copy.get_heads()[0]) {
|
|
1172
|
+
break (doc1copy, doc2copy);
|
|
1173
|
+
}
|
|
1174
|
+
i += 1;
|
|
1175
|
+
};
|
|
1176
|
+
|
|
1177
|
+
let mut all_heads = doc1.get_heads();
|
|
1178
|
+
all_heads.extend(doc2.get_heads());
|
|
1179
|
+
all_heads.sort();
|
|
1180
|
+
|
|
1181
|
+
// reset sync states
|
|
1182
|
+
let (_, mut s1) = State::parse(Input::new(s1.encode().as_slice())).unwrap();
|
|
1183
|
+
let (_, mut s2) = State::parse(Input::new(s2.encode().as_slice())).unwrap();
|
|
1184
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
1185
|
+
assert_eq!(doc1.get_heads(), all_heads);
|
|
1186
|
+
assert_eq!(doc2.get_heads(), all_heads);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
#[test]
|
|
1190
|
+
fn should_handle_chains_of_false_positives() {
|
|
1191
|
+
//// Scenario: ,-- c5
|
|
1192
|
+
//// c0 <-- c1 <-- c2 <-- c3 <-- c4 <-+
|
|
1193
|
+
//// `-- n2c1 <-- n2c2 <-- n2c3
|
|
1194
|
+
//// where n2c1 and n2c2 are both false positives in the Bloom filter containing {c5}.
|
|
1195
|
+
//// lastSync is c4.
|
|
1196
|
+
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
1197
|
+
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
1198
|
+
let mut s1 = State::new();
|
|
1199
|
+
let mut s2 = State::new();
|
|
1200
|
+
|
|
1201
|
+
for i in 0..10 {
|
|
1202
|
+
doc1.put(crate::ROOT, "x", i).unwrap();
|
|
1203
|
+
doc1.commit();
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
1207
|
+
|
|
1208
|
+
doc1.put(crate::ROOT, "x", 5).unwrap();
|
|
1209
|
+
doc1.commit();
|
|
1210
|
+
let bloom = BloomFilter::from_hashes(doc1.get_heads().into_iter());
|
|
1211
|
+
|
|
1212
|
+
// search for false positive; see comment above
|
|
1213
|
+
let mut i = 0;
|
|
1214
|
+
let mut doc2 = loop {
|
|
1215
|
+
let mut doc = doc2
|
|
1216
|
+
.fork()
|
|
1217
|
+
.with_actor(ActorId::try_from("89abcdef").unwrap());
|
|
1218
|
+
doc.put(crate::ROOT, "x", format!("{} at 89abdef", i))
|
|
1219
|
+
.unwrap();
|
|
1220
|
+
doc.commit();
|
|
1221
|
+
if bloom.contains_hash(&doc.get_heads()[0]) {
|
|
1222
|
+
break doc;
|
|
1223
|
+
}
|
|
1224
|
+
i += 1;
|
|
1225
|
+
};
|
|
1226
|
+
|
|
1227
|
+
// find another false positive building on the first
|
|
1228
|
+
i = 0;
|
|
1229
|
+
let mut doc2 = loop {
|
|
1230
|
+
let mut doc = doc2
|
|
1231
|
+
.fork()
|
|
1232
|
+
.with_actor(ActorId::try_from("89abcdef").unwrap());
|
|
1233
|
+
doc.put(crate::ROOT, "x", format!("{} again", i)).unwrap();
|
|
1234
|
+
doc.commit();
|
|
1235
|
+
if bloom.contains_hash(&doc.get_heads()[0]) {
|
|
1236
|
+
break doc;
|
|
1237
|
+
}
|
|
1238
|
+
i += 1;
|
|
1239
|
+
};
|
|
1240
|
+
|
|
1241
|
+
doc2.put(crate::ROOT, "x", "final @ 89abcdef").unwrap();
|
|
1242
|
+
|
|
1243
|
+
let mut all_heads = doc1.get_heads();
|
|
1244
|
+
all_heads.extend(doc2.get_heads());
|
|
1245
|
+
all_heads.sort();
|
|
1246
|
+
|
|
1247
|
+
let (_, mut s1) = State::parse(Input::new(s1.encode().as_slice())).unwrap();
|
|
1248
|
+
let (_, mut s2) = State::parse(Input::new(s2.encode().as_slice())).unwrap();
|
|
1249
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
1250
|
+
assert_eq!(doc1.get_heads(), all_heads);
|
|
1251
|
+
assert_eq!(doc2.get_heads(), all_heads);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
#[test]
|
|
1255
|
+
fn should_handle_lots_of_branching_and_merging() {
|
|
1256
|
+
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("01234567").unwrap());
|
|
1257
|
+
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("89abcdef").unwrap());
|
|
1258
|
+
let mut doc3 = crate::AutoCommit::new().with_actor(ActorId::try_from("fedcba98").unwrap());
|
|
1259
|
+
let mut s1 = State::new();
|
|
1260
|
+
let mut s2 = State::new();
|
|
1261
|
+
|
|
1262
|
+
doc1.put(crate::ROOT, "x", 0).unwrap();
|
|
1263
|
+
let change1 = doc1.get_last_local_change().unwrap().clone();
|
|
1264
|
+
|
|
1265
|
+
doc2.apply_changes([change1.clone()]).unwrap();
|
|
1266
|
+
doc3.apply_changes([change1]).unwrap();
|
|
1267
|
+
|
|
1268
|
+
doc3.put(crate::ROOT, "x", 1).unwrap();
|
|
1269
|
+
|
|
1270
|
+
//// - n1c1 <------ n1c2 <------ n1c3 <-- etc. <-- n1c20 <------ n1c21
|
|
1271
|
+
//// / \/ \/ \/
|
|
1272
|
+
//// / /\ /\ /\
|
|
1273
|
+
//// c0 <---- n2c1 <------ n2c2 <------ n2c3 <-- etc. <-- n2c20 <------ n2c21
|
|
1274
|
+
//// \ /
|
|
1275
|
+
//// ---------------------------------------------- n3c1 <-----
|
|
1276
|
+
for i in 1..20 {
|
|
1277
|
+
doc1.put(crate::ROOT, "n1", i).unwrap();
|
|
1278
|
+
doc2.put(crate::ROOT, "n2", i).unwrap();
|
|
1279
|
+
let change1 = doc1.get_last_local_change().unwrap().clone();
|
|
1280
|
+
let change2 = doc2.get_last_local_change().unwrap().clone();
|
|
1281
|
+
doc1.apply_changes([change2.clone()]).unwrap();
|
|
1282
|
+
doc2.apply_changes([change1]).unwrap();
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
1286
|
+
|
|
1287
|
+
//// Having n3's last change concurrent to the last sync heads forces us into the slower code path
|
|
1288
|
+
let change3 = doc3.get_last_local_change().unwrap().clone();
|
|
1289
|
+
doc2.apply_changes([change3]).unwrap();
|
|
1290
|
+
|
|
1291
|
+
doc1.put(crate::ROOT, "n1", "final").unwrap();
|
|
1292
|
+
doc2.put(crate::ROOT, "n1", "final").unwrap();
|
|
1293
|
+
|
|
1294
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
1295
|
+
|
|
1296
|
+
assert_eq!(doc1.get_heads(), doc2.get_heads());
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
#[test]
|
|
1300
|
+
fn in_flight_logic_should_not_sabotage_concurrent_changes() {
|
|
1301
|
+
// This reproduces issue https://github.com/automerge/automerge/issues/702
|
|
1302
|
+
//
|
|
1303
|
+
// This problem manifested as a situation where the sync states of two
|
|
1304
|
+
// ends of a connection both return None from `generate_sync_message` -
|
|
1305
|
+
// indicating that there is nothing to send - yet the documents were
|
|
1306
|
+
// different at either end.
|
|
1307
|
+
|
|
1308
|
+
// Because this logic depends on bloom filter false positives we have to
|
|
1309
|
+
// run the test many times, hence this loop
|
|
1310
|
+
for _ in 0..300 {
|
|
1311
|
+
// create two documents
|
|
1312
|
+
let mut doc1 = crate::AutoCommit::new();
|
|
1313
|
+
let mut doc2 = crate::AutoCommit::new();
|
|
1314
|
+
let mut s1 = State::new();
|
|
1315
|
+
let mut s2 = State::new();
|
|
1316
|
+
|
|
1317
|
+
// get them in sync
|
|
1318
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
1319
|
+
|
|
1320
|
+
// make a change on doc2
|
|
1321
|
+
doc2.put(crate::ROOT, "x", 0).unwrap();
|
|
1322
|
+
|
|
1323
|
+
// generate a sync message containing the change (this should
|
|
1324
|
+
// alwasy be Some because we have generated new local changes)
|
|
1325
|
+
let msg = doc2.sync().generate_sync_message(&mut s2).unwrap();
|
|
1326
|
+
// Receive that sync message on doc1
|
|
1327
|
+
doc1.sync().receive_sync_message(&mut s1, msg).unwrap();
|
|
1328
|
+
|
|
1329
|
+
// now before sending any messages back to doc2, make a change on
|
|
1330
|
+
// doc1
|
|
1331
|
+
doc1.put(crate::ROOT, "x", 1).unwrap();
|
|
1332
|
+
|
|
1333
|
+
// now synchronize
|
|
1334
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
1335
|
+
|
|
1336
|
+
// At this point both documents should be equal
|
|
1337
|
+
assert_eq!(doc1.get_heads(), doc2.get_heads());
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
fn sync(
|
|
1342
|
+
a: &mut crate::AutoCommit,
|
|
1343
|
+
b: &mut crate::AutoCommit,
|
|
1344
|
+
a_sync_state: &mut State,
|
|
1345
|
+
b_sync_state: &mut State,
|
|
1346
|
+
) {
|
|
1347
|
+
//function sync(a: Automerge, b: Automerge, aSyncState = initSyncState(), bSyncState = initSyncState()) {
|
|
1348
|
+
const MAX_ITER: usize = 10;
|
|
1349
|
+
let mut iterations = 0;
|
|
1350
|
+
|
|
1351
|
+
loop {
|
|
1352
|
+
let a_to_b = a.sync().generate_sync_message(a_sync_state);
|
|
1353
|
+
let b_to_a = b.sync().generate_sync_message(b_sync_state);
|
|
1354
|
+
if a_to_b.is_none() && b_to_a.is_none() {
|
|
1355
|
+
break;
|
|
1356
|
+
}
|
|
1357
|
+
if iterations > MAX_ITER {
|
|
1358
|
+
panic!("failed to sync in {} iterations", MAX_ITER);
|
|
1359
|
+
}
|
|
1360
|
+
if let Some(msg) = a_to_b {
|
|
1361
|
+
b.sync().receive_sync_message(b_sync_state, msg).unwrap()
|
|
1362
|
+
}
|
|
1363
|
+
if let Some(msg) = b_to_a {
|
|
1364
|
+
a.sync().receive_sync_message(a_sync_state, msg).unwrap()
|
|
1365
|
+
}
|
|
1366
|
+
iterations += 1;
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
#[test]
|
|
1371
|
+
fn if_first_message_has_no_heads_and_supports_v2_message_send_whole_doc() {
|
|
1372
|
+
let mut doc1 = crate::AutoCommit::new();
|
|
1373
|
+
let mut doc2 = crate::AutoCommit::new();
|
|
1374
|
+
doc2.put(crate::ROOT, "foo", "bar").unwrap();
|
|
1375
|
+
|
|
1376
|
+
let mut s1 = State::new();
|
|
1377
|
+
let mut s2 = State::new();
|
|
1378
|
+
|
|
1379
|
+
let outgoing = doc1
|
|
1380
|
+
.sync()
|
|
1381
|
+
.generate_sync_message(&mut s1)
|
|
1382
|
+
.expect("message was none");
|
|
1383
|
+
|
|
1384
|
+
doc2.sync().receive_sync_message(&mut s2, outgoing).unwrap();
|
|
1385
|
+
|
|
1386
|
+
let response = doc2
|
|
1387
|
+
.sync()
|
|
1388
|
+
.generate_sync_message(&mut s2)
|
|
1389
|
+
.expect("response was none");
|
|
1390
|
+
|
|
1391
|
+
let Message { changes, .. } = response;
|
|
1392
|
+
|
|
1393
|
+
let (_, chunk) = Chunk::parse(Input::new(&changes.0[0])).unwrap();
|
|
1394
|
+
assert!(matches!(chunk, Chunk::Document(_)));
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
#[test]
|
|
1398
|
+
fn read_only_sync_does_not_apply_incoming_changes() {
|
|
1399
|
+
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
1400
|
+
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
1401
|
+
|
|
1402
|
+
doc1.put(crate::ROOT, "from_doc1", "hello").unwrap();
|
|
1403
|
+
doc1.commit();
|
|
1404
|
+
doc2.put(crate::ROOT, "from_doc2", "world").unwrap();
|
|
1405
|
+
doc2.commit();
|
|
1406
|
+
|
|
1407
|
+
let doc1_heads_before = doc1.get_heads();
|
|
1408
|
+
let doc2_heads_before = doc2.get_heads();
|
|
1409
|
+
|
|
1410
|
+
// doc1 is read-only: it should send its changes but not accept doc2's
|
|
1411
|
+
let mut s1 = State::new_read_only();
|
|
1412
|
+
let mut s2 = State::new();
|
|
1413
|
+
|
|
1414
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
1415
|
+
|
|
1416
|
+
// doc2 should have received doc1's changes
|
|
1417
|
+
assert!(doc2.get(crate::ROOT, "from_doc1").unwrap().is_some());
|
|
1418
|
+
assert!(doc2.get(crate::ROOT, "from_doc2").unwrap().is_some());
|
|
1419
|
+
|
|
1420
|
+
// doc1 should NOT have received doc2's changes
|
|
1421
|
+
assert!(doc1.get(crate::ROOT, "from_doc1").unwrap().is_some());
|
|
1422
|
+
assert!(doc1.get(crate::ROOT, "from_doc2").unwrap().is_none());
|
|
1423
|
+
|
|
1424
|
+
// doc1's heads should be unchanged
|
|
1425
|
+
assert_eq!(doc1.get_heads(), doc1_heads_before);
|
|
1426
|
+
|
|
1427
|
+
// doc2's heads should have advanced
|
|
1428
|
+
assert_ne!(doc2.get_heads(), doc2_heads_before);
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
#[test]
|
|
1432
|
+
fn read_only_empty_peer_syncs_with_data_peer() {
|
|
1433
|
+
// An empty read-only peer syncs with a peer that has data.
|
|
1434
|
+
// This exercises the V2 "send full document" path on the non-read-only side.
|
|
1435
|
+
// The read-only peer should ignore the document and the protocol should converge.
|
|
1436
|
+
let mut doc1 = crate::AutoCommit::new();
|
|
1437
|
+
let mut doc2 = crate::AutoCommit::new();
|
|
1438
|
+
doc2.put(crate::ROOT, "key", "value").unwrap();
|
|
1439
|
+
doc2.commit();
|
|
1440
|
+
|
|
1441
|
+
let mut s1 = State::new_read_only();
|
|
1442
|
+
let mut s2 = State::new();
|
|
1443
|
+
|
|
1444
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
1445
|
+
|
|
1446
|
+
// doc1 should still be empty
|
|
1447
|
+
assert!(doc1.get(crate::ROOT, "key").unwrap().is_none());
|
|
1448
|
+
assert!(doc1.get_heads().is_empty());
|
|
1449
|
+
|
|
1450
|
+
// doc2 should be unchanged
|
|
1451
|
+
assert!(doc2.get(crate::ROOT, "key").unwrap().is_some());
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
#[test]
|
|
1455
|
+
fn both_peers_read_only() {
|
|
1456
|
+
// When both peers are read-only, neither applies the other's changes.
|
|
1457
|
+
// The protocol should still converge.
|
|
1458
|
+
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
1459
|
+
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
1460
|
+
|
|
1461
|
+
doc1.put(crate::ROOT, "from_doc1", "hello").unwrap();
|
|
1462
|
+
doc1.commit();
|
|
1463
|
+
doc2.put(crate::ROOT, "from_doc2", "world").unwrap();
|
|
1464
|
+
doc2.commit();
|
|
1465
|
+
|
|
1466
|
+
let doc1_heads = doc1.get_heads();
|
|
1467
|
+
let doc2_heads = doc2.get_heads();
|
|
1468
|
+
|
|
1469
|
+
let mut s1 = State::new_read_only();
|
|
1470
|
+
let mut s2 = State::new_read_only();
|
|
1471
|
+
|
|
1472
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
1473
|
+
|
|
1474
|
+
// Neither peer should have the other's changes
|
|
1475
|
+
assert!(doc1.get(crate::ROOT, "from_doc2").unwrap().is_none());
|
|
1476
|
+
assert!(doc2.get(crate::ROOT, "from_doc1").unwrap().is_none());
|
|
1477
|
+
|
|
1478
|
+
// Both heads unchanged
|
|
1479
|
+
assert_eq!(doc1.get_heads(), doc1_heads);
|
|
1480
|
+
assert_eq!(doc2.get_heads(), doc2_heads);
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
#[test]
|
|
1484
|
+
fn both_peers_read_only_converges_to_none() {
|
|
1485
|
+
// Explicitly verify that generate_sync_message returns None on both
|
|
1486
|
+
// sides after the initial exchange between two read-only peers.
|
|
1487
|
+
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
1488
|
+
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
1489
|
+
|
|
1490
|
+
doc1.put(crate::ROOT, "from_doc1", "hello").unwrap();
|
|
1491
|
+
doc1.commit();
|
|
1492
|
+
doc2.put(crate::ROOT, "from_doc2", "world").unwrap();
|
|
1493
|
+
doc2.commit();
|
|
1494
|
+
|
|
1495
|
+
let mut s1 = State::new_read_only();
|
|
1496
|
+
let mut s2 = State::new_read_only();
|
|
1497
|
+
|
|
1498
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
1499
|
+
|
|
1500
|
+
// After sync, both must return None — no infinite loop
|
|
1501
|
+
assert!(doc1.sync().generate_sync_message(&mut s1).is_none());
|
|
1502
|
+
assert!(doc2.sync().generate_sync_message(&mut s2).is_none());
|
|
1503
|
+
|
|
1504
|
+
// Both discover the other is read-only
|
|
1505
|
+
assert!(s1.is_peer_read_only());
|
|
1506
|
+
assert!(s2.is_peer_read_only());
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
#[test]
|
|
1510
|
+
fn both_read_only_one_makes_local_changes() {
|
|
1511
|
+
// Both peers are read-only. One makes local changes between sync
|
|
1512
|
+
// rounds. The updated heads should be communicated (no changes sent),
|
|
1513
|
+
// and the protocol should converge.
|
|
1514
|
+
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
1515
|
+
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
1516
|
+
|
|
1517
|
+
let mut s1 = State::new_read_only();
|
|
1518
|
+
let mut s2 = State::new_read_only();
|
|
1519
|
+
|
|
1520
|
+
// Initial sync with no data — just exchange hellos
|
|
1521
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
1522
|
+
|
|
1523
|
+
// doc1 makes local changes
|
|
1524
|
+
doc1.put(crate::ROOT, "key", "value1").unwrap();
|
|
1525
|
+
doc1.commit();
|
|
1526
|
+
let doc1_heads_after = doc1.get_heads();
|
|
1527
|
+
|
|
1528
|
+
// Sync again — should converge, doc1's new heads communicated
|
|
1529
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
1530
|
+
|
|
1531
|
+
// doc2 should know about doc1's heads but not have the changes
|
|
1532
|
+
assert_eq!(s2.their_heads.as_ref().unwrap(), &doc1_heads_after);
|
|
1533
|
+
assert!(doc2.get(crate::ROOT, "key").unwrap().is_none());
|
|
1534
|
+
|
|
1535
|
+
// doc1 makes more changes
|
|
1536
|
+
doc1.put(crate::ROOT, "key", "value2").unwrap();
|
|
1537
|
+
doc1.commit();
|
|
1538
|
+
let doc1_heads_after2 = doc1.get_heads();
|
|
1539
|
+
|
|
1540
|
+
// Sync again
|
|
1541
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
1542
|
+
|
|
1543
|
+
// doc2 should see the updated heads
|
|
1544
|
+
assert_eq!(s2.their_heads.as_ref().unwrap(), &doc1_heads_after2);
|
|
1545
|
+
assert!(doc2.get(crate::ROOT, "key").unwrap().is_none());
|
|
1546
|
+
|
|
1547
|
+
// Must converge
|
|
1548
|
+
assert!(doc1.sync().generate_sync_message(&mut s1).is_none());
|
|
1549
|
+
assert!(doc2.sync().generate_sync_message(&mut s2).is_none());
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
#[test]
|
|
1553
|
+
fn both_read_only_both_make_local_changes() {
|
|
1554
|
+
// Both peers are read-only and both make local changes between sync
|
|
1555
|
+
// rounds. Both should learn the other's updated heads, neither should
|
|
1556
|
+
// receive actual changes, and the protocol should converge each round.
|
|
1557
|
+
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
1558
|
+
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
1559
|
+
|
|
1560
|
+
let mut s1 = State::new_read_only();
|
|
1561
|
+
let mut s2 = State::new_read_only();
|
|
1562
|
+
|
|
1563
|
+
for round in 0..5 {
|
|
1564
|
+
doc1.put(crate::ROOT, "doc1_counter", round as i64).unwrap();
|
|
1565
|
+
doc1.commit();
|
|
1566
|
+
doc2.put(crate::ROOT, "doc2_counter", round as i64).unwrap();
|
|
1567
|
+
doc2.commit();
|
|
1568
|
+
|
|
1569
|
+
let doc1_heads = doc1.get_heads();
|
|
1570
|
+
let doc2_heads = doc2.get_heads();
|
|
1571
|
+
|
|
1572
|
+
// Must converge each round (sync helper panics after 10 iterations)
|
|
1573
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
1574
|
+
|
|
1575
|
+
// Each peer should know the other's heads
|
|
1576
|
+
assert_eq!(
|
|
1577
|
+
s1.their_heads.as_ref().unwrap(),
|
|
1578
|
+
&doc2_heads,
|
|
1579
|
+
"round {round}: doc1 should know doc2's heads"
|
|
1580
|
+
);
|
|
1581
|
+
assert_eq!(
|
|
1582
|
+
s2.their_heads.as_ref().unwrap(),
|
|
1583
|
+
&doc1_heads,
|
|
1584
|
+
"round {round}: doc2 should know doc1's heads"
|
|
1585
|
+
);
|
|
1586
|
+
|
|
1587
|
+
// Neither should have the other's data
|
|
1588
|
+
assert!(
|
|
1589
|
+
doc1.get(crate::ROOT, "doc2_counter").unwrap().is_none(),
|
|
1590
|
+
"round {round}: doc1 should not have doc2's changes"
|
|
1591
|
+
);
|
|
1592
|
+
assert!(
|
|
1593
|
+
doc2.get(crate::ROOT, "doc1_counter").unwrap().is_none(),
|
|
1594
|
+
"round {round}: doc2 should not have doc1's changes"
|
|
1595
|
+
);
|
|
1596
|
+
|
|
1597
|
+
// Must be fully converged
|
|
1598
|
+
assert!(
|
|
1599
|
+
doc1.sync().generate_sync_message(&mut s1).is_none(),
|
|
1600
|
+
"round {round}: doc1 should have nothing more to send"
|
|
1601
|
+
);
|
|
1602
|
+
assert!(
|
|
1603
|
+
doc2.sync().generate_sync_message(&mut s2).is_none(),
|
|
1604
|
+
"round {round}: doc2 should have nothing more to send"
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
#[test]
|
|
1610
|
+
fn both_read_only_simultaneous_changes_during_sync() {
|
|
1611
|
+
// Both peers are read-only. Both make changes, then exchange messages
|
|
1612
|
+
// simultaneously (like the simultaneous sync test). Must converge.
|
|
1613
|
+
for _ in 0..100 {
|
|
1614
|
+
let mut doc1 =
|
|
1615
|
+
crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
1616
|
+
let mut doc2 =
|
|
1617
|
+
crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
1618
|
+
|
|
1619
|
+
let mut s1 = State::new_read_only();
|
|
1620
|
+
let mut s2 = State::new_read_only();
|
|
1621
|
+
|
|
1622
|
+
// Both make changes
|
|
1623
|
+
doc1.put(crate::ROOT, "x", 1).unwrap();
|
|
1624
|
+
doc1.commit();
|
|
1625
|
+
doc2.put(crate::ROOT, "y", 2).unwrap();
|
|
1626
|
+
doc2.commit();
|
|
1627
|
+
|
|
1628
|
+
// Sync (uses the helper which panics on non-convergence)
|
|
1629
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
1630
|
+
|
|
1631
|
+
// Now both make MORE changes
|
|
1632
|
+
doc1.put(crate::ROOT, "x", 3).unwrap();
|
|
1633
|
+
doc1.commit();
|
|
1634
|
+
doc2.put(crate::ROOT, "y", 4).unwrap();
|
|
1635
|
+
doc2.commit();
|
|
1636
|
+
|
|
1637
|
+
// Sync again
|
|
1638
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
1639
|
+
|
|
1640
|
+
// Must be fully converged
|
|
1641
|
+
assert!(doc1.sync().generate_sync_message(&mut s1).is_none());
|
|
1642
|
+
assert!(doc2.sync().generate_sync_message(&mut s2).is_none());
|
|
1643
|
+
|
|
1644
|
+
// Neither has the other's data
|
|
1645
|
+
assert!(doc1.get(crate::ROOT, "y").unwrap().is_none());
|
|
1646
|
+
assert!(doc2.get(crate::ROOT, "x").unwrap().is_none());
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
#[test]
|
|
1651
|
+
fn read_only_peer_new_changes_between_sync_rounds() {
|
|
1652
|
+
// After an initial sync converges, the read-only peer makes new local
|
|
1653
|
+
// changes and syncs again. The new changes should flow to the other peer.
|
|
1654
|
+
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
1655
|
+
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
1656
|
+
|
|
1657
|
+
doc1.put(crate::ROOT, "round1", "from_doc1").unwrap();
|
|
1658
|
+
doc1.commit();
|
|
1659
|
+
doc2.put(crate::ROOT, "round1", "from_doc2").unwrap();
|
|
1660
|
+
doc2.commit();
|
|
1661
|
+
|
|
1662
|
+
let mut s1 = State::new_read_only();
|
|
1663
|
+
let mut s2 = State::new();
|
|
1664
|
+
|
|
1665
|
+
// First sync round
|
|
1666
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
1667
|
+
assert!(doc2.get(crate::ROOT, "round1").unwrap().is_some());
|
|
1668
|
+
|
|
1669
|
+
// doc1 makes new changes
|
|
1670
|
+
doc1.put(crate::ROOT, "round2", "new_from_doc1").unwrap();
|
|
1671
|
+
doc1.commit();
|
|
1672
|
+
|
|
1673
|
+
// doc2 also makes new changes
|
|
1674
|
+
doc2.put(crate::ROOT, "round2", "new_from_doc2").unwrap();
|
|
1675
|
+
doc2.commit();
|
|
1676
|
+
|
|
1677
|
+
// Second sync round
|
|
1678
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
1679
|
+
|
|
1680
|
+
// doc2 should have doc1's new changes
|
|
1681
|
+
assert_eq!(
|
|
1682
|
+
doc2.get(crate::ROOT, "round2").unwrap().unwrap().0.to_str(),
|
|
1683
|
+
// doc2 has both values as a conflict, but the winning value depends on actor ordering;
|
|
1684
|
+
// just check the key exists
|
|
1685
|
+
doc2.get(crate::ROOT, "round2").unwrap().unwrap().0.to_str(),
|
|
1686
|
+
);
|
|
1687
|
+
// Verify doc2 has received "new_from_doc1" somewhere in the conflicts
|
|
1688
|
+
let all_values: Vec<_> = doc2
|
|
1689
|
+
.get_all(crate::ROOT, "round2")
|
|
1690
|
+
.unwrap()
|
|
1691
|
+
.into_iter()
|
|
1692
|
+
.map(|(v, _)| v.into_string().unwrap())
|
|
1693
|
+
.collect();
|
|
1694
|
+
assert!(all_values.contains(&"new_from_doc1".to_string()));
|
|
1695
|
+
assert!(all_values.contains(&"new_from_doc2".to_string()));
|
|
1696
|
+
|
|
1697
|
+
// doc1 should still NOT have doc2's changes
|
|
1698
|
+
assert!(
|
|
1699
|
+
doc1.get(crate::ROOT, "from_doc2").is_err()
|
|
1700
|
+
|| doc1.get(crate::ROOT, "from_doc2").unwrap().is_none()
|
|
1701
|
+
);
|
|
1702
|
+
let doc1_round2_values: Vec<_> = doc1
|
|
1703
|
+
.get_all(crate::ROOT, "round2")
|
|
1704
|
+
.unwrap()
|
|
1705
|
+
.into_iter()
|
|
1706
|
+
.map(|(v, _)| v.into_string().unwrap())
|
|
1707
|
+
.collect();
|
|
1708
|
+
assert_eq!(doc1_round2_values, vec!["new_from_doc1".to_string()]);
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
#[test]
|
|
1712
|
+
fn read_only_peer_concurrent_changes_during_sync() {
|
|
1713
|
+
// Like in_flight_logic_should_not_sabotage_concurrent_changes but with
|
|
1714
|
+
// a read-only peer. The read-only peer makes a change after receiving a
|
|
1715
|
+
// message but before the sync loop completes.
|
|
1716
|
+
for _ in 0..300 {
|
|
1717
|
+
let mut doc1 =
|
|
1718
|
+
crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
1719
|
+
let mut doc2 =
|
|
1720
|
+
crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
1721
|
+
let mut s1 = State::new_read_only();
|
|
1722
|
+
let mut s2 = State::new();
|
|
1723
|
+
|
|
1724
|
+
// get them in sync (doc1 read-only, so only doc2 gets doc1's changes)
|
|
1725
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
1726
|
+
|
|
1727
|
+
// doc2 makes a change
|
|
1728
|
+
doc2.put(crate::ROOT, "x", 0).unwrap();
|
|
1729
|
+
|
|
1730
|
+
// generate + receive one message from doc2 to doc1
|
|
1731
|
+
let msg = doc2.sync().generate_sync_message(&mut s2).unwrap();
|
|
1732
|
+
doc1.sync().receive_sync_message(&mut s1, msg).unwrap();
|
|
1733
|
+
|
|
1734
|
+
// before sending anything back, doc1 (read-only) makes a local change
|
|
1735
|
+
doc1.put(crate::ROOT, "y", 1).unwrap();
|
|
1736
|
+
|
|
1737
|
+
// now synchronize the rest
|
|
1738
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
1739
|
+
|
|
1740
|
+
// doc2 should have doc1's change
|
|
1741
|
+
assert!(doc2.get(crate::ROOT, "y").unwrap().is_some());
|
|
1742
|
+
// doc1 should NOT have doc2's change
|
|
1743
|
+
assert!(doc1.get(crate::ROOT, "x").unwrap().is_none());
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
#[test]
|
|
1748
|
+
fn read_only_publisher_to_multiple_consumers() {
|
|
1749
|
+
// R (read-only) publishes to A and B separately.
|
|
1750
|
+
// A makes its own changes. Then B syncs with R again.
|
|
1751
|
+
// B should NOT get A's changes through R (R never accepted them).
|
|
1752
|
+
let mut r = crate::AutoCommit::new().with_actor(ActorId::try_from("aaaaaa").unwrap());
|
|
1753
|
+
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("bbbbbb").unwrap());
|
|
1754
|
+
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("cccccc").unwrap());
|
|
1755
|
+
|
|
1756
|
+
r.put(crate::ROOT, "from_r", "hello").unwrap();
|
|
1757
|
+
r.commit();
|
|
1758
|
+
|
|
1759
|
+
// R syncs with A (read-only: A gets R's changes, R ignores A's)
|
|
1760
|
+
let mut sr_a = State::new_read_only();
|
|
1761
|
+
let mut sa_r = State::new();
|
|
1762
|
+
sync(&mut r, &mut a, &mut sr_a, &mut sa_r);
|
|
1763
|
+
assert!(a.get(crate::ROOT, "from_r").unwrap().is_some());
|
|
1764
|
+
|
|
1765
|
+
// A makes its own changes
|
|
1766
|
+
a.put(crate::ROOT, "from_a", "world").unwrap();
|
|
1767
|
+
a.commit();
|
|
1768
|
+
|
|
1769
|
+
// A syncs with R — R should ignore A's changes
|
|
1770
|
+
sync(&mut r, &mut a, &mut sr_a, &mut sa_r);
|
|
1771
|
+
assert!(r.get(crate::ROOT, "from_a").unwrap().is_none());
|
|
1772
|
+
|
|
1773
|
+
// R syncs with B (read-only: B gets R's changes, R ignores B's)
|
|
1774
|
+
let mut sr_b = State::new_read_only();
|
|
1775
|
+
let mut sb_r = State::new();
|
|
1776
|
+
sync(&mut r, &mut b, &mut sr_b, &mut sb_r);
|
|
1777
|
+
|
|
1778
|
+
// B should have R's changes but NOT A's
|
|
1779
|
+
assert!(b.get(crate::ROOT, "from_r").unwrap().is_some());
|
|
1780
|
+
assert!(b.get(crate::ROOT, "from_a").unwrap().is_none());
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
#[test]
|
|
1784
|
+
fn triangle_changes_arrive_via_two_paths() {
|
|
1785
|
+
// R (read-only) makes changes, syncs with A. A gets R's changes.
|
|
1786
|
+
// A syncs with B (read-write). B gets R's changes via A.
|
|
1787
|
+
// Now B syncs with R directly. R should recognize B already has its
|
|
1788
|
+
// changes (via bloom filter) and not redundantly send them.
|
|
1789
|
+
// The protocol should converge.
|
|
1790
|
+
let mut r = crate::AutoCommit::new().with_actor(ActorId::try_from("aaaaaa").unwrap());
|
|
1791
|
+
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("bbbbbb").unwrap());
|
|
1792
|
+
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("cccccc").unwrap());
|
|
1793
|
+
|
|
1794
|
+
r.put(crate::ROOT, "from_r", "hello").unwrap();
|
|
1795
|
+
r.commit();
|
|
1796
|
+
a.put(crate::ROOT, "from_a", "world").unwrap();
|
|
1797
|
+
a.commit();
|
|
1798
|
+
|
|
1799
|
+
// R syncs with A (read-only)
|
|
1800
|
+
let mut sr_a = State::new_read_only();
|
|
1801
|
+
let mut sa_r = State::new();
|
|
1802
|
+
sync(&mut r, &mut a, &mut sr_a, &mut sa_r);
|
|
1803
|
+
|
|
1804
|
+
// A now has R's changes
|
|
1805
|
+
assert!(a.get(crate::ROOT, "from_r").unwrap().is_some());
|
|
1806
|
+
|
|
1807
|
+
// A syncs with B (both read-write)
|
|
1808
|
+
let mut sa_b = State::new();
|
|
1809
|
+
let mut sb_a = State::new();
|
|
1810
|
+
sync(&mut a, &mut b, &mut sa_b, &mut sb_a);
|
|
1811
|
+
|
|
1812
|
+
// B now has R's changes (received via A)
|
|
1813
|
+
assert!(b.get(crate::ROOT, "from_r").unwrap().is_some());
|
|
1814
|
+
assert!(b.get(crate::ROOT, "from_a").unwrap().is_some());
|
|
1815
|
+
|
|
1816
|
+
// Now B syncs with R directly
|
|
1817
|
+
let mut sr_b = State::new_read_only();
|
|
1818
|
+
let mut sb_r = State::new();
|
|
1819
|
+
sync(&mut r, &mut b, &mut sr_b, &mut sb_r);
|
|
1820
|
+
|
|
1821
|
+
// R should still only have its own changes
|
|
1822
|
+
assert!(r.get(crate::ROOT, "from_a").unwrap().is_none());
|
|
1823
|
+
|
|
1824
|
+
// B should still have everything (R's changes aren't duplicated, just confirmed)
|
|
1825
|
+
assert!(b.get(crate::ROOT, "from_r").unwrap().is_some());
|
|
1826
|
+
assert!(b.get(crate::ROOT, "from_a").unwrap().is_some());
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
#[test]
|
|
1830
|
+
fn read_only_fully_connected_triangle() {
|
|
1831
|
+
// Three peers: A, B, R. R is read-only toward both A and B.
|
|
1832
|
+
// All three have their own changes. Each pair syncs.
|
|
1833
|
+
// A and B should converge to having A's + B's + R's changes.
|
|
1834
|
+
// R should only have its own changes.
|
|
1835
|
+
let mut r = crate::AutoCommit::new().with_actor(ActorId::try_from("aaaaaa").unwrap());
|
|
1836
|
+
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("bbbbbb").unwrap());
|
|
1837
|
+
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("cccccc").unwrap());
|
|
1838
|
+
|
|
1839
|
+
r.put(crate::ROOT, "from_r", "r_val").unwrap();
|
|
1840
|
+
r.commit();
|
|
1841
|
+
a.put(crate::ROOT, "from_a", "a_val").unwrap();
|
|
1842
|
+
a.commit();
|
|
1843
|
+
b.put(crate::ROOT, "from_b", "b_val").unwrap();
|
|
1844
|
+
b.commit();
|
|
1845
|
+
|
|
1846
|
+
let r_heads = r.get_heads();
|
|
1847
|
+
|
|
1848
|
+
// R syncs with A (read-only)
|
|
1849
|
+
let mut sr_a = State::new_read_only();
|
|
1850
|
+
let mut sa_r = State::new();
|
|
1851
|
+
sync(&mut r, &mut a, &mut sr_a, &mut sa_r);
|
|
1852
|
+
|
|
1853
|
+
// R syncs with B (read-only)
|
|
1854
|
+
let mut sr_b = State::new_read_only();
|
|
1855
|
+
let mut sb_r = State::new();
|
|
1856
|
+
sync(&mut r, &mut b, &mut sr_b, &mut sb_r);
|
|
1857
|
+
|
|
1858
|
+
// A and B each have R's changes via separate sync sessions
|
|
1859
|
+
assert!(a.get(crate::ROOT, "from_r").unwrap().is_some());
|
|
1860
|
+
assert!(b.get(crate::ROOT, "from_r").unwrap().is_some());
|
|
1861
|
+
|
|
1862
|
+
// A syncs with B (read-write) — both already have R's changes
|
|
1863
|
+
let mut sa_b = State::new();
|
|
1864
|
+
let mut sb_a = State::new();
|
|
1865
|
+
sync(&mut a, &mut b, &mut sa_b, &mut sb_a);
|
|
1866
|
+
|
|
1867
|
+
// A and B should have all three sets of changes
|
|
1868
|
+
assert!(a.get(crate::ROOT, "from_a").unwrap().is_some());
|
|
1869
|
+
assert!(a.get(crate::ROOT, "from_b").unwrap().is_some());
|
|
1870
|
+
assert!(a.get(crate::ROOT, "from_r").unwrap().is_some());
|
|
1871
|
+
assert!(b.get(crate::ROOT, "from_a").unwrap().is_some());
|
|
1872
|
+
assert!(b.get(crate::ROOT, "from_b").unwrap().is_some());
|
|
1873
|
+
assert!(b.get(crate::ROOT, "from_r").unwrap().is_some());
|
|
1874
|
+
|
|
1875
|
+
// A and B should have the same heads
|
|
1876
|
+
assert_eq!(a.get_heads(), b.get_heads());
|
|
1877
|
+
|
|
1878
|
+
// R should only have its own changes
|
|
1879
|
+
assert_eq!(r.get_heads(), r_heads);
|
|
1880
|
+
assert!(r.get(crate::ROOT, "from_a").unwrap().is_none());
|
|
1881
|
+
assert!(r.get(crate::ROOT, "from_b").unwrap().is_none());
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
#[test]
|
|
1885
|
+
fn stale_shared_heads_after_read_only_sync() {
|
|
1886
|
+
// R (read-only) syncs with A. Then A syncs with B (B gets everything).
|
|
1887
|
+
// Now B syncs with R using a fresh State. R tries to send its changes
|
|
1888
|
+
// to B, but B already has them (received via A). The bloom filter
|
|
1889
|
+
// should prevent redundant sending and the protocol should converge.
|
|
1890
|
+
let mut r = crate::AutoCommit::new().with_actor(ActorId::try_from("aaaaaa").unwrap());
|
|
1891
|
+
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("bbbbbb").unwrap());
|
|
1892
|
+
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("cccccc").unwrap());
|
|
1893
|
+
|
|
1894
|
+
// R has several changes to make bloom filter interaction interesting
|
|
1895
|
+
for i in 0..10 {
|
|
1896
|
+
r.put(crate::ROOT, "counter", i as i64).unwrap();
|
|
1897
|
+
r.commit();
|
|
1898
|
+
}
|
|
1899
|
+
a.put(crate::ROOT, "from_a", "a_val").unwrap();
|
|
1900
|
+
a.commit();
|
|
1901
|
+
|
|
1902
|
+
// R syncs with A (read-only)
|
|
1903
|
+
let mut sr_a = State::new_read_only();
|
|
1904
|
+
let mut sa_r = State::new();
|
|
1905
|
+
sync(&mut r, &mut a, &mut sr_a, &mut sa_r);
|
|
1906
|
+
|
|
1907
|
+
// A now has R's 10 changes
|
|
1908
|
+
assert!(a.get(crate::ROOT, "counter").unwrap().is_some());
|
|
1909
|
+
|
|
1910
|
+
// A syncs with B — B gets everything (R's changes + A's changes)
|
|
1911
|
+
let mut sa_b = State::new();
|
|
1912
|
+
let mut sb_a = State::new();
|
|
1913
|
+
sync(&mut a, &mut b, &mut sa_b, &mut sb_a);
|
|
1914
|
+
|
|
1915
|
+
assert!(b.get(crate::ROOT, "counter").unwrap().is_some());
|
|
1916
|
+
assert!(b.get(crate::ROOT, "from_a").unwrap().is_some());
|
|
1917
|
+
|
|
1918
|
+
// Now B syncs directly with R (fresh sync state)
|
|
1919
|
+
let mut sr_b = State::new_read_only();
|
|
1920
|
+
let mut sb_r = State::new();
|
|
1921
|
+
sync(&mut r, &mut b, &mut sr_b, &mut sb_r);
|
|
1922
|
+
|
|
1923
|
+
// R should not have B's or A's changes
|
|
1924
|
+
assert!(r.get(crate::ROOT, "from_a").unwrap().is_none());
|
|
1925
|
+
|
|
1926
|
+
// B should still have everything
|
|
1927
|
+
assert!(b.get(crate::ROOT, "counter").unwrap().is_some());
|
|
1928
|
+
assert!(b.get(crate::ROOT, "from_a").unwrap().is_some());
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
#[test]
|
|
1932
|
+
fn read_only_peer_receives_same_changes_from_two_peers() {
|
|
1933
|
+
// A and B sync with each other first (both have the same data).
|
|
1934
|
+
// Then A syncs with R (read-only), then B syncs with R (read-only).
|
|
1935
|
+
// R ignores changes both times. R's sync state must handle receiving
|
|
1936
|
+
// announcements about the same changes from two different peers.
|
|
1937
|
+
let mut r = crate::AutoCommit::new().with_actor(ActorId::try_from("aaaaaa").unwrap());
|
|
1938
|
+
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("bbbbbb").unwrap());
|
|
1939
|
+
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("cccccc").unwrap());
|
|
1940
|
+
|
|
1941
|
+
r.put(crate::ROOT, "from_r", "r_val").unwrap();
|
|
1942
|
+
r.commit();
|
|
1943
|
+
a.put(crate::ROOT, "from_a", "a_val").unwrap();
|
|
1944
|
+
a.commit();
|
|
1945
|
+
b.put(crate::ROOT, "from_b", "b_val").unwrap();
|
|
1946
|
+
b.commit();
|
|
1947
|
+
|
|
1948
|
+
// A and B sync — they now share A's + B's changes
|
|
1949
|
+
let mut sa_b = State::new();
|
|
1950
|
+
let mut sb_a = State::new();
|
|
1951
|
+
sync(&mut a, &mut b, &mut sa_b, &mut sb_a);
|
|
1952
|
+
assert_eq!(a.get_heads(), b.get_heads());
|
|
1953
|
+
|
|
1954
|
+
let r_heads = r.get_heads();
|
|
1955
|
+
|
|
1956
|
+
// A syncs with R (read-only) — R ignores A's+B's changes, A gets R's
|
|
1957
|
+
let mut sr_a = State::new_read_only();
|
|
1958
|
+
let mut sa_r = State::new();
|
|
1959
|
+
sync(&mut r, &mut a, &mut sr_a, &mut sa_r);
|
|
1960
|
+
assert!(a.get(crate::ROOT, "from_r").unwrap().is_some());
|
|
1961
|
+
assert_eq!(r.get_heads(), r_heads);
|
|
1962
|
+
|
|
1963
|
+
// B syncs with R (read-only) — R ignores the same changes again, B gets R's
|
|
1964
|
+
let mut sr_b = State::new_read_only();
|
|
1965
|
+
let mut sb_r = State::new();
|
|
1966
|
+
sync(&mut r, &mut b, &mut sr_b, &mut sb_r);
|
|
1967
|
+
assert!(b.get(crate::ROOT, "from_r").unwrap().is_some());
|
|
1968
|
+
|
|
1969
|
+
// R should still only have its own changes
|
|
1970
|
+
assert_eq!(r.get_heads(), r_heads);
|
|
1971
|
+
assert!(r.get(crate::ROOT, "from_a").unwrap().is_none());
|
|
1972
|
+
assert!(r.get(crate::ROOT, "from_b").unwrap().is_none());
|
|
1973
|
+
|
|
1974
|
+
// R makes a new change
|
|
1975
|
+
r.put(crate::ROOT, "from_r_2", "new").unwrap();
|
|
1976
|
+
r.commit();
|
|
1977
|
+
|
|
1978
|
+
// Sync R with A again — A should get the new change
|
|
1979
|
+
sync(&mut r, &mut a, &mut sr_a, &mut sa_r);
|
|
1980
|
+
assert!(a.get(crate::ROOT, "from_r_2").unwrap().is_some());
|
|
1981
|
+
|
|
1982
|
+
// Sync R with B again — B should also get the new change
|
|
1983
|
+
sync(&mut r, &mut b, &mut sr_b, &mut sb_r);
|
|
1984
|
+
assert!(b.get(crate::ROOT, "from_r_2").unwrap().is_some());
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
#[test]
|
|
1988
|
+
fn switch_read_only_to_read_write_mid_session() {
|
|
1989
|
+
// A starts read-only, syncs with B (A sends changes, ignores B's).
|
|
1990
|
+
// Then A switches to read-write and syncs again.
|
|
1991
|
+
// A should now receive B's changes despite B having previously sent
|
|
1992
|
+
// them (the sent_hashes problem).
|
|
1993
|
+
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
1994
|
+
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
1995
|
+
|
|
1996
|
+
a.put(crate::ROOT, "from_a", "hello").unwrap();
|
|
1997
|
+
a.commit();
|
|
1998
|
+
b.put(crate::ROOT, "from_b", "world").unwrap();
|
|
1999
|
+
b.commit();
|
|
2000
|
+
|
|
2001
|
+
let mut sa = State::new_read_only();
|
|
2002
|
+
let mut sb = State::new();
|
|
2003
|
+
|
|
2004
|
+
// First sync: A is read-only
|
|
2005
|
+
sync(&mut a, &mut b, &mut sa, &mut sb);
|
|
2006
|
+
assert!(b.get(crate::ROOT, "from_a").unwrap().is_some());
|
|
2007
|
+
assert!(a.get(crate::ROOT, "from_b").unwrap().is_none());
|
|
2008
|
+
|
|
2009
|
+
// Switch A to read-write
|
|
2010
|
+
sa.set_read_only(false);
|
|
2011
|
+
|
|
2012
|
+
// Second sync: A should now receive B's changes
|
|
2013
|
+
sync(&mut a, &mut b, &mut sa, &mut sb);
|
|
2014
|
+
assert!(a.get(crate::ROOT, "from_b").unwrap().is_some());
|
|
2015
|
+
|
|
2016
|
+
// Both should have the same heads now
|
|
2017
|
+
assert_eq!(a.get_heads(), b.get_heads());
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
#[test]
|
|
2021
|
+
fn switch_read_write_to_read_only_mid_session() {
|
|
2022
|
+
// A and B sync normally (both read-write). Then A switches to
|
|
2023
|
+
// read-only. B makes new changes. A should not receive them.
|
|
2024
|
+
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
2025
|
+
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
2026
|
+
|
|
2027
|
+
a.put(crate::ROOT, "from_a", "hello").unwrap();
|
|
2028
|
+
a.commit();
|
|
2029
|
+
b.put(crate::ROOT, "from_b", "world").unwrap();
|
|
2030
|
+
b.commit();
|
|
2031
|
+
|
|
2032
|
+
let mut sa = State::new();
|
|
2033
|
+
let mut sb = State::new();
|
|
2034
|
+
|
|
2035
|
+
// First sync: both read-write
|
|
2036
|
+
sync(&mut a, &mut b, &mut sa, &mut sb);
|
|
2037
|
+
assert_eq!(a.get_heads(), b.get_heads());
|
|
2038
|
+
|
|
2039
|
+
// Switch A to read-only
|
|
2040
|
+
sa.set_read_only(true);
|
|
2041
|
+
|
|
2042
|
+
// B makes new changes
|
|
2043
|
+
b.put(crate::ROOT, "new_from_b", "secret").unwrap();
|
|
2044
|
+
b.commit();
|
|
2045
|
+
|
|
2046
|
+
// A also makes a new change
|
|
2047
|
+
a.put(crate::ROOT, "new_from_a", "published").unwrap();
|
|
2048
|
+
a.commit();
|
|
2049
|
+
|
|
2050
|
+
// Second sync: A is now read-only
|
|
2051
|
+
sync(&mut a, &mut b, &mut sa, &mut sb);
|
|
2052
|
+
|
|
2053
|
+
// B should have A's new change
|
|
2054
|
+
assert!(b.get(crate::ROOT, "new_from_a").unwrap().is_some());
|
|
2055
|
+
// A should NOT have B's new change
|
|
2056
|
+
assert!(a.get(crate::ROOT, "new_from_b").unwrap().is_none());
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
#[test]
|
|
2060
|
+
fn switch_read_only_to_read_write_with_multiple_rounds() {
|
|
2061
|
+
// A starts read-only, syncs multiple rounds with B (B makes changes
|
|
2062
|
+
// each round). A switches to read-write. A should receive ALL of B's
|
|
2063
|
+
// accumulated changes.
|
|
2064
|
+
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
2065
|
+
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
2066
|
+
|
|
2067
|
+
a.put(crate::ROOT, "from_a", "initial").unwrap();
|
|
2068
|
+
a.commit();
|
|
2069
|
+
|
|
2070
|
+
let mut sa = State::new_read_only();
|
|
2071
|
+
let mut sb = State::new();
|
|
2072
|
+
|
|
2073
|
+
// Round 1: B has changes, A is read-only
|
|
2074
|
+
b.put(crate::ROOT, "round1", "from_b").unwrap();
|
|
2075
|
+
b.commit();
|
|
2076
|
+
sync(&mut a, &mut b, &mut sa, &mut sb);
|
|
2077
|
+
assert!(a.get(crate::ROOT, "round1").unwrap().is_none());
|
|
2078
|
+
|
|
2079
|
+
// Round 2: B has more changes, A still read-only
|
|
2080
|
+
b.put(crate::ROOT, "round2", "from_b").unwrap();
|
|
2081
|
+
b.commit();
|
|
2082
|
+
sync(&mut a, &mut b, &mut sa, &mut sb);
|
|
2083
|
+
assert!(a.get(crate::ROOT, "round2").unwrap().is_none());
|
|
2084
|
+
|
|
2085
|
+
// Round 3: B has even more changes, A still read-only
|
|
2086
|
+
b.put(crate::ROOT, "round3", "from_b").unwrap();
|
|
2087
|
+
b.commit();
|
|
2088
|
+
sync(&mut a, &mut b, &mut sa, &mut sb);
|
|
2089
|
+
assert!(a.get(crate::ROOT, "round3").unwrap().is_none());
|
|
2090
|
+
|
|
2091
|
+
// Switch A to read-write
|
|
2092
|
+
sa.set_read_only(false);
|
|
2093
|
+
|
|
2094
|
+
// Sync again — A should receive ALL of B's changes from all rounds
|
|
2095
|
+
sync(&mut a, &mut b, &mut sa, &mut sb);
|
|
2096
|
+
assert!(a.get(crate::ROOT, "round1").unwrap().is_some());
|
|
2097
|
+
assert!(a.get(crate::ROOT, "round2").unwrap().is_some());
|
|
2098
|
+
assert!(a.get(crate::ROOT, "round3").unwrap().is_some());
|
|
2099
|
+
|
|
2100
|
+
assert_eq!(a.get_heads(), b.get_heads());
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
#[test]
|
|
2104
|
+
fn toggle_read_only_multiple_times() {
|
|
2105
|
+
// Rapidly toggle read-only on and off, making changes between each toggle.
|
|
2106
|
+
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
2107
|
+
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
2108
|
+
|
|
2109
|
+
let mut sa = State::new_read_only();
|
|
2110
|
+
let mut sb = State::new();
|
|
2111
|
+
|
|
2112
|
+
// Round 1: A read-only, B makes changes
|
|
2113
|
+
b.put(crate::ROOT, "b1", "val").unwrap();
|
|
2114
|
+
b.commit();
|
|
2115
|
+
a.put(crate::ROOT, "a1", "val").unwrap();
|
|
2116
|
+
a.commit();
|
|
2117
|
+
sync(&mut a, &mut b, &mut sa, &mut sb);
|
|
2118
|
+
assert!(b.get(crate::ROOT, "a1").unwrap().is_some());
|
|
2119
|
+
assert!(a.get(crate::ROOT, "b1").unwrap().is_none());
|
|
2120
|
+
|
|
2121
|
+
// Round 2: switch to read-write
|
|
2122
|
+
sa.set_read_only(false);
|
|
2123
|
+
b.put(crate::ROOT, "b2", "val").unwrap();
|
|
2124
|
+
b.commit();
|
|
2125
|
+
a.put(crate::ROOT, "a2", "val").unwrap();
|
|
2126
|
+
a.commit();
|
|
2127
|
+
sync(&mut a, &mut b, &mut sa, &mut sb);
|
|
2128
|
+
assert!(a.get(crate::ROOT, "b1").unwrap().is_some());
|
|
2129
|
+
assert!(a.get(crate::ROOT, "b2").unwrap().is_some());
|
|
2130
|
+
assert!(b.get(crate::ROOT, "a2").unwrap().is_some());
|
|
2131
|
+
|
|
2132
|
+
// Round 3: switch back to read-only
|
|
2133
|
+
sa.set_read_only(true);
|
|
2134
|
+
b.put(crate::ROOT, "b3", "val").unwrap();
|
|
2135
|
+
b.commit();
|
|
2136
|
+
a.put(crate::ROOT, "a3", "val").unwrap();
|
|
2137
|
+
a.commit();
|
|
2138
|
+
sync(&mut a, &mut b, &mut sa, &mut sb);
|
|
2139
|
+
assert!(b.get(crate::ROOT, "a3").unwrap().is_some());
|
|
2140
|
+
assert!(a.get(crate::ROOT, "b3").unwrap().is_none());
|
|
2141
|
+
|
|
2142
|
+
// Round 4: switch to read-write again
|
|
2143
|
+
sa.set_read_only(false);
|
|
2144
|
+
sync(&mut a, &mut b, &mut sa, &mut sb);
|
|
2145
|
+
assert!(a.get(crate::ROOT, "b3").unwrap().is_some());
|
|
2146
|
+
assert_eq!(a.get_heads(), b.get_heads());
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
#[test]
|
|
2150
|
+
fn peer_discovers_remote_read_only_status() {
|
|
2151
|
+
// After exchanging messages, B should discover that A is read-only.
|
|
2152
|
+
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
2153
|
+
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
2154
|
+
|
|
2155
|
+
a.put(crate::ROOT, "from_a", "hello").unwrap();
|
|
2156
|
+
a.commit();
|
|
2157
|
+
b.put(crate::ROOT, "from_b", "world").unwrap();
|
|
2158
|
+
b.commit();
|
|
2159
|
+
|
|
2160
|
+
let mut sa = State::new_read_only();
|
|
2161
|
+
let mut sb = State::new();
|
|
2162
|
+
|
|
2163
|
+
// Before any exchange, B doesn't know A's status
|
|
2164
|
+
assert!(!sb.is_peer_read_only());
|
|
2165
|
+
|
|
2166
|
+
// A generates a message (includes ReadOnly capability)
|
|
2167
|
+
let msg = a.sync().generate_sync_message(&mut sa).unwrap();
|
|
2168
|
+
|
|
2169
|
+
// B receives it and discovers A is read-only
|
|
2170
|
+
b.sync().receive_sync_message(&mut sb, msg).unwrap();
|
|
2171
|
+
assert!(sb.is_peer_read_only());
|
|
2172
|
+
|
|
2173
|
+
// Complete the sync
|
|
2174
|
+
sync(&mut a, &mut b, &mut sa, &mut sb);
|
|
2175
|
+
assert!(sb.is_peer_read_only());
|
|
2176
|
+
|
|
2177
|
+
// A switches to read-write
|
|
2178
|
+
sa.set_read_only(false);
|
|
2179
|
+
|
|
2180
|
+
// After exchanging messages, B discovers A is no longer read-only
|
|
2181
|
+
let msg = a.sync().generate_sync_message(&mut sa).unwrap();
|
|
2182
|
+
b.sync().receive_sync_message(&mut sb, msg).unwrap();
|
|
2183
|
+
assert!(!sb.is_peer_read_only());
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
#[test]
|
|
2187
|
+
fn changes_not_sent_to_read_only_peer() {
|
|
2188
|
+
// B should not send changes to A when B knows A is read-only.
|
|
2189
|
+
// This saves bandwidth.
|
|
2190
|
+
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
2191
|
+
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
2192
|
+
|
|
2193
|
+
b.put(crate::ROOT, "from_b", "world").unwrap();
|
|
2194
|
+
b.commit();
|
|
2195
|
+
|
|
2196
|
+
let mut sa = State::new_read_only();
|
|
2197
|
+
let mut sb = State::new();
|
|
2198
|
+
|
|
2199
|
+
// Exchange initial messages so B discovers A is read-only
|
|
2200
|
+
let msg_a = a.sync().generate_sync_message(&mut sa).unwrap();
|
|
2201
|
+
b.sync().receive_sync_message(&mut sb, msg_a).unwrap();
|
|
2202
|
+
assert!(sb.is_peer_read_only());
|
|
2203
|
+
|
|
2204
|
+
// B generates a response — should have no changes since A is read-only
|
|
2205
|
+
let msg_b = b.sync().generate_sync_message(&mut sb).unwrap();
|
|
2206
|
+
assert!(msg_b.changes.is_empty());
|
|
2207
|
+
|
|
2208
|
+
// Complete the sync
|
|
2209
|
+
a.sync().receive_sync_message(&mut sa, msg_b).unwrap();
|
|
2210
|
+
sync(&mut a, &mut b, &mut sa, &mut sb);
|
|
2211
|
+
|
|
2212
|
+
// A still doesn't have B's changes (read-only + B didn't even send them)
|
|
2213
|
+
assert!(a.get(crate::ROOT, "from_b").unwrap().is_none());
|
|
2214
|
+
|
|
2215
|
+
// Now A switches to read-write
|
|
2216
|
+
sa.set_read_only(false);
|
|
2217
|
+
sync(&mut a, &mut b, &mut sa, &mut sb);
|
|
2218
|
+
|
|
2219
|
+
// A should now have B's changes
|
|
2220
|
+
assert!(a.get(crate::ROOT, "from_b").unwrap().is_some());
|
|
2221
|
+
assert_eq!(a.get_heads(), b.get_heads());
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
#[test]
|
|
2225
|
+
fn generate_message_after_set_read_only_even_with_in_flight() {
|
|
2226
|
+
// After calling generate_sync_message (setting in_flight=true),
|
|
2227
|
+
// then set_read_only, the next generate_sync_message must still
|
|
2228
|
+
// produce a message so the peer learns about the mode change.
|
|
2229
|
+
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
2230
|
+
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
2231
|
+
|
|
2232
|
+
a.put(crate::ROOT, "from_a", "hello").unwrap();
|
|
2233
|
+
a.commit();
|
|
2234
|
+
b.put(crate::ROOT, "from_b", "world").unwrap();
|
|
2235
|
+
b.commit();
|
|
2236
|
+
|
|
2237
|
+
let mut sa = State::new();
|
|
2238
|
+
let mut sb = State::new();
|
|
2239
|
+
|
|
2240
|
+
// Normal sync to get in sync
|
|
2241
|
+
sync(&mut a, &mut b, &mut sa, &mut sb);
|
|
2242
|
+
|
|
2243
|
+
// B makes a new change and sends it to A
|
|
2244
|
+
b.put(crate::ROOT, "new_from_b", "secret").unwrap();
|
|
2245
|
+
b.commit();
|
|
2246
|
+
let msg_b = b.sync().generate_sync_message(&mut sb).unwrap();
|
|
2247
|
+
a.sync().receive_sync_message(&mut sa, msg_b).unwrap();
|
|
2248
|
+
|
|
2249
|
+
// A generates a response (sets in_flight=true)
|
|
2250
|
+
let msg = a.sync().generate_sync_message(&mut sa);
|
|
2251
|
+
assert!(msg.is_some());
|
|
2252
|
+
assert!(sa.in_flight);
|
|
2253
|
+
|
|
2254
|
+
// A switches to read-only BEFORE receiving B's next message
|
|
2255
|
+
sa.set_read_only(true);
|
|
2256
|
+
|
|
2257
|
+
// A must be able to generate another message to advertise ReadOnly
|
|
2258
|
+
let msg = a.sync().generate_sync_message(&mut sa);
|
|
2259
|
+
assert!(
|
|
2260
|
+
msg.is_some(),
|
|
2261
|
+
"should generate message after set_read_only even with prior in_flight"
|
|
2262
|
+
);
|
|
2263
|
+
|
|
2264
|
+
// Verify the message advertises ReadOnly
|
|
2265
|
+
let msg = msg.unwrap();
|
|
2266
|
+
assert!(msg.flags.unwrap().contains(MessageFlags::READ_ONLY));
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
#[test]
|
|
2270
|
+
fn generate_message_after_set_read_only_false_even_with_in_flight() {
|
|
2271
|
+
// Same as above but switching from read-only to read-write.
|
|
2272
|
+
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
2273
|
+
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
2274
|
+
|
|
2275
|
+
a.put(crate::ROOT, "from_a", "hello").unwrap();
|
|
2276
|
+
a.commit();
|
|
2277
|
+
|
|
2278
|
+
let mut sa = State::new_read_only();
|
|
2279
|
+
let mut sb = State::new();
|
|
2280
|
+
|
|
2281
|
+
// Sync while read-only
|
|
2282
|
+
sync(&mut a, &mut b, &mut sa, &mut sb);
|
|
2283
|
+
|
|
2284
|
+
// B makes a change
|
|
2285
|
+
b.put(crate::ROOT, "from_b", "world").unwrap();
|
|
2286
|
+
b.commit();
|
|
2287
|
+
|
|
2288
|
+
// A generates (sets in_flight)
|
|
2289
|
+
let _msg = a.sync().generate_sync_message(&mut sa);
|
|
2290
|
+
// Force some state by receiving a message
|
|
2291
|
+
let msg_b = b.sync().generate_sync_message(&mut sb).unwrap();
|
|
2292
|
+
a.sync().receive_sync_message(&mut sa, msg_b).unwrap();
|
|
2293
|
+
let _ = a.sync().generate_sync_message(&mut sa);
|
|
2294
|
+
|
|
2295
|
+
// Now switch to read-write
|
|
2296
|
+
sa.set_read_only(false);
|
|
2297
|
+
|
|
2298
|
+
// Must generate a message with SyncReset
|
|
2299
|
+
let msg = a.sync().generate_sync_message(&mut sa);
|
|
2300
|
+
assert!(
|
|
2301
|
+
msg.is_some(),
|
|
2302
|
+
"should generate message after switching to read-write"
|
|
2303
|
+
);
|
|
2304
|
+
let msg = msg.unwrap();
|
|
2305
|
+
let flags = msg.flags.unwrap();
|
|
2306
|
+
assert!(flags.contains(MessageFlags::SYNC_RESET));
|
|
2307
|
+
assert!(!flags.contains(MessageFlags::READ_ONLY));
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
#[test]
|
|
2311
|
+
fn both_toggle_read_only_to_read_write_simultaneously() {
|
|
2312
|
+
// Both peers start read-only with their own changes, sync (exchanging
|
|
2313
|
+
// heads but not changes), then both switch to read-write simultaneously.
|
|
2314
|
+
// After syncing again, both should have each other's changes.
|
|
2315
|
+
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
2316
|
+
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
2317
|
+
|
|
2318
|
+
doc1.put(crate::ROOT, "from_doc1", "hello").unwrap();
|
|
2319
|
+
doc1.commit();
|
|
2320
|
+
doc2.put(crate::ROOT, "from_doc2", "world").unwrap();
|
|
2321
|
+
doc2.commit();
|
|
2322
|
+
|
|
2323
|
+
let mut s1 = State::new_read_only();
|
|
2324
|
+
let mut s2 = State::new_read_only();
|
|
2325
|
+
|
|
2326
|
+
// Sync while both read-only — neither gets the other's changes
|
|
2327
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
2328
|
+
assert!(doc1.get(crate::ROOT, "from_doc2").unwrap().is_none());
|
|
2329
|
+
assert!(doc2.get(crate::ROOT, "from_doc1").unwrap().is_none());
|
|
2330
|
+
|
|
2331
|
+
// Both switch to read-write simultaneously
|
|
2332
|
+
s1.set_read_only(false);
|
|
2333
|
+
s2.set_read_only(false);
|
|
2334
|
+
|
|
2335
|
+
// Sync again — both should exchange and apply changes
|
|
2336
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
2337
|
+
|
|
2338
|
+
assert!(doc1.get(crate::ROOT, "from_doc2").unwrap().is_some());
|
|
2339
|
+
assert!(doc2.get(crate::ROOT, "from_doc1").unwrap().is_some());
|
|
2340
|
+
assert_eq!(doc1.get_heads(), doc2.get_heads());
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
#[test]
|
|
2344
|
+
fn both_toggle_read_only_to_read_write_with_new_changes() {
|
|
2345
|
+
// Both peers start read-only, sync, then both switch to read-write
|
|
2346
|
+
// and make additional changes before syncing.
|
|
2347
|
+
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
2348
|
+
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
2349
|
+
|
|
2350
|
+
doc1.put(crate::ROOT, "original_1", "v1").unwrap();
|
|
2351
|
+
doc1.commit();
|
|
2352
|
+
doc2.put(crate::ROOT, "original_2", "v2").unwrap();
|
|
2353
|
+
doc2.commit();
|
|
2354
|
+
|
|
2355
|
+
let mut s1 = State::new_read_only();
|
|
2356
|
+
let mut s2 = State::new_read_only();
|
|
2357
|
+
|
|
2358
|
+
// Sync while both read-only
|
|
2359
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
2360
|
+
|
|
2361
|
+
// Both switch to read-write
|
|
2362
|
+
s1.set_read_only(false);
|
|
2363
|
+
s2.set_read_only(false);
|
|
2364
|
+
|
|
2365
|
+
// Both make NEW changes after switching
|
|
2366
|
+
doc1.put(crate::ROOT, "new_1", "after_switch").unwrap();
|
|
2367
|
+
doc1.commit();
|
|
2368
|
+
doc2.put(crate::ROOT, "new_2", "after_switch").unwrap();
|
|
2369
|
+
doc2.commit();
|
|
2370
|
+
|
|
2371
|
+
// Sync — should get both original AND new changes from each other
|
|
2372
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
2373
|
+
|
|
2374
|
+
assert!(doc1.get(crate::ROOT, "original_2").unwrap().is_some());
|
|
2375
|
+
assert!(doc1.get(crate::ROOT, "new_2").unwrap().is_some());
|
|
2376
|
+
assert!(doc2.get(crate::ROOT, "original_1").unwrap().is_some());
|
|
2377
|
+
assert!(doc2.get(crate::ROOT, "new_1").unwrap().is_some());
|
|
2378
|
+
assert_eq!(doc1.get_heads(), doc2.get_heads());
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
#[test]
|
|
2382
|
+
fn both_toggle_after_multiple_read_only_rounds() {
|
|
2383
|
+
// Both peers are read-only for several rounds, making changes each
|
|
2384
|
+
// round. Then both switch to read-write. All accumulated changes
|
|
2385
|
+
// from all rounds should be exchanged.
|
|
2386
|
+
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
2387
|
+
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
2388
|
+
|
|
2389
|
+
let mut s1 = State::new_read_only();
|
|
2390
|
+
let mut s2 = State::new_read_only();
|
|
2391
|
+
|
|
2392
|
+
// Several rounds of read-only sync with changes each round
|
|
2393
|
+
for i in 0..5 {
|
|
2394
|
+
doc1.put(crate::ROOT, format!("doc1_r{i}"), i as i64)
|
|
2395
|
+
.unwrap();
|
|
2396
|
+
doc1.commit();
|
|
2397
|
+
doc2.put(crate::ROOT, format!("doc2_r{i}"), i as i64)
|
|
2398
|
+
.unwrap();
|
|
2399
|
+
doc2.commit();
|
|
2400
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
// Neither has the other's changes from any round
|
|
2404
|
+
for i in 0..5 {
|
|
2405
|
+
assert!(doc1
|
|
2406
|
+
.get(crate::ROOT, format!("doc2_r{i}"))
|
|
2407
|
+
.unwrap()
|
|
2408
|
+
.is_none());
|
|
2409
|
+
assert!(doc2
|
|
2410
|
+
.get(crate::ROOT, format!("doc1_r{i}"))
|
|
2411
|
+
.unwrap()
|
|
2412
|
+
.is_none());
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
// Both switch to read-write
|
|
2416
|
+
s1.set_read_only(false);
|
|
2417
|
+
s2.set_read_only(false);
|
|
2418
|
+
|
|
2419
|
+
// Sync — all accumulated changes should flow
|
|
2420
|
+
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
|
|
2421
|
+
|
|
2422
|
+
for i in 0..5 {
|
|
2423
|
+
assert!(
|
|
2424
|
+
doc1.get(crate::ROOT, format!("doc2_r{i}"))
|
|
2425
|
+
.unwrap()
|
|
2426
|
+
.is_some(),
|
|
2427
|
+
"doc1 missing doc2_r{i}"
|
|
2428
|
+
);
|
|
2429
|
+
assert!(
|
|
2430
|
+
doc2.get(crate::ROOT, format!("doc1_r{i}"))
|
|
2431
|
+
.unwrap()
|
|
2432
|
+
.is_some(),
|
|
2433
|
+
"doc2 missing doc1_r{i}"
|
|
2434
|
+
);
|
|
2435
|
+
}
|
|
2436
|
+
assert_eq!(doc1.get_heads(), doc2.get_heads());
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
#[test]
|
|
2440
|
+
fn switch_to_read_write_with_old_peer() {
|
|
2441
|
+
// Simulates switching from read-only to read-write when the remote
|
|
2442
|
+
// peer is an old implementation that doesn't understand SyncReset.
|
|
2443
|
+
// Old peers have their_capabilities = None. The fallback sends empty
|
|
2444
|
+
// heads, which triggers the old "peer lost all data" reset path.
|
|
2445
|
+
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
|
|
2446
|
+
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
|
|
2447
|
+
|
|
2448
|
+
a.put(crate::ROOT, "from_a", "hello").unwrap();
|
|
2449
|
+
a.commit();
|
|
2450
|
+
b.put(crate::ROOT, "from_b", "world").unwrap();
|
|
2451
|
+
b.commit();
|
|
2452
|
+
|
|
2453
|
+
let mut sa = State::new_read_only();
|
|
2454
|
+
let mut sb = State::new();
|
|
2455
|
+
|
|
2456
|
+
// Sync while read-only
|
|
2457
|
+
sync(&mut a, &mut b, &mut sa, &mut sb);
|
|
2458
|
+
assert!(a.get(crate::ROOT, "from_b").unwrap().is_none());
|
|
2459
|
+
assert!(b.get(crate::ROOT, "from_a").unwrap().is_some());
|
|
2460
|
+
|
|
2461
|
+
// Simulate an old peer by clearing their_capabilities before switching.
|
|
2462
|
+
// set_read_only preserves their_capabilities, so we clear after.
|
|
2463
|
+
sa.set_read_only(false);
|
|
2464
|
+
sa.their_capabilities = None;
|
|
2465
|
+
|
|
2466
|
+
// The first message should have empty heads (old peer fallback)
|
|
2467
|
+
let msg = a.sync().generate_sync_message(&mut sa).unwrap();
|
|
2468
|
+
assert!(msg.heads.is_empty(), "should send empty heads for old peer");
|
|
2469
|
+
assert!(
|
|
2470
|
+
!msg.flags.unwrap().contains(MessageFlags::SYNC_RESET),
|
|
2471
|
+
"should not include SyncReset for old peer"
|
|
2472
|
+
);
|
|
2473
|
+
|
|
2474
|
+
// Complete the sync — old peer sees empty heads, clears sent_hashes
|
|
2475
|
+
b.sync().receive_sync_message(&mut sb, msg).unwrap();
|
|
2476
|
+
sync(&mut a, &mut b, &mut sa, &mut sb);
|
|
2477
|
+
|
|
2478
|
+
// A should now have B's changes
|
|
2479
|
+
assert!(a.get(crate::ROOT, "from_b").unwrap().is_some());
|
|
2480
|
+
assert_eq!(a.get_heads(), b.get_heads());
|
|
2481
|
+
}
|
|
2482
|
+
}
|