<commit_before>#include "MMDB.h"
#include <sys/stat.h>
#include <arpa/inet.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <assert.h>

#if HAVE_CONFIG_H
#include <config.h>
#endif

#define KEYS(...) __VA_ARGS__, NULL

#if MMDB_DEBUG
#define LOCAL
#else
#define LOCAL static
#endif

// prototypes
//
LOCAL void DPRINT_KEY(MMDB_s * mmdb, MMDB_return_s * data);

LOCAL uint32_t get_uint_value(MMDB_entry_s * start, ...);
LOCAL int fdskip_hash_array(MMDB_s * mmdb, MMDB_decode_s * decode);
LOCAL void skip_hash_array(MMDB_s * mmdb, MMDB_decode_s * decode);
LOCAL int fdvget_value(MMDB_entry_s * start, MMDB_return_s * result,
                       va_list params);
LOCAL int fdcmp(MMDB_s * mmdb, MMDB_return_s const *const result,
                char *src_key);

LOCAL int get_tree(MMDB_s * mmdb, uint32_t offset, MMDB_decode_all_s * decode);
int MMDB_vget_value(MMDB_entry_s * start, MMDB_return_s * result,
                    va_list params);

LOCAL MMDB_decode_all_s *dump(MMDB_s * mmdb, MMDB_decode_all_s * decode_all,
                              int indent);

#if !defined HAVE_MEMMEM
LOCAL void *memmem(const void *big, size_t big_len, const void *little,
                   size_t little_len)
{
    if (little_len) {
        int first_char = ((uint8_t *) little)[0];
        const void *ptr = big;
        size_t len = big_len;
        while (len >= little_len
               && (ptr = memchr(ptr, first_char, len - little_len + 1))) {
            if (!memcmp(ptr, little, little_len)) {
                return (void *)ptr;
            }
            len = big_len - (++ptr - big);
        }
    }
    return NULL;
}
#endif

int MMDB_resolve_address(const char *host, int ai_family, int ai_flags,
                         void *ip)
{
    struct addrinfo hints = {.ai_family = ai_family,
        .ai_flags = ai_flags,
        .ai_socktype = SOCK_STREAM
    }, *aifirst;
    int gaierr = getaddrinfo(host, NULL, &hints, &aifirst);

    if (gaierr == 0) {
        if (ai_family == AF_INET) {
            memcpy(&((struct in_addr *)ip)->s_addr,
                   &((struct sockaddr_in *)aifirst->ai_addr)->sin_addr, 4);
        } else if (ai_family == AF_INET6) {
            memcpy(&((struct in6_addr *)ip)->s6_addr,
                   ((struct sockaddr_in6 *)aifirst->ai_addr)->sin6_addr.s6_addr,
                   16);

        } else {
            /* should never happen */
            assert(0);
        }
        freeaddrinfo(aifirst);
    }
    return gaierr;
}

LOCAL float get_ieee754_float(const uint8_t * restrict p)
{
    volatile float f;
    uint8_t *q = (void *)&f;
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
    q[3] = p[0];
    q[2] = p[1];
    q[1] = p[2];
    q[0] = p[3];
#else
    memcpy(q, p, 4);
#endif
    return f;
}

LOCAL double get_ieee754_double(const uint8_t * restrict p)
{
    volatile double d;
    uint8_t *q = (void *)&d;

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
    q[7] = p[0];
    q[6] = p[1];
    q[5] = p[2];
    q[4] = p[3];
    q[3] = p[4];
    q[2] = p[5];
    q[1] = p[6];
    q[0] = p[7];
#else
    memcpy(q, p, 8);
#endif

    return d;
}

LOCAL uint32_t get_uint32(const uint8_t * p)
{
    return (p[0] * 16777216U + p[1] * 65536 + p[2] * 256 + p[3]);
}

LOCAL uint32_t get_uint24(const uint8_t * p)
{
    return (p[0] * 65536U + p[1] * 256 + p[2]);
}

LOCAL uint32_t get_uint16(const uint8_t * p)
{
    return (p[0] * 256U + p[1]);
}

LOCAL uint32_t get_uintX(const uint8_t * p, int length)
{
    uint32_t r = 0;
    while (length-- > 0) {
        r <<= 8;
        r += *p++;
    }
    return r;
}

LOCAL int get_sintX(const uint8_t * p, int length)
{
    return (int)get_uintX(p, length);
}

LOCAL int int_pread(int fd, uint8_t * buffer, ssize_t to_read, off_t offset)
{
    while (to_read > 0) {
        ssize_t have_read = pread(fd, buffer, to_read, offset);
        if (have_read <= 0) {
            return MMDB_IO_ERROR;
        }
        to_read -= have_read;
        if (to_read == 0) {
            break;
        }
        offset += have_read;
        buffer += have_read;
    }
    return MMDB_SUCCESS;
}

int MMDB_pread(int fd, uint8_t * buffer, ssize_t to_read, off_t offset)
{
    return int_pread(fd, buffer, to_read, offset);
}

LOCAL uint32_t get_ptr_from(uint8_t ctrl, uint8_t const *const ptr,
                            int ptr_size)
{
    uint32_t new_offset;
    switch (ptr_size) {
    case 0:
        new_offset = (ctrl & 7) * 256 + ptr[0];
        break;
    case 1:
        new_offset = 2048 + (ctrl & 7) * 65536 + ptr[0] * 256 + ptr[1];
        break;
    case 2:
        new_offset = 2048 + 524288 + (ctrl & 7) * 16777216 + get_uint24(ptr);
        break;
    case 3:
    default:
        new_offset = get_uint32(ptr);
        break;
    }
    return MMDB_DATASECTION_NOOP_SIZE + new_offset;
}

LOCAL char *bytesdup(MMDB_return_s const *const ret)
{
    char *mem = NULL;
    if (ret->offset) {
        mem = malloc(ret->data_size + 1);
        assert(mem != NULL);

        memcpy(mem, ret->ptr, ret->data_size);
        mem[ret->data_size] = '\0';
    }
    return mem;
}

// 0 match like strcmp
int MMDB_strcmp_result(MMDB_s * mmdb, MMDB_return_s const *const result,
                       char *str)
{
    if ((mmdb->flags & MMDB_MODE_MASK) == MMDB_MODE_MEMORY_CACHE) {

        if (result->offset > 0) {
            char *str1 = bytesdup(result);
            int ret = strcmp(str1, str);
            if (str1) {
                free(str1);
            }
            return ret;
        }
        return 1;
    }

    /* MMDB_MODE_STANDARD */
    return fdcmp(mmdb, result, str);

}

LOCAL int fdcmp(MMDB_s * mmdb, MMDB_return_s const *const result, char *src_key)
{
    int src_keylen = result->data_size;
    if (result->data_size != src_keylen) {
        return 1;
    }
    uint32_t segments = mmdb->full_record_size_bytes * mmdb->node_count;
    ssize_t offset = (ssize_t) result->ptr;
    uint8_t buff[1024];
    int len = src_keylen;
    while (len > 0) {
        int want_bytes = len > sizeof(buff) ? sizeof(buff) : len;
        int err = int_pread(mmdb->fd, &buff[0], want_bytes,
                            segments + offset);
        if (err == MMDB_SUCCESS) {
            if (memcmp(buff, src_key, want_bytes)) {
                return 1;       // does not match
            }
            src_key += want_bytes;
            offset += want_bytes;
            len -= want_bytes;
        } else {
            /* in case of read error */
            return 1;
        }
    }
    return 0;
}

LOCAL int get_ext_type(int raw_ext_type)
{
#if defined BROKEN_TYPE
    return 7 + raw_ext_type;
#else
    return 8 + raw_ext_type;
#endif
}

#define FD_RET_ON_ERR(fn) do{                   \
        int err = (fn);                         \
        if ( err != MMDB_SUCCESS )              \
            return err;                         \
    } while(0)

LOCAL int fddecode_one(MMDB_s * mmdb, uint32_t offset, MMDB_decode_s * decode)
{
    const ssize_t segments = mmdb->full_record_size_bytes * mmdb->node_count;
    uint8_t ctrl;
    int type;
    uint8_t b[16];
    int fd = mmdb->fd;
    decode->data.offset = offset;
    FD_RET_ON_ERR(int_pread(fd, &ctrl, 1, segments + offset++));
    type = (ctrl >> 5) & 7;
    if (type == MMDB_DTYPE_EXT) {
        FD_RET_ON_ERR(int_pread(fd, &b[0], 1, segments + offset++));
        type = get_ext_type(b[0]);
    }

    decode->data.type = type;

    if (type == MMDB_DTYPE_PTR) {
        int psize = (ctrl >> 3) & 3;
        FD_RET_ON_ERR(int_pread(fd, &b[0], psize + 1, segments + offset));

        decode->data.uinteger = get_ptr_from(ctrl, b, psize);
        decode->data.data_size = psize + 1;
        decode->offset_to_next = offset + psize + 1;
        MMDB_DBG_CARP
            ("fddecode_one{ptr} ctrl:%d, offset:%d psize:%d point_to::%d\n",
             ctrl, offset, psize, decode->data.uinteger);
        return MMDB_SUCCESS;
    }

    int size = ctrl & 31;
    switch (size) {
    case 29:
        FD_RET_ON_ERR(int_pread(fd, &b[0], 1, segments + offset++));
        size = 29 + b[0];
        break;
    case 30:
        FD_RET_ON_ERR(int_pread(fd, &b[0], 2, segments + offset));
        size = 285 + b[0] * 256 + b[1];
        offset += 2;
        break;
    case 31:
        FD_RET_ON_ERR(int_pread(fd, &b[0], 3, segments + offset));
        size = 65821 + get_uint24(b);
        offset += 3;
    default:
        break;
    }

    if (type == MMDB_DTYPE_MAP || type == MMDB_DTYPE_ARRAY) {
        decode->data.data_size = size;
        decode->offset_to_next = offset;
        return MMDB_SUCCESS;
    }

    if (type == MMDB_DTYPE_BOOLEAN) {
        decode->data.uinteger = size ? 1 : 0;
        decode->data.data_size = 0;
        decode->offset_to_next = offset;
        return MMDB_SUCCESS;
    }

    if (size == 0 && type != MMDB_DTYPE_UINT16 && type != MMDB_DTYPE_UINT32
        && type != MMDB_DTYPE_INT32) {
        decode->data.ptr = NULL;
        decode->data.data_size = 0;
        decode->offset_to_next = offset;
        return MMDB_SUCCESS;
    }

    if ((type == MMDB_DTYPE_UINT32) || (type == MMDB_DTYPE_UINT16)) {
        FD_RET_ON_ERR(int_pread(fd, &b[0], size, segments + offset));
        decode->data.uinteger = get_uintX(b, size);
    } else if (type == MMDB_DTYPE_INT32) {
        FD_RET_ON_ERR(int_pread(fd, &b[0], size, segments + offset));
        decode->data.sinteger = get_sintX(b, size);
    } else if (type == MMDB_DTYPE_UINT64) {
        assert(size >= 0 && size <= 8);
        memset(b, 0, 8);
        if (size > 0)
            FD_RET_ON_ERR(int_pread(fd, &b[8 - size], size, segments + offset));
        memcpy(decode->data.c8, b, 8);
    } else if (type == MMDB_DTYPE_UINT128) {
        assert(size >= 0 && size <= 16);
        memset(b, 0, 16);
        if (size > 0) {
            FD_RET_ON_ERR(int_pread
                          (fd, &b[16 - size], size, segments + offset));
        }
        memcpy(decode->data.c16, b, 16);
    } else if (type == MMDB_DTYPE_IEEE754_FLOAT) {
        size = 4;
        FD_RET_ON_ERR(int_pread(fd, &b[0], size, segments + offset));
        decode->data.float_value = get_ieee754_float(b);
    } else if (type == MMDB_DTYPE_IEEE754_DOUBLE) {
        size = 8;
        FD_RET_ON_ERR(int_pread(fd, &b[0], size, segments + offset));
        decode->data.double_value = get_ieee754_double(b);
    } else {
        decode->data.ptr = NULL + offset;
        decode->data.data_size = size;
    }
    decode->offset_to_next = offset + size;
    return MMDB_SUCCESS;
}

LOCAL void free_all(MMDB_s * mmdb)
{
    if (mmdb) {
        if (mmdb->fname) {
            free(mmdb->fname);
        }
        if (mmdb->fd >= 0) {
            close(mmdb->fd);
        }
        if (mmdb->file_in_mem_ptr) {
            free((void *)mmdb->file_in_mem_ptr);
        } else if (mmdb->meta_data_content) {
            free(mmdb->meta_data_content);
        }
        if (mmdb->fake_metadata_db) {
            free(mmdb->fake_metadata_db);
        }
        free((void *)mmdb);
    }
}

#define RETURN_ON_END_OF_SEARCHX(offset, segments, depth, maxdepth, res)   \
    if ((offset) >= (segments)) {                                       \
        (res)->netmask = (maxdepth) - (depth);                          \
        (res)->entry.offset = (offset) - (segments);                    \
        return MMDB_SUCCESS;                                            \
    }

#define RETURN_ON_END_OF_SEARCH32(offset, segments, depth, res)           \
    MMDB_DBG_CARP( "RETURN_ON_END_OF_SEARCH32 depth:%d offset:%u segments:%d\n", depth, (unsigned int)offset, segments); \
    RETURN_ON_END_OF_SEARCHX(offset,segments,depth, 32, res)

#define RETURN_ON_END_OF_SEARCH128(offset, segments, depth, res)  \
    RETURN_ON_END_OF_SEARCHX(offset,segments,depth,128, res)

LOCAL int fdlookup_by_ipnum(uint32_t ipnum, MMDB_root_entry_s * result)
{
    MMDB_s *mmdb = result->entry.mmdb;
    int segments = mmdb->node_count;
    off_t offset = 0;
    int byte_offset;
    int rl = mmdb->full_record_size_bytes;
    int fd = mmdb->fd;
    uint32_t mask = 0x80000000U;
    int depth;
    uint8_t b[4];

    if (rl == 6) {
        for (depth = 32 - 1; depth >= 0; depth--, mask >>= 1) {
            FD_RET_ON_ERR(int_pread
                          (fd, &b[0], 3,
                           offset * rl + ((ipnum & mask) ? 3 : 0)));
            offset = get_uint24(b);
            RETURN_ON_END_OF_SEARCH32(offset, segments, depth, result);
        }
    } else if (rl == 7) {
        for (depth = 32 - 1; depth >= 0; depth--, mask >>= 1) {
            byte_offset = offset * rl;
            if (ipnum & mask) {
                FD_RET_ON_ERR(int_pread(fd, &b[0], 4, byte_offset + 3));
                offset = get_uint32(b);
                offset &= 0xfffffff;
            } else {
                FD_RET_ON_ERR(int_pread(fd, &b[0], 4, byte_offset));
                offset =
                    b[0] * 65536 + b[1] * 256 + b[2] + ((b[3] & 0xf0) << 20);
            }
            RETURN_ON_END_OF_SEARCH32(offset, segments, depth, result);
        }
    } else if (rl == 8) {
        for (depth = 32 - 1; depth >= 0; depth--, mask >>= 1) {
            FD_RET_ON_ERR(int_pread
                          (fd, &b[0], 4,
                           offset * rl + ((ipnum & mask) ? 4 : 0)));
            offset = get_uint32(b);
            RETURN_ON_END_OF_SEARCH32(offset, segments, depth, result);
        }
    }
    // This should never happen, but if it does something went horribly wrong.
    return MMDB_CORRUPT_DATABASE;
}

#define MMDB_CHKBIT_128(bit,ptr) ((ptr)[((127U - (bit)) >> 3)] & (1U << (~(127U - (bit)) & 7)))

LOCAL int
fdlookup_by_ipnum_128(struct in6_addr ipnum, MMDB_root_entry_s * result)
{
    MMDB_s *mmdb = result->entry.mmdb;
    int segments = mmdb->node_count;
    uint32_t offset = 0;
    int byte_offset;
    int rl = mmdb->full_record_size_bytes;
    int fd = mmdb->fd;
    int depth;
    uint8_t b[4];
    if (rl == 6) {
        for (depth = mmdb->depth - 1; depth >= 0; depth--) {
            byte_offset = offset * rl;
            if (MMDB_CHKBIT_128(depth, (uint8_t *) & ipnum)) {
                byte_offset += 3;
            }
            FD_RET_ON_ERR(int_pread(fd, &b[0], 3, byte_offset));
            offset = get_uint24(b);
            RETURN_ON_END_OF_SEARCH128(offset, segments, depth, result);
        }
    } else if (rl == 7) {
        for (depth = mmdb->depth - 1; depth >= 0; depth--) {
            byte_offset = offset * rl;
            if (MMDB_CHKBIT_128(depth, (uint8_t *) & ipnum)) {
                byte_offset += 3;
                FD_RET_ON_ERR(int_pread(fd, &b[0], 4, byte_offset));
                offset = get_uint32(b);
                offset &= 0xfffffff;
            } else {
                FD_RET_ON_ERR(int_pread(fd, &b[0], 4, byte_offset));
                offset =
                    b[0] * 65536 + b[1] * 256 + b[2] + ((b[3] & 0xf0) << 20);
            }
            RETURN_ON_END_OF_SEARCH128(offset, segments, depth, result);
        }
    } else if (rl == 8) {
        for (depth = mmdb->depth - 1; depth >= 0; depth--) {
            byte_offset = offset * rl;
            if (MMDB_CHKBIT_128(depth, (uint8_t *) & ipnum)) {
                byte_offset += 4;
            }
            FD_RET_ON_ERR(int_pread(fd, &b[0], 4, byte_offset));
            offset = get_uint32(b);
            RETURN_ON_END_OF_SEARCH128(offset, segments, depth, result);
        }
    }
    //uhhh should never happen !
    return MMDB_CORRUPT_DATABASE;
}

int MMDB_lookup_by_ipnum_128(struct in6_addr ipnum, MMDB_root_entry_s * result)
{
    MMDB_s *mmdb = result->entry.mmdb;

    if (mmdb->fd >= 0) {
        return fdlookup_by_ipnum_128(ipnum, result);
    }

    int segments = mmdb->node_count;
    uint32_t offset = 0;
    int rl = mmdb->full_record_size_bytes;
    const uint8_t *mem = mmdb->file_in_mem_ptr;
    const uint8_t *p;
    int depth;
    if (rl == 6) {
        for (depth = mmdb->depth - 1; depth >= 0; depth--) {
            p = &mem[offset * rl];
            if (MMDB_CHKBIT_128(depth, (uint8_t *) & ipnum)) {
                p += 3;
            }
            offset = get_uint24(p);
            RETURN_ON_END_OF_SEARCH128(offset, segments, depth, result);
        }
    } else if (rl == 7) {
        for (depth = mmdb->depth - 1; depth >= 0; depth--) {
            p = &mem[offset * rl];
            if (MMDB_CHKBIT_128(depth, (uint8_t *) & ipnum)) {
                p += 3;
                offset = get_uint32(p);
                offset &= 0xfffffff;
            } else {
                offset =
                    p[0] * 65536 + p[1] * 256 + p[2] + ((p[3] & 0xf0) << 20);
            }
            RETURN_ON_END_OF_SEARCH128(offset, segments, depth, result);
        }
    } else if (rl == 8) {
        for (depth = mmdb->depth - 1; depth >= 0; depth--) {
            p = &mem[offset * rl];
            if (MMDB_CHKBIT_128(depth, (uint8_t *) & ipnum)) {
                p += 4;
            }
            offset = get_uint32(p);
            RETURN_ON_END_OF_SEARCH128(offset, segments, depth, result);
        }
    }
    //uhhh should never happen !
    return MMDB_CORRUPT_DATABASE;
}

int MMDB_lookup_by_ipnum(uint32_t ipnum, MMDB_root_entry_s * res)
{
    MMDB_s *mmdb = res->entry.mmdb;

    MMDB_DBG_CARP("MMDB_lookup_by_ipnum{mmdb} fd:%d depth:%d node_count:%d\n",
                  mmdb->fd, mmdb->depth, mmdb->node_count);
    MMDB_DBG_CARP("MMDB_lookup_by_ipnum ip:%u fd:%d\n", ipnum, mmdb->fd);

    if (mmdb->fd >= 0) {
        return fdlookup_by_ipnum(ipnum, res);
    }

    int segments = mmdb->node_count;
    uint32_t offset = 0;
    int rl = mmdb->full_record_size_bytes;
    const uint8_t *mem = mmdb->file_in_mem_ptr;
    const uint8_t *p;
    uint32_t mask = 0x80000000U;
    int depth;
    if (rl == 6) {
        for (depth = 32 - 1; depth >= 0; depth--, mask >>= 1) {
            p = &mem[offset * rl];
            if (ipnum & mask) {
                p += 3;
            }
            offset = get_uint24(p);
            RETURN_ON_END_OF_SEARCH32(offset, segments, depth, res);
        }
    } else if (rl == 7) {
        for (depth = 32 - 1; depth >= 0; depth--, mask >>= 1) {
            p = &mem[offset * rl];
            if (ipnum & mask) {
                p += 3;
                offset = get_uint32(p);
                offset &= 0xfffffff;
            } else {
                offset =
                    p[0] * 65536 + p[1] * 256 + p[2] + ((p[3] & 0xf0) << 20);
            }
            RETURN_ON_END_OF_SEARCH32(offset, segments, depth, res);
        }
    } else if (rl == 8) {
        for (depth = 32 - 1; depth >= 0; depth--, mask >>= 1) {
            p = &mem[offset * rl];
            if (ipnum & mask) {
                p += 4;
            }
            offset = get_uint32(p);
            RETURN_ON_END_OF_SEARCH32(offset, segments, depth, res);
        }
    }
    //uhhh should never happen !
    return MMDB_CORRUPT_DATABASE;
}

typedef union {
    struct in_addr v4;
    struct in6_addr v6;
} in_addr_any;

LOCAL int resolve_any_address(const char *ipstr, int is_ipv4,
                              in_addr_any * in_addr)
{
    int ai_flags = AI_NUMERICHOST;
    struct addrinfo hints = {
        .ai_socktype = SOCK_STREAM
    };
    struct addrinfo *addresses;
    int gai_status;

    if (is_ipv4) {
        hints.ai_flags = ai_flags;
        hints.ai_family = AF_INET;
    } else {
        hints.ai_flags = ai_flags | AI_V4MAPPED;
        hints.ai_family = AF_INET6;
    }

    gai_status = getaddrinfo(ipstr, NULL, &hints, &addresses);
    if (gai_status) {
        return gai_status;
    }

    if (hints.ai_family == AF_INET) {
        memcpy(&((struct in_addr *)in_addr)->s_addr,
               &((struct sockaddr_in *)addresses->ai_addr)->sin_addr, 4);
    } else if (hints.ai_family == AF_INET6) {
        memcpy(&((struct in6_addr *)in_addr)->s6_addr,
               ((struct sockaddr_in6 *)addresses->ai_addr)->sin6_addr.s6_addr,
               16);
    } else {
        /* This should never happen */
        assert(0);
    }

    freeaddrinfo(addresses);

    return 0;
}

MMDB_root_entry_s *MMDB_lookup(MMDB_s * mmdb, const char *ipstr, int *gai_error,
                               int *mmdb_error)
{
    // XXX ip version should be in the metadata structure
    int is_ipv4 = mmdb->depth == 32 ? 1 : 0;
    in_addr_any in_addr;

    MMDB_root_entry_s *root = malloc(sizeof(MMDB_root_entry_s *));
    root->entry.mmdb = mmdb;

    *gai_error = resolve_any_address(ipstr, is_ipv4, &in_addr);

    if (*gai_error) {
        return NULL;
    }

    if (is_ipv4) {
        *mmdb_error = MMDB_lookup_by_ipnum(in_addr.v4.s_addr, root);
    } else {
        *mmdb_error = MMDB_lookup_by_ipnum_128(in_addr.v6, root);
    }

    if (*mmdb_error) {
        return NULL;
    }

    if (root->entry.offset > 0) {
        return root;
    } else {
        return NULL;
    }
}

LOCAL uint16_t init(MMDB_s * mmdb, const char *fname, uint32_t flags)
{
    struct stat s;
    int fd;
    uint8_t *ptr;
    ssize_t size;
    off_t offset;

    mmdb->fname = strdup(fname);
    if (mmdb->fname == NULL) {
        return MMDB_OUT_OF_MEMORY;
    }

    mmdb->fd = fd = open(fname, O_RDONLY);
    if (fd < 0) {
        return MMDB_FILE_OPEN_ERROR;
    }

    fstat(fd, &s);
    mmdb->flags = flags;
    if ((flags & MMDB_MODE_MASK) == MMDB_MODE_MEMORY_CACHE) {
        mmdb->fd = -1;
        size = s.st_size;
        offset = 0;
    } else {
        mmdb->fd = fd;
        size = s.st_size < 2000 ? s.st_size : 2000;
        offset = s.st_size - size;
    }

    ptr = mmdb->meta_data_content = malloc(size);
    if (ptr == NULL) {
        return MMDB_INVALID_DATABASE;
    }

    if (MMDB_SUCCESS != int_pread(fd, mmdb->meta_data_content, size, offset)) {
        return MMDB_IO_ERROR;
    }

    const uint8_t *metadata = memmem(ptr, size, "\xab\xcd\xefMaxMind.com", 14);
    if (metadata == NULL) {<commit_msg>Check all malloc calls with an assert<commit_after>#include "MMDB.h"
#include <sys/stat.h>
#include <arpa/inet.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <assert.h>

#if HAVE_CONFIG_H
#include <config.h>
#endif

#define KEYS(...) __VA_ARGS__, NULL

#if MMDB_DEBUG
#define LOCAL
#else
#define LOCAL static
#endif

// prototypes
//
LOCAL void DPRINT_KEY(MMDB_s * mmdb, MMDB_return_s * data);

LOCAL uint32_t get_uint_value(MMDB_entry_s * start, ...);
LOCAL int fdskip_hash_array(MMDB_s * mmdb, MMDB_decode_s * decode);
LOCAL void skip_hash_array(MMDB_s * mmdb, MMDB_decode_s * decode);
LOCAL int fdvget_value(MMDB_entry_s * start, MMDB_return_s * result,
                       va_list params);
LOCAL int fdcmp(MMDB_s * mmdb, MMDB_return_s const *const result,
                char *src_key);

LOCAL int get_tree(MMDB_s * mmdb, uint32_t offset, MMDB_decode_all_s * decode);
int MMDB_vget_value(MMDB_entry_s * start, MMDB_return_s * result,
                    va_list params);

LOCAL MMDB_decode_all_s *dump(MMDB_s * mmdb, MMDB_decode_all_s * decode_all,
                              int indent);

#if !defined HAVE_MEMMEM
LOCAL void *memmem(const void *big, size_t big_len, const void *little,
                   size_t little_len)
{
    if (little_len) {
        int first_char = ((uint8_t *) little)[0];
        const void *ptr = big;
        size_t len = big_len;
        while (len >= little_len
               && (ptr = memchr(ptr, first_char, len - little_len + 1))) {
            if (!memcmp(ptr, little, little_len)) {
                return (void *)ptr;
            }
            len = big_len - (++ptr - big);
        }
    }
    return NULL;
}
#endif

int MMDB_resolve_address(const char *host, int ai_family, int ai_flags,
                         void *ip)
{
    struct addrinfo hints = {.ai_family = ai_family,
        .ai_flags = ai_flags,
        .ai_socktype = SOCK_STREAM
    }, *aifirst;
    int gaierr = getaddrinfo(host, NULL, &hints, &aifirst);

    if (gaierr == 0) {
        if (ai_family == AF_INET) {
            memcpy(&((struct in_addr *)ip)->s_addr,
                   &((struct sockaddr_in *)aifirst->ai_addr)->sin_addr, 4);
        } else if (ai_family == AF_INET6) {
            memcpy(&((struct in6_addr *)ip)->s6_addr,
                   ((struct sockaddr_in6 *)aifirst->ai_addr)->sin6_addr.s6_addr,
                   16);

        } else {
            /* should never happen */
            assert(0);
        }
        freeaddrinfo(aifirst);
    }
    return gaierr;
}

LOCAL float get_ieee754_float(const uint8_t * restrict p)
{
    volatile float f;
    uint8_t *q = (void *)&f;
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
    q[3] = p[0];
    q[2] = p[1];
    q[1] = p[2];
    q[0] = p[3];
#else
    memcpy(q, p, 4);
#endif
    return f;
}

LOCAL double get_ieee754_double(const uint8_t * restrict p)
{
    volatile double d;
    uint8_t *q = (void *)&d;

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
    q[7] = p[0];
    q[6] = p[1];
    q[5] = p[2];
    q[4] = p[3];
    q[3] = p[4];
    q[2] = p[5];
    q[1] = p[6];
    q[0] = p[7];
#else
    memcpy(q, p, 8);
#endif

    return d;
}

LOCAL uint32_t get_uint32(const uint8_t * p)
{
    return (p[0] * 16777216U + p[1] * 65536 + p[2] * 256 + p[3]);
}

LOCAL uint32_t get_uint24(const uint8_t * p)
{
    return (p[0] * 65536U + p[1] * 256 + p[2]);
}

LOCAL uint32_t get_uint16(const uint8_t * p)
{
    return (p[0] * 256U + p[1]);
}

LOCAL uint32_t get_uintX(const uint8_t * p, int length)
{
    uint32_t r = 0;
    while (length-- > 0) {
        r <<= 8;
        r += *p++;
    }
    return r;
}

LOCAL int get_sintX(const uint8_t * p, int length)
{
    return (int)get_uintX(p, length);
}

LOCAL int int_pread(int fd, uint8_t * buffer, ssize_t to_read, off_t offset)
{
    while (to_read > 0) {
        ssize_t have_read = pread(fd, buffer, to_read, offset);
        if (have_read <= 0) {
            return MMDB_IO_ERROR;
        }
        to_read -= have_read;
        if (to_read == 0) {
            break;
        }
        offset += have_read;
        buffer += have_read;
    }
    return MMDB_SUCCESS;
}

int MMDB_pread(int fd, uint8_t * buffer, ssize_t to_read, off_t offset)
{
    return int_pread(fd, buffer, to_read, offset);
}

LOCAL uint32_t get_ptr_from(uint8_t ctrl, uint8_t const *const ptr,
                            int ptr_size)
{
    uint32_t new_offset;
    switch (ptr_size) {
    case 0:
        new_offset = (ctrl & 7) * 256 + ptr[0];
        break;
    case 1:
        new_offset = 2048 + (ctrl & 7) * 65536 + ptr[0] * 256 + ptr[1];
        break;
    case 2:
        new_offset = 2048 + 524288 + (ctrl & 7) * 16777216 + get_uint24(ptr);
        break;
    case 3:
    default:
        new_offset = get_uint32(ptr);
        break;
    }
    return MMDB_DATASECTION_NOOP_SIZE + new_offset;
}

LOCAL char *bytesdup(MMDB_return_s const *const ret)
{
    char *mem = NULL;
    if (ret->offset) {
        mem = malloc(ret->data_size + 1);
        assert(mem != NULL);

        memcpy(mem, ret->ptr, ret->data_size);
        mem[ret->data_size] = '\0';
    }
    return mem;
}

// 0 match like strcmp
int MMDB_strcmp_result(MMDB_s * mmdb, MMDB_return_s const *const result,
                       char *str)
{
    if ((mmdb->flags & MMDB_MODE_MASK) == MMDB_MODE_MEMORY_CACHE) {

        if (result->offset > 0) {
            char *str1 = bytesdup(result);
            int ret = strcmp(str1, str);
            if (str1) {
                free(str1);
            }
            return ret;
        }
        return 1;
    }

    /* MMDB_MODE_STANDARD */
    return fdcmp(mmdb, result, str);

}

LOCAL int fdcmp(MMDB_s * mmdb, MMDB_return_s const *const result, char *src_key)
{
    int src_keylen = result->data_size;
    if (result->data_size != src_keylen) {
        return 1;
    }
    uint32_t segments = mmdb->full_record_size_bytes * mmdb->node_count;
    ssize_t offset = (ssize_t) result->ptr;
    uint8_t buff[1024];
    int len = src_keylen;
    while (len > 0) {
        int want_bytes = len > sizeof(buff) ? sizeof(buff) : len;
        int err = int_pread(mmdb->fd, &buff[0], want_bytes,
                            segments + offset);
        if (err == MMDB_SUCCESS) {
            if (memcmp(buff, src_key, want_bytes)) {
                return 1;       // does not match
            }
            src_key += want_bytes;
            offset += want_bytes;
            len -= want_bytes;
        } else {
            /* in case of read error */
            return 1;
        }
    }
    return 0;
}

LOCAL int get_ext_type(int raw_ext_type)
{
#if defined BROKEN_TYPE
    return 7 + raw_ext_type;
#else
    return 8 + raw_ext_type;
#endif
}

#define FD_RET_ON_ERR(fn) do{                   \
        int err = (fn);                         \
        if ( err != MMDB_SUCCESS )              \
            return err;                         \
    } while(0)

LOCAL int fddecode_one(MMDB_s * mmdb, uint32_t offset, MMDB_decode_s * decode)
{
    const ssize_t segments = mmdb->full_record_size_bytes * mmdb->node_count;
    uint8_t ctrl;
    int type;
    uint8_t b[16];
    int fd = mmdb->fd;
    decode->data.offset = offset;
    FD_RET_ON_ERR(int_pread(fd, &ctrl, 1, segments + offset++));
    type = (ctrl >> 5) & 7;
    if (type == MMDB_DTYPE_EXT) {
        FD_RET_ON_ERR(int_pread(fd, &b[0], 1, segments + offset++));
        type = get_ext_type(b[0]);
    }

    decode->data.type = type;

    if (type == MMDB_DTYPE_PTR) {
        int psize = (ctrl >> 3) & 3;
        FD_RET_ON_ERR(int_pread(fd, &b[0], psize + 1, segments + offset));

        decode->data.uinteger = get_ptr_from(ctrl, b, psize);
        decode->data.data_size = psize + 1;
        decode->offset_to_next = offset + psize + 1;
        MMDB_DBG_CARP
            ("fddecode_one{ptr} ctrl:%d, offset:%d psize:%d point_to::%d\n",
             ctrl, offset, psize, decode->data.uinteger);
        return MMDB_SUCCESS;
    }

    int size = ctrl & 31;
    switch (size) {
    case 29:
        FD_RET_ON_ERR(int_pread(fd, &b[0], 1, segments + offset++));
        size = 29 + b[0];
        break;
    case 30:
        FD_RET_ON_ERR(int_pread(fd, &b[0], 2, segments + offset));
        size = 285 + b[0] * 256 + b[1];
        offset += 2;
        break;
    case 31:
        FD_RET_ON_ERR(int_pread(fd, &b[0], 3, segments + offset));
        size = 65821 + get_uint24(b);
        offset += 3;
    default:
        break;
    }

    if (type == MMDB_DTYPE_MAP || type == MMDB_DTYPE_ARRAY) {
        decode->data.data_size = size;
        decode->offset_to_next = offset;
        return MMDB_SUCCESS;
    }

    if (type == MMDB_DTYPE_BOOLEAN) {
        decode->data.uinteger = size ? 1 : 0;
        decode->data.data_size = 0;
        decode->offset_to_next = offset;
        return MMDB_SUCCESS;
    }

    if (size == 0 && type != MMDB_DTYPE_UINT16 && type != MMDB_DTYPE_UINT32
        && type != MMDB_DTYPE_INT32) {
        decode->data.ptr = NULL;
        decode->data.data_size = 0;
        decode->offset_to_next = offset;
        return MMDB_SUCCESS;
    }

    if ((type == MMDB_DTYPE_UINT32) || (type == MMDB_DTYPE_UINT16)) {
        FD_RET_ON_ERR(int_pread(fd, &b[0], size, segments + offset));
        decode->data.uinteger = get_uintX(b, size);
    } else if (type == MMDB_DTYPE_INT32) {
        FD_RET_ON_ERR(int_pread(fd, &b[0], size, segments + offset));
        decode->data.sinteger = get_sintX(b, size);
    } else if (type == MMDB_DTYPE_UINT64) {
        assert(size >= 0 && size <= 8);
        memset(b, 0, 8);
        if (size > 0)
            FD_RET_ON_ERR(int_pread(fd, &b[8 - size], size, segments + offset));
        memcpy(decode->data.c8, b, 8);
    } else if (type == MMDB_DTYPE_UINT128) {
        assert(size >= 0 && size <= 16);
        memset(b, 0, 16);
        if (size > 0) {
            FD_RET_ON_ERR(int_pread
                          (fd, &b[16 - size], size, segments + offset));
        }
        memcpy(decode->data.c16, b, 16);
    } else if (type == MMDB_DTYPE_IEEE754_FLOAT) {
        size = 4;
        FD_RET_ON_ERR(int_pread(fd, &b[0], size, segments + offset));
        decode->data.float_value = get_ieee754_float(b);
    } else if (type == MMDB_DTYPE_IEEE754_DOUBLE) {
        size = 8;
        FD_RET_ON_ERR(int_pread(fd, &b[0], size, segments + offset));
        decode->data.double_value = get_ieee754_double(b);
    } else {
        decode->data.ptr = NULL + offset;
        decode->data.data_size = size;
    }
    decode->offset_to_next = offset + size;
    return MMDB_SUCCESS;
}

LOCAL void free_all(MMDB_s * mmdb)
{
    if (mmdb) {
        if (mmdb->fname) {
            free(mmdb->fname);
        }
        if (mmdb->fd >= 0) {
            close(mmdb->fd);
        }
        if (mmdb->file_in_mem_ptr) {
            free((void *)mmdb->file_in_mem_ptr);
        } else if (mmdb->meta_data_content) {
            free(mmdb->meta_data_content);
        }
        if (mmdb->fake_metadata_db) {
            free(mmdb->fake_metadata_db);
        }
        free((void *)mmdb);
    }
}

#define RETURN_ON_END_OF_SEARCHX(offset, segments, depth, maxdepth, res)   \
    if ((offset) >= (segments)) {                                       \
        (res)->netmask = (maxdepth) - (depth);                          \
        (res)->entry.offset = (offset) - (segments);                    \
        return MMDB_SUCCESS;                                            \
    }

#define RETURN_ON_END_OF_SEARCH32(offset, segments, depth, res)           \
    MMDB_DBG_CARP( "RETURN_ON_END_OF_SEARCH32 depth:%d offset:%u segments:%d\n", depth, (unsigned int)offset, segments); \
    RETURN_ON_END_OF_SEARCHX(offset,segments,depth, 32, res)

#define RETURN_ON_END_OF_SEARCH128(offset, segments, depth, res)  \
    RETURN_ON_END_OF_SEARCHX(offset,segments,depth,128, res)

LOCAL int fdlookup_by_ipnum(uint32_t ipnum, MMDB_root_entry_s * result)
{
    MMDB_s *mmdb = result->entry.mmdb;
    int segments = mmdb->node_count;
    off_t offset = 0;
    int byte_offset;
    int rl = mmdb->full_record_size_bytes;
    int fd = mmdb->fd;
    uint32_t mask = 0x80000000U;
    int depth;
    uint8_t b[4];

    if (rl == 6) {
        for (depth = 32 - 1; depth >= 0; depth--, mask >>= 1) {
            FD_RET_ON_ERR(int_pread
                          (fd, &b[0], 3,
                           offset * rl + ((ipnum & mask) ? 3 : 0)));
            offset = get_uint24(b);
            RETURN_ON_END_OF_SEARCH32(offset, segments, depth, result);
        }
    } else if (rl == 7) {
        for (depth = 32 - 1; depth >= 0; depth--, mask >>= 1) {
            byte_offset = offset * rl;
            if (ipnum & mask) {
                FD_RET_ON_ERR(int_pread(fd, &b[0], 4, byte_offset + 3));
                offset = get_uint32(b);
                offset &= 0xfffffff;
            } else {
                FD_RET_ON_ERR(int_pread(fd, &b[0], 4, byte_offset));
                offset =
                    b[0] * 65536 + b[1] * 256 + b[2] + ((b[3] & 0xf0) << 20);
            }
            RETURN_ON_END_OF_SEARCH32(offset, segments, depth, result);
        }
    } else if (rl == 8) {
        for (depth = 32 - 1; depth >= 0; depth--, mask >>= 1) {
            FD_RET_ON_ERR(int_pread
                          (fd, &b[0], 4,
                           offset * rl + ((ipnum & mask) ? 4 : 0)));
            offset = get_uint32(b);
            RETURN_ON_END_OF_SEARCH32(offset, segments, depth, result);
        }
    }
    // This should never happen, but if it does something went horribly wrong.
    return MMDB_CORRUPT_DATABASE;
}

#define MMDB_CHKBIT_128(bit,ptr) ((ptr)[((127U - (bit)) >> 3)] & (1U << (~(127U - (bit)) & 7)))

LOCAL int
fdlookup_by_ipnum_128(struct in6_addr ipnum, MMDB_root_entry_s * result)
{
    MMDB_s *mmdb = result->entry.mmdb;
    int segments = mmdb->node_count;
    uint32_t offset = 0;
    int byte_offset;
    int rl = mmdb->full_record_size_bytes;
    int fd = mmdb->fd;
    int depth;
    uint8_t b[4];
    if (rl == 6) {
        for (depth = mmdb->depth - 1; depth >= 0; depth--) {
            byte_offset = offset * rl;
            if (MMDB_CHKBIT_128(depth, (uint8_t *) & ipnum)) {
                byte_offset += 3;
            }
            FD_RET_ON_ERR(int_pread(fd, &b[0], 3, byte_offset));
            offset = get_uint24(b);
            RETURN_ON_END_OF_SEARCH128(offset, segments, depth, result);
        }
    } else if (rl == 7) {
        for (depth = mmdb->depth - 1; depth >= 0; depth--) {
            byte_offset = offset * rl;
            if (MMDB_CHKBIT_128(depth, (uint8_t *) & ipnum)) {
                byte_offset += 3;
                FD_RET_ON_ERR(int_pread(fd, &b[0], 4, byte_offset));
                offset = get_uint32(b);
                offset &= 0xfffffff;
            } else {
                FD_RET_ON_ERR(int_pread(fd, &b[0], 4, byte_offset));
                offset =
                    b[0] * 65536 + b[1] * 256 + b[2] + ((b[3] & 0xf0) << 20);
            }
            RETURN_ON_END_OF_SEARCH128(offset, segments, depth, result);
        }
    } else if (rl == 8) {
        for (depth = mmdb->depth - 1; depth >= 0; depth--) {
            byte_offset = offset * rl;
            if (MMDB_CHKBIT_128(depth, (uint8_t *) & ipnum)) {
                byte_offset += 4;
            }
            FD_RET_ON_ERR(int_pread(fd, &b[0], 4, byte_offset));
            offset = get_uint32(b);
            RETURN_ON_END_OF_SEARCH128(offset, segments, depth, result);
        }
    }
    //uhhh should never happen !
    return MMDB_CORRUPT_DATABASE;
}

int MMDB_lookup_by_ipnum_128(struct in6_addr ipnum, MMDB_root_entry_s * result)
{
    MMDB_s *mmdb = result->entry.mmdb;

    if (mmdb->fd >= 0) {
        return fdlookup_by_ipnum_128(ipnum, result);
    }

    int segments = mmdb->node_count;
    uint32_t offset = 0;
    int rl = mmdb->full_record_size_bytes;
    const uint8_t *mem = mmdb->file_in_mem_ptr;
    const uint8_t *p;
    int depth;
    if (rl == 6) {
        for (depth = mmdb->depth - 1; depth >= 0; depth--) {
            p = &mem[offset * rl];
            if (MMDB_CHKBIT_128(depth, (uint8_t *) & ipnum)) {
                p += 3;
            }
            offset = get_uint24(p);
            RETURN_ON_END_OF_SEARCH128(offset, segments, depth, result);
        }
    } else if (rl == 7) {
        for (depth = mmdb->depth - 1; depth >= 0; depth--) {
            p = &mem[offset * rl];
            if (MMDB_CHKBIT_128(depth, (uint8_t *) & ipnum)) {
                p += 3;
                offset = get_uint32(p);
                offset &= 0xfffffff;
            } else {
                offset =
                    p[0] * 65536 + p[1] * 256 + p[2] + ((p[3] & 0xf0) << 20);
            }
            RETURN_ON_END_OF_SEARCH128(offset, segments, depth, result);
        }
    } else if (rl == 8) {
        for (depth = mmdb->depth - 1; depth >= 0; depth--) {
            p = &mem[offset * rl];
            if (MMDB_CHKBIT_128(depth, (uint8_t *) & ipnum)) {
                p += 4;
            }
            offset = get_uint32(p);
            RETURN_ON_END_OF_SEARCH128(offset, segments, depth, result);
        }
    }
    //uhhh should never happen !
    return MMDB_CORRUPT_DATABASE;
}

int MMDB_lookup_by_ipnum(uint32_t ipnum, MMDB_root_entry_s * res)
{
    MMDB_s *mmdb = res->entry.mmdb;

    MMDB_DBG_CARP("MMDB_lookup_by_ipnum{mmdb} fd:%d depth:%d node_count:%d\n",
                  mmdb->fd, mmdb->depth, mmdb->node_count);
    MMDB_DBG_CARP("MMDB_lookup_by_ipnum ip:%u fd:%d\n", ipnum, mmdb->fd);

    if (mmdb->fd >= 0) {
        return fdlookup_by_ipnum(ipnum, res);
    }

    int segments = mmdb->node_count;
    uint32_t offset = 0;
    int rl = mmdb->full_record_size_bytes;
    const uint8_t *mem = mmdb->file_in_mem_ptr;
    const uint8_t *p;
    uint32_t mask = 0x80000000U;
    int depth;
    if (rl == 6) {
        for (depth = 32 - 1; depth >= 0; depth--, mask >>= 1) {
            p = &mem[offset * rl];
            if (ipnum & mask) {
                p += 3;
            }
            offset = get_uint24(p);
            RETURN_ON_END_OF_SEARCH32(offset, segments, depth, res);
        }
    } else if (rl == 7) {
        for (depth = 32 - 1; depth >= 0; depth--, mask >>= 1) {
            p = &mem[offset * rl];
            if (ipnum & mask) {
                p += 3;
                offset = get_uint32(p);
                offset &= 0xfffffff;
            } else {
                offset =
                    p[0] * 65536 + p[1] * 256 + p[2] + ((p[3] & 0xf0) << 20);
            }
            RETURN_ON_END_OF_SEARCH32(offset, segments, depth, res);
        }
    } else if (rl == 8) {
        for (depth = 32 - 1; depth >= 0; depth--, mask >>= 1) {
            p = &mem[offset * rl];
            if (ipnum & mask) {
                p += 4;
            }
            offset = get_uint32(p);
            RETURN_ON_END_OF_SEARCH32(offset, segments, depth, res);
        }
    }
    //uhhh should never happen !
    return MMDB_CORRUPT_DATABASE;
}

typedef union {
    struct in_addr v4;
    struct in6_addr v6;
} in_addr_any;

LOCAL int resolve_any_address(const char *ipstr, int is_ipv4,
                              in_addr_any * in_addr)
{
    int ai_flags = AI_NUMERICHOST;
    struct addrinfo hints = {
        .ai_socktype = SOCK_STREAM
    };
    struct addrinfo *addresses;
    int gai_status;

    if (is_ipv4) {
        hints.ai_flags = ai_flags;
        hints.ai_family = AF_INET;
    } else {
        hints.ai_flags = ai_flags | AI_V4MAPPED;
        hints.ai_family = AF_INET6;
    }

    gai_status = getaddrinfo(ipstr, NULL, &hints, &addresses);
    if (gai_status) {
        return gai_status;
    }

    if (hints.ai_family == AF_INET) {
        memcpy(&((struct in_addr *)in_addr)->s_addr,
               &((struct sockaddr_in *)addresses->ai_addr)->sin_addr, 4);
    } else if (hints.ai_family == AF_INET6) {
        memcpy(&((struct in6_addr *)in_addr)->s6_addr,
               ((struct sockaddr_in6 *)addresses->ai_addr)->sin6_addr.s6_addr,
               16);
    } else {
        /* This should never happen */
        assert(0);
    }

    freeaddrinfo(addresses);

    return 0;
}

MMDB_root_entry_s *MMDB_lookup(MMDB_s * mmdb, const char *ipstr, int *gai_error,
                               int *mmdb_error)
{
    // XXX ip version should be in the metadata structure
    int is_ipv4 = mmdb->depth == 32 ? 1 : 0;
    in_addr_any in_addr;

    MMDB_root_entry_s *root = malloc(sizeof(MMDB_root_entry_s *));
    assert(root != NULL);

    root->entry.mmdb = mmdb;

    *gai_error = resolve_any_address(ipstr, is_ipv4, &in_addr);

    if (*gai_error) {
        return NULL;
    }

    if (is_ipv4) {
        *mmdb_error = MMDB_lookup_by_ipnum(in_addr.v4.s_addr, root);
    } else {
        *mmdb_error = MMDB_lookup_by_ipnum_128(in_addr.v6, root);
    }

    if (*mmdb_error) {
        return NULL;
    }

    if (root->entry.offset > 0) {
        return root;
    } else {
        return NULL;
    }
}

LOCAL uint16_t init(MMDB_s * mmdb, const char *fname, uint32_t flags)
{
    struct stat s;
    int fd;
    uint8_t *ptr;
    ssize_t size;
    off_t offset;

    mmdb->fname = strdup(fname);
    if (mmdb->fname == NULL) {
        return MMDB_OUT_OF_MEMORY;
    }

    mmdb->fd = fd = open(fname, O_RDONLY);
    if (fd < 0) {
        return MMDB_FILE_OPEN_ERROR;
    }

    fstat(fd, &s);
    mmdb->flags = flags;
    if ((flags & MMDB_MODE_MASK) == MMDB_MODE_MEMORY_CACHE) {
        mmdb->fd = -1;
        size = s.st_size;
        offset = 0;
    } else {
        mmdb->fd = fd;
        size = s.st_size < 2000 ? s.st_size : 2000;
        offset = s.st_size - size;
    }

    ptr = mmdb->meta_data_content = malloc(size);
    assert(ptr != NULL);

    if (ptr == NULL) {
        return MMDB_INVALID_DATABASE;
    }

    if (MMDB_SUCCESS != int_pread(fd, mmdb->meta_data_content, size, offset)) {
        return MMDB_IO_ERROR;
    }

    const uint8_t *metadata = memmem(ptr, size, "\xab\xcd\xefMaxMind.com", 14);
    if (metadata == NULL) {
{'fraction_non_alphanumeric': 0.1322025442753804, 'fraction_numerical': 0.03047688156194018, 'mean_word_length': 1.9496354758879004, 'pattern_counts': {'":': 0, '<': 59, '<?xml version=': 0, '>': 319, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 12, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2816535', 'n_tokens_mistral': 17382, 'n_tokens_neox': 16176, 'n_words': 3518}