_oboron/
lib.rs

1use pyo3::exceptions::PyValueError;
2use pyo3::prelude::*;
3use pyo3::types::PyBytes;
4
5/// Macro to generate Python wrapper classes for fixed-format ObtextCodec types
6macro_rules! impl_codec_class {
7    ($py_name:ident, $rust_type:ty, $doc:expr) => {
8        #[doc = $doc]
9        #[pyclass]
10        #[allow(non_camel_case_types)]
11        struct $py_name {
12            inner: $rust_type,
13        }
14
15        #[pymethods]
16        impl $py_name {
17            /// Create a new codec instance.
18            ///
19            /// Args:
20            ///     key:     86-character base64 string key (512 bits).  Required if keyless=False.
21            ///     keyless: If True, uses the hardcoded key (testing only, NOT SECURE).
22            ///
23            /// Returns:
24            ///     A new codec instance.
25            ///
26            /// Raises:
27            ///     ValueError: If key is invalid or both key and keyless are provided.
28            #[new]
29            #[pyo3(signature = (key=None, keyless=false))]
30            fn new(key: Option<String>, keyless: bool) -> PyResult<Self> {
31                let inner = match (key, keyless) {
32                    (Some(key), false) => <$rust_type>::new(&key).map_err(|e| {
33                        PyValueError::new_err(format!("Failed to create codec: {}", e))
34                    })?,
35                    (None, true) => <$rust_type>::new_keyless().map_err(|e| {
36                        PyValueError::new_err(format!(
37                            "Failed to create codec with hardcoded key: {}",
38                            e
39                        ))
40                    })?,
41                    (Some(_), true) => {
42                        return Err(PyValueError::new_err(
43                            "Cannot specify both key and keyless=True",
44                        ));
45                    }
46                    (None, false) => {
47                        return Err(PyValueError::new_err(
48                            "Must provide either key or set keyless=True",
49                        ));
50                    }
51                };
52
53                Ok(Self { inner })
54            }
55
56            /// Encrypt+encode a plaintext string.
57            ///
58            /// Args:
59            ///     plaintext: The plaintext string to encrypt+encode.
60            ///
61            /// Returns:
62            ///     The obtext string.
63            ///
64            /// Raises:
65            ///     ValueError: If the enc operation fails.
66            fn enc(&self, plaintext: &str) -> PyResult<String> {
67                let result = self.inner.enc(plaintext);
68                result.map_err(|e| PyValueError::new_err(format!("Enc operation failed: {}", e)))
69            }
70
71            /// Decode+decrypt an obtext string back to plaintext.
72            ///
73            /// Args:
74            ///     obtext: The encrypted+encoded string to decode+decrypt.
75            ///
76            /// Returns:
77            ///     The decoded+decrypted plaintext string.
78            ///
79            /// Raises:
80            ///     ValueError: If the dec operation fails
81            #[pyo3(signature = (obtext))]
82            fn dec(&self, obtext: &str) -> PyResult<String> {
83                let result = self.inner.dec(obtext);
84                result.map_err(|e| PyValueError::new_err(format!("Dec operation failed: {}", e)))
85            }
86
87            /// Get the current format string.
88            ///
89            /// Returns:
90            ///     Format string like "zrbcx.c32", "zrbcx.b32", "aags.b64", etc.
91            #[getter]
92            fn format(&self) -> String {
93                format!("{}", self.inner.format())
94            }
95
96            /// The scheme used by this instance.
97            #[getter]
98            fn scheme(&self) -> String {
99                self.inner.scheme().to_string()
100            }
101
102            /// The encoding format used by this instance.
103            #[getter]
104            fn encoding(&self) -> String {
105                self.inner.encoding().to_string()
106            }
107
108            /// Get the key used by this instance (as base64 string).
109            #[getter]
110            fn key(&self) -> String {
111                self.inner.key()
112            }
113
114            /// Get the key as hex used by this instance.
115            #[getter]
116            fn key_hex(&self) -> String {
117                self.inner.key_hex()
118            }
119
120            /// Get the key as bytes used by this instance.
121            #[getter]
122            fn key_bytes(&self, py: Python) -> PyResult<Py<PyBytes>> {
123                Ok(PyBytes::new_bound(py, self.inner.key_bytes()).into())
124            }
125        }
126    };
127}
128
129macro_rules! impl_zcodec_class {
130    ($py_name:ident, $rust_type:ty, $doc:expr) => {
131        #[doc = $doc]
132        #[pyclass]
133        #[allow(non_camel_case_types)]
134        struct $py_name {
135            inner: $rust_type,
136        }
137
138        #[pymethods]
139        impl $py_name {
140            /// Create a new codec instance.
141            ///
142            /// Args:
143            ///     key:     43-character base64 string secret (256 bits).  Required if keyless=False.
144            ///     keyless: If True, uses the hardcoded secret (testing only, NOT SECURE).
145            ///
146            /// Returns:
147            ///     A new codec instance.
148            ///
149            /// Raises:
150            ///     ValueError: If key is invalid or both key and keyless are provided.
151            #[new]
152            #[pyo3(signature = (secret=None, keyless=false))]
153            fn new(secret: Option<String>, keyless: bool) -> PyResult<Self> {
154                let inner = match (secret, keyless) {
155                    (Some(secret), false) => <$rust_type>::new(&secret).map_err(|e| {
156                        PyValueError::new_err(format!("Failed to create codec: {}", e))
157                    })?,
158                    (None, true) => <$rust_type>::new_keyless().map_err(|e| {
159                        PyValueError::new_err(format!(
160                            "Failed to create codec with hardcoded secret: {}",
161                            e
162                        ))
163                    })?,
164                    (Some(_), true) => {
165                        return Err(PyValueError::new_err(
166                            "Cannot specify both secret and keyless=True",
167                        ));
168                    }
169                    (None, false) => {
170                        return Err(PyValueError::new_err(
171                            "Must provide either secret or set keyless=True",
172                        ));
173                    }
174                };
175
176                Ok(Self { inner })
177            }
178
179            /// Encrypt+encode a plaintext string.
180            ///
181            /// Args:
182            ///     plaintext: The plaintext string to encrypt+encode.
183            ///
184            /// Returns:
185            ///     The obtext string.
186            ///
187            /// Raises:
188            ///     ValueError: If the enc operation fails.
189            fn enc(&self, plaintext: &str) -> PyResult<String> {
190                let result = self.inner.enc(plaintext);
191                result.map_err(|e| PyValueError::new_err(format!("Enc operation failed: {}", e)))
192            }
193
194            /// Decode+decrypt an obtext string back to plaintext.
195            ///
196            /// Args:
197            ///     obtext: The encrypted+encoded string to decode+decrypt.
198            ///
199            /// Returns:
200            ///     The decoded+decrypted plaintext string.
201            ///
202            /// Raises:
203            ///     ValueError: If the dec operation fails
204            #[pyo3(signature = (obtext))]
205            fn dec(&self, obtext: &str) -> PyResult<String> {
206                let result = self.inner.dec(obtext);
207                result.map_err(|e| PyValueError::new_err(format!("Dec operation failed: {}", e)))
208            }
209
210            /// Get the current format string.
211            ///
212            /// Returns:
213            ///     Format string like "zrbcx.c32", "zrbcx.b32", etc.
214            #[getter]
215            fn format(&self) -> String {
216                format!("{}", self.inner.format())
217            }
218
219            /// The scheme used by this instance.
220            #[getter]
221            fn scheme(&self) -> String {
222                self.inner.scheme().to_string()
223            }
224
225            /// The encoding format used by this instance.
226            #[getter]
227            fn encoding(&self) -> String {
228                self.inner.encoding().to_string()
229            }
230
231            /// Get the secret used by this instance (as base64 string).
232            #[getter]
233            fn secret(&self) -> String {
234                self.inner.secret()
235            }
236
237            /// Get the secret as bytes used by this instance.
238            #[getter]
239            fn secret_hex(&self) -> String {
240                self.inner.secret_hex()
241            }
242
243            /// Get the secret as bytes used by this instance.
244            #[getter]
245            fn secret_bytes(&self, py: Python) -> PyResult<Py<PyBytes>> {
246                Ok(PyBytes::new_bound(py, self.inner.secret_bytes()).into())
247            }
248        }
249    };
250}
251
252// Aags variants
253// -------------
254#[cfg(feature = "aags")]
255impl_codec_class!(
256    AagsB32,
257    ::oboron::AagsB32,
258    "Aags codec (deterministic AES-GCM-SIV) with B32 encoding "
259);
260#[cfg(feature = "aags")]
261impl_codec_class!(
262    AagsB64,
263    ::oboron::AagsB64,
264    "Aags codec (deterministic AES-GCM-SIV) with B64 encoding"
265);
266#[cfg(feature = "aags")]
267impl_codec_class!(
268    AagsC32,
269    ::oboron::AagsC32,
270    "Aags codec (deterministic AES-GCM-SIV) with C32 encoding"
271);
272#[cfg(feature = "aags")]
273impl_codec_class!(
274    AagsHex,
275    ::oboron::AagsHex,
276    "Aags codec (deterministic AES-GCM-SIV) with Hex encoding"
277);
278
279// Aasv variants
280// -------------
281#[cfg(feature = "aasv")]
282impl_codec_class!(
283    AasvB32,
284    ::oboron::AasvB32,
285    "Aasv codec (deterministic AES-SIV, nonce-misuse resistant) with B32 encoding"
286);
287#[cfg(feature = "aasv")]
288impl_codec_class!(
289    AasvB64,
290    ::oboron::AasvB64,
291    "Aasv codec (deterministic AES-SIV, nonce-misuse resistant) with B64 encoding"
292);
293#[cfg(feature = "aasv")]
294impl_codec_class!(
295    AasvC32,
296    ::oboron::AasvC32,
297    "Aasv codec (deterministic AES-SIV, nonce-misuse resistant) with C32 encoding"
298);
299#[cfg(feature = "aasv")]
300impl_codec_class!(
301    AasvHex,
302    ::oboron::AasvHex,
303    "Aasv codec (deterministic AES-SIV, nonce-misuse resistant) with Hex encoding"
304);
305
306// Apgs variants
307// --------------
308#[cfg(feature = "apgs")]
309impl_codec_class!(
310    ApgsB32,
311    ::oboron::ApgsB32,
312    "Apgs codec (probabilistic AES-GCM-SIV) with B32 encoding"
313);
314#[cfg(feature = "apgs")]
315impl_codec_class!(
316    ApgsB64,
317    ::oboron::ApgsB64,
318    "Apgs codec (probabilistic AES-GCM-SIV) with B64 encoding"
319);
320#[cfg(feature = "apgs")]
321impl_codec_class!(
322    ApgsC32,
323    ::oboron::ApgsC32,
324    "Apgs codec (probabilistic AES-GCM-SIV) with C32 encoding"
325);
326#[cfg(feature = "apgs")]
327impl_codec_class!(
328    ApgsHex,
329    ::oboron::ApgsHex,
330    "Apgs codec (probabilistic AES-GCM-SIV) with Hex encoding"
331);
332
333// Apsv variants
334// --------------
335#[cfg(feature = "apsv")]
336impl_codec_class!(
337    ApsvB32,
338    ::oboron::ApsvB32,
339    "Apsv codec (probabilistic AES-SIV) with B32 encoding"
340);
341#[cfg(feature = "apsv")]
342impl_codec_class!(
343    ApsvB64,
344    ::oboron::ApsvB64,
345    "Apsv codec (probabilistic AES-SIV) with B64 encoding"
346);
347#[cfg(feature = "apsv")]
348impl_codec_class!(
349    ApsvC32,
350    ::oboron::ApsvC32,
351    "Apsv codec (probabilistic AES-SIV) with C32 encoding"
352);
353#[cfg(feature = "apsv")]
354impl_codec_class!(
355    ApsvHex,
356    ::oboron::ApsvHex,
357    "Apsv codec (probabilistic AES-SIV) with Hex encoding"
358);
359
360// Upbc variants
361// ------------
362#[cfg(feature = "upbc")]
363impl_codec_class!(
364    UpbcB32,
365    ::oboron::UpbcB32,
366    "Upbc codec (probabilistic AES-CBC) with B32 encoding"
367);
368#[cfg(feature = "upbc")]
369impl_codec_class!(
370    UpbcB64,
371    ::oboron::UpbcB64,
372    "Upbc codec (probabilistic AES-CBC) with B64 encoding"
373);
374#[cfg(feature = "upbc")]
375impl_codec_class!(
376    UpbcC32,
377    ::oboron::UpbcC32,
378    "Upbc codec (probabilistic AES-CBC) with C32 encoding"
379);
380#[cfg(feature = "upbc")]
381impl_codec_class!(
382    UpbcHex,
383    ::oboron::UpbcHex,
384    "Upbc codec (probabilistic AES-CBC) with Hex encoding"
385);
386
387// Zrbcx variants
388// -------------
389#[cfg(feature = "zrbcx")]
390impl_zcodec_class!(
391    ZrbcxB32,
392    ::oboron::ztier::ZrbcxB32,
393    "Zrbcx codec (deterministic AES-CBC, constant IV) with B32 encoding "
394);
395#[cfg(feature = "zrbcx")]
396impl_zcodec_class!(
397    ZrbcxB64,
398    ::oboron::ztier::ZrbcxB64,
399    "Zrbcx codec (deterministic AES-CBC, constant IV) with B64 encoding"
400);
401#[cfg(feature = "zrbcx")]
402impl_zcodec_class!(
403    ZrbcxC32,
404    ::oboron::ztier::ZrbcxC32,
405    "Zrbcx codec (deterministic AES-CBC, constant IV) with C32 encoding"
406);
407#[cfg(feature = "zrbcx")]
408impl_zcodec_class!(
409    ZrbcxHex,
410    ::oboron::ztier::ZrbcxHex,
411    "Zrbcx codec (deterministic AES-CBC, constant IV) with Hex encoding"
412);
413
414// --- TESTING CLASSES ---
415
416// Mock1 variants
417// -------------
418impl_codec_class!(
419    Mock1B32,
420    ::oboron::Mock1B32,
421    "Mock1 codec (identity scheme, for testing) with B32 encoding"
422);
423impl_codec_class!(
424    Mock1B64,
425    ::oboron::Mock1B64,
426    "Mock1 codec (identity scheme, for testing) with B64 encoding"
427);
428impl_codec_class!(
429    Mock1C32,
430    ::oboron::Mock1C32,
431    "Mock1 codec (identity scheme, for testing) with C32 encoding"
432);
433impl_codec_class!(
434    Mock1Hex,
435    ::oboron::Mock1Hex,
436    "Mock1 codec (identity scheme, for testing) with Hex encoding"
437);
438
439// Mock2 variants
440// -------------
441impl_codec_class!(
442    Mock2B32,
443    ::oboron::Mock2B32,
444    "Mock2 codec (reverse plaintext scheme, for testing) with B32 encoding"
445);
446impl_codec_class!(
447    Mock2B64,
448    ::oboron::Mock2B64,
449    "Mock2 codec (reverse plaintext scheme, for testing) with B64 encoding"
450);
451impl_codec_class!(
452    Mock2C32,
453    ::oboron::Mock2C32,
454    "Mock2 codec (reverse plaintext scheme, for testing) with C32 encoding"
455);
456impl_codec_class!(
457    Mock2Hex,
458    ::oboron::Mock2Hex,
459    "Mock2 codec (reverse plaintext scheme, for testing) with Hex encoding"
460);
461
462// Zmock1 variants
463// -------------
464impl_zcodec_class!(
465    Zmock1B32,
466    ::oboron::ztier::Zmock1B32,
467    "Zmock1 codec (identity scheme, for testing) with B32 encoding"
468);
469impl_zcodec_class!(
470    Zmock1B64,
471    ::oboron::ztier::Zmock1B64,
472    "Zmock1 codec (identity scheme, for testing) with B64 encoding"
473);
474impl_zcodec_class!(
475    Zmock1C32,
476    ::oboron::ztier::Zmock1C32,
477    "Zmock1 codec (identity scheme, for testing) with C32 encoding"
478);
479impl_zcodec_class!(
480    Zmock1Hex,
481    ::oboron::ztier::Zmock1Hex,
482    "Zmock1 codec (identity scheme, for testing) with Hex encoding"
483);
484
485// Legacy - LEGACY variants
486// ----------------------
487#[cfg(feature = "legacy")]
488impl_zcodec_class!(
489    LegacyB32,
490    ::oboron::ztier::LegacyB32,
491    "Legacy codec (deterministic AES-CBC, constant IV, custom padding) with B32 encoding\n\n\
492     **LEGACY**: This scheme is maintained for backward compatibility only.\n\
493     For new projects, use Zrbcx or more secure schemes like Aags/Aasv."
494);
495#[cfg(feature = "legacy")]
496impl_zcodec_class!(
497    LegacyB64,
498    ::oboron::ztier::LegacyB64,
499    "Legacy codec (deterministic AES-CBC, constant IV, custom padding) with B64 encoding\n\n\
500     **LEGACY**: This scheme is maintained for backward compatibility only.\n\
501     For new projects, use Zrbcx or more secure schemes like Aags/Aasv."
502);
503#[cfg(feature = "legacy")]
504impl_zcodec_class!(
505    LegacyC32,
506    ::oboron::ztier::LegacyC32,
507    "Legacy codec (deterministic AES-CBC, constant IV, custom padding) with C32 encoding\n\n\
508     **LEGACY**: This scheme is maintained for backward compatibility only.\n\
509     For new projects, use Zrbcx or more secure schemes like Aags/Aasv."
510);
511#[cfg(feature = "legacy")]
512impl_zcodec_class!(
513    LegacyHex,
514    ::oboron::ztier::LegacyHex,
515    "Legacy codec (deterministic AES-CBC, constant IV, custom padding) with Hex encoding\n\n\
516     **LEGACY**: This scheme is maintained for backward compatibility only.\n\
517     For new projects, use Zrbcx or more secure schemes like Aags/Aasv."
518);
519
520/// Ob - Flexible codec with runtime format selection.   
521///
522/// This is the main interface for most use cases.  It wraps Rust's Ob
523/// and allows changing the format (scheme + encoding) at runtime.
524#[pyclass]
525struct Ob {
526    inner: ::oboron::Ob,
527}
528
529#[pymethods]
530impl Ob {
531    /// Create a new Ob instance.
532    ///
533    /// Args:
534    ///     format: Format string like "aags.b64", "apsv.hex", "zrbcx.c32", "zrbcx.b32", etc.
535    ///     key:     86-character base64 string key (512 bits). Required if keyless=False.
536    ///     keyless: If True, uses the hardcoded key (testing only, NOT SECURE).
537    ///
538    /// Returns:
539    ///     A new Ob instance.
540    ///
541    /// Raises:
542    ///     ValueError: If key or format is invalid.
543    #[new]
544    #[pyo3(signature = (format, key=None, keyless=false))]
545    fn new(format: &str, key: Option<String>, keyless: bool) -> PyResult<Self> {
546        let inner = match (key, keyless) {
547            (Some(key), false) => ::oboron::Ob::new(format, &key)
548                .map_err(|e| PyValueError::new_err(format!("Failed to create Ob: {}", e)))?,
549            (None, true) => ::oboron::Ob::new_keyless(format).map_err(|e| {
550                PyValueError::new_err(format!("Failed to create Ob with hardcoded key: {}", e))
551            })?,
552            (Some(_), true) => {
553                return Err(PyValueError::new_err(
554                    "Cannot specify both key and keyless=True",
555                ));
556            }
557            (None, false) => {
558                return Err(PyValueError::new_err(
559                    "Must provide either key or set keyless=True",
560                ));
561            }
562        };
563
564        Ok(Self { inner })
565    }
566
567    /// Encrypt+encode a plaintext string.
568    ///
569    /// Args:  
570    ///     plaintext: The plaintext string to encrypt+encode.
571    ///
572    /// Returns:  
573    ///     The obtext string.
574    ///
575    /// Raises:  
576    ///     ValueError: If encoding fails.
577    fn enc(&self, plaintext: &str) -> PyResult<String> {
578        let result = self.inner.enc(plaintext);
579        result.map_err(|e| PyValueError::new_err(format!("Enc operation failed: {}", e)))
580    }
581
582    /// Decode+decrypt an obtext string back to plaintext.  
583    ///
584    /// Args:  
585    ///     obtext: The encrypted+encoded string to decode.  
586    ///
587    /// Returns:  
588    ///     The decoded plaintext string.
589    ///
590    /// Raises:  
591    ///     ValueError: If the dec operation fails
592    #[pyo3(signature = (obtext))]
593    fn dec(&self, obtext: &str) -> PyResult<String> {
594        let result = self.inner.dec(obtext);
595        result.map_err(|e| PyValueError::new_err(format!("Dec operation failed: {}", e)))
596    }
597
598    /// Decode+decrypt with automatic scheme and encoding detection.
599    ///
600    /// This method tries to decode with the instance's encoding, and if that fails
601    /// it does full format autodetection (`Omnib.autodec()` functionality as failover)
602    ///
603    /// Args:  
604    ///     obtext: The encrypted+encoded string to decode+decrypt.
605    ///
606    /// Returns:  
607    ///     The decoded+decrypted plaintext string.
608    ///
609    /// Raises:  
610    ///     ValueError: If the dec operation fails or format cannot be detected.
611    fn autodec(&self, obtext: &str) -> PyResult<String> {
612        let result = self.inner.autodec(obtext);
613        result.map_err(|e| PyValueError::new_err(format!("Autodec operation failed: {}", e)))
614    }
615
616    /// Get the current format string.
617    ///
618    /// Returns:
619    ///     Format string like "aags.b64", "apgs.c32", "aasv.b32", etc.
620    #[getter]
621    fn format(&self) -> String {
622        format!("{}", self.inner.format())
623    }
624
625    /// The scheme used by this instance.
626    #[getter]
627    fn scheme(&self) -> String {
628        self.inner.scheme().to_string()
629    }
630
631    /// The encoding format used by this instance.
632    #[getter]
633    fn encoding(&self) -> String {
634        self.inner.encoding().to_string()
635    }
636
637    /// Get the key used by this instance (as base64 string).
638    #[getter]
639    fn key(&self) -> String {
640        self.inner.key()
641    }
642
643    /// Get the key as hex used by this instance.
644    #[getter]
645    fn key_hex(&self) -> String {
646        self.inner.key_hex()
647    }
648
649    /// Get the key as bytes used by this instance.
650    #[getter]
651    fn key_bytes(&self, py: Python) -> PyResult<Py<PyBytes>> {
652        Ok(PyBytes::new_bound(py, self.inner.key_bytes()).into())
653    }
654
655    /// Change the format (scheme + encoding).  
656    ///
657    /// Args:  
658    ///     format: Format string like "aags.b64", "apsv.hex", "apgs.c32", "aasv.b32", etc.
659    ///
660    /// Raises:  
661    ///     ValueError: If format is invalid.
662    fn set_format(&mut self, format: &str) -> PyResult<()> {
663        self.inner
664            .set_format(format)
665            .map_err(|e| PyValueError::new_err(format!("Failed to set format: {}", e)))
666    }
667
668    /// Change the scheme while keeping the current encoding.
669    ///
670    /// Args:  
671    ///     scheme: Scheme name like "aags", "apsv", "apgs", etc.  
672    ///
673    /// Raises:  
674    ///     ValueError: If scheme is invalid.
675    fn set_scheme(&mut self, scheme: &str) -> PyResult<()> {
676        let scheme_enum = ::oboron::Scheme::from_str(scheme)
677            .map_err(|e| PyValueError::new_err(format!("Invalid scheme: {}", e)))?;
678        self.inner
679            .set_scheme(scheme_enum)
680            .map_err(|e| PyValueError::new_err(format!("Failed to set scheme: {}", e)))
681    }
682
683    /// Change the encoding while keeping the current scheme.
684    ///
685    /// Args:  
686    ///     encoding: Encoding name: "b32", "b64", "c32", "hex".
687    ///               Also accepts long forms: "base32rfc", "base64", "base32crockford", or "hex".
688    ///
689    /// Raises:  
690    ///     ValueError: If encoding is invalid.
691    fn set_encoding(&mut self, encoding: &str) -> PyResult<()> {
692        let encoding_enum = ::oboron::Encoding::from_str(encoding)
693            .map_err(|e| PyValueError::new_err(format!("Invalid encoding: {}", e)))?;
694        self.inner
695            .set_encoding(encoding_enum)
696            .map_err(|e| PyValueError::new_err(format!("Failed to set encoding: {}", e)))
697    }
698}
699
700/// Omnib - Multi-format codec with full autodetection.
701///
702/// Unlike other codecs, Omnib doesn't store a format internally.
703/// The format must be specified for each enc operation, and it can
704/// automatically detect both scheme and encoding on dec operations.
705#[pyclass]
706struct Omnib {
707    inner: ::oboron::Omnib,
708}
709
710#[pymethods]
711impl Omnib {
712    /// Create a new Omnib instance.
713    ///
714    /// Args:
715    ///     key:     86-character base64 string key (512 bits).  Required if keyless=False.
716    ///     keyless: If True, uses the hardcoded key (testing only, NOT SECURE).
717    ///
718    /// Returns:
719    ///     A new Omnib instance.
720    ///
721    /// Raises:
722    ///     ValueError: If key is invalid.
723    #[new]
724    #[pyo3(signature = (key=None, keyless=false))]
725    fn new(key: Option<String>, keyless: bool) -> PyResult<Self> {
726        let inner = match (key, keyless) {
727            (Some(key), false) => ::oboron::Omnib::new(&key)
728                .map_err(|e| PyValueError::new_err(format!("Failed to create Omnib: {}", e)))?,
729            (None, true) => ::oboron::Omnib::new_keyless().map_err(|e| {
730                PyValueError::new_err(format!("Failed to create Omnib with hardcoded key: {}", e))
731            })?,
732            (Some(_), true) => {
733                return Err(PyValueError::new_err(
734                    "Cannot specify both key and keyless=True",
735                ));
736            }
737            (None, false) => {
738                return Err(PyValueError::new_err(
739                    "Must provide either key or set keyless=True",
740                ));
741            }
742        };
743
744        Ok(Self { inner })
745    }
746
747    /// Ecrypt+encode a plaintext string with a specific format.
748    ///
749    /// Args:
750    ///     plaintext: The plaintext string to encrypt+encode.
751    ///     format: Format string like "aags.b64", "apsv.hex", etc.
752    ///
753    /// Returns:
754    ///     The obtext string.
755    ///
756    /// Raises:
757    ///     ValueError: If the enc operation fails or format is invalid.
758    fn enc(&self, plaintext: &str, format: &str) -> PyResult<String> {
759        let result = self.inner.enc(plaintext, format);
760        result.map_err(|e| PyValueError::new_err(format!("Enc operation failed: {}", e)))
761    }
762
763    /// Decode+decrypt an obtext string with a specific format.
764    ///
765    /// Args:
766    ///     obtext: The encrypted+encoded string to decode+decrypt.  
767    ///     format: Format string like "aags.b64", "apsv.hex", etc.
768    ///
769    /// Returns:
770    ///     The decoded+decrypted plaintext string.
771    ///
772    /// Raises:
773    ///     ValueError: If the dec operation fails or format is invalid.
774    fn dec(&self, obtext: &str, format: &str) -> PyResult<String> {
775        let result = self.inner.dec(obtext, format);
776        result.map_err(|e| PyValueError::new_err(format!("Dec operation failed: {}", e)))
777    }
778
779    /// Decode+decrypt with automatic scheme and encoding detection.
780    ///
781    /// This is the only decoder that can automatically detect both the scheme
782    /// (aags, apsv, etc.) AND the encoding (b32, b64, c32, hex).
783    ///
784    /// Args:
785    ///     obtext: The encrypted+encoded string to decode+decrypt.
786    ///
787    /// Returns:
788    ///     The decoded+decrypted plaintext string.
789    ///
790    /// Raises:
791    ///     ValueError: If the dec operation fails or format cannot be detected.
792    fn autodec(&self, obtext: &str) -> PyResult<String> {
793        let result = self.inner.autodec(obtext);
794        result.map_err(|e| PyValueError::new_err(format!("Autodec operation failed: {}", e)))
795    }
796
797    /// Get the key used by this instance (as base64 string).
798    #[getter]
799    fn key(&self) -> String {
800        self.inner.key()
801    }
802
803    /// Get the key as hex used by this instance.
804    #[getter]
805    fn key_hex(&self) -> String {
806        self.inner.key_hex()
807    }
808
809    /// Get the key as bytes used by this instance.
810    #[getter]
811    fn key_bytes(&self, py: Python) -> PyResult<Py<PyBytes>> {
812        Ok(PyBytes::new_bound(py, self.inner.key_bytes()).into())
813    }
814}
815
816// Z-tier schemes - Obz
817#[pyclass]
818struct Obz {
819    inner: ::oboron::ztier::Obz,
820}
821
822#[pymethods]
823impl Obz {
824    /// Create a new Obz instance.
825    ///
826    /// Args:
827    ///     format: Format string like "aags.b64", "apsv.hex", "zrbcx.c32", "zrbcx.b32", etc.
828    ///     key:     86-character base64 string key (512 bits). Required if keyless=False.
829    ///     keyless: If True, uses the hardcoded key (testing only, NOT SECURE).
830    ///
831    /// Returns:
832    ///     A new Obz instance.
833    ///
834    /// Raises:
835    ///     ValueError: If key or format is invalid.
836    #[new]
837    #[pyo3(signature = (format, key=None, keyless=false))]
838    fn new(format: &str, key: Option<String>, keyless: bool) -> PyResult<Self> {
839        let inner = match (key, keyless) {
840            (Some(key), false) => ::oboron::ztier::Obz::new(format, &key)
841                .map_err(|e| PyValueError::new_err(format!("Failed to create Obz: {}", e)))?,
842            (None, true) => ::oboron::ztier::Obz::new_keyless(format).map_err(|e| {
843                PyValueError::new_err(format!("Failed to create Obz with hardcoded key: {}", e))
844            })?,
845            (Some(_), true) => {
846                return Err(PyValueError::new_err(
847                    "Cannot specify both key and keyless=True",
848                ));
849            }
850            (None, false) => {
851                return Err(PyValueError::new_err(
852                    "Must provide either key or set keyless=True",
853                ));
854            }
855        };
856
857        Ok(Self { inner })
858    }
859
860    /// Encrypt+encode a plaintext string.
861    ///
862    /// Args:  
863    ///     plaintext: The plaintext string to encrypt+encode.
864    ///
865    /// Returns:  
866    ///     The obtext string.
867    ///
868    /// Raises:  
869    ///     ValueError: If encoding fails.
870    fn enc(&self, plaintext: &str) -> PyResult<String> {
871        let result = self.inner.enc(plaintext);
872        result.map_err(|e| PyValueError::new_err(format!("Enc operation failed: {}", e)))
873    }
874
875    /// Decode+decrypt an obtext string back to plaintext.  
876    ///
877    /// Args:  
878    ///     obtext: The encrypted+encoded string to decode.  
879    ///
880    /// Returns:  
881    ///     The decoded plaintext string.
882    ///
883    /// Raises:  
884    ///     ValueError: If the dec operation fails
885    #[pyo3(signature = (obtext))]
886    fn dec(&self, obtext: &str) -> PyResult<String> {
887        let result = self.inner.dec(obtext);
888        result.map_err(|e| PyValueError::new_err(format!("Dec operation failed: {}", e)))
889    }
890
891    /// Decode+decrypt with automatic scheme and encoding detection.
892    ///
893    /// This method tries to decode with the instance's encoding, and if that fails
894    /// it does full format autodetection (`Omnib.autodec()` functionality as failover)
895    ///
896    /// Args:  
897    ///     obtext: The encrypted+encoded string to decode+decrypt.
898    ///
899    /// Returns:  
900    ///     The decoded+decrypted plaintext string.
901    ///
902    /// Raises:  
903    ///     ValueError: If the dec operation fails or format cannot be detected.
904    fn autodec(&self, obtext: &str) -> PyResult<String> {
905        let result = self.inner.autodec(obtext);
906        result.map_err(|e| PyValueError::new_err(format!("Autodec operation failed: {}", e)))
907    }
908
909    /// Get the current format string.
910    ///
911    /// Returns:
912    ///     Format string like "zrbcx.hex", "zrbcx.c32", "zrbcx.b32", etc.
913    #[getter]
914    fn format(&self) -> String {
915        format!("{}", self.inner.format())
916    }
917
918    /// The scheme used by this instance.
919    #[getter]
920    fn scheme(&self) -> String {
921        self.inner.scheme().to_string()
922    }
923
924    /// The encoding format used by this instance.
925    #[getter]
926    fn encoding(&self) -> String {
927        self.inner.encoding().to_string()
928    }
929
930    /// Get the secret used by this instance (as base64 string).
931    #[getter]
932    fn secret(&self) -> String {
933        self.inner.secret()
934    }
935
936    /// Get the secret as hex used by this instance.
937    #[getter]
938    fn secret_hex(&self) -> String {
939        self.inner.secret_hex()
940    }
941
942    /// Get the secret as bytes used by this instance.
943    #[getter]
944    fn secret_bytes(&self, py: Python) -> PyResult<Py<PyBytes>> {
945        Ok(PyBytes::new_bound(py, self.inner.secret_bytes()).into())
946    }
947
948    /// Change the format (scheme + encoding).  
949    ///
950    /// Args:  
951    ///     format: Format string like "zrbcx.b64", "zrbcx.hex", "zrbcx.c32", "zrbcx.b32", etc.
952    ///
953    /// Raises:  
954    ///     ValueError: If format is invalid.
955    fn set_format(&mut self, format: &str) -> PyResult<()> {
956        self.inner
957            .set_format(format)
958            .map_err(|e| PyValueError::new_err(format!("Failed to set format: {}", e)))
959    }
960
961    /// Change the scheme while keeping the current encoding.
962    ///
963    /// Args:  
964    ///     scheme: Scheme name like "zrbcx", "zmock1", etc.  
965    ///
966    /// Raises:  
967    ///     ValueError: If scheme is invalid.
968    fn set_scheme(&mut self, scheme: &str) -> PyResult<()> {
969        let scheme_enum = ::oboron::Scheme::from_str(scheme)
970            .map_err(|e| PyValueError::new_err(format!("Invalid scheme: {}", e)))?;
971        self.inner
972            .set_scheme(scheme_enum)
973            .map_err(|e| PyValueError::new_err(format!("Failed to set scheme: {}", e)))
974    }
975
976    /// Change the encoding while keeping the current scheme.
977    ///
978    /// Args:  
979    ///     encoding: Encoding name: "b32", "b64", "c32", "hex".
980    ///               Also accepts long forms: "base32rfc", "base64", "base32crockford", or "hex".
981    ///
982    /// Raises:  
983    ///     ValueError: If encoding is invalid.
984    fn set_encoding(&mut self, encoding: &str) -> PyResult<()> {
985        let encoding_enum = ::oboron::Encoding::from_str(encoding)
986            .map_err(|e| PyValueError::new_err(format!("Invalid encoding: {}", e)))?;
987        self.inner
988            .set_encoding(encoding_enum)
989            .map_err(|e| PyValueError::new_err(format!("Failed to set encoding: {}", e)))
990    }
991}
992
993// Z-tier schemes - Omnibz
994#[pyclass]
995struct Omnibz {
996    inner: ::oboron::ztier::Omnibz,
997}
998
999#[pymethods]
1000impl Omnibz {
1001    /// Create a new Omnibz instance.
1002    ///
1003    /// Args:
1004    ///     secret:     43-character base64 string key (256 bits).  Required if keyless=False.
1005    ///     keyless: If True, uses the hardcoded secret (testing only, NOT SECURE).
1006    ///
1007    /// Returns:
1008    ///     A new Omnibz instance.
1009    ///
1010    /// Raises:
1011    ///     ValueError: If secret is invalid.
1012    #[new]
1013    #[pyo3(signature = (secret=None, keyless=false))]
1014    fn new(secret: Option<String>, keyless: bool) -> PyResult<Self> {
1015        let inner = match (secret, keyless) {
1016            (Some(secret), false) => ::oboron::ztier::Omnibz::new(&secret)
1017                .map_err(|e| PyValueError::new_err(format!("Failed to create Omnibz: {}", e)))?,
1018            (None, true) => ::oboron::ztier::Omnibz::new_keyless().map_err(|e| {
1019                PyValueError::new_err(format!(
1020                    "Failed to create Omnibz with hardcoded secret: {}",
1021                    e
1022                ))
1023            })?,
1024            (Some(_), true) => {
1025                return Err(PyValueError::new_err(
1026                    "Cannot specify both secret and keyless=True",
1027                ));
1028            }
1029            (None, false) => {
1030                return Err(PyValueError::new_err(
1031                    "Must provide either secret or set keyless=True",
1032                ));
1033            }
1034        };
1035
1036        Ok(Self { inner })
1037    }
1038
1039    /// Ecrypt+encode a plaintext string with a specific format.
1040    ///
1041    /// Args:
1042    ///     plaintext: The plaintext string to encrypt+encode.
1043    ///     format: Format string like "zrbcx.c32", "zrbcx.b32", etc.
1044    ///
1045    /// Returns:
1046    ///     The obtext string.
1047    ///
1048    /// Raises:
1049    ///     ValueError: If the enc operation fails or format is invalid.
1050    fn enc(&self, plaintext: &str, format: &str) -> PyResult<String> {
1051        let result = self.inner.enc(plaintext, format);
1052        result.map_err(|e| PyValueError::new_err(format!("Enc operation failed: {}", e)))
1053    }
1054
1055    /// Decode+decrypt an obtext string with a specific format.
1056    ///
1057    /// Args:
1058    ///     obtext: The encrypted+encoded string to decode+decrypt.  
1059    ///     format: Format string like "zrbcx.c32", "zrbcx.b32", etc.
1060    ///
1061    /// Returns:
1062    ///     The decoded+decrypted plaintext string.
1063    ///
1064    /// Raises:
1065    ///     ValueError: If the dec operation fails or format is invalid.
1066    fn dec(&self, obtext: &str, format: &str) -> PyResult<String> {
1067        let result = self.inner.dec(obtext, format);
1068        result.map_err(|e| PyValueError::new_err(format!("Dec operation failed: {}", e)))
1069    }
1070
1071    /// Decode+decrypt with automatic scheme and encoding detection.
1072    ///
1073    /// This is the only decoder that can automatically detect both the scheme
1074    /// (zrbcx, zmock1, etc.) AND the encoding (b32, b64, c32, hex).
1075    ///
1076    /// Args:
1077    ///     obtext: The encrypted+encoded string to decode+decrypt.
1078    ///
1079    /// Returns:
1080    ///     The decoded+decrypted plaintext string.
1081    ///
1082    /// Raises:
1083    ///     ValueError: If the dec operation fails or format cannot be detected.
1084    fn autodec(&self, obtext: &str) -> PyResult<String> {
1085        let result = self.inner.autodec(obtext);
1086        result.map_err(|e| PyValueError::new_err(format!("Autodec operation failed: {}", e)))
1087    }
1088
1089    /// Get the secret used by this instance (as base64 string).
1090    fn secret(&self) -> String {
1091        self.inner.secret()
1092    }
1093
1094    /// Get the secret as hex used by this instance.
1095    fn secret_hex(&self) -> String {
1096        self.inner.secret_hex()
1097    }
1098
1099    /// Get the key as bytes used by this instance.
1100    fn secret_bytes(&self, py: Python) -> PyResult<Py<PyBytes>> {
1101        Ok(PyBytes::new_bound(py, self.inner.secret_bytes()).into())
1102    }
1103}
1104
1105/// Generate a random 64-byte key as a base64 string.
1106///
1107/// Returns:
1108///     A random 64-byte key as a 86-character base64 string.
1109#[pyfunction]
1110fn generate_key() -> PyResult<String> {
1111    Ok(::oboron::generate_key())
1112}
1113
1114/// Generate a random 64-byte key as a hex string.
1115///
1116/// Returns:
1117///     A random 64-byte key as a 128-character hex string.
1118#[pyfunction]
1119fn generate_key_hex() -> PyResult<String> {
1120    Ok(::oboron::generate_key_hex())
1121}
1122
1123/// Generate a random 64-byte key as bytes.
1124///
1125/// Returns:
1126///     A random 64-byte key as bytes.
1127#[pyfunction]
1128fn generate_key_bytes(py: Python) -> PyResult<Py<PyBytes>> {
1129    let key = ::oboron::generate_key_bytes();
1130    Ok(PyBytes::new_bound(py, &key).into())
1131}
1132
1133/// Generate a random 32-byte secret as a base64 string.
1134///
1135/// Returns:
1136///     A random 64-byte key as a 43-character base64 string.
1137#[pyfunction]
1138fn generate_secret() -> PyResult<String> {
1139    Ok(::oboron::generate_secret())
1140}
1141
1142/// Generate a random 32-byte secret as a hex string.
1143///
1144/// Returns:
1145///     A random 32-byte key as a 64-character hex string.
1146#[pyfunction]
1147fn generate_secret_hex() -> PyResult<String> {
1148    Ok(::oboron::generate_secret_hex())
1149}
1150
1151/// Generate a random 32-byte secret as bytes.
1152///
1153/// Returns:
1154///     A random 32-byte secret as bytes.
1155#[pyfunction]
1156fn generate_secret_bytes(py: Python) -> PyResult<Py<PyBytes>> {
1157    let secret = ::oboron::generate_secret_bytes();
1158    Ok(PyBytes::new_bound(py, &secret).into())
1159}
1160
1161// ============================================================================
1162// Convenience Functions
1163// ============================================================================
1164
1165/// Encrypt+encode plaintext with a specified format.
1166///
1167/// Args:
1168///     plaintext: The plaintext string to encode.
1169///     format: Format string like "aags.b64", "apsv.hex", "zrbcx.b32", etc.
1170///     key:     86-character base64 string key (512 bits).
1171///
1172/// Returns:
1173///     The obtext string.
1174///
1175/// Raises:
1176///     ValueError: If the enc operation fails.
1177#[pyfunction]
1178fn enc(plaintext: &str, format: &str, key: &str) -> PyResult<String> {
1179    ::oboron::enc(plaintext, format, key)
1180        .map_err(|e| PyValueError::new_err(format!("Enc operation failed: {}", e)))
1181}
1182
1183/// Encrypt+encode plaintext with a specified format using the hardcoded key (testing only).
1184///
1185/// Args:
1186///     plaintext: The plaintext string to encrypt+encode.
1187///     format: Format string like "aags.b64", "apsv.hex", "zrbcx.b32", etc.
1188///
1189/// Returns:
1190///     The obtext string.
1191///
1192/// Raises:
1193///     ValueError: If the enc operation fails.
1194#[pyfunction]
1195#[cfg(feature = "keyless")]
1196fn enc_keyless(plaintext: &str, format: &str) -> PyResult<String> {
1197    ::oboron::enc_keyless(plaintext, format)
1198        .map_err(|e| PyValueError::new_err(format!("Enc operation failed: {}", e)))
1199}
1200
1201/// Decode+decrypt obtext with a specified format.
1202///
1203/// Args:
1204///     obtext: The encrypted+encoded string to decode+decrypt  
1205///     format: Format string like "zrbcx.b32", "aags.b64", "apsv.hex", etc.  
1206///     key:    86-character base64 string key (512 bits).
1207///
1208/// Returns:
1209///     The decoded+decrypted plaintext string.
1210///
1211/// Raises:
1212///     ValueError: If the dec operation fails.
1213#[pyfunction]
1214fn dec(obtext: &str, format: &str, key: &str) -> PyResult<String> {
1215    ::oboron::dec(obtext, format, key)
1216        .map_err(|e| PyValueError::new_err(format!("Dec operation failed: {}", e)))
1217}
1218
1219/// Decode+decrypt obtext with a specified format using the hardcoded key (testing only).
1220///
1221/// Args:
1222///     obtext: The encrypted+encoded string to decode+decrypt.  
1223///     format: Format string like "aags.b64", "apsv.hex", "zrbcx.b32", etc.
1224///
1225/// Returns:
1226///     The decoded+decrypted plaintext string.
1227///
1228/// Raises:
1229///     ValueError: If the dec operation fails.
1230#[pyfunction]
1231#[cfg(feature = "keyless")]
1232fn dec_keyless(obtext: &str, format: &str) -> PyResult<String> {
1233    ::oboron::dec_keyless(obtext, format)
1234        .map_err(|e| PyValueError::new_err(format!("Dec operation failed: {}", e)))
1235}
1236
1237/// Decode+decrypt obtext with automatic format detection.
1238///
1239/// Args:
1240///     obtext: The encrypted+encoded string to decode+decrypt.
1241///     key:    86-character base64 string key (512 bits).
1242///
1243/// Returns:
1244///     The decoded+decrypted plaintext string.
1245///
1246/// Raises:
1247///     ValueError: If the dec operation fails.
1248#[pyfunction]
1249fn autodec(obtext: &str, key: &str) -> PyResult<String> {
1250    ::oboron::autodec(obtext, key)
1251        .map_err(|e| PyValueError::new_err(format!("Autodec operation failed: {}", e)))
1252}
1253
1254/// Decode+decrypt obtext with automatic format detection using the hardcoded key (testing only).
1255///
1256/// Args:
1257///     obtext: The encrypted+encoded string to decode+decrypt.
1258///
1259/// Returns:
1260///     The decoded+decrypted plaintext string.
1261///
1262/// Raises:
1263///     ValueError: If the autodec operation fails.
1264#[pyfunction]
1265#[cfg(feature = "keyless")]
1266fn autodec_keyless(obtext: &str) -> PyResult<String> {
1267    ::oboron::autodec_keyless(obtext)
1268        .map_err(|e| PyValueError::new_err(format!("Autodec operation failed: {}", e)))
1269}
1270
1271/// Python module for Oboron (internal Rust extension)
1272#[pymodule]
1273fn _oboron(m: &Bound<'_, PyModule>) -> PyResult<()> {
1274    // Add version from Cargo.toml
1275    m.add("__version__", env!("CARGO_PKG_VERSION"))?;
1276
1277    // Main flexible interface
1278    m.add_class::<Ob>()?;
1279
1280    // Multi-format interface
1281    m.add_class::<Omnib>()?;
1282
1283    // Aags variants
1284    #[cfg(feature = "aags")]
1285    {
1286        m.add_class::<AagsC32>()?;
1287        m.add_class::<AagsB32>()?;
1288        m.add_class::<AagsB64>()?;
1289        m.add_class::<AagsHex>()?;
1290    }
1291
1292    // Apgs variants
1293    #[cfg(feature = "apgs")]
1294    {
1295        m.add_class::<ApgsC32>()?;
1296        m.add_class::<ApgsB32>()?;
1297        m.add_class::<ApgsB64>()?;
1298        m.add_class::<ApgsHex>()?;
1299    }
1300
1301    // Aasv variants
1302    #[cfg(feature = "aasv")]
1303    {
1304        m.add_class::<AasvC32>()?;
1305        m.add_class::<AasvB32>()?;
1306        m.add_class::<AasvB64>()?;
1307        m.add_class::<AasvHex>()?;
1308    }
1309
1310    // Apsv variants
1311    #[cfg(feature = "apsv")]
1312    {
1313        m.add_class::<ApsvC32>()?;
1314        m.add_class::<ApsvB32>()?;
1315        m.add_class::<ApsvB64>()?;
1316        m.add_class::<ApsvHex>()?;
1317    }
1318
1319    // Upbc variants
1320    #[cfg(feature = "upbc")]
1321    {
1322        m.add_class::<UpbcC32>()?;
1323        m.add_class::<UpbcB32>()?;
1324        m.add_class::<UpbcB64>()?;
1325        m.add_class::<UpbcHex>()?;
1326    }
1327
1328    // TESTING =======================
1329
1330    // Mock variants
1331    #[cfg(feature = "mock")]
1332    {
1333        // Mock1 variants
1334        m.add_class::<Mock1C32>()?;
1335        m.add_class::<Mock1B32>()?;
1336        m.add_class::<Mock1B64>()?;
1337        m.add_class::<Mock1Hex>()?;
1338        // Mock2 variants
1339        m.add_class::<Mock2C32>()?;
1340        m.add_class::<Mock2B32>()?;
1341        m.add_class::<Mock2B64>()?;
1342        m.add_class::<Mock2Hex>()?;
1343    }
1344
1345    // Z-TIER =========================
1346    //
1347    // Main flexible interface
1348    m.add_class::<Obz>()?;
1349
1350    // Multi-format interface
1351    m.add_class::<Omnibz>()?;
1352
1353    // Zrbcx variants
1354    #[cfg(feature = "zrbcx")]
1355    {
1356        m.add_class::<ZrbcxC32>()?;
1357        m.add_class::<ZrbcxB32>()?;
1358        m.add_class::<ZrbcxB64>()?;
1359        m.add_class::<ZrbcxHex>()?;
1360    }
1361
1362    // Zmock variants
1363    #[cfg(feature = "zmock")]
1364    {
1365        // Zmock1 variants
1366        m.add_class::<Zmock1C32>()?;
1367        m.add_class::<Zmock1B32>()?;
1368        m.add_class::<Zmock1B64>()?;
1369        m.add_class::<Zmock1Hex>()?;
1370    }
1371
1372    // Legacy variants
1373    #[cfg(feature = "legacy")]
1374    {
1375        m.add_class::<LegacyC32>()?;
1376        m.add_class::<LegacyB32>()?;
1377        m.add_class::<LegacyB64>()?;
1378        m.add_class::<LegacyHex>()?;
1379    }
1380
1381    // Functions =====================
1382
1383    // Utility functions
1384    m.add_function(wrap_pyfunction!(generate_key, m)?)?;
1385    m.add_function(wrap_pyfunction!(generate_key_hex, m)?)?;
1386    m.add_function(wrap_pyfunction!(generate_key_bytes, m)?)?;
1387    m.add_function(wrap_pyfunction!(generate_secret, m)?)?;
1388    m.add_function(wrap_pyfunction!(generate_secret_hex, m)?)?;
1389    m.add_function(wrap_pyfunction!(generate_secret_bytes, m)?)?;
1390
1391    // Convenience functions
1392    m.add_function(wrap_pyfunction!(enc, m)?)?;
1393    #[cfg(feature = "keyless")]
1394    m.add_function(wrap_pyfunction!(enc_keyless, m)?)?;
1395    m.add_function(wrap_pyfunction!(dec, m)?)?;
1396    #[cfg(feature = "keyless")]
1397    m.add_function(wrap_pyfunction!(dec_keyless, m)?)?;
1398    m.add_function(wrap_pyfunction!(autodec, m)?)?;
1399    #[cfg(feature = "keyless")]
1400    m.add_function(wrap_pyfunction!(autodec_keyless, m)?)?;
1401
1402    Ok(())
1403}