diff options
author | Vivien Kraus <vivien@planete-kraus.eu> | 2023-03-21 20:51:00 +0100 |
---|---|---|
committer | Vivien Kraus <vivien@planete-kraus.eu> | 2023-03-25 13:24:39 +0100 |
commit | 2c04502589876dc4c19bdbff4426757571ac76b0 (patch) | |
tree | 302bbb198829c00b51a64898cb6510c3da43d955 | |
parent | b7a76711ed9898dca90b91f87c5dcbd49be9553e (diff) |
Optionally store a key, response header and body in a cache entry
-rw-r--r-- | bootstrap.conf | 1 | ||||
-rw-r--r-- | doc/disfluid.texi | 161 | ||||
-rw-r--r-- | include/disfluid/cache_entry.h | 49 | ||||
-rw-r--r-- | src/libdisfluid/disfluid-cache-entry.h | 527 | ||||
-rw-r--r-- | src/libdisfluid/disfluid-tests.h | 129 | ||||
-rw-r--r-- | src/libdisfluid/main.c | 46 |
6 files changed, 812 insertions, 101 deletions
diff --git a/bootstrap.conf b/bootstrap.conf index da85715..b708257 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -27,6 +27,7 @@ copy-file-range do-release-commit-and-tag errno fclose +flexmember free-posix ftruncate gettext-h diff --git a/doc/disfluid.texi b/doc/disfluid.texi index be1d82f..bf66908 100644 --- a/doc/disfluid.texi +++ b/doc/disfluid.texi @@ -7,7 +7,7 @@ @settitle Demanding Interoperability to Strengthen the Free (Libre) Web: Introducing Disfluid @c %**end of header -@set UPDATED 20 March 2023 +@set UPDATED 21 March 2023 @set UPDATED-MONTH March 2023 @include version_number.texi @@ -219,4 +219,163 @@ NUL-terminated, and the value @code{-2} is returned. Otherwise, @code{0} is returned. @end deftypefun +@deftp struct disfluid_cache_entry +A structure that can hold everything related to a cache entry. The +specific layout is hidden, but the overall size and alignment can be +queried at run-time. +@end deftp + +@deftypefun size_t disfluid_cache_entry_size (size_t @var{max_key}, size_t @var{max_header}, size_t @var{max_body}) +Return the size to allocate for a cache entry that can hold at most +@var{max_key} bytes of the key (method, URL and varied header values), +at most @var{max_header} for the response header, and at most +@var{max_body} for the response body. Do not hesitate to demand server +to send terse responses to you. 512 bytes for the key, 4 kB for the +header, and 2 MB for the body should be more than enough for proper +cache handling. + +This function is a constant function according to GCC: its output only +depends on the three inputs, and it is not allowed to read global +data. The compiler is free to reuse the same value without callig the +function again. Define the @code{ATTRIBUTE_CONST} macro to the syntax +for function attributes for your compiler. If you use gnulib, use the +@samp{attribute} module and include @code{attribute.h} before +@code{disfluid.h} to use this optimization. +@end deftypefun + +@deftypefun size_t disfluid_cache_entry_alignment (void) +Return the proper alignment for a cache entry. This function is +constant too. +@end deftypefun + +@deftypefun size_t disfluid_cache_entry_init (struct disfluid_cache_entry *@var{entry}, size_t @var{max_key}, size_t @var{max_header}, size_t @var{max_body}) +Initialize the @var{entry}, that must be aligned properly, and +allocated with an appropriate size, such as given by +@code{disfluid_cache_entry_size}. +@end deftypefun + +@deftypefun void disfluid_cache_entry_minimum_size (const struct disfluid_cache_entry *@var{entry}, size_t *@var{min_key}, size_t *@var{min_header}, size_t *@var{min_body}) +Set @var{min_key}, @var{min_header} and @var{min_body} to the minimum +sizes that should be allocated to hold the key, header and body of +@var{entry}. +@end deftypefun + +@deftypefun int disfluid_cache_entry_copy (struct disfluid_cache_entry *@var{dest}, const struct disfluid_cache_entry *@var{src}) +Copy @var{src} to @var{dest}. If one of the @var{dest} limits is too +short, return a negative error code. Otherwise, return 0. + +@table @code +@item -1, -3, -5, -7 +indicates that the key does not fit in @var{dest}; +@item -2, -3, -6, -7 +indicates that the response header does not fit in @var{dest}; +@item -4, -5, -6, -7 +indicates that the response body does not fit in @var{dest}. +@end table +@end deftypefun + +@deftypefun {struct disfluid_cache_entry *} disfluid_cache_entry_alloc (size_t @var{max_key}, size_t @var{max_header}, size_t @var{max_body}) +@deftypefunx {struct disfluid_cache_entry *} disfluid_cache_entry_dup (const struct disfluid_cache_entry *@var{src}) +Allocate a new cache entry with the libc standard +allocator. @code{disfluid_cache_entry_dup} allocates the same limits +as those of @var{src}. +@end deftypefun + +@deftypefun void disfluid_cache_entry_free (struct disfluid_cache_entry *@var{entry}) +Free a cache entry allocated with the standard libc allocator. +@end deftypefun + +To make memory allocation a bit safer, you can use the +@code{DISFLUID_CACHE_ENTRY_ALLOC (@var{ptr}, @var{max_key}, +@var{max_header}, @var{max_body})} and +@code{DISFLUID_CACHE_ENTRY_FREE(@var{ptr})} macros as: + +@example +struct disfluid_cache_entry *entry; +if (DISFLUID_CACHE_ENTRY_ALLOC (entry, 512, 4096, 2 * 1024 * 1024) < 0) + @{ + /* Handle memory allocation failure */ + @} +else + @{ + /* Do something with entry… */ + @} +DISFLUID_CACHE_ENTRY_FREE (entry); +/* entry is set to NULL */ +assert (entry == NULL); +@end example + +@deftypefun void disfluid_cache_entry_set_request_date (struct disfluid_cache_entry *@var{entry}, const struct timespec *@var{request_date}) +@deftypefunx void disfluid_cache_entry_set_response_date (struct disfluid_cache_entry *@var{entry}, const struct timespec *@var{response_date}) +@deftypefunx void disfluid_cache_entry_set_invalidated (struct disfluid_cache_entry *@var{entry}, int @var{invalidated}) +@deftypefunx void disfluid_cache_entry_invalidate (struct disfluid_cache_entry *@var{entry}) +Manage the metadata associated with @var{entry}. +@end deftypefun + +@deftypefun int disfluid_cache_entry_set_key (struct disfluid_cache_entry *@var{entry}, const char *@var{key}) +@deftypefunx int disfluid_cache_entry_set_header (struct disfluid_cache_entry *@var{entry}, const char *@var{header}) +@deftypefunx int disfluid_cache_entry_set_body (struct disfluid_cache_entry *@var{entry}, size_t @var{body_length}, const char *@var{body}) +Try and set the relevant cache entry section in @var{entry}. If the +respective limit is too short, return a negative error +code. Otherwise, return 0. +@end deftypefun + +@deftypefun void disfluid_cache_entry_get_request_date (const struct disfluid_cache_entry *@var{entry}, struct timespec *@var{request_date}) +@deftypefunx void disfluid_cache_entry_get_response_date (const struct disfluid_cache_entry *@var{entry}, struct timespec *@var{response_date}) +@deftypefunx int disfluid_cache_entry_is_invalidated (const struct disfluid_cache_entry *@var{entry}) +Query the metadata of @var{entry}. +@end deftypefun + +@deftypefun size_t disfluid_cache_entry_get_key (const struct disfluid_cache_entry *@var{entry}, size_t @var{start}, size_t @var{max}, char *@var{dst}) +@deftypefunx size_t disfluid_cache_entry_get_header (const struct disfluid_cache_entry *@var{entry}, size_t @var{start}, size_t @var{max}, char *@var{dst}) +@deftypefunx size_t disfluid_cache_entry_get_body (const struct disfluid_cache_entry *@var{entry}, size_t @var{start}, size_t @var{max}, char *@var{dst}) +Ignore the first @var{start} bytes, and copy the next bytes of the +relevant section in @var{entry}, up to @var{max}, to @var{dst}. Return +the total length of the section. If there is more room allocated, make +sure that @var{dst} is NUL-terminated. +@end deftypefun + +@deftypefun int disfluid_cache_entry_load (struct disfluid_cache_entry *@var{entry}, int @var{load_key}, int @var{load_header}, int @var{load_body}, ssize_t (*@var{read_impl}) (void *, void *, size_t), int (*@var{skip}) (void *, size_t), void *@var{context}) +@deftypefunx int disfluid_cache_entry_read (struct disfluid_cache_entry *@var{entry}, int @var{load_key}, int @var{load_header}, int @var{load_body}, int @var{fd}) +@deftypefunx int disfluid_cache_entry_fread (struct disfluid_cache_entry *@var{entry}, int @var{load_key}, int @var{load_header}, int @var{load_body}, FILE *@var{handle}) +Load a file to set up @var{entry}. If @var{load_key} (respectively, +@var{load_header} and @var{load_body}) is a strictly positive value, +then attempt to read the key (respectively, the header and body). If +it is 0, then skip it, but keep the previously loaded value. If it is +negative, then erase the value. The specific +@code{disfluid_cache_entry_read} and @code{disfluid_cache_entry_fread} +use a file descriptor and a stream file handle respectively, instead +of custom callbacks. + +@code{disfluid_cache_entry_load} uses custom callbacks to read the +file. The file is passed as @var{context}, and during the calls to +@var{read_impl} and @var{skip}, it is bound to the first argument. The +second argument of @var{read_impl} is bound to a buffer where to read +the bytes. The last argument is bound to the read request size, in +bytes. + +@var{read_impl} is supposed to return a negative value if a file +system failure occurs, 0 if the file is at end, or a value at most +equal to the read request, and set the bytes in the buffer +accordingly. @var{skip} is supposed to return 0 on success and advance +the file pointer by the request size, or return a negative error code. + +The loading functions return 0 on success, -1 in case of a file system +error, -2 if the file is invalid, -3 if it is incomplete, -4 if the +key is too large, -5 if the header is too large, -6 if the body is too +large. +@end deftypefun + +@deftypefun int disfluid_cache_entry_save (const struct disfluid_cache_entry *@var{entry}, ssize_t (*@var{write_impl}) (void *, const void *, size_t), void *@var{context}) +@deftypefunx int disfluid_cache_entry_write (const struct disfluid_cache_entry *@var{entry}, int @var{fd}) +@deftypefunx int disfluid_cache_entry_fwrite (const struct disfluid_cache_entry *@var{entry}, FILE *@var{handle}) +Save @var{entry} to a file. Return 0 on success, -1 if writing to the +file raised an error, -2 if the key, header or body is unset in +@var{entry}. @var{write_impl} is called with @var{context} bound to +the first argument, an array of bytes to write as the second, and the +array length as the third. It should return a negative value to signal +an error, or a value at most the write request length, and write the +corresponding bytes in the request to file. +@end deftypefun + @bye diff --git a/include/disfluid/cache_entry.h b/include/disfluid/cache_entry.h index fcfb43f..6004932 100644 --- a/include/disfluid/cache_entry.h +++ b/include/disfluid/cache_entry.h @@ -15,8 +15,7 @@ extern "C" size_t max_header, size_t max_body); LIBDISFLUID_API LIBDISFLUID_CONST extern size_t - disfluid_cache_entry_alignment (size_t max_key, - size_t max_header, size_t max_body); + disfluid_cache_entry_alignment (void); LIBDISFLUID_API extern void disfluid_cache_entry_init (struct disfluid_cache_entry *entry, @@ -67,6 +66,20 @@ extern "C" LIBDISFLUID_API extern void disfluid_cache_entry_invalidate (struct disfluid_cache_entry *entry); + LIBDISFLUID_API extern int + disfluid_cache_entry_set_key (struct disfluid_cache_entry *entry, + const char *key); + + LIBDISFLUID_API extern int + disfluid_cache_entry_set_response_header (struct disfluid_cache_entry + *entry, const char *header); + + LIBDISFLUID_API extern int + disfluid_cache_entry_set_response_body (struct disfluid_cache_entry + *entry, + size_t body_length, + const char *body); + LIBDISFLUID_API extern void disfluid_cache_entry_get_request_date (const struct disfluid_cache_entry *entry, struct timespec *date); @@ -79,19 +92,39 @@ extern "C" disfluid_cache_entry_is_invalidated (const struct disfluid_cache_entry *entry); + LIBDISFLUID_API extern size_t + disfluid_cache_entry_get_key (const struct disfluid_cache_entry *entry, + size_t start, size_t max, char *key); + + LIBDISFLUID_API extern size_t + disfluid_cache_entry_get_header (const struct disfluid_cache_entry *entry, + size_t start, size_t max, char *header); + + LIBDISFLUID_API extern size_t + disfluid_cache_entry_get_body (const struct disfluid_cache_entry *entry, + size_t start, size_t max, char *body); + LIBDISFLUID_NODISCARD LIBDISFLUID_API extern int disfluid_cache_entry_load (struct disfluid_cache_entry *entry, + int load_key, + int load_header, + int load_body, ssize_t (*read_impl) (void *context, void *buffer, size_t max_size), + int (*skip) (void *context, size_t size), void *context); LIBDISFLUID_NODISCARD LIBDISFLUID_FD_ARG_READ_2 LIBDISFLUID_API extern int disfluid_cache_entry_read (struct disfluid_cache_entry *entry, - int fd); + int load_key, + int load_header, + int load_body, int fd); LIBDISFLUID_NODISCARD LIBDISFLUID_API extern int - disfluid_cache_entry_fread (struct disfluid_cache_entry *entry, FILE * f); + disfluid_cache_entry_fread (struct disfluid_cache_entry *entry, + int load_key, + int load_header, int load_body, FILE * f); LIBDISFLUID_NODISCARD LIBDISFLUID_API extern int disfluid_cache_entry_save (const struct disfluid_cache_entry *entry, @@ -108,8 +141,12 @@ extern "C" disfluid_cache_entry_fwrite (const struct disfluid_cache_entry *entry, FILE * f); -# define DISFLUID_CACHE_ENTRY_ALLOC(ptr, max_header, max_body) \ - (ptr = disfluid_cache_entry_alloc (max_header, max_body), \ +# define DISFLUID_CACHE_ENTRY_ALLOC(ptr, max_key, max_header, max_body) \ + (ptr = disfluid_cache_entry_alloc (max_key, max_header, max_body), \ + assert (sizeof (size_t) == sizeof (struct disfluid_cache_entry *)), \ + size_t offset, \ + memcpy (&offset, &ptr, sizeof (size_t)), \ + assert (offset % disfluid_cache_entry_alignment () == 0), \ ptr == NULL ? -1 : 0) # define DISFLUID_CACHE_ENTRY_FREE(ptr) \ diff --git a/src/libdisfluid/disfluid-cache-entry.h b/src/libdisfluid/disfluid-cache-entry.h index eb09cdf..ea76217 100644 --- a/src/libdisfluid/disfluid-cache-entry.h +++ b/src/libdisfluid/disfluid-cache-entry.h @@ -4,8 +4,7 @@ MAYBE_UNUSED static size_t cache_entry_size (size_t max_key, size_t max_header, size_t max_body); -MAYBE_UNUSED static size_t -cache_entry_alignment (size_t max_key, size_t max_header, size_t max_body); +MAYBE_UNUSED static size_t cache_entry_alignment (void); MAYBE_UNUSED static void cache_entry_init (struct disfluid_cache_entry *entry, size_t max_key, @@ -16,6 +15,10 @@ cache_entry_minimum_size (const struct disfluid_cache_entry *entry, size_t *used_key, size_t *used_header, size_t *used_body); +MAYBE_UNUSED static int +cache_entry_copy (struct disfluid_cache_entry *restrict dest, + const struct disfluid_cache_entry *restrict src); + MAYBE_UNUSED static struct disfluid_cache_entry *cache_entry_alloc (size_t max_key, size_t max_header, size_t max_body); @@ -40,18 +43,36 @@ MAYBE_UNUSED static void cache_entry_set_invalidated (struct disfluid_cache_entry *entry, bool invalidated); +MAYBE_UNUSED static int +cache_entry_set_key (struct disfluid_cache_entry *entry, const char *key); + +MAYBE_UNUSED static int +cache_entry_set_response_header (struct disfluid_cache_entry *entry, + const char *header); + +MAYBE_UNUSED static int +cache_entry_set_response_body (struct disfluid_cache_entry *entry, + size_t length, const char *body); + /* Return value: 0 -> OK, -1 -> read_impl error, -2 -> invalid file, - -3 -> incomplete file. */ + -3 -> incomplete file, -4 -> key too large, -5 -> header too large, + -6 -> body too large. */ MAYBE_UNUSED static int cache_entry_load (struct disfluid_cache_entry *entry, + int load_key, + int load_header, + int load_body, ssize_t (*read_impl) (void *context, void *buffer, - size_t max_size), void *context); + size_t max_size), + int (*skip) (void *context, size_t size), void *context); MAYBE_UNUSED static int -cache_entry_read (struct disfluid_cache_entry *entry, int fd); +cache_entry_read (struct disfluid_cache_entry *entry, int load_key, + int load_header, int load_body, int fd); MAYBE_UNUSED static int -cache_entry_fread (struct disfluid_cache_entry *entry, FILE * f); +cache_entry_fread (struct disfluid_cache_entry *entry, int load_key, + int load_header, int load_body, FILE * f); MAYBE_UNUSED static void cache_entry_get_request_date (const struct disfluid_cache_entry *entry, @@ -64,7 +85,25 @@ cache_entry_get_response_date (const struct disfluid_cache_entry *entry, MAYBE_UNUSED static bool cache_entry_is_invalidated (const struct disfluid_cache_entry *entry); -/* -1: write_impl failed. 0: OK. */ +MAYBE_UNUSED static void +cache_entry_is_loaded (const struct disfluid_cache_entry *entry, + bool *key_loaded, bool *header_loaded, + bool *body_loaded); + +/* If max is too large, key will be NUL-terminated. */ +MAYBE_UNUSED static size_t +cache_entry_get_key (const struct disfluid_cache_entry *entry, size_t start, + size_t max, char *key); + +MAYBE_UNUSED static size_t +cache_entry_get_header (const struct disfluid_cache_entry *entry, + size_t start, size_t max, char *header); + +MAYBE_UNUSED static size_t +cache_entry_get_body (const struct disfluid_cache_entry *entry, size_t start, + size_t max, char *body); + +/* -1: write_impl failed. 0: OK. -2: the entry is not fully loaded. */ MAYBE_UNUSED static int cache_entry_save (const struct disfluid_cache_entry *entry, ssize_t (*write_impl) (void *context, const void *buffer, @@ -77,69 +116,69 @@ MAYBE_UNUSED static int cache_entry_fwrite (const struct disfluid_cache_entry *entry, FILE * f); # include <assert.h> +# include <flexmember.h> # include "disfluid-init.h" +# define INVALIDATED 0b1000 +# define KEY_NOT_LOADED 0b0001 +# define HEADER_NOT_LOADED 0b0010 +# define BODY_NOT_LOADED 0b0100 + struct disfluid_cache_entry { struct timespec request_date; struct timespec response_date; - bool invalidated; + int flags; size_t max_key; size_t max_header; size_t max_body; + size_t key_length; + size_t header_length; + size_t body_length; + char data[FLEXIBLE_ARRAY_MEMBER]; /* Contains max_key + 1 + + max_header + 1 + max_body + elements */ + /* The key and header are NUL-terminated in memory. */ }; -static void * +static char * +cache_entry_internal_data (struct disfluid_cache_entry *entry) +{ + return &(entry->data[0]); +} + +static char * cache_entry_key (struct disfluid_cache_entry *entry) { - size_t entry_offset; - memcpy (&entry_offset, &entry, sizeof (entry)); - size_t key_offset = entry_offset + sizeof (struct disfluid_cache_entry); - void *ret; - memcpy (&ret, &key_offset, sizeof (size_t)); - return ret; + return cache_entry_internal_data (entry); } -static void * +static char * cache_entry_header (struct disfluid_cache_entry *entry) { - size_t entry_offset; - memcpy (&entry_offset, &entry, sizeof (entry)); - size_t header_offset = - entry_offset + sizeof (struct disfluid_cache_entry) + entry->max_key; - void *ret; - memcpy (&ret, &header_offset, sizeof (size_t)); - return ret; + char *key = cache_entry_key (entry); + return &(key[entry->max_key + 1]); } -static void * +static char * cache_entry_body (struct disfluid_cache_entry *entry) { - size_t entry_offset; - memcpy (&entry_offset, &entry, sizeof (entry)); - size_t body_offset = - entry_offset + sizeof (struct disfluid_cache_entry) - + entry->max_key + entry->max_header; - void *ret; - memcpy (&ret, &body_offset, sizeof (size_t)); - return ret; + char *key = cache_entry_header (entry); + return &(key[entry->max_header + 1]); } static size_t cache_entry_size (size_t max_key, size_t max_header, size_t max_body) { - return sizeof (struct disfluid_cache_entry) - + max_key + max_header + max_body; + return FLEXSIZEOF (struct disfluid_cache_entry, data, + max_key + 1 + max_header + 1 + max_body); } static size_t -cache_entry_alignment (size_t max_key, size_t max_header, size_t max_body) +cache_entry_alignment (void) { - (void) max_key; - (void) max_header; - (void) max_body; - return alignof (struct disfluid_cache_entry); + return FLEXALIGNOF (struct disfluid_cache_entry); } static void @@ -151,10 +190,17 @@ cache_entry_init (struct disfluid_cache_entry *entry, size_t max_key, entry->request_date.tv_nsec = 0; entry->response_date.tv_sec = 0; entry->response_date.tv_nsec = 0; - entry->invalidated = false; + entry->flags = KEY_NOT_LOADED | HEADER_NOT_LOADED | BODY_NOT_LOADED; entry->max_key = max_key; entry->max_header = max_header; entry->max_body = max_body; + entry->key_length = 0; + entry->header_length = 0; + entry->body_length = 0; + char *key = cache_entry_key (entry); + char *header = cache_entry_key (entry); + *key = '\0'; + *header = '\0'; } static void @@ -163,9 +209,16 @@ cache_entry_minimum_size (const struct disfluid_cache_entry *entry, size_t *min_body) { (void) entry; - *min_key = 0; - *min_header = 0; - *min_body = 0; + *min_key = entry->key_length; + *min_header = entry->header_length; + *min_body = entry->body_length; + assert (entry->flags & KEY_NOT_LOADED + || (strlen (cache_entry_key ((struct disfluid_cache_entry *) entry)) + == entry->key_length)); + assert (entry->flags & HEADER_NOT_LOADED + || + (strlen (cache_entry_header ((struct disfluid_cache_entry *) entry)) + == entry->header_length)); } static int @@ -176,17 +229,17 @@ cache_entry_copy (struct disfluid_cache_entry *restrict dest, dest->request_date.tv_nsec = src->request_date.tv_nsec; dest->response_date.tv_sec = src->response_date.tv_sec; dest->response_date.tv_nsec = src->response_date.tv_nsec; - dest->invalidated = src->invalidated; + dest->flags = src->flags; size_t src_key_size, src_header_size, src_body_size; cache_entry_minimum_size (src, &src_key_size, &src_header_size, &src_body_size); - void *dest_key = cache_entry_key (dest); - void *dest_header = cache_entry_header (dest); - void *dest_body = cache_entry_body (dest); - const void *src_key = cache_entry_key ((struct disfluid_cache_entry *) src); - const void *src_header = + char *dest_key = cache_entry_key (dest); + char *dest_header = cache_entry_header (dest); + char *dest_body = cache_entry_body (dest); + const char *src_key = cache_entry_key ((struct disfluid_cache_entry *) src); + const char *src_header = cache_entry_header ((struct disfluid_cache_entry *) src); - const void *src_body = + const char *src_body = cache_entry_body ((struct disfluid_cache_entry *) src); int flags = 0; if (src_key_size > dest->max_key) @@ -203,9 +256,18 @@ cache_entry_copy (struct disfluid_cache_entry *restrict dest, } if (flags == 0) { - memcpy (dest_key, src_key, src_key_size); - memcpy (dest_header, src_header, src_header_size); - memcpy (dest_body, src_body, src_body_size); + if (!(src->flags & KEY_NOT_LOADED)) + { + strcpy (dest_key, src_key); + } + if (!(src->flags & HEADER_NOT_LOADED)) + { + strcpy (dest_header, src_header); + } + if (!(src->flags & BODY_NOT_LOADED)) + { + memcpy (dest_body, src_body, src_body_size); + } } return -flags; } @@ -214,8 +276,7 @@ static struct disfluid_cache_entry * cache_entry_alloc (size_t max_key, size_t max_header, size_t max_body) { const size_t size = cache_entry_size (max_key, max_header, max_body); - const size_t alignment = - cache_entry_alignment (max_key, max_header, max_body); + const size_t alignment = cache_entry_alignment (); struct disfluid_cache_entry *ret = calloc (1, size); if (ret != NULL) { @@ -287,14 +348,62 @@ cache_entry_set_response_date (struct disfluid_cache_entry *entry, static void cache_entry_invalidate (struct disfluid_cache_entry *entry) { - entry->invalidated = true; + cache_entry_set_invalidated (entry, true); } static void cache_entry_set_invalidated (struct disfluid_cache_entry *entry, bool invalidated) { - entry->invalidated = invalidated; + if (invalidated) + { + entry->flags = entry->flags | INVALIDATED; + } + else + { + entry->flags = entry->flags & ~INVALIDATED; + } +} + +static int +cache_entry_set_key (struct disfluid_cache_entry *entry, const char *key) +{ + if (strlen (key) < entry->max_key) + { + strcpy (cache_entry_key (entry), key); + entry->key_length = strlen (key); + entry->flags = entry->flags & ~KEY_NOT_LOADED; + return 0; + } + return -1; +} + +static int +cache_entry_set_response_header (struct disfluid_cache_entry *entry, + const char *header) +{ + if (strlen (header) < entry->max_header) + { + strcpy (cache_entry_header (entry), header); + entry->header_length = strlen (header); + entry->flags = entry->flags & ~HEADER_NOT_LOADED; + return 0; + } + return -1; +} + +static int +cache_entry_set_response_body (struct disfluid_cache_entry *entry, + size_t length, const char *body) +{ + if (length <= entry->max_body) + { + memcpy (cache_entry_body (entry), body, length); + entry->body_length = length; + entry->flags = entry->flags & ~BODY_NOT_LOADED; + return 0; + } + return -1; } /* The cache entry file is composed as: */ @@ -305,10 +414,17 @@ cache_entry_set_invalidated (struct disfluid_cache_entry *entry, tv_nsec field (must be < 1e9). */ /* The response date on 12 bytes. */ /* On 1 byte: a value >= 128 if invalidated, < 128 if still valid. */ +/* On 8 bytes: the key length. */ +/* The key. */ +/* On 8 bytes: the header length. */ +/* The header. */ +/* On 8 bytes: the body length. */ +/* The body. */ struct disfluid_cache_entry_loader { ssize_t (*read_impl) (void *, void *, size_t); + int (*skip) (void *, size_t); void *context; }; @@ -338,6 +454,12 @@ cache_entry_really_load_n (struct disfluid_cache_entry_loader *loader, } static int +cache_entry_skip_n (struct disfluid_cache_entry_loader *loader, size_t n) +{ + return loader->skip (loader->context, n); +} + +static int cache_entry_load_magic (struct disfluid_cache_entry_loader *loader) { uint8_t magic[16]; @@ -393,12 +515,118 @@ cache_entry_load_flags (struct disfluid_cache_entry_loader *loader, } static int +cache_entry_skip_or_load_section (struct disfluid_cache_entry_loader *loader, + int mode, + int flag_bit, + int *restrict flags_value, + size_t *restrict length, + bool nul_terminate, + size_t max_data, char *restrict data) +{ + uint8_t size[sizeof (*length)] = { 0 }; + int error = cache_entry_really_load_n (loader, sizeof (size), size); + if (error) + { + return error; + } + *length = 0; + for (size_t i = 0; i < sizeof (size); i++) + { + *length *= 256; + *length += size[i]; + } + if (*length > max_data) + { + return -4; + } + if (mode <= 0) + { + if (mode < 0) + { + *flags_value = *flags_value | flag_bit; + if (max_data != 0) + { + *data = '\0'; + } + } + error = cache_entry_skip_n (loader, *length); + } + else + { + error = cache_entry_really_load_n (loader, *length, (uint8_t *) data); + } + if (error == 0 && nul_terminate) + { + /* We are allowed to access data[max_data]. */ + data[*length] = '\0'; + } + return error; +} + +static int +cache_entry_skip_or_load_key (struct disfluid_cache_entry_loader *loader, + int mode, + int *restrict flags, + size_t *restrict length, + size_t max_data, char *restrict data) +{ + int error = cache_entry_skip_or_load_section (loader, mode, KEY_NOT_LOADED, + flags, length, true, max_data, + data); + if (error == -4) + { + /* -4 means the recipient is too small. Coincidentally, it is + also the correct return value for cache_entry_load. */ + return -4; + } + return error; +} + +static int +cache_entry_skip_or_load_header (struct disfluid_cache_entry_loader *loader, + int mode, + int *restrict flags, + size_t *restrict length, + size_t max_data, char *restrict data) +{ + int error = + cache_entry_skip_or_load_section (loader, mode, HEADER_NOT_LOADED, + flags, length, true, max_data, data); + if (error == -4) + { + return -5; + } + return error; +} + +static int +cache_entry_skip_or_load_body (struct disfluid_cache_entry_loader *loader, + int mode, + int *restrict flags, + size_t *restrict length, + size_t max_data, char *restrict data) +{ + int error = cache_entry_skip_or_load_section (loader, mode, BODY_NOT_LOADED, + flags, length, true, max_data, + data); + if (error == -4) + { + return -6; + } + return error; +} + +static int cache_entry_load (struct disfluid_cache_entry *entry, + int load_key, + int load_header, + int load_body, ssize_t (*read_impl) (void *, void *, size_t), - void *context) + int (*skip) (void *, size_t), void *context) { struct disfluid_cache_entry_loader loader = { .read_impl = read_impl, + .skip = skip, .context = context }; int error = 0; @@ -422,6 +650,29 @@ cache_entry_load (struct disfluid_cache_entry *entry, } if (error == 0) { + error = + cache_entry_skip_or_load_key (&loader, load_key, &(entry->flags), + &(entry->key_length), entry->max_key, + cache_entry_key (entry)); + } + if (error == 0) + { + error = + cache_entry_skip_or_load_header (&loader, load_header, + &(entry->flags), + &(entry->header_length), + entry->max_header, + cache_entry_header (entry)); + } + if (error == 0) + { + error = + cache_entry_skip_or_load_body (&loader, load_body, &(entry->flags), + &(entry->body_length), entry->max_body, + cache_entry_body (entry)); + } + if (error == 0) + { cache_entry_set_request_date (entry, &request_date); cache_entry_set_response_date (entry, &response_date); cache_entry_set_invalidated (entry, invalidated); @@ -436,6 +687,18 @@ disfluid_cache_entry_loader_read (void *context, void *buffer, size_t max) return read (*fdp, buffer, max); } +static int +disfluid_cache_entry_loader_lseek (void *context, size_t size) +{ + int *fdp = context; + off_t error = lseek (*fdp, size, SEEK_CUR); + if (error < 0) + { + return -1; + } + return 0; +} + static ssize_t disfluid_cache_entry_loader_fread (void *context, void *buffer, size_t max) { @@ -452,16 +715,34 @@ disfluid_cache_entry_loader_fread (void *context, void *buffer, size_t max) } static int -cache_entry_read (struct disfluid_cache_entry *entry, int fd) +disfluid_cache_entry_loader_fseek (void *context, size_t size) +{ + FILE *f = context; + off_t error = fseek (f, size, SEEK_CUR); + if (error < 0) + { + return -1; + } + return 0; +} + +static int +cache_entry_read (struct disfluid_cache_entry *entry, int load_key, + int load_header, int load_body, int fd) { int *fdp = &fd; - return cache_entry_load (entry, disfluid_cache_entry_loader_read, fdp); + return cache_entry_load (entry, load_key, load_header, load_body, + disfluid_cache_entry_loader_read, + disfluid_cache_entry_loader_lseek, fdp); } static int -cache_entry_fread (struct disfluid_cache_entry *entry, FILE * f) +cache_entry_fread (struct disfluid_cache_entry *entry, int load_key, + int load_header, int load_body, FILE * f) { - return cache_entry_load (entry, disfluid_cache_entry_loader_fread, f); + return cache_entry_load (entry, load_key, load_header, load_body, + disfluid_cache_entry_loader_fread, + disfluid_cache_entry_loader_fseek, f); } static void @@ -483,7 +764,17 @@ cache_entry_get_response_date (const struct disfluid_cache_entry *entry, static bool cache_entry_is_invalidated (const struct disfluid_cache_entry *entry) { - return entry->invalidated; + return (entry->flags & INVALIDATED) != 0; +} + +static void +cache_entry_is_loaded (const struct disfluid_cache_entry *entry, + bool *key_loaded, bool *header_loaded, + bool *body_loaded) +{ + *key_loaded = !(entry->flags & KEY_NOT_LOADED); + *header_loaded = !(entry->flags & HEADER_NOT_LOADED); + *body_loaded = !(entry->flags & BODY_NOT_LOADED); } struct disfluid_cache_entry_saver @@ -492,6 +783,66 @@ struct disfluid_cache_entry_saver void *context; }; +static size_t +cache_entry_careful_copy (size_t skip, size_t dest_max, char *dest, + size_t source_length, const char *source) +{ + size_t max = dest_max; + const char *relevant_source = NULL; + if (skip <= source_length) + { + relevant_source = &(source[skip]); + size_t max_source = source_length - skip; + max = max_source; + if (max > dest_max) + { + max = dest_max; + } + assert (skip + max <= source_length); + assert (max <= dest_max); + } + else + { + max = 0; + } + memcpy (dest, relevant_source, max); + if (max < dest_max) + { + dest[max] = '\0'; + } + return source_length; +} + +static size_t +cache_entry_get_key (const struct disfluid_cache_entry *entry, size_t start, + size_t max, char *key) +{ + return cache_entry_careful_copy (start, max, key, entry->key_length, + cache_entry_key ((struct + disfluid_cache_entry *) + entry)); +} + +static size_t +cache_entry_get_header (const struct disfluid_cache_entry *entry, + size_t start, size_t max, char *header) +{ + return cache_entry_careful_copy (start, max, header, entry->header_length, + cache_entry_header ((struct + disfluid_cache_entry + *) entry)); +} + +static size_t +cache_entry_get_body (const struct disfluid_cache_entry *entry, size_t start, + size_t max, char *body) +{ + return cache_entry_careful_copy (start, max, body, entry->body_length, + cache_entry_body ((struct + disfluid_cache_entry *) + entry)); +} + static int cache_entry_really_save_n (struct disfluid_cache_entry_saver *saver, size_t n, const uint8_t * data) @@ -559,6 +910,26 @@ cache_entry_save_flags (struct disfluid_cache_entry_saver *saver, } static int +cache_entry_save_section (struct disfluid_cache_entry_saver *saver, + size_t length, const char *section) +{ + uint8_t size[sizeof (length)] = { 0 }; + size_t size_n = length; + for (size_t i = sizeof (length); i-- > 0;) + { + size[i] = size_n % 256; + size_n /= 256; + } + int error = cache_entry_really_save_n (saver, sizeof (length), size); + if (error == 0) + { + error = + cache_entry_really_save_n (saver, length, (const uint8_t *) section); + } + return error; +} + +static int cache_entry_save (const struct disfluid_cache_entry *entry, ssize_t (*write_impl) (void *context, const void *buffer, size_t max_size), void *context) @@ -568,6 +939,10 @@ cache_entry_save (const struct disfluid_cache_entry *entry, .context = context }; int error = 0; + if (entry->flags & (KEY_NOT_LOADED | HEADER_NOT_LOADED | BODY_NOT_LOADED)) + { + error = -2; + } if (error == 0) { error = cache_entry_save_magic (&saver); @@ -582,7 +957,31 @@ cache_entry_save (const struct disfluid_cache_entry *entry, } if (error == 0) { - error = cache_entry_save_flags (&saver, entry->invalidated); + error = cache_entry_save_flags (&saver, !!(entry->flags & INVALIDATED)); + } + if (error == 0) + { + error = + cache_entry_save_section (&saver, entry->key_length, + cache_entry_key ((struct + disfluid_cache_entry *) + entry)); + } + if (error == 0) + { + error = + cache_entry_save_section (&saver, entry->header_length, + cache_entry_header ((struct + disfluid_cache_entry *) + entry)); + } + if (error == 0) + { + error = + cache_entry_save_section (&saver, entry->body_length, + cache_entry_body ((struct + disfluid_cache_entry *) + entry)); } return error; } diff --git a/src/libdisfluid/disfluid-tests.h b/src/libdisfluid/disfluid-tests.h index 6633872..df6d534 100644 --- a/src/libdisfluid/disfluid-tests.h +++ b/src/libdisfluid/disfluid-tests.h @@ -92,7 +92,7 @@ START_TEST (test_read_invalid_magic_number) { abort (); } - int error = cache_entry_read (entry, fd); + int error = cache_entry_read (entry, true, true, true, fd); cache_entry_free (entry); close (fd); ck_assert_int_eq (error, -2); @@ -104,13 +104,26 @@ END_TEST static inline void test_check_partial_situation (size_t n_partial) { - uint8_t data[16 + 12 + 12 + 1] = { 0 }; - memcpy (data, "disfluid c.entry", 16); - data[16 + 8 - 1] = 1; - data[16 + 12 - 1] = 2; - data[16 + 12 + 8 - 1] = 3; - data[16 + 12 + 12 - 1] = 4; - data[16 + 12 + 12 + 1 - 1] = 0x80; + const uint8_t data[] = { 'd', 'i', 's', 'f', 'l', 'u', 'i', 'd', + ' ', 'c', '.', 'e', 'n', 't', 'r', 'y', + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 4, + 128, + 0, 0, 0, 0, 0, 0, 0, 25, + 'G', 'E', 'T', ' ', 'h', 't', 't', 'p', + 's', ':', '/', '/', 'e', 'x', 'a', 'm', + 'p', 'l', 'e', '.', 'c', 'o', 'm', '\r', + '\n', + 0, 0, 0, 0, 0, 0, 0, 22, + 't', 'h', 'e', ' ', 'r', 'e', 's', 'p', + 'o', 'n', 's', 'e', ' ', 'h', 'e', 'a', + 'd', 'e', 'r', '.', '.', '.', + 0, 0, 0, 0, 0, 0, 0, 20, + 't', 'h', 'e', ' ', 'r', 'e', 's', 'p', + 'o', 'n', 's', 'e', ' ', 'b', 'o', 'd', + 'y', '.', '.', '.' + }; + assert (sizeof (data) == 132); /* The data has a request date of 1s + 2ns, response of 3s + 4ns, and is invalidated. */ assert (n_partial <= sizeof (data) / sizeof (data[0])); @@ -125,7 +138,7 @@ test_check_partial_situation (size_t n_partial) { abort (); } - int error = cache_entry_read (entry, fd); + int error = cache_entry_read (entry, 1, 1, 1, fd); if (n_partial < sizeof (data) / sizeof (data[0])) { ck_assert_int_eq (error, -3); @@ -137,11 +150,26 @@ test_check_partial_situation (size_t n_partial) cache_entry_get_request_date (entry, &request); cache_entry_get_response_date (entry, &response); bool invalidated = cache_entry_is_invalidated (entry); + char key[512 BYTES]; + char header[4 KILOBYTES]; + char body[4 KILOBYTES]; /* Do NOT allocate 2 megabytes on the + stack, valgrind finds it + offensive. */ ck_assert_int_eq (request.tv_sec, 1); ck_assert_int_eq (request.tv_nsec, 2); ck_assert_int_eq (response.tv_sec, 3); ck_assert_int_eq (response.tv_nsec, 4); ck_assert (invalidated); + ck_assert_int_eq (cache_entry_get_key (entry, 0, sizeof (key), key), + 25); + ck_assert_int_eq (cache_entry_get_header + (entry, 0, sizeof (header), header), 22); + ck_assert_int_eq (cache_entry_get_body (entry, 0, sizeof (body), body), + 20); + body[strlen ("the response body...")] = '\0'; + ck_assert_str_eq (key, "GET https://example.com\r\n"); + ck_assert_str_eq (header, "the response header..."); + ck_assert_str_eq (body, "the response body..."); } cache_entry_free (entry); close (fd); @@ -254,6 +282,36 @@ END_TEST /* *INDENT-ON* */ /* *INDENT-ON* */ +START_TEST (test_read_cache_file_partial_50) +/* *INDENT-ON* */ +{ + test_check_partial_situation (50); +} +/* *INDENT-OFF* */ +END_TEST +/* *INDENT-ON* */ + +/* *INDENT-ON* */ +START_TEST (test_read_cache_file_partial_131) +/* *INDENT-ON* */ +{ + test_check_partial_situation (131); +} +/* *INDENT-OFF* */ +END_TEST +/* *INDENT-ON* */ + +/* *INDENT-ON* */ +START_TEST (test_read_cache_file_partial_132) +/* *INDENT-ON* */ +{ + test_check_partial_situation (132); +} +/* *INDENT-OFF* */ +END_TEST +/* *INDENT-ON* */ + +/* *INDENT-ON* */ START_TEST (test_load_and_shrink) /* *INDENT-ON* */ { @@ -277,9 +335,7 @@ START_TEST (test_load_and_shrink) const size_t required_size = cache_entry_size (default_key_size, default_header_size, default_body_size); - const size_t required_alignment = - cache_entry_alignment (default_key_size, default_header_size, - default_body_size); + const size_t required_alignment = cache_entry_alignment (); entry = calloc (1, required_size); if (entry == NULL) { @@ -342,7 +398,7 @@ START_TEST (test_write_cache_entry) /* *INDENT-ON* */ { - uint8_t old_data[16 + 12 + 12 + 1] = { 0 }; + uint8_t old_data[16 + 12 + 12 + 1 + 8 + 8 + 8 + 1024] = { 0 }; memcpy (old_data, "disfluid c.entry", 16); old_data[16 + 8 - 1] = 1; old_data[16 + 12 - 1] = 2; @@ -372,10 +428,13 @@ START_TEST (test_write_cache_entry) cache_entry_set_request_date (entry, &request); cache_entry_set_response_date (entry, &response); cache_entry_set_invalidated (entry, false); + cache_entry_set_key (entry, "GET https://example.com\r\n"); + cache_entry_set_response_header (entry, "the response header..."); + cache_entry_set_response_body (entry, strlen ("the response body..."), + "the response body..."); int error = cache_entry_write (entry, fd); ck_assert_int_eq (error, 0); - uint8_t check_data[16 + 12 + 12 + 1]; - assert (sizeof (check_data) == sizeof (old_data)); + uint8_t check_data[16 + 12 + 12 + 1 + 8 + 8 + 8 + 25 + 22 + 20]; error = lseek (fd, 0, SEEK_SET); ck_assert_int_eq (error, 0); struct disfluid_cache_entry_loader read_loader = { @@ -387,12 +446,37 @@ START_TEST (test_write_cache_entry) (&read_loader, (sizeof (check_data) / sizeof (check_data[0])), check_data)); ck_assert_int_eq (check_error, 0); - ck_assert_int_eq (memcmp (check_data, "disfluid c.entry", 16), 0); - ck_assert_int_eq (check_data[16 + 8 - 1], 10); - ck_assert_int_eq (check_data[16 + 12 - 1], 11); - ck_assert_int_eq (check_data[16 + 12 + 8 - 1], 12); - ck_assert_int_eq (check_data[16 + 12 + 12 - 1], 13); - ck_assert_int_eq (check_data[16 + 12 + 12 + 1 - 1], 0); + static const uint8_t expected_data[] = + { 'd', 'i', 's', 'f', 'l', 'u', 'i', 'd', + ' ', 'c', '.', 'e', 'n', 't', 'r', 'y', + 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 11, + 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 13, + 0, + 0, 0, 0, 0, 0, 0, 0, 25, + 'G', 'E', 'T', ' ', 'h', 't', 't', 'p', + 's', ':', '/', '/', 'e', 'x', 'a', 'm', + 'p', 'l', 'e', '.', 'c', 'o', 'm', '\r', + '\n', + 0, 0, 0, 0, 0, 0, 0, 22, + 't', 'h', 'e', ' ', 'r', 'e', 's', 'p', + 'o', 'n', 's', 'e', ' ', 'h', 'e', 'a', + 'd', 'e', 'r', '.', '.', '.', + 0, 0, 0, 0, 0, 0, 0, 20, + 't', 'h', 'e', ' ', 'r', 'e', 's', 'p', + 'o', 'n', 's', 'e', ' ', 'b', 'o', 'd', + 'y', '.', '.', '.', + }; + ck_assert_int_eq (sizeof (expected_data), sizeof (check_data)); + for (size_t i = 0; i < sizeof (expected_data); i++) + { + if (check_data[i] != expected_data[i]) + { + fprintf (stderr, "%s:%d: %lu: %d vs %d.\n", __FILE__, __LINE__, i, + (int) check_data[i], (int) expected_data[i]); + } + } + ck_assert_int_eq (memcmp + (check_data, expected_data, sizeof (expected_data)), 0); close (fd); cache_entry_free (entry); } @@ -698,6 +782,9 @@ run_tests (size_t *n_tests, size_t *n_errors) tcase_add_test (cache_entry, test_read_cache_file_partial_37); tcase_add_test (cache_entry, test_read_cache_file_partial_40); tcase_add_test (cache_entry, test_read_cache_file_partial_41); + tcase_add_test (cache_entry, test_read_cache_file_partial_50); + tcase_add_test (cache_entry, test_read_cache_file_partial_131); + tcase_add_test (cache_entry, test_read_cache_file_partial_132); tcase_add_test (cache_entry, test_load_and_shrink); tcase_add_test (cache_entry, test_write_cache_entry); suite_add_tcase (suite, cache_entry); diff --git a/src/libdisfluid/main.c b/src/libdisfluid/main.c index 4d5b573..6b21295 100644 --- a/src/libdisfluid/main.c +++ b/src/libdisfluid/main.c @@ -124,10 +124,9 @@ disfluid_cache_entry_size (size_t max_key, size_t max_header, size_t max_body) } size_t -disfluid_cache_entry_alignment (size_t max_key, size_t max_header, - size_t max_body) +disfluid_cache_entry_alignment (void) { - return cache_entry_alignment (max_key, max_header, max_body); + return cache_entry_alignment (); } void @@ -198,6 +197,27 @@ disfluid_cache_entry_set_invalidated (struct disfluid_cache_entry *entry, cache_entry_set_invalidated (entry, invalidated); } +int +disfluid_cache_entry_set_key (struct disfluid_cache_entry *entry, + const char *key) +{ + return cache_entry_set_key (entry, key); +} + +int +disfluid_cache_entry_set_response_header (struct disfluid_cache_entry *entry, + const char *header) +{ + return cache_entry_set_response_header (entry, header); +} + +int +disfluid_cache_entry_set_response_body (struct disfluid_cache_entry *entry, + size_t body_length, const char *body) +{ + return cache_entry_set_response_body (entry, body_length, body); +} + void disfluid_cache_entry_get_request_date (const struct disfluid_cache_entry *entry, struct timespec *date) @@ -220,10 +240,14 @@ disfluid_cache_entry_is_invalidated (const struct disfluid_cache_entry *entry) int disfluid_cache_entry_load (struct disfluid_cache_entry *entry, + int load_key, + int load_header, + int load_body, ssize_t (*read_impl) (void *, void *, size_t), - void *context) + int (*skip) (void *, size_t), void *context) { - return cache_entry_load (entry, read_impl, context); + return cache_entry_load (entry, load_key, load_header, load_body, read_impl, + skip, context); } int @@ -235,15 +259,19 @@ disfluid_cache_entry_save (const struct disfluid_cache_entry *entry, } int -disfluid_cache_entry_read (struct disfluid_cache_entry *entry, int fd) +disfluid_cache_entry_read (struct disfluid_cache_entry *entry, + int read_key, + int read_header, int read_body, int fd) { - return cache_entry_read (entry, fd); + return cache_entry_read (entry, read_key, read_header, read_body, fd); } int -disfluid_cache_entry_fread (struct disfluid_cache_entry *entry, FILE * f) +disfluid_cache_entry_fread (struct disfluid_cache_entry *entry, + int read_key, + int read_header, int read_body, FILE * f) { - return cache_entry_fread (entry, f); + return cache_entry_fread (entry, read_key, read_header, read_body, f); } int |