diff options
author | Jose Bollo <jose.bollo@iot.bzh> | 2018-09-10 12:00:18 +0200 |
---|---|---|
committer | Jose Bollo <jose.bollo@iot.bzh> | 2018-09-10 12:00:18 +0200 |
commit | 11654afcb5753a54a033db12e1ed4a19b3f7c86e (patch) | |
tree | 0d493c80584392eec2c5dc0f1c1c68c9057cf043 /src/prot.c |
Initial commit
Signed-off-by: Jose Bollo <jose.bollo@iot.bzh>
Diffstat (limited to 'src/prot.c')
-rw-r--r-- | src/prot.c | 440 |
1 files changed, 440 insertions, 0 deletions
diff --git a/src/prot.c b/src/prot.c new file mode 100644 index 0000000..db0e41e --- /dev/null +++ b/src/prot.c @@ -0,0 +1,440 @@ +#define _GNU_SOURCE + +#include <stdlib.h> +#include <limits.h> +#include <stdarg.h> +#include <assert.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <sys/uio.h> + +#include "prot.h" + +/** the structure buf is generic the meaning of pos/count is not fixed */ +struct buf +{ + /** a position */ + unsigned pos; + + /** a count */ + unsigned count; + + /** a fixed size content */ + char content[MAXBUFLEN]; +}; +typedef struct buf buf_t; + +/** structure for recording received fields */ +struct fields +{ + /** count of field (negative if invalid) */ + int count; + + /** the fields as strings */ + const char *fields[MAXARGS]; +}; +typedef struct fields fields_t; + +/** structure for handling the protocol */ +struct prot +{ + /** input buf, pos is the scanning position */ + buf_t inbuf; + + /** output buf, pos is to be written position */ + buf_t outbuf; + + /** the fields */ + fields_t fields; +}; + + +/** + * Put the 'count' in 'fields' to the 'buf' + * returns: + * - 0 on success + * - -EINVAL if the count of fields is too big + * - -ECANCELED if there is not enought space in the buffer + */ +static +int +buf_put_fields( + buf_t *buf, + unsigned count, + const char **fields +) { + unsigned ifield, pos, remain; + const char *t; + char c; + + /* check the count of fields */ + if (count > MAXARGS) + return -EINVAL; + + /* get the writing position and the free count */ + pos = buf->pos + buf->count; + if (pos >= MAXBUFLEN) + pos -= MAXBUFLEN; + remain = MAXBUFLEN - buf->count; + + /* put all fields */ + for (ifield = 0 ; ifield < count ; ifield++) { + /* prepend the field separator if needed */ + if (ifield) { + if (!remain--) + goto cancel; + buf->content[pos++] = FS; + if (pos == MAXBUFLEN) + pos = 0; + } + /* put the field if any (NULL aliases "") */ + t = fields[ifield]; + if (t) { + /* put all chars of the field */ + while((c = *t++)) { + /* escape special characters */ + if (c == FS || c == RS || c == ESC) { + if (!remain--) + goto cancel; + buf->content[pos++] = ESC; + if (pos == MAXBUFLEN) + pos = 0; + } + /* put the char */ + if (!remain--) + goto cancel; + buf->content[pos++] = c; + if (pos == MAXBUFLEN) + pos = 0; + } + } + } + + /* put the end indicator */ + if (!remain--) + goto cancel; + buf->content[pos] = RS; + + /* record the new values */ + buf->count = MAXBUFLEN - remain; + return 0; + +cancel: + return -ECANCELED; +} + +/** + * write the content of 'buf' to 'fd' + */ +static +int +buf_write( + buf_t *buf, + int fd +) { + int n; + unsigned count; + ssize_t rc; + struct iovec vec[2]; + + /* get the count of byte to write (avoid int overflow) */ + count = buf->count > INT_MAX ? INT_MAX : buf->count; + + /* calling it with nothing to write is an error */ + if (count == 0) + return -ENODATA; + + /* prepare the iovec */ + vec[0].iov_base = buf->content + buf->pos; + if (buf->pos + count <= MAXBUFLEN) { + vec[0].iov_len = count; + n = 1; + } else { + vec[0].iov_len = MAXBUFLEN - buf->pos; + vec[1].iov_base = buf->content; + vec[1].iov_len = count - vec[0].iov_len; + n = 2; + } + + /* write the buffers */ + do { + rc = writev(fd, vec, n); + } while(rc < 0 && errno == EINTR); + + /* check error */ + if (rc < 0) + rc = -errno; + else { + /* update the state */ + buf->count -= (unsigned)rc; + buf->pos += (unsigned)rc; + if (buf->pos >= MAXBUFLEN) + buf->pos -= MAXBUFLEN; + } + + return (int)rc; +} + +/* get the 'fields' from 'buf' */ +static +void +buf_get_fields( + buf_t *buf, + fields_t *fields +) { + char c; + unsigned read, write; + + /* advance the pos after the end */ + assert(buf->content[buf->pos] == RS); + buf->pos++; + + /* init first field */ + fields->fields[fields->count = 0] = buf->content; + read = write = 0; + for (;;) { + c = buf->content[read++]; + switch(c) { + case FS: /* field separator */ + buf->content[write++] = 0; + if (fields->count >= MAXARGS) + return; + fields->fields[++fields->count] = &buf->content[write]; + break; + case RS: /* end of line (record separator) */ + buf->content[write] = 0; + fields->count += (write > 0); + return; + case ESC: /* escaping */ + c = buf->content[read++]; + if (c != FS && c != RS && c != ESC) + buf->content[write++] = ESC; + buf->content[write++] = c; + break; + default: /* other characters */ + buf->content[write++] = c; + break; + } + } +} + +/** + * Advance pos of 'buf' until end of record RS found in buffer. + * return 1 if found or 0 if not found + */ +static +int +buf_scan_end_record( + buf_t *buf +) { + unsigned nesc; + + /* search the next RS */ + while(buf->pos < buf->count) { + if (buf->content[buf->pos] == RS) { + /* check whether RS is escaped */ + nesc = 0; + while (buf->pos > nesc && buf->content[buf->pos - (nesc + 1)] == ESC) + nesc++; + if ((nesc & 1) == 0) + return 1; /* not escaped */ + } + buf->pos++; + } + return 0; +} + +/** remove chars of 'buf' until pos */ +static +void +buf_crop( + buf_t *buf +) { + buf->count -= buf->pos; + if (buf->count) + memmove(buf->content, buf->content + buf->pos, buf->count); + buf->pos = 0; +} + +/** read input 'buf' from 'fd' */ +static +int +inbuf_read( + buf_t *buf, + int fd +) { + ssize_t szr; + int rc; + + if (buf->count == MAXBUFLEN) + return -ENOBUFS; + + do { + szr = read(fd, buf->content + buf->count, MAXBUFLEN - buf->count); + } while(szr < 0 && errno == EINTR); + if (szr >= 0) + buf->count += (unsigned)(rc = (int)szr); + else if (szr < 0) + rc = -(errno == EWOULDBLOCK ? EAGAIN : errno); + + return rc; +} + +/** + * create the prot structure in 'prot' + * Return 0 in case of success or -ENOMEM in case of error + */ +int +prot_create( + prot_t **prot +) { + prot_t *p; + + /* allocation of the structure */ + *prot = p = malloc(sizeof *p); + if (p == NULL) + return -ENOMEM; + + /* initialisation of the structure */ + prot_reset(p); + + /* terminate */ + return 0; +} + +/** + * Destroys the protocol 'prot' + */ +void +prot_destroy( + prot_t *prot +) { + free(prot); +} + +/** + * reset the protocol 'prot' + */ +void +prot_reset( + prot_t *prot +) { + /* initialisation of the structure */ + prot->inbuf.pos = prot->inbuf.count = 0; + prot->outbuf.pos = prot->outbuf.count = 0; + prot->fields.count = -1; +} + +/** + * Put protocol encoded 'count' 'fields' to the output buffer + * returns: + * - 0 on success + * - -EINVAL if the count of fields is too big + * - -ECANCELED if there is not enought space in the buffer + */ +int +prot_put( + prot_t *prot, + unsigned count, + const char **fields +) { + return buf_put_fields(&prot->outbuf, count, fields); +} + +/** + * Put protocol encoded fields until NULL found to the output buffer + * returns: + * - 0 on success + * - -EINVAL if the count of fields is too big + * - -ECANCELED if there is not enought space in the buffer + */ +int +prot_putx( + prot_t *prot, + ... +) { + const char *p, *fields[MAXARGS]; + unsigned n; + va_list l; + + va_start(l, prot); + n = 0; + p = va_arg(l, const char *); + while (p) { + if (n == MAXARGS) + return -EINVAL; + fields[n++] = p; + p = va_arg(l, const char *); + } + va_end(l); + return prot_put(prot, n, fields); +} + +/** + * Check whether write should be done or not + * Returns 1 if there is something to write or 0 otherwise + */ +int +prot_should_write( + prot_t *prot +) { + return prot->outbuf.count > 0; +} + +/** + * Write the content to write and return either the count + * of bytes written or an error code (negative). Note that + * the returned value tries to be the same as those returned + * by "man 2 write". The only exception is -ENODATA that is + * returned if there is nothing to be written. + */ +int +prot_write( + prot_t *prot, + int fdout +) { + return buf_write(&prot->outbuf, fdout); +} + +int +prot_can_read( + prot_t *prot +) { + return prot->inbuf.count < MAXBUFLEN; +} + +int +prot_read( + prot_t *prot, + int fdin +) { + return inbuf_read(&prot->inbuf, fdin); +} + +int +prot_get( + prot_t *prot, + const char ***fields +) { + if (prot->fields.count < 0) { + if (!buf_scan_end_record(&prot->inbuf)) + return -EAGAIN; + buf_get_fields(&prot->inbuf, &prot->fields); + } + if (fields) + *fields = prot->fields.fields; + return (int)prot->fields.count; +} + +void +prot_next( + prot_t *prot +) { + if (prot->fields.count >= 0) { + buf_crop(&prot->inbuf); + prot->fields.count = -1; + } +} + + |