diff options
Diffstat (limited to 'lib/ptm_lib.c')
| -rw-r--r-- | lib/ptm_lib.c | 464 | 
1 files changed, 464 insertions, 0 deletions
diff --git a/lib/ptm_lib.c b/lib/ptm_lib.c new file mode 100644 index 0000000000..175cf1443e --- /dev/null +++ b/lib/ptm_lib.c @@ -0,0 +1,464 @@ +/********************************************************************* + * Copyright 2015 Cumulus Networks, LLC.  All rights reserved. + * + * library file used by clients for sending commands and parsing response + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stddef.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <errno.h> +#include <sys/socket.h> +#include "csv.h" +#include "ptm_lib.h" + +#define DEBUG_E 0 +#define DEBUG_V 0 + +#define ERRLOG(fmt, ...) \ +  do { if (DEBUG_E) fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \ +			    __LINE__, __func__, ##__VA_ARGS__); } while (0) + +#define DLOG(fmt, ...) \ +  do { if (DEBUG_V) fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \ +			    __LINE__, __func__, ##__VA_ARGS__); } while (0) + +typedef struct ptm_lib_msg_ctxt_s { +    int cmd_id; +    csv_t *csv; +    ptmlib_msg_type type; +} ptm_lib_msg_ctxt_t; + +static csv_record_t * +_ptm_lib_encode_header(csv_t *csv, +		               csv_record_t *rec, +		               int msglen, +		               int version, +                       int type, +                       int cmd_id, +                       char *client_name) +{ +    char msglen_buf[16], vers_buf[16], type_buf[16], cmdid_buf[16]; +    char client_buf[32]; +    csv_record_t *rec1; + +    sprintf(msglen_buf, "%4u", msglen); +    sprintf(vers_buf, "%4u", version); +    sprintf(type_buf, "%4u", type); +    sprintf(cmdid_buf, "%4u", cmd_id); +    snprintf(client_buf, 17, "%16.16s", client_name); +    if (rec) { +        rec1 = csv_encode_record(csv, rec, 5, msglen_buf, vers_buf, +                type_buf, cmdid_buf, client_buf); +    } else { +        rec1 = csv_encode(csv, 5, msglen_buf, vers_buf, +                type_buf, cmdid_buf, client_buf); +    } +    return (rec1); +} + +static int +_ptm_lib_decode_header (csv_t *csv, +                       int *msglen, +                       int *version, +                       int *type, +                       int *cmd_id, +                       char *client_name) +{ +    char *hdr; +    csv_record_t *rec; +    csv_field_t *fld; +    int i, j; + +    csv_decode(csv, NULL); +    rec = csv_record_iter(csv); +    if (rec == NULL) { +        DLOG("malformed CSV\n"); +        return (-1); +    } +    hdr = csv_field_iter(rec, &fld); +    if (hdr == NULL) { +        DLOG("malformed CSV\n"); +        return (-1); +    } +    *msglen = atoi(hdr); +    hdr = csv_field_iter_next(&fld); +    if (hdr == NULL) { +        DLOG("malformed CSV\n"); +        return (-1); +    } +    *version = atoi(hdr); +    hdr = csv_field_iter_next(&fld); +    if (hdr == NULL) { +        DLOG("malformed CSV\n"); +        return (-1); +    } +    *type = atoi(hdr); +    hdr = csv_field_iter_next(&fld); +    if (hdr == NULL) { +        DLOG("malformed CSV\n"); +        return (-1); +    } +    *cmd_id = atoi(hdr); +    hdr = csv_field_iter_next(&fld); +    if (hdr == NULL) { +        DLOG("malformed CSV\n"); +        return (-1); +    } +    /* remove leading spaces */ +    for (i = j = 0; i < csv_field_len(fld); i++) { +        if (!isspace(hdr[i])) { +            client_name[j] = hdr[i]; +            j++; +        } +    } +    client_name[j] = '\0'; + +    return (0); +} + +int +ptm_lib_append_msg(ptm_lib_handle_t *hdl, void *ctxt, +                   const char *key, char *val) +{ +    ptm_lib_msg_ctxt_t *p_ctxt = ctxt; +    csv_t *csv; +    csv_record_t *mh_rec, *rec; + +    if (!p_ctxt) { +        ERRLOG("%s: no context \n", __FUNCTION__); +        return -1; +    } + +    csv = p_ctxt->csv; +    mh_rec = csv_record_iter(csv); +    rec = csv_record_iter_next(mh_rec); + +    /* append to the hdr record */ +    rec = csv_append_record(csv, rec, 1, key); +    if (!rec) { +        ERRLOG("%s: Could not append key \n", __FUNCTION__); +        return -1; +    } + +    rec = csv_record_iter_next(rec); +    /* append to the data record */ +    rec = csv_append_record(csv, rec, 1, val); +    if (!rec) { +        ERRLOG("%s: Could not append val \n", __FUNCTION__); +        return -1; +    } + +    /* update the msg hdr */ +    _ptm_lib_encode_header(csv, mh_rec, +                    (csvlen(csv) - PTMLIB_MSG_HDR_LEN), +                    PTMLIB_MSG_VERSION, p_ctxt->type, +                    p_ctxt->cmd_id, hdl->client_name); + +    return 0; +} + +int +ptm_lib_init_msg(ptm_lib_handle_t *hdl, int cmd_id, int type, +                 void *in_ctxt, void **out_ctxt) +{ +    ptm_lib_msg_ctxt_t *p_ctxt; +    ptm_lib_msg_ctxt_t *p_in_ctxt = in_ctxt; +    csv_t *csv; +    csv_record_t *rec, *d_rec; + +    /* Initialize csv for using discrete record buffers */ +    csv = csv_init(NULL, NULL, PTMLIB_MSG_SZ); + +    if (!csv) { +        ERRLOG("%s: Could not allocate csv \n", __FUNCTION__); +        return -1; +    } + +    rec = _ptm_lib_encode_header(csv, NULL, 0, +                    PTMLIB_MSG_VERSION, type, +                    cmd_id, hdl->client_name); + +    if (!rec) { +        ERRLOG("%s: Could not allocate record \n", __FUNCTION__); +        csv_clean(csv); +        csv_free(csv); +        return -1; +    } + +    p_ctxt = calloc(1, sizeof(*p_ctxt)); +    if (!p_ctxt) { +        ERRLOG("%s: Could not allocate context \n", __FUNCTION__); +        csv_clean(csv); +        csv_free(csv); +        return -1; +    } + +    p_ctxt->csv = csv; +    p_ctxt->cmd_id = cmd_id; +    p_ctxt->type = type; + +    *(ptm_lib_msg_ctxt_t **)out_ctxt = p_ctxt; + +    /* caller supplied a context to initialize with? */ +    if (p_in_ctxt) { +        /* insert the hdr rec */ +        rec = csv_record_iter(p_in_ctxt->csv); +        csv_clone_record (p_in_ctxt->csv, rec, &d_rec); +        csv_insert_record (csv, d_rec); +        /* insert the data rec */ +        rec = csv_record_iter_next(rec); +        csv_clone_record (p_in_ctxt->csv, rec, &d_rec); +        csv_insert_record (csv, d_rec); +    } +    return 0; +} + +int +ptm_lib_complete_msg(ptm_lib_handle_t *hdl, void *ctxt, +                     char *buf, int *len) +{ +    ptm_lib_msg_ctxt_t *p_ctxt = ctxt; +    csv_t *csv; +    csv_record_t *rec; + +    if (!p_ctxt) { +        ERRLOG("%s: no context \n", __FUNCTION__); +        return -1; +    } + +    csv = p_ctxt->csv; +    rec = csv_record_iter(csv); + +    _ptm_lib_encode_header(csv, rec, +                    (csvlen(csv) - PTMLIB_MSG_HDR_LEN), +                    PTMLIB_MSG_VERSION, p_ctxt->type, +                    p_ctxt->cmd_id, hdl->client_name); + +    /* parse csv contents into string */ +    if (buf && len) { +        if (csv_serialize(csv, buf, *len)) { +            ERRLOG("%s: cannot serialize\n", __FUNCTION__); +            return -1; +        } +        *len = csvlen(csv); +    } + +    csv_clean(csv); +    csv_free(csv); +    free(p_ctxt); + +    return 0; +} + +int +ptm_lib_find_key_in_msg(void *ctxt, const char *key, char *val) +{ +    ptm_lib_msg_ctxt_t *p_ctxt = ctxt; +    csv_t *csv = p_ctxt->csv; +    csv_record_t *hrec, *drec; +    csv_field_t *hfld, *dfld; +    char *hstr, *dstr; + +    /** +     * skip over ptm hdr if present +     * The next hdr is the keys (column name) +     * The next hdr is the data +     */ +    if (csv_num_records(csv) > 2) { +        hrec = csv_record_iter(csv); +        hrec = csv_record_iter_next(hrec); +    } else { +        hrec = csv_record_iter(csv); +    } +    drec = csv_record_iter_next(hrec); +    val[0] = '\0'; +    for(hstr = csv_field_iter(hrec, &hfld), +        dstr = csv_field_iter(drec, &dfld); +        (hstr && dstr); +        hstr = csv_field_iter_next(&hfld), +        dstr = csv_field_iter_next(&dfld)) { +        if (!strncmp(hstr, key, csv_field_len(hfld))) { +            snprintf(val, csv_field_len(dfld)+1, "%s", dstr); +            return 0; +        } +    } + +    return -1; +} + +static int +_ptm_lib_read_ptm_socket(int fd, char *buf, int len) +{ +    int retries = 0, rc; +    int bytes_read = 0; + +    while (bytes_read != len) { +        rc = recv(fd, (void *) (buf + bytes_read), (len - bytes_read), +                  MSG_DONTWAIT); +        if (rc <= 0) { +            if (errno && (errno != EAGAIN) && (errno != EWOULDBLOCK)) { +                ERRLOG("fatal recv error(%s), closing connection, rc %d\n", +                       strerror(errno), rc); +                return (rc); +            } else { +                if (retries++ < 2) { +                    usleep(10000); +                    continue; +                } +                DLOG("max retries - recv error(%d - %s) bytes read %d (%d)\n", +                     errno, strerror(errno), bytes_read, len); +                return (bytes_read); +            } +            break; +        } else { +            bytes_read += rc; +        } +    } + +    return bytes_read; +} + +int +ptm_lib_process_msg(ptm_lib_handle_t *hdl, int fd, +                    char *inbuf, int inlen, void *arg) +{ +    int rc, len; +    char client_name[32]; +    int cmd_id, type, ver, msglen; +    csv_t *csv; +    ptm_lib_msg_ctxt_t *p_ctxt; + +    len = _ptm_lib_read_ptm_socket(fd, inbuf, PTMLIB_MSG_HDR_LEN); +    if (len <= 0) +        return (len); + +    csv = csv_init(NULL, inbuf, PTMLIB_MSG_HDR_LEN); + +    if (!csv) { +        DLOG("Cannot allocate csv for hdr\n"); +        return (-1); +    } + +    rc = _ptm_lib_decode_header(csv, &msglen, &ver, &type, &cmd_id, client_name); + +    csv_clean(csv); +    csv_free(csv); + +    if (rc < 0) { +        /* could not decode the CSV - maybe its legacy cmd? +         * get the entire cmd from the socket and see if we can process it +         */ +        if (len == PTMLIB_MSG_HDR_LEN) { +            len += _ptm_lib_read_ptm_socket(fd, (inbuf+PTMLIB_MSG_HDR_LEN), +                                        inlen - PTMLIB_MSG_HDR_LEN); +            if (len <= 0) +                return (len); +        } + +        inbuf[len] = '\0'; +        /* we only support the get-status cmd */ +        if (strcmp(inbuf, PTMLIB_CMD_GET_STATUS)) { +            DLOG("unsupported legacy cmd %s\n", inbuf); +            return (-1); +        } +        /* internally create a csv-style cmd */ +        ptm_lib_init_msg(hdl, 0, PTMLIB_MSG_TYPE_CMD, NULL, (void *)&p_ctxt); +        if (!p_ctxt) { +            DLOG("couldnt allocate context\n"); +            return (-1); +        } +        ptm_lib_append_msg(hdl, p_ctxt, "cmd", PTMLIB_CMD_GET_STATUS); + +    } else { + +        if (msglen > inlen) { +            DLOG("msglen [%d] > inlen [%d]\n", msglen, inlen); +            return -1; +        } + +        /* read the rest of the msg */ +        len = _ptm_lib_read_ptm_socket(fd, inbuf, msglen); +        if (len <= 0) { +            return (len); +        } + +        inbuf[len] = '\0'; + +        csv = csv_init(NULL, NULL, PTMLIB_MSG_SZ); +        if (!csv) { +            ERRLOG("Cannot allocate csv for msg\n"); +            return -1; +        } + +        csv_decode(csv, inbuf); +        p_ctxt = calloc(1, sizeof(*p_ctxt)); +        if (!p_ctxt) { +            ERRLOG("%s: Could not allocate context \n", __FUNCTION__); +            csv_clean(csv); +            csv_free(csv); +            return -1; +        } + +        p_ctxt->csv = csv; +        p_ctxt->cmd_id = cmd_id; +        p_ctxt->type = type; +    } + +    switch(p_ctxt->type) { +    case PTMLIB_MSG_TYPE_NOTIFICATION: +        if (hdl->notify_cb) +            hdl->notify_cb(arg, p_ctxt); +        break; +    case PTMLIB_MSG_TYPE_CMD: +        if (hdl->cmd_cb) +            hdl->cmd_cb(arg, p_ctxt); +        break; +    case PTMLIB_MSG_TYPE_RESPONSE: +        if (hdl->response_cb) +            hdl->response_cb(arg, p_ctxt); +        break; +    default: +        return -1; +    } + +    csv_clean(p_ctxt->csv); +    csv_free(p_ctxt->csv); +    free(p_ctxt); + +    return len; +} + +ptm_lib_handle_t * +ptm_lib_register(char *client_name, +                 ptm_cmd_cb cmd_cb, +                 ptm_notify_cb notify_cb, +                 ptm_response_cb response_cb) +{ +    ptm_lib_handle_t *hdl; + +    hdl = calloc(1, sizeof(*hdl)); + +    if (hdl) { +        strcpy(hdl->client_name, client_name); +        hdl->cmd_cb = cmd_cb; +        hdl->notify_cb = notify_cb; +        hdl->response_cb = response_cb; +    } + +    return hdl; +} + +void +ptm_lib_deregister(ptm_lib_handle_t *hdl) +{ +    if (hdl) { +        memset(hdl, 0x00, sizeof(*hdl)); +        free(hdl); +    } +}  | 
