//! 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())?; // Our own server is pinned, the contacts' server is not: the two // states the trust lines and the pin prompts distinguish. store .pin_server(&addr.host, addr.port, &identity(42).pk()) .map_err(|e| e.to_string())?; let pk = me.me().pk(); let mut read_ids: Vec = 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.";