Skip to main content

happy_cracking/crypto/
binary.rs

1use 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    // Optimization: Pre-allocate the exact string capacity needed to avoid multiple allocations.
36    // Length is (8 bits + 1 space) per byte, minus the trailing space.
37    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        // Optimization: Manually compute bits instead of using format!("{:08b}", b)
44        // which introduces dynamic formatting overhead.
45        let mut bits = [0u8; 8];
46        for j in 0..8 {
47            bits[7 - j] = b'0' + ((b >> j) & 1);
48        }
49        // Security: use safe std::str::from_utf8 instead of unsafe from_utf8_unchecked.
50        // While the current logic only produces b'0' and b'1' (valid ASCII/UTF-8),
51        // using unsafe here is unnecessary and risks undefined behavior if a future
52        // refactor breaks this invariant.
53        out.push_str(std::str::from_utf8(&bits).expect("bits are ASCII digits"));
54    }
55    out
56}
57
58pub fn decode(input: &str) -> Result<String> {
59    // Optimization: Filter at the byte level directly into a pre-allocated Vec<u8>
60    // to bypass `char` conversion overhead and intermediate String allocation.
61    let mut cleaned_bytes = Vec::with_capacity(input.len());
62    for b in input.bytes() {
63        if b == b'0' || b == b'1' {
64            cleaned_bytes.push(b);
65        }
66    }
67
68    if !cleaned_bytes.len().is_multiple_of(8) {
69        anyhow::bail!("Binary string length must be a multiple of 8");
70    }
71
72    let bytes: Result<Vec<u8>, _> = cleaned_bytes
73        .chunks(8)
74        .map(|chunk| {
75            // Security: use safe std::str::from_utf8 instead of unsafe from_utf8_unchecked
76            // to prevent undefined behavior if the invariant is ever broken by refactoring.
77            let s = std::str::from_utf8(chunk).expect("cleaned_bytes are ASCII digits");
78            u8::from_str_radix(s, 2)
79        })
80        .collect();
81
82    let bytes = bytes.context("Failed to parse binary")?;
83    String::from_utf8(bytes).context("Decoded data is not valid UTF-8")
84}