Skip to main content

robinpath_modules/modules/
hash_mod.rs

1use robinpath::{RobinPath, Value};
2
3pub fn register(rp: &mut RobinPath) {
4    // hash.md5 input → hex string
5    rp.register_builtin("hash.md5", |args, _| {
6        let input = args.first().map(|v| v.to_display_string()).unwrap_or_default();
7        use md5::Digest;
8        let result = md5::Md5::digest(input.as_bytes());
9        Ok(Value::String(hex::encode(result)))
10    });
11
12    // hash.sha1 input → hex string
13    rp.register_builtin("hash.sha1", |args, _| {
14        let input = args.first().map(|v| v.to_display_string()).unwrap_or_default();
15        use sha1::Digest;
16        let result = sha1::Sha1::digest(input.as_bytes());
17        Ok(Value::String(hex::encode(result)))
18    });
19
20    // hash.sha256 input → hex string
21    rp.register_builtin("hash.sha256", |args, _| {
22        let input = args.first().map(|v| v.to_display_string()).unwrap_or_default();
23        use sha2::Digest;
24        let result = sha2::Sha256::digest(input.as_bytes());
25        Ok(Value::String(hex::encode(result)))
26    });
27
28    // hash.sha512 input → hex string
29    rp.register_builtin("hash.sha512", |args, _| {
30        let input = args.first().map(|v| v.to_display_string()).unwrap_or_default();
31        use sha2::Digest;
32        let result = sha2::Sha512::digest(input.as_bytes());
33        Ok(Value::String(hex::encode(result)))
34    });
35
36    // hash.hmac input key algorithm? → hex string
37    rp.register_builtin("hash.hmac", |args, _| {
38        let input = args.first().map(|v| v.to_display_string()).unwrap_or_default();
39        let key = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
40        let algo = args.get(2).map(|v| v.to_display_string()).unwrap_or_else(|| "sha256".to_string());
41        use hmac::{Hmac, Mac};
42        match algo.as_str() {
43            "sha256" => {
44                let mut mac = Hmac::<sha2::Sha256>::new_from_slice(key.as_bytes())
45                    .map_err(|e| format!("HMAC error: {}", e))?;
46                mac.update(input.as_bytes());
47                Ok(Value::String(hex::encode(mac.finalize().into_bytes())))
48            }
49            "sha1" => {
50                let mut mac = Hmac::<sha1::Sha1>::new_from_slice(key.as_bytes())
51                    .map_err(|e| format!("HMAC error: {}", e))?;
52                mac.update(input.as_bytes());
53                Ok(Value::String(hex::encode(mac.finalize().into_bytes())))
54            }
55            "sha512" => {
56                let mut mac = Hmac::<sha2::Sha512>::new_from_slice(key.as_bytes())
57                    .map_err(|e| format!("HMAC error: {}", e))?;
58                mac.update(input.as_bytes());
59                Ok(Value::String(hex::encode(mac.finalize().into_bytes())))
60            }
61            "md5" => {
62                let mut mac = Hmac::<md5::Md5>::new_from_slice(key.as_bytes())
63                    .map_err(|e| format!("HMAC error: {}", e))?;
64                mac.update(input.as_bytes());
65                Ok(Value::String(hex::encode(mac.finalize().into_bytes())))
66            }
67            _ => Err(format!("Unsupported algorithm: {}", algo)),
68        }
69    });
70
71    // hash.hashFile filePath algorithm? → hex string
72    rp.register_builtin("hash.hashFile", |args, _| {
73        let path = args.first().map(|v| v.to_display_string()).unwrap_or_default();
74        let algo = args.get(1).map(|v| v.to_display_string()).unwrap_or_else(|| "sha256".to_string());
75        let data = std::fs::read(&path).map_err(|e| format!("hash.hashFile error: {}", e))?;
76        match algo.as_str() {
77            "sha256" => { use sha2::Digest; Ok(Value::String(hex::encode(sha2::Sha256::digest(&data)))) }
78            "sha512" => { use sha2::Digest; Ok(Value::String(hex::encode(sha2::Sha512::digest(&data)))) }
79            "sha1" => { use sha1::Digest; Ok(Value::String(hex::encode(sha1::Sha1::digest(&data)))) }
80            "md5" => { use md5::Digest; Ok(Value::String(hex::encode(md5::Md5::digest(&data)))) }
81            _ => Err(format!("Unsupported algorithm: {}", algo)),
82        }
83    });
84
85    // hash.crc32 input → number
86    rp.register_builtin("hash.crc32", |args, _| {
87        let input = args.first().map(|v| v.to_display_string()).unwrap_or_default();
88        let crc = crc32_compute(input.as_bytes());
89        let as_hex = args.get(1).map(|v| match v { Value::Bool(b) => *b, _ => false }).unwrap_or(false);
90        if as_hex {
91            Ok(Value::String(format!("{:08x}", crc)))
92        } else {
93            Ok(Value::Number(crc as f64))
94        }
95    });
96
97    // hash.checksum input expectedHash algorithm? → bool
98    rp.register_builtin("hash.checksum", |args, _| {
99        let input = args.first().map(|v| v.to_display_string()).unwrap_or_default();
100        let expected = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
101        let algo = args.get(2).map(|v| v.to_display_string()).unwrap_or_else(|| "sha256".to_string());
102        let actual = match algo.as_str() {
103            "sha256" => { use sha2::Digest; hex::encode(sha2::Sha256::digest(input.as_bytes())) }
104            "sha512" => { use sha2::Digest; hex::encode(sha2::Sha512::digest(input.as_bytes())) }
105            "sha1" => { use sha1::Digest; hex::encode(sha1::Sha1::digest(input.as_bytes())) }
106            "md5" => { use md5::Digest; hex::encode(md5::Md5::digest(input.as_bytes())) }
107            _ => return Err(format!("Unsupported algorithm: {}", algo)),
108        };
109        Ok(Value::Bool(actual == expected.to_lowercase()))
110    });
111
112    // hash.compare a b → bool (constant-time)
113    rp.register_builtin("hash.compare", |args, _| {
114        let a = args.first().map(|v| v.to_display_string()).unwrap_or_default();
115        let b = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
116        if a.len() != b.len() {
117            return Ok(Value::Bool(false));
118        }
119        let mut diff = 0u8;
120        for (x, y) in a.bytes().zip(b.bytes()) {
121            diff |= x ^ y;
122        }
123        Ok(Value::Bool(diff == 0))
124    });
125
126    // hash.randomHex length → hex string
127    rp.register_builtin("hash.randomHex", |args, _| {
128        let length = args.first().map(|v| v.to_number() as usize).unwrap_or(32);
129        let bytes = random_bytes(length / 2 + 1);
130        let hex: String = bytes.iter().map(|b| format!("{:02x}", b)).collect();
131        Ok(Value::String(hex[..length.min(hex.len())].to_string()))
132    });
133
134    // hash.fingerprint input → {md5, sha256}
135    rp.register_builtin("hash.fingerprint", |args, _| {
136        let input = args.first().map(|v| v.to_display_string()).unwrap_or_default();
137        let md5_hash = { use md5::Digest; hex::encode(md5::Md5::digest(input.as_bytes())) };
138        let sha256_hash = { use sha2::Digest; hex::encode(sha2::Sha256::digest(input.as_bytes())) };
139        let mut obj = indexmap::IndexMap::new();
140        obj.insert("md5".to_string(), Value::String(md5_hash));
141        obj.insert("sha256".to_string(), Value::String(sha256_hash));
142        Ok(Value::Object(obj))
143    });
144}
145
146fn crc32_compute(data: &[u8]) -> u32 {
147    let mut crc: u32 = 0xFFFFFFFF;
148    for &byte in data {
149        crc ^= byte as u32;
150        for _ in 0..8 {
151            if crc & 1 != 0 {
152                crc = (crc >> 1) ^ 0xEDB88320;
153            } else {
154                crc >>= 1;
155            }
156        }
157    }
158    !crc
159}
160
161fn random_bytes(n: usize) -> Vec<u8> {
162    use std::collections::hash_map::RandomState;
163    use std::hash::{BuildHasher, Hasher};
164    let mut bytes = Vec::with_capacity(n);
165    for i in 0..n {
166        let state = RandomState::new();
167        let mut hasher = state.build_hasher();
168        hasher.write_usize(i);
169        bytes.push((hasher.finish() & 0xFF) as u8);
170    }
171    bytes
172}