Skip to main content

offline_protocol_leaf/
device.rs

1//! The device: what it does with a frame, and what it hands back.
2
3use alloc::{
4    format,
5    string::{String, ToString},
6    sync::Arc,
7    vec,
8    vec::Vec,
9};
10use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
11use mls_rs::client_builder::MlsConfig;
12use mls_rs::group::ReceivedMessage;
13use mls_rs::{Client, Group, MlsMessage};
14use offline_protocol_core::{Address, AppId, Message, MessagePriority};
15use offline_protocol_sealed::{
16    prefixes, EncryptedMessage, GroupId, KeyPackagePayload, MlsMessageType, WelcomeMessage,
17    MLS_ENVELOPE_COMPACT_V1,
18};
19
20use crate::adapters::{KeyPackageAdapter, PeerRecord, PRIOR_EPOCH_RETENTION};
21use crate::error::{LeafError, Result};
22use crate::frames;
23use crate::identity::{build_client, Identity};
24use crate::keypkg;
25use crate::store::{
26    LeafStore, KEY_TYPE_GROUP_EPOCH, KEY_TYPE_GROUP_STATE, KEY_TYPE_KEY_PACKAGE, KEY_TYPE_PEER,
27    KEY_TYPE_PEER_INDEX,
28};
29
30/// How many peers an **inbound** frame may add records for.
31///
32/// Every peer that sends a well-formed key package gets a record and a minted
33/// package in return, both of which are writes to flash. The signature on that
34/// frame proves the sender holds the key its address derives from, which is
35/// exactly as hard as generating a key, so without a bound a stranger fills a
36/// device's storage for the cost of some signing. The phone bounds the same
37/// exchange for the same reason.
38///
39/// It bounds what arrives, not what firmware chooses:
40/// [`LeafDevice::key_package_frame`] is the integrator deciding to pair and is
41/// not held to this. The failure being prevented is a stranger spending a
42/// device's flash, not an owner spending their own.
43///
44/// Sixteen covers a household and its guests. What it costs when full is
45/// stated on [`LeafError::TooManyPeers`].
46const MAX_PEERS: usize = 16;
47
48/// How far past the retained window [`LeafDevice::unpair`] sweeps, in **both**
49/// directions.
50///
51/// [`PRIOR_EPOCH_RETENTION`] says what a healthy device holds. This is the
52/// margin for one that is not, and each side of the window needs it for a
53/// different reason. Below: a delete that failed during trimming leaves a
54/// record holding an epoch's secrets. Above: the adapter writes epoch records
55/// before the state entry that sequences them, so a cut between the two leaves
56/// a record that every anchor agrees is not there. Forgetting a peer is the
57/// moment to clear both rather than the moment to assume they are absent.
58const FORGET_EPOCH_SLACK: u64 = 16;
59
60/// Something that happened, for the firmware to act on.
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub enum LeafEvent {
63    /// A peer advertised itself. The device now knows what it may emit.
64    PeerAdvertised {
65        /// The peer's address.
66        peer: String,
67    },
68    /// A session came up. The device can seal to this peer from here.
69    SessionEstablished {
70        /// The peer's address.
71        peer: String,
72    },
73    /// A peer discarded its session and the device followed.
74    SessionReset {
75        /// The peer's address.
76        peer: String,
77    },
78    /// An application message arrived and decrypted.
79    ///
80    /// `peer` is proven: it is the address the sealing group member's own
81    /// signature key derives to. What it is not is permission. Any address in
82    /// radio range can complete a pairing, so firmware decides what a message
83    /// from this particular peer is allowed to actuate. A lock that opens for
84    /// whatever arrives on an established session opens for anyone patient
85    /// enough to pair with it.
86    MessageReceived {
87        /// The peer's address, proven rather than claimed.
88        peer: String,
89        /// The plaintext.
90        text: String,
91    },
92    /// A commit arrived and was applied. Post-compromise security advances
93    /// here, on the cadence the peer sets, because this device never commits.
94    CommitApplied {
95        /// The peer's address.
96        peer: String,
97        /// The epoch the session is now in.
98        epoch: u64,
99    },
100    /// A frame was not for this device, or carried nothing to act on.
101    ///
102    /// Surfaced rather than swallowed so a bench can tell "refused" from
103    /// "silently dropped", which are the two states a pairing failure looks
104    /// like from the outside.
105    Ignored {
106        /// Why nothing happened.
107        reason: String,
108    },
109}
110
111/// What a frame produced.
112#[derive(Debug, Default)]
113pub struct Handled {
114    /// Frames to transmit, in order. Every one is already durable.
115    pub outbound: Vec<Message>,
116    /// What the firmware should know about.
117    pub events: Vec<LeafEvent>,
118}
119
120/// What a frame proved about the peer it names.
121///
122/// A frame's sender is a plaintext field, and proving it is the whole business
123/// of the handlers [`LeafDevice::dispatch`] routes to. Two of that function's
124/// arms prove nothing, because a frame carrying nothing this device acts on
125/// gives them nothing to work with, and those two are not failures: they
126/// return normally, with a reason.
127///
128/// So "did not fail" and "proved who sent it" are different questions, and an
129/// acknowledgement is owed only for the second. Answering on the first would
130/// hand anyone within radio range exactly what
131/// [`LeafDevice::acknowledge`]'s known-peer gate exists to withhold, because
132/// naming a paired peer costs an attacker nothing beyond having overheard one
133/// frame.
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135enum Sender {
136    /// A control signature verified against the address the frame names, or
137    /// the frame opened under a key only that peer holds.
138    Proven,
139    /// Nothing was checked. The sender is whatever the last hand to touch the
140    /// frame wrote there.
141    Claimed,
142}
143
144/// A leaf node.
145///
146/// # The never-committing profile
147///
148/// The peer creates the group, adds this device, and issues every commit. The
149/// device joins, opens what arrives, answers, and persists. It never emits a
150/// Welcome, a commit or a proposal, and never any group, rich, document or
151/// relay frame. Per-commit cost here is two elliptic-curve operations;
152/// per-message cost is symmetric only.
153///
154/// Post-compromise security therefore arrives on the peer's cadence rather
155/// than this device's, and a driven rekey reaches it as a key package with
156/// `session_reset` set rather than as an unsolicited Welcome.
157///
158/// # Storage is the source of truth
159///
160/// No MLS state is cached in this value. Every operation loads the group from
161/// the store, does its work, writes back, and only then returns the frame to
162/// send. That costs a load per frame, which is the right trade for a device
163/// that handles a frame every few minutes and must survive losing power
164/// between any two instructions: there is no in-RAM state for a power cut to
165/// desynchronize from what is on flash.
166///
167/// # One writer at a time
168///
169/// Every operation that advances state takes `&mut self`, so the compiler
170/// enforces what the ratchet requires. Two seals running at once would load
171/// the same generation, encrypt under the same key and nonce, and emit both
172/// frames: the AEAD nonce reuse this crate's whole persist-before-emit rule
173/// exists to prevent, arrived at without ever losing power. The same argument
174/// covers the send counter, where a race mints one id twice and the peer's
175/// deduplicator swallows the second message.
176///
177/// A device is therefore one value, exclusively held. Two `LeafDevice`s over
178/// one [`LeafStore`] would each satisfy the borrow checker and race anyway;
179/// construct one per store.
180///
181/// # What it does not defend against
182///
183/// **Anyone pairing.** Every gate here answers "is this peer the address it
184/// claims to be", and none of them answers "did the owner mean this peer".
185/// Producing a key that derives to its own address costs nothing, so an
186/// unattended device admits whoever asks, up to the bound above. That is the
187/// same position two phones are in, where the out-of-band artifact carries
188/// first-contact trust; on a device it is firmware that decides when the radio
189/// accepts a pairing at all, and firmware that decides what an opened message
190/// may actuate. [`LeafDevice::peers`] is how it audits what accumulated.
191///
192/// A replayed control frame, **as far as this device's clock is honest**. The
193/// signed payload states when the frame was made, so one older than two days
194/// is refused outright, and a reset-flagged key package (the destructive case,
195/// since it tears down a live session) is additionally refused unless it is
196/// newer than the last reset this device acted on. One capture is therefore
197/// worth one teardown, and only inside a two-day window.
198///
199/// What that leaves is the clock. Both checks are made against the time source
200/// firmware supplies, so a device that supplies a wrong one either refuses its
201/// peer or, if the clock is far enough behind, admits frames it should have
202/// refused. The obligation is stated with the others in
203/// [the provisioning chapter](https://github.com/Offline-Protocol/offline-protocol-sdk/blob/main/docs/spec/leaf-provisioning.md),
204/// and it is why the time source is a parameter on every entry point here
205/// rather than something this crate reaches for.
206///
207/// `Debug` renders the device's address and nothing else. Everything else it
208/// holds is either secret or a handle to secrets, and a device that printed
209/// its identity key into a log would undo the part of the threat model that
210/// says the key never leaves storage.
211pub struct LeafDevice {
212    store: Arc<dyn LeafStore>,
213    identity: Identity,
214    app_id: AppId,
215}
216
217impl LeafDevice {
218    /// Generates an identity and returns a device that holds it.
219    ///
220    /// Draws from the `getrandom` backend the firmware registered. Refuses if
221    /// the store already holds an identity, because replacing one changes the
222    /// device's address and silently orphans every peer paired with it.
223    pub fn provision(store: Arc<dyn LeafStore>, app_id: &str) -> Result<Self> {
224        let identity = Identity::provision(&store)?;
225        Ok(Self {
226            store,
227            identity,
228            app_id: parse_app_id(app_id)?,
229        })
230    }
231
232    /// Loads a device that was provisioned earlier.
233    pub fn resume(store: Arc<dyn LeafStore>, app_id: &str) -> Result<Self> {
234        let identity = Identity::resume(&store)?;
235        Ok(Self {
236            store,
237            identity,
238            app_id: parse_app_id(app_id)?,
239        })
240    }
241
242    /// Loads a device, provisioning one on first boot.
243    pub fn open(store: Arc<dyn LeafStore>, app_id: &str) -> Result<Self> {
244        match Self::resume(Arc::clone(&store), app_id) {
245            Err(LeafError::NotProvisioned) => Self::provision(store, app_id),
246            other => other,
247        }
248    }
249
250    /// This device's address. Self-certifying: it is the hash of the identity
251    /// key, so a peer checks a claim rather than trusting a directory.
252    pub fn address(&self) -> &Address {
253        &self.identity.address
254    }
255
256    /// Mints a key package and wraps it in a signed frame for `peer`.
257    ///
258    /// `now_unix_secs` is the pairing time source. It is a parameter because a
259    /// device has no clock, and passing something wrong here is the difference
260    /// between pairing and being refused as expired.
261    pub fn key_package_frame(&mut self, peer: &str, now_unix_secs: u64) -> Result<Message> {
262        self.key_package_frame_inner(peer, now_unix_secs, false)
263    }
264
265    fn key_package_frame_inner(
266        &mut self,
267        peer: &str,
268        now_unix_secs: u64,
269        session_reset: bool,
270    ) -> Result<Message> {
271        let client = self.client()?;
272        let minted = keypkg::mint(&client, now_unix_secs)?;
273        let payload = keypkg::payload(
274            &self.identity.address.to_string(),
275            minted.data,
276            session_reset,
277        );
278        let body = serde_json::to_string(&payload)
279            .map_err(|e| LeafError::MalformedFrame(format!("cannot encode key package: {e}")))?;
280
281        let mut message = frames::build(
282            &self.store,
283            &self.identity,
284            &self.app_id,
285            peer,
286            format!("{}{}", prefixes::KEY_PACKAGE, body),
287            now_unix_secs,
288            MessagePriority::High,
289        )?;
290        frames::sign_control_frame(&self.identity, &mut message)?;
291
292        // Recorded before the frame is handed back, so a device that emits one
293        // and loses power does not emit a second on the next boot and leave
294        // the peer holding two init keys of which only one is ever spent.
295        //
296        // The reference is recorded here for a second reason: it is the only
297        // thing that later distinguishes this peer's Welcome from one built by
298        // whoever else heard this frame. Minting has already written the
299        // package itself, so a cut between the two leaves a package no Welcome
300        // can name, which the adapter evicts in its turn. The opposite order
301        // would leave a name pointing at nothing.
302        let mut record = self.peer_record(peer)?;
303        record.key_package_sent = true;
304        record.key_package_ref = Some(minted.reference);
305        record
306            .save(&self.store, peer)
307            .map_err(|e| LeafError::Storage(e.to_string()))?;
308
309        // Handing out a package is the moment a pairing with this peer becomes
310        // possible, so it is the moment the peer becomes something firmware
311        // should be able to see in [`LeafDevice::peers`]. Inbound exchanges
312        // reach here having already been admitted, where this is a no-op.
313        self.index_peer(peer)?;
314
315        Ok(message)
316    }
317
318    /// Seals `plaintext` to `peer`.
319    ///
320    /// The ratchet advances, the new state is persisted, and only then does
321    /// the frame exist. A store that fails produces an error and no frame,
322    /// which is the whole point: a device that emitted first and persisted
323    /// second would, after a power cut, come back and reuse an AEAD nonce.
324    pub fn seal(&mut self, peer: &str, plaintext: &str, now_unix_secs: u64) -> Result<Message> {
325        self.seal_content(peer, plaintext.as_bytes(), now_unix_secs)
326    }
327
328    fn seal_content(
329        &mut self,
330        peer: &str,
331        plaintext: &[u8],
332        now_unix_secs: u64,
333    ) -> Result<Message> {
334        let client = self.client()?;
335        let group_id = self.group_id(peer)?;
336        let mut group = self.load_group(&client, peer, &group_id)?;
337
338        // Nothing is sealed into a group that stopped being this pair. The
339        // roster is the only thing that says it did, and the commit that
340        // changed it is applied and durable by the time anything can read it,
341        // so the question has to be asked at the use and not only at the
342        // change: a refusal returned once is a value firmware may drop and a
343        // reboot certainly does, and the device would then seal every later
344        // message into a room with a member the owner never chose.
345        self.require_still_a_pair(&group, peer)?;
346
347        let sealed = group
348            .encrypt_application_message(plaintext, Vec::new())
349            .map_err(|e| LeafError::Mls(format!("cannot seal: {e:?}")))?
350            .to_bytes()
351            .map_err(|e| LeafError::Mls(format!("cannot encode sealed message: {e:?}")))?;
352        let epoch = group.current_epoch();
353
354        // Persist before emit. Everything below this line only shapes bytes
355        // that are already accounted for on flash.
356        group
357            .write_to_storage()
358            .map_err(|e| LeafError::Storage(format!("cannot persist group state: {e:?}")))?;
359
360        let envelope = EncryptedMessage {
361            group_id,
362            message_type: MlsMessageType::Application,
363            epoch,
364            ciphertext: sealed,
365            sender_id: self.identity.address.to_string(),
366            timestamp_ms: (now_unix_secs.saturating_mul(1000)),
367        };
368
369        let body = self.encode_envelope(peer, &envelope)?;
370        frames::build(
371            &self.store,
372            &self.identity,
373            &self.app_id,
374            peer,
375            format!("{}{}", prefixes::ENCRYPTED, body),
376            now_unix_secs,
377            MessagePriority::Medium,
378        )
379    }
380
381    /// Chooses the envelope encoding for this peer.
382    ///
383    /// Compact only when the peer advertised it. Otherwise the JSON floor,
384    /// which every conforming receiver parses unconditionally and which no
385    /// negotiation ever removes.
386    fn encode_envelope(&self, peer: &str, envelope: &EncryptedMessage) -> Result<String> {
387        let record = self.peer_record(peer)?;
388        if record.env_versions.contains(&MLS_ENVELOPE_COMPACT_V1) {
389            Ok(BASE64.encode(envelope.to_bytes()))
390        } else {
391            serde_json::to_string(envelope)
392                .map_err(|e| LeafError::MalformedFrame(format!("cannot encode envelope: {e}")))
393        }
394    }
395
396    /// Handles one inbound frame.
397    ///
398    /// Returns the frames to send and what happened. Everything in
399    /// [`Handled::outbound`] is already durable by the time it is returned.
400    ///
401    /// # A frame addressed elsewhere is answered by nothing
402    ///
403    /// A radio hears what it is not the recipient of, so this is the first
404    /// question asked, before a signature is verified or a prefix is read.
405    /// Nothing further down asks it again, and neither kind of frame answers
406    /// it on its own: a control frame's signature **covers** the recipient
407    /// rather than checking it, so one honestly signed for somebody else
408    /// verifies perfectly here, and a sealed frame carries no signature at
409    /// all, so its recipient is whatever the last hand to touch it wrote
410    /// there. Being able to open a frame is a different claim from having been
411    /// sent it.
412    ///
413    /// Three things follow from not asking. An overheard key package admits a
414    /// peer, spends flash on a record, mints a private init key nobody asked
415    /// this device for, and answers a phone that never addressed it. A sealed
416    /// frame this device really can open is acted on after anyone who captured
417    /// it rewrote the recipient, because that field is not inside the AEAD.
418    /// And every other prefix arrives as an identity-binding failure, so
419    /// ordinary traffic between two neighbours reaches firmware wearing the
420    /// shape of an attack, on a device whose only account of itself is that
421    /// error stream.
422    ///
423    /// What the check does not do is keep anyone else's ciphertext readable
424    /// here: the group and credential gates below hold either way.
425    ///
426    /// It is [`LeafEvent::Ignored`] rather than an error because overhearing is
427    /// what a shared radio does, and because firmware that carries frames for
428    /// its neighbours needs "not mine" to be a fact it can act on rather than a
429    /// failure it has to interpret.
430    pub fn handle(&mut self, message: &Message, now_unix_secs: u64) -> Result<Handled> {
431        if !self.is_addressed_to_me(message) {
432            return Ok(ignored("frame is addressed to another node"));
433        }
434
435        // Before the frame is opened, because opening it a second time is what
436        // fails: the ratchet has spent that generation and refuses it, and the
437        // peer is only asking again because the first answer went missing.
438        if let Some(repeat) = self.repeat_acknowledgement(message, now_unix_secs)? {
439            return Ok(repeat);
440        }
441
442        let (mut handled, sender) = self.dispatch(message, now_unix_secs)?;
443
444        // Only a frame that proved who sent it is answered, which is narrower
445        // than "was not refused". An `Err` above never reaches here, and that
446        // much is the phone's own rule and the reason for it: an
447        // acknowledgement is a receipt, and handing one to whoever just failed
448        // the signature gate tells them their frames are being processed. But
449        // a frame carrying no prefix fails nothing, so it needs the other half
450        // of the question: see [`Sender`].
451        if sender == Sender::Proven {
452            match self.acknowledge(message, now_unix_secs) {
453                Ok(Some(ack)) => handled.outbound.push(ack),
454                Ok(None) => {}
455                // Never propagated, because by here the frame is open and what
456                // it produced is already owed to firmware: the ratchet has
457                // spent that generation, so returning the error would discard
458                // an unlock this device really did receive, and the
459                // retransmission that followed would be refused as a replay
460                // with nothing remembered to answer it from. The answer is
461                // dropped instead, which costs the retry ladder that ran
462                // before any of this existed and is recovered by it.
463                Err(e) => handled.events.push(LeafEvent::Ignored {
464                    reason: format!("the answer this frame is owed could not be stored: {e}"),
465                }),
466            }
467        }
468
469        Ok(handled)
470    }
471
472    /// Routes a frame to the handler for its prefix, and says whether anything
473    /// on that route proved the sender the frame names.
474    ///
475    /// Every handler below either verifies a control signature as its first
476    /// act or opens the frame under the pair's group key, on every path that
477    /// returns normally. That is what makes their success [`Sender::Proven`],
478    /// and a handler that stopped doing it would have to say so here.
479    fn dispatch(&mut self, message: &Message, now_unix_secs: u64) -> Result<(Handled, Sender)> {
480        let content = &message.content;
481
482        // Order matters: the encrypted-confirm prefix is not checked here at
483        // all, because it never travels as a frame. It is only ever found
484        // inside a decrypted plaintext, which is where this looks for it.
485        if let Some(body) = frames::strip_prefix(content, prefixes::KEY_PACKAGE) {
486            Ok((
487                self.on_key_package(message, body, now_unix_secs)?,
488                Sender::Proven,
489            ))
490        } else if let Some(body) = frames::strip_prefix(content, prefixes::WELCOME) {
491            Ok((
492                self.on_welcome(message, body, now_unix_secs)?,
493                Sender::Proven,
494            ))
495        } else if let Some(body) = frames::strip_prefix(content, prefixes::ENCRYPTED) {
496            Ok((
497                self.on_encrypted(message, body, now_unix_secs)?,
498                Sender::Proven,
499            ))
500        } else if frames::strip_prefix(content, prefixes::SESSION_CONFIRM_PROBE).is_some() {
501            Ok((self.on_probe(message, now_unix_secs)?, Sender::Proven))
502        } else if frames::strip_prefix(content, prefixes::SESSION_CONFIRM_ACK).is_some() {
503            // Never acted on, and not a frame this device accepts at all. A
504            // leaf emits an acknowledgement and never a probe, so it has none
505            // outstanding and every inbound one is unsolicited.
506            //
507            // Reading one as proof of a session is the bypass: producing a
508            // frame that derives to its own address costs an attacker nothing,
509            // so acting on it would let anyone holding a keypair tell firmware
510            // a session exists that this device would refuse to seal into. The
511            // phone gates the same frame on holding a session of its own; the
512            // profile in the spec lists this prefix under what a leaf emits and
513            // not under what it accepts.
514            Ok((
515                ignored("an acknowledgement arrived for a probe this device never sends"),
516                Sender::Claimed,
517            ))
518        } else {
519            Ok((
520                ignored("frame carries no prefix this device answers"),
521                Sender::Claimed,
522            ))
523        }
524    }
525
526    fn on_key_package(
527        &mut self,
528        message: &Message,
529        body: &str,
530        now_unix_secs: u64,
531    ) -> Result<Handled> {
532        // The one frame class that admits the older payload, because it is the
533        // frame that *teaches* a peer which payload this device accepts: a
534        // sender that has never held this device's key package signs the older
535        // one, whatever release it runs, and refusing it makes a
536        // phone-initiated pairing impossible rather than making the device
537        // safer. See [`frames`] for the full reasoning and for what it costs.
538        let signed_under =
539            frames::verify_control_frame(message, now_unix_secs, frames::UndatedPayload::Admitted)?;
540        let payload: KeyPackagePayload = serde_json::from_str(body)
541            .map_err(|e| LeafError::MalformedFrame(format!("key package body: {e}")))?;
542
543        let sender = message.sender.as_str();
544
545        // The body names its own owner, and the frame names its sender. A
546        // package that claims to belong to someone other than the peer that
547        // signed the frame is one being relayed under a borrowed name, so the
548        // two must agree before anything is stored under either.
549        if payload.user_id != sender {
550            return Err(LeafError::IdentityBinding(format!(
551                "key package claims to be '{}' but the frame is signed by '{}'",
552                payload.user_id, sender
553            )));
554        }
555
556        // Bounded before the first byte is written under this peer's name.
557        // Everything below here stores something, and a signature that only
558        // proves a key derives to its own address is not a scarce thing to
559        // produce.
560        self.admit_peer(sender)?;
561
562        let mut events = Vec::new();
563        let mut record = self.peer_record(sender)?;
564
565        // A reset carried under the older payload is refused its teardown and
566        // nothing else. That payload leaves the timestamp outside the
567        // signature, so the high-water mark below would be comparing a number
568        // an attacker rewrote at will: parked in the future once, it would
569        // deny this pair every future reset, which is a worse failure than the
570        // replay it would be defending against. The rest of the frame is still
571        // acted on, because capability advertisement is unauthenticated hint
572        // data everywhere in this protocol, and the peer re-sends a reset
573        // under the newer payload as soon as this device's key package has
574        // taught it to.
575        if payload.session_reset && signed_under != frames::ControlPayload::Fresh {
576            events.push(LeafEvent::Ignored {
577                reason: String::from(
578                    "a session reset arrived on a frame that states no time, so its age \
579                     cannot be judged and its teardown is refused",
580                ),
581            });
582        } else if payload.session_reset {
583            // The frame's own stamp, which `verify_control_frame` has already
584            // proved is the sender's and inside the window this device
585            // accepts. Everything below rests on both of those: an unproved
586            // stamp would let anyone park the mark past every future reset.
587            let stamp = message.timestamp.as_millis();
588            if !record.reset_is_unspent(stamp) {
589                // A reset already acted on, or older than one that was.
590                // Tearing down again is how a captured frame becomes a
591                // repeatable way to break a session that has since been
592                // rebuilt, so the teardown is what a replay loses; the record
593                // below is still refreshed, because a peer restating its
594                // capabilities is harmless and a retransmission is the
595                // ordinary reason to see this twice.
596                events.push(LeafEvent::Ignored {
597                    reason: String::from("a session reset arrived that this device has spent"),
598                });
599            } else {
600                // Marked before the teardown, never after. The teardown is
601                // followed by a fresh pairing, so a power cut in between would
602                // leave this frame able to break the replacement too, and a
603                // power cut is the one event this crate assumes will happen.
604                record.remember_reset(stamp);
605                record.key_package_sent = false;
606                // Written here *and* again at the end of this function, which
607                // is deliberate rather than redundant: the second write
608                // carries the peer's refreshed capabilities and cannot be
609                // moved earlier, and this one cannot be moved later without
610                // putting a power cut between the mark and the teardown it
611                // authorizes. Two writes on a reset frame, which arrives about
612                // as often as a rekey; the ordinary frame still writes once.
613                record
614                    .save(&self.store, sender)
615                    .map_err(|e| LeafError::Storage(e.to_string()))?;
616                self.forget_session(sender)?;
617                events.push(LeafEvent::SessionReset {
618                    peer: sender.to_string(),
619                });
620            }
621        }
622
623        record.env_versions = payload.env_versions.clone();
624        record.wire_versions = payload.wire_versions.clone();
625        record
626            .save(&self.store, sender)
627            .map_err(|e| LeafError::Storage(e.to_string()))?;
628        events.push(LeafEvent::PeerAdvertised {
629            peer: sender.to_string(),
630        });
631
632        // Answer with our own package only if this peer has not already had
633        // one. Without that guard two peers that both answer trade key
634        // packages forever, each one spending an init key.
635        let outbound = if record.key_package_sent {
636            Vec::new()
637        } else {
638            vec![self.key_package_frame_inner(sender, now_unix_secs, false)?]
639        };
640
641        Ok(Handled { outbound, events })
642    }
643
644    fn on_welcome(&mut self, message: &Message, body: &str, now_unix_secs: u64) -> Result<Handled> {
645        // The older payload is refused here and needs no accommodation: a
646        // Welcome can only follow this device's own key package reaching the
647        // peer, and that is the frame which teaches the peer to sign the
648        // freshness-bound one.
649        frames::verify_control_frame(message, now_unix_secs, frames::UndatedPayload::Refused)?;
650        let welcome: WelcomeMessage = serde_json::from_str(body)
651            .map_err(|e| LeafError::MalformedFrame(format!("welcome body: {e}")))?;
652
653        let sender = message.sender.as_str();
654
655        // The inviter named inside the body must be the peer that signed the
656        // frame, and the group must be the one this pair would build. Without
657        // the second check a peer could hand over a Welcome for a group it
658        // built with somebody else, and the device would join it and seal into
659        // a room whose membership it never checked.
660        if welcome.inviter_id != sender {
661            return Err(LeafError::IdentityBinding(format!(
662                "welcome names inviter '{}' but the frame is signed by '{}'",
663                welcome.inviter_id, sender
664            )));
665        }
666        let expected = self.group_id(sender)?;
667        if welcome.group_id != expected {
668            return Err(LeafError::IdentityBinding(format!(
669                "welcome is for group '{}', not this pair's '{}'",
670                welcome.group_id, expected
671            )));
672        }
673
674        let client = self.client()?;
675        let welcome_message = MlsMessage::from_bytes(&welcome.welcome_data)
676            .map_err(|e| LeafError::Mls(format!("welcome does not decode: {e:?}")))?;
677
678        // And the Welcome must spend the key package this device minted **for
679        // this peer**. The two checks above are about the peer and the group;
680        // this one is about the material, and nothing else in the exchange
681        // covers it, because a key package rides in a frame that is signed but
682        // not encrypted and is therefore spendable by whoever copied it off the
683        // air.
684        //
685        // Checked before the join, which is the whole point: joining spends the
686        // init key, and the failure being prevented is not a stranger reading
687        // anything (the group and credential gates hold either way) but this
688        // device's package being **burned by somebody it was not meant for**,
689        // which leaves the intended peer holding a Welcome that can no longer
690        // be joined and a pairing that only a driven reset recovers.
691        self.require_own_key_package(sender, &welcome_message)?;
692
693        // `tree_data: None` because the peer puts the ratchet tree in the
694        // Welcome. A device that needed it out of band would need a side
695        // channel it does not have.
696        let (mut group, _info) = client
697            .join_group(None, &welcome_message, None)
698            .map_err(|e| LeafError::Mls(format!("cannot join from the welcome: {e:?}")))?;
699
700        // The group checked above was the one the body *claimed*. This is the
701        // one that was actually joined, and they are separate values: the
702        // claim is a JSON field beside the Welcome, and the Welcome carries
703        // its own group id inside. A frame that puts an honest claim in front
704        // of somebody else's Welcome passes the first check and fails here.
705        //
706        // Refused before the state is written, because joining has already
707        // spent this device's init key, and persisting the group as well would
708        // leave a room it never chose sitting on flash for every later gate to
709        // keep refusing.
710        if group.group_id() != expected.as_str().as_bytes() {
711            return Err(LeafError::IdentityBinding(format!(
712                "welcome claimed group '{expected}' but joined a different one"
713            )));
714        }
715
716        group
717            .write_to_storage()
718            .map_err(|e| LeafError::Storage(format!("cannot persist group state: {e:?}")))?;
719
720        // A session exists from here, so the peer has to be in the index
721        // whatever route it took to get one: a session firmware cannot see in
722        // [`LeafDevice::peers`] is one it can neither audit nor
723        // [`LeafDevice::unpair`].
724        //
725        // Ordinarily it is already there, put there by the same mint that
726        // recorded the reference this Welcome just spent, and an eviction
727        // since then would have taken that reference with it and been refused
728        // above. What is left is the one order that separates them: the mint
729        // saves the peer record and indexes second, so an index write that
730        // failed leaves a peer holding a live reference and no slot. That peer
731        // arrives here.
732        self.index_peer(sender)?;
733
734        // The confirmation is a group-aware decrypt, sealed inside an ordinary
735        // envelope. A peer that created a session of its own confirms only on
736        // a successful decrypt, so a plaintext acknowledgement would leave it
737        // unconfirmed however many times it was sent.
738        let confirm = self.seal_content(
739            sender,
740            prefixes::SESSION_CONFIRM_ENCRYPTED.as_bytes(),
741            now_unix_secs,
742        )?;
743
744        Ok(Handled {
745            outbound: vec![confirm],
746            events: vec![LeafEvent::SessionEstablished {
747                peer: sender.to_string(),
748            }],
749        })
750    }
751
752    fn on_encrypted(&mut self, message: &Message, body: &str, _now: u64) -> Result<Handled> {
753        // Not signature-gated: this is the data plane, and MLS authenticates
754        // its own sender. A second signature on the outside would state what
755        // the AEAD already proves on the inside.
756        let envelope = parse_envelope(body)?;
757        let sender = message.sender.as_str();
758
759        // The envelope names a sender too, and it is inside nothing: it rides
760        // in the clear beside the ciphertext. Binding it to the wire sender
761        // before the group is loaded keeps a relayed frame from being
762        // attributed to whoever forwarded it.
763        if envelope.sender_id != sender {
764            return Err(LeafError::IdentityBinding(format!(
765                "envelope claims sender '{}' but the frame came from '{}'",
766                envelope.sender_id, sender
767            )));
768        }
769
770        let client = self.client()?;
771        let group_id = self.group_id(sender)?;
772        if envelope.group_id != group_id {
773            return Err(LeafError::IdentityBinding(format!(
774                "envelope is for group '{}', not this pair's '{}'",
775                envelope.group_id, group_id
776            )));
777        }
778
779        let mut group = self.load_group(&client, sender, &group_id)?;
780
781        // Asked before the frame is opened, for the reason a seal asks it
782        // before sealing: a group that stopped being this pair is one this
783        // device holds no conversation with in either direction. The commit
784        // that widens a roster is caught below instead, after it applies,
785        // because that is the only moment it can be seen at all; this is what
786        // keeps every frame after that one from arriving as ordinary traffic.
787        self.require_still_a_pair(&group, sender)?;
788
789        let inbound = MlsMessage::from_bytes(&envelope.ciphertext)
790            .map_err(|e| LeafError::Mls(format!("sealed payload does not decode: {e:?}")))?;
791
792        let received = group
793            .process_incoming_message(inbound)
794            .map_err(|e| LeafError::Mls(format!("cannot open: {e:?}")))?;
795
796        let epoch = group.current_epoch();
797        group
798            .write_to_storage()
799            .map_err(|e| LeafError::Storage(format!("cannot persist group state: {e:?}")))?;
800
801        let events = match received {
802            ReceivedMessage::ApplicationMessage(app) => {
803                // The leaf identity binding, applied here as it is on the
804                // phone: the group member that actually sealed this must be
805                // the peer the frame claims to come from. MLS proves a member
806                // sealed it; only re-deriving the address from that member's
807                // signature key proves *which* member, and in a group this
808                // device did not build, membership is not something it chose.
809                self.bind_sender_credential(&group, app.sender_index, sender)?;
810
811                let text = String::from_utf8_lossy(app.data()).to_string();
812                // Consumed and never surfaced: it exists to be a decrypt, not
813                // to be read.
814                if text == prefixes::SESSION_CONFIRM_ENCRYPTED {
815                    vec![LeafEvent::SessionEstablished {
816                        peer: sender.to_string(),
817                    }]
818                } else {
819                    vec![LeafEvent::MessageReceived {
820                        peer: sender.to_string(),
821                        text,
822                    }]
823                }
824            }
825            ReceivedMessage::Commit(commit) => {
826                // Two questions a commit has to answer, ordered so the
827                // first failure is the informative one: is this still the
828                // pair this device joined, and did the peer whose frame
829                // carried this actually author it. The roster answers the
830                // first. Only the committer's own credential answers the
831                // second, because the frame states who sent it and a peer is
832                // free to relay a third member's work under its own name,
833                // which without this reaches firmware wearing the peer's
834                // address.
835                //
836                // Reported rather than rolled back. A member cannot skip one
837                // commit and keep decrypting the next, so by the time there
838                // is anything to read the commit is applied and durable. What
839                // these two buy is that firmware hears a group stopped being
840                // the pair it agreed to, rather than nothing at all.
841                self.require_still_a_pair(&group, sender)?;
842                self.bind_sender_credential(&group, commit.committer, sender)?;
843                vec![LeafEvent::CommitApplied {
844                    peer: sender.to_string(),
845                    epoch,
846                }]
847            }
848            _ => vec![LeafEvent::Ignored {
849                reason: String::from("sealed frame carried nothing this device acts on"),
850            }],
851        };
852
853        Ok(Handled {
854            outbound: Vec::new(),
855            events,
856        })
857    }
858
859    /// Answers a liveness probe, but only for a peer this device can still
860    /// talk to.
861    ///
862    /// The acknowledgement is not a pleasantry: a peer treats it as proof the
863    /// session is usable and confirms on it, then flushes everything it had
864    /// queued into that session. A device that answered after losing its store
865    /// would confirm a session it cannot decrypt a single frame of, and the
866    /// peer would have no way to find that out, because the device's silence
867    /// afterwards is indistinguishable from a quiet link. Staying quiet here
868    /// leaves the peer unconfirmed, which is a state it knows how to repair.
869    ///
870    /// This is the peer's own rule, applied on the device: it answers a probe
871    /// only while it holds a session of its own.
872    fn on_probe(&mut self, message: &Message, now_unix_secs: u64) -> Result<Handled> {
873        // The older payload is refused here too, and for the same reason it is
874        // refused on a Welcome: a probe only ever follows an established
875        // session, which this device's own key package is what starts.
876        frames::verify_control_frame(message, now_unix_secs, frames::UndatedPayload::Refused)?;
877        let sender = message.sender.as_str();
878
879        // Loaded rather than counted. "State is on flash" and "this device can
880        // decrypt" are different claims, and an acknowledgement asserts the
881        // second: a device that answered on the strength of bytes it cannot
882        // load would confirm a session it cannot open one frame of, which is
883        // the failure staying quiet exists to avoid. A state that is present
884        // and unloadable is also not silence, it is a store handing back what
885        // this device did not write, and it propagates for the reason
886        // [`LeafDevice::load_group`] separates the two.
887        let client = self.client()?;
888        let group_id = self.group_id(sender)?;
889        let group = match self.load_group(&client, sender, &group_id) {
890            Ok(group) => group,
891            Err(LeafError::NoSession(_)) => {
892                return Ok(ignored(
893                    "a confirmation probe arrived for a peer this device has no session with",
894                ))
895            }
896            Err(e) => return Err(e),
897        };
898
899        // And it must still be this pair. An acknowledgement is a promise to
900        // use the session rather than a report that bytes exist, and a device
901        // that will refuse to seal into this group has no such promise to
902        // make: the peer would confirm, flush everything it had queued, and
903        // hear nothing further. Refused rather than ignored, because unlike an
904        // absent session this is a state firmware needs to hear about.
905        self.require_still_a_pair(&group, sender)?;
906
907        let mut ack = frames::build(
908            &self.store,
909            &self.identity,
910            &self.app_id,
911            sender,
912            String::from(prefixes::SESSION_CONFIRM_ACK),
913            now_unix_secs,
914            MessagePriority::High,
915        )?;
916        frames::sign_control_frame(&self.identity, &mut ack)?;
917        Ok(Handled {
918            outbound: vec![ack],
919            events: Vec::new(),
920        })
921    }
922
923    /// Discards everything belonging to a session with `peer`.
924    ///
925    /// Called when a peer says it has discarded its own. A device that kept
926    /// the old session would hold one the peer has already thrown away, and
927    /// every later frame from it would decrypt to nothing.
928    ///
929    /// # The prior epochs go too
930    ///
931    /// A session is the group state, the marker, **and** the prior-epoch
932    /// records, and the last of those is the part that is easy to leave
933    /// behind. Each holds an epoch's secrets, so records that outlive the
934    /// session they belong to are key material surviving an erasure the owner
935    /// asked for. They also outlive it under a name the next session answers
936    /// to: a pair's group id is derived from the two addresses, so a device
937    /// that re-pairs with the same peer rebuilds the same id and starts
938    /// writing epochs beside the last session's.
939    fn forget_session(&mut self, peer: &str) -> Result<()> {
940        let group_id = self.group_id(peer)?;
941        let key = crate::adapters::hex(group_id.as_str().as_bytes());
942
943        // The anchor is the middle of the sweep rather than its top. Trimming
944        // keeps only the newest few below it and the slack covers a delete
945        // that failed while it was doing so, but a record can also sit
946        // **above** every anchor: the adapter writes epoch records before the
947        // state entry that names them, so a cut between the two leaves an
948        // epoch on flash that the marker, the high-water record and the
949        // group's own epoch all agree is not there. Sweeping only downward
950        // would delete every record but that one and report success, which is
951        // the same erasure-that-did-not-happen the three anchors below exist
952        // to prevent. A delete of a key that is not there is not an error,
953        // which is what makes a fixed window the right shape rather than an
954        // enumeration this seam cannot offer.
955        //
956        // Three sources, tried in order, because an anchor that is missing or
957        // unreadable cannot bound anything and anchoring at zero would delete
958        // one record, return `Ok`, and leave every other epoch's secrets on
959        // flash: an erasure the owner asked for that reports success and did
960        // not happen. The marker inside the state entry is first because it is
961        // the one written atomically with the state. The separate high-water
962        // record is second and covers the case that one cannot: a state entry
963        // the part hands back as something else. The group's own epoch is
964        // last, and names the same neighbourhood.
965        let anchor = match crate::adapters::state_marker(&self.store, &key) {
966            Some(highest) => Some(highest),
967            None => self.max_epoch(&key)?,
968        };
969        let highest = anchor
970            .or_else(|| self.current_epoch(&group_id))
971            .unwrap_or(0);
972        let floor = highest.saturating_sub(PRIOR_EPOCH_RETENTION + FORGET_EPOCH_SLACK);
973        let ceiling = highest.saturating_add(FORGET_EPOCH_SLACK);
974        for epoch in floor..=ceiling {
975            self.store
976                .delete(KEY_TYPE_GROUP_EPOCH, &format!("{key}:{epoch}"))
977                .map_err(|e| LeafError::Storage(e.to_string()))?;
978        }
979
980        self.store
981            .delete(KEY_TYPE_GROUP_STATE, &key)
982            .map_err(|e| LeafError::Storage(e.to_string()))?;
983        self.store
984            .delete(KEY_TYPE_GROUP_EPOCH, &format!("{key}:max"))
985            .map_err(|e| LeafError::Storage(e.to_string()))?;
986        Ok(())
987    }
988
989    /// The highest epoch id ever written for a group, by storage key.
990    ///
991    /// `None` covers both a marker that was never written and one that does
992    /// not decode. The two are the same answer here, which is safe only
993    /// because the caller treats `None` as "no bound available" and finds
994    /// another anchor, rather than as "no epochs".
995    fn max_epoch(&self, group_key: &str) -> Result<Option<u64>> {
996        let raw = self
997            .store
998            .load(KEY_TYPE_GROUP_EPOCH, &format!("{group_key}:max"))
999            .map_err(|e| LeafError::Storage(e.to_string()))?;
1000        Ok(raw
1001            .and_then(|bytes| <[u8; 8]>::try_from(bytes.as_slice()).ok())
1002            .map(u64::from_be_bytes))
1003    }
1004
1005    /// Forgets a peer's session, its key package and what it advertised,
1006    /// leaving the index.
1007    fn forget_peer(&mut self, peer: &str) -> Result<()> {
1008        // Read before the record holding it is deleted, because that reference
1009        // is the only pointer this device has to the key package minted for
1010        // this peer, and the package is private key material. An init key is
1011        // single use, so one nobody spends is never consumed, and the only
1012        // other thing that reclaims it is four later mints pushing it out of
1013        // the ring. On a device that pairs twice a year that is years of
1014        // holding an init key for a peer the owner asked it to forget.
1015        //
1016        // Best effort, though. A record that will not decode has no reference
1017        // to give, and refusing the whole call for that would leave the owner
1018        // unable to forget a peer at all: the one escape hatch a device has,
1019        // closed by exactly the corruption it exists to recover from. An
1020        // unreclaimed package is the smaller residue, and it is the same
1021        // reading [`LeafDevice::peer_index`] gives an index that does not
1022        // parse.
1023        let minted = self
1024            .peer_record(peer)
1025            .ok()
1026            .and_then(|record| record.key_package_ref);
1027
1028        self.forget_session(peer)?;
1029
1030        // Erased before the record stops naming it, which is the ordering the
1031        // adapter's own eviction uses and for the same reason: a failed erase
1032        // leaves a pointer a retry can still follow, where the other order
1033        // leaves key material that nothing names and nothing reclaims.
1034        if let Some(reference) = minted {
1035            KeyPackageAdapter::new(Arc::clone(&self.store))
1036                .erase(&reference)
1037                .map_err(|e| LeafError::Storage(e.to_string()))?;
1038        }
1039
1040        self.store
1041            .delete(KEY_TYPE_PEER, peer)
1042            .map_err(|e| LeafError::Storage(e.to_string()))?;
1043        Ok(())
1044    }
1045
1046    /// The peers this device holds records for.
1047    ///
1048    /// An index that does not decode is read as empty rather than as an error.
1049    /// It is a bound on storage, not a security claim, and a device that
1050    /// refused to pair because a list of previous peers is unreadable would
1051    /// have turned a recoverable annoyance into a brick.
1052    fn peer_index(&self) -> Result<Vec<String>> {
1053        let Some(raw) = self
1054            .store
1055            .load(KEY_TYPE_PEER_INDEX, "peers")
1056            .map_err(|e| LeafError::Storage(e.to_string()))?
1057        else {
1058            return Ok(Vec::new());
1059        };
1060        Ok(serde_json::from_slice(&raw).unwrap_or_default())
1061    }
1062
1063    fn save_peer_index(&self, index: &[String]) -> Result<()> {
1064        let encoded = serde_json::to_vec(index)
1065            .map_err(|e| LeafError::MalformedFrame(format!("cannot encode peer index: {e}")))?;
1066        self.store
1067            .store(KEY_TYPE_PEER_INDEX, "peers", &encoded)
1068            .map_err(|e| LeafError::Storage(e.to_string()))
1069    }
1070
1071    /// Records `peer` in the index without holding it to [`MAX_PEERS`].
1072    ///
1073    /// The bound exists to stop a stranger spending a device's flash, and
1074    /// neither caller here is a stranger: one is firmware choosing to pair,
1075    /// the other is a Welcome, and a Welcome is only reached by a peer whose
1076    /// recorded key package reference it spends. That reference is written
1077    /// only by a mint, and a mint is reached only through firmware or through
1078    /// [`LeafDevice::admit_peer`], which is where the bound is applied. So
1079    /// every peer arriving here has already been counted or chosen.
1080    ///
1081    /// That is a claim about [`LeafDevice::require_own_key_package`] and would
1082    /// be false without it: a key package travels unencrypted, so before that
1083    /// gate any listener could copy one, build this pair's group around it and
1084    /// arrive here having been counted by nothing.
1085    ///
1086    /// It is the index rather than the bound that has to be complete. A peer
1087    /// missing from it holds a session nothing can audit and
1088    /// [`LeafDevice::unpair`] cannot be pointed at, and a session is exactly
1089    /// what the authorization obligation asks firmware to review, since none
1090    /// of the gates in this crate answers whether the owner meant this peer.
1091    fn index_peer(&mut self, peer: &str) -> Result<()> {
1092        let mut index = self.peer_index()?;
1093        if index.iter().any(|held| held == peer) {
1094            return Ok(());
1095        }
1096        index.push(peer.to_string());
1097        self.save_peer_index(&index)
1098    }
1099
1100    /// Makes room for `peer` in the bounded set, or refuses.
1101    ///
1102    /// A peer already held is admitted for free. A new one at capacity takes
1103    /// the place of a pairing that never completed, which is what a record
1104    /// with no session is; if every slot holds a real session, the answer is
1105    /// [`LeafError::TooManyPeers`] rather than the eviction of somebody the
1106    /// owner actually paired with. That direction is the whole point of having
1107    /// the rule: a stranger who can provoke a record must not be able to
1108    /// provoke the removal of one.
1109    fn admit_peer(&mut self, peer: &str) -> Result<()> {
1110        let mut index = self.peer_index()?;
1111        if index.iter().any(|held| held == peer) {
1112            return Ok(());
1113        }
1114
1115        if index.len() >= MAX_PEERS {
1116            // A store that fails the session check counts as holding one, so a
1117            // read error protects the peer rather than evicting it.
1118            let victim = index
1119                .iter()
1120                .find(|held| !self.has_session(held).unwrap_or(true))
1121                .cloned();
1122            let Some(victim) = victim else {
1123                return Err(LeafError::TooManyPeers);
1124            };
1125            self.forget_peer(&victim)?;
1126            index.retain(|held| held != &victim);
1127        }
1128
1129        index.push(peer.to_string());
1130        self.save_peer_index(&index)
1131    }
1132
1133    /// Requires the group to still be the pair this device agreed to.
1134    ///
1135    /// The Welcome gate is what keeps this device out of a room it never
1136    /// chose, and it runs once. Nothing repeats it afterwards, and a commit is
1137    /// free to add a member without the group id changing, so a device that
1138    /// checked only at the join would follow its peer into a room one member
1139    /// at a time and never see it happen. The never-committing profile makes
1140    /// that entirely the peer's decision, which is exactly why it is worth
1141    /// checking rather than trusting.
1142    ///
1143    /// So the roster is re-read on **every use of the group**, and not only on
1144    /// the commit that can change it: two members, and both of them
1145    /// **derived** rather than read off a credential, since a basic credential
1146    /// is a bare assertion.
1147    ///
1148    /// Checking only at the commit would be checking at the one moment whose
1149    /// answer cannot be kept. A commit is applied and durable by the time
1150    /// there is a roster to read, so the refusal is a returned error and
1151    /// nothing else: it survives no reboot, and a device that came back up
1152    /// would seal its next message into the widened room and call it an
1153    /// ordinary session. Reading the roster where the group is used puts the
1154    /// answer somewhere it cannot be lost, which is the same reason nothing
1155    /// else in this crate caches MLS state.
1156    ///
1157    /// It costs a roster read and two SHA-256 derivations per frame. Both are
1158    /// symmetric work, so the profile's promise that per-message cost is
1159    /// symmetric only still holds.
1160    ///
1161    /// Reported rather than rolled back. A member cannot skip one commit and
1162    /// keep decrypting the next, so the choice is not whether to follow the
1163    /// peer but whether firmware is told, and what it does about it is
1164    /// [`LeafDevice::unpair`] or the peer's own reset.
1165    fn require_still_a_pair(&self, group: &Group<impl MlsConfig>, peer: &str) -> Result<()> {
1166        let roster = group.roster();
1167        let members = roster.members();
1168        if members.len() != 2 {
1169            return Err(LeafError::IdentityBinding(format!(
1170                "a commit left this group holding {} members, and a leaf node's group is a pair",
1171                members.len()
1172            )));
1173        }
1174
1175        let mine = self.identity.address.to_string();
1176        for member in &members {
1177            let credential = member
1178                .signing_identity
1179                .credential
1180                .as_basic()
1181                .ok_or_else(|| {
1182                    LeafError::IdentityBinding(String::from(
1183                        "a group member presents no basic credential, so it names no address",
1184                    ))
1185                })?;
1186            let address = crate::adapters::credential_address(credential)?;
1187            if address != mine && address != peer {
1188                return Err(LeafError::IdentityBinding(format!(
1189                    "a commit left '{address}' in this group, which is neither this device nor '{peer}'"
1190                )));
1191            }
1192            frames::verify_sender_derivation(
1193                address,
1194                member.signing_identity.signature_key.as_bytes(),
1195            )?;
1196        }
1197        Ok(())
1198    }
1199
1200    /// Requires a Welcome to spend the key package minted for its sender.
1201    ///
1202    /// A key package is a bearer token: this device hands one to a peer in a
1203    /// frame addressed to that peer, and a shared radio carries it to everyone
1204    /// else as well. A listener that copies it can build a group whose id is
1205    /// the one this pair would build, sign the Welcome with its own key, and
1206    /// name itself as inviter, so the inviter check and the group check both
1207    /// pass honestly. What it cannot do is present the reference of a package
1208    /// this device minted for **it**.
1209    ///
1210    /// A peer with no recorded reference is refused for the same reason an
1211    /// unparseable identifier is elsewhere in this crate: there is nothing to
1212    /// compare, and "nothing to compare" is the bypass rather than a lenience.
1213    /// It also restores the bound: every peer reaching a session has been
1214    /// through [`LeafDevice::admit_peer`] or was chosen by firmware, which is
1215    /// what lets [`LeafDevice::index_peer`] skip [`MAX_PEERS`].
1216    fn require_own_key_package(&self, peer: &str, welcome: &MlsMessage) -> Result<()> {
1217        let Some(minted) = self.peer_record(peer)?.key_package_ref else {
1218            return Err(LeafError::UnsolicitedWelcome(format!(
1219                "no key package was ever minted for '{peer}', so nothing it sends can spend one"
1220            )));
1221        };
1222
1223        let spends_ours = welcome
1224            .welcome_key_package_references()
1225            .into_iter()
1226            .any(|reference| crate::adapters::hex(reference) == minted);
1227
1228        if !spends_ours {
1229            return Err(LeafError::UnsolicitedWelcome(format!(
1230                "welcome from '{peer}' spends a key package this device did not mint for it"
1231            )));
1232        }
1233
1234        // The reference matches, so this is the peer's own package. Whether it
1235        // is still on flash is a separate question, and it is asked here
1236        // because the answer decides which failure firmware is handed. An init
1237        // key is single use, so a package an earlier join consumed is gone,
1238        // and the ring bounding unspent packages evicts on the fourth later
1239        // mint. Left to the join, either arrives from inside MLS as a Welcome
1240        // that will not decode, which reads as a broken peer and sends a bench
1241        // to the wire when the repair is a fresh package.
1242        if self
1243            .store
1244            .load(KEY_TYPE_KEY_PACKAGE, &minted)
1245            .map_err(|e| LeafError::Storage(e.to_string()))?
1246            .is_none()
1247        {
1248            return Err(LeafError::StaleKeyPackage(format!(
1249                "the key package '{peer}' spends was minted for it and is no longer held"
1250            )));
1251        }
1252        Ok(())
1253    }
1254
1255    /// Requires the MLS member at `index` to derive to `claimed`.
1256    ///
1257    /// The refusal is deliberately the same for a member whose credential is
1258    /// not an address at all: a credential with no derivation to check is not
1259    /// one to wave through, it is the bypass. That is the rule
1260    /// [ADR 0010](https://github.com/Offline-Protocol/offline-protocol-sdk/blob/main/docs/adr/0010-unconditional-leaf-identity-binding.md)
1261    /// makes unconditional on the phone, and a device that skipped it would be
1262    /// the weaker end of a protocol whose whole claim is that both ends are
1263    /// the same.
1264    fn bind_sender_credential(
1265        &self,
1266        group: &Group<impl MlsConfig>,
1267        index: u32,
1268        claimed: &str,
1269    ) -> Result<()> {
1270        let member = group.member_at_index(index).ok_or_else(|| {
1271            LeafError::IdentityBinding(format!("no group member at index {index}"))
1272        })?;
1273
1274        let credential = member
1275            .signing_identity
1276            .credential
1277            .as_basic()
1278            .ok_or_else(|| {
1279                LeafError::IdentityBinding(String::from(
1280                    "group member presents no basic credential, so it names no address",
1281                ))
1282            })?;
1283
1284        let credential_address = crate::adapters::credential_address(credential)?;
1285        if credential_address != claimed {
1286            return Err(LeafError::IdentityBinding(format!(
1287                "authored by group member '{credential_address}', but the frame claims '{claimed}'"
1288            )));
1289        }
1290
1291        // And the credential's claim is itself checked rather than trusted: a
1292        // basic credential is self-asserted, so the address in it means
1293        // something only because it is the hash of the key beside it.
1294        frames::verify_sender_derivation(
1295            credential_address,
1296            member.signing_identity.signature_key.as_bytes(),
1297        )
1298    }
1299
1300    /// Whether this frame names this device as its recipient.
1301    ///
1302    /// The comparison is between parsed addresses rather than between strings,
1303    /// which costs nothing and is the same test every other identity claim in
1304    /// this protocol gets. A recipient that is not an address at all is not
1305    /// this device: this device is named by one, and by exactly one spelling
1306    /// of it, since [`Address`] refuses anything but the canonical rendering.
1307    fn is_addressed_to_me(&self, message: &Message) -> bool {
1308        message
1309            .recipient
1310            .as_str()
1311            .parse::<Address>()
1312            .is_ok_and(|recipient| recipient == self.identity.address)
1313    }
1314
1315    fn client(&self) -> Result<Client<impl MlsConfig>> {
1316        build_client(&self.identity, &self.store)
1317    }
1318
1319    /// Loads the group for a session with `peer`, and says which failure it is.
1320    ///
1321    /// A group that will not load is two different things wearing one error.
1322    /// State that is **absent** is a device that never paired with this peer,
1323    /// or one that unpaired, and re-pairing is the repair. State that is
1324    /// **present and unloadable** is a store handing back bytes this device did
1325    /// not write, which no amount of re-pairing fixes and which a bench needs
1326    /// to be told about rather than sent chasing a pairing problem.
1327    fn load_group<C: MlsConfig>(
1328        &self,
1329        client: &Client<C>,
1330        peer: &str,
1331        group_id: &GroupId,
1332    ) -> Result<Group<C>> {
1333        match client.load_group(group_id.as_str().as_bytes()) {
1334            Ok(group) => Ok(group),
1335            Err(e) if self.state_present(group_id)? => Err(LeafError::Storage(format!(
1336                "group state for {peer} is on flash but does not load: {e:?}"
1337            ))),
1338            Err(_) => Err(LeafError::NoSession(peer.to_string())),
1339        }
1340    }
1341
1342    /// The epoch a group is in, read from its stored state.
1343    ///
1344    /// Best effort by construction: the one caller is the sweep in
1345    /// [`LeafDevice::forget_session`], which needs an anchor when the marker
1346    /// cannot give it one, and a state that will not load leaves nothing to
1347    /// read. A device with neither has nothing above the window to have
1348    /// written.
1349    fn current_epoch(&self, group_id: &GroupId) -> Option<u64> {
1350        let client = self.client().ok()?;
1351        client
1352            .load_group(group_id.as_str().as_bytes())
1353            .ok()
1354            .map(|group| group.current_epoch())
1355    }
1356
1357    fn group_id(&self, peer: &str) -> Result<GroupId> {
1358        Ok(GroupId::for_session(
1359            &self.identity.address.to_string(),
1360            peer,
1361        )?)
1362    }
1363
1364    /// Repeats an acknowledgement for a frame this device already answered.
1365    ///
1366    /// The peer only retransmits because it heard nothing, and the frame it
1367    /// sends again is the one it sent before: the same bytes and the same id,
1368    /// frozen in its outbox. Opening it a second time cannot work, because the
1369    /// ratchet spent that generation on the first copy, so before this the
1370    /// device refused it and stayed quiet and the peer went right on asking.
1371    ///
1372    /// Answering from memory is honest. The claim an acknowledgement makes is
1373    /// that the frame with this id reached its recipient, and it did.
1374    fn repeat_acknowledgement(
1375        &mut self,
1376        message: &Message,
1377        now_unix_secs: u64,
1378    ) -> Result<Option<Handled>> {
1379        if !message.requires_ack {
1380            return Ok(None);
1381        }
1382
1383        let Some(record) = self.known_peer(message.sender.as_str())? else {
1384            return Ok(None);
1385        };
1386        if !record.was_acknowledged(message.id.to_string().as_str()) {
1387            return Ok(None);
1388        }
1389
1390        Ok(Some(Handled {
1391            outbound: alloc::vec![frames::acknowledge(
1392                &self.store,
1393                &self.identity,
1394                &self.app_id,
1395                message,
1396                now_unix_secs,
1397            )?],
1398            events: alloc::vec![LeafEvent::Ignored {
1399                reason: String::from("a frame this device already answered arrived again"),
1400            }],
1401        }))
1402    }
1403
1404    /// Mints the acknowledgement owed for a frame that was accepted, and
1405    /// remembers it before the caller can send it.
1406    ///
1407    /// # Why only a peer this device already knows
1408    ///
1409    /// A record exists for a peer that got through the key package gate, which
1410    /// means a verified signature and an address that derives to the key that
1411    /// made it. Answering anyone else would hand a stranger within radio range
1412    /// two things they do not have today: a way to make the device transmit on
1413    /// demand, and an answer to "is there a node at this address", which on a
1414    /// lock is the question worth asking first. It would also let a flood of
1415    /// forged senders each write a record on a part with a few hundred
1416    /// kilobytes of flash.
1417    ///
1418    /// Nothing is lost by the restriction. The peer that sends frames worth
1419    /// acknowledging is the one this device is paired with, and an unknown
1420    /// sender gets exactly the silence it got before.
1421    ///
1422    /// # Why the record is not the whole gate
1423    ///
1424    /// It answers "has this address ever paired", and the frame in hand names
1425    /// its sender in plaintext, so on its own the record clears anyone who
1426    /// overheard the pair once. The caller asks [`Sender`] first, and only a
1427    /// frame that verified a signature or opened under the pair's key reaches
1428    /// here. The two are one gate: this one says the peer is known, that one
1429    /// says the frame is from it.
1430    fn acknowledge(&mut self, message: &Message, now_unix_secs: u64) -> Result<Option<Message>> {
1431        if !message.requires_ack {
1432            return Ok(None);
1433        }
1434
1435        let peer = message.sender.as_str();
1436        let Some(mut record) = self.known_peer(peer)? else {
1437            return Ok(None);
1438        };
1439
1440        let ack = frames::acknowledge(
1441            &self.store,
1442            &self.identity,
1443            &self.app_id,
1444            message,
1445            now_unix_secs,
1446        )?;
1447
1448        // Durable before it is emitted, which is this profile's third
1449        // obligation. A device that answered and then lost the record would
1450        // meet the retransmission with silence, which is the state this
1451        // whole mechanism exists to leave.
1452        record.remember_acknowledged(message.id.to_string().as_str());
1453        record
1454            .save(&self.store, peer)
1455            .map_err(|e| LeafError::Storage(e.to_string()))?;
1456
1457        Ok(Some(ack))
1458    }
1459
1460    /// The record for a peer this device has paired with, or `None`.
1461    ///
1462    /// Distinct from [`Self::peer_record`], which returns a default for an
1463    /// unknown peer. Here the difference between "known" and "not" is the
1464    /// decision being made, so it must not be flattened away.
1465    fn known_peer(&self, peer: &str) -> Result<Option<PeerRecord>> {
1466        PeerRecord::load(&self.store, peer).map_err(|e| LeafError::Storage(e.to_string()))
1467    }
1468
1469    fn peer_record(&self, peer: &str) -> Result<PeerRecord> {
1470        Ok(PeerRecord::load(&self.store, peer)
1471            .map_err(|e| LeafError::Storage(e.to_string()))?
1472            .unwrap_or_default())
1473    }
1474
1475    /// Whether a session with `peer` exists on flash.
1476    pub fn has_session(&self, peer: &str) -> Result<bool> {
1477        let group_id = self.group_id(peer)?;
1478        self.state_present(&group_id)
1479    }
1480
1481    /// Whether group state for this id is on flash, whatever its condition.
1482    fn state_present(&self, group_id: &GroupId) -> Result<bool> {
1483        Ok(self
1484            .store
1485            .load(
1486                KEY_TYPE_GROUP_STATE,
1487                &crate::adapters::hex(group_id.as_str().as_bytes()),
1488            )
1489            .map_err(|e| LeafError::Storage(e.to_string()))?
1490            .is_some())
1491    }
1492
1493    /// The peers this device holds records for.
1494    ///
1495    /// Firmware's way to audit what a device accumulated. A session proves who
1496    /// a peer is and never that the owner meant to have them, so a device that
1497    /// has been in a hallway for a year may hold peers nobody chose; this is
1498    /// how they are found, and [`LeafDevice::unpair`] is how they are removed.
1499    /// Not every entry holds a session: a pairing that stopped after the first
1500    /// frame leaves a record too, and those are the slots a new peer recycles.
1501    pub fn peers(&self) -> Result<Vec<String>> {
1502        self.peer_index()
1503    }
1504
1505    /// What this device recorded about a peer's capabilities.
1506    pub fn peer_env_versions(&self, peer: &str) -> Result<Vec<u8>> {
1507        Ok(self.peer_record(peer)?.env_versions)
1508    }
1509
1510    /// Forgets a peer: its session, its prior epochs, the key package minted
1511    /// for it, and what it advertised.
1512    ///
1513    /// Exposed because a device that is factory reset or unpaired must be able
1514    /// to forget, and because leaving MLS state behind for a peer the owner
1515    /// removed is the kind of residue that outlives the reason it existed.
1516    ///
1517    /// The key package is part of that and is easy to miss, because the only
1518    /// pointer to it is the reference inside the record this call deletes. It
1519    /// holds an init key, which is single use and therefore never consumed if
1520    /// the pairing it was minted for never completed.
1521    ///
1522    /// Also releases the slot it held, so unpairing is how an owner makes room
1523    /// on a device whose peer table is full.
1524    pub fn unpair(&mut self, peer: &str) -> Result<()> {
1525        self.forget_peer(peer)?;
1526
1527        let mut index = self.peer_index()?;
1528        if index.iter().any(|held| held == peer) {
1529            index.retain(|held| held != peer);
1530            self.save_peer_index(&index)?;
1531        }
1532        Ok(())
1533    }
1534}
1535
1536impl core::fmt::Debug for LeafDevice {
1537    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1538        f.debug_struct("LeafDevice")
1539            .field("address", &self.identity.address.to_string())
1540            .finish_non_exhaustive()
1541    }
1542}
1543
1544/// A frame that produced nothing, and why.
1545///
1546/// Surfaced rather than swallowed so a bench can tell "refused" from "silently
1547/// dropped", which are the two states a pairing failure looks like from
1548/// outside a device with no console.
1549fn ignored(reason: &str) -> Handled {
1550    Handled {
1551        outbound: Vec::new(),
1552        events: vec![LeafEvent::Ignored {
1553            reason: String::from(reason),
1554        }],
1555    }
1556}
1557
1558fn parse_app_id(app_id: &str) -> Result<AppId> {
1559    AppId::new(app_id).map_err(|e| LeafError::MalformedFrame(format!("app id: {e}")))
1560}
1561
1562/// Parses an inbound envelope in every form a conforming sender may emit.
1563///
1564/// Three forms, and none of them is gated on what this device advertised.
1565/// Parsing is unconditional in this protocol: a device that decoded only the
1566/// form it asked for would drop frames from a peer that legitimately believed
1567/// it capable, which is exactly what happens after a partial fleet upgrade.
1568///
1569/// The sniff works because `{` opens JSON and, read as the little-endian
1570/// length prefix the compact codec starts with, is a number far above the
1571/// codec's own string cap.
1572fn parse_envelope(body: &str) -> Result<EncryptedMessage> {
1573    if body.starts_with('{') {
1574        return serde_json::from_str(body)
1575            .map_err(|e| LeafError::MalformedFrame(format!("json envelope: {e}")));
1576    }
1577    let bytes = BASE64
1578        .decode(body)
1579        .map_err(|e| LeafError::MalformedFrame(format!("envelope is not base64: {e}")))?;
1580
1581    if let Ok(envelope) = EncryptedMessage::from_bytes(&bytes) {
1582        return Ok(envelope);
1583    }
1584    serde_json::from_slice(&bytes)
1585        .map_err(|e| LeafError::MalformedFrame(format!("base64 envelope: {e}")))
1586}