pub(crate) struct Input<'a>(pub(crate) &'a [u8]);
impl<'a> Input<'a> {
pub(crate) fn byte(&mut self) -> u8 {
let Some((&b, rest)) = self.0.split_first() else {
return 0;
};
self.0 = rest;
b
}
pub(crate) fn part(&mut self) -> &'a [u8] {
let len = usize::from(u16::from_be_bytes([self.byte(), self.byte()]));
let (part, rest) = self.0.split_at(len.min(self.0.len()));
self.0 = rest;
part
}
pub(crate) fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
pub fn frame(head: &[u8], parts: &[&[u8]]) -> Vec<u8> {
let mut out = head.to_vec();
if let Some((last, framed)) = parts.split_last() {
for part in framed {
out.extend(u16::try_from(part.len()).expect("small part").to_be_bytes());
out.extend(*part);
}
out.extend(*last);
}
out
}
pub fn tail(data: &[u8]) {
super::encrypted_tail::fuzz::tail(data)
}
pub fn tail_apply(data: &[u8]) {
super::encrypted_tail::fuzz::apply(data)
}
pub fn attachment(data: &[u8]) {
super::encrypted_tail::fuzz::attachment(data)
}
pub fn membership(data: &[u8]) {
super::seed_claim::membership::test_support::fuzz(data)
}
pub fn bootstrap(data: &[u8]) {
super::shared_state::package::publication::fuzz::bootstrap(data)
}
pub fn invitation(text: &[u8]) {
use super::client::invitation::{DeviceInvitation, InvitationCheck, SetupInvitation};
use super::invitation_text;
let Ok(text) = std::str::from_utf8(text) else {
return;
};
let _ = InvitationCheck::of(text);
if let Some(decoded) = invitation_text::decode(text) {
let encoded = invitation_text::encode(decoded.kind, &decoded.server, &decoded.secret)
.expect("a decoded invitation re-encodes");
let again = invitation_text::decode(&encoded).expect("re-encoded invitation decodes");
assert_eq!(
(again.kind, &again.server, &again.secret),
(decoded.kind, &decoded.server, &decoded.secret)
);
}
if let Ok(setup) = SetupInvitation::decode(text) {
let encoded = setup
.encode()
.expect("a decoded setup invitation re-encodes");
let again = SetupInvitation::decode(&encoded).expect("setup round trip");
assert_eq!(again.setup_id, setup.setup_id);
}
if let Ok(device) = DeviceInvitation::decode(text) {
let encoded = device
.encode()
.expect("a decoded device invitation re-encodes");
let again = DeviceInvitation::decode(&encoded).expect("device round trip");
assert_eq!(again.invitation.vault(), device.invitation.vault());
}
}
pub fn wire(data: &[u8]) {
use super::encrypted_tail::{BatchOperation, BatchReply, Operation, Reply, batch};
use super::seed_claim::membership::{Evidence, Mailbox};
let mut input = Input(data);
let selector = input.byte();
let body = input.0;
match selector % 7 {
0 => drop(serde_json::from_slice::<Reply>(body)),
1 => drop(serde_json::from_slice::<Operation>(body)),
2 => drop(serde_json::from_slice::<batch::Envelope<BatchReply>>(body)),
3 => drop(serde_json::from_slice::<batch::Envelope<BatchOperation>>(
body,
)),
4 => drop(Evidence::decode_unverified(body)),
5 => drop(serde_json::from_slice::<Mailbox>(body)),
_ => super::encrypted_tail::fuzz::strict_json(body),
}
}
pub(crate) fn with_history<T>(f: impl AsyncFnOnce(&crate::db::Database) -> T) -> T {
use crate::operations::{TaskDraft, TaskUpdate};
use crate::test_support as t;
let dir = tempfile::tempdir().unwrap();
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
runtime.block_on(async {
let db = t::open_blank_database(&dir.path().join("seed.db"))
.await
.unwrap();
let mut conn = db.acquire_writer().await.unwrap();
let w = t::ensure_default_workspace(&mut conn).await.unwrap();
for label in ["bug", "ux", "later"] {
t::create_label_operation(&mut conn, &w, label)
.await
.unwrap();
}
let draft = |title: &str, is_epic| TaskDraft {
title: title.into(),
description: "body".into(),
project: Some("app".into()),
status: "todo".into(),
priority: "high".into(),
source: crate::choices::TaskSource::Cli,
labels: vec!["bug".into()],
metadata: Vec::new(),
available_at: None,
due_on: Some("2026-10-04".into()),
is_epic,
};
let mut ids = Vec::new();
for (title, is_epic) in [("a", false), ("b", false), ("epic", true)] {
let task = t::create_task(&mut conn, &w, draft(title, is_epic))
.await
.unwrap();
ids.push(task.task.id);
}
let [a, b, epic] = &ids[..] else {
unreachable!()
};
let update = TaskUpdate {
title: Some("renamed".into()),
status: Some("active".into()),
add_labels: vec!["ux".into()],
remove_labels: vec!["bug".into()],
..TaskUpdate::default()
};
t::update_task(&mut conn, &w, a, update).await.unwrap();
t::add_task_dependency(&mut conn, &w, a, b).await.unwrap();
t::add_task_to_epic(&mut conn, &w, b, epic).await.unwrap();
t::set_task_deleted(&mut conn, &w, b, true).await.unwrap();
drop(conn);
f(&db).await
})
}
pub fn seeds() -> Vec<(&'static str, Vec<u8>)> {
use super::invitation_text::{Kind, encode};
let mut seeds = Vec::new();
for (kind, server) in [
(Kind::Setup, "https://sync.example.com"),
(Kind::Device, "http://127.0.0.1:8080"),
] {
let secret = vec![7; kind.secret_len()];
let text = encode(kind, server, &secret).unwrap();
seeds.push(("invitation", text.as_bytes().to_vec()));
}
seeds.extend(super::encrypted_tail::fuzz::seeds());
seeds.extend(super::seed_claim::membership::test_support::fuzz_seeds());
seeds.extend(super::shared_state::package::publication::fuzz::seeds());
seeds
}