1 #include <stdlib.h>
2 #include <sys/socket.h>
3 #include <sys/types.h>
4 #include <netdb.h>
5 #include <unistd.h>
6 #include <fcntl.h>
7 #include <string.h>
8 #include <errno.h>
9 #include <stdio.h>
10 #include <limits.h>
11 #include <ctype.h>
12 
13 // TODO: move these structs into mcmc.h, but only expose them if
14 // MCMC_EXPOSE_INTERNALS is defined... for tests and this thing.
15 #include "mcmc.h"
16 
17 // TODO: if there's a parse error or unknown status code, we likely have a
18 // protocol desync and need to disconnect.
19 
20 // A "reasonable" minimum buffer size to work with.
21 // Callers are allowed to create a buffer of any size larger than this.
22 // TODO: Put the math/documentation in here.
23 // This is essentially the largest return value status line possible.
24 // at least doubled for wiggle room.
25 #define MIN_BUFFER_SIZE 2048
26 
27 #define FLAG_BUF_WANTED_READ 0x4
28 
29 #define STATE_DEFAULT 0 // looking for any kind of response
30 #define STATE_GET_RESP 1 // processing VALUE's until END
31 #define STATE_STAT_RESP 2 // processing STAT's until END
32 #define STATE_STAT_RESP_DONE 3
33 
34 typedef struct mcmc_ctx {
35     int fd;
36     int gai_status; // getaddrinfo() last status.
37     int sent_bytes_partial; // note for partially sent buffers.
38     int error; // latest error code.
39     uint32_t status_flags; // internal only flags.
40 
41     // FIXME: s/buffer_used/buffer_filled/ ?
42     size_t buffer_used; // amount of bytes read into the buffer so far.
43     char *buffer_head; // buffer pointer currently in use.
44 } mcmc_ctx_t;
45 
46 // INTERNAL FUNCTIONS
47 
48 #define TOKENIZER_MAXLEN USHRT_MAX
49 
50 // Find the starting offsets of each token; ignoring length.
51 // This creates a fast small (<= cacheline) index into the request,
52 // where we later scan or directly feed data into API's.
53 #define _mcmc_tokenize(t, line, len, mstart, max) _mcmc_tokenize_meta(t, line, len, UINT8_MAX, max)
54 
55 // specialized tokenizer for meta protocol commands or responses, fills in the
56 // bitflags while scanning.
57 // Function _assumes_ const char *line ends with \n or \r\n, but will not
58 // break so long as the passed in 'len' is reasonable.
_mcmc_tokenize_meta(mcmc_tokenizer_t * t,const char * line,size_t len,const int mstart,const int max)59 MCMC_STATIC int _mcmc_tokenize_meta(mcmc_tokenizer_t *t, const char *line, size_t len, const int mstart, const int max) {
60     const char *s = line;
61     t->metaflags = 0;
62 
63     // since multigets can be huge, we can't purely judge reqlen against this
64     // limit, but we also can't index past it since the tokens are shorts.
65     if (len > TOKENIZER_MAXLEN) {
66         len = TOKENIZER_MAXLEN;
67     }
68 
69     if (line[len-2] == '\r') {
70         len -= 2;
71     } else {
72         len -= 1;
73     }
74 
75     const char *end = s + len;
76     int curtoken = 0;
77 
78     int state = 0;
79     while (s != end) {
80         switch (state) {
81             case 0:
82                 // scanning for first non-space to find a token.
83                 if (*s != ' ') {
84                     if (curtoken >= mstart) {
85                         if (*s > 64 && *s < 123) {
86                             t->metaflags |= (uint64_t)1 << (*s - 65);
87                         } else if (isdigit(*s) == 0) {
88                             return MCMC_NOK;
89                         }
90                     }
91                     t->tokens[curtoken] = s - line;
92                     if (++curtoken == max) {
93                         s++;
94                         state = 2;
95                         break;
96                     }
97                     state = 1;
98                 }
99                 s++;
100                 break;
101             case 1:
102                 // advance over a token
103                 if (*s != ' ') {
104                     s++;
105                 } else {
106                     state = 0;
107                 }
108                 break;
109             case 2:
110                 // hit max tokens before end of the line.
111                 // keep advancing so we can place endcap token.
112                 if (*s == ' ') {
113                     goto endloop;
114                 }
115                 s++;
116                 break;
117         }
118     }
119 endloop:
120 
121     // endcap token so we can quickly find the length of any token by looking
122     // at the next one.
123     t->tokens[curtoken] = s - line;
124     t->ntokens = curtoken;
125     t->mstart = mstart;
126 
127     return MCMC_OK;
128 }
129 
_mcmc_token_len(const char * line,mcmc_tokenizer_t * t,size_t token)130 __attribute__((unused)) MCMC_STATIC int _mcmc_token_len(const char *line, mcmc_tokenizer_t *t, size_t token) {
131     const char *s = line + t->tokens[token];
132     const char *e = line + t->tokens[token+1];
133     // start of next token is after any space delimiters, so back those out.
134     while (*(e-1) == ' ') {
135         e--;
136     }
137     return e - s;
138 }
139 
_mcmc_token(const char * line,mcmc_tokenizer_t * t,size_t token,int * len)140 __attribute__((unused)) MCMC_STATIC const char *_mcmc_token(const char *line, mcmc_tokenizer_t *t, size_t token, int *len) {
141     const char *s = line + t->tokens[token];
142     if (len != NULL) {
143         const char *e = line + t->tokens[token+1];
144         while (*(e-1) == ' ') {
145             e--;
146         }
147         *len = e - s;
148     }
149     return s;
150 }
151 
_mcmc_parse_value_line(const char * buf,size_t read,mcmc_resp_t * r)152 static int _mcmc_parse_value_line(const char *buf, size_t read, mcmc_resp_t *r) {
153     // we know that "VALUE " has matched, so skip that.
154     const char *p = buf+6;
155     size_t l = r->reslen;
156 
157     // <key> <flags> <bytes> [<cas unique>]
158     const char *key = p;
159     int keylen;
160     p = memchr(p, ' ', l - 6);
161     if (p == NULL) {
162         return -MCMC_ERR_VALUE;
163     }
164 
165     keylen = p - key;
166 
167     // convert flags into something useful.
168     // FIXME: do we need to prevent overruns in strtoul?
169     // we know for sure the line will eventually end in a \n.
170     char *n = NULL;
171     errno = 0;
172     uint32_t flags = strtoul(p, &n, 10);
173     if ((errno == ERANGE) || (p == n) || (*n != ' ')) {
174         return -MCMC_ERR_VALUE;
175     }
176     p = n;
177 
178     errno = 0;
179     uint32_t bytes = strtoul(p, &n, 10);
180     if ((errno == ERANGE) || (p == n)) {
181         return -MCMC_ERR_VALUE;
182     }
183     p = n;
184 
185     // If next byte is a space, we read the optional CAS value.
186     uint64_t cas = 0;
187     if (*n == ' ') {
188         errno = 0;
189         cas = strtoull(p, &n, 10);
190         if ((errno == ERANGE) || (p == n)) {
191             return -MCMC_ERR_VALUE;
192         }
193     }
194 
195     // If we made it this far, we've parsed everything, stuff the details into
196     // the context for fetching later.
197     r->vlen = bytes + 2; // add in the \r\n
198     int buffer_remain = read - r->reslen;
199     if (buffer_remain >= r->vlen) {
200         r->vlen_read = r->vlen;
201     } else {
202         r->vlen_read = buffer_remain;
203     }
204     r->key = key;
205     r->klen = keylen;
206     r->flags = flags;
207     r->cas = cas;
208     r->type = MCMC_RESP_GET;
209 
210     // NOTE: if value_offset < read, has part of the value in the
211     // buffer already.
212 
213     return MCMC_CODE_OK;
214 }
215 
_mcmc_parse_stat_line(const char * buf,mcmc_resp_t * r)216 static int _mcmc_parse_stat_line(const char *buf, mcmc_resp_t *r) {
217     const char *p = buf+5; // pass "STAT "
218     size_t l = r->reslen;
219 
220     // STAT key value
221     const char *sname = p;
222     p = memchr(p, ' ', l-5);
223     if (p == NULL) {
224         return -MCMC_ERR_VALUE;
225     }
226 
227     int snamelen = p - sname;
228     while (*p == ' ') {
229         p++;
230     }
231     const char *stat = p;
232     int statlen = l - (p - buf) - 2;
233 
234     r->sname = sname;
235     r->snamelen = snamelen;
236     r->stat = stat;
237     r->statlen = statlen;
238 
239     return MCMC_CODE_OK;
240 }
241 
242 // FIXME: This is broken for ASCII multiget.
243 // if we get VALUE back, we need to stay in ASCII GET read mode until an END
244 // is seen.
_mcmc_parse_response(const char * buf,size_t read,mcmc_resp_t * r)245 static int _mcmc_parse_response(const char *buf, size_t read, mcmc_resp_t *r) {
246     const char *cur = buf;
247     int rlen; // response code length.
248     int more = 0;
249     r->type = MCMC_RESP_FAIL;
250 
251     // walk until the \r\n
252     // we can't enter this function without there being a '\n' in the buffer.
253     while (*cur != '\r' && *cur != '\n') {
254         if (*cur == ' ') {
255             more = 1;
256             break;
257         }
258         cur++;
259     }
260     rlen = cur - buf;
261 
262     // incr/decr returns a number with no code :(
263     // not checking length first since buf must have at least one char to
264     // enter this function.
265     if (buf[0] >= '0' && buf[0] <= '9') {
266         // TODO: parse it as a number on request.
267         // TODO: validate whole thing as digits here?
268         r->type = MCMC_RESP_NUMERIC;
269         r->code = MCMC_CODE_OK;
270         return MCMC_OK;
271     }
272 
273     if (rlen < 2) {
274         r->code = MCMC_ERR_SHORT;
275         return MCMC_ERR;
276     }
277 
278     int code = MCMC_ERR;
279     switch (rlen) {
280         case 2:
281             // meta, "OK"
282             // FIXME: adding new return codes would make the client completely
283             // fail. The rest of the client is agnostic to requests/flags for
284             // meta.
285             // can we make it agnostic for return codes outside of "read this
286             // data" types?
287             // As-is it should fail down to the "send the return code to the
288             // user". not sure that's right.
289             r->type = MCMC_RESP_META;
290             switch (buf[0]) {
291             case 'E':
292                 if (buf[1] == 'N') {
293                     code = MCMC_CODE_END;
294                 } else if (buf[1] == 'X') {
295                     code = MCMC_CODE_EXISTS;
296                 }
297                 break;
298             case 'H':
299                 if (buf[1] == 'D') {
300                     // typical meta response.
301                     code = MCMC_CODE_OK;
302                 }
303                 break;
304             case 'M':
305                 if (buf[1] == 'N') {
306                     // specific return code so user can see pipeline end.
307                     code = MCMC_CODE_NOP;
308                 } else if (buf[1] == 'E') {
309                     // ME is the debug output line.
310                     // TODO: this just gets returned as an rline?
311                     // specific code? specific type?
312                     // ME <key> <key=value debug line>
313                     code = MCMC_CODE_OK;
314                 }
315                 break;
316             case 'N':
317                 if (buf[1] == 'F') {
318                     code = MCMC_CODE_NOT_FOUND;
319                 } else if (buf[1] == 'S') {
320                     code = MCMC_CODE_NOT_STORED;
321                 }
322                 break;
323             case 'O':
324                 if (buf[1] == 'K') {
325                     // Used by many random management commands
326                     r->type = MCMC_RESP_GENERIC;
327                     code = MCMC_CODE_OK;
328                 }
329                 break;
330             case 'V':
331                 if (buf[1] == 'A') {
332                     // VA <size> <flags>*\r\n
333                     if (more) {
334                         errno = 0;
335                         char *n = NULL;
336                         uint32_t vsize = strtoul(cur, &n, 10);
337                         if ((errno == ERANGE) || (cur == n)) {
338                             r->type = MCMC_RESP_FAIL;
339                             code = -MCMC_ERR_PARSE;
340                         } else {
341                             r->vlen = vsize + 2; // tag in the \r\n.
342                             // FIXME: macro.
343                             int buffer_remain = read - r->reslen;
344                             if (buffer_remain >= r->vlen) {
345                                 r->vlen_read = r->vlen;
346                             } else {
347                                 r->vlen_read = buffer_remain;
348                             }
349                             cur = n;
350                             if (*cur != ' ') {
351                                 more = 0;
352                             }
353                             code = MCMC_CODE_OK;
354                         }
355                     } else {
356                         r->type = MCMC_RESP_FAIL;
357                         code = -MCMC_ERR_PARSE;
358                     }
359                 }
360                 break;
361             }
362             // maybe: if !rv and !fail, do something special?
363             // if (more), there are flags. shove them in the right place.
364             if (more) {
365                 // walk until not space
366                 while (*cur == ' ') {
367                     cur++;
368                 }
369                 r->rline = cur;
370                 r->rlen = r->reslen - (cur - buf);
371                 // cut \n or \r\n
372                 if (buf[r->reslen-2] == '\r') {
373                     r->rlen -= 2;
374                 } else {
375                     r->rlen--;
376                 }
377             } else {
378                 r->rline = NULL;
379                 r->rlen = 0;
380             }
381             break;
382         case 3:
383             if (memcmp(buf, "END", 3) == 0) {
384                 // Either end of STAT results, or end of ascii GET key list.
385                 code = MCMC_CODE_END;
386                 r->type = MCMC_RESP_END;
387             }
388             break;
389         case 4:
390             if (memcmp(buf, "STAT", 4) == 0) {
391                 r->type = MCMC_RESP_STAT;
392                 code = _mcmc_parse_stat_line(buf, r);
393             }
394             break;
395         case 5:
396             if (memcmp(buf, "VALUE", 5) == 0) {
397                 if (more) {
398                     // <key> <flags> <bytes> [<cas unique>]
399                     code = _mcmc_parse_value_line(buf, read, r);
400                 } else {
401                     code = -MCMC_ERR_PARSE;
402                 }
403             } else if (memcmp(buf, "ERROR", 5) == 0) {
404                 r->type = MCMC_RESP_ERRMSG;
405                 code = -MCMC_CODE_ERROR;
406             }
407             break;
408         case 6:
409             if (memcmp(buf, "STORED", 6) == 0) {
410                 r->type = MCMC_RESP_GENERIC;
411                 code = MCMC_CODE_STORED;
412             } else if (memcmp(buf, "EXISTS", 6) == 0) {
413                 r->type = MCMC_RESP_GENERIC;
414                 code = MCMC_CODE_EXISTS;
415                 // TODO: type -> ASCII?
416             }
417             break;
418         case 7:
419             if (memcmp(buf, "DELETED", 7) == 0) {
420                 r->type = MCMC_RESP_GENERIC;
421                 code = MCMC_CODE_DELETED;
422             } else if (memcmp(buf, "TOUCHED", 7) == 0) {
423                 r->type = MCMC_RESP_GENERIC;
424                 code = MCMC_CODE_TOUCHED;
425             } else if (memcmp(buf, "VERSION", 7) == 0) {
426                 code = MCMC_CODE_VERSION;
427                 r->type = MCMC_RESP_VERSION;
428                 // TODO: prep the version line for return
429             }
430             break;
431         case 9:
432             if (memcmp(buf, "NOT_FOUND", 9) == 0) {
433                 r->type = MCMC_RESP_GENERIC;
434                 code = MCMC_CODE_NOT_FOUND;
435             }
436             break;
437         case 10:
438             if (memcmp(buf, "NOT_STORED", 10) == 0) {
439                 r->type = MCMC_RESP_GENERIC;
440                 code = MCMC_CODE_NOT_STORED;
441             }
442             break;
443         default:
444             // Unknown code, assume error.
445             if (memcmp(buf, "SERVER_ERROR", 12) == 0) {
446                 r->type = MCMC_RESP_ERRMSG;
447                 code = -MCMC_CODE_SERVER_ERROR;
448             } else if (memcmp(buf, "CLIENT_ERROR", 12) == 0) {
449                 r->type = MCMC_RESP_ERRMSG;
450                 code = -MCMC_CODE_CLIENT_ERROR;
451             }
452             break;
453     }
454 
455     if (code < MCMC_OK) {
456         r->code = -code;
457         return MCMC_ERR;
458     } else {
459         r->code = code;
460         return MCMC_OK;
461     }
462 }
463 
464 // TODO: possible to codegen the 32 vs 64 variants
465 // easy with a macro I just hate how that looks
466 #define MCMC_TOKTO32_MAX 11
467 #define MCMC_TOKTO64_MAX 22
468 
469 // TODO: test if __builtin_add_overflow exists and use that instead for
470 // hardware boost.
471 // just need to split the mul and the add? if (__builtin_mul_overflow(etc))
472 // - need a method to force compile both functions for the test suite.
mcmc_toktou32(const char * t,size_t len,uint32_t * out)473 MCMC_STATIC int mcmc_toktou32(const char *t, size_t len, uint32_t *out) {
474     uint32_t sum = 0;
475     const char *pos = t;
476     // We clamp the possible length to make input length errors less likely to
477     // go for a walk through memory.
478     if (len > MCMC_TOKTO32_MAX) {
479         return MCMC_TOKTO_ELONG;
480     }
481     while (len--) {
482         char num = pos[0] - '0';
483         if (num > -1 && num < 10) {
484             uint32_t lim = (UINT32_MAX - num) / 10;
485             if (sum > lim) {
486                 return MCMC_TOKTO_ERANGE;
487             }
488             sum = sum * 10 + num;
489         } else {
490             return MCMC_TOKTO_EINVALID;
491         }
492         pos++;
493     }
494     *out = sum;
495     return MCMC_OK;
496 }
497 
mcmc_toktou64(const char * t,size_t len,uint64_t * out)498 MCMC_STATIC int mcmc_toktou64(const char *t, size_t len, uint64_t *out) {
499     uint64_t sum = 0;
500     const char *pos = t;
501     if (len > MCMC_TOKTO64_MAX) {
502         return MCMC_TOKTO_ELONG;
503     }
504     while (len--) {
505         char num = pos[0] - '0';
506         if (num > -1 && num < 10) {
507             uint64_t lim = (UINT64_MAX - num) / 10;
508             if (sum > lim) {
509                 return MCMC_TOKTO_ERANGE;
510             }
511             sum = sum * 10 + num;
512         } else {
513             return MCMC_TOKTO_EINVALID;
514         }
515         pos++;
516     }
517     *out = sum;
518     return MCMC_OK;
519 }
520 
521 // TODO: these funcs aren't defending against len == 0
522 // note that the command strings _must_ end in a \n so it _should_ be
523 // impossible to land after the buffer.
524 // However this should be adjusted next time I work on it:
525 // - instead of len, calculate end.
526 // - only do '-' check if pos != end
527 // - check pos against end in the while loop and just incr pos
528 
mcmc_tokto32(const char * t,size_t len,int32_t * out)529 MCMC_STATIC int mcmc_tokto32(const char *t, size_t len, int32_t *out) {
530     int32_t sum = 0;
531     const char *pos = t;
532     int is_sig = 0;
533     if (len > MCMC_TOKTO32_MAX) {
534         return MCMC_TOKTO_ELONG;
535     }
536     // If we're negative the first character must be -
537     if (pos[0] == '-') {
538         len--;
539         pos++;
540         is_sig = 1;
541     }
542     while (len--) {
543         char num = pos[0] - '0';
544         if (num > -1 && num < 10) {
545             if (is_sig) {
546                 int32_t lim = (INT32_MIN + num) / 10;
547                 if (sum < lim) {
548                     return MCMC_TOKTO_ERANGE;
549                 }
550                 sum = sum * 10 - num;
551             } else {
552                 int32_t lim = (INT32_MAX - num) / 10;
553                 if (sum > lim) {
554                     return MCMC_TOKTO_ERANGE;
555                 }
556                 sum = sum * 10 + num;
557             }
558         } else {
559             return MCMC_TOKTO_EINVALID;
560         }
561         pos++;
562     }
563     *out = sum;
564     return MCMC_OK;
565 }
566 
mcmc_tokto64(const char * t,size_t len,int64_t * out)567 MCMC_STATIC int mcmc_tokto64(const char *t, size_t len, int64_t *out) {
568     int64_t sum = 0;
569     const char *pos = t;
570     int is_sig = 0;
571     if (len > MCMC_TOKTO64_MAX) {
572         return MCMC_TOKTO_ELONG;
573     }
574     // If we're negative the first character must be -
575     if (pos[0] == '-') {
576         len--;
577         pos++;
578         is_sig = 1;
579     }
580     while (len--) {
581         char num = pos[0] - '0';
582         if (num > -1 && num < 10) {
583             if (is_sig) {
584                 int64_t lim = (INT64_MIN + num) / 10;
585                 if (sum < lim) {
586                     return MCMC_TOKTO_ERANGE;
587                 }
588                 sum = sum * 10 - num;
589             } else {
590                 int64_t lim = (INT64_MAX - num) / 10;
591                 if (sum > lim) {
592                     return MCMC_TOKTO_ERANGE;
593                 }
594                 sum = sum * 10 + num;
595             }
596         } else {
597             return MCMC_TOKTO_EINVALID;
598         }
599         pos++;
600     }
601     *out = sum;
602     return MCMC_OK;
603 }
604 
605 // END INTERNAL FUNCTIONS
606 
607 // Context-less bare API.
608 
609 //MCMC_STATIC int _mcmc_tokenize_meta(mcmc_tokenizer_t *t, const char *line, size_t len, const int mstart, const int max) {
610 
611 // TODO: ideally this does a macro test of t->ntokens before calling a
612 // function. are compilers smart enough that I don't have to do this tho?
mcmc_tokenize_res(const char * l,size_t len,mcmc_tokenizer_t * t)613 int mcmc_tokenize_res(const char *l, size_t len, mcmc_tokenizer_t *t) {
614     if (t->ntokens == 0) {
615         return _mcmc_tokenize_meta(t, l, len, 1, MCMC_PARSER_MAX_TOKENS-1);
616     }
617     return 0;
618 }
619 
mcmc_token_get(const char * l,mcmc_tokenizer_t * t,int idx,int * len)620 const char *mcmc_token_get(const char *l, mcmc_tokenizer_t *t, int idx, int *len) {
621     if (idx > 0 && idx < t->ntokens) {
622         return _mcmc_token(l, t, idx, len);
623     } else {
624         return NULL;
625     }
626 }
627 
628 #define X(n, p, c) \
629     int n(const char *l, mcmc_tokenizer_t *t, int idx, p *val) { \
630         if (idx > 0 && idx < t->ntokens) { \
631             int tlen = 0; \
632             const char *tok = _mcmc_token(l, t, idx, &tlen); \
633             return c(tok, tlen, val); \
634         } else { \
635             return MCMC_ERR; \
636         } \
637     }
638 
X(mcmc_token_get_u32,uint32_t,mcmc_toktou32)639 X(mcmc_token_get_u32, uint32_t, mcmc_toktou32)
640 X(mcmc_token_get_u64, uint64_t, mcmc_toktou64)
641 X(mcmc_token_get_32, int32_t, mcmc_tokto32)
642 X(mcmc_token_get_64, int64_t, mcmc_tokto64)
643 
644 #undef X
645 
646 int mcmc_token_has_flag(const char *l, mcmc_tokenizer_t *t, char flag) {
647     if (flag < 65 || flag > 122) {
648         return MCMC_ERR;
649     }
650     uint64_t flagbit = (uint64_t)1 << (flag-65);
651     if (t->metaflags & flagbit) {
652         return MCMC_OK;
653     } else {
654         return MCMC_NOK;
655     }
656 }
657 
658 // User can optionally call has_flag or has_flag_bit before calling these
659 // functions. Is optional because it can be either wasteful to check if the
660 // flag will always be there, or it's useful to the caller to know the flag
661 // exists but there wasn't a token attached or we failed to parse it.
mcmc_token_get_flag(const char * l,mcmc_tokenizer_t * t,char flag,int * len)662 const char *mcmc_token_get_flag(const char *l, mcmc_tokenizer_t *t, char flag, int *len) {
663     for (int x = t->mstart; x < t->ntokens; x++) {
664         const char *tflag = l + t->tokens[x];
665         if (tflag[0] == flag) {
666             int tlen = _mcmc_token_len(l, t, x) - 1; // subtract the flag.
667             // ensure the flag actually has a token.
668             if (tlen > 0) {
669                 if (len) {
670                     *len = tlen;
671                 }
672                 return tflag+1;
673             } else {
674                 break;
675             }
676         }
677     }
678 
679     return NULL;
680 }
681 
mcmc_token_get_flag_u32(const char * l,mcmc_tokenizer_t * t,char flag,uint32_t * val)682 int mcmc_token_get_flag_u32(const char *l, mcmc_tokenizer_t *t, char flag, uint32_t *val) {
683     int tlen = 0;
684     const char *tok = mcmc_token_get_flag(l, t, flag, &tlen);
685     if (tok) {
686         return mcmc_toktou32(tok, tlen, val);
687     }
688 
689     return MCMC_NOK;
690 }
691 
mcmc_token_get_flag_u64(const char * l,mcmc_tokenizer_t * t,char flag,uint64_t * val)692 int mcmc_token_get_flag_u64(const char *l, mcmc_tokenizer_t *t, char flag, uint64_t *val) {
693     int tlen = 0;
694     const char *tok = mcmc_token_get_flag(l, t, flag, &tlen);
695     if (tok) {
696         return mcmc_toktou64(tok, tlen, val);
697     }
698 
699     return MCMC_NOK;
700 }
701 
mcmc_token_get_flag_32(const char * l,mcmc_tokenizer_t * t,char flag,int32_t * val)702 int mcmc_token_get_flag_32(const char *l, mcmc_tokenizer_t *t, char flag, int32_t *val) {
703     int tlen = 0;
704     const char *tok = mcmc_token_get_flag(l, t, flag, &tlen);
705     if (tok) {
706         return mcmc_tokto32(tok, tlen, val);
707     }
708 
709     return MCMC_NOK;
710 }
711 
mcmc_token_get_flag_64(const char * l,mcmc_tokenizer_t * t,char flag,int64_t * val)712 int mcmc_token_get_flag_64(const char *l, mcmc_tokenizer_t *t, char flag, int64_t *val) {
713     int tlen = 0;
714     const char *tok = mcmc_token_get_flag(l, t, flag, &tlen);
715     if (tok) {
716         return mcmc_tokto64(tok, tlen, val);
717     }
718 
719     return MCMC_NOK;
720 }
721 
mcmc_token_get_flag_idx(const char * l,mcmc_tokenizer_t * t,char flag)722 int mcmc_token_get_flag_idx(const char *l, mcmc_tokenizer_t *t, char flag) {
723     for (int x = t->mstart; x < t->ntokens; x++) {
724         const char *tflag = l + t->tokens[x];
725         if (tflag[0] == flag) {
726             return x;
727         }
728     }
729 
730     return -1;
731 }
732 
733 // Directly parse a buffer with read data of size len.
734 // r->reslen + r->vlen_read is the bytes consumed from the buffer read.
735 // Caller manages how to retry if MCMC_WANT_READ or an error happens.
mcmc_parse_buf(const char * buf,size_t read,mcmc_resp_t * r)736 int mcmc_parse_buf(const char *buf, size_t read, mcmc_resp_t *r) {
737     char *el;
738 
739     memset(r, 0, sizeof(*r));
740     el = memchr(buf, '\n', read);
741     if (el == NULL) {
742         r->code = MCMC_WANT_READ;
743         return MCMC_ERR;
744     }
745 
746     // Consume through the newline, note where the value would start if exists
747     r->value = el+1;
748     r->reslen = r->value - buf;
749 
750     // FIXME: the server must be stricter in what it sends back. should always
751     // have a \r. check for it and fail?
752 
753     return _mcmc_parse_response(buf, read, r);
754 }
755 
756 // Context-ful API.
757 
mcmc_fd(void * c)758 int mcmc_fd(void *c) {
759     mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
760     return ctx->fd;
761 }
762 
mcmc_size(int options)763 size_t mcmc_size(int options) {
764     return sizeof(mcmc_ctx_t);
765 }
766 
767 // Allow returning this dynamically based on options set.
768 // FIXME: it might be more flexible to call this after mcmc_connect()...
769 // but this is probably more convenient for the caller if it's less dynamic.
mcmc_min_buffer_size(int options)770 size_t mcmc_min_buffer_size(int options) {
771     return MIN_BUFFER_SIZE;
772 }
773 
774 /*** Functions wrapping syscalls **/
775 
776 // TODO: should be able to flip between block and nonblock.
777 
778 // used for checking on async connections.
mcmc_check_nonblock_connect(void * c,int * err)779 int mcmc_check_nonblock_connect(void *c, int *err) {
780     mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
781     socklen_t errsize = sizeof(*err);
782     if (getsockopt(ctx->fd, SOL_SOCKET, SO_ERROR, err, &errsize) == 0) {
783         if (*err == 0) {
784             return MCMC_OK;
785         }
786     } else {
787         // getsockopt failed. still need to pass up the error.
788         *err = errno;
789     }
790 
791     return MCMC_ERR;
792 }
793 
794 // TODO:
795 // - option for connecting 4 -> 6 or 6 -> 4
796 // connect_unix()
797 // connect_bind_tcp()
798 // ^ fill an internal struct from the stack and call into this central
799 // connect?
mcmc_connect(void * c,char * host,char * port,int options)800 int mcmc_connect(void *c, char *host, char *port, int options) {
801     mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
802 
803     int s;
804     int sock;
805     int res = MCMC_CONNECTED;
806     struct addrinfo hints;
807     struct addrinfo *ai = NULL;
808     struct addrinfo *next = NULL;
809 
810     // Since our cx memory was likely malloc'ed, ensure we start clear.
811     memset(ctx, 0, sizeof(mcmc_ctx_t));
812     memset(&hints, 0, sizeof(hints));
813     hints.ai_family = AF_INET;
814     hints.ai_socktype = SOCK_STREAM;
815     ctx->fd = 0;
816 
817     s = getaddrinfo(host, port, &hints, &ai);
818 
819     if (s != 0) {
820         hints.ai_family = AF_INET6;
821         s = getaddrinfo(host, port, &hints, &ai);
822         if (s != 0) {
823             // TODO: gai_strerror(s)
824             ctx->gai_status = s;
825             res = MCMC_ERR;
826             goto end;
827         }
828     }
829 
830     for (next = ai; next != NULL; next = next->ai_next) {
831         sock = socket(next->ai_family, next->ai_socktype,
832                 next->ai_protocol);
833         if (sock == -1)
834             continue;
835 
836         if (options & MCMC_OPTION_TCP_KEEPALIVE) {
837             int optval = 1;
838             if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) < 0) {
839                 res = MCMC_ERR;
840                 close(sock);
841                 goto end;
842             }
843         }
844 
845         if (options & MCMC_OPTION_NONBLOCK) {
846             int flags = fcntl(sock, F_GETFL);
847             if (flags < 0) {
848                 res = MCMC_ERR;
849                 close(sock);
850                 goto end;
851             }
852             if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
853                 res = MCMC_ERR;
854                 close(sock);
855                 goto end;
856             }
857             res = MCMC_CONNECTING;
858 
859             if (connect(sock, next->ai_addr, next->ai_addrlen) != -1) {
860                 if (errno == EINPROGRESS) {
861                     break; // We're good, stop the loop.
862                 }
863             }
864 
865             break;
866         } else {
867             // TODO: BIND local port.
868             if (connect(sock, next->ai_addr, next->ai_addrlen) != -1)
869                 break;
870         }
871 
872         close(sock);
873     }
874 
875     // TODO: cache last connect status code?
876     if (next == NULL) {
877         res = MCMC_ERR;
878         goto end;
879     }
880 
881     ctx->fd = sock;
882 end:
883     if (ai) {
884         freeaddrinfo(ai);
885     }
886     return res;
887 }
888 
889 // NOTE: if WANT_WRITE returned, call with same arguments.
890 // FIXME: len -> size_t?
891 // TODO: rename to mcmc_request_send
mcmc_send_request(void * c,const char * request,int len,int count)892 int mcmc_send_request(void *c, const char *request, int len, int count) {
893     mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
894 
895     // adjust our send buffer by how much has already been sent.
896     const char *r = request + ctx->sent_bytes_partial;
897     int l = len - ctx->sent_bytes_partial;
898     int sent = send(ctx->fd, r, l, 0);
899     if (sent == -1) {
900         // implicitly handle nonblock mode.
901         if (errno == EAGAIN || errno == EWOULDBLOCK) {
902             return MCMC_WANT_WRITE;
903         } else {
904             return MCMC_ERR;
905         }
906     }
907 
908     if (sent < len) {
909         // can happen anytime, but mostly in nonblocking mode.
910         ctx->sent_bytes_partial += sent;
911         return MCMC_WANT_WRITE;
912     } else {
913         ctx->sent_bytes_partial = 0;
914     }
915 
916     return MCMC_OK;
917 }
918 
919 // TODO: pretty sure I don't want this function chewing on a submitted iov
920 // stack, but it might make for less client code :(
921 // so for now, lets not.
mcmc_request_writev(void * c,const struct iovec * iov,int iovcnt,ssize_t * sent,int count)922 int mcmc_request_writev(void *c, const struct iovec *iov, int iovcnt, ssize_t *sent, int count) {
923     mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
924     // need to track sent vs tosend to know when to update counters.
925     ssize_t tosend = 0;
926     for (int i = 0; i < iovcnt; i++) {
927         tosend += iov[i].iov_len;
928     }
929 
930     *sent = writev(ctx->fd, iov, iovcnt);
931     if (*sent == -1) {
932         // implicitly handle nonblock mode.
933         if (errno == EAGAIN || errno == EWOULDBLOCK) {
934             return MCMC_WANT_WRITE;
935         } else {
936             return MCMC_ERR;
937         }
938     }
939 
940     if (*sent < tosend) {
941         // can happen anytime, but mostly in nonblocking mode.
942         return MCMC_WANT_WRITE;
943     }
944 
945     return MCMC_OK;
946 }
947 
948 // TODO: return consumed bytes and end ptr?
949 // FIXME: mcmc no longer tracks the buffer inbetween commands, which was
950 // causing issues with the API and bugs.
951 // This function wraps the recv call, so it needs to understand the buffer a
952 // little bit. Since memcached doesn't currently use this function I'm
953 // commenting it out with this note so it can be rewritten in terms of an
954 // external buffer later.
955 /*
956 int mcmc_read(void *c, char *buf, size_t bufsize, mcmc_resp_t *r) {
957     mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
958     char *el;
959     memset(r, 0, sizeof(*r));
960 
961     // If there's still data in the buffer try to use it before potentially
962     // hanging on the network read.
963     // Also skip this check if we specifically wanted more bytes from net.
964     if (ctx->buffer_used && !(ctx->status_flags & FLAG_BUF_WANTED_READ)) {
965         el = memchr(buf, '\n', ctx->buffer_used);
966         if (el) {
967             goto parse;
968         }
969     }
970 
971     // adjust buffer by how far we've already consumed.
972     char *b = buf + ctx->buffer_used;
973     size_t l = bufsize - ctx->buffer_used;
974 
975     int read = recv(ctx->fd, b, l, 0);
976     if (read == 0) {
977         return MCMC_NOT_CONNECTED;
978     } else if (read == -1) {
979         // implicitly handle nonblocking configurations.
980         if (errno == EAGAIN || errno == EWOULDBLOCK) {
981             return MCMC_WANT_READ;
982         } else {
983             return MCMC_ERR;
984         }
985     }
986 
987     ctx->buffer_used += read;
988 
989     // Always scan from the start of the original buffer.
990     el = memchr(buf, '\n', ctx->buffer_used);
991     if (!el) {
992         // FIXME: error if buffer is full but no \n is found.
993         ctx->status_flags |= FLAG_BUF_WANTED_READ;
994         return MCMC_WANT_READ;
995     }
996 parse:
997     // Consume through the newline.
998     r->value = el+1;
999 
1000     // FIXME: the server must be stricter in what it sends back. should always
1001     // have a \r. check for it and fail?
1002     ctx->buffer_request_len = r->value - buf;
1003     // leave the \r\n in the line end cache.
1004     ctx->buffer_head = buf;
1005     // TODO: handling for nonblock case.
1006 
1007     // We have a result line. Now pass it through the parser.
1008     // Then we indicate to the user that a response is ready.
1009     return _mcmc_parse_response(ctx, r);
1010 }
1011 */
1012 
mcmc_get_error(void * c,char * code,size_t clen,char * msg,size_t mlen)1013 void mcmc_get_error(void *c, char *code, size_t clen, char *msg, size_t mlen) {
1014     code[0] = '\0';
1015     msg[0] = '\0';
1016 }
1017 
1018 // read into the buffer, up to a max size of vsize.
1019 // will read (vsize-read) into the buffer pointed to by (val+read).
1020 // you are able to stream the value into different buffers, or process the
1021 // value and reuse the same buffer, by adjusting vsize and *read between
1022 // calls.
1023 // vsize must not be larger than the remaining value size pending read.
1024 /* TODO: see notes on mcmc_read()
1025 int mcmc_read_value(void *c, char *val, mcmc_resp_t *r, int *read) {
1026     mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
1027     size_t l;
1028 
1029     // If the distance between tail/head is smaller than what we read into the
1030     // main buffer, we have some value to copy out.
1031     size_t vsize = r->vlen;
1032     if (*read < r->vlen_read) {
1033         memcpy(val + *read, r->value, r->vlen_read);
1034         *read += r->vlen_read;
1035         if (r->vlen_read >= r->vlen) {
1036             return MCMC_OK;
1037         }
1038     }
1039 
1040     char *v = val + *read;
1041     l = vsize - *read;
1042 
1043     int rd = recv(ctx->fd, v, l, 0);
1044     if (rd == 0) {
1045         // TODO: some internal disconnect work?
1046         return MCMC_NOT_CONNECTED;
1047     }
1048     // FIXME: EAGAIN || EWOULDBLOCK!
1049     if (rd == -1) {
1050         return MCMC_ERR;
1051     }
1052 
1053     *read += rd;
1054 
1055     if (*read < vsize) {
1056         return MCMC_WANT_READ;
1057     } else {
1058         return MCMC_OK;
1059     }
1060 }
1061 */
1062 
mcmc_disconnect(void * c)1063 int mcmc_disconnect(void *c) {
1064     mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
1065 
1066     // FIXME: I forget if 0 can be valid.
1067     if (ctx->fd != 0) {
1068         close(ctx->fd);
1069         ctx->fd = 0;
1070         return MCMC_OK;
1071     } else {
1072         return MCMC_NOT_CONNECTED;
1073     }
1074 }
1075