#[cfg(feature = "std")]
use alloc::sync::Arc;
pub trait KeyLog: Send + Sync {
fn log(&self, label: &str, client_random: &[u8; 32], secret: &[u8]);
}
#[cfg(feature = "std")]
pub struct WriterKeyLog<W: std::io::Write + Send> {
writer: std::sync::Mutex<W>,
}
#[cfg(feature = "std")]
impl<W: std::io::Write + Send> WriterKeyLog<W> {
pub fn new(w: W) -> Self {
Self {
writer: std::sync::Mutex::new(w),
}
}
#[cfg(test)]
pub(crate) fn writer_lock_for_test(&self) -> std::sync::MutexGuard<'_, W> {
self.writer.lock().expect("keylog mutex")
}
}
#[cfg(feature = "std")]
impl<W: std::io::Write + Send> KeyLog for WriterKeyLog<W> {
fn log(&self, label: &str, client_random: &[u8; 32], secret: &[u8]) {
let mut line =
alloc::string::String::with_capacity(label.len() + 1 + 64 + 1 + secret.len() * 2 + 1);
line.push_str(label);
line.push(' ');
append_hex(&mut line, client_random);
line.push(' ');
append_hex(&mut line, secret);
line.push('\n');
if let Ok(mut w) = self.writer.lock() {
let _ = w.write_all(line.as_bytes());
let _ = w.flush();
}
}
}
#[cfg(feature = "std")]
pub fn file_keylog(path: &std::path::Path) -> std::io::Result<Arc<WriterKeyLog<std::fs::File>>> {
let mut opts = std::fs::OpenOptions::new();
opts.create(true).append(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
opts.mode(0o600);
}
let f = opts.open(path)?;
Ok(Arc::new(WriterKeyLog::new(f)))
}
fn append_hex(out: &mut alloc::string::String, bytes: &[u8]) {
for b in bytes {
out.push(char::from_digit((b >> 4) as u32, 16).unwrap());
out.push(char::from_digit((b & 0xf) as u32, 16).unwrap());
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
use alloc::sync::Arc;
use alloc::vec::Vec;
#[test]
fn writer_keylog_format() {
let buf: Vec<u8> = Vec::new();
let sink: Arc<WriterKeyLog<Vec<u8>>> = Arc::new(WriterKeyLog::new(buf));
let cr: [u8; 32] = [0xab; 32];
let secret: [u8; 48] = [0xcd; 48];
sink.log("CLIENT_RANDOM", &cr, &secret);
let got = sink.writer_lock_for_test();
let line = core::str::from_utf8(&got).unwrap();
let expected_cr = "ab".repeat(32);
let expected_secret = "cd".repeat(48);
let expected = alloc::format!("CLIENT_RANDOM {expected_cr} {expected_secret}\n");
assert_eq!(line, expected.as_str());
}
}