ocular_protocol/
memcached.rs1pub fn parse_memcached_request(buf: &[u8]) -> Option<String> {
5 let s = std::str::from_utf8(buf).ok()?;
6 let line = s.lines().next()?;
7 let parts: Vec<&str> = line.split_whitespace().collect();
8 if parts.is_empty() { return None; }
9 let cmd = parts[0].to_uppercase();
10 match cmd.as_str() {
11 "GET" | "GETS" => Some(parts.join(" ")),
12 "SET" | "ADD" | "REPLACE" | "APPEND" | "PREPEND" | "CAS" => {
13 if parts.len() >= 5 {
15 let key = parts[1];
16 let bytes: usize = parts[4].parse().unwrap_or(0);
17 if let Some(data_start) = s.find("\r\n").map(|i| i + 2) {
19 let data = &s[data_start..];
20 let value = data.get(..bytes.min(64)).unwrap_or(data).trim_end_matches("\r\n");
21 Some(format!("{} {} \"{}\"", cmd, key, value))
22 } else {
23 Some(format!("{} {} ({} bytes)", cmd, key, bytes))
24 }
25 } else {
26 Some(parts.join(" "))
27 }
28 }
29 "DELETE" => Some(format!("DELETE {}", parts.get(1).unwrap_or(&""))),
30 "INCR" | "DECR" => {
31 let key = parts.get(1).unwrap_or(&"");
32 let val = parts.get(2).unwrap_or(&"1");
33 Some(format!("{} {} {}", cmd, key, val))
34 }
35 "TOUCH" => {
36 let key = parts.get(1).unwrap_or(&"");
37 let exp = parts.get(2).unwrap_or(&"0");
38 Some(format!("TOUCH {} {}", key, exp))
39 }
40 "STATS" | "VERSION" | "FLUSH_ALL" | "QUIT" => Some(cmd),
41 _ => Some(parts.join(" ")),
42 }
43}
44
45pub fn parse_memcached_response(buf: &[u8]) -> Option<String> {
47 let s = std::str::from_utf8(buf).ok()?;
48 let line = s.lines().next()?;
49 match line {
50 "STORED" => Some("STORED".into()),
51 "NOT_STORED" => Some("NOT_STORED".into()),
52 "EXISTS" => Some("EXISTS".into()),
53 "NOT_FOUND" => Some("NOT_FOUND".into()),
54 "DELETED" => Some("DELETED".into()),
55 "TOUCHED" => Some("TOUCHED".into()),
56 "OK" => Some("OK".into()),
57 "ERROR" => Some("ERROR".into()),
58 "END" => Some("(empty)".into()),
59 _ if line.starts_with("VALUE ") => {
60 let count = s.lines().filter(|l| l.starts_with("VALUE ")).count();
62 if count == 1 {
63 let parts: Vec<&str> = line.split_whitespace().collect();
64 let key = parts.get(1).unwrap_or(&"?");
65 let bytes: usize = parts.get(3).and_then(|b| b.parse().ok()).unwrap_or(0);
66 Some(format!("VALUE {} ({} bytes)", key, bytes))
67 } else {
68 Some(format!("{} values", count))
69 }
70 }
71 _ if line.starts_with("VERSION ") => Some(line.to_string()),
72 _ if line.starts_with("STAT ") => {
73 let count = s.lines().filter(|l| l.starts_with("STAT ")).count();
74 Some(format!("STATS ({} entries)", count))
75 }
76 _ if line.starts_with("SERVER_ERROR") || line.starts_with("CLIENT_ERROR") => {
77 Some(line.to_string())
78 }
79 _ => line.parse::<u64>().ok().map(|n| format!("{}", n)),
81 }
82}
83
84pub fn format_memcached_response_detail(buf: &[u8]) -> Option<String> {
86 let s = std::str::from_utf8(buf).ok()?;
87 let first = s.lines().next()?;
88 if first.starts_with("VALUE ") {
89 let mut result = String::new();
91 let mut lines = s.lines().peekable();
92 while let Some(line) = lines.next() {
93 if line.starts_with("VALUE ") {
94 result.push_str(line);
95 result.push('\n');
96 if let Some(data) = lines.next() {
97 if data != "END" {
98 result.push_str(data);
99 result.push('\n');
100 }
101 }
102 }
103 }
104 Some(result.trim_end().to_string())
105 } else if first.starts_with("STAT ") {
106 Some(s.lines().take_while(|l| l.starts_with("STAT ")).collect::<Vec<_>>().join("\n"))
107 } else {
108 Some(first.to_string())
109 }
110}
111
112pub fn memcached_request_complete(buf: &[u8]) -> bool {
114 let s = match std::str::from_utf8(buf) {
115 Ok(s) => s,
116 Err(_) => return buf.ends_with(b"\r\n"),
117 };
118 let Some(first_crlf) = s.find("\r\n") else { return false };
119 let line = &s[..first_crlf];
120 let parts: Vec<&str> = line.split_whitespace().collect();
121 let cmd = parts.first().map(|c| c.to_uppercase()).unwrap_or_default();
122 match cmd.as_str() {
123 "SET" | "ADD" | "REPLACE" | "APPEND" | "PREPEND" | "CAS" => {
124 let bytes: usize = parts.get(4).and_then(|b| b.parse().ok()).unwrap_or(0);
126 let expected = first_crlf + 2 + bytes + 2;
127 buf.len() >= expected
128 }
129 _ => buf.ends_with(b"\r\n"),
130 }
131}
132
133pub fn memcached_response_complete(buf: &[u8]) -> bool {
135 let s = match std::str::from_utf8(buf) {
136 Ok(s) => s,
137 Err(_) => return buf.ends_with(b"\r\n"),
138 };
139 if s.starts_with("VALUE ") {
141 return s.ends_with("END\r\n");
142 }
143 if s.starts_with("STAT ") {
145 return s.ends_with("END\r\n");
146 }
147 s.ends_with("\r\n")
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn test_parse_get() {
157 assert_eq!(parse_memcached_request(b"get user:1\r\n"), Some("get user:1".into()));
158 }
159
160 #[test]
161 fn test_parse_set() {
162 let req = b"set user:1 0 300 5\r\nhello\r\n";
163 assert_eq!(parse_memcached_request(req), Some("SET user:1 \"hello\"".into()));
164 }
165
166 #[test]
167 fn test_parse_response_stored() {
168 assert_eq!(parse_memcached_response(b"STORED\r\n"), Some("STORED".into()));
169 }
170
171 #[test]
172 fn test_parse_response_value() {
173 let resp = b"VALUE user:1 0 5\r\nhello\r\nEND\r\n";
174 assert_eq!(parse_memcached_response(resp), Some("VALUE user:1 (5 bytes)".into()));
175 }
176
177 #[test]
178 fn test_request_complete_get() {
179 assert!(memcached_request_complete(b"get key\r\n"));
180 assert!(!memcached_request_complete(b"get key"));
181 }
182
183 #[test]
184 fn test_request_complete_set() {
185 assert!(memcached_request_complete(b"set k 0 0 3\r\nabc\r\n"));
186 assert!(!memcached_request_complete(b"set k 0 0 3\r\nab"));
187 }
188
189 #[test]
190 fn test_response_complete_value() {
191 assert!(memcached_response_complete(b"VALUE k 0 3\r\nabc\r\nEND\r\n"));
192 assert!(!memcached_response_complete(b"VALUE k 0 3\r\nabc\r\n"));
193 }
194}