|
1 |
|
2 #include "line_proto.h" |
|
3 |
|
4 #include <string.h> |
|
5 #include <stdlib.h> |
|
6 #include <assert.h> |
|
7 |
|
8 /* |
|
9 * Our state |
|
10 */ |
|
11 struct line_proto { |
|
12 /* The sock_stream we read/write with */ |
|
13 struct sock_stream *sock; |
|
14 |
|
15 /* Offset of trailing data in buf */ |
|
16 size_t tail_offset; |
|
17 |
|
18 /* Length of trailing data in buf, if any */ |
|
19 size_t tail_len; |
|
20 |
|
21 /* Last error */ |
|
22 struct error_info err; |
|
23 }; |
|
24 |
|
25 err_t line_proto_create (struct line_proto **lp_ptr, struct sock_stream *sock, struct error_info *err) |
|
26 { |
|
27 struct line_proto *lp; |
|
28 |
|
29 // allocate |
|
30 if ((lp = calloc(1, sizeof(*lp))) == NULL) |
|
31 return SET_ERROR(err, ERR_CALLOC); |
|
32 |
|
33 // store |
|
34 lp->sock = sock; |
|
35 |
|
36 // return |
|
37 *lp_ptr = lp; |
|
38 |
|
39 return SUCCESS; |
|
40 } |
|
41 |
|
42 /* |
|
43 * This looks for a full '\r\n' terminated line at the beginning of the given buffer. If found, the \r\n will be |
|
44 * replaced with a '\0', and the offset to the beginning of the next line returned. If not found, zero is returned |
|
45 * (which is never a valid next-line offset). |
|
46 * |
|
47 * The given \a hint is an hint as to the offset at which to start scanning, used for incremental invocations of this |
|
48 * on the same buffer. |
|
49 * |
|
50 */ |
|
51 int _parse_line (char *buf, size_t len, size_t *hint) { |
|
52 int i; |
|
53 |
|
54 // empty buffer -> nothing |
|
55 if (len == 0) |
|
56 return 0; |
|
57 |
|
58 // look for terminating '\r\n' sequence |
|
59 for (i = *hint; i < len - 1; i++) { |
|
60 // match this + next char |
|
61 if (buf[i] == '\r' && buf[i + 1] == '\n') |
|
62 break; |
|
63 } |
|
64 |
|
65 // incomplete? |
|
66 if (i >= len - 1) { |
|
67 *hint = len - 1; |
|
68 return 0; |
|
69 } |
|
70 |
|
71 // mangle the newline off |
|
72 buf[i] = '\0'; |
|
73 |
|
74 // return offset to next line |
|
75 return i + 2; |
|
76 } |
|
77 |
|
78 err_t line_proto_read (struct line_proto *lp, char *buf, size_t len) |
|
79 { |
|
80 // offset to recv() new data into, offset to _parse_line hint, offset to next line from _parse_line |
|
81 size_t recv_offset = 0, peek_offset = 0, next_offset = 0; |
|
82 int ret; |
|
83 |
|
84 // adjust offset from previous data |
|
85 recv_offset = lp->tail_len; |
|
86 |
|
87 // move trailing data from previous line to front of buffer |
|
88 if (lp->tail_offset) { |
|
89 // move to front |
|
90 memmove(buf, buf + lp->tail_offset, lp->tail_len); |
|
91 |
|
92 // reset |
|
93 lp->tail_offset = 0; |
|
94 lp->tail_len = 0; |
|
95 } |
|
96 |
|
97 // readline loop |
|
98 do { |
|
99 // parse any line at the beginning of the buffer |
|
100 if ((next_offset = _parse_line(buf, recv_offset, &peek_offset)) > 0) |
|
101 break; |
|
102 |
|
103 // ensure there's enough space for it |
|
104 assert(recv_offset < len); |
|
105 |
|
106 // otherwise, read more data |
|
107 if ((ret = sock_stream_read(lp->sock, buf + recv_offset, len - recv_offset)) < 0) |
|
108 RETURN_SET_ERROR_INFO(&lp->err, sock_stream_error(lp->sock)); |
|
109 |
|
110 // EOF? |
|
111 if (ret == 0) |
|
112 return SET_ERROR(&lp->err, ERR_READ_EOF); |
|
113 |
|
114 // update recv_offset |
|
115 recv_offset += ret; |
|
116 |
|
117 } while (1); |
|
118 |
|
119 // update state for next call |
|
120 lp->tail_offset = next_offset; |
|
121 lp->tail_len = recv_offset - next_offset; |
|
122 |
|
123 // ok |
|
124 return SUCCESS; |
|
125 } |
|
126 |
|
127 const struct error_info* line_proto_error (struct line_proto *lp) |
|
128 { |
|
129 // return pointer |
|
130 return &lp->err; |
|
131 } |