Browse Source

[linebuf] Support buffering of multiple lines

Allow line buffer to accumulate multiple lines, with buffered_line()
returning each freshly-completed line as it is encountered.  This
allows buffered lines to be subsequently processed as a group.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
tags/v1.20.1
Michael Brown 8 years ago
parent
commit
1e4ff872be
4 changed files with 358 additions and 45 deletions
  1. 40
    13
      src/core/linebuf.c
  2. 6
    6
      src/include/ipxe/linebuf.h
  3. 311
    26
      src/tests/linebuf_test.c
  4. 1
    0
      src/tests/tests.c

+ 40
- 13
src/core/linebuf.c View File

@@ -43,7 +43,18 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
43 43
  * @ret line		Buffered line, or NULL if no line ready to read
44 44
  */
45 45
 char * buffered_line ( struct line_buffer *linebuf ) {
46
-	return ( linebuf->ready ? linebuf->data : NULL );
46
+	char *line = &linebuf->data[ linebuf->len ];
47
+
48
+	/* Fail unless we have a newly completed line to retrieve */
49
+	if ( ( linebuf->len == 0 ) || ( linebuf->consumed == 0 ) ||
50
+	     ( *(--line) != '\0' ) )
51
+		return NULL;
52
+
53
+	/* Identify start of line */
54
+	while ( ( line > linebuf->data ) && ( line[-1] != '\0' ) )
55
+		line--;
56
+
57
+	return line;
47 58
 }
48 59
 
49 60
 /**
@@ -52,10 +63,11 @@ char * buffered_line ( struct line_buffer *linebuf ) {
52 63
  * @v linebuf		Line buffer
53 64
  */
54 65
 void empty_line_buffer ( struct line_buffer *linebuf ) {
66
+
55 67
 	free ( linebuf->data );
56 68
 	linebuf->data = NULL;
57 69
 	linebuf->len = 0;
58
-	linebuf->ready = 0;
70
+	linebuf->consumed = 0;
59 71
 }
60 72
 
61 73
 /**
@@ -76,16 +88,13 @@ void empty_line_buffer ( struct line_buffer *linebuf ) {
76 88
  * should call empty_line_buffer() before freeing a @c struct @c
77 89
  * line_buffer.
78 90
  */
79
-ssize_t line_buffer ( struct line_buffer *linebuf,
80
-		      const char *data, size_t len ) {
91
+int line_buffer ( struct line_buffer *linebuf, const char *data, size_t len ) {
81 92
 	const char *eol;
82 93
 	size_t consume;
83 94
 	size_t new_len;
84 95
 	char *new_data;
85
-
86
-	/* Free any completed line from previous iteration */
87
-	if ( linebuf->ready )
88
-		empty_line_buffer ( linebuf );
96
+	char *lf;
97
+	char *cr;
89 98
 
90 99
 	/* Search for line terminator */
91 100
 	if ( ( eol = memchr ( data, '\n', len ) ) ) {
@@ -94,6 +103,10 @@ ssize_t line_buffer ( struct line_buffer *linebuf,
94 103
 		consume = len;
95 104
 	}
96 105
 
106
+	/* Reject any embedded NULs within the data to be consumed */
107
+	if ( memchr ( data, '\0', consume ) )
108
+		return -EINVAL;
109
+
97 110
 	/* Reallocate data buffer and copy in new data */
98 111
 	new_len = ( linebuf->len + consume );
99 112
 	new_data = realloc ( linebuf->data, ( new_len + 1 ) );
@@ -104,13 +117,27 @@ ssize_t line_buffer ( struct line_buffer *linebuf,
104 117
 	linebuf->data = new_data;
105 118
 	linebuf->len = new_len;
106 119
 
107
-	/* If we have reached end of line, trim the line and mark as ready */
120
+	/* If we have reached end of line, terminate the line */
108 121
 	if ( eol ) {
109
-		linebuf->data[--linebuf->len] = '\0'; /* trim NL */
110
-		if ( linebuf->data[linebuf->len - 1] == '\r' )
111
-			linebuf->data[--linebuf->len] = '\0'; /* trim CR */
112
-		linebuf->ready = 1;
122
+
123
+		/* Overwrite trailing LF (which must exist at this point) */
124
+		assert ( linebuf->len > 0 );
125
+		lf = &linebuf->data[ linebuf->len - 1 ];
126
+		assert ( *lf == '\n' );
127
+		*lf = '\0';
128
+
129
+		/* Trim (and overwrite) trailing CR, if present */
130
+		if ( linebuf->len > 1 ) {
131
+			cr = ( lf - 1 );
132
+			if ( *cr == '\r' ) {
133
+				linebuf->len--;
134
+				*cr = '\0';
135
+			}
136
+		}
113 137
 	}
114 138
 
139
+	/* Record consumed length */
140
+	linebuf->consumed = consume;
141
+
115 142
 	return consume;
116 143
 }

+ 6
- 6
src/include/ipxe/linebuf.h View File

@@ -14,17 +14,17 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
14 14
 
15 15
 /** A line buffer */
16 16
 struct line_buffer {
17
-	/** Current string in the buffer */
17
+	/** Data buffer */
18 18
 	char *data;
19
-	/** Length of current string, excluding the terminating NUL */
19
+	/** Length of buffered data */
20 20
 	size_t len;
21
-	/** String is ready to read */
22
-	int ready;
21
+	/** Most recently consumed length */
22
+	size_t consumed;
23 23
 };
24 24
 
25 25
 extern char * buffered_line ( struct line_buffer *linebuf );
26
-extern ssize_t line_buffer ( struct line_buffer *linebuf,
27
-			     const char *data, size_t len );
26
+extern int line_buffer ( struct line_buffer *linebuf,
27
+			 const char *data, size_t len );
28 28
 extern void empty_line_buffer ( struct line_buffer *linebuf );
29 29
 
30 30
 #endif /* _IPXE_LINEBUF_H */

+ 311
- 26
src/tests/linebuf_test.c View File

@@ -1,35 +1,320 @@
1
-#include <stdint.h>
1
+/*
2
+ * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
3
+ *
4
+ * This program is free software; you can redistribute it and/or
5
+ * modify it under the terms of the GNU General Public License as
6
+ * published by the Free Software Foundation; either version 2 of the
7
+ * License, or (at your option) any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful, but
10
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12
+ * General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License
15
+ * along with this program; if not, write to the Free Software
16
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17
+ * 02110-1301, USA.
18
+ *
19
+ * You can also choose to distribute this program under the terms of
20
+ * the Unmodified Binary Distribution Licence (as given in the file
21
+ * COPYING.UBDL), provided that you have satisfied its requirements.
22
+ */
23
+
24
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25
+
26
+/** @file
27
+ *
28
+ * Line buffer self-tests
29
+ *
30
+ */
31
+
32
+/* Forcibly enable assertions */
33
+#undef NDEBUG
34
+
2 35
 #include <string.h>
3
-#include <stdio.h>
36
+#include <assert.h>
4 37
 #include <ipxe/linebuf.h>
38
+#include <ipxe/test.h>
5 39
 
6
-static const char data1[] = 
7
-"Hello world\r\n"
8
-"This is a reasonably nice set of lines\n"
9
-"with not many different terminators\r\n\r\n"
10
-"There should be exactly one blank line above\n"
11
-"and this line should never appear at all since it has no terminator";
40
+/** Define inline raw data */
41
+#define DATA(...) { __VA_ARGS__ }
12 42
 
13
-void linebuf_test ( void ) {
14
-	struct line_buffer linebuf;
15
-	const char *data = data1;
16
-	size_t len = ( sizeof ( data1 ) - 1 /* be mean; strip the NUL */ );
17
-	ssize_t frag_len;
18
-	char *line;
19
-
20
-	memset ( &linebuf, 0, sizeof ( linebuf ) );
21
-	while ( len ) {
22
-		frag_len = line_buffer ( &linebuf, data, len );
23
-		if ( frag_len < 0 ) {
24
-			printf ( "line_buffer() failed: %s\n",
25
-				 strerror ( frag_len ) );
43
+/** Define inline lines */
44
+#define LINES(...) { __VA_ARGS__ }
45
+
46
+/** A line buffer test */
47
+struct linebuf_test {
48
+	/** Raw data */
49
+	const void *data;
50
+	/** Length of raw data */
51
+	size_t len;
52
+	/** Expected sequence of lines */
53
+	const char **lines;
54
+	/** Number of expected lines */
55
+	unsigned int count;
56
+};
57
+
58
+/** Line buffer test expected failure indicator */
59
+static const char linebuf_failure[1];
60
+
61
+/**
62
+ * Define a line buffer test
63
+ *
64
+ * @v name		Test name
65
+ * @v DATA		Raw data
66
+ * @v LINES		Expected sequence of lines
67
+ * @ret test		Line buffer test
68
+ */
69
+#define LINEBUF_TEST( name, DATA, LINES )				\
70
+	static const char name ## _data[] = DATA;			\
71
+	static const char * name ## _lines[] = LINES;			\
72
+	static struct linebuf_test name = {				\
73
+		.data = name ## _data,					\
74
+		.len = ( sizeof ( name ## _data ) - 1 /* NUL */ ),	\
75
+		.lines = name ## _lines,				\
76
+		.count = ( sizeof ( name ## _lines ) /			\
77
+			   sizeof ( name ## _lines[0] ) ),		\
78
+	}
79
+
80
+/** Simple line buffer test */
81
+LINEBUF_TEST ( simple,
82
+	       ( "HTTP/1.1 200 OK\r\n"
83
+		 "Content-Length: 123\r\n"
84
+		 "Content-Type: text/plain\r\n"
85
+		 "\r\n" ),
86
+	       LINES ( "HTTP/1.1 200 OK",
87
+		       "Content-Length: 123",
88
+		       "Content-Type: text/plain",
89
+		       "" ) );
90
+
91
+/** Mixed line terminators */
92
+LINEBUF_TEST ( mixed,
93
+	       ( "LF only\n" "CRLF\r\n" "\n" "\n" "\r\n" "\r\n" "CR only\r" ),
94
+	       LINES ( "LF only", "CRLF", "", "", "", "",
95
+		       NULL /* \r should not be treated as a terminator */ ) );
96
+
97
+/** Split consumption: part 1 */
98
+LINEBUF_TEST ( split_1,
99
+	       ( "This line was" ),
100
+	       LINES ( NULL ) );
101
+
102
+/** Split consumption: part 2 */
103
+LINEBUF_TEST ( split_2,
104
+	       ( " split across" ),
105
+	       LINES ( NULL ) );
106
+
107
+/** Split consumption: part 3 */
108
+LINEBUF_TEST ( split_3,
109
+	       ( " multiple calls\r\nand so was this one\r" ),
110
+	       LINES ( "This line was split across multiple calls", NULL ) );
111
+
112
+/** Split consumption: part 4 */
113
+LINEBUF_TEST ( split_4,
114
+	       ( "\nbut not this one\r\n" ),
115
+	       LINES ( "and so was this one", "but not this one" ) );
116
+
117
+/** Split consumption: part 5 */
118
+LINEBUF_TEST ( split_5,
119
+	       ( "" ),
120
+	       LINES ( NULL ) );
121
+
122
+/** Split consumption: part 6 */
123
+LINEBUF_TEST ( split_6,
124
+	       ( "This line came after a zero-length call\r\n" ),
125
+	       LINES ( "This line came after a zero-length call" ) );
126
+
127
+/** Embedded NULs */
128
+LINEBUF_TEST ( embedded_nuls,
129
+	       ( "This\r\ntest\r\nincludes\r\n\r\nsome\0binary\0data\r\n" ),
130
+	       LINES ( "This", "test", "includes", "", linebuf_failure ) );
131
+
132
+/**
133
+ * Report line buffer initialisation test result
134
+ *
135
+ * @v linebuf		Line buffer
136
+ * @v file		Test code file
137
+ * @v line		Test code line
138
+ */
139
+static void linebuf_init_okx ( struct line_buffer *linebuf,
140
+				const char *file, unsigned int line ) {
141
+
142
+	/* Initialise line buffer */
143
+	memset ( linebuf, 0, sizeof ( *linebuf ) );
144
+	okx ( buffered_line ( linebuf ) == NULL, file, line );
145
+}
146
+#define linebuf_init_ok( linebuf ) \
147
+	linebuf_init_okx ( linebuf, __FILE__, __LINE__ )
148
+
149
+/**
150
+ * Report line buffer consumption test result
151
+ *
152
+ * @v test		Line buffer test
153
+ * @v linebuf		Line buffer
154
+ * @v file		Test code file
155
+ * @v line		Test code line
156
+ */
157
+static void linebuf_consume_okx ( struct linebuf_test *test,
158
+				  struct line_buffer *linebuf,
159
+				  const char *file, unsigned int line ) {
160
+	const char *data = test->data;
161
+	size_t remaining = test->len;
162
+	int len;
163
+	unsigned int i;
164
+	const char *expected;
165
+	char *actual;
166
+	int rc;
167
+
168
+	DBGC ( test, "LINEBUF %p:\n", test );
169
+	DBGC_HDA ( test, 0, data, remaining );
170
+
171
+	/* Consume data one line at a time */
172
+	for ( i = 0 ; i < test->count ; i++ ) {
173
+
174
+		/* Add data to line buffer */
175
+		len = line_buffer ( linebuf, data, remaining );
176
+
177
+		/* Get buffered line, if any */
178
+		actual = buffered_line ( linebuf );
179
+		if ( len < 0 ) {
180
+			rc = len;
181
+			DBGC ( test, "LINEBUF %p %s\n", test, strerror ( rc ) );
182
+		} else if ( actual != NULL ) {
183
+			DBGC ( test, "LINEBUF %p \"%s\" (consumed %d)\n",
184
+			       test, actual, len );
185
+		} else {
186
+			DBGC ( test, "LINEBUF %p unterminated (consumed %d)\n",
187
+			       test, len );
188
+		}
189
+
190
+		/* Check for success/failure */
191
+		expected = test->lines[i];
192
+		if ( expected == linebuf_failure ) {
193
+			rc = len;
194
+			okx ( rc < 0, file, line );
195
+			okx ( remaining > 0, file, line );
26 196
 			return;
27 197
 		}
28
-		data += frag_len;
29
-		len -= frag_len;
30
-		if ( ( line = buffered_line ( &linebuf ) ) )
31
-			printf ( "\"%s\"\n", line );
198
+		okx ( len >= 0, file, line );
199
+		okx ( ( ( size_t ) len ) <= remaining, file, line );
200
+
201
+		/* Check expected result */
202
+		if ( expected == NULL ) {
203
+			okx ( actual == NULL, file, line );
204
+		} else {
205
+			okx ( actual != NULL, file, line );
206
+			okx ( strcmp ( actual, expected ) == 0, file, line );
207
+		}
208
+
209
+		/* Consume data */
210
+		data += len;
211
+		remaining -= len;
212
+	}
213
+
214
+	/* Check that all data was consumed */
215
+	okx ( remaining == 0, file, line );
216
+}
217
+#define linebuf_consume_ok( test, linebuf ) \
218
+	linebuf_consume_okx ( test, linebuf, __FILE__, __LINE__ )
219
+
220
+/**
221
+ * Report line buffer accumulation test result
222
+ *
223
+ * @v test		Line buffer test
224
+ * @v linebuf		Line buffer
225
+ * @v file		Test code file
226
+ * @v line		Test code line
227
+ */
228
+static void linebuf_accumulated_okx ( struct linebuf_test *test,
229
+				      struct line_buffer *linebuf,
230
+				      const char *file, unsigned int line ) {
231
+	const char *actual;
232
+	const char *expected;
233
+	unsigned int i;
234
+
235
+	/* Check each accumulated line */
236
+	actual = linebuf->data;
237
+	for ( i = 0 ; i < test->count ; i++ ) {
238
+
239
+		/* Check accumulated line */
240
+		okx ( actual != NULL, file, line );
241
+		okx ( actual >= linebuf->data, file, line );
242
+		expected = test->lines[i];
243
+		if ( ( expected == NULL ) || ( expected == linebuf_failure ) )
244
+			return;
245
+		okx ( strcmp ( actual, expected ) == 0, file, line );
246
+
247
+		/* Move to next line */
248
+		actual += ( strlen ( actual ) + 1 /* NUL */ );
249
+		okx ( actual <= ( linebuf->data + linebuf->len ), file, line );
32 250
 	}
251
+}
252
+#define linebuf_accumulated_ok( test, linebuf ) \
253
+	linebuf_accumulated_okx ( test, linebuf, __FILE__, __LINE__ )
254
+
255
+/**
256
+ * Report line buffer emptying test result
257
+ *
258
+ * @v linebuf		Line buffer
259
+ * @v file		Test code file
260
+ * @v line		Test code line
261
+ */
262
+static void linebuf_empty_okx ( struct line_buffer *linebuf,
263
+				const char *file, unsigned int line ) {
33 264
 
34
-	empty_line_buffer ( &linebuf );
265
+	/* Empty line buffer */
266
+	empty_line_buffer ( linebuf );
267
+	okx ( buffered_line ( linebuf ) == NULL, file, line );
35 268
 }
269
+#define linebuf_empty_ok( linebuf ) \
270
+	linebuf_empty_okx ( linebuf, __FILE__, __LINE__ )
271
+
272
+/**
273
+ * Report line buffer combined test result
274
+ *
275
+ * @v test		Line buffer test
276
+ * @v file		Test code file
277
+ * @v line		Test code line
278
+ */
279
+static void linebuf_okx ( struct linebuf_test *test, const char *file,
280
+			  unsigned int line ) {
281
+	struct line_buffer linebuf;
282
+
283
+	linebuf_init_okx ( &linebuf, file, line );
284
+	linebuf_consume_okx ( test, &linebuf, file, line );
285
+	linebuf_accumulated_okx ( test, &linebuf, file, line );
286
+	linebuf_empty_okx ( &linebuf, file, line );
287
+}
288
+#define linebuf_ok( test ) \
289
+	linebuf_okx ( test, __FILE__, __LINE__ )
290
+
291
+/**
292
+ * Perform line buffer self-tests
293
+ *
294
+ */
295
+static void linebuf_test_exec ( void ) {
296
+	struct line_buffer linebuf;
297
+
298
+	/* Basic tests */
299
+	linebuf_ok ( &simple );
300
+	linebuf_ok ( &mixed );
301
+
302
+	/* Split consumption test */
303
+	linebuf_init_ok ( &linebuf );
304
+	linebuf_consume_ok ( &split_1, &linebuf );
305
+	linebuf_consume_ok ( &split_2, &linebuf );
306
+	linebuf_consume_ok ( &split_3, &linebuf );
307
+	linebuf_consume_ok ( &split_4, &linebuf );
308
+	linebuf_consume_ok ( &split_5, &linebuf );
309
+	linebuf_consume_ok ( &split_6, &linebuf );
310
+	linebuf_empty_ok ( &linebuf );
311
+
312
+	/* Embedded NULs */
313
+	linebuf_ok ( &embedded_nuls );
314
+}
315
+
316
+/** Line buffer self-test */
317
+struct self_test linebuf_test __self_test = {
318
+	.name = "linebuf",
319
+	.exec = linebuf_test_exec,
320
+};

+ 1
- 0
src/tests/tests.c View File

@@ -66,3 +66,4 @@ REQUIRE_OBJECT ( uri_test );
66 66
 REQUIRE_OBJECT ( profile_test );
67 67
 REQUIRE_OBJECT ( setjmp_test );
68 68
 REQUIRE_OBJECT ( pccrc_test );
69
+REQUIRE_OBJECT ( linebuf_test );

Loading…
Cancel
Save