Avoid useless strlen, faster query_format.
authorFlorent Bruneau <florent.bruneau@polytechnique.org>
Tue, 11 Nov 2008 11:25:41 +0000 (12:25 +0100)
committerFlorent Bruneau <florent.bruneau@polytechnique.org>
Tue, 11 Nov 2008 11:25:41 +0000 (12:25 +0100)
Signed-off-by: Florent Bruneau <florent.bruneau@polytechnique.org>
common/str.h
postlicyd/Makefile
postlicyd/greylist.c
postlicyd/iplist.c
postlicyd/main-postlicyd.c
postlicyd/match.c
postlicyd/query.c
postlicyd/query.h
postlicyd/strlist.c
postlicyd/tst-qf.c [new file with mode: 0644]

index bc1cf9e..bd2d052 100644 (file)
@@ -371,5 +371,18 @@ m_stristr(const char *haystack, const char *needle) {
     return m_stristrn(haystack, needle, m_strlen(needle));
 }
 
+/****************************************************************************/
+/* static strings                                                           */
+/****************************************************************************/
+
+/** Store a pointer to a string with a pre-computed length.
+ * This intends to store pointers to a part of a longer string and to avoid
+ * useless strlen.
+ */
+typedef struct static_str_t {
+    const char *str;
+    ssize_t    len;
+} static_str_t;
+
 /*@}*/
 #endif /* PFIXTOOLS_STR_H */
index cdbca81..e0b8474 100644 (file)
@@ -36,7 +36,7 @@ GENERATED = policy_tokens.h policy_tokens.c \
                                                filter_tokens.h filter_tokens.c \
                                                hook_tokens.h hook_tokens.c \
                                                param_tokens.h param_tokens.c
-TESTS     = tst-rbl tst-filters tst-greylist
+TESTS     = tst-rbl tst-filters tst-greylist tst-qf
 
 UB_LIBS   = -lunbound
 
@@ -54,9 +54,11 @@ tst-filters_LIBADD  = $(UB_LIBS) $(TC_LIBS) -lev
 tst-greylist_SOURCES = tst-greylist.c resources.c ../common/lib.a
 tst-greylist_LIBADD  = $(TC_LIBS)
 
-hook_tokens.h hook_tokens.c: $(FILTERS)
-param_tokens.c param_tokens.h: $(FILTERS) config.c
+tst-qf_SOURCES = tst-qf.c query.c ../common/lib.a $(GENERATED)
 
 all:
 
+hook_tokens.h hook_tokens.c: $(FILTERS)
+param_tokens.c param_tokens.h: $(FILTERS) config.c
+
 include ../mk/common.mk
index 9626c20..beba34d 100644 (file)
@@ -39,6 +39,7 @@
 #include "str.h"
 #include "resources.h"
 
+static const static_str_t static_cleanup = { "@@cleanup@@", 11 };
 
 typedef struct greylist_config_t {
     unsigned lookup_by_host : 1;
@@ -113,7 +114,7 @@ static inline bool greylist_db_need_cleanup(const greylist_config_t *config, TCB
 {
     int len = 0;
     time_t now = time(NULL);
-    const time_t *last_cleanup = tcbdbget3(db, "@@cleanup@@", strlen("@@cleanup@@"), &len);
+    const time_t *last_cleanup = tcbdbget3(db, static_cleanup.str, static_cleanup.len, &len);
     if (last_cleanup == NULL) {
         debug("No last cleanup time");
     } else {
@@ -193,7 +194,7 @@ static TCBDB **greylist_db_get(const greylist_config_t *config, const char *path
                         }
                         ++old_count;
                     } while (tcbdbcurnext(cur));
-                    tcbdbput(tmp_db, "@@cleanup@@", strlen("@@cleanup@@"), &now, sizeof(now));
+                    tcbdbput(tmp_db, static_cleanup.str, static_cleanup.len, &now, sizeof(now));
                 }
                 tcxstrdel(key);
                 tcxstrdel(value);
@@ -364,15 +365,15 @@ static const char *c_net(const greylist_config_t *config,
 
 
 static bool try_greylist(const greylist_config_t *config,
-                         const char *sender, const char *c_addr,
-                         const char *c_name, const char *rcpt)
+                         const static_str_t *sender, const static_str_t *c_addr,
+                         const static_str_t *c_name, const static_str_t *rcpt)
 {
 #define INCR_AWL                                              \
     aent.count++;                                             \
     aent.last = now;                                          \
     debug("whitelist entry for %.*s updated, count %d",       \
-          c_addrlen, c_addr, aent.count);                     \
-    tcbdbput(awl_db, c_addr, c_addrlen, &aent, sizeof(aent));
+          c_addr->len, c_addr->str, aent.count);                     \
+    tcbdbput(awl_db, c_addr->str, c_addr->len, &aent, sizeof(aent));
 
     char sbuf[BUFSIZ], cnet[64], key[BUFSIZ];
     const void *res;
@@ -381,31 +382,31 @@ static bool try_greylist(const greylist_config_t *config,
     struct obj_entry oent = { now, now };
     struct awl_entry aent = { 0, 0 };
 
-    int len, klen, c_addrlen = strlen(c_addr);
+    int len, klen;
     TCBDB * const awl_db = config->awl_db ? *(config->awl_db) : NULL;
     TCBDB * const obj_db = config->obj_db ? *(config->obj_db) : NULL;
 
     /* Auto whitelist clients.
      */
     if (config->client_awl) {
-        res = tcbdbget3(awl_db, c_addr, c_addrlen, &len);
+        res = tcbdbget3(awl_db, c_addr->str, c_addr->len, &len);
         if (res && len == sizeof(aent)) {
             memcpy(&aent, res, len);
             debug("client %.*s has a whitelist entry, count is %d",
-                  c_addrlen, c_addr, aent.count);
+                  c_addr->len, c_addr->str, aent.count);
         }
 
         if (!greylist_check_awlentry(config, &aent, now)) {
             aent.count = 0;
             aent.last  = 0;
             debug("client %.*s whitelist entry too old",
-                  c_addrlen, c_addr);
+                  c_addr->len, c_addr->str);
         }
 
         /* Whitelist if count is enough.
          */
         if (aent.count >= config->client_awl) {
-            debug("client %.*s whitelisted", c_addrlen, c_addr);
+            debug("client %.*s whitelisted", c_addr->len, c_addr->str);
             if (now < aent.last + 3600) {
                 INCR_AWL
             }
@@ -419,9 +420,9 @@ static bool try_greylist(const greylist_config_t *config,
     /* Lookup.
      */
     klen = snprintf(key, sizeof(key), "%s/%s/%s",
-                    c_net(config, c_addr, c_name, cnet, sizeof(cnet)),
-                    config->no_sender ? "" : sender_normalize(sender, sbuf, sizeof(sbuf)),
-                    config->no_recipient ? "" : rcpt);
+                    c_net(config, c_addr->str, c_name->str, cnet, sizeof(cnet)),
+                    config->no_sender ? "" : sender_normalize(sender->str, sbuf, sizeof(sbuf)),
+                    config->no_recipient ? "" : rcpt->str);
     klen = MIN(klen, ssizeof(key) - 1);
 
     res = tcbdbget3(obj_db, key, klen, &len);
@@ -550,8 +551,8 @@ static filter_result_t greylist_filter(const filter_t *filter,
         return HTK_ABORT;
     }
 
-    return try_greylist(config, query->sender, query->client_address,
-                        query->client_name, query->recipient) ?
+    return try_greylist(config, &query->sender, &query->client_address,
+                        &query->client_name, &query->recipient) ?
            HTK_WHITELIST : HTK_GREYLIST;
 }
 
index 353dc08..efc3b86 100644 (file)
@@ -487,13 +487,13 @@ static filter_result_t iplist_filter(const filter_t *filter, const query_t *quer
     const iplist_filter_t *data = filter->data;
     bool  error = true;
 
-    if (parse_ipv4(query->client_address, &end, &ip) != 0) {
-        if (strchr(query->client_address, ':')) {
+    if (parse_ipv4(query->client_address.str, &end, &ip) != 0) {
+        if (strchr(query->client_address.str, ':')) {
             /* iplist only works on IPv4 */
             return HTK_FAIL;
         }
         warn("invalid client address: %s, expected ipv4",
-             query->client_address);
+             query->client_address.str);
         return HTK_ERROR;
     }
     for (uint32_t i = 0 ; i < data->rbls.len ; ++i) {
index a4543ec..b6d3e2d 100644 (file)
@@ -167,7 +167,7 @@ static bool policy_process(client_t *pcy, const config_t *mconfig)
     const query_t* query = &context->query;
     const filter_t *filter;
     if (mconfig->entry_points[query->state] == -1) {
-        warn("no filter defined for current protocol_state (%s)", smtp_state_names[query->state]);
+        warn("no filter defined for current protocol_state (%s)", smtp_state_names[query->state].str);
         return false;
     }
     if (context->context.current_filter != NULL) {
@@ -225,9 +225,10 @@ static int policy_run(client_t *pcy, void* vconfig)
     query->eoq = eoq + strlen("\n\n");
 
     /* The instance changed => reset the static context */
-    if (query->instance == NULL || strcmp(context->context.instance, query->instance) != 0) {
+    if (query->instance.str == NULL || query->instance.len == 0
+        || strcmp(context->context.instance, query->instance.str) != 0) {
         filter_context_clean(&context->context);
-        m_strcat(context->context.instance, 64, query->instance);
+        m_strcat(context->context.instance, 64, query->instance.str);
     }
     client_io_none(pcy);
     return policy_process(pcy, mconfig) ? 0 : -1;
index 558bc88..d00b6d1 100644 (file)
@@ -49,8 +49,7 @@ typedef struct match_condition_t {
         MATCH_EMPTY,
     } condition;
 
-    char *value;
-    ssize_t value_len;
+    static_str_t value;
 } match_condition_t;
 ARRAY(match_condition_t)
 
@@ -63,7 +62,7 @@ static const char *condition_names[] = {
   "is empty"
 };
 
-#define CONDITION_INIT { PTK_UNKNOWN, false, MATCH_UNKNOWN, NULL, 0 }
+#define CONDITION_INIT { PTK_UNKNOWN, false, MATCH_UNKNOWN, { NULL, 0 } }
 
 typedef struct match_config_t {
     A(match_condition_t) conditions;
@@ -77,8 +76,10 @@ static match_config_t *match_config_new(void)
 
 static inline void match_condition_wipe(match_condition_t *condition)
 {
-    p_delete(&condition->value);
-    condition->value_len = 0;
+    char *str = (char*)condition->value.str;
+    p_delete(&str);
+    condition->value.str = NULL;
+    condition->value.len = 0;
 }
 
 static void match_config_delete(match_config_t **config)
@@ -157,8 +158,8 @@ static bool match_filter_constructor(filter_t *filter)
             if (condition.condition != MATCH_EMPTY) {
                 p = skipspaces(n + 1);
                 PARSE_CHECK(*p, "no value defined to check the condition");
-                condition.value_len = param->value_len - (p - param->value);
-                condition.value     = p_dupstr(p, condition.value_len);
+                condition.value.len = param->value_len - (p - param->value);
+                condition.value.str = p_dupstr(p, condition.value.len);
             }
             array_add(config->conditions, condition);
           } break;
@@ -182,50 +183,50 @@ static void match_filter_destructor(filter_t *filter)
 
 static inline bool match_condition(const match_condition_t *cond, const query_t *query)
 {
-    const char *field = query_field_for_id(query, cond->field);
+    const static_str_t *field = query_field_for_id(query, cond->field);
     debug("running condition: \"%s\" %s %s\"%s\"",
-          field, condition_names[cond->condition],
+          field->str, condition_names[cond->condition],
           cond->case_sensitive ? "" : "(alternative) ",
-          cond->value ? cond->value : "(none)");
+          cond->value.str ? cond->value.str : "(none)");
     switch (cond->condition) {
       case MATCH_EQUAL:
       case MATCH_DIFFER:
-        if (field == NULL) {
+        if (field == NULL || field->str == NULL) {
             return cond->condition != MATCH_DIFFER;
         }
         if (cond->case_sensitive) {
-            return !!((strcmp(field, cond->value) == 0)
+            return !!((strcmp(field->str, cond->value.str) == 0)
                       ^ (cond->condition == MATCH_DIFFER));
         } else {
-            return !!((ascii_strcasecmp(field, cond->value) == 0)
+            return !!((ascii_strcasecmp(field->str, cond->value.str) == 0)
                       ^ (cond->condition == MATCH_DIFFER));
         }
         break;
 
       case MATCH_CONTAINS:
-        if (field == NULL) {
+        if (field == NULL || field->str == NULL) {
             return false;
         }
         if (cond->case_sensitive) {
-            return strstr(field, cond->value);
+            return strstr(field->str, cond->value.str);
         } else {
-            return m_stristrn(field, cond->value, cond->value_len);
+            return m_stristrn(field->str, cond->value.str, cond->value.len);
         }
         break;
 
       case MATCH_CONTAINED:
-        if (field == NULL) {
+        if (field == NULL || field->str == NULL) {
             return false;
         }
         if (cond->case_sensitive) {
-            return strstr(cond->value, field);
+            return strstr(cond->value.str, field->str);
         } else {
-            return m_stristr(cond->value, field);
+            return m_stristr(cond->value.str, field->str);
         }
         break;
 
       case MATCH_EMPTY:
-        return !!((field == NULL || *field == '\0') ^ (!cond->case_sensitive));
+        return !!((field == NULL || field->len == 0) ^ (!cond->case_sensitive));
 
       default:
         assert(false && "invalid condition type");
index 599b9ec..f24a5d2 100644 (file)
 #include "policy_tokens.h"
 #include "str.h"
 
-const char *smtp_state_names[SMTP_count] = {
-  "CONNECT",
-  "HELO",
-  "MAIL",
-  "RCPT",
-  "DATA",
-  "END-OF-MESSAGE",
-  "VRFY",
-  "ETRN",
+const static_str_t smtp_state_names[SMTP_count] = {
+  { "CONNECT", 7 },
+  { "HELO", 4 },
+  { "MAIL", 4 },
+  { "RCPT", 4 },
+  { "DATA", 4 },
+  { "END-OF-MESSAGE", 14 },
+  { "VRFY", 4 },
+  { "ETRN", 4 },
 };
 
+static const static_str_t static_ESMTP = { "ESMTP", 5 };
+static const static_str_t static_SMTP  = { "SMTP",  4 };
+
 bool query_parse(query_t *query, char *p)
 {
 #define PARSE_CHECK(expr, error, ...)                                        \
@@ -81,7 +84,7 @@ bool query_parse(query_t *query, char *p)
 
         vtk = policy_tokenize(v, vlen);
         switch (policy_tokenize(k, klen)) {
-#define CASE(up, low)  case PTK_##up: query->low = v; v[vlen] = '\0';  break;
+#define CASE(up, low)  case PTK_##up: query->low.str = v; query->low.len = vlen; v[vlen] = '\0';  break;
             CASE(HELO_NAME,           helo_name);
             CASE(QUEUE_ID,            queue_id);
             CASE(RECIPIENT_COUNT,     recipient_count);
@@ -104,20 +107,27 @@ bool query_parse(query_t *query, char *p)
 #undef CASE
 
           case PTK_SENDER:
-            query->sender = v;
+            query->sender.str = v;
+            query->sender.len = vlen;
             v[vlen] = '\0';
-            query->sender_domain = memchr(query->sender, '@', vlen);
-            if (query->sender_domain != NULL) {
-                ++query->sender_domain;
+            query->sender_domain.str = memchr(query->sender.str, '@', vlen);
+            if (query->sender_domain.str != NULL) {
+                ++query->sender_domain.str;
+                query->sender_domain.len = query->sender.len
+                                         - (query->sender_domain.str - query->sender.str);
             }
             break;
 
           case PTK_RECIPIENT:
-            query->recipient = v;
+            query->recipient.str = v;
+            query->recipient.len = vlen;
             v[vlen] = '\0';
-            query->recipient_domain = memchr(query->recipient, '@', vlen);
-            if (query->recipient_domain != NULL) {
-                ++query->recipient_domain;
+            query->recipient_domain.str = memchr(query->recipient.str, '@', vlen);
+            if (query->recipient_domain.str != NULL) {
+                ++query->recipient_domain.str;
+                query->recipient_domain.len = query->recipient.len
+                                         - (query->recipient_domain.str - query->recipient.str);
+
             }
             break;
 
@@ -161,11 +171,11 @@ bool query_parse(query_t *query, char *p)
 #undef PARSE_CHECK
 }
 
-const char *query_field_for_id(const query_t *query, postlicyd_token id)
+const static_str_t *query_field_for_id(const query_t *query, postlicyd_token id)
 {
     switch (id) {
 #define CASE(Up, Low)                                                          \
-      case PTK_ ## Up: return query->Low;
+      case PTK_ ## Up: return &query->Low;
       CASE(HELO_NAME, helo_name)
       CASE(QUEUE_ID, queue_id)
       CASE(SENDER, sender)
@@ -191,16 +201,16 @@ const char *query_field_for_id(const query_t *query, postlicyd_token id)
       CASE(STRESS, stress)
 #undef CASE
       case PTK_PROTOCOL_NAME:
-        return query->esmtp ? "ESMTP" : "SMTP";
+        return query->esmtp ? &static_ESMTP : &static_SMTP;
 
       case PTK_PROTOCOL_STATE:
-        return smtp_state_names[query->state];
+        return &smtp_state_names[query->state];
 
       default: return NULL;
     }
 }
 
-const char *query_field_for_name(const query_t *query, const char *name)
+const static_str_t *query_field_for_name(const query_t *query, const char *name)
 {
     postlicyd_token id = policy_tokenize(name, strlen(name));
     if (id == PTK_UNKNOWN) {
@@ -214,7 +224,6 @@ ssize_t query_format(char *dest, size_t len, const char *fmt, const query_t *que
 {
     size_t written = 0;
     size_t pos = 0;
-    const char *end = fmt + m_strlen(fmt);
 
 #define WRITE(Src, Len)                                                        \
     do {                                                                       \
@@ -227,9 +236,12 @@ ssize_t query_format(char *dest, size_t len, const char *fmt, const query_t *que
         pos += __len;                                                          \
     } while (0)
     while (*fmt != '\0') {
-        const char *next_format = strstr(fmt, "${");
+        const char *next_format = strchr(fmt, '$');
+        while (next_format != NULL && next_format[1] != '{') {
+            next_format = strchr(next_format + 1, '$');
+        }
         if (next_format == NULL) {
-            next_format = end;
+            next_format = fmt + m_strlen(fmt);
         }
         WRITE(fmt, next_format - fmt);
         fmt = next_format;
@@ -244,11 +256,12 @@ ssize_t query_format(char *dest, size_t len, const char *fmt, const query_t *que
             if (tok == PTK_UNKNOWN) {
                 warn("unknown field name \"%.*s\"", (int)(next_format - fmt), fmt);
             }
-            const char *field = query == NULL ? NULL : query_field_for_id(query, tok);
+            const static_str_t *field = query == NULL ? NULL
+                                                      : query_field_for_id(query, tok);
             if (field == NULL) {
                 WRITE("(null)", 6);
             } else {
-                WRITE(field, m_strlen(field));
+                WRITE(field->str, field->len);
             }
             fmt = next_format + 1;
         }
index 7e9dc7f..2081e59 100644 (file)
@@ -56,44 +56,44 @@ enum smtp_state {
     SMTP_UNKNOWN,
 };
 
-extern const char *smtp_state_names[SMTP_count];
+extern const static_str_t smtp_state_names[SMTP_count];
 
 /* \see http://www.postfix.org/SMTPD_POLICY_README.html */
 typedef struct query_t {
     unsigned state : 4;
     unsigned esmtp : 1;
 
-    const char *helo_name;
-    const char *queue_id;
-    const char *sender;
-    const char *recipient;
-    const char *recipient_count;
-    const char *client_address;
-    const char *client_name;
-    const char *reverse_client_name;
-    const char *instance;
+    static_str_t helo_name;
+    static_str_t queue_id;
+    static_str_t sender;
+    static_str_t recipient;
+    static_str_t recipient_count;
+    static_str_t client_address;
+    static_str_t client_name;
+    static_str_t reverse_client_name;
+    static_str_t instance;
 
     /* useful data extracted from previous ones */
-    const char *sender_domain;
-    const char *recipient_domain;
+    static_str_t sender_domain;
+    static_str_t recipient_domain;
 
     /* postfix 2.2+ */
-    const char *sasl_method;
-    const char *sasl_username;
-    const char *sasl_sender;
-    const char *size;
-    const char *ccert_subject;
-    const char *ccert_issuer;
-    const char *ccert_fingerprint;
+    static_str_t sasl_method;
+    static_str_t sasl_username;
+    static_str_t sasl_sender;
+    static_str_t size;
+    static_str_t ccert_subject;
+    static_str_t ccert_issuer;
+    static_str_t ccert_fingerprint;
 
     /* postfix 2.3+ */
-    const char *encryption_protocol;
-    const char *encryption_cipher;
-    const char *encryption_keysize;
-    const char *etrn_domain;
+    static_str_t encryption_protocol;
+    static_str_t encryption_cipher;
+    static_str_t encryption_keysize;
+    static_str_t etrn_domain;
 
     /* postfix 2.5+ */
-    const char *stress;
+    static_str_t stress;
 
     const char *eoq;
 } query_t;
@@ -109,12 +109,12 @@ bool query_parse(query_t *query, char *p);
 /** Return the value of the field with the given name.
  */
 __attribute__((nonnull(1,2)))
-const char *query_field_for_name(const query_t *query, const char *name);
+const static_str_t *query_field_for_name(const query_t *query, const char *name);
 
 /** Returns the value of the field with the given id.
  */
 __attribute__((nonnull))
-const char *query_field_for_id(const query_t *query, postlicyd_token id);
+const static_str_t *query_field_for_id(const query_t *query, postlicyd_token id);
 
 /** Formats the given string by replacing ${field_name} with the content
  * of the query.
index e8202ff..4208f7c 100644 (file)
@@ -681,9 +681,9 @@ static filter_result_t strlist_filter(const filter_t *filter, const query_t *que
     }
 #define LOOKUP(Flag, Field)                                                    \
     if (config->match_ ## Flag) {                                              \
-        const int len = m_strlen(query->Field);                                \
-        strlist_copy(normal, query->Field, len, false);                        \
-        strlist_copy(reverse, query->Field, len, true);                        \
+        const int len = query->Field.len;                                      \
+        strlist_copy(normal, query->Field.str, len, false);                    \
+        strlist_copy(reverse, query->Field.str, len, true);                    \
         foreach (strlist_local_t *entry, config->locals) {                     \
             if ((!entry->partial && trie_lookup(*(entry->db),                  \
                                       entry->reverse ? reverse : normal))      \
@@ -699,8 +699,8 @@ static filter_result_t strlist_filter(const filter_t *filter, const query_t *que
     }
 #define DNS(Flag, Field)                                                       \
     if (config->match_ ## Flag) {                                              \
-        const int len = m_strlen(query->Field);                                \
-        strlist_copy(normal, query->Field, len, false);                        \
+        const int len = query->Field.len;                                      \
+        strlist_copy(normal, query->Field.str, len, false);                    \
         for (uint32_t i = 0 ; len > 0 && i < config->host_offsets.len ; ++i) { \
             const char *rbl = array_ptr(config->hosts,                         \
                                         array_elt(config->host_offsets, i));   \
diff --git a/postlicyd/tst-qf.c b/postlicyd/tst-qf.c
new file mode 100644 (file)
index 0000000..1d8343b
--- /dev/null
@@ -0,0 +1,105 @@
+/******************************************************************************/
+/*          pfixtools: a collection of postfix related tools                  */
+/*          ~~~~~~~~~                                                         */
+/*  ________________________________________________________________________  */
+/*                                                                            */
+/*  Redistribution and use in source and binary forms, with or without        */
+/*  modification, are permitted provided that the following conditions        */
+/*  are met:                                                                  */
+/*                                                                            */
+/*  1. Redistributions of source code must retain the above copyright         */
+/*     notice, this list of conditions and the following disclaimer.          */
+/*  2. Redistributions in binary form must reproduce the above copyright      */
+/*     notice, this list of conditions and the following disclaimer in the    */
+/*     documentation and/or other materials provided with the distribution.   */
+/*  3. The names of its contributors may not be used to endorse or promote    */
+/*     products derived from this software without specific prior written     */
+/*     permission.                                                            */
+/*                                                                            */
+/*  THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND   */
+/*  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE     */
+/*  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR        */
+/*  PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS    */
+/*  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR    */
+/*  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF      */
+/*  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS  */
+/*  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN   */
+/*  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)   */
+/*  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF    */
+/*  THE POSSIBILITY OF SUCH DAMAGE.                                           */
+/******************************************************************************/
+
+/*
+ * Copyright © 2008 Florent Bruneau
+ */
+
+#include "common.h"
+#include "file.h"
+#include "query.h"
+
+static bool read_query(const char *base, const char *file, query_t *query,
+                       char *buff)
+{
+    char path[FILENAME_MAX];
+    snprintf(path, FILENAME_MAX, "%s%s", base, file);
+    {
+        file_map_t map;
+        if (!file_map_open(&map, path, false)) {
+            UNIXERR("open");
+            return false;
+        }
+        if (map.end - map.map >= BUFSIZ) {
+            err("File too large for a testcase: %s", path);
+            file_map_close(&map);
+            return false;
+        }
+        memcpy(buff, map.map, map.end - map.map);
+        buff[map.end - map.map] = '\0';
+        file_map_close(&map);
+    }
+
+    char *eoq = strstr(buff, "\n\n");
+    if (eoq == NULL) {
+        return false;
+    }
+    if (!query_parse(query, buff)) {
+        err("Cannot parse query from file %s", path);
+        return false;
+    }
+    return true;
+}
+
+int main(int argc, char *argv[])
+{
+    char basepath[FILENAME_MAX];
+    char buff[BUFSIZ];
+    char *p;
+
+    p = strrchr(argv[0], '/');
+    if (p == NULL) {
+        p = argv[0];
+    } else {
+        ++p;
+    }
+    snprintf(basepath, FILENAME_MAX, "%.*sdata/", p - argv[0], argv[0]);
+
+    query_t q;
+    if (!read_query(basepath, "testcase_1", &q, buff)) {
+        return EXIT_FAILURE;
+    }
+
+    static const char *format = "${sender} ${recipient} and ${client_name}[${client_address}] at ${protocol_state}";
+
+    time_t now = time(0);
+
+    char str[BUFSIZ];
+    static const int iterations = 10000000;
+    for (int i = 0 ; i < iterations ; ++i) {
+        query_format(str, BUFSIZ, format, &q);
+    }
+
+    time_t ellapsed = time(0) - now;
+    printf("Done %d iterations in %us (%d format per second)\n", iterations,
+           (uint32_t)ellapsed, (int)(iterations / ellapsed));
+    return 0;
+}