buup/transformers/
uuid_generate.rs

1// WARNING: This uses a non-cryptographically secure pseudo-random number generator.
2// It is purely for demonstration purposes within the zero-dependency constraint.
3// Do NOT use these UUIDs for security-sensitive applications.
4
5use crate::{Transform, TransformError, TransformerCategory};
6use core::cell::Cell; // Using Cell for interior mutability for the PRNG state
7use core::fmt::Write;
8
9// Simple Linear Congruential Generator (LCG) state
10// Parameters from POSIX `rand()` - not great, but simple and dependency-free
11// We use Cell for interior mutability without needing &mut self in transform
12thread_local!(static LCG_STATE: Cell<u32> = const { Cell::new(12345) });
13
14fn lcg_rand() -> u32 {
15    LCG_STATE.with(|state_cell| {
16        let current_state = state_cell.get();
17        // LCG formula: X_{n+1} = (a * X_n + c) mod m
18        // Using m = 2^31, a = 1103515245, c = 12345 from POSIX standard
19        // We compute using u64 to avoid overflow during multiplication
20        let next_state = ((1103515245u64 * current_state as u64 + 12345) % 2147483648u64) as u32;
21        state_cell.set(next_state);
22        // Return the upper 16 bits like some `rand()` implementations do
23        // to get slightly better distribution in higher bits
24        // but for UUID we need 32 bits, so let's just return next_state for now.
25        next_state
26    })
27}
28
29// Function to generate 16 bytes of pseudo-random data
30fn generate_random_bytes() -> [u8; 16] {
31    let mut bytes = [0u8; 16];
32    for chunk in bytes.chunks_mut(4) {
33        let random_u32 = lcg_rand();
34        chunk.copy_from_slice(&random_u32.to_be_bytes());
35    }
36    bytes
37}
38
39/// UUID Generate transformer
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub struct UuidGenerate;
42
43impl Transform for UuidGenerate {
44    fn name(&self) -> &'static str {
45        "UUID Generate (v4)"
46    }
47
48    fn id(&self) -> &'static str {
49        "uuid_generate"
50    }
51
52    fn description(&self) -> &'static str {
53        "Generates a version 4 UUID. Input is ignored. WARNING: Uses a non-cryptographically secure PRNG."
54    }
55
56    fn category(&self) -> TransformerCategory {
57        TransformerCategory::Other
58    }
59
60    fn transform(&self, _input: &str) -> Result<String, TransformError> {
61        // Seed the LCG minimally on first call per thread if needed,
62        // using something slightly varying. Still very weak.
63        // A proper seed would ideally use system time or /dev/urandom if allowed.
64        LCG_STATE.with(|state_cell| {
65            if state_cell.get() == 12345 {
66                // Default initial value
67                // Use address of input string XORed with a constant as a *very weak* seed attempt
68                let seed = (_input.as_ptr() as u32) ^ 0xDEADBEEF;
69                state_cell.set(seed.wrapping_add(1)); // Avoid 0 if possible
70            }
71        });
72
73        let mut bytes = generate_random_bytes();
74
75        // Set version (4) and variant (RFC 4122)
76        bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4
77        bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 1 (RFC 4122)
78
79        // Format as UUID string xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
80        let mut uuid_str = String::with_capacity(36);
81        write!(
82            &mut uuid_str,
83            "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
84            bytes[0], bytes[1], bytes[2], bytes[3],
85            bytes[4], bytes[5],
86            bytes[6], bytes[7],
87            bytes[8], bytes[9],
88            bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]
89        ).map_err(|e| TransformError::InvalidArgument(format!("Failed to format UUID: {}", e).into()))?;
90
91        Ok(uuid_str)
92    }
93
94    fn default_test_input(&self) -> &'static str {
95        "" // Input is ignored, so empty string is fine
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use std::collections::HashSet;
103
104    #[test]
105    fn test_uuid_generate_format() {
106        let transformer = UuidGenerate;
107        let uuid_str = transformer.transform("test").unwrap();
108
109        // Check length
110        assert_eq!(uuid_str.len(), 36);
111
112        // Check hyphens
113        assert_eq!(uuid_str.chars().nth(8), Some('-'));
114        assert_eq!(uuid_str.chars().nth(13), Some('-'));
115        assert_eq!(uuid_str.chars().nth(18), Some('-'));
116        assert_eq!(uuid_str.chars().nth(23), Some('-'));
117
118        // Check version (char 14 should be '4')
119        assert_eq!(uuid_str.chars().nth(14), Some('4'));
120
121        // Check variant (char 19 should be '8', '9', 'a', or 'b')
122        let variant_char = uuid_str.chars().nth(19).unwrap();
123        assert!(matches!(variant_char, '8' | '9' | 'a' | 'b'));
124
125        // Check if all other chars are hex
126        for (i, c) in uuid_str.chars().enumerate() {
127            if ![8, 13, 18, 23].contains(&i) {
128                assert!(
129                    c.is_ascii_hexdigit(),
130                    "Char at index {} is not hex: {}",
131                    i,
132                    c
133                );
134            }
135        }
136    }
137
138    #[test]
139    fn test_uuid_generate_uniqueness_basic() {
140        // This test is weak due to the poor PRNG, but checks for basic differences.
141        let transformer = UuidGenerate;
142        let mut generated_uuids = HashSet::new();
143        for i in 0..100 {
144            let uuid_str = transformer.transform(&format!("seed_{}", i)).unwrap(); // Vary input slightly
145            assert!(
146                generated_uuids.insert(uuid_str),
147                "Duplicate UUID generated (basic check)"
148            );
149        }
150        assert_eq!(generated_uuids.len(), 100);
151    }
152}