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
This commit is contained in:
Nick Mathewson 2009-04-03 01:21:36 +00:00
parent 0afb1f7ffb
commit f90500a5df
4 changed files with 271 additions and 18 deletions

145
buffer.c
View File

@ -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;
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;
}
unsigned char *search;
struct evbuffer_ptr ptr;
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;
}
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)
{

View 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);
/**
Find a string within an evbuffer.
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
@return a pointer to the beginning of the search string, or NULL if the search failed.
@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.
*/
unsigned char *evbuffer_find(struct evbuffer *buffer, const unsigned char *what, size_t len);
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
};
/**
Sets the search pointer in the buffer to positiion.
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.

View 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

View 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 },