1pub const STORE_ID: coven_foundation::id_source::StoreId =
5 coven_foundation::id_source::StoreId(uuid::Uuid::from_bytes([0x10; 16]));
6
7use crate::admin::*;
8use crate::codes::{InviteCode, InviteKind, KeychainRestoreCode, RestoreCode};
9use crate::key::encode_key;
10use crate::objects::*;
11use crate::snapshot::{SnapshotEncoder, SnapshotHeader, SnapshotRecord};
12use crate::snapshot_rows::*;
13use crate::value::*;
14use crate::write::*;
15use crate::Object;
16use coven_crypto::{InviteSecret, MemberIdentity, MemberKeys, SecretBytes};
17use coven_foundation::id_source::{CircleId, DeviceId, InviteId, KeyId, MemberId, StoreId};
18use coven_merge::slot;
19use coven_merge::{
20 Audience, Cell, Change, ColumnValue, LostKey, LostValue, MergeError, Operation, Parent, RowId,
21 RowState, Timestamp, WriteId, WriteOracle,
22};
23use std::collections::{BTreeMap, BTreeSet};
24use uuid::Uuid;
25use zeroize::Zeroizing;
26
27pub fn fixture_bytes(text: &str) -> Vec<u8> {
29 hex::decode(text.trim()).expect("pinned hexadecimal fixture")
30}
31
32pub fn position() -> WriteId {
34 WriteId { device: DeviceId(1), number: 3 }
35}
36pub fn timestamp() -> Timestamp {
38 Timestamp::new(6, 3, DeviceId(1)).unwrap()
39}
40pub fn row() -> RowId {
42 RowId {
43 table: "t".into(),
44 key: encode_key(&[Value::Text("k".into())]).unwrap(),
45 audience: Audience::Store,
46 }
47}
48pub fn column(value: Value) -> ColumnValue<Value> {
50 ColumnValue { value, parents: BTreeMap::new() }
51}
52pub fn keys_from_seed(seed: u8) -> MemberKeys {
54 MemberKeys::from_seed(seed)
55}
56pub fn member_id() -> MemberId {
58 MemberId(Uuid::from_bytes([0x4d; 16]))
59}
60pub fn member_keys() -> MemberKeys {
62 keys_from_seed(0x33)
63}
64pub fn identity() -> MemberIdentity {
66 MemberIdentity::new(member_id(), member_keys())
67}
68pub fn member() -> MemberPublicKeys {
70 member_keys().public_keys()
71}
72pub fn replacement_keys() -> MemberKeys {
74 keys_from_seed(0x34)
75}
76pub fn replacement() -> MemberPublicKeys {
78 replacement_keys().public_keys()
79}
80pub fn other_member_id() -> MemberId {
82 MemberId(Uuid::from_bytes([0x42; 16]))
83}
84pub fn other_member() -> MemberPublicKeys {
86 keys_from_seed(0x35).public_keys()
87}
88pub fn write() -> WriteRecord {
90 let mut new = column(Value::Integer(2));
91 new.parents.insert(
92 coven_merge::ForeignKey::new(["parent_fk"], "p", ["id"]),
93 Parent {
94 row: RowId {
95 table: "p".into(),
96 key: encode_key(&[Value::Text("q".into())]).unwrap(),
97 audience: Audience::Store,
98 },
99 generation: 1,
100 },
101 );
102 WriteRecord {
103 header: WriteHeader {
104 read_through: 5,
105 position: position(),
106 timestamp: timestamp(),
107 schema_version: 1,
108 disposition: WriteDisposition::Apply,
109 },
110 parts: vec![WritePart {
111 dismissals: Vec::new(),
112 audience: Audience::Store,
113 executions: vec![transient_execution(Audience::Store)],
114 rows: vec![RowChange {
115 row: row(),
116 creator: None,
117 execution: Execution {
118 steps: vec![Step {
119 sequence: 0,
120 change: Change {
121 generation: 1,
122 operation: Operation::Update(BTreeMap::from([(
123 "x".into(),
124 new.clone(),
125 )])),
126 },
127 half: Half::Whole,
128 }],
129 sources: Default::default(),
130 },
131 change: Change {
132 generation: 1,
133 operation: Operation::Update(BTreeMap::from([("x".into(), new)])),
134 },
135 old: BTreeMap::new(),
136 sources: crate::write_sources::WriteSources {
137 generation: Some(crate::write_sources::ChangeId {
138 write: WriteId { device: DeviceId(1), number: 1 },
139 row: row(),
140 }),
141 setters: [(
142 "x".into(),
143 crate::write_sources::CellSource {
144 change: crate::write_sources::ChangeId {
145 write: WriteId { device: DeviceId(2), number: 1 },
146 row: row(),
147 },
148 column: "x".into(),
149 },
150 )]
151 .into(),
152 },
153 }],
154 }],
155 }
156}
157pub fn transient_execution(audience: Audience) -> RowExecution {
160 let row = RowId { key: encode_key(&[Value::Text("l".into())]).unwrap(), audience, ..row() };
161 RowExecution {
162 row,
163 execution: Execution {
164 steps: vec![
165 Step {
166 sequence: 1,
167 change: Change {
168 generation: 0,
169 operation: Operation::Insert(BTreeMap::from([(
170 "x".into(),
171 column(Value::Integer(5)),
172 )])),
173 },
174 half: Half::Whole,
175 },
176 Step {
177 sequence: 2,
178 change: Change { generation: 1, operation: Operation::Delete },
179 half: Half::Whole,
180 },
181 ],
182 sources: Default::default(),
183 },
184 }
185}
186pub fn store_key() -> coven_crypto::StoreKey {
188 coven_crypto::StoreKey::from_bytes(KeyId(Uuid::from_bytes([1; 16])), [17; 32])
189}
190pub fn creation_change() -> StoreChange {
192 StoreChange::CreateStore {
193 access: crate::MemberAccess::S3AccessKey { access_key_id: "fixture-access-key".into() },
194 store: STORE_ID,
195 name: "S".into(),
196 admin: member(),
197 key: store_key().id(),
198 key_hash: store_key().hash(),
199 device_name: "D".into(),
200 }
201}
202pub fn member_removal() -> StoreChange {
204 StoreChange::RemoveMember { member: other_member_id() }
205}
206pub fn loss_entry() -> Slot {
208 slot(2)
209}
210pub fn slot_path(number: u64) -> crate::path::ObjectPath {
212 crate::path::ObjectPath::slot(STORE_ID, number.try_into().unwrap())
213}
214pub fn slot_header(slot: u64, claim: u64) -> crate::sequence::SlotHeader {
216 crate::sequence::SlotHeader { device: DeviceId(1), claim, author: member_id(), after: slot - 1 }
217}
218pub fn bounded_slot(
220 slot: u64,
221 claim: u64,
222 kind: crate::sealed_slot::SlotKind,
223 frame: crate::sequence::SlotFrame,
224) -> Vec<u8> {
225 let path = slot_path(slot);
226 let prefix = crate::sealed_slot::SlotObjectPrefix {
227 format: crate::FormatVersion::CURRENT,
228 key: store_key().id(),
229 kind,
230 };
231 let plain = frame.encode(&slot_header(slot, claim), &path).unwrap();
232 let chunk = store_key().derive().reseal_object_chunk(
233 path.as_str(),
234 &prefix.encode().unwrap(),
235 0,
236 0,
237 &plain,
238 );
239 let mut bytes = prefix.encode_chunk(&chunk).unwrap();
240 let mut hash = coven_crypto::ObjectHasher::new();
241 hash.update(&bytes);
242 bytes.extend_from_slice(member_keys().sign_object(path.as_str(), &hash.finish()).as_bytes());
243 bytes
244}
245pub fn announcement() -> crate::sequence::SnapshotAnnouncement {
247 crate::sequence::SnapshotAnnouncement {
248 snapshot: snapshot_header().id,
249 covers: 6,
250 breaking_version: 0,
251 purpose: crate::sequence::Purpose::Ordinary,
252 digest: [0x99; 32],
253 }
254}
255#[derive(Clone)]
257pub struct TestOracle {
258 pub writes: BTreeMap<WriteId, AppliedWrite>,
260}
261impl WriteOracle for TestOracle {
262 type Error = crate::Error;
263 fn timestamp(&self, id: WriteId) -> Result<Option<Timestamp>, crate::Error> {
264 Ok(self.writes.get(&id).map(|w| w.timestamp))
265 }
266 fn had_read(&self, reader: WriteId, earlier: WriteId) -> Result<bool, crate::Error> {
267 let write = self.writes.get(&reader).ok_or(MergeError::MissingWrite(reader))?;
268 let slot = self.writes.get(&earlier).map(|earlier| earlier.slot);
269 Ok(coven_merge::had_read(reader, write.read_through, earlier, slot))
270 }
271}
272impl crate::snapshot::WriteSlots for TestOracle {
273 fn slot(&self, write: WriteId) -> Result<Option<Slot>, crate::Error> {
274 Ok(self.writes.get(&write).map(|write| write.slot))
275 }
276 fn landed(&self, write: WriteId) -> Result<bool, crate::Error> {
277 Ok(self.writes.keys().any(|id| id.device == write.device && id.number >= write.number))
278 }
279}
280pub fn oracle() -> TestOracle {
282 let first = WriteId { device: DeviceId(1), number: 1 };
283 let winner = WriteId { device: DeviceId(1), number: 2 };
284 let loser = WriteId { device: DeviceId(2), number: 1 };
285 TestOracle {
286 writes: [
287 AppliedWrite {
288 id: first,
289 slot: slot(3),
290 timestamp: Timestamp::new(1, 0, first.device).unwrap(),
291 read_through: 0,
292 },
293 AppliedWrite {
294 id: winner,
295 slot: slot(5),
296 timestamp: Timestamp::new(4, 0, winner.device).unwrap(),
297 read_through: 0,
298 },
299 AppliedWrite {
300 id: loser,
301 slot: slot(4),
302 timestamp: Timestamp::new(3, 0, loser.device).unwrap(),
303 read_through: 3,
304 },
305 AppliedWrite { id: position(), slot: slot(6), timestamp: timestamp(), read_through: 5 },
306 ]
307 .into_iter()
308 .map(|w| (w.id, w))
309 .collect(),
310 }
311}
312pub fn merge_row() -> MergeRow {
314 merge_row_of(row(), Value::Integer(2))
315}
316pub fn merge_row_of(row: RowId, value: Value) -> MergeRow {
318 let state = RowState::from_parts(
319 row,
320 BTreeMap::from([(1, WriteId { device: DeviceId(1), number: 1 })]),
321 BTreeMap::from([(
322 "x".into(),
323 Cell { write: WriteId { device: DeviceId(1), number: 2 }, value: column(value) },
324 )]),
325 BTreeMap::new(),
326 &oracle(),
327 )
328 .unwrap();
329 MergeRow { state }
330}
331pub fn snapshot_header() -> SnapshotHeader {
333 SnapshotHeader {
334 id: SnapshotId { device: DeviceId(1), number: 1, audience: Audience::Store },
335 schema_version: 2,
336 covers: 9,
337 counts: [4, 1, 1, 4, 5, 1, 1, 1, 1, 1, 2],
338 }
339}
340pub fn snapshot_prefix(header: &SnapshotHeader) -> crate::sealed_snapshot::SnapshotObjectPrefix {
342 crate::sealed_snapshot::SnapshotObjectPrefix {
343 audience: header.id.audience.clone(),
344 key: KeyId(Uuid::from_bytes([1; 16])),
345 covers: header.covers,
346 purpose: crate::sequence::Purpose::Raise(header.schema_version),
347 }
348}
349pub fn snapshot_records() -> Vec<SnapshotRecord> {
351 let mut writes = oracle().writes;
352 let header = write().header;
353 writes.insert(
354 header.position,
355 AppliedWrite {
356 id: header.position,
357 timestamp: header.timestamp,
358 slot: slot(6),
359 read_through: header.read_through,
360 },
361 );
362 let mut records: Vec<_> = writes.into_values().map(SnapshotRecord::Write).collect();
363 records.push(SnapshotRecord::Column(SyncedColumn { table: "t".into(), column: "x".into() }));
364 records.push(SnapshotRecord::Merge(merge_row()));
365 records.extend(
366 retained_losses()
367 .into_iter()
368 .chain([concurrent_loss(), excluded_loss()])
369 .map(SnapshotRecord::Loss),
370 );
371 records.extend(authored_records().into_iter().map(SnapshotRecord::Authored));
372 records.push(SnapshotRecord::Creation(creation()));
373 records.push(SnapshotRecord::Evidence(write_evidence()));
374 records.push(SnapshotRecord::Admin(crate::floor::OriginalSlot {
375 slot: slot(1),
376 object: bounded_slot(
377 1,
378 1,
379 crate::sealed_slot::SlotKind::Admin,
380 crate::sequence::SlotFrame::Admin(creation_change()),
381 ),
382 }));
383 records.push(SnapshotRecord::KeyCopy(crate::floor::OriginalSlot {
384 slot: slot(2),
385 object: hex::decode(include_str!("../fixtures/sealed-store-key.hex").trim()).unwrap(),
386 }));
387 records.push(SnapshotRecord::Announcement(crate::floor::OriginalSlot {
388 slot: slot(7),
389 object: bounded_slot(
390 7,
391 6,
392 crate::sealed_slot::SlotKind::Announcement,
393 crate::sequence::SlotFrame::Announcement(announcement()),
394 ),
395 }));
396 records.push(SnapshotRecord::Claim(crate::snapshot::LastClaim {
397 device: DeviceId(1),
398 number: 8,
399 digest: Some([0x81; 32]),
400 }));
401 records.push(SnapshotRecord::Claim(crate::snapshot::LastClaim {
402 device: DeviceId(2),
403 number: 1,
404 digest: Some([0x21; 32]),
405 }));
406 records
407}
408pub fn creation() -> crate::authorship::Creation {
410 let write = WriteId { device: DeviceId(1), number: 1 };
411 crate::authorship::Creation {
412 row: row(),
413 write,
414 creator: crate::write_sources::ChangeId { write, row: row() },
415 }
416}
417pub fn write_evidence() -> crate::authorship::WriteEvidence {
419 let leaves = crate::authorship::part_leaves(&write().parts[0]);
420 let hasher = store_key().derive().authorship_hasher();
421 let proof = crate::authorship::Multiproof::prove(&hasher, &leaves, |leaf| {
422 matches!(leaf, crate::authorship::Leaf::Cell { .. })
423 })
424 .unwrap();
425 let mut header = 7u32.to_be_bytes().to_vec();
426 header.extend([0x23; 24 + 7 + 16]);
427 crate::authorship::WriteEvidence {
428 write: position(),
429 prefix: crate::sealed_write::WriteObjectPrefix {
430 format: crate::FormatVersion::CURRENT,
431 store_key: KeyId(Uuid::from_bytes([1; 16])),
432 part_keys: vec![KeyId(Uuid::from_bytes([1; 16]))],
433 }
434 .encode()
435 .unwrap(),
436 header,
437 roots: vec![crate::authorship::root(&hasher, &leaves).unwrap()],
438 signature: [0x22; 64],
439 proof,
440 }
441}
442pub fn authored_records() -> Vec<crate::snapshot_rows::AuthoredRecord> {
447 use crate::snapshot_rows::{
448 AuthoredChange, AuthoredPart, AuthoredRecord, ChangeVerdict, KeptChange, KeptRecord,
449 KeptValue,
450 };
451 let first = WriteId { device: DeviceId(1), number: 1 };
452 let created = KeptRecord::Change(KeptChange {
453 row: row(),
454 change: Change {
455 generation: 0,
456 operation: Operation::Insert(BTreeMap::from([(
457 "x".into(),
458 ColumnValue {
459 value: KeptValue::Digest(KeptValue::digest(&Value::Integer(1)).unwrap()),
460 parents: BTreeMap::new(),
461 },
462 )])),
463 },
464 creator: None,
465 steps: 1,
466 });
467 let write = write();
468 let part = &write.parts[0];
469 let applied = |record, stop| {
470 AuthoredRecord::Change(AuthoredChange { record, verdict: ChangeVerdict::Applied, stop })
471 };
472 vec![
473 AuthoredRecord::Part(AuthoredPart {
474 header: WriteHeader {
475 position: first,
476 timestamp: Timestamp::new(1, 0, first.device).unwrap(),
477 read_through: 0,
478 schema_version: 1,
479 disposition: WriteDisposition::Apply,
480 },
481 }),
482 applied(created, None),
483 AuthoredRecord::Part(AuthoredPart { header: write.header.clone() }),
484 applied(KeptRecord::keep(PartRecord::Change(&part.rows[0]), |_| true).unwrap(), None),
485 applied(
487 KeptRecord::keep(PartRecord::Execution(&part.executions[0]), |_| true).unwrap(),
488 Some(slot(6)),
489 ),
490 ]
491}
492pub fn concurrent_loss() -> crate::loss::Loss {
494 crate::loss::Loss::cell(
495 row(),
496 LostKey { column: "x".into(), write: WriteId { device: DeviceId(2), number: 1 } },
497 LostValue {
498 incarnation: 1,
499 value: column(Value::Integer(1)),
500 replaced_by: WriteId { device: DeviceId(1), number: 2 },
501 },
502 )
503}
504pub fn retained_losses() -> Vec<crate::loss::Loss> {
506 use crate::loss::Loss;
507 let row = RowId { table: "removed".into(), ..row() };
508 let mut cell = concurrent_loss();
509 cell.row = row.clone();
510 cell.frozen = true;
511 let mut removed =
512 Loss::removed(&merge_row().state, [coven_merge::Rule::Check("valid".into())].into());
513 removed.row = row;
514 removed.frozen = true;
515 vec![cell, removed]
516}
517pub fn excluded_loss() -> crate::loss::Loss {
519 use crate::loss::{Loss, LossCause, LossValues};
520 let change = write().parts.remove(0).rows.remove(0);
521 let Operation::Update(values) = change.change.operation else { unreachable!() };
522 Loss {
523 row: change.row,
524 generation: change.change.generation,
525 frozen: true,
526 values: LossValues::Row(
527 values
528 .into_iter()
529 .map(|(name, mut value)| {
530 value.parents.clear();
531 (name, Cell { write: position(), value })
532 })
533 .collect(),
534 ),
535 cause: LossCause::Excluded { write: write().header.position, version: 2 },
536 }
537}
538#[expect(clippy::map_err_ignore, reason = "a length past usize cannot be allocated")]
540pub fn write_plaintext(record: &WriteRecord) -> Result<Vec<u8>, crate::Error> {
541 let encoder = crate::write_stream::WriteEncoder::new(record)?;
542 let length =
543 usize::try_from(encoder.plaintext_length()).map_err(|_| crate::Error::Allocation)?;
544 let mut bytes = vec![0; length];
545 encoder.encode_plaintext(&mut bytes)?;
546 Ok(bytes)
547}
548pub fn snapshot_frames() -> Vec<Vec<u8>> {
550 let (mut encoder, header) = SnapshotEncoder::start(snapshot_header()).unwrap();
551 let mut frames = vec![header];
552 for record in snapshot_records() {
553 frames.push(encoder.record(record).unwrap());
554 }
555 frames.push(encoder.finish().unwrap());
556 frames
557}
558const S3_LOCATION: &[u8] = b"\0\0\0\0\x06bucket\0\0\0\x06region\0\0";
562
563pub fn restore() -> RestoreCode {
565 RestoreCode {
566 store: StoreId(Uuid::from_bytes([0x10; 16])),
567 member: member_id(),
568 member_keys: member_keys(),
569 storage: SecretBytes::new([S3_LOCATION, b"\0"].concat()),
570 }
571}
572pub fn keychain_restore() -> KeychainRestoreCode {
574 KeychainRestoreCode { name: "Family".into(), named_at: slot(41), code: restore() }
575}
576pub fn invite() -> InviteCode {
578 InviteCode {
579 store: StoreId(Uuid::from_bytes([0x10; 16])),
580 name: "S".into(),
581 inviter: "A".into(),
582 kind: InviteKind::Recovery,
584 expires_at: 86_400,
585 invite: InviteId(Uuid::from_bytes([0x55; 16])),
586 member: member_id(),
588 secret: InviteSecret::from_bytes([0x66; 32]),
589 initial_key: store_key().id(),
590 inviting_writer: DeviceId(1),
591 read_through: 12,
592 storage: SecretBytes::new([S3_LOCATION, b"\0\0\0\x02id\0\0\0\x06secret"].concat()),
594 }
595}
596pub fn store_changes() -> Vec<StoreChange> {
598 let c = CircleId(uuid::Uuid::from_bytes([2; 16]));
599 let m = member_id();
600 vec![
601 creation_change(),
602 StoreChange::AdmitMember {
603 member: other_member_id(),
604 keys: other_member(),
605 role: MemberRole::Member,
606 access: crate::MemberAccess::ProviderAccount("[email protected]".into()),
607 device: DeviceId(2),
608 device_name: "D".into(),
609 },
610 member_removal(),
611 StoreChange::ChangeRole { member: m, role: MemberRole::Admin },
612 StoreChange::AddDevice { name: "D".into(), replaces: None },
613 StoreChange::RemoveDevice { device: DeviceId(2) },
614 StoreChange::CreateCircle {
615 circle: c,
616 name: "C".into(),
617 key: coven_foundation::id_source::KeyId(uuid::Uuid::from_bytes([1; 16])),
618 key_hash: store_key().hash(),
619 breaking_version: 2,
620 },
621 StoreChange::RenameCircle { circle: c, name: "N".into() },
622 StoreChange::DeleteCircle { circle: c },
623 StoreChange::AddCircleMember { circle: c, member: m },
624 StoreChange::RemoveCircleMember { circle: c, member: m },
625 StoreChange::RaiseSchema { version: 2, announcement: slot(7) },
626 StoreChange::Reset { announcement: slot(8) },
627 StoreChange::SetAccess {
628 member: m,
629 access: crate::MemberAccess::S3AccessKey { access_key_id: "replacement-key".into() },
630 },
631 StoreChange::RotateKey {
632 audience: Audience::Store,
633 key: KeyId(Uuid::from_bytes([3; 16])),
634 key_hash: [0x91; 32],
635 },
636 StoreChange::RotateKey {
637 audience: Audience::Circle(c),
638 key: KeyId(Uuid::from_bytes([4; 16])),
639 key_hash: [0xa2; 32],
640 },
641 StoreChange::ReplaceMemberKeys { member: m, expected: member(), keys: replacement() },
642 StoreChange::RenameStore { name: "N".into() },
643 StoreChange::AddInviteShare {
644 invite: InviteId(Uuid::from_bytes([0x55; 16])),
645 account: "[email protected]".into(),
646 },
647 StoreChange::EndInviteShare { invite: InviteId(Uuid::from_bytes([0x55; 16])) },
648 StoreChange::RecoverMember {
649 member: m,
650 expected: member(),
651 keys: replacement(),
652 access: Some(crate::MemberAccess::S3AccessKey {
653 access_key_id: "recovered-key".into(),
654 }),
655 device: DeviceId(3),
656 device_name: "D".into(),
657 },
658 ]
659}
660pub fn objects() -> Vec<Object> {
662 store_changes()
663 .into_iter()
664 .map(Object::Admin)
665 .chain([Object::JoinRequest(join_request())])
666 .collect()
667}
668pub fn join_request() -> JoinRequest {
671 JoinRequest {
672 invite: InviteId(Uuid::from_bytes([0x55; 16])),
673 member: member_id(),
674 keys: replacement(),
675 device: DeviceId(3),
676 device_name: "D".into(),
677 joiner: Joiner::Recovery,
678 }
679}
680pub fn chunked_write() -> WriteRecord {
682 let mut record = write();
683 let mut circle = record.parts[0].clone();
684 circle.audience = Audience::Circle(CircleId(Uuid::from_u128(1)));
685 circle.rows[0].row.audience = circle.audience.clone();
686 circle.executions = vec![transient_execution(circle.audience.clone())];
687 record.parts.push(circle);
688 let blob = Operation::Update(BTreeMap::from([(
689 "x".into(),
690 column(Value::Blob(vec![0x41; crate::chunks::CHUNK_SIZE * 2 + 29])),
691 )]));
692 record.parts[0].rows[0].change.operation = blob.clone();
693 record.parts[0].rows[0].execution.steps[0].change.operation = blob;
694 record
695}
696pub fn chunked_snapshot_frames() -> Vec<Vec<u8>> {
698 let (mut encoder, header) = SnapshotEncoder::start(snapshot_header()).unwrap();
699 let mut records = snapshot_records();
700 let large = records.iter_mut().find(|record| matches!(record, SnapshotRecord::Merge(_)));
701 *large.expect("the fixture's merge row") = SnapshotRecord::Merge(merge_row_of(
702 row(),
703 Value::Blob(vec![0x42; crate::chunks::CHUNK_SIZE + 31]),
704 ));
705 let mut frames = vec![header];
706 for record in records {
707 frames.push(encoder.record(record).unwrap());
708 }
709 frames.push(encoder.finish().unwrap());
710 frames
711}
712pub fn frame_examples() -> Vec<Zeroizing<Vec<u8>>> {
714 let mut frames: Vec<_> =
715 objects().iter().map(|v| Zeroizing::new(v.encode().unwrap())).collect();
716 let record = write();
717 let encoder = crate::write_stream::WriteEncoder::new(&record).unwrap();
718 frames.push(Zeroizing::new(encoder.header_frame().to_vec()));
719 frames.push(Zeroizing::new(record.parts[0].rows[0].encode().unwrap()));
720 frames.push(Zeroizing::new(
721 crate::dismissal::Dismissal {
722 row: row(),
723 target: crate::dismissal::DismissalTarget::Cell("x".into()),
724 write: position(),
725 }
726 .encode()
727 .unwrap(),
728 ));
729 let mut migration = record.clone();
730 migration.header.disposition = WriteDisposition::Migration;
731 migration.parts.clear();
732 frames.push(Zeroizing::new(write_plaintext(&migration).unwrap()));
733 frames.extend(snapshot_frames().into_iter().map(Zeroizing::new));
734 frames
735}
736pub fn encoded_examples() -> Vec<Zeroizing<Vec<u8>>> {
739 let mut pieces = frame_examples();
740 let record = chunked_write();
741 let encoder = crate::write_stream::WriteEncoder::new(&record).unwrap();
742 pieces.push(Zeroizing::new(
743 crate::sealed_write::WriteObjectPrefix {
744 format: crate::FormatVersion::CURRENT,
745 store_key: KeyId(Uuid::from_bytes([1; 16])),
746 part_keys: vec![KeyId(Uuid::from_bytes([1; 16])), KeyId(Uuid::from_bytes([2; 16]))],
747 }
748 .encode()
749 .unwrap(),
750 ));
751 pieces.push(Zeroizing::new(encoder.header_frame().to_vec()));
752 for index in 0..record.parts.len() {
753 pieces.extend(encoder.part_chunks(index).unwrap().map(|v| Zeroizing::new(v.unwrap())));
754 }
755 pieces.push(Zeroizing::new(snapshot_prefix(&snapshot_header()).encode().unwrap()));
756 pieces.extend(
757 crate::chunks::PlaintextChunks::new(chunked_snapshot_frames().into_iter().map(Ok))
758 .map(|v| Zeroizing::new(v.unwrap())),
759 );
760 pieces
761}
762
763pub fn past() -> BTreeSet<WriteId> {
765 [(1, 1), (1, 2), (2, 1)]
766 .map(|(device, number)| WriteId { device: DeviceId(device), number })
767 .into()
768}
769pub fn check_past(record: &WriteRecord) -> Result<(), crate::Error> {
771 let past = past();
772 for part in &record.parts {
773 for row in &part.rows {
774 row.validate_past(&past)?;
775 }
776 for execution in &part.executions {
777 execution.execution.sources.validate_past(&past)?;
778 }
779 for dismissal in &part.dismissals {
780 dismissal.validate_past(&past)?;
781 }
782 }
783 Ok(())
784}