1 /* vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
5 * Copyright (C) 2011 Data Differential, http://datadifferential.com/
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are
11 * * Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * * Redistributions in binary form must reproduce the above
15 * copyright notice, this list of conditions and the following disclaimer
16 * in the documentation and/or other materials provided with the
19 * * The names of its contributors may not be used to endorse or
20 * promote products derived from this software without specific prior
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 #include <libmemcached/protocol/common.h>
38 #include <libmemcached/byteorder.h>
46 * Try to parse a key from the string.
47 * @pointer start pointer to a pointer to the string (IN and OUT)
48 * @return length of the string of -1 if this was an illegal key (invalid
49 * characters or invalid length)
52 static uint16_t parse_ascii_key(char **start
)
56 /* Strip leading whitespaces */
64 while (*c
!= '\0' && !isspace(*c
) && !iscntrl(*c
))
71 if (len
== 0 || len
> 240 || (*c
!= '\0' && *c
!= '\r' && iscntrl(*c
)))
80 * Spool a zero-terminated string
81 * @param client destination
82 * @param text the text to spool
83 * @return status of the spool operation
85 static protocol_binary_response_status
86 spool_string(memcached_protocol_client_st
*client
, const char *text
)
88 return client
->root
->spool(client
, text
, strlen(text
));
92 * Send a "CLIENT_ERROR" message back to the client with the correct
93 * format of the command being sent
94 * @param client the client to send the message to
96 static void send_command_usage(memcached_protocol_client_st
*client
)
98 const char *errmsg
[]= {
99 [GET_CMD
]= "CLIENT_ERROR: Syntax error: get <key>*\r\n",
100 [GETS_CMD
]= "CLIENT_ERROR: Syntax error: gets <key>*\r\n",
101 [SET_CMD
]= "CLIENT_ERROR: Syntax error: set <key> <flags> <exptime> <bytes> [noreply]\r\n",
102 [ADD_CMD
]= "CLIENT_ERROR: Syntax error: add <key> <flags> <exptime> <bytes> [noreply]\r\n",
103 [REPLACE_CMD
]= "CLIENT_ERROR: Syntax error: replace <key> <flags> <exptime> <bytes> [noreply]\r\n",
104 [CAS_CMD
]= "CLIENT_ERROR: Syntax error: cas <key> <flags> <exptime> <bytes> <casid> [noreply]\r\n",
105 [APPEND_CMD
]= "CLIENT_ERROR: Syntax error: append <key> <flags> <exptime> <bytes> [noreply]\r\n",
106 [PREPEND_CMD
]= "CLIENT_ERROR: Syntax error: prepend <key> <flags> <exptime> <bytes> [noreply]\r\n",
107 [DELETE_CMD
]= "CLIENT_ERROR: Syntax error: delete <key> [noreply]\r\n",
108 [INCR_CMD
]= "CLIENT_ERROR: Syntax error: incr <key> <value> [noreply]\r\n",
109 [DECR_CMD
]= "CLIENT_ERROR: Syntax error: decr <key> <value> [noreply]\r\n",
110 [STATS_CMD
]= "CLIENT_ERROR: Syntax error: stats [key]\r\n",
111 [FLUSH_ALL_CMD
]= "CLIENT_ERROR: Syntax error: flush_all [timeout] [noreply]\r\n",
112 [VERSION_CMD
]= "CLIENT_ERROR: Syntax error: version\r\n",
113 [QUIT_CMD
]="CLIENT_ERROR: Syntax error: quit\r\n",
115 [VERBOSITY_CMD
]= "CLIENT_ERROR: Syntax error: verbosity <num>\r\n",
116 [UNKNOWN_CMD
]= "CLIENT_ERROR: Unknown command\r\n",
119 client
->mute
= false;
120 spool_string(client
, errmsg
[client
->ascii_command
]);
124 * Callback for the VERSION responses
125 * @param cookie client identifier
126 * @param text the length of the body
127 * @param textlen the length of the body
129 static protocol_binary_response_status
130 ascii_version_response_handler(const void *cookie
,
134 memcached_protocol_client_st
*client
= (memcached_protocol_client_st
*)cookie
;
135 spool_string(client
, "VERSION ");
136 client
->root
->spool(client
, text
, textlen
);
137 spool_string(client
, "\r\n");
138 return PROTOCOL_BINARY_RESPONSE_SUCCESS
;
142 * Callback for the GET/GETQ/GETK and GETKQ responses
143 * @param cookie client identifier
144 * @param key the key for the item
145 * @param keylen the length of the key
146 * @param body the length of the body
147 * @param bodylen the length of the body
148 * @param flags the flags for the item
149 * @param cas the CAS id for the item
151 static protocol_binary_response_status
152 ascii_get_response_handler(const void *cookie
,
160 memcached_protocol_client_st
*client
= (void*)cookie
;
162 strcpy(buffer
, "VALUE ");
163 const char *source
= key
;
164 char *dest
= buffer
+ 6;
166 for (int x
= 0; x
< keylen
; ++x
)
168 if (*source
!= '\0' && !isspace(*source
) && !iscntrl(*source
))
174 return PROTOCOL_BINARY_RESPONSE_EINVAL
; /* key constraints in ascii */
181 size_t used
= (size_t)(dest
- buffer
);
183 if (client
->ascii_command
== GETS_CMD
)
185 snprintf(dest
, sizeof(buffer
) - used
, " %u %u %" PRIu64
"\r\n", flags
,
190 snprintf(dest
, sizeof(buffer
) - used
, " %u %u\r\n", flags
, bodylen
);
193 client
->root
->spool(client
, buffer
, strlen(buffer
));
194 client
->root
->spool(client
, body
, bodylen
);
195 client
->root
->spool(client
, "\r\n", 2);
197 return PROTOCOL_BINARY_RESPONSE_SUCCESS
;
201 * Callback for the STAT responses
202 * @param cookie client identifier
203 * @param key the key for the item
204 * @param keylen the length of the key
205 * @param body the length of the body
206 * @param bodylen the length of the body
208 static protocol_binary_response_status
209 ascii_stat_response_handler(const void *cookie
,
216 memcached_protocol_client_st
*client
= (void*)cookie
;
220 spool_string(client
, "STAT ");
221 client
->root
->spool(client
, key
, keylen
);
222 spool_string(client
, " ");
223 client
->root
->spool(client
, body
, bodylen
);
224 spool_string(client
, "\r\n");
228 spool_string(client
, "END\r\n");
231 return PROTOCOL_BINARY_RESPONSE_SUCCESS
;
235 * Process a get or a gets request.
236 * @param client the client handle
237 * @param buffer the complete get(s) command
238 * @param end the last character in the command
240 static void ascii_process_gets(memcached_protocol_client_st
*client
,
241 char *buffer
, char *end
)
246 key
+= (client
->ascii_command
== GETS_CMD
) ? 5 : 4;
251 uint16_t nkey
= parse_ascii_key(&key
);
252 if (nkey
== 0) /* Invalid key... stop processing this line */
257 (void)client
->root
->callback
->interface
.v1
.get(client
, key
, nkey
,
258 ascii_get_response_handler
);
265 send_command_usage(client
);
268 client
->root
->spool(client
, "END\r\n", 5);
272 * Try to split up the command line "asdf asdf asdf asdf\n" into an
273 * argument vector for easier parsing.
274 * @param start the first character in the command line
275 * @param end the last character in the command line ("\n")
276 * @param vec the vector to insert the pointers into
277 * @size the number of elements in the vector
278 * @return the number of tokens in the vector
280 static int ascii_tokenize_command(char *str
, char *end
, char **vec
, int size
)
286 /* Skip leading blanks */
287 while (str
< end
&& isspace(*str
))
298 /* find the next non-blank field */
299 while (str
< end
&& !isspace(*str
))
304 /* zero-terminate it for easier parsing later on */
308 /* Is the vector full? */
319 * If we for some reasons needs to push the line back to read more
320 * data we have to reverse the tokenization. Just do the brain-dead replace
321 * of all '\0' to ' ' and set the last character to '\n'. We could have used
322 * the vector we created, but then we would have to search for all of the
323 * spaces we ignored...
324 * @param start pointer to the first character in the buffer to recover
325 * @param end pointer to the last character in the buffer to recover
327 static void recover_tokenize_command(char *start
, char *end
)
340 * Convert the textual command into a comcode
342 static enum ascii_cmd
ascii_to_cmd(char *start
, size_t length
)
349 { .cmd
= "get", .len
= 3, .cc
= GET_CMD
},
350 { .cmd
= "gets", .len
= 4, .cc
= GETS_CMD
},
351 { .cmd
= "set", .len
= 3, .cc
= SET_CMD
},
352 { .cmd
= "add", .len
= 3, .cc
= ADD_CMD
},
353 { .cmd
= "replace", .len
= 7, .cc
= REPLACE_CMD
},
354 { .cmd
= "cas", .len
= 3, .cc
= CAS_CMD
},
355 { .cmd
= "append", .len
= 6, .cc
= APPEND_CMD
},
356 { .cmd
= "prepend", .len
= 7, .cc
= PREPEND_CMD
},
357 { .cmd
= "delete", .len
= 6, .cc
= DELETE_CMD
},
358 { .cmd
= "incr", .len
= 4, .cc
= INCR_CMD
},
359 { .cmd
= "decr", .len
= 4, .cc
= DECR_CMD
},
360 { .cmd
= "stats", .len
= 5, .cc
= STATS_CMD
},
361 { .cmd
= "flush_all", .len
= 9, .cc
= FLUSH_ALL_CMD
},
362 { .cmd
= "version", .len
= 7, .cc
= VERSION_CMD
},
363 { .cmd
= "quit", .len
= 4, .cc
= QUIT_CMD
},
364 { .cmd
= "verbosity", .len
= 9, .cc
= VERBOSITY_CMD
},
365 { .cmd
= NULL
, .len
= 0, .cc
= UNKNOWN_CMD
}};
368 while (commands
[x
].len
> 0) {
369 if (length
>= commands
[x
].len
)
371 if (strncmp(start
, commands
[x
].cmd
, commands
[x
].len
) == 0)
374 if (length
== commands
[x
].len
|| isspace(*(start
+ commands
[x
].len
)))
376 return commands
[x
].cc
;
387 * Perform a delete operation.
389 * @param client client requesting the deletion
390 * @param tokens the command as a vector
391 * @param ntokens the number of items in the vector
393 static void process_delete(memcached_protocol_client_st
*client
,
394 char **tokens
, int ntokens
)
396 char *key
= tokens
[1];
399 if (ntokens
!= 2 || (nkey
= parse_ascii_key(&key
)) == 0)
401 send_command_usage(client
);
405 if (client
->root
->callback
->interface
.v1
.delete == NULL
)
407 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
411 protocol_binary_response_status rval
;
412 rval
= client
->root
->callback
->interface
.v1
.delete(client
, key
, nkey
, 0);
414 if (rval
== PROTOCOL_BINARY_RESPONSE_SUCCESS
)
416 spool_string(client
, "DELETED\r\n");
418 else if (rval
== PROTOCOL_BINARY_RESPONSE_KEY_ENOENT
)
420 spool_string(client
, "NOT_FOUND\r\n");
425 snprintf(msg
, sizeof(msg
), "SERVER_ERROR: delete failed %u\r\n",(uint32_t)rval
);
426 spool_string(client
, msg
);
430 static void process_arithmetic(memcached_protocol_client_st
*client
,
431 char **tokens
, int ntokens
)
433 char *key
= tokens
[1];
436 if (ntokens
!= 3 || (nkey
= parse_ascii_key(&key
)) == 0)
438 send_command_usage(client
);
444 uint64_t delta
= strtoull(tokens
[2], NULL
, 10);
446 protocol_binary_response_status rval
;
447 if (client
->ascii_command
== INCR_CMD
)
449 if (client
->root
->callback
->interface
.v1
.increment
== NULL
)
451 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
454 rval
= client
->root
->callback
->interface
.v1
.increment(client
,
463 if (client
->root
->callback
->interface
.v1
.decrement
== NULL
)
465 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
468 rval
= client
->root
->callback
->interface
.v1
.decrement(client
,
476 if (rval
== PROTOCOL_BINARY_RESPONSE_SUCCESS
)
479 snprintf(buffer
, sizeof(buffer
), "%"PRIu64
"\r\n", result
);
480 spool_string(client
, buffer
);
484 spool_string(client
, "NOT_FOUND\r\n");
489 * Process the stats command (with or without a key specified)
490 * @param key pointer to the first character after "stats"
491 * @param end pointer to the "\n"
493 static void process_stats(memcached_protocol_client_st
*client
,
494 char *key
, char *end
)
496 if (client
->root
->callback
->interface
.v1
.stat
== NULL
)
498 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
502 while (isspace(*key
))
505 uint16_t nkey
= (uint16_t)(end
- key
);
506 (void)client
->root
->callback
->interface
.v1
.stat(client
, key
, nkey
,
507 ascii_stat_response_handler
);
510 static void process_version(memcached_protocol_client_st
*client
,
511 char **tokens
, int ntokens
)
516 send_command_usage(client
);
520 if (client
->root
->callback
->interface
.v1
.version
== NULL
)
522 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
526 client
->root
->callback
->interface
.v1
.version(client
,
527 ascii_version_response_handler
);
530 static void process_flush(memcached_protocol_client_st
*client
,
531 char **tokens
, int ntokens
)
535 send_command_usage(client
);
539 if (client
->root
->callback
->interface
.v1
.flush
== NULL
)
541 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
548 timeout
= (uint32_t)strtoul(tokens
[1], NULL
, 10);
551 protocol_binary_response_status rval
;
552 rval
= client
->root
->callback
->interface
.v1
.flush(client
, timeout
);
553 if (rval
== PROTOCOL_BINARY_RESPONSE_SUCCESS
)
554 spool_string(client
, "OK\r\n");
556 spool_string(client
, "SERVER_ERROR: internal error\r\n");
560 * Process one of the storage commands
561 * @param client the client performing the operation
562 * @param tokens the command tokens
563 * @param ntokens the number of tokens
564 * @param start pointer to the first character in the line
565 * @param end pointer to the pointer where the last character of this
566 * command is (IN and OUT)
567 * @param length the number of bytes available
568 * @return -1 if an error occurs (and we should just terminate the connection
569 * because we are out of sync)
570 * 0 storage command completed, continue processing
571 * 1 We need more data, so just go ahead and wait for more!
573 static inline int process_storage_command(memcached_protocol_client_st
*client
,
574 char **tokens
, int ntokens
, char *start
,
575 char **end
, ssize_t length
)
577 (void)ntokens
; /* already checked */
578 char *key
= tokens
[1];
579 uint16_t nkey
= parse_ascii_key(&key
);
583 spool_string(client
, "CLIENT_ERROR: bad key\r\n");
587 uint32_t flags
= (uint32_t)strtoul(tokens
[2], NULL
, 10);
588 uint32_t timeout
= (uint32_t)strtoul(tokens
[3], NULL
, 10);
589 unsigned long nbytes
= strtoul(tokens
[4], NULL
, 10);
591 /* Do we have all data? */
592 unsigned long need
= nbytes
+ (unsigned long)((*end
- start
) + 1) + 2; /* \n\r\n */
593 if ((ssize_t
)need
> length
)
595 /* Keep on reading */
596 recover_tokenize_command(start
, *end
);
600 void *data
= (*end
) + 1;
603 protocol_binary_response_status rval
;
604 switch (client
->ascii_command
)
607 rval
= client
->root
->callback
->interface
.v1
.set(client
, key
,
616 rval
= client
->root
->callback
->interface
.v1
.add(client
, key
,
621 timeout
, &result_cas
);
624 cas
= strtoull(tokens
[5], NULL
, 10);
627 rval
= client
->root
->callback
->interface
.v1
.replace(client
, key
,
636 rval
= client
->root
->callback
->interface
.v1
.append(client
, key
,
644 rval
= client
->root
->callback
->interface
.v1
.prepend(client
, key
,
652 /* gcc complains if I don't put all of the enums in here.. */
665 abort(); /* impossible */
668 if (rval
== PROTOCOL_BINARY_RESPONSE_SUCCESS
)
670 spool_string(client
, "STORED\r\n");
674 if (client
->ascii_command
== CAS_CMD
)
676 if (rval
== PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS
)
678 spool_string(client
, "EXISTS\r\n");
680 else if (rval
== PROTOCOL_BINARY_RESPONSE_KEY_ENOENT
)
682 spool_string(client
, "NOT_FOUND\r\n");
686 spool_string(client
, "NOT_STORED\r\n");
691 spool_string(client
, "NOT_STORED\r\n");
700 static int process_cas_command(memcached_protocol_client_st
*client
,
701 char **tokens
, int ntokens
, char *start
,
702 char **end
, ssize_t length
)
706 send_command_usage(client
);
710 if (client
->root
->callback
->interface
.v1
.replace
== NULL
)
712 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
716 return process_storage_command(client
, tokens
, ntokens
, start
, end
, length
);
719 static int process_set_command(memcached_protocol_client_st
*client
,
720 char **tokens
, int ntokens
, char *start
,
721 char **end
, ssize_t length
)
725 send_command_usage(client
);
729 if (client
->root
->callback
->interface
.v1
.set
== NULL
)
731 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
735 return process_storage_command(client
, tokens
, ntokens
, start
, end
, length
);
738 static int process_add_command(memcached_protocol_client_st
*client
,
739 char **tokens
, int ntokens
, char *start
,
740 char **end
, ssize_t length
)
744 send_command_usage(client
);
748 if (client
->root
->callback
->interface
.v1
.add
== NULL
)
750 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
754 return process_storage_command(client
, tokens
, ntokens
, start
, end
, length
);
757 static int process_replace_command(memcached_protocol_client_st
*client
,
758 char **tokens
, int ntokens
, char *start
,
759 char **end
, ssize_t length
)
763 send_command_usage(client
);
767 if (client
->root
->callback
->interface
.v1
.replace
== NULL
)
769 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
773 return process_storage_command(client
, tokens
, ntokens
, start
, end
, length
);
776 static int process_append_command(memcached_protocol_client_st
*client
,
777 char **tokens
, int ntokens
, char *start
,
778 char **end
, ssize_t length
)
782 send_command_usage(client
);
786 if (client
->root
->callback
->interface
.v1
.append
== NULL
)
788 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
792 return process_storage_command(client
, tokens
, ntokens
, start
, end
, length
);
795 static int process_prepend_command(memcached_protocol_client_st
*client
,
796 char **tokens
, int ntokens
, char *start
,
797 char **end
, ssize_t length
)
801 send_command_usage(client
);
805 if (client
->root
->callback
->interface
.v1
.prepend
== NULL
)
807 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
811 return process_storage_command(client
, tokens
, ntokens
, start
, end
, length
);
815 * The ASCII protocol support is just one giant big hack. Instead of adding
816 * a optimal ascii support, I just convert the ASCII commands to the binary
817 * protocol and calls back into the command handlers for the binary protocol ;)
819 memcached_protocol_event_t
memcached_ascii_protocol_process_data(memcached_protocol_client_st
*client
, ssize_t
*length
, void **endptr
)
821 char *ptr
= (char*)client
->root
->input_buffer
;
825 /* Do we have \n (indicating the command preamble)*/
826 char *end
= memchr(ptr
, '\n', (size_t)*length
);
830 return MEMCACHED_PROTOCOL_READ_EVENT
;
833 client
->ascii_command
= ascii_to_cmd(ptr
, (size_t)(*length
));
835 /* A multiget lists all of the keys, and I don't want to have an
836 * avector of let's say 512 pointers to tokenize all of them, so let's
837 * just handle them immediately
839 if (client
->ascii_command
== GET_CMD
||
840 client
->ascii_command
== GETS_CMD
) {
841 if (client
->root
->callback
->interface
.v1
.get
!= NULL
)
842 ascii_process_gets(client
, ptr
, end
);
844 spool_string(client
, "SERVER_ERROR: Command not implemented\n");
846 /* None of the defined commands takes 10 parameters, so lets just use
847 * that as a maximum limit.
850 int ntokens
= ascii_tokenize_command(ptr
, end
, tokens
, 10);
854 client
->mute
= strcmp(tokens
[ntokens
- 1], "noreply") == 0;
856 --ntokens
; /* processed noreply token*/
861 switch (client
->ascii_command
) {
863 error
= process_set_command(client
, tokens
, ntokens
, ptr
, &end
, *length
);
866 error
= process_add_command(client
, tokens
, ntokens
, ptr
, &end
, *length
);
869 error
= process_replace_command(client
, tokens
, ntokens
,
873 error
= process_cas_command(client
, tokens
, ntokens
, ptr
, &end
, *length
);
876 error
= process_append_command(client
, tokens
, ntokens
,
880 error
= process_prepend_command(client
, tokens
, ntokens
,
884 process_delete(client
, tokens
, ntokens
);
887 case INCR_CMD
: /* FALLTHROUGH */
889 process_arithmetic(client
, tokens
, ntokens
);
894 send_command_usage(client
);
898 recover_tokenize_command(ptr
, end
);
899 process_stats(client
, ptr
+ 6, end
);
903 process_flush(client
, tokens
, ntokens
);
908 send_command_usage(client
);
912 process_version(client
, tokens
, ntokens
);
916 if (ntokens
!= 1 || client
->mute
)
918 send_command_usage(client
);
922 if (client
->root
->callback
->interface
.v1
.quit
!= NULL
)
923 client
->root
->callback
->interface
.v1
.quit(client
);
925 return MEMCACHED_PROTOCOL_ERROR_EVENT
;
931 send_command_usage(client
);
933 spool_string(client
, "OK\r\n");
937 send_command_usage(client
);
943 /* Should already be handled */
948 return MEMCACHED_PROTOCOL_ERROR_EVENT
;
950 return MEMCACHED_PROTOCOL_READ_EVENT
;
955 *length
-= end
- ptr
;
957 } while (*length
> 0);
960 return MEMCACHED_PROTOCOL_READ_EVENT
;