use std::sync::OnceLock;
use pyo3::prelude::*;
use pyo3::types::PyBytes;
use synta::{Decoder, Encoding};
use crate::types::PyObjectIdentifier;
#[pyclass(frozen, name = "KEMRecipientInfo")]
pub struct PyKEMRecipientInfo {
_data: Py<PyBytes>,
raw: &'static [u8],
inner: OnceLock<Box<synta_certificate::cms_kem_types::KEMRecipientInfo<'static>>>,
kem_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
kdf_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
key_encryption_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
kem_algorithm_params_cache: OnceLock<Option<Py<PyBytes>>>,
kdf_algorithm_params_cache: OnceLock<Option<Py<PyBytes>>>,
key_encryption_algorithm_params_cache: OnceLock<Option<Py<PyBytes>>>,
recipient_id_cache: OnceLock<Py<PyBytes>>,
kem_ciphertext_cache: OnceLock<Py<PyBytes>>,
encrypted_key_cache: OnceLock<Py<PyBytes>>,
ukm_cache: OnceLock<Option<Py<PyBytes>>>,
}
impl PyKEMRecipientInfo {
fn kemri(&self) -> PyResult<&synta_certificate::cms_kem_types::KEMRecipientInfo<'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!(
"KEMRecipientInfo DER decode failed: {e}"
))
})?;
let _ = self.inner.set(Box::new(decoded));
Ok(self.inner.get().unwrap().as_ref())
}
}
#[pymethods]
impl PyKEMRecipientInfo {
#[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(),
kem_algorithm_oid_cache: OnceLock::new(),
kdf_algorithm_oid_cache: OnceLock::new(),
key_encryption_algorithm_oid_cache: OnceLock::new(),
kem_algorithm_params_cache: OnceLock::new(),
kdf_algorithm_params_cache: OnceLock::new(),
key_encryption_algorithm_params_cache: OnceLock::new(),
recipient_id_cache: OnceLock::new(),
kem_ciphertext_cache: OnceLock::new(),
encrypted_key_cache: OnceLock::new(),
ukm_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.kemri()?.version.as_i64().unwrap_or(0))
}
#[getter]
fn recipient_id<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if let Some(cached) = self.recipient_id_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let py_bytes = PyBytes::new(py, self.kemri()?.rid.as_bytes()).unbind();
let _ = self.recipient_id_cache.set(py_bytes.clone_ref(py));
Ok(py_bytes.into_bound(py))
}
#[getter]
fn kem_algorithm_oid<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.kem_algorithm_oid_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(self.kemri()?.kem.algorithm.clone()),
)?;
let _ = self.kem_algorithm_oid_cache.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn kem_algorithm_params<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.kem_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.kemri()?.kem.parameters.as_ref())?;
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.kem_algorithm_params_cache.set(to_store);
Ok(computed)
}
#[getter]
fn kem_ciphertext<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if let Some(cached) = self.kem_ciphertext_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let py_bytes = PyBytes::new(py, self.kemri()?.kemct.as_bytes()).unbind();
let _ = self.kem_ciphertext_cache.set(py_bytes.clone_ref(py));
Ok(py_bytes.into_bound(py))
}
#[getter]
fn kdf_algorithm_oid<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.kdf_algorithm_oid_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(self.kemri()?.kdf.algorithm.clone()),
)?;
let _ = self.kdf_algorithm_oid_cache.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn kdf_algorithm_params<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.kdf_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.kemri()?.kdf.parameters.as_ref())?;
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.kdf_algorithm_params_cache.set(to_store);
Ok(computed)
}
#[getter]
fn kek_length(&self) -> PyResult<i64> {
Ok(self.kemri()?.kek_length.as_i64().unwrap_or(0))
}
#[getter]
fn ukm<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.ukm_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.kemri()?
.ukm
.as_ref()
.map(|u| PyBytes::new(py, u.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.ukm_cache.set(to_store);
Ok(computed)
}
#[getter]
fn key_encryption_algorithm_oid<'py>(
&self,
py: Python<'py>,
) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.key_encryption_algorithm_oid_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(self.kemri()?.wrap.algorithm.clone()),
)?;
let _ = self
.key_encryption_algorithm_oid_cache
.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn key_encryption_algorithm_params<'py>(
&self,
py: Python<'py>,
) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.key_encryption_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.kemri()?.wrap.parameters.as_ref())?;
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.key_encryption_algorithm_params_cache.set(to_store);
Ok(computed)
}
#[getter]
fn encrypted_key<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if let Some(cached) = self.encrypted_key_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let py_bytes = PyBytes::new(py, self.kemri()?.encrypted_key.as_bytes()).unbind();
let _ = self.encrypted_key_cache.set(py_bytes.clone_ref(py));
Ok(py_bytes.into_bound(py))
}
fn __repr__(&self) -> PyResult<String> {
let kemri = self.kemri()?;
Ok(format!(
"KEMRecipientInfo(kem={}, kdf={}, kek_length={})",
kemri.kem.algorithm,
kemri.kdf.algorithm,
kemri.kek_length.as_i64().unwrap_or(0),
))
}
}
#[pyclass(frozen, name = "CMSORIforKEMOtherInfo")]
pub struct PyCMSORIforKEMOtherInfo {
_data: Py<PyBytes>,
raw: &'static [u8],
inner: OnceLock<Box<synta_certificate::cms_kem_types::CMSORIforKEMOtherInfo<'static>>>,
key_encryption_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
key_encryption_algorithm_params_cache: OnceLock<Option<Py<PyBytes>>>,
ukm_cache: OnceLock<Option<Py<PyBytes>>>,
}
impl PyCMSORIforKEMOtherInfo {
fn info(&self) -> PyResult<&synta_certificate::cms_kem_types::CMSORIforKEMOtherInfo<'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!(
"CMSORIforKEMOtherInfo DER decode failed: {e}"
))
})?;
let _ = self.inner.set(Box::new(decoded));
Ok(self.inner.get().unwrap().as_ref())
}
}
#[pymethods]
impl PyCMSORIforKEMOtherInfo {
#[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(),
key_encryption_algorithm_oid_cache: OnceLock::new(),
key_encryption_algorithm_params_cache: OnceLock::new(),
ukm_cache: OnceLock::new(),
})
}
fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
self._data.clone_ref(py).into_bound(py)
}
#[getter]
fn key_encryption_algorithm_oid<'py>(
&self,
py: Python<'py>,
) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.key_encryption_algorithm_oid_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(self.info()?.wrap.algorithm.clone()),
)?;
let _ = self
.key_encryption_algorithm_oid_cache
.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn key_encryption_algorithm_params<'py>(
&self,
py: Python<'py>,
) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.key_encryption_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.info()?.wrap.parameters.as_ref())?;
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.key_encryption_algorithm_params_cache.set(to_store);
Ok(computed)
}
#[getter]
fn kek_length(&self) -> PyResult<i64> {
Ok(self.info()?.kek_length.as_i64().unwrap_or(0))
}
#[getter]
fn ukm<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.ukm_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.info()?
.ukm
.as_ref()
.map(|u| PyBytes::new(py, u.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.ukm_cache.set(to_store);
Ok(computed)
}
fn __repr__(&self) -> PyResult<String> {
let info = self.info()?;
Ok(format!(
"CMSORIforKEMOtherInfo(wrap={}, kek_length={})",
info.wrap.algorithm,
info.kek_length.as_i64().unwrap_or(0),
))
}
}