//! CMS (RFC 5652) — Cryptographic Message Syntax.
//!
//! [`CmsContentInfo`] wraps `CMS_ContentInfo*` and covers the `SignedData`
//! content type used in modern PKI: signing a payload with an X.509 certificate,
//! verifying a signed message against a trust store, and extracting the embedded
//! certificates and CRLs.
//!
//! # Quick example
//!
//! ```rust,no_run
//! # use native_ossl::cms::{CmsContentInfo, CmsSignFlags, CmsVerifyFlags};
//! # use native_ossl::x509::{X509, X509Store};
//! # use native_ossl::pkey::Pkey;
//! # use native_ossl::pkey::Private;
//! # fn example(cert: &X509, key: &Pkey<Private>, store: &X509Store) -> Result<(), native_ossl::error::ErrorStack> {
//! let payload = b"hello, PKINIT";
//! let signed = CmsContentInfo::sign(cert, key, &[], payload, CmsSignFlags::NONE)?;
//! let der = signed.to_der()?;
//!
//! let parsed = CmsContentInfo::from_der(&der)?;
//! let content = parsed.verify(store, &[], CmsVerifyFlags::NONE)?;
//! assert_eq!(content, payload);
//! # Ok(()) }
//! ```

use crate::bio::{MemBio, MemBioBuf};
use crate::error::ErrorStack;
use crate::obj::Oid;
use crate::pkey::{Pkey, Private};
use crate::x509::{X509Crl, X509Store, X509};
use native_ossl_sys as sys;
use std::marker::PhantomData;
use std::sync::Arc;

// ── CmsVerifyFlags ────────────────────────────────────────────────────────────

/// Flags that control [`CmsContentInfo::verify`].
///
/// Combine multiple flags with `|`.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct CmsVerifyFlags(pub u32);

impl CmsVerifyFlags {
    /// No flags — full verification (signature + chain).
    pub const NONE: Self = Self(0);
    /// Skip signer certificate verification (equivalent to `CMS_NOVERIFY`).
    pub const NO_SIGNER_CERT_VERIFY: Self = Self(0x20);
    /// Skip all verification — only decode the structure.
    pub const NOVERIFY: Self = Self(0x20);
    /// Do not search for signers in the embedded certificate set.
    pub const NOINTERN: Self = Self(0x10);
    /// Do not consult embedded CRLs for revocation checks.
    pub const NOCRL: Self = Self(0x2000);
}

impl std::ops::BitOr for CmsVerifyFlags {
    type Output = Self;
    fn bitor(self, rhs: Self) -> Self {
        Self(self.0 | rhs.0)
    }
}

// ── CmsSignFlags ──────────────────────────────────────────────────────────────

/// Flags that control [`CmsContentInfo::sign`].
///
/// Combine multiple flags with `|`.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct CmsSignFlags(pub u32);

impl CmsSignFlags {
    /// No flags — produce a full `SignedData` structure with embedded content.
    pub const NONE: Self = Self(0);
    /// Produce detached content — the signed bytes are not embedded.
    pub const DETACHED: Self = Self(0x40);
    /// Treat content as binary (do not convert line endings).
    pub const BINARY: Self = Self(0x80);
    /// Omit S/MIME capabilities from signed attributes.
    pub const NOSMIMECAP: Self = Self(0x200);
    /// Do not embed the signer certificate in the `SignedData`.
    pub const NOCERTS: Self = Self(0x2);
    /// Omit signed attributes entirely.
    pub const NOATTR: Self = Self(0x100);
}

impl std::ops::BitOr for CmsSignFlags {
    type Output = Self;
    fn bitor(self, rhs: Self) -> Self {
        Self(self.0 | rhs.0)
    }
}

// ── CmsSignerInfo ─────────────────────────────────────────────────────────────

/// A borrowed reference to a `CMS_SignerInfo` within a [`CmsContentInfo`].
///
/// The lifetime is tied to the [`CmsContentInfo`] that owns the signer info
/// stack; the pointer is valid only while that parent is alive.
pub struct CmsSignerInfo<'a> {
    ptr: *mut sys::CMS_SignerInfo,
    _marker: PhantomData<&'a CmsContentInfo>,
}

impl CmsSignerInfo<'_> {
    /// Return the signer certificate embedded in this signer info, if any.
    ///
    /// Returns `None` if the certificate was not embedded (e.g. `CmsSignFlags::NOCERTS`
    /// was set when signing).
    #[must_use]
    pub fn signer_cert(&self) -> Option<X509> {
        let mut signer: *mut sys::X509 = std::ptr::null_mut();
        // SAFETY: self.ptr is valid for the lifetime of the parent CmsContentInfo;
        // all out-params we don't need are passed as null.
        unsafe {
            sys::CMS_SignerInfo_get0_algs(
                self.ptr,
                std::ptr::null_mut(), // pk
                std::ptr::addr_of_mut!(signer),
                std::ptr::null_mut(), // pdig
                std::ptr::null_mut(), // psig
            );
        }
        if signer.is_null() {
            return None;
        }
        // The returned pointer is borrowed from the SignedData structure; call
        // up_ref to take an independent reference for the returned X509.
        unsafe { sys::X509_up_ref(signer) };
        Some(unsafe { X509::from_ptr(signer) })
    }
}

// ── CmsContentInfo ────────────────────────────────────────────────────────────

/// An owned `CMS_ContentInfo*` — the top-level CMS container.
pub struct CmsContentInfo {
    ptr: *mut sys::CMS_ContentInfo,
}

// SAFETY: CMS_ContentInfo* has no thread-local state; read-only methods
// access only the allocated data through a shared reference.
unsafe impl Send for CmsContentInfo {}
unsafe impl Sync for CmsContentInfo {}

impl Drop for CmsContentInfo {
    fn drop(&mut self) {
        // SAFETY: ptr is a valid, non-null, owned CMS_ContentInfo*.
        unsafe { sys::CMS_ContentInfo_free(self.ptr) };
    }
}

impl CmsContentInfo {
    // ── Constructors ─────────────────────────────────────────────────────────

    /// Decode a DER-encoded `CMS_ContentInfo` structure.
    ///
    /// # Errors
    ///
    /// Returns `Err` if `data` is not valid CMS DER.
    pub fn from_der(data: &[u8]) -> Result<Self, ErrorStack> {
        let bio = MemBioBuf::new(data)?;
        // SAFETY: bio.as_ptr() is valid; second arg NULL means allocate fresh.
        let ptr = unsafe { sys::d2i_CMS_bio(bio.as_ptr(), std::ptr::null_mut()) };
        if ptr.is_null() {
            Err(ErrorStack::drain())
        } else {
            Ok(CmsContentInfo { ptr })
        }
    }

    /// Decode a DER-encoded `CMS_ContentInfo` using an explicit library context.
    ///
    /// Allocates the container with `CMS_ContentInfo_new_ex(ctx)` first, then
    /// decodes into it so that any internal algorithm fetches use `ctx`'s
    /// provider set.
    ///
    /// # Errors
    pub fn from_der_in(ctx: &Arc<crate::lib_ctx::LibCtx>, data: &[u8]) -> Result<Self, ErrorStack> {
        let shell = unsafe { sys::CMS_ContentInfo_new_ex(ctx.as_ptr(), std::ptr::null()) };
        if shell.is_null() {
            return Err(ErrorStack::drain());
        }
        let mut ptr = data.as_ptr();
        let len = i64::try_from(data.len()).unwrap_or(i64::MAX);
        // d2i_CMS_ContentInfo with a non-null `a` pointer will free the old
        // value and write the newly decoded one.
        let mut shell_mut = shell;
        let result = unsafe {
            sys::d2i_CMS_ContentInfo(
                std::ptr::addr_of_mut!(shell_mut),
                std::ptr::addr_of_mut!(ptr),
                len,
            )
        };
        if result.is_null() {
            // d2i freed `shell` on failure.
            return Err(ErrorStack::drain());
        }
        Ok(CmsContentInfo { ptr: result })
    }

    // ── Serialisation ────────────────────────────────────────────────────────

    /// DER-encode this `CMS_ContentInfo`.
    ///
    /// # Errors
    pub fn to_der(&self) -> Result<Vec<u8>, ErrorStack> {
        let mut bio = MemBio::new()?;
        // SAFETY: self.ptr is valid; bio.as_ptr() is valid.
        let rc = unsafe { sys::i2d_CMS_bio(bio.as_ptr(), self.ptr) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(bio.into_vec())
    }

    // ── Accessors ────────────────────────────────────────────────────────────

    /// Return the content-type OID (the outer `contentType` field).
    ///
    /// For `SignedData` this is OID `1.2.840.113549.1.7.2`.
    ///
    /// # Panics
    ///
    /// Panics if OpenSSL fails to allocate memory for the OID copy.
    #[must_use]
    pub fn content_type_oid(&self) -> Oid {
        // SAFETY: CMS_get0_type returns a const pointer borrowed from self.ptr;
        // OBJ_dup copies it into a new heap allocation for us to own.
        let obj = unsafe { sys::CMS_get0_type(self.ptr) };
        let dup = unsafe { sys::OBJ_dup(obj.cast_mut()) };
        assert!(!dup.is_null(), "OBJ_dup: allocation failed");
        unsafe { Oid::from_ptr(dup) }
    }

    /// Return the encapsulated content type OID (`eContentType`).
    ///
    /// For ordinary `SignedData` carrying arbitrary data this is typically
    /// `data` (OID `1.2.840.113549.1.7.1`); PKINIT uses its own OIDs
    /// (`id-pkinit-authData`, `id-pkinit-DHKeyData`, etc.).
    ///
    /// # Panics
    ///
    /// Panics if OpenSSL fails to allocate memory for the OID copy.
    #[must_use]
    pub fn econtent_type_oid(&self) -> Oid {
        let obj = unsafe { sys::CMS_get0_eContentType(self.ptr) };
        let dup = unsafe { sys::OBJ_dup(obj.cast_mut()) };
        assert!(!dup.is_null(), "OBJ_dup: allocation failed");
        unsafe { Oid::from_ptr(dup) }
    }

    /// Return `true` if the content is detached (not embedded in the structure).
    #[must_use]
    pub fn is_detached(&self) -> bool {
        unsafe { sys::CMS_is_detached(self.ptr) == 1 }
    }

    /// Return the raw encapsulated content bytes, or `None` if absent or detached.
    #[must_use]
    pub fn content(&self) -> Option<Vec<u8>> {
        // CMS_get0_content returns ASN1_OCTET_STRING** — a pointer to the
        // pointer stored inside the structure.  We dereference twice.
        // SAFETY: self.ptr is valid; the returned pointer lives as long as self.
        let pp = unsafe { sys::CMS_get0_content(self.ptr) };
        if pp.is_null() {
            return None;
        }
        let ostr = unsafe { *pp };
        if ostr.is_null() {
            return None;
        }
        // SAFETY: ostr is a valid, non-null ASN1_OCTET_STRING*.
        let data_ptr = unsafe { sys::ASN1_STRING_get0_data(ostr.cast()) };
        let data_len = usize::try_from(unsafe { sys::ASN1_STRING_length(ostr.cast()) }).ok()?;
        if data_ptr.is_null() || data_len == 0 {
            return None;
        }
        let slice = unsafe { std::slice::from_raw_parts(data_ptr, data_len) };
        Some(slice.to_vec())
    }

    /// Return the list of signer infos (borrowed from this `CmsContentInfo`).
    #[must_use]
    pub fn signers(&self) -> Vec<CmsSignerInfo<'_>> {
        // SAFETY: self.ptr is valid; the returned stack is borrowed from self.
        let stack = unsafe { sys::CMS_get0_SignerInfos(self.ptr) };
        if stack.is_null() {
            return Vec::new();
        }
        // OPENSSL_sk_num returns c_int; loop with i32 to avoid any cast.
        let n = unsafe { sys::OPENSSL_sk_num(stack.cast::<sys::OPENSSL_STACK>()) };
        let count = usize::try_from(n.max(0)).unwrap_or(0);
        let mut out = Vec::with_capacity(count);
        for i in 0..n {
            let raw = unsafe { sys::OPENSSL_sk_value(stack.cast::<sys::OPENSSL_STACK>(), i) };
            if !raw.is_null() {
                out.push(CmsSignerInfo {
                    ptr: raw.cast::<sys::CMS_SignerInfo>(),
                    _marker: PhantomData,
                });
            }
        }
        out
    }

    /// Return the certificates embedded in this `CMS_ContentInfo`.
    ///
    /// `CMS_get1_certs` increments the reference count of each returned
    /// certificate; each `X509` in the returned `Vec` owns an independent ref.
    #[must_use]
    pub fn certs(&self) -> Vec<X509> {
        let stack = unsafe { sys::CMS_get1_certs(self.ptr) };
        collect_x509_stack(stack)
    }

    /// Return the CRLs embedded in this `CMS_ContentInfo`.
    #[must_use]
    pub fn crls(&self) -> Vec<X509Crl> {
        let stack = unsafe { sys::CMS_get1_crls(self.ptr) };
        collect_x509_crl_stack(stack)
    }

    // ── Signature operations ──────────────────────────────────────────────────

    /// Verify the CMS `SignedData`, returning the encapsulated content on success.
    ///
    /// - `store` — trust anchor store.
    /// - `certs` — additional untrusted certificates to include in chain building.
    /// - `flags` — verification control flags (see [`CmsVerifyFlags`]).
    ///
    /// # Errors
    ///
    /// Returns `Err` if signature verification fails, the chain cannot be built,
    /// or the content is missing or malformed.
    pub fn verify(
        &self,
        store: &X509Store,
        certs: &[X509],
        flags: CmsVerifyFlags,
    ) -> Result<Vec<u8>, ErrorStack> {
        let certs_stack = build_x509_stack(certs)?;
        let mut out_bio = MemBio::new()?;

        // SAFETY: all pointers are valid for the duration of the call;
        // certs_stack is freed below.  dcont=NULL means use the embedded content.
        let rc = unsafe {
            sys::CMS_verify(
                self.ptr,
                certs_stack,
                store.as_ptr(),
                std::ptr::null_mut(), // dcont
                out_bio.as_ptr(),
                flags.0,
            )
        };

        // Free the temporary certs stack (elements are borrowed; not pop_free).
        if !certs_stack.is_null() {
            unsafe { sys::OPENSSL_sk_free(certs_stack.cast()) };
        }

        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(out_bio.into_vec())
    }

    /// Create a CMS `SignedData` wrapping `content`.
    ///
    /// - `cert` — signer certificate.
    /// - `key` — corresponding private key.
    /// - `extra_certs` — additional certificates to embed in the `SignedData`
    ///   (e.g. intermediate CA certificates for chain building).
    /// - `content` — the payload to sign.
    /// - `flags` — sign control flags (see [`CmsSignFlags`]).
    ///
    /// # Errors
    pub fn sign(
        cert: &X509,
        key: &Pkey<Private>,
        extra_certs: &[X509],
        content: &[u8],
        flags: CmsSignFlags,
    ) -> Result<Self, ErrorStack> {
        let data_bio = MemBioBuf::new(content)?;
        let certs_stack = build_x509_stack(extra_certs)?;

        // SAFETY: all pointers are valid; certs_stack is freed below.
        let ptr = unsafe {
            sys::CMS_sign(
                cert.as_ptr(),
                key.as_ptr(),
                certs_stack,
                data_bio.as_ptr(),
                flags.0,
            )
        };

        if !certs_stack.is_null() {
            unsafe { sys::OPENSSL_sk_free(certs_stack.cast()) };
        }

        if ptr.is_null() {
            Err(ErrorStack::drain())
        } else {
            Ok(CmsContentInfo { ptr })
        }
    }

    /// Create a CMS `SignedData` using an explicit library context.
    ///
    /// Calls `CMS_sign_ex` so all algorithm fetches use `ctx`'s provider set.
    ///
    /// # Errors
    pub fn sign_in(
        ctx: &Arc<crate::lib_ctx::LibCtx>,
        cert: &X509,
        key: &Pkey<Private>,
        extra_certs: &[X509],
        content: &[u8],
        flags: CmsSignFlags,
    ) -> Result<Self, ErrorStack> {
        let data_bio = MemBioBuf::new(content)?;
        let certs_stack = build_x509_stack(extra_certs)?;

        let ptr = unsafe {
            sys::CMS_sign_ex(
                cert.as_ptr(),
                key.as_ptr(),
                certs_stack,
                data_bio.as_ptr(),
                flags.0,
                ctx.as_ptr(),
                std::ptr::null(),
            )
        };

        if !certs_stack.is_null() {
            unsafe { sys::OPENSSL_sk_free(certs_stack.cast()) };
        }

        if ptr.is_null() {
            Err(ErrorStack::drain())
        } else {
            Ok(CmsContentInfo { ptr })
        }
    }
}

// ── Internal helpers ──────────────────────────────────────────────────────────

/// Build a temporary `STACK_OF(X509)*` from a borrowed `&[X509]` slice.
///
/// The stack is a *shallow* owner — elements are not ref-counted up; the
/// caller must free it with `OPENSSL_sk_free` (not `pop_free`) once done.
/// Returns `null` when `certs` is empty (acceptable as NULL to OpenSSL).
fn build_x509_stack(certs: &[X509]) -> Result<*mut sys::stack_st_X509, ErrorStack> {
    if certs.is_empty() {
        return Ok(std::ptr::null_mut());
    }
    let stack = unsafe { sys::OPENSSL_sk_new_null() };
    if stack.is_null() {
        return Err(ErrorStack::drain());
    }
    for cert in certs {
        unsafe { sys::OPENSSL_sk_push(stack, cert.as_ptr().cast()) };
    }
    Ok(stack.cast())
}

/// Consume an owned `STACK_OF(X509)*` returned by `CMS_get1_certs` and
/// convert it to a `Vec<X509>`.
///
/// `CMS_get1_certs` increments the ref count of each element; each `X509`
/// we create takes ownership of that extra ref.  The stack *structure* is freed
/// with `OPENSSL_sk_free` (not `pop_free`) to avoid double-freeing.
fn collect_x509_stack(stack: *mut sys::stack_st_X509) -> Vec<X509> {
    if stack.is_null() {
        return Vec::new();
    }
    // OPENSSL_sk_num returns c_int; loop with i32 to avoid casts.
    let n = unsafe { sys::OPENSSL_sk_num(stack.cast::<sys::OPENSSL_STACK>()) };
    let count = usize::try_from(n.max(0)).unwrap_or(0);
    let mut out = Vec::with_capacity(count);
    for i in 0..n {
        let raw = unsafe { sys::OPENSSL_sk_value(stack.cast::<sys::OPENSSL_STACK>(), i) };
        if !raw.is_null() {
            // SAFETY: raw is a valid X509* with an extra ref from CMS_get1_certs.
            out.push(unsafe { X509::from_ptr(raw.cast()) });
        }
    }
    // Free the stack structure; elements are now owned by the Rust X509 values.
    unsafe { sys::OPENSSL_sk_free(stack.cast()) };
    out
}

/// Consume an owned `STACK_OF(X509_CRL)*` returned by `CMS_get1_crls` and
/// convert it to a `Vec<X509Crl>`.
fn collect_x509_crl_stack(stack: *mut sys::stack_st_X509_CRL) -> Vec<X509Crl> {
    if stack.is_null() {
        return Vec::new();
    }
    let n = unsafe { sys::OPENSSL_sk_num(stack.cast::<sys::OPENSSL_STACK>()) };
    let count = usize::try_from(n.max(0)).unwrap_or(0);
    let mut out = Vec::with_capacity(count);
    for i in 0..n {
        let raw = unsafe { sys::OPENSSL_sk_value(stack.cast::<sys::OPENSSL_STACK>(), i) };
        if !raw.is_null() {
            // SAFETY: raw is a valid X509_CRL* with an extra ref from CMS_get1_crls.
            out.push(unsafe { X509Crl::from_ptr(raw.cast()) });
        }
    }
    unsafe { sys::OPENSSL_sk_free(stack.cast()) };
    out
}

// ── Tests ─────────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;
    use crate::obj::Oid;
    use crate::params::ParamBuilder;
    use crate::pkey::KeygenCtx;
    use crate::x509::{X509Builder, X509NameOwned};

    fn make_cert_and_key() -> (X509, Pkey<Private>) {
        // Use EC P-256 — fast keygen and has a default digest for CMS_sign.
        let priv_key = (|| -> Result<Pkey<Private>, ErrorStack> {
            let mut kg = KeygenCtx::new(c"EC")?;
            let params = ParamBuilder::new()?
                .push_utf8_string(c"group", c"P-256")?
                .build()?;
            kg.set_params(&params)?;
            kg.generate()
        })()
        .expect("EC P-256 keygen");

        let pub_key: crate::pkey::Pkey<crate::pkey::Public> = priv_key.clone().into();
        let mut name = X509NameOwned::new().expect("X509NameOwned::new");
        name.add_entry_by_txt(c"CN", b"cms-test").expect("CN");

        let cert = (|| -> Result<X509, ErrorStack> {
            Ok(X509Builder::new()?
                .set_version(2)?
                .set_serial_number(1)?
                .set_not_before_offset(0)?
                .set_not_after_offset(365 * 86400)?
                .set_subject_name(&name)?
                .set_issuer_name(&name)?
                .set_public_key(&pub_key)?
                .sign(&priv_key, None)?
                .build())
        })()
        .expect("X509Builder");

        (cert, priv_key)
    }

    #[test]
    fn sign_and_verify_roundtrip() {
        let (cert, key) = make_cert_and_key();
        let payload = b"native-ossl cms test payload";

        let signed = CmsContentInfo::sign(
            &cert,
            &key,
            &[],
            payload,
            CmsSignFlags::BINARY | CmsSignFlags::NOSMIMECAP,
        )
        .expect("CMS_sign");

        let der = signed.to_der().expect("to_der");
        assert!(!der.is_empty());

        let parsed = CmsContentInfo::from_der(&der).expect("from_der");

        let mut store = X509Store::new().expect("X509Store");
        store.add_cert(&cert).expect("add_cert");

        let content = parsed
            .verify(&store, &[], CmsVerifyFlags::NONE)
            .expect("verify");
        assert_eq!(content, payload);
    }

    #[test]
    fn content_type_oid_is_signed_data() {
        let (cert, key) = make_cert_and_key();
        let cms = CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::NONE).expect("sign");
        assert_eq!(cms.content_type_oid().to_string(), "1.2.840.113549.1.7.2");
    }

    #[test]
    fn econtent_type_oid_is_data() {
        let (cert, key) = make_cert_and_key();
        let cms = CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::NONE).expect("sign");
        assert_eq!(cms.econtent_type_oid().to_string(), "1.2.840.113549.1.7.1");
    }

    #[test]
    fn embedded_signer_cert() {
        let (cert, key) = make_cert_and_key();
        let cms = CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::NONE).expect("sign");
        let signers = cms.signers();
        assert_eq!(signers.len(), 1);
        let signer_cert = signers[0].signer_cert().expect("signer cert present");
        assert_eq!(cert.to_der().unwrap(), signer_cert.to_der().unwrap());
    }

    #[test]
    fn certs_list_contains_embedded_cert() {
        let (cert, key) = make_cert_and_key();
        let cms = CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::NONE).expect("sign");
        assert!(!cms.certs().is_empty());
    }

    #[test]
    fn nocerts_flag_omits_embedded_certs() {
        let (cert, key) = make_cert_and_key();
        let cms =
            CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::NOCERTS).expect("sign");
        assert!(
            cms.certs().is_empty(),
            "NOCERTS should produce no embedded certs"
        );
    }

    #[test]
    fn verify_fails_with_wrong_trust_anchor() {
        let (cert, key) = make_cert_and_key();
        let (other_cert, _) = make_cert_and_key();

        let cms = CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::BINARY).expect("sign");
        let der = cms.to_der().expect("to_der");
        let parsed = CmsContentInfo::from_der(&der).expect("from_der");

        let mut store = X509Store::new().expect("store");
        store.add_cert(&other_cert).expect("add_cert");

        assert!(
            parsed.verify(&store, &[], CmsVerifyFlags::NONE).is_err(),
            "verify with wrong trust anchor must fail"
        );
    }

    #[test]
    fn content_accessor_returns_payload() {
        let (cert, key) = make_cert_and_key();
        let payload = b"detectable content";
        let cms =
            CmsContentInfo::sign(&cert, &key, &[], payload, CmsSignFlags::BINARY).expect("sign");
        let content = cms.content().expect("content present");
        assert_eq!(content, payload);
    }

    #[test]
    fn is_detached_flag() {
        let (cert, key) = make_cert_and_key();
        let embedded =
            CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::NONE).expect("sign");
        let detached =
            CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::DETACHED).expect("sign");
        assert!(!embedded.is_detached());
        assert!(detached.is_detached());
    }

    #[test]
    fn oid_comparison_econtent() {
        let (cert, key) = make_cert_and_key();
        let cms = CmsContentInfo::sign(&cert, &key, &[], b"x", CmsSignFlags::NONE).expect("sign");
        let data_oid = Oid::from_text("1.2.840.113549.1.7.1").expect("data oid");
        assert_eq!(cms.econtent_type_oid(), data_oid);
    }
}
