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;
+}