]> granicus.if.org Git - libevent/commitdiff
Add a new improved search function.
authorNick Mathewson <nickm@torproject.org>
Fri, 3 Apr 2009 01:21:36 +0000 (01:21 +0000)
committerNick Mathewson <nickm@torproject.org>
Fri, 3 Apr 2009 01:21:36 +0000 (01:21 +0000)
The old evbuffer_find didn't allow iterative searching, and forced us
to repack the buffer completely every time we searched in it.  The
new evbuffer_search addresses both of these.  As a side-effect, the
evbuffer_find implementation is now a little more efficient.

svn:r1130

buffer.c
include/event2/buffer.h
include/event2/buffer_compat.h
test/regress_buffer.c

index bb3cd72b948d678851f4d0f71b905e3c711d2544..4f539a56b2b98e6ce74f922a2c1fed2b2b9798ea 100644 (file)
--- a/buffer.c
+++ b/buffer.c
@@ -1340,22 +1340,143 @@ evbuffer_write(struct evbuffer *buffer, evutil_socket_t fd)
 unsigned char *
 evbuffer_find(struct evbuffer *buffer, const unsigned char *what, size_t len)
 {
-       unsigned char *search = evbuffer_pullup(buffer, -1);
-       unsigned char *end = search + buffer->total_len;
-       unsigned char *p;
+        unsigned char *search;
+        struct evbuffer_ptr ptr;
 
-       while (search < end &&
-           (p = memchr(search, *what, end - search)) != NULL) {
-               if (p + len > end)
-                       break;
-               if (memcmp(p, what, len) == 0)
-                       return (p);
-               search = p + 1;
+        ptr = evbuffer_search(buffer, (const char *)what, len, NULL);
+        if (ptr.pos < 0)
+                return (NULL);
+
+        search = evbuffer_pullup(buffer, ptr.pos + len);
+        return search + ptr.pos;
+}
+
+int
+evbuffer_ptr_set(struct evbuffer *buf, struct evbuffer_ptr *pos,
+    size_t position, enum evbuffer_ptr_how how)
+{
+        size_t left = position;
+       struct evbuffer_chain *chain = NULL;
+
+       switch (how) {
+       case EVBUFFER_PTR_SET:
+               chain = buf->first;
+               pos->pos = position;
+               position = 0;
+               break;
+       case EVBUFFER_PTR_ADD:
+               /* this avoids iterating over all previous chains if
+                  we just want to advance the position */
+               chain = pos->_internal.chain;
+               pos->pos += position;
+               position = pos->_internal.pos_in_chain;
+               break;
        }
 
-       return (NULL);
+       while (chain && position + left >= chain->off) {
+               left -= chain->off - position;
+               chain = chain->next;
+               position = 0;
+       }
+       if (chain) {
+               pos->_internal.chain = chain;
+               pos->_internal.pos_in_chain = position + left;
+       } else {
+               pos->_internal.chain = NULL;
+               pos->pos = -1;
+       }
+
+       return chain != NULL ? 0 : -1;
+}
+
+/**
+   Compare the bytes in buf at position pos to the len bytes in mem.  Return
+   less than 0, 0, or greater than 0 as memcmp.
+ */
+static int
+evbuffer_ptr_memcmp(const struct evbuffer *buf, const struct evbuffer_ptr *pos,
+    const char *mem, size_t len)
+{
+        struct evbuffer_chain *chain;
+        size_t position;
+        int r;
+
+        if (pos->pos + len > buf->total_len)
+                return -1;
+
+        chain = pos->_internal.chain;
+        position = pos->_internal.pos_in_chain;
+        while (len && chain) {
+                size_t n_comparable;
+                if (len + position > chain->off)
+                        n_comparable = chain->off - position;
+                else
+                        n_comparable = len;
+                r = memcmp(chain->buffer + chain->misalign + position, mem,
+                    n_comparable);
+                if (r)
+                        return r;
+                mem += n_comparable;
+                len -= n_comparable;
+                position = 0;
+                chain = chain->next;
+        }
+
+        return 0;
+}
+
+struct evbuffer_ptr
+evbuffer_search(struct evbuffer *buffer, const char *what, size_t len, const struct evbuffer_ptr *start)
+{
+        struct evbuffer_ptr pos;
+        struct evbuffer_chain *chain;
+       const unsigned char *p;
+        char first;
+
+        if (start) {
+                memcpy(&pos, start, sizeof(pos));
+                chain = pos._internal.chain;
+        } else {
+                pos.pos = 0;
+                chain = pos._internal.chain = buffer->first;
+                pos._internal.pos_in_chain = 0;
+        }
+
+        if (!len)
+                return pos;
+
+        first = what[0];
+
+        while (chain) {
+                const unsigned char *start_at =
+                    chain->buffer + chain->misalign +
+                    pos._internal.pos_in_chain;
+                p = memchr(start_at, first,
+                    chain->off - pos._internal.pos_in_chain);
+                if (p) {
+                        pos.pos += p - start_at;
+                        pos._internal.pos_in_chain += p - start_at;
+                        if (!evbuffer_ptr_memcmp(buffer, &pos, what, len))
+                                return pos;
+                        ++pos.pos;
+                        ++pos._internal.pos_in_chain;
+                        if (pos._internal.pos_in_chain == chain->off) {
+                                chain = pos._internal.chain = chain->next;
+                                pos._internal.pos_in_chain = 0;
+                        }
+                } else {
+                        pos.pos += chain->off - pos._internal.pos_in_chain;
+                        chain = pos._internal.chain = chain->next;
+                        pos._internal.pos_in_chain = 0;
+                }
+        }
+
+        pos.pos = -1;
+        pos._internal.chain = NULL;
+        return pos;
 }
 
+
 int
 evbuffer_add_vprintf(struct evbuffer *buf, const char *fmt, va_list ap)
 {
index 3300c8cc0a1c36345270cefbdb8909c1dcba0aeb..b9c5766807817aa2f365a3fe4b489f54cc83cda4 100644 (file)
@@ -70,6 +70,21 @@ extern "C" {
 
 struct evbuffer;
 
+/** Points to a position within an evbuffer. Used when repeatedly searching
+    through a buffer.  Calls to any function that modifies or re-packs the
+    buffer contents may invalidate all evbuffer_ptrs for that buffer.  Do not
+    modify these values except with evbuffer_ptr_set.
+ */
+struct evbuffer_ptr {
+       ssize_t pos;
+
+       /* Do not alter the values of fields. */
+       struct {
+               void *chain;
+               size_t pos_in_chain;
+       } _internal;
+};
+
 /**
   Allocate storage for a new evbuffer.
 
@@ -345,17 +360,42 @@ int evbuffer_write_atmost(struct evbuffer *buffer, evutil_socket_t fd,
  */
 int evbuffer_read(struct evbuffer *buffer, evutil_socket_t fd, int howmuch);
 
+/**
+   Search for a string within an evbuffer.
+
+   @param buffer the evbuffer to be searched
+   @param what the string to be searched for
+   @param len the length of the search string
+   @param start NULL or a pointer to a valid struct evbuffer_ptr.
+   @return a struct evbuffer_ptr whose 'pos' field has the offset of the
+     first occurrence of the string in the buffer after 'start'.  The 'pos'
+     field of the result is -1 if the string was not found.
+ */
+struct evbuffer_ptr evbuffer_search(struct evbuffer *buffer, const char *what, size_t len, const struct evbuffer_ptr *start);
+
+enum evbuffer_ptr_how {
+       /** Sets the pointer to the position; can be called on with an
+           uninitalized evbuffer_ptr. */
+       EVBUFFER_PTR_SET,
+       /** Advances the pointer by adding to the current position. */
+       EVBUFFER_PTR_ADD
+};
 
 /**
-  Find a string within an evbuffer.
+   Sets the search pointer in the buffer to positiion.
 
-  @param buffer the evbuffer to be searched
-  @param what the string to be searched for
-  @param len the length of the search string
-  @return a pointer to the beginning of the search string, or NULL if the search failed.
- */
-unsigned char *evbuffer_find(struct evbuffer *buffer, const unsigned char *what, size_t len);
+   If evbuffer_ptr is not initalized.  This function can only be called
+   with EVBUFFER_PTR_SET.
 
+   @param buffer the evbuffer to be search
+   @param ptr a pointer to a struct evbuffer_ptr
+   @param position the position at which to start the next search
+   @param how determines how the pointer should be manipulated.
+   @returns 0 on success or -1 otherwise
+*/
+int
+evbuffer_ptr_set(struct evbuffer *buffer, struct evbuffer_ptr *pos,
+    size_t position, enum evbuffer_ptr_how how);
 
 /** Type definition for a callback that is invoked whenever data is added or
     removed from an evbuffer.
index 6f1aad9ef765f9e211c73e01bdce9823f0644b12..e77b81edd49f42593bf10bdc6488d501135a5ebf 100644 (file)
@@ -43,5 +43,16 @@ char *evbuffer_readline(struct evbuffer *buffer);
  */
 void evbuffer_setcb(struct evbuffer *buffer, evbuffer_cb cb, void *cbarg);
 
+
+/**
+  Find a string within an evbuffer.
+
+  @param buffer the evbuffer to be searched
+  @param what the string to be searched for
+  @param len the length of the search string
+  @return a pointer to the beginning of the search string, or NULL if the search failed.
+ */
+unsigned char *evbuffer_find(struct evbuffer *buffer, const unsigned char *what, size_t len);
+
 #endif
 
index 1fda858c5c0b13b5d25c82b063275d3220771619..1054d27e2bdfcf60bc25a38d0510e9f87e01a967 100644 (file)
@@ -542,6 +542,85 @@ end:
                evbuffer_free(buf);
 }
 
+static void
+test_evbuffer_ptr_set(void *ptr)
+{
+       struct evbuffer *buf = evbuffer_new();
+       struct evbuffer_ptr pos;
+
+       /* create some chains */
+       evbuffer_reserve_space(buf, 5000);
+       evbuffer_commit_space(buf, 5000);
+       evbuffer_reserve_space(buf, 4000);
+       evbuffer_commit_space(buf, 4000);
+       evbuffer_reserve_space(buf, 3000);
+       evbuffer_commit_space(buf, 3000);
+
+       tt_assert(evbuffer_ptr_set(buf, &pos, 13000, EVBUFFER_PTR_SET) == -1);
+       tt_assert(pos.pos == -1);
+       tt_assert(evbuffer_ptr_set(buf, &pos, 0, EVBUFFER_PTR_SET) == 0);
+       tt_assert(pos.pos == 0);
+       tt_assert(evbuffer_ptr_set(buf, &pos, 13000, EVBUFFER_PTR_ADD) == -1);
+
+       tt_assert(evbuffer_ptr_set(buf, &pos, 0, EVBUFFER_PTR_SET) == 0);
+       tt_assert(pos.pos == 0);
+       tt_assert(evbuffer_ptr_set(buf, &pos, 10000, EVBUFFER_PTR_ADD) == 0);
+       tt_assert(pos.pos == 10000);
+       tt_assert(evbuffer_ptr_set(buf, &pos, 1000, EVBUFFER_PTR_ADD) == 0);
+       tt_assert(pos.pos == 11000);
+       tt_assert(evbuffer_ptr_set(buf, &pos, 1000, EVBUFFER_PTR_ADD) == -1);
+       tt_assert(pos.pos == -1);
+
+end:
+       if (buf)
+               evbuffer_free(buf);
+}
+
+static void
+test_evbuffer_search(void *ptr)
+{
+       struct evbuffer *buf = evbuffer_new();
+       struct evbuffer *tmp = evbuffer_new();
+       struct evbuffer_ptr pos;
+
+       /* set up our chains */
+       evbuffer_add_printf(tmp, "hello");  /* 5 chars */
+       evbuffer_add_buffer(buf, tmp);
+       evbuffer_add_printf(tmp, "foo");    /* 3 chars */
+       evbuffer_add_buffer(buf, tmp);
+       evbuffer_add_printf(tmp, "cat");    /* 3 chars */
+       evbuffer_add_buffer(buf, tmp);
+       evbuffer_add_printf(tmp, "attack");
+       evbuffer_add_buffer(buf, tmp);
+
+       pos = evbuffer_search(buf, "attack", 6, NULL);
+       tt_int_op(pos.pos, ==, 11);
+       pos = evbuffer_search(buf, "attacker", 8, NULL);
+       tt_int_op(pos.pos, ==, -1);
+
+       /* test continuing search */
+       pos = evbuffer_search(buf, "oc", 2, NULL);
+       tt_int_op(pos.pos, ==, 7);
+       pos = evbuffer_search(buf, "cat", 3, &pos);
+       tt_int_op(pos.pos, ==, 8);
+       pos = evbuffer_search(buf, "tacking", 7, &pos);
+       tt_int_op(pos.pos, ==, -1);
+
+       evbuffer_ptr_set(buf, &pos, 5, EVBUFFER_PTR_SET);
+       pos = evbuffer_search(buf, "foo", 3, &pos);
+       tt_int_op(pos.pos, ==, 5);
+
+       evbuffer_ptr_set(buf, &pos, 2, EVBUFFER_PTR_ADD);
+       pos = evbuffer_search(buf, "tat", 3, &pos);
+       tt_int_op(pos.pos, ==, 10);
+
+end:
+       if (buf)
+               evbuffer_free(buf);
+       if (tmp)
+               evbuffer_free(tmp);
+}
+
 static void
 log_change_callback(struct evbuffer *buffer, size_t old_len, size_t new_len,
               void *arg)
@@ -775,6 +854,8 @@ struct testcase_t evbuffer_testcases[] = {
        { "iterative", test_evbuffer_iterative, 0, NULL, NULL },
        { "readln", test_evbuffer_readln, 0, NULL, NULL },
        { "find", test_evbuffer_find, 0, NULL, NULL },
+       { "ptr_set", test_evbuffer_ptr_set, 0, NULL, NULL },
+       { "search", test_evbuffer_search, 0, NULL, NULL },
        { "callbacks", test_evbuffer_callbacks, 0, NULL, NULL },
        { "add_reference", test_evbuffer_add_reference, 0, NULL, NULL },
        { "prepend", test_evbuffer_prepend, 0, NULL, NULL },