happy_cracking/crypto/
base62.rs1use anyhow::{Context, Result};
2use clap::Subcommand;
3use num_bigint::BigUint;
4use num_traits::Zero;
5use std::sync::LazyLock;
6
7#[derive(Subcommand)]
8pub enum Base62Action {
9 #[command(about = "Encode to Base62")]
10 Encode {
11 #[arg(help = "Input text")]
12 input: String,
13 },
14 #[command(about = "Decode from Base62")]
15 Decode {
16 #[arg(help = "Base62 encoded string")]
17 input: String,
18 },
19}
20
21pub fn run(action: Base62Action) -> Result<()> {
22 match action {
23 Base62Action::Encode { input } => {
24 println!("{}", encode(&input));
25 }
26 Base62Action::Decode { input } => {
27 println!("{}", decode(&input)?);
28 }
29 }
30 Ok(())
31}
32
33const ALPHABET: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
34const BASE: u32 = 62;
35
36static DECODE_TABLE: LazyLock<[u8; 256]> = LazyLock::new(|| {
39 let mut table = [0xFFu8; 256];
40 for (i, &c) in ALPHABET.iter().enumerate() {
41 table[c as usize] = i as u8;
42 }
43 table
44});
45
46pub fn encode(input: &str) -> String {
47 let bytes = input.as_bytes();
48 if bytes.is_empty() {
49 return String::new();
50 }
51
52 let leading_zeros = bytes.iter().take_while(|&&b| b == 0).count();
53
54 let mut num = BigUint::from_bytes_be(bytes);
55 let base = BigUint::from(BASE);
56 let mut encoded = Vec::new();
57
58 while !num.is_zero() {
59 let remainder = &num % &base;
60 let digit = remainder.to_u32_digits().first().copied().unwrap_or(0) as usize;
61 encoded.push(ALPHABET[digit] as char);
62 num /= &base;
63 }
64
65 for _ in 0..leading_zeros {
66 encoded.push(ALPHABET[0] as char);
67 }
68
69 encoded.iter().rev().collect()
70}
71
72pub fn decode(input: &str) -> Result<String> {
73 let input = input.trim();
74 if input.is_empty() {
75 return Ok(String::new());
76 }
77
78 let leading_zeros = input.bytes().take_while(|&b| b == ALPHABET[0]).count();
79
80 let base = BigUint::from(BASE);
81 let mut num = BigUint::zero();
82
83 for c in input.bytes() {
84 let digit = DECODE_TABLE[c as usize];
85 if digit == 0xFF {
86 anyhow::bail!("Invalid Base62 character: {}", c as char);
87 }
88 num = num * &base + BigUint::from(digit as u32);
89 }
90
91 let mut bytes = num.to_bytes_be();
92
93 let mut result = vec![0u8; leading_zeros];
94 result.append(&mut bytes);
95
96 if result.is_empty() {
97 return Ok(String::new());
98 }
99
100 String::from_utf8(result).context("Decoded data is not valid UTF-8")
101}