happy_cracking/crypto/
base91.rs1use anyhow::{Context, Result};
2use clap::Subcommand;
3use std::sync::LazyLock;
4
5const ALPHABET: &[u8] =
6 b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[]^_`{|}~\"";
7
8static 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; if v > 88 {
62 b >>= 13;
63 n -= 13;
64 } else {
65 v = b & 16383; 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 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}