quantacore/
utils.rs

1//! Utility functions for data manipulation.
2//!
3//! This module provides helper functions for encoding, decoding,
4//! and secure memory operations.
5
6use zeroize::Zeroize;
7
8/// Convert bytes to hexadecimal string.
9///
10/// # Example
11///
12/// ```
13/// use quantacore::utils::to_hex;
14///
15/// let hex = to_hex(&[0xde, 0xad, 0xbe, 0xef]);
16/// assert_eq!(hex, "deadbeef");
17/// ```
18pub fn to_hex(data: &[u8]) -> String {
19    hex::encode(data)
20}
21
22/// Convert bytes to uppercase hexadecimal string.
23pub fn to_hex_upper(data: &[u8]) -> String {
24    hex::encode_upper(data)
25}
26
27/// Convert hexadecimal string to bytes.
28///
29/// # Example
30///
31/// ```
32/// use quantacore::utils::from_hex;
33///
34/// let bytes = from_hex("deadbeef").unwrap();
35/// assert_eq!(bytes, vec![0xde, 0xad, 0xbe, 0xef]);
36/// ```
37pub fn from_hex(s: &str) -> Result<Vec<u8>, hex::FromHexError> {
38    hex::decode(s)
39}
40
41/// Convert bytes to base64 string.
42///
43/// # Example
44///
45/// ```
46/// use quantacore::utils::to_base64;
47///
48/// let b64 = to_base64(b"Hello");
49/// assert_eq!(b64, "SGVsbG8=");
50/// ```
51pub fn to_base64(data: &[u8]) -> String {
52    use base64::{Engine, engine::general_purpose::STANDARD};
53    STANDARD.encode(data)
54}
55
56/// Convert base64 string to bytes.
57///
58/// # Example
59///
60/// ```
61/// use quantacore::utils::from_base64;
62///
63/// let bytes = from_base64("SGVsbG8=").unwrap();
64/// assert_eq!(bytes, b"Hello");
65/// ```
66pub fn from_base64(s: &str) -> Result<Vec<u8>, base64::DecodeError> {
67    use base64::{Engine, engine::general_purpose::STANDARD};
68    STANDARD.decode(s)
69}
70
71/// Convert bytes to URL-safe base64 string (no padding).
72pub fn to_base64url(data: &[u8]) -> String {
73    use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
74    URL_SAFE_NO_PAD.encode(data)
75}
76
77/// Convert URL-safe base64 string to bytes.
78pub fn from_base64url(s: &str) -> Result<Vec<u8>, base64::DecodeError> {
79    use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
80    URL_SAFE_NO_PAD.decode(s)
81}
82
83/// Securely zero a byte slice.
84///
85/// This uses the `zeroize` crate to ensure the memory is actually zeroed
86/// and not optimized away by the compiler.
87///
88/// # Example
89///
90/// ```
91/// use quantacore::utils::secure_zero;
92///
93/// let mut secret = vec![0x42u8; 32];
94/// secure_zero(&mut secret);
95/// assert!(secret.iter().all(|&b| b == 0));
96/// ```
97pub fn secure_zero(data: &mut [u8]) {
98    data.zeroize();
99}
100
101/// Securely zero a vector and clear it.
102pub fn secure_clear(data: &mut Vec<u8>) {
103    data.zeroize();
104    data.clear();
105}
106
107/// Constant-time comparison of two byte slices.
108///
109/// Returns `true` if the slices are equal, `false` otherwise.
110/// The comparison time is constant regardless of where (or if) the
111/// slices differ.
112///
113/// # Example
114///
115/// ```
116/// use quantacore::utils::secure_compare;
117///
118/// assert!(secure_compare(b"hello", b"hello"));
119/// assert!(!secure_compare(b"hello", b"world"));
120/// ```
121pub fn secure_compare(a: &[u8], b: &[u8]) -> bool {
122    if a.len() != b.len() {
123        return false;
124    }
125
126    let mut result = 0u8;
127    for (x, y) in a.iter().zip(b.iter()) {
128        result |= x ^ y;
129    }
130    result == 0
131}
132
133/// Concatenate multiple byte slices.
134///
135/// # Example
136///
137/// ```
138/// use quantacore::utils::concat;
139///
140/// let result = concat(&[b"hello", b" ", b"world"]);
141/// assert_eq!(result, b"hello world");
142/// ```
143pub fn concat(slices: &[&[u8]]) -> Vec<u8> {
144    let total_len: usize = slices.iter().map(|s| s.len()).sum();
145    let mut result = Vec::with_capacity(total_len);
146    for slice in slices {
147        result.extend_from_slice(slice);
148    }
149    result
150}
151
152/// XOR two byte slices of equal length.
153///
154/// # Panics
155///
156/// Panics if the slices have different lengths.
157///
158/// # Example
159///
160/// ```
161/// use quantacore::utils::xor_bytes;
162///
163/// let a = [0x00, 0xFF, 0x00, 0xFF];
164/// let b = [0xFF, 0x00, 0xFF, 0x00];
165/// let result = xor_bytes(&a, &b);
166/// assert_eq!(result, vec![0xFF, 0xFF, 0xFF, 0xFF]);
167/// ```
168pub fn xor_bytes(a: &[u8], b: &[u8]) -> Vec<u8> {
169    assert_eq!(a.len(), b.len(), "Byte slices must have equal length");
170    a.iter().zip(b.iter()).map(|(x, y)| x ^ y).collect()
171}
172
173/// Try to XOR two byte slices, returning an error if lengths differ.
174pub fn try_xor_bytes(a: &[u8], b: &[u8]) -> Result<Vec<u8>, &'static str> {
175    if a.len() != b.len() {
176        return Err("Byte slices must have equal length");
177    }
178    Ok(xor_bytes(a, b))
179}
180
181/// Apply PKCS#7 padding.
182///
183/// # Arguments
184///
185/// * `data` - The data to pad
186/// * `block_size` - The block size (typically 16 for AES)
187///
188/// # Example
189///
190/// ```
191/// use quantacore::utils::pad_pkcs7;
192///
193/// let padded = pad_pkcs7(b"hello", 16);
194/// assert_eq!(padded.len(), 16);
195/// assert_eq!(padded[5..], vec![11u8; 11]);
196/// ```
197pub fn pad_pkcs7(data: &[u8], block_size: usize) -> Vec<u8> {
198    let padding_len = block_size - (data.len() % block_size);
199    let mut result = Vec::with_capacity(data.len() + padding_len);
200    result.extend_from_slice(data);
201    result.extend(std::iter::repeat(padding_len as u8).take(padding_len));
202    result
203}
204
205/// Remove PKCS#7 padding.
206///
207/// # Returns
208///
209/// The unpadded data, or an error if the padding is invalid.
210///
211/// # Example
212///
213/// ```
214/// use quantacore::utils::{pad_pkcs7, unpad_pkcs7};
215///
216/// let padded = pad_pkcs7(b"hello", 16);
217/// let unpadded = unpad_pkcs7(&padded).unwrap();
218/// assert_eq!(unpadded, b"hello");
219/// ```
220pub fn unpad_pkcs7(data: &[u8]) -> Result<Vec<u8>, &'static str> {
221    if data.is_empty() {
222        return Err("Data is empty");
223    }
224
225    let padding_len = *data.last().unwrap() as usize;
226    if padding_len == 0 || padding_len > data.len() {
227        return Err("Invalid padding length");
228    }
229
230    // Verify padding bytes
231    for &byte in &data[data.len() - padding_len..] {
232        if byte as usize != padding_len {
233            return Err("Invalid padding bytes");
234        }
235    }
236
237    Ok(data[..data.len() - padding_len].to_vec())
238}
239
240/// Convert an integer to big-endian bytes.
241pub fn int_to_bytes_be(value: u64, length: usize) -> Vec<u8> {
242    let bytes = value.to_be_bytes();
243    if length >= 8 {
244        let mut result = vec![0u8; length - 8];
245        result.extend_from_slice(&bytes);
246        result
247    } else {
248        bytes[8 - length..].to_vec()
249    }
250}
251
252/// Convert an integer to little-endian bytes.
253pub fn int_to_bytes_le(value: u64, length: usize) -> Vec<u8> {
254    let bytes = value.to_le_bytes();
255    let mut result = bytes[..length.min(8)].to_vec();
256    result.resize(length, 0);
257    result
258}
259
260/// Convert big-endian bytes to an integer.
261pub fn bytes_to_int_be(data: &[u8]) -> u64 {
262    let mut bytes = [0u8; 8];
263    let start = 8usize.saturating_sub(data.len());
264    let copy_len = data.len().min(8);
265    bytes[start..start + copy_len].copy_from_slice(&data[..copy_len]);
266    u64::from_be_bytes(bytes)
267}
268
269/// Convert little-endian bytes to an integer.
270pub fn bytes_to_int_le(data: &[u8]) -> u64 {
271    let mut bytes = [0u8; 8];
272    let copy_len = data.len().min(8);
273    bytes[..copy_len].copy_from_slice(&data[..copy_len]);
274    u64::from_le_bytes(bytes)
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280
281    #[test]
282    fn test_hex_roundtrip() {
283        let data = vec![0xde, 0xad, 0xbe, 0xef];
284        let hex = to_hex(&data);
285        assert_eq!(hex, "deadbeef");
286        let decoded = from_hex(&hex).unwrap();
287        assert_eq!(decoded, data);
288    }
289
290    #[test]
291    fn test_base64_roundtrip() {
292        let data = b"Hello, World!";
293        let b64 = to_base64(data);
294        let decoded = from_base64(&b64).unwrap();
295        assert_eq!(decoded, data);
296    }
297
298    #[test]
299    fn test_base64url_roundtrip() {
300        let data = vec![0xff, 0xfe, 0xfd];
301        let b64 = to_base64url(&data);
302        assert!(!b64.contains('+'));
303        assert!(!b64.contains('/'));
304        let decoded = from_base64url(&b64).unwrap();
305        assert_eq!(decoded, data);
306    }
307
308    #[test]
309    fn test_secure_zero() {
310        let mut data = vec![0x42u8; 32];
311        secure_zero(&mut data);
312        assert!(data.iter().all(|&b| b == 0));
313    }
314
315    #[test]
316    fn test_secure_compare() {
317        assert!(secure_compare(b"hello", b"hello"));
318        assert!(!secure_compare(b"hello", b"world"));
319        assert!(!secure_compare(b"hello", b"helloworld"));
320    }
321
322    #[test]
323    fn test_concat() {
324        let result = concat(&[b"hello", b" ", b"world"]);
325        assert_eq!(result, b"hello world");
326    }
327
328    #[test]
329    fn test_xor_bytes() {
330        let a = [0x00, 0xFF, 0x00, 0xFF];
331        let b = [0xFF, 0x00, 0xFF, 0x00];
332        let result = xor_bytes(&a, &b);
333        assert_eq!(result, vec![0xFF, 0xFF, 0xFF, 0xFF]);
334    }
335
336    #[test]
337    fn test_pkcs7_padding() {
338        let padded = pad_pkcs7(b"hello", 16);
339        assert_eq!(padded.len(), 16);
340        assert_eq!(padded[5..], vec![11u8; 11]);
341
342        let unpadded = unpad_pkcs7(&padded).unwrap();
343        assert_eq!(unpadded, b"hello");
344    }
345
346    #[test]
347    fn test_int_bytes_conversion() {
348        let value = 0x0102u64;
349        let bytes = int_to_bytes_be(value, 2);
350        assert_eq!(bytes, vec![0x01, 0x02]);
351        assert_eq!(bytes_to_int_be(&bytes), value);
352
353        let bytes_le = int_to_bytes_le(value, 2);
354        assert_eq!(bytes_le, vec![0x02, 0x01]);
355        assert_eq!(bytes_to_int_le(&bytes_le), value);
356    }
357}