Convert stats to vector.
[awesomized/libmemcached] / libmemcached / response.cc
1 /* vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
2 *
3 * Libmemcached library
4 *
5 * Copyright (C) 2011 Data Differential, http://datadifferential.com/
6 * Copyright (C) 2006-2009 Brian Aker All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are
10 * met:
11 *
12 * * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * * Redistributions in binary form must reproduce the above
16 * copyright notice, this list of conditions and the following disclaimer
17 * in the documentation and/or other materials provided with the
18 * distribution.
19 *
20 * * The names of its contributors may not be used to endorse or
21 * promote products derived from this software without specific prior
22 * written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 *
36 */
37
38 #include <libmemcached/common.h>
39 #include <libmemcached/string.hpp>
40
41 static memcached_return_t textual_read_one_response(memcached_server_write_instance_st ptr,
42 char *buffer, size_t buffer_length,
43 memcached_result_st *result);
44 static memcached_return_t binary_read_one_response(memcached_server_write_instance_st ptr,
45 char *buffer, size_t buffer_length,
46 memcached_result_st *result);
47
48 memcached_return_t memcached_read_one_response(memcached_server_write_instance_st ptr,
49 char *buffer, size_t buffer_length,
50 memcached_result_st *result)
51 {
52 memcached_server_response_decrement(ptr);
53
54 if (result == NULL)
55 {
56 memcached_st *root= (memcached_st *)ptr->root;
57 result = &root->result;
58 }
59
60 memcached_return_t rc;
61 if (ptr->root->flags.binary_protocol)
62 {
63 rc= binary_read_one_response(ptr, buffer, buffer_length, result);
64 }
65 else
66 {
67 rc= textual_read_one_response(ptr, buffer, buffer_length, result);
68 }
69
70 unlikely(rc == MEMCACHED_UNKNOWN_READ_FAILURE or
71 rc == MEMCACHED_PROTOCOL_ERROR or
72 rc == MEMCACHED_CLIENT_ERROR or
73 rc == MEMCACHED_MEMORY_ALLOCATION_FAILURE)
74 {
75 memcached_io_reset(ptr);
76 }
77
78 return rc;
79 }
80
81 memcached_return_t memcached_response(memcached_server_write_instance_st ptr,
82 char *buffer, size_t buffer_length,
83 memcached_result_st *result)
84 {
85 /* We may have old commands in the buffer not set, first purge */
86 if ((ptr->root->flags.no_block) && (memcached_is_processing_input(ptr->root) == false))
87 {
88 (void)memcached_io_write(ptr, NULL, 0, true);
89 }
90
91 /*
92 * The previous implementation purged all pending requests and just
93 * returned the last one. Purge all pending messages to ensure backwards
94 * compatibility.
95 */
96 if (ptr->root->flags.binary_protocol == false)
97 {
98 while (memcached_server_response_count(ptr) > 1)
99 {
100 memcached_return_t rc= memcached_read_one_response(ptr, buffer, buffer_length, result);
101
102 unlikely (rc != MEMCACHED_END &&
103 rc != MEMCACHED_STORED &&
104 rc != MEMCACHED_SUCCESS &&
105 rc != MEMCACHED_STAT &&
106 rc != MEMCACHED_DELETED &&
107 rc != MEMCACHED_NOTFOUND &&
108 rc != MEMCACHED_NOTSTORED &&
109 rc != MEMCACHED_DATA_EXISTS)
110 return rc;
111 }
112 }
113
114 return memcached_read_one_response(ptr, buffer, buffer_length, result);
115 }
116
117 static memcached_return_t textual_value_fetch(memcached_server_write_instance_st ptr,
118 char *buffer,
119 memcached_result_st *result)
120 {
121 char *string_ptr;
122 char *end_ptr;
123 char *next_ptr;
124 size_t value_length;
125 size_t to_read;
126 ssize_t read_length= 0;
127
128 if (ptr->root->flags.use_udp)
129 {
130 return memcached_set_error(*ptr, MEMCACHED_NOT_SUPPORTED, MEMCACHED_AT);
131 }
132
133 WATCHPOINT_ASSERT(ptr->root);
134 end_ptr= buffer + MEMCACHED_DEFAULT_COMMAND_SIZE;
135
136 memcached_result_reset(result);
137
138 string_ptr= buffer;
139 string_ptr+= 6; /* "VALUE " */
140
141
142 /* We load the key */
143 {
144 char *key;
145 size_t prefix_length;
146
147 key= result->item_key;
148 result->key_length= 0;
149
150 for (prefix_length= memcached_array_size(ptr->root->_namespace); !(iscntrl(*string_ptr) || isspace(*string_ptr)) ; string_ptr++)
151 {
152 if (prefix_length == 0)
153 {
154 *key= *string_ptr;
155 key++;
156 result->key_length++;
157 }
158 else
159 prefix_length--;
160 }
161 result->item_key[result->key_length]= 0;
162 }
163
164 if (end_ptr == string_ptr)
165 goto read_error;
166
167 /* Flags fetch move past space */
168 string_ptr++;
169 if (end_ptr == string_ptr)
170 goto read_error;
171
172 for (next_ptr= string_ptr; isdigit(*string_ptr); string_ptr++) {};
173 result->item_flags= (uint32_t) strtoul(next_ptr, &string_ptr, 10);
174
175 if (end_ptr == string_ptr)
176 goto read_error;
177
178 /* Length fetch move past space*/
179 string_ptr++;
180 if (end_ptr == string_ptr)
181 goto read_error;
182
183 for (next_ptr= string_ptr; isdigit(*string_ptr); string_ptr++) {};
184 value_length= (size_t)strtoull(next_ptr, &string_ptr, 10);
185
186 if (end_ptr == string_ptr)
187 {
188 goto read_error;
189 }
190
191 /* Skip spaces */
192 if (*string_ptr == '\r')
193 {
194 /* Skip past the \r\n */
195 string_ptr+= 2;
196 }
197 else
198 {
199 string_ptr++;
200 for (next_ptr= string_ptr; isdigit(*string_ptr); string_ptr++) {};
201 result->item_cas= strtoull(next_ptr, &string_ptr, 10);
202 }
203
204 if (end_ptr < string_ptr)
205 goto read_error;
206
207 /* We add two bytes so that we can walk the \r\n */
208 if (memcached_failed(memcached_string_check(&result->value, value_length +2)))
209 {
210 return memcached_set_error(*ptr, MEMCACHED_MEMORY_ALLOCATION_FAILURE, MEMCACHED_AT);
211 }
212
213 {
214 char *value_ptr= memcached_string_value_mutable(&result->value);
215 /*
216 We read the \r\n into the string since not doing so is more
217 cycles then the waster of memory to do so.
218
219 We are null terminating through, which will most likely make
220 some people lazy about using the return length.
221 */
222 to_read= (value_length) + 2;
223 memcached_return_t rrc= memcached_io_read(ptr, value_ptr, to_read, &read_length);
224 if (memcached_failed(rrc) and rrc == MEMCACHED_IN_PROGRESS)
225 {
226 memcached_quit_server(ptr, true);
227 return memcached_set_error(*ptr, MEMCACHED_IN_PROGRESS, MEMCACHED_AT);
228 }
229 else if (memcached_failed(rrc))
230 {
231 return rrc;
232 }
233 }
234
235 if (read_length != (ssize_t)(value_length + 2))
236 {
237 goto read_error;
238 }
239
240 /* This next bit blows the API, but this is internal....*/
241 {
242 char *char_ptr;
243 char_ptr= memcached_string_value_mutable(&result->value);;
244 char_ptr[value_length]= 0;
245 char_ptr[value_length +1]= 0;
246 memcached_string_set_length(&result->value, value_length);
247 }
248
249 return MEMCACHED_SUCCESS;
250
251 read_error:
252 memcached_io_reset(ptr);
253
254 return MEMCACHED_PARTIAL_READ;
255 }
256
257 static memcached_return_t textual_read_one_response(memcached_server_write_instance_st ptr,
258 char *buffer, size_t buffer_length,
259 memcached_result_st *result)
260 {
261 size_t total_read;
262 memcached_return_t rc= memcached_io_readline(ptr, buffer, buffer_length, total_read);
263
264 if (memcached_failed(rc))
265 {
266 return rc;
267 }
268
269 switch(buffer[0])
270 {
271 case 'V': /* VALUE || VERSION */
272 if (buffer[1] == 'A') /* VALUE */
273 {
274 /* We add back in one because we will need to search for END */
275 memcached_server_response_increment(ptr);
276 return textual_value_fetch(ptr, buffer, result);
277 }
278 else if (buffer[1] == 'E') /* VERSION */
279 {
280 return MEMCACHED_SUCCESS;
281 }
282 else
283 {
284 WATCHPOINT_STRING(buffer);
285 return MEMCACHED_UNKNOWN_READ_FAILURE;
286 }
287 case 'O': /* OK */
288 return MEMCACHED_SUCCESS;
289
290 case 'S': /* STORED STATS SERVER_ERROR */
291 {
292 if (buffer[2] == 'A') /* STORED STATS */
293 {
294 memcached_server_response_increment(ptr);
295 return MEMCACHED_STAT;
296 }
297 else if (buffer[1] == 'E') /* SERVER_ERROR */
298 {
299 if (total_read == memcached_literal_param_size("SERVER_ERROR"))
300 {
301 return MEMCACHED_SERVER_ERROR;
302 }
303
304 if (total_read > memcached_literal_param_size("SERVER_ERROR object too large for cache") and
305 (memcmp(buffer, memcached_literal_param("SERVER_ERROR object too large for cache")) == 0))
306 {
307 return MEMCACHED_E2BIG;
308 }
309
310 // Move past the basic error message and whitespace
311 char *startptr= buffer + memcached_literal_param_size("SERVER_ERROR");
312 if (startptr[0] == ' ')
313 {
314 startptr++;
315 }
316
317 char *endptr= startptr;
318 while (*endptr != '\r' && *endptr != '\n') endptr++;
319
320 return memcached_set_error(*ptr, MEMCACHED_SERVER_ERROR, MEMCACHED_AT, startptr, size_t(endptr - startptr));
321 }
322 else if (buffer[1] == 'T')
323 {
324 return MEMCACHED_STORED;
325 }
326 else
327 {
328 WATCHPOINT_STRING(buffer);
329 return MEMCACHED_UNKNOWN_READ_FAILURE;
330 }
331 }
332 case 'D': /* DELETED */
333 return MEMCACHED_DELETED;
334
335 case 'N': /* NOT_FOUND */
336 {
337 if (buffer[4] == 'F')
338 {
339 return MEMCACHED_NOTFOUND;
340 }
341 else if (buffer[4] == 'S')
342 {
343 return MEMCACHED_NOTSTORED;
344 }
345 else
346 {
347 WATCHPOINT_STRING(buffer);
348 return MEMCACHED_UNKNOWN_READ_FAILURE;
349 }
350 }
351 case 'E': /* PROTOCOL ERROR or END */
352 {
353 if (buffer[1] == 'N')
354 {
355 return MEMCACHED_END;
356 }
357 else if (buffer[1] == 'R')
358 {
359 return MEMCACHED_PROTOCOL_ERROR;
360 }
361 else if (buffer[1] == 'X')
362 {
363 return MEMCACHED_DATA_EXISTS;
364 }
365 else
366 {
367 WATCHPOINT_STRING(buffer);
368 return MEMCACHED_UNKNOWN_READ_FAILURE;
369 }
370
371 }
372 case 'T': /* TOUCHED */
373 {
374 if (buffer[1] == 'O' and buffer[2] == 'U'
375 and buffer[3] == 'C' and buffer[4] == 'H'
376 and buffer[5] == 'E' and buffer[6] == 'D')
377 {
378 return MEMCACHED_SUCCESS;
379 }
380 }
381 return MEMCACHED_UNKNOWN_READ_FAILURE;
382
383 case 'I': /* CLIENT ERROR */
384 /* We add back in one because we will need to search for END */
385 memcached_server_response_increment(ptr);
386 return MEMCACHED_ITEM;
387
388 case 'C': /* CLIENT ERROR */
389 return MEMCACHED_CLIENT_ERROR;
390
391 default:
392 {
393 unsigned long long int auto_return_value= strtoull(buffer, (char **)NULL, 10);
394
395 if (auto_return_value == ULLONG_MAX and errno == ERANGE)
396 {
397 return MEMCACHED_UNKNOWN_READ_FAILURE;
398 }
399 else if (errno == EINVAL)
400 {
401 return MEMCACHED_UNKNOWN_READ_FAILURE;
402 }
403
404 WATCHPOINT_STRING(buffer);
405 return MEMCACHED_SUCCESS;
406 }
407 }
408
409 /* NOTREACHED */
410 }
411
412 static memcached_return_t binary_read_one_response(memcached_server_write_instance_st ptr,
413 char *buffer, size_t buffer_length,
414 memcached_result_st *result)
415 {
416 memcached_return_t rc;
417 protocol_binary_response_header header;
418
419 if ((rc= memcached_safe_read(ptr, &header.bytes, sizeof(header.bytes))) != MEMCACHED_SUCCESS)
420 {
421 WATCHPOINT_ERROR(rc);
422 return rc;
423 }
424
425 if (header.response.magic != PROTOCOL_BINARY_RES)
426 {
427 return MEMCACHED_PROTOCOL_ERROR;
428 }
429
430 /*
431 ** Convert the header to host local endian!
432 */
433 header.response.keylen= ntohs(header.response.keylen);
434 header.response.status= ntohs(header.response.status);
435 header.response.bodylen= ntohl(header.response.bodylen);
436 header.response.cas= memcached_ntohll(header.response.cas);
437 uint32_t bodylen= header.response.bodylen;
438
439 if (header.response.status == PROTOCOL_BINARY_RESPONSE_SUCCESS or
440 header.response.status == PROTOCOL_BINARY_RESPONSE_AUTH_CONTINUE)
441 {
442 switch (header.response.opcode)
443 {
444 case PROTOCOL_BINARY_CMD_GETKQ:
445 /*
446 * We didn't increment the response counter for the GETKQ packet
447 * (only the final NOOP), so we need to increment the counter again.
448 */
449 memcached_server_response_increment(ptr);
450 /* FALLTHROUGH */
451 case PROTOCOL_BINARY_CMD_GETK:
452 {
453 uint16_t keylen= header.response.keylen;
454 memcached_result_reset(result);
455 result->item_cas= header.response.cas;
456
457 if ((rc= memcached_safe_read(ptr, &result->item_flags, sizeof (result->item_flags))) != MEMCACHED_SUCCESS)
458 {
459 WATCHPOINT_ERROR(rc);
460 return MEMCACHED_UNKNOWN_READ_FAILURE;
461 }
462
463 result->item_flags= ntohl(result->item_flags);
464 bodylen -= header.response.extlen;
465
466 result->key_length= keylen;
467 if (memcached_failed(rc= memcached_safe_read(ptr, result->item_key, keylen)))
468 {
469 WATCHPOINT_ERROR(rc);
470 return MEMCACHED_UNKNOWN_READ_FAILURE;
471 }
472
473 // Only bother with doing this if key_length > 0
474 if (result->key_length)
475 {
476 if (memcached_array_size(ptr->root->_namespace) and memcached_array_size(ptr->root->_namespace) >= result->key_length)
477 {
478 return memcached_set_error(*ptr, MEMCACHED_UNKNOWN_READ_FAILURE, MEMCACHED_AT);
479 }
480
481 if (memcached_array_size(ptr->root->_namespace))
482 {
483 result->key_length-= memcached_array_size(ptr->root->_namespace);
484 memmove(result->item_key, result->item_key +memcached_array_size(ptr->root->_namespace), result->key_length);
485 }
486 }
487
488 bodylen -= keylen;
489 if (memcached_failed(memcached_string_check(&result->value, bodylen)))
490 {
491 return MEMCACHED_MEMORY_ALLOCATION_FAILURE;
492 }
493
494 char *vptr= memcached_string_value_mutable(&result->value);
495 if (memcached_failed(rc= memcached_safe_read(ptr, vptr, bodylen)))
496 {
497 WATCHPOINT_ERROR(rc);
498 return MEMCACHED_UNKNOWN_READ_FAILURE;
499 }
500
501 memcached_string_set_length(&result->value, bodylen);
502 }
503 break;
504
505 case PROTOCOL_BINARY_CMD_INCREMENT:
506 case PROTOCOL_BINARY_CMD_DECREMENT:
507 {
508 if (bodylen != sizeof(uint64_t) || buffer_length != sizeof(uint64_t))
509 {
510 return MEMCACHED_PROTOCOL_ERROR;
511 }
512
513 WATCHPOINT_ASSERT(bodylen == buffer_length);
514 uint64_t val;
515 if ((rc= memcached_safe_read(ptr, &val, sizeof(val))) != MEMCACHED_SUCCESS)
516 {
517 WATCHPOINT_ERROR(rc);
518 return MEMCACHED_UNKNOWN_READ_FAILURE;
519 }
520
521 val= memcached_ntohll(val);
522 memcpy(buffer, &val, sizeof(val));
523 }
524 break;
525
526 case PROTOCOL_BINARY_CMD_SASL_LIST_MECHS:
527 case PROTOCOL_BINARY_CMD_VERSION:
528 {
529 memset(buffer, 0, buffer_length);
530 if (bodylen >= buffer_length)
531 {
532 /* not enough space in buffer.. should not happen... */
533 return MEMCACHED_UNKNOWN_READ_FAILURE;
534 }
535 else if ((rc= memcached_safe_read(ptr, buffer, bodylen)) != MEMCACHED_SUCCESS)
536 {
537 WATCHPOINT_ERROR(rc);
538 return MEMCACHED_UNKNOWN_READ_FAILURE;
539 }
540 }
541 break;
542 case PROTOCOL_BINARY_CMD_FLUSH:
543 case PROTOCOL_BINARY_CMD_QUIT:
544 case PROTOCOL_BINARY_CMD_SET:
545 case PROTOCOL_BINARY_CMD_ADD:
546 case PROTOCOL_BINARY_CMD_REPLACE:
547 case PROTOCOL_BINARY_CMD_APPEND:
548 case PROTOCOL_BINARY_CMD_PREPEND:
549 case PROTOCOL_BINARY_CMD_DELETE:
550 case PROTOCOL_BINARY_CMD_TOUCH:
551 {
552 WATCHPOINT_ASSERT(bodylen == 0);
553 return MEMCACHED_SUCCESS;
554 }
555
556 case PROTOCOL_BINARY_CMD_NOOP:
557 {
558 WATCHPOINT_ASSERT(bodylen == 0);
559 return MEMCACHED_END;
560 }
561
562 case PROTOCOL_BINARY_CMD_STAT:
563 {
564 if (bodylen == 0)
565 {
566 return MEMCACHED_END;
567 }
568 else if (bodylen + 1 > buffer_length)
569 {
570 /* not enough space in buffer.. should not happen... */
571 return MEMCACHED_UNKNOWN_READ_FAILURE;
572 }
573 else
574 {
575 size_t keylen= header.response.keylen;
576 memset(buffer, 0, buffer_length);
577 if ((rc= memcached_safe_read(ptr, buffer, keylen)) != MEMCACHED_SUCCESS ||
578 (rc= memcached_safe_read(ptr, buffer + keylen + 1, bodylen - keylen)) != MEMCACHED_SUCCESS)
579 {
580 WATCHPOINT_ERROR(rc);
581 return MEMCACHED_UNKNOWN_READ_FAILURE;
582 }
583 }
584 }
585 break;
586
587 case PROTOCOL_BINARY_CMD_SASL_AUTH:
588 case PROTOCOL_BINARY_CMD_SASL_STEP:
589 {
590 memcached_result_reset(result);
591 result->item_cas= header.response.cas;
592
593 if (memcached_string_check(&result->value,
594 bodylen) != MEMCACHED_SUCCESS)
595 return MEMCACHED_MEMORY_ALLOCATION_FAILURE;
596
597 char *vptr= memcached_string_value_mutable(&result->value);
598 if ((rc= memcached_safe_read(ptr, vptr, bodylen)) != MEMCACHED_SUCCESS)
599 {
600 WATCHPOINT_ERROR(rc);
601 return MEMCACHED_UNKNOWN_READ_FAILURE;
602 }
603
604 memcached_string_set_length(&result->value, bodylen);
605 }
606 break;
607 default:
608 {
609 /* Command not implemented yet! */
610 WATCHPOINT_ASSERT(0);
611 return MEMCACHED_PROTOCOL_ERROR;
612 }
613 }
614 }
615 else if (header.response.bodylen)
616 {
617 /* What should I do with the error message??? just discard it for now */
618 char hole[SMALL_STRING_LEN];
619 while (bodylen > 0)
620 {
621 size_t nr= (bodylen > SMALL_STRING_LEN) ? SMALL_STRING_LEN : bodylen;
622 if ((rc= memcached_safe_read(ptr, hole, nr)) != MEMCACHED_SUCCESS)
623 {
624 WATCHPOINT_ERROR(rc);
625 return memcached_set_error(*ptr, MEMCACHED_UNKNOWN_READ_FAILURE, MEMCACHED_AT);
626 }
627 bodylen-= (uint32_t) nr;
628 }
629
630 /* This might be an error from one of the quiet commands.. if
631 * so, just throw it away and get the next one. What about creating
632 * a callback to the user with the error information?
633 */
634 switch (header.response.opcode)
635 {
636 case PROTOCOL_BINARY_CMD_SETQ:
637 case PROTOCOL_BINARY_CMD_ADDQ:
638 case PROTOCOL_BINARY_CMD_REPLACEQ:
639 case PROTOCOL_BINARY_CMD_APPENDQ:
640 case PROTOCOL_BINARY_CMD_PREPENDQ:
641 return binary_read_one_response(ptr, buffer, buffer_length, result);
642
643 default:
644 break;
645 }
646 }
647
648 rc= MEMCACHED_SUCCESS;
649 if (header.response.status != 0)
650 {
651 switch (header.response.status)
652 {
653 case PROTOCOL_BINARY_RESPONSE_KEY_ENOENT:
654 rc= MEMCACHED_NOTFOUND;
655 break;
656
657 case PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS:
658 rc= MEMCACHED_DATA_EXISTS;
659 break;
660
661 case PROTOCOL_BINARY_RESPONSE_NOT_STORED:
662 rc= MEMCACHED_NOTSTORED;
663 break;
664
665 case PROTOCOL_BINARY_RESPONSE_E2BIG:
666 rc= MEMCACHED_E2BIG;
667 break;
668
669 case PROTOCOL_BINARY_RESPONSE_ENOMEM:
670 rc= MEMCACHED_MEMORY_ALLOCATION_FAILURE;
671 break;
672
673 case PROTOCOL_BINARY_RESPONSE_AUTH_CONTINUE:
674 rc= MEMCACHED_AUTH_CONTINUE;
675 break;
676
677 case PROTOCOL_BINARY_RESPONSE_AUTH_ERROR:
678 rc= MEMCACHED_AUTH_FAILURE;
679 break;
680
681 case PROTOCOL_BINARY_RESPONSE_EINVAL:
682 case PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND:
683 default:
684 /* @todo fix the error mappings */
685 rc= MEMCACHED_PROTOCOL_ERROR;
686 break;
687 }
688 }
689
690 return rc;
691 }