//! A single selectively-disclosable claim.
//!
//! A disclosure is the salted preimage `[salt, name, value]` of one digest
//! listed in the credential's `_sd` array. The issuer ships every disclosure;
//! the holder forwards only the ones a given verifier should see. Because the
//! digest is taken over the *encoded* string, the salt makes two equal values
//! hash differently, so a verifier cannot guess an undisclosed claim by
//! hashing candidate values.

use crate::base64url;
use crate::error::Error;
use rand_core::{OsRng, RngCore as _};
use sha2::{Digest as _, Sha256};

/// One object-property disclosure: a claim name, its value, and the salt that
/// blinds its digest.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Disclosure {
    name: String,
    value: serde_json::Value,
    encoded: String,
}

impl Disclosure {
    /// Create a disclosure for `name = value` with a fresh 128-bit salt.
    pub(crate) fn new(name: &str, value: serde_json::Value) -> Result<Self, Error> {
        let mut salt = [0u8; 16];
        OsRng
            .try_fill_bytes(&mut salt)
            .map_err(|_| Error::Entropy)?;
        let array = serde_json::json!([base64url::encode(salt), name, value]);
        let json = serde_json::to_vec(&array).map_err(Error::json("disclosure"))?;
        Ok(Self {
            name: name.to_owned(),
            value,
            encoded: base64url::encode(json),
        })
    }

    /// Parse a received disclosure string, rejecting anything that is not the
    /// `[salt, name, value]` object-property form.
    pub(crate) fn parse(encoded: &str) -> Result<Self, Error> {
        let bytes = base64url::decode(encoded, "disclosure")?;
        let array: Vec<serde_json::Value> =
            serde_json::from_slice(&bytes).map_err(Error::json("disclosure"))?;
        match array.as_slice() {
            [
                serde_json::Value::String(_salt),
                serde_json::Value::String(name),
                value,
            ] => Ok(Self {
                name: name.clone(),
                value: value.clone(),
                encoded: encoded.to_owned(),
            }),
            _ => Err(Error::MalformedSdJwt {
                reason: "a disclosure must encode [salt, name, value]".to_owned(),
            }),
        }
    }

    /// The claim name carried by this disclosure.
    pub(crate) fn name(&self) -> &str {
        &self.name
    }

    /// The claim value carried by this disclosure.
    pub(crate) fn value(&self) -> &serde_json::Value {
        &self.value
    }

    /// The base64url disclosure string, as it appears between `~` separators.
    pub(crate) fn encoded(&self) -> &str {
        &self.encoded
    }

    /// The base64url SHA-256 digest the credential's `_sd` array must contain
    /// for this disclosure to be accepted.
    pub(crate) fn digest(&self) -> String {
        base64url::encode(Sha256::digest(self.encoded.as_bytes()))
    }
}
