From: Nick Mathewson Date: Fri, 3 Apr 2009 01:21:36 +0000 (+0000) Subject: Add a new improved search function. X-Git-Tag: release-2.0.1-alpha~59 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=f90500a5df9af60502215d640ccbf2ba031d30e0;p=libevent Add a new improved search function. 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 --- diff --git a/buffer.c b/buffer.c index bb3cd72b..4f539a56 100644 --- 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) { diff --git a/include/event2/buffer.h b/include/event2/buffer.h index 3300c8cc..b9c57668 100644 --- a/include/event2/buffer.h +++ b/include/event2/buffer.h @@ -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. diff --git a/include/event2/buffer_compat.h b/include/event2/buffer_compat.h index 6f1aad9e..e77b81ed 100644 --- a/include/event2/buffer_compat.h +++ b/include/event2/buffer_compat.h @@ -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 diff --git a/test/regress_buffer.c b/test/regress_buffer.c index 1fda858c..1054d27e 100644 --- a/test/regress_buffer.c +++ b/test/regress_buffer.c @@ -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 },