async def load_block(self, access: BlockAccess) -> None: """ Raises: FSError FSRemoteBlockNotFound FSBackendOfflineError FSWorkspaceInMaintenance FSWorkspaceNoAccess """ # Download rep = await self._backend_cmds("block_read", access.id) if rep["status"] == "not_found": raise FSRemoteBlockNotFound(access) elif rep["status"] == "not_allowed": # Seems we lost the access to the realm raise FSWorkspaceNoReadAccess("Cannot load block: no read access") elif rep["status"] == "in_maintenance": raise FSWorkspaceInMaintenance( f"Cannot download block while the workspace in maintenance") elif rep["status"] != "ok": raise FSError(f"Cannot download block: `{rep['status']}`") # Decryption try: block = access.key.decrypt(rep["block"]) # Decryption error except CryptoError as exc: raise FSError(f"Cannot decrypt block: {exc}") from exc # TODO: let encryption manager do the digest check ? assert HashDigest.from_data(block) == access.digest, access await self.local_storage.set_clean_block(access.id, block)
async def _do_wait_peer(self) -> Tuple[SASCode, SASCode, SecretKey]: claimer_private_key = PrivateKey.generate() rep = await self._cmds.invite_1_claimer_wait_peer( claimer_public_key=claimer_private_key.public_key) if rep["status"] != "ok": raise InviteError(f"Backend error during step 1: {rep}") shared_secret_key = generate_shared_secret_key( our_private_key=claimer_private_key, peer_public_key=rep["greeter_public_key"]) claimer_nonce = generate_nonce() rep = await self._cmds.invite_2a_claimer_send_hashed_nonce( claimer_hashed_nonce=HashDigest.from_data(claimer_nonce)) if rep["status"] == "invalid_state": raise InvitePeerResetError() elif rep["status"] != "ok": raise InviteError(f"Backend error during step 2a: {rep}") claimer_sas, greeter_sas = generate_sas_codes( claimer_nonce=claimer_nonce, greeter_nonce=rep["greeter_nonce"], shared_secret_key=shared_secret_key, ) rep = await self._cmds.invite_2b_claimer_send_nonce( claimer_nonce=claimer_nonce) if rep["status"] == "invalid_state": raise InvitePeerResetError() elif rep["status"] != "ok": raise InviteError(f"Backend error during step 2b: {rep}") return claimer_sas, greeter_sas, shared_secret_key
async def _do_wait_peer(self) -> Tuple[SASCode, SASCode, SecretKey]: claimer_private_key = PrivateKey.generate() rep = await self._cmds.invite_1_claimer_wait_peer( claimer_public_key=claimer_private_key.public_key) _check_rep(rep, step_name="step 1") shared_secret_key = generate_shared_secret_key( our_private_key=claimer_private_key, peer_public_key=rep["greeter_public_key"]) claimer_nonce = generate_nonce() rep = await self._cmds.invite_2a_claimer_send_hashed_nonce( claimer_hashed_nonce=HashDigest.from_data(claimer_nonce)) _check_rep(rep, step_name="step 2a") claimer_sas, greeter_sas = generate_sas_codes( claimer_nonce=claimer_nonce, greeter_nonce=rep["greeter_nonce"], shared_secret_key=shared_secret_key, ) rep = await self._cmds.invite_2b_claimer_send_nonce( claimer_nonce=claimer_nonce) _check_rep(rep, step_name="step 2b") return claimer_sas, greeter_sas, shared_secret_key
def test_hash_digest(bytes_cls): from parsec.crypto import _PyHashDigest, _RsHashDigest, HashDigest assert HashDigest is _RsHashDigest rst = HashDigest.from_data(bytes_cls(b"abc")) py = _PyHashDigest.from_data(bytes_cls(b"abc")) assert rst.digest == py.digest assert rst.hexdigest() == py.hexdigest() assert repr(rst) == repr(py) rst2 = HashDigest.from_data(bytes_cls(b"abc")) rst3 = HashDigest.from_data(bytes_cls(b"def")) assert rst == rst2 assert not rst != rst2 assert rst != rst3 assert not rst == rst3
async def _greeter_step_2(): rep = await invite_2a_claimer_send_hashed_nonce( invited_sock, claimer_hashed_nonce=HashDigest.from_data(b"<retry_nonce>") ) assert rep == {"status": "ok", "greeter_nonce": b"greeter nonce"} rep = await invite_2b_claimer_send_nonce( invited_sock, claimer_nonce=b"claimer nonce" ) assert rep == {"status": "ok"}
def test_block_access(): from parsec.api.data.manifest import _RsBlockAccess, BlockAccess, _PyBlockAccess assert BlockAccess is _RsBlockAccess def _assert_block_access_eq(py, rs): assert isinstance(py, _PyBlockAccess) assert isinstance(rs, _RsBlockAccess) assert py.id == rs.id assert py.key == rs.key assert py.offset == rs.offset assert py.size == rs.size assert py.digest == rs.digest kwargs = { "id": BlockID.new(), "key": SecretKey.generate(), "offset": 0, "size": 1024, "digest": HashDigest.from_data(b"a"), } py_ba = _PyBlockAccess(**kwargs) rs_ba = BlockAccess(**kwargs) _assert_block_access_eq(py_ba, rs_ba) kwargs = { "id": BlockID.new(), "key": SecretKey.generate(), "offset": 64, "size": 2048, "digest": HashDigest.from_data(b"b"), } py_ba = py_ba.evolve(**kwargs) rs_ba = rs_ba.evolve(**kwargs) _assert_block_access_eq(py_ba, rs_ba) kwargs["size"] = 0 with pytest.raises(ValueError): BlockAccess(**kwargs)
async def _claimer_step_2(): rep = await invite_2a_greeter_get_hashed_nonce( alice_backend_sock, token=invitation.token ) assert rep == { "status": "ok", "claimer_hashed_nonce": HashDigest.from_data(b"<retry_nonce>"), } rep = await invite_2b_greeter_send_nonce( alice_backend_sock, token=invitation.token, greeter_nonce=b"greeter nonce" ) assert rep == {"status": "ok", "claimer_nonce": b"claimer nonce"}
async def _do_wait_peer(self) -> Tuple[SASCode, SASCode, SecretKey]: greeter_private_key = PrivateKey.generate() rep = await self._cmds.invite_1_greeter_wait_peer( token=self.token, greeter_public_key=greeter_private_key.public_key) if rep["status"] in ("not_found", "already_deleted"): raise InviteNotAvailableError() elif rep["status"] != "ok": raise InviteError(f"Backend error during step 1: {rep}") shared_secret_key = generate_shared_secret_key( our_private_key=greeter_private_key, peer_public_key=rep["claimer_public_key"]) greeter_nonce = generate_nonce() rep = await self._cmds.invite_2a_greeter_get_hashed_nonce( token=self.token) if rep["status"] in ("not_found", "already_deleted"): raise InviteNotAvailableError() elif rep["status"] == "invalid_state": raise InvitePeerResetError() elif rep["status"] != "ok": raise InviteError(f"Backend error during step 2a: {rep}") claimer_hashed_nonce = rep["claimer_hashed_nonce"] rep = await self._cmds.invite_2b_greeter_send_nonce( token=self.token, greeter_nonce=greeter_nonce) if rep["status"] in ("not_found", "already_deleted"): raise InviteNotAvailableError() elif rep["status"] == "invalid_state": raise InvitePeerResetError() elif rep["status"] != "ok": raise InviteError(f"Backend error during step 2b: {rep}") if HashDigest.from_data(rep["claimer_nonce"]) != claimer_hashed_nonce: raise InviteError("Invitee nonce and hashed nonce doesn't match") claimer_sas, greeter_sas = generate_sas_codes( claimer_nonce=rep["claimer_nonce"], greeter_nonce=greeter_nonce, shared_secret_key=shared_secret_key, ) return claimer_sas, greeter_sas, shared_secret_key
async def _update_file(workspace_fs: WorkspaceFS, entry_id: EntryID, local_path: AnyPath, workspace_path: FsPath): remote_file_manifest = await workspace_fs.remote_loader.load_manifest( entry_id) remote_access_digests = [ access.digest for access in remote_file_manifest.blocks ] offset = 0 for idx, chunk in enumerate(await _chunks_from_path(local_path)): if HashDigest.from_data(chunk) != remote_access_digests[idx]: await workspace_fs.write_bytes(workspace_path, chunk, offset) print(f"update the block {idx} in {workspace_path}") offset += len(chunk) await workspace_fs.sync_by_id(entry_id, remote_changed=False, recursive=False)
async def _run_claimer(tb): peer_controller = tb.claimer_ctlr while True: order, step_4_payload = await peer_controller.peer_next_order() if order == "invite_info": await peer_controller.peer_do(invite_info, tb.claimer_sock) elif order == "1_wait_peer": await peer_controller.peer_do( invite_1_claimer_wait_peer, tb.claimer_sock, claimer_public_key=tb.claimer_privkey.public_key, ) elif order == "2a_send_hashed_nonce": await peer_controller.peer_do( invite_2a_claimer_send_hashed_nonce, tb.claimer_sock, claimer_hashed_nonce=HashDigest.from_data( b"<claimer_nonce>"), ) elif order == "2b_send_nonce": await peer_controller.peer_do(invite_2b_claimer_send_nonce, tb.claimer_sock, claimer_nonce=b"<claimer_nonce>") elif order == "3a_signify_trust": await peer_controller.peer_do(invite_3a_claimer_signify_trust, tb.claimer_sock) elif order == "3b_wait_peer_trust": await peer_controller.peer_do( invite_3b_claimer_wait_peer_trust, tb.claimer_sock) elif order == "4_communicate": assert step_4_payload is not None await peer_controller.peer_do(invite_4_claimer_communicate, tb.claimer_sock, payload=step_4_payload) else: assert False
def evolve_as_block(self, data: bytes) -> "Chunk": # No-op if self.is_block: return self # Check alignement if self.raw_offset != self.start: raise TypeError("This chunk is not aligned") # Craft access access = BlockAccess( id=BlockID(self.id), key=SecretKey.generate(), offset=self.start, size=self.stop - self.start, digest=HashDigest.from_data(data), ) # Evolve return self.evolve(access=access)
async def _run_claimer(peer_controller): while True: order, order_arg = await peer_controller.peer_next_order() if order == "1_wait_peer": await peer_controller.peer_do( invite_1_claimer_wait_peer, invited_sock, claimer_public_key=claimer_privkey.public_key, ) elif order == "2a_send_hashed_nonce": await peer_controller.peer_do( invite_2a_claimer_send_hashed_nonce, invited_sock, claimer_hashed_nonce=HashDigest.from_data(b"<claimer_nonce>"), ) elif order == "2b_send_nonce": await peer_controller.peer_do( invite_2b_claimer_send_nonce, invited_sock, claimer_nonce=b"<claimer_nonce>" ) elif order == "3a_signify_trust": await peer_controller.peer_do(invite_3a_claimer_signify_trust, invited_sock) elif order == "3b_wait_peer_trust": await peer_controller.peer_do(invite_3b_claimer_wait_peer_trust, invited_sock) elif order == "4_communicate": assert order_arg is not None await peer_controller.peer_do( invite_4_claimer_communicate, invited_sock, payload=order_arg ) else: assert False
def test_file_manifest(): from parsec.api.data.manifest import _RsFileManifest, FileManifest, _PyFileManifest, BlockAccess assert FileManifest is _RsFileManifest def _assert_file_manifest_eq(py, rs): assert isinstance(py, _PyFileManifest) assert isinstance(rs, _RsFileManifest) assert py.author == rs.author assert py.id == rs.id assert py.parent == rs.parent assert py.version == rs.version assert py.size == rs.size assert py.blocksize == rs.blocksize assert py.timestamp == rs.timestamp assert py.created == rs.created assert py.updated == rs.updated assert len(py.blocks) == len(rs.blocks) assert all( isinstance(b2, BlockAccess) and b1.id == b2.id and b1.offset == b2.offset and b1.size == b2.size for (b1, b2) in zip(py.blocks, rs.blocks)) kwargs = { "author": DeviceID("user@device"), "id": EntryID.new(), "parent": EntryID.new(), "version": 42, "size": 1337, "blocksize": 64, "timestamp": pendulum.now(), "created": pendulum.now(), "updated": pendulum.now(), "blocks": (BlockAccess( id=BlockID.new(), key=SecretKey.generate(), offset=0, size=1024, digest=HashDigest.from_data(b"a"), ), ), } py_fm = _PyFileManifest(**kwargs) rs_fm = FileManifest(**kwargs) _assert_file_manifest_eq(py_fm, rs_fm) kwargs = { "author": DeviceID("a@b"), "id": EntryID.new(), "parent": EntryID.new(), "version": 1337, "timestamp": pendulum.now(), "created": pendulum.now(), "updated": pendulum.now(), "blocks": (BlockAccess( id=BlockID.new(), key=SecretKey.generate(), offset=64, size=2048, digest=HashDigest.from_data(b"b"), ), ), } py_fm = py_fm.evolve(**kwargs) rs_fm = rs_fm.evolve(**kwargs) _assert_file_manifest_eq(py_fm, rs_fm)
async def test_claimer_step_2_retry( backend, alice, backend_sock_factory, alice_backend_sock, invitation, invited_sock ): greeter_privkey = PrivateKey.generate() claimer_privkey = PrivateKey.generate() greeter_retry_privkey = PrivateKey.generate() claimer_retry_privkey = PrivateKey.generate() # Step 1 async with real_clock_timeout(): async with invite_1_greeter_wait_peer.async_call( alice_backend_sock, token=invitation.token, greeter_public_key=greeter_privkey.public_key, ) as greeter_async_rep: claimer_rep = await invite_1_claimer_wait_peer( invited_sock, claimer_public_key=claimer_privkey.public_key ) assert claimer_rep == {"status": "ok", "greeter_public_key": greeter_privkey.public_key} assert greeter_async_rep.rep == { "status": "ok", "claimer_public_key": claimer_privkey.public_key, } # Greeter initiates step 2a... async with real_clock_timeout(): with backend.event_bus.listen() as spy: async with invite_2a_greeter_get_hashed_nonce.async_call( alice_backend_sock, token=invitation.token ) as greeter_2a_async_rep: await spy.wait_with_timeout( BackendEvent.INVITE_CONDUIT_UPDATED, kwargs={"organization_id": alice.organization_id, "token": invitation.token}, ) # ...but changes his mind and reset from another connection ! async with backend_sock_factory(backend, alice) as alice_backend_sock2: async with invite_1_greeter_wait_peer.async_call( alice_backend_sock2, token=invitation.token, greeter_public_key=greeter_retry_privkey.public_key, ) as greeter_retry_1_async_rep: # First connection should be notified of the reset await greeter_2a_async_rep.do_recv() assert greeter_2a_async_rep.rep == {"status": "invalid_state"} # Claimer now arrives and try to do step 2a rep = await invite_2a_claimer_send_hashed_nonce( invited_sock, claimer_hashed_nonce=HashDigest.from_data(b"<claimer_nonce>"), ) assert rep == {"status": "invalid_state"} # So claimer returns to step 1 rep = await invite_1_claimer_wait_peer( invited_sock, claimer_public_key=claimer_retry_privkey.public_key ) assert rep == { "status": "ok", "greeter_public_key": greeter_retry_privkey.public_key, } assert greeter_retry_1_async_rep.rep == { "status": "ok", "claimer_public_key": claimer_retry_privkey.public_key, } # Finally retry and achieve step 2 async def _claimer_step_2(): rep = await invite_2a_greeter_get_hashed_nonce( alice_backend_sock, token=invitation.token ) assert rep == { "status": "ok", "claimer_hashed_nonce": HashDigest.from_data(b"<retry_nonce>"), } rep = await invite_2b_greeter_send_nonce( alice_backend_sock, token=invitation.token, greeter_nonce=b"greeter nonce" ) assert rep == {"status": "ok", "claimer_nonce": b"claimer nonce"} async def _greeter_step_2(): rep = await invite_2a_claimer_send_hashed_nonce( invited_sock, claimer_hashed_nonce=HashDigest.from_data(b"<retry_nonce>") ) assert rep == {"status": "ok", "greeter_nonce": b"greeter nonce"} rep = await invite_2b_claimer_send_nonce( invited_sock, claimer_nonce=b"claimer nonce" ) assert rep == {"status": "ok"} async with real_clock_timeout(): async with trio.open_nursery() as nursery: nursery.start_soon(_claimer_step_2) nursery.start_soon(_greeter_step_2)
async def test_conduit_exchange_good(exchange_testbed, leader): greeter_privkey, claimer_privkey, greeter_ctlr, claimer_ctlr = exchange_testbed # Step 1 if leader == "greeter": await greeter_ctlr.send_order("1_wait_peer") await claimer_ctlr.send_order("1_wait_peer") else: await claimer_ctlr.send_order("1_wait_peer") await greeter_ctlr.send_order("1_wait_peer") greeter_rep = await greeter_ctlr.get_result() claimer_rep = await claimer_ctlr.get_result() assert greeter_rep == {"status": "ok", "claimer_public_key": claimer_privkey.public_key} assert claimer_rep == {"status": "ok", "greeter_public_key": greeter_privkey.public_key} # Step 2 if leader == "greeter": await greeter_ctlr.send_order("2a_get_hashed_nonce") await claimer_ctlr.send_order("2a_send_hashed_nonce") else: await claimer_ctlr.send_order("2a_send_hashed_nonce") await greeter_ctlr.send_order("2a_get_hashed_nonce") greeter_rep = await greeter_ctlr.get_result() assert greeter_rep == { "status": "ok", "claimer_hashed_nonce": HashDigest.from_data(b"<claimer_nonce>"), } await greeter_ctlr.send_order("2b_send_nonce") claimer_rep = await claimer_ctlr.get_result() assert claimer_rep == {"status": "ok", "greeter_nonce": b"<greeter_nonce>"} await claimer_ctlr.send_order("2b_send_nonce") greeter_rep = await greeter_ctlr.get_result() assert greeter_rep == {"status": "ok", "claimer_nonce": b"<claimer_nonce>"} claimer_rep = await claimer_ctlr.get_result() assert claimer_rep == {"status": "ok"} # Step 3a if leader == "greeter": await greeter_ctlr.send_order("3a_wait_peer_trust") await claimer_ctlr.send_order("3a_signify_trust") else: await claimer_ctlr.send_order("3a_signify_trust") await greeter_ctlr.send_order("3a_wait_peer_trust") greeter_rep = await greeter_ctlr.get_result() assert greeter_rep == {"status": "ok"} claimer_rep = await claimer_ctlr.get_result() assert claimer_rep == {"status": "ok"} # Step 3b if leader == "greeter": await greeter_ctlr.send_order("3b_signify_trust") await claimer_ctlr.send_order("3b_wait_peer_trust") else: await claimer_ctlr.send_order("3b_wait_peer_trust") await greeter_ctlr.send_order("3b_signify_trust") greeter_rep = await greeter_ctlr.get_result() assert greeter_rep == {"status": "ok"} claimer_rep = await claimer_ctlr.get_result() assert claimer_rep == {"status": "ok"} # Step 4 if leader == "greeter": await greeter_ctlr.send_order("4_communicate", b"hello from greeter") await claimer_ctlr.send_order("4_communicate", b"hello from claimer") else: await claimer_ctlr.send_order("4_communicate", b"hello from claimer") await greeter_ctlr.send_order("4_communicate", b"hello from greeter") greeter_rep = await greeter_ctlr.get_result() assert greeter_rep == {"status": "ok", "payload": b"hello from claimer"} claimer_rep = await claimer_ctlr.get_result() assert claimer_rep == {"status": "ok", "payload": b"hello from greeter"} if leader == "greeter": await greeter_ctlr.send_order("4_communicate", b"") await claimer_ctlr.send_order("4_communicate", b"") else: await claimer_ctlr.send_order("4_communicate", b"") await greeter_ctlr.send_order("4_communicate", b"") greeter_rep = await greeter_ctlr.get_result() assert greeter_rep == {"status": "ok", "payload": b""} claimer_rep = await claimer_ctlr.get_result() assert claimer_rep == {"status": "ok", "payload": b""}
def test_local_file_manifest(): from parsec.api.data.manifest import BlockAccess from parsec.core.types.manifest import ( _RsLocalFileManifest, LocalFileManifest, _PyLocalFileManifest, Chunk, ) assert LocalFileManifest is _RsLocalFileManifest def _assert_local_file_manifest_eq(py, rs, exclude_base=False, exclude_id=False): assert isinstance(py, _PyLocalFileManifest) assert isinstance(rs, _RsLocalFileManifest) if not exclude_base: assert py.base == rs.base assert py.need_sync == rs.need_sync assert py.updated == rs.updated assert py.size == rs.size assert py.blocksize == rs.blocksize assert len(py.blocks) == len(rs.blocks) assert isinstance(rs.blocks, type(py.blocks)) if len(py.blocks): assert isinstance(rs.blocks[0], type(py.blocks[0])) if not exclude_id: assert py.id == rs.id assert py.created == rs.created assert py.base_version == rs.base_version assert py.is_placeholder == rs.is_placeholder assert py.is_reshaped() == rs.is_reshaped() for (b1, b2) in zip(sorted(py.blocks), sorted(rs.blocks)): assert len(b1) == len(b2) assert all( isinstance(c2, Chunk) and c1.id == c2.id and c1.start == c2. start and c1.stop == c2.stop and c1.raw_offset == c2.raw_offset and c1.raw_size == c2.raw_size and c1.access == c2.access for (c1, c2) in zip(b1, b2)) def _assert_file_manifest_eq(py, rs): assert py.author == rs.author assert py.parent == rs.parent assert py.version == rs.version assert py.size == rs.size assert py.blocksize == rs.blocksize assert py.timestamp == rs.timestamp assert py.created == rs.created assert py.updated == rs.updated assert len(py.blocks) == len(rs.blocks) assert isinstance(rs.blocks, type(py.blocks)) assert all( isinstance(b2, BlockAccess) and b1.id == b2.id and b1.offset == b2.offset and b1.size == b2.size for (b1, b2) in zip(sorted(py.blocks), sorted(rs.blocks))) kwargs = { "base": FileManifest( author=DeviceID("user@device"), id=EntryID.new(), parent=EntryID.new(), version=42, size=1337, blocksize=85, timestamp=pendulum.now(), created=pendulum.now(), updated=pendulum.now(), blocks=(BlockAccess( id=BlockID.new(), key=SecretKey.generate(), offset=0, size=1024, digest=HashDigest.from_data(b"a"), ), ), ), "need_sync": True, "updated": pendulum.now(), "size": 42, "blocksize": 64, "blocks": (( Chunk( id=ChunkID.new(), start=0, stop=250, raw_offset=0, raw_size=512, access=BlockAccess( id=BlockID.new(), key=SecretKey.generate(), offset=0, size=512, digest=HashDigest.from_data(b"aa"), ), ), Chunk(id=ChunkID.new(), start=0, stop=250, raw_offset=250, raw_size=250, access=None), ), ), } py_lfm = _PyLocalFileManifest(**kwargs) rs_lfm = LocalFileManifest(**kwargs) _assert_local_file_manifest_eq(py_lfm, rs_lfm) kwargs = { "base": kwargs["base"].evolve( **{ "author": DeviceID("a@b"), "id": EntryID.new(), "parent": EntryID.new(), "version": 1337, "size": 4096, "blocksize": 512, "timestamp": pendulum.now(), "created": pendulum.now(), "updated": pendulum.now(), "blocks": (BlockAccess( id=BlockID.new(), key=SecretKey.generate(), offset=64, size=2048, digest=HashDigest.from_data(b"b"), ), ), }), "need_sync": False, "updated": pendulum.now(), "size": 2048, "blocksize": 1024, "blocks": (( Chunk( id=ChunkID.new(), start=0, stop=1024, raw_offset=0, raw_size=1024, access=BlockAccess( id=BlockID.new(), key=SecretKey.generate(), offset=0, size=1024, digest=HashDigest.from_data(b"bb"), ), ), Chunk( id=ChunkID.new(), start=1024, stop=2048, raw_offset=1024, raw_size=1024, access=None, ), ), ), } py_lfm = py_lfm.evolve(**kwargs) rs_lfm = rs_lfm.evolve(**kwargs) _assert_local_file_manifest_eq(py_lfm, rs_lfm) sk = SecretKey.generate() py_enc = py_lfm.dump_and_encrypt(sk) rs_enc = rs_lfm.dump_and_encrypt(sk) # Decrypt rust encrypted with Python and vice versa lfm1 = _PyLocalFileManifest.decrypt_and_load(rs_enc, sk) lfm2 = LocalFileManifest.decrypt_and_load(py_enc, sk) assert isinstance(lfm1, LocalFileManifest) assert isinstance(lfm2, LocalFileManifest) assert lfm1 == lfm2 py_lfm = py_lfm.evolve(**{"size": 1337}) rs_lfm = rs_lfm.evolve(**{"size": 1337}) _assert_local_file_manifest_eq(py_lfm, rs_lfm) with pytest.raises(AssertionError): py_lfm.assert_integrity() with pytest.raises(AssertionError): rs_lfm.assert_integrity() assert py_lfm.to_stats() == rs_lfm.to_stats() assert py_lfm.parent == rs_lfm.parent assert py_lfm.get_chunks(0) == rs_lfm.get_chunks(0) assert py_lfm.get_chunks(1000) == rs_lfm.get_chunks(1000) assert py_lfm.asdict() == rs_lfm.asdict() di = DeviceID("a@b") ts = pendulum.now() kwargs = { "size": 1024, "blocksize": 1024, "blocks": ((Chunk( id=ChunkID.new(), start=0, stop=1024, raw_offset=0, raw_size=1024, access=BlockAccess( id=BlockID.new(), key=SecretKey.generate(), offset=0, size=1024, digest=HashDigest.from_data(b"bb"), ), ), ), ), } py_lfm = py_lfm.evolve(**kwargs) rs_lfm = rs_lfm.evolve(**kwargs) _assert_local_file_manifest_eq(py_lfm, rs_lfm) py_rfm = py_lfm.to_remote(author=di, timestamp=ts) rs_rfm = rs_lfm.to_remote(author=di, timestamp=ts) _assert_file_manifest_eq(py_rfm, rs_rfm) py_lfm2 = _PyLocalFileManifest.from_remote(py_rfm) rs_lfm2 = LocalFileManifest.from_remote(rs_rfm) _assert_local_file_manifest_eq(py_lfm2, rs_lfm2, exclude_base=True, exclude_id=True) py_lfm2 = _PyLocalFileManifest.from_remote_with_local_context( remote=py_rfm, prevent_sync_pattern=r".+", local_manifest=py_lfm2, timestamp=ts) rs_lfm2 = LocalFileManifest.from_remote_with_local_context( remote=rs_rfm, prevent_sync_pattern=r".+", local_manifest=rs_lfm2, timestamp=ts) assert py_lfm.match_remote(py_rfm) == rs_lfm.match_remote(rs_rfm) py_lfm = py_lfm.evolve_and_mark_updated(timestamp=ts, size=4096) rs_lfm = rs_lfm.evolve_and_mark_updated(timestamp=ts, size=4096) _assert_local_file_manifest_eq(py_lfm, rs_lfm, exclude_base=True, exclude_id=True) with pytest.raises(TypeError) as excinfo: py_lfm.evolve_and_mark_updated(timestamp=ts, need_sync=True) assert str(excinfo.value) == "Unexpected keyword argument `need_sync`" with pytest.raises(TypeError) as excinfo: rs_lfm.evolve_and_mark_updated(timestamp=ts, need_sync=True) assert str(excinfo.value) == "Unexpected keyword argument `need_sync`" ei = EntryID.new() # Without blocksize py_lfm = _PyLocalFileManifest.new_placeholder(author=di, parent=ei, timestamp=ts) rs_lfm = LocalFileManifest.new_placeholder(author=di, parent=ei, timestamp=ts) _assert_local_file_manifest_eq(py_lfm, rs_lfm, exclude_base=True, exclude_id=True) # With blocksize py_lfm = _PyLocalFileManifest.new_placeholder(author=di, parent=ei, timestamp=ts, blocksize=1024) rs_lfm = LocalFileManifest.new_placeholder(author=di, parent=ei, timestamp=ts, blocksize=1024) _assert_local_file_manifest_eq(py_lfm, rs_lfm, exclude_base=True, exclude_id=True)