use bytes::{BufMut, BytesMut};
use md5::Context;
use crate::message::frontend::{MessageCode, frame};
pub fn md5_password(username: &str, password: &str, salt: &[u8; 4]) -> BytesMut {
let mut ctx = Context::new();
ctx.consume(password.as_bytes());
ctx.consume(username.as_bytes());
let inner_hash = ctx.compute();
let inner_hex = hex_encode(&inner_hash.0);
let mut ctx = Context::new();
ctx.consume(inner_hex.as_bytes());
ctx.consume(salt);
let outer_hash = ctx.compute();
let outer_hex = hex_encode(&outer_hash.0);
let mut msg = BytesMut::new();
msg.put_u8(MessageCode::PASSWORD_MESSAGE.as_u8());
frame(&mut msg, 4 + outer_hex.len(), |buf| {
buf.put_slice(b"md5");
buf.put_slice(outer_hex.as_bytes());
buf.put_u8(0);
});
msg
}
fn hex_encode(bytes: &[u8]) -> String {
const HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
let mut result = String::with_capacity(bytes.len() * 2);
for &byte in bytes {
result.push(HEX_CHARS[(byte >> 4) as usize] as char);
result.push(HEX_CHARS[(byte & 0x0f) as usize] as char);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_md5_password() {
let salt = [0x8b, 0x60, 0x4c, 0x73];
let msg = md5_password("postgres", "password", &salt);
assert_eq!(msg[0], b'p');
let password_str = String::from_utf8_lossy(&msg[5..msg.len() - 1]);
assert!(password_str.starts_with("md5"));
assert_eq!(password_str.len(), 35); }
#[test]
fn test_hex_encode() {
assert_eq!(hex_encode(&[0x00]), "00");
assert_eq!(hex_encode(&[0xff]), "ff");
assert_eq!(hex_encode(&[0xde, 0xad, 0xbe, 0xef]), "deadbeef");
}
#[test]
fn test_known_md5_hash() {
let salt = [0x00, 0x00, 0x00, 0x00];
let msg = md5_password("postgres", "password", &salt);
let password_str = String::from_utf8_lossy(&msg[5..msg.len() - 1]);
assert!(password_str.starts_with("md5"));
}
}