/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "apr_fnmatch.h"
#include "apr_lib.h"
#include "apr_strings.h"

#include "httpd.h"
#include "http_log.h"

#include "ssl_ct_util.h"

APLOG_USE_MODULE(ssl_ct);

apr_status_t ctutil_path_join(char **out, const char *dirname, const char *basename,
                              apr_pool_t *p, server_rec *s)
{
    apr_status_t rv;

    rv = apr_filepath_merge(out, dirname, basename, 0, p);
    if (rv != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                     APLOGNO(02776) "can't build filename from %s and %s",
                     dirname, basename);
    }

    return rv;
}

int ctutil_dir_exists(apr_pool_t *p, const char *dirname)
{
    apr_finfo_t finfo;
    apr_status_t rv = apr_stat(&finfo, dirname, APR_FINFO_TYPE, p);

    return rv == APR_SUCCESS && finfo.filetype == APR_DIR;
}

int ctutil_file_exists(apr_pool_t *p, const char *filename)
{
    apr_finfo_t finfo;
    apr_status_t rv = apr_stat(&finfo, filename, APR_FINFO_TYPE, p);

    return rv == APR_SUCCESS && finfo.filetype == APR_REG;
}

void ctutil_buffer_to_array(apr_pool_t *p, const char *b,
                            apr_size_t b_size, apr_array_header_t **out)
{
    apr_array_header_t *arr = apr_array_make(p, 10, sizeof(char *));
    const char *ch, *last;

    ch = b;
    last = b + b_size - 1;
    while (ch < last) {
        const char *end = memchr(ch, '\n', last - ch);
        const char *line;

        if (!end) {
            end = last + 1;
        }
        while (apr_isspace(*ch) && ch < end) {
            ch++;
        }
        if (ch < end) {
            const char *tmpend = end - 1;

            while (tmpend > ch
                   && isspace(*tmpend)) {
                --tmpend;
            }
            
            line = apr_pstrndup(p, ch, 1 + tmpend - ch);
            *(const char **)apr_array_push(arr) = line;
        }
        ch = end + 1;
    }

    *out = arr;
}

apr_status_t ctutil_fopen(const char *fn, const char *mode, FILE **f)
{
    apr_status_t rv;

    *f = fopen(fn, mode);
    if (*f == NULL) {
        rv = errno; /* XXX Windows equivalent -- CreateFile + fdopen? */
    }
    else {
        rv = APR_SUCCESS;
    }

    return rv;
}

/* read_dir() is remarkably like apr_match_glob(), which could
 * probably use some processing flags to indicate variations on
 * the basic behavior (and implement better error checking).
 */
apr_status_t ctutil_read_dir(apr_pool_t *p,
                             server_rec *s,
                             const char *dirname,
                             const char *pattern,
                             apr_array_header_t **outarr)
{
    apr_array_header_t *arr;
    apr_dir_t *d;
    apr_finfo_t finfo;
    apr_status_t rv;
    int reported = 0;

    /* add to existing array if it already exists */
    arr = *outarr;
    if (arr == NULL) {
        arr = apr_array_make(p, 4, sizeof(char *));
    }

    rv = apr_dir_open(&d, dirname, p);
    if (rv != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                     APLOGNO(02777) "couldn't read dir %s",
                     dirname);
        return rv;
    }

    while ((rv = apr_dir_read(&finfo, APR_FINFO_NAME, d)) == APR_SUCCESS) {
        const char *fn;

        if (APR_SUCCESS == apr_fnmatch(pattern, finfo.name, APR_FNM_CASE_BLIND)) {
            rv = ctutil_path_join((char **)&fn, dirname, finfo.name, p, s);
            if (rv != APR_SUCCESS) {
                reported = 1;
                break;
            }

            *(char **)apr_array_push(arr) = apr_pstrdup(p, fn);
        }
    }

    if (APR_STATUS_IS_ENOENT(rv)) {
        rv = APR_SUCCESS;
    }
    else if (rv != APR_SUCCESS && !reported) {
        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                     APLOGNO(02778) "couldn't read entry from dir %s", dirname);
    }

    apr_dir_close(d);

    if (rv == APR_SUCCESS) {
        *outarr = arr;
    }

    return rv;
}

apr_status_t ctutil_read_file(apr_pool_t *p,
                              server_rec *s,
                              const char *fn,
                              apr_off_t limit,
                              char **contents,
                              apr_size_t *contents_size)
{
    apr_file_t *f;
    apr_finfo_t finfo;
    apr_status_t rv;
    apr_size_t nbytes;

    *contents = NULL;
    *contents_size = 0;

    rv = apr_file_open(&f, fn, APR_READ | APR_BINARY, APR_FPROT_OS_DEFAULT, p);
    if (rv != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                     APLOGNO(02779) "couldn't read %s", fn);
        return rv;
    }
    
    rv = apr_file_info_get(&finfo, APR_FINFO_SIZE, f);
    if (rv != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                     APLOGNO(02780) "couldn't retrieve size of %s", fn);
        apr_file_close(f);
        return rv;
    }

    if (finfo.size > limit) {
        rv = APR_ENOSPC;
        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                     APLOGNO(02781) "size %" APR_OFF_T_FMT " of %s exceeds "
                     "limit (%" APR_OFF_T_FMT ")", finfo.size, fn, limit);
        apr_file_close(f);
        return rv;
    }

    nbytes = (apr_size_t)finfo.size;
    *contents = apr_palloc(p, nbytes);
    rv = apr_file_read_full(f, *contents, nbytes, contents_size);
    if (rv != APR_SUCCESS) { /* shouldn't get APR_EOF since we know
                              * how big the file is
                              */
        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                     APLOGNO(02782) "apr_file_read_full");
    }
    apr_file_close(f);

    return rv;
}

#if APR_FILES_AS_SOCKETS
static void io_loop(apr_pool_t *p, server_rec *s, apr_proc_t *proc,
                    const char *desc_for_log)
{
    apr_status_t rv;
    apr_pollfd_t pfd = {0};
    apr_pollset_t *pollset;
    int fds_waiting;

    rv = apr_pollset_create(&pollset, 2, p, 0);
    ap_assert(rv == APR_SUCCESS);

    fds_waiting = 0;

    pfd.p = p;
    pfd.desc_type = APR_POLL_FILE;
    pfd.reqevents = APR_POLLIN;
    pfd.desc.f = proc->err;
    rv = apr_pollset_add(pollset, &pfd);
    ap_assert(rv == APR_SUCCESS);
    ++fds_waiting;

    pfd.desc.f = proc->out;
    rv = apr_pollset_add(pollset, &pfd);
    ap_assert(rv == APR_SUCCESS);
    ++fds_waiting;

    while (fds_waiting) {
        int i, num_events;
        const apr_pollfd_t *pdesc;
        char buf[4096];
        apr_size_t len;

        rv = apr_pollset_poll(pollset, apr_time_from_sec(10),
                              &num_events, &pdesc);
        if (rv != APR_SUCCESS && !APR_STATUS_IS_EINTR(rv)) {
            ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                         APLOGNO(02783) "apr_pollset_poll");
            break;
        }

        for (i = 0; i < num_events; i++) {
            len = sizeof buf;
            rv = apr_file_read(pdesc[i].desc.f, buf, &len);
            if (APR_STATUS_IS_EOF(rv)) {
                apr_file_close(pdesc[i].desc.f);
                apr_pollset_remove(pollset, &pdesc[i]);
                --fds_waiting;
            }
            else if (rv != APR_SUCCESS) {
                ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                             APLOGNO(02784) "apr_file_read");
            }
            else {
                ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, s,
                             "%s: %.*s", desc_for_log, (int)len, buf);
            }
        }
    }
}
#else /* APR_FILES_AS_SOCKETS */
static void io_loop(apr_pool_t *p, server_rec *s, apr_proc_t *proc,
                    const char *desc_for_log)
{
    apr_status_t rv;
    apr_file_t *fds[2] = {proc->out, proc->err};
    apr_size_t len;
    char buf[4096];
    int fds_waiting = 2;

    while (fds_waiting) {
        int i;
        int read = 0;

        for (i = 0; i < sizeof fds / sizeof fds[0]; i++) {
            if (!fds[i]) {
                continue;
            }
            len = sizeof buf;
            rv = apr_file_read(fds[i], buf, &len);
            if (APR_STATUS_IS_EOF(rv)) {
                apr_file_close(fds[i]);
                fds[i] = NULL;
                --fds_waiting;
            }
            else if (APR_STATUS_IS_EAGAIN(rv)) {
                /* we don't actually know if data is ready before reading, so
                 * this isn't an error
                 */
            }
            else if (rv != APR_SUCCESS) {
                ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                             APLOGNO(02785) "apr_file_read");
            }
            else {
                ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, s,
                             "%s: %.*s", desc_for_log, (int)len, buf);
                ++read;
            }
        }
        if (fds_waiting && !read) {
            /* no tight loop */
            apr_sleep(apr_time_from_msec(100));
        }
    }
}
#endif /* APR_FILES_AS_SOCKETS */

apr_status_t ctutil_run_to_log(apr_pool_t *p,
                               server_rec *s,
                               const char *args[8],
                               const char *desc_for_log)
{
    apr_exit_why_e exitwhy;
    apr_proc_t proc = {0};
    apr_procattr_t *attr;
    apr_status_t rv;
    int exitcode;

    rv = apr_procattr_create(&attr, p);
    if (rv != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                     APLOGNO(02786) "apr_procattr_create failed");
        return rv;
    }

    rv = apr_procattr_io_set(attr,
                             APR_NO_PIPE,
                             APR_CHILD_BLOCK,
                             APR_CHILD_BLOCK);
    if (rv != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                     APLOGNO(02787) "apr_procattr_io_set failed");
        return rv;
    }

    if (APLOGtrace1(s)) {
        const char *cmdline = "";
        const char **curarg = args;

        while (*curarg) {
            cmdline = apr_pstrcat(p, cmdline, *curarg, " ", NULL);
            curarg++;
        }
        ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s,
                     "Running \"%s\"", cmdline);
    }

    rv = apr_proc_create(&proc, args[0], args, NULL, attr, p);
    if (rv != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                     APLOGNO(02788) "apr_proc_create failed");
        return rv;
    }

    io_loop(p, s, &proc, desc_for_log);

    rv = apr_proc_wait(&proc, &exitcode, &exitwhy, APR_WAIT);
    rv = rv == APR_CHILD_DONE ? APR_SUCCESS : rv;

    ap_log_error(APLOG_MARK,
                 rv != APR_SUCCESS || exitcode ? APLOG_ERR : APLOG_DEBUG,
                 rv, s,
                 APLOGNO(02789) "exit code from %s: %d (%s)", 
                 desc_for_log, exitcode,
                 exitwhy == APR_PROC_EXIT ? "exited normally" : "exited due to a signal");

    if (rv == APR_SUCCESS && exitcode) {
        rv = APR_EGENERAL;
    }

    return rv;
}

void ctutil_thread_mutex_lock(apr_thread_mutex_t *m)
{
    apr_status_t rv = apr_thread_mutex_lock(m);
    ap_assert(rv == APR_SUCCESS);
}

void ctutil_thread_mutex_unlock(apr_thread_mutex_t *m)
{
    apr_status_t rv = apr_thread_mutex_unlock(m);
    ap_assert(rv == APR_SUCCESS);
}

apr_status_t ctutil_file_write_uint16(server_rec *s,
                                      apr_file_t *f,
                                      apr_uint16_t in_val)
{
    apr_size_t nbytes;
    apr_status_t rv;
    char vals[2];

    vals[0] = (in_val & 0xFF00) >> 8;
    vals[1] = (in_val & 0x00FF);
    nbytes = sizeof(vals);
    rv = apr_file_write(f, vals, &nbytes);
    if (rv != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                     APLOGNO(02790) "can't write 2-byte length to file");
    }
    return rv;
}

apr_status_t ctutil_file_write_uint24(server_rec *s,
                                      apr_file_t *f,
                                      apr_uint32_t in_val)
{
    apr_size_t nbytes;
    apr_status_t rv;
    char vals[3];

    vals[0] = (in_val & 0xFF0000) >> 16;
    vals[1] = (in_val & 0x00FF00) >> 8;
    vals[2] = (in_val & 0x0000FF) >> 0;
    nbytes = sizeof(vals);
    rv = apr_file_write(f, vals, &nbytes);
    if (rv != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                     APLOGNO(02791) "can't write 3-byte length to file");
    }
    return rv;
}

void ctutil_log_array(const char *file, int line, int module_index,
                      int level, server_rec *s, const char *desc,
                      apr_array_header_t *arr)
{
    const char **elts = (const char **)arr->elts;
    int i;

    /* Intentional no APLOGNO */
    ap_log_error(file, line, module_index, level,
                 0, s, "%s", desc);
    for (i = 0; i < arr->nelts; i++) {
        /* Intentional no APLOGNO */
        ap_log_error(file, line, module_index, level,
                     0, s, ">>%s", elts[i]);
    }
}

static apr_status_t deserialize_uint(const unsigned char **mem,
                                     apr_size_t *avail,
                                     apr_byte_t num_bits, apr_uint64_t *pval)
{
    apr_byte_t num_bytes = num_bits / 8;
    apr_uint64_t val = 0;
    int i;

    if (*avail < num_bytes || num_bits > 64) {
        return APR_EINVAL;
    }

    for (i = 0; i < num_bytes; i++) {
        val = (val << 8) | **mem;
        *mem += 1;
        *avail -= 1;
    }

    *pval = val;
    return APR_SUCCESS;
}

apr_status_t ctutil_deserialize_uint64(const unsigned char **mem,
                                       apr_size_t *avail,
                                       apr_uint64_t *pval)
{
    return deserialize_uint(mem, avail, 64, pval);
}

apr_status_t ctutil_deserialize_uint16(const unsigned char **mem,
                                       apr_size_t *avail,
                                       apr_uint16_t *pval)
{
    apr_status_t rv;
    apr_uint64_t val64 = 0;

    rv = deserialize_uint(mem, avail, 16, &val64);
    *pval = (apr_uint16_t)val64;
    return rv;
}

static apr_status_t serialize_uint(unsigned char **mem, apr_size_t *avail,
                                   apr_byte_t num_bits, apr_uint64_t val)
{
    apr_byte_t num_bytes = num_bits / 8;
    int i;
    apr_uint64_t mask;
    apr_byte_t shift;

    if (*avail < num_bytes || num_bits > 64) {
        return APR_EINVAL;
    }

    mask = (apr_uint64_t)0xFF << (num_bits - 8);
    shift = num_bits - 8;
    for (i = 0; i < num_bytes; i++) {
        **mem = (unsigned char)((val & mask) >> shift);
        *mem += 1;
        *avail -= 1;
        mask = mask >> 8;
        shift -= 8;
    }

    return APR_SUCCESS;
}

apr_status_t ctutil_serialize_uint64(unsigned char **mem, apr_size_t *avail,
                                     apr_uint64_t val)
{
    return serialize_uint(mem, avail, 64, val);
}

apr_status_t ctutil_serialize_uint24(unsigned char **mem, apr_size_t *avail,
                                     apr_uint32_t val)
{
    return serialize_uint(mem, avail, 24, val);
}

apr_status_t ctutil_serialize_uint16(unsigned char **mem, apr_size_t *avail,
                                     apr_uint16_t val)
{
    return serialize_uint(mem, avail, 16, val);
}

apr_status_t ctutil_serialize_uint8(unsigned char **mem, apr_size_t *avail,
                                    unsigned char val)
{
    return serialize_uint(mem, avail, 8, val);
}

apr_status_t ctutil_write_var16_bytes(unsigned char **mem, apr_size_t *avail,
                                      const unsigned char *val,
                                      apr_uint16_t len)
{
    apr_status_t rv;

    if (*avail < (sizeof(apr_uint16_t) + len)) {
        return APR_EINVAL;
    }

    rv = ctutil_serialize_uint16(mem, avail, len);
    if (rv != APR_SUCCESS) { /* should not occur */
        return rv;
    }

    memcpy(*mem, val, len);
    *mem += len;
    *avail -= len;
    return APR_SUCCESS;
}

apr_status_t ctutil_write_var24_bytes(unsigned char **mem, apr_size_t *avail,
                                      const unsigned char *val,
                                      apr_uint32_t len)
{
    apr_status_t rv;

    if (*avail < (3 + len)) {
        return APR_EINVAL;
    }

    rv = ctutil_serialize_uint24(mem, avail, len);
    if (rv != APR_SUCCESS) { /* should not occur */
        return rv;
    }

    memcpy(*mem, val, len);
    *mem += len;
    *avail -= len;
    return APR_SUCCESS;
}

/* all this deserialization crap is of course from
 * c-t/src/proto/serializer.cc
 */
static apr_status_t read_length_prefix(const unsigned char **mem, apr_size_t *avail,
                                       apr_size_t *result)
{
    apr_status_t rv;
    apr_uint16_t val;

    rv = ctutil_deserialize_uint16(mem, avail, &val);
    if (rv == APR_SUCCESS) {
        *result = val;
    }

    return rv;
}

static apr_status_t read_fixed_bytes(const unsigned char **mem, apr_size_t *avail,
                                     apr_size_t len,
                                     const unsigned char **start)
{
    if (*avail < len) {
        return APR_EINVAL;
    }

    *start = *mem;
    *avail -= len;
    *mem += len;

    return APR_SUCCESS;
}

apr_status_t ctutil_read_var_bytes(const unsigned char **mem, apr_size_t *avail,
                                   const unsigned char **start, apr_size_t *len)
{
    apr_status_t rv;

    rv = read_length_prefix(mem, avail, len);
    if (rv != APR_SUCCESS) {
        return rv;
    }

    rv = read_fixed_bytes(mem, avail, *len, start);
    return rv;
}

#define TESTURL1 "http://127.0.0.1:8888"
#define TESTURL2 "http://127.0.0.1:9999"
#define TESTURL3 "http://127.0.0.1:10000"

void ctutil_run_internal_tests(apr_pool_t *p)
{
    apr_array_header_t *arr;
    const char *filecontents =
      " " TESTURL1 " \r\n" TESTURL2 "\n"
      TESTURL3 /* no "\n" */ ;
    unsigned char buf[8], *ch;
    const unsigned char *const_ch;
    apr_size_t avail;
    apr_status_t rv;
    apr_uint16_t val16;
    apr_uint64_t val64;

    ctutil_buffer_to_array(p, filecontents, strlen(filecontents), &arr);
    
    ap_assert(ap_array_str_contains(arr, TESTURL1));
    ap_assert(ap_array_str_contains(arr, TESTURL2));
    ap_assert(ap_array_str_contains(arr, TESTURL3));
    ap_assert(!ap_array_str_contains(arr, TESTURL1 "x"));

    ch = buf;
    avail = 8;
    rv = ctutil_serialize_uint64(&ch, &avail, 0xDEADBEEFCAFEBABE);
    ap_assert(rv == APR_SUCCESS);
    ap_assert(avail == 0);
    ap_assert(ch == buf + 8);
    ap_assert(buf[0] == 0xDE);
    ap_assert(buf[1] == 0xAD);
    ap_assert(buf[2] == 0xBE);
    ap_assert(buf[3] == 0xEF);
    ap_assert(buf[4] == 0xCA);
    ap_assert(buf[5] == 0xFE);
    ap_assert(buf[6] == 0xBA);
    ap_assert(buf[7] == 0xBE);

    const_ch = buf;
    avail = 8;
    rv = ctutil_deserialize_uint64(&const_ch, &avail, &val64);
    ap_assert(rv == APR_SUCCESS);
    ap_assert(avail == 0);
    ap_assert(const_ch == buf + 8);
    ap_assert(val64 == 0xDEADBEEFCAFEBABE);

    ch = buf;
    avail = 7;
    ap_assert(ctutil_serialize_uint64(&ch, &avail, 0xDEADBEEFCAFEBABE)
              == APR_EINVAL);

    ch = buf;
    avail = 3;
    rv = ctutil_serialize_uint24(&ch, &avail, 0xDEADBE);
    ap_assert(rv == APR_SUCCESS);
    ap_assert(avail == 0);
    ap_assert(ch == buf + 3);
    ap_assert(buf[0] == 0xDE);
    ap_assert(buf[1] == 0xAD);
    ap_assert(buf[2] == 0xBE);

    ch = buf;
    avail = 1;
    ap_assert(ctutil_serialize_uint16(&ch, &avail, 0xDEAD)
              == APR_EINVAL);

    ch = buf;
    avail = 2;
    rv = ctutil_serialize_uint16(&ch, &avail, 0xDEAD);
    ap_assert(rv == APR_SUCCESS);
    ap_assert(avail == 0);
    ap_assert(ch == buf + 2);
    ap_assert(buf[0] == 0xDE);
    ap_assert(buf[1] == 0xAD);

    const_ch = buf;
    avail = 2;
    rv = ctutil_deserialize_uint16(&const_ch, &avail, &val16);
    ap_assert(rv == APR_SUCCESS);
    ap_assert(avail == 0);
    ap_assert(val16 == 0xDEAD);

    ch = buf;
    avail = 1;
    ap_assert(ctutil_serialize_uint16(&ch, &avail, 0xDEAD)
              == APR_EINVAL);

    ch = buf;
    avail = 1;
    rv = ctutil_serialize_uint8(&ch, &avail, 0xDE);
    ap_assert(rv == APR_SUCCESS);
    ap_assert(avail == 0);
    ap_assert(ch == buf + 1);
    ap_assert(buf[0] == 0xDE);

    ch = buf;
    avail = 0;
    ap_assert(ctutil_serialize_uint8(&ch, &avail, 0xDE)
              == APR_EINVAL);

    ch = buf;
    avail = 8;
    rv = ctutil_write_var16_bytes(&ch, &avail, 
                                  (unsigned char *)"\x01""\x02""\x03""\x04", 4);
    ap_assert(rv == APR_SUCCESS);
    ap_assert(avail == 2);
    ap_assert(ch == buf + 6);
    ap_assert(buf[0] == 0);
    ap_assert(buf[1] == 4);
    ap_assert(buf[2] == 0x01);
    ap_assert(buf[3] == 0x02);
    ap_assert(buf[4] == 0x03);
    ap_assert(buf[5] == 0x04);

    ch = buf;
    avail = 3;
    rv = ctutil_write_var16_bytes(&ch, &avail, 
                                  (unsigned char *)"\x01""\x02""\x03""\x04", 4);
    ap_assert(rv == APR_EINVAL);

    ch = buf;
    avail = 8;
    rv = ctutil_write_var24_bytes(&ch, &avail, 
                                  (unsigned char *)"\x01""\x02""\x03""\x04", 4);
    ap_assert(rv == APR_SUCCESS);
    ap_assert(avail == 1);
    ap_assert(ch == buf + 7);
    ap_assert(buf[0] == 0);
    ap_assert(buf[1] == 0);
    ap_assert(buf[2] == 4);
    ap_assert(buf[3] == 0x01);
    ap_assert(buf[4] == 0x02);
    ap_assert(buf[5] == 0x03);
    ap_assert(buf[6] == 0x04);

    ch = buf;
    avail = 4;
    rv = ctutil_write_var24_bytes(&ch, &avail, 
                                  (unsigned char *)"\x01""\x02""\x03""\x04", 4);
    ap_assert(rv == APR_EINVAL);
}
