//! Where a parameter set is fetched, and the rules around holding one
//! (extensions/prior-carriage.md §15 to §19; extensions/http-delivery.md §8.5, §8.7, §8.8,
//! §12.5).
//!
//! A pin names a set by its SHA-256 and nothing else (SPEC §3.1). This module is what a sink
//! does to obtain a set it lacks: the set roots it asks, in their order, the location hints a
//! MoQ catalog entry or a WAI multivariant playlist states, and the checks that hold a
//! response's bytes only when their digest is the pin's. It opens no connection: the caller
//! supplies each response.
//!
//! - [`locations`]: set roots, hints, a set's URL, and a fetch over supplied responses.
//! - [`dictionary`]: a set as an RFC 9842 dictionary: the fields' values, the checks of a
//!   coded response, and `dcz` decoding and coding.
//! - [`amortise`]: when a set is worth holding, charged per sink, with the generations a sink
//!   holds.
//! - [`usage`], [`sealed`], [`privacy`]: a set's usage statement, a set sealed under a key its
//!   origin releases, and what a sink discloses of the sets it holds.
//! - [`tree`]: the chunk-tree form, a set fetched by range from several peers and verified per
//!   piece.
//!
//! The conformance corpus is `live-conformance/prior-carriage/` ([`corpus`]), and its second
//! implementation `live-conformance/prior_carriage_verify.py`.

pub mod amortise;
pub mod corpus;
pub mod dictionary;
pub mod locations;
pub mod privacy;
pub mod sealed;
pub mod tree;
pub mod usage;

/// The reasons http-delivery §13 lists for §8.7 and §8.8, in the order its rows give them.
pub const HTTP_CODES: &[&str] = &[
    "set_location_malformed",
    "set_location_unavailable",
    "set_too_large",
    "set_location_mismatch",
    "dictionary_unrequested",
    "dictionary_coding_malformed",
    "dictionary_mismatch",
    "dictionary_window_too_large",
];

/// The reasons prior-carriage §20 lists, in its order.
pub const CODES: &[&str] = &[
    amortise::PLAN_MALFORMED,
    usage::USAGE_MALFORMED,
    sealed::SEALED_MALFORMED,
    sealed::SEALED_INNER_FORM,
    sealed::SET_KEY_ABSENT,
    sealed::SET_KEY_MISMATCH,
    tree::TREE_HEAD_MALFORMED,
    tree::TREE_INNER_FORM,
    tree::TREE_ROOT_MISMATCH,
    tree::TREE_PIECE_MISMATCH,
    tree::TREE_DIGEST_MISMATCH,
];

/// 64 lowercase hexadecimal digits.
pub(crate) fn hex(b: &[u8]) -> String {
    b.iter().map(|x| format!("{x:02x}")).collect()
}

/// A digest written as 64 lowercase hexadecimal digits, or `None`.
pub(crate) fn digest_of_hex(s: &str) -> Option<[u8; 32]> {
    if s.len() != 64 || !s.bytes().all(|c| c.is_ascii_digit() || (b'a'..=b'f').contains(&c)) {
        return None;
    }
    let mut d = [0u8; 32];
    for (i, o) in d.iter_mut().enumerate() {
        *o = u8::from_str_radix(&s[2 * i..2 * i + 2], 16).ok()?;
    }
    Some(d)
}
