summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLi Baiyang <[email protected]>2018-11-13 10:33:59 +0800
committerLi Baiyang <[email protected]>2018-11-13 10:33:59 +0800
commit45fc5bdffc619b424608016c94c7174897f318e9 (patch)
tree28777e26d3b979eb7567ae804a4d01e33bdcaca0 /src
initialize
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am25
-rw-r--r--src/Makefile.in871
-rw-r--r--src/data-pool.c180
-rw-r--r--src/data-pool.h52
-rw-r--r--src/libmaxminddb.pc.in11
-rw-r--r--src/maxminddb-compat-util.h167
-rw-r--r--src/maxminddb.c2181
7 files changed, 3487 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..6d57aca
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,25 @@
+include $(top_srcdir)/common.mk
+
+lib_LTLIBRARIES = libmaxminddb.la
+
+libmaxminddb_la_SOURCES = maxminddb.c maxminddb-compat-util.h \
+ data-pool.c data-pool.h
+libmaxminddb_la_LDFLAGS = -version-info 0:7:0 -export-symbols-regex '^MMDB_.*'
+include_HEADERS = $(top_srcdir)/include/maxminddb.h
+
+pkgconfig_DATA = libmaxminddb.pc
+
+TESTS = test-data-pool
+
+check_PROGRAMS = test-data-pool
+
+test_data_pool_SOURCES = data-pool.c data-pool.h
+test_data_pool_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/t -DTEST_DATA_POOL
+test_data_pool_LDADD = $(top_srcdir)/t/libmmdbtest.la \
+ $(top_srcdir)/t/libtap/libtap.a
+
+$(top_srcdir)/t/libmmdbtest.la:
+ $(MAKE) -C $(top_srcdir)/t libmmdbtest.la
+
+$(top_srcdir)/t/libtap/libtap.a:
+ $(MAKE) -C $(top_srcdir)/t/libtap libtap.a
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 0000000..77971a8
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,871 @@
+# Makefile.in generated by automake 1.15 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2014 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+TESTS = test-data-pool$(EXEEXT)
+check_PROGRAMS = test-data-pool$(EXEEXT)
+subdir = src
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(include_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h \
+ $(top_builddir)/include/maxminddb_config.h
+CONFIG_CLEAN_FILES = libmaxminddb.pc
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(pkgconfigdir)" \
+ "$(DESTDIR)$(includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+libmaxminddb_la_LIBADD =
+am_libmaxminddb_la_OBJECTS = maxminddb.lo data-pool.lo
+libmaxminddb_la_OBJECTS = $(am_libmaxminddb_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libmaxminddb_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(libmaxminddb_la_LDFLAGS) $(LDFLAGS) \
+ -o $@
+am_test_data_pool_OBJECTS = test_data_pool-data-pool.$(OBJEXT)
+test_data_pool_OBJECTS = $(am_test_data_pool_OBJECTS)
+test_data_pool_DEPENDENCIES = $(top_srcdir)/t/libmmdbtest.la \
+ $(top_srcdir)/t/libtap/libtap.a
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) -I$(top_builddir)/include
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libmaxminddb_la_SOURCES) $(test_data_pool_SOURCES)
+DIST_SOURCES = $(libmaxminddb_la_SOURCES) $(test_data_pool_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+DATA = $(pkgconfig_DATA)
+HEADERS = $(include_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ fi; \
+}
+am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/libmaxminddb.pc.in \
+ $(top_srcdir)/common.mk $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgconfigdir = @pkgconfigdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+@DEBUG_FALSE@AM_CFLAGS = -O2 -g
+@DEBUG_TRUE@AM_CFLAGS = -O0 -g -Wall -Wextra
+AM_CPPFLAGS = -I$(top_srcdir)/include
+lib_LTLIBRARIES = libmaxminddb.la
+libmaxminddb_la_SOURCES = maxminddb.c maxminddb-compat-util.h \
+ data-pool.c data-pool.h
+
+libmaxminddb_la_LDFLAGS = -version-info 0:7:0 -export-symbols-regex '^MMDB_.*'
+include_HEADERS = $(top_srcdir)/include/maxminddb.h
+pkgconfig_DATA = libmaxminddb.pc
+test_data_pool_SOURCES = data-pool.c data-pool.h
+test_data_pool_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/t -DTEST_DATA_POOL
+test_data_pool_LDADD = $(top_srcdir)/t/libmmdbtest.la \
+ $(top_srcdir)/t/libtap/libtap.a
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(top_srcdir)/common.mk $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+$(top_srcdir)/common.mk $(am__empty):
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+libmaxminddb.pc: $(top_builddir)/config.status $(srcdir)/libmaxminddb.pc.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \
+ }
+
+uninstall-libLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \
+ done
+
+clean-libLTLIBRARIES:
+ -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+ @list='$(lib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libmaxminddb.la: $(libmaxminddb_la_OBJECTS) $(libmaxminddb_la_DEPENDENCIES) $(EXTRA_libmaxminddb_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libmaxminddb_la_LINK) -rpath $(libdir) $(libmaxminddb_la_OBJECTS) $(libmaxminddb_la_LIBADD) $(LIBS)
+
+clean-checkPROGRAMS:
+ @list='$(check_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+test-data-pool$(EXEEXT): $(test_data_pool_OBJECTS) $(test_data_pool_DEPENDENCIES) $(EXTRA_test_data_pool_DEPENDENCIES)
+ @rm -f test-data-pool$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_data_pool_OBJECTS) $(test_data_pool_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data-pool.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/maxminddb.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_data_pool-data-pool.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+test_data_pool-data-pool.o: data-pool.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_data_pool_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_data_pool-data-pool.o -MD -MP -MF $(DEPDIR)/test_data_pool-data-pool.Tpo -c -o test_data_pool-data-pool.o `test -f 'data-pool.c' || echo '$(srcdir)/'`data-pool.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_data_pool-data-pool.Tpo $(DEPDIR)/test_data_pool-data-pool.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='data-pool.c' object='test_data_pool-data-pool.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_data_pool_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_data_pool-data-pool.o `test -f 'data-pool.c' || echo '$(srcdir)/'`data-pool.c
+
+test_data_pool-data-pool.obj: data-pool.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_data_pool_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_data_pool-data-pool.obj -MD -MP -MF $(DEPDIR)/test_data_pool-data-pool.Tpo -c -o test_data_pool-data-pool.obj `if test -f 'data-pool.c'; then $(CYGPATH_W) 'data-pool.c'; else $(CYGPATH_W) '$(srcdir)/data-pool.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_data_pool-data-pool.Tpo $(DEPDIR)/test_data_pool-data-pool.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='data-pool.c' object='test_data_pool-data-pool.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_data_pool_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_data_pool-data-pool.obj `if test -f 'data-pool.c'; then $(CYGPATH_W) 'data-pool.c'; else $(CYGPATH_W) '$(srcdir)/data-pool.c'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkgconfigDATA: $(pkgconfig_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkgconfigdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkgconfigdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(pkgconfigdir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(pkgconfigdir)" || exit $$?; \
+ done
+
+uninstall-pkgconfigDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkgconfigdir)'; $(am__uninstall_files_from_dir)
+install-includeHEADERS: $(include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(include_HEADERS)'; test -n "$(includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(includedir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(includedir)" || exit $$?; \
+ done
+
+uninstall-includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(include_HEADERS)'; test -n "$(includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(includedir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+check-TESTS: $(TESTS)
+ @failed=0; all=0; xfail=0; xpass=0; skip=0; \
+ srcdir=$(srcdir); export srcdir; \
+ list=' $(TESTS) '; \
+ $(am__tty_colors); \
+ if test -n "$$list"; then \
+ for tst in $$list; do \
+ if test -f ./$$tst; then dir=./; \
+ elif test -f $$tst; then dir=; \
+ else dir="$(srcdir)/"; fi; \
+ if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xpass=`expr $$xpass + 1`; \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=XPASS; \
+ ;; \
+ *) \
+ col=$$grn; res=PASS; \
+ ;; \
+ esac; \
+ elif test $$? -ne 77; then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xfail=`expr $$xfail + 1`; \
+ col=$$lgn; res=XFAIL; \
+ ;; \
+ *) \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=FAIL; \
+ ;; \
+ esac; \
+ else \
+ skip=`expr $$skip + 1`; \
+ col=$$blu; res=SKIP; \
+ fi; \
+ echo "$${col}$$res$${std}: $$tst"; \
+ done; \
+ if test "$$all" -eq 1; then \
+ tests="test"; \
+ All=""; \
+ else \
+ tests="tests"; \
+ All="All "; \
+ fi; \
+ if test "$$failed" -eq 0; then \
+ if test "$$xfail" -eq 0; then \
+ banner="$$All$$all $$tests passed"; \
+ else \
+ if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \
+ banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \
+ fi; \
+ else \
+ if test "$$xpass" -eq 0; then \
+ banner="$$failed of $$all $$tests failed"; \
+ else \
+ if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \
+ banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \
+ fi; \
+ fi; \
+ dashes="$$banner"; \
+ skipped=""; \
+ if test "$$skip" -ne 0; then \
+ if test "$$skip" -eq 1; then \
+ skipped="($$skip test was not run)"; \
+ else \
+ skipped="($$skip tests were not run)"; \
+ fi; \
+ test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$skipped"; \
+ fi; \
+ report=""; \
+ if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \
+ report="Please report to $(PACKAGE_BUGREPORT)"; \
+ test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$report"; \
+ fi; \
+ dashes=`echo "$$dashes" | sed s/./=/g`; \
+ if test "$$failed" -eq 0; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ fi; \
+ echo "$${col}$$dashes$${std}"; \
+ echo "$${col}$$banner$${std}"; \
+ test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \
+ test -z "$$report" || echo "$${col}$$report$${std}"; \
+ echo "$${col}$$dashes$${std}"; \
+ test "$$failed" -eq 0; \
+ else :; fi
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) $(check_PROGRAMS)
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(DATA) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(pkgconfigdir)" "$(DESTDIR)$(includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-checkPROGRAMS clean-generic clean-libLTLIBRARIES \
+ clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-includeHEADERS install-pkgconfigDATA
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-libLTLIBRARIES
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-includeHEADERS uninstall-libLTLIBRARIES \
+ uninstall-pkgconfigDATA
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am check check-TESTS check-am clean \
+ clean-checkPROGRAMS clean-generic clean-libLTLIBRARIES \
+ clean-libtool cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-includeHEADERS install-info \
+ install-info-am install-libLTLIBRARIES install-man install-pdf \
+ install-pdf-am install-pkgconfigDATA install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-includeHEADERS uninstall-libLTLIBRARIES \
+ uninstall-pkgconfigDATA
+
+.PRECIOUS: Makefile
+
+
+$(top_srcdir)/t/libmmdbtest.la:
+ $(MAKE) -C $(top_srcdir)/t libmmdbtest.la
+
+$(top_srcdir)/t/libtap/libtap.a:
+ $(MAKE) -C $(top_srcdir)/t/libtap libtap.a
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/data-pool.c b/src/data-pool.c
new file mode 100644
index 0000000..48521b6
--- /dev/null
+++ b/src/data-pool.c
@@ -0,0 +1,180 @@
+#include "data-pool.h"
+#include "maxminddb.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+static bool can_multiply(size_t const, size_t const, size_t const);
+
+// Allocate an MMDB_data_pool_s. It initially has space for size
+// MMDB_entry_data_list_s structs.
+MMDB_data_pool_s *data_pool_new(size_t const size)
+{
+ MMDB_data_pool_s *const pool = calloc(1, sizeof(MMDB_data_pool_s));
+ if (!pool) {
+ return NULL;
+ }
+
+ if (size == 0 ||
+ !can_multiply(SIZE_MAX, size, sizeof(MMDB_entry_data_list_s))) {
+ data_pool_destroy(pool);
+ return NULL;
+ }
+ pool->size = size;
+ pool->blocks[0] = calloc(pool->size, sizeof(MMDB_entry_data_list_s));
+ if (!pool->blocks[0]) {
+ data_pool_destroy(pool);
+ return NULL;
+ }
+ pool->blocks[0]->pool = pool;
+
+ pool->sizes[0] = size;
+
+ pool->block = pool->blocks[0];
+
+ return pool;
+}
+
+// Determine if we can multiply m*n. We can do this if the result will be below
+// the given max. max will typically be SIZE_MAX.
+//
+// We want to know if we'll wrap around.
+static bool can_multiply(size_t const max, size_t const m, size_t const n)
+{
+ if (m == 0) {
+ return false;
+ }
+
+ return n <= max / m;
+}
+
+// Clean up the data pool.
+void data_pool_destroy(MMDB_data_pool_s *const pool)
+{
+ if (!pool) {
+ return;
+ }
+
+ for (size_t i = 0; i <= pool->index; i++) {
+ free(pool->blocks[i]);
+ }
+
+ free(pool);
+}
+
+// Claim a new struct from the pool. Doing this may cause the pool's size to
+// grow.
+MMDB_entry_data_list_s *data_pool_alloc(MMDB_data_pool_s *const pool)
+{
+ if (!pool) {
+ return NULL;
+ }
+
+ if (pool->used < pool->size) {
+ MMDB_entry_data_list_s *const element = pool->block + pool->used;
+ pool->used++;
+ return element;
+ }
+
+ // Take it from a new block of memory.
+
+ size_t const new_index = pool->index + 1;
+ if (new_index == DATA_POOL_NUM_BLOCKS) {
+ // See the comment about not growing this on DATA_POOL_NUM_BLOCKS.
+ return NULL;
+ }
+
+ if (!can_multiply(SIZE_MAX, pool->size, 2)) {
+ return NULL;
+ }
+ size_t const new_size = pool->size * 2;
+
+ if (!can_multiply(SIZE_MAX, new_size, sizeof(MMDB_entry_data_list_s))) {
+ return NULL;
+ }
+ pool->blocks[new_index] = calloc(new_size, sizeof(MMDB_entry_data_list_s));
+ if (!pool->blocks[new_index]) {
+ return NULL;
+ }
+
+ // We don't need to set this, but it's useful for introspection in tests.
+ pool->blocks[new_index]->pool = pool;
+
+ pool->index = new_index;
+ pool->block = pool->blocks[pool->index];
+
+ pool->size = new_size;
+ pool->sizes[pool->index] = pool->size;
+
+ MMDB_entry_data_list_s *const element = pool->block;
+ pool->used = 1;
+ return element;
+}
+
+// Turn the structs in the array-like pool into a linked list.
+//
+// Before calling this function, the list isn't linked up.
+MMDB_entry_data_list_s *data_pool_to_list(MMDB_data_pool_s *const pool)
+{
+ if (!pool) {
+ return NULL;
+ }
+
+ if (pool->index == 0 && pool->used == 0) {
+ return NULL;
+ }
+
+ for (size_t i = 0; i <= pool->index; i++) {
+ MMDB_entry_data_list_s *const block = pool->blocks[i];
+
+ size_t size = pool->sizes[i];
+ if (i == pool->index) {
+ size = pool->used;
+ }
+
+ for (size_t j = 0; j < size - 1; j++) {
+ MMDB_entry_data_list_s *const cur = block + j;
+ cur->next = block + j + 1;
+ }
+
+ if (i < pool->index) {
+ MMDB_entry_data_list_s *const last = block + size - 1;
+ last->next = pool->blocks[i + 1];
+ }
+ }
+
+ return pool->blocks[0];
+}
+
+#ifdef TEST_DATA_POOL
+
+#include <libtap/tap.h>
+#include <maxminddb_test_helper.h>
+
+static void test_can_multiply(void);
+
+int main(void)
+{
+ plan(NO_PLAN);
+ test_can_multiply();
+ done_testing();
+}
+
+static void test_can_multiply(void)
+{
+ {
+ ok(can_multiply(SIZE_MAX, 1, SIZE_MAX), "1*SIZE_MAX is ok");
+ }
+
+ {
+ ok(!can_multiply(SIZE_MAX, 2, SIZE_MAX), "2*SIZE_MAX is not ok");
+ }
+
+ {
+ ok(can_multiply(SIZE_MAX, 10240, sizeof(MMDB_entry_data_list_s)),
+ "1024 entry_data_list_s's are okay");
+ }
+}
+
+#endif
diff --git a/src/data-pool.h b/src/data-pool.h
new file mode 100644
index 0000000..25d0992
--- /dev/null
+++ b/src/data-pool.h
@@ -0,0 +1,52 @@
+#ifndef DATA_POOL_H
+#define DATA_POOL_H
+
+#include "maxminddb.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+
+// This should be large enough that we never need to grow the array of pointers
+// to blocks. 32 is enough. Even starting out of with size 1 (1 struct), the
+// 32nd element alone will provide 2**32 structs as we exponentially increase
+// the number in each block. Being confident that we do not have to grow the
+// array lets us avoid writing code to do that. That code would be risky as it
+// would rarely be hit and likely not be well tested.
+#define DATA_POOL_NUM_BLOCKS 32
+
+// A pool of memory for MMDB_entry_data_list_s structs. This is so we can
+// allocate multiple up front rather than one at a time for performance
+// reasons.
+//
+// The order you add elements to it (by calling data_pool_alloc()) ends up as
+// the order of the list.
+//
+// The memory only grows. There is no support for releasing an element you take
+// back to the pool.
+typedef struct MMDB_data_pool_s {
+ // Index of the current block we're allocating out of.
+ size_t index;
+
+ // The size of the current block, counting by structs.
+ size_t size;
+
+ // How many used in the current block, counting by structs.
+ size_t used;
+
+ // The current block we're allocating out of.
+ MMDB_entry_data_list_s *block;
+
+ // The size of each block.
+ size_t sizes[DATA_POOL_NUM_BLOCKS];
+
+ // An array of pointers to blocks of memory holding space for list
+ // elements.
+ MMDB_entry_data_list_s *blocks[DATA_POOL_NUM_BLOCKS];
+} MMDB_data_pool_s;
+
+MMDB_data_pool_s *data_pool_new(size_t const);
+void data_pool_destroy(MMDB_data_pool_s *const);
+MMDB_entry_data_list_s *data_pool_alloc(MMDB_data_pool_s *const);
+MMDB_entry_data_list_s *data_pool_to_list(MMDB_data_pool_s *const);
+
+#endif
diff --git a/src/libmaxminddb.pc.in b/src/libmaxminddb.pc.in
new file mode 100644
index 0000000..00ced3b
--- /dev/null
+++ b/src/libmaxminddb.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libmaxminddb
+Description: C library for the MaxMind DB file format
+URL: http://maxmind.github.io/libmaxminddb/
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lmaxminddb
+Cflags: -I${includedir}
diff --git a/src/maxminddb-compat-util.h b/src/maxminddb-compat-util.h
new file mode 100644
index 0000000..e3f0320
--- /dev/null
+++ b/src/maxminddb-compat-util.h
@@ -0,0 +1,167 @@
+#include <stdlib.h>
+#include <string.h>
+
+/* *INDENT-OFF* */
+
+/* The memmem, strdup, and strndup functions were all copied from the
+ * FreeBSD source, along with the relevant copyright notice.
+ *
+ * It'd be nicer to simply use the functions available on the system if they
+ * exist, but there doesn't seem to be a good way to detect them without also
+ * defining things like _GNU_SOURCE, which we want to avoid, because then we
+ * end up _accidentally_ using GNU features without noticing, which then
+ * breaks on systems like OSX.
+ *
+ * C is fun! */
+
+/* Applies to memmem implementation */
+/*-
+ * Copyright (c) 2005 Pascal Gloor <[email protected]>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+static void *
+mmdb_memmem(const void *l, size_t l_len, const void *s, size_t s_len)
+{
+ register char *cur, *last;
+ const char *cl = (const char *)l;
+ const char *cs = (const char *)s;
+
+ /* we need something to compare */
+ if (l_len == 0 || s_len == 0)
+ return NULL;
+
+ /* "s" must be smaller or equal to "l" */
+ if (l_len < s_len)
+ return NULL;
+
+ /* special case where s_len == 1 */
+ if (s_len == 1)
+ return memchr(l, (int)*cs, l_len);
+
+ /* the last position where its possible to find "s" in "l" */
+ last = (char *)cl + l_len - s_len;
+
+ for (cur = (char *)cl; cur <= last; cur++)
+ if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0)
+ return cur;
+
+ return NULL;
+}
+
+/* Applies to strnlen implementation */
+/*-
+ * Copyright (c) 2009 David Schultz <[email protected]>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+static size_t
+mmdb_strnlen(const char *s, size_t maxlen)
+{
+ size_t len;
+
+ for (len = 0; len < maxlen; len++, s++) {
+ if (!*s)
+ break;
+ }
+ return (len);
+}
+
+/* Applies to strdup and strndup implementation */
+/*
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+static char *
+mmdb_strdup(const char *str)
+{
+ size_t len;
+ char *copy;
+
+ len = strlen(str) + 1;
+ if ((copy = malloc(len)) == NULL)
+ return (NULL);
+ memcpy(copy, str, len);
+ return (copy);
+}
+
+static char *
+mmdb_strndup(const char *str, size_t n)
+{
+ size_t len;
+ char *copy;
+
+ len = mmdb_strnlen(str, n);
+ if ((copy = malloc(len + 1)) == NULL)
+ return (NULL);
+ memcpy(copy, str, len);
+ copy[len] = '\0';
+ return (copy);
+}
+/* *INDENT-ON* */
diff --git a/src/maxminddb.c b/src/maxminddb.c
new file mode 100644
index 0000000..7580e1e
--- /dev/null
+++ b/src/maxminddb.c
@@ -0,0 +1,2181 @@
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "data-pool.h"
+#include "maxminddb.h"
+#include "maxminddb-compat-util.h"
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#ifdef _WIN32
+#include <windows.h>
+#include <ws2ipdef.h>
+#else
+#include <arpa/inet.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#endif
+
+#define MMDB_DATA_SECTION_SEPARATOR (16)
+#define MAXIMUM_DATA_STRUCTURE_DEPTH (512)
+
+#ifdef MMDB_DEBUG
+#define LOCAL
+#define NO_PROTO
+#define DEBUG_FUNC
+#define DEBUG_MSG(msg) fprintf(stderr, msg "\n")
+#define DEBUG_MSGF(fmt, ...) fprintf(stderr, fmt "\n", __VA_ARGS__)
+#define DEBUG_BINARY(fmt, byte) \
+ do { \
+ char *binary = byte_to_binary(byte); \
+ if (NULL == binary) { \
+ fprintf(stderr, "Malloc failed in DEBUG_BINARY\n"); \
+ abort(); \
+ } \
+ fprintf(stderr, fmt "\n", binary); \
+ free(binary); \
+ } while (0)
+#define DEBUG_NL fprintf(stderr, "\n")
+#else
+#define LOCAL static
+#define NO_PROTO static
+#define DEBUG_MSG(...)
+#define DEBUG_MSGF(...)
+#define DEBUG_BINARY(...)
+#define DEBUG_NL
+#endif
+
+#ifdef MMDB_DEBUG
+DEBUG_FUNC char *byte_to_binary(uint8_t byte)
+{
+ char *bits = malloc(sizeof(char) * 9);
+ if (NULL == bits) {
+ return bits;
+ }
+
+ for (uint8_t i = 0; i < 8; i++) {
+ bits[i] = byte & (128 >> i) ? '1' : '0';
+ }
+ bits[8] = '\0';
+
+ return bits;
+}
+
+DEBUG_FUNC char *type_num_to_name(uint8_t num)
+{
+ switch (num) {
+ case 0:
+ return "extended";
+ case 1:
+ return "pointer";
+ case 2:
+ return "utf8_string";
+ case 3:
+ return "double";
+ case 4:
+ return "bytes";
+ case 5:
+ return "uint16";
+ case 6:
+ return "uint32";
+ case 7:
+ return "map";
+ case 8:
+ return "int32";
+ case 9:
+ return "uint64";
+ case 10:
+ return "uint128";
+ case 11:
+ return "array";
+ case 12:
+ return "container";
+ case 13:
+ return "end_marker";
+ case 14:
+ return "boolean";
+ case 15:
+ return "float";
+ default:
+ return "unknown type";
+ }
+}
+#endif
+
+/* None of the values we check on the lhs are bigger than uint32_t, so on
+ * platforms where SIZE_MAX is a 64-bit integer, this would be a no-op, and it
+ * makes the compiler complain if we do the check anyway. */
+#if SIZE_MAX == UINT32_MAX
+#define MAYBE_CHECK_SIZE_OVERFLOW(lhs, rhs, error) \
+ if ((lhs) > (rhs)) { \
+ return error; \
+ }
+#else
+#define MAYBE_CHECK_SIZE_OVERFLOW(...)
+#endif
+
+typedef struct record_info_s {
+ uint16_t record_length;
+ uint32_t (*left_record_getter)(const uint8_t *);
+ uint32_t (*right_record_getter)(const uint8_t *);
+ uint8_t right_record_offset;
+} record_info_s;
+
+#define METADATA_MARKER "\xab\xcd\xefMaxMind.com"
+/* This is 128kb */
+#define METADATA_BLOCK_MAX_SIZE 131072
+
+// 64 leads us to allocating 4 KiB on a 64bit system.
+#define MMDB_POOL_INIT_SIZE 64
+
+/* *INDENT-OFF* */
+/* --prototypes automatically generated by dev-bin/regen-prototypes.pl - don't remove this comment */
+LOCAL int map_file(MMDB_s *const mmdb);
+LOCAL const uint8_t *find_metadata(const uint8_t *file_content,
+ ssize_t file_size, uint32_t *metadata_size);
+LOCAL int read_metadata(MMDB_s *mmdb);
+LOCAL MMDB_s make_fake_metadata_db(MMDB_s *mmdb);
+LOCAL int value_for_key_as_uint16(MMDB_entry_s *start, char *key,
+ uint16_t *value);
+LOCAL int value_for_key_as_uint32(MMDB_entry_s *start, char *key,
+ uint32_t *value);
+LOCAL int value_for_key_as_uint64(MMDB_entry_s *start, char *key,
+ uint64_t *value);
+LOCAL int value_for_key_as_string(MMDB_entry_s *start, char *key,
+ char const **value);
+LOCAL int populate_languages_metadata(MMDB_s *mmdb, MMDB_s *metadata_db,
+ MMDB_entry_s *metadata_start);
+LOCAL int populate_description_metadata(MMDB_s *mmdb, MMDB_s *metadata_db,
+ MMDB_entry_s *metadata_start);
+LOCAL int resolve_any_address(const char *ipstr, struct addrinfo **addresses);
+LOCAL int find_address_in_search_tree(MMDB_s *mmdb, uint8_t *address,
+ sa_family_t address_family,
+ MMDB_lookup_result_s *result);
+LOCAL record_info_s record_info_for_database(MMDB_s *mmdb);
+LOCAL int find_ipv4_start_node(MMDB_s *mmdb);
+LOCAL uint8_t maybe_populate_result(MMDB_s *mmdb, uint32_t record,
+ uint16_t netmask,
+ MMDB_lookup_result_s *result);
+LOCAL uint8_t record_type(MMDB_s *const mmdb, uint64_t record);
+LOCAL uint32_t get_left_28_bit_record(const uint8_t *record);
+LOCAL uint32_t get_right_28_bit_record(const uint8_t *record);
+LOCAL uint32_t data_section_offset_for_record(MMDB_s *const mmdb,
+ uint64_t record);
+LOCAL int path_length(va_list va_path);
+LOCAL int lookup_path_in_array(const char *path_elem, MMDB_s *mmdb,
+ MMDB_entry_data_s *entry_data);
+LOCAL int lookup_path_in_map(const char *path_elem, MMDB_s *mmdb,
+ MMDB_entry_data_s *entry_data);
+LOCAL int skip_map_or_array(MMDB_s *mmdb, MMDB_entry_data_s *entry_data);
+LOCAL int decode_one_follow(MMDB_s *mmdb, uint32_t offset,
+ MMDB_entry_data_s *entry_data);
+LOCAL int decode_one(MMDB_s *mmdb, uint32_t offset,
+ MMDB_entry_data_s *entry_data);
+LOCAL int get_ext_type(int raw_ext_type);
+LOCAL uint32_t get_ptr_from(uint8_t ctrl, uint8_t const *const ptr,
+ int ptr_size);
+LOCAL int get_entry_data_list(MMDB_s *mmdb,
+ uint32_t offset,
+ MMDB_entry_data_list_s *const entry_data_list,
+ MMDB_data_pool_s *const pool,
+ int depth);
+LOCAL float get_ieee754_float(const uint8_t *restrict p);
+LOCAL double get_ieee754_double(const uint8_t *restrict p);
+LOCAL uint32_t get_uint32(const uint8_t *p);
+LOCAL uint32_t get_uint24(const uint8_t *p);
+LOCAL uint32_t get_uint16(const uint8_t *p);
+LOCAL uint64_t get_uintX(const uint8_t *p, int length);
+LOCAL int32_t get_sintX(const uint8_t *p, int length);
+LOCAL void free_mmdb_struct(MMDB_s *const mmdb);
+LOCAL void free_languages_metadata(MMDB_s *mmdb);
+LOCAL void free_descriptions_metadata(MMDB_s *mmdb);
+LOCAL MMDB_entry_data_list_s *dump_entry_data_list(
+ FILE *stream, MMDB_entry_data_list_s *entry_data_list, int indent,
+ int *status);
+LOCAL void print_indentation(FILE *stream, int i);
+LOCAL char *bytes_to_hex(uint8_t *bytes, uint32_t size);
+/* --prototypes end - don't remove this comment-- */
+/* *INDENT-ON* */
+
+#define CHECKED_DECODE_ONE(mmdb, offset, entry_data) \
+ do { \
+ int status = decode_one(mmdb, offset, entry_data); \
+ if (MMDB_SUCCESS != status) { \
+ DEBUG_MSGF("CHECKED_DECODE_ONE failed." \
+ " status = %d (%s)", status, MMDB_strerror(status)); \
+ return status; \
+ } \
+ } while (0)
+
+#define CHECKED_DECODE_ONE_FOLLOW(mmdb, offset, entry_data) \
+ do { \
+ int status = decode_one_follow(mmdb, offset, entry_data); \
+ if (MMDB_SUCCESS != status) { \
+ DEBUG_MSGF("CHECKED_DECODE_ONE_FOLLOW failed." \
+ " status = %d (%s)", status, MMDB_strerror(status)); \
+ return status; \
+ } \
+ } while (0)
+
+#define FREE_AND_SET_NULL(p) { free((void *)(p)); (p) = NULL; }
+
+int MMDB_open(const char *const filename, uint32_t flags, MMDB_s *const mmdb)
+{
+ int status = MMDB_SUCCESS;
+
+ mmdb->file_content = NULL;
+ mmdb->data_section = NULL;
+ mmdb->metadata.database_type = NULL;
+ mmdb->metadata.languages.count = 0;
+ mmdb->metadata.description.count = 0;
+
+ mmdb->filename = mmdb_strdup(filename);
+ if (NULL == mmdb->filename) {
+ status = MMDB_OUT_OF_MEMORY_ERROR;
+ goto cleanup;
+ }
+
+ if ((flags & MMDB_MODE_MASK) == 0) {
+ flags |= MMDB_MODE_MMAP;
+ }
+ mmdb->flags = flags;
+
+ if (MMDB_SUCCESS != (status = map_file(mmdb))) {
+ goto cleanup;
+ }
+
+#ifdef _WIN32
+ WSADATA wsa;
+ WSAStartup(MAKEWORD(2, 2), &wsa);
+#endif
+
+ uint32_t metadata_size = 0;
+ const uint8_t *metadata = find_metadata(mmdb->file_content, mmdb->file_size,
+ &metadata_size);
+ if (NULL == metadata) {
+ status = MMDB_INVALID_METADATA_ERROR;
+ goto cleanup;
+ }
+
+ mmdb->metadata_section = metadata;
+ mmdb->metadata_section_size = metadata_size;
+
+ status = read_metadata(mmdb);
+ if (MMDB_SUCCESS != status) {
+ goto cleanup;
+ }
+
+ if (mmdb->metadata.binary_format_major_version != 2) {
+ status = MMDB_UNKNOWN_DATABASE_FORMAT_ERROR;
+ goto cleanup;
+ }
+
+ uint32_t search_tree_size = mmdb->metadata.node_count *
+ mmdb->full_record_byte_size;
+
+ mmdb->data_section = mmdb->file_content + search_tree_size
+ + MMDB_DATA_SECTION_SEPARATOR;
+ if (search_tree_size + MMDB_DATA_SECTION_SEPARATOR >
+ (uint32_t)mmdb->file_size) {
+ status = MMDB_INVALID_METADATA_ERROR;
+ goto cleanup;
+ }
+ mmdb->data_section_size = (uint32_t)mmdb->file_size - search_tree_size -
+ MMDB_DATA_SECTION_SEPARATOR;
+
+ // Although it is likely not possible to construct a database with valid
+ // valid metadata, as parsed above, and a data_section_size less than 3,
+ // we do this check as later we assume it is at least three when doing
+ // bound checks.
+ if (mmdb->data_section_size < 3) {
+ status = MMDB_INVALID_DATA_ERROR;
+ goto cleanup;
+ }
+
+ mmdb->metadata_section = metadata;
+ mmdb->ipv4_start_node.node_value = 0;
+ mmdb->ipv4_start_node.netmask = 0;
+
+ // We do this immediately as otherwise there is a race to set
+ // ipv4_start_node.node_value and ipv4_start_node.netmask.
+ if (mmdb->metadata.ip_version == 6) {
+ status = find_ipv4_start_node(mmdb);
+ if (status != MMDB_SUCCESS) {
+ goto cleanup;
+ }
+ }
+
+ cleanup:
+ if (MMDB_SUCCESS != status) {
+ int saved_errno = errno;
+ free_mmdb_struct(mmdb);
+ errno = saved_errno;
+ }
+ return status;
+}
+
+#ifdef _WIN32
+
+LOCAL int map_file(MMDB_s *const mmdb)
+{
+ DWORD size;
+ int status = MMDB_SUCCESS;
+ HANDLE mmh = NULL;
+ HANDLE fd = CreateFileA(mmdb->filename, GENERIC_READ, FILE_SHARE_READ, NULL,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (fd == INVALID_HANDLE_VALUE) {
+ status = MMDB_FILE_OPEN_ERROR;
+ goto cleanup;
+ }
+ size = GetFileSize(fd, NULL);
+ if (size == INVALID_FILE_SIZE) {
+ status = MMDB_FILE_OPEN_ERROR;
+ goto cleanup;
+ }
+ mmh = CreateFileMappingA(fd, NULL, PAGE_READONLY, 0, size, NULL);
+ /* Microsoft documentation for CreateFileMapping indicates this returns
+ NULL not INVALID_HANDLE_VALUE on error */
+ if (NULL == mmh) {
+ status = MMDB_IO_ERROR;
+ goto cleanup;
+ }
+ uint8_t *file_content =
+ (uint8_t *)MapViewOfFile(mmh, FILE_MAP_READ, 0, 0, 0);
+ if (file_content == NULL) {
+ status = MMDB_IO_ERROR;
+ goto cleanup;
+ }
+
+ mmdb->file_size = size;
+ mmdb->file_content = file_content;
+
+ cleanup:;
+ int saved_errno = errno;
+ if (INVALID_HANDLE_VALUE != fd) {
+ CloseHandle(fd);
+ }
+ if (NULL != mmh) {
+ CloseHandle(mmh);
+ }
+ errno = saved_errno;
+
+ return status;
+}
+
+#else
+
+LOCAL int map_file(MMDB_s *const mmdb)
+{
+ ssize_t size;
+ int status = MMDB_SUCCESS;
+
+ int flags = O_RDONLY;
+#ifdef O_CLOEXEC
+ flags |= O_CLOEXEC;
+#endif
+ int fd = open(mmdb->filename, flags);
+ struct stat s;
+ if (fd < 0 || fstat(fd, &s)) {
+ status = MMDB_FILE_OPEN_ERROR;
+ goto cleanup;
+ }
+
+ size = s.st_size;
+ if (size < 0 || size != s.st_size) {
+ status = MMDB_OUT_OF_MEMORY_ERROR;
+ goto cleanup;
+ }
+
+ uint8_t *file_content =
+ (uint8_t *)mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
+ if (MAP_FAILED == file_content) {
+ if (ENOMEM == errno) {
+ status = MMDB_OUT_OF_MEMORY_ERROR;
+ } else {
+ status = MMDB_IO_ERROR;
+ }
+ goto cleanup;
+ }
+
+ mmdb->file_size = size;
+ mmdb->file_content = file_content;
+
+ cleanup:;
+ int saved_errno = errno;
+ if (fd >= 0) {
+ close(fd);
+ }
+ errno = saved_errno;
+
+ return status;
+}
+
+#endif
+
+LOCAL const uint8_t *find_metadata(const uint8_t *file_content,
+ ssize_t file_size, uint32_t *metadata_size)
+{
+ const ssize_t marker_len = sizeof(METADATA_MARKER) - 1;
+ ssize_t max_size = file_size >
+ METADATA_BLOCK_MAX_SIZE ? METADATA_BLOCK_MAX_SIZE :
+ file_size;
+
+ uint8_t *search_area = (uint8_t *)(file_content + (file_size - max_size));
+ uint8_t *start = search_area;
+ uint8_t *tmp;
+ do {
+ tmp = mmdb_memmem(search_area, max_size,
+ METADATA_MARKER, marker_len);
+
+ if (NULL != tmp) {
+ max_size -= tmp - search_area;
+ search_area = tmp;
+
+ /* Continue searching just after the marker we just read, in case
+ * there are multiple markers in the same file. This would be odd
+ * but is certainly not impossible. */
+ max_size -= marker_len;
+ search_area += marker_len;
+ }
+ } while (NULL != tmp);
+
+ if (search_area == start) {
+ return NULL;
+ }
+
+ *metadata_size = (uint32_t)max_size;
+
+ return search_area;
+}
+
+LOCAL int read_metadata(MMDB_s *mmdb)
+{
+ /* We need to create a fake MMDB_s struct in order to decode values from
+ the metadata. The metadata is basically just like the data section, so we
+ want to use the same functions we use for the data section to get metadata
+ values. */
+ MMDB_s metadata_db = make_fake_metadata_db(mmdb);
+
+ MMDB_entry_s metadata_start = {
+ .mmdb = &metadata_db,
+ .offset = 0
+ };
+
+ int status =
+ value_for_key_as_uint32(&metadata_start, "node_count",
+ &mmdb->metadata.node_count);
+ if (MMDB_SUCCESS != status) {
+ return status;
+ }
+ if (!mmdb->metadata.node_count) {
+ DEBUG_MSG("could not find node_count value in metadata");
+ return MMDB_INVALID_METADATA_ERROR;
+ }
+
+ status = value_for_key_as_uint16(&metadata_start, "record_size",
+ &mmdb->metadata.record_size);
+ if (MMDB_SUCCESS != status) {
+ return status;
+ }
+ if (!mmdb->metadata.record_size) {
+ DEBUG_MSG("could not find record_size value in metadata");
+ return MMDB_INVALID_METADATA_ERROR;
+ }
+
+ if (mmdb->metadata.record_size != 24 && mmdb->metadata.record_size != 28
+ && mmdb->metadata.record_size != 32) {
+ DEBUG_MSGF("bad record size in metadata: %i",
+ mmdb->metadata.record_size);
+ return MMDB_UNKNOWN_DATABASE_FORMAT_ERROR;
+ }
+
+ status = value_for_key_as_uint16(&metadata_start, "ip_version",
+ &mmdb->metadata.ip_version);
+ if (MMDB_SUCCESS != status) {
+ return status;
+ }
+ if (!mmdb->metadata.ip_version) {
+ DEBUG_MSG("could not find ip_version value in metadata");
+ return MMDB_INVALID_METADATA_ERROR;
+ }
+ if (!(mmdb->metadata.ip_version == 4 || mmdb->metadata.ip_version == 6)) {
+ DEBUG_MSGF("ip_version value in metadata is not 4 or 6 - it was %i",
+ mmdb->metadata.ip_version);
+ return MMDB_INVALID_METADATA_ERROR;
+ }
+
+ status = value_for_key_as_string(&metadata_start, "database_type",
+ &mmdb->metadata.database_type);
+ if (MMDB_SUCCESS != status) {
+ DEBUG_MSG("error finding database_type value in metadata");
+ return status;
+ }
+
+ status =
+ populate_languages_metadata(mmdb, &metadata_db, &metadata_start);
+ if (MMDB_SUCCESS != status) {
+ DEBUG_MSG("could not populate languages from metadata");
+ return status;
+ }
+
+ status = value_for_key_as_uint16(
+ &metadata_start, "binary_format_major_version",
+ &mmdb->metadata.binary_format_major_version);
+ if (MMDB_SUCCESS != status) {
+ return status;
+ }
+ if (!mmdb->metadata.binary_format_major_version) {
+ DEBUG_MSG(
+ "could not find binary_format_major_version value in metadata");
+ return MMDB_INVALID_METADATA_ERROR;
+ }
+
+ status = value_for_key_as_uint16(
+ &metadata_start, "binary_format_minor_version",
+ &mmdb->metadata.binary_format_minor_version);
+ if (MMDB_SUCCESS != status) {
+ return status;
+ }
+
+ status = value_for_key_as_uint64(&metadata_start, "build_epoch",
+ &mmdb->metadata.build_epoch);
+ if (MMDB_SUCCESS != status) {
+ return status;
+ }
+ if (!mmdb->metadata.build_epoch) {
+ DEBUG_MSG("could not find build_epoch value in metadata");
+ return MMDB_INVALID_METADATA_ERROR;
+ }
+
+ status = populate_description_metadata(mmdb, &metadata_db, &metadata_start);
+ if (MMDB_SUCCESS != status) {
+ DEBUG_MSG("could not populate description from metadata");
+ return status;
+ }
+
+ mmdb->full_record_byte_size = mmdb->metadata.record_size * 2 / 8U;
+
+ mmdb->depth = mmdb->metadata.ip_version == 4 ? 32 : 128;
+
+ return MMDB_SUCCESS;
+}
+
+LOCAL MMDB_s make_fake_metadata_db(MMDB_s *mmdb)
+{
+ MMDB_s fake_metadata_db = {
+ .data_section = mmdb->metadata_section,
+ .data_section_size = mmdb->metadata_section_size
+ };
+
+ return fake_metadata_db;
+}
+
+LOCAL int value_for_key_as_uint16(MMDB_entry_s *start, char *key,
+ uint16_t *value)
+{
+ MMDB_entry_data_s entry_data;
+ const char *path[] = { key, NULL };
+ int status = MMDB_aget_value(start, &entry_data, path);
+ if (MMDB_SUCCESS != status) {
+ return status;
+ }
+ if (MMDB_DATA_TYPE_UINT16 != entry_data.type) {
+ DEBUG_MSGF("expect uint16 for %s but received %s", key,
+ type_num_to_name(
+ entry_data.type));
+ return MMDB_INVALID_METADATA_ERROR;
+ }
+ *value = entry_data.uint16;
+ return MMDB_SUCCESS;
+}
+
+LOCAL int value_for_key_as_uint32(MMDB_entry_s *start, char *key,
+ uint32_t *value)
+{
+ MMDB_entry_data_s entry_data;
+ const char *path[] = { key, NULL };
+ int status = MMDB_aget_value(start, &entry_data, path);
+ if (MMDB_SUCCESS != status) {
+ return status;
+ }
+ if (MMDB_DATA_TYPE_UINT32 != entry_data.type) {
+ DEBUG_MSGF("expect uint32 for %s but received %s", key,
+ type_num_to_name(
+ entry_data.type));
+ return MMDB_INVALID_METADATA_ERROR;
+ }
+ *value = entry_data.uint32;
+ return MMDB_SUCCESS;
+}
+
+LOCAL int value_for_key_as_uint64(MMDB_entry_s *start, char *key,
+ uint64_t *value)
+{
+ MMDB_entry_data_s entry_data;
+ const char *path[] = { key, NULL };
+ int status = MMDB_aget_value(start, &entry_data, path);
+ if (MMDB_SUCCESS != status) {
+ return status;
+ }
+ if (MMDB_DATA_TYPE_UINT64 != entry_data.type) {
+ DEBUG_MSGF("expect uint64 for %s but received %s", key,
+ type_num_to_name(
+ entry_data.type));
+ return MMDB_INVALID_METADATA_ERROR;
+ }
+ *value = entry_data.uint64;
+ return MMDB_SUCCESS;
+}
+
+LOCAL int value_for_key_as_string(MMDB_entry_s *start, char *key,
+ char const **value)
+{
+ MMDB_entry_data_s entry_data;
+ const char *path[] = { key, NULL };
+ int status = MMDB_aget_value(start, &entry_data, path);
+ if (MMDB_SUCCESS != status) {
+ return status;
+ }
+ if (MMDB_DATA_TYPE_UTF8_STRING != entry_data.type) {
+ DEBUG_MSGF("expect string for %s but received %s", key,
+ type_num_to_name(
+ entry_data.type));
+ return MMDB_INVALID_METADATA_ERROR;
+ }
+ *value = mmdb_strndup((char *)entry_data.utf8_string, entry_data.data_size);
+ if (NULL == *value) {
+ return MMDB_OUT_OF_MEMORY_ERROR;
+ }
+ return MMDB_SUCCESS;
+}
+
+LOCAL int populate_languages_metadata(MMDB_s *mmdb, MMDB_s *metadata_db,
+ MMDB_entry_s *metadata_start)
+{
+ MMDB_entry_data_s entry_data;
+
+ const char *path[] = { "languages", NULL };
+ int status = MMDB_aget_value(metadata_start, &entry_data, path);
+ if (MMDB_SUCCESS != status) {
+ return status;
+ }
+ if (MMDB_DATA_TYPE_ARRAY != entry_data.type) {
+ return MMDB_INVALID_METADATA_ERROR;
+ }
+
+ MMDB_entry_s array_start = {
+ .mmdb = metadata_db,
+ .offset = entry_data.offset
+ };
+
+ MMDB_entry_data_list_s *member;
+ status = MMDB_get_entry_data_list(&array_start, &member);
+ if (MMDB_SUCCESS != status) {
+ return status;
+ }
+
+ MMDB_entry_data_list_s *first_member = member;
+
+ uint32_t array_size = member->entry_data.data_size;
+ MAYBE_CHECK_SIZE_OVERFLOW(array_size, SIZE_MAX / sizeof(char *),
+ MMDB_INVALID_METADATA_ERROR);
+
+ mmdb->metadata.languages.count = 0;
+ mmdb->metadata.languages.names = malloc(array_size * sizeof(char *));
+ if (NULL == mmdb->metadata.languages.names) {
+ return MMDB_OUT_OF_MEMORY_ERROR;
+ }
+
+ for (uint32_t i = 0; i < array_size; i++) {
+ member = member->next;
+ if (MMDB_DATA_TYPE_UTF8_STRING != member->entry_data.type) {
+ return MMDB_INVALID_METADATA_ERROR;
+ }
+
+ mmdb->metadata.languages.names[i] =
+ mmdb_strndup((char *)member->entry_data.utf8_string,
+ member->entry_data.data_size);
+
+ if (NULL == mmdb->metadata.languages.names[i]) {
+ return MMDB_OUT_OF_MEMORY_ERROR;
+ }
+ // We assign this as we go so that if we fail a malloc and need to
+ // free it, the count is right.
+ mmdb->metadata.languages.count = i + 1;
+ }
+
+ MMDB_free_entry_data_list(first_member);
+
+ return MMDB_SUCCESS;
+}
+
+LOCAL int populate_description_metadata(MMDB_s *mmdb, MMDB_s *metadata_db,
+ MMDB_entry_s *metadata_start)
+{
+ MMDB_entry_data_s entry_data;
+
+ const char *path[] = { "description", NULL };
+ int status = MMDB_aget_value(metadata_start, &entry_data, path);
+ if (MMDB_SUCCESS != status) {
+ return status;
+ }
+
+ if (MMDB_DATA_TYPE_MAP != entry_data.type) {
+ DEBUG_MSGF("Unexpected entry_data type: %d", entry_data.type);
+ return MMDB_INVALID_METADATA_ERROR;
+ }
+
+ MMDB_entry_s map_start = {
+ .mmdb = metadata_db,
+ .offset = entry_data.offset
+ };
+
+ MMDB_entry_data_list_s *member;
+ status = MMDB_get_entry_data_list(&map_start, &member);
+ if (MMDB_SUCCESS != status) {
+ DEBUG_MSGF(
+ "MMDB_get_entry_data_list failed while populating description."
+ " status = %d (%s)", status, MMDB_strerror(status));
+ return status;
+ }
+
+ MMDB_entry_data_list_s *first_member = member;
+
+ uint32_t map_size = member->entry_data.data_size;
+ mmdb->metadata.description.count = 0;
+ if (0 == map_size) {
+ mmdb->metadata.description.descriptions = NULL;
+ goto cleanup;
+ }
+ MAYBE_CHECK_SIZE_OVERFLOW(map_size, SIZE_MAX / sizeof(MMDB_description_s *),
+ MMDB_INVALID_METADATA_ERROR);
+
+ mmdb->metadata.description.descriptions =
+ malloc(map_size * sizeof(MMDB_description_s *));
+ if (NULL == mmdb->metadata.description.descriptions) {
+ status = MMDB_OUT_OF_MEMORY_ERROR;
+ goto cleanup;
+ }
+
+ for (uint32_t i = 0; i < map_size; i++) {
+ mmdb->metadata.description.descriptions[i] =
+ malloc(sizeof(MMDB_description_s));
+ if (NULL == mmdb->metadata.description.descriptions[i]) {
+ status = MMDB_OUT_OF_MEMORY_ERROR;
+ goto cleanup;
+ }
+
+ mmdb->metadata.description.count = i + 1;
+ mmdb->metadata.description.descriptions[i]->language = NULL;
+ mmdb->metadata.description.descriptions[i]->description = NULL;
+
+ member = member->next;
+
+ if (MMDB_DATA_TYPE_UTF8_STRING != member->entry_data.type) {
+ status = MMDB_INVALID_METADATA_ERROR;
+ goto cleanup;
+ }
+
+ mmdb->metadata.description.descriptions[i]->language =
+ mmdb_strndup((char *)member->entry_data.utf8_string,
+ member->entry_data.data_size);
+
+ if (NULL == mmdb->metadata.description.descriptions[i]->language) {
+ status = MMDB_OUT_OF_MEMORY_ERROR;
+ goto cleanup;
+ }
+
+ member = member->next;
+
+ if (MMDB_DATA_TYPE_UTF8_STRING != member->entry_data.type) {
+ status = MMDB_INVALID_METADATA_ERROR;
+ goto cleanup;
+ }
+
+ mmdb->metadata.description.descriptions[i]->description =
+ mmdb_strndup((char *)member->entry_data.utf8_string,
+ member->entry_data.data_size);
+
+ if (NULL == mmdb->metadata.description.descriptions[i]->description) {
+ status = MMDB_OUT_OF_MEMORY_ERROR;
+ goto cleanup;
+ }
+ }
+
+ cleanup:
+ MMDB_free_entry_data_list(first_member);
+
+ return status;
+}
+
+MMDB_lookup_result_s MMDB_lookup_string(MMDB_s *const mmdb,
+ const char *const ipstr,
+ int *const gai_error,
+ int *const mmdb_error)
+{
+ MMDB_lookup_result_s result = {
+ .found_entry = false,
+ .netmask = 0,
+ .entry = {
+ .mmdb = mmdb,
+ .offset = 0
+ }
+ };
+
+ struct addrinfo *addresses = NULL;
+ *gai_error = resolve_any_address(ipstr, &addresses);
+
+ if (!*gai_error) {
+ result = MMDB_lookup_sockaddr(mmdb, addresses->ai_addr, mmdb_error);
+ }
+
+ if (NULL != addresses) {
+ freeaddrinfo(addresses);
+ }
+
+ return result;
+}
+
+LOCAL int resolve_any_address(const char *ipstr, struct addrinfo **addresses)
+{
+ struct addrinfo hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_flags = AI_NUMERICHOST,
+ // We set ai_socktype so that we only get one result back
+ .ai_socktype = SOCK_STREAM
+ };
+
+ int gai_status = getaddrinfo(ipstr, NULL, &hints, addresses);
+ if (gai_status) {
+ return gai_status;
+ }
+
+ return 0;
+}
+
+MMDB_lookup_result_s MMDB_lookup_sockaddr(
+ MMDB_s *const mmdb,
+ const struct sockaddr *const sockaddr,
+ int *const mmdb_error)
+{
+ MMDB_lookup_result_s result = {
+ .found_entry = false,
+ .netmask = 0,
+ .entry = {
+ .mmdb = mmdb,
+ .offset = 0
+ }
+ };
+
+ uint8_t mapped_address[16], *address;
+ if (mmdb->metadata.ip_version == 4) {
+ if (sockaddr->sa_family == AF_INET6) {
+ *mmdb_error = MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR;
+ return result;
+ }
+ address = (uint8_t *)&((struct sockaddr_in *)sockaddr)->sin_addr.s_addr;
+ } else {
+ if (sockaddr->sa_family == AF_INET6) {
+ address =
+ (uint8_t *)&((struct sockaddr_in6 *)sockaddr)->sin6_addr.
+ s6_addr;
+ } else {
+ address = mapped_address;
+ memset(address, 0, 12);
+ memcpy(address + 12,
+ &((struct sockaddr_in *)sockaddr)->sin_addr.s_addr, 4);
+ }
+ }
+
+ *mmdb_error =
+ find_address_in_search_tree(mmdb, address, sockaddr->sa_family,
+ &result);
+
+ return result;
+}
+
+LOCAL int find_address_in_search_tree(MMDB_s *mmdb, uint8_t *address,
+ sa_family_t address_family,
+ MMDB_lookup_result_s *result)
+{
+ record_info_s record_info = record_info_for_database(mmdb);
+ if (0 == record_info.right_record_offset) {
+ return MMDB_UNKNOWN_DATABASE_FORMAT_ERROR;
+ }
+
+ DEBUG_NL;
+ DEBUG_MSG("Looking for address in search tree");
+
+ uint32_t value = 0;
+ uint16_t max_depth0 = mmdb->depth - 1;
+ uint16_t start_bit = max_depth0;
+
+ if (mmdb->metadata.ip_version == 6 && address_family == AF_INET) {
+ int mmdb_error = find_ipv4_start_node(mmdb);
+ if (MMDB_SUCCESS != mmdb_error) {
+ return mmdb_error;
+ }
+ DEBUG_MSGF("IPv4 start node is %u (netmask %u)",
+ mmdb->ipv4_start_node.node_value,
+ mmdb->ipv4_start_node.netmask);
+
+ uint8_t type = maybe_populate_result(mmdb,
+ mmdb->ipv4_start_node.node_value,
+ mmdb->ipv4_start_node.netmask,
+ result);
+ if (MMDB_RECORD_TYPE_INVALID == type) {
+ return MMDB_CORRUPT_SEARCH_TREE_ERROR;
+ }
+
+ /* We have an IPv6 database with no IPv4 data */
+ if (MMDB_RECORD_TYPE_SEARCH_NODE != type) {
+ return MMDB_SUCCESS;
+ }
+
+ value = mmdb->ipv4_start_node.node_value;
+ start_bit -= mmdb->ipv4_start_node.netmask;
+ }
+
+ const uint8_t *search_tree = mmdb->file_content;
+ const uint8_t *record_pointer;
+ for (int current_bit = start_bit; current_bit >= 0; current_bit--) {
+ uint8_t bit_is_true =
+ address[(max_depth0 - current_bit) >> 3]
+ & (1U << (~(max_depth0 - current_bit) & 7)) ? 1 : 0;
+
+ DEBUG_MSGF("Looking at bit %i - bit's value is %i", current_bit,
+ bit_is_true);
+ DEBUG_MSGF(" current node = %u", value);
+
+ record_pointer = &search_tree[value * record_info.record_length];
+ if (record_pointer + record_info.record_length > mmdb->data_section) {
+ return MMDB_CORRUPT_SEARCH_TREE_ERROR;
+ }
+ if (bit_is_true) {
+ record_pointer += record_info.right_record_offset;
+ value = record_info.right_record_getter(record_pointer);
+ } else {
+ value = record_info.left_record_getter(record_pointer);
+ }
+
+ uint8_t type = maybe_populate_result(mmdb, value, (uint16_t)current_bit,
+ result);
+ if (MMDB_RECORD_TYPE_INVALID == type) {
+ return MMDB_CORRUPT_SEARCH_TREE_ERROR;
+ }
+
+ if (MMDB_RECORD_TYPE_SEARCH_NODE != type) {
+ return MMDB_SUCCESS;
+ }
+
+ DEBUG_MSGF(" proceeding to search tree node %i", value);
+ }
+
+ DEBUG_MSG(
+ "Reached the end of the address bits without leaving the search tree");
+
+ // We should not be able to reach this return. If we do, something very bad happened.
+ return MMDB_CORRUPT_SEARCH_TREE_ERROR;
+}
+
+LOCAL record_info_s record_info_for_database(MMDB_s *mmdb)
+{
+ record_info_s record_info = {
+ .record_length = mmdb->full_record_byte_size,
+ .right_record_offset = 0
+ };
+
+ if (record_info.record_length == 6) {
+ record_info.left_record_getter = &get_uint24;
+ record_info.right_record_getter = &get_uint24;
+ record_info.right_record_offset = 3;
+ } else if (record_info.record_length == 7) {
+ record_info.left_record_getter = &get_left_28_bit_record;
+ record_info.right_record_getter = &get_right_28_bit_record;
+ record_info.right_record_offset = 3;
+ } else if (record_info.record_length == 8) {
+ record_info.left_record_getter = &get_uint32;
+ record_info.right_record_getter = &get_uint32;
+ record_info.right_record_offset = 4;
+ } else {
+ assert(false);
+ }
+
+ return record_info;
+}
+
+LOCAL int find_ipv4_start_node(MMDB_s *mmdb)
+{
+ /* In a pathological case of a database with a single node search tree,
+ * this check will be true even after we've found the IPv4 start node, but
+ * that doesn't seem worth trying to fix. */
+ if (mmdb->ipv4_start_node.node_value != 0) {
+ return MMDB_SUCCESS;
+ }
+
+ record_info_s record_info = record_info_for_database(mmdb);
+
+ const uint8_t *search_tree = mmdb->file_content;
+ uint32_t node_value = 0;
+ const uint8_t *record_pointer;
+ uint16_t netmask;
+ for (netmask = 0; netmask < 96; netmask++) {
+ record_pointer = &search_tree[node_value * record_info.record_length];
+ if (record_pointer + record_info.record_length > mmdb->data_section) {
+ return MMDB_CORRUPT_SEARCH_TREE_ERROR;
+ }
+ node_value = record_info.left_record_getter(record_pointer);
+ /* This can happen if there's no IPv4 data _or_ if there is a subnet
+ * with data that contains the entire IPv4 range (like ::/64) */
+ if (node_value >= mmdb->metadata.node_count) {
+ break;
+ }
+ }
+
+ mmdb->ipv4_start_node.node_value = node_value;
+ mmdb->ipv4_start_node.netmask = netmask;
+
+ return MMDB_SUCCESS;
+}
+
+LOCAL uint8_t maybe_populate_result(MMDB_s *mmdb, uint32_t record,
+ uint16_t netmask,
+ MMDB_lookup_result_s *result)
+{
+ uint8_t type = record_type(mmdb, record);
+
+ if (MMDB_RECORD_TYPE_SEARCH_NODE == type ||
+ MMDB_RECORD_TYPE_INVALID == type) {
+ return type;
+ }
+
+ result->netmask = mmdb->depth - netmask;
+
+ result->entry.offset = data_section_offset_for_record(mmdb, record);
+
+ // type is either MMDB_RECORD_TYPE_DATA or MMDB_RECORD_TYPE_EMPTY
+ // at this point
+ result->found_entry = MMDB_RECORD_TYPE_DATA == type;
+
+ return type;
+}
+
+LOCAL uint8_t record_type(MMDB_s *const mmdb, uint64_t record)
+{
+ uint32_t node_count = mmdb->metadata.node_count;
+
+ /* Ideally we'd check to make sure that a record never points to a
+ * previously seen value, but that's more complicated. For now, we can
+ * at least check that we don't end up at the top of the tree again. */
+ if (record == 0) {
+ DEBUG_MSG("record has a value of 0");
+ return MMDB_RECORD_TYPE_INVALID;
+ }
+
+ if (record < node_count) {
+ return MMDB_RECORD_TYPE_SEARCH_NODE;
+ }
+
+ if (record == node_count) {
+ return MMDB_RECORD_TYPE_EMPTY;
+ }
+
+ if (record - node_count < mmdb->data_section_size) {
+ return MMDB_RECORD_TYPE_DATA;
+ }
+
+ DEBUG_MSG("record has a value that points outside of the database");
+ return MMDB_RECORD_TYPE_INVALID;
+}
+
+LOCAL uint32_t get_left_28_bit_record(const uint8_t *record)
+{
+ return record[0] * 65536 + record[1] * 256 + record[2] +
+ ((record[3] & 0xf0) << 20);
+}
+
+LOCAL uint32_t get_right_28_bit_record(const uint8_t *record)
+{
+ uint32_t value = get_uint32(record);
+ return value & 0xfffffff;
+}
+
+int MMDB_read_node(MMDB_s *const mmdb, uint32_t node_number,
+ MMDB_search_node_s *const node)
+{
+ record_info_s record_info = record_info_for_database(mmdb);
+ if (0 == record_info.right_record_offset) {
+ return MMDB_UNKNOWN_DATABASE_FORMAT_ERROR;
+ }
+
+ if (node_number > mmdb->metadata.node_count) {
+ return MMDB_INVALID_NODE_NUMBER_ERROR;
+ }
+
+ const uint8_t *search_tree = mmdb->file_content;
+ const uint8_t *record_pointer =
+ &search_tree[node_number * record_info.record_length];
+ node->left_record = record_info.left_record_getter(record_pointer);
+ record_pointer += record_info.right_record_offset;
+ node->right_record = record_info.right_record_getter(record_pointer);
+
+ node->left_record_type = record_type(mmdb, node->left_record);
+ node->right_record_type = record_type(mmdb, node->right_record);
+
+ // Note that offset will be invalid if the record type is not
+ // MMDB_RECORD_TYPE_DATA, but that's ok. Any use of the record entry
+ // for other data types is a programming error.
+ node->left_record_entry = (struct MMDB_entry_s) {
+ .mmdb = mmdb,
+ .offset = data_section_offset_for_record(mmdb, node->left_record),
+ };
+ node->right_record_entry = (struct MMDB_entry_s) {
+ .mmdb = mmdb,
+ .offset = data_section_offset_for_record(mmdb, node->right_record),
+ };
+
+ return MMDB_SUCCESS;
+}
+
+LOCAL uint32_t data_section_offset_for_record(MMDB_s *const mmdb,
+ uint64_t record)
+{
+ return (uint32_t)record - mmdb->metadata.node_count -
+ MMDB_DATA_SECTION_SEPARATOR;
+}
+
+int MMDB_get_value(MMDB_entry_s *const start,
+ MMDB_entry_data_s *const entry_data,
+ ...)
+{
+ va_list path;
+ va_start(path, entry_data);
+ int status = MMDB_vget_value(start, entry_data, path);
+ va_end(path);
+ return status;
+}
+
+int MMDB_vget_value(MMDB_entry_s *const start,
+ MMDB_entry_data_s *const entry_data,
+ va_list va_path)
+{
+ int length = path_length(va_path);
+ const char *path_elem;
+ int i = 0;
+
+ MAYBE_CHECK_SIZE_OVERFLOW(length, SIZE_MAX / sizeof(const char *) - 1,
+ MMDB_INVALID_METADATA_ERROR);
+
+ const char **path = malloc((length + 1) * sizeof(const char *));
+ if (NULL == path) {
+ return MMDB_OUT_OF_MEMORY_ERROR;
+ }
+
+ while (NULL != (path_elem = va_arg(va_path, char *))) {
+ path[i] = path_elem;
+ i++;
+ }
+ path[i] = NULL;
+
+ int status = MMDB_aget_value(start, entry_data, path);
+
+ free((char **)path);
+
+ return status;
+}
+
+LOCAL int path_length(va_list va_path)
+{
+ int i = 0;
+ const char *ignore;
+ va_list path_copy;
+ va_copy(path_copy, va_path);
+
+ while (NULL != (ignore = va_arg(path_copy, char *))) {
+ i++;
+ }
+
+ va_end(path_copy);
+
+ return i;
+}
+
+int MMDB_aget_value(MMDB_entry_s *const start,
+ MMDB_entry_data_s *const entry_data,
+ const char *const *const path)
+{
+ MMDB_s *mmdb = start->mmdb;
+ uint32_t offset = start->offset;
+
+ memset(entry_data, 0, sizeof(MMDB_entry_data_s));
+ DEBUG_NL;
+ DEBUG_MSG("looking up value by path");
+
+ CHECKED_DECODE_ONE_FOLLOW(mmdb, offset, entry_data);
+
+ DEBUG_NL;
+ DEBUG_MSGF("top level element is a %s", type_num_to_name(entry_data->type));
+
+ /* Can this happen? It'd probably represent a pathological case under
+ * normal use, but there's nothing preventing someone from passing an
+ * invalid MMDB_entry_s struct to this function */
+ if (!entry_data->has_data) {
+ return MMDB_INVALID_LOOKUP_PATH_ERROR;
+ }
+
+ const char *path_elem;
+ int i = 0;
+ while (NULL != (path_elem = path[i++])) {
+ DEBUG_NL;
+ DEBUG_MSGF("path elem = %s", path_elem);
+
+ /* XXX - it'd be good to find a quicker way to skip through these
+ entries that doesn't involve decoding them
+ completely. Basically we need to just use the size from the
+ control byte to advance our pointer rather than calling
+ decode_one(). */
+ if (entry_data->type == MMDB_DATA_TYPE_ARRAY) {
+ int status = lookup_path_in_array(path_elem, mmdb, entry_data);
+ if (MMDB_SUCCESS != status) {
+ memset(entry_data, 0, sizeof(MMDB_entry_data_s));
+ return status;
+ }
+ } else if (entry_data->type == MMDB_DATA_TYPE_MAP) {
+ int status = lookup_path_in_map(path_elem, mmdb, entry_data);
+ if (MMDB_SUCCESS != status) {
+ memset(entry_data, 0, sizeof(MMDB_entry_data_s));
+ return status;
+ }
+ } else {
+ /* Once we make the code traverse maps & arrays without calling
+ * decode_one() we can get rid of this. */
+ memset(entry_data, 0, sizeof(MMDB_entry_data_s));
+ return MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR;
+ }
+ }
+
+ return MMDB_SUCCESS;
+}
+
+LOCAL int lookup_path_in_array(const char *path_elem, MMDB_s *mmdb,
+ MMDB_entry_data_s *entry_data)
+{
+ uint32_t size = entry_data->data_size;
+ char *first_invalid;
+
+ int saved_errno = errno;
+ errno = 0;
+ int array_index = strtol(path_elem, &first_invalid, 10);
+ if (array_index < 0 || ERANGE == errno) {
+ errno = saved_errno;
+ return MMDB_INVALID_LOOKUP_PATH_ERROR;
+ }
+ errno = saved_errno;
+
+ if (*first_invalid || (uint32_t)array_index >= size) {
+ return MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR;
+ }
+
+ for (int i = 0; i < array_index; i++) {
+ /* We don't want to follow a pointer here. If the next element is a
+ * pointer we simply skip it and keep going */
+ CHECKED_DECODE_ONE(mmdb, entry_data->offset_to_next, entry_data);
+ int status = skip_map_or_array(mmdb, entry_data);
+ if (MMDB_SUCCESS != status) {
+ return status;
+ }
+ }
+
+ MMDB_entry_data_s value;
+ CHECKED_DECODE_ONE_FOLLOW(mmdb, entry_data->offset_to_next, &value);
+ memcpy(entry_data, &value, sizeof(MMDB_entry_data_s));
+
+ return MMDB_SUCCESS;
+}
+
+LOCAL int lookup_path_in_map(const char *path_elem, MMDB_s *mmdb,
+ MMDB_entry_data_s *entry_data)
+{
+ uint32_t size = entry_data->data_size;
+ uint32_t offset = entry_data->offset_to_next;
+ size_t path_elem_len = strlen(path_elem);
+
+ while (size-- > 0) {
+ MMDB_entry_data_s key, value;
+ CHECKED_DECODE_ONE_FOLLOW(mmdb, offset, &key);
+
+ uint32_t offset_to_value = key.offset_to_next;
+
+ if (MMDB_DATA_TYPE_UTF8_STRING != key.type) {
+ return MMDB_INVALID_DATA_ERROR;
+ }
+
+ if (key.data_size == path_elem_len &&
+ !memcmp(path_elem, key.utf8_string, path_elem_len)) {
+
+ DEBUG_MSG("found key matching path elem");
+
+ CHECKED_DECODE_ONE_FOLLOW(mmdb, offset_to_value, &value);
+ memcpy(entry_data, &value, sizeof(MMDB_entry_data_s));
+ return MMDB_SUCCESS;
+ } else {
+ /* We don't want to follow a pointer here. If the next element is
+ * a pointer we simply skip it and keep going */
+ CHECKED_DECODE_ONE(mmdb, offset_to_value, &value);
+ int status = skip_map_or_array(mmdb, &value);
+ if (MMDB_SUCCESS != status) {
+ return status;
+ }
+ offset = value.offset_to_next;
+ }
+ }
+
+ memset(entry_data, 0, sizeof(MMDB_entry_data_s));
+ return MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR;
+}
+
+LOCAL int skip_map_or_array(MMDB_s *mmdb, MMDB_entry_data_s *entry_data)
+{
+ if (entry_data->type == MMDB_DATA_TYPE_MAP) {
+ uint32_t size = entry_data->data_size;
+ while (size-- > 0) {
+ CHECKED_DECODE_ONE(mmdb, entry_data->offset_to_next, entry_data); // key
+ CHECKED_DECODE_ONE(mmdb, entry_data->offset_to_next, entry_data); // value
+ int status = skip_map_or_array(mmdb, entry_data);
+ if (MMDB_SUCCESS != status) {
+ return status;
+ }
+ }
+ } else if (entry_data->type == MMDB_DATA_TYPE_ARRAY) {
+ uint32_t size = entry_data->data_size;
+ while (size-- > 0) {
+ CHECKED_DECODE_ONE(mmdb, entry_data->offset_to_next, entry_data); // value
+ int status = skip_map_or_array(mmdb, entry_data);
+ if (MMDB_SUCCESS != status) {
+ return status;
+ }
+ }
+ }
+
+ return MMDB_SUCCESS;
+}
+
+LOCAL int decode_one_follow(MMDB_s *mmdb, uint32_t offset,
+ MMDB_entry_data_s *entry_data)
+{
+ CHECKED_DECODE_ONE(mmdb, offset, entry_data);
+ if (entry_data->type == MMDB_DATA_TYPE_POINTER) {
+ uint32_t next = entry_data->offset_to_next;
+ CHECKED_DECODE_ONE(mmdb, entry_data->pointer, entry_data);
+ /* Pointers to pointers are illegal under the spec */
+ if (entry_data->type == MMDB_DATA_TYPE_POINTER) {
+ DEBUG_MSG("pointer points to another pointer");
+ return MMDB_INVALID_DATA_ERROR;
+ }
+
+ /* The pointer could point to any part of the data section but the
+ * next entry for this particular offset may be the one after the
+ * pointer, not the one after whatever the pointer points to. This
+ * depends on whether the pointer points to something that is a simple
+ * value or a compound value. For a compound value, the next one is
+ * the one after the pointer result, not the one after the pointer. */
+ if (entry_data->type != MMDB_DATA_TYPE_MAP
+ && entry_data->type != MMDB_DATA_TYPE_ARRAY) {
+
+ entry_data->offset_to_next = next;
+ }
+ }
+
+ return MMDB_SUCCESS;
+}
+
+#if !MMDB_UINT128_IS_BYTE_ARRAY
+NO_PROTO mmdb_uint128_t get_uint128(const uint8_t *p, int length)
+{
+ mmdb_uint128_t value = 0;
+ while (length-- > 0) {
+ value <<= 8;
+ value += *p++;
+ }
+ return value;
+}
+#endif
+
+LOCAL int decode_one(MMDB_s *mmdb, uint32_t offset,
+ MMDB_entry_data_s *entry_data)
+{
+ const uint8_t *mem = mmdb->data_section;
+
+ // We subtract rather than add as it possible that offset + 1
+ // could overflow for a corrupt database while an underflow
+ // from data_section_size - 1 should not be possible.
+ if (offset > mmdb->data_section_size - 1) {
+ DEBUG_MSGF("Offset (%d) past data section (%d)", offset,
+ mmdb->data_section_size);
+ return MMDB_INVALID_DATA_ERROR;
+ }
+
+ entry_data->offset = offset;
+ entry_data->has_data = true;
+
+ DEBUG_NL;
+ DEBUG_MSGF("Offset: %i", offset);
+
+ uint8_t ctrl = mem[offset++];
+ DEBUG_BINARY("Control byte: %s", ctrl);
+
+ int type = (ctrl >> 5) & 7;
+ DEBUG_MSGF("Type: %i (%s)", type, type_num_to_name(type));
+
+ if (type == MMDB_DATA_TYPE_EXTENDED) {
+ // Subtracting 1 to avoid possible overflow on offset + 1
+ if (offset > mmdb->data_section_size - 1) {
+ DEBUG_MSGF("Extended type offset (%d) past data section (%d)",
+ offset,
+ mmdb->data_section_size);
+ return MMDB_INVALID_DATA_ERROR;
+ }
+ type = get_ext_type(mem[offset++]);
+ DEBUG_MSGF("Extended type: %i (%s)", type, type_num_to_name(type));
+ }
+
+ entry_data->type = type;
+
+ if (type == MMDB_DATA_TYPE_POINTER) {
+ uint8_t psize = ((ctrl >> 3) & 3) + 1;
+ DEBUG_MSGF("Pointer size: %i", psize);
+
+ // We check that the offset does not extend past the end of the
+ // database and that the subtraction of psize did not underflow.
+ if (offset > mmdb->data_section_size - psize ||
+ mmdb->data_section_size < psize) {
+ DEBUG_MSGF("Pointer offset (%d) past data section (%d)", offset +
+ psize,
+ mmdb->data_section_size);
+ return MMDB_INVALID_DATA_ERROR;
+ }
+ entry_data->pointer = get_ptr_from(ctrl, &mem[offset], psize);
+ DEBUG_MSGF("Pointer to: %i", entry_data->pointer);
+
+ entry_data->data_size = psize;
+ entry_data->offset_to_next = offset + psize;
+ return MMDB_SUCCESS;
+ }
+
+ uint32_t size = ctrl & 31;
+ switch (size) {
+ case 29:
+ // We subtract when checking offset to avoid possible overflow
+ if (offset > mmdb->data_section_size - 1) {
+ DEBUG_MSGF("String end (%d, case 29) past data section (%d)",
+ offset,
+ mmdb->data_section_size);
+ return MMDB_INVALID_DATA_ERROR;
+ }
+ size = 29 + mem[offset++];
+ break;
+ case 30:
+ // We subtract when checking offset to avoid possible overflow
+ if (offset > mmdb->data_section_size - 2) {
+ DEBUG_MSGF("String end (%d, case 30) past data section (%d)",
+ offset,
+ mmdb->data_section_size);
+ return MMDB_INVALID_DATA_ERROR;
+ }
+ size = 285 + get_uint16(&mem[offset]);
+ offset += 2;
+ break;
+ case 31:
+ // We subtract when checking offset to avoid possible overflow
+ if (offset > mmdb->data_section_size - 3) {
+ DEBUG_MSGF("String end (%d, case 31) past data section (%d)",
+ offset,
+ mmdb->data_section_size);
+ return MMDB_INVALID_DATA_ERROR;
+ }
+ size = 65821 + get_uint24(&mem[offset]);
+ offset += 3;
+ default:
+ break;
+ }
+
+ DEBUG_MSGF("Size: %i", size);
+
+ if (type == MMDB_DATA_TYPE_MAP || type == MMDB_DATA_TYPE_ARRAY) {
+ entry_data->data_size = size;
+ entry_data->offset_to_next = offset;
+ return MMDB_SUCCESS;
+ }
+
+ if (type == MMDB_DATA_TYPE_BOOLEAN) {
+ entry_data->boolean = size ? true : false;
+ entry_data->data_size = 0;
+ entry_data->offset_to_next = offset;
+ DEBUG_MSGF("boolean value: %s", entry_data->boolean ? "true" : "false");
+ return MMDB_SUCCESS;
+ }
+
+ // Check that the data doesn't extend past the end of the memory
+ // buffer and that the calculation in doing this did not underflow.
+ if (offset > mmdb->data_section_size - size ||
+ mmdb->data_section_size < size) {
+ DEBUG_MSGF("Data end (%d) past data section (%d)", offset + size,
+ mmdb->data_section_size);
+ return MMDB_INVALID_DATA_ERROR;
+ }
+
+ if (type == MMDB_DATA_TYPE_UINT16) {
+ if (size > 2) {
+ DEBUG_MSGF("uint16 of size %d", size);
+ return MMDB_INVALID_DATA_ERROR;
+ }
+ entry_data->uint16 = (uint16_t)get_uintX(&mem[offset], size);
+ DEBUG_MSGF("uint16 value: %u", entry_data->uint16);
+ } else if (type == MMDB_DATA_TYPE_UINT32) {
+ if (size > 4) {
+ DEBUG_MSGF("uint32 of size %d", size);
+ return MMDB_INVALID_DATA_ERROR;
+ }
+ entry_data->uint32 = (uint32_t)get_uintX(&mem[offset], size);
+ DEBUG_MSGF("uint32 value: %u", entry_data->uint32);
+ } else if (type == MMDB_DATA_TYPE_INT32) {
+ if (size > 4) {
+ DEBUG_MSGF("int32 of size %d", size);
+ return MMDB_INVALID_DATA_ERROR;
+ }
+ entry_data->int32 = get_sintX(&mem[offset], size);
+ DEBUG_MSGF("int32 value: %i", entry_data->int32);
+ } else if (type == MMDB_DATA_TYPE_UINT64) {
+ if (size > 8) {
+ DEBUG_MSGF("uint64 of size %d", size);
+ return MMDB_INVALID_DATA_ERROR;
+ }
+ entry_data->uint64 = get_uintX(&mem[offset], size);
+ DEBUG_MSGF("uint64 value: %" PRIu64, entry_data->uint64);
+ } else if (type == MMDB_DATA_TYPE_UINT128) {
+ if (size > 16) {
+ DEBUG_MSGF("uint128 of size %d", size);
+ return MMDB_INVALID_DATA_ERROR;
+ }
+#if MMDB_UINT128_IS_BYTE_ARRAY
+ memset(entry_data->uint128, 0, 16);
+ if (size > 0) {
+ memcpy(entry_data->uint128 + 16 - size, &mem[offset], size);
+ }
+#else
+ entry_data->uint128 = get_uint128(&mem[offset], size);
+#endif
+ } else if (type == MMDB_DATA_TYPE_FLOAT) {
+ if (size != 4) {
+ DEBUG_MSGF("float of size %d", size);
+ return MMDB_INVALID_DATA_ERROR;
+ }
+ size = 4;
+ entry_data->float_value = get_ieee754_float(&mem[offset]);
+ DEBUG_MSGF("float value: %f", entry_data->float_value);
+ } else if (type == MMDB_DATA_TYPE_DOUBLE) {
+ if (size != 8) {
+ DEBUG_MSGF("double of size %d", size);
+ return MMDB_INVALID_DATA_ERROR;
+ }
+ size = 8;
+ entry_data->double_value = get_ieee754_double(&mem[offset]);
+ DEBUG_MSGF("double value: %f", entry_data->double_value);
+ } else if (type == MMDB_DATA_TYPE_UTF8_STRING) {
+ entry_data->utf8_string = size == 0 ? "" : (char *)&mem[offset];
+ entry_data->data_size = size;
+#ifdef MMDB_DEBUG
+ char *string = mmdb_strndup(entry_data->utf8_string,
+ size > 50 ? 50 : size);
+ if (NULL == string) {
+ abort();
+ }
+ DEBUG_MSGF("string value: %s", string);
+ free(string);
+#endif
+ } else if (type == MMDB_DATA_TYPE_BYTES) {
+ entry_data->bytes = &mem[offset];
+ entry_data->data_size = size;
+ }
+
+ entry_data->offset_to_next = offset + size;
+
+ return MMDB_SUCCESS;
+}
+
+LOCAL int get_ext_type(int raw_ext_type)
+{
+ return 7 + raw_ext_type;
+}
+
+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 1:
+ new_offset = ( (ctrl & 7) << 8) + ptr[0];
+ break;
+ case 2:
+ new_offset = 2048 + ( (ctrl & 7) << 16 ) + ( ptr[0] << 8) + ptr[1];
+ break;
+ case 3:
+ new_offset = 2048 + 524288 + ( (ctrl & 7) << 24 ) + get_uint24(ptr);
+ break;
+ case 4:
+ default:
+ new_offset = get_uint32(ptr);
+ break;
+ }
+ return new_offset;
+}
+
+int MMDB_get_metadata_as_entry_data_list(
+ MMDB_s *const mmdb, MMDB_entry_data_list_s **const entry_data_list)
+{
+ MMDB_s metadata_db = make_fake_metadata_db(mmdb);
+
+ MMDB_entry_s metadata_start = {
+ .mmdb = &metadata_db,
+ .offset = 0
+ };
+
+ return MMDB_get_entry_data_list(&metadata_start, entry_data_list);
+}
+
+int MMDB_get_entry_data_list(
+ MMDB_entry_s *start, MMDB_entry_data_list_s **const entry_data_list)
+{
+ MMDB_data_pool_s *const pool = data_pool_new(MMDB_POOL_INIT_SIZE);
+ if (!pool) {
+ return MMDB_OUT_OF_MEMORY_ERROR;
+ }
+
+ MMDB_entry_data_list_s *const list = data_pool_alloc(pool);
+ if (!list) {
+ data_pool_destroy(pool);
+ return MMDB_OUT_OF_MEMORY_ERROR;
+ }
+
+ int const status = get_entry_data_list(start->mmdb, start->offset, list,
+ pool, 0);
+
+ *entry_data_list = data_pool_to_list(pool);
+ if (!*entry_data_list) {
+ data_pool_destroy(pool);
+ return MMDB_OUT_OF_MEMORY_ERROR;
+ }
+
+ return status;
+}
+
+LOCAL int get_entry_data_list(MMDB_s *mmdb,
+ uint32_t offset,
+ MMDB_entry_data_list_s *const entry_data_list,
+ MMDB_data_pool_s *const pool,
+ int depth)
+{
+ if (depth >= MAXIMUM_DATA_STRUCTURE_DEPTH) {
+ DEBUG_MSG("reached the maximum data structure depth");
+ return MMDB_INVALID_DATA_ERROR;
+ }
+ depth++;
+ CHECKED_DECODE_ONE(mmdb, offset, &entry_data_list->entry_data);
+
+ switch (entry_data_list->entry_data.type) {
+ case MMDB_DATA_TYPE_POINTER:
+ {
+ uint32_t next_offset = entry_data_list->entry_data.offset_to_next;
+ uint32_t last_offset;
+ CHECKED_DECODE_ONE(mmdb, last_offset =
+ entry_data_list->entry_data.pointer,
+ &entry_data_list->entry_data);
+
+ /* Pointers to pointers are illegal under the spec */
+ if (entry_data_list->entry_data.type == MMDB_DATA_TYPE_POINTER) {
+ DEBUG_MSG("pointer points to another pointer");
+ return MMDB_INVALID_DATA_ERROR;
+ }
+
+ if (entry_data_list->entry_data.type == MMDB_DATA_TYPE_ARRAY
+ || entry_data_list->entry_data.type == MMDB_DATA_TYPE_MAP) {
+
+ int status =
+ get_entry_data_list(mmdb, last_offset, entry_data_list,
+ pool, depth);
+ if (MMDB_SUCCESS != status) {
+ DEBUG_MSG("get_entry_data_list on pointer failed.");
+ return status;
+ }
+ }
+ entry_data_list->entry_data.offset_to_next = next_offset;
+ }
+ break;
+ case MMDB_DATA_TYPE_ARRAY:
+ {
+ uint32_t array_size = entry_data_list->entry_data.data_size;
+ uint32_t array_offset = entry_data_list->entry_data.offset_to_next;
+ while (array_size-- > 0) {
+ MMDB_entry_data_list_s *entry_data_list_to =
+ data_pool_alloc(pool);
+ if (!entry_data_list_to) {
+ return MMDB_OUT_OF_MEMORY_ERROR;
+ }
+
+ int status =
+ get_entry_data_list(mmdb, array_offset, entry_data_list_to,
+ pool, depth);
+ if (MMDB_SUCCESS != status) {
+ DEBUG_MSG("get_entry_data_list on array element failed.");
+ return status;
+ }
+
+ array_offset = entry_data_list_to->entry_data.offset_to_next;
+ }
+ entry_data_list->entry_data.offset_to_next = array_offset;
+
+ }
+ break;
+ case MMDB_DATA_TYPE_MAP:
+ {
+ uint32_t size = entry_data_list->entry_data.data_size;
+
+ offset = entry_data_list->entry_data.offset_to_next;
+ while (size-- > 0) {
+ MMDB_entry_data_list_s *list_key = data_pool_alloc(pool);
+ if (!list_key) {
+ return MMDB_OUT_OF_MEMORY_ERROR;
+ }
+
+ int status =
+ get_entry_data_list(mmdb, offset, list_key, pool, depth);
+ if (MMDB_SUCCESS != status) {
+ DEBUG_MSG("get_entry_data_list on map key failed.");
+ return status;
+ }
+
+ offset = list_key->entry_data.offset_to_next;
+
+ MMDB_entry_data_list_s *list_value = data_pool_alloc(pool);
+ if (!list_value) {
+ return MMDB_OUT_OF_MEMORY_ERROR;
+ }
+
+ status = get_entry_data_list(mmdb, offset, list_value, pool,
+ depth);
+ if (MMDB_SUCCESS != status) {
+ DEBUG_MSG("get_entry_data_list on map element failed.");
+ return status;
+ }
+ offset = list_value->entry_data.offset_to_next;
+ }
+ entry_data_list->entry_data.offset_to_next = offset;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return MMDB_SUCCESS;
+}
+
+LOCAL float get_ieee754_float(const uint8_t *restrict p)
+{
+ volatile float f;
+ uint8_t *q = (void *)&f;
+/* Windows builds don't use autoconf but we can assume they're all
+ * little-endian. */
+#if MMDB_LITTLE_ENDIAN || _WIN32
+ 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 MMDB_LITTLE_ENDIAN || _WIN32
+ 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 uint64_t get_uintX(const uint8_t *p, int length)
+{
+ uint64_t value = 0;
+ while (length-- > 0) {
+ value <<= 8;
+ value += *p++;
+ }
+ return value;
+}
+
+LOCAL int32_t get_sintX(const uint8_t *p, int length)
+{
+ return (int32_t)get_uintX(p, length);
+}
+
+void MMDB_free_entry_data_list(MMDB_entry_data_list_s *const entry_data_list)
+{
+ if (entry_data_list == NULL) {
+ return;
+ }
+ data_pool_destroy(entry_data_list->pool);
+}
+
+void MMDB_close(MMDB_s *const mmdb)
+{
+ free_mmdb_struct(mmdb);
+}
+
+LOCAL void free_mmdb_struct(MMDB_s *const mmdb)
+{
+ if (!mmdb) {
+ return;
+ }
+
+ if (NULL != mmdb->filename) {
+ FREE_AND_SET_NULL(mmdb->filename);
+ }
+ if (NULL != mmdb->file_content) {
+#ifdef _WIN32
+ UnmapViewOfFile(mmdb->file_content);
+ /* Winsock is only initialized if open was successful so we only have
+ * to cleanup then. */
+ WSACleanup();
+#else
+ munmap((void *)mmdb->file_content, mmdb->file_size);
+#endif
+ }
+
+ if (NULL != mmdb->metadata.database_type) {
+ FREE_AND_SET_NULL(mmdb->metadata.database_type);
+ }
+
+ free_languages_metadata(mmdb);
+ free_descriptions_metadata(mmdb);
+}
+
+LOCAL void free_languages_metadata(MMDB_s *mmdb)
+{
+ if (!mmdb->metadata.languages.count) {
+ return;
+ }
+
+ for (size_t i = 0; i < mmdb->metadata.languages.count; i++) {
+ FREE_AND_SET_NULL(mmdb->metadata.languages.names[i]);
+ }
+ FREE_AND_SET_NULL(mmdb->metadata.languages.names);
+}
+
+LOCAL void free_descriptions_metadata(MMDB_s *mmdb)
+{
+ if (!mmdb->metadata.description.count) {
+ return;
+ }
+
+ for (size_t i = 0; i < mmdb->metadata.description.count; i++) {
+ if (NULL != mmdb->metadata.description.descriptions[i]) {
+ if (NULL !=
+ mmdb->metadata.description.descriptions[i]->language) {
+ FREE_AND_SET_NULL(
+ mmdb->metadata.description.descriptions[i]->language);
+ }
+
+ if (NULL !=
+ mmdb->metadata.description.descriptions[i]->description) {
+ FREE_AND_SET_NULL(
+ mmdb->metadata.description.descriptions[i]->description);
+ }
+ FREE_AND_SET_NULL(mmdb->metadata.description.descriptions[i]);
+ }
+ }
+
+ FREE_AND_SET_NULL(mmdb->metadata.description.descriptions);
+}
+
+const char *MMDB_lib_version(void)
+{
+ return PACKAGE_VERSION;
+}
+
+int MMDB_dump_entry_data_list(FILE *const stream,
+ MMDB_entry_data_list_s *const entry_data_list,
+ int indent)
+{
+ int status;
+ dump_entry_data_list(stream, entry_data_list, indent, &status);
+ return status;
+}
+
+LOCAL MMDB_entry_data_list_s *dump_entry_data_list(
+ FILE *stream, MMDB_entry_data_list_s *entry_data_list, int indent,
+ int *status)
+{
+ switch (entry_data_list->entry_data.type) {
+ case MMDB_DATA_TYPE_MAP:
+ {
+ uint32_t size = entry_data_list->entry_data.data_size;
+
+ print_indentation(stream, indent);
+ fprintf(stream, "{\n");
+ indent += 2;
+
+ for (entry_data_list = entry_data_list->next;
+ size && entry_data_list; size--) {
+
+ if (MMDB_DATA_TYPE_UTF8_STRING !=
+ entry_data_list->entry_data.type) {
+ *status = MMDB_INVALID_DATA_ERROR;
+ return NULL;
+ }
+ char *key =
+ mmdb_strndup(
+ (char *)entry_data_list->entry_data.utf8_string,
+ entry_data_list->entry_data.data_size);
+ if (NULL == key) {
+ *status = MMDB_OUT_OF_MEMORY_ERROR;
+ return NULL;
+ }
+
+ print_indentation(stream, indent);
+ fprintf(stream, "\"%s\": \n", key);
+ free(key);
+
+ entry_data_list = entry_data_list->next;
+ entry_data_list =
+ dump_entry_data_list(stream, entry_data_list, indent + 2,
+ status);
+
+ if (MMDB_SUCCESS != *status) {
+ return NULL;
+ }
+ }
+
+ indent -= 2;
+ print_indentation(stream, indent);
+ fprintf(stream, "}\n");
+ }
+ break;
+ case MMDB_DATA_TYPE_ARRAY:
+ {
+ uint32_t size = entry_data_list->entry_data.data_size;
+
+ print_indentation(stream, indent);
+ fprintf(stream, "[\n");
+ indent += 2;
+
+ for (entry_data_list = entry_data_list->next;
+ size && entry_data_list; size--) {
+ entry_data_list =
+ dump_entry_data_list(stream, entry_data_list, indent,
+ status);
+ if (MMDB_SUCCESS != *status) {
+ return NULL;
+ }
+ }
+
+ indent -= 2;
+ print_indentation(stream, indent);
+ fprintf(stream, "]\n");
+ }
+ break;
+ case MMDB_DATA_TYPE_UTF8_STRING:
+ {
+ char *string =
+ mmdb_strndup((char *)entry_data_list->entry_data.utf8_string,
+ entry_data_list->entry_data.data_size);
+ if (NULL == string) {
+ *status = MMDB_OUT_OF_MEMORY_ERROR;
+ return NULL;
+ }
+ print_indentation(stream, indent);
+ fprintf(stream, "\"%s\" <utf8_string>\n", string);
+ free(string);
+ entry_data_list = entry_data_list->next;
+ }
+ break;
+ case MMDB_DATA_TYPE_BYTES:
+ {
+ char *hex_string =
+ bytes_to_hex((uint8_t *)entry_data_list->entry_data.bytes,
+ entry_data_list->entry_data.data_size);
+ if (NULL == hex_string) {
+ *status = MMDB_OUT_OF_MEMORY_ERROR;
+ return NULL;
+ }
+
+ print_indentation(stream, indent);
+ fprintf(stream, "%s <bytes>\n", hex_string);
+ free(hex_string);
+
+ entry_data_list = entry_data_list->next;
+ }
+ break;
+ case MMDB_DATA_TYPE_DOUBLE:
+ print_indentation(stream, indent);
+ fprintf(stream, "%f <double>\n",
+ entry_data_list->entry_data.double_value);
+ entry_data_list = entry_data_list->next;
+ break;
+ case MMDB_DATA_TYPE_FLOAT:
+ print_indentation(stream, indent);
+ fprintf(stream, "%f <float>\n",
+ entry_data_list->entry_data.float_value);
+ entry_data_list = entry_data_list->next;
+ break;
+ case MMDB_DATA_TYPE_UINT16:
+ print_indentation(stream, indent);
+ fprintf(stream, "%u <uint16>\n", entry_data_list->entry_data.uint16);
+ entry_data_list = entry_data_list->next;
+ break;
+ case MMDB_DATA_TYPE_UINT32:
+ print_indentation(stream, indent);
+ fprintf(stream, "%u <uint32>\n", entry_data_list->entry_data.uint32);
+ entry_data_list = entry_data_list->next;
+ break;
+ case MMDB_DATA_TYPE_BOOLEAN:
+ print_indentation(stream, indent);
+ fprintf(stream, "%s <boolean>\n",
+ entry_data_list->entry_data.boolean ? "true" : "false");
+ entry_data_list = entry_data_list->next;
+ break;
+ case MMDB_DATA_TYPE_UINT64:
+ print_indentation(stream, indent);
+ fprintf(stream, "%" PRIu64 " <uint64>\n",
+ entry_data_list->entry_data.uint64);
+ entry_data_list = entry_data_list->next;
+ break;
+ case MMDB_DATA_TYPE_UINT128:
+ print_indentation(stream, indent);
+#if MMDB_UINT128_IS_BYTE_ARRAY
+ char *hex_string =
+ bytes_to_hex((uint8_t *)entry_data_list->entry_data.uint128, 16);
+ if (NULL == hex_string) {
+ *status = MMDB_OUT_OF_MEMORY_ERROR;
+ return NULL;
+ }
+ fprintf(stream, "0x%s <uint128>\n", hex_string);
+ free(hex_string);
+#else
+ uint64_t high = entry_data_list->entry_data.uint128 >> 64;
+ uint64_t low = (uint64_t)entry_data_list->entry_data.uint128;
+ fprintf(stream, "0x%016" PRIX64 "%016" PRIX64 " <uint128>\n", high,
+ low);
+#endif
+ entry_data_list = entry_data_list->next;
+ break;
+ case MMDB_DATA_TYPE_INT32:
+ print_indentation(stream, indent);
+ fprintf(stream, "%d <int32>\n", entry_data_list->entry_data.int32);
+ entry_data_list = entry_data_list->next;
+ break;
+ default:
+ *status = MMDB_INVALID_DATA_ERROR;
+ return NULL;
+ }
+
+ *status = MMDB_SUCCESS;
+ return entry_data_list;
+}
+
+LOCAL void print_indentation(FILE *stream, int i)
+{
+ char buffer[1024];
+ int size = i >= 1024 ? 1023 : i;
+ memset(buffer, 32, size);
+ buffer[size] = '\0';
+ fputs(buffer, stream);
+}
+
+LOCAL char *bytes_to_hex(uint8_t *bytes, uint32_t size)
+{
+ char *hex_string;
+ MAYBE_CHECK_SIZE_OVERFLOW(size, SIZE_MAX / 2 - 1, NULL);
+
+ hex_string = malloc((size * 2) + 1);
+ if (NULL == hex_string) {
+ return NULL;
+ }
+
+ for (uint32_t i = 0; i < size; i++) {
+ sprintf(hex_string + (2 * i), "%02X", bytes[i]);
+ }
+
+ return hex_string;
+}
+
+const char *MMDB_strerror(int error_code)
+{
+ switch (error_code) {
+ case MMDB_SUCCESS:
+ return "Success (not an error)";
+ case MMDB_FILE_OPEN_ERROR:
+ return "Error opening the specified MaxMind DB file";
+ case MMDB_CORRUPT_SEARCH_TREE_ERROR:
+ return "The MaxMind DB file's search tree is corrupt";
+ case MMDB_INVALID_METADATA_ERROR:
+ return "The MaxMind DB file contains invalid metadata";
+ case MMDB_IO_ERROR:
+ return "An attempt to read data from the MaxMind DB file failed";
+ case MMDB_OUT_OF_MEMORY_ERROR:
+ return "A memory allocation call failed";
+ case MMDB_UNKNOWN_DATABASE_FORMAT_ERROR:
+ return
+ "The MaxMind DB file is in a format this library can't handle (unknown record size or binary format version)";
+ case MMDB_INVALID_DATA_ERROR:
+ return
+ "The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)";
+ case MMDB_INVALID_LOOKUP_PATH_ERROR:
+ return
+ "The lookup path contained an invalid value (like a negative integer for an array index)";
+ case MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR:
+ return
+ "The lookup path does not match the data (key that doesn't exist, array index bigger than the array, expected array or map where none exists)";
+ case MMDB_INVALID_NODE_NUMBER_ERROR:
+ return
+ "The MMDB_read_node function was called with a node number that does not exist in the search tree";
+ case MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR:
+ return
+ "You attempted to look up an IPv6 address in an IPv4-only database";
+ default:
+ return "Unknown error code";
+ }
+}