memcache/command.c
author Tero Marttila <terom@fixme.fi>
Wed, 27 Aug 2008 22:42:27 +0300
changeset 42 0e503189af2f
parent 41 540737bf6bac
child 43 e5b714190dee
permissions -rw-r--r--
more reply-receiving code, but still incomplete
#include <string.h>
#include <stdlib.h>
#include <assert.h>

#include "command.h"
#include "../common.h"

static char *memcache_cmd_names[MEMCACHE_CMD_MAX] = {
    NULL,       // MEMCACHE_CMD_INVALID
    "get",      // MEMCACHE_CMD_FETCH_GET
    "set",      // MEMCACHE_CMD_STORE_SET
    "add",      // MEMCACHE_CMD_STORE_ADD
    "replace",  // MEMCACHE_CMD_STORE_REPLACE
    "append",   // MEMCACHE_CMD_STORE_APPEND
    "prepend"   // MEMCACHE_CMD_STORE_PREPEND
};

static struct memcache_reply_info {
    enum memcache_reply type;
    char *name;

} memcache_cmd_replies[MEMCACHE_RPL_MAX] = {
    {   MEMCACHE_RPL_INVALID,       NULL            },
    {   MEMCACHE_RPL_ERROR,         "ERROR"         },
    {   MEMCACHE_RPL_CLIENT_ERROR,  "CLIENT_ERROR"  },
    {   MEMCACHE_RPL_SERVER_ERROR,  "SERVER_ERROR"  },
    
    // MEMCACHE_CMD_FETCH_*
    {   MEMCACHE_RPL_VALUE,         "VALUE"         },
    {   MEMCACHE_RPL_END,           "END"           },
    
    // MEMCACHE_CMD_STORE_*
    {   MEMCACHE_RPL_STORED,        "STORED"        },
    {   MEMCACHE_RPL_NOT_STORED,    "NOT_STORED"    },
    {   MEMCACHE_RPL_EXISTS,        "EXISTS"        },
    {   MEMCACHE_RPL_NOT_FOUND,     "NOT_FOUND"     }
};



int memcache_cmd_init (struct memcache_cmd *cmd, enum memcache_command cmd_type, struct memcache_key *key, struct memcache_obj *obj) {
    // shouldn't already have a request header yet?
    assert(cmd->req_header == NULL);

    // allocate the request header
    if ((cmd->req_header = evbuffer_new()) == NULL)
        ERROR("evbuffer_new");

    // format the command
    if (memcache_cmd_format_header(cmd->req_header, cmd_type, key, obj))
        goto error;

    // XXX: prepare the rest

    // success
    return 0;

error:    
    if (cmd->req_header)
        evbuffer_free(cmd->req_header);

    return -1;
}

int memcache_cmd_format_header (struct evbuffer *buf, enum memcache_command cmd_type, struct memcache_key *key, struct memcache_obj *obj) {
    char *cmd_name;

    // valid command
    assert(cmd_type < MEMCACHE_CMD_MAX);

    if (cmd_type == MEMCACHE_CMD_INVALID)
        ERROR("invalid command");
    
    // map the command to a string
    cmd_name = memcache_cmd_names[cmd_type];

    // format the request header
    switch (cmd_type) {
        case MEMCACHE_CMD_FETCH_GET:
            assert(key != NULL && obj == NULL);
            assert(key->len > 0 && key->buf != NULL);

            if (evbuffer_add_printf(buf, "%s %*s\r\n", cmd_name, (int) key->len, key->buf) == -1)
                ERROR("evbuffer_add_printf");

            break;

        case MEMCACHE_CMD_STORE_SET:
        case MEMCACHE_CMD_STORE_ADD:
        case MEMCACHE_CMD_STORE_REPLACE:
        case MEMCACHE_CMD_STORE_APPEND:
        case MEMCACHE_CMD_STORE_PREPEND:
            assert(key != NULL && obj != NULL);
            assert(key->len > 0 && key->buf != NULL);
            assert(obj->bytes > 0);

            if (evbuffer_add_printf(buf, "%s %*s %u %lu %zu\r\n", cmd_name, (int) key->len, key->buf, obj->flags, obj->exptime, obj->bytes))
                ERROR("evbuffer_add_printf");

            break;

        case MEMCACHE_CMD_STORE_CAS:
        default:
            // XXX: not supported yet/invalid
            assert(0);
    };
    
    // success
    return 0;

error:
    return -1;

}

int memcache_cmd_parse_header (struct evbuffer *buf, char **header_data, enum memcache_reply *reply_type, struct memcache_key *key, struct memcache_obj *obj, int *has_data) {
    size_t line_length, buf_size;
    char *line = NULL, *token_cursor, *token, *invalid;
    int i;

    // take note of how long the buffer is
    buf_size = evbuffer_get_length(buf);

    // first, try and read a full line
    if ((line = evbuffer_readln(buf, &line_length, EVBUFFER_EOL_CRLF_STRICT)) == NULL) {
        // check if any data was consumed
        if (evbuffer_get_length(buf) != buf_size) {
            // faaaail!
            return -1;

        } else {
            // no complete line found
            return 0;
        }
    }
    
    // just check to make sure that it really is null-delimited
    assert(line[line_length - 1] == '\0');

    // empty lines?
    if (line_length == 0) {
        PWARNING("empty reply line !?!");
        return 0;
    }

    // use strsep
    token_cursor = line;

    // the first token should be the reply name
    if ((token = strsep(&token_cursor, " ")) == NULL)
        ERROR("no reply name in response line: %s", line);
    
    // figure out the reply type
    for (i = MEMCACHE_RPL_INVALID + 1; i < MEMCACHE_RPL_MAX; i++) {
        if (strcmp(token, memcache_cmd_replies[i].name) == 0)
            break;
    }
    
    // did we figure out what this reply means?
    if (i == MEMCACHE_RPL_MAX)
        ERROR("unrecognized reply: %s", line);
    
    // we found the type
    *reply_type = memcache_cmd_replies[i].type;

    // default to no data
    *has_data = 0;

    switch (*reply_type) {
        case MEMCACHE_RPL_ERROR:
            // no additional data

            break;
        
        case MEMCACHE_RPL_CLIENT_ERROR:
        case MEMCACHE_RPL_SERVER_ERROR:
            // the rest of the line is a human-readable error message
            WARNING("received a %s reply: %s", token, token_cursor);
            
            break;
        
        case MEMCACHE_RPL_VALUE:
            // <key> <flags> <bytes> [<cas unique>]

            // the key field
            if ((key->buf = strsep(&token_cursor, " ")) == NULL)
                ERROR("missing key in VALUE reply");
            
            if ((key->len = strlen(key->buf)) == 0)
                ERROR("zero-length key in VALUE reply");
            
            // the flags field
            if ((token = strsep(&token_cursor, " ")) == NULL)
                ERROR("missing flags in VALUE reply");

            obj->flags = (u_int32_t) strtol(token, &invalid, 10);

            if (*invalid != '\0')
                ERROR("invalid flags in VALUE reply: %s (%s)", token, invalid);
            
            // the bytes field
            if ((token = strsep(&token_cursor, " ")) == NULL)
                ERROR("missing bytes in VALUE reply");

            obj->bytes = (u_int32_t) strtol(token, &invalid, 10);

            if (*invalid != '\0')
                ERROR("invalid bytes in VALUE reply: %s (%s)", token, invalid);

            // the optional cas field
            if ((token = strsep(&token_cursor, " ")) != NULL) {
                // there is a cas value present
                obj->cas = (u_int64_t) strtoll(token, &invalid, 10);
                
                if (*invalid != '\0')
                    ERROR("invalid cas value in VALUE reply: %s (%s)", token, invalid);

            } else {
                obj->cas = 0;

            }
            
            // and we do have data following this
            *has_data = 1;

            break;
        
        case MEMCACHE_RPL_END:
            // no additional data

            break;

        case MEMCACHE_RPL_STORED:
        case MEMCACHE_RPL_NOT_STORED:
        case MEMCACHE_RPL_EXISTS:
        case MEMCACHE_RPL_NOT_FOUND:
            // no additional data

            break;
        
        default:
            assert(0);
    };

    // success
    *header_data = line;

    return 0;

error:
    free(line);

    return -1;
}