Skip to main content

wafrift_encoding/encoding/keyword/
case.rs

1//! Case manipulation strategies.
2
3/// Shared alternating-case utility.
4///
5/// `SELECT` → `SeLeCt`. Bypasses case-sensitive keyword filters.
6pub fn alternating_case(payload: &str, start_upper: bool) -> String {
7    let mut upper = start_upper;
8    payload
9        .chars()
10        .map(|ch| {
11            if ch.is_ascii_alphabetic() {
12                let result = if upper {
13                    ch.to_ascii_uppercase()
14                } else {
15                    ch.to_ascii_lowercase()
16                };
17                upper = !upper;
18                result
19            } else {
20                ch
21            }
22        })
23        .collect()
24}
25
26/// Case alternation — deterministic alternating upper/lower.
27///
28/// **Idempotency.** Idempotent after the first application. The output
29/// always follows the fixed positional pattern `SeLeCt` regardless of
30/// input case, so re-applying leaves the result unchanged.
31pub fn case_alternate(payload: &str) -> String {
32    alternating_case(payload, true)
33}
34
35/// Random case alternation — unpredictable mixed-case output.
36///
37/// **Idempotency.** NOT idempotent. Each application re-randomises the
38/// case of every alphabetic character independently.
39pub fn random_case_alternate(payload: &str) -> String {
40    payload
41        .chars()
42        .map(|ch| {
43            if ch.is_ascii_alphabetic() {
44                if rand::random::<bool>() {
45                    ch.to_ascii_uppercase()
46                } else {
47                    ch.to_ascii_lowercase()
48                }
49            } else {
50                ch
51            }
52        })
53        .collect()
54}
55
56/// Full lowercase conversion.
57pub fn lowercase(payload: &str) -> String {
58    payload.to_ascii_lowercase()
59}
60
61/// Full uppercase conversion.
62pub fn uppercase(payload: &str) -> String {
63    payload.to_ascii_uppercase()
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn case_alternate_basic() {
72        assert_eq!(case_alternate("select"), "SeLeCt");
73    }
74
75    #[test]
76    fn random_case_non_deterministic() {
77        let a = random_case_alternate("SELECT");
78        let b = random_case_alternate("SELECT");
79        // Very unlikely to match by chance, but allow for it
80        assert_eq!(a.to_ascii_lowercase(), "select");
81        assert_eq!(b.to_ascii_lowercase(), "select");
82    }
83
84    #[test]
85    fn lowercase_basic() {
86        assert_eq!(lowercase("SeLeCt"), "select");
87    }
88
89    #[test]
90    fn uppercase_basic() {
91        assert_eq!(uppercase("SeLeCt"), "SELECT");
92    }
93
94    #[test]
95    fn case_alternate_idempotent_after_first() {
96        let once = case_alternate("select");
97        let twice = case_alternate(&once);
98        assert_eq!(
99            once, twice,
100            "case_alternate must be idempotent after first application"
101        );
102        assert_eq!(once, "SeLeCt");
103    }
104
105    #[test]
106    fn random_case_not_idempotent() {
107        let a = random_case_alternate("SELECT");
108        let b = random_case_alternate(&a);
109        // Statistically almost certain to differ.
110        assert_ne!(
111            a, b,
112            "random_case should re-randomise on second application"
113        );
114    }
115}