diff --git a/Makefile b/Makefile index 08bb43aaf..56fd89a3c 100644 --- a/Makefile +++ b/Makefile @@ -953,7 +953,8 @@ OBJS += src/mux_h2.o src/mux_fcgi.o src/http_ana.o src/mux_h1.o \ src/base64.o src/uri_auth.o src/time.o src/ebsttree.o src/ebistree.o \ src/dynbuf.o src/auth.o src/wdt.o src/pipe.o src/http_acl.o \ src/hpack-huff.o src/hpack-enc.o src/dict.o src/init.o src/freq_ctr.o \ - src/ebtree.o src/hash.o src/dgram.o src/version.o src/conn_stream.o + src/ebtree.o src/hash.o src/dgram.o src/version.o src/conn_stream.o \ + src/ncbuf.o ifneq ($(TRACE),) OBJS += src/calltrace.o diff --git a/include/haproxy/ncbuf-t.h b/include/haproxy/ncbuf-t.h new file mode 100644 index 000000000..4623096b6 --- /dev/null +++ b/include/haproxy/ncbuf-t.h @@ -0,0 +1,54 @@ +#ifndef _HAPROXY_NCBUF_T_H +#define _HAPROXY_NCBUF_T_H + +/* **** public documentation **** + * + * stands for non-contiguous circular buffer. This type can be used to + * store data in a non-linear way with gaps between them. The buffer is + * circular and so data may wrapped. + * + * The API of is splitted in two parts. Please refer to the public API + * declared in this header file which should cover all the needs. + * + * To minimize the memory footprint, size of data and gaps are inserted in the + * gaps themselves. This way does not need to maintain a separate list + * of data offsets in a dedicated structure. However, this put some limitations + * on the buffer usage that the user need to know. + * + * First, a space will always be reserved in the allocated buffer area to store + * the size of the first data block. Use ncb_size(buf) to retrieve the usable + * size of the allocated buffer excluding the reserved space. + * + * Second, add and deletion operations are constraint and may be impossible if + * a minimal gap size between data is not respected. A caller must always + * inspect the return values of these functions. To limit these errors and + * improve the buffer performance, should be reserved for use-cases + * where the number of formed gaps is kept minimal and evenly spread. + */ + +#include + +/* ncb_sz_t is the basic type used in ncbuf to represent data and gap sizes. + * Use a bigger type to extend the maximum data size supported in the buffer. + * On the other hand, this also increases the minimal gap size which can + * cause more rejection for add/delete operations. + */ +typedef uint32_t ncb_sz_t; + +/* reserved size before head used to store first data block size */ +#define NCB_RESERVED_SZ (sizeof(ncb_sz_t)) + +/* A gap contains its size and the size of the data following it. */ +#define NCB_GAP_MIN_SZ (sizeof(ncb_sz_t) * 2) +#define NCB_GAP_SZ_OFF 0 +#define NCB_GAP_SZ_DATA_OFF (sizeof(ncb_sz_t)) + +#define NCBUF_NULL ((struct ncbuf){ }) + +struct ncbuf { + char *area; + ncb_sz_t size; + ncb_sz_t head; +}; + +#endif /* _HAPROXY_NCBUF_T_H */ diff --git a/include/haproxy/ncbuf.h b/include/haproxy/ncbuf.h new file mode 100644 index 000000000..ea5400833 --- /dev/null +++ b/include/haproxy/ncbuf.h @@ -0,0 +1,18 @@ +#ifndef _HAPROXY_NCBUF_H +#define _HAPROXY_NCBUF_H + +#include + +int ncb_is_null(const struct ncbuf *buf); +void ncb_init(struct ncbuf *buf, ncb_sz_t head); +struct ncbuf ncb_make(char *area, ncb_sz_t size, ncb_sz_t head); + +char *ncb_orig(const struct ncbuf *buf); +char *ncb_head(const struct ncbuf *buf); +char *ncb_wrap(const struct ncbuf *buf); + +ncb_sz_t ncb_size(const struct ncbuf *buf); +int ncb_is_empty(const struct ncbuf *buf); +int ncb_is_full(const struct ncbuf *buf); + +#endif /* _HAPROXY_NCBUF_H */ diff --git a/src/ncbuf.c b/src/ncbuf.c new file mode 100644 index 000000000..662f404c0 --- /dev/null +++ b/src/ncbuf.c @@ -0,0 +1,143 @@ +#include + +#ifdef DEBUG_DEV +# include +#else +# include +# include + +# undef BUG_ON +# define BUG_ON(x) if (x) { fprintf(stderr, "CRASH ON %s:%d\n", __func__, __LINE__); abort(); } + +# undef BUG_ON_HOT +# define BUG_ON_HOT(x) if (x) { fprintf(stderr, "CRASH ON %s:%d\n", __func__, __LINE__); abort(); } +#endif /* DEBUG_DEV */ + +/* ******** internal API ******** */ + +/* Return pointer to relative to head. Support buffer wrapping. */ +static char *ncb_peek(const struct ncbuf *buf, ncb_sz_t off) +{ + char *ptr = ncb_head(buf) + off; + if (ptr >= buf->area + buf->size) + ptr -= buf->size; + return ptr; +} + +/* Returns the reserved space of which contains the size of the first + * data block. + */ +static char *ncb_reserved(const struct ncbuf *buf) +{ + return ncb_peek(buf, buf->size - NCB_RESERVED_SZ); +} + +/* Encode at position in . Support wrapping. */ +static void ncb_write_off(const struct ncbuf *buf, char *st, ncb_sz_t off) +{ + int i; + + BUG_ON_HOT(st >= buf->area + buf->size); + + for (i = 0; i < sizeof(ncb_sz_t); ++i) { + (*st) = off >> (8 * i) & 0xff; + + if ((++st) == ncb_wrap(buf)) + st = ncb_orig(buf); + } +} + +/* Decode offset stored at position in . Support wrapping. */ +static ncb_sz_t ncb_read_off(const struct ncbuf *buf, char *st) +{ + int i; + ncb_sz_t off = 0; + + BUG_ON_HOT(st >= buf->area + buf->size); + + for (i = 0; i < sizeof(ncb_sz_t); ++i) { + off |= (unsigned char )(*st) << (8 * i); + + if ((++st) == ncb_wrap(buf)) + st = ncb_orig(buf); + } + + return off; +} + +/* ******** public API ******** */ + +int ncb_is_null(const struct ncbuf *buf) +{ + return buf->size == 0; +} + +/* Initialize or reset by clearing all data. Its size is untouched. + * Buffer is positioned to offset. Use 0 to realign it. + */ +void ncb_init(struct ncbuf *buf, ncb_sz_t head) +{ + BUG_ON_HOT(head >= buf->size); + buf->head = head; + + ncb_write_off(buf, ncb_reserved(buf), 0); + ncb_write_off(buf, ncb_head(buf), ncb_size(buf)); + ncb_write_off(buf, ncb_peek(buf, sizeof(ncb_sz_t)), 0); +} + +/* Construct a ncbuf with all its parameters. */ +struct ncbuf ncb_make(char *area, ncb_sz_t size, ncb_sz_t head) +{ + struct ncbuf buf; + + /* Ensure that there is enough space for the reserved space and data. + * This is the minimal value to not crash later. + */ + BUG_ON_HOT(size <= NCB_RESERVED_SZ); + + buf.area = area; + buf.size = size; + buf.head = head; + + return buf; +} + +/* Returns start of allocated buffer area. */ +char *ncb_orig(const struct ncbuf *buf) +{ + return buf->area; +} + +/* Returns current head pointer into buffer area. */ +char *ncb_head(const struct ncbuf *buf) +{ + return buf->area + buf->head; +} + +/* Returns the first byte after the allocated buffer area. */ +char *ncb_wrap(const struct ncbuf *buf) +{ + return buf->area + buf->size; +} + +/* Returns the usable size of for data storage. This is the size of the + * allocated buffer without the reserved header space. + */ +ncb_sz_t ncb_size(const struct ncbuf *buf) +{ + return buf->size - NCB_RESERVED_SZ; +} + +/* Returns true if there is no data anywhere in . */ +int ncb_is_empty(const struct ncbuf *buf) +{ + BUG_ON_HOT(*ncb_reserved(buf) + *ncb_head(buf) > ncb_size(buf)); + return *ncb_reserved(buf) == 0 && *ncb_head(buf) == ncb_size(buf); +} + +/* Returns true if no more data can be inserted in . */ +int ncb_is_full(const struct ncbuf *buf) +{ + BUG_ON_HOT(ncb_read_off(buf, ncb_reserved(buf)) > ncb_size(buf)); + return ncb_read_off(buf, ncb_reserved(buf)) == ncb_size(buf); +}