//! `transmission`, `reconciliation` and `cache-transmission` (jwp-receipts §2.4.14, §2.4.15,
//! §2.4.18; `extensions/bearer-receipts.md` §4, §5; `extensions/cache-authority.md` §5): a
//! publisher's declaration of the bearer a group was sent over, a sink's later statement of the
//! objects of a group it received, and a node's declaration of the objects of a group another key
//! signed that it sent again, from what it held, over a bearer instance of its own.
//!
//! This file holds steps 2 and 4 of jwp-receipts §3.1 for the three classes — their bodies'
//! members and types, bearer-receipts §4.2 and §5.2 and cache-authority §5.2 — and step 6, their
//! checks against the group receipt (§4.3) and the object receipts (§5.3; cache-authority §5.3).
//! A reconciliation and a cache-transmission claim share one bitmap rule and one root check. The
//! verdict a verifier derives for a group from them is `crate::bearer_receipts`'s, and with the
//! caches' instances `crate::cache_authority`'s.

use super::*;
use crate::jwp_delivery::{root as merkle_root, GroupReceipt, ObjectReceipt};

/// A bearer's class (bearer-receipts §2).
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Bearer {
    /// Each sink is reached over a path that can carry its statements back.
    Bidirectional,
    /// One transmission reaches every sink that joined it; no per-sink path on the bearer.
    Multicast,
    /// One transmission, and no path back from any sink.
    Unidirectional,
}

impl Bearer {
    /// cbindgen:ignore
    pub const ALL: [Bearer; 3] = [Bearer::Bidirectional, Bearer::Multicast, Bearer::Unidirectional];

    pub fn label(self) -> &'static str {
        match self {
            Bearer::Bidirectional => "bidirectional",
            Bearer::Multicast => "multicast",
            Bearer::Unidirectional => "unidirectional",
        }
    }

    pub fn from_label(s: &str) -> Option<Bearer> {
        Bearer::ALL.into_iter().find(|b| b.label() == s)
    }
}

/// What a transmission claim attests, in-band (bearer-receipts §4.2 rule 1).
pub const ATTESTS_TRANSMITTED: &str = "transmitted";
/// What a reconciliation attests, in-band (bearer-receipts §5.2 rule 1).
pub const ATTESTS_RECEIVED: &str = "received";

/// A transmission body's members (bearer-receipts §4.1), in the order step 2 reads them.
const TRANSMISSION_MEMBERS: &[&str] = &["bearer", "bearer_id", "energy", "attests"];
/// A reconciliation body's members (bearer-receipts §5.1), in the order step 2 reads them.
const RECONCILIATION_MEMBERS: &[&str] = &["bearer", "bearer_id", "leaf_count", "received", "received_root", "attests"];
/// A cache-transmission body's members (cache-authority §5.1), in the order step 2 reads them.
const CACHE_TRANSMISSION_MEMBERS: &[&str] = &["bearer", "bearer_id", "leaf_count", "sent", "sent_root", "energy", "attests"];
/// The members of a transmission's `energy`, as jwp-receipts §2.4.12 writes a session's.
const ENERGY_MEMBERS: &[&str] =
    &["joules_micro", "energy_provenance", "energy_uncertainty", "energy_counter_filtering", "energy_calibration_ref"];

/// `transmission` (bearer-receipts §4): the publisher's declaration that it sent the group
/// its subject names over one bearer instance. Its energy figure is written as a session's
/// ([`SessionEnergy`]).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TransmissionClaim {
    pub bearer: Bearer,
    /// The bearer instance, an absolute URI compared byte for byte.
    pub bearer_id: String,
    /// The energy the bearer spent transmitting the group on this instance; `None` when it
    /// was not metered.
    pub energy: Option<SessionEnergy>,
    /// MUST be `"transmitted"`.
    pub attests: String,
}

/// `cache-transmission` (cache-authority §5): a node's declaration that it sent, from what it
/// held, the objects of the group its subject names whose bits are set, over a `multicast` or
/// `unidirectional` bearer instance of its own. Signed by the node's key, never the group's.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CacheTransmissionClaim {
    pub bearer: Bearer,
    /// The node's own instance, an absolute URI compared byte for byte.
    pub bearer_id: String,
    /// The group's leaf count.
    pub leaf_count: u64,
    /// ⌈leaf_count / 8⌉ bytes, object *i* at bit `0x80 >> (i % 8)` of byte `i / 8`.
    pub sent: Vec<u8>,
    /// The jwp-receipts §2.2 root over the sent objects' leaves, in object order.
    pub sent_root: [u8; 32],
    /// What the instance spent sending these objects; `None` when it was not metered.
    pub energy: Option<SessionEnergy>,
    /// MUST be `"transmitted"`.
    pub attests: String,
}

/// `reconciliation` (bearer-receipts §5): a sink's statement of the objects of the group its
/// subject names that it received.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ReconciliationClaim {
    pub bearer: Bearer,
    pub bearer_id: String,
    /// The group's leaf count.
    pub leaf_count: u64,
    /// ⌈leaf_count / 8⌉ bytes, object *i* at bit `0x80 >> (i % 8)` of byte `i / 8`.
    pub received: Vec<u8>,
    /// The jwp-receipts §2.2 root over the received objects' leaves, in object order.
    pub received_root: [u8; 32],
    /// MUST be `"received"`.
    pub attests: String,
}

fn energy_value(e: &Option<SessionEnergy>) -> Value {
    e.as_ref().map_or(Value::Null, |e| {
        let mut o = Map::new();
        o.insert("joules_micro".into(), e.joules_micro.into());
        o.insert("energy_provenance".into(), e.energy_provenance.clone().into());
        o.insert("energy_uncertainty".into(), uncertainty_value(e.energy_uncertainty));
        o.insert("energy_counter_filtering".into(), e.energy_counter_filtering.clone().map_or(Value::Null, Value::String));
        o.insert("energy_calibration_ref".into(), e.energy_calibration_ref.clone().map_or(Value::Null, Value::String));
        Value::Object(o)
    })
}

fn get_bearer(m: &Map<String, Value>) -> Result<Bearer, Reason> {
    Bearer::from_label(&get_str(m, "bearer")?).ok_or(Reason::FieldType)
}

fn get_bearer_id(m: &Map<String, Value>) -> Result<String, Reason> {
    let s = get_str(m, "bearer_id")?;
    uri_ok(&s)?;
    Ok(s)
}

/// The number of bytes a bitmap of `leaf_count` objects holds, where a claim can hold one.
fn bitmap_len(leaf_count: u64) -> u64 {
    leaf_count.div_ceil(8)
}

/// Whether bit `i` of a bitmap is set.
fn bit_set(bits: &[u8], i: u64) -> bool {
    bits.get((i / 8) as usize).is_some_and(|b| b & (0x80 >> (i % 8)) != 0)
}

/// The positions set in a bitmap, in order.
fn bits_set(bits: &[u8]) -> Vec<u64> {
    (0..bits.len() as u64 * 8).filter(|&i| bit_set(bits, i)).collect()
}

/// A bitmap's one form (bearer-receipts §5.2 rule 2; cache-authority §5.2 rule 3): at least one
/// object, exactly ⌈`leaf_count` / 8⌉ bytes, no bit at or past the leaf count, and a bit set.
fn bitmap_well_formed(leaf_count: u64, bits: &[u8]) -> bool {
    if leaf_count == 0 || bits.len() as u64 != bitmap_len(leaf_count) {
        return false;
    }
    // No bit at or past the leaf count: the last byte's unused low bits are clear.
    let used = (leaf_count % 8) as u32;
    if used != 0 && bits.last().is_some_and(|b| b & (0xffu8 >> used) != 0) {
        return false;
    }
    bits.iter().any(|b| *b != 0)
}

/// Why a bitmap's root is not the claim's.
enum RootFault {
    /// A set position whose object receipt the verifier does not hold.
    NotHeld,
    /// The root over the leaves is another.
    Mismatch,
}

/// The root check of bearer-receipts §5.3 and cache-authority §5.3 step 4: with every bit set,
/// the group's own root; otherwise the root over the leaves at the set positions, each of whose
/// object receipts the verifier holds and verifies by inclusion.
fn root_holds(group: &GroupReceipt, objects: &[&ObjectReceipt], bits: &[u8], leaf_count: u64, root: &[u8; 32]) -> Result<(), RootFault> {
    let whole = (0..leaf_count).all(|i| bit_set(bits, i));
    let computed = if whole {
        group.root_hash
    } else {
        let mut leaves = Vec::new();
        for i in bits_set(bits) {
            let held = objects.iter().find(|o| o.object_id == i && o.verify_inclusion(group).is_ok());
            match held {
                Some(o) => leaves.push(o.leaf()),
                None => return Err(RootFault::NotHeld),
            }
        }
        merkle_root(&leaves)
    };
    if computed != *root {
        return Err(RootFault::Mismatch);
    }
    Ok(())
}

impl TransmissionClaim {
    pub(super) fn to_value(&self) -> Value {
        let mut m = Map::new();
        m.insert("bearer".into(), self.bearer.label().into());
        m.insert("bearer_id".into(), self.bearer_id.clone().into());
        m.insert("energy".into(), energy_value(&self.energy));
        m.insert("attests".into(), self.attests.clone().into());
        Value::Object(m)
    }

    /// Step 2: the members — missing, then unknown — then each member's type in the
    /// table's order, `energy`'s members and types where the table reaches it.
    pub(super) fn read(m: &Map<String, Value>) -> Result<TransmissionClaim, Reason> {
        members(m, TRANSMISSION_MEMBERS)?;
        let bearer = get_bearer(m)?;
        let bearer_id = get_bearer_id(m)?;
        let energy = read_energy(&m["energy"])?;
        let attests = get_str(m, "attests")?;
        Ok(TransmissionClaim { bearer, bearer_id, energy, attests })
    }

    /// Step 2's checks for a body built field by field.
    pub(super) fn check_values(&self) -> Result<(), Reason> {
        uri_ok(&self.bearer_id)?;
        energy_ints(&self.energy)
    }

    /// Step 4: bearer-receipts §4.2, in its order.
    pub(super) fn rules(&self) -> Result<(), Reason> {
        if self.attests != ATTESTS_TRANSMITTED {
            return Err(Reason::TransmissionNotTransmitted);
        }
        energy_rules(&self.energy)
    }
}

/// Step 2: an `energy` member, `null` or an object with a session's energy members and types.
fn read_energy(v: &Value) -> Result<Option<SessionEnergy>, Reason> {
    match v {
        Value::Null => Ok(None),
        Value::Object(o) => {
            members(o, ENERGY_MEMBERS)?;
            Ok(Some(SessionEnergy {
                joules_micro: get_int(o, "joules_micro")?,
                energy_provenance: get_str(o, "energy_provenance")?,
                energy_uncertainty: get_uncertainty(o)?,
                energy_counter_filtering: get_opt_str(o, "energy_counter_filtering")?,
                energy_calibration_ref: get_opt_str(o, "energy_calibration_ref")?,
            }))
        }
        _ => Err(Reason::FieldType),
    }
}

/// Step 2's integer checks of an energy figure built field by field.
fn energy_ints(e: &Option<SessionEnergy>) -> Result<(), Reason> {
    if let Some(e) = e {
        int_ok(e.joules_micro)?;
        if let Some(u) = e.energy_uncertainty {
            int_ok(u.relative_ppm)?;
            int_ok(u.window_us)?;
        }
    }
    Ok(())
}

/// Step 4: bearer-receipts §4.2 rule 2 on an energy figure that is not `null`.
fn energy_rules(e: &Option<SessionEnergy>) -> Result<(), Reason> {
    if let Some(e) = e {
        if e.joules_micro == 0 {
            return Err(Reason::EnergyZeroFigure);
        }
        e.acquisition_class()?;
    }
    Ok(())
}

impl ReconciliationClaim {
    /// The bitmap of `leaf_count` objects with the objects `indices` set.
    pub fn bitmap(leaf_count: u64, indices: &[u64]) -> Vec<u8> {
        let mut b = vec![0u8; bitmap_len(leaf_count) as usize];
        for &i in indices {
            b[(i / 8) as usize] |= 0x80 >> (i % 8);
        }
        b
    }

    /// Whether object `i` is set.
    pub fn has(&self, i: u64) -> bool {
        bit_set(&self.received, i)
    }

    /// The objects set, in order. Only for a body that passed step 4, whose bits lie below
    /// its leaf count.
    pub fn indices(&self) -> Vec<u64> {
        bits_set(&self.received)
    }

    /// Whether every object of the group is set.
    pub fn whole(&self) -> bool {
        (0..self.leaf_count).all(|i| self.has(i))
    }

    pub(super) fn to_value(&self) -> Value {
        let mut m = Map::new();
        m.insert("bearer".into(), self.bearer.label().into());
        m.insert("bearer_id".into(), self.bearer_id.clone().into());
        m.insert("leaf_count".into(), self.leaf_count.into());
        m.insert("received".into(), hex(&self.received).into());
        m.insert("received_root".into(), hex(&self.received_root).into());
        m.insert("attests".into(), self.attests.clone().into());
        Value::Object(m)
    }

    /// Step 2: the members, then each member's type in the table's order. `received` is a
    /// byte string of any length here; its length is a class rule (step 4).
    pub(super) fn read(m: &Map<String, Value>) -> Result<ReconciliationClaim, Reason> {
        members(m, RECONCILIATION_MEMBERS)?;
        Ok(ReconciliationClaim {
            bearer: get_bearer(m)?,
            bearer_id: get_bearer_id(m)?,
            leaf_count: get_int(m, "leaf_count")?,
            received: get_bytes(m, "received")?,
            received_root: get_hash(m, "received_root")?,
            attests: get_str(m, "attests")?,
        })
    }

    pub(super) fn check_values(&self) -> Result<(), Reason> {
        uri_ok(&self.bearer_id)?;
        int_ok(self.leaf_count)
    }

    /// Step 4: bearer-receipts §5.2, in its order.
    pub(super) fn rules(&self) -> Result<(), Reason> {
        if self.attests != ATTESTS_RECEIVED {
            return Err(Reason::ReconciliationNotReceived);
        }
        if !bitmap_well_formed(self.leaf_count, &self.received) {
            return Err(Reason::ReconciliationBitmapMalformed);
        }
        Ok(())
    }
}

impl CacheTransmissionClaim {
    /// Whether object `i` is set.
    pub fn has(&self, i: u64) -> bool {
        bit_set(&self.sent, i)
    }

    /// The objects set, in order. Only for a body that passed step 4, whose bits lie below
    /// its leaf count.
    pub fn indices(&self) -> Vec<u64> {
        bits_set(&self.sent)
    }

    pub(super) fn to_value(&self) -> Value {
        let mut m = Map::new();
        m.insert("bearer".into(), self.bearer.label().into());
        m.insert("bearer_id".into(), self.bearer_id.clone().into());
        m.insert("leaf_count".into(), self.leaf_count.into());
        m.insert("sent".into(), hex(&self.sent).into());
        m.insert("sent_root".into(), hex(&self.sent_root).into());
        m.insert("energy".into(), energy_value(&self.energy));
        m.insert("attests".into(), self.attests.clone().into());
        Value::Object(m)
    }

    /// Step 2: the members, then each member's type in the table's order, `energy`'s members
    /// and types where the table reaches it. `sent` is a byte string of any length here; its
    /// length is a class rule (step 4).
    pub(super) fn read(m: &Map<String, Value>) -> Result<CacheTransmissionClaim, Reason> {
        members(m, CACHE_TRANSMISSION_MEMBERS)?;
        let bearer = get_bearer(m)?;
        let bearer_id = get_bearer_id(m)?;
        let leaf_count = get_int(m, "leaf_count")?;
        let sent = get_bytes(m, "sent")?;
        let sent_root = get_hash(m, "sent_root")?;
        let energy = read_energy(&m["energy"])?;
        let attests = get_str(m, "attests")?;
        Ok(CacheTransmissionClaim { bearer, bearer_id, leaf_count, sent, sent_root, energy, attests })
    }

    pub(super) fn check_values(&self) -> Result<(), Reason> {
        uri_ok(&self.bearer_id)?;
        int_ok(self.leaf_count)?;
        energy_ints(&self.energy)
    }

    /// Step 4: cache-authority §5.2, in its order.
    pub(super) fn rules(&self) -> Result<(), Reason> {
        if self.attests != ATTESTS_TRANSMITTED {
            return Err(Reason::CacheTransmissionNotTransmitted);
        }
        if self.bearer == Bearer::Bidirectional {
            return Err(Reason::CacheTransmissionOnBidirectional);
        }
        if !bitmap_well_formed(self.leaf_count, &self.sent) {
            return Err(Reason::CacheTransmissionBitmapMalformed);
        }
        energy_rules(&self.energy)
    }
}

impl ClaimReceipt {
    /// Step 6 (bearer-receipts §4.3): a `transmission` claim against the group receipt its
    /// subject names. In order: the claim is a transmission (`context_mismatch`); the group
    /// passes jwp-receipts §3 steps 1 and 2 (`context_invalid`); its link hash is the claim's
    /// subject (`transmission_subject_mismatch`); its key signed the claim
    /// (`transmission_signer_mismatch`).
    pub fn check_transmission_against(&self, group: &GroupReceipt) -> Result<(), Rejection> {
        let ClaimBody::Transmission(_) = &self.body else {
            return Err(Rejection::at(6, Reason::ContextMismatch));
        };
        group.verify().map_err(|_| Rejection::at(6, Reason::ContextInvalid))?;
        if group.link_hash() != self.subject {
            return Err(Rejection::at(6, Reason::TransmissionSubjectMismatch));
        }
        if group.signer_pubkey != self.signer_pubkey {
            return Err(Rejection::at(6, Reason::TransmissionSignerMismatch));
        }
        Ok(())
    }

    /// Step 6 (bearer-receipts §5.3): a `reconciliation` claim against the group receipt its
    /// subject names and the object receipts the verifier holds under it, of which only
    /// those that pass jwp-receipts §3 step 5 against the group are used. In order: the claim
    /// is a reconciliation (`context_mismatch`); the group passes §3 steps 1 and 2
    /// (`context_invalid`); its link hash is the subject
    /// (`reconciliation_subject_mismatch`); its key did not sign the claim
    /// (`reconciliation_by_publisher`); its leaf count is the claim's
    /// (`reconciliation_leaf_count_mismatch`); and the root — the group's own where every
    /// object is set, else the root over the leaves of the objects set, each of whose
    /// receipts the verifier holds (`reconciliation_objects_not_held`) — is the claim's
    /// (`reconciliation_root_mismatch`).
    pub fn check_reconciliation_against(&self, group: &GroupReceipt, objects: &[&ObjectReceipt]) -> Result<(), Rejection> {
        let ClaimBody::Reconciliation(r) = &self.body else {
            return Err(Rejection::at(6, Reason::ContextMismatch));
        };
        let at = |reason| Err(Rejection::at(6, reason));
        if group.verify().is_err() {
            return at(Reason::ContextInvalid);
        }
        if group.link_hash() != self.subject {
            return at(Reason::ReconciliationSubjectMismatch);
        }
        if group.signer_pubkey == self.signer_pubkey {
            return at(Reason::ReconciliationByPublisher);
        }
        if group.leaf_count != r.leaf_count {
            return at(Reason::ReconciliationLeafCountMismatch);
        }
        match root_holds(group, objects, &r.received, r.leaf_count, &r.received_root) {
            Ok(()) => Ok(()),
            Err(RootFault::NotHeld) => at(Reason::ReconciliationObjectsNotHeld),
            Err(RootFault::Mismatch) => at(Reason::ReconciliationRootMismatch),
        }
    }

    /// Step 6 (cache-authority §5.3): a `cache-transmission` claim against the group receipt its
    /// subject names and the object receipts the verifier holds under it, of which only those
    /// that pass jwp-receipts §3 step 5 against the group are used. In order: the claim is a
    /// cache-transmission (`context_mismatch`); the group passes §3 steps 1 and 2
    /// (`context_invalid`); its link hash is the subject (`cache_transmission_subject_mismatch`);
    /// its key did not sign the claim (`cache_transmission_by_publisher`); its leaf count is the
    /// claim's (`cache_transmission_leaf_count_mismatch`); and the root — the group's own where
    /// every object is set, else the root over the leaves of the objects set, each of whose
    /// receipts the verifier holds (`cache_transmission_objects_not_held`) — is the claim's
    /// (`cache_transmission_root_mismatch`).
    pub fn check_cache_transmission_against(&self, group: &GroupReceipt, objects: &[&ObjectReceipt]) -> Result<(), Rejection> {
        let ClaimBody::CacheTransmission(t) = &self.body else {
            return Err(Rejection::at(6, Reason::ContextMismatch));
        };
        let at = |reason| Err(Rejection::at(6, reason));
        if group.verify().is_err() {
            return at(Reason::ContextInvalid);
        }
        if group.link_hash() != self.subject {
            return at(Reason::CacheTransmissionSubjectMismatch);
        }
        if group.signer_pubkey == self.signer_pubkey {
            return at(Reason::CacheTransmissionByPublisher);
        }
        if group.leaf_count != t.leaf_count {
            return at(Reason::CacheTransmissionLeafCountMismatch);
        }
        match root_holds(group, objects, &t.sent, t.leaf_count, &t.sent_root) {
            Ok(()) => Ok(()),
            Err(RootFault::NotHeld) => at(Reason::CacheTransmissionObjectsNotHeld),
            Err(RootFault::Mismatch) => at(Reason::CacheTransmissionRootMismatch),
        }
    }

    /// The bearer body of a transmission claim, if this is one.
    pub fn transmission(&self) -> Option<&TransmissionClaim> {
        match &self.body {
            ClaimBody::Transmission(t) => Some(t),
            _ => None,
        }
    }

    /// The body of a reconciliation claim, if this is one.
    pub fn reconciliation(&self) -> Option<&ReconciliationClaim> {
        match &self.body {
            ClaimBody::Reconciliation(r) => Some(r),
            _ => None,
        }
    }

    /// The body of a cache-transmission claim, if this is one.
    pub fn cache_transmission(&self) -> Option<&CacheTransmissionClaim> {
        match &self.body {
            ClaimBody::CacheTransmission(t) => Some(t),
            _ => None,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn rec(leaf_count: u64, received: Vec<u8>) -> ReconciliationClaim {
        ReconciliationClaim {
            bearer: Bearer::Multicast,
            bearer_id: "urn:example:bearer".into(),
            leaf_count,
            received,
            received_root: [0; 32],
            attests: ATTESTS_RECEIVED.into(),
        }
    }

    #[test]
    fn the_bitmap_reads_in_object_order() {
        assert_eq!(ReconciliationClaim::bitmap(10, &[0, 3, 9]), vec![0b1001_0000, 0b0100_0000]);
        let r = rec(10, ReconciliationClaim::bitmap(10, &[0, 3, 9]));
        assert_eq!(r.indices(), vec![0, 3, 9]);
        assert!(!r.whole());
        assert!(rec(3, vec![0xe0]).whole());
    }

    #[test]
    fn the_bitmap_rules_refuse_each_defect() {
        let bad = Err(Reason::ReconciliationBitmapMalformed);
        assert_eq!(rec(10, vec![0x80, 0x40]).rules(), Ok(()));
        assert_eq!(rec(0, vec![]).rules(), bad, "no object");
        assert_eq!(rec(10, vec![0x80]).rules(), bad, "too short");
        assert_eq!(rec(10, vec![0x80, 0x00, 0x00]).rules(), bad, "too long");
        assert_eq!(rec(10, vec![0x80, 0x20]).rules(), bad, "a bit past the leaf count");
        assert_eq!(rec(10, vec![0x00, 0x00]).rules(), bad, "nothing received");
        assert_eq!(rec(8, vec![0x01]).rules(), Ok(()), "every bit of a full byte is an object");
        let mut r = rec(10, vec![0x80, 0x40]);
        r.attests = "rendered".into();
        assert_eq!(r.rules(), Err(Reason::ReconciliationNotReceived));
    }

    fn cache(bearer: Bearer, leaf_count: u64, sent: Vec<u8>) -> CacheTransmissionClaim {
        CacheTransmissionClaim {
            bearer,
            bearer_id: "urn:example:bearer".into(),
            leaf_count,
            sent,
            sent_root: [0; 32],
            energy: None,
            attests: ATTESTS_TRANSMITTED.into(),
        }
    }

    #[test]
    fn a_cache_transmission_is_held_to_its_rules_in_order() {
        assert_eq!(cache(Bearer::Multicast, 4, vec![0x50]).rules(), Ok(()));
        assert_eq!(cache(Bearer::Multicast, 4, vec![0x50]).indices(), vec![1, 3]);
        assert_eq!(cache(Bearer::Unidirectional, 9, vec![0x00, 0x80]).rules(), Ok(()), "the last of nine");
        let bad = Err(Reason::CacheTransmissionBitmapMalformed);
        assert_eq!(cache(Bearer::Multicast, 0, vec![]).rules(), bad, "no object");
        assert_eq!(cache(Bearer::Multicast, 9, vec![0x80]).rules(), bad, "too short");
        assert_eq!(cache(Bearer::Multicast, 9, vec![0x00, 0x40]).rules(), bad, "a bit past the leaf count");
        assert_eq!(cache(Bearer::Multicast, 4, vec![0x00]).rules(), bad, "nothing sent");
        let mut c = cache(Bearer::Bidirectional, 0, vec![]);
        assert_eq!(c.rules(), Err(Reason::CacheTransmissionOnBidirectional), "the bearer before the bitmap");
        c.attests = "served".into();
        assert_eq!(c.rules(), Err(Reason::CacheTransmissionNotTransmitted), "attests first");
        let mut e = cache(Bearer::Multicast, 4, vec![0x50]);
        e.energy = Some(SessionEnergy::new(0, &crate::energy_class::EnergyProvenance::Estimator));
        assert_eq!(e.rules(), Err(Reason::EnergyZeroFigure));
    }

    #[test]
    fn bearers_read_their_labels() {
        for b in Bearer::ALL {
            assert_eq!(Bearer::from_label(b.label()), Some(b));
        }
        assert_eq!(Bearer::from_label("broadcast"), None);
    }
}
