280 lines
9.1 KiB
Rust
280 lines
9.1 KiB
Rust
//! Sample data for layout work, compiled into dev builds only. The dev
|
|
//! build uses its own `carrier-demo` profile directory; seeding runs once
|
|
//! when it has no key yet, so read marks survive across refinement
|
|
//! sessions and the release profile's real account is never touched.
|
|
|
|
use std::path::Path;
|
|
|
|
use fumi_core::account::{Account, Identity};
|
|
use fumi_core::address::Address;
|
|
use fumi_core::crypto::{b32, KEY_LEN};
|
|
use fumi_core::message::{build_frontmatter, message_id, seal};
|
|
use fumi_core::store::{now, Store};
|
|
|
|
/// The demo master: fixed bytes, so every dev build derives the same
|
|
/// identity and can open the same sealed sample mail.
|
|
fn demo_master() -> [u8; KEY_LEN] {
|
|
core::array::from_fn(|i| i as u8 + 1)
|
|
}
|
|
|
|
/// A stable sender identity per seed byte.
|
|
fn identity(tag: u8) -> Identity {
|
|
Identity::from_seed([tag; KEY_LEN])
|
|
}
|
|
|
|
pub fn seed(dir: &Path) {
|
|
if dir.join("carrier.key").exists() {
|
|
return;
|
|
}
|
|
if let Err(e) = plant(dir) {
|
|
eprintln!("demo seed failed: {e}");
|
|
}
|
|
}
|
|
|
|
fn plant(dir: &Path) -> Result<(), String> {
|
|
let master = demo_master();
|
|
crate::ops::save_key(&dir.join("carrier.key"), &master)?;
|
|
|
|
let store = Store::open(&dir.join("carrier.db")).map_err(|e| e.to_string())?;
|
|
let me = Account::new(master, 0).map_err(|e| e.to_string())?;
|
|
let addr = Address::parse("demo@carrier.local").map_err(|e| e.to_string())?;
|
|
store.set_account(&addr).map_err(|e| e.to_string())?;
|
|
|
|
let pk = me.me().pk();
|
|
let mut read_ids: Vec<String> = Vec::new();
|
|
|
|
// contacts: (address, tag, verified) — acceptance set below
|
|
let contacts = [
|
|
("alice@example.org", 11u8, true),
|
|
("bob@example.org", 12, false),
|
|
("carol@example.org", 13, true),
|
|
("dave@example.org", 14, false),
|
|
("erin@example.org", 15, true),
|
|
("frank@example.org", 16, false),
|
|
("grace@example.org", 17, true),
|
|
("heidi@example.org", 18, false),
|
|
];
|
|
for (address, tag, verified) in contacts {
|
|
let sender = identity(tag);
|
|
store
|
|
.save_contact(address, &sender.pk(), verified)
|
|
.map_err(|e| e.to_string())?;
|
|
}
|
|
// a spread of states: accepted, pending, blocked
|
|
for (address, tag) in [
|
|
("alice@example.org", 11),
|
|
("bob@example.org", 12),
|
|
("grace@example.org", 17),
|
|
] {
|
|
store
|
|
.accept(address, &identity(tag).pk())
|
|
.map_err(|e| e.to_string())?;
|
|
}
|
|
store
|
|
.accept("erin@example.org", &identity(15).pk())
|
|
.map_err(|e| e.to_string())?;
|
|
store.block("erin@example.org").map_err(|e| e.to_string())?;
|
|
|
|
let mut deliver = |sender: &Identity,
|
|
from: &str,
|
|
subject: Option<&str>,
|
|
body: &str,
|
|
hours_ago: i64,
|
|
requests: bool,
|
|
read: bool|
|
|
-> Result<(), String> {
|
|
let mut fields: Vec<(&str, &str)> = Vec::new();
|
|
if let Some(subject) = subject {
|
|
fields.push(("Subject", subject));
|
|
}
|
|
let uri = Address::parse(from).unwrap().uri(&sender.pk());
|
|
fields.push(("Reply-To", &uri));
|
|
let text = build_frontmatter(&fields, body);
|
|
let envelope = seal(sender, &pk, text.as_bytes(), now() - hours_ago * 3600, true)
|
|
.map_err(|e| e.to_string())?;
|
|
let id = message_id(&envelope);
|
|
store
|
|
.store_inbox(&id, &envelope, now() - hours_ago * 3600, requests, false)
|
|
.map_err(|e| e.to_string())?;
|
|
if read {
|
|
read_ids.push(id.iter().map(|b| format!("{b:02x}")).collect());
|
|
}
|
|
Ok(())
|
|
};
|
|
|
|
// inbox: mixed senders, subjects, read state and times
|
|
deliver(
|
|
&identity(11),
|
|
"alice@example.org",
|
|
Some("Re: deploying the new relay"),
|
|
RELAY_BODY,
|
|
1,
|
|
false,
|
|
false,
|
|
)?;
|
|
deliver(
|
|
&identity(12),
|
|
"bob@example.org",
|
|
Some("lunch on friday?"),
|
|
"There's a new place near the market hall.\n\nSay 12:30?",
|
|
2,
|
|
false,
|
|
false,
|
|
)?;
|
|
deliver(
|
|
&identity(11),
|
|
"alice@example.org",
|
|
None,
|
|
"no subject, just a short note — the layout shows the fallback.",
|
|
26,
|
|
false,
|
|
true,
|
|
)?;
|
|
deliver(
|
|
&identity(17),
|
|
"grace@example.org",
|
|
Some("quarterly infrastructure review — action items, owners and the follow-ups from the migration plan"),
|
|
"Long subjects exercise the row's ellipsis.\n\nMore detail in the pad.",
|
|
49,
|
|
false,
|
|
false,
|
|
)?;
|
|
deliver(
|
|
&identity(13),
|
|
"carol@example.org",
|
|
Some("reading list"),
|
|
LIST_BODY,
|
|
73,
|
|
false,
|
|
true,
|
|
)?;
|
|
deliver(
|
|
&identity(12),
|
|
"bob@example.org",
|
|
Some("Re: lunch on friday?"),
|
|
"12:30 works. See you there.",
|
|
74,
|
|
false,
|
|
true,
|
|
)?;
|
|
|
|
// one inbox mail with the sender's own frontmatter fields, so the
|
|
// detail view has a custom header block to show
|
|
{
|
|
let sender = identity(11);
|
|
let uri = Address::parse("alice@example.org")
|
|
.unwrap()
|
|
.uri(&sender.pk());
|
|
let text = build_frontmatter(
|
|
&[
|
|
("Subject", "canary rollout report"),
|
|
("Reply-To", &uri),
|
|
("Priority", "high"),
|
|
("X-Stage", "canary at 5%"),
|
|
],
|
|
"The custom fields on this one are the sender's own — carrier shows them between the meta line and the body.",
|
|
);
|
|
let envelope = seal(&sender, &pk, text.as_bytes(), now() - 12 * 3600, true)
|
|
.map_err(|e| e.to_string())?;
|
|
let id = message_id(&envelope);
|
|
store
|
|
.store_inbox(&id, &envelope, now() - 12 * 3600, false, false)
|
|
.map_err(|e| e.to_string())?;
|
|
}
|
|
|
|
// requests: senders not in the contact list
|
|
deliver(
|
|
&identity(31),
|
|
"ivy@example.org",
|
|
Some("intro — heard you run a mailbox"),
|
|
"A friend pointed me here. Wanted to say hello and ask how the server holds up.",
|
|
5,
|
|
true,
|
|
false,
|
|
)?;
|
|
deliver(
|
|
&identity(32),
|
|
"judy@example.org",
|
|
Some("hello"),
|
|
"first message, no subject given on the next one.",
|
|
9,
|
|
true,
|
|
true,
|
|
)?;
|
|
deliver(
|
|
&identity(33),
|
|
"mallory@example.org",
|
|
Some("URGENT: verify your account"),
|
|
"Click nothing. This row exists to test how spam sits in the requests tier.",
|
|
30,
|
|
true,
|
|
false,
|
|
)?;
|
|
|
|
// sent: self-copies opened with our own keys
|
|
for (to, subject, hours_ago, read) in [
|
|
("alice@example.org", "Re: deploying the new relay", 3, true),
|
|
("bob@example.org", "lunch on friday?", 27, true),
|
|
(
|
|
"grace@example.org",
|
|
"re: quarterly infrastructure review",
|
|
52,
|
|
true,
|
|
),
|
|
] {
|
|
let text = build_frontmatter(&[("Subject", subject)], "the sent copy of the message");
|
|
let envelope = seal(
|
|
me.me(),
|
|
&pk,
|
|
text.as_bytes(),
|
|
now() - hours_ago * 3600,
|
|
true,
|
|
)
|
|
.map_err(|e| e.to_string())?;
|
|
let id = message_id(&envelope);
|
|
store
|
|
.store_sent(&id, to, &envelope, now() - hours_ago * 3600)
|
|
.map_err(|e| e.to_string())?;
|
|
if read {
|
|
read_ids.push(id.iter().map(|b| format!("{b:02x}")).collect());
|
|
}
|
|
}
|
|
|
|
// pre-marked read rows, so unread dots have both states to show
|
|
std::fs::write(dir.join("read.txt"), read_ids.join("\n") + "\n").map_err(|e| e.to_string())?;
|
|
|
|
// one sample draft, so the drafts folder has a row to show
|
|
crate::ops::save_drafts(
|
|
&dir.join("drafts.txt"),
|
|
&[crate::ops::Draft {
|
|
id: "0000000000000000".into(),
|
|
to: "grace@example.org".into(),
|
|
subject: "Re: quarterly infrastructure review".into(),
|
|
body: "started a reply, will finish it after the review call".into(),
|
|
headers: vec!["Urgency: low".into()],
|
|
reply_to: None,
|
|
at: now() - 3 * 3600,
|
|
}],
|
|
);
|
|
|
|
let _ = b32(&pk);
|
|
Ok(())
|
|
}
|
|
|
|
const RELAY_BODY: &str = "The relay is up and taking traffic again.
|
|
|
|
I rotated the upstream certificate last night; the old chain still verifies for the transition week, so nothing should fall over mid-handshake. If your client complains, re-resolve once and it will pick up the new key.
|
|
|
|
Two things to watch:
|
|
- the queue depth alarm is set to 5000, we hovered near 3000 during the migration
|
|
- retention on the requests tier stays at seven days, main tier at thirty
|
|
|
|
If nothing explodes by friday, I will collapse the fallback route.";
|
|
|
|
const LIST_BODY: &str = "A few things worth your evening:
|
|
|
|
- 'The Rise and Fall of the BBS Scene' — long, nostalgic, worth it for the FidoNet chapter alone
|
|
- the paper on Noise protocol patterns we discussed
|
|
- that talk about small mail servers, the one with the graphs about latency budgets
|
|
|
|
Nothing urgent in there, just good company for a cup of tea.";
|