dcrypt_utils/
data_conversion.rs

1//! data_conversion.rs
2//!
3//! Utility functions for converting data between common formats like
4//! hexadecimal, Base64, and byte arrays.
5
6// Ensure Vec is available for no_std + alloc
7#[cfg(all(not(feature = "std"), feature = "alloc"))]
8extern crate alloc;
9#[cfg(all(not(feature = "std"), feature = "alloc"))]
10use alloc::{format, string::String, vec::Vec};
11
12#[cfg(feature = "std")]
13use std::{format, string::String, vec::Vec};
14
15/// Converts a hexadecimal string to a byte vector.
16///
17/// # Arguments
18/// * `s`: A string slice representing the hexadecimal data.
19///
20/// # Returns
21/// A `Result` containing the byte vector on success, or an error message string on failure.
22pub fn hex_to_bytes(s: &str) -> Result<Vec<u8>, String> {
23    hex::decode(s).map_err(|e| format!("Hex decoding failed for input string '{}': {}", s, e))
24}
25
26/// Converts a byte slice to a hexadecimal string.
27///
28/// # Arguments
29/// * `b`: A byte slice.
30///
31/// # Returns
32/// The hexadecimal string representation of the byte slice.
33pub fn bytes_to_hex(b: &[u8]) -> String {
34    hex::encode(b)
35}
36
37/// Converts a Base64 encoded string to a byte vector.
38/// Uses the standard Base64 alphabet and padding.
39///
40/// # Arguments
41/// * `s`: A string slice representing the Base64 encoded data.
42///
43/// # Returns
44/// A `Result` containing the byte vector on success, or an error message string on failure.
45pub fn base64_to_bytes(s: &str) -> Result<Vec<u8>, String> {
46    use base64::{engine::general_purpose::STANDARD, Engine as _};
47    STANDARD
48        .decode(s)
49        .map_err(|e| format!("Base64 decoding failed for input string '{}': {}", s, e))
50}
51
52/// Converts a byte slice to a Base64 encoded string.
53/// Uses the standard Base64 alphabet and padding.
54///
55/// # Arguments
56/// * `b`: A byte slice.
57///
58/// # Returns
59/// The Base64 encoded string representation of the byte slice.
60pub fn bytes_to_base64(b: &[u8]) -> String {
61    use base64::{engine::general_purpose::STANDARD, Engine as _};
62    STANDARD.encode(b)
63}
64
65/// Converts a Base64 encoded string (URL-safe, no padding) to a byte vector.
66///
67/// # Arguments
68/// * `s`: A string slice representing the URL-safe Base64 encoded data without padding.
69///
70/// # Returns
71/// A `Result` containing the byte vector on success, or an error message string on failure.
72pub fn base64url_nopad_to_bytes(s: &str) -> Result<Vec<u8>, String> {
73    use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
74    URL_SAFE_NO_PAD.decode(s).map_err(|e| {
75        format!(
76            "Base64 (URL-safe, no pad) decoding failed for '{}': {}",
77            s, e
78        )
79    })
80}
81
82/// Converts a byte slice to a Base64 encoded string (URL-safe, no padding).
83///
84/// # Arguments
85/// * `b`: A byte slice.
86///
87/// # Returns
88/// The URL-safe Base64 encoded string representation (no padding) of the byte slice.
89pub fn bytes_to_base64url_nopad(b: &[u8]) -> String {
90    use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
91    URL_SAFE_NO_PAD.encode(b)
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_hex_conversions() {
100        let bytes = vec![0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef];
101        let hex_string = "0123456789abcdef";
102
103        assert_eq!(bytes_to_hex(&bytes), hex_string);
104        assert_eq!(hex_to_bytes(hex_string).unwrap(), bytes);
105
106        assert_eq!(bytes_to_hex(&[]), "");
107        assert_eq!(hex_to_bytes("").unwrap(), Vec::<u8>::new());
108
109        assert!(hex_to_bytes("invalid hex").is_err());
110        assert!(hex_to_bytes("0g").is_err()); // invalid char
111        assert!(hex_to_bytes("012").is_err()); // odd length
112    }
113
114    #[test]
115    fn test_base64_standard_conversions() {
116        let original_str = "Hello, DCRYPT! This is a test string.";
117        let bytes = original_str.as_bytes();
118        let base64_string = "SGVsbG8sIERDUllQVCEgVGhpcyBpcyBhIHRlc3Qgc3RyaW5nLg==";
119
120        assert_eq!(bytes_to_base64(bytes), base64_string);
121        assert_eq!(base64_to_bytes(base64_string).unwrap(), bytes);
122
123        assert_eq!(bytes_to_base64(&[]), "");
124        assert_eq!(base64_to_bytes("").unwrap(), Vec::<u8>::new());
125
126        assert!(base64_to_bytes("invalid base64 char !@#").is_err());
127        assert!(
128            base64_to_bytes("SGVsbG8sIERCWVBUISEgVGhpcyBpcyBhIHRlc3Qgc3RyaW5nLg").is_err(),
129            "Missing padding should fail strict decode"
130        );
131        // A slightly more lenient decoder might accept it, but `base64` crate's standard engine is strict.
132    }
133
134    #[test]
135    fn test_base64url_nopad_conversions() {
136        // Example from RFC 4648 for base64url
137        let bytes1 = vec![0xfb, 0xfb, 0xff]; // \xfb\xfb\xff
138        let base64url_string1 = "-_v_";
139        assert_eq!(bytes_to_base64url_nopad(&bytes1), base64url_string1);
140        assert_eq!(base64url_nopad_to_bytes(base64url_string1).unwrap(), bytes1);
141
142        let original_str = "Many hands make light work.";
143        let bytes = original_str.as_bytes();
144        let base64url_nopad_string = "TWFueSBoYW5kcyBtYWtlIGxpZ2h0IHdvcmsu"; // Standard would be "TWFueSBoYW5kcyBtYWtlIGxpZ2h0IHdvcmsu" (same here)
145
146        assert_eq!(bytes_to_base64url_nopad(bytes), base64url_nopad_string);
147        assert_eq!(
148            base64url_nopad_to_bytes(base64url_nopad_string).unwrap(),
149            bytes
150        );
151
152        assert_eq!(bytes_to_base64url_nopad(&[]), "");
153        assert_eq!(base64url_nopad_to_bytes("").unwrap(), Vec::<u8>::new());
154
155        assert!(
156            base64url_nopad_to_bytes("invalid base64 char +/=").is_err(),
157            "Standard chars should fail URL-safe"
158        );
159    }
160}