use std::sync::OnceLock;
use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyList};
use synta::traits::Encode;
use synta::{Decoder, Encoding};
use crate::types::PyObjectIdentifier;
#[pyclass(frozen, name = "SignedData")]
pub struct PySignedData {
_data: Py<PyBytes>,
raw: &'static [u8],
inner: OnceLock<Box<synta_certificate::cms_rfc5652_types::SignedData<'static>>>,
encap_content_type_cache: OnceLock<Py<PyObjectIdentifier>>,
encap_content_cache: OnceLock<Option<Py<PyBytes>>>,
certificates_cache: OnceLock<Option<Py<PyBytes>>>,
crls_cache: OnceLock<Option<Py<PyBytes>>>,
signer_infos_cache: OnceLock<Py<PyList>>,
}
impl PySignedData {
fn signed_data(&self) -> PyResult<&synta_certificate::cms_rfc5652_types::SignedData<'static>> {
if let Some(v) = self.inner.get() {
return Ok(v.as_ref());
}
let mut decoder = Decoder::new(self.raw, Encoding::Ber);
let decoded = decoder.decode().map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("SignedData BER decode failed: {e}"))
})?;
let _ = self.inner.set(Box::new(decoded));
Ok(self.inner.get().unwrap().as_ref())
}
}
#[pymethods]
impl PySignedData {
#[staticmethod]
fn from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
let py_bytes = data.unbind();
let raw: &'static [u8] = unsafe {
let s = py_bytes.bind(py).as_bytes();
std::slice::from_raw_parts(s.as_ptr(), s.len())
};
{
let mut d = Decoder::new(raw, Encoding::Ber);
d.read_tag()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
d.read_length()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
}
Ok(Self {
_data: py_bytes,
raw,
inner: OnceLock::new(),
encap_content_type_cache: OnceLock::new(),
encap_content_cache: OnceLock::new(),
certificates_cache: OnceLock::new(),
crls_cache: OnceLock::new(),
signer_infos_cache: OnceLock::new(),
})
}
fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
self._data.clone_ref(py).into_bound(py)
}
#[getter]
fn version(&self) -> PyResult<i64> {
Ok(self.signed_data()?.version.as_i64().unwrap_or(0))
}
#[getter]
fn encap_content_type<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.encap_content_type_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(
self.signed_data()?
.encap_content_info
.e_content_type
.clone(),
),
)?;
let _ = self.encap_content_type_cache.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn encap_content<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.encap_content_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.signed_data()?
.encap_content_info
.e_content
.as_ref()
.map(|c| PyBytes::new(py, c.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.encap_content_cache.set(to_store);
Ok(computed)
}
#[getter]
fn certificates<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.certificates_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.signed_data()?
.certificates
.as_ref()
.map(|c| PyBytes::new(py, c.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.certificates_cache.set(to_store);
Ok(computed)
}
#[getter]
fn crls<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.crls_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.signed_data()?
.crls
.as_ref()
.map(|c| PyBytes::new(py, c.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.crls_cache.set(to_store);
Ok(computed)
}
#[getter]
fn signer_infos<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyList>> {
if let Some(cached) = self.signer_infos_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let list = PyList::empty(py);
for si in self.signed_data()?.signer_infos.elements() {
let mut enc = synta::Encoder::new(Encoding::Der);
si.encode(&mut enc)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
let der = enc
.finish()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
let pybytes = PyBytes::new(py, &der).unbind();
let raw_static: &'static [u8] = unsafe {
let s = pybytes.bind(py).as_bytes();
std::slice::from_raw_parts(s.as_ptr(), s.len())
};
let si_obj = Py::new(
py,
PySignerInfo {
_data: pybytes,
raw: raw_static,
inner: OnceLock::new(),
sid_cache: OnceLock::new(),
digest_algorithm_oid_cache: OnceLock::new(),
digest_algorithm_params_cache: OnceLock::new(),
signature_algorithm_oid_cache: OnceLock::new(),
signature_algorithm_params_cache: OnceLock::new(),
signature_cache: OnceLock::new(),
signed_attrs_cache: OnceLock::new(),
unsigned_attrs_cache: OnceLock::new(),
},
)?;
list.append(si_obj.into_bound(py))?;
}
let list_unbound = list.unbind();
let _ = self.signer_infos_cache.set(list_unbound.clone_ref(py));
Ok(list_unbound.into_bound(py))
}
fn __repr__(&self) -> PyResult<String> {
let sd = self.signed_data()?;
Ok(format!(
"SignedData(version={}, signer_count={})",
sd.version.as_i64().unwrap_or(0),
sd.signer_infos.len(),
))
}
}
#[pyclass(frozen, name = "SignerInfo")]
pub struct PySignerInfo {
pub(super) _data: Py<PyBytes>,
pub(super) raw: &'static [u8],
pub(super) inner: OnceLock<Box<synta_certificate::cms_rfc5652_types::SignerInfo<'static>>>,
pub(super) sid_cache: OnceLock<Py<PyBytes>>,
pub(super) digest_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
pub(super) digest_algorithm_params_cache: OnceLock<Option<Py<PyBytes>>>,
pub(super) signature_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
pub(super) signature_algorithm_params_cache: OnceLock<Option<Py<PyBytes>>>,
pub(super) signature_cache: OnceLock<Py<PyBytes>>,
pub(super) signed_attrs_cache: OnceLock<Option<Py<PyBytes>>>,
pub(super) unsigned_attrs_cache: OnceLock<Option<Py<PyBytes>>>,
}
impl PySignerInfo {
fn signer_info(&self) -> PyResult<&synta_certificate::cms_rfc5652_types::SignerInfo<'static>> {
if let Some(v) = self.inner.get() {
return Ok(v.as_ref());
}
let mut decoder = Decoder::new(self.raw, Encoding::Der);
let decoded = decoder.decode().map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("SignerInfo DER decode failed: {e}"))
})?;
let _ = self.inner.set(Box::new(decoded));
Ok(self.inner.get().unwrap().as_ref())
}
}
#[pymethods]
impl PySignerInfo {
#[staticmethod]
fn from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
let py_bytes = data.unbind();
let raw: &'static [u8] = unsafe {
let s = py_bytes.bind(py).as_bytes();
std::slice::from_raw_parts(s.as_ptr(), s.len())
};
{
let mut d = Decoder::new(raw, Encoding::Der);
d.read_tag()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
d.read_length()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
}
Ok(Self {
_data: py_bytes,
raw,
inner: OnceLock::new(),
sid_cache: OnceLock::new(),
digest_algorithm_oid_cache: OnceLock::new(),
digest_algorithm_params_cache: OnceLock::new(),
signature_algorithm_oid_cache: OnceLock::new(),
signature_algorithm_params_cache: OnceLock::new(),
signature_cache: OnceLock::new(),
signed_attrs_cache: OnceLock::new(),
unsigned_attrs_cache: OnceLock::new(),
})
}
fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
self._data.clone_ref(py).into_bound(py)
}
#[getter]
fn version(&self) -> PyResult<i64> {
Ok(self.signer_info()?.version.as_i64().unwrap_or(0))
}
#[getter]
fn sid<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if let Some(cached) = self.sid_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let py_bytes = PyBytes::new(py, self.signer_info()?.sid.as_bytes()).unbind();
let _ = self.sid_cache.set(py_bytes.clone_ref(py));
Ok(py_bytes.into_bound(py))
}
#[getter]
fn digest_algorithm_oid<'py>(
&self,
py: Python<'py>,
) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.digest_algorithm_oid_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(self.signer_info()?.digest_algorithm.algorithm.clone()),
)?;
let _ = self.digest_algorithm_oid_cache.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn digest_algorithm_params<'py>(
&self,
py: Python<'py>,
) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.digest_algorithm_params_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = super::encode_element_opt(
py,
self.signer_info()?.digest_algorithm.parameters.as_ref(),
)?;
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.digest_algorithm_params_cache.set(to_store);
Ok(computed)
}
#[getter]
fn signature_algorithm_oid<'py>(
&self,
py: Python<'py>,
) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.signature_algorithm_oid_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(self.signer_info()?.signature_algorithm.algorithm.clone()),
)?;
let _ = self.signature_algorithm_oid_cache.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn signature_algorithm_params<'py>(
&self,
py: Python<'py>,
) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.signature_algorithm_params_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = super::encode_element_opt(
py,
self.signer_info()?.signature_algorithm.parameters.as_ref(),
)?;
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.signature_algorithm_params_cache.set(to_store);
Ok(computed)
}
#[getter]
fn signature<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if let Some(cached) = self.signature_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let py_bytes = PyBytes::new(py, self.signer_info()?.signature.as_bytes()).unbind();
let _ = self.signature_cache.set(py_bytes.clone_ref(py));
Ok(py_bytes.into_bound(py))
}
#[getter]
fn signed_attrs<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.signed_attrs_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.signer_info()?
.signed_attrs
.as_ref()
.map(|a| PyBytes::new(py, a.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.signed_attrs_cache.set(to_store);
Ok(computed)
}
#[getter]
fn unsigned_attrs<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.unsigned_attrs_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.signer_info()?
.unsigned_attrs
.as_ref()
.map(|a| PyBytes::new(py, a.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.unsigned_attrs_cache.set(to_store);
Ok(computed)
}
fn __repr__(&self) -> PyResult<String> {
let si = self.signer_info()?;
Ok(format!(
"SignerInfo(version={}, digest_algorithm={})",
si.version.as_i64().unwrap_or(0),
si.digest_algorithm.algorithm,
))
}
}