synta-python 0.2.5

Python extension module for the synta ASN.1 library
Documentation
//! Python bindings for the RFC 9399 Logotype certificate extension builder.
//!
//! Exposes [`PyLogotypeExtnBuilder`] for constructing DER-encoded
//! ``LogotypeExtn`` structures (OID ``1.3.6.1.5.5.7.1.12``).

use pyo3::prelude::*;
use pyo3::types::PyBytes;

// ── PyLogotypeExtnBuilder ─────────────────────────────────────────────────────

/// Python-facing wrapper for [`synta_certificate::LogotypeExtnBuilder`].
///
/// Builds a DER-encoded ``LogotypeExtn`` SEQUENCE (RFC 9399).
///
/// The extension value goes inside the OCTET STRING wrapper of an X.509
/// ``Extension`` SEQUENCE for OID ``1.3.6.1.5.5.7.1.12``.
///
/// At least one of ``communityLogos``, ``issuerLogo``, ``subjectLogo``, or
/// ``otherLogos`` must be set before calling :meth:`build`.
///
/// Each logo is described by four parameters passed as keyword arguments or
/// positional values to the setter methods:
///
/// - ``media_type`` — MIME type string, e.g. ``"image/png"``
/// - ``hash_alg_oid`` — hash algorithm OID as a list of arc integers,
///   e.g. ``list(synta.oids.SHA256.components())``
/// - ``hash_value`` — raw hash bytes of the logotype object
/// - ``uri`` — URI from which the logotype can be retrieved
///
/// Example::
///
///     import hashlib, synta
///
///     img_hash = hashlib.sha256(open("logo.png", "rb").read()).digest()
///     sha256_comps = list(synta.oids.SHA256.components())
///
///     extn_der = (
///         synta.LogotypeExtnBuilder()
///         .subject_logo_direct(
///             media_type="image/png",
///             hash_alg_oid=sha256_comps,
///             hash_value=img_hash,
///             uri="https://www.example.com/logo.png",
///         )
///         .build()
///     )
#[pyclass(name = "LogotypeExtnBuilder")]
pub struct PyLogotypeExtnBuilder {
    inner: synta_certificate::LogotypeExtnBuilder,
    built: bool,
}

#[pymethods]
impl PyLogotypeExtnBuilder {
    /// Create a new, empty ``LogotypeExtnBuilder``.
    #[new]
    fn new() -> Self {
        Self {
            inner: synta_certificate::LogotypeExtnBuilder::new(),
            built: false,
        }
    }

    /// Set the ``issuerLogo`` to a direct ``LogotypeData`` with one image.
    ///
    /// :param media_type: MIME type string (IA5String), e.g. ``"image/png"``.
    /// :param hash_alg_oid: hash algorithm OID arc components as a list of
    ///     ints, e.g. ``list(synta.oids.SHA256.components())``.
    /// :param hash_value: raw hash bytes of the logotype object.
    /// :param uri: URI from which the logotype can be retrieved (IA5String).
    /// :raises ValueError: if any value is invalid (deferred to :meth:`build`).
    #[pyo3(signature = (media_type, hash_alg_oid, hash_value, uri))]
    fn issuer_logo_direct<'py>(
        slf: Bound<'py, Self>,
        media_type: &str,
        hash_alg_oid: Vec<u32>,
        hash_value: &[u8],
        uri: &str,
    ) -> Bound<'py, Self> {
        let spec = synta_certificate::LogotypeDetailsSpec {
            media_type,
            hash_alg_oid: &hash_alg_oid,
            hash_value,
            uri,
        };
        {
            let mut guard = slf.borrow_mut();
            let old = std::mem::replace(
                &mut guard.inner,
                synta_certificate::LogotypeExtnBuilder::new(),
            );
            guard.inner = old.issuer_logo_direct(spec);
        }
        slf
    }

    /// Set the ``subjectLogo`` to a direct ``LogotypeData`` with one image.
    ///
    /// :param media_type: MIME type string (IA5String), e.g. ``"image/png"``.
    /// :param hash_alg_oid: hash algorithm OID arc components as a list of
    ///     ints, e.g. ``list(synta.oids.SHA256.components())``.
    /// :param hash_value: raw hash bytes of the logotype object.
    /// :param uri: URI from which the logotype can be retrieved (IA5String).
    /// :raises ValueError: if any value is invalid (deferred to :meth:`build`).
    #[pyo3(signature = (media_type, hash_alg_oid, hash_value, uri))]
    fn subject_logo_direct<'py>(
        slf: Bound<'py, Self>,
        media_type: &str,
        hash_alg_oid: Vec<u32>,
        hash_value: &[u8],
        uri: &str,
    ) -> Bound<'py, Self> {
        let spec = synta_certificate::LogotypeDetailsSpec {
            media_type,
            hash_alg_oid: &hash_alg_oid,
            hash_value,
            uri,
        };
        {
            let mut guard = slf.borrow_mut();
            let old = std::mem::replace(
                &mut guard.inner,
                synta_certificate::LogotypeExtnBuilder::new(),
            );
            guard.inner = old.subject_logo_direct(spec);
        }
        slf
    }

    /// Add one community logo (direct ``LogotypeData`` with one image) to
    /// the ``communityLogos`` list.
    ///
    /// :param media_type: MIME type string (IA5String), e.g. ``"image/png"``.
    /// :param hash_alg_oid: hash algorithm OID arc components as a list of
    ///     ints.
    /// :param hash_value: raw hash bytes of the logotype object.
    /// :param uri: URI from which the logotype can be retrieved (IA5String).
    /// :raises ValueError: if any value is invalid (deferred to :meth:`build`).
    #[pyo3(signature = (media_type, hash_alg_oid, hash_value, uri))]
    fn add_community_logo_direct<'py>(
        slf: Bound<'py, Self>,
        media_type: &str,
        hash_alg_oid: Vec<u32>,
        hash_value: &[u8],
        uri: &str,
    ) -> Bound<'py, Self> {
        let spec = synta_certificate::LogotypeDetailsSpec {
            media_type,
            hash_alg_oid: &hash_alg_oid,
            hash_value,
            uri,
        };
        {
            let mut guard = slf.borrow_mut();
            let old = std::mem::replace(
                &mut guard.inner,
                synta_certificate::LogotypeExtnBuilder::new(),
            );
            guard.inner = old.add_community_logo_direct(spec);
        }
        slf
    }

    /// Add an ``OtherLogotypeInfo`` entry with a direct ``LogotypeData``.
    ///
    /// :param logotype_type_oid: OID arc components identifying the logotype
    ///     type.
    /// :param media_type: MIME type string (IA5String).
    /// :param hash_alg_oid: hash algorithm OID arc components as a list of
    ///     ints.
    /// :param hash_value: raw hash bytes of the logotype object.
    /// :param uri: URI from which the logotype can be retrieved (IA5String).
    /// :raises ValueError: if any value is invalid (deferred to :meth:`build`).
    #[pyo3(signature = (logotype_type_oid, media_type, hash_alg_oid, hash_value, uri))]
    fn add_other_logo_direct<'py>(
        slf: Bound<'py, Self>,
        logotype_type_oid: Vec<u32>,
        media_type: &str,
        hash_alg_oid: Vec<u32>,
        hash_value: &[u8],
        uri: &str,
    ) -> Bound<'py, Self> {
        let spec = synta_certificate::LogotypeDetailsSpec {
            media_type,
            hash_alg_oid: &hash_alg_oid,
            hash_value,
            uri,
        };
        {
            let mut guard = slf.borrow_mut();
            let old = std::mem::replace(
                &mut guard.inner,
                synta_certificate::LogotypeExtnBuilder::new(),
            );
            guard.inner = old.add_other_logo_direct(&logotype_type_oid, spec);
        }
        slf
    }

    /// Build the DER-encoded ``LogotypeExtn`` SEQUENCE.
    ///
    /// :returns: DER bytes of the ``LogotypeExtn``.
    /// :raises ValueError: if no logotype fields were set or if DER encoding
    ///     fails.
    fn build<'py>(&mut self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
        if self.built {
            return Err(pyo3::exceptions::PyValueError::new_err(
                "build() has already been called; create a new builder",
            ));
        }
        self.built = true;
        let inner = std::mem::replace(
            &mut self.inner,
            synta_certificate::LogotypeExtnBuilder::new(),
        );
        let der = inner
            .build()
            .map_err(pyo3::exceptions::PyValueError::new_err)?;
        Ok(PyBytes::new(py, &der))
    }

    fn __repr__(&self) -> String {
        "LogotypeExtnBuilder()".to_string()
    }
}

// ── register ──────────────────────────────────────────────────────────────────

/// Register logotype builder classes into the given module.
pub(super) fn register_logotype_classes(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_class::<PyLogotypeExtnBuilder>()?;
    Ok(())
}