dispnet_hash/
lib.rs

1use std::{
2    fmt,
3    str::{from_utf8, FromStr},
4};
5
6#[derive(Debug)]
7pub enum HashError {
8    Undefined,
9    InvalidDigest { hex_digest: String },
10    DigestLength { raw_digest_length: String },
11    DigestLengthMissmatch { length: usize, digest: Vec<u8> },
12}
13
14#[derive(Debug)]
15pub struct HashConfig {
16    pub salt: Option<Box<Vec<u8>>>,
17}
18
19#[derive(Debug, PartialEq)]
20pub enum HashType {
21    Blake3,
22    CRC,
23    Argon2,
24}
25
26impl fmt::Display for HashType {
27    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
28        match *self {
29            HashType::Argon2 => {
30                write!(f, "{:02}", 3)
31            }
32            HashType::CRC => {
33                write!(f, "{:02}", 2)
34            }
35            _ => {
36                write!(f, "{:02}", 1)
37            }
38        }
39    }
40}
41
42/// Dispnet hash is as self descriping hash format.
43///
44/// # Display format is structured as followed:
45///
46/// * First 2 characters are the hash type as integer with a leading 0 (Default is 01 which is Blake3 hash).
47/// * Then come 4 characters as integer with leading 0 which is the length of the bytes from the digest.
48/// * Digest value as hex.
49///
50/// # Examles
51/// ```
52/// fn new_hash() {
53///     let dispnet_hash = dispnet_hash::DispnetHash::new("test".as_bytes());
54///     let display_hash = format!("{}", dispnet_hash);
55///     assert_eq!(display_hash, "010324878ca0425c739fa427f7eda20fe845f6b2e46ba5fe2a14df5b1e32f50603215");
56/// }
57/// ```
58#[derive(Debug)]
59pub struct DispnetHash {
60    pub hash_type: HashType,
61    pub digest_length: usize,
62    pub digest_value: Vec<u8>,
63    pub digest_encoded: u64,
64    value: String,
65}
66
67trait Hash {
68    fn equal(hash: DispnetHash) -> bool;
69    fn upgrade();
70}
71
72impl DispnetHash {
73    /// Create a hash with the default typ (Blake3).
74    pub fn new(value: &[u8]) -> Self {
75        DispnetHash::create(HashType::Blake3, value, None)
76    }
77
78    /// Create a new dispnet hash.
79    /// 
80    /// # Usage
81    /// ```
82    /// use dispnet_hash::{DispnetHash, HashType, HashConfig};
83    /// 
84    /// fn create_hashes() {
85    ///     let dispnet_hash_Balke3 = DispnetHash::create(HashType::Blake3, "test".as_bytes(), None);
86    ///     let dispnet_hash_CRC = DispnetHash::create(HashType::CRC, "test".as_bytes(), None);
87    ///     let dispnet_hash_Argon2 = DispnetHash::create(HashType::Argon2, "test".as_bytes(), None);
88    ///     let dispnet_hash_Argon2_slat = DispnetHash::create(HashType::Argon2, "test".as_bytes(), Some(HashConfig { salt: Some(Box::new(b"12345678".to_vec())) }));
89    /// }
90    /// ```
91    pub fn create(hash_type: HashType, value: &[u8], config: Option<HashConfig>) -> Self {
92        let internal_hash = InternalDispnetHash::new(hash_type, value, config);
93        let internal_hash_value = format!("{}", internal_hash);
94        let encoded: u64 = DispnetHash::encoded_u64(&internal_hash.digest_value);
95        Self {
96            hash_type: internal_hash.hash_type,
97            digest_length: internal_hash.digest_length,
98            digest_value: internal_hash.digest_value,
99            digest_encoded: encoded,
100            value: internal_hash_value,
101        }
102    }
103
104    /// Verify a dispnet hash string with raw value.
105    /// The hash must be created with the Argon2 type
106    /// # Usage
107    /// ```
108    /// use dispnet_hash::{DispnetHash, HashType};
109    /// 
110    /// fn verify_hash() {
111    ///     let dispnet_hash = DispnetHash::create(HashType::Argon2, "test".as_bytes(), None);
112    ///     
113    ///     DispnetHash::verify(&dispnet_hash.to_string(), "test".as_bytes());
114    /// }
115    /// ```
116    pub fn verify(hash: &str, value: &[u8]) -> bool {
117        let dispnet_hash = hash.parse::<DispnetHash>();
118        if let Ok(hash) = dispnet_hash {
119            return DispnetHash::verify_instance(&hash, value);
120        }
121        false
122    }
123
124    /// Verify a dispnet hash instance with raw value.
125    /// The hash must be created with the Argon2 type
126    /// # Usage
127    /// ```
128    /// use dispnet_hash::{DispnetHash, HashType};
129    /// 
130    /// fn verify_hash_instance() {
131    ///     let dispnet_hash = DispnetHash::create(HashType::Argon2, "test".as_bytes(), None);
132    ///     
133    ///     DispnetHash::verify_instance(&dispnet_hash, "test".as_bytes());
134    /// }
135    /// ```
136    pub fn verify_instance(hash: &DispnetHash, value: &[u8]) -> bool {
137        let str_hash = from_utf8(&hash.digest_value).unwrap();
138        let matches_result = argon2::verify_encoded(str_hash, value);
139        if let Ok(matches) = matches_result {
140            return matches;
141        }
142        false
143    }
144
145    fn parse(hash_value: &str) -> Result<Self, HashError> {
146        let internal_hash_result = InternalDispnetHash::parse(hash_value);
147        if let Ok(internal_hash) = internal_hash_result {
148            let internal_hash_value = format!("{}", internal_hash);
149            let encoded: u64 = DispnetHash::encoded_u64(&internal_hash.digest_value);
150            return Ok(Self {
151                hash_type: internal_hash.hash_type,
152                digest_length: internal_hash.digest_length,
153                digest_value: internal_hash.digest_value,
154                digest_encoded: encoded,
155                value: internal_hash_value,
156            });
157        }
158        Err(internal_hash_result.err().unwrap())
159    }
160
161    /// Convert a hexadecimal string to a vector of bytes.
162    /// Returns `None` if the input string has an odd length which makes it an invalid hex string.
163    /// # Usage
164    /// ```
165    /// use dispnet_hash::DispnetHash;
166    ///
167    /// fn hex_to_bytes() {
168    ///     let hex_string = "74657374";
169    ///     let bytes = DispnetHash::hex_to_bytes(hex_string).unwrap();
170    ///     assert_eq!(bytes, vec![116, 101, 115, 116]);
171    /// }
172    /// ```
173    pub fn hex_to_bytes(s: &str) -> Option<Vec<u8>> {
174        if s.len() % 2 == 0 {
175            (0..s.len())
176                .step_by(2)
177                .map(|i| {
178                    s.get(i..i + 2)
179                        .and_then(|sub| u8::from_str_radix(sub, 16).ok())
180                })
181                .collect()
182        } else {
183            None
184        }
185    }
186    
187    /// Convert a slice of bytes to a hexadecimal string.
188    /// # Usage
189    /// ```
190    /// use dispnet_hash::DispnetHash;
191    ///
192    /// fn bytes_to_hex() {
193    ///     let bytes = vec![116, 101, 115, 116];
194    ///     let hex_string = DispnetHash::bytes_to_hex(&bytes);
195    ///     assert_eq!(hex_string, "74657374");
196    /// }
197    /// ```
198    pub fn bytes_to_hex(bytes: &[u8]) -> String {
199        bytes.iter().map(|b| format!("{:02x}", b)).collect()
200    }
201
202    /// Convert a slice of bytes to a u64 integer.
203    /// If the length of the slice is less than 8, it is converted to a u64 integer using little-endian byte order.
204    /// Otherwise, the last 8 bytes of the slice are converted to a u64 integer using little-endian byte order.
205    /// # Usage
206    /// ```
207    /// use dispnet_hash::DispnetHash;
208    ///
209    /// fn encoded_u64() {
210    ///     let bytes = vec![0, 0, 0, 0, 0, 0, 0, 1];
211    ///     let encoded = DispnetHash::encoded_u64(&bytes);
212    ///     assert_eq!(encoded, 72057594037927936);
213    /// }
214    /// ```
215    pub fn encoded_u64(bytes: &[u8]) -> u64 {
216        if bytes.len() < 8 {
217            let mut b = [0; 8];
218            b[..bytes.len()].copy_from_slice(bytes);
219            return u64::from_le_bytes(b);
220        }
221        u64::from_le_bytes(bytes[(bytes.len() - 8)..].try_into().unwrap())
222    }
223}
224
225impl fmt::Display for DispnetHash {
226    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
227        write!(f, "{}", self.value)
228    }
229}
230
231impl PartialEq for DispnetHash {
232    fn eq(&self, other: &Self) -> bool {
233        self.value == other.value
234    }
235}
236
237impl PartialEq<String> for DispnetHash {
238    fn eq(&self, other: &String) -> bool {
239        self.value == *other
240    }
241}
242
243impl FromStr for DispnetHash {
244    type Err = HashError;
245
246    fn from_str(s: &str) -> Result<Self, HashError> {
247        DispnetHash::parse(s)
248    }
249}
250
251#[derive(Debug)]
252struct InternalDispnetHash {
253    pub hash_type: HashType,
254    pub digest_length: usize,
255    pub digest_value: Vec<u8>,
256}
257
258impl InternalDispnetHash {
259    fn new(hash_type: HashType, value: &[u8], config: Option<HashConfig>) -> Self {
260        let mut _hash_config: HashConfig = HashConfig { salt: None };
261        let mut config_hash_salt: Box<Vec<u8>> =
262            Box::new("A8nUz1Pkc0IZ0uJSZNnMlvdLz0T3al5Hjhg2".as_bytes().to_owned());
263        let salt: &[u8];
264
265        if let Some(_hash_config) = config {
266            if let Some(config_hash_salt_value) = _hash_config.salt {
267                config_hash_salt = config_hash_salt_value;
268                salt = &(*config_hash_salt);
269            } else {
270                salt = &(*config_hash_salt);
271            }
272        } else {
273            salt = &(*config_hash_salt);
274        }
275        match hash_type {
276            HashType::Argon2 => {
277                let argon2_config = argon2::Config::default();
278                let hash = argon2::hash_encoded(value, salt, &argon2_config).unwrap();
279                Self {
280                    hash_type: HashType::Argon2,
281                    digest_length: hash.len(),
282                    digest_value: hash.into_bytes().to_vec(),
283                }
284            }
285            HashType::CRC => {
286                let crc32 = crc::Crc::<u32>::new(&crc::CRC_32_ISCSI);
287                let hash = crc32.checksum(value).to_string();
288                Self {
289                    hash_type: HashType::CRC,
290                    digest_length: hash.len(),
291                    digest_value: hash.into_bytes().to_vec(),
292                }
293            }
294            _ => {
295                let hash = blake3::hash(value);
296                let hash_bytes = hash.as_bytes();
297                Self {
298                    hash_type: HashType::Blake3,
299                    digest_length: hash_bytes.len(),
300                    digest_value: hash_bytes.to_vec(),
301                }
302            }
303        }
304    }
305
306    fn parse(hash_value: &str) -> Result<Self, HashError> {
307        let (raw_type, raw_digest_len_value) = hash_value.split_at(2);
308        let (raw_digest_len, raw_digest_value) = raw_digest_len_value.split_at(4);
309        let mut type_result = HashType::Blake3;
310        let raw_type_result = raw_type.parse::<u8>();
311        if let Ok(raw_type) = raw_type_result {
312            match raw_type {
313                3 => {
314                    type_result = HashType::Argon2;
315                }
316                2 => {
317                    type_result = HashType::CRC;
318                }
319                _ => {
320                    type_result = HashType::Blake3;
321                }
322            }
323        } else {
324            println!(
325                "Invalid hash type raw value:{}. Use Blake3 as fallback!",
326                raw_type
327            );
328        }
329
330        let hex_result = DispnetHash::hex_to_bytes(raw_digest_value);
331        if let Some(hash_bytes) = hex_result {
332            let digest_len_result = raw_digest_len.parse::<usize>();
333            if let Ok(hash_bytes_len) = digest_len_result {
334                if hash_bytes_len == hash_bytes.len() {
335                    Ok(Self {
336                        hash_type: type_result,
337                        digest_length: hash_bytes_len,
338                        digest_value: hash_bytes,
339                    })
340                } else {
341                    println!(
342                        "Length missmatch for digest. Length:{} Digest:{}",
343                        hash_bytes_len,
344                        hash_bytes.len()
345                    );
346                    Err(HashError::DigestLengthMissmatch {
347                        length: hash_bytes_len,
348                        digest: hash_bytes,
349                    })
350                }
351            } else {
352                println!("Digest length is not a valid usize:{}", raw_digest_len);
353                Err(HashError::DigestLength {
354                    raw_digest_length: raw_digest_len.to_owned(),
355                })
356            }
357        } else {
358            println!("Invalid digest hex value:{}", raw_digest_value);
359            Err(HashError::InvalidDigest {
360                hex_digest: raw_digest_value.to_owned(),
361            })
362        }
363    }
364}
365
366impl fmt::Display for InternalDispnetHash {
367    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
368        write!(
369            f,
370            "{}{:04}{}",
371            self.hash_type,
372            self.digest_length,
373            self.digest_value
374                .iter()
375                .map(|x| format!("{:02x}", x))
376                .collect::<String>()
377        )
378    }
379}
380
381#[cfg(test)]
382mod tests {
383    use crate::{DispnetHash, HashType, HashConfig};
384
385    #[test]
386    fn new_hash() {
387        let dispnet_hash = DispnetHash::new("test".as_bytes());
388        let display_hash = format!("{}", dispnet_hash);
389        assert_eq!(display_hash, "0100324878ca0425c739fa427f7eda20fe845f6b2e46ba5fe2a14df5b1e32f50603215");
390    }
391
392    #[test]
393    fn create_blake3_hash() {
394        let dispnet_hash = DispnetHash::create(HashType::Blake3, "test".as_bytes(), None);
395        let display_hash = format!("{}", dispnet_hash);
396        assert_eq!(display_hash, "0100324878ca0425c739fa427f7eda20fe845f6b2e46ba5fe2a14df5b1e32f50603215");
397        assert_eq!(dispnet_hash.digest_encoded, 1527389121149121013);
398    }
399
400    #[test]
401    fn create_crc32_hash() {
402        let dispnet_hash = DispnetHash::create(HashType::CRC, "test".as_bytes(), None);
403        let display_hash = format!("{}", dispnet_hash);
404        assert_eq!(display_hash, "02001032323538363632303830");
405        assert_eq!(dispnet_hash.digest_encoded, 3474580104732358709);
406    }
407
408    #[test]
409    fn create_argon2_hash() {
410        let dispnet_hash = DispnetHash::create(HashType::Argon2, "test".as_bytes(), None);
411        let display_hash = format!("{}", dispnet_hash);
412        assert_eq!(display_hash, "030121246172676f6e326924763d3139246d3d343039362c743d332c703d31245154687556586f785547746a4d456c614d48564b5531704f626b3173646d524d656a42554d3246734e5568716147637924464d4f7a6f46647754464676397a31435a485751684b7a2f63696f754c55427571494a54756a574d375338");
413        assert_eq!(dispnet_hash.digest_encoded, 4058648494509552980);
414    }
415
416    #[test]
417    fn create_argon2_salt_hash() {
418        let dispnet_hash = DispnetHash::create(HashType::Argon2, "test".as_bytes(), Some(HashConfig { salt: Some(Box::new(b"12345678".to_vec())) }));
419        let display_hash = format!("{}", dispnet_hash);
420        assert_eq!(display_hash, "030084246172676f6e326924763d3139246d3d343039362c743d332c703d31244d54497a4e4455324e7a6724686f56354d494638596a39746b39356c467365546279554a6e393336484944586754685533637065643151");
421        assert_eq!(dispnet_hash.digest_encoded, 5850567777771008853);
422    }
423
424    #[test]
425    fn parse_hash() {
426        let dispnet_hash = "0100324878ca0425c739fa427f7eda20fe845f6b2e46ba5fe2a14df5b1e32f50603215".parse::<DispnetHash>().unwrap();
427        assert_eq!(dispnet_hash.hash_type, HashType::Blake3);
428        assert_eq!(dispnet_hash.digest_length, 32);
429        assert_eq!(dispnet_hash.digest_value.len(), 32);
430    }
431
432    #[test]
433    fn parse_crc32_hash() {
434        let dispnet_hash = "02001032323538363632303830".parse::<DispnetHash>().unwrap();
435        assert_eq!(dispnet_hash.hash_type, HashType::CRC);
436        assert_eq!(dispnet_hash.digest_length, 10);
437        assert_eq!(dispnet_hash.digest_value.len(), 10);
438    }
439
440    #[test]
441    fn parse_argon2_hash() {
442        let dispnet_hash = "030121246172676f6e326924763d3139246d3d343039362c743d332c703d31245154687556586f785547746a4d456c614d48564b5531704f626b3173646d524d656a42554d3246734e5568716147637924464d4f7a6f46647754464676397a31435a485751684b7a2f63696f754c55427571494a54756a574d375338".parse::<DispnetHash>().unwrap();
443        assert_eq!(dispnet_hash.hash_type, HashType::Argon2);
444        assert_eq!(dispnet_hash.digest_length, 121);
445        assert_eq!(dispnet_hash.digest_value.len(), 121);
446    }
447
448    #[test]
449    fn parse_argon2_salt_hash() {
450        let dispnet_hash = "030084246172676f6e326924763d3139246d3d343039362c743d332c703d31244d54497a4e4455324e7a6724686f56354d494638596a39746b39356c467365546279554a6e393336484944586754685533637065643151".parse::<DispnetHash>().unwrap();
451        assert_eq!(dispnet_hash.hash_type, HashType::Argon2);
452        assert_eq!(dispnet_hash.digest_length, 84);
453        assert_eq!(dispnet_hash.digest_value.len(), 84);
454    }
455
456    #[test]
457    fn compare_hash_instances() {
458        let dispnet_hash_1 = DispnetHash::new("test".as_bytes());
459        let dispnet_hash_2 = DispnetHash::new("test".as_bytes());
460        assert_eq!(dispnet_hash_1, dispnet_hash_2);
461    }
462
463    #[test]
464    fn compare_crc32_hash_instances() {
465        let dispnet_hash_1 = DispnetHash::create(HashType::CRC, "test".as_bytes(), None);
466        let dispnet_hash_2 = DispnetHash::create(HashType::CRC, "test".as_bytes(), None);
467        assert_eq!(dispnet_hash_1, dispnet_hash_2);
468    }
469
470    #[test]
471    fn compare_argon2_hash_instances() {
472        let dispnet_hash_1 = DispnetHash::create(HashType::Argon2, "test".as_bytes(), None);
473        let dispnet_hash_2 = DispnetHash::create(HashType::Argon2, "test".as_bytes(), None);
474        assert_eq!(dispnet_hash_1, dispnet_hash_2);
475    }
476
477    #[test]
478    fn compare_argon2_salt_hash_instances() {
479        let dispnet_hash_1 = DispnetHash::create(HashType::Argon2, "test".as_bytes(), Some(HashConfig { salt: Some(Box::new(b"12345678".to_vec())) }));
480        let dispnet_hash_2 = DispnetHash::create(HashType::Argon2, "test".as_bytes(), Some(HashConfig { salt: Some(Box::new(b"12345678".to_vec())) }));
481        assert_eq!(dispnet_hash_1, dispnet_hash_2);
482    }
483
484    #[test]
485    fn compare_hash_instance_and_prase() {
486        let dispnet_hash_1 = DispnetHash::new("test".as_bytes());
487        let dispnet_hash_2 = "0100324878ca0425c739fa427f7eda20fe845f6b2e46ba5fe2a14df5b1e32f50603215".parse::<DispnetHash>().unwrap();
488        assert_eq!(dispnet_hash_1, dispnet_hash_2);
489    }
490
491    #[test]
492    fn compare_crc32_hash_instance_and_prase() {
493        let dispnet_hash_1 = DispnetHash::create(HashType::CRC, "test".as_bytes(), None);
494        let dispnet_hash_2 = "02001032323538363632303830".parse::<DispnetHash>().unwrap();
495        assert_eq!(dispnet_hash_1, dispnet_hash_2);
496    }
497
498    #[test]
499    fn compare_argon2_hash_instance_and_prase() {
500        let dispnet_hash_1 = DispnetHash::create(HashType::Argon2, "test".as_bytes(), None);
501        let dispnet_hash_2 = "030121246172676f6e326924763d3139246d3d343039362c743d332c703d31245154687556586f785547746a4d456c614d48564b5531704f626b3173646d524d656a42554d3246734e5568716147637924464d4f7a6f46647754464676397a31435a485751684b7a2f63696f754c55427571494a54756a574d375338".parse::<DispnetHash>().unwrap();
502        assert_eq!(dispnet_hash_1, dispnet_hash_2);
503    }
504
505    #[test]
506    fn compare_argon2_salt_hash_instance_and_prase() {
507        let dispnet_hash_1 = DispnetHash::create(HashType::Argon2, "test".as_bytes(), Some(HashConfig { salt: Some(Box::new(b"12345678".to_vec())) }));
508        let dispnet_hash_2 = "030084246172676f6e326924763d3139246d3d343039362c743d332c703d31244d54497a4e4455324e7a6724686f56354d494638596a39746b39356c467365546279554a6e393336484944586754685533637065643151".parse::<DispnetHash>().unwrap();
509        assert_eq!(dispnet_hash_1, dispnet_hash_2);
510    }
511
512    #[test]
513    fn compare_hash_instance_and_string() {
514        let dispnet_hash_1 = DispnetHash::new("test".as_bytes());
515        assert_eq!(dispnet_hash_1, "0100324878ca0425c739fa427f7eda20fe845f6b2e46ba5fe2a14df5b1e32f50603215".to_owned());
516    }
517
518    #[test]
519    fn compare_crc32_hash_instance_and_string() {
520        let dispnet_hash_1 = DispnetHash::create(HashType::CRC, "test".as_bytes(), None);
521        assert_eq!(dispnet_hash_1, "02001032323538363632303830".to_owned());
522    }
523
524    #[test]
525    fn verify_argon2_hash() {
526        assert!(DispnetHash::verify("030084246172676f6e326924763d3139246d3d343039362c743d332c703d31244d54497a4e4455324e7a6724686f56354d494638596a39746b39356c467365546279554a6e393336484944586754685533637065643151", "test".as_bytes()));
527        assert!(!DispnetHash::verify("030084246172676f6e326924763d3139246d3d343039362c743d332c703d31244d54497a4e4455324e7a6724686f56354d494638596a39746b39356c467365546279554a6e393336484944586754685533637065644262", "test".as_bytes()));
528    }
529
530    #[test]
531    fn hex() {
532        assert_eq!(DispnetHash::bytes_to_hex("test".as_bytes()), "74657374");
533        assert_eq!(DispnetHash::hex_to_bytes("74657374").unwrap(), "test".as_bytes());
534    }
535
536    #[test]
537    fn encoded_u64() {
538        assert_eq!(DispnetHash::encoded_u64("test".as_bytes()), 1953719668);
539        assert_eq!(DispnetHash::encoded_u64("a".as_bytes()), 97);
540        assert_eq!(DispnetHash::encoded_u64("aasdsakdljaslfhaksjhuahwiuewasdfgs4354sg".as_bytes()), 7454359211325289319);
541    }
542}