1use std::str::FromStr;
4use std::sync::OnceLock;
5
6use pyo3::prelude::*;
7use pyo3::types::{PyString, PyTuple};
8
9use synta::ObjectIdentifier;
10
11pub mod elements;
12pub mod primitives;
13pub mod strings;
14
15pub use elements::{element_to_pyobject, PyRawElement, PyTaggedElement};
16pub use primitives::{
17 PyBitString, PyBoolean, PyGeneralizedTime, PyInteger, PyNull, PyOctetString, PyReal, PyUtcTime,
18};
19pub use strings::{
20 PyBmpString, PyGeneralString, PyIA5String, PyNumericString, PyPrintableString, PyTeletexString,
21 PyUniversalString, PyUtf8String, PyVisibleString,
22};
23
24#[pyclass(frozen, name = "ObjectIdentifier")]
37#[derive(Debug, Clone)]
38pub struct PyObjectIdentifier {
39 pub(crate) inner: ObjectIdentifier,
40 dotted_cache: OnceLock<String>,
43}
44
45impl PyObjectIdentifier {
46 pub fn from_oid(inner: ObjectIdentifier) -> Self {
49 Self {
50 inner,
51 dotted_cache: OnceLock::new(),
52 }
53 }
54}
55
56#[pymethods]
57impl PyObjectIdentifier {
58 #[new]
60 fn new(oid_str: &str) -> PyResult<Self> {
61 let inner = ObjectIdentifier::from_str(oid_str)
62 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("Invalid OID: {e:?}")))?;
63 Ok(Self {
64 inner,
65 dotted_cache: OnceLock::new(),
66 })
67 }
68
69 #[staticmethod]
71 fn from_components(components: Vec<u32>) -> PyResult<Self> {
72 let inner = ObjectIdentifier::new(&components)
73 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("Invalid OID: {e:?}")))?;
74 Ok(Self {
75 inner,
76 dotted_cache: OnceLock::new(),
77 })
78 }
79
80 #[staticmethod]
93 fn from_der_value(data: &[u8]) -> PyResult<Self> {
94 let inner = ObjectIdentifier::from_content_bytes(data).map_err(|e| {
95 pyo3::exceptions::PyValueError::new_err(format!("Invalid OID content: {e:?}"))
96 })?;
97 Ok(Self {
98 inner,
99 dotted_cache: OnceLock::new(),
100 })
101 }
102
103 fn components<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {
105 PyTuple::new(py, self.inner.components())
106 }
107
108 fn __str__(&self) -> &str {
109 self.dotted_cache.get_or_init(|| self.inner.to_string())
110 }
111
112 fn __repr__(&self) -> String {
113 format!(
114 "ObjectIdentifier('{}')",
115 self.dotted_cache.get_or_init(|| self.inner.to_string())
116 )
117 }
118
119 fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
122 if let Ok(other_oid) = other.extract::<PyRef<PyObjectIdentifier>>() {
123 return self.inner == other_oid.inner;
124 }
125 if let Ok(s) = other.extract::<String>() {
126 return self.dotted_cache.get_or_init(|| self.inner.to_string()) == &s;
127 }
128 false
129 }
130
131 fn __hash__(&self, py: Python<'_>) -> PyResult<isize> {
135 PyString::new(py, self.dotted_cache.get_or_init(|| self.inner.to_string())).hash()
136 }
137}
138
139#[pyclass(frozen, name = "RelativeOid")]
157#[derive(Debug, Clone)]
158pub struct PyRelativeOid {
159 pub(crate) inner: synta::RelativeOid,
160 dotted_cache: OnceLock<String>,
161}
162
163impl PyRelativeOid {
164 pub fn from_roid(inner: synta::RelativeOid) -> Self {
166 Self {
167 inner,
168 dotted_cache: OnceLock::new(),
169 }
170 }
171}
172
173#[pymethods]
174impl PyRelativeOid {
175 #[new]
177 fn new(s: &str) -> PyResult<Self> {
178 use std::str::FromStr;
179 let inner = synta::RelativeOid::from_str(s).map_err(|e| {
180 pyo3::exceptions::PyValueError::new_err(format!("Invalid RELATIVE-OID: {e:?}"))
181 })?;
182 Ok(Self {
183 inner,
184 dotted_cache: OnceLock::new(),
185 })
186 }
187
188 #[staticmethod]
190 fn from_components(components: Vec<u32>) -> Self {
191 Self {
192 inner: synta::RelativeOid::new(&components),
193 dotted_cache: OnceLock::new(),
194 }
195 }
196
197 #[staticmethod]
199 fn from_der(data: &[u8]) -> PyResult<Self> {
200 use synta::traits::Decode;
201 let mut dec = synta::Decoder::new(data, synta::Encoding::Der);
202 let inner = synta::RelativeOid::decode(&mut dec).map_err(|e| {
203 pyo3::exceptions::PyValueError::new_err(format!("Invalid RELATIVE-OID DER: {e:?}"))
204 })?;
205 Ok(Self {
206 inner,
207 dotted_cache: OnceLock::new(),
208 })
209 }
210
211 fn to_der<'py>(&self, py: Python<'py>) -> PyResult<pyo3::Bound<'py, pyo3::types::PyBytes>> {
213 use synta::traits::Encode;
214 let mut enc = synta::Encoder::new(synta::Encoding::Der);
215 self.inner.encode(&mut enc).map_err(|e| {
216 pyo3::exceptions::PyValueError::new_err(format!("RELATIVE-OID encode failed: {e}"))
217 })?;
218 let bytes = enc.finish().map_err(|e| {
219 pyo3::exceptions::PyValueError::new_err(format!("RELATIVE-OID finish failed: {e}"))
220 })?;
221 Ok(pyo3::types::PyBytes::new(py, &bytes))
222 }
223
224 fn components<'py>(&self, py: Python<'py>) -> PyResult<pyo3::Bound<'py, PyTuple>> {
226 PyTuple::new(py, self.inner.components())
227 }
228
229 fn __str__(&self) -> &str {
230 self.dotted_cache.get_or_init(|| self.inner.to_string())
231 }
232
233 fn __repr__(&self) -> String {
234 format!(
235 "RelativeOid('{}')",
236 self.dotted_cache.get_or_init(|| self.inner.to_string())
237 )
238 }
239
240 fn __eq__(&self, other: &pyo3::Bound<'_, pyo3::PyAny>) -> bool {
241 if let Ok(other_roid) = other.extract::<pyo3::PyRef<PyRelativeOid>>() {
242 return self.inner == other_roid.inner;
243 }
244 if let Ok(s) = other.extract::<String>() {
245 return self.dotted_cache.get_or_init(|| self.inner.to_string()) == &s;
246 }
247 false
248 }
249
250 fn __hash__(&self, py: Python<'_>) -> PyResult<isize> {
251 PyString::new(py, self.dotted_cache.get_or_init(|| self.inner.to_string())).hash()
252 }
253}