resp.c

Basic RESP handling library in C
git clone git://git.finwo.net/lib/resp.c
Log | Files | Refs | LICENSE

resp.c (18291B)


      1 #include "resp.h"
      2 
      3 #include <errno.h>
      4 #include <stdio.h>
      5 #include <stdlib.h>
      6 #include <string.h>
      7 #include <unistd.h>
      8 
      9 #define MAX_BULK_LEN (256 * 1024)
     10 #define LINE_BUF     4096
     11 
     12 static void resp_free_internal(resp_object *o);
     13 
     14 int resp_read_buf(const char *buf, size_t len, resp_object **out_obj) {
     15   if (!out_obj) return -1;
     16 
     17   const char *start     = buf;
     18   const char *p         = buf;
     19   size_t      remaining = len;
     20 
     21   // We need at least 1 byte
     22   if (len <= 0) return -1;
     23 
     24   // Ensure we have memory to place data in
     25   resp_object *output = *out_obj;
     26   if (!output) {
     27     *out_obj = output = calloc(1, sizeof(resp_object));
     28     if (!output) return -1;
     29   }
     30 
     31   // Skip empty lines (only \r\n)
     32   if (p[0] == '\r' || p[0] == '\n') {
     33     while (remaining > 0 && (p[0] == '\r' || p[0] == '\n')) {
     34       p++;
     35       remaining--;
     36     }
     37     if (remaining == 0) return 0;  // only whitespace, need more data
     38   }
     39 
     40   // Consume first character for object type detection
     41   int type_c = p[0];
     42   remaining--;
     43   p++;
     44 
     45   // And act accordingly
     46   switch ((char)type_c) {
     47     case '+':
     48       output->type = output->type ? output->type : RESPT_SIMPLE;
     49       if (output->type != RESPT_SIMPLE) {
     50         return -2;  // Mismatching types
     51       }
     52       // Read until \r\n, don't include \r\n in string
     53       {
     54         size_t i = 0;
     55         char   line[LINE_BUF];
     56         int    found_crlf = 0;
     57         while (i + 1 < LINE_BUF && remaining > 0) {
     58           if (remaining >= 2 && p[0] == '\r' && p[1] == '\n') {
     59             p += 2;
     60             remaining -= 2;
     61             found_crlf = 1;
     62             break;
     63           }
     64           line[i++] = p[0];
     65           p++;
     66           remaining--;
     67         }
     68         if (!found_crlf) {
     69           return -1;  // Incomplete, need more data
     70         }
     71         line[i] = '\0';
     72         if (output->u.s) free(output->u.s);
     73         output->u.s = strdup(line);
     74       }
     75       break;
     76 
     77     case '-':
     78       output->type = output->type ? output->type : RESPT_ERROR;
     79       if (output->type != RESPT_ERROR) {
     80         return -2;  // Mismatching types
     81       }
     82       // Read until \r\n, don't include \r\n in string
     83       {
     84         size_t i = 0;
     85         char   line[LINE_BUF];
     86         int    found_crlf = 0;
     87         while (i + 1 < LINE_BUF && remaining > 0) {
     88           if (remaining >= 2 && p[0] == '\r' && p[1] == '\n') {
     89             p += 2;
     90             remaining -= 2;
     91             found_crlf = 1;
     92             break;
     93           }
     94           line[i++] = p[0];
     95           p++;
     96           remaining--;
     97         }
     98         if (!found_crlf) {
     99           return -1;  // Incomplete, need more data
    100         }
    101         line[i] = '\0';
    102         if (output->u.s) free(output->u.s);
    103         output->u.s = strdup(line);
    104       }
    105       break;
    106 
    107     case ':':
    108       output->type = output->type ? output->type : RESPT_INT;
    109       if (output->type != RESPT_INT) {
    110         return -2;  // Mismatching types
    111       }
    112       // Read until \r\n, don't include \r\n in string
    113       // value = strtoll(line);
    114       {
    115         size_t i = 0;
    116         char   line[LINE_BUF];
    117         int    found_crlf = 0;
    118         while (i + 1 < LINE_BUF && remaining > 0) {
    119           if (remaining >= 2 && p[0] == '\r' && p[1] == '\n') {
    120             p += 2;
    121             remaining -= 2;
    122             found_crlf = 1;
    123             break;
    124           }
    125           line[i++] = p[0];
    126           p++;
    127           remaining--;
    128         }
    129         if (!found_crlf) {
    130           return -1;  // Incomplete, need more data
    131         }
    132         line[i]     = '\0';
    133         output->u.i = strtoll(line, NULL, 10);
    134       }
    135       break;
    136 
    137     case '$':
    138       output->type = output->type ? output->type : RESPT_BULK;
    139       if (output->type != RESPT_BULK) {
    140         return -2;  // Mismatching types
    141       }
    142       // Read until \r\n, don't include \r\n in string
    143       // data_length = strtoll(line);
    144       {
    145         size_t i = 0;
    146         char   line[LINE_BUF];
    147         int    found_crlf = 0;
    148         while (i + 1 < LINE_BUF && remaining > 0) {
    149           if (remaining >= 2 && p[0] == '\r' && p[1] == '\n') {
    150             p += 2;
    151             remaining -= 2;
    152             found_crlf = 1;
    153             break;
    154           }
    155           line[i++] = p[0];
    156           p++;
    157           remaining--;
    158         }
    159         if (!found_crlf) {
    160           return -1;  // Incomplete, need more data
    161         }
    162         line[i]          = '\0';
    163         long data_length = strtol(line, NULL, 10);
    164 
    165         if (data_length < 0) {
    166           output->u.s = NULL;
    167         } else if (data_length == 0) {
    168           // Null bulk string or empty string - need \r\n
    169 
    170           if (remaining >= 2 && p[0] == '\r' && p[1] == '\n') {
    171             p += 2;
    172             remaining -= 2;
    173           } else {
    174             return -1;  // Incomplete, need more data
    175           }
    176           if (output->u.s) free(output->u.s);
    177           output->u.s = strdup("");
    178         } else {
    179           // Read data_length bytes
    180           if ((size_t)data_length > remaining) {
    181             return -1;  // not enough data
    182           }
    183           if (output->u.s) free(output->u.s);
    184           output->u.s = malloc((size_t)data_length + 1);
    185           if (!output->u.s) return -1;
    186           memcpy(output->u.s, p, (size_t)data_length);
    187           output->u.s[data_length] = '\0';
    188           p += data_length;
    189           remaining -= data_length;
    190           // Skip \r\n
    191           if (remaining >= 2 && p[0] == '\r' && p[1] == '\n') {
    192             p += 2;
    193             remaining -= 2;
    194           } else {
    195             free(output->u.s);
    196             output->u.s = NULL;
    197             return -1;  // Incomplete, need more data
    198           }
    199         }
    200       }
    201       break;
    202 
    203     case '*':
    204       output->type = output->type ? output->type : RESPT_ARRAY;
    205       if (output->type != RESPT_ARRAY) {
    206         return -2;  // Mismatching types
    207       }
    208       // Read until \r\n, don't include \r\n in string
    209       // items = strtoll(line);
    210       {
    211         size_t i = 0;
    212         char   line[LINE_BUF];
    213         int    found_crlf = 0;
    214         while (i + 1 < LINE_BUF && remaining > 0) {
    215           if (remaining >= 2 && p[0] == '\r' && p[1] == '\n') {
    216             p += 2;
    217             remaining -= 2;
    218             found_crlf = 1;
    219             break;
    220           }
    221           line[i++] = p[0];
    222           p++;
    223           remaining--;
    224         }
    225         if (!found_crlf) {
    226           return -1;  // Incomplete, need more data
    227         }
    228         line[i]    = '\0';
    229         long items = strtol(line, NULL, 10);
    230 
    231         if (items < 0 || items > 65536) {
    232           return -1;
    233         }
    234 
    235         // Initialize array if needed
    236         if (!output->u.arr.elem) {
    237           output->u.arr.n    = 0;
    238           output->u.arr.elem = NULL;
    239         }
    240 
    241         for (size_t j = 0; j < (size_t)items; j++) {
    242           if (remaining == 0) {
    243             return -1;
    244           }
    245           resp_object *element          = NULL;
    246           int          element_consumed = resp_read_buf(p, remaining, &element);
    247           if (element_consumed <= 0) {
    248             return -1;
    249           }
    250           if (resp_array_append_obj(output, element) != 0) {
    251             resp_free(element);
    252             return -1;
    253           }
    254           p += element_consumed;
    255           remaining -= element_consumed;
    256         }
    257       }
    258       break;
    259 
    260     default:
    261       return -1;
    262   }
    263 
    264   return (int)(p - start);
    265 }
    266 
    267 static int resp_read_byte(int fd) {
    268   unsigned char c;
    269   ssize_t       n = read(fd, &c, 1);
    270   if (n != 1) {
    271     if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) return -2;
    272     return -1;
    273   }
    274   return (int)c;
    275 }
    276 
    277 static int resp_read_line(int fd, char *buf, size_t buf_size) {
    278   size_t i    = 0;
    279   int    prev = -1;
    280   while (i + 1 < buf_size) {
    281     int b = resp_read_byte(fd);
    282     if (b < 0) return -1;
    283     if (prev == '\r' && b == '\n') {
    284       buf[i - 1] = '\0';
    285       return 0;
    286     }
    287     prev     = b;
    288     buf[i++] = (char)b;
    289   }
    290   return -1;
    291 }
    292 
    293 resp_object *resp_read(int fd) {
    294   int type_c = resp_read_byte(fd);
    295   if (type_c < 0) return NULL;
    296   if (type_c == -2) return NULL;
    297   resp_object *o = calloc(1, sizeof(resp_object));
    298   if (!o) return NULL;
    299   char line[LINE_BUF];
    300   switch ((char)type_c) {
    301     case '+':
    302       o->type = RESPT_SIMPLE;
    303       if (resp_read_line(fd, line, sizeof(line)) != 0) {
    304         free(o);
    305         return NULL;
    306       }
    307       o->u.s = strdup(line);
    308       break;
    309     case '-':
    310       o->type = RESPT_ERROR;
    311       if (resp_read_line(fd, line, sizeof(line)) != 0) {
    312         free(o);
    313         return NULL;
    314       }
    315       o->u.s = strdup(line);
    316       break;
    317     case ':':
    318       {
    319         if (resp_read_line(fd, line, sizeof(line)) != 0) {
    320           free(o);
    321           return NULL;
    322         }
    323         o->type = RESPT_INT;
    324         o->u.i  = (long long)strtoll(line, NULL, 10);
    325         break;
    326       }
    327     case '$':
    328       {
    329         if (resp_read_line(fd, line, sizeof(line)) != 0) {
    330           free(o);
    331           return NULL;
    332         }
    333         long len = strtol(line, NULL, 10);
    334         if (len < 0 || len > (long)MAX_BULK_LEN) {
    335           free(o);
    336           return NULL;
    337         }
    338         o->type = RESPT_BULK;
    339         if (len == 0) {
    340           o->u.s = strdup("");
    341           if (resp_read_line(fd, line, sizeof(line)) != 0) {
    342             free(o->u.s);
    343             free(o);
    344             return NULL;
    345           }
    346         } else {
    347           o->u.s = malloc((size_t)len + 1);
    348           if (!o->u.s) {
    349             free(o);
    350             return NULL;
    351           }
    352           if (read(fd, o->u.s, (size_t)len) != (ssize_t)len) {
    353             free(o->u.s);
    354             free(o);
    355             return NULL;
    356           }
    357           o->u.s[len] = '\0';
    358           if (resp_read_byte(fd) != '\r' || resp_read_byte(fd) != '\n') {
    359             free(o->u.s);
    360             free(o);
    361             return NULL;
    362           }
    363         }
    364         break;
    365       }
    366     case '*':
    367       {
    368         if (resp_read_line(fd, line, sizeof(line)) != 0) {
    369           free(o);
    370           return NULL;
    371         }
    372         long n = strtol(line, NULL, 10);
    373         if (n < 0 || n > 65536) {
    374           free(o);
    375           return NULL;
    376         }
    377         o->type       = RESPT_ARRAY;
    378         o->u.arr.n    = (size_t)n;
    379         o->u.arr.elem = n ? calloc((size_t)n, sizeof(resp_object)) : NULL;
    380         if (n && !o->u.arr.elem) {
    381           free(o);
    382           return NULL;
    383         }
    384         for (size_t i = 0; i < (size_t)n; i++) {
    385           resp_object *sub = resp_read(fd);
    386           if (!sub) {
    387             for (size_t j = 0; j < i; j++) resp_free_internal(&o->u.arr.elem[j]);
    388             free(o->u.arr.elem);
    389             free(o);
    390             return NULL;
    391           }
    392           o->u.arr.elem[i] = *sub;
    393           free(sub);
    394         }
    395         break;
    396       }
    397     default:
    398       free(o);
    399       return NULL;
    400   }
    401   return o;
    402 }
    403 
    404 static void resp_free_internal(resp_object *o) {
    405   if (!o) return;
    406   if (o->type == RESPT_SIMPLE || o->type == RESPT_ERROR || o->type == RESPT_BULK) {
    407     free(o->u.s);
    408   } else if (o->type == RESPT_ARRAY) {
    409     for (size_t i = 0; i < o->u.arr.n; i++) resp_free_internal(&o->u.arr.elem[i]);
    410     free(o->u.arr.elem);
    411   }
    412 }
    413 
    414 void resp_free(resp_object *o) {
    415   resp_free_internal(o);
    416   free(o);
    417 }
    418 
    419 resp_object *resp_deep_copy(const resp_object *o) {
    420   if (!o) return NULL;
    421   resp_object *c = (resp_object *)calloc(1, sizeof(resp_object));
    422   if (!c) return NULL;
    423   c->type = o->type;
    424   if (o->type == RESPT_SIMPLE || o->type == RESPT_ERROR || o->type == RESPT_BULK) {
    425     c->u.s = o->u.s ? strdup(o->u.s) : NULL;
    426     if (o->u.s && !c->u.s) {
    427       free(c);
    428       return NULL;
    429     }
    430     return c;
    431   }
    432   if (o->type == RESPT_INT) {
    433     c->u.i = o->u.i;
    434     return c;
    435   }
    436   if (o->type == RESPT_ARRAY) {
    437     c->u.arr.n    = o->u.arr.n;
    438     c->u.arr.elem = o->u.arr.n ? (resp_object *)calloc(o->u.arr.n, sizeof(resp_object)) : NULL;
    439     if (o->u.arr.n && !c->u.arr.elem) {
    440       free(c);
    441       return NULL;
    442     }
    443     for (size_t i = 0; i < o->u.arr.n; i++) {
    444       resp_object *sub = resp_deep_copy(&o->u.arr.elem[i]);
    445       if (!sub) {
    446         for (size_t j = 0; j < i; j++) resp_free_internal(&c->u.arr.elem[j]);
    447         free(c->u.arr.elem);
    448         free(c);
    449         return NULL;
    450       }
    451       c->u.arr.elem[i] = *sub;
    452       free(sub);
    453     }
    454     return c;
    455   }
    456   free(c);
    457   return NULL;
    458 }
    459 
    460 resp_object *resp_map_get(const resp_object *o, const char *key) {
    461   if (!o || !key || o->type != RESPT_ARRAY) return NULL;
    462   size_t n = o->u.arr.n;
    463   if (n & 1) return NULL;
    464   for (size_t i = 0; i < n; i += 2) {
    465     const resp_object *k = &o->u.arr.elem[i];
    466     const char        *s = (k->type == RESPT_BULK || k->type == RESPT_SIMPLE) ? k->u.s : NULL;
    467     if (s && strcmp(s, key) == 0 && i + 1 < n) return (resp_object *)&o->u.arr.elem[i + 1];
    468   }
    469   return NULL;
    470 }
    471 
    472 const char *resp_map_get_string(const resp_object *o, const char *key) {
    473   resp_object *val = resp_map_get(o, key);
    474   if (!val) return NULL;
    475   if (val->type == RESPT_BULK || val->type == RESPT_SIMPLE) return val->u.s;
    476   return NULL;
    477 }
    478 
    479 void resp_map_set(resp_object *o, const char *key, resp_object *value) {
    480   if (!o || !key || o->type != RESPT_ARRAY) return;
    481   for (size_t i = 0; i + 1 < o->u.arr.n; i += 2) {
    482     const resp_object *k = &o->u.arr.elem[i];
    483     const char        *s = (k->type == RESPT_BULK || k->type == RESPT_SIMPLE) ? k->u.s : NULL;
    484     if (s && strcmp(s, key) == 0 && i + 1 < o->u.arr.n) {
    485       resp_free(&o->u.arr.elem[i + 1]);
    486       o->u.arr.elem[i + 1] = *value;
    487       free(value);
    488       return;
    489     }
    490   }
    491   resp_array_append_bulk(o, key);
    492   resp_array_append_obj(o, value);
    493 }
    494 
    495 static int resp_append_object(char **buf, size_t *cap, size_t *len, const resp_object *o) {
    496   if (!o) return -1;
    497   size_t need = *len + 256;
    498   if (o->type == RESPT_BULK || o->type == RESPT_SIMPLE || o->type == RESPT_ERROR) {
    499     size_t slen = o->u.s ? strlen(o->u.s) : 0;
    500     need        = *len + 32 + slen + 2;
    501   } else if (o->type == RESPT_ARRAY) {
    502     need = *len + 32;
    503     for (size_t i = 0; i < o->u.arr.n; i++) need += 64;
    504   }
    505   if (need > *cap) {
    506     size_t newcap = need + 4096;
    507     char  *n      = realloc(*buf, newcap);
    508     if (!n) return -1;
    509     *buf = n;
    510     *cap = newcap;
    511   }
    512   switch (o->type) {
    513     case RESPT_SIMPLE:
    514       {
    515         const char *s = o->u.s ? o->u.s : "";
    516         *len += (size_t)snprintf(*buf + *len, *cap - *len, "+%s\r\n", s);
    517         break;
    518       }
    519     case RESPT_ERROR:
    520       {
    521         const char *s = o->u.s ? o->u.s : "";
    522         *len += (size_t)snprintf(*buf + *len, *cap - *len, "-%s\r\n", s);
    523         break;
    524       }
    525     case RESPT_INT:
    526       *len += (size_t)snprintf(*buf + *len, *cap - *len, ":%lld\r\n", (long long)o->u.i);
    527       break;
    528     case RESPT_BULK:
    529       {
    530         const char *s    = o->u.s ? o->u.s : "";
    531         size_t      slen = strlen(s);
    532         *len += (size_t)snprintf(*buf + *len, *cap - *len, "$%zu\r\n%s\r\n", slen, s);
    533         break;
    534       }
    535     case RESPT_ARRAY:
    536       {
    537         size_t n = o->u.arr.n;
    538         *len += (size_t)snprintf(*buf + *len, *cap - *len, "*%zu\r\n", n);
    539         for (size_t i = 0; i < n; i++) {
    540           if (resp_append_object(buf, cap, len, &o->u.arr.elem[i]) != 0) return -1;
    541         }
    542         break;
    543       }
    544     default:
    545       return -1;
    546   }
    547   return 0;
    548 }
    549 
    550 int resp_encode_array(int argc, const resp_object *const *argv, char **out_buf, size_t *out_len) {
    551   size_t cap = 64;
    552   size_t len = 0;
    553   char  *buf = malloc(cap);
    554   if (!buf) return -1;
    555   len += (size_t)snprintf(buf + len, cap - len, "*%d\r\n", argc);
    556   if (len >= cap) {
    557     free(buf);
    558     return -1;
    559   }
    560   for (int i = 0; i < argc; i++) {
    561     if (resp_append_object(&buf, &cap, &len, argv[i]) != 0) {
    562       free(buf);
    563       return -1;
    564     }
    565   }
    566   *out_buf = buf;
    567   *out_len = len;
    568   return 0;
    569 }
    570 
    571 int resp_serialize(const resp_object *o, char **out_buf, size_t *out_len) {
    572   size_t cap = 64;
    573   size_t len = 0;
    574   char  *buf = malloc(cap);
    575   if (!buf) return -1;
    576   if (resp_append_object(&buf, &cap, &len, o) != 0) {
    577     free(buf);
    578     return -1;
    579   }
    580   *out_buf = buf;
    581   *out_len = len;
    582   return 0;
    583 }
    584 
    585 resp_object *resp_array_init(void) {
    586   resp_object *o = calloc(1, sizeof(resp_object));
    587   if (!o) return NULL;
    588   o->type       = RESPT_ARRAY;
    589   o->u.arr.n    = 0;
    590   o->u.arr.elem = NULL;
    591   return o;
    592 }
    593 
    594 resp_object *resp_simple_init(const char *value) {
    595   resp_object *o = calloc(1, sizeof(resp_object));
    596   if (!o) return NULL;
    597   o->type = RESPT_SIMPLE;
    598   o->u.s  = value ? strdup(value) : NULL;
    599   return o;
    600 }
    601 
    602 int resp_array_append_obj(resp_object *destination, resp_object *value) {
    603   if (!destination || destination->type != RESPT_ARRAY || !value) return -1;
    604   size_t       n        = destination->u.arr.n;
    605   resp_object *new_elem = realloc(destination->u.arr.elem, (n + 1) * sizeof(resp_object));
    606   if (!new_elem) return -1;
    607   destination->u.arr.elem    = new_elem;
    608   destination->u.arr.elem[n] = *value;
    609   destination->u.arr.n++;
    610   free(value);
    611   return 0;
    612 }
    613 
    614 resp_object *resp_error_init(const char *value) {
    615   resp_object *o = calloc(1, sizeof(resp_object));
    616   if (!o) return NULL;
    617   o->type = RESPT_ERROR;
    618   o->u.s  = strdup(value ? value : "");
    619   if (!o->u.s) {
    620     free(o);
    621     return NULL;
    622   }
    623   return o;
    624 }
    625 
    626 int resp_array_append_simple(resp_object *destination, const char *str) {
    627   resp_object *o = calloc(1, sizeof(resp_object));
    628   if (!o) return -1;
    629   o->type = RESPT_SIMPLE;
    630   o->u.s  = strdup(str ? str : "");
    631   if (!o->u.s) {
    632     free(o);
    633     return -1;
    634   }
    635   if (resp_array_append_obj(destination, o) != 0) {
    636     free(o->u.s);
    637     free(o);
    638     return -1;
    639   }
    640   return 0;
    641 }
    642 
    643 int resp_array_append_error(resp_object *destination, const char *str) {
    644   resp_object *o = calloc(1, sizeof(resp_object));
    645   if (!o) return -1;
    646   o->type = RESPT_ERROR;
    647   o->u.s  = strdup(str ? str : "");
    648   if (!o->u.s) {
    649     free(o);
    650     return -1;
    651   }
    652   if (resp_array_append_obj(destination, o) != 0) {
    653     free(o->u.s);
    654     free(o);
    655     return -1;
    656   }
    657   return 0;
    658 }
    659 
    660 int resp_array_append_bulk(resp_object *destination, const char *str) {
    661   resp_object *o = calloc(1, sizeof(resp_object));
    662   if (!o) return -1;
    663   o->type = RESPT_BULK;
    664   o->u.s  = strdup(str ? str : "");
    665   if (!o->u.s) {
    666     free(o);
    667     return -1;
    668   }
    669   if (resp_array_append_obj(destination, o) != 0) {
    670     free(o->u.s);
    671     free(o);
    672     return -1;
    673   }
    674   return 0;
    675 }
    676 
    677 int resp_array_append_int(resp_object *destination, long long i) {
    678   resp_object *o = malloc(sizeof(resp_object));
    679   if (!o) return -1;
    680   o->type = RESPT_INT;
    681   o->u.i  = i;
    682   return resp_array_append_obj(destination, o);
    683 }