redis_protocol/
utils.rs

1use crate::error::{RedisParseError, RedisProtocolError, RedisProtocolErrorKind};
2use core::str;
3use crc16::{State, XMODEM};
4
5use crate::types::REDIS_CLUSTER_SLOTS;
6#[cfg(feature = "bytes")]
7use bytes::BytesMut;
8
9/// Returns the number of bytes necessary to encode a string representation of `d`.
10#[cfg(feature = "std")]
11pub fn digits_in_usize(d: usize) -> usize {
12  if d == 0 {
13    return 1;
14  }
15
16  ((d as f64).log10()).floor() as usize + 1
17}
18
19/// Returns the number of bytes necessary to encode a string representation of `d`.
20#[cfg(feature = "libm")]
21pub fn digits_in_usize(d: usize) -> usize {
22  if d == 0 {
23    return 1;
24  }
25
26  libm::floor(libm::log10(d as f64)) as usize + 1
27}
28
29/// Returns the number of bytes necessary to encode a string representation of `d`.
30#[cfg(feature = "std")]
31pub fn digits_in_i64(d: i64) -> usize {
32  if d == 0 {
33    return 1;
34  }
35  let prefix = if d.is_negative() { 1 } else { 0 };
36
37  prefix + ((d.unsigned_abs() as f64).log10()).floor() as usize + 1
38}
39
40/// Returns the number of bytes necessary to encode a string representation of `d`.
41#[cfg(feature = "libm")]
42pub fn digits_in_i64(d: i64) -> usize {
43  if d == 0 {
44    return 1;
45  }
46  let prefix = if d.is_negative() { 1 } else { 0 };
47
48  prefix + libm::floor(libm::log10(d.unsigned_abs() as f64)) as usize + 1
49}
50
51pub fn isize_to_usize<T>(val: isize) -> Result<usize, RedisParseError<T>> {
52  if val >= 0 {
53    Ok(val as usize)
54  } else {
55    Err(RedisParseError::new_custom("isize_to_usize", "Invalid length."))
56  }
57}
58
59/// Extend the buffer by `amt`.
60#[cfg(feature = "bytes")]
61#[cfg_attr(docsrs, doc(cfg(feature = "bytes")))]
62pub fn zero_extend(buf: &mut BytesMut, amt: usize) {
63  buf.resize(buf.len() + amt, 0);
64}
65
66/// Whether an error payload is a `MOVED` or `ASK` redirection.
67pub(crate) fn is_redirection(payload: &str) -> bool {
68  if payload.starts_with("MOVED") || payload.starts_with("ASK") {
69    payload.split(' ').count() == 3
70  } else {
71    false
72  }
73}
74
75/// Perform a crc16 XMODEM operation against a string slice.
76fn crc16_xmodem(key: &[u8]) -> u16 {
77  State::<XMODEM>::calculate(key) % REDIS_CLUSTER_SLOTS
78}
79
80/// Map a key to the corresponding cluster key slot.
81///
82/// ```ignore
83/// $ redis-cli cluster keyslot "8xjx7vWrfPq54mKfFD3Y1CcjjofpnAcQ"
84/// (integer) 5458
85/// ```
86///
87/// ```
88/// # use redis_protocol::redis_keyslot;
89/// assert_eq!(redis_keyslot(b"8xjx7vWrfPq54mKfFD3Y1CcjjofpnAcQ"), 5458);
90/// ```
91pub fn redis_keyslot(key: &[u8]) -> u16 {
92  let (mut i, mut j): (Option<usize>, Option<usize>) = (None, None);
93
94  for (idx, c) in key.iter().enumerate() {
95    if *c == b'{' {
96      i = Some(idx);
97      break;
98    }
99  }
100
101  if i.is_none() || (i.is_some() && i.unwrap() == key.len() - 1) {
102    return crc16_xmodem(key);
103  }
104
105  let i = i.unwrap();
106  for (idx, c) in key[i + 1 ..].iter().enumerate() {
107    if *c == b'}' {
108      j = Some(idx);
109      break;
110    }
111  }
112
113  if j.is_none() {
114    return crc16_xmodem(key);
115  }
116
117  let j = j.unwrap();
118  if i + j == key.len() || j == 0 {
119    crc16_xmodem(key)
120  } else {
121    crc16_xmodem(&key[i + 1 .. i + j + 1])
122  }
123}
124
125/// Convert a string to a double, supporting "nan", "+inf" and "-inf".
126pub fn str_to_f64(s: &str) -> Result<f64, RedisProtocolError> {
127  // this is changing in newer versions of redis to lose the "+" prefix
128  match s {
129    "+inf" | "inf" => Ok(f64::INFINITY),
130    "-inf" => Ok(f64::NEG_INFINITY),
131    "nan" => Ok(f64::NAN),
132    _ => s.parse::<f64>().map_err(|_| {
133      RedisProtocolError::new(
134        RedisProtocolErrorKind::Unknown,
135        "Could not convert to floating point value.",
136      )
137    }),
138  }
139}
140
141/// Convert bytes to a boolean.
142pub(crate) fn bytes_to_bool(b: &[u8]) -> Option<bool> {
143  match b {
144    b"true" | b"TRUE" | b"t" | b"T" | b"1" => Some(true),
145    b"false" | b"FALSE" | b"f" | b"F" | b"0" => Some(false),
146    _ => None,
147  }
148}
149
150#[cfg(test)]
151mod tests {
152  use super::*;
153  use alloc::vec::Vec;
154
155  fn read_kitten_file() -> Vec<u8> {
156    include_bytes!("../tests/kitten.jpeg").to_vec()
157  }
158
159  #[test]
160  fn should_crc16_123456789() {
161    let key = "123456789";
162    // 31C3
163    let expected: u16 = 12739;
164    let actual = redis_keyslot(key.as_bytes());
165
166    assert_eq!(actual, expected);
167  }
168
169  #[test]
170  fn should_crc16_with_brackets() {
171    let key = "foo{123456789}bar";
172    // 31C3
173    let expected: u16 = 12739;
174    let actual = redis_keyslot(key.as_bytes());
175
176    assert_eq!(actual, expected);
177  }
178
179  #[test]
180  fn should_crc16_with_brackets_no_padding() {
181    let key = "{123456789}";
182    // 31C3
183    let expected: u16 = 12739;
184    let actual = redis_keyslot(key.as_bytes());
185
186    assert_eq!(actual, expected);
187  }
188
189  #[test]
190  fn should_crc16_with_invalid_brackets_lhs() {
191    let key = "foo{123456789";
192    // 288A
193    let expected: u16 = 10378;
194    let actual = redis_keyslot(key.as_bytes());
195
196    assert_eq!(actual, expected);
197  }
198
199  #[test]
200  fn should_crc16_with_invalid_brackets_rhs() {
201    let key = "foo}123456789";
202    // 5B35 = 23349, 23349 % 16384 = 6965
203    let expected: u16 = 6965;
204    let actual = redis_keyslot(key.as_bytes());
205
206    assert_eq!(actual, expected);
207  }
208
209  #[test]
210  fn should_crc16_with_random_string() {
211    let key = "8xjx7vWrfPq54mKfFD3Y1CcjjofpnAcQ";
212    // 127.0.0.1:30001> cluster keyslot 8xjx7vWrfPq54mKfFD3Y1CcjjofpnAcQ
213    // (integer) 5458
214    let expected: u16 = 5458;
215    let actual = redis_keyslot(key.as_bytes());
216
217    assert_eq!(actual, expected);
218  }
219
220  #[test]
221  fn should_hash_non_ascii_string_bytes() {
222    let key = "💩 👻 💀 ☠️ 👽 👾";
223    // 127.0.0.1:30001> cluster keyslot "💩 👻 💀 ☠️ 👽 👾"
224    // (integer) 13954
225    let expected: u16 = 13954;
226    let actual = redis_keyslot(key.as_bytes());
227
228    assert_eq!(actual, expected);
229  }
230
231  #[test]
232  fn should_hash_non_ascii_string_bytes_with_tag() {
233    let key = "💩 👻 💀{123456789}☠️ 👽 👾";
234    // 127.0.0.1:30001> cluster keyslot "💩 👻 💀{123456789}☠️ 👽 👾"
235    // (integer) 12739
236    let expected: u16 = 12739;
237    let actual = redis_keyslot(key.as_bytes());
238
239    assert_eq!(actual, expected);
240  }
241
242  #[test]
243  fn should_hash_non_utf8_string_bytes() {
244    let key = read_kitten_file();
245    let expected: u16 = 1589;
246    let actual = redis_keyslot(&key);
247
248    assert_eq!(actual, expected)
249  }
250
251  #[test]
252  fn should_hash_non_utf8_string_bytes_with_tag() {
253    let mut key = read_kitten_file();
254    for (idx, c) in "{123456789}".as_bytes().iter().enumerate() {
255      key[242 + idx] = *c;
256    }
257
258    let expected: u16 = 12739;
259    let actual = redis_keyslot(&key);
260    assert_eq!(actual, expected)
261  }
262}