6
6
#include < absl/container/flat_hash_map.h>
7
7
#include < absl/container/inlined_vector.h>
8
8
#include < absl/strings/ascii.h>
9
+ #include < absl/strings/escaping.h>
9
10
#include < absl/strings/numbers.h>
10
11
#include < absl/strings/str_split.h>
11
12
#include < absl/types/span.h>
12
13
13
14
#include " base/logging.h"
14
15
#include " base/stl_util.h"
16
+ #include " facade/facade_types.h"
15
17
16
18
namespace facade {
17
19
using namespace std ;
@@ -29,16 +31,37 @@ MP::CmdType From(string_view token) {
29
31
{" quit" , MP::QUIT}, {" version" , MP::VERSION},
30
32
};
31
33
32
- auto it = cmd_map.find (token);
33
- if (it == cmd_map.end ())
34
+ if (token.size () == 2 ) {
35
+ // META_COMMANDS
36
+ if (token[0 ] != ' m' )
37
+ return MP::INVALID;
38
+ switch (token[1 ]) {
39
+ case ' s' :
40
+ return MP::META_SET;
41
+ case ' g' :
42
+ return MP::META_GET;
43
+ case ' d' :
44
+ return MP::META_DEL;
45
+ case ' a' :
46
+ return MP::META_ARITHM;
47
+ case ' n' :
48
+ return MP::META_NOOP;
49
+ case ' e' :
50
+ return MP::META_DEBUG;
51
+ }
34
52
return MP::INVALID;
53
+ }
35
54
36
- return it->second ;
55
+ if (token.size () > 2 ) {
56
+ auto it = cmd_map.find (token);
57
+ if (it == cmd_map.end ())
58
+ return MP::INVALID;
59
+ return it->second ;
60
+ }
61
+ return MP::INVALID;
37
62
}
38
63
39
- using TokensView = absl::Span<std::string_view>;
40
-
41
- MP::Result ParseStore (TokensView tokens, MP::Command* res) {
64
+ MP::Result ParseStore (ArgSlice tokens, MP::Command* res) {
42
65
const size_t num_tokens = tokens.size ();
43
66
unsigned opt_pos = 3 ;
44
67
if (res->type == MP::CAS) {
@@ -70,7 +93,7 @@ MP::Result ParseStore(TokensView tokens, MP::Command* res) {
70
93
return MP::OK;
71
94
}
72
95
73
- MP::Result ParseValueless (TokensView tokens, MP::Command* res) {
96
+ MP::Result ParseValueless (ArgSlice tokens, MP::Command* res) {
74
97
const size_t num_tokens = tokens.size ();
75
98
size_t key_pos = 0 ;
76
99
if (res->type == MP::GAT || res->type == MP::GATS) {
@@ -116,13 +139,159 @@ MP::Result ParseValueless(TokensView tokens, MP::Command* res) {
116
139
return MP::OK;
117
140
}
118
141
142
+ bool ParseMetaMode (char m, MP::Command* res) {
143
+ if (res->type == MP::SET) {
144
+ switch (m) {
145
+ case ' E' :
146
+ res->type = MP::ADD;
147
+ break ;
148
+ case ' A' :
149
+ res->type = MP::APPEND;
150
+ break ;
151
+ case ' R' :
152
+ res->type = MP::REPLACE;
153
+ break ;
154
+ case ' P' :
155
+ res->type = MP::PREPEND;
156
+ break ;
157
+ case ' S' :
158
+ break ;
159
+ default :
160
+ return false ;
161
+ }
162
+ return true ;
163
+ }
164
+
165
+ if (res->type == MP::INCR) {
166
+ switch (m) {
167
+ case ' I' :
168
+ case ' +' :
169
+ break ;
170
+ case ' D' :
171
+ case ' -' :
172
+ res->type = MP::DECR;
173
+ break ;
174
+ default :
175
+ return false ;
176
+ }
177
+ return true ;
178
+ }
179
+ return false ;
180
+ }
181
+
182
+ // See https://raw.githubusercontent.com/memcached/memcached/refs/heads/master/doc/protocol.txt
183
+ MP::Result ParseMeta (ArgSlice tokens, MP::Command* res) {
184
+ DCHECK (!tokens.empty ());
185
+
186
+ if (res->type == MP::META_DEBUG) {
187
+ LOG (ERROR) << " meta debug not yet implemented" ;
188
+ return MP::PARSE_ERROR;
189
+ }
190
+
191
+ if (tokens[0 ].size () > 250 )
192
+ return MP::PARSE_ERROR;
193
+
194
+ res->meta = true ;
195
+ res->key = tokens[0 ];
196
+ res->bytes_len = 0 ;
197
+ res->flags = 0 ;
198
+ res->expire_ts = 0 ;
199
+
200
+ tokens.remove_prefix (1 );
201
+
202
+ // We emulate the behavior by returning the high level commands.
203
+ // TODO: we should reverse the interface in the future, so that a high level command
204
+ // will be represented in MemcacheParser::Command by a meta command with flags.
205
+ // high level commands should not be part of the interface in the future.
206
+ switch (res->type ) {
207
+ case MP::META_GET:
208
+ res->type = MP::GET;
209
+ break ;
210
+ case MP::META_DEL:
211
+ res->type = MP::DELETE;
212
+ break ;
213
+ case MP::META_SET:
214
+ if (tokens.empty ()) {
215
+ return MP::PARSE_ERROR;
216
+ }
217
+ if (!absl::SimpleAtoi (tokens[0 ], &res->bytes_len ))
218
+ return MP::BAD_INT;
219
+
220
+ res->type = MP::SET;
221
+ tokens.remove_prefix (1 );
222
+ break ;
223
+ case MP::META_ARITHM:
224
+ res->type = MP::INCR;
225
+ res->delta = 1 ;
226
+ break ;
227
+ default :
228
+ return MP::PARSE_ERROR;
229
+ }
230
+
231
+ for (size_t i = 0 ; i < tokens.size (); ++i) {
232
+ string_view token = tokens[i];
233
+
234
+ switch (token[0 ]) {
235
+ case ' T' :
236
+ if (!absl::SimpleAtoi (token.substr (1 ), &res->expire_ts ))
237
+ return MP::BAD_INT;
238
+ break ;
239
+ case ' b' :
240
+ if (token.size () != 1 )
241
+ return MP::PARSE_ERROR;
242
+ if (!absl::Base64Unescape (res->key , &res->blob ))
243
+ return MP::PARSE_ERROR;
244
+ res->key = res->blob ;
245
+ res->base64 = true ;
246
+ break ;
247
+ case ' F' :
248
+ if (!absl::SimpleAtoi (token.substr (1 ), &res->flags ))
249
+ return MP::BAD_INT;
250
+ break ;
251
+ case ' M' :
252
+ if (token.size () != 2 || !ParseMetaMode (token[1 ], res))
253
+ return MP::PARSE_ERROR;
254
+ break ;
255
+ case ' D' :
256
+ if (!absl::SimpleAtoi (token.substr (1 ), &res->delta ))
257
+ return MP::BAD_INT;
258
+ break ;
259
+ case ' q' :
260
+ res->no_reply = true ;
261
+ break ;
262
+ case ' f' :
263
+ res->return_flags = true ;
264
+ break ;
265
+ case ' v' :
266
+ res->return_value = true ;
267
+ break ;
268
+ case ' t' :
269
+ res->return_ttl = true ;
270
+ break ;
271
+ case ' l' :
272
+ res->return_access_time = true ;
273
+ break ;
274
+ case ' h' :
275
+ res->return_hit = true ;
276
+ break ;
277
+ default :
278
+ LOG (WARNING) << " unknown meta flag: " << token; // not yet implemented
279
+ return MP::PARSE_ERROR;
280
+ }
281
+ }
282
+
283
+ return MP::OK;
284
+ }
285
+
119
286
} // namespace
120
287
121
288
auto MP::Parse (string_view str, uint32_t * consumed, Command* cmd) -> Result {
122
289
cmd->no_reply = false ; // re-initialize
123
290
auto pos = str.find (" \r\n " );
124
291
*consumed = 0 ;
125
292
if (pos == string_view::npos) {
293
+ // We need more data to parse the command. For get/gets commands this line can be very long.
294
+ // we limit maxmimum buffer capacity in the higher levels using max_client_iobuf_len.
126
295
return INPUT_PENDING;
127
296
}
128
297
@@ -131,42 +300,47 @@ auto MP::Parse(string_view str, uint32_t* consumed, Command* cmd) -> Result {
131
300
}
132
301
*consumed = pos + 2 ;
133
302
134
- std:: string_view tokens_expression = str.substr (0 , pos);
303
+ string_view tokens_expression = str.substr (0 , pos);
135
304
136
305
// cas <key> <flags> <exptime> <bytes> <cas unique> [noreply]\r\n
137
306
// get <key>*\r\n
138
- absl::InlinedVector<std::string_view, 32 > tokens =
307
+ // ms <key> <datalen> <flags>*\r\n
308
+ absl::InlinedVector<string_view, 32 > tokens =
139
309
absl::StrSplit (tokens_expression, ' ' , absl::SkipWhitespace ());
140
310
141
- const size_t num_tokens = tokens.size ();
142
-
143
- if (num_tokens == 0 )
311
+ if (tokens.empty ())
144
312
return PARSE_ERROR;
145
313
146
314
cmd->type = From (tokens[0 ]);
147
315
if (cmd->type == INVALID) {
148
316
return UNKNOWN_CMD;
149
317
}
150
318
151
- if (cmd->type <= CAS) { // Store command
152
- if (num_tokens < 5 || tokens[1 ].size () > 250 ) { // key length limit
319
+ ArgSlice tokens_view{tokens};
320
+ tokens_view.remove_prefix (1 );
321
+
322
+ if (cmd->type <= CAS) { // Store command
323
+ if (tokens_view.size () < 4 || tokens[0 ].size () > 250 ) { // key length limit
153
324
return MP::PARSE_ERROR;
154
325
}
155
326
156
- cmd->key = string_view{tokens[ 1 ]. data (), tokens[ 1 ]. size ()} ;
327
+ cmd->key = tokens_view[ 0 ] ;
157
328
158
- TokensView tokens_view{tokens. begin () + 2 , num_tokens - 2 } ;
329
+ tokens_view. remove_prefix ( 1 ) ;
159
330
return ParseStore (tokens_view, cmd);
160
331
}
161
332
162
- if (num_tokens == 1 ) {
163
- if (base::_in (cmd->type , {MP::STATS, MP::FLUSHALL, MP::QUIT, MP::VERSION})) {
333
+ if (cmd->type >= META_SET) {
334
+ return tokens_view.empty () ? MP::PARSE_ERROR : ParseMeta (tokens_view, cmd);
335
+ }
336
+
337
+ if (tokens_view.empty ()) {
338
+ if (base::_in (cmd->type , {MP::STATS, MP::FLUSHALL, MP::QUIT, MP::VERSION, MP::META_NOOP})) {
164
339
return MP::OK;
165
340
}
166
341
return MP::PARSE_ERROR;
167
342
}
168
343
169
- TokensView tokens_view{tokens.begin () + 1 , num_tokens - 1 };
170
344
return ParseValueless (tokens_view, cmd);
171
345
};
172
346
0 commit comments