wot-network-rpgpie 0.0.2

Transform OpenPGP certificate sets into a wot-network structure
Documentation
use chrono::{DateTime, Utc};
use pgp::{
    packet::{Signature, SignatureType, SubpacketData, SubpacketType, UserId},
    types::SignedUser,
};
use rpgpie::certificate::Checked;
use wot_network::{Certificate, Identity, Regex};

/// Extract a list of `Regex`es from the subpackets of a `Signature`
pub(crate) fn regexes_from(sig: &Signature) -> Vec<Regex> {
    let Some(config) = sig.config() else {
        return vec![];
    };

    config
        .hashed_subpackets
        .iter()
        .filter(|sp| sp.typ() == SubpacketType::RegularExpression)
        .map(|sp| &sp.data)
        .map(|d| {
            let SubpacketData::RegularExpression(regex) = &d else {
                unreachable!()
            };
            regex
        })
        .filter_map(|r| String::from_utf8(r.to_vec()).ok())
        .filter_map(|mut s| {
            if s.ends_with(char::from(0)) {
                // Regex subpacket data has a trailing zero byte - we drop it
                s.truncate(s.len() - 1);
                Some(s)
            } else {
                // We treat "no trailing zero byte" as an invalid regex subpacket and ignore it
                None
            }
        })
        .map(Regex::new)
        .collect()
}

/// Consider a user id valid if no (first-party) revocation signature exists that
/// predates the reference time.
///
/// TODO: Use rpgpie to determine validity of user ids! (also handle expiration!)
pub(crate) fn user_id_valid(user: &SignedUser, reference_time: &DateTime<Utc>) -> bool {
    !user
        .signatures
        .iter()
        .filter(|s| match s.created() {
            None => false,
            Some(created) => created <= reference_time,
        })
        .any(|s| s.typ() == Some(SignatureType::CertRevocation))
}

/// Pick the latest signature.
///
/// Assumes that `sigs` is already filtered for temporal validity.
///
/// TODO: Upstream to rpgpie?
pub(crate) fn pick_latest<'a>(sigs: &[&'a Signature]) -> &'a Signature {
    let mut latest = *sigs.first().unwrap();
    for s in sigs {
        if s.created() > latest.created() {
            latest = s;
        }
    }

    latest
}

/// Transform `Checked` into a `Certificate` for use in `wot-network`
pub(crate) fn to_certificate(checked: &Checked) -> Certificate {
    let fp = checked.fingerprint();
    let hex_fp = hex::encode(fp.as_bytes());
    hex_fp.into()
}

/// Transform `UserId` into an `Identity` for use in `wot-network`
pub(crate) fn to_identity(id: &UserId) -> Option<Identity> {
    String::from_utf8(id.id().to_vec()).ok().map(Into::into)
}