Skip to content

Commit

Permalink
implement functions for matching key-value pairs in TfwStr strings
Browse files Browse the repository at this point in the history
related to:
#5 - Load balancing
  • Loading branch information
vdmit11 committed Oct 10, 2014
1 parent 69883ab commit a5ee1d2
Show file tree
Hide file tree
Showing 6 changed files with 435 additions and 8 deletions.
184 changes: 179 additions & 5 deletions tempesta_fw/str.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@
*/

#include <linux/kernel.h>
#include <linux/ctype.h>
#include "str.h"

#ifndef DEBUG
#define validate_tfw_str(str)
#define validate_cstr(cstr, len)
#define validate_key(key, len)
#else

static void
Expand All @@ -48,13 +50,33 @@ static void
validate_cstr(const char *cstr, unsigned int len)
{
/* Usually C strings are patterns for matching against TfwStr, so we
* can make some asumptions on them:
* - Length corresponds to strlen().
* can make some assumptions on them:
* - They don't contain control and non-ASCII characters.
* - Their length corresponds to strlen().
* - They are shorter than 2^16. Opposite likely means an error,
* perhaps an error code (negative value) is used as an unsigned int.
* perhaps an error code (the negative value) was used as an
* unsigned integer.
*/
BUG_ON(len >= (1<<16));
int i;
for (i = 0; i < len; ++i)
BUG_ON(iscntrl(cstr[i]) || !isascii(cstr[i]));
BUG_ON(strnlen(cstr, len) != len);
BUG_ON(len >= (1<<16));
}

static void
validate_key(const char *key, int len)
{
/* The term 'key' is even stricter than 'cstr'.
* A key must be a valid cstr, but in addition:
* - It should not contain spaces (or tokenization would be tricky).
* - Expected length won't exceed 256 characters.
*/
int i;
for (i = 0; i < len; ++i)
BUG_ON(isspace(key[i]));
BUG_ON(len >= (1<<8));
validate_cstr(key, len);
}

#endif /* ifndef DEBUG */
Expand Down Expand Up @@ -129,7 +151,8 @@ EXPORT_SYMBOL(tfw_str_len);
* - CMP_CI - use case-insensitive comparison function.
*/
static bool
str_cmp_cstr(const TfwStr *str, const char *cstr, unsigned int cstr_len, u8 flags)
str_cmp_cstr(const TfwStr *str, const char *cstr, unsigned int cstr_len,
u8 flags)
{
const TfwStr *chunk;
unsigned int cmp_len;
Expand Down Expand Up @@ -194,3 +217,154 @@ tfw_str_subjoins_cstr_ci(const TfwStr *str, const char *cstr, int cstr_len)
}
EXPORT_SYMBOL(tfw_str_subjoins_cstr_ci);



/**
* Generic function for comparing TfwStr and a key-value pair of C strings.
*
* The key-value pair has the following form:
* (@key)[:space:]*(@sep)[:space:]*(@val)
*
* For example, if:
* @key = "Connection"
* @sep = ':'
* @val = "keep-alive"
* Then all the following TfwStr values will match it:
* "Connection:keep-alive"
* "Connection: keep-alive"
* "Connection : keep-alive"
* "Connection \r\n : \t keep-alive"
*
* Note: Space characters are tested using isspace(), so chars like \r\n\t
* are treated as space.
*
* @key should not contain spaces (although current implementation allows it).
* @sep is a single character, no repetitions allowed (e.g "==").
* @val must not start with a space (because all spaces are eaten after @sep).
* @str may consist of any number of chunks, there is no limitation
* on how @key/@sep/@val are spread across the chunks.
*
* @flags allows to specify additional options for comparison:
* - CMP_CI
* Use case-insensitive comparison for @key and @val.
* The @sep is always case-sensitive.
*
* - CMP_PREFIX
* Treat @val as a prefix.
* For example, if @val = "text", then it will match to:
* "Content-Type: text"
* "Content-Type: text/html"
* "Content-Type: text/html; charset=UTF-8"
* The flag affects only @val (the @key comparison is always case-insensitive
* and @sep is always case-sensitive).
*/
static bool
str_cmp_kv(const TfwStr *str, const char *key, int key_len, char sep,
const char *val, int val_len, u8 flags)
{
const TfwStr *chunk;
char *p;
enum {
NA = 0,
KEY,
WS1,
SEP,
WS2,
VAL,
} state = KEY;
char c;
u8 val_case_mask;

validate_tfw_str(str);
validate_key(key, key_len);
validate_cstr(val, val_len);

/* The mask turns off the case bit in alphabetic ASCII characters. */
val_case_mask = (flags & CMP_CI) ? 0xDF : 0xEF;
#define _CMP_KEY(c1, c2) ((c1 ^ c2) & 0xDF)
#define _CMP_VAL(c1, c2) ((c1 ^ c2) & val_case_mask)

/* A tiny FSM here. It compares one character at a time, so perhaps it
* is not the fastest one, but the overhead is amortized by absence of
* function calls.
* The switch() below looks a little bit strange, usual 'break's are
* omitted intentionally since state transitions are always sequential,
* so there is no need to break their code which is ordered naturally.
* Read the code keeping in mind that:
* break; => eat a character and re-enter the current state
* ++state; => switch to the next state, but don't eat a character
*/
TFW_STR_FOR_EACH_CHUNK (chunk, str) {
for (p = chunk->ptr; p < ((char *)chunk->ptr + chunk->len); ++p) {
c = *p;
switch (state) {
default:
BUG();
case KEY:
if (key_len) {
if (_CMP_KEY(c, *key))
return false;
++key;
--key_len;
break;
}
++state;
case WS1:
if (isspace(c))
break;
++state;
case SEP:
if (c != sep)
return false;
++state;
break;
case WS2:
if (isspace(c))
break;
++state;
case VAL:
if (!val_len)
return (flags & CMP_PREFIX);
if (_CMP_VAL(c, *val))
return false;
++val;
--val_len;
}
}
}

return !val_len;
}

bool
tfw_str_eq_kv(const TfwStr *str, const char *key, int key_len,
char sep, const char *val, int val_len)
{
return str_cmp_kv(str, key, key_len, sep, val, val_len, 0);
}
EXPORT_SYMBOL(tfw_str_eq_kv);

bool
tfw_str_eq_kv_ci(const TfwStr *str, const char *key, int key_len,
char sep, const char *val, int val_len)
{
return str_cmp_kv(str, key, key_len, sep, val, val_len, CMP_CI);
}
EXPORT_SYMBOL(tfw_str_eq_kv_ci);

bool
tfw_str_subjoins_kv(const TfwStr *str, const char *key, int key_len,
char sep, const char *val, int val_len)
{
return str_cmp_kv(str, key, key_len, sep, val, val_len, CMP_PREFIX);
}
EXPORT_SYMBOL(tfw_str_subjoins_kv);

bool
tfw_str_subjoins_kv_ci(const TfwStr *str, const char *key, int key_len,
char sep, const char *val, int val_len)
{
return str_cmp_kv(str, key, key_len, sep, val, val_len,
(CMP_PREFIX | CMP_CI));
}
EXPORT_SYMBOL(tfw_str_subjoins_kv_ci);
11 changes: 11 additions & 0 deletions tempesta_fw/str.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,20 @@ typedef struct {

TfwStr *tfw_str_add_compound(TfwPool *pool, TfwStr *str);
int tfw_str_len(const TfwStr *str);

bool tfw_str_eq_cstr(const TfwStr *str, const char *cstr, int cstr_len);
bool tfw_str_eq_cstr_ci(const TfwStr *str, const char *cstr, int cstr_len);
bool tfw_str_subjoins_cstr(const TfwStr *str, const char *cstr, int cstr_len);
bool tfw_str_subjoins_cstr_ci(const TfwStr *str, const char *cstr, int cstr_len);


bool tfw_str_eq_kv(const TfwStr *str, const char *key, int key_len,
char sep, const char *val, int val_len);
bool tfw_str_eq_kv_ci(const TfwStr *str, const char *key, int key_len,
char sep, const char *val, int val_len);
bool tfw_str_subjoins_kv(const TfwStr *str, const char *key, int key_len,
char sep, const char *val, int val_len);
bool tfw_str_subjoins_kv_ci(const TfwStr *str, const char *key, int key_len,
char sep, const char *val, int val_len);

#endif /* __TFW_STR_H__ */
2 changes: 1 addition & 1 deletion tempesta_fw/t/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ EXTRA_CFLAGS += -Werror -O0 -g3 -DDEBUG -I$(src)/../ \
-I$(src)/../../tempesta_db -I$(src)/../../sync_socket

obj-m += tfw_test.o
tfw_test-objs = main.o test.o test_http_match.o test_tfw_str.o
tfw_test-objs = main.o test.o test_http_match.o test_tfw_str.o test_tfw_str_kv.o
4 changes: 2 additions & 2 deletions tempesta_fw/t/run_all_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function run_test_mod() {
}

function show_last_run_log() {
dmesg | tac | grep -m 1 -B 200 "tfw_test: start" | tac
dmesg | tac | grep -m 1 -B 10000 "tfw_test: start" | tac
}

function show_last_run_summary() {
Expand All @@ -34,4 +34,4 @@ echo_header Test run full log:
show_last_run_log

echo_header Test run summary:
show_last_run_summary
show_last_run_summary
2 changes: 2 additions & 0 deletions tempesta_fw/t/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ test_call_teardown_fn(void)

TEST_SUITE(http_match);
TEST_SUITE(tfw_str);
TEST_SUITE(tfw_str_kv);

int
test_run_all(void)
Expand All @@ -70,6 +71,7 @@ test_run_all(void)

TEST_SUITE_RUN(http_match);
TEST_SUITE_RUN(tfw_str);
TEST_SUITE_RUN(tfw_str_kv);

return test_fail_counter;
}
Loading

0 comments on commit a5ee1d2

Please sign in to comment.