cbor2 1.1.3

Full-featured CBOR (RFC 8949) for serde: async item I/O, canonical encoding, no_std, Value/RawValue, simple values, tags, COSE keys, well-formedness checks and diagnostics.
Documentation
use alloc::{format, vec, vec::Vec};

use crate::de::Error;
use crate::value::Value;

use super::types::{Arg, Atom};

#[cfg(feature = "cdn")]
pub(super) fn hash_args(
    args: Vec<Arg>,
    offset: usize,
) -> Result<(Vec<u8>, Option<HashAlg>), Error> {
    if !(1..=2).contains(&args.len()) {
        return Err(Error::semantic(
            offset,
            "hash expects one string and an optional algorithm",
        ));
    }

    let mut iter = args.into_iter();
    let data = match iter.next().unwrap().value {
        Value::Text(text) => text.into_bytes(),
        Value::Bytes(bytes) => bytes,
        _ => return Err(Error::semantic(offset, "hash input must be a string")),
    };

    let alg = match iter.next() {
        Some(arg) => Some(hash_alg_from_value(arg.value, offset)?),
        None => None,
    };
    Ok((data, alg))
}

#[cfg(feature = "cdn")]
#[derive(Clone, Copy)]
pub(super) enum HashAlg {
    Sha256,
    Sha256_64,
    Sha512_256,
    Sha384,
    Sha512,
    Shake128,
    Shake256,
}

#[cfg(feature = "cdn")]
fn hash_alg_from_value(value: Value, offset: usize) -> Result<HashAlg, Error> {
    match value {
        Value::Integer(n) => hash_alg_from_id(i128::from(n), offset),
        Value::Text(name) => hash_alg_from_name(&name, offset),
        _ => Err(Error::semantic(
            offset,
            "hash algorithm must be an integer or text string",
        )),
    }
}

#[cfg(feature = "cdn")]
fn hash_alg_from_id(id: i128, offset: usize) -> Result<HashAlg, Error> {
    match id {
        -15 => Ok(HashAlg::Sha256_64),
        -16 => Ok(HashAlg::Sha256),
        -17 => Ok(HashAlg::Sha512_256),
        -18 => Ok(HashAlg::Shake128),
        -43 => Ok(HashAlg::Sha384),
        -44 => Ok(HashAlg::Sha512),
        -45 => Ok(HashAlg::Shake256),
        _ => Err(Error::semantic(
            offset,
            format!("unsupported COSE hash algorithm `{id}`"),
        )),
    }
}

#[cfg(feature = "cdn")]
fn hash_alg_from_name(name: &str, offset: usize) -> Result<HashAlg, Error> {
    match name {
        "SHA-256" => Ok(HashAlg::Sha256),
        "SHA-256/64" => Ok(HashAlg::Sha256_64),
        "SHA-512/256" => Ok(HashAlg::Sha512_256),
        "SHA-384" => Ok(HashAlg::Sha384),
        "SHA-512" => Ok(HashAlg::Sha512),
        "SHAKE128" | "SHAKE-128" => Ok(HashAlg::Shake128),
        "SHAKE256" | "SHAKE-256" => Ok(HashAlg::Shake256),
        _ => Err(Error::semantic(
            offset,
            format!("unsupported COSE hash algorithm `{name}`"),
        )),
    }
}

#[cfg(feature = "cdn")]
pub(super) fn hash_atom(data: Vec<u8>, alg: Option<HashAlg>, offset: usize) -> Result<Atom, Error> {
    use sha2::{Digest, Sha256, Sha384, Sha512, Sha512_256};
    use shake::{
        digest::{ExtendableOutput, Update, XofReader},
        Shake128, Shake256,
    };

    let alg = alg.unwrap_or(HashAlg::Sha256);
    let bytes = match alg {
        HashAlg::Sha256 => Sha256::digest(&data).to_vec(),
        HashAlg::Sha256_64 => Sha256::digest(&data)[..8].to_vec(),
        HashAlg::Sha512_256 => Sha512_256::digest(&data).to_vec(),
        HashAlg::Sha384 => Sha384::digest(&data).to_vec(),
        HashAlg::Sha512 => Sha512::digest(&data).to_vec(),
        HashAlg::Shake128 => {
            let mut hasher = Shake128::default();
            hasher.update(&data);
            let mut reader = hasher.finalize_xof();
            let mut out = vec![0; 32];
            reader.read(&mut out);
            out
        }
        HashAlg::Shake256 => {
            let mut hasher = Shake256::default();
            hasher.update(&data);
            let mut reader = hasher.finalize_xof();
            let mut out = vec![0; 64];
            reader.read(&mut out);
            out
        }
    };

    if bytes.is_empty() {
        return Err(Error::Syntax(offset));
    }
    Ok(Atom::Bytes(bytes))
}