1#![no_main]
2use libfuzzer_sys::fuzz_target;
3use onestore::{
4 CommitState, ExGuid, RevisionIndex, Store,
5 document::{Document, Kind},
6 op::{Op, PageOp},
7};
8use std::sync::LazyLock;
9
10#[path = "../../crates/onestore/tests/support/disk.rs"]
11mod disk;
12#[path = "../../crates/onestore/tests/support/ops.rs"]
13mod ops;
14use disk::Disk;
15
16const SOURCES: [&[u8]; 2] = [
17 include_bytes!("../../corpus/native/20260905-05/snapshots/02-text/notebook/synthetic.one"),
18 include_bytes!("../../corpus/append/round-01/tx-255/notebook/synthetic.one"),
19];
20static TARGET: LazyLock<(ExGuid, ExGuid)> = LazyLock::new(|| {
21 let store = Store::parse(SOURCES[0]).unwrap();
22 let index = RevisionIndex::parse(&store).unwrap();
23 let document = Document::parse(&index).unwrap();
24 for (sid, space) in &document.spaces {
25 let revision = &space.revisions[&space.contexts[&ExGuid::default()]];
26 for (oid, node) in &revision.nodes {
27 if matches!(&node.kind, Kind::RichText { text, .. } if text == "Fictitious plain text.")
28 {
29 return (*sid, *oid);
30 }
31 }
32 }
33 panic!("Missing native seed text")
34});
35
36fn current(bytes: &[u8]) -> String {
37 let store = Store::parse(bytes).unwrap();
38 assert!(store.checksum_mismatches.is_empty());
39 let index = RevisionIndex::parse(&store).unwrap();
40 index.validate_current().unwrap();
41 let document = Document::parse(&index).unwrap();
42 let (sid, oid) = *TARGET;
43 let space = &document.spaces[&sid];
44 let revision = &space.revisions[&space.contexts[&ExGuid::default()]];
45 let Kind::RichText { text, .. } = &revision.nodes[&oid].kind else {
46 panic!()
47 };
48 text.clone()
49}
50
51fuzz_target!(|input: &[u8]| {
52 if input.len() < 12 {
53 return;
54 }
55 let mut source = SOURCES[usize::from(input[0].is_multiple_of(4))].to_vec();
56 let (sid, oid) = *TARGET;
57 for step in 0..=input[1] % 4 {
58 let value: String = input[12..]
59 .iter()
60 .chain([&step])
61 .map(|byte| char::from(b'a' + byte % 26))
62 .collect();
63 let before = current(&source);
64 let op = PageOp::Text {
65 text: oid,
66 range: 0..before.encode_utf16().count() as u32,
67 with: value.clone(),
68 };
69 let Some(transaction) =
70 ops::transaction(&source, "Fuzz", vec![Op::Page { space: sid, op }]).unwrap()
71 else {
72 assert_eq!(before, value);
73 continue;
74 };
75 let mut storage = Disk {
76 visible: source.clone(),
77 durable: source.clone(),
78 operation: 0,
79 fail_at: Some(usize::from(u16::from_le_bytes([input[2], input[3]]))),
80 write_limit: usize::from(input[4]) + 1,
81 random: u64::from_le_bytes(input[4..12].try_into().unwrap()),
82 };
83 let result = transaction.commit(&mut storage);
84 let persisted = current(&storage.durable);
85 match result {
86 Ok(()) => assert_eq!(persisted, value),
87 Err(error) => match error.state {
88 CommitState::NotCommitted => assert_eq!(persisted, before),
89 CommitState::Committed => assert_eq!(persisted, value),
90 CommitState::Unknown => assert!(persisted == before || persisted == value),
91 },
92 }
93 source = storage.durable;
94 }
95});