1use crate::server::memcached::protocol::{MAX_KEY_LEN, MAX_VALUE_LEN};
30use serde_json::{json, Map, Value};
31
32pub const MAX_REPLY_LINE: usize = 2048;
38
39pub const MAX_STATS_ENTRIES: usize = 4096;
45
46pub const MAX_RESPONSE_BYTES: usize = 4 * 1024 * 1024;
52
53pub const MAX_GET_KEYS: usize = 32;
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum StoreVerb {
60 Set,
61 Add,
62 Replace,
63 Append,
64 Prepend,
65 Cas,
66}
67
68impl StoreVerb {
69 pub fn as_str(self) -> &'static str {
70 match self {
71 StoreVerb::Set => "set",
72 StoreVerb::Add => "add",
73 StoreVerb::Replace => "replace",
74 StoreVerb::Append => "append",
75 StoreVerb::Prepend => "prepend",
76 StoreVerb::Cas => "cas",
77 }
78 }
79}
80
81#[derive(Debug, Clone, PartialEq, Eq)]
83pub enum Request {
84 Get {
85 keys: Vec<String>,
86 with_cas: bool,
87 },
88 Store {
89 verb: StoreVerb,
90 key: String,
91 flags: u32,
92 exptime: i64,
93 value: String,
94 cas_unique: Option<u64>,
96 },
97 Delete {
98 key: String,
99 },
100 Incr {
101 key: String,
102 delta: u64,
103 },
104 Decr {
105 key: String,
106 delta: u64,
107 },
108 Touch {
109 key: String,
110 exptime: i64,
111 },
112 Stats {
113 group: Option<String>,
114 },
115 Version,
116 FlushAll {
117 delay: Option<u64>,
118 },
119}
120
121impl Request {
122 pub fn command(&self) -> &'static str {
124 match self {
125 Request::Get {
126 with_cas: false, ..
127 } => "get",
128 Request::Get { with_cas: true, .. } => "gets",
129 Request::Store { verb, .. } => verb.as_str(),
130 Request::Delete { .. } => "delete",
131 Request::Incr { .. } => "incr",
132 Request::Decr { .. } => "decr",
133 Request::Touch { .. } => "touch",
134 Request::Stats { .. } => "stats",
135 Request::Version => "version",
136 Request::FlushAll { .. } => "flush_all",
137 }
138 }
139
140 pub fn encode(&self) -> Vec<u8> {
143 let line = match self {
144 Request::Get { keys, .. } => format!("{} {}", self.command(), keys.join(" ")),
145 Request::Store {
146 verb,
147 key,
148 flags,
149 exptime,
150 value,
151 cas_unique,
152 } => {
153 let mut head = format!(
154 "{} {} {} {} {}",
155 verb.as_str(),
156 key,
157 flags,
158 exptime,
159 value.len()
160 );
161 if let Some(cas) = cas_unique {
162 head.push_str(&format!(" {cas}"));
163 }
164 let mut out = head.into_bytes();
165 out.extend_from_slice(b"\r\n");
166 out.extend_from_slice(value.as_bytes());
167 out.extend_from_slice(b"\r\n");
168 return out;
169 }
170 Request::Delete { key } => format!("delete {key}"),
171 Request::Incr { key, delta } => format!("incr {key} {delta}"),
172 Request::Decr { key, delta } => format!("decr {key} {delta}"),
173 Request::Touch { key, exptime } => format!("touch {key} {exptime}"),
174 Request::Stats { group: None } => "stats".to_string(),
175 Request::Stats { group: Some(g) } => format!("stats {g}"),
176 Request::Version => "version".to_string(),
177 Request::FlushAll { delay: None } => "flush_all".to_string(),
178 Request::FlushAll { delay: Some(d) } => format!("flush_all {d}"),
179 };
180 let mut out = line.into_bytes();
181 out.extend_from_slice(b"\r\n");
182 out
183 }
184
185 pub fn expect(&self) -> Expect {
187 let command = self.command();
188 match self {
189 Request::Get { keys, with_cas } => Expect::Values {
190 keys: keys.clone(),
191 with_cas: *with_cas,
192 },
193 Request::Store { key, .. }
194 | Request::Delete { key }
195 | Request::Incr { key, .. }
196 | Request::Decr { key, .. }
197 | Request::Touch { key, .. } => Expect::Status {
198 command,
199 key: Some(key.clone()),
200 },
201 Request::Stats { group } => Expect::Stats {
202 group: group.clone(),
203 },
204 Request::Version | Request::FlushAll { .. } => Expect::Status { command, key: None },
205 }
206 }
207}
208
209pub fn validate_key(key: &str) -> Result<(), String> {
214 if key.is_empty() {
215 return Err("a memcached key cannot be empty".to_string());
216 }
217 if key.len() > MAX_KEY_LEN {
218 return Err(format!(
219 "key is {} bytes; memcached refuses keys longer than {MAX_KEY_LEN}",
220 key.len()
221 ));
222 }
223 if key.bytes().any(|b| b <= b' ' || b == 0x7f) {
224 return Err(format!(
225 "key {key:?} contains a space or a control character; a memcached key is one \
226 token on the command line"
227 ));
228 }
229 Ok(())
230}
231
232pub fn validate_stats_group(group: &str) -> Result<(), String> {
234 if group.is_empty() || group.len() > 32 || !group.bytes().all(|b| b.is_ascii_lowercase()) {
235 return Err(format!(
236 "stats group {group:?} is not one lowercase word such as settings, items or slabs"
237 ));
238 }
239 Ok(())
240}
241
242#[derive(Debug, Clone, PartialEq, Eq)]
244pub enum Expect {
245 Values { keys: Vec<String>, with_cas: bool },
247 Status {
249 command: &'static str,
250 key: Option<String>,
251 },
252 Stats { group: Option<String> },
254}
255
256#[derive(Debug, Clone, PartialEq, Eq)]
258pub struct Item {
259 pub key: String,
260 pub flags: u32,
261 pub cas: Option<u64>,
262 pub data: Vec<u8>,
263}
264
265#[derive(Debug, Clone, PartialEq, Eq)]
267pub enum Reply {
268 Values {
271 items: Vec<Item>,
272 misses: Vec<String>,
273 },
274 Status { line: String },
277 Stats { entries: Vec<(String, String)> },
279 Error { kind: &'static str, message: String },
281}
282
283#[derive(Debug, Clone, PartialEq, Eq)]
286pub enum WireError {
287 LineTooLong,
288 ValueTooLarge { key: String, declared: u64 },
289 UnexpectedLine(String),
290 UnrequestedKey(String),
291 TooManyStats,
292 BadValueTerminator,
293 ResponseTooLarge,
294}
295
296impl std::fmt::Display for WireError {
297 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
298 match self {
299 WireError::LineTooLong => write!(
300 f,
301 "server sent more than {MAX_REPLY_LINE} octets without a CRLF"
302 ),
303 WireError::ValueTooLarge { key, declared } => write!(
304 f,
305 "VALUE for {key:?} declares {declared} octets, over the {MAX_VALUE_LEN}-octet \
306 item limit this client reads"
307 ),
308 WireError::UnexpectedLine(line) => write!(
309 f,
310 "reply line {:?} is not a valid answer to the request in flight",
311 crate::utils::truncate::truncate_for_log(line, 80)
312 ),
313 WireError::UnrequestedKey(key) => {
314 write!(
315 f,
316 "server returned a VALUE for {key:?}, which was not requested"
317 )
318 }
319 WireError::TooManyStats => {
320 write!(f, "stats response exceeded {MAX_STATS_ENTRIES} STAT lines")
321 }
322 WireError::BadValueTerminator => {
323 write!(f, "a VALUE data block was not followed by CRLF")
324 }
325 WireError::ResponseTooLarge => write!(
326 f,
327 "one response exceeded {MAX_RESPONSE_BYTES} octets before it was complete"
328 ),
329 }
330 }
331}
332
333impl std::error::Error for WireError {}
334
335fn line_at(buf: &[u8], at: usize) -> Result<Option<(&str, usize)>, WireError> {
337 let rest = &buf[at..];
338 let window = &rest[..rest.len().min(MAX_REPLY_LINE + 2)];
339 match window.windows(2).position(|w| w == b"\r\n") {
340 Some(end) => {
341 let line = std::str::from_utf8(&rest[..end]).map_err(|_| {
342 WireError::UnexpectedLine(String::from_utf8_lossy(&rest[..end]).into())
343 })?;
344 Ok(Some((line, at + end + 2)))
345 }
346 None if rest.len() > MAX_REPLY_LINE => Err(WireError::LineTooLong),
347 None => Ok(None),
348 }
349}
350
351fn error_line(line: &str) -> Option<Reply> {
353 if line == "ERROR" {
354 return Some(Reply::Error {
355 kind: "error",
356 message: "the server did not recognise the command".to_string(),
357 });
358 }
359 if let Some(msg) = line.strip_prefix("CLIENT_ERROR") {
360 return Some(Reply::Error {
361 kind: "client_error",
362 message: msg.trim().to_string(),
363 });
364 }
365 if let Some(msg) = line.strip_prefix("SERVER_ERROR") {
366 return Some(Reply::Error {
367 kind: "server_error",
368 message: msg.trim().to_string(),
369 });
370 }
371 None
372}
373
374pub fn parse_reply(buf: &[u8], expect: &Expect) -> Result<Option<(Reply, usize)>, WireError> {
379 let Some((first, mut pos)) = line_at(buf, 0)? else {
380 return Ok(None);
381 };
382 if let Some(err) = error_line(first) {
383 return Ok(Some((err, pos)));
384 }
385
386 match expect {
387 Expect::Status { .. } => {
388 if first.starts_with("VALUE ") || first.starts_with("STAT ") || first == "END" {
389 return Err(WireError::UnexpectedLine(first.to_string()));
390 }
391 Ok(Some((
392 Reply::Status {
393 line: first.to_string(),
394 },
395 pos,
396 )))
397 }
398 Expect::Stats { .. } => {
399 let mut entries = Vec::new();
400 let mut line = first;
401 loop {
402 if line == "END" {
403 return Ok(Some((Reply::Stats { entries }, pos)));
404 }
405 let Some(rest) = line.strip_prefix("STAT ") else {
406 return Err(WireError::UnexpectedLine(line.to_string()));
407 };
408 if entries.len() >= MAX_STATS_ENTRIES {
409 return Err(WireError::TooManyStats);
410 }
411 if pos > MAX_RESPONSE_BYTES {
412 return Err(WireError::ResponseTooLarge);
413 }
414 let (name, value) = rest.split_once(' ').unwrap_or((rest, ""));
415 entries.push((name.to_string(), value.to_string()));
416 match line_at(buf, pos)? {
417 Some((next, next_pos)) => {
418 line = next;
419 pos = next_pos;
420 }
421 None => return Ok(None),
422 }
423 }
424 }
425 Expect::Values { keys, with_cas } => {
426 let mut items: Vec<Item> = Vec::new();
427 let mut line = first;
428 loop {
429 if line == "END" {
430 let misses = keys
431 .iter()
432 .filter(|k| !items.iter().any(|i| &i.key == *k))
433 .cloned()
434 .collect();
435 return Ok(Some((Reply::Values { items, misses }, pos)));
436 }
437 let Some(rest) = line.strip_prefix("VALUE ") else {
438 return Err(WireError::UnexpectedLine(line.to_string()));
439 };
440 let fields: Vec<&str> = rest.split(' ').collect();
441 let (key, flags, bytes, cas) = match (fields.as_slice(), with_cas) {
442 ([k, f, b], false) => (*k, *f, *b, None),
443 ([k, f, b, c], true) => (*k, *f, *b, Some(*c)),
444 _ => return Err(WireError::UnexpectedLine(line.to_string())),
445 };
446 if !keys.iter().any(|k| k == key) || items.iter().any(|i| i.key == key) {
447 return Err(WireError::UnrequestedKey(key.to_string()));
448 }
449 let bad = || WireError::UnexpectedLine(line.to_string());
450 let flags: u32 = flags.parse().map_err(|_| bad())?;
451 let declared: u64 = bytes.parse().map_err(|_| bad())?;
452 let cas = match cas {
453 Some(c) => Some(c.parse::<u64>().map_err(|_| bad())?),
454 None => None,
455 };
456 let len = usize::try_from(declared)
458 .ok()
459 .filter(|n| *n <= MAX_VALUE_LEN)
460 .ok_or_else(|| WireError::ValueTooLarge {
461 key: key.to_string(),
462 declared,
463 })?;
464 let key = key.to_string();
465 if pos + len + 2 > MAX_RESPONSE_BYTES {
466 return Err(WireError::ResponseTooLarge);
467 }
468 if buf.len() < pos + len + 2 {
469 return Ok(None);
470 }
471 if &buf[pos + len..pos + len + 2] != b"\r\n" {
472 return Err(WireError::BadValueTerminator);
473 }
474 items.push(Item {
475 key,
476 flags,
477 cas,
478 data: buf[pos..pos + len].to_vec(),
479 });
480 pos += len + 2;
481 match line_at(buf, pos)? {
482 Some((next, next_pos)) => {
483 line = next;
484 pos = next_pos;
485 }
486 None => return Ok(None),
487 }
488 }
489 }
490 }
491}
492
493pub fn stats_json(entries: &[(String, String)]) -> Value {
495 let mut map = Map::new();
496 for (k, v) in entries {
497 map.insert(k.clone(), json!(v));
498 }
499 Value::Object(map)
500}