use crate::pool::Pool;
use alloc::format;
use alloc::string::String;
use core::marker::PhantomData;
use core::mem::MaybeUninit;
pub struct Sha1Context<'pool> {
ctx: apr_sys::apr_sha1_ctx_t,
_pool: PhantomData<&'pool Pool<'pool>>,
}
impl<'pool> Sha1Context<'pool> {
pub fn new(_pool: &'pool Pool<'pool>) -> Self {
let mut ctx = MaybeUninit::uninit();
unsafe {
apr_sys::apr_sha1_init(ctx.as_mut_ptr());
}
Sha1Context {
ctx: unsafe { ctx.assume_init() },
_pool: PhantomData,
}
}
pub fn update(&mut self, data: &[u8]) {
unsafe {
apr_sys::apr_sha1_update(
&mut self.ctx,
data.as_ptr() as *const core::ffi::c_char,
data.len() as core::ffi::c_uint,
);
}
}
pub fn update_binary(&mut self, data: &[u8]) {
unsafe {
apr_sys::apr_sha1_update_binary(
&mut self.ctx,
data.as_ptr() as *const core::ffi::c_uchar,
data.len() as core::ffi::c_uint,
);
}
}
pub fn finalize(mut self) -> [u8; APR_SHA1_DIGESTSIZE] {
let mut digest = [0u8; APR_SHA1_DIGESTSIZE];
unsafe {
apr_sys::apr_sha1_final(digest.as_mut_ptr(), &mut self.ctx);
}
digest
}
}
pub const APR_SHA1_DIGESTSIZE: usize = 20;
pub fn hash(data: &[u8]) -> [u8; APR_SHA1_DIGESTSIZE] {
crate::pool::with_tmp_pool(|pool| sha1(data, pool))
}
pub fn hash_hex(data: &[u8]) -> String {
crate::pool::with_tmp_pool(|pool| sha1_encode(data, pool))
}
pub fn sha1(data: &[u8], pool: &Pool<'_>) -> [u8; APR_SHA1_DIGESTSIZE] {
let mut ctx = Sha1Context::new(pool);
ctx.update_binary(data);
ctx.finalize()
}
pub fn sha1_encode(data: &[u8], pool: &Pool<'_>) -> String {
let digest = sha1(data, pool);
let mut result = String::with_capacity(APR_SHA1_DIGESTSIZE * 2);
for byte in digest.iter() {
result.push_str(&format!("{:02x}", byte));
}
result
}
pub fn sha1_base64(data: &[u8], pool: &Pool<'_>) -> String {
let digest = sha1(data, pool);
base64_encode(&digest)
}
fn base64_encode(data: &[u8]) -> String {
const BASE64_CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut result = String::new();
let mut i = 0;
while i < data.len() {
let b1 = data[i];
let b2 = if i + 1 < data.len() { data[i + 1] } else { 0 };
let b3 = if i + 2 < data.len() { data[i + 2] } else { 0 };
result.push(BASE64_CHARS[(b1 >> 2) as usize] as char);
result.push(BASE64_CHARS[(((b1 & 0x03) << 4) | (b2 >> 4)) as usize] as char);
if i + 1 < data.len() {
result.push(BASE64_CHARS[(((b2 & 0x0f) << 2) | (b3 >> 6)) as usize] as char);
} else {
result.push('=');
}
if i + 2 < data.len() {
result.push(BASE64_CHARS[(b3 & 0x3f) as usize] as char);
} else {
result.push('=');
}
i += 3;
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sha1_empty() {
let pool = Pool::new();
let hex = sha1_encode(b"", &pool);
assert_eq!(hex, "da39a3ee5e6b4b0d3255bfef95601890afd80709");
}
#[test]
fn test_sha1_hello_world() {
let pool = Pool::new();
let hex = sha1_encode(b"Hello, World!", &pool);
assert_eq!(hex, "0a0a9f2a6772942557ab5355d76af442f8f65e01");
}
#[test]
fn test_sha1_incremental() {
let pool = Pool::new();
let mut ctx = Sha1Context::new(&pool);
ctx.update_binary(b"Hello, ");
ctx.update_binary(b"World!");
let digest = ctx.finalize();
let expected = sha1(b"Hello, World!", &pool);
assert_eq!(digest, expected);
}
#[test]
fn test_sha1_the_quick_brown_fox() {
let pool = Pool::new();
let hex = sha1_encode(b"The quick brown fox jumps over the lazy dog", &pool);
assert_eq!(hex, "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12");
}
}