#include "line_proto.h"
#include <string.h>
#include <stdlib.h>
#include <assert.h>
/*
* Our state
*/
struct line_proto {
/* The sock_stream we read/write with */
struct sock_stream *sock;
/* Offset of trailing data in buf */
size_t tail_offset;
/* Length of trailing data in buf, if any */
size_t tail_len;
/* Last error */
struct error_info err;
};
err_t line_proto_create (struct line_proto **lp_ptr, struct sock_stream *sock, struct error_info *err)
{
struct line_proto *lp;
// allocate
if ((lp = calloc(1, sizeof(*lp))) == NULL)
return SET_ERROR(err, ERR_CALLOC);
// store
lp->sock = sock;
// return
*lp_ptr = lp;
return SUCCESS;
}
/*
* This looks for a full '\r\n' terminated line at the beginning of the given buffer. If found, the \r\n will be
* replaced with a '\0', and the offset to the beginning of the next line returned. If not found, zero is returned
* (which is never a valid next-line offset).
*
* The given \a hint is an hint as to the offset at which to start scanning, used for incremental invocations of this
* on the same buffer.
*
*/
int _parse_line (char *buf, size_t len, size_t *hint) {
int i;
// empty buffer -> nothing
if (len == 0)
return 0;
// look for terminating '\r\n' sequence
for (i = *hint; i < len - 1; i++) {
// match this + next char
if (buf[i] == '\r' && buf[i + 1] == '\n')
break;
}
// incomplete?
if (i >= len - 1) {
*hint = len - 1;
return 0;
}
// mangle the newline off
buf[i] = '\0';
// return offset to next line
return i + 2;
}
err_t line_proto_read (struct line_proto *lp, char *buf, size_t len)
{
// offset to recv() new data into, offset to _parse_line hint, offset to next line from _parse_line
size_t recv_offset = 0, peek_offset = 0, next_offset = 0;
int ret;
// adjust offset from previous data
recv_offset = lp->tail_len;
// move trailing data from previous line to front of buffer
if (lp->tail_offset) {
// move to front
memmove(buf, buf + lp->tail_offset, lp->tail_len);
// reset
lp->tail_offset = 0;
lp->tail_len = 0;
}
// readline loop
do {
// parse any line at the beginning of the buffer
if ((next_offset = _parse_line(buf, recv_offset, &peek_offset)) > 0)
break;
// ensure there's enough space for it
assert(recv_offset < len);
// otherwise, read more data
if ((ret = sock_stream_read(lp->sock, buf + recv_offset, len - recv_offset)) < 0)
RETURN_SET_ERROR_INFO(&lp->err, sock_stream_error(lp->sock));
// EOF?
if (ret == 0)
return SET_ERROR(&lp->err, ERR_READ_EOF);
// update recv_offset
recv_offset += ret;
} while (1);
// update state for next call
lp->tail_offset = next_offset;
lp->tail_len = recv_offset - next_offset;
// ok
return SUCCESS;
}
const struct error_info* line_proto_error (struct line_proto *lp)
{
// return pointer
return &lp->err;
}