use std::str::FromStr;
use std::sync::OnceLock;
use pyo3::prelude::*;
use pyo3::types::{PyString, PyTuple};
use synta::ObjectIdentifier;
pub mod elements;
pub mod primitives;
pub mod strings;
pub use elements::{element_to_pyobject, PyRawElement, PyTaggedElement};
pub use primitives::{
PyBitString, PyBoolean, PyGeneralizedTime, PyInteger, PyNull, PyOctetString, PyReal, PyUtcTime,
};
pub use strings::{
PyBmpString, PyGeneralString, PyIA5String, PyNumericString, PyPrintableString, PyTeletexString,
PyUniversalString, PyUtf8String, PyVisibleString,
};
#[pyclass(frozen, name = "ObjectIdentifier")]
#[derive(Debug, Clone)]
pub struct PyObjectIdentifier {
pub(crate) inner: ObjectIdentifier,
dotted_cache: OnceLock<String>,
}
impl PyObjectIdentifier {
pub fn from_oid(inner: ObjectIdentifier) -> Self {
Self {
inner,
dotted_cache: OnceLock::new(),
}
}
}
#[pymethods]
impl PyObjectIdentifier {
#[new]
fn new(oid_str: &str) -> PyResult<Self> {
let inner = ObjectIdentifier::from_str(oid_str)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("Invalid OID: {e:?}")))?;
Ok(Self {
inner,
dotted_cache: OnceLock::new(),
})
}
#[staticmethod]
fn from_components(components: Vec<u32>) -> PyResult<Self> {
let inner = ObjectIdentifier::new(&components)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("Invalid OID: {e:?}")))?;
Ok(Self {
inner,
dotted_cache: OnceLock::new(),
})
}
#[staticmethod]
fn from_der_value(data: &[u8]) -> PyResult<Self> {
let inner = ObjectIdentifier::from_content_bytes(data).map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("Invalid OID content: {e:?}"))
})?;
Ok(Self {
inner,
dotted_cache: OnceLock::new(),
})
}
fn components<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {
PyTuple::new(py, self.inner.components())
}
fn __str__(&self) -> &str {
self.dotted_cache.get_or_init(|| self.inner.to_string())
}
fn __repr__(&self) -> String {
format!(
"ObjectIdentifier('{}')",
self.dotted_cache.get_or_init(|| self.inner.to_string())
)
}
fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
if let Ok(other_oid) = other.extract::<PyRef<PyObjectIdentifier>>() {
return self.inner == other_oid.inner;
}
if let Ok(s) = other.extract::<String>() {
return self.dotted_cache.get_or_init(|| self.inner.to_string()) == &s;
}
false
}
fn __hash__(&self, py: Python<'_>) -> PyResult<isize> {
PyString::new(py, self.dotted_cache.get_or_init(|| self.inner.to_string())).hash()
}
}
#[pyclass(frozen, name = "RelativeOid")]
#[derive(Debug, Clone)]
pub struct PyRelativeOid {
pub(crate) inner: synta::RelativeOid,
dotted_cache: OnceLock<String>,
}
impl PyRelativeOid {
pub fn from_roid(inner: synta::RelativeOid) -> Self {
Self {
inner,
dotted_cache: OnceLock::new(),
}
}
}
#[pymethods]
impl PyRelativeOid {
#[new]
fn new(s: &str) -> PyResult<Self> {
use std::str::FromStr;
let inner = synta::RelativeOid::from_str(s).map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("Invalid RELATIVE-OID: {e:?}"))
})?;
Ok(Self {
inner,
dotted_cache: OnceLock::new(),
})
}
#[staticmethod]
fn from_components(components: Vec<u32>) -> Self {
Self {
inner: synta::RelativeOid::new(&components),
dotted_cache: OnceLock::new(),
}
}
#[staticmethod]
fn from_der(data: &[u8]) -> PyResult<Self> {
use synta::traits::Decode;
let mut dec = synta::Decoder::new(data, synta::Encoding::Der);
let inner = synta::RelativeOid::decode(&mut dec).map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("Invalid RELATIVE-OID DER: {e:?}"))
})?;
Ok(Self {
inner,
dotted_cache: OnceLock::new(),
})
}
fn to_der<'py>(&self, py: Python<'py>) -> PyResult<pyo3::Bound<'py, pyo3::types::PyBytes>> {
use synta::traits::Encode;
let mut enc = synta::Encoder::new(synta::Encoding::Der);
self.inner.encode(&mut enc).map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("RELATIVE-OID encode failed: {e}"))
})?;
let bytes = enc.finish().map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("RELATIVE-OID finish failed: {e}"))
})?;
Ok(pyo3::types::PyBytes::new(py, &bytes))
}
fn components<'py>(&self, py: Python<'py>) -> PyResult<pyo3::Bound<'py, PyTuple>> {
PyTuple::new(py, self.inner.components())
}
fn __str__(&self) -> &str {
self.dotted_cache.get_or_init(|| self.inner.to_string())
}
fn __repr__(&self) -> String {
format!(
"RelativeOid('{}')",
self.dotted_cache.get_or_init(|| self.inner.to_string())
)
}
fn __eq__(&self, other: &pyo3::Bound<'_, pyo3::PyAny>) -> bool {
if let Ok(other_roid) = other.extract::<pyo3::PyRef<PyRelativeOid>>() {
return self.inner == other_roid.inner;
}
if let Ok(s) = other.extract::<String>() {
return self.dotted_cache.get_or_init(|| self.inner.to_string()) == &s;
}
false
}
fn __hash__(&self, py: Python<'_>) -> PyResult<isize> {
PyString::new(py, self.dotted_cache.get_or_init(|| self.inner.to_string())).hash()
}
}