happy_cracking/crypto/
semaphore.rs1use anyhow::{Context, Result};
2use clap::Subcommand;
3use std::collections::HashMap;
4use std::sync::LazyLock;
5
6#[derive(Subcommand)]
7pub enum SemaphoreAction {
8 #[command(about = "Encode text to flag semaphore positions")]
9 Encode {
10 #[arg(help = "Input text")]
11 input: String,
12 },
13 #[command(about = "Decode flag semaphore positions to text")]
14 Decode {
15 #[arg(help = "Semaphore encoded string (e.g. 1-2 1-3 1-4)")]
16 input: String,
17 },
18}
19
20pub fn run(action: SemaphoreAction) -> Result<()> {
21 match action {
22 SemaphoreAction::Encode { input } => {
23 println!("{}", encode(&input)?);
24 }
25 SemaphoreAction::Decode { input } => {
26 println!("{}", decode(&input)?);
27 }
28 }
29 Ok(())
30}
31
32const SEMAPHORE_MAP: &[(char, &str)] = &[
34 ('A', "1-2"),
35 ('B', "1-3"),
36 ('C', "1-4"),
37 ('D', "1-5"),
38 ('E', "1-6"),
39 ('F', "1-7"),
40 ('G', "1-8"),
41 ('H', "2-3"),
42 ('I', "2-4"),
43 ('J', "5-6"),
44 ('K', "3-1"),
45 ('L', "3-2"),
46 ('M', "3-4"),
47 ('N', "3-5"),
48 ('O', "3-6"),
49 ('P', "3-7"),
50 ('Q', "3-8"),
51 ('R', "4-3"),
52 ('S', "4-5"),
53 ('T', "4-6"),
54 ('U', "4-7"),
55 ('V', "5-1"),
56 ('W', "6-2"),
57 ('X', "6-3"),
58 ('Y', "6-4"),
59 ('Z', "6-5"),
60];
61
62const ENCODE_LUT: [Option<&str>; 26] = {
66 let mut table: [Option<&str>; 26] = [None; 26];
67 let mut i = 0;
68 while i < SEMAPHORE_MAP.len() {
69 let (ch, code) = SEMAPHORE_MAP[i];
70 table[ch as usize - 'A' as usize] = Some(code);
71 i += 1;
72 }
73 table
74};
75
76static SEMAPHORE_TO_CHAR: LazyLock<HashMap<&'static str, char>> =
77 LazyLock::new(|| SEMAPHORE_MAP.iter().map(|&(c, code)| (code, c)).collect());
78
79pub fn encode(input: &str) -> Result<String> {
83 if input.is_empty() {
84 return Ok(String::new());
85 }
86
87 let mut result = String::with_capacity(input.len() * 4);
89 let mut has_content = false;
90
91 for b in input.bytes() {
92 let upper = match b {
93 b'a'..=b'z' => b - b'a',
94 b'A'..=b'Z' => b - b'A',
95 _ => continue,
96 };
97 if has_content {
98 result.push(' ');
99 }
100 result.push_str(ENCODE_LUT[upper as usize].ok_or_else(|| {
101 anyhow::anyhow!("No semaphore code for character: {}", upper as char)
102 })?);
103 has_content = true;
104 }
105
106 Ok(result)
107}
108
109pub fn decode(input: &str) -> Result<String> {
110 let input = input.trim();
111 if input.is_empty() {
112 return Ok(String::new());
113 }
114
115 input
116 .split_whitespace()
117 .map(|code| {
118 SEMAPHORE_TO_CHAR
119 .get(code)
120 .copied()
121 .with_context(|| format!("Unknown semaphore code: {}", code))
122 })
123 .collect()
124}