Skip to main content

libdd_trace_obfuscation/
redis.rs

1// Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::redis_tokenizer::{RedisTokenType, RedisTokenizer};
5
6const REDIS_TRUNCATION_MARK: &str = "...";
7const MAX_REDIS_NB_COMMANDS: usize = 3;
8
9/// Uppercase a single char to match Go's unicode.ToUpper (Unicode 15.0).
10/// Rust uses Unicode 16.0 which added case pairs for U+16E80–U+16EFF (Bamum Supplement)
11/// that Go 1.25 doesn't know about — keep those chars unchanged to match Go.
12fn go_toupper(c: char) -> char {
13    if (0x16E80..=0x16EFF).contains(&(c as u32)) {
14        return c;
15    }
16    let mut upper = c.to_uppercase();
17    match (upper.next(), upper.next()) {
18        (Some(u), None) => u,
19        _ => c,
20    }
21}
22
23/// Returns a quantized version of a Redis query, keeping only up to 3 command names.
24pub fn quantize_redis_string(query: &str) -> String {
25    let mut commands: Vec<String> = Vec::with_capacity(MAX_REDIS_NB_COMMANDS);
26    let mut truncated = false;
27
28    // Split on '\n' only (like Go's strings.IndexByte), preserving '\r' in line content
29    for raw_line in query.split('\n') {
30        if commands.len() >= MAX_REDIS_NB_COMMANDS {
31            break;
32        }
33
34        // Go's QuantizeRedisString trims only ASCII spaces (strings.Trim(rawLine, " ")),
35        // not all whitespace. Use trim_matches(' ') to match that behavior.
36        let line = raw_line.trim_matches(' ');
37        if line.is_empty() {
38            continue;
39        }
40
41        // Go splits on spaces only (strings.SplitN(line, " ", 3)), not all whitespace.
42        // Use split(' ').filter to match that behavior and preserve tab tokens.
43        let mut tokens = line.split(' ').filter(|s| !s.is_empty());
44        let Some(first) = tokens.next() else { continue };
45
46        if first.ends_with(REDIS_TRUNCATION_MARK) {
47            truncated = true;
48            continue;
49        }
50
51        let cmd: String = first.chars().map(go_toupper).collect();
52        let command = match cmd.as_bytes() {
53            b"CLIENT" | b"CLUSTER" | b"COMMAND" | b"CONFIG" | b"DEBUG" | b"SCRIPT" => {
54                match tokens.next() {
55                    Some(sub) if sub.ends_with(REDIS_TRUNCATION_MARK) => {
56                        truncated = true;
57                        continue;
58                    }
59                    Some(sub) => {
60                        format!("{cmd} {}", sub.chars().map(go_toupper).collect::<String>())
61                    }
62                    None => cmd,
63                }
64            }
65            _ => cmd,
66        };
67
68        commands.push(command);
69        truncated = false;
70    }
71
72    let mut result = commands.join(" ");
73    if commands.len() == MAX_REDIS_NB_COMMANDS || truncated {
74        if !result.is_empty() {
75            result.push(' ');
76        }
77        result.push_str("...");
78    }
79    result
80}
81
82#[must_use]
83pub fn obfuscate_redis_string(cmd: &str) -> String {
84    // Go's newRedisTokenizer calls bytes.TrimSpace before tokenizing
85    let cmd = cmd.trim();
86    let mut tokenizer = RedisTokenizer::new(cmd);
87    let s = &mut String::new();
88    let mut cmd: Option<&str> = None;
89    let mut args: Vec<&str> = Vec::new();
90
91    loop {
92        let res = tokenizer.scan();
93        match res.token_type {
94            RedisTokenType::RedisTokenCommand => {
95                if let Some(cmd) = cmd {
96                    args = obfuscate_redis_cmd(s, cmd, args);
97                    s.push('\n');
98                }
99                cmd = Some(res.token);
100                args.clear();
101            }
102            RedisTokenType::RedisTokenArgument => args.push(res.token),
103        }
104        if res.done {
105            obfuscate_redis_cmd(s, cmd.unwrap_or_default(), args);
106            break;
107        }
108    }
109    s.clone()
110}
111
112fn obfuscate_redis_cmd<'a>(str: &mut String, cmd: &'a str, mut args: Vec<&'a str>) -> Vec<&'a str> {
113    str.push_str(cmd);
114    if args.is_empty() {
115        return args;
116    }
117    str.push(' ');
118    let mut uppercase_cmd = [0; 32]; // no redis cmd is longer than 32 chars
119    let uppercase_cmd = ascii_uppercase(cmd, &mut uppercase_cmd).unwrap_or(&[]);
120    match uppercase_cmd {
121        b"AUTH" | b"MIGRATE" | b"HELLO"
122            // Obfuscate everything after command:
123            // • AUTH password
124            // • MIGRATE host port key|"" destination-db timeout [COPY] [REPLACE] [AUTH password]
125            //   [AUTH2 username password] [KEYS key [key ...]]
126            // • HELLO [protover [AUTH username password] [SETNAME clientname]]
127            if !args.is_empty() => {
128                args.clear();
129                args.push("?");
130            }
131        b"ACL" | b"GEOHASH" | b"GEOPOS" | b"GEODIST" | b"LPUSH" | b"RPUSH" | b"SREM" | b"ZREM"
132        | b"SADD"
133            // Obfuscate all arguments after the first token:
134            // • ACL SETUSER username on >password ~keys &channels +commands
135            // • ACL GETUSER username
136            // • ACL DELUSER username [username ...]
137            // • ACL LIST
138            // • ACL WHOAMI
139            // • GEOHASH key member [member ...]
140            // • GEOPOS key member [member ...]
141            // • GEODIST key member1 member2 [unit]
142            // • LPUSH key value [value ...]
143            // • RPUSH key value [value ...]
144            // • SREM key member [member ...]
145            // • ZREM key member [member ...]
146            // • SADD key member [member ...]
147            if args.len() > 1 => {
148                args[1] = "?";
149                args.drain(2..);
150            }
151        b"APPEND" | b"GETSET" | b"LPUSHX" | b"GEORADIUSBYMEMBER" | b"RPUSHX" | b"SET"
152        | b"SETNX" | b"SISMEMBER" | b"ZRANK" | b"ZREVRANK" | b"ZSCORE" => {
153            // Obfuscate 2nd argument:
154            // • APPEND key value
155            // • GETSET key value
156            // • LPUSHX key value
157            // • GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH]
158            // [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
159            // • RPUSHX key value
160            // • SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
161            // • SETNX key value
162            // • SISMEMBER key member
163            // • ZRANK key member
164            // • ZREVRANK key member
165            // • ZSCORE key member
166            args = obfuscate_redis_args_n(args, 1);
167        }
168        b"HSETNX" | b"LREM" | b"LSET" | b"SETBIT" | b"SETEX" | b"PSETEX" | b"SETRANGE"
169        | b"ZINCRBY" | b"SMOVE" | b"RESTORE" => {
170            // Obfuscate 3rd argument:
171            // • HSET key field value
172            // • HSETNX key field value
173            // • LREM key count value
174            // • LSET key index value
175            // • SETBIT key offset value
176            // • SETEX key seconds value
177            // • PSETEX key milliseconds value
178            // • SETRANGE key offset value
179            // • ZINCRBY key increment member
180            // • SMOVE source destination member
181            // • RESTORE key ttl serialized-value [REPLACE]
182            args = obfuscate_redis_args_n(args, 2);
183        }
184        b"LINSERT" => {
185            // Obfuscate 4th argument:
186            // • LINSERT key BEFORE|AFTER pivot value
187            args = obfuscate_redis_args_n(args, 3);
188        }
189        b"GEOADD" => {
190            // Obfuscating every 3rd argument starting from first
191            // • GEOADD key longitude latitude member [longitude latitude member ...]
192            args = obfuscate_redis_args_step(args, 1, 3);
193        }
194        b"HMSET" | b"HSET" => {
195            // Every 2nd argument starting from first.
196            // • HMSET key field value [field value ...]
197            args = obfuscate_redis_args_step(args, 1, 2);
198        }
199        b"MSET" | b"MSETNX" => {
200            // Every 2nd argument starting from command.
201            // • MSET key value [key value ...]
202            // • MSETNX key value [key value ...]
203            args = obfuscate_redis_args_step(args, 0, 2);
204        }
205        b"CONFIG" => {
206            // Obfuscate 2nd argument to SET sub-command.
207            // • CONFIG SET parameter value
208            let mut uppercase_arg = [0; 8];
209            let uppercase_arg = ascii_uppercase(args[0], &mut uppercase_arg).unwrap_or(b"");
210            if uppercase_arg == b"SET" {
211                args = obfuscate_redis_args_n(args, 2);
212            }
213        }
214        b"BITFIELD" => {
215            // Obfuscate 3rd argument to SET sub-command:
216            // • BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset
217            // increment] [OVERFLOW WRAP|SAT|FAIL]
218            let mut n = 0;
219            for (i, arg) in args.iter_mut().enumerate() {
220                let mut uppercase_arg = [0; 8];
221                let uppercase_arg = ascii_uppercase(arg, &mut uppercase_arg).unwrap_or(b"");
222                if uppercase_arg == b"SET" {
223                    n = i;
224                }
225                if n > 0 && i - n == 3 {
226                    *arg = "?";
227                    break;
228                }
229            }
230        }
231        b"ZADD" => {
232            for i in 0..args.len() {
233                if i == 0 {
234                    continue; // key
235                }
236                let mut uppercase_arg = [0; 8];
237                let uppercase_arg = ascii_uppercase(args[i], &mut uppercase_arg).unwrap_or(b"");
238                match uppercase_arg {
239                    b"NX" | b"XX" | b"CH" | b"INCR" => {}
240                    _ => {
241                        args = obfuscate_redis_args_step(args, i, 2);
242                        break;
243                    }
244                }
245            }
246        }
247        _ => {}
248    }
249    str.push_str(&args.join(" "));
250    args
251}
252
253fn obfuscate_redis_args_n(mut args: Vec<&str>, n: usize) -> Vec<&str> {
254    if args.len() > n {
255        args[n] = "?";
256    }
257    args
258}
259
260fn obfuscate_redis_args_step(mut args: Vec<&str>, start: usize, step: usize) -> Vec<&str> {
261    if start + step > args.len() {
262        return args;
263    }
264    for i in ((start + step - 1)..args.len()).step_by(step) {
265        args[i] = "?";
266    }
267    args
268}
269
270#[must_use]
271pub fn remove_all_redis_args(redis_cmd: &str) -> String {
272    let mut redis_cmd_iter = redis_cmd.split_whitespace().peekable();
273    let mut obfuscated_cmd = String::new();
274
275    // If the redis command is empty, return immediately. Otherwise, store the command token.
276    let Some(cmd) = redis_cmd_iter.next() else {
277        return obfuscated_cmd;
278    };
279    obfuscated_cmd.push_str(cmd);
280
281    // If there are no tokens left in the iterator, return the obfuscated result with just the
282    // command.
283    if redis_cmd_iter.peek().is_none() {
284        return obfuscated_cmd;
285    }
286
287    obfuscated_cmd.push(' ');
288
289    let mut uppercase_cmd = [0; 32];
290    let uppercase_cmd = ascii_uppercase(cmd, &mut uppercase_cmd).unwrap_or(&[]);
291    match uppercase_cmd {
292        b"BITFIELD" => {
293            obfuscated_cmd.push('?');
294            for a in redis_cmd_iter {
295                let mut uppercase_arg = [0; 8];
296                let uppercase_arg = ascii_uppercase(a, &mut uppercase_arg).unwrap_or(b"");
297                if uppercase_arg == b"SET" || uppercase_arg == b"GET" || uppercase_arg == b"INCRBY"
298                {
299                    obfuscated_cmd.push_str(format!(" {a} ?").as_str());
300                }
301            }
302        }
303        b"CONFIG" => {
304            let a = redis_cmd_iter.next().unwrap_or_default();
305            let mut uppercase_arg = [0; 16];
306            let uppercase_arg = ascii_uppercase(a, &mut uppercase_arg).unwrap_or(b"");
307            if uppercase_arg == b"GET"
308                || uppercase_arg == b"SET"
309                || uppercase_arg == b"RESETSTAT"
310                || uppercase_arg == b"REWRITE"
311            {
312                obfuscated_cmd.push_str(format!("{a} ?").as_str());
313            } else {
314                obfuscated_cmd.push('?');
315            }
316        }
317        _ => {
318            obfuscated_cmd.push('?');
319        }
320    }
321
322    obfuscated_cmd
323}
324
325fn ascii_uppercase<'a>(s: &str, dest: &'a mut [u8]) -> Option<&'a [u8]> {
326    if s.len() > dest.len() {
327        return None;
328    }
329    for (i, c) in s.as_bytes().iter().enumerate() {
330        if c.is_ascii() {
331            dest[i] = c.to_ascii_uppercase();
332        }
333    }
334    Some(&dest[0..s.len()])
335}
336
337#[cfg(test)]
338mod tests {
339    use duplicate::duplicate_item;
340
341    use super::{obfuscate_redis_string, quantize_redis_string, remove_all_redis_args};
342
343    #[duplicate_item(
344        [
345            test_name   [test_quantize_redis_string_client]
346            input       ["CLIENT"]
347            expected    ["CLIENT"];
348        ]
349        [
350            test_name   [test_quantize_redis_string_client_list]
351            input       ["CLIENT LIST"]
352            expected    ["CLIENT LIST"];
353        ]
354        [
355            test_name   [test_quantize_redis_string_client_truncated]
356            input       ["CLIENT ..."]
357            expected    ["..."];
358        ]
359        [
360            test_name   [test_quantize_redis_string_get_lowercase]
361            input       ["get my_key"]
362            expected    ["GET"];
363        ]
364        [
365            test_name   [test_quantize_redis_string_set]
366            input       ["SET le_key le_value"]
367            expected    ["SET"];
368        ]
369        [
370            test_name   [test_quantize_redis_string_set_with_newlines]
371            input       ["\n\n  \nSET foo bar  \n  \n\n  "]
372            expected    ["SET"];
373        ]
374        [
375            test_name   [test_quantize_redis_string_config_set]
376            input       ["CONFIG SET parameter value"]
377            expected    ["CONFIG SET"];
378        ]
379        [
380            test_name   [test_quantize_redis_string_two_cmds]
381            input       ["SET toto tata \n \n  EXPIRE toto 15  "]
382            expected    ["SET EXPIRE"];
383        ]
384        [
385            test_name   [test_quantize_redis_string_mset]
386            input       ["MSET toto tata toto tata toto tata \n "]
387            expected    ["MSET"];
388        ]
389        [
390            test_name   [test_quantize_redis_string_max_cmds]
391            input       ["MULTI\nSET k1 v1\nSET k2 v2\nSET k3 v3\nSET k4 v4\nDEL to_del\nEXEC"]
392            expected    ["MULTI SET SET ..."];
393        ]
394        [
395            test_name   [test_quantize_redis_string_truncation_first]
396            input       ["GET..."]
397            expected    ["..."];
398        ]
399        [
400            test_name   [test_quantize_redis_string_truncation_arg]
401            input       ["GET k..."]
402            expected    ["GET"];
403        ]
404        [
405            test_name   [test_quantize_redis_string_truncation_third]
406            input       ["GET k1\nGET k2\nG..."]
407            expected    ["GET GET ..."];
408        ]
409        [
410            test_name   [test_quantize_redis_string_truncation_after_max]
411            input       ["GET k1\nGET k2\nDEL k3\nGET k..."]
412            expected    ["GET GET DEL ..."];
413        ]
414        [
415            test_name   [test_quantize_redis_string_truncation_hdel]
416            input       ["GET k1\nGET k2\nHDEL k3 a\nG..."]
417            expected    ["GET GET HDEL ..."];
418        ]
419        [
420            test_name   [test_quantize_redis_string_truncation_mid]
421            input       ["GET k...\nDEL k2\nMS..."]
422            expected    ["GET DEL ..."];
423        ]
424        [
425            test_name   [test_quantize_redis_string_truncation_early]
426            input       ["GET k...\nDE...\nMS..."]
427            expected    ["GET ..."];
428        ]
429        [
430            test_name   [test_quantize_redis_string_truncation_then_cmd]
431            input       ["GET k1\nDE...\nGET k2"]
432            expected    ["GET GET"];
433        ]
434        [
435            test_name   [test_quantize_redis_string_truncation_complex]
436            input       ["GET k1\nDE...\nGET k2\nHDEL k3 a\nGET k4\nDEL k5"]
437            expected    ["GET GET HDEL ..."];
438        ]
439        [
440            test_name   [test_quantize_redis_string_unknown]
441            input       ["UNKNOWN 123"]
442            expected    ["UNKNOWN"];
443        ]
444        [
445            test_name   [fuzzing_3286489773]
446            input       ["ꭺ"]
447            expected    ["Ꭺ"];
448        ]
449        [
450            test_name   [fuzzing_2812552373]
451            input       ["\t"]
452            expected    ["\t"];
453        ]
454        [
455            test_name   [fuzzing_crlf]
456            input       ["\r\n"]
457            // Split on \n specifically, not newlines, to copy agent's behaviour
458            expected    ["\r"];
459        ]
460        [
461            test_name   [fuzzing_box_char]
462            input       ["𖺻"]
463            expected    ["𖺻"];
464        ]
465    )]
466    #[test]
467    fn test_name() {
468        let result = quantize_redis_string(input);
469        assert_eq!(result, expected);
470    }
471
472    #[duplicate_item(
473        [
474            test_name   [test_obfuscate_redis_string_1]
475            input       ["AUTH my-secret-password"]
476            expected    ["AUTH ?"];
477        ]
478        [
479            test_name   [test_obfuscate_redis_string_2]
480            input       ["AUTH james my-secret-password"]
481            expected    ["AUTH ?"];
482        ]
483        [
484            test_name   [test_obfuscate_redis_string_3]
485            input       ["AUTH"]
486            expected    ["AUTH"];
487        ]
488        [
489            test_name   [test_obfuscate_redis_string_migrate_basic]
490            input       ["MIGRATE host port key destination-db timeout"]
491            expected    ["MIGRATE ?"];
492        ]
493        [
494            test_name   [test_obfuscate_redis_string_migrate_with_flags]
495            input       ["MIGRATE host port key destination-db timeout COPY REPLACE"]
496            expected    ["MIGRATE ?"];
497        ]
498        [
499            test_name   [test_obfuscate_redis_string_migrate_with_keys]
500            input       [r#"MIGRATE host port "" destination-db timeout KEYS key1 key2 key3"#]
501            expected    ["MIGRATE ?"];
502        ]
503        [
504            test_name   [test_obfuscate_redis_string_migrate_no_args]
505            input       ["MIGRATE"]
506            expected    ["MIGRATE"];
507        ]
508        [
509            test_name   [test_obfuscate_redis_string_hello_version]
510            input       ["HELLO 3"]
511            expected    ["HELLO ?"];
512        ]
513        [
514            test_name   [test_obfuscate_redis_string_hello_auth]
515            input       ["HELLO 3 AUTH username password"]
516            expected    ["HELLO ?"];
517        ]
518        [
519            test_name   [test_obfuscate_redis_string_hello_auth_setname]
520            input       ["HELLO 3 AUTH username password SETNAME clientname"]
521            expected    ["HELLO ?"];
522        ]
523        [
524            test_name   [test_obfuscate_redis_string_hello_no_args]
525            input       ["HELLO"]
526            expected    ["HELLO"];
527        ]
528        [
529            test_name   [test_obfuscate_redis_string_acl_setuser]
530            input       ["ACL SETUSER alice on >password ~* &* +@all"]
531            expected    ["ACL SETUSER ?"];
532        ]
533        [
534            test_name   [test_obfuscate_redis_string_acl_setuser_complex]
535            input       ["ACL SETUSER bob on >mysecretpassword ~keys:* resetchannels &channel:* +@all -@dangerous"]
536            expected    ["ACL SETUSER ?"];
537        ]
538        [
539            test_name   [test_obfuscate_redis_string_acl_getuser]
540            input       ["ACL GETUSER alice"]
541            expected    ["ACL GETUSER ?"];
542        ]
543        [
544            test_name   [test_obfuscate_redis_string_acl_deluser]
545            input       ["ACL DELUSER alice"]
546            expected    ["ACL DELUSER ?"];
547        ]
548        [
549            test_name   [test_obfuscate_redis_string_acl_deluser_multi]
550            input       ["ACL DELUSER alice bob charlie"]
551            expected    ["ACL DELUSER ?"];
552        ]
553        [
554            test_name   [test_obfuscate_redis_string_acl_list]
555            input       ["ACL LIST"]
556            expected    ["ACL LIST"];
557        ]
558        [
559            test_name   [test_obfuscate_redis_string_acl_whoami]
560            input       ["ACL WHOAMI"]
561            expected    ["ACL WHOAMI"];
562        ]
563        [
564            test_name   [test_obfuscate_redis_string_acl_no_args]
565            input       ["ACL"]
566            expected    ["ACL"];
567        ]
568        [
569            test_name   [test_obfuscate_redis_string_4]
570            input       ["APPEND key value"]
571            expected    ["APPEND key ?"];
572        ]
573        [
574            test_name   [test_obfuscate_redis_string_5]
575            input       ["GETSET key value"]
576            expected    ["GETSET key ?"];
577        ]
578        [
579            test_name   [test_obfuscate_redis_string_6]
580            input       ["LPUSHX key value"]
581            expected    ["LPUSHX key ?"];
582        ]
583        [
584            test_name   [test_obfuscate_redis_string_7]
585            input       ["GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]"]
586            expected    ["GEORADIUSBYMEMBER key ? radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]"];
587        ]
588        [
589            test_name   [test_obfuscate_redis_string_8]
590            input       ["RPUSHX key value"]
591            expected    ["RPUSHX key ?"];
592        ]
593        [
594            test_name   [test_obfuscate_redis_string_9]
595            input       ["SET key value"]
596            expected    ["SET key ?"];
597        ]
598        [
599            test_name   [test_obfuscate_redis_string_10]
600            input       ["SET key value [expiration EX seconds|PX milliseconds] [NX|XX]"]
601            expected    ["SET key ? [expiration EX seconds|PX milliseconds] [NX|XX]"];
602        ]
603        [
604            test_name   [test_obfuscate_redis_string_11]
605            input       ["SETNX key value"]
606            expected    ["SETNX key ?"];
607        ]
608        [
609            test_name   [test_obfuscate_redis_string_12]
610            input       ["SISMEMBER key member"]
611            expected    ["SISMEMBER key ?"];
612        ]
613        [
614            test_name   [test_obfuscate_redis_string_13]
615            input       ["ZRANK key member"]
616            expected    ["ZRANK key ?"];
617        ]
618        [
619            test_name   [test_obfuscate_redis_string_14]
620            input       ["ZREVRANK key member"]
621            expected    ["ZREVRANK key ?"];
622        ]
623        [
624            test_name   [test_obfuscate_redis_string_15]
625            input       ["ZSCORE key member"]
626            expected    ["ZSCORE key ?"];
627        ]
628        [
629            test_name   [test_obfuscate_redis_string_16]
630            input       ["BITFIELD key GET type offset SET type offset value INCRBY type"]
631            expected    ["BITFIELD key GET type offset SET type offset ? INCRBY type"];
632        ]
633        [
634            test_name   [test_obfuscate_redis_string_17]
635            input       ["BITFIELD key SET type offset value INCRBY type"]
636            expected    ["BITFIELD key SET type offset ? INCRBY type"];
637        ]
638        [
639            test_name   [test_obfuscate_redis_string_18]
640            input       ["BITFIELD key GET type offset INCRBY type"]
641            expected    ["BITFIELD key GET type offset INCRBY type"];
642        ]
643        [
644            test_name   [test_obfuscate_redis_string_19]
645            input       ["BITFIELD key SET type offset"]
646            expected    ["BITFIELD key SET type offset"];
647        ]
648        [
649            test_name   [test_obfuscate_redis_string_20]
650            input       ["CONFIG SET parameter value"]
651            expected    ["CONFIG SET parameter ?"];
652        ]
653        [
654            test_name   [test_obfuscate_redis_string_21]
655            input       ["CONFIG foo bar baz"]
656            expected    ["CONFIG foo bar baz"];
657        ]
658        [
659            test_name   [test_obfuscate_redis_string_22]
660            input       ["GEOADD key longitude latitude member longitude latitude member longitude latitude member"]
661            expected    ["GEOADD key longitude latitude ? longitude latitude ? longitude latitude ?"];
662        ]
663        [
664            test_name   [test_obfuscate_redis_string_23]
665            input       ["GEOADD key longitude latitude member longitude latitude member"]
666            expected    ["GEOADD key longitude latitude ? longitude latitude ?"];
667        ]
668        [
669            test_name   [test_obfuscate_redis_string_24]
670            input       ["GEOADD key longitude latitude member"]
671            expected    ["GEOADD key longitude latitude ?"];
672        ]
673        [
674            test_name   [test_obfuscate_redis_string_25]
675            input       ["GEOADD key longitude latitude"]
676            expected    ["GEOADD key longitude latitude"];
677        ]
678        [
679            test_name   [test_obfuscate_redis_string_26]
680            input       ["GEOADD key"]
681            expected    ["GEOADD key"];
682        ]
683        [
684            test_name   [test_obfuscate_redis_string_27]
685            input       ["GEOHASH key\nGEOPOS key\n GEODIST key"]
686            expected    ["GEOHASH key\nGEOPOS key\nGEODIST key"];
687        ]
688        [
689            test_name   [test_obfuscate_redis_string_28]
690            input       ["GEOHASH key member\nGEOPOS key member\nGEODIST key member\n"]
691            expected    ["GEOHASH key ?\nGEOPOS key ?\nGEODIST key ?"];
692        ]
693        [
694            test_name   [test_obfuscate_redis_string_29]
695            input       ["GEOHASH key member member member\nGEOPOS key member member \n  GEODIST key member member member"]
696            expected    ["GEOHASH key ?\nGEOPOS key ?\nGEODIST key ?"];
697        ]
698        [
699            test_name   [test_obfuscate_redis_string_30]
700            input       ["GEOPOS key member [member ...]"]
701            expected    ["GEOPOS key ?"];
702        ]
703        [
704            test_name   [test_obfuscate_redis_string_31]
705            input       ["SREM key member [member ...]"]
706            expected    ["SREM key ?"];
707        ]
708        [
709            test_name   [test_obfuscate_redis_string_32]
710            input       ["ZREM key member [member ...]"]
711            expected    ["ZREM key ?"];
712        ]
713        [
714            test_name   [test_obfuscate_redis_string_33]
715            input       ["SADD key member [member ...]"]
716            expected    ["SADD key ?"];
717        ]
718        [
719            test_name   [test_obfuscate_redis_string_34]
720            input       ["GEODIST key member1 member2 [unit]"]
721            expected    ["GEODIST key ?"];
722        ]
723        [
724            test_name   [test_obfuscate_redis_string_35]
725            input       ["LPUSH key value [value ...]"]
726            expected    ["LPUSH key ?"];
727        ]
728        [
729            test_name   [test_obfuscate_redis_string_36]
730            input       ["RPUSH key value [value ...]"]
731            expected    ["RPUSH key ?"];
732        ]
733        [
734            test_name   [test_obfuscate_redis_string_37]
735            input       ["HSET key field value \nHSETNX key field value\nBLAH"]
736            expected    ["HSET key field ?\nHSETNX key field ?\nBLAH"];
737        ]
738        [
739            test_name   [test_obfuscate_redis_string_38]
740            input       ["HSET key field value"]
741            expected    ["HSET key field ?"];
742        ]
743        [
744            test_name   [test_obfuscate_redis_string_39]
745            input       ["HSETNX key field value"]
746            expected    ["HSETNX key field ?"];
747        ]
748        [
749            test_name   [test_obfuscate_redis_string_40]
750            input       ["LREM key count value"]
751            expected    ["LREM key count ?"];
752        ]
753        [
754            test_name   [test_obfuscate_redis_string_41]
755            input       ["LSET key index value"]
756            expected    ["LSET key index ?"];
757        ]
758        [
759            test_name   [test_obfuscate_redis_string_42]
760            input       ["SETBIT key offset value"]
761            expected    ["SETBIT key offset ?"];
762        ]
763        [
764            test_name   [test_obfuscate_redis_string_43]
765            input       ["SETRANGE key offset value"]
766            expected    ["SETRANGE key offset ?"];
767        ]
768        [
769            test_name   [test_obfuscate_redis_string_44]
770            input       ["SETEX key seconds value"]
771            expected    ["SETEX key seconds ?"];
772        ]
773        [
774            test_name   [test_obfuscate_redis_string_45]
775            input       ["PSETEX key milliseconds value"]
776            expected    ["PSETEX key milliseconds ?"];
777        ]
778        [
779            test_name   [test_obfuscate_redis_string_46]
780            input       ["ZINCRBY key increment member"]
781            expected    ["ZINCRBY key increment ?"];
782        ]
783        [
784            test_name   [test_obfuscate_redis_string_47]
785            input       ["SMOVE source destination member"]
786            expected    ["SMOVE source destination ?"];
787        ]
788        [
789            test_name   [test_obfuscate_redis_string_48]
790            input       ["RESTORE key ttl serialized-value [REPLACE]"]
791            expected    ["RESTORE key ttl ? [REPLACE]"];
792        ]
793        [
794            test_name   [test_obfuscate_redis_string_49]
795            input       ["LINSERT key BEFORE pivot value"]
796            expected    ["LINSERT key BEFORE pivot ?"];
797        ]
798        [
799            test_name   [test_obfuscate_redis_string_50]
800            input       ["LINSERT key AFTER pivot value"]
801            expected    ["LINSERT key AFTER pivot ?"];
802        ]
803        [
804            test_name   [test_obfuscate_redis_string_51]
805            input       ["HMSET key field value field value"]
806            expected    ["HMSET key field ? field ?"];
807        ]
808        [
809            test_name   [test_obfuscate_redis_string_52]
810            input       ["HMSET key field value \n HMSET key field value\n\n "]
811            expected    ["HMSET key field ?\nHMSET key field ?"];
812        ]
813        [
814            test_name   [test_obfuscate_redis_string_53]
815            input       ["HMSET key field"]
816            expected    ["HMSET key field"];
817        ]
818        [
819            test_name   [test_obfuscate_redis_string_54]
820            input       ["MSET key value key value"]
821            expected    ["MSET key ? key ?"];
822        ]
823        [
824            test_name   [test_obfuscate_redis_string_55]
825            input       ["HMSET key field"]
826            expected    ["HMSET key field"];
827        ]
828        [
829            test_name   [test_obfuscate_redis_string_56]
830            input       ["MSET\nMSET key value"]
831            expected    ["MSET\nMSET key ?"];
832        ]
833        [
834            test_name   [test_obfuscate_redis_string_57]
835            input       ["MSET key value"]
836            expected    ["MSET key ?"];
837        ]
838        [
839            test_name   [test_obfuscate_redis_string_58]
840            input       ["MSETNX key value key value"]
841            expected    ["MSETNX key ? key ?"];
842        ]
843        [
844            test_name   [test_obfuscate_redis_string_59]
845            input       ["ZADD key score member score member"]
846            expected    ["ZADD key score ? score ?"];
847        ]
848        [
849            test_name   [test_obfuscate_redis_string_60]
850            input       ["ZADD key NX score member score member"]
851            expected    ["ZADD key NX score ? score ?"];
852        ]
853        [
854            test_name   [test_obfuscate_redis_string_61]
855            input       ["ZADD key NX CH score member score member"]
856            expected    ["ZADD key NX CH score ? score ?"];
857        ]
858        [
859            test_name   [test_obfuscate_redis_string_62]
860            input       ["ZADD key NX CH INCR score member score member"]
861            expected    ["ZADD key NX CH INCR score ? score ?"];
862        ]
863        [
864            test_name   [test_obfuscate_redis_string_63]
865            input       ["ZADD key XX INCR score member score member"]
866            expected    ["ZADD key XX INCR score ? score ?"];
867        ]
868        [
869            test_name   [test_obfuscate_redis_string_64]
870            input       ["ZADD key XX INCR score member"]
871            expected    ["ZADD key XX INCR score ?"];
872        ]
873        [
874            test_name   [test_obfuscate_redis_string_65]
875            input       ["ZADD key XX INCR score"]
876            expected    ["ZADD key XX INCR score"];
877        ]
878        [
879            test_name   [test_obfuscate_redis_string_66]
880            input       [r"
881CONFIG command
882SET k v
883                        "]
884            expected    [r"CONFIG command
885SET k ?"];
886        ]
887        [
888            test_name   [test_obfuscate_redis_string_67]
889            input       ["HSET key field value field value"]
890            expected    ["HSET key field ? field ?"];
891        ]
892    )]
893    #[test]
894    fn test_name() {
895        let result = obfuscate_redis_string(input);
896        assert_eq!(result, expected);
897    }
898
899    #[duplicate_item(
900        [
901            test_name   [test_obfuscate_all_redis_args_1]
902            input       [""]
903            expected    [""];
904        ]
905        [
906            test_name   [test_obfuscate_all_redis_args_2]
907            input       ["SET key value"]
908            expected    ["SET ?"];
909        ]
910        [
911            test_name   [test_obfuscate_all_redis_args_3]
912            input       ["GET k"]
913            expected    ["GET ?"];
914        ]
915        [
916            test_name   [test_obfuscate_all_redis_args_4]
917            input       ["FAKECMD key value hash"]
918            expected    ["FAKECMD ?"];
919        ]
920        [
921            test_name   [test_obfuscate_all_redis_args_5]
922            input       ["AUTH password"]
923            expected    ["AUTH ?"];
924        ]
925        [
926            test_name   [test_obfuscate_all_redis_args_6]
927            input       ["GET"]
928            expected    ["GET"];
929        ]
930        [
931            test_name   [test_obfuscate_all_redis_args_7]
932            input       ["CONFIG SET key value"]
933            expected    ["CONFIG SET ?"];
934        ]
935        [
936            test_name   [test_obfuscate_all_redis_args_8]
937            input       ["CONFIG GET key"]
938            expected    ["CONFIG GET ?"];
939        ]
940        [
941            test_name   [test_obfuscate_all_redis_args_9]
942            input       ["CONFIG key"]
943            expected    ["CONFIG ?"];
944        ]
945        [
946            test_name   [test_obfuscate_all_redis_args_10]
947            input       ["BITFIELD key SET key value GET key"]
948            expected    ["BITFIELD ? SET ? GET ?"];
949        ]
950        [
951            test_name   [test_obfuscate_all_redis_args_11]
952            input       ["BITFIELD key INCRBY value"]
953            expected    ["BITFIELD ? INCRBY ?"];
954        ]
955        [
956            test_name   [test_obfuscate_all_redis_args_12]
957            input       ["BITFIELD secret key"]
958            expected    ["BITFIELD ?"];
959        ]
960        [
961            test_name   [test_obfuscate_all_redis_args_13]
962            input       ["set key value"]
963            expected    ["set ?"];
964        ]
965        [
966            test_name   [test_obfuscate_all_redis_args_14]
967            input       ["Get key"]
968            expected    ["Get ?"];
969        ]
970        [
971            test_name   [test_obfuscate_all_redis_args_15]
972            input       ["config key"]
973            expected    ["config ?"];
974        ]
975        [
976            test_name   [test_obfuscate_all_redis_args_16]
977            input       ["CONFIG get key"]
978            expected    ["CONFIG get ?"];
979        ]
980        [
981            test_name   [test_obfuscate_all_redis_args_17]
982            input       ["bitfield key SET key value incrby 3"]
983            expected    ["bitfield ? SET ? incrby ?"];
984        ]
985        [
986            test_name   [test_obfuscate_fuzzing_unicode]
987            input       ["\u{00b}ჸ"]
988            expected    ["ჸ"];
989        ]
990        [
991            test_name   [test_obfuscate_fuzzing_whitespaces]
992            input       ["ჸ\n\tჸ"]
993            expected    ["ჸ ?"];
994        ]
995    )]
996    #[test]
997    fn test_name() {
998        let result = remove_all_redis_args(input);
999        assert_eq!(result, expected);
1000    }
1001}