agentmux_srv\backend\utilfn/
hash.rs

1#![allow(dead_code)]
2// Copyright 2025-2026, AgentMux Corp.
3// SPDX-License-Identifier: Apache-2.0
4
5
6// ---- Public API ----
7
8/// SHA1 hash of data, returned as base64 string.
9pub fn sha1_hash(data: &[u8]) -> String {
10    let mut hasher = Sha1::new();
11    hasher.update(data);
12    let result = hasher.finalize();
13    base64_encode(&result)
14}
15
16/// FNV-64a hash of a string, returned as base64url (no padding) string.
17pub fn quick_hash_string(s: &str) -> String {
18    let mut hasher = Fnv64a::new();
19    hasher.update(s.as_bytes());
20    let result = hasher.finalize();
21    base64_url_encode(&result)
22}
23
24// ---- SHA1 implementation ----
25
26struct Sha1 {
27    state: [u32; 5],
28    count: u64,
29    buffer: [u8; 64],
30    buffer_len: usize,
31}
32
33impl Sha1 {
34    fn new() -> Self {
35        Self {
36            state: [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0],
37            count: 0,
38            buffer: [0u8; 64],
39            buffer_len: 0,
40        }
41    }
42
43    fn update(&mut self, data: &[u8]) {
44        let mut i = 0;
45        self.count += data.len() as u64;
46        if self.buffer_len > 0 {
47            let space = 64 - self.buffer_len;
48            let copy_len = std::cmp::min(space, data.len());
49            self.buffer[self.buffer_len..self.buffer_len + copy_len].copy_from_slice(&data[..copy_len]);
50            self.buffer_len += copy_len;
51            i = copy_len;
52            if self.buffer_len == 64 {
53                let block = self.buffer;
54                self.process_block(&block);
55                self.buffer_len = 0;
56            }
57        }
58        while i + 64 <= data.len() {
59            let mut block = [0u8; 64];
60            block.copy_from_slice(&data[i..i + 64]);
61            self.process_block(&block);
62            i += 64;
63        }
64        if i < data.len() {
65            let remaining = data.len() - i;
66            self.buffer[..remaining].copy_from_slice(&data[i..]);
67            self.buffer_len = remaining;
68        }
69    }
70
71    #[allow(clippy::needless_range_loop)]
72    fn process_block(&mut self, block: &[u8; 64]) {
73        let mut w = [0u32; 80];
74        for i in 0..16 {
75            w[i] = u32::from_be_bytes([block[i * 4], block[i * 4 + 1], block[i * 4 + 2], block[i * 4 + 3]]);
76        }
77        for i in 16..80 {
78            w[i] = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]).rotate_left(1);
79        }
80        let [mut a, mut b, mut c, mut d, mut e] = self.state;
81        for i in 0..80 {
82            let (f, k) = match i {
83                0..=19 => ((b & c) | ((!b) & d), 0x5A827999u32),
84                20..=39 => (b ^ c ^ d, 0x6ED9EBA1u32),
85                40..=59 => ((b & c) | (b & d) | (c & d), 0x8F1BBCDCu32),
86                _ => (b ^ c ^ d, 0xCA62C1D6u32),
87            };
88            let temp = a.rotate_left(5).wrapping_add(f).wrapping_add(e).wrapping_add(k).wrapping_add(w[i]);
89            e = d;
90            d = c;
91            c = b.rotate_left(30);
92            b = a;
93            a = temp;
94        }
95        self.state[0] = self.state[0].wrapping_add(a);
96        self.state[1] = self.state[1].wrapping_add(b);
97        self.state[2] = self.state[2].wrapping_add(c);
98        self.state[3] = self.state[3].wrapping_add(d);
99        self.state[4] = self.state[4].wrapping_add(e);
100    }
101
102    fn finalize(mut self) -> [u8; 20] {
103        let bit_count = self.count * 8;
104        // Padding
105        let mut padding = vec![0x80u8];
106        let pad_len = if self.buffer_len < 56 {
107            56 - self.buffer_len - 1
108        } else {
109            120 - self.buffer_len - 1
110        };
111        padding.extend(std::iter::repeat_n(0u8, pad_len));
112        padding.extend_from_slice(&bit_count.to_be_bytes());
113        self.update(&padding);
114
115        let mut result = [0u8; 20];
116        for (i, &s) in self.state.iter().enumerate() {
117            result[i * 4..i * 4 + 4].copy_from_slice(&s.to_be_bytes());
118        }
119        result
120    }
121}
122
123// ---- FNV-64a implementation ----
124
125struct Fnv64a {
126    hash: u64,
127}
128
129impl Fnv64a {
130    const OFFSET_BASIS: u64 = 0xcbf29ce484222325;
131    const PRIME: u64 = 0x100000001b3;
132
133    fn new() -> Self {
134        Self { hash: Self::OFFSET_BASIS }
135    }
136
137    fn update(&mut self, data: &[u8]) {
138        for &b in data {
139            self.hash ^= b as u64;
140            self.hash = self.hash.wrapping_mul(Self::PRIME);
141        }
142    }
143
144    fn finalize(&self) -> [u8; 8] {
145        self.hash.to_be_bytes()
146    }
147}
148
149// ---- Base64 encoding ----
150
151fn base64_encode(data: &[u8]) -> String {
152    const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
153    let mut result = String::new();
154    let mut i = 0;
155    while i < data.len() {
156        let b0 = data[i] as u32;
157        let b1 = if i + 1 < data.len() { data[i + 1] as u32 } else { 0 };
158        let b2 = if i + 2 < data.len() { data[i + 2] as u32 } else { 0 };
159        let triple = (b0 << 16) | (b1 << 8) | b2;
160        result.push(CHARS[((triple >> 18) & 0x3F) as usize] as char);
161        result.push(CHARS[((triple >> 12) & 0x3F) as usize] as char);
162        if i + 1 < data.len() {
163            result.push(CHARS[((triple >> 6) & 0x3F) as usize] as char);
164        } else {
165            result.push('=');
166        }
167        if i + 2 < data.len() {
168            result.push(CHARS[(triple & 0x3F) as usize] as char);
169        } else {
170            result.push('=');
171        }
172        i += 3;
173    }
174    result
175}
176
177fn base64_url_encode(data: &[u8]) -> String {
178    const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
179    let mut result = String::new();
180    let mut i = 0;
181    while i < data.len() {
182        let b0 = data[i] as u32;
183        let b1 = if i + 1 < data.len() { data[i + 1] as u32 } else { 0 };
184        let b2 = if i + 2 < data.len() { data[i + 2] as u32 } else { 0 };
185        let triple = (b0 << 16) | (b1 << 8) | b2;
186        result.push(CHARS[((triple >> 18) & 0x3F) as usize] as char);
187        result.push(CHARS[((triple >> 12) & 0x3F) as usize] as char);
188        if i + 1 < data.len() {
189            result.push(CHARS[((triple >> 6) & 0x3F) as usize] as char);
190        }
191        if i + 2 < data.len() {
192            result.push(CHARS[(triple & 0x3F) as usize] as char);
193        }
194        i += 3;
195    }
196    result
197}