aboutsummaryrefslogblamecommitdiffstats
path: root/src/libmdsclient/proto-util.c
blob: 8752e4681916b03b6f88c984ace536c0f1cad865 (plain) (tree)
1
2
3

                                 
                                                                       













                                                                        


                    




                       
                  







                                             
                                                                                










                                                     
                                                                               










                                               
                                                                                          











                                                             

                                                     
 











                                                 











                                                                         

                                                        
 


















                                                         


























                                                                                          


                                                                                               
 







                                                                                          




















                                                                                          

                                                                                            
 








                                                                                     




















                                                                                          
                                                                                                 
 








                                                                                   




















                                                                                          

                                                                                            
 








                                                                                     





















                                                                                          

                                                                                          
 








                                                                                   


























                                                                                          


                                                                                                 
 



































                                                                                                           
         







                                




















                                                                                          

                                                                                                       
 



















                                                                          






















                                                                                          

                                                                                                     
 






















                                                                          
         




















                                                                                          

                                                                                                       
 
















                                                                                                   





















                                                                                          

                                                                                                     
 























                                                                                                                      
         









                                                                    

                                                                 
 
                                                                 

 




























                                                                                                 







                                                                                             





                                                                                     


                                                                                             
 







                                                                                         






























                                                                                                 







                                                                                             





                                                                                     


                                                                                                   
 



















                                                                                         
         












                                                    

                                                     
                                                            



                                                                                       
                           









                                               
      














                                                                                                    
         










                                                                                                
         
 
                 












                                                                              

 





















                                                                                  


                                                                                
 



                                  
                                             











                                                             
 


                         
/**
 * mds — A micro-display server
 * Copyright © 2014, 2015, 2016, 2017  Mattias Andrée (m@maandree.se)
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include "proto-util.h"

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>



/**
 * The number of headers there must be before
 * it is beneficial to sort them.
 */
#ifndef LIBMDS_HEADERS_SORT_THRESHOLD
# define LIBMDS_HEADERS_SORT_THRESHOLD 100  /* XXX: Value is chosen at random */
#endif

/**
 * The number of headers there must be before
 * it is beneficial to copy them, if that
 * it required to sort them. Note that this
 * value plus that of `LIBMDS_HEADERS_SORT_THRESHOLD`
 * is the threshold for copying and sorting
 * a header array.
 */
#ifndef LIBMDS_HEADERS_COPY_THRESHOLD
# define LIBMDS_HEADERS_COPY_THRESHOLD 10  /* XXX: Value is chosen at random */
#endif

/**
 * The number of headers there must be before
 * it is beneficial to search them using binary
 * search rather than linear search.
 * 
 * Note that hybrid search is not implemented,
 * is is either full binary or full linear.
 */
#ifndef LIBMDS_HEADERS_BINSEARCH_THRESHOLD
# define LIBMDS_HEADERS_BINSEARCH_THRESHOLD 1000  /* XXX: Value is chosen at semirandom */
#endif



/**
 * Variant of `strcmp` that regards the first string as
 * ending at the first occurrence of the substring ": "
 * 
 * @param   header_a  The header that may include a value
 * @param   header_b  The header that may not include a value
 * @return            Same syntax as strcmp(3)
 */
static int
headercmp(const char *header_a, const char *header_b)
{
	int a, b;
	for (;;) {
		a = (int)*header_a++;
		b = (int)*header_b++;

		if (a == ':' && *header_a == ' ')
			return 0 - b;
		if (!a || !b)
			return a - b;
		if (a - b)
			return a - b;
	}
}


/**
 * Variant for `headercmp` where the arguments are pointers to the
 * values that should be compared, and the second parameter is
 * treated the same way as the first parameter
 * 
 * @param   strp_a  Pointer to the first string
 * @param   strp_b  Pointer to the second string
 * @return          Will return `strcmp(*strp_a, *strp_b)`, see strcmp(3)
 */
static int
headerpcmp(const void *headerp_a, const void *headerp_b)
{
	const char *header_a = *(char *const *)headerp_a;
	const char *header_b = *(char *const *)headerp_b;
	int a, b, az, bz;

	for (;;) {
		a = (int)*header_a++;
		b = (int)*header_b++;
		az = a == ':' && *header_a == ' ';
		bz = b == ':' && *header_b == ' ';

		if (az)
			return 0 - !(bz || !b);
		if (bz)
			return a - 0;
		if (!a || !b)
			return a - b;
		if (a - b)
			return a - b;
	}
}


/**
 * Cherrypick headers from a message
 * 
 * @param   headers       The headers in the message
 * @param   header_count  The number of headers
 * @param   found         Output parameter for the number of found headers of those that
 *                        where requested. `NULL` is permitted.
 * @param   optimisation  Optimisations the function may use, only one value please
 * @param   ...           The first argument should be the name of a header, it should
 *                        have the type `const char*`. The second argument should be
 *                        a pointer to the location where the header named by the first
 *                        argument in the argument list names. It should have the type
 *                        `char**`. If the header is found, its value will be stored,
 *                        and it will be a NUL-terminated string. If the header is not
 *                        found, `NULL` will be stored.  The next two arguments is
 *                        interpreted analogously to the first two arguments, and the
 *                        following two in the same way, and so on. When there are no
 *                        more headers in the list, it should be terminated with a `NULL`.
 * @return                Zero on success, -1 on error, `errno` will have been set
 *                        accordingly on error.
 * 
 * @throws  ENOMEM        Out of memory, Possibly, the process hit the RLIMIT_AS or
 *                        RLIMIT_DATA limit described in getrlimit(2).
 */
int
libmds_headers_cherrypick(char **restrict headers, size_t header_count, size_t *restrict found,
                          libmds_cherrypick_optimisation_t optimisation, ...)
{
	va_list args;
	int r, saved_errno;
	va_start(args, optimisation);
	r = libmds_headers_cherrypick_v(headers, header_count, found, optimisation, args);
	saved_errno = errno;
	va_end(args);
	errno = saved_errno;
	return r;
}


/**
 * Cherrypick headers from a message,
 * linear search will be used without optimisation
 * 
 * @param   headers       The headers in the message
 * @param   header_count  The number of headers
 * @param   ...           The first argument should be the name of a header, it should
 *                        have the type `const char*`. The second argument should be
 *                        a pointer to the location where the header named by the first
 *                        argument in the argument list names. It should have the type
 *                        `char**`. If the header is found, its value will be stored,
 *                        and it will be a NUL-terminated string. If the header is not
 *                        found, `NULL` will be stored.  The next two arguments is
 *                        interpreted analogously to the first two arguments, and the
 *                        following two in the same way, and so on. When there are no
 *                        more headers in the list, it should be terminated with a `NULL`.
 * @return                The number of found headers of those that were requested
 */
size_t
libmds_headers_cherrypick_linear_unsorted(char **restrict headers, size_t header_count, ...)
{
	va_list args;
	size_t r;
	int saved_errno;
	va_start(args, header_count);
	r = libmds_headers_cherrypick_linear_unsorted_v(headers, header_count, args);
	saved_errno = errno;
	va_end(args);
	errno = saved_errno;
	return r;
}


/**
 * Cherrypick headers from a message,
 * linear search will be used with optimisation based on that the array is sorted
 * 
 * @param   headers       The headers in the message
 * @param   header_count  The number of headers
 * @param   ...           The first argument should be the name of a header, it should
 *                        have the type `const char*`. The second argument should be
 *                        a pointer to the location where the header named by the first
 *                        argument in the argument list names. It should have the type
 *                        `char**`. If the header is found, its value will be stored,
 *                        and it will be a NUL-terminated string. If the header is not
 *                        found, `NULL` will be stored.  The next two arguments is
 *                        interpreted analogously to the first two arguments, and the
 *                        following two in the same way, and so on. When there are no
 *                        more headers in the list, it should be terminated with a `NULL`.
 * @return                The number of found headers of those that were requested
 */
size_t libmds_headers_cherrypick_linear_sorted(char **restrict headers, size_t header_count, ...)
{
	va_list args;
	size_t r;
	int saved_errno;
	va_start(args, header_count);
	r = libmds_headers_cherrypick_linear_sorted_v(headers, header_count, args);
	saved_errno = errno;
	va_end(args);
	errno = saved_errno;
	return r;
}


/**
 * Cherrypick headers from a message,
 * binary search will be used
 * 
 * @param   headers       The headers in the message
 * @param   header_count  The number of headers
 * @param   ...           The first argument should be the name of a header, it should
 *                        have the type `const char*`. The second argument should be
 *                        a pointer to the location where the header named by the first
 *                        argument in the argument list names. It should have the type
 *                        `char**`. If the header is found, its value will be stored,
 *                        and it will be a NUL-terminated string. If the header is not
 *                        found, `NULL` will be stored.  The next two arguments is
 *                        interpreted analogously to the first two arguments, and the
 *                        following two in the same way, and so on. When there are no
 *                        more headers in the list, it should be terminated with a `NULL`.
 * @return                The number of found headers of those that were requested
 */
size_t
libmds_headers_cherrypick_binary_unsorted(char **restrict headers, size_t header_count, ...)
{
	va_list args;
	size_t r;
	int saved_errno;
	va_start(args, header_count);
	r = libmds_headers_cherrypick_binary_unsorted_v(headers, header_count, args);
	saved_errno = errno;
	va_end(args);
	errno = saved_errno;
	return r;
}


/**
 * Cherrypick headers from a message,
 * binary search will be used with optimisation based on
 * that list of requested headers (`...`)  is sorted
 * 
 * @param   headers       The headers in the message
 * @param   header_count  The number of headers
 * @param   ...           The first argument should be the name of a header, it should
 *                        have the type `const char*`. The second argument should be
 *                        a pointer to the location where the header named by the first
 *                        argument in the argument list names. It should have the type
 *                        `char**`. If the header is found, its value will be stored,
 *                        and it will be a NUL-terminated string. If the header is not
 *                        found, `NULL` will be stored.  The next two arguments is
 *                        interpreted analogously to the first two arguments, and the
 *                        following two in the same way, and so on. When there are no
 *                        more headers in the list, it should be terminated with a `NULL`.
 * @return                The number of found headers of those that were requested
 */
size_t
libmds_headers_cherrypick_binary_sorted(char **restrict headers, size_t header_count, ...)
{
	va_list args;
	size_t r;
	int saved_errno;
	va_start(args, header_count);
	r = libmds_headers_cherrypick_binary_sorted_v(headers, header_count, args);
	saved_errno = errno;
	va_end(args);
	errno = saved_errno;
	return r;
}


/**
 * Cherrypick headers from a message
 * 
 * @param   headers       The headers in the message
 * @param   header_count  The number of headers
 * @param   found         Output parameter for the number of found headers of those that
 *                        were requested. `NULL` is permitted.
 * @param   optimisation  Optimisations the function may use, only one value please
 * @param   args          The first argument should be the name of a header, it should
 *                        have the type `const char*`. The second argument should be
 *                        a pointer to the location where the header named by the first
 *                        argument in the argument list names. It should have the type
 *                        `char**`. If the header is found, its value will be stored,
 *                        and it will be a NUL-terminated string. If the header is not
 *                        found, `NULL` will be stored.  The next two arguments is
 *                        interpreted analogously to the first two arguments, and the
 *                        following two in the same way, and so on. When there are no
 *                        more headers in the list, it should be terminated with a `NULL`.
 * @return                Zero on success, -1 on error, `errno` will have been set
 *                        accordingly on error.
 * 
 * @throws  ENOMEM        Out of memory, Possibly, the process hit the RLIMIT_AS or
 *                        RLIMIT_DATA limit described in getrlimit(2).
 */
int
libmds_headers_cherrypick_v(char **restrict headers, size_t header_count, size_t *restrict found,
                            libmds_cherrypick_optimisation_t optimisation, va_list args)
{
	char **headers_ = headers;
	size_t found_;
	libmds_cherrypick_optimisation_t sorted;

	if (found)
		*found = 0;

	optimisation ^= sorted = optimisation & ARGS_SORTED;

	if (optimisation == DO_NOT_SORT) {
		if (header_count >= LIBMDS_HEADERS_SORT_THRESHOLD + LIBMDS_HEADERS_COPY_THRESHOLD) {
			headers_ = malloc(header_count * sizeof(char*));
			if (!headers_)
				return -1;
			memcpy(headers_, headers, header_count * sizeof(char*));
			libmds_headers_sort(headers_, header_count);
			optimisation = SORTED;
		}
	} else if (optimisation == SORT) {
		if (header_count >= LIBMDS_HEADERS_SORT_THRESHOLD) {
			libmds_headers_sort(headers_, header_count);
			optimisation = SORTED;
		}
	}
 
	if (optimisation != SORTED) {
		found_ = libmds_headers_cherrypick_linear_unsorted_v(headers_, header_count, args);
	} else if (header_count < LIBMDS_HEADERS_BINSEARCH_THRESHOLD) {
		if (sorted)
			found_ = libmds_headers_cherrypick_linear_sorted_v(headers_, header_count, args);
		else
			found_ = libmds_headers_cherrypick_linear_unsorted_v(headers_, header_count, args);
	} else if (sorted) {
		found_ = libmds_headers_cherrypick_binary_sorted_v(headers_, header_count, args);
	} else {
		found_ = libmds_headers_cherrypick_binary_unsorted_v(headers_, header_count, args);
	}

	if (found)
		*found = found_;

	if (headers_ != headers)
		free(headers_);

	return 0;
}


/**
 * Cherrypick headers from a message,
 * linear search will be used without optimisation
 * 
 * @param   headers       The headers in the message
 * @param   header_count  The number of headers
 * @param   args          The first argument should be the name of a header, it should
 *                        have the type `const char*`. The second argument should be
 *                        a pointer to the location where the header named by the first
 *                        argument in the argument list names. It should have the type
 *                        `char**`. If the header is found, its value will be stored,
 *                        and it will be a NUL-terminated string. If the header is not
 *                        found, `NULL` will be stored.  The next two arguments is
 *                        interpreted analogously to the first two arguments, and the
 *                        following two in the same way, and so on. When there are no
 *                        more headers in the list, it should be terminated with a `NULL`.
 * @return                The number of found headers of those that were requested
 */
size_t
libmds_headers_cherrypick_linear_unsorted_v(char **restrict headers, size_t header_count, va_list args)
{
	const char *header;
	char **value_out;
	size_t found = 0, i;

	for (;;) {
		header = va_arg(args, const char*);
		if (!header)
			return found;

		value_out = va_arg(args, char**);
		*value_out = NULL;

		for (i = 0; i < header_count; i++) {
			if (!headercmp(headers[i], header)) {
				*value_out = strstr(headers[i], ": ") + 2;
				found++;
				break;
			}
		}
	}
}


/**
 * Cherrypick headers from a message,
 * linear search will be used with optimisation based
 * on that the array is sorted as well as the list of
 * requested headers (`args`)
 * 
 * @param   headers       The headers in the message
 * @param   header_count  The number of headers
 * @param   args          The first argument should be the name of a header, it should
 *                        have the type `const char*`. The second argument should be
 *                        a pointer to the location where the header named by the first
 *                        argument in the argument list names. It should have the type
 *                        `char**`. If the header is found, its value will be stored,
 *                        and it will be a NUL-terminated string. If the header is not
 *                        found, `NULL` will be stored.  The next two arguments is
 *                        interpreted analogously to the first two arguments, and the
 *                        following two in the same way, and so on. When there are no
 *                        more headers in the list, it should be terminated with a `NULL`.
 * @return                The number of found headers of those that were requested
 */
size_t
libmds_headers_cherrypick_linear_sorted_v(char **restrict headers, size_t header_count, va_list args)
{
	const char *header;
	char **value_out;
	size_t found = 0, i = 0;
	int r;

	for (;;) {
		header = va_arg(args, const char*);
		if (!header)
			return found;

		value_out = va_arg(args, char**);
		*value_out = NULL;

		for (; i < header_count; i++) {
			r = headercmp(headers[i], header);
			if (!r) {
				*value_out = strstr(headers[i], ": ") + 2;
				found++, i++;
				break;
			}
			if (r > 0)
				break;
		}
	}
}


/**
 * Cherrypick headers from a message,
 * binary search will be used
 * 
 * @param   headers       The headers in the message
 * @param   header_count  The number of headers
 * @param   args          The first argument should be the name of a header, it should
 *                        have the type `const char*`. The second argument should be
 *                        a pointer to the location where the header named by the first
 *                        argument in the argument list names. It should have the type
 *                        `char**`. If the header is found, its value will be stored,
 *                        and it will be a NUL-terminated string. If the header is not
 *                        found, `NULL` will be stored.  The next two arguments is
 *                        interpreted analogously to the first two arguments, and the
 *                        following two in the same way, and so on. When there are no
 *                        more headers in the list, it should be terminated with a `NULL`.
 * @return                The number of found headers of those that were requested
 */
size_t
libmds_headers_cherrypick_binary_unsorted_v(char **restrict headers, size_t header_count, va_list args)
{
	const char *header;
	char **value_out;
	void *found_element;
	size_t found = 0;

	for (;;) {
		header = va_arg(args, const char*);
		if (!header)
			return found;

		value_out = va_arg(args, char**);
		*value_out = NULL;

		found_element = bsearch(&header, headers, header_count, sizeof(char*), headerpcmp);
		if (found_element)
			*value_out = strstr(*(char**)found_element, ": ") + 2, found++;
	}
}


/**
 * Cherrypick headers from a message,
 * binary search will be used with optimisation based on
 * that list of requested headers (`args`)  is sorted
 * 
 * @param   headers       The headers in the message
 * @param   header_count  The number of headers
 * @param   args          The first argument should be the name of a header, it should
 *                        have the type `const char*`. The second argument should be
 *                        a pointer to the location where the header named by the first
 *                        argument in the argument list names. It should have the type
 *                        `char**`. If the header is found, its value will be stored,
 *                        and it will be a NUL-terminated string. If the header is not
 *                        found, `NULL` will be stored.  The next two arguments is
 *                        interpreted analogously to the first two arguments, and the
 *                        following two in the same way, and so on. When there are no
 *                        more headers in the list, it should be terminated with a `NULL`.
 * @return                The number of found headers of those that were requested
 */
size_t
libmds_headers_cherrypick_binary_sorted_v(char **restrict headers, size_t header_count, va_list args)
{
	const char *header;
	char **value_out;
	void *found_element;
	size_t found = 0;
	size_t offset = 0;

	/* I sincerely doubt even this amount of optimisation is needed,
	 * so there is no need for even faster algorithms, keep in mind
	 * that the overhead increases with faster algorithms. */

	for (;;) {
		header = va_arg(args, const char*);
		if (!header)
			return found;

		value_out = va_arg(args, char**);
		*value_out = NULL;

		found_element = bsearch(&header, headers + offset, header_count - offset, sizeof(char *), headerpcmp);
		if (found_element) {
			offset = (size_t)((char **)found_element - headers);
			*value_out = strstr(*(char **)found_element, ": ") + 2;
			found++;
		}
	}
}


/**
 * Sort the a header array, this is what `libmds_headers_cherrypick`
 * uses to optimise its procedure.
 * 
 * @param  headers      The array of headers
 * @param  headr_count  The number of elements in `headers`
 */
void
libmds_headers_sort(char **restrict headers, size_t header_count)
{
	qsort(headers, header_count, sizeof(char *), headerpcmp);
}


/**
 * Compose a message
 * 
 * @param   buffer          Pointer to the buffer where the message should be written,
 *                          may point to `NULL` if `buffer_size` points to zero, but the
 *                          pointer itself must not be `NULL`. The buffer may be reallocated.
 *                          Will be updated with the new buffer it is allocated.
 *                          The buffer the pointer points to will be filled with the
 *                          message, however there is no guarantee it will be NUL-terminated.
 * @param   buffer_size     The allocation size, in `char`, of `*buffer`, if and only if `buffer`
 *                          points to `NULL`, this point should point to zero, and vice versa.
 *                          Must not be `NULL`. Will be update with the new allocation size.
 *                          It is guaranteed that there will be remove to NUL-terminate
 *                          the message even if it was already NUL-terminate (terminate it
 *                          doubly in that case.)
 * @param   length          Output parameter for the length of the constructed message.
 *                          Must not be `NULL`.
 * @param   payload         The payload that should be added to the message, it should end
 *                          with a LF. `NULL` if the message should not have a payload.
 * @param   payload_length  Pointer to the length of the payload. If `NULL`, `payload`
 *                          must be NUL-terminated, and if it is `NULL` this NUL-termination
 *                          will be used to find the length of the payload. This variable is
 *                          not used if `payload` is `NULL`.
 * @param   ...             The first argument should be a line describing the first header,
 *                          it should be a printf(3)-formatted line that fully describes the
 *                          header line, that is, the header name, colon, blank space and then
 *                          the header's value. No LF should be included. The following
 *                          arguments should be the argument to format the header line.
 *                          If a format for a line begins with a question mark, the remainder
 *                          of the line used, but only if the next argument is non-zero
 *                          (it should be of type `int`,) that argument will not be used
 *                          for the formatting. This may be be iterated any number of this.
 *                          The last argument should be `NULL` to specify that there are no
 *                          more headers. The `Length`-header should not be included, it is
 *                          added automatically. A header may not have a length larger than
 *                          2¹⁵, otherwise the behaviour of this function is undefined.
 * @return                  Zero on success, -1 on error, `errno` will have been set
 *                          accordingly on error.
 * 
 * @throws  ENOMEM          Out of memory, Possibly, the process hit the RLIMIT_AS or
 *                          RLIMIT_DATA limit described in getrlimit(2).
 */
int
libmds_compose(char **restrict buffer, size_t *restrict buffer_size, size_t *restrict length,
               const char *restrict payload, const size_t *restrict payload_length, ...)
{
	va_list args;
	int r, saved_errno;
	va_start(args, payload_length);
	r = libmds_compose_v(buffer, buffer_size, length, payload, payload_length, args);
	saved_errno = errno;
	va_end(args);
	errno = saved_errno;
	return r;
}


/**
 * Compose a message
 * 
 * @param   buffer          Pointer to the buffer where the message should be written,
 *                          may point to `NULL` if `buffer_size` points to zero, but the
 *                          pointer itself must not be `NULL`. The buffer may be reallocated.
 *                          Will be updated with the new buffer it is allocated.
 *                          The buffer the pointer points to will be filled with the
 *                          message, however there is no guarantee it will be NUL-terminated.
 * @param   buffer_size     The allocation size, in `char`, of `*buffer`, if and only if `buffer`
 *                          points to `NULL`, this point should point to zero, and vice versa.
 *                          Must not be `NULL`. Will be update with the new allocation size.
 *                          It is guaranteed that there will be remove to NUL-terminate
 *                          the message even if it was already NUL-terminate (terminate it
 *                          doubly in that case.)
 * @param   length          Output parameter for the length of the constructed message.
 *                          Must not be `NULL`.
 * @param   payload         The payload that should be added to the message, it should end
 *                          with a LF. `NULL` if the message should not have a payload.
 * @param   payload_length  Pointer to the length of the payload. If `NULL`, `payload`
 *                          must be NUL-terminated, and if it is `NULL` this NUL-termination
 *                          will be used to find the length of the payload. This variable is
 *                          not used if `payload` is `NULL`.
 * @param   args            The first argument should be a line describing the first header,
 *                          it should be a printf(3)-formatted line that fully describes the
 *                          header line, that is, the header name, colon, blank space and then
 *                          the header's value. No LF should be included. The following
 *                          arguments should be the argument to format the header line.
 *                          If a format for a line begins with a question mark, the remainder
 *                          of the line used, but only if the next argument is non-zero
 *                          (it should be of type `int`,) that argument will not be used
 *                          for the formatting. This may be be iterated any number of this.
 *                          The last argument should be `NULL` to specify that there are no
 *                          more headers. The `Length`-header should not be included, it is
 *                          added automatically. A header may not have a length larger than
 *                          2¹⁵, otherwise the behaviour of this function is undefined.
 * @return                  Zero on success, -1 on error, `errno` will have been set
 *                          accordingly on error.
 * 
 * @throws  ENOMEM          Out of memory, Possibly, the process hit the RLIMIT_AS or
 *                          RLIMIT_DATA limit described in getrlimit(2).
 */
int
libmds_compose_v(char **restrict buffer, size_t *restrict buffer_size, size_t *restrict length,
                 const char *restrict payload, const size_t *restrict payload_length, va_list args)
{
	char *buf = *buffer;
	size_t bufsize = *buffer_size;
	size_t len = 0;
	int part_len;
	char *part_msg;
	size_t payload_len = 0;
	const char* format;
	int include;
	int saved_errno;

	*length = 0;

	if (payload)
		payload_len = payload_length == NULL ? strlen(payload) : *payload_length;

	if (!bufsize) {
		bufsize = 128;
		buf = realloc(buf, bufsize * sizeof(char));
		if (!buf)
			return -1;
	}

	for (;;) {
		format = va_arg(args, const char*);
		if (!format)
			break;

		include = 1;
		if (*format == '?') {
			include = va_arg(args, int);
			format++;
		}

#if defined(__GNUC__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wformat-nonliteral"
# pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
#endif
		part_len = vasprintf(&part_msg, format, args);
		/* XXX vbprintf in slibc will be a nice replacement for this horror. */
#if defined(__GNUC__)
# pragma GCC diagnostic pop
#endif

		if (!include) {
			if (part_len >= 0)
				free(part_msg);
			continue;
		}

		if (part_len < 0)
			return -1;
      
		if (len + (size_t)part_len >= bufsize) {
			*buffer_size = bufsize;
			*buffer = buf;
			do
				bufsize <<= 1;
			while (len + (size_t)part_len >= bufsize);
			buf = realloc(buf, bufsize * sizeof(char));
			if (!buf)
				return saved_errno = errno, free(part_msg), errno = saved_errno, -1;
		}

		memcpy(buf + len, part_msg, ((size_t)part_len) * sizeof(char));
		len += (size_t)part_len;
		buf[len++] = '\n';
		free(part_msg);
	}

#define LENGTH_LEN \
	(payload_len > 0 ? ((sizeof("Length: \n") / sizeof(char) - 1) + 3 * sizeof(size_t)) : 0)

	if (len + LENGTH_LEN + 1 + payload_len + 1 > bufsize) {
		*buffer_size = bufsize;
		*buffer = buf;
		bufsize = len + LENGTH_LEN + 1 + payload_len + 1;
		buf = realloc(buf, bufsize * sizeof(char));
		if (!buf)
			return -1;
	}

#undef LENGTH_LEN

	if (payload_len > 0)
		sprintf(buf + len, "Length: %zu\n%n", payload_len, &part_len),
			len += (size_t)part_len;
	buf[len++] = '\n';
	if (payload_len > 0)
		memcpy(buf + len, payload, payload_len * sizeof(char)),
			len += payload_len;

	*buffer_size = bufsize;
	*buffer = buf;
	*length = len;
	return 0;
}


/**
 * Increase the message ID counter
 * 
 * @param   message_id  Pointer to the current message ID, will be update with
 *                      the next free message ID. Must not be `NULL`.
 * @param   test        Function that tests whether an message ID is free,
 *                      it takes the message ID to test as its first argument,
 *                      and `data` as its second argument, and returns zero if
 *                      it is in used, a positive integer if it is free, and a
 *                      negative integer if an error occurred. `NULL` if there
 *                      should be no testing.
 * @param   data        Argument to pass to `test` so that it can deal with
 *                      threading or any other issues. Unused if `test` is `NULL`.
 * @return              Zero on success, -1 on error, `errno` will have been set
 *                      accordingly on error.
 * 
 * @throws  EAGAIN      If there are no free message ID to be used.
 *                      It is advisable to make `test` throw this immediately if
 *                      there are no free message ID:s.
 * @throws              Any error that `test` may throw.
 */
int
libmds_next_message_id(uint32_t *restrict message_id,
                       int (*test)(uint32_t message_id, void *data), void *data)
{
	uint32_t id = *message_id;
	uint32_t first_test;
	int r;

	id = id == UINT32_MAX ? 0 : (id + 1);
	if (test) {
		for (first_test = id;;) {
			r = test(id, data);
			if (r < 0)
				return -1;
			if (r)
				break;
			id = id == UINT32_MAX ? 0 : (id + 1);
			if (id == first_test)
				return errno = EAGAIN, -1;
		}
	}

	*message_id = id;
	return 0;
}