/*
 * Project    : ipv6calc
 * File       : databases/lib/libipv6calc_db_wrapper.c
 * Version    : $Id: libipv6calc_db_wrapper.c,v 1.55 2014/10/24 06:20:34 ds6peter Exp $
 * Copyright  : 2013-2014 by Peter Bieringer <pb (at) bieringer.de>
 *
 * Information:
 *  ipv6calc database wrapper (for decoupling databases from main binary)
 */

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

#include "config.h"

#include "libipv6calcdebug.h"
#include "libipv6calc.h"

#define _ipv6calcoptions_h_ 1	// don't read options
#include "ipv6calcoptions.h"

#include "libipv6calc_db_wrapper.h"
#include "libipv6calc_db_wrapper_GeoIP.h"
#include "libipv6calc_db_wrapper_IP2Location.h"
#include "libipv6calc_db_wrapper_DBIP.h"
#include "libipv6calc_db_wrapper_External.h"
#include "libipv6calc_db_wrapper_BuiltIn.h"

static int wrapper_GeoIP_disable       = 0;
static int wrapper_IP2Location_disable = 0;
static int wrapper_DBIP_disable        = 0;
static int wrapper_External_disable    = 0;
static int wrapper_BuiltIn_disable     = 0;

static int wrapper_GeoIP_status = 0;
static int wrapper_IP2Location_status = 0;
static int wrapper_DBIP_status = 0;
static int wrapper_External_status = 0;
static int wrapper_BuiltIn_status = 0;

uint32_t wrapper_features = 0;
uint32_t wrapper_features_by_source[IPV6CALC_DB_SOURCE_MAX + 1];
uint32_t wrapper_features_by_source_implemented[IPV6CALC_DB_SOURCE_MAX + 1];

int wrapper_features_selector[IPV6CALC_DB_FEATURE_NUM_MAX + 1][IPV6CALC_DB_PRIO_MAX + 1];


/*
 * function initialise the main wrapper
 *
 * in : (nothing)
 * out: 0=ok, 1=error
 */
int libipv6calc_db_wrapper_init(void) {
	int result = 0, f, p, s;

#if defined SUPPORT_GEOIP || defined SUPPORT_IP2LOCATION || defined SUPPORT_DBIP || defined SUPPORT_EXTERNAL || defined SUPPORT_BUILTIN
	int r;
#endif

	DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Called");

	// clear selector
	for (f = IPV6CALC_DB_FEATURE_NUM_MIN; f <= IPV6CALC_DB_FEATURE_NUM_MAX; f++) {
		for (p = 0; p < IPV6CALC_DB_PRIO_MAX; p++) {
			wrapper_features_selector[f][p] = 0;
		};
	};

	for (s = 0; s <= IPV6CALC_DB_SOURCE_MAX; s++) {
		wrapper_features_by_source[s] = 0;
	};

	if (wrapper_GeoIP_disable != 1) {
#ifdef SUPPORT_GEOIP
		// Call GeoIP wrapper
		DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Call libipv6calc_db_wrapper_GeoIP_wrapper_init");

		r = libipv6calc_db_wrapper_GeoIP_wrapper_init();

		DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "GeoIP_wrapper_init result: %d wrapper_features=0x%08x", r, wrapper_features);

		if (r != 0) {
#ifndef SUPPORT_GEOIP_DYN
			// only non-dynamic-load results in a problem
			result = 1;
#endif
		} else {
			wrapper_GeoIP_status = 1; // ok
		};
	} else {
		NONQUIETPRINT_NA("Support for GeoIP disabled by option");
#endif // SUPPORT_GEOIP
	};

	if (wrapper_IP2Location_disable != 1) {
#ifdef SUPPORT_IP2LOCATION
		// Call IP2Location wrapper
		DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Call libipv6calc_db_wrapper_IP2Location_wrapper_init");

		r = libipv6calc_db_wrapper_IP2Location_wrapper_init();

		DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "IP2Location_wrapper_init result: %d wrapper_features=0x%08x", r, wrapper_features);

		if (r != 0) {
#ifndef SUPPORT_IP2LOCATION_DYN
			// only non-dynamic-load results in a problem
			result = 1;
#endif
		} else {
			wrapper_IP2Location_status = 1; // ok
		};
	} else {
		NONQUIETPRINT_NA("Support for IP2Location disabled by option");
#endif // SUPPORT_IP2LOCATION
	};

	if (wrapper_DBIP_disable != 1) {
#ifdef SUPPORT_DBIP
		// Call DBIP wrapper
		DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Call libipv6calc_db_wrapper_DBIP_wrapper_init");

		r = libipv6calc_db_wrapper_DBIP_wrapper_init();

		DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "DBIP_wrapper_init result: %d wrapper_features=0x%08x", r, wrapper_features);

		if (r != 0) {
			result = 1;
		} else {
			wrapper_DBIP_status = 1; // ok
		};
	} else {
		NONQUIETPRINT_NA("Support for db-ip.com disabled by option");
#endif // SUPPORT_DBIP
	};

	if (wrapper_External_disable != 1) {
#ifdef SUPPORT_EXTERNAL
		// Call External wrapper
		DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Call libipv6calc_db_wrapper_External_wrapper_init");

		r = libipv6calc_db_wrapper_External_wrapper_init();

		DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "External_wrapper_init result: %d wrapper_features=0x%08x", r, wrapper_features);

		if (r != 0) {
			result = 1;
		} else {
			wrapper_External_status = 1; // ok
		};
	} else {
		NONQUIETPRINT_NA("Support for External disabled by option");
#endif // SUPPORT_EXTERNAL
	};


	if (wrapper_BuiltIn_disable != 1) {
#ifdef SUPPORT_BUILTIN
		// Call BuiltIn wrapper
		DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Call libipv6calc_db_wrapper_BuiltIn_wrapper_init");

		r = libipv6calc_db_wrapper_BuiltIn_wrapper_init();

		DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "BuiltIn_wrapper_init result: %d wrapper_features=0x%08x", r, wrapper_features);

		if (r != 0) {
			result = 1;
		} else {
			wrapper_BuiltIn_status = 1; // ok
		};
#endif // SUPPORT_BUILTIN
	};

	// select source for feature by standard priority (from last to first in list)
	DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "select source for feature by standard priority");
	// run through feature numbers
	for (f = IPV6CALC_DB_FEATURE_NUM_MIN; f <= IPV6CALC_DB_FEATURE_NUM_MAX; f++) {
		DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "check feature f=%d", f);
		// run through sources
		for (s = IPV6CALC_DB_SOURCE_MIN; s <= IPV6CALC_DB_SOURCE_MAX; s++) {
			DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "check feature by source f=%d s=%d", f, s);
			if ((wrapper_features_by_source[s] & (1 << f)) != 0) {
				// supported, run through prio array
				for (p = 0; p < IPV6CALC_DB_PRIO_MAX; p++) {
					if (wrapper_features_selector[f][p] == 0) {
						DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "select feature with source f=%d s=%d p=%d", f, s, p);
						wrapper_features_selector[f][p] = s;
						break;
					} else {
#if defined SUPPORT_BUILTIN && defined SUPPORT_EXTERNAL
						// special handling for BuiltIn/External (no subsequent calls)
						if ((wrapper_BuiltIn_disable != 1) && (wrapper_External_disable != 1)) {
							// IPv4 -> Registry
							if (((1 << f) == IPV6CALC_DB_IPV4_TO_REGISTRY) && \
								( (s == IPV6CALC_DB_SOURCE_BUILTIN && (wrapper_features_selector[f][p] == IPV6CALC_DB_SOURCE_EXTERNAL)) \
								  || (s == IPV6CALC_DB_SOURCE_EXTERNAL && (wrapper_features_selector[f][p] == IPV6CALC_DB_SOURCE_BUILTIN))
								)) {
									// BuiltIn & External have feature enabled and one of them is already selected for p=0
									time_t db_unixtime_BuiltIn  = libipv6calc_db_wrapper_BuiltIn_db_unixtime_by_feature(IPV6CALC_DB_IPV4_TO_REGISTRY);
									time_t db_unixtime_External = libipv6calc_db_wrapper_External_db_unixtime_by_feature(IPV6CALC_DB_IPV4_TO_REGISTRY);

									if ((db_unixtime_BuiltIn > 0) && (db_unixtime_External > 0)) {
										if (db_unixtime_BuiltIn < db_unixtime_External) {
											wrapper_features_selector[f][p] = IPV6CALC_DB_SOURCE_EXTERNAL;
											DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "BuiltIn has older DB than External for IPV6CALC_DB_IPV4_TO_REGISTRY");
										} else {
											wrapper_features_selector[f][p] = IPV6CALC_DB_SOURCE_BUILTIN;
											DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "External has older DB than BuiltIn for IPV6CALC_DB_IPV4_TO_REGISTRY");
									};
									break; // no further extension of priority list
								};
							};

							// IPv6 -> Registry
							if (((1 << f) == IPV6CALC_DB_IPV6_TO_REGISTRY) && \
								( (s == IPV6CALC_DB_SOURCE_BUILTIN && (wrapper_features_selector[f][p] == IPV6CALC_DB_SOURCE_EXTERNAL)) \
								  || (s == IPV6CALC_DB_SOURCE_EXTERNAL && (wrapper_features_selector[f][p] == IPV6CALC_DB_SOURCE_BUILTIN))
								)) {
									// BuiltIn & External have feature enabled and one of them is already selected for p=0
									time_t db_unixtime_BuiltIn  = libipv6calc_db_wrapper_BuiltIn_db_unixtime_by_feature(IPV6CALC_DB_IPV6_TO_REGISTRY);
									time_t db_unixtime_External = libipv6calc_db_wrapper_External_db_unixtime_by_feature(IPV6CALC_DB_IPV6_TO_REGISTRY);

									if ((db_unixtime_BuiltIn > 0) && (db_unixtime_External > 0)) {
										if (db_unixtime_BuiltIn < db_unixtime_External) {
											wrapper_features_selector[f][p] = IPV6CALC_DB_SOURCE_EXTERNAL;
											DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "BuiltIn has older DB than External for IPV6CALC_DB_IPV6_TO_REGISTRY");
										} else {
											wrapper_features_selector[f][p] = IPV6CALC_DB_SOURCE_BUILTIN;
											DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "External has older DB than BuiltIn for IPV6CALC_DB_IPV6_TO_REGISTRY");
									};
									break; // no further extension of priority list
								};
							};
						};
#endif // SUPPORT_BUILTIN && SUPPORT_EXTERNAL
					};
				};
			};
		};
	};

#ifdef TEST
#if defined SUPPORT_BUILTIN && defined SUPPORT_EXTERNAL
	// IPv4 -> Registry
	if ((wrapper_BuiltIn_status == 1) && (wrapper_External_status == 1)) {
		if (libipv6calc_db_wrapper_External_has_features(IPV6CALC_DB_IPV4_TO_REGISTRY) == 1 \
			&& libipv6calc_db_wrapper_BuiltIn_has_features(IPV6CALC_DB_IPV4_TO_REGISTRY) == 1) {
			// BuiltIn & External have feature enabled (both)
			time_t db_unixtime_BuiltIn  = libipv6calc_db_wrapper_BuiltIn_db_unixtime_by_feature(IPV6CALC_DB_IPV4_TO_REGISTRY);
			time_t db_unixtime_External = libipv6calc_db_wrapper_External_db_unixtime_by_feature(IPV6CALC_DB_IPV4_TO_REGISTRY);

			if ((db_unixtime_BuiltIn > 0) && (db_unixtime_External > 0)) {
				if (db_unixtime_BuiltIn < db_unixtime_External) {
					wrapper_features_selector[IPV6CALC_DB_FEATURE_NUM_IPV4_TO_REGISTRY][0] = IPV6CALC_DB_SOURCE_EXTERNAL;
					DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "BuiltIn has older DB than External for IPV6CALC_DB_IPV4_TO_REGISTRY, disable it further");
				} else {
					wrapper_features_selector[IPV6CALC_DB_FEATURE_NUM_IPV4_TO_REGISTRY][0] = IPV6CALC_DB_SOURCE_BUILTIN;
					DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "External has older DB than BuiltIn for IPV6CALC_DB_IPV4_TO_REGISTRY, disable it further");
				};
			};
		};
	};
#endif // SUPPORT_BUILTIN && SUPPORT_EXTERNAL
#endif

	return(result);
};


/*
 * function cleanup the main wrapper
 *
 * in : (nothing)
 * out: 0=ok, 1=error
 */
int libipv6calc_db_wrapper_cleanup(void) {
	int result = 0;

#if defined SUPPORT_GEOIP || defined SUPPORT_IP2LOCATION || defined SUPPORT_DBIP || defined SUPPORT_EXTERNAL || defined SUPPORT_BUILTIN
	int r;
#endif

	DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Called");

#ifdef SUPPORT_GEOIP
	// Call GeoIP wrapper
	r = libipv6calc_db_wrapper_GeoIP_wrapper_cleanup();
	if (r != 0) {
		result = 1;
	};
#endif

#ifdef SUPPORT_IP2LOCATION
	// Call IP2Location wrapper
	r = libipv6calc_db_wrapper_IP2Location_wrapper_cleanup();
	if (r != 0) {
		result = 1;
	};
#endif

#ifdef SUPPORT_DBIP
	// Call DBIP wrapper
	r = libipv6calc_db_wrapper_DBIP_wrapper_cleanup();
	if (r != 0) {
		result = 1;
	};
#endif

#ifdef SUPPORT_BUILTIN
	// Call BuiltIn wrapper
	r = libipv6calc_db_wrapper_BuiltIn_wrapper_cleanup();
	if (r != 0) {
		result = 1;
	};
#endif

	return(result);
};


/* function get info strings */
void libipv6calc_db_wrapper_info(char *string, const size_t size) {
	DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Called");

#ifdef SUPPORT_GEOIP
	// Call GeoIP wrapper
	libipv6calc_db_wrapper_GeoIP_wrapper_info(string, size);
#endif

#ifdef SUPPORT_IP2LOCATION
	// Call IP2Location wrapper
	libipv6calc_db_wrapper_IP2Location_wrapper_info(string, size);
#endif

#ifdef SUPPORT_DBIP
	// Call DBIP wrapper
	libipv6calc_db_wrapper_BuiltIn_wrapper_info(string, size);
#endif

#ifdef SUPPORT_BUILTIN
	// Call BuiltIn wrapper
	libipv6calc_db_wrapper_BuiltIn_wrapper_info(string, size);
#endif

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Result: %s", string);

	return;
};


/* function get feature string */
void libipv6calc_db_wrapper_features(char *string, const size_t size) {
	int i;
	char tempstring[NI_MAXHOST];
	char *separator;

	DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Called");

	for (i = 0; i < MAXENTRIES_ARRAY(ipv6calc_db_features); i++) {
		if (wrapper_features & ipv6calc_db_features[i].number) {
			if (strlen(string) == 0) {
				separator = "";
			} else {
				separator = " ";
			};
			snprintf(tempstring, sizeof(tempstring), "%s%s%s", string, separator, ipv6calc_db_features[i].token);
			snprintf(string, size, "%s", tempstring);
		};
	};

	DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Return");

	return;
};


/* function get capability string */
void libipv6calc_db_wrapper_capabilities(char *string, const size_t size) {
#if defined SUPPORT_GEOIP || defined SUPPORT_IP2LOCATION || defined SUPPORT_DBIP || defined SUPPORT_EXTERNAL || defined SUPPORT_BUILTIN
	char tempstring[NI_MAXHOST];
#endif

	DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Called");

	if (wrapper_GeoIP_disable != 1) {
#ifdef SUPPORT_GEOIP
#ifdef SUPPORT_GEOIP_DYN
		snprintf(tempstring, sizeof(tempstring), "%s%sGeoIP(dyn-load)", string, strlen(string) > 0 ? " " : "");
		snprintf(string, size, "%s", tempstring);
#else // SUPPORT_GEOIP_DYN
#ifdef SUPPORT_GEOIP_STATIC
		snprintf(tempstring, sizeof(tempstring), "%s%sGeoIP(static)", string, strlen(string) > 0 ? " " : "");
		snprintf(string, size, "%s", tempstring);
#else // SUPPORT_GEOIP_STATIC
		snprintf(tempstring, sizeof(tempstring), "%s%sGeoIP(linked)", string, strlen(string) > 0 ? " " : "");
		snprintf(string, size, "%s", tempstring);
#endif // SUPPORT_GEOIP_STATIC
#endif // SUPPORT_GEOIP_DYN
#endif // SUPPORT_GEOIP
	};

	if (wrapper_IP2Location_disable != 1) {
#ifdef SUPPORT_IP2LOCATION
#ifdef SUPPORT_IP2LOCATION_DYN
		snprintf(tempstring, sizeof(tempstring), "%s%sIP2Location(dyn-load)", string, strlen(string) > 0 ? " " : "");
		snprintf(string, size, "%s", tempstring);
#else // SUPPORT_IP2LOCATION_DYN
#ifdef SUPPORT_IP2LOCATION_STATIC
		snprintf(tempstring, sizeof(tempstring), "%s%sIP2Location(static)", string, strlen(string) > 0 ? " " : "");
		snprintf(string, size, "%s", tempstring);
#else // SUPPORT_IP2LOCATION_STATIC
		snprintf(tempstring, sizeof(tempstring), "%s%sIP2Location(linked)", string, strlen(string) > 0 ? " " : "");
		snprintf(string, size, "%s", tempstring);
#endif // SUPPORT_IP2LOCATION_STATIC
#endif //SUPPORT_IP2LOCATION_DYN
#endif // SUPPORT_IP2LOCATION
	};

	if (wrapper_DBIP_disable != 1) {
#ifdef SUPPORT_DBIP
		snprintf(tempstring, sizeof(tempstring), "%s%sDBIP", string, strlen(string) > 0 ? " " : "");
		snprintf(string, size, "%s", tempstring);
#endif
	};

	if (wrapper_External_disable != 1) {
#ifdef SUPPORT_EXTERNAL
		snprintf(tempstring, sizeof(tempstring), "%s%sExternal", string, strlen(string) > 0 ? " " : "");
		snprintf(string, size, "%s", tempstring);
#endif
	};

	if (wrapper_BuiltIn_disable != 1) {
#ifdef SUPPORT_BUILTIN
		snprintf(tempstring, sizeof(tempstring), "%s%sDB_AS_REG(BuiltIn)", string, strlen(string) > 0 ? " " : "");
		snprintf(string, size, "%s", tempstring);
		snprintf(tempstring, sizeof(tempstring), "%s%sDB_CC_REG(BuiltIn)", string, strlen(string) > 0 ? " " : "");
		snprintf(string, size, "%s", tempstring);

#ifdef SUPPORT_DB_IPV4_REG
		snprintf(tempstring, sizeof(tempstring), "%s%sDB_IPV4_REG(BuiltIn)", string, strlen(string) > 0 ? " " : "");
		snprintf(string, size, "%s", tempstring);
#endif

#ifdef SUPPORT_DB_IPV6_REG
		snprintf(tempstring, sizeof(tempstring), "%s%sDB_IPV6_REG(BuiltIn)", string, strlen(string) > 0 ? " " : "");
		snprintf(string, size, "%s", tempstring);
#endif

#ifdef SUPPORT_DB_IEEE
		snprintf(tempstring, sizeof(tempstring), "%s%sDB_IEEE(BuiltIn)", string, strlen(string) > 0 ? " " : "");
		snprintf(string, size, "%s", tempstring);
#endif
#endif // SUPPORT_BUILTIN
	};

	DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Return");

	return;
};


/* function get source name by number
 * in: source number
 * out: source name
 */
const char *libipv6calc_db_wrapper_get_data_source_name_by_number(const int number) {
	int i;

	for (i = 0; i < MAXENTRIES_ARRAY(data_sources); i++) {
		if (data_sources[i].number == number) {
			return(data_sources[i].name);
		};
	};

	ERRORPRINT_WA("unsupported data_source number: %d (FIX CODE)\n", number);
	exit(1);
};


/* function get feature index by number
 * in: feature number
 * out: index
 */
static int libipv6calc_db_wrapper_get_feature_index_by_feature(const int feature) {
	int i;

	for (i = 0; i < MAXENTRIES_ARRAY(ipv6calc_db_features); i++) {
		if (ipv6calc_db_features[i].number == feature) {
			return(i);
		};
	};

	ERRORPRINT_WA("unsupported feature: %d (FIX CODE)\n", feature);
	exit(1);
};



/* function print feature string help */
void libipv6calc_db_wrapper_features_help(void) {
	int i, s, first;

	DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Called");

	for (i = 0; i < MAXENTRIES_ARRAY(ipv6calc_db_features); i++) {
		fprintf(stderr, "%-22s%c %s", ipv6calc_db_features[i].token, (wrapper_features & ipv6calc_db_features[i].number) ? '+' : '-', ipv6calc_db_features[i].explanation);

		first = 1;
		for (s = IPV6CALC_DB_SOURCE_MIN; s <= IPV6CALC_DB_SOURCE_MAX; s++) {
			if ((wrapper_features_by_source_implemented[s] & ipv6calc_db_features[i].number) != 0) {
				fprintf(stderr, "%s%s", (first == 1) ? " (provided by " : ",", libipv6calc_db_wrapper_get_data_source_name_by_number(s));
				first = 0;
			};
		};
		fprintf(stderr, "%s\n", (first == 1) ? "" : ")");
	};

	DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Return");

	return;


};


/* function print db info */
void libipv6calc_db_wrapper_print_db_info(const int level_verbose, const char *prefix_string) {
	int f, p, f_index;

	DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Called");

	printf("%sDB features: 0x%08x\n\n", prefix_string, wrapper_features);

#ifdef SUPPORT_GEOIP
	if (wrapper_GeoIP_disable != 1) {
		// Call GeoIP wrapper
		libipv6calc_db_wrapper_GeoIP_wrapper_print_db_info(level_verbose, prefix_string);
	} else {
		printf("GeoIP support available but disabled by option\n");
	};
	printf("\n");
#endif

#ifdef SUPPORT_IP2LOCATION
	if (wrapper_IP2Location_disable != 1) {
		// Call IP2Location wrapper
		libipv6calc_db_wrapper_IP2Location_wrapper_print_db_info(level_verbose, prefix_string);
	} else {
		printf("IP2Location support available but disabled by option\n");
	};
	printf("\n");
#endif

#ifdef SUPPORT_DBIP
	if (wrapper_DBIP_disable != 1) {
		// Call DBIP wrapper
		libipv6calc_db_wrapper_DBIP_wrapper_print_db_info(level_verbose, prefix_string);
	} else {
		printf("db-ip.com support available but disabled by option\n");
	};
	printf("\n");
#endif

#ifdef SUPPORT_EXTERNAL
	if (wrapper_External_disable != 1) {
		// Call External wrapper
		libipv6calc_db_wrapper_External_wrapper_print_db_info(level_verbose, prefix_string);
	} else {
		printf("External support available but disabled by option\n");
	};
	printf("\n");
#endif

#ifdef SUPPORT_BUILTIN
	if (wrapper_BuiltIn_disable != 1) {
		// Call BuiltIn wrapper
		libipv6calc_db_wrapper_BuiltIn_wrapper_print_db_info(level_verbose, prefix_string);
	} else {
		printf("BuiltIn support available but disabled by option\n");
	};
	printf("\n");
#endif

	// summary
	printf("Database selection or priorization ('->': subsequential calls)\n");
	for (f = IPV6CALC_DB_FEATURE_NUM_MIN; f <= IPV6CALC_DB_FEATURE_NUM_MAX; f++) {
		DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "f=%d", f);

		f_index = libipv6calc_db_wrapper_get_feature_index_by_feature(1 << f);

		fprintf(stderr, "%s (%s): ", ipv6calc_db_features[f_index].token, ipv6calc_db_features[f_index].explanation);

		if (wrapper_features_selector[f][0] == 0) {
			fprintf(stderr, "NO-DATABASE");
		} else {
			for (p = 0; p < IPV6CALC_DB_PRIO_MAX; p++) {
				if (wrapper_features_selector[f][p] != 0) {
					fprintf(stderr, "%s%s", (p == 0) ? "" : "->", libipv6calc_db_wrapper_get_data_source_name_by_number(wrapper_features_selector[f][p]));
				};
			};
		};
		fprintf(stderr, "\n");
	};

	printf("\n");

	DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Return");

	return;
};


/* function query for feature set
 * ret=-1: unknown
 * 0 : not matching
 * 1 : ok
 */
int libipv6calc_db_wrapper_has_features(uint32_t features) {
	int result = -1;

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Called with feature value to test: 0x%08x", features);

	if ((wrapper_features & features) == features) {
		result = 1;
	} else {
		result = 0;
	};

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Return with result: %d", result);

	return(result);
};


/*********************************************
 * Option handling
 * return < 0: error
 *********************************************/
int libipv6calc_db_wrapper_options(const int opt, const char *optarg, const struct option longopts[]) {
	int result = -1;

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Called with option: %08x", opt);

	switch(opt) {
		case DB_ip2location_disable:
			wrapper_IP2Location_disable = 1;
			result = 0;
			break;

		case DB_geoip_disable:
			wrapper_GeoIP_disable = 1;
			result = 0;
			break;

		case DB_dbip_disable:
			wrapper_DBIP_disable = 1;
			result = 0;
			break;

		case DB_external_disable:
			wrapper_External_disable = 1;
			result = 0;
			break;

		case DB_builtin_disable:
			wrapper_BuiltIn_disable = 1;
			result = 0;
			break;

		case DB_ip2location_lib:
#ifdef SUPPORT_IP2LOCATION_DYN
			result = snprintf(ip2location_lib_file, sizeof(ip2location_lib_file), "%s", optarg);
#else
			NONQUIETPRINT_WA("Support for IP2Location dyn-load not compiled-in, skipping option: --%s", ipv6calcoption_name(opt, longopts));
#endif
			result = 0;
			break;

		case DB_geoip_lib:
#ifdef SUPPORT_GEOIP_DYN
			result = snprintf(geoip_lib_file, sizeof(geoip_lib_file), "%s", optarg);
#else
			NONQUIETPRINT_WA("Support for GeoIP dyn-load not compiled-in, skipping option: --%s", ipv6calcoption_name(opt, longopts));
#endif
			result = 0;
			break;

		case DB_ip2location_dir:
#ifdef SUPPORT_IP2LOCATION
			result = snprintf(ip2location_db_dir, sizeof(ip2location_db_dir), "%s", optarg);
#else
			NONQUIETPRINT_WA("Support for IP2Location not compiled-in, skipping option: --%s", ipv6calcoption_name(opt, longopts));
#endif
			result = 0;
			break;

		case DB_geoip_dir:
#ifdef SUPPORT_GEOIP
			result = snprintf(geoip_db_dir, sizeof(geoip_db_dir), "%s", optarg);
#else
			NONQUIETPRINT_WA("Support for GeoIP not compiled-in, skipping option: --%s", ipv6calcoption_name(opt, longopts));
#endif
			result = 0;
			break;

		case DB_dbip_dir:
#ifdef SUPPORT_DBIP
			result = snprintf(dbip_db_dir, sizeof(dbip_db_dir), "%s", optarg);
#else
			NONQUIETPRINT_WA("Support for db-ip.com not compiled-in, skipping option: --%s", ipv6calcoption_name(opt, longopts));
#endif
			result = 0;
			break;

		case DB_external_dir:
#ifdef SUPPORT_EXTERNAL
			result = snprintf(external_db_dir, sizeof(external_db_dir), "%s", optarg);
#else
			NONQUIETPRINT_WA("Support for external not compiled-in, skipping option: --%s", ipv6calcoption_name(opt, longopts));
#endif
			result = 0;
			break;

		/* obsolete options */
		case DB_ip2location_ipv4:
		case DB_ip2location_ipv6:
			NONQUIETPRINT_WA("Obsolete option skipped: --%s <file>, use instead: --%s <dir>", ipv6calcoption_name(opt, longopts), ipv6calcoption_name(DB_ip2location_dir, longopts));
			result = 0;
			break;

		case DB_geoip_ipv4:
		case DB_geoip_ipv6:
			NONQUIETPRINT_WA("Obsolete option skipped: --%s <file>, use instead: --%s <dir>", ipv6calcoption_name(opt, longopts), ipv6calcoption_name(DB_geoip_dir, longopts));
			result = 0;
			break;
	};

	if (result > 0) {
		result = 0;
	};

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Return with result: %d", result);

	return(result);
};


/*********************************************
 * Abstract functions
 *********************************************/

/*
 * get registry number by AS number
 */
int libipv6calc_db_wrapper_registry_num_by_as_num32(const uint32_t as_num32) {
	// currently only supported by BuiltIn
	return(libipv6calc_db_wrapper_BuiltIn_registry_num_by_as_num32(as_num32));
};


/*
 * get registry number by CC index
 */
int libipv6calc_db_wrapper_registry_num_by_cc_index(const uint16_t cc_index) {
	// currently only supported by BuiltIn
	return(libipv6calc_db_wrapper_BuiltIn_registry_num_by_cc_index(cc_index));
};


/*
 * get CountryCode in text form
 */
char *libipv6calc_db_wrapper_country_code_by_addr(const ipv6calc_ipaddr *ipaddrp, unsigned int *data_source_ptr) {
	char *result_char_ptr = NULL;
	unsigned int data_source = IPV6CALC_DB_SOURCE_UNKNOWN;
	int f = 0, p;

#if defined SUPPORT_GEOIP || defined SUPPORT_IP2LOCATION
	char tempstring[IPV6CALC_ADDR_STRING_MAX] = "";
#endif

#if defined SUPPORT_DBIP || defined SUPPORT_EXTERNAL
	static char country_code[256] = "";
#endif

	DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Called");

	if (ipaddrp->proto == IPV6CALC_PROTO_IPV4) {
		f = IPV6CALC_DB_FEATURE_NUM_IPV4_TO_CC;
		DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Given IPv4 address: %08x", (unsigned int) ipaddrp->addr[0]);
	} else if (ipaddrp->proto == IPV6CALC_PROTO_IPV6) {
		f = IPV6CALC_DB_FEATURE_NUM_IPV6_TO_CC;
		DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Given IPv6 address prefix (0-63): %08x%08x", (unsigned int) ipaddrp->addr[0], (unsigned int) ipaddrp->addr[1]);
	} else {
		ERRORPRINT_WA("unsupported proto=%d (FIX CODE)", ipaddrp->proto);
		exit(EXIT_FAILURE);
	};

	// run through priorities
	for (p = 0; p < IPV6CALC_DB_PRIO_MAX; p++) {
#if defined SUPPORT_GEOIP || defined SUPPORT_IP2LOCATION
		switch(wrapper_features_selector[f][p]) {
		    case IPV6CALC_DB_SOURCE_GEOIP:
		    case IPV6CALC_DB_SOURCE_IP2LOCATION:
			// need IP address as string
			if (strlen(tempstring) == 0) {
				libipaddr_ipaddrstruct_to_string(ipaddrp, tempstring, sizeof(tempstring));
			};
		};
#endif

		switch(wrapper_features_selector[f][p]) {
		    case 0:
			// last
			goto END_libipv6calc_db_wrapper; // ok
			break;

		    case IPV6CALC_DB_SOURCE_GEOIP:
			if (wrapper_GeoIP_status == 1) {
#ifdef SUPPORT_GEOIP
				DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Call now GeoIP with %s", tempstring);

				result_char_ptr = (char *) libipv6calc_db_wrapper_GeoIP_wrapper_country_code_by_addr(tempstring, ipaddrp->proto);

				if (result_char_ptr != NULL) {
					data_source = IPV6CALC_DB_SOURCE_GEOIP;
					goto END_libipv6calc_db_wrapper; // ok
				} else {
					DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Called GeoIP did not return a valid country_code");
				};
#endif
			};
			break;

		    case IPV6CALC_DB_SOURCE_IP2LOCATION:
			if (wrapper_IP2Location_status == 1) {
#ifdef SUPPORT_IP2LOCATION
				DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Call now IP2Location with %s", tempstring);

				result_char_ptr = libipv6calc_db_wrapper_IP2Location_wrapper_country_code_by_addr(tempstring, ipaddrp->proto);

				if (result_char_ptr != NULL) {
					data_source = IPV6CALC_DB_SOURCE_IP2LOCATION;
					goto END_libipv6calc_db_wrapper; // ok
				} else {
					DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Called IP2Location did not return a valid country_code");
				};
#endif
			};
			break;

		    case IPV6CALC_DB_SOURCE_DBIP:
			if (wrapper_DBIP_status == 1) {
#ifdef SUPPORT_DBIP
				DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Call now DBIP");

				country_code[0] = '\0'; // clear contents			

				int ret = libipv6calc_db_wrapper_DBIP_wrapper_country_code_by_addr(ipaddrp, country_code, sizeof(country_code));
				if (ret == 0) {
					result_char_ptr = country_code;
				};

				if (result_char_ptr != NULL) {
					data_source = IPV6CALC_DB_SOURCE_DBIP;
					goto END_libipv6calc_db_wrapper; // ok
				} else {
					DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Called db-ip.com did not return a valid country_code");
				};
#endif
			};
			break;

		    case IPV6CALC_DB_SOURCE_EXTERNAL:
			if (wrapper_External_status == 1) {
#ifdef SUPPORT_EXTERNAL
				DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Call now External");

				country_code[0] = '\0'; // clear contents			

				int ret = libipv6calc_db_wrapper_External_country_code_by_addr(ipaddrp, country_code, sizeof(country_code));

				if (ret == 0) {
					result_char_ptr = country_code;
				};

				if (result_char_ptr != NULL) {
					data_source = IPV6CALC_DB_SOURCE_EXTERNAL;
					goto END_libipv6calc_db_wrapper; // ok
				} else {
					DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Called External did not return a valid country_code");
				};
#endif
			};
			break;

		    default:
			goto END_libipv6calc_db_wrapper; // dummy goto in case no db is enabled
			break;
		};
	};

END_libipv6calc_db_wrapper:
	if (data_source_ptr != NULL) {
		// set data_source if pointer not NULL
		*data_source_ptr = data_source;
	};

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Result: %s (data_source=%d)", result_char_ptr, data_source);

	return(result_char_ptr);
};


/*
 * get CountryCode in special internal form (index) [A-Z] (26) x [0-9A-Z] (36)
 */
uint16_t libipv6calc_db_wrapper_cc_index_by_addr(const ipv6calc_ipaddr *ipaddrp, unsigned int *data_source_ptr) {
	uint16_t index = COUNTRYCODE_INDEX_UNKNOWN;

	const char *cc_text = libipv6calc_db_wrapper_country_code_by_addr(ipaddrp, data_source_ptr);
	uint8_t c1, c2;

	DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Called");

	if ((cc_text != NULL) && (strlen(cc_text) == 2)) {
		if (isalpha(cc_text[0]) && isalnum(cc_text[1])) {
			c1 = toupper(cc_text[0]);
			if (! (c1 >= 'A' && c1 <= 'Z')) {
				goto END_libipv6calc_db_wrapper_cc_index_by_addr; // something wrong
			};
			c1 -= 'A';

			c2 = toupper(cc_text[1]);
			if (c2 >= '0' && c2 <= '9') {
				c2 -= '0';
			} else if (c2 >= 'A' && c2 <= 'Z') {
				c2 -= 'A';
				c2 += 10;
			} else {
				goto END_libipv6calc_db_wrapper_cc_index_by_addr; // something wrong
			};

			index = c1 + c2 * COUNTRYCODE_LETTER1_MAX;

			DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "c1=%d c2=%d index=%d (0x%03x) -> test: %c%c", c1, c2, index, index, COUNTRYCODE_INDEX_TO_CHAR1(index), COUNTRYCODE_INDEX_TO_CHAR2(index));

			if (index >= COUNTRYCODE_INDEX_MAX) {
				index = COUNTRYCODE_INDEX_UNKNOWN; // failsafe
				fprintf(stderr, "%s/%s: unexpected index (too high): %d\n", __FILE__, __func__, index);
			};
		};
	};

END_libipv6calc_db_wrapper_cc_index_by_addr:
	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Finished with %d (0x%x)", index, index);

	return(index);
};


/*
 * get country code string by index
 */
int libipv6calc_db_wrapper_country_code_by_cc_index(char *string, int length, const uint16_t cc_index) {
	int result = 0;

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Called with cc_index=%d", cc_index);

	if (cc_index <= COUNTRYCODE_INDEX_LETTER_MAX) {
		snprintf(string, length, "%c%c", COUNTRYCODE_INDEX_TO_CHAR1(cc_index), COUNTRYCODE_INDEX_TO_CHAR2(cc_index));
	} else if (cc_index == COUNTRYCODE_INDEX_UNKNOWN) {
		snprintf(string, length, "unknown");
	} else if ((cc_index >= COUNTRYCODE_INDEX_UNKNOWN_REGISTRY_MAP_MIN) && (cc_index <= COUNTRYCODE_INDEX_UNKNOWN_REGISTRY_MAP_MAX)) {
		snprintf(string, length, "unknown");
	} else {
		snprintf(string, length, "unsupported");
	};

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Return country code: %s", string);
	return(result);
};


/*
 * get AS information in text form
 */
char *libipv6calc_db_wrapper_as_text_by_addr(const ipv6calc_ipaddr *ipaddrp) {
	char * result_char_ptr = NULL;

#if defined SUPPORT_GEOIP
	char tempstring[IPV6CALC_ADDR_STRING_MAX] = "";
#endif

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Called: addr=%04x%04x%04x%04x proto=%d", ipaddrp->addr[0], ipaddrp->addr[1], ipaddrp->addr[2], ipaddrp->addr[3], ipaddrp->proto);

	if (wrapper_GeoIP_status == 1) {
#ifdef SUPPORT_GEOIP
		// conversion sto string needed for GeoIP
		if (strlen(tempstring) == 0) {
			libipaddr_ipaddrstruct_to_string(ipaddrp, tempstring, sizeof(tempstring));
		};

		DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Call now GeoIP with addr=%s proto=%d)", tempstring, ipaddrp->proto);

		result_char_ptr = libipv6calc_db_wrapper_GeoIP_wrapper_asnum_by_addr(tempstring, ipaddrp->proto);
#endif
	};

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Result: %s", result_char_ptr);

	return(result_char_ptr);
};



/*
 * get AS 32-bit number
 */
uint32_t libipv6calc_db_wrapper_as_num32_by_addr(const ipv6calc_ipaddr *ipaddrp) {
	char *as_text;
	char as_number_string[11];  // max: 4294967295 = 10 digits + \0
	uint32_t as_num32 = ASNUM_AS_UNKNOWN; // default
	int i, valid = 1;

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Called: addr=%04x%04x%04x%04x proto=%d", ipaddrp->addr[0], ipaddrp->addr[1], ipaddrp->addr[2], ipaddrp->addr[3], ipaddrp->proto);

	// TODO: switch mechanism depending on backend (GeoIP supports AS only by text representation)
	as_text = libipv6calc_db_wrapper_as_text_by_addr(ipaddrp);

	if ((as_text != NULL) && (strncmp(as_text, "AS", 2) == 0) && (strlen(as_text) > 2)) {
		// catch AS....
		for (i = 0; i < (strlen(as_text) - 2); i++) {
			if ((as_text[i+2] == ' ') || (as_text[i+2] == '\0')) {
				break;
			} else if (isdigit(as_text[i+2])) {
				continue;
			} else {
				// something wrong
				valid = 0;
				break;
			};
		};

		if (i > 10) {
			// too many digits
			valid = 0;
		};

		if (valid == 1) {
			snprintf(as_number_string, 11, "%s", as_text + 2);
			as_num32 = atol(as_number_string);
		};
	};

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Result: %d (0x%08x)", as_num32, as_num32);

	return(as_num32);
};


/*
 * get AS 16-bit number
 */
uint16_t libipv6calc_db_wrapper_as_num16_by_addr(const ipv6calc_ipaddr *ipaddrp) {
	uint16_t as_num16 = 0;

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Called: addr=%04x%04x%04x%04x proto=%d", ipaddrp->addr[0], ipaddrp->addr[1], ipaddrp->addr[2], ipaddrp->addr[3], ipaddrp->proto);

	// get 32-bit ASN
	uint32_t as_num32 = libipv6calc_db_wrapper_as_num32_by_addr(ipaddrp);

	as_num16 = (uint16_t) (as_num32 < 65536 ? as_num32 : ASNUM_AS_TRANS);

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Result: %d (0x%04x)", as_num16, as_num16);

	// return 16-bit ASN or AS_TRANS in case of > 16-bit
	return(as_num16);
};


/*
 * compress AS 32-bit number to 17 bit
 */
uint32_t libipv6calc_db_wrapper_as_num32_comp17(const uint32_t as_num32) {;
	uint32_t as_num32_comp17 = 0;
	uint32_t as_num32_comp17_reg = 0;
	uint32_t as_num32_comp17_asn = 0;

	if (as_num32 <= 0xffff) {
		as_num32_comp17 = as_num32;
	} else {
		if ((as_num32 & 0x00070000) == (as_num32 & 0xfff70000)) {
			// 3 of 16 MSB bits active (which are  at least in 2013 maped 1:1 to related registry)
			as_num32_comp17_reg = (as_num32 & 0x0007000) >> 3;
		} else {
			// map to unknown registry
			as_num32_comp17_reg = 0;
		};

		if ((as_num32 & 0x0fff) == (as_num32 & 0xffff)) {
			// only 12 of 16 LSB bits active
			as_num32_comp17_asn = as_num32 & 0x0fff;
		} else {
			// more than 12 bits are in use, unspecified result, but keeping registry and set special flag
			as_num32_comp17_asn = 0x1000;
		};

		// fill compressed value and set flag
		as_num32_comp17 = as_num32_comp17_reg | as_num32_comp17_asn | 0x00010000;
	};

	return(as_num32_comp17);
};


/*
 * Decompress AS 32-bit number from 17 bit
 */
uint32_t libipv6calc_db_wrapper_as_num32_decomp17(const uint32_t as_num32_comp17) {;
	uint32_t as_num32 = ASNUM_AS_UNKNOWN;

	if ((as_num32_comp17 & 0x00010000) == 0x00000000) {
		as_num32 = as_num32_comp17;
	} else {
		if ((as_num32_comp17 & 0xe000) == 0x0000) {
			as_num32 = ASNUM_AS_UNKNOWN;
		} else {
			as_num32 |= (as_num32_comp17 & 0xe000) << 3;

			if ((as_num32_comp17 & 0x1000) == 0x1000) {
				// keep only ASN registry
			} else {
				as_num32 |= (as_num32_comp17 & 0x0fff);
			};
		};
	};

	return(as_num32);
};


/*
 * Get IEEE vendor string
 * in:  macaddrp
 * mod: resultstring
 * out: 0=found, 1=not found
 */
int libipv6calc_db_wrapper_ieee_vendor_string_by_macaddr(char *resultstring, const size_t resultstring_length, const ipv6calc_macaddr *macaddrp) {
	int retval = 1;
#ifdef SUPPORT_BUILTIN
	retval = libipv6calc_db_wrapper_BuiltIn_ieee_vendor_string_by_macaddr(resultstring, resultstring_length, macaddrp);
#endif
	return (retval);
};


/*
 * Get short IEEE vendor string
 * in:  macaddrp
 * mod: resultstring
 * out: 0=found, 1=not found
 */
int libipv6calc_db_wrapper_ieee_vendor_string_short_by_macaddr(char *resultstring, const size_t resultstring_length, const ipv6calc_macaddr *macaddrp) {
	int retval = 1;
#ifdef SUPPORT_BUILTIN
	retval = libipv6calc_db_wrapper_BuiltIn_ieee_vendor_string_short_by_macaddr(resultstring, resultstring_length, macaddrp);
#endif
	return (retval);
};


/********************************************
 * IPv4/IPv6 -> Registry lookup
 ********************************************/

/*
 * Get reserved IPv4 address information as string
 * ret: NULL: not reserved, !=NULL: pointer to string
 */
static const char *libipv6calc_db_wrapper_reserved_string_by_ipv4addr(const ipv6calc_ipv4addr *ipv4addrp) {
	const char *info = NULL;

	uint32_t ipv4 = ipv4addr_getdword(ipv4addrp);

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Given IPv4 address: %08x", (unsigned int) ipv4);

	// see also: https://en.wikipedia.org/wiki/Reserved_IP_addresses

	if ((ipv4 & 0xff000000u) == 0x00000000u) {
		// 0.0.0.0/8 (RFC 1122)
		info = "reserved(RFC1122#3.2.1.3)";
	} else if ((ipv4 & 0xff000000u) == 0x0a000000u) {
		// 10.0.0.0/8 (RFC 1918)
		info = "reserved(RFC1918#3)";
	} else if ((ipv4 & 0xffc00000u) == 0x64400000u) {
		// 100.64.0.0/10 (RFC 6598)
		info = "reserved(RFC6598)";
	} else if ((ipv4 & 0xff000000u) == 0x7f000000u) {
		// 127.0.0.0/8 (RFC 1122)
		info = "reserved(RFC1122#3.2.1.3)";
	} else if ((ipv4 & 0xffff0000u) == 0xa9fe0000u) {
		// 169.254.0.0/16 (RFC 1918)
		info = "reserved(RFC3927#1)";
	} else if ((ipv4 & 0xfff00000u) == 0xac100000u) {
		// 172.16.0.0/12 (RFC 1918)
		info = "reserved(RFC1918#3)";
	} else if ((ipv4 & 0xffff0000u) == 0xc0a80000u) {
		// 192.168.0.0/16 (RFC 1918)
		info = "reserved(RFC1918#3)";
	} else if ((ipv4 & 0xffffff00u) == 0xc0000000u) {
		// 192.0.0.0/24 (RFC 5736)
		info = "reserved(RFC5736#1)";
	} else if ((ipv4 & 0xffffff00u) == 0xc0000200u) {
		// 192.0.2.0/24 (RFC 3330)
		info = "reserved(RFC5737#1)";
	} else if ((ipv4 & 0xffffff00u) == 0xc0586300u) {
		// 192.88.99.0/24 (RFC 3068)
		info = "reserved(RFC3068#2.3)";
	} else if ((ipv4 & 0xfffe0000u) == 0xc6120000u) {
		// 198.18.0.0/15 (RFC 2544)
		info = "reserved(RFC2544#C.2.2)";
	} else if ((ipv4 & 0xffffff00u) == 0xc6336400u) {
		// 198.51.100.0/24 (RFC 5737)
		info = "reserved(RFC5737#3)";
	} else if ((ipv4 & 0xffffff00u) == 0xcb007100u) {
		// 203.0.113.0/24 (RFC 5737)
		info = "reserved(RFC5737#3)";
	} else if ((ipv4 & 0xf0000000u) == 0xe0000000u) {
		// 224.0.0.0/4 (RFC 3171)
		info = "reserved(RFC3171#2)";
	} else if ((ipv4 & 0xffffffffu) == 0xffffffffu) {
		// 255.255.255.255/32
		info = "reserved(RFC919#7)";
	} else if ((ipv4 & 0xf0000000u) == 0xf0000000u) {
		// 240.0.0.0/4 (RFC 1112)
		info = "reserved(RFC1112#4)";
	}; 

	if (info == NULL) {
		DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Given IPv4 address is not reserved: %08x", (unsigned int) ipv4);
	} else {
		DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Given IPv4 address is reserved: %08x (%s)", (unsigned int) ipv4, info);
	};

	return(info);
};


/*
 * Get reserved IPv6 address information as string
 * ret: NULL: not reserved, !=NULL: pointer to string
 */
static const char *libipv6calc_db_wrapper_reserved_string_by_ipv6addr(const ipv6calc_ipv6addr *ipv6addrp) {
	const char *info = NULL;

	uint32_t ipv6_00_31 = ipv6addr_getdword(ipv6addrp, 0);
	uint32_t ipv6_32_63 = ipv6addr_getdword(ipv6addrp, 1);
	uint32_t ipv6_64_95 = ipv6addr_getdword(ipv6addrp, 2);
	uint32_t ipv6_96_127 = ipv6addr_getdword(ipv6addrp, 3);
	
	uint16_t ipv6_00_15 = ipv6addr_getword(ipv6addrp, 0);

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Given ipv6 prefix: %08x%08x", (unsigned int) ipv6_00_31, (unsigned int) ipv6_32_63);

	// see also: https://en.wikipedia.org/wiki/Reserved_IP_addresses
	//
	if ((ipv6_00_31 == 0) && (ipv6_32_63 == 0) && (ipv6_64_95 == 0) && (ipv6_96_127 == 0)) {
		// :: (RFC 4291)
		info = "reserved(RFC4291#2.5.2)";
	} else if ((ipv6_00_31 == 0) && (ipv6_32_63 == 0) && (ipv6_64_95 == 0) && (ipv6_96_127 == 1)) {
		// ::1 (RFC 4291)
		info = "reserved(RFC4291#2.5.3)";
	} else if ((ipv6_00_31 == 0) && (ipv6_32_63 == 0) && (ipv6_64_95 == 0)) {
		// ::x.x.x.x (RFC 4291)
		info = "reserved(RFC4291#2.5.5.1)";
	} else if ((ipv6_00_31 == 0) && (ipv6_32_63 == 0) && (ipv6_64_95 == 0x0000ffff)) {
		// ::ffff:x.x.x.x (RFC 4291)
		info = "reserved(RFC4291#2.5.5.2)";
	} else if ((ipv6_00_31 == 0x01000000) && (ipv6_32_63 == 0)) {
		// 0100::0/64 (RFC 6666)
		info = "reserved(RFC6666)";
	} else if (ipv6_00_31 == 0x20010000) {
		// 2001:0000::/32 (RFC 4380)
		info = "reserved(RFC4380#6)";
	} else if ((ipv6_00_31 & 0xfffffff0) == 0x20010010) {
		// 2001:0010::/28 (RFC 4843)
		info = "reserved(RFC4843#2)";
	} else if (ipv6_00_31 == 0x20010db8) {
		// 2001:0db8::/32 (RFC 3849)
		info = "reserved(RFC3849#4)";
	} else if ((ipv6_00_15 & 0xffff) == 0x2002) {
		// 2002::/16 (RFC 3056)
		info = "reserved(RFC3056#2)";
	} else if ((ipv6_00_15 & 0xfe00) == 0xfc00) {
		// fc00::/7 (RFC 4193)
		info = "reserved(RFC4193#3.1)";
	} else if ((ipv6_00_15 & 0xffe0) == 0xfe80) {
		// fe80::/10 (RFC 4291)
		info = "reserved(RFC4291#2.5.6)";
	} else if ((ipv6_00_15 & 0xffe0) == 0xfec0) {
		// fec0::/10 (RFC 4291)
		info = "reserved(RFC4291#2.5.7)";
	} else if ((ipv6_00_15 & 0xff00) == 0xff00) {
		// ffxx::/8 (RFC 4291)
		info = "reserved(RFC4291#2.7)";
	};

	if (info == NULL) {
		DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Given IPv6 address is not reserved: %08x%08x%08x%08x", (unsigned int) ipv6_00_31, (unsigned int) ipv6_32_63, (unsigned int) ipv6_64_95, (unsigned int) ipv6_96_127);
	} else {
		DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Given IPv6 address is reserved: %08x%08x%08x%08x (%s)", (unsigned int) ipv6_00_31, (unsigned int) ipv6_32_63, (unsigned int) ipv6_64_95, (unsigned int) ipv6_96_127, info);
	};

	return(info);
};


/*
 * get registry string of an IPv4 address
 *
 * in:  ipv4addr = IPv4 address structure
 * out: *resultstring = Registry string
 * ret: 0: ok, 1: unknown, 2: reserved
 */
int libipv6calc_db_wrapper_registry_string_by_ipv4addr(const ipv6calc_ipv4addr *ipv4addrp, char *resultstring, const size_t resultstring_length) {
	int retval = 1;
	int registry;

	DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Called");

	const char *info = libipv6calc_db_wrapper_reserved_string_by_ipv4addr(ipv4addrp);

	if (info != NULL) {
		// is reserved
		snprintf(resultstring, resultstring_length, "%s", info);
		retval = 2;
	} else {
		registry = libipv6calc_db_wrapper_registry_num_by_ipv4addr(ipv4addrp);
		snprintf(resultstring, resultstring_length, "%s", libipv6calc_registry_string_by_num(registry));
		if (registry != REGISTRY_UNKNOWN) {
			retval = 0;
		} else {
			retval = 1;
		};
	};

	return (retval);
};


/*
 * get registry number of an IPv4 address
 *
 * in:  ipv4addr = IPv4 address structure
 * out: registry number
 */
int libipv6calc_db_wrapper_registry_num_by_ipv4addr(const ipv6calc_ipv4addr *ipv4addrp) {
	int retval = REGISTRY_UNKNOWN, p, f;

#if defined SUPPORT_EXTERNAL
	ipv6calc_ipaddr ipaddr;
#endif

	DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Called");

	const char *info = libipv6calc_db_wrapper_reserved_string_by_ipv4addr(ipv4addrp);

	if (info != NULL) {
		return(REGISTRY_RESERVED);
	};

	f = IPV6CALC_DB_FEATURE_NUM_IPV4_TO_REGISTRY;

	// run through priorities
	for (p = 0; p < IPV6CALC_DB_PRIO_MAX; p++) {
		switch(wrapper_features_selector[f][p]) {
		    case 0:
			// last
			goto END_libipv6calc_db_wrapper; // ok
			break;

		    case IPV6CALC_DB_SOURCE_BUILTIN:
			if (wrapper_BuiltIn_status == 1) {
#ifdef SUPPORT_BUILTIN
				DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Call now BuiltIn");

				retval = libipv6calc_db_wrapper_BuiltIn_registry_num_by_ipv4addr(ipv4addrp);
#endif
			};
			break;

		    case IPV6CALC_DB_SOURCE_EXTERNAL:
			if (wrapper_External_status == 1) {
#ifdef SUPPORT_EXTERNAL
				DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Call now External");
				CONVERT_IPV4ADDRP_IPADDR(ipv4addrp, ipaddr);
				retval = libipv6calc_db_wrapper_External_registry_num_by_addr(&ipaddr);
#endif
			};
			break;

		    default:
			goto END_libipv6calc_db_wrapper; // dummy goto in case no db is enabled
			break;
		};
	};

END_libipv6calc_db_wrapper:
	return (retval);
};


/*
 * get registry string of an IPv6 address
 *
 * in:  ipv6addr = IPv6 address structure
 * out: *resultstring = Registry string
 * ret: 0: ok, 1: unknown, 2: reserved
 */
int libipv6calc_db_wrapper_registry_string_by_ipv6addr(const ipv6calc_ipv6addr *ipv6addrp, char *resultstring, const size_t resultstring_length) {
	int retval = 1;
	int registry;

	DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Called");

	const char *info = libipv6calc_db_wrapper_reserved_string_by_ipv6addr(ipv6addrp);

	if (info != NULL) {
		// is reserved
		snprintf(resultstring, resultstring_length, "%s", info);
		retval = 2;
	} else {
		registry = libipv6calc_db_wrapper_registry_num_by_ipv6addr(ipv6addrp);
		snprintf(resultstring, resultstring_length, "%s", libipv6calc_registry_string_by_num(registry));

		if (registry != REGISTRY_UNKNOWN) {
			retval = 0;
		} else {
			retval = 1;
		};
	};

	return (retval);
};


/*
 * get registry number of an IPv6 address
 *
 * in:  ipv6addr = IPv6 address structure
 * out: assignment number (-1 = no result)
 */
int libipv6calc_db_wrapper_registry_num_by_ipv6addr(const ipv6calc_ipv6addr *ipv6addrp) {
	int retval = REGISTRY_UNKNOWN, p, f;

#if defined SUPPORT_EXTERNAL
	ipv6calc_ipaddr ipaddr;
#endif

	DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Called");

	const char *info = libipv6calc_db_wrapper_reserved_string_by_ipv6addr(ipv6addrp);

	if (info != NULL) {
		return(REGISTRY_RESERVED);
	};

	if (ipv6addr_getword(ipv6addrp, 0) == 0x3ffe) {
		// special handling of 6BONE
		return(REGISTRY_6BONE);
	};

	f = IPV6CALC_DB_FEATURE_NUM_IPV6_TO_REGISTRY;

	// run through priorities
	for (p = 0; p < IPV6CALC_DB_PRIO_MAX; p++) {
		switch(wrapper_features_selector[f][p]) {
		    case 0:
			// last
			goto END_libipv6calc_db_wrapper; // ok
			break;

		    case IPV6CALC_DB_SOURCE_BUILTIN:
			if (wrapper_BuiltIn_status == 1) {
#ifdef SUPPORT_BUILTIN
				DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Call now BuiltIn");

				retval = libipv6calc_db_wrapper_BuiltIn_registry_num_by_ipv6addr(ipv6addrp);
#endif
			};
			break;

		    case IPV6CALC_DB_SOURCE_EXTERNAL:
			if (wrapper_External_status == 1) {
#ifdef SUPPORT_EXTERNAL
				DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Call now External");
				CONVERT_IPV6ADDRP_IPADDR(ipv6addrp, ipaddr);
				retval = libipv6calc_db_wrapper_External_registry_num_by_addr(&ipaddr);
#endif
			};
			break;

		    default:
			goto END_libipv6calc_db_wrapper; // dummy goto in case no db is enabled
			break;
		};
	};

END_libipv6calc_db_wrapper:
	return (retval);
};


#ifdef HAVE_BERKELEY_DB_SUPPORT
/********************************************
 * some generic Berkeley DB helper functions
 ********************************************/

/*
 * get value of token from Berkeley DB
 * in : DB pointer, token, max length of value
 * out: value
 * ret: 0=ok -1=error
 */
int libipv6calc_db_wrapper_bdb_get_data_by_key(DB *dbp, char *token, char *value, const size_t value_size) {
	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Called: dbp=%p token=%s", dbp, token);

	DBT key, data;
	int ret;

	memset(&key, 0, sizeof(key));
	memset(&data, 0, sizeof(data));

	key.data = token;
	key.size = strlen(token);

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "try to get key.data=%p key.size=%d", key.data, key.size);

	if ((ret = dbp->get(dbp, NULL, &key, &data, 0)) != 0) {
		dbp->err(dbp, ret, "DB->get token=%s", token);
		return(-1);
	};

	snprintf(value, (data.size + 1) >= value_size ? value_size : data.size + 1, "%s", (char *) data.data);

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Return: dbp=%p token=%s value=%s", dbp, token, value);

	return(0);
};



/*
 * generic fetch of a Berkeley DB row
 */
int libipv6calc_db_wrapper_bdb_fetch_row(
	DB 			*db_ptr,		// pointer to DB
	const uint8_t		db_format,		// DB format
	const long int		row,			// row number
	uint32_t		*data_1_00_31_ptr,	// data 1 (MSB in case of 64 bits)
	uint32_t		*data_1_32_63_ptr,	// data 1 (LSB in case of 64 bits)
	uint32_t		*data_2_00_31_ptr,	// data 2 (MSB in case of 64 bits)
	uint32_t		*data_2_32_63_ptr,	// data 2 (LSB in case of 64 bits)
	void			*data_ptr		// pointer to data
	) {

	int retval = -1;

	const char *db_format_row[] = {
		"%u;%u;%[^%]",			// IPV6CALC_DB_LOOKUP_DATA_DBD_FORMAT_SEMICOLON_SEP_DEC_32x2
		"%u;%u;%u;%u;%[^%]",		// IPV6CALC_DB_LOOKUP_DATA_DBD_FORMAT_SEMICOLON_SEP_DEC_32x4
		"%x;%x;%[^%]",			// IPV6CALC_DB_LOOKUP_DATA_DBD_FORMAT_SEMICOLON_SEP_HEX_32x2
		"%x;%x;%x;%x;%[^%]",		// IPV6CALC_DB_LOOKUP_DATA_DBD_FORMAT_SEMICOLON_SEP_HEX_32x4
		"%x;%x;%d;%[^%]",			// IPV6CALC_DB_LOOKUP_DATA_DBD_FORMAT_SEMICOLON_SEP_HEX_WITH_VALUE_32x2
		"%x;%x;%x;%x;%d;%[^%]",		// IPV6CALC_DB_LOOKUP_DATA_DBD_FORMAT_SEMICOLON_SEP_HEX_WITH_VALUE_32x4
		"0x%x;0x%x;%[^%]",			// IPV6CALC_DB_LOOKUP_DATA_DBD_FORMAT_SEMICOLON_SEP_HEX_WITH_PREF_32x2
		"0x%x;0x%x;0x%x;0x%x;%[^%]",	// IPV6CALC_DB_LOOKUP_DATA_DBD_FORMAT_SEMICOLON_SEP_HEX_WITH_PREF_32x4
	};

	const int db_format_values[] = {
		3,	// IPV6CALC_DB_LOOKUP_DATA_DBD_FORMAT_SEMICOLON_SEP_DEC_32x2
		5,	// IPV6CALC_DB_LOOKUP_DATA_DBD_FORMAT_SEMICOLON_SEP_DEC_32x4
		3,	// IPV6CALC_DB_LOOKUP_DATA_DBD_FORMAT_SEMICOLON_SEP_HEX_32x2
		5,	// IPV6CALC_DB_LOOKUP_DATA_DBD_FORMAT_SEMICOLON_SEP_HEX_32x4
		4,	// IPV6CALC_DB_LOOKUP_DATA_DBD_FORMAT_SEMICOLON_SEP_HEX_WITH_VALUE_32x2
		6,	// IPV6CALC_DB_LOOKUP_DATA_DBD_FORMAT_SEMICOLON_SEP_HEX_WITH_VALUE_32x4
		3,	// IPV6CALC_DB_LOOKUP_DATA_DBD_FORMAT_SEMICOLON_SEP_HEX_WITH_PREF_32x2
		5,	// IPV6CALC_DB_LOOKUP_DATA_DBD_FORMAT_SEMICOLON_SEP_HEX_WITH_PREF_32x4
	};

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Called db_ptr=%p, data_ptr=%p", db_ptr, data_ptr);

	if (MAXENTRIES_ARRAY(db_format_row) != MAXENTRIES_ARRAY(db_format_values)) {
		ERRORPRINT_NA("inconsistent array definition (FIX CODE)");
		exit(EXIT_FAILURE);
	};

	if (db_format >= MAXENTRIES_ARRAY(db_format_row)) {
		ERRORPRINT_WA("unsupported db_format (FIX CODE): %u", db_format);
		exit(EXIT_FAILURE);
	};

	char datastring[NI_MAXHOST];

	DBT key, data;

	db_recno_t recno = row;

	memset(&key, 0, sizeof(key));
	memset(&data, 0, sizeof(data));

	key.data = &recno;
	key.size = sizeof(recno);

	int ret, value;

	// DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Try to get row");

	ret = db_ptr->get(db_ptr, NULL, &key, &data, 0);

	// DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "get executed");

	if (ret != 0) {
		db_ptr->err(db_ptr, ret, "DB->get");
		goto END_libipv6calc_db_wrapper_bdb_fetch_row;
	};

	// DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Got row");

	snprintf(datastring, (data.size + 1) >= sizeof(datastring) ? sizeof(datastring) : data.size + 1, "%s", (char *) data.data);

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "start parsing database row %u: %s (size=%d) data_ptr=%p values=%u format=%s", recno, datastring, data.size, data_ptr, db_format_values[db_format], db_format_row[db_format]);

	if (db_format_values[db_format] == 3) {
		ret = sscanf(datastring, db_format_row[db_format], data_1_00_31_ptr, data_2_00_31_ptr, (char *) data_ptr);
		*data_1_32_63_ptr = 0;
		*data_2_32_63_ptr = 0;
	} else if (db_format_values[db_format] == 4) {
		ret = sscanf(datastring, db_format_row[db_format], data_1_00_31_ptr, data_2_00_31_ptr, &value, (char *) data_ptr);
		*data_1_32_63_ptr = 0;
		*data_2_32_63_ptr = 0;
	} else if (db_format_values[db_format] == 5) {
		ret = sscanf(datastring, db_format_row[db_format], data_1_00_31_ptr, data_1_32_63_ptr, data_2_00_31_ptr, data_2_32_63_ptr, (char *) data_ptr);
	} else if (db_format_values[db_format] == 6) {
		ret = sscanf(datastring, db_format_row[db_format], data_1_00_31_ptr, data_1_32_63_ptr, data_2_00_31_ptr, data_2_32_63_ptr, &value, (char *) data_ptr);
	} else {
		ERRORPRINT_WA("unsupported db_format_values (FIX CODE): %u", db_format_values[db_format]);
		exit(EXIT_FAILURE);
	};

	if (ret != db_format_values[db_format]) {
		ERRORPRINT_WA("row parsing error, corrupted database: %s (ret=%d format=%s expectedvalues=%d)", datastring, ret, db_format_row[db_format], db_format_values[db_format]);
		goto END_libipv6calc_db_wrapper_bdb_fetch_row;
	};

	if ((db_format == IPV6CALC_DB_LOOKUP_DATA_DBD_FORMAT_SEMICOLON_SEP_HEX_WITH_VALUE_32x4) \
	  || (db_format == IPV6CALC_DB_LOOKUP_DATA_DBD_FORMAT_SEMICOLON_SEP_HEX_WITH_VALUE_32x4)) {
		retval = value; // for Longest Match
	} else {
		retval = 0; // ok
	};

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "database row %lu parsed: data_1_00_31=%08x data_1_32_63=%08x data_2_00_31=%08x data_2_32_63=%08x value=%s)", row, *data_1_00_31_ptr, *data_1_32_63_ptr, *data_2_00_31_ptr, *data_2_32_63_ptr, (char *) data_ptr);
	
END_libipv6calc_db_wrapper_bdb_fetch_row:
	return(retval);
};
#endif // HAVE_BERKELEY_DB_SUPPORT


/*
 * generic internal/external database lookup function
 * return:	 -1 : no lookup result
 * 		>= 0: matching row
 */
long int libipv6calc_db_wrapper_get_entry_generic(
	void 		*db_ptr,		// pointer to database in case of IPV6CALC_DB_LOOKUP_DATA_PTR_TYPE_BDB, otherwise NULL
	const uint8_t	data_ptr_type,		// type of data_ptr
	const uint8_t	data_key_type,		// key type
	const uint8_t	data_key_format,	// key format
	const uint8_t	data_key_length,	// key length
	const uint8_t	data_search_type,	// search type
	const uint32_t	data_num_rows,		// number of rows
	const uint32_t	lookup_key_00_31,	// lookup key MSB
	const uint32_t	lookup_key_32_63,	// lookup key LSB
	void            *data_ptr,		// pointer to DB data in case of IPV6CALC_DB_LOOKUP_DATA_PTR_TYPE_BDB, otherwise NULL
	int  (*get_array_row)()			// function to get array row
	) {

	int retval = -1;

	uint32_t value_first_00_31 = 0, value_last_00_31 = 0;
	uint32_t value_first_32_63 = 0, value_last_32_63 = 0;

#ifdef HAVE_BERKELEY_DB_SUPPORT
	DB *dbp = NULL;
#endif // HAVE_BERKELEY_DB_SUPPORT

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Called with data_ptr_type=%u data_key_type=%u data_key_format=%u, data_key_length=%u data_num_rows=%u lookup_key_00_31=%08lx lookup_key_32_63=%08lx db_ptr=%p, data_ptr=%p",
		data_ptr_type,
		data_key_type,
		data_key_format,
		data_key_length,
		data_num_rows,
		(long unsigned int) lookup_key_00_31,
		(long unsigned int) lookup_key_32_63,
		db_ptr,
		data_ptr
	);

	if (data_num_rows < 1) {
		ERRORPRINT_WA("unsupported data_key_num_rows (FIX CODE): %u", data_num_rows);
		exit(EXIT_FAILURE);
	};

	/* check data_ptr_type */
	switch(data_ptr_type) {
	    case IPV6CALC_DB_LOOKUP_DATA_PTR_TYPE_ARRAY:
		if (get_array_row == NULL) {
			ERRORPRINT_NA("get_array_row function is unexpected NULL (FIX CODE)");
			exit(EXIT_FAILURE);
		};

		if (data_ptr != NULL) {
			ERRORPRINT_NA("data_ptr is unexpected NOT NULL - not supported on IPV6CALC_DB_LOOKUP_DATA_PTR_TYPE_ARRAY (FIX CODE)");
			exit(EXIT_FAILURE);
		};
		break;

#ifdef HAVE_BERKELEY_DB_SUPPORT
	    case IPV6CALC_DB_LOOKUP_DATA_PTR_TYPE_BDB:
		if (get_array_row != NULL) {
			ERRORPRINT_NA("get_array_row function is unexpected NOT NULL (FIX CODE)");
			exit(EXIT_FAILURE);
		};

		if (data_ptr == NULL) {
			ERRORPRINT_NA("data_ptr is unexpected NULL - not supported on IPV6CALC_DB_LOOKUP_DATA_PTR_TYPE_BDB (FIX CODE)");
			exit(EXIT_FAILURE);
		};

		// supported
		dbp = (DB *) db_ptr; // map db_ptr to DB ptr
		break;
#endif // HAVE_BERKELEY_DB_SUPPORT

	    default:
		ERRORPRINT_WA("unsupported data_ptr_type (FIX CODE): %u", data_ptr_type);
		exit(EXIT_FAILURE);
		break;
	};

	/* check data_key_length */
	switch(data_key_length) {
	    case 32:
	    case 64:
		// supported
		break;

	    default:
		ERRORPRINT_WA("unsupported data_key_length (FIX CODE): %d", data_key_length);
		exit(EXIT_FAILURE);
	};

	/* check data search type */
	switch(data_search_type) {
	    case IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_BINARY:
	    case IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_SEQLONGEST:
		// supported
		break;

	    default:
		ERRORPRINT_WA("unsupported data_search_type (FIX CODE): %d", data_search_type);
		exit(EXIT_FAILURE);
	};

	/* check data_key_type */
	switch(data_key_type) {
	    case IPV6CALC_DB_LOOKUP_DATA_KEY_TYPE_FIRST_LAST:
	    case IPV6CALC_DB_LOOKUP_DATA_KEY_TYPE_BASE_MASK:
		// supported
		break;

	    default:
		ERRORPRINT_WA("unsupported data_key_type (FIX CODE): %u", data_key_type);
		exit(EXIT_FAILURE);
	};


	long int i = -1;
	long int match = -1;
	int seqlongest = -1;
	long int i_min, i_max, i_old;

	int ret = -1;

	i_min = 0; i_max = data_num_rows - 1; i_old = -1;

	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Start binary search over entries: data_num_rows=%u", data_num_rows);

	if (data_search_type == IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_BINARY) {
		// binary search in provided data
		i = i_max / 2;
	} else if (data_search_type == IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_SEQLONGEST) {
		// sequential search in provided data
		i_old = i_max;
		i = 0;
	};

	while (i_old != i) {
		if ((i >= data_num_rows) || (i < 0)) {
			ERRORPRINT_WA("i out of range (FIX CODE): i=%ld data_num_rows=%u", i, data_num_rows);
			exit(EXIT_FAILURE);
		};

		DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Analyze entry i=%ld", i);

		if (data_ptr_type == IPV6CALC_DB_LOOKUP_DATA_PTR_TYPE_ARRAY) {
			ret = get_array_row(i, &value_first_00_31, &value_first_32_63, &value_last_00_31, &value_last_32_63);
			if (ret < 0) {
				ERRORPRINT_WA("can't retrieve keys from array for row: %lu", i);
				exit(EXIT_FAILURE);
			};	
#ifdef HAVE_BERKELEY_DB_SUPPORT	
		} else if (data_ptr_type == IPV6CALC_DB_LOOKUP_DATA_PTR_TYPE_BDB) {
			DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Try to get row from Berkeley DB dbp=%p row=%ld", dbp, i + 1);
			ret = libipv6calc_db_wrapper_bdb_fetch_row(
				dbp,				// pointer to DB
				data_key_format,		// DB format
				(long unsigned int) i + 1,	// row number (BDB starts always with 1, so add offset)
				&value_first_00_31,		// data 1 (MSB in case of 64 bits)
				&value_first_32_63,		// data 1 (LSB in case of 64 bits)
				&value_last_00_31,		// data 2 (MSB in case of 64 bits)
				&value_last_32_63,		// data 2 (LSB in case of 64 bits)
				data_ptr			// pointer to data
			);

			if (ret < 0) {
				ERRORPRINT_WA("can't retrieve keys from data for row: %ld", i);
				exit(EXIT_FAILURE);
			};
#endif // HAVE_BERKELEY_DB_SUPPORT
		};

		if (data_key_length == 32) {
			if (data_key_type == IPV6CALC_DB_LOOKUP_DATA_KEY_TYPE_FIRST_LAST) {
				DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Test %08x range %08x - %08x i=%ld i_min=%ld i_max=%ld",
					(unsigned int) lookup_key_00_31,
					(unsigned int) value_first_00_31,
					(unsigned int) value_last_00_31,
					i, i_min, i_max);
			} else if (data_key_type == IPV6CALC_DB_LOOKUP_DATA_KEY_TYPE_BASE_MASK) {
				DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Test %08x base/mask %08x/%08x i=%ld i_min=%ld i_max=%ld",
					(unsigned int) lookup_key_00_31,
					(unsigned int) value_first_00_31,
					(unsigned int) value_last_00_31,
					 i, i_min, i_max);
			};
		} else if (data_key_length == 64) {
			if (data_key_type == IPV6CALC_DB_LOOKUP_DATA_KEY_TYPE_FIRST_LAST) {
				DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Test %08x:%08x range %08x:%08x - %08x:%08x i=%ld i_min=%ld i_max=%ld",
					(unsigned int) lookup_key_00_31,
					(unsigned int) lookup_key_32_63,
					(unsigned int) value_first_00_31,
					(unsigned int) value_first_32_63,
					(unsigned int) value_last_00_31,
					(unsigned int) value_last_32_63,
					 i, i_min, i_max);
			} else if (data_key_type == IPV6CALC_DB_LOOKUP_DATA_KEY_TYPE_BASE_MASK) {
				DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Test %08x:%08x base/mask %08x:%08x/%08x:%08x i=%ld i_min=%ld i_max=%ld",
					(unsigned int) lookup_key_00_31,
					(unsigned int) lookup_key_32_63,
					(unsigned int) value_first_00_31,
					(unsigned int) value_first_32_63,
					(unsigned int) value_last_00_31,
					(unsigned int) value_last_32_63,
					 i, i_min, i_max);
			};
		};

		if (data_key_length == 32) {
			if (data_key_type == IPV6CALC_DB_LOOKUP_DATA_KEY_TYPE_FIRST_LAST) {
				if (lookup_key_00_31 < value_first_00_31) {
					// to high in array, jump down
					DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "to high in array, jump down");
					i_max = i;
				} else if (lookup_key_00_31 > value_last_00_31) {
					// to low in array, jump up
					DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "to low in array, jump up");
					i_min = i;
				} else {
					// hit
					match = i;
					if (data_search_type == IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_BINARY) {
						// binary search in provided data
						DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "matching");
						break;
					} else if (data_search_type == IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_SEQLONGEST) {
						// sequential search in provided data
						if (ret > seqlongest) {
							DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "matching, seqlongest old=%d new=%d", seqlongest, ret);
							seqlongest = ret;
						} else {
							DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "matching, keep seqlongest=%d", seqlongest);
						};
					};
				};
			} else if (data_key_type == IPV6CALC_DB_LOOKUP_DATA_KEY_TYPE_BASE_MASK) {
				if ((lookup_key_00_31 & value_last_00_31) < value_first_00_31) {
					// to high in array, jump down
					DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "to high in array, jump down");
					i_max = i;
				} else if ((lookup_key_00_31 & value_last_00_31) > value_first_00_31) {
					// to low in array, jump up
					DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "to low in array, jump up");
					i_min = i;
				} else {
					// hit
					match = i;
					if (data_search_type == IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_BINARY) {
						// binary search in provided data
						break;
					} else if (data_search_type == IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_SEQLONGEST) {
						// sequential search in provided data
						if (ret > seqlongest) {
							DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "matching, seqlongest old=%d new=%d", seqlongest, ret);
							seqlongest = ret;
						} else {
							DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "matching, keep seqlongest=%d", seqlongest);
						};
					};
				};
			};
		} else if (data_key_length == 64) {
			if (data_key_type == IPV6CALC_DB_LOOKUP_DATA_KEY_TYPE_FIRST_LAST) {
				if (lookup_key_00_31 < value_first_00_31) {
					if (data_search_type == IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_BINARY) {
						/* to high in array, jump down */
						DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "to high in array, jump down");
						i_max = i;
					};
				} else if (lookup_key_00_31 > value_last_00_31) {
					if (data_search_type == IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_BINARY) {
						// to low in array, jump up
						DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "to low in array, jump up");
						i_min = i;
					};
				} else if ((lookup_key_00_31 == value_first_00_31) && (lookup_key_32_63 < value_first_32_63)) {
					if (data_search_type == IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_BINARY) {
						/* to high in array, jump down */
						DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "to high in array, jump down");
						i_max = i;
					};
				} else if ((lookup_key_00_31 == value_last_00_31) && (lookup_key_32_63 > value_last_32_63)) {
					if (data_search_type == IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_BINARY) {
						// to low in array, jump up
						DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "to low in array, jump up");
						i_min = i;
					};
				} else {
					// hit
					match = i;
					if (data_search_type == IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_BINARY) {
						// binary search in provided data
						break;
					} else if (data_search_type == IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_SEQLONGEST) {
						// sequential search in provided data
						if (ret > seqlongest) {
							DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "matching, seqlongest old=%d new=%d", seqlongest, ret);
							seqlongest = ret;
						} else {
							DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "matching, keep seqlongest=%d", seqlongest);
						};
					};
				};
			} else if (data_key_type == IPV6CALC_DB_LOOKUP_DATA_KEY_TYPE_BASE_MASK) {
				if ((lookup_key_00_31 & value_last_00_31) < value_first_00_31) {
					DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "(lookup_key_00_31 & value_last_00_31) < value_first_00_31");
					if (data_search_type == IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_BINARY) {
						/* to high in array, jump down */
						DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "to high in array, jump down");
						i_max = i;
					};
				} else if ((lookup_key_00_31 & value_last_00_31) > value_first_00_31) {
					DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "(lookup_key_00_31 & value_last_00_31) > value_first_00_31");
					if (data_search_type == IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_BINARY) {
						// to low in array, jump up
						DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "to low in array, jump up");
						i_min = i;
					};
				} else if (((lookup_key_00_31 & value_last_00_31) == value_first_00_31) && ((lookup_key_32_63 & value_last_32_63) < value_first_32_63)) {
					DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "(lookup_key_32_63 & value_last_32_63) < value_first_32_63");
					if (data_search_type == IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_BINARY) {
						/* to high in array, jump down */
						DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "to high in array, jump down");
						i_max = i;
					};
				} else if (((lookup_key_00_31 & value_last_00_31) == value_first_00_31) && ((lookup_key_32_63 & value_last_32_63) > value_first_32_63)) {
					DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "(lookup_key_32_63 & value_last_32_63) > value_first_32_63");
					if (data_search_type == IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_BINARY) {
						// to low in array, jump up
						DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "to low in array, jump up");
						i_min = i;
					};
				} else {
					// hit
					match = i;
					if (data_search_type == IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_BINARY) {
						// binary search in provided data
						break;
					} else if (data_search_type == IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_SEQLONGEST) {
						// sequential search in provided data
						if (ret > seqlongest) {
							DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "matching, seqlongest old=%d new=%d", seqlongest, ret);
							seqlongest = ret;
						} else {
							DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "matching, keep seqlongest=%d", seqlongest);
						};
					};
				};
			};
		};

		if (data_search_type == IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_BINARY) {
			// binary search in provided data
			i_old = i;
			i = (i_max - i_min) / 2 + i_min;

			// jump to last entry in special way if needed, otherwise it's not reachable
			if ((i == i_old) && ((i + 1) == (data_num_rows - 1))) {
				DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "workaround for last entry activated");
				i = i_max;
			};

			DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "i_old=%ld i_min=%ld i_max=%ld i=%ld", i_old, i_min, i_max, i);
		} else if (data_search_type == IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_SEQLONGEST) {
			// sequential search in provided data
			i++;
		};
	};

	if (match != -1) {
		retval = match;

		if (data_search_type == IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_BINARY) {
			DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Entry matched: %ld", i);
			if (data_ptr_type == IPV6CALC_DB_LOOKUP_DATA_PTR_TYPE_ARRAY) {
				// currently nothing to do
#ifdef HAVE_BERKELEY_DB_SUPPORT
			} else if (data_ptr_type == IPV6CALC_DB_LOOKUP_DATA_PTR_TYPE_BDB) {
				// currently nothing to do
#endif // HAVE_BERKELEY_DB_SUPPORT
			};
		} else if (data_search_type == IPV6CALC_DB_LOOKUP_DATA_SEARCH_TYPE_SEQLONGEST) {
			DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Finally entry matched: %ld", match);
			if (data_ptr_type == IPV6CALC_DB_LOOKUP_DATA_PTR_TYPE_ARRAY) {
				// currently nothing to do
#ifdef HAVE_BERKELEY_DB_SUPPORT
			} else if (data_ptr_type == IPV6CALC_DB_LOOKUP_DATA_PTR_TYPE_BDB) {
				// fetch matching row
				DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Try to get row from Berkeley DB dbp=%p recno=%ld", dbp, match);
				ret = libipv6calc_db_wrapper_bdb_fetch_row(
					dbp,			// pointer to DB
					data_key_format,	// DB format
					match + 1,		// row number
					&value_first_00_31,	// data 1 (MSB in case of 64 bits)
					&value_first_32_63,	// data 1 (LSB in case of 64 bits)
					&value_last_00_31,	// data 2 (MSB in case of 64 bits)
					&value_last_32_63,	// data 2 (LSB in case of 64 bits)
					data_ptr		// pointer to data
				);

				if (ret < 0) {
					ERRORPRINT_WA("can't retrieve keys from data for row: %lu", match);
					exit(EXIT_FAILURE);
				};
#endif // HAVE_BERKELEY_DB_SUPPORT
			};
		};

		if (data_ptr != NULL) {
			DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Finished with success result (DB): match=%ld", match);
		};
	} else {
		DEBUGPRINT_NA(DEBUG_libipv6calc_db_wrapper, "Finished with NO SUCCESS result (DB)");
	};

//END_libipv6calc_db_wrapper_get_entry_generic:
	DEBUGPRINT_WA(DEBUG_libipv6calc_db_wrapper, "Return: %d", retval);
	return(retval);
};
