use codec_cbor::{
decode_dag_cbor as decode_primitive_dag_cbor,
decode_deterministic_cbor as decode_primitive_deterministic_cbor,
encode_dag_cbor as encode_primitive_dag_cbor,
encode_deterministic_cbor as encode_primitive_deterministic_cbor,
verify_dag_cbor_cid as verify_primitive_dag_cbor_cid, CborError, CborValue,
CidVerificationStatus, DeterministicCborError, DeterministicCborValue, MAX_DAG_CBOR_INPUT_LEN,
};
use zeroize::Zeroizing;
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum DagCborOperationError {
#[error("dag-cbor payload too large")]
PayloadTooLarge,
#[error("invalid dag-cbor payload")]
InvalidPayload(CborError),
}
pub struct DagCborCidVerification {
valid: bool,
expected_cid: String,
actual_cid: String,
}
impl DagCborCidVerification {
#[cfg(test)]
pub const fn valid(&self) -> bool {
self.valid
}
#[cfg(test)]
pub fn expected_cid(&self) -> &str {
self.expected_cid.as_str()
}
#[cfg(test)]
pub fn actual_cid(&self) -> &str {
self.actual_cid.as_str()
}
pub(crate) fn into_parts(self) -> (bool, String, String) {
(self.valid, self.expected_cid, self.actual_cid)
}
}
pub fn verify_dag_cbor_cid(
cid: &str,
payload: &[u8],
) -> Result<DagCborCidVerification, DagCborOperationError> {
if payload.len() > MAX_DAG_CBOR_INPUT_LEN {
return Err(DagCborOperationError::PayloadTooLarge);
}
let verified = verify_primitive_dag_cbor_cid(cid, payload)
.map_err(DagCborOperationError::InvalidPayload)?;
let (status, expected_cid, actual_cid) = verified.into_parts();
Ok(DagCborCidVerification {
valid: status == CidVerificationStatus::Match,
expected_cid,
actual_cid,
})
}
pub fn encode_dag_cbor_value(value: &CborValue) -> Result<Zeroizing<Vec<u8>>, CborError> {
encode_primitive_dag_cbor(value).map(Zeroizing::new)
}
pub fn decode_dag_cbor_value(bytes: &[u8]) -> Result<Zeroizing<CborValue>, CborError> {
decode_primitive_dag_cbor(bytes).map(Zeroizing::new)
}
pub fn encode_deterministic_cbor_value(
value: &DeterministicCborValue,
) -> Result<Zeroizing<Vec<u8>>, DeterministicCborError> {
encode_primitive_deterministic_cbor(value)
}
pub fn decode_deterministic_cbor_value(
bytes: &[u8],
) -> Result<DeterministicCborValue, DeterministicCborError> {
decode_primitive_deterministic_cbor(bytes)
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use codec_cbor::{compute_cid_dag_cbor, CidVerificationStatus};
use super::{
verify_dag_cbor_cid, verify_primitive_dag_cbor_cid, DagCborOperationError,
MAX_DAG_CBOR_INPUT_LEN,
};
#[test]
fn verify_dag_cbor_cid_preserves_primitive_verification_semantics() {
let payload = [0xa0];
let cid = compute_cid_dag_cbor(&payload);
let primitive = verify_primitive_dag_cbor_cid(&cid, &payload).unwrap();
let verification = verify_dag_cbor_cid(&cid, &payload).unwrap();
assert_eq!(
verification.valid(),
primitive.status() == CidVerificationStatus::Match
);
assert_eq!(verification.expected_cid(), primitive.expected_cid());
assert_eq!(verification.actual_cid(), primitive.actual_cid());
}
#[test]
fn verify_dag_cbor_cid_preserves_invalid_cid_sanitization() {
let payload = [0xa0];
let verification = verify_dag_cbor_cid("not-a-cid", &payload).unwrap();
assert!(!verification.valid());
assert_eq!(verification.expected_cid(), compute_cid_dag_cbor(&payload));
assert_eq!(verification.actual_cid(), "");
}
#[test]
fn verify_dag_cbor_cid_rejects_payloads_above_limit() {
let payload = vec![0_u8; MAX_DAG_CBOR_INPUT_LEN + 1];
let error = match verify_dag_cbor_cid("", &payload) {
Ok(_) => DagCborOperationError::PayloadTooLarge,
Err(error) => error,
};
assert_eq!(error, DagCborOperationError::PayloadTooLarge);
}
}