use crate::data_model::common::{Asn1TimeEncoding, Asn1TimeUtc, DerReader, der_uint_from_bytes};
use crate::data_model::oid::{
    OID_AD_SIGNED_OBJECT, OID_CMS_ATTR_CONTENT_TYPE, OID_CMS_ATTR_CONTENT_TYPE_RAW,
    OID_CMS_ATTR_MESSAGE_DIGEST, OID_CMS_ATTR_MESSAGE_DIGEST_RAW, OID_CMS_ATTR_SIGNING_TIME,
    OID_CMS_ATTR_SIGNING_TIME_RAW, OID_CT_ASPA, OID_CT_ASPA_RAW, OID_CT_ROUTE_ORIGIN_AUTHZ,
    OID_CT_ROUTE_ORIGIN_AUTHZ_RAW, OID_CT_RPKI_MANIFEST, OID_CT_RPKI_MANIFEST_RAW,
    OID_KEY_USAGE_RAW, OID_RSA_ENCRYPTION, OID_RSA_ENCRYPTION_RAW, OID_SHA256, OID_SHA256_RAW,
    OID_SHA256_WITH_RSA_ENCRYPTION, OID_SHA256_WITH_RSA_ENCRYPTION_RAW, OID_SIGNED_DATA,
    OID_SIGNED_DATA_RAW, OID_SUBJECT_INFO_ACCESS,
};
use crate::data_model::rc::{ResourceCertificate, ResourceCertificateRole, SubjectInfoAccess};
use asn1_rs::{Any, Class, FromBer, FromDer as Asn1FromDer, Header, Tag};
use ring::digest;
use x509_parser::extensions::ParsedExtension;
use x509_parser::prelude::X509Certificate;
use x509_parser::public_key::PublicKey;
use x509_parser::x509::SubjectPublicKeyInfo;

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum EeKeyUsageSummary {
    DigitalSignatureOnly,
    Missing,
    NotCritical,
    InvalidBits,
    ParseError(String),
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResourceEeCertificate {
    pub raw_der: Vec<u8>,
    pub subject_key_identifier: Vec<u8>,
    pub spki_der: Vec<u8>,
    pub rsa_public_modulus: Vec<u8>,
    pub rsa_public_exponent: Vec<u8>,
    pub tbs_certificate_der: Vec<u8>,
    pub signature_bytes: Vec<u8>,
    pub key_usage_summary: EeKeyUsageSummary,
    pub sia_signed_object_uris: Vec<String>,
    pub resource_cert: ResourceCertificate,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RpkiSignedObject {
    pub raw_der: Vec<u8>,
    pub content_info_content_type: String,
    pub signed_data: SignedDataProfiled,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SignedDataProfiled {
    pub version: u32,
    pub digest_algorithms: Vec<String>,
    pub encap_content_info: EncapsulatedContentInfo,
    pub certificates: Vec<ResourceEeCertificate>,
    pub crls_present: bool,
    pub signer_infos: Vec<SignerInfoProfiled>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EncapsulatedContentInfo {
    pub econtent_type: String,
    pub econtent: Vec<u8>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SignerInfoProfiled {
    pub version: u32,
    pub sid_ski: Vec<u8>,
    pub digest_algorithm: String,
    pub signature_algorithm: String,
    pub signed_attrs: SignedAttrsProfiled,
    pub unsigned_attrs_present: bool,
    pub signature: Vec<u8>,
    pub signed_attrs_der_for_signature: Vec<u8>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SignedAttrsProfiled {
    pub content_type: String,
    pub message_digest: Vec<u8>,
    pub signing_time: Asn1TimeUtc,
    pub other_attrs_present: bool,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RpkiSignedObjectParsed {
    pub raw_der: Vec<u8>,
    pub content_info_content_type: String,
    pub signed_data: SignedDataParsed,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SignedDataParsed {
    pub version: u64,
    pub digest_algorithms: Vec<AlgorithmIdentifierParsed>,
    pub encap_content_info: EncapsulatedContentInfoParsed,
    pub certificates: Option<Vec<Vec<u8>>>,
    pub crls_present: bool,
    pub signer_infos: Vec<SignerInfoParsed>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AlgorithmIdentifierParsed {
    pub oid: String,
    pub params_ok: bool,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EncapsulatedContentInfoParsed {
    pub econtent_type: String,
    pub econtent: Option<Vec<u8>>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SignerInfoParsed {
    pub version: u64,
    pub sid: SignerIdentifierParsed,
    pub digest_algorithm: AlgorithmIdentifierParsed,
    pub signature_algorithm: AlgorithmIdentifierParsed,
    pub signed_attrs_content: Option<Vec<u8>>,
    pub signed_attrs_der_for_signature: Option<Vec<u8>>,
    pub unsigned_attrs_present: bool,
    pub signature: Vec<u8>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SignerIdentifierParsed {
    SubjectKeyIdentifier(Vec<u8>),
    Other,
}

#[derive(Debug, thiserror::Error)]
pub enum SignedObjectParseError {
    #[error("DER parse error: {0} (RFC 6488 §2; RFC 6488 §3(1l); RFC 5652 §3/§5)")]
    Parse(String),

    #[error("trailing bytes after DER object: {0} bytes (DER; RFC 6488 §3(1l))")]
    TrailingBytes(usize),
}

#[derive(Debug, thiserror::Error)]
pub enum SignedObjectValidateError {
    #[error(
        "ContentInfo.contentType must be SignedData ({OID_SIGNED_DATA}), got {0} (RFC 6488 §3(1a); RFC 5652 §3)"
    )]
    InvalidContentInfoContentType(String),

    #[error(
        "SignedData.version must be 3, got {0} (RFC 6488 §2.1.1; RFC 6488 §3(1b); RFC 5652 §5.1)"
    )]
    InvalidSignedDataVersion(u64),

    #[error(
        "SignedData.digestAlgorithms must contain exactly one AlgorithmIdentifier, got {0} (RFC 6488 §2.1.2; RFC 6488 §3(1b); RFC 5652 §5.1)"
    )]
    InvalidDigestAlgorithmsCount(usize),

    #[error(
        "digest algorithm must be id-sha256 ({OID_SHA256}), got {0} (RFC 6488 §2.1.2; RFC 6488 §3(1b); RFC 7935 §2)"
    )]
    InvalidDigestAlgorithm(String),

    #[error("SignedData.certificates MUST be present (RFC 6488 §3(1c); RFC 5652 §5.1)")]
    CertificatesMissing,

    #[error(
        "SignedData.certificates must contain exactly one EE certificate, got {0} (RFC 6488 §3(1c))"
    )]
    InvalidCertificatesCount(usize),

    #[error("SignedData.crls MUST be omitted (RFC 6488 §3(1d))")]
    CrlsPresent,

    #[error(
        "SignedData.signerInfos must contain exactly one SignerInfo, got {0} (RFC 6488 §2.1; RFC 6488 §3(1e); RFC 5652 §5.1)"
    )]
    InvalidSignerInfosCount(usize),

    #[error("SignerInfo.version must be 3, got {0} (RFC 6488 §3(1e); RFC 5652 §5.3)")]
    InvalidSignerInfoVersion(u64),

    #[error("SignerInfo.sid must be subjectKeyIdentifier [0] (RFC 6488 §3(1c); RFC 5652 §5.3)")]
    InvalidSignerIdentifier,

    #[error(
        "SignerInfo.digestAlgorithm must be id-sha256 ({OID_SHA256}), got {0} (RFC 6488 §3(1j); RFC 7935 §2)"
    )]
    InvalidSignerInfoDigestAlgorithm(String),

    #[error("SignerInfo.signedAttrs MUST be present (RFC 9589 §4; RFC 6488 §3(1f))")]
    SignedAttrsMissing,

    #[error("SignerInfo.unsignedAttrs MUST be omitted (RFC 6488 §3(1i))")]
    UnsignedAttrsPresent,

    #[error(
        "SignerInfo.signatureAlgorithm must be rsaEncryption ({OID_RSA_ENCRYPTION}) or \
sha256WithRSAEncryption ({OID_SHA256_WITH_RSA_ENCRYPTION}), got {0} (RFC 6488 §3(1k); RFC 7935 §2)"
    )]
    InvalidSignatureAlgorithm(String),

    #[error(
        "SignerInfo.signatureAlgorithm parameters must be absent or NULL (RFC 5280 §4.1.1.2; RFC 7935 §2)"
    )]
    InvalidSignatureAlgorithmParameters,

    #[error("signedAttrs contains unsupported attribute OID {0} (RFC 9589 §4; RFC 6488 §2.1.6.4)")]
    UnsupportedSignedAttribute(String),

    #[error("signedAttrs contains duplicate attribute OID {0} (RFC 6488 §2.1.6.4; RFC 9589 §4)")]
    DuplicateSignedAttribute(String),

    #[error("signedAttrs parse error: {0} (RFC 5652 §5.3; RFC 6488 §3(1f); RFC 9589 §4)")]
    SignedAttrsParse(String),

    #[error(
        "signedAttrs attribute {oid} attrValues must contain exactly one value, got {count} (RFC 6488 §2.1.6.4; RFC 5652 §5.3)"
    )]
    InvalidSignedAttributeValuesCount { oid: String, count: usize },

    #[error(
        "signedAttrs missing content-type attribute (RFC 9589 §4; RFC 5652 §11.1; RFC 6488 §2.1.6.4)"
    )]
    SignedAttrsContentTypeMissing,

    #[error(
        "signedAttrs missing message-digest attribute (RFC 9589 §4; RFC 5652 §11.2; RFC 6488 §2.1.6.4)"
    )]
    SignedAttrsMessageDigestMissing,

    #[error(
        "signedAttrs missing signing-time attribute (RFC 9589 §4; RFC 5652 §11.3; RFC 6488 §2.1.6.4)"
    )]
    SignedAttrsSigningTimeMissing,

    #[error(
        "signedAttrs.content-type attrValues must equal eContentType ({econtent_type}), got {attr_content_type} (RFC 6488 §3(1h); RFC 9589 §4)"
    )]
    ContentTypeAttrMismatch {
        econtent_type: String,
        attr_content_type: String,
    },

    #[error("EncapsulatedContentInfo.eContent MUST be present (RFC 6488 §2.1.3; RFC 5652 §5.2)")]
    EContentMissing,

    #[error(
        "signedAttrs.message-digest does not match SHA-256(eContent) (RFC 6488 §3(1f); RFC 5652 §11.2)"
    )]
    MessageDigestMismatch,

    #[error("EE certificate parse error: {0} (RFC 6488 §3(1c); RFC 6487 §4)")]
    EeCertificateParse(String),

    #[error(
        "EE certificate missing SubjectKeyIdentifier extension (RFC 6488 §3(1c); RFC 6487 §4.8.2)"
    )]
    EeCertificateMissingSki,

    #[error(
        "EE certificate missing SubjectInfoAccess extension ({OID_SUBJECT_INFO_ACCESS}) (RFC 6487 §4.8.8.2)"
    )]
    EeCertificateMissingSia,

    #[error(
        "EE certificate SIA missing id-ad-signedObject access method ({OID_AD_SIGNED_OBJECT}) (RFC 6487 §4.8.8.2)"
    )]
    EeCertificateMissingSignedObjectSia,

    #[error(
        "EE certificate SIA id-ad-signedObject accessLocation must be a URI (RFC 6487 §4.8.8.2; RFC 5280 §4.2.2.2)"
    )]
    EeCertificateSignedObjectSiaNotUri,

    #[error(
        "EE certificate SIA id-ad-signedObject must include at least one rsync:// URI (RFC 6487 §4.8.8.2)"
    )]
    EeCertificateSignedObjectSiaNoRsync,

    #[error(
        "SignerInfo.sid SKI does not match EE certificate SKI (RFC 6488 §3(1c); RFC 5652 §5.3)"
    )]
    SidSkiMismatch,

    #[error(
        "invalid signing-time attribute value (expected UTCTime or GeneralizedTime) (RFC 5652 §11.3; RFC 9589 §4)"
    )]
    InvalidSigningTimeValue,
}

#[derive(Debug, thiserror::Error)]
pub enum SignedObjectDecodeError {
    #[error("SignedObject parse error: {0}")]
    Parse(#[from] SignedObjectParseError),

    #[error("SignedObject validate error: {0}")]
    Validate(#[from] SignedObjectValidateError),
}

#[derive(Debug, thiserror::Error)]
pub enum SignedObjectVerifyError {
    #[error("EE SubjectPublicKeyInfo parse error: {0} (RFC 5280 §4.1.2.7)")]
    EeSpkiParse(String),

    #[error("trailing bytes after EE SubjectPublicKeyInfo DER: {0} bytes (DER; RFC 5280 §4.1.2.7)")]
    EeSpkiTrailingBytes(usize),

    #[error("unsupported EE public key algorithm (only RSA supported in M3) (RFC 7935 §2)")]
    UnsupportedEePublicKeyAlgorithm,

    #[error("EE RSA public exponent invalid (RFC 8017 §A.1.1; RFC 7935 §2)")]
    InvalidEeRsaExponent,

    #[error("signature verification failed (RFC 6488 §3(2)-(3); RFC 5652 §5.3; RFC 7935 §2)")]
    InvalidSignature,
}

impl RpkiSignedObject {
    /// Parse a DER-encoded RPKI Signed Object (CMS ContentInfo wrapping SignedData).
    ///
    /// This performs encoding/structure parsing only. Profile constraints are enforced by
    /// `RpkiSignedObjectParsed::validate_profile`.
    pub fn parse_der(der: &[u8]) -> Result<RpkiSignedObjectParsed, SignedObjectParseError> {
        parse_signed_object_content_info(der, der, CmsParseMode::BerCompatible)
    }

    pub fn parse_der_strict_cms(
        der: &[u8],
    ) -> Result<RpkiSignedObjectParsed, SignedObjectParseError> {
        parse_signed_object_content_info(der, der, CmsParseMode::DerStrict)
    }

    /// Decode a DER-encoded RPKI Signed Object (CMS ContentInfo wrapping SignedData) and enforce
    /// the profile constraints from RFC 6488 §2-§3 and RFC 9589 §4.
    pub fn decode_der(der: &[u8]) -> Result<Self, SignedObjectDecodeError> {
        let parsed = Self::parse_der(der)?;
        Ok(parsed.validate_profile()?)
    }

    pub fn decode_der_with_strict_options(
        der: &[u8],
        strict_cms_der: bool,
        strict_name: bool,
    ) -> Result<Self, SignedObjectDecodeError> {
        let parsed = if strict_cms_der {
            Self::parse_der_strict_cms(der)?
        } else {
            Self::parse_der(der)?
        };
        Ok(parsed.validate_profile_with_strict_name(strict_name)?)
    }

    /// Scheme-A naming for signature verification.
    pub fn verify(&self) -> Result<(), SignedObjectVerifyError> {
        self.verify_signature()
    }

    /// Verify the CMS signature using the embedded EE certificate public key.
    pub fn verify_signature(&self) -> Result<(), SignedObjectVerifyError> {
        let ee = &self.signed_data.certificates[0];
        let signer = &self.signed_data.signer_infos[0];
        crate::crypto_sig_cache::verify_with_cache(
            crate::crypto_sig_cache::CryptoSigVerifyPoint::CmsSignedObject,
            &signer.signed_attrs_der_for_signature,
            &signer.signature,
            &ee.spki_der,
            || {
                self.verify_signature_with_rsa_components(
                    &ee.rsa_public_modulus,
                    &ee.rsa_public_exponent,
                )
            },
        )
    }

    /// Verify the CMS signature using a DER-encoded SubjectPublicKeyInfo.
    pub fn verify_signature_with_ee_spki_der(
        &self,
        ee_spki_der: &[u8],
    ) -> Result<(), SignedObjectVerifyError> {
        let (rem, spki) = SubjectPublicKeyInfo::from_der(ee_spki_der)
            .map_err(|e| SignedObjectVerifyError::EeSpkiParse(e.to_string()))?;
        if !rem.is_empty() {
            return Err(SignedObjectVerifyError::EeSpkiTrailingBytes(rem.len()));
        }
        self.verify_signature_with_ee_spki(&spki)
    }

    /// Verify the CMS signature using a parsed SubjectPublicKeyInfo.
    pub fn verify_signature_with_ee_spki(
        &self,
        ee_spki: &SubjectPublicKeyInfo<'_>,
    ) -> Result<(), SignedObjectVerifyError> {
        let pk = ee_spki
            .parsed()
            .map_err(|_e| SignedObjectVerifyError::UnsupportedEePublicKeyAlgorithm)?;
        let (n, e) = match pk {
            PublicKey::RSA(rsa) => {
                let n = strip_leading_zeros(rsa.modulus).to_vec();
                let e = strip_leading_zeros(rsa.exponent).to_vec();
                let _exp = rsa
                    .try_exponent()
                    .map_err(|_e| SignedObjectVerifyError::InvalidEeRsaExponent)?;
                (n, e)
            }
            _ => return Err(SignedObjectVerifyError::UnsupportedEePublicKeyAlgorithm),
        };

        let signer = &self.signed_data.signer_infos[0];
        crate::crypto_sig_cache::verify_with_cache(
            crate::crypto_sig_cache::CryptoSigVerifyPoint::CmsSignedObject,
            &signer.signed_attrs_der_for_signature,
            &signer.signature,
            ee_spki.raw,
            || self.verify_signature_with_rsa_components(n.as_slice(), e.as_slice()),
        )
    }

    fn verify_signature_with_rsa_components(
        &self,
        modulus: &[u8],
        exponent: &[u8],
    ) -> Result<(), SignedObjectVerifyError> {
        let signer = &self.signed_data.signer_infos[0];
        let msg = &signer.signed_attrs_der_for_signature;

        let pk = ring::signature::RsaPublicKeyComponents {
            n: modulus,
            e: exponent,
        };
        pk.verify(
            &ring::signature::RSA_PKCS1_2048_8192_SHA256,
            msg,
            &signer.signature,
        )
        .map_err(|_e| SignedObjectVerifyError::InvalidSignature)
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum CmsParseMode {
    BerCompatible,
    DerStrict,
}

struct CmsReader<'a> {
    buf: &'a [u8],
    mode: CmsParseMode,
}

impl<'a> CmsReader<'a> {
    fn new(buf: &'a [u8], mode: CmsParseMode) -> Self {
        Self { buf, mode }
    }

    fn is_empty(&self) -> bool {
        self.buf.is_empty()
    }

    fn remaining_len(&self) -> usize {
        self.buf.len()
    }

    fn peek_tag(&self) -> Result<u8, String> {
        let (_rem, any) = parse_any(self.buf, self.mode)?;
        header_to_single_byte_tag(&any.header)
    }

    fn take_any(&mut self) -> Result<(u8, &'a [u8]), String> {
        let (rem, any) = parse_any(self.buf, self.mode)?;
        let tag = header_to_single_byte_tag(&any.header)?;
        self.buf = rem;
        Ok((tag, any.data))
    }

    fn take_any_full(&mut self) -> Result<(u8, &'a [u8], &'a [u8]), String> {
        let (rem, any) = parse_any(self.buf, self.mode)?;
        let consumed = self.buf.len() - rem.len();
        let full = &self.buf[..consumed];
        let tag = header_to_single_byte_tag(&any.header)?;
        self.buf = rem;
        Ok((tag, full, any.data))
    }

    fn skip_any(&mut self) -> Result<(), String> {
        let _ = self.take_any()?;
        Ok(())
    }

    fn take_tag(&mut self, expected_tag: u8) -> Result<&'a [u8], String> {
        let (tag, value) = self.take_any()?;
        if tag != expected_tag {
            return Err(format!(
                "unexpected tag: got 0x{tag:02X}, expected 0x{expected_tag:02X}"
            ));
        }
        Ok(value)
    }

    fn take_sequence(&mut self) -> Result<CmsReader<'a>, String> {
        let value = self.take_tag(0x30)?;
        Ok(CmsReader::new(value, self.mode))
    }

    fn take_octet_string(&mut self) -> Result<Vec<u8>, String> {
        let (rem, any) = parse_any(self.buf, self.mode)?;
        let tag = header_to_single_byte_tag(&any.header)?;
        if self.mode == CmsParseMode::DerStrict && tag != 0x04 {
            return Err(format!(
                "unexpected tag in DER strict mode: got 0x{tag:02X}, expected 0x04"
            ));
        }
        if tag != 0x04 && tag != 0x24 {
            return Err(format!("unexpected tag: got 0x{tag:02X}, expected 0x04"));
        }
        let octets = flatten_octet_string(any, self.mode)?;
        self.buf = rem;
        Ok(octets)
    }

    fn take_uint_u64(&mut self) -> Result<u64, String> {
        let value = self.take_tag(0x02)?;
        der_uint_from_bytes(value)
    }

    fn take_explicit(&mut self, expected_outer_tag: u8) -> Result<(u8, &'a [u8]), String> {
        let inner_der = self.take_tag(expected_outer_tag)?;
        let (tag, value, rem) = cms_take_tlv(inner_der, self.mode)?;
        if !rem.is_empty() {
            return Err("trailing bytes inside EXPLICIT value".into());
        }
        Ok((tag, value))
    }

    fn take_explicit_der(&mut self, expected_outer_tag: u8) -> Result<&'a [u8], String> {
        let inner_der = self.take_tag(expected_outer_tag)?;
        let (_tag, _value, rem) = cms_take_tlv(inner_der, self.mode)?;
        if !rem.is_empty() {
            return Err("trailing bytes inside EXPLICIT value".into());
        }
        Ok(inner_der)
    }
}

fn parse_signed_object_content_info(
    raw_der: &[u8],
    parse_der: &[u8],
    mode: CmsParseMode,
) -> Result<RpkiSignedObjectParsed, SignedObjectParseError> {
    let mut r = CmsReader::new(parse_der, mode);
    let mut content_info_seq = r
        .take_sequence()
        .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
    if !r.is_empty() {
        return Err(SignedObjectParseError::TrailingBytes(r.remaining_len()));
    }

    let content_type = take_oid_string(&mut content_info_seq)?;
    let signed_data = parse_signed_data_from_contentinfo_cursor(&mut content_info_seq)?;
    if !content_info_seq.is_empty() {
        return Err(SignedObjectParseError::Parse(
            "ContentInfo must be a SEQUENCE of 2 elements".into(),
        ));
    }

    Ok(RpkiSignedObjectParsed {
        raw_der: raw_der.to_vec(),
        content_info_content_type: content_type,
        signed_data,
    })
}

fn parse_any<'a>(input: &'a [u8], mode: CmsParseMode) -> Result<(&'a [u8], Any<'a>), String> {
    match mode {
        CmsParseMode::BerCompatible => {
            Any::from_ber(input).map_err(|e| format!("BER parse error: {e}"))
        }
        CmsParseMode::DerStrict => {
            Any::from_der(input).map_err(|e| format!("DER parse error: {e}"))
        }
    }
}

fn header_to_single_byte_tag(header: &Header<'_>) -> Result<u8, String> {
    let tag_no = header.tag().0;
    if tag_no > 30 {
        return Err(format!("high-tag-number form not supported: {tag_no}"));
    }
    Ok(((header.class() as u8) << 6)
        | if header.constructed() { 0x20 } else { 0x00 }
        | tag_no as u8)
}

fn cms_take_tlv(input: &[u8], mode: CmsParseMode) -> Result<(u8, &[u8], &[u8]), String> {
    let (rem, any) = parse_any(input, mode)?;
    let tag = header_to_single_byte_tag(&any.header)?;
    Ok((tag, any.data, rem))
}

fn flatten_octet_string(any: Any<'_>, mode: CmsParseMode) -> Result<Vec<u8>, String> {
    if any.class() != Class::Universal || any.tag() != Tag::OctetString {
        return Err("expected OCTET STRING".into());
    }
    if !any.header.constructed() {
        return Ok(any.data.to_vec());
    }
    if mode == CmsParseMode::DerStrict {
        return Err("constructed OCTET STRING is not allowed in DER strict mode".into());
    }
    let mut out = Vec::new();
    let mut input = any.data;
    while !input.is_empty() {
        let (rem, child) = Any::from_ber(input).map_err(|e| format!("BER parse error: {e}"))?;
        out.extend(flatten_octet_string(child, mode)?);
        input = rem;
    }
    Ok(out)
}

impl RpkiSignedObjectParsed {
    pub fn validate_profile(self) -> Result<RpkiSignedObject, SignedObjectValidateError> {
        self.validate_profile_with_strict_name(false)
    }

    pub fn validate_profile_with_strict_name(
        self,
        strict_name: bool,
    ) -> Result<RpkiSignedObject, SignedObjectValidateError> {
        if self.content_info_content_type != OID_SIGNED_DATA {
            return Err(SignedObjectValidateError::InvalidContentInfoContentType(
                self.content_info_content_type,
            ));
        }

        let signed_data = validate_signed_data_profile(self.signed_data, strict_name)?;

        Ok(RpkiSignedObject {
            raw_der: self.raw_der,
            content_info_content_type: OID_SIGNED_DATA.to_string(),
            signed_data,
        })
    }
}

fn parse_signed_data_from_contentinfo_cursor(
    seq: &mut CmsReader<'_>,
) -> Result<SignedDataParsed, SignedObjectParseError> {
    let inner_der = seq.take_explicit_der(0xA0).map_err(|_e| {
        SignedObjectParseError::Parse("ContentInfo.content must be [0] EXPLICIT".into())
    })?;
    let mut r = CmsReader::new(inner_der, seq.mode);
    let signed_data_seq = r
        .take_sequence()
        .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
    if !r.is_empty() {
        return Err(SignedObjectParseError::Parse(
            "trailing bytes inside ContentInfo.content".into(),
        ));
    }
    parse_signed_data_cursor(signed_data_seq)
}

fn parse_signed_data_cursor(
    mut seq: CmsReader<'_>,
) -> Result<SignedDataParsed, SignedObjectParseError> {
    let version = seq
        .take_uint_u64()
        .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;

    let digest_set_bytes = seq
        .take_tag(0x31)
        .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
    let mut digest_set = CmsReader::new(digest_set_bytes, seq.mode);
    let mut digest_algorithms: Vec<AlgorithmIdentifierParsed> = Vec::new();
    while !digest_set.is_empty() {
        let alg = digest_set
            .take_sequence()
            .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
        let (oid, params_ok) = parse_algorithm_identifier_cursor(alg)?;
        digest_algorithms.push(AlgorithmIdentifierParsed { oid, params_ok });
    }

    let encap_content_info = parse_encapsulated_content_info_cursor(
        seq.take_sequence()
            .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?,
    )?;

    let mut certificates: Option<Vec<Vec<u8>>> = None;
    let mut crls_present = false;
    let mut signer_infos: Option<Vec<SignerInfoParsed>> = None;

    while !seq.is_empty() {
        let tag = seq
            .peek_tag()
            .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
        match tag {
            0xA0 => {
                if certificates.is_some() {
                    return Err(SignedObjectParseError::Parse(
                        "SignedData.certificates appears more than once".into(),
                    ));
                }
                let content = seq
                    .take_tag(0xA0)
                    .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
                certificates = Some(split_der_objects(content, seq.mode)?);
            }
            0xA1 => {
                crls_present = true;
                seq.skip_any()
                    .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
            }
            0x31 => {
                if signer_infos.is_some() {
                    return Err(SignedObjectParseError::Parse(
                        "SignedData.signerInfos appears more than once".into(),
                    ));
                }
                let set_bytes = seq
                    .take_tag(0x31)
                    .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
                signer_infos = Some(parse_signer_infos_set_cursor(set_bytes, seq.mode)?);
            }
            _ => {
                return Err(SignedObjectParseError::Parse(
                    "unexpected field in SignedData".into(),
                ));
            }
        }
    }

    let signer_infos = signer_infos
        .ok_or_else(|| SignedObjectParseError::Parse("SignedData.signerInfos missing".into()))?;

    Ok(SignedDataParsed {
        version,
        digest_algorithms,
        encap_content_info,
        certificates,
        crls_present,
        signer_infos,
    })
}

fn parse_encapsulated_content_info_cursor(
    mut seq: CmsReader<'_>,
) -> Result<EncapsulatedContentInfoParsed, SignedObjectParseError> {
    if seq.is_empty() {
        return Err(SignedObjectParseError::Parse(
            "EncapsulatedContentInfo must be SEQUENCE of 1..2".into(),
        ));
    }

    let econtent_type = take_oid_string(&mut seq)?;

    let econtent = if seq.is_empty() {
        None
    } else {
        let inner_der = seq.take_explicit_der(0xA0).map_err(|_e| {
            SignedObjectParseError::Parse(
                "EncapsulatedContentInfo.eContent must be [0] EXPLICIT".into(),
            )
        })?;
        let mut inner = CmsReader::new(inner_der, seq.mode);
        let octets = inner
            .take_octet_string()
            .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
        if !inner.is_empty() {
            return Err(SignedObjectParseError::Parse(
                "trailing bytes inside EncapsulatedContentInfo.eContent".into(),
            ));
        }
        Some(octets)
    };
    if !seq.is_empty() {
        return Err(SignedObjectParseError::Parse(
            "EncapsulatedContentInfo must be SEQUENCE of 1..2".into(),
        ));
    }

    Ok(EncapsulatedContentInfoParsed {
        econtent_type,
        econtent,
    })
}

fn split_der_objects(
    mut input: &[u8],
    mode: CmsParseMode,
) -> Result<Vec<Vec<u8>>, SignedObjectParseError> {
    let mut out: Vec<Vec<u8>> = Vec::new();
    while !input.is_empty() {
        let (_tag, _value, rem) =
            cms_take_tlv(input, mode).map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
        let consumed = input.len() - rem.len();
        out.push(input[..consumed].to_vec());
        input = rem;
    }
    Ok(out)
}

fn parse_signer_infos_set_cursor(
    set_bytes: &[u8],
    mode: CmsParseMode,
) -> Result<Vec<SignerInfoParsed>, SignedObjectParseError> {
    let mut set = CmsReader::new(set_bytes, mode);
    let mut out: Vec<SignerInfoParsed> = Vec::new();
    while !set.is_empty() {
        let si = set
            .take_sequence()
            .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
        out.push(parse_signer_info_cursor(si)?);
    }
    Ok(out)
}

fn validate_ee_certificate(
    der: &[u8],
    strict_name: bool,
) -> Result<ResourceEeCertificate, SignedObjectValidateError> {
    let (rem, cert) = X509Certificate::from_der(der)
        .map_err(|e| SignedObjectValidateError::EeCertificateParse(e.to_string()))?;
    if !rem.is_empty() {
        return Err(SignedObjectValidateError::EeCertificateParse(format!(
            "trailing bytes after EE certificate DER: {}",
            rem.len()
        )));
    }

    let rc = match ResourceCertificate::from_der(der) {
        Ok(v) => v,
        Err(e) => {
            return match e {
                crate::data_model::rc::ResourceCertificateDecodeError::Validate(
                    crate::data_model::rc::ResourceCertificateProfileError::SignedObjectSiaNotUri,
                ) => Err(SignedObjectValidateError::EeCertificateSignedObjectSiaNotUri),
                crate::data_model::rc::ResourceCertificateDecodeError::Validate(
                    crate::data_model::rc::ResourceCertificateProfileError::SignedObjectSiaNoRsync,
                ) => Err(SignedObjectValidateError::EeCertificateSignedObjectSiaNoRsync),
                _ => Err(SignedObjectValidateError::EeCertificateParse(e.to_string())),
            };
        }
    };
    rc.validate_rfc6487_profile(ResourceCertificateRole::SignedObjectEe)
        .map_err(|e| SignedObjectValidateError::EeCertificateParse(e.to_string()))?;
    if strict_name {
        rc.validate_strict_name_profile()
            .map_err(|e| SignedObjectValidateError::EeCertificateParse(e.to_string()))?;
    }

    let ski = rc
        .tbs
        .extensions
        .subject_key_identifier
        .clone()
        .ok_or(SignedObjectValidateError::EeCertificateMissingSki)?;

    let spki_der = rc.tbs.subject_public_key_info.clone();
    let (rem, spki) = SubjectPublicKeyInfo::from_der(&spki_der)
        .map_err(|e| SignedObjectValidateError::EeCertificateParse(e.to_string()))?;
    if !rem.is_empty() {
        return Err(SignedObjectValidateError::EeCertificateParse(
            "trailing bytes after EE SubjectPublicKeyInfo DER".to_string(),
        ));
    }
    let parsed_pk = spki.parsed().map_err(|_e| {
        SignedObjectValidateError::EeCertificateParse(
            "unsupported EE public key algorithm".to_string(),
        )
    })?;
    let (rsa_public_modulus, rsa_public_exponent) = match parsed_pk {
        PublicKey::RSA(rsa) => {
            let modulus = strip_leading_zeros(rsa.modulus).to_vec();
            let exponent = strip_leading_zeros(rsa.exponent).to_vec();
            let _ = rsa.try_exponent().map_err(|_e| {
                SignedObjectValidateError::EeCertificateParse("invalid EE RSA exponent".to_string())
            })?;
            (modulus, exponent)
        }
        _ => {
            return Err(SignedObjectValidateError::EeCertificateParse(
                "unsupported EE public key algorithm".to_string(),
            ));
        }
    };

    let sia = rc
        .tbs
        .extensions
        .subject_info_access
        .as_ref()
        .ok_or(SignedObjectValidateError::EeCertificateMissingSia)?;
    let signed_object_uris: Vec<String> = match sia {
        SubjectInfoAccess::Ee(ee) => ee.signed_object_uris.clone(),
        SubjectInfoAccess::Ca(_ca) => Vec::new(),
    };
    if signed_object_uris.is_empty() {
        return Err(SignedObjectValidateError::EeCertificateMissingSignedObjectSia);
    }
    if !signed_object_uris.iter().any(|u| u.starts_with("rsync://")) {
        return Err(SignedObjectValidateError::EeCertificateSignedObjectSiaNoRsync);
    }

    Ok(ResourceEeCertificate {
        raw_der: der.to_vec(),
        subject_key_identifier: ski,
        spki_der,
        rsa_public_modulus,
        rsa_public_exponent,
        tbs_certificate_der: cert.tbs_certificate.as_ref().to_vec(),
        signature_bytes: cert.signature_value.data.to_vec(),
        key_usage_summary: summarize_ee_key_usage(&cert),
        sia_signed_object_uris: signed_object_uris,
        resource_cert: rc,
    })
}

fn summarize_ee_key_usage(cert: &X509Certificate<'_>) -> EeKeyUsageSummary {
    for ext in cert.extensions() {
        if ext.oid.as_bytes() == OID_KEY_USAGE_RAW {
            match ext.parsed_extension() {
                ParsedExtension::KeyUsage(ku) => {
                    if !ext.critical {
                        return EeKeyUsageSummary::NotCritical;
                    }
                    let ok = ku.digital_signature()
                        && !ku.key_cert_sign()
                        && !ku.crl_sign()
                        && !ku.non_repudiation()
                        && !ku.key_encipherment()
                        && !ku.data_encipherment()
                        && !ku.key_agreement()
                        && !ku.encipher_only()
                        && !ku.decipher_only();
                    return if ok {
                        EeKeyUsageSummary::DigitalSignatureOnly
                    } else {
                        EeKeyUsageSummary::InvalidBits
                    };
                }
                other => {
                    return EeKeyUsageSummary::ParseError(format!(
                        "unexpected parsed keyUsage extension: {other:?}"
                    ));
                }
            }
        }
    }

    EeKeyUsageSummary::Missing
}

fn parse_signer_info_cursor(
    mut seq: CmsReader<'_>,
) -> Result<SignerInfoParsed, SignedObjectParseError> {
    let version = seq
        .take_uint_u64()
        .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;

    let (sid_tag, sid_bytes) = seq
        .take_any()
        .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
    let sid = if (sid_tag & 0xC0) == 0x80 && (sid_tag & 0x1F) == 0 {
        SignerIdentifierParsed::SubjectKeyIdentifier(sid_bytes.to_vec())
    } else {
        SignerIdentifierParsed::Other
    };

    let digest_alg_seq = seq
        .take_sequence()
        .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
    let (digest_oid, digest_params_ok) = parse_algorithm_identifier_cursor(digest_alg_seq)?;
    let digest_algorithm = AlgorithmIdentifierParsed {
        oid: digest_oid,
        params_ok: digest_params_ok,
    };

    let mut signed_attrs_content: Option<Vec<u8>> = None;
    let mut signed_attrs_der_for_signature: Option<Vec<u8>> = None;

    if seq
        .peek_tag()
        .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?
        == 0xA0
    {
        let (tag, full_tlv, value) = seq
            .take_any_full()
            .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
        if tag != 0xA0 {
            return Err(SignedObjectParseError::Parse(
                "SignerInfo.signedAttrs must be [0] IMPLICIT".into(),
            ));
        }
        signed_attrs_content = Some(value.to_vec());
        signed_attrs_der_for_signature = Some(make_signed_attrs_der_for_signature(full_tlv)?);
    }

    let sig_alg_seq = seq
        .take_sequence()
        .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
    let (signature_oid, signature_params_ok) = parse_algorithm_identifier_cursor(sig_alg_seq)?;
    let signature_algorithm = AlgorithmIdentifierParsed {
        oid: signature_oid,
        params_ok: signature_params_ok,
    };

    let signature = seq
        .take_octet_string()
        .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?
        .to_vec();

    let unsigned_attrs_present = !seq.is_empty();

    Ok(SignerInfoParsed {
        version,
        sid,
        digest_algorithm,
        signature_algorithm,
        signed_attrs_content,
        signed_attrs_der_for_signature,
        unsigned_attrs_present,
        signature,
    })
}

fn validate_signed_data_profile(
    signed_data: SignedDataParsed,
    strict_name: bool,
) -> Result<SignedDataProfiled, SignedObjectValidateError> {
    if signed_data.version != 3 {
        return Err(SignedObjectValidateError::InvalidSignedDataVersion(
            signed_data.version,
        ));
    }

    if signed_data.digest_algorithms.len() != 1 {
        return Err(SignedObjectValidateError::InvalidDigestAlgorithmsCount(
            signed_data.digest_algorithms.len(),
        ));
    }
    let digest_alg = &signed_data.digest_algorithms[0];
    if digest_alg.oid != OID_SHA256 {
        return Err(SignedObjectValidateError::InvalidDigestAlgorithm(
            digest_alg.oid.clone(),
        ));
    }

    if signed_data.crls_present {
        return Err(SignedObjectValidateError::CrlsPresent);
    }

    let econtent = signed_data
        .encap_content_info
        .econtent
        .clone()
        .ok_or(SignedObjectValidateError::EContentMissing)?;
    if econtent.is_empty() {
        return Err(SignedObjectValidateError::EContentMissing);
    }
    let encap_content_info = EncapsulatedContentInfo {
        econtent_type: signed_data.encap_content_info.econtent_type.clone(),
        econtent: econtent.clone(),
    };

    let certs = signed_data
        .certificates
        .as_ref()
        .ok_or(SignedObjectValidateError::CertificatesMissing)?;
    if certs.len() != 1 {
        return Err(SignedObjectValidateError::InvalidCertificatesCount(
            certs.len(),
        ));
    }
    let ee = validate_ee_certificate(&certs[0], strict_name)?;

    if signed_data.signer_infos.len() != 1 {
        return Err(SignedObjectValidateError::InvalidSignerInfosCount(
            signed_data.signer_infos.len(),
        ));
    }
    let signer = &signed_data.signer_infos[0];

    if signer.version != 3 {
        return Err(SignedObjectValidateError::InvalidSignerInfoVersion(
            signer.version,
        ));
    }
    let sid_ski = match &signer.sid {
        SignerIdentifierParsed::SubjectKeyIdentifier(ski) => ski.clone(),
        SignerIdentifierParsed::Other => {
            return Err(SignedObjectValidateError::InvalidSignerIdentifier);
        }
    };

    if signer.digest_algorithm.oid != OID_SHA256 {
        return Err(SignedObjectValidateError::InvalidSignerInfoDigestAlgorithm(
            signer.digest_algorithm.oid.clone(),
        ));
    }

    let signed_attrs_content = signer
        .signed_attrs_content
        .as_deref()
        .ok_or(SignedObjectValidateError::SignedAttrsMissing)?;
    let signed_attrs_der_for_signature = signer
        .signed_attrs_der_for_signature
        .clone()
        .ok_or(SignedObjectValidateError::SignedAttrsMissing)?;
    let signed_attrs = parse_signed_attrs_implicit(signed_attrs_content)?;

    if signer.unsigned_attrs_present {
        return Err(SignedObjectValidateError::UnsignedAttrsPresent);
    }

    if !signer.signature_algorithm.params_ok {
        return Err(SignedObjectValidateError::InvalidSignatureAlgorithmParameters);
    }
    let signature_algorithm = signer.signature_algorithm.oid.clone();
    if signature_algorithm != OID_RSA_ENCRYPTION
        && signature_algorithm != OID_SHA256_WITH_RSA_ENCRYPTION
    {
        return Err(SignedObjectValidateError::InvalidSignatureAlgorithm(
            signature_algorithm,
        ));
    }

    if sid_ski != ee.subject_key_identifier {
        return Err(SignedObjectValidateError::SidSkiMismatch);
    }
    if signed_attrs.content_type != encap_content_info.econtent_type {
        return Err(SignedObjectValidateError::ContentTypeAttrMismatch {
            econtent_type: encap_content_info.econtent_type.clone(),
            attr_content_type: signed_attrs.content_type.clone(),
        });
    }

    let computed = digest::digest(&digest::SHA256, &encap_content_info.econtent);
    if computed.as_ref() != signed_attrs.message_digest.as_slice() {
        return Err(SignedObjectValidateError::MessageDigestMismatch);
    }

    Ok(SignedDataProfiled {
        version: 3,
        digest_algorithms: vec![OID_SHA256.to_string()],
        encap_content_info,
        certificates: vec![ee.clone()],
        crls_present: false,
        signer_infos: vec![SignerInfoProfiled {
            version: 3,
            sid_ski,
            digest_algorithm: OID_SHA256.to_string(),
            signature_algorithm: signer.signature_algorithm.oid.clone(),
            signed_attrs,
            unsigned_attrs_present: false,
            signature: signer.signature.clone(),
            signed_attrs_der_for_signature,
        }],
    })
}

fn parse_signed_attrs_implicit(
    input: &[u8],
) -> Result<SignedAttrsProfiled, SignedObjectValidateError> {
    let mut content_type: Option<String> = None;
    let mut message_digest: Option<Vec<u8>> = None;
    let mut signing_time: Option<Asn1TimeUtc> = None;

    fn count_elements(mut r: DerReader<'_>) -> Result<usize, String> {
        let mut n = 0usize;
        while !r.is_empty() {
            r.skip_any()?;
            n += 1;
        }
        Ok(n)
    }

    let mut remaining = DerReader::new(input);
    while !remaining.is_empty() {
        let mut attr = remaining
            .take_sequence()
            .map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;

        let oid_bytes = attr
            .take_tag(0x06)
            .map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
        let oid = oid_value_bytes_to_string(oid_bytes);

        let values_bytes = attr
            .take_tag(0x31)
            .map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
        if !attr.is_empty() {
            return Err(SignedObjectValidateError::SignedAttrsParse(
                "Attribute must be SEQUENCE of 2".into(),
            ));
        }

        let mut values = DerReader::new(values_bytes);
        let count = if values.is_empty() {
            0
        } else {
            values
                .skip_any()
                .map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
            if values.is_empty() {
                1
            } else {
                1 + count_elements(values)
                    .map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?
            }
        };
        if count != 1 {
            return Err(
                SignedObjectValidateError::InvalidSignedAttributeValuesCount { oid, count },
            );
        }

        // Re-parse the sole value.
        let mut values = DerReader::new(values_bytes);
        let (val_tag, val_bytes) = values
            .take_any()
            .map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;

        match oid.as_str() {
            OID_CMS_ATTR_CONTENT_TYPE => {
                if content_type.is_some() {
                    return Err(SignedObjectValidateError::DuplicateSignedAttribute(oid));
                }
                if val_tag != 0x06 {
                    return Err(SignedObjectValidateError::SignedAttrsParse(
                        "content-type attr value must be OBJECT IDENTIFIER".into(),
                    ));
                }
                content_type = Some(oid_value_bytes_to_string(val_bytes));
            }
            OID_CMS_ATTR_MESSAGE_DIGEST => {
                if message_digest.is_some() {
                    return Err(SignedObjectValidateError::DuplicateSignedAttribute(oid));
                }
                if val_tag != 0x04 {
                    return Err(SignedObjectValidateError::SignedAttrsParse(
                        "message-digest attr value must be OCTET STRING".into(),
                    ));
                }
                message_digest = Some(val_bytes.to_vec());
            }
            OID_CMS_ATTR_SIGNING_TIME => {
                if signing_time.is_some() {
                    return Err(SignedObjectValidateError::DuplicateSignedAttribute(oid));
                }
                signing_time = Some(parse_signing_time_value_tlv(val_tag, val_bytes)?);
            }
            _ => {
                return Err(SignedObjectValidateError::UnsupportedSignedAttribute(oid));
            }
        }
    }

    Ok(SignedAttrsProfiled {
        content_type: content_type
            .ok_or(SignedObjectValidateError::SignedAttrsContentTypeMissing)?,
        message_digest: message_digest
            .ok_or(SignedObjectValidateError::SignedAttrsMessageDigestMissing)?,
        signing_time: signing_time
            .ok_or(SignedObjectValidateError::SignedAttrsSigningTimeMissing)?,
        other_attrs_present: false,
    })
}

fn parse_signing_time_value_tlv(
    tag: u8,
    value: &[u8],
) -> Result<Asn1TimeUtc, SignedObjectValidateError> {
    match tag {
        0x17 => Ok(Asn1TimeUtc {
            utc: parse_utctime(value)?,
            encoding: Asn1TimeEncoding::UtcTime,
        }),
        0x18 => Ok(Asn1TimeUtc {
            utc: parse_generalized_time(value)?,
            encoding: Asn1TimeEncoding::GeneralizedTime,
        }),
        _ => Err(SignedObjectValidateError::InvalidSigningTimeValue),
    }
}

fn make_signed_attrs_der_for_signature(full_tlv: &[u8]) -> Result<Vec<u8>, SignedObjectParseError> {
    // We need the DER encoding of SignedAttributes (SET OF Attribute) as signature input.
    // The SignedAttributes field in SignerInfo is `[0] IMPLICIT`, so the on-wire bytes start with
    // a context-specific constructed tag (0xA0 for tag 0). For signature verification, this tag
    // is replaced with the universal SET tag (0x31), leaving length+content unchanged.
    //
    let mut cs_der = full_tlv.to_vec();
    if cs_der.is_empty() {
        return Err(SignedObjectParseError::Parse(
            "signedAttrs encoding is empty".into(),
        ));
    }
    // The first byte should be the context-specific tag (0xA0) for [0] constructed.
    // Replace it with universal SET (0x31) for signature input.
    cs_der[0] = 0x31;
    Ok(cs_der)
}

fn take_oid_string(seq: &mut CmsReader<'_>) -> Result<String, SignedObjectParseError> {
    let oid = seq
        .take_tag(0x06)
        .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
    Ok(oid_value_bytes_to_string(oid))
}

fn oid_value_bytes_to_string(oid_value: &[u8]) -> String {
    if oid_value == OID_SHA256_RAW {
        return OID_SHA256.to_string();
    }
    if oid_value == OID_SIGNED_DATA_RAW {
        return OID_SIGNED_DATA.to_string();
    }
    if oid_value == OID_CMS_ATTR_CONTENT_TYPE_RAW {
        return OID_CMS_ATTR_CONTENT_TYPE.to_string();
    }
    if oid_value == OID_CMS_ATTR_MESSAGE_DIGEST_RAW {
        return OID_CMS_ATTR_MESSAGE_DIGEST.to_string();
    }
    if oid_value == OID_CMS_ATTR_SIGNING_TIME_RAW {
        return OID_CMS_ATTR_SIGNING_TIME.to_string();
    }
    if oid_value == OID_RSA_ENCRYPTION_RAW {
        return OID_RSA_ENCRYPTION.to_string();
    }
    if oid_value == OID_SHA256_WITH_RSA_ENCRYPTION_RAW {
        return OID_SHA256_WITH_RSA_ENCRYPTION.to_string();
    }
    if oid_value == OID_CT_RPKI_MANIFEST_RAW {
        return OID_CT_RPKI_MANIFEST.to_string();
    }
    if oid_value == OID_CT_ROUTE_ORIGIN_AUTHZ_RAW {
        return OID_CT_ROUTE_ORIGIN_AUTHZ.to_string();
    }
    if oid_value == OID_CT_ASPA_RAW {
        return OID_CT_ASPA.to_string();
    }
    decode_oid_to_dotted_string(oid_value)
}

fn decode_oid_to_dotted_string(value: &[u8]) -> String {
    if value.is_empty() {
        return "<empty-oid>".into();
    }
    let first = value[0];
    let a = (first / 40) as u32;
    let b = (first % 40) as u32;
    let mut out = String::new();
    out.push_str(&a.to_string());
    out.push('.');
    out.push_str(&b.to_string());

    let mut idx = 1usize;
    while idx < value.len() {
        let mut v: u32 = 0;
        loop {
            if idx >= value.len() {
                out.push_str(".<truncated>");
                return out;
            }
            let byte = value[idx];
            idx += 1;
            v = (v << 7) | (byte as u32 & 0x7F);
            if (byte & 0x80) == 0 {
                break;
            }
        }
        out.push('.');
        out.push_str(&v.to_string());
    }
    out
}

fn parse_algorithm_identifier_cursor(
    mut seq: CmsReader<'_>,
) -> Result<(String, bool), SignedObjectParseError> {
    if seq.is_empty() {
        return Err(SignedObjectParseError::Parse(
            "AlgorithmIdentifier must be SEQUENCE of 1..2".into(),
        ));
    }
    let oid = take_oid_string(&mut seq)?;
    let params_ok = if seq.is_empty() {
        true
    } else {
        let (tag, value) = seq
            .take_any()
            .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
        tag == 0x05 && value.is_empty()
    };
    if !seq.is_empty() {
        return Err(SignedObjectParseError::Parse(
            "AlgorithmIdentifier must be SEQUENCE of 1..2".into(),
        ));
    }
    Ok((oid, params_ok))
}

fn parse_utctime(value: &[u8]) -> Result<time::OffsetDateTime, SignedObjectValidateError> {
    let s = std::str::from_utf8(value)
        .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
    if !s.ends_with('Z') {
        return Err(SignedObjectValidateError::InvalidSigningTimeValue);
    }
    let digits = &s[..s.len() - 1];
    if digits.len() != 10 && digits.len() != 12 {
        return Err(SignedObjectValidateError::InvalidSigningTimeValue);
    }
    if !digits.as_bytes().iter().all(|b| b.is_ascii_digit()) {
        return Err(SignedObjectValidateError::InvalidSigningTimeValue);
    }
    let yy: i32 = digits[0..2]
        .parse()
        .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
    let year = if yy <= 49 { 2000 + yy } else { 1900 + yy };
    let mon: u8 = digits[2..4]
        .parse()
        .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
    let day: u8 = digits[4..6]
        .parse()
        .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
    let hour: u8 = digits[6..8]
        .parse()
        .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
    let min: u8 = digits[8..10]
        .parse()
        .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
    let sec: u8 = if digits.len() == 12 {
        digits[10..12]
            .parse()
            .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?
    } else {
        0
    };
    let month = time::Month::try_from(mon)
        .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
    let date = time::Date::from_calendar_date(year, month, day)
        .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
    let time = time::Time::from_hms(hour, min, sec)
        .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
    Ok(time::OffsetDateTime::new_utc(date, time))
}

fn parse_generalized_time(value: &[u8]) -> Result<time::OffsetDateTime, SignedObjectValidateError> {
    let s = std::str::from_utf8(value)
        .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
    if !s.ends_with('Z') {
        return Err(SignedObjectValidateError::InvalidSigningTimeValue);
    }
    let digits = &s[..s.len() - 1];
    if digits.len() != 12 && digits.len() != 14 {
        return Err(SignedObjectValidateError::InvalidSigningTimeValue);
    }
    if !digits.as_bytes().iter().all(|b| b.is_ascii_digit()) {
        return Err(SignedObjectValidateError::InvalidSigningTimeValue);
    }
    let year: i32 = digits[0..4]
        .parse()
        .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
    let mon: u8 = digits[4..6]
        .parse()
        .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
    let day: u8 = digits[6..8]
        .parse()
        .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
    let hour: u8 = digits[8..10]
        .parse()
        .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
    let min: u8 = digits[10..12]
        .parse()
        .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
    let sec: u8 = if digits.len() == 14 {
        digits[12..14]
            .parse()
            .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?
    } else {
        0
    };
    let month = time::Month::try_from(mon)
        .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
    let date = time::Date::from_calendar_date(year, month, day)
        .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
    let time = time::Time::from_hms(hour, min, sec)
        .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
    Ok(time::OffsetDateTime::new_utc(date, time))
}

fn strip_leading_zeros(bytes: &[u8]) -> &[u8] {
    let mut idx = 0;
    while idx < bytes.len() && bytes[idx] == 0 {
        idx += 1;
    }
    if idx == bytes.len() {
        &bytes[bytes.len() - 1..]
    } else {
        &bytes[idx..]
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn strict_cms_der_rejects_constructed_octet_string_fixture() {
        let der = std::fs::read(std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
            "tests/fixtures/repository/ca.rg.net/rpki/RGnet-OU/bW-_qXU9uNhGQz21NR2ansB8lr0.mft",
        ))
        .expect("read manifest fixture");
        let parsed = RpkiSignedObject::parse_der(&der).expect("parse fixture");
        let econtent = parsed
            .signed_data
            .encap_content_info
            .econtent
            .clone()
            .expect("fixture eContent");
        assert_eq!(
            parsed.signed_data.encap_content_info.econtent.as_deref(),
            Some(econtent.as_slice())
        );

        let primitive_octets = der_tlv(0x04, &econtent);
        let constructed_octets = same_size_constructed_octet_string(&primitive_octets, &econtent);
        let mutated = replace_first_subslice(&der, &primitive_octets, &constructed_octets)
            .expect("replace eContent OCTET STRING");
        let compatible = RpkiSignedObject::parse_der(&mutated).expect("BER-compatible parse");
        assert!(
            econtent.starts_with(
                compatible
                    .signed_data
                    .encap_content_info
                    .econtent
                    .as_deref()
                    .expect("compatible eContent")
            )
        );
        let err = RpkiSignedObject::parse_der_strict_cms(&mutated)
            .expect_err("DER strict rejects constructed OCTET STRING");
        assert!(err.to_string().contains("DER"), "{err}");
    }

    fn replace_first_subslice(input: &[u8], from: &[u8], to: &[u8]) -> Option<Vec<u8>> {
        let pos = input
            .windows(from.len())
            .position(|candidate| candidate == from)?;
        let mut out = Vec::with_capacity(input.len() - from.len() + to.len());
        out.extend_from_slice(&input[..pos]);
        out.extend_from_slice(to);
        out.extend_from_slice(&input[pos + from.len()..]);
        Some(out)
    }

    fn same_size_constructed_octet_string(primitive: &[u8], content: &[u8]) -> Vec<u8> {
        assert_eq!(primitive[0], 0x04);
        let header_len = tlv_header_len(primitive);
        let outer_value_len = primitive.len() - header_len;
        let child_len = (0..=outer_value_len)
            .rev()
            .find(|candidate| 1 + len_len(*candidate) + *candidate == outer_value_len)
            .expect("find child length");
        let mut out = primitive[..header_len].to_vec();
        out[0] = 0x24;
        out.extend(der_tlv(0x04, &content[..child_len]));
        assert_eq!(out.len(), primitive.len());
        out
    }

    fn tlv_header_len(tlv: &[u8]) -> usize {
        if tlv[1] & 0x80 == 0 {
            2
        } else {
            2 + (tlv[1] & 0x7F) as usize
        }
    }

    fn len_len(len: usize) -> usize {
        if len < 0x80 {
            return 1;
        }
        let mut value = len;
        let mut n = 0usize;
        while value > 0 {
            n += 1;
            value >>= 8;
        }
        1 + n
    }

    fn der_tlv(tag: u8, value: &[u8]) -> Vec<u8> {
        let mut out = vec![tag];
        encode_len(value.len(), &mut out);
        out.extend_from_slice(value);
        out
    }

    fn encode_len(len: usize, out: &mut Vec<u8>) {
        if len < 0x80 {
            out.push(len as u8);
            return;
        }
        let mut bytes = Vec::new();
        let mut value = len;
        while value > 0 {
            bytes.push((value & 0xFF) as u8);
            value >>= 8;
        }
        bytes.reverse();
        out.push(0x80 | bytes.len() as u8);
        out.extend(bytes);
    }
}
