1#![no_main]
2//! Chains text edits on a protected section kept open under its key, now and then writing
3//! it anew under another password: every sealed image opens with the key, reads back as the
4//! model predicts and stores none of the new text in clear. Text typed at the end of a text
5//! whose empty final run keeps an insertion style takes that style, which the page model
6//! cannot show, so only its characters are predicted.
7use libfuzzer_sys::fuzz_target;
8use onestore::{
9 Arena, ExGuid, Section,
10 op::{Edit, Op, PageOp, predict},
11 page::{Page, PageObject},
12 protected::{Key, rekey},
13};
14use std::sync::LazyLock;
15
16const SOURCE: &[u8] =
17 include_bytes!("../../corpus/native-encrypted/encrypted-01/notebook/synthetic.one");
18static KEYS: LazyLock<[Key; 2]> = LazyLock::new(|| {
19 [
20 Key::open(SOURCE, "fictitious-only").unwrap(),
21 Key::new("another fictitious password").unwrap(),
22 ]
23});
24
25/// The first page.
26fn page(bytes: &[u8], key: &Key) -> (ExGuid, Page) {
27 let arena = Arena::default();
28 let mut section = Section::unlock(&arena, bytes.to_vec(), key).unwrap();
29 let (space, ..) = section.pages().unwrap()[0];
30 (space, section.page(space).unwrap())
31}
32
33fn texts(page: &Page) -> Vec<String> {
34 page.objects
35 .iter()
36 .filter_map(|object| match object {
37 PageObject::Outline(outline) => Some(&outline.paragraphs),
38 _ => None,
39 })
40 .flatten()
41 .filter_map(|paragraph| Some(paragraph.text()?.text.text().to_owned()))
42 .collect()
43}
44
45fuzz_target!(|data: &[u8]| {
46 let mut bytes = SOURCE.to_vec();
47 let mut key = &KEYS[0];
48 for (at, step) in (0..).zip(data.chunks(12).take(4)) {
49 if step.len() < 4 {
50 return;
51 }
52 if step[3] == 0xff {
53 let other = &KEYS[usize::from(std::ptr::eq(key, &KEYS[0]))];
54 let before = page(&bytes, key).1;
55 bytes = rekey(&bytes, Some(key), Some(other)).unwrap();
56 key = other;
57 assert_eq!(texts(&page(&bytes, key).1), texts(&before));
58 continue;
59 }
60 let (space, mut expected) = page(&bytes, key);
61 let candidates: Vec<_> = expected
62 .objects
63 .iter()
64 .filter_map(|object| match object {
65 PageObject::Outline(outline) => Some(&outline.paragraphs),
66 _ => None,
67 })
68 .flatten()
69 .filter_map(|paragraph| paragraph.text())
70 .collect();
71 let text = candidates[usize::from(step[0]) % candidates.len()];
72 let end = text.text.utf16_offset(text.text.text().len()).unwrap();
73 let start = u32::from(step[1]) % (end + 1);
74 let stop = (start + u32::from(step[2]) % 8).min(end);
75 // Marked so its absence from the stored bytes is checkable.
76 let inserted = format!(
77 "\u{1f512}sealed\u{1f512}{}",
78 String::from_utf8_lossy(&step[3..]).replace(['\0', '\n', '\u{fffc}'], "")
79 );
80 let op = PageOp::Text {
81 text: text.id,
82 range: start..stop,
83 with: inserted.clone(),
84 };
85 if predict(&mut expected, &op).is_err() {
86 return;
87 }
88 let edit = Edit {
89 at: 133_000_000_000_000_000 + at * 10_000_000,
90 ops: vec![Op::Page { space, op }],
91 };
92 let arena = Arena::default();
93 let mut section = Section::unlock(&arena, bytes.clone(), key).unwrap();
94 section.apply("Fuzz", &edit).unwrap();
95 let transaction = section.seal().unwrap().unwrap();
96 let mut written = bytes.clone();
97 transaction.apply(&mut written).unwrap();
98 let clear: Vec<u8> = inserted.encode_utf16().flat_map(u16::to_le_bytes).collect();
99 assert!(
100 !written[bytes.len()..]
101 .windows(clear.len())
102 .any(|w| w == clear)
103 );
104 assert_eq!(texts(&page(&written, key).1), texts(&expected));
105 bytes = written;
106 }
107});