Skip to main content

zerodds_websocket_bridge/
masking.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! WebSocket Payload Masking — RFC 6455 §5.3.
5
6use core::sync::atomic::{AtomicU64, Ordering};
7
8/// Spec §5.3 (S. 32): "octet i of the transformed data ('transformed-
9/// octet-i') is the XOR of octet i of the original data ('original-
10/// octet-i') with octet at index i modulo 4 of the masking key
11/// ('masking-key-octet-j')".
12///
13/// Diese Operation ist symmetrisch — derselbe Aufruf masked **und**
14/// unmasked.
15pub fn apply_mask(data: &mut [u8], key: [u8; 4]) {
16    for (i, b) in data.iter_mut().enumerate() {
17        *b ^= key[i & 0x3];
18    }
19}
20
21/// Generiert einen Masking-Key. Spec §5.3 (S. 32): "The masking key is
22/// a 32-bit value chosen at random by the client. [...] The masking
23/// key needs to be unpredictable; thus, the masking key MUST be
24/// derived from a strong source of entropy."
25///
26/// Diese Library liefert eine Default-Implementation auf Basis eines
27/// Splitmix64-PRNG mit Seed aus einer monoton wachsenden Counter +
28/// `core::ptr::addr_of!`-Position-Hash. Spec-Komformitaet **fordert**
29/// eine kryptographisch starke Quelle — Anwendungen mit Security-
30/// Bedarf MUESSEN ein eigenes Key-Generation-Verfahren einsetzen
31/// (z.B. `getrandom`-Crate oder OS-RNG). Diese Funktion ist nur
32/// als Codec-Convenience gedacht; sie ist explizit `not for security`.
33#[must_use]
34pub fn generate_masking_key() -> [u8; 4] {
35    static COUNTER: AtomicU64 = AtomicU64::new(0xCAFE_F00D_DEAD_BEEF);
36    let n = COUNTER.fetch_add(0x9E37_79B9_7F4A_7C15, Ordering::SeqCst);
37    let mut x = n;
38    x = (x ^ (x >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
39    x = (x ^ (x >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
40    x ^= x >> 31;
41    let bytes = x.to_le_bytes();
42    [bytes[0], bytes[1], bytes[2], bytes[3]]
43}
44
45/// Spec §5.3 — `MaskingKeyProvider`-Trait fuer caller-injected
46/// secure RNGs.
47///
48/// Anwendungen mit Security-Bedarf MUESSEN diesen Trait implementieren
49/// und einen kryptographisch starken RNG (z.B. `getrandom`,
50/// `rand::rngs::OsRng`) anbinden. Die Default-Implementation
51/// [`InsecureSplitmixProvider`] ist `not for security`.
52pub trait MaskingKeyProvider {
53    /// Liefert einen 32-bit Masking-Key.
54    fn next_key(&mut self) -> [u8; 4];
55}
56
57/// Default-Provider — explicit `not for security`.
58///
59/// Wraps die Library-internal `generate_masking_key`-Funktion.
60#[derive(Debug, Default)]
61pub struct InsecureSplitmixProvider;
62
63impl MaskingKeyProvider for InsecureSplitmixProvider {
64    fn next_key(&mut self) -> [u8; 4] {
65        generate_masking_key()
66    }
67}
68
69/// Caller-supplied Provider — wraps eine `FnMut` die einen Key
70/// liefert. Erlaubt Anwendungen, eigene Sources (OsRng, getrandom,
71/// Hardware-RNG) ohne eigene Trait-Impl zu injizieren.
72pub struct ClosureMaskingKeyProvider<F: FnMut() -> [u8; 4]>(pub F);
73
74impl<F: FnMut() -> [u8; 4]> MaskingKeyProvider for ClosureMaskingKeyProvider<F> {
75    fn next_key(&mut self) -> [u8; 4] {
76        (self.0)()
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn apply_mask_is_symmetric() {
86        // Spec §5.3 — XOR ist self-inverse.
87        let key = [0x01, 0x02, 0x03, 0x04];
88        let original: alloc::vec::Vec<u8> = (0..16).collect();
89        let mut masked = original.clone();
90        apply_mask(&mut masked, key);
91        assert_ne!(masked, original);
92        apply_mask(&mut masked, key);
93        assert_eq!(masked, original);
94    }
95
96    #[test]
97    fn apply_mask_xors_with_key_modulo_4() {
98        // Spec §5.3 — index modulo 4.
99        let key = [0xFF, 0x00, 0xFF, 0x00];
100        let mut data = [0xAA; 8];
101        apply_mask(&mut data, key);
102        assert_eq!(data, [0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA]);
103    }
104
105    #[test]
106    fn apply_mask_handles_partial_key_alignment() {
107        // Spec §5.3 — payload-len kein Vielfaches von 4 muss korrekt
108        // funktionieren.
109        let key = [0x01, 0x02, 0x03, 0x04];
110        let mut data = [0x10, 0x20, 0x30, 0x40, 0x50];
111        apply_mask(&mut data, key);
112        // Index 0..3 = key[0..3], Index 4 = key[0].
113        assert_eq!(data[0], 0x10 ^ 0x01);
114        assert_eq!(data[1], 0x20 ^ 0x02);
115        assert_eq!(data[2], 0x30 ^ 0x03);
116        assert_eq!(data[3], 0x40 ^ 0x04);
117        assert_eq!(data[4], 0x50 ^ 0x01);
118    }
119
120    #[test]
121    fn empty_payload_is_unchanged() {
122        let mut data: alloc::vec::Vec<u8> = alloc::vec::Vec::new();
123        apply_mask(&mut data, [1, 2, 3, 4]);
124        assert!(data.is_empty());
125    }
126
127    #[test]
128    fn generate_masking_key_returns_4_bytes() {
129        let k = generate_masking_key();
130        assert_eq!(k.len(), 4);
131    }
132
133    #[test]
134    fn generate_masking_key_returns_distinct_values_across_calls() {
135        // Spec §5.3 verlangt unpredictable; minimal Sanity: Counter
136        // produziert unterschiedliche Werte.
137        let k1 = generate_masking_key();
138        let k2 = generate_masking_key();
139        assert_ne!(k1, k2);
140    }
141
142    #[test]
143    fn insecure_splitmix_provider_implements_trait() {
144        let mut p = InsecureSplitmixProvider;
145        let k = p.next_key();
146        assert_eq!(k.len(), 4);
147    }
148
149    #[test]
150    fn closure_provider_calls_user_supplied_fn() {
151        let mut p = ClosureMaskingKeyProvider(|| [9, 9, 9, 9]);
152        assert_eq!(p.next_key(), [9, 9, 9, 9]);
153    }
154}