use std::io::Write;
use data_encoding::{
BASE64, BASE64_NOPAD, BASE64URL, BASE64URL_NOPAD, DecodeError, DecodeKind, HEXLOWER, HEXUPPER,
};
use roughenough_protocol::tags::PublicKey;
pub fn try_decode_key(encoded_key: &str) -> Result<PublicKey, DecodeError> {
let key = try_decode(encoded_key)?;
if key.len() != 32 {
return Err(DecodeError {
position: key.len(),
kind: DecodeKind::Length,
});
}
Ok(PublicKey::from(key.as_slice()))
}
pub fn try_decode(encoded_value: &str) -> Result<Vec<u8>, DecodeError> {
let value = HEXLOWER
.decode(encoded_value.as_bytes())
.or_else(|_| HEXUPPER.decode(encoded_value.as_bytes()))
.or_else(|_| BASE64URL.decode(encoded_value.as_bytes()))
.or_else(|_| BASE64URL_NOPAD.decode(encoded_value.as_bytes()))
.or_else(|_| BASE64.decode(encoded_value.as_bytes()))
.or_else(|_| BASE64_NOPAD.decode(encoded_value.as_bytes()))?;
Ok(value)
}
pub fn hexdump<W: Write>(data: &[u8], writer: &mut W) -> std::io::Result<()> {
const BYTES_PER_LINE: usize = 16;
for (offset, chunk) in data.chunks(BYTES_PER_LINE).enumerate() {
write!(writer, "{:08x}: ", offset * BYTES_PER_LINE)?;
for (i, byte) in chunk.iter().enumerate() {
write!(writer, "{byte:02x}")?;
if i % 2 == 1 {
write!(writer, " ")?;
}
}
let padding = BYTES_PER_LINE - chunk.len();
for i in 0..padding {
write!(writer, " ")?;
if (chunk.len() + i) % 2 == 1 {
write!(writer, " ")?;
}
}
write!(writer, " |")?;
for byte in chunk {
let ch = if byte.is_ascii_graphic() || *byte == b' ' {
*byte as char
} else {
'.'
};
write!(writer, "{ch}")?;
}
writeln!(writer, "|")?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_decode_key_hex_lower() {
let key_hex = "0101010101010101010101010101010101010101010101010101010101010101";
let result = try_decode_key(key_hex).unwrap();
assert_eq!(result.as_ref(), &[0x01u8; 32]);
}
#[test]
fn test_decode_key_hex_upper() {
let key_hex = "0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A";
let result = try_decode_key(key_hex).unwrap();
assert_eq!(result.as_ref(), &[0x0au8; 32]);
}
#[test]
fn test_decode_key_base64() {
let key_b64 = "QkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkI=";
let result = try_decode_key(key_b64).unwrap();
assert_eq!(result.as_ref(), &[0x42u8; 32]);
}
#[test]
fn test_decode_key_base64_nopad() {
let key_b64 = "QkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkI";
let result = try_decode_key(key_b64).unwrap();
assert_eq!(result.as_ref(), &[0x42u8; 32]);
}
#[test]
fn test_decode_key_wrong_length() {
let key_hex = "0101010101010101"; let result = try_decode_key(key_hex);
assert!(matches!(
result,
Err(DecodeError {
position: 8,
kind: DecodeKind::Length
})
));
}
#[test]
fn test_decode_key_invalid_encoding() {
let key = "not-a-valid-key-encoding!!!";
let result = try_decode_key(key);
assert!(matches!(result, Err(DecodeError { .. })));
}
#[test]
fn test_hexdump() {
let data = b"Hello, World! This is a test.";
let mut output = Vec::new();
hexdump(data, &mut output).unwrap();
let result = String::from_utf8(output).unwrap();
assert!(result.contains("00000000:"));
assert!(result.contains("4865 6c6c")); assert!(result.contains("|Hello, World! Th|"));
assert!(result.contains("|is is a test.|"));
}
#[test]
fn test_hexdump_short() {
let data = b"Test";
let mut output = Vec::new();
hexdump(data, &mut output).unwrap();
let result = String::from_utf8(output).unwrap();
assert!(result.contains("00000000: 5465 7374"));
assert!(result.contains("|Test|"));
}
}