happy_cracking/crypto/
binary.rs1use anyhow::{Context, Result};
2use clap::Subcommand;
3
4#[derive(Subcommand)]
5pub enum BinaryAction {
6 #[command(about = "Encode text to binary")]
7 Encode {
8 #[arg(help = "Input text")]
9 input: String,
10 },
11 #[command(about = "Decode binary to text")]
12 Decode {
13 #[arg(help = "Binary string (space or no space separated)")]
14 input: String,
15 },
16}
17
18pub fn run(action: BinaryAction) -> Result<()> {
19 match action {
20 BinaryAction::Encode { input } => {
21 println!("{}", encode(&input));
22 }
23 BinaryAction::Decode { input } => {
24 println!("{}", decode(&input)?);
25 }
26 }
27 Ok(())
28}
29
30pub fn encode(input: &str) -> String {
31 if input.is_empty() {
32 return String::new();
33 }
34
35 let mut out = String::with_capacity(input.len() * 9 - 1);
38 for (i, b) in input.bytes().enumerate() {
39 if i > 0 {
40 out.push(' ');
41 }
42
43 let mut bits = [0u8; 8];
46 for j in 0..8 {
47 bits[7 - j] = b'0' + ((b >> j) & 1);
48 }
49 out.push_str(unsafe { std::str::from_utf8_unchecked(&bits) });
51 }
52 out
53}
54
55pub fn decode(input: &str) -> Result<String> {
56 let mut cleaned_bytes = Vec::with_capacity(input.len());
59 for b in input.bytes() {
60 if b == b'0' || b == b'1' {
61 cleaned_bytes.push(b);
62 }
63 }
64
65 if !cleaned_bytes.len().is_multiple_of(8) {
66 anyhow::bail!("Binary string length must be a multiple of 8");
67 }
68
69 let bytes: Result<Vec<u8>, _> = cleaned_bytes
70 .chunks(8)
71 .map(|chunk| {
72 let s = unsafe { std::str::from_utf8_unchecked(chunk) };
74 u8::from_str_radix(s, 2)
75 })
76 .collect();
77
78 let bytes = bytes.context("Failed to parse binary")?;
79 String::from_utf8(bytes).context("Decoded data is not valid UTF-8")
80}