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