diff --git a/include/haproxy/ncbuf-t.h b/include/haproxy/ncbuf-t.h index 4623096b6..d2b1c10ac 100644 --- a/include/haproxy/ncbuf-t.h +++ b/include/haproxy/ncbuf-t.h @@ -26,6 +26,36 @@ * where the number of formed gaps is kept minimal and evenly spread. */ +/* **** internal documentation **** + * + * This section is useful to users who need to understand how ncbuf are + * implemented. + * + * Public and internal functions all shared a common abstraction of the buffer. + * The buffer content is represented as a list of blocks, alternating between + * DATA and GAP blocks. This simplifies the buffer examination loop and + * insertion/deletion. Note that this list of blocks is not stored in the + * buffer structure. + * + * The buffer is considered to always start with a DATA block. The size of this + * block is stored just before which is the pointer for offset 0. This + * space will always be reserved for this usage. It can be accessed through + * ncb_int_head(buf). If the buffer has no data at head, the reserved space + * will simply contains the value 0, and will be follow by a gap. + * + * A gap always contains the size of the gap itself and the size of the next + * data block. Here is a small representation of a gap stored at offset + * before a data block at offset . + * + * x y + * ------------------------------------------------------------ + * xxxxxx| GAP-SZ | DATA-SZ | | xxxxxxxxxxxxx... + * ------------------------------------------------------------ + * | -------- GAP-SZ -------------- > | --- DATA-SZ ---> + * + * This means that a gap must be at least big enough to store two sizes. + */ + #include /* ncb_sz_t is the basic type used in ncbuf to represent data and gap sizes. diff --git a/include/haproxy/ncbuf.h b/include/haproxy/ncbuf.h index ea5400833..f0352aff4 100644 --- a/include/haproxy/ncbuf.h +++ b/include/haproxy/ncbuf.h @@ -12,7 +12,10 @@ char *ncb_head(const struct ncbuf *buf); char *ncb_wrap(const struct ncbuf *buf); ncb_sz_t ncb_size(const struct ncbuf *buf); +ncb_sz_t ncb_total_data(const struct ncbuf *buf); int ncb_is_empty(const struct ncbuf *buf); int ncb_is_full(const struct ncbuf *buf); +ncb_sz_t ncb_data(const struct ncbuf *buf, ncb_sz_t offset); + #endif /* _HAPROXY_NCBUF_H */ diff --git a/src/ncbuf.c b/src/ncbuf.c index 662f404c0..445c144c3 100644 --- a/src/ncbuf.c +++ b/src/ncbuf.c @@ -15,6 +15,21 @@ /* ******** internal API ******** */ +#define NCB_BLK_NULL ((struct ncb_blk){ .st = NULL }) + +#define NCB_BK_F_GAP 0x01 /* block represents a gap */ +struct ncb_blk { + char *st; /* first byte of the block */ + char *end; /* first byte after this block */ + + char *sz_ptr; /* pointer to size element */ + ncb_sz_t sz; /* size of the block */ + ncb_sz_t sz_data; /* size of the data following the block - only valid for GAP */ + ncb_sz_t off; /* offset of block in buffer */ + + char flag; +}; + /* Return pointer to relative to head. Support buffer wrapping. */ static char *ncb_peek(const struct ncbuf *buf, ncb_sz_t off) { @@ -65,6 +80,100 @@ static ncb_sz_t ncb_read_off(const struct ncbuf *buf, char *st) return off; } +/* Returns true if is the special NULL block. */ +static int ncb_blk_is_null(const struct ncb_blk blk) +{ + return !blk.st; +} + +/* Returns true if is the last block of . */ +static int ncb_blk_is_last(const struct ncbuf *buf, const struct ncb_blk blk) +{ + BUG_ON_HOT(blk.off + blk.sz > ncb_size(buf)); + return blk.off + blk.sz == ncb_size(buf); +} + +/* Returns the first block of which is always a DATA. */ +static struct ncb_blk ncb_blk_first(const struct ncbuf *buf) +{ + struct ncb_blk blk; + + blk.st = ncb_head(buf); + + blk.sz_ptr = ncb_reserved(buf); + blk.sz = ncb_read_off(buf, ncb_reserved(buf)); + BUG_ON_HOT(blk.sz > ncb_size(buf)); + + blk.end = ncb_peek(buf, blk.sz); + blk.off = 0; + blk.flag = 0; + + return blk; +} + +/* Returns the block following in the buffer . */ +static struct ncb_blk ncb_blk_next(const struct ncbuf *buf, + const struct ncb_blk prev) +{ + struct ncb_blk blk; + + BUG_ON_HOT(ncb_blk_is_null(prev)); + + if (ncb_blk_is_last(buf, prev)) + return NCB_BLK_NULL; + + blk.st = prev.end; + blk.off = prev.off + prev.sz; + blk.flag = ~prev.flag & NCB_BK_F_GAP; + + if (blk.flag & NCB_BK_F_GAP) { + blk.sz_ptr = ncb_peek(buf, blk.off + NCB_GAP_SZ_OFF); + blk.sz = ncb_read_off(buf, blk.sz_ptr); + BUG_ON_HOT(blk.sz < NCB_GAP_MIN_SZ); + blk.sz_data = ncb_read_off(buf, ncb_peek(buf, blk.off + NCB_GAP_SZ_DATA_OFF)); + } + else { + blk.sz_ptr = ncb_peek(buf, prev.off + NCB_GAP_SZ_DATA_OFF); + blk.sz = prev.sz_data; + blk.sz_data = 0; + + /* only first DATA block can be empty. If this happens, a GAP + * merge should have been realized. + */ + BUG_ON_HOT(!blk.sz); + } + + BUG_ON_HOT(blk.off + blk.sz > ncb_size(buf)); + blk.end = ncb_peek(buf, blk.off + blk.sz); + + return blk; +} + +/* Returns the block containing offset . Note that if is at the + * frontier between two blocks, this function will return the preceding one. + * This is done to easily merge blocks on insertion/deletion. + */ +static struct ncb_blk ncb_blk_find(const struct ncbuf *buf, ncb_sz_t off) +{ + struct ncb_blk blk; + + BUG_ON_HOT(off >= ncb_size(buf)); + + for (blk = ncb_blk_first(buf); off > blk.off + blk.sz; + blk = ncb_blk_next(buf, blk)) { + } + + return blk; +} + +/* Transform absolute offset to a relative one from start. */ +static ncb_sz_t ncb_blk_off(const struct ncb_blk blk, ncb_sz_t off) +{ + BUG_ON_HOT(off < blk.off || off > blk.off + blk.sz); + BUG_ON_HOT(off - blk.off > blk.sz); + return off - blk.off; +} + /* ******** public API ******** */ int ncb_is_null(const struct ncbuf *buf) @@ -128,6 +237,20 @@ ncb_sz_t ncb_size(const struct ncbuf *buf) return buf->size - NCB_RESERVED_SZ; } +/* Returns the total number of bytes stored in whole . */ +ncb_sz_t ncb_total_data(const struct ncbuf *buf) +{ + struct ncb_blk blk; + int total = 0; + + for (blk = ncb_blk_first(buf); !ncb_blk_is_null(blk); blk = ncb_blk_next(buf, blk)) { + if (!(blk.flag & NCB_BK_F_GAP)) + total += blk.sz; + } + + return total; +} + /* Returns true if there is no data anywhere in . */ int ncb_is_empty(const struct ncbuf *buf) { @@ -141,3 +264,27 @@ 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); } + +/* Returns the number of bytes of data avaiable in starting at offset + * until the next gap or the buffer end. The counted data may wrapped if + * the buffer storage is not aligned. + */ +ncb_sz_t ncb_data(const struct ncbuf *buf, ncb_sz_t off) +{ + struct ncb_blk blk = ncb_blk_find(buf, off); + ncb_sz_t off_blk = ncb_blk_off(blk, off); + + /* if at the frontier between two and is gap, retrieve the + * next data block. + */ + if (blk.flag & NCB_BK_F_GAP && off_blk == blk.sz && + !ncb_blk_is_last(buf, blk)) { + blk = ncb_blk_next(buf, blk); + off_blk = ncb_blk_off(blk, off); + } + + if (blk.flag & NCB_BK_F_GAP) + return 0; + + return blk.sz - off_blk; +}