1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
//! Filing: a sender that does not wait for its mail to arrive (a board's dispatcher, a script) writes the envelope
//! into the receiver's mailbox here and stops (docs/adr/0008-agent-mailbox.md, decision 17; D140). The write is local,
//! needs no network and is idempotent by the message id its key fixes. The mailbox delivers it from there: the
//! machine daemon's watch loop ([`crate::mail_watch::MailCarrier`]) hands it to its session by the session's own door,
//! or, for an address on another machine, carries it there through Teams and files it with the same wake.
use crate::mail_route::Caller;
use crate::mailbox::{mail_root, Envelope, MailAddress, MailKind, Mailbox, ReplyVia};
/// What a filing did.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Filed {
/// The message's id (the one its key fixes, when it had one).
pub message_id: String,
/// Whether this filing wrote it; false when the id was already filed (a second filer, or a repeat).
pub new: bool,
}
/// The id of a message `sender` sends under `key`: a key that is itself a whole message id (`m-` and 24 lowercase
/// hex digits) is that id, so a sender that chose it beforehand (a board blocking a card on the answer to the question
/// it is about to send) knows what the answer will name; any other key is hashed with the sender's address; no key is
/// a fresh id.
pub fn message_id_for(sender: &MailAddress, key: Option<&str>) -> std::io::Result<String> {
Ok(match key {
Some(key)
if key.len() == 26
&& key.starts_with("m-")
&& key[2..]
.bytes()
.all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase()) =>
{
key.to_string()
}
Some(key) => format!(
"m-{}",
&blake3::hash(format!("{sender}\0{key}").as_bytes()).to_hex()[..24]
),
None => crate::mailbox::new_message_id()?,
})
}
/// One message to file.
#[derive(Debug, Clone, Default)]
pub struct Filing {
/// A short subject, separate from the body.
pub subject: Option<String>,
/// Idempotency key: filing again under it files nothing new.
pub key: Option<String>,
/// Ask the receiver to deliver it now (wake an idle session); false leaves it for the session's next turn.
pub wake: bool,
}
/// File `body` from `caller` in the mailbox of `to`, on this machine's mail root whatever machine `to` names (mail for
/// another machine waits here until the watch loop has carried it there). A wake is requested only by the filing that
/// wrote the message, so a repeat never delivers it twice.
pub fn file(
caller: &Caller,
to: &MailAddress,
body: &str,
filing: &Filing,
) -> std::io::Result<Filed> {
let mut envelope = Envelope::new(
caller.address.clone(),
caller.name.clone(),
MailKind::Peer,
ReplyVia::Command,
body,
)?;
envelope.id = message_id_for(&caller.address, filing.key.as_deref())?;
envelope.subject = filing.subject.as_deref().map(|value| {
value
.lines()
.next()
.unwrap_or("")
.chars()
.take(200)
.collect()
});
let mailbox = Mailbox::open(&mail_root(), to)?;
if mailbox.find(&envelope.id)?.is_some() {
return Ok(Filed {
message_id: envelope.id,
new: false,
});
}
mailbox.deliver(&envelope)?;
if filing.wake {
mailbox.request_wake(&envelope.id)?;
}
Ok(Filed {
message_id: envelope.id,
new: true,
})
}