Skip to main content

_synta/types/
strings.rs

1//! Python wrappers for ASN.1 string types: UTF8String, PrintableString, IA5String,
2//! NumericString, TeletexString, VisibleString, GeneralString, UniversalString, BMPString.
3
4use pyo3::exceptions::PyValueError;
5use pyo3::prelude::*;
6use pyo3::types::PyBytes;
7
8use synta::{
9    BmpString, GeneralString, IA5String, NumericString, PrintableString, TeletexString,
10    UniversalString, Utf8String, VisibleString,
11};
12
13/// Python wrapper for ASN.1 UTF8String
14#[pyclass(name = "Utf8String")]
15#[derive(Debug, Clone)]
16pub struct PyUtf8String {
17    pub(crate) inner: Utf8String,
18}
19
20#[pymethods]
21impl PyUtf8String {
22    /// Create a new UTF8String from a Python str
23    #[new]
24    fn new(value: &str) -> Self {
25        Self {
26            inner: Utf8String::new(value.to_string()),
27        }
28    }
29
30    /// Get the string value
31    fn as_str(&self) -> &str {
32        self.inner.as_str()
33    }
34
35    fn __str__(&self) -> &str {
36        self.inner.as_str()
37    }
38
39    fn __repr__(&self) -> String {
40        format!("Utf8String('{}')", self.inner.as_str())
41    }
42
43    fn __eq__(&self, other: &Self) -> bool {
44        self.inner == other.inner
45    }
46
47    fn __len__(&self) -> usize {
48        self.inner.as_str().chars().count()
49    }
50}
51
52/// Python wrapper for ASN.1 PrintableString
53///
54/// PrintableString is a subset of ASCII containing A-Z, a-z, 0-9,
55/// and the characters: ' ( ) + , - . / : = ?
56#[pyclass(name = "PrintableString")]
57#[derive(Debug, Clone)]
58pub struct PyPrintableString {
59    pub(crate) inner: PrintableString,
60}
61
62#[pymethods]
63impl PyPrintableString {
64    /// Create a new PrintableString
65    ///
66    /// Raises ValueError if the string contains characters outside the PrintableString charset.
67    #[new]
68    fn new(value: &str) -> PyResult<Self> {
69        let inner = PrintableString::new(value.to_string())
70            .map_err(|e| PyValueError::new_err(format!("Invalid PrintableString: {:?}", e)))?;
71        Ok(Self { inner })
72    }
73
74    /// Get the string value
75    fn as_str(&self) -> &str {
76        self.inner.as_str()
77    }
78
79    fn __str__(&self) -> &str {
80        self.inner.as_str()
81    }
82
83    fn __repr__(&self) -> String {
84        format!("PrintableString('{}')", self.inner.as_str())
85    }
86
87    fn __eq__(&self, other: &Self) -> bool {
88        self.inner == other.inner
89    }
90
91    fn __len__(&self) -> usize {
92        self.inner.as_str().chars().count()
93    }
94}
95
96/// Python wrapper for ASN.1 IA5String (International Alphabet 5 = ASCII)
97#[pyclass(name = "IA5String")]
98#[derive(Debug, Clone)]
99pub struct PyIA5String {
100    pub(crate) inner: IA5String,
101}
102
103#[pymethods]
104impl PyIA5String {
105    /// Create a new IA5String
106    ///
107    /// Raises ValueError if the string contains non-ASCII characters.
108    #[new]
109    fn new(value: &str) -> PyResult<Self> {
110        let inner = IA5String::new(value.to_string())
111            .map_err(|e| PyValueError::new_err(format!("Invalid IA5String: {:?}", e)))?;
112        Ok(Self { inner })
113    }
114
115    /// Get the string value
116    fn as_str(&self) -> &str {
117        self.inner.as_str()
118    }
119
120    fn __str__(&self) -> &str {
121        self.inner.as_str()
122    }
123
124    fn __repr__(&self) -> String {
125        format!("IA5String('{}')", self.inner.as_str())
126    }
127
128    fn __eq__(&self, other: &Self) -> bool {
129        self.inner == other.inner
130    }
131
132    fn __len__(&self) -> usize {
133        self.inner.as_str().chars().count()
134    }
135}
136
137/// Python wrapper for ASN.1 NumericString (tag 18) — digits and space
138#[pyclass(name = "NumericString")]
139#[derive(Debug, Clone)]
140pub struct PyNumericString {
141    pub(crate) inner: NumericString,
142}
143
144#[pymethods]
145impl PyNumericString {
146    #[new]
147    fn new(value: &str) -> PyResult<Self> {
148        let inner = NumericString::new(value.to_string())
149            .map_err(|e| PyValueError::new_err(format!("Invalid NumericString: {:?}", e)))?;
150        Ok(Self { inner })
151    }
152
153    fn as_str(&self) -> &str {
154        self.inner.as_str()
155    }
156
157    fn __str__(&self) -> &str {
158        self.inner.as_str()
159    }
160
161    fn __repr__(&self) -> String {
162        format!("NumericString('{}')", self.inner.as_str())
163    }
164
165    fn __eq__(&self, other: &Self) -> bool {
166        self.inner == other.inner
167    }
168
169    fn __len__(&self) -> usize {
170        self.inner.as_str().chars().count()
171    }
172}
173
174/// Python wrapper for ASN.1 TeletexString / T61String (tag 20) — arbitrary 8-bit bytes
175#[pyclass(name = "TeletexString")]
176#[derive(Debug, Clone)]
177pub struct PyTeletexString {
178    pub(crate) inner: TeletexString,
179}
180
181#[pymethods]
182impl PyTeletexString {
183    /// Create from raw bytes.
184    #[new]
185    fn new(data: Vec<u8>) -> Self {
186        Self {
187            inner: TeletexString::new(data),
188        }
189    }
190
191    /// Create from a Python str, encoding as Latin-1.
192    ///
193    /// Raises ValueError if any character is outside the Latin-1 range (U+0000..=U+00FF).
194    #[staticmethod]
195    fn from_latin1(value: &str) -> PyResult<Self> {
196        TeletexString::from_latin1(value)
197            .map(|inner| Self { inner })
198            .map_err(|e| PyValueError::new_err(format!("{:?}", e)))
199    }
200
201    /// Create from a Python str, encoding as Latin-1.
202    ///
203    /// Raises ValueError if any character is outside the Latin-1 range (U+0000..=U+00FF).
204    #[staticmethod]
205    fn from_str(value: &str) -> PyResult<Self> {
206        TeletexString::from_latin1(value)
207            .map(|inner| Self { inner })
208            .map_err(|e| PyValueError::new_err(format!("{:?}", e)))
209    }
210
211    /// Return the raw bytes.
212    fn to_bytes<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
213        PyBytes::new(py, self.inner.as_bytes())
214    }
215
216    /// Decode as a Latin-1 string.
217    fn as_str(&self) -> String {
218        self.inner.as_latin1_string()
219    }
220
221    fn __str__(&self) -> String {
222        self.inner.as_latin1_string()
223    }
224
225    fn __repr__(&self) -> String {
226        format!("TeletexString(<{} bytes>)", self.inner.as_bytes().len())
227    }
228
229    fn __len__(&self) -> usize {
230        self.inner.as_bytes().len()
231    }
232
233    fn __eq__(&self, other: &Self) -> bool {
234        self.inner == other.inner
235    }
236}
237
238/// Python wrapper for ASN.1 VisibleString (tag 26) — printable ASCII
239#[pyclass(name = "VisibleString")]
240#[derive(Debug, Clone)]
241pub struct PyVisibleString {
242    pub(crate) inner: VisibleString,
243}
244
245#[pymethods]
246impl PyVisibleString {
247    #[new]
248    fn new(value: &str) -> PyResult<Self> {
249        let inner = VisibleString::new(value.to_string())
250            .map_err(|e| PyValueError::new_err(format!("Invalid VisibleString: {:?}", e)))?;
251        Ok(Self { inner })
252    }
253
254    fn as_str(&self) -> &str {
255        self.inner.as_str()
256    }
257
258    fn __str__(&self) -> &str {
259        self.inner.as_str()
260    }
261
262    fn __repr__(&self) -> String {
263        format!("VisibleString('{}')", self.inner.as_str())
264    }
265
266    fn __eq__(&self, other: &Self) -> bool {
267        self.inner == other.inner
268    }
269
270    fn __len__(&self) -> usize {
271        self.inner.as_str().len()
272    }
273}
274
275/// Python wrapper for ASN.1 GeneralString (tag 27) — 8-bit bytes, treated as Latin-1
276#[pyclass(name = "GeneralString")]
277#[derive(Debug, Clone)]
278pub struct PyGeneralString {
279    pub(crate) inner: GeneralString,
280}
281
282#[pymethods]
283impl PyGeneralString {
284    /// Create from raw bytes.
285    #[new]
286    fn new(data: Vec<u8>) -> Self {
287        Self {
288            inner: GeneralString::new(data),
289        }
290    }
291
292    /// Create from a Python str, encoding as ASCII.
293    ///
294    /// Raises ValueError if any character is outside the ASCII range (U+0000..=U+007F).
295    #[staticmethod]
296    fn from_ascii(value: &str) -> PyResult<Self> {
297        GeneralString::from_ascii(value)
298            .map(|inner| Self { inner })
299            .map_err(|e| PyValueError::new_err(format!("{:?}", e)))
300    }
301
302    /// Create from a Python str, encoding as ASCII.
303    ///
304    /// Raises ValueError if any character is outside the ASCII range (U+0000..=U+007F).
305    #[staticmethod]
306    fn from_str(value: &str) -> PyResult<Self> {
307        GeneralString::from_ascii(value)
308            .map(|inner| Self { inner })
309            .map_err(|e| PyValueError::new_err(format!("{:?}", e)))
310    }
311
312    /// Return the raw bytes.
313    fn to_bytes<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
314        PyBytes::new(py, self.inner.as_bytes())
315    }
316
317    /// Decode as a Latin-1 string.
318    fn as_str(&self) -> String {
319        self.inner.as_latin1_string()
320    }
321
322    fn __str__(&self) -> String {
323        self.inner.as_latin1_string()
324    }
325
326    fn __repr__(&self) -> String {
327        format!("GeneralString(<{} bytes>)", self.inner.as_bytes().len())
328    }
329
330    fn __len__(&self) -> usize {
331        self.inner.as_bytes().len()
332    }
333
334    fn __eq__(&self, other: &Self) -> bool {
335        self.inner == other.inner
336    }
337}
338
339/// Python wrapper for ASN.1 UniversalString (tag 28) — UCS-4 big-endian
340#[pyclass(name = "UniversalString")]
341#[derive(Debug, Clone)]
342pub struct PyUniversalString {
343    pub(crate) inner: UniversalString,
344}
345
346#[pymethods]
347impl PyUniversalString {
348    /// Create from a Python str.
349    #[new]
350    fn new(value: &str) -> Self {
351        Self {
352            inner: UniversalString::new(value.to_string()),
353        }
354    }
355
356    /// Create from raw UCS-4 big-endian bytes.
357    #[staticmethod]
358    fn from_bytes(data: &[u8]) -> PyResult<Self> {
359        let inner = UniversalString::from_ucs4_be(data)
360            .map_err(|e| PyValueError::new_err(format!("Invalid UniversalString: {:?}", e)))?;
361        Ok(Self { inner })
362    }
363
364    fn as_str(&self) -> &str {
365        self.inner.as_str()
366    }
367
368    fn __str__(&self) -> &str {
369        self.inner.as_str()
370    }
371
372    fn __repr__(&self) -> String {
373        format!("UniversalString('{}')", self.inner.as_str())
374    }
375
376    fn __eq__(&self, other: &Self) -> bool {
377        self.inner == other.inner
378    }
379
380    fn __len__(&self) -> usize {
381        self.inner.as_str().chars().count()
382    }
383}
384
385/// Python wrapper for ASN.1 BMPString (tag 30) — UCS-2 big-endian (BMP only)
386#[pyclass(name = "BmpString")]
387#[derive(Debug, Clone)]
388pub struct PyBmpString {
389    pub(crate) inner: BmpString,
390}
391
392#[pymethods]
393impl PyBmpString {
394    /// Create from a Python str (must be BMP-only, i.e. no code points > U+FFFF).
395    #[new]
396    fn new(value: &str) -> PyResult<Self> {
397        let inner = BmpString::new(value.to_string())
398            .map_err(|e| PyValueError::new_err(format!("Invalid BMPString: {:?}", e)))?;
399        Ok(Self { inner })
400    }
401
402    /// Create from raw UCS-2 big-endian bytes.
403    #[staticmethod]
404    fn from_bytes(data: &[u8]) -> PyResult<Self> {
405        let inner = BmpString::from_ucs2_be(data)
406            .map_err(|e| PyValueError::new_err(format!("Invalid BMPString: {:?}", e)))?;
407        Ok(Self { inner })
408    }
409
410    fn as_str(&self) -> &str {
411        self.inner.as_str()
412    }
413
414    fn __str__(&self) -> &str {
415        self.inner.as_str()
416    }
417
418    fn __repr__(&self) -> String {
419        format!("BmpString('{}')", self.inner.as_str())
420    }
421
422    fn __eq__(&self, other: &Self) -> bool {
423        self.inner == other.inner
424    }
425
426    fn __len__(&self) -> usize {
427        self.inner.as_str().chars().count()
428    }
429}