blob: f7ee48913ff0ff129d0e62528ec82091e0a1e1b0 [file] [log] [blame]
Amaury Denoyelle1b5f77f2022-05-09 09:37:27 +02001#include <haproxy/ncbuf.h>
2
Amaury Denoyelle077e0962022-05-09 09:43:11 +02003#include <string.h>
4
5#ifndef MIN
6#define MIN(a, b) (((a) < (b)) ? (a) : (b))
7#endif
8
Amaury Denoyelleeeeeed42022-05-04 16:51:19 +02009#ifdef STANDALONE
10#include <stdarg.h>
11#include <stdlib.h>
12#include <stdio.h>
13#include <unistd.h>
14
15#include <haproxy/list.h>
16#endif /* STANDALONE */
17
Amaury Denoyelle1b5f77f2022-05-09 09:37:27 +020018#ifdef DEBUG_DEV
19# include <haproxy/bug.h>
20#else
21# include <stdio.h>
22# include <stdlib.h>
23
24# undef BUG_ON
25# define BUG_ON(x) if (x) { fprintf(stderr, "CRASH ON %s:%d\n", __func__, __LINE__); abort(); }
26
27# undef BUG_ON_HOT
28# define BUG_ON_HOT(x) if (x) { fprintf(stderr, "CRASH ON %s:%d\n", __func__, __LINE__); abort(); }
29#endif /* DEBUG_DEV */
30
31/* ******** internal API ******** */
32
Amaury Denoyelled5d2ed92022-05-09 09:38:45 +020033#define NCB_BLK_NULL ((struct ncb_blk){ .st = NULL })
34
35#define NCB_BK_F_GAP 0x01 /* block represents a gap */
Amaury Denoyelleedeb0a62022-05-04 16:16:39 +020036#define NCB_BK_F_FIN 0x02 /* special reduced gap present at the end of the buffer */
Amaury Denoyelled5d2ed92022-05-09 09:38:45 +020037struct ncb_blk {
38 char *st; /* first byte of the block */
39 char *end; /* first byte after this block */
40
Amaury Denoyelleedeb0a62022-05-04 16:16:39 +020041 char *sz_ptr; /* pointer to size element - NULL for reduced gap */
Amaury Denoyelled5d2ed92022-05-09 09:38:45 +020042 ncb_sz_t sz; /* size of the block */
Amaury Denoyelleedeb0a62022-05-04 16:16:39 +020043 ncb_sz_t sz_data; /* size of the data following the block - invalid for reduced GAP */
Amaury Denoyelled5d2ed92022-05-09 09:38:45 +020044 ncb_sz_t off; /* offset of block in buffer */
45
46 char flag;
47};
48
Amaury Denoyelle1b5f77f2022-05-09 09:37:27 +020049/* Return pointer to <off> relative to <buf> head. Support buffer wrapping. */
50static char *ncb_peek(const struct ncbuf *buf, ncb_sz_t off)
51{
52 char *ptr = ncb_head(buf) + off;
53 if (ptr >= buf->area + buf->size)
54 ptr -= buf->size;
55 return ptr;
56}
57
58/* Returns the reserved space of <buf> which contains the size of the first
59 * data block.
60 */
61static char *ncb_reserved(const struct ncbuf *buf)
62{
63 return ncb_peek(buf, buf->size - NCB_RESERVED_SZ);
64}
65
66/* Encode <off> at <st> position in <buf>. Support wrapping. */
67static void ncb_write_off(const struct ncbuf *buf, char *st, ncb_sz_t off)
68{
69 int i;
70
71 BUG_ON_HOT(st >= buf->area + buf->size);
72
73 for (i = 0; i < sizeof(ncb_sz_t); ++i) {
74 (*st) = off >> (8 * i) & 0xff;
75
76 if ((++st) == ncb_wrap(buf))
77 st = ncb_orig(buf);
78 }
79}
80
81/* Decode offset stored at <st> position in <buf>. Support wrapping. */
82static ncb_sz_t ncb_read_off(const struct ncbuf *buf, char *st)
83{
84 int i;
85 ncb_sz_t off = 0;
86
87 BUG_ON_HOT(st >= buf->area + buf->size);
88
89 for (i = 0; i < sizeof(ncb_sz_t); ++i) {
90 off |= (unsigned char )(*st) << (8 * i);
91
92 if ((++st) == ncb_wrap(buf))
93 st = ncb_orig(buf);
94 }
95
96 return off;
97}
98
Amaury Denoyelle077e0962022-05-09 09:43:11 +020099/* Add <off> to the offset stored at <st> in <buf>. Support wrapping. */
100static void ncb_inc_off(const struct ncbuf *buf, char *st, ncb_sz_t off)
101{
102 const ncb_sz_t old = ncb_read_off(buf, st);
103 ncb_write_off(buf, st, old + off);
104}
105
Amaury Denoyelleedeb0a62022-05-04 16:16:39 +0200106/* Returns true if a gap cannot be inserted at <off> : a reduced gap must be used. */
107static int ncb_off_reduced(const struct ncbuf *b, ncb_sz_t off)
108{
109 return off + NCB_GAP_MIN_SZ > ncb_size(b);
110}
111
Amaury Denoyelled5d2ed92022-05-09 09:38:45 +0200112/* Returns true if <blk> is the special NULL block. */
113static int ncb_blk_is_null(const struct ncb_blk blk)
114{
115 return !blk.st;
116}
117
118/* Returns true if <blk> is the last block of <buf>. */
119static int ncb_blk_is_last(const struct ncbuf *buf, const struct ncb_blk blk)
120{
121 BUG_ON_HOT(blk.off + blk.sz > ncb_size(buf));
122 return blk.off + blk.sz == ncb_size(buf);
123}
124
125/* Returns the first block of <buf> which is always a DATA. */
126static struct ncb_blk ncb_blk_first(const struct ncbuf *buf)
127{
128 struct ncb_blk blk;
129
130 blk.st = ncb_head(buf);
131
132 blk.sz_ptr = ncb_reserved(buf);
133 blk.sz = ncb_read_off(buf, ncb_reserved(buf));
134 BUG_ON_HOT(blk.sz > ncb_size(buf));
135
136 blk.end = ncb_peek(buf, blk.sz);
137 blk.off = 0;
138 blk.flag = 0;
139
140 return blk;
141}
142
143/* Returns the block following <prev> in the buffer <buf>. */
144static struct ncb_blk ncb_blk_next(const struct ncbuf *buf,
145 const struct ncb_blk prev)
146{
147 struct ncb_blk blk;
148
149 BUG_ON_HOT(ncb_blk_is_null(prev));
150
151 if (ncb_blk_is_last(buf, prev))
152 return NCB_BLK_NULL;
153
154 blk.st = prev.end;
155 blk.off = prev.off + prev.sz;
156 blk.flag = ~prev.flag & NCB_BK_F_GAP;
157
158 if (blk.flag & NCB_BK_F_GAP) {
Amaury Denoyelleedeb0a62022-05-04 16:16:39 +0200159 if (ncb_off_reduced(buf, blk.off)) {
160 blk.flag |= NCB_BK_F_FIN;
161 blk.sz_ptr = NULL;
162 blk.sz = ncb_size(buf) - blk.off;
163 blk.sz_data = 0;
164
165 /* A reduced gap can only be the last block. */
166 BUG_ON_HOT(!ncb_blk_is_last(buf, blk));
167 }
168 else {
169 blk.sz_ptr = ncb_peek(buf, blk.off + NCB_GAP_SZ_OFF);
170 blk.sz = ncb_read_off(buf, blk.sz_ptr);
171 blk.sz_data = ncb_read_off(buf, ncb_peek(buf, blk.off + NCB_GAP_SZ_DATA_OFF));
172 BUG_ON_HOT(blk.sz < NCB_GAP_MIN_SZ);
173 }
Amaury Denoyelled5d2ed92022-05-09 09:38:45 +0200174 }
175 else {
176 blk.sz_ptr = ncb_peek(buf, prev.off + NCB_GAP_SZ_DATA_OFF);
177 blk.sz = prev.sz_data;
178 blk.sz_data = 0;
179
180 /* only first DATA block can be empty. If this happens, a GAP
181 * merge should have been realized.
182 */
183 BUG_ON_HOT(!blk.sz);
184 }
185
186 BUG_ON_HOT(blk.off + blk.sz > ncb_size(buf));
187 blk.end = ncb_peek(buf, blk.off + blk.sz);
188
189 return blk;
190}
191
192/* Returns the block containing offset <off>. Note that if <off> is at the
193 * frontier between two blocks, this function will return the preceding one.
194 * This is done to easily merge blocks on insertion/deletion.
195 */
196static struct ncb_blk ncb_blk_find(const struct ncbuf *buf, ncb_sz_t off)
197{
198 struct ncb_blk blk;
199
200 BUG_ON_HOT(off >= ncb_size(buf));
201
202 for (blk = ncb_blk_first(buf); off > blk.off + blk.sz;
203 blk = ncb_blk_next(buf, blk)) {
204 }
205
206 return blk;
207}
208
209/* Transform absolute offset <off> to a relative one from <blk> start. */
210static ncb_sz_t ncb_blk_off(const struct ncb_blk blk, ncb_sz_t off)
211{
212 BUG_ON_HOT(off < blk.off || off > blk.off + blk.sz);
213 BUG_ON_HOT(off - blk.off > blk.sz);
214 return off - blk.off;
215}
216
Amaury Denoyelle077e0962022-05-09 09:43:11 +0200217/* Simulate insertion in <buf> of <data> of length <len> at offset <off>. This
218 * ensures that minimal block size are respected for newly formed gaps. <blk>
Amaury Denoyelleb830f0d2022-05-09 11:59:15 +0200219 * must be the block where the insert operation begins. If <mode> is
220 * NCB_ADD_COMPARE, old and new overlapped data are compared to validate the
221 * insertion.
Amaury Denoyelle077e0962022-05-09 09:43:11 +0200222 *
223 * Returns NCB_RET_OK if insertion can proceed.
224 */
225static enum ncb_ret ncb_check_insert(const struct ncbuf *buf,
226 struct ncb_blk blk, ncb_sz_t off,
Amaury Denoyelleb830f0d2022-05-09 11:59:15 +0200227 const char *data, ncb_sz_t len,
228 enum ncb_add_mode mode)
Amaury Denoyelle077e0962022-05-09 09:43:11 +0200229{
230 ncb_sz_t off_blk = ncb_blk_off(blk, off);
231 ncb_sz_t to_copy;
232 ncb_sz_t left = len;
233
234 /* If insertion starts in a gap, it must leave enough space to keep the
235 * gap header.
236 */
237 if (left && (blk.flag & NCB_BK_F_GAP)) {
238 if (off_blk < NCB_GAP_MIN_SZ)
239 return NCB_RET_GAP_SIZE;
240 }
241
242 while (left) {
243 off_blk = ncb_blk_off(blk, off);
244 to_copy = MIN(left, blk.sz - off_blk);
245
246 if (blk.flag & NCB_BK_F_GAP && off_blk + to_copy < blk.sz) {
247 /* Insertion must leave enough space for a new gap
248 * header if stopped in a middle of a gap.
249 */
250 const ncb_sz_t gap_sz = blk.sz - (off_blk + to_copy);
251 if (gap_sz < NCB_GAP_MIN_SZ && !ncb_blk_is_last(buf, blk))
252 return NCB_RET_GAP_SIZE;
253 }
Amaury Denoyelleb830f0d2022-05-09 11:59:15 +0200254 else if (!(blk.flag & NCB_BK_F_GAP) && mode == NCB_ADD_COMPARE) {
255 /* Compare memory of data block in NCB_ADD_COMPARE mode. */
256 const ncb_sz_t off_blk = ncb_blk_off(blk, off);
257 char *st = ncb_peek(buf, off);
258
259 to_copy = MIN(left, blk.sz - off_blk);
260 if (st + to_copy > ncb_wrap(buf)) {
261 const ncb_sz_t sz1 = ncb_wrap(buf) - st;
262 if (memcmp(st, data, sz1))
263 return NCB_RET_DATA_REJ;
264 if (memcmp(ncb_orig(buf), data + sz1, to_copy - sz1))
265 return NCB_RET_DATA_REJ;
266 }
267 else {
268 if (memcmp(st, data, to_copy))
269 return NCB_RET_DATA_REJ;
270 }
271 }
Amaury Denoyelle077e0962022-05-09 09:43:11 +0200272
273 left -= to_copy;
274 data += to_copy;
275 off += to_copy;
276
277 blk = ncb_blk_next(buf, blk);
278 }
279
280 return NCB_RET_OK;
281}
282
283/* Fill new <data> of length <len> inside an already existing data <blk> at
284 * offset <off>. Offset is relative to <blk> so it cannot be greater than the
Amaury Denoyelleb830f0d2022-05-09 11:59:15 +0200285 * block size. <mode> specifies if old data are preserved or overwritten.
Amaury Denoyelle077e0962022-05-09 09:43:11 +0200286 */
287static ncb_sz_t ncb_fill_data_blk(const struct ncbuf *buf,
288 struct ncb_blk blk, ncb_sz_t off,
Amaury Denoyelleb830f0d2022-05-09 11:59:15 +0200289 const char *data, ncb_sz_t len,
290 enum ncb_add_mode mode)
Amaury Denoyelle077e0962022-05-09 09:43:11 +0200291{
292 const ncb_sz_t to_copy = MIN(len, blk.sz - off);
Amaury Denoyelleb830f0d2022-05-09 11:59:15 +0200293 char *ptr = NULL;
Amaury Denoyelle077e0962022-05-09 09:43:11 +0200294
295 BUG_ON_HOT(off > blk.sz);
296 /* This can happens due to previous ncb_blk_find() usage. In this
297 * case the current fill is a noop.
298 */
299 if (off == blk.sz)
300 return 0;
301
Amaury Denoyelleb830f0d2022-05-09 11:59:15 +0200302 if (mode == NCB_ADD_OVERWRT) {
303 ptr = ncb_peek(buf, blk.off + off);
304
305 if (ptr + to_copy >= ncb_wrap(buf)) {
306 const ncb_sz_t sz1 = ncb_wrap(buf) - ptr;
307 memcpy(ptr, data, sz1);
308 memcpy(ncb_orig(buf), data + sz1, to_copy - sz1);
309 }
310 else {
311 memcpy(ptr, data, to_copy);
312 }
313 }
314
Amaury Denoyelle077e0962022-05-09 09:43:11 +0200315 return to_copy;
316}
317
318/* Fill the gap <blk> starting at <off> with new <data> of length <len>. <off>
319 * is relative to <blk> so it cannot be greater than the block size.
320 */
321static ncb_sz_t ncb_fill_gap_blk(const struct ncbuf *buf,
322 struct ncb_blk blk, ncb_sz_t off,
323 const char *data, ncb_sz_t len)
324{
325 const ncb_sz_t to_copy = MIN(len, blk.sz - off);
326 char *ptr;
327
328 BUG_ON_HOT(off > blk.sz);
329 /* This can happens due to previous ncb_blk_find() usage. In this
330 * case the current fill is a noop.
331 */
332 if (off == blk.sz)
333 return 0;
334
335 /* A new gap must be created if insertion stopped before gap end. */
336 if (off + to_copy < blk.sz) {
337 const ncb_sz_t gap_off = blk.off + off + to_copy;
338 const ncb_sz_t gap_sz = blk.sz - off - to_copy;
339
340 BUG_ON_HOT(!ncb_off_reduced(buf, gap_off) &&
341 blk.off + blk.sz - gap_off < NCB_GAP_MIN_SZ);
342
343 /* write the new gap header unless this is a reduced gap. */
344 if (!ncb_off_reduced(buf, gap_off)) {
345 char *gap_ptr = ncb_peek(buf, gap_off + NCB_GAP_SZ_OFF);
346 char *gap_data_ptr = ncb_peek(buf, gap_off + NCB_GAP_SZ_DATA_OFF);
347
348 ncb_write_off(buf, gap_ptr, gap_sz);
349 ncb_write_off(buf, gap_data_ptr, blk.sz_data);
350 }
351 }
352
353 /* fill the gap with new data */
354 ptr = ncb_peek(buf, blk.off + off);
355 if (ptr + to_copy >= ncb_wrap(buf)) {
356 ncb_sz_t sz1 = ncb_wrap(buf) - ptr;
357 memcpy(ptr, data, sz1);
358 memcpy(ncb_orig(buf), data + sz1, to_copy - sz1);
359 }
360 else {
361 memcpy(ptr, data, to_copy);
362 }
363
364 return to_copy;
365}
366
Amaury Denoyelle1b5f77f2022-05-09 09:37:27 +0200367/* ******** public API ******** */
368
369int ncb_is_null(const struct ncbuf *buf)
370{
371 return buf->size == 0;
372}
373
374/* Initialize or reset <buf> by clearing all data. Its size is untouched.
375 * Buffer is positioned to <head> offset. Use 0 to realign it.
376 */
377void ncb_init(struct ncbuf *buf, ncb_sz_t head)
378{
379 BUG_ON_HOT(head >= buf->size);
380 buf->head = head;
381
382 ncb_write_off(buf, ncb_reserved(buf), 0);
383 ncb_write_off(buf, ncb_head(buf), ncb_size(buf));
384 ncb_write_off(buf, ncb_peek(buf, sizeof(ncb_sz_t)), 0);
385}
386
387/* Construct a ncbuf with all its parameters. */
388struct ncbuf ncb_make(char *area, ncb_sz_t size, ncb_sz_t head)
389{
390 struct ncbuf buf;
391
392 /* Ensure that there is enough space for the reserved space and data.
393 * This is the minimal value to not crash later.
394 */
395 BUG_ON_HOT(size <= NCB_RESERVED_SZ);
396
397 buf.area = area;
398 buf.size = size;
399 buf.head = head;
400
401 return buf;
402}
403
404/* Returns start of allocated buffer area. */
405char *ncb_orig(const struct ncbuf *buf)
406{
407 return buf->area;
408}
409
410/* Returns current head pointer into buffer area. */
411char *ncb_head(const struct ncbuf *buf)
412{
413 return buf->area + buf->head;
414}
415
416/* Returns the first byte after the allocated buffer area. */
417char *ncb_wrap(const struct ncbuf *buf)
418{
419 return buf->area + buf->size;
420}
421
422/* Returns the usable size of <buf> for data storage. This is the size of the
423 * allocated buffer without the reserved header space.
424 */
425ncb_sz_t ncb_size(const struct ncbuf *buf)
426{
427 return buf->size - NCB_RESERVED_SZ;
428}
429
Amaury Denoyelled5d2ed92022-05-09 09:38:45 +0200430/* Returns the total number of bytes stored in whole <buf>. */
431ncb_sz_t ncb_total_data(const struct ncbuf *buf)
432{
433 struct ncb_blk blk;
434 int total = 0;
435
436 for (blk = ncb_blk_first(buf); !ncb_blk_is_null(blk); blk = ncb_blk_next(buf, blk)) {
437 if (!(blk.flag & NCB_BK_F_GAP))
438 total += blk.sz;
439 }
440
441 return total;
442}
443
Amaury Denoyelle1b5f77f2022-05-09 09:37:27 +0200444/* Returns true if there is no data anywhere in <buf>. */
445int ncb_is_empty(const struct ncbuf *buf)
446{
447 BUG_ON_HOT(*ncb_reserved(buf) + *ncb_head(buf) > ncb_size(buf));
448 return *ncb_reserved(buf) == 0 && *ncb_head(buf) == ncb_size(buf);
449}
450
451/* Returns true if no more data can be inserted in <buf>. */
452int ncb_is_full(const struct ncbuf *buf)
453{
454 BUG_ON_HOT(ncb_read_off(buf, ncb_reserved(buf)) > ncb_size(buf));
455 return ncb_read_off(buf, ncb_reserved(buf)) == ncb_size(buf);
456}
Amaury Denoyelled5d2ed92022-05-09 09:38:45 +0200457
458/* Returns the number of bytes of data avaiable in <buf> starting at offset
459 * <off> until the next gap or the buffer end. The counted data may wrapped if
460 * the buffer storage is not aligned.
461 */
462ncb_sz_t ncb_data(const struct ncbuf *buf, ncb_sz_t off)
463{
464 struct ncb_blk blk = ncb_blk_find(buf, off);
465 ncb_sz_t off_blk = ncb_blk_off(blk, off);
466
467 /* if <off> at the frontier between two and <blk> is gap, retrieve the
468 * next data block.
469 */
470 if (blk.flag & NCB_BK_F_GAP && off_blk == blk.sz &&
471 !ncb_blk_is_last(buf, blk)) {
472 blk = ncb_blk_next(buf, blk);
473 off_blk = ncb_blk_off(blk, off);
474 }
475
476 if (blk.flag & NCB_BK_F_GAP)
477 return 0;
478
479 return blk.sz - off_blk;
480}
Amaury Denoyelle077e0962022-05-09 09:43:11 +0200481
482/* Add a new block at <data> of size <len> in <buf> at offset <off>.
483 *
484 * Returns NCB_RET_OK on success. On error the following codes are returned :
485 * - NCB_RET_GAP_SIZE : cannot add data because the gap formed is too small
Amaury Denoyelleb830f0d2022-05-09 11:59:15 +0200486 * - NCB_RET_DATA_REJ : old data would be overwritten by different ones in
487 * NCB_ADD_COMPARE mode.
Amaury Denoyelle077e0962022-05-09 09:43:11 +0200488 */
489enum ncb_ret ncb_add(struct ncbuf *buf, ncb_sz_t off,
Amaury Denoyelleb830f0d2022-05-09 11:59:15 +0200490 const char *data, ncb_sz_t len, enum ncb_add_mode mode)
Amaury Denoyelle077e0962022-05-09 09:43:11 +0200491{
492 struct ncb_blk blk;
493 ncb_sz_t left = len;
494 enum ncb_ret ret;
495 char *new_sz;
496
497 if (!len)
498 return NCB_RET_OK;
499
500 BUG_ON_HOT(off + len > ncb_size(buf));
501
502 /* Get block where insertion begins. */
503 blk = ncb_blk_find(buf, off);
504
505 /* Check if insertion is possible. */
Amaury Denoyelleb830f0d2022-05-09 11:59:15 +0200506 ret = ncb_check_insert(buf, blk, off, data, len, mode);
Amaury Denoyelle077e0962022-05-09 09:43:11 +0200507 if (ret != NCB_RET_OK)
508 return ret;
509
510 if (blk.flag & NCB_BK_F_GAP) {
511 /* Reduce gap size if insertion begins in a gap. Gap data size
512 * is reset and will be recalculated during insertion.
513 */
514 const ncb_sz_t gap_sz = off - blk.off;
515 BUG_ON_HOT(gap_sz < NCB_GAP_MIN_SZ);
516
517 /* pointer to data size to increase. */
518 new_sz = ncb_peek(buf, blk.off + NCB_GAP_SZ_DATA_OFF);
519
520 ncb_write_off(buf, blk.sz_ptr, gap_sz);
521 ncb_write_off(buf, new_sz, 0);
522 }
523 else {
524 /* pointer to data size to increase. */
525 new_sz = blk.sz_ptr;
526 }
527
528 /* insert data */
529 while (left) {
530 struct ncb_blk next;
531 const ncb_sz_t off_blk = ncb_blk_off(blk, off);
532 ncb_sz_t done;
533
534 /* retrieve the next block. This is necessary to do this
535 * before overwritting a gap.
536 */
537 next = ncb_blk_next(buf, blk);
538
539 if (blk.flag & NCB_BK_F_GAP) {
540 done = ncb_fill_gap_blk(buf, blk, off_blk, data, left);
541
542 /* update the inserted data block size */
543 if (off + done == blk.off + blk.sz) {
544 /* merge next data block if insertion reached gap end */
545 ncb_inc_off(buf, new_sz, done + blk.sz_data);
546 }
547 else {
548 /* insertion stopped before gap end */
549 ncb_inc_off(buf, new_sz, done);
550 }
551 }
552 else {
Amaury Denoyelleb830f0d2022-05-09 11:59:15 +0200553 done = ncb_fill_data_blk(buf, blk, off_blk, data, left, mode);
Amaury Denoyelle077e0962022-05-09 09:43:11 +0200554 }
555
556 BUG_ON_HOT(done > blk.sz || done > left);
557 left -= done;
558 data += done;
559 off += done;
560
561 blk = next;
562 }
563
564 return NCB_RET_OK;
565}
Amaury Denoyelledf25acf2022-05-04 16:47:09 +0200566
567/* Advance the head of <buf> to the offset <off>. Data at the start of buffer
568 * will be lost while some space will be formed at the end to be able to insert
569 * new data.
570 *
571 * Returns NCB_RET_OK on success.
572 */
573enum ncb_ret ncb_advance(struct ncbuf *buf, ncb_sz_t off)
574{
575 struct ncb_blk blk, last;
576 ncb_sz_t off_blk;
577 ncb_sz_t first_data_sz;
578
579 BUG_ON_HOT(off > ncb_size(buf));
580 if (!off)
581 return NCB_RET_OK;
582
583 /* Special case if off is full size. This is equivalent to a reset. */
584 if (off == ncb_size(buf)) {
585 ncb_init(buf, buf->head);
586 return NCB_RET_OK;
587 }
588
589 last = blk = ncb_blk_find(buf, off);
590 while (!ncb_blk_is_last(buf, last))
591 last = ncb_blk_next(buf, last);
592
593 off_blk = ncb_blk_off(blk, off);
594
595 /* If new head points in a GAP, the GAP size must be big enough. */
596 if (blk.flag & NCB_BK_F_GAP) {
597 if (blk.sz == off_blk) {
598 /* GAP si completely removed. */
599 first_data_sz = blk.sz_data;
600 }
601 else if (!ncb_blk_is_last(buf, blk) &&
602 blk.sz - off_blk < NCB_GAP_MIN_SZ) {
603 return NCB_RET_GAP_SIZE;
604 }
605 else {
606 /* A GAP will be present at the front. */
607 first_data_sz = 0;
608 }
609 }
610 else {
611 /* If off_blk less than blk.sz, the data block will becomes the
612 * first block. If equal, the data block is completely removed
613 * and thus the following GAP will be the first block.
614 */
615 first_data_sz = blk.sz - off_blk;
616 }
617
618 /* Insert a new GAP if :
619 * - last block is DATA
620 * - last block is GAP and but is not the same as blk
621 *
622 * In the the of last block is a GAP and is the same as blk, it means
623 * that a GAP will be formed to recover the whole buffer content.
624 */
625 if (last.flag & NCB_BK_F_GAP && !ncb_blk_is_last(buf, blk)) {
626 /* last block is a GAP : extends it unless this is a reduced
627 * gap and the new gap size is still not big enough.
628 */
629 if (!(last.flag & NCB_BK_F_FIN) || last.sz + off >= NCB_GAP_MIN_SZ) {
630 /* use .st instead of .sz_ptr which can be NULL if reduced gap */
631 ncb_write_off(buf, last.st, last.sz + off);
632 ncb_write_off(buf, ncb_peek(buf, last.off + NCB_GAP_SZ_DATA_OFF), 0);
633 }
634 }
635 else if (!(last.flag & NCB_BK_F_GAP)) {
636 /* last block DATA : insert a new gap after the deleted data.
637 * If the gap is not big enough, it will be a reduced gap.
638 */
639 if (off >= NCB_GAP_MIN_SZ) {
640 ncb_write_off(buf, ncb_peek(buf, last.off + last.sz + NCB_GAP_SZ_OFF), off);
641 ncb_write_off(buf, ncb_peek(buf, last.off + last.sz + NCB_GAP_SZ_DATA_OFF), 0);
642 }
643 }
644
645 /* Advance head and update the buffer reserved header which contains
646 * the first data block size.
647 */
648 buf->head += off;
649 if (buf->head >= buf->size)
650 buf->head -= buf->size;
651 ncb_write_off(buf, ncb_reserved(buf), first_data_sz);
652
653 /* Update the first block GAP size if needed. */
654 if (blk.flag & NCB_BK_F_GAP && !first_data_sz) {
655 /* If first block GAP is also last one, cover whole buf. */
656 if (ncb_blk_is_last(buf, blk))
657 ncb_write_off(buf, ncb_head(buf), ncb_size(buf));
658 else
659 ncb_write_off(buf, ncb_head(buf), blk.sz - off_blk);
660
661 /* Recopy the block sz_data at the new position. */
662 ncb_write_off(buf, ncb_peek(buf, NCB_GAP_SZ_DATA_OFF), blk.sz_data);
663 }
664
665 return NCB_RET_OK;
666}
Amaury Denoyelleeeeeed42022-05-04 16:51:19 +0200667
668/* ******** testing API ******** */
669/* To build it :
670 * gcc -DSTANDALONE -lasan -I./include -o ncbuf src/ncbuf.c
671 */
672#ifdef STANDALONE
673
674int ncb_print = 0;
675
676static void ncbuf_printf(char *str, ...)
677{
678 va_list args;
679
680 va_start(args, str);
681 if (ncb_print)
682 vfprintf(stderr, str, args);
683 va_end(args);
684}
685
686struct rand_off {
687 struct list el;
688 ncb_sz_t off;
689 ncb_sz_t len;
690};
691
692static struct rand_off *ncb_generate_rand_off(const struct ncbuf *buf)
693{
694 struct rand_off *roff;
695 roff = calloc(1, sizeof(struct rand_off));
696 BUG_ON(!roff);
697
698 roff->off = rand() % (ncb_size(buf));
699 if (roff->off > 0 && roff->off < NCB_GAP_MIN_SZ)
700 roff->off = 0;
701
702 roff->len = rand() % (ncb_size(buf) - roff->off + 1);
703
704 return roff;
705}
706
707static void ncb_print_blk(const struct ncb_blk blk)
708{
709 if (ncb_print) {
710 fprintf(stderr, "%s(%s): %2zu/%zu.\n",
711 blk.flag & NCB_BK_F_GAP ? "GAP " : "DATA",
712 blk.flag & NCB_BK_F_FIN ? "F" : "-", blk.off, blk.sz);
713 }
714}
715
716static inline int ncb_is_null_blk(const struct ncb_blk blk)
717{
718 return !blk.st;
719}
720
721static void ncb_loop(const struct ncbuf *buf)
722{
723 struct ncb_blk blk;
724
725 blk = ncb_blk_first(buf);
726 do {
727 ncb_print_blk(blk);
728 blk = ncb_blk_next(buf, blk);
729 } while (!ncb_is_null_blk(blk));
730
731 ncbuf_printf("\n");
732}
733
734static void ncbuf_print_buf(struct ncbuf *b, ncb_sz_t len,
735 unsigned char *area, int line)
736{
737 int i;
738
739 ncbuf_printf("buffer status at line %d\n", line);
740 for (i = 0; i < len; ++i) {
741 ncbuf_printf("%02x.", area[i]);
742 if (i && i % 32 == 31) ncbuf_printf("\n");
743 else if (i && i % 8 == 7) ncbuf_printf(" ");
744 }
745 ncbuf_printf("\n");
746
747 ncb_loop(b);
748
749 if (ncb_print)
750 getchar();
751}
752
753static struct ncbuf b;
754static unsigned char *bufarea = NULL;
755static ncb_sz_t bufsize = 16384;
756static ncb_sz_t bufhead = 15;
757
758#define NCB_INIT(buf) \
759 if ((reset)) { memset(bufarea, 0xaa, bufsize); } \
760 ncb_init(buf, bufhead); \
761 ncbuf_print_buf(&b, bufsize, bufarea, __LINE__);
762
763#define NCB_ADD_EQ(buf, off, data, sz, mode, ret) \
764 BUG_ON(ncb_add((buf), (off), (data), (sz), (mode)) != (ret)); \
765 ncbuf_print_buf(buf, bufsize, bufarea, __LINE__);
766
767#define NCB_ADD_NEQ(buf, off, data, sz, mode, ret) \
768 BUG_ON(ncb_add((buf), (off), (data), (sz), (mode)) == (ret)); \
769 ncbuf_print_buf(buf, bufsize, bufarea, __LINE__);
770
771#define NCB_ADVANCE_EQ(buf, off, ret) \
772 BUG_ON(ncb_advance((buf), (off)) != (ret)); \
773 ncbuf_print_buf(buf, bufsize, bufarea, __LINE__);
774
775#define NCB_TOTAL_DATA_EQ(buf, data) \
776 BUG_ON(ncb_total_data((buf)) != (data));
777
778#define NCB_DATA_EQ(buf, off, data) \
779 BUG_ON(ncb_data((buf), (off)) != (data));
780
781static int ncbuf_test(ncb_sz_t head, int reset, int print_delay)
782{
783 char *data0, data1[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f };
784 struct list list = LIST_HEAD_INIT(list);
785 struct rand_off *roff, *roff_tmp;
786 enum ncb_ret ret;
787
788 data0 = malloc(bufsize);
789 memset(data0, 0xff, bufsize);
790
791 bufarea = malloc(bufsize);
792
793 b.area = bufarea;
794 b.size = bufsize;
795 b.head = head;
796 NCB_INIT(&b);
797
798 fprintf(stderr, "running unit tests\n");
799
800 /* insertion test suite */
801 NCB_INIT(&b);
802 NCB_DATA_EQ(&b, 0, 0); NCB_DATA_EQ(&b, bufsize - NCB_RESERVED_SZ - 1, 0); /* first and last offset */
803 NCB_ADD_EQ(&b, 24, data0, 9, NCB_ADD_PRESERVE, NCB_RET_OK); NCB_DATA_EQ(&b, 24, 9);
804 /* insert new data at the same offset as old */
805 NCB_ADD_EQ(&b, 24, data0, 16, NCB_ADD_PRESERVE, NCB_RET_OK); NCB_DATA_EQ(&b, 24, 16);
806
807 NCB_INIT(&b); NCB_DATA_EQ(&b, 0, 0);
808 NCB_ADD_EQ(&b, 0, data0, 16, NCB_ADD_PRESERVE, NCB_RET_OK); NCB_DATA_EQ(&b, 0, 16);
809 NCB_ADD_EQ(&b, 24, data0, 16, NCB_ADD_PRESERVE, NCB_RET_OK); NCB_DATA_EQ(&b, 0, 16);
810 /* insert data overlapping two data blocks and a gap */
811 NCB_ADD_EQ(&b, 12, data0, 16, NCB_ADD_PRESERVE, NCB_RET_OK); NCB_DATA_EQ(&b, 0, 40);
812
813 NCB_INIT(&b);
814 NCB_ADD_EQ(&b, 32, data0, 16, NCB_ADD_PRESERVE, NCB_RET_OK); NCB_DATA_EQ(&b, 0, 0); NCB_DATA_EQ(&b, 16, 0); NCB_DATA_EQ(&b, 32, 16);
815 NCB_ADD_EQ(&b, 0, data0, 16, NCB_ADD_PRESERVE, NCB_RET_OK); NCB_DATA_EQ(&b, 0, 16); NCB_DATA_EQ(&b, 16, 0); NCB_DATA_EQ(&b, 32, 16);
816 /* insert data to exactly cover a gap between two data blocks */
817 NCB_ADD_EQ(&b, 16, data0, 16, NCB_ADD_PRESERVE, NCB_RET_OK); NCB_DATA_EQ(&b, 0, 48); NCB_DATA_EQ(&b, 16, 32); NCB_DATA_EQ(&b, 32, 16);
818
819 NCB_INIT(&b);
820 NCB_ADD_EQ(&b, 0, data0, 8, NCB_ADD_PRESERVE, NCB_RET_OK);
821 /* this insertion must be rejected because of minimal gap size */
822 NCB_ADD_EQ(&b, 10, data0, 8, NCB_ADD_PRESERVE, NCB_RET_GAP_SIZE);
823
824 /* Test reduced gap support */
825 NCB_INIT(&b);
826 /* this insertion will form a reduced gap */
827 NCB_ADD_EQ(&b, 0, data0, bufsize - (NCB_GAP_MIN_SZ - 1), NCB_ADD_COMPARE, NCB_RET_OK);
828
829 /* Test the various insertion mode */
830 NCB_INIT(&b);
831 NCB_ADD_EQ(&b, 10, data1, 16, NCB_ADD_PRESERVE, NCB_RET_OK);
832 NCB_ADD_EQ(&b, 12, data1, 16, NCB_ADD_COMPARE, NCB_RET_DATA_REJ);
833 NCB_ADD_EQ(&b, 12, data1, 16, NCB_ADD_PRESERVE, NCB_RET_OK); BUG_ON(*ncb_peek(&b, 12) != data1[2]);
834 NCB_ADD_EQ(&b, 12, data1, 16, NCB_ADD_OVERWRT, NCB_RET_OK); BUG_ON(*ncb_peek(&b, 12) == data1[2]);
835
836 /* advance test suite */
837 NCB_INIT(&b);
838 NCB_ADVANCE_EQ(&b, 10, NCB_RET_OK); /* advance in an empty buffer; this ensures we do not leave an empty DATA in the middle of the buffer */
839 NCB_ADVANCE_EQ(&b, ncb_size(&b) - 2, NCB_RET_OK);
840
841 NCB_INIT(&b);
842 /* first fill the buffer */
843 NCB_ADD_EQ(&b, 0, data0, bufsize - NCB_RESERVED_SZ, NCB_ADD_COMPARE, NCB_RET_OK);
844 /* delete 2 bytes : a reduced gap must be created */
845 NCB_ADVANCE_EQ(&b, 2, NCB_RET_OK); NCB_TOTAL_DATA_EQ(&b, ncb_size(&b) - 2);
846 /* delete 1 byte : extend the reduced gap */
847 NCB_ADVANCE_EQ(&b, 1, NCB_RET_OK); NCB_TOTAL_DATA_EQ(&b, ncb_size(&b) - 3);
848 /* delete 5 bytes : a full gap must be present */
849 NCB_ADVANCE_EQ(&b, 5, NCB_RET_OK); NCB_TOTAL_DATA_EQ(&b, ncb_size(&b) - 8);
850 /* completely clear the buffer */
851 NCB_ADVANCE_EQ(&b, bufsize - NCB_RESERVED_SZ, NCB_RET_OK); NCB_TOTAL_DATA_EQ(&b, 0);
852
853
854 NCB_INIT(&b);
855 NCB_ADD_EQ(&b, 10, data0, 10, NCB_ADD_PRESERVE, NCB_RET_OK);
856 NCB_ADVANCE_EQ(&b, 2, NCB_RET_OK); /* reduce a gap in front of the buffer */
857 NCB_ADVANCE_EQ(&b, 1, NCB_RET_GAP_SIZE); /* reject */
858 NCB_ADVANCE_EQ(&b, 8, NCB_RET_OK); /* remove completely the gap */
859 NCB_ADVANCE_EQ(&b, 8, NCB_RET_OK); /* remove inside the data */
860 NCB_ADVANCE_EQ(&b, 10, NCB_RET_OK); /* remove completely the data */
861
862 fprintf(stderr, "first random pass\n");
863 NCB_INIT(&b);
864
865 /* generate randon data offsets until the buffer is full */
866 while (!ncb_is_full(&b)) {
867 roff = ncb_generate_rand_off(&b);
868 LIST_INSERT(&list, &roff->el);
869
870 ret = ncb_add(&b, roff->off, data0, roff->len, NCB_ADD_COMPARE);
871 BUG_ON(ret == NCB_RET_DATA_REJ);
872 ncbuf_print_buf(&b, bufsize, bufarea, __LINE__);
873 usleep(print_delay);
874 }
875
876 fprintf(stderr, "buf full, prepare for reverse random\n");
877 ncbuf_print_buf(&b, bufsize, bufarea, __LINE__);
878
879 /* insert the previously generated random offsets in the reverse order.
880 * At the end, the buffer should be full.
881 */
882 NCB_INIT(&b);
883 list_for_each_entry_safe(roff, roff_tmp, &list, el) {
884 int full = ncb_is_full(&b);
885 if (!full) {
886 ret = ncb_add(&b, roff->off, data0, roff->len, NCB_ADD_COMPARE);
887 BUG_ON(ret == NCB_RET_DATA_REJ);
888 ncbuf_print_buf(&b, bufsize, bufarea, __LINE__);
889 usleep(print_delay);
890 }
891
892 LIST_DELETE(&roff->el);
893 free(roff);
894 }
895
896 if (!ncb_is_full(&b))
897 abort();
898
899 fprintf(stderr, "done\n");
900
901 free(bufarea);
902 free(data0);
903
904 return 1;
905}
906
907int main(int argc, char **argv)
908{
909 int reset = 0;
910 int print_delay = 100000;
911 char c;
912
913 opterr = 0;
914 while ((c = getopt(argc, argv, "h:s:rp::")) != -1) {
915 switch (c) {
916 case 'h':
917 bufhead = atoi(optarg);
918 break;
919 case 's':
920 bufsize = atoi(optarg);
921 if (bufsize < 64) {
922 fprintf(stderr, "bufsize should be at least 64 bytes for unit test suite\n");
923 exit(127);
924 }
925 break;
926 case 'r':
927 reset = 1;
928 break;
929 case 'p':
930 if (optarg)
931 print_delay = atoi(optarg);
932 ncb_print = 1;
933 break;
934 case '?':
935 default:
936 fprintf(stderr, "usage: %s [-r] [-s bufsize] [-h bufhead] [-p <delay_msec>]\n", argv[0]);
937 exit(127);
938 }
939 }
940
941 ncbuf_test(bufhead, reset, print_delay);
942 return EXIT_SUCCESS;
943}
944
945#endif /* STANDALONE */