Skip to main content

happy_cracking/crypto/
base91.rs

1use anyhow::{Context, Result};
2use clap::Subcommand;
3use std::sync::LazyLock;
4
5const ALPHABET: &[u8] =
6    b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[]^_`{|}~\"";
7
8// Optimization: Pre-compute the reverse lookup table once using LazyLock instead
9// of rebuilding a [u8; 256] decode table on every call to decode(). This avoids
10// redundant O(91) initialization work per invocation and makes decoding pure O(n).
11static DECODE_TABLE: LazyLock<[u8; 256]> = LazyLock::new(|| {
12    let mut table = [255u8; 256];
13    for (i, &c) in ALPHABET.iter().enumerate() {
14        table[c as usize] = i as u8;
15    }
16    table
17});
18
19#[derive(Subcommand)]
20pub enum Base91Action {
21    #[command(about = "Encode to Base91")]
22    Encode {
23        #[arg(help = "Input text")]
24        input: String,
25    },
26    #[command(about = "Decode from Base91")]
27    Decode {
28        #[arg(help = "Base91 encoded string")]
29        input: String,
30    },
31}
32
33pub fn run(action: Base91Action) -> Result<()> {
34    match action {
35        Base91Action::Encode { input } => {
36            println!("{}", encode(&input)?);
37        }
38        Base91Action::Decode { input } => {
39            println!("{}", decode(&input)?);
40        }
41    }
42    Ok(())
43}
44
45pub fn encode(input: &str) -> Result<String> {
46    let data = input.as_bytes();
47    if data.is_empty() {
48        return Ok(String::new());
49    }
50
51    let mut result = Vec::new();
52    let mut b: u32 = 0;
53    let mut n: u32 = 0;
54
55    for &byte in data {
56        b |= (byte as u32) << n;
57        n += 8;
58
59        if n > 13 {
60            let mut v = b & 8191; // 13 bits
61            if v > 88 {
62                b >>= 13;
63                n -= 13;
64            } else {
65                v = b & 16383; // 14 bits
66                b >>= 14;
67                n -= 14;
68            }
69            result.push(ALPHABET[(v % 91) as usize]);
70            result.push(ALPHABET[(v / 91) as usize]);
71        }
72    }
73
74    if n > 0 {
75        result.push(ALPHABET[(b % 91) as usize]);
76        if n > 7 || b > 90 {
77            result.push(ALPHABET[(b / 91) as usize]);
78        }
79    }
80
81    // Use proper error handling instead of unwrap to prevent panics
82    String::from_utf8(result).context("Encoded data is not valid UTF-8")
83}
84
85pub fn decode(input: &str) -> Result<String> {
86    let input = input.trim();
87    if input.is_empty() {
88        return Ok(String::new());
89    }
90
91    let mut result = Vec::new();
92    let mut b: u32 = 0;
93    let mut n: u32 = 0;
94    let mut v: i32 = -1;
95
96    for &byte in input.as_bytes() {
97        let d = DECODE_TABLE[byte as usize];
98        if d == 255 {
99            anyhow::bail!("Invalid Base91 character: {}", byte as char);
100        }
101        if v < 0 {
102            v = d as i32;
103        } else {
104            v += (d as i32) * 91;
105            b |= (v as u32) << n;
106            n += if (v & 8191) > 88 { 13 } else { 14 };
107            loop {
108                result.push((b & 255) as u8);
109                b >>= 8;
110                n -= 8;
111                if n <= 7 {
112                    break;
113                }
114            }
115            v = -1;
116        }
117    }
118
119    if v >= 0 {
120        result.push((b | (v as u32) << n) as u8);
121    }
122
123    String::from_utf8(result).context("Decoded data is not valid UTF-8")
124}