summaryrefslogtreecommitdiffstats
path: root/addons/deviceatlas
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:18:05 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:18:05 +0000
commitb46aad6df449445a9fc4aa7b32bd40005438e3f7 (patch)
tree751aa858ca01f35de800164516b298887382919d /addons/deviceatlas
parentInitial commit. (diff)
downloadhaproxy-b46aad6df449445a9fc4aa7b32bd40005438e3f7.tar.xz
haproxy-b46aad6df449445a9fc4aa7b32bd40005438e3f7.zip
Adding upstream version 2.9.5.upstream/2.9.5
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'addons/deviceatlas')
-rw-r--r--addons/deviceatlas/Makefile48
-rw-r--r--addons/deviceatlas/da.c501
-rw-r--r--addons/deviceatlas/dadwsch.c195
-rw-r--r--addons/deviceatlas/dummy/Makefile12
-rw-r--r--addons/deviceatlas/dummy/Os/daunix.c9
-rw-r--r--addons/deviceatlas/dummy/dac.c222
-rw-r--r--addons/deviceatlas/dummy/dac.h600
-rw-r--r--addons/deviceatlas/dummy/dadwcom.c1
-rw-r--r--addons/deviceatlas/dummy/dasch.c1
-rw-r--r--addons/deviceatlas/dummy/json.c1
10 files changed, 1590 insertions, 0 deletions
diff --git a/addons/deviceatlas/Makefile b/addons/deviceatlas/Makefile
new file mode 100644
index 0000000..fbcffca
--- /dev/null
+++ b/addons/deviceatlas/Makefile
@@ -0,0 +1,48 @@
+# DEVICEATLAS_SRC : DeviceAtlas API source root path
+
+
+OS := $(shell uname -s)
+OBJS := dadwsch.o
+CFLAGS := -g -O2
+LDFLAGS :=
+
+CURL_CONFIG := curl-config
+CURLDIR := $(shell $(CURL_CONFIG) --prefix 2>/dev/null || echo /usr/local)
+CURL_INC := $(CURLDIR)/include
+CURL_LIB := $(CURLDIR)/lib
+CURL_LDFLAGS := $(shell $(CURL_CONFIG) --libs 2>/dev/null || echo -L /usr/local/lib -lcurl)
+
+PCRE2_CONFIG := pcre2-config
+PCRE2DIR := $(shell $(PCRE2_CONFIG) --prefix 2>/dev/null || echo /usr/local)
+PCRE2_INC := $(PCRE2DIR)/include
+PCRE2_LIB := $(PCRE2DIR)/lib
+PCRE2_LDFLAGS := $(shell $(PCRE2_CONFIG) --libs8 2>/dev/null || echo /usr/local)
+
+ifeq ($(DEVICEATLAS_SRC),)
+dadwsch: dadwsch.c
+ $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+
+LDFLAGS += -lda
+else
+DEVICEATLAS_INC = $(DEVICEATLAS_SRC)
+DEVICEATLAS_LIB = $(DEVICEATLAS_SRC)
+CFLAGS += -DDA_REGEX_HDR=\"dac_pcre2.c\" -DDA_REGEX_TAG=2
+CFLAGS += -DMOBI_CURL -DMOBI_CURLSSET -DMOBI_GZ -DMOBI_ZIP
+CFLAGS += -I$(DEVICEATLAS_INC) -I$(CURL_INC) -I$(PCRE2DIR)
+LDFLAGS += $(CURL_LDFLAGS) $(PCRE2_LDFLAGS) -lz -lzip -lpthread
+
+dadwsch: dadwsch.c $(DEVICEATLAS_SRC)/dac.c $(DEVICEATLAS_SRC)/dasch.c $(DEVICEATLAS_SRC)/dadwarc.c $(DEVICEATLAS_SRC)/dadwcom.c $(DEVICEATLAS_SRC)/dadwcurl.c $(DEVICEATLAS_SRC)/json.c $(DEVICEATLAS_SRC)/Os/daunix.c
+ $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+endif
+
+ifeq ($(OS), Linux)
+LDFLAGS += -lrt
+endif
+ifeq ($(OS), SunOS)
+LDFLAGS += -lrt
+endif
+
+clean:
+ rm -f *.o
+ rm -f $(DEVICEATLAS_LIB)*.o
+ rm -f dadwsch
diff --git a/addons/deviceatlas/da.c b/addons/deviceatlas/da.c
new file mode 100644
index 0000000..969dfaa
--- /dev/null
+++ b/addons/deviceatlas/da.c
@@ -0,0 +1,501 @@
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <errno.h>
+
+#include <haproxy/api.h>
+#include <haproxy/arg.h>
+#include <haproxy/cfgparse.h>
+#include <haproxy/errors.h>
+#include <haproxy/global.h>
+#include <haproxy/http.h>
+#include <haproxy/http_ana.h>
+#include <haproxy/http_fetch.h>
+#include <haproxy/http_htx.h>
+#include <haproxy/htx.h>
+#include <haproxy/sample.h>
+#include <haproxy/tools.h>
+#include <dac.h>
+
+#define ATLASTOKSZ PATH_MAX
+#define ATLASMAPNM "/hapdeviceatlas"
+
+static struct {
+ void *atlasimgptr;
+ void *atlasmap;
+ char *jsonpath;
+ char *cookiename;
+ size_t cookienamelen;
+ int atlasfd;
+ da_atlas_t atlas;
+ da_evidence_id_t useragentid;
+ da_severity_t loglevel;
+ char separator;
+ unsigned char daset:1;
+} global_deviceatlas = {
+ .loglevel = 0,
+ .jsonpath = 0,
+ .cookiename = 0,
+ .cookienamelen = 0,
+ .atlasmap = NULL,
+ .atlasfd = -1,
+ .useragentid = 0,
+ .daset = 0,
+ .separator = '|',
+};
+
+__decl_thread(HA_SPINLOCK_T dadwsch_lock);
+
+static int da_json_file(char **args, int section_type, struct proxy *curpx,
+ const struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+ if (*(args[1]) == 0) {
+ memprintf(err, "deviceatlas json file : expects a json path.\n");
+ return -1;
+ }
+ global_deviceatlas.jsonpath = strdup(args[1]);
+ return 0;
+}
+
+static int da_log_level(char **args, int section_type, struct proxy *curpx,
+ const struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+ int loglevel;
+ if (*(args[1]) == 0) {
+ memprintf(err, "deviceatlas log level : expects an integer argument.\n");
+ return -1;
+ }
+
+ loglevel = atol(args[1]);
+ if (loglevel < 0 || loglevel > 3) {
+ memprintf(err, "deviceatlas log level : expects a log level between 0 and 3, %s given.\n", args[1]);
+ } else {
+ global_deviceatlas.loglevel = (da_severity_t)loglevel;
+ }
+
+ return 0;
+}
+
+static int da_property_separator(char **args, int section_type, struct proxy *curpx,
+ const struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+ if (*(args[1]) == 0) {
+ memprintf(err, "deviceatlas property separator : expects a character argument.\n");
+ return -1;
+ }
+ global_deviceatlas.separator = *args[1];
+ return 0;
+}
+
+static int da_properties_cookie(char **args, int section_type, struct proxy *curpx,
+ const struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+ if (*(args[1]) == 0) {
+ memprintf(err, "deviceatlas cookie name : expects a string argument.\n");
+ return -1;
+ } else {
+ global_deviceatlas.cookiename = strdup(args[1]);
+ }
+ global_deviceatlas.cookienamelen = strlen(global_deviceatlas.cookiename);
+ return 0;
+}
+
+static size_t da_haproxy_read(void *ctx, size_t len, char *buf)
+{
+ return fread(buf, 1, len, ctx);
+}
+
+static da_status_t da_haproxy_seek(void *ctx, off_t off)
+{
+ return fseek(ctx, off, SEEK_SET) != -1 ? DA_OK : DA_SYS;
+}
+
+static void da_haproxy_log(da_severity_t severity, da_status_t status,
+ const char *fmt, va_list args)
+{
+ if (global_deviceatlas.loglevel && severity <= global_deviceatlas.loglevel) {
+ char logbuf[256];
+ vsnprintf(logbuf, sizeof(logbuf), fmt, args);
+ ha_warning("deviceatlas : %s.\n", logbuf);
+ }
+}
+
+#define DA_COOKIENAME_DEFAULT "DAPROPS"
+
+/*
+ * module init / deinit functions. Returns 0 if OK, or a combination of ERR_*.
+ */
+static int init_deviceatlas(void)
+{
+ int err_code = ERR_NONE;
+
+ if (global_deviceatlas.jsonpath != 0) {
+ FILE *jsonp;
+ da_property_decl_t extraprops[] = {{0, 0}};
+ size_t atlasimglen;
+ da_status_t status;
+
+ jsonp = fopen(global_deviceatlas.jsonpath, "r");
+ if (jsonp == 0) {
+ ha_alert("deviceatlas : '%s' json file has invalid path or is not readable.\n",
+ global_deviceatlas.jsonpath);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+
+ da_init();
+ da_seterrorfunc(da_haproxy_log);
+ status = da_atlas_compile(jsonp, da_haproxy_read, da_haproxy_seek,
+ &global_deviceatlas.atlasimgptr, &atlasimglen);
+ fclose(jsonp);
+ if (status != DA_OK) {
+ ha_alert("deviceatlas : '%s' json file is invalid.\n",
+ global_deviceatlas.jsonpath);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+
+ status = da_atlas_open(&global_deviceatlas.atlas, extraprops,
+ global_deviceatlas.atlasimgptr, atlasimglen);
+
+ if (status != DA_OK) {
+ ha_alert("deviceatlas : data could not be compiled.\n");
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+
+ if (global_deviceatlas.cookiename == 0) {
+ global_deviceatlas.cookiename = strdup(DA_COOKIENAME_DEFAULT);
+ global_deviceatlas.cookienamelen = strlen(global_deviceatlas.cookiename);
+ }
+
+ global_deviceatlas.useragentid = da_atlas_header_evidence_id(&global_deviceatlas.atlas,
+ "user-agent");
+ if ((global_deviceatlas.atlasfd = shm_open(ATLASMAPNM, O_RDWR, 0660)) != -1) {
+ global_deviceatlas.atlasmap = mmap(NULL, ATLASTOKSZ, PROT_READ | PROT_WRITE, MAP_SHARED, global_deviceatlas.atlasfd, 0);
+ if (global_deviceatlas.atlasmap == MAP_FAILED) {
+ close(global_deviceatlas.atlasfd);
+ global_deviceatlas.atlasfd = -1;
+ global_deviceatlas.atlasmap = NULL;
+ } else {
+ fprintf(stdout, "Deviceatlas : scheduling support enabled.\n");
+ }
+ }
+ global_deviceatlas.daset = 1;
+
+ fprintf(stdout, "Deviceatlas module loaded.\n");
+ }
+
+out:
+ return err_code;
+}
+
+static void deinit_deviceatlas(void)
+{
+ if (global_deviceatlas.jsonpath != 0) {
+ free(global_deviceatlas.jsonpath);
+ }
+
+ if (global_deviceatlas.daset == 1) {
+ free(global_deviceatlas.cookiename);
+ da_atlas_close(&global_deviceatlas.atlas);
+ free(global_deviceatlas.atlasimgptr);
+ }
+
+ if (global_deviceatlas.atlasfd != -1) {
+ munmap(global_deviceatlas.atlasmap, ATLASTOKSZ);
+ close(global_deviceatlas.atlasfd);
+ shm_unlink(ATLASMAPNM);
+ }
+
+ da_fini();
+}
+
+static void da_haproxy_checkinst(void)
+{
+ if (global_deviceatlas.atlasmap != 0) {
+ char *base;
+ base = (char *)global_deviceatlas.atlasmap;
+
+ if (base[0] != 0) {
+ void *cnew;
+ size_t atlassz;
+ char atlasp[ATLASTOKSZ] = {0};
+ da_atlas_t inst;
+ da_property_decl_t extraprops[1] = {{NULL, 0}};
+#ifdef USE_THREAD
+ HA_SPIN_LOCK(OTHER_LOCK, &dadwsch_lock);
+#endif
+ strlcpy2(atlasp, base, sizeof(atlasp));
+ if (da_atlas_read_mapped(atlasp, NULL, &cnew, &atlassz) == DA_OK) {
+ if (da_atlas_open(&inst, extraprops, cnew, atlassz) == DA_OK) {
+ char jsonbuf[26];
+ time_t jsond;
+
+ da_atlas_close(&global_deviceatlas.atlas);
+ free(global_deviceatlas.atlasimgptr);
+ global_deviceatlas.atlasimgptr = cnew;
+ global_deviceatlas.atlas = inst;
+ memset(base, 0, ATLASTOKSZ);
+ jsond = da_getdatacreation(&global_deviceatlas.atlas);
+ ctime_r(&jsond, jsonbuf);
+ jsonbuf[24] = 0;
+ printf("deviceatlas: new instance, data file date `%s`.\n", jsonbuf);
+ } else {
+ ha_warning("deviceatlas: instance update failed.\n");
+ memset(base, 0, ATLASTOKSZ);
+ free(cnew);
+ }
+ }
+#ifdef USE_THREAD
+ HA_SPIN_UNLOCK(OTHER_LOCK, &dadwsch_lock);
+#endif
+ }
+ }
+}
+
+static int da_haproxy(const struct arg *args, struct sample *smp, da_deviceinfo_t *devinfo)
+{
+ struct buffer *tmp;
+ da_propid_t prop, *pprop;
+ da_status_t status;
+ da_type_t proptype;
+ const char *propname;
+ int i;
+
+ tmp = get_trash_chunk();
+ chunk_reset(tmp);
+
+ propname = (const char *) args[0].data.str.area;
+ i = 0;
+
+ for (; propname != 0; i ++,
+ propname = (const char *) args[i].data.str.area) {
+ status = da_atlas_getpropid(&global_deviceatlas.atlas,
+ propname, &prop);
+ if (status != DA_OK) {
+ chunk_appendf(tmp, "%c", global_deviceatlas.separator);
+ continue;
+ }
+ pprop = &prop;
+ da_atlas_getproptype(&global_deviceatlas.atlas, *pprop, &proptype);
+
+ switch (proptype) {
+ case DA_TYPE_BOOLEAN: {
+ bool val;
+ status = da_getpropboolean(devinfo, *pprop, &val);
+ if (status == DA_OK) {
+ chunk_appendf(tmp, "%d", val);
+ }
+ break;
+ }
+ case DA_TYPE_INTEGER:
+ case DA_TYPE_NUMBER: {
+ long val;
+ status = da_getpropinteger(devinfo, *pprop, &val);
+ if (status == DA_OK) {
+ chunk_appendf(tmp, "%ld", val);
+ }
+ break;
+ }
+ case DA_TYPE_STRING: {
+ const char *val;
+ status = da_getpropstring(devinfo, *pprop, &val);
+ if (status == DA_OK) {
+ chunk_appendf(tmp, "%s", val);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ chunk_appendf(tmp, "%c", global_deviceatlas.separator);
+ }
+
+ da_close(devinfo);
+
+ if (tmp->data) {
+ --tmp->data;
+ tmp->area[tmp->data] = 0;
+ }
+
+ smp->data.u.str.area = tmp->area;
+ smp->data.u.str.data = tmp->data;
+ smp->data.type = SMP_T_STR;
+
+ return 1;
+}
+
+static int da_haproxy_conv(const struct arg *args, struct sample *smp, void *private)
+{
+ da_deviceinfo_t devinfo;
+ da_status_t status;
+ const char *useragent;
+ char useragentbuf[1024] = { 0 };
+ int i;
+
+ if (global_deviceatlas.daset == 0 || smp->data.u.str.data == 0) {
+ return 1;
+ }
+
+ da_haproxy_checkinst();
+
+ i = smp->data.u.str.data > sizeof(useragentbuf) ? sizeof(useragentbuf) : smp->data.u.str.data;
+ memcpy(useragentbuf, smp->data.u.str.area, i - 1);
+ useragentbuf[i - 1] = 0;
+
+ useragent = (const char *)useragentbuf;
+
+ status = da_search(&global_deviceatlas.atlas, &devinfo,
+ global_deviceatlas.useragentid, useragent, 0);
+
+ return status != DA_OK ? 0 : da_haproxy(args, smp, &devinfo);
+}
+
+#define DA_MAX_HEADERS 24
+
+static int da_haproxy_fetch(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+ da_evidence_t ev[DA_MAX_HEADERS];
+ da_deviceinfo_t devinfo;
+ da_status_t status;
+ struct channel *chn;
+ struct htx *htx;
+ struct htx_blk *blk;
+ char vbuf[DA_MAX_HEADERS][1024] = {{ 0 }};
+ int i, nbh = 0;
+
+ if (global_deviceatlas.daset == 0) {
+ return 0;
+ }
+
+ da_haproxy_checkinst();
+
+ chn = (smp->strm ? &smp->strm->req : NULL);
+ htx = smp_prefetch_htx(smp, chn, NULL, 1);
+ if (!htx)
+ return 0;
+
+ i = 0;
+ for (blk = htx_get_first_blk(htx); nbh < DA_MAX_HEADERS && blk; blk = htx_get_next_blk(htx, blk)) {
+ size_t vlen;
+ char *pval;
+ da_evidence_id_t evid;
+ enum htx_blk_type type;
+ struct ist n, v;
+ char hbuf[24] = { 0 };
+ char tval[1024] = { 0 };
+
+ type = htx_get_blk_type(blk);
+
+ if (type == HTX_BLK_HDR) {
+ n = htx_get_blk_name(htx, blk);
+ v = htx_get_blk_value(htx, blk);
+ } else if (type == HTX_BLK_EOH) {
+ break;
+ } else {
+ continue;
+ }
+
+ /* The HTTP headers used by the DeviceAtlas API are not longer */
+ if (n.len >= sizeof(hbuf)) {
+ continue;
+ }
+
+ memcpy(hbuf, n.ptr, n.len);
+ hbuf[n.len] = 0;
+ pval = v.ptr;
+ vlen = v.len;
+ evid = -1;
+ i = v.len > sizeof(tval) - 1 ? sizeof(tval) - 1 : v.len;
+ memcpy(tval, v.ptr, i);
+ tval[i] = 0;
+ pval = tval;
+
+ if (strcasecmp(hbuf, "Accept-Language") == 0) {
+ evid = da_atlas_accept_language_evidence_id(&global_deviceatlas.atlas);
+ } else if (strcasecmp(hbuf, "Cookie") == 0) {
+ char *p, *eval;
+ size_t pl;
+
+ eval = pval + vlen;
+ /**
+ * The cookie value, if it exists, is located between the current header's
+ * value position and the next one
+ */
+ if (http_extract_cookie_value(pval, eval, global_deviceatlas.cookiename,
+ global_deviceatlas.cookienamelen, 1, &p, &pl) == NULL) {
+ continue;
+ }
+
+ vlen -= global_deviceatlas.cookienamelen - 1;
+ pval = p;
+ evid = da_atlas_clientprop_evidence_id(&global_deviceatlas.atlas);
+ } else {
+ evid = da_atlas_header_evidence_id(&global_deviceatlas.atlas, hbuf);
+ }
+
+ if (evid == -1) {
+ continue;
+ }
+
+ i = vlen > sizeof(vbuf[nbh]) - 1 ? sizeof(vbuf[nbh]) - 1 : vlen;
+ memcpy(vbuf[nbh], pval, i);
+ vbuf[nbh][i] = 0;
+ ev[nbh].key = evid;
+ ev[nbh].value = vbuf[nbh];
+ ++ nbh;
+ }
+
+ status = da_searchv(&global_deviceatlas.atlas, &devinfo,
+ ev, nbh);
+
+ return status != DA_OK ? 0 : da_haproxy(args, smp, &devinfo);
+}
+
+static struct cfg_kw_list dacfg_kws = {{ }, {
+ { CFG_GLOBAL, "deviceatlas-json-file", da_json_file },
+ { CFG_GLOBAL, "deviceatlas-log-level", da_log_level },
+ { CFG_GLOBAL, "deviceatlas-property-separator", da_property_separator },
+ { CFG_GLOBAL, "deviceatlas-properties-cookie", da_properties_cookie },
+ { 0, NULL, NULL },
+}};
+
+INITCALL1(STG_REGISTER, cfg_register_keywords, &dacfg_kws);
+
+/* Note: must not be declared <const> as its list will be overwritten */
+static struct sample_fetch_kw_list fetch_kws = {ILH, {
+ { "da-csv-fetch", da_haproxy_fetch, ARG12(1,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR), NULL, SMP_T_STR, SMP_USE_HRQHV },
+ { NULL, NULL, 0, 0, 0 },
+}};
+
+INITCALL1(STG_REGISTER, sample_register_fetches, &fetch_kws);
+
+/* Note: must not be declared <const> as its list will be overwritten */
+static struct sample_conv_kw_list conv_kws = {ILH, {
+ { "da-csv-conv", da_haproxy_conv, ARG12(1,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR), NULL, SMP_T_STR, SMP_T_STR },
+ { NULL, NULL, 0, 0, 0 },
+}};
+
+static void da_haproxy_register_build_options()
+{
+ char *ptr = NULL;
+
+#ifdef MOBI_DA_DUMMY_LIBRARY
+ memprintf(&ptr, "Built with DeviceAtlas support (dummy library only).");
+#else
+ memprintf(&ptr, "Built with DeviceAtlas support (library version %u.%u).", MOBI_DA_MAJOR, MOBI_DA_MINOR);
+#endif
+ hap_register_build_opts(ptr, 1);
+}
+
+INITCALL1(STG_REGISTER, sample_register_convs, &conv_kws);
+
+REGISTER_POST_CHECK(init_deviceatlas);
+REGISTER_POST_DEINIT(deinit_deviceatlas);
+INITCALL0(STG_REGISTER, da_haproxy_register_build_options);
diff --git a/addons/deviceatlas/dadwsch.c b/addons/deviceatlas/dadwsch.c
new file mode 100644
index 0000000..e35566a
--- /dev/null
+++ b/addons/deviceatlas/dadwsch.c
@@ -0,0 +1,195 @@
+#define _GNU_SOURCE
+#include <dac.h>
+#include <dadwcurl.h>
+#include <dadwarc.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+
+#define ATLASTOKSZ PATH_MAX
+#define ATLASMAPNM "/hapdeviceatlas"
+
+const char *__pgname;
+
+static struct {
+ da_dwatlas_t o;
+ int ofd;
+ void* atlasmap;
+} global_deviceatlassch = {
+ .ofd = -1,
+ .atlasmap = NULL
+};
+
+
+void usage(void)
+{
+ fprintf(stderr, "%s -u download URL [-d hour (in H:M:S format) current hour by default] [-p path for the downloaded file, /tmp by default]\n", __pgname);
+ exit(EXIT_FAILURE);
+}
+
+static size_t jsonread(void *ctx, size_t count, char *buf)
+{
+ return fread(buf, 1, count, ctx);
+}
+
+static da_status_t jsonseek(void *ctx, off_t pos)
+{
+ return fseek(ctx, pos, SEEK_SET) != -1 ? DA_OK : DA_SYS;
+}
+
+static void dadwlog(dw_config_t cfg, const char* msg)
+{
+ time_t now = time(NULL);
+ char buf[26] = {0};
+ ctime_r(&now, buf);
+ buf[24] = 0;
+ fprintf(stderr, "%s: %s\n", buf, msg);
+}
+
+static dw_status_t dadwnot(void *a, dw_config_t *cfg)
+{
+ da_dwatlas_t *o = (da_dwatlas_t *)a;
+ if (!o)
+ return DW_ERR;
+ char *e;
+ char jsondbuf[26] = {0}, buf[26] = {0}, atlasp[ATLASTOKSZ] = {0};
+ time_t now = time(NULL);
+ time_t jsond;
+ int fd = -1;
+ (void)a;
+ jsond = da_getdatacreation(&o->atlas);
+ dwgetfinalp(o->dcfg.info, atlasp, sizeof(atlasp));
+ ctime_r(&jsond, jsondbuf);
+ ctime_r(&now, buf);
+ jsondbuf[24] = 0;
+ buf[24] = 0;
+
+ printf("%s: data file generated on `%s`\n", buf, jsondbuf);
+ int val = 1;
+ unsigned char *ptr = (unsigned char *)global_deviceatlassch.atlasmap;
+ memset(ptr, 0, sizeof(atlasp));
+ strcpy(ptr, atlasp);
+ return DW_OK;
+}
+
+static da_status_t dadwinit(void)
+{
+ if ((global_deviceatlassch.ofd = shm_open(ATLASMAPNM, O_RDWR | O_CREAT, 0660)) == -1) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ return DA_SYS;
+ }
+
+ if (ftruncate(global_deviceatlassch.ofd, ATLASTOKSZ) == -1) {
+ close(global_deviceatlassch.ofd);
+ return DA_SYS;
+ }
+ lseek(global_deviceatlassch.ofd, 0, SEEK_SET);
+ global_deviceatlassch.atlasmap = mmap(0, ATLASTOKSZ, PROT_READ | PROT_WRITE, MAP_SHARED, global_deviceatlassch.ofd, 0);
+ if (global_deviceatlassch.atlasmap == MAP_FAILED) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ return DA_SYS;
+ } else {
+ memset(global_deviceatlassch.atlasmap, 0, ATLASTOKSZ);
+ return DA_OK;
+ }
+}
+
+static void dadwexit(int sig __attribute__((unused)), siginfo_t *s __attribute__((unused)), void *ctx __attribute__((unused)))
+{
+ ssize_t w;
+
+ fprintf(stderr, "%s: exit\n", __pgname);
+ dw_daatlas_close(&global_deviceatlassch.o);
+ da_fini();
+ munmap(global_deviceatlassch.atlasmap, ATLASTOKSZ);
+ close(global_deviceatlassch.ofd);
+ shm_unlink(ATLASMAPNM);
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ const char *opts = "u:p:d:h";
+ bool dset = false;
+ size_t i;
+ int ch;
+
+ da_property_decl_t extraprops[1] = {
+ { 0, 0 }
+ };
+
+ __pgname = argv[0];
+
+ dw_df_dainit_fn = curldwinit;
+ dw_df_dacleanup_fn = curldwcleanup;
+
+ da_init();
+ memset(&global_deviceatlassch.o.dcfg, 0, sizeof(global_deviceatlassch.o.dcfg));
+ while ((ch = getopt(argc, argv, opts)) != -1) {
+ switch (ch) {
+ case 'u':
+ global_deviceatlassch.o.dcfg.info.url = strdup(optarg);
+ break;
+ case 'p':
+ global_deviceatlassch.o.dcfg.info.path = strdup(optarg);
+ break;
+ case 'd':
+ if (strptime(optarg, "%H:%M:%S", &global_deviceatlassch.o.dcfg.info.rtm) != NULL)
+ dset = true;
+ else
+ usage();
+ break;
+ case 'h':
+ default:
+ usage();
+ }
+ }
+
+ if (!dset) {
+ time_t now = time(NULL);
+ struct tm *cnow = gmtime(&now);
+ memcpy(&global_deviceatlassch.o.dcfg.info.rtm, cnow, offsetof(struct tm, tm_mday));
+ }
+
+ if (!global_deviceatlassch.o.dcfg.info.url)
+ usage();
+
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_flags = SA_SIGINFO | SA_RESTART;
+ sa.sa_sigaction = dadwexit;
+
+ global_deviceatlassch.o.dcfg.info.datatm = 1;
+ global_deviceatlassch.o.dcfg.info.chksum = 1;
+ global_deviceatlassch.o.dcfg.info.reload = 1;
+ global_deviceatlassch.o.dcfg.info.tobin = 1;
+ global_deviceatlassch.o.dcfg.ep = extraprops;
+ global_deviceatlassch.o.dcfg.dwproc = curldwproc;
+ global_deviceatlassch.o.dcfg.dwextract = dadwextract;
+ global_deviceatlassch.o.dcfg.lptr = (void *)stderr;
+ global_deviceatlassch.o.dcfg.dwlog = &dadwlog;
+ global_deviceatlassch.o.dcfg.dwnotify_n = &dadwnot;
+ global_deviceatlassch.o.rfn = jsonread;
+ global_deviceatlassch.o.posfn = jsonseek;
+
+ if (dadwinit() != DA_OK) {
+ fprintf(stderr, "%s init failed\n", __pgname);
+ exit(EXIT_FAILURE);
+ }
+
+ if (da_atlas_open_schedule(&global_deviceatlassch.o) != DA_OK) {
+ fprintf(stderr, "%s scheduling failed\n", __pgname);
+ exit(EXIT_FAILURE);
+ }
+
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGQUIT, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+
+ while (true) sleep(1);
+
+ return 0;
+}
diff --git a/addons/deviceatlas/dummy/Makefile b/addons/deviceatlas/dummy/Makefile
new file mode 100644
index 0000000..8bba840
--- /dev/null
+++ b/addons/deviceatlas/dummy/Makefile
@@ -0,0 +1,12 @@
+# makefile for dummy DeviceAtlas library
+#
+# To enable the DeviceAtlas module support, the following are needed
+# make TARGET=<target> DEVICEATLAS_SRC=addons/deviceatlas/dummy USE_PCRE=1 USE_DEVICEATLAS=1
+
+build: libda.a
+
+libda.a: dac.o
+ ar rv $@ $<
+
+clean:
+ rm -rf *.a *.o
diff --git a/addons/deviceatlas/dummy/Os/daunix.c b/addons/deviceatlas/dummy/Os/daunix.c
new file mode 100644
index 0000000..ca696f9
--- /dev/null
+++ b/addons/deviceatlas/dummy/Os/daunix.c
@@ -0,0 +1,9 @@
+#include "dac.h"
+
+static char const __attribute__((unused)) rcsid[] = "$Id: dac.c, v dummy 1970/01/01 00:00:01 dcarlier Exp $";
+
+da_status_t
+da_atlas_read_mapped(const char *path, void *m, void **p, size_t *l)
+{
+ return DA_SYS;
+}
diff --git a/addons/deviceatlas/dummy/dac.c b/addons/deviceatlas/dummy/dac.c
new file mode 100644
index 0000000..720dc6a
--- /dev/null
+++ b/addons/deviceatlas/dummy/dac.c
@@ -0,0 +1,222 @@
+#include "dac.h"
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+static char const __attribute__((unused)) rcsid[] = "$Id: dac.c, v dummy 1970/01/01 00:00:01 dcarlier Exp $";
+
+struct da_bitset {
+ unsigned long bits[8];
+ size_t bit_count;
+};
+
+/*
+ * Constructor/Destructor for possible globals.
+ */
+
+void
+da_init()
+{
+}
+
+void
+da_fini()
+{
+}
+
+
+void
+da_seterrorfunc(da_errorfunc_t callback)
+{
+}
+
+const char *
+da_typename(da_type_t fieldtype)
+{
+ return "none";
+}
+
+char *
+da_getdataversion(da_atlas_t *atlas)
+{
+ return "dummy library version 1.0";
+}
+
+time_t
+da_getdatacreation(da_atlas_t *atlas)
+{
+ return time(NULL);
+}
+
+int
+da_getdatarevision(da_atlas_t *atlas)
+{
+ return 1;
+}
+
+da_status_t
+da_atlas_compile(void *ctx, da_read_fn readfn, da_setpos_fn rewind, void **ptr, size_t *size)
+{
+ return DA_OK;
+}
+
+da_status_t
+da_atlas_open(da_atlas_t *atlas, da_property_decl_t *extraprops, const void *ptr, size_t len)
+{
+ void *ptr2 = malloc(len);
+ free(ptr2);
+ return ptr2 ? DA_OK : DA_NOMEM;
+}
+
+void
+da_atlas_close(da_atlas_t *atlas)
+{
+}
+
+da_evidence_id_t
+da_atlas_clientprop_evidence_id(const da_atlas_t *atlas)
+{
+ return (da_evidence_id_t)2;
+}
+
+da_evidence_id_t
+da_atlas_accept_language_evidence_id(const da_atlas_t *atlas)
+{
+ return (da_evidence_id_t)3;
+}
+
+da_evidence_id_t
+da_atlas_header_evidence_id(const da_atlas_t *atlas, const char *evidence_name)
+{
+ return (da_evidence_id_t)1;
+}
+
+da_status_t
+da_atlas_getproptype(const da_atlas_t *atlas, da_propid_t propid, da_type_t *type)
+{
+ *type = DA_TYPE_BOOLEAN;
+ return DA_OK;
+}
+
+da_status_t
+da_atlas_getpropname(const da_atlas_t *atlas, da_propid_t propid, const char **name)
+{
+ *name = "isRobot";
+ return DA_OK;
+}
+
+da_status_t
+da_atlas_getpropid(const da_atlas_t *atlas, const char *propname, da_propid_t *property)
+{
+ *property = (da_propid_t)1;
+ return DA_OK;
+}
+
+size_t
+da_atlas_getpropcount(const da_atlas_t *atlas)
+{
+ return 1;
+}
+
+void
+da_atlas_setconfig(da_atlas_t *atlas, da_config_t *config)
+{
+}
+
+da_status_t
+da_searchv(const da_atlas_t *atlas, da_deviceinfo_t *result, da_evidence_t *evidence, size_t count)
+{
+ memset(result, 0, sizeof(*result));
+ result->propcount = count;
+ return DA_OK;
+}
+
+da_status_t
+da_search(const da_atlas_t *atlas, da_deviceinfo_t *result, ...)
+{
+ da_evidence_t vec[4]; /* XXX: this will have to grow if more evidence is supported. */
+ size_t i;
+ va_list args;
+ va_start(args, result);
+ for (i = 0; i < sizeof vec / sizeof vec[0];) {
+ vec[i].key = va_arg(args, da_evidence_id_t);
+ if (vec[i].key == 0)
+ break;
+ vec[i++].value = va_arg(args, char *);
+ }
+ va_end(args);
+ return da_searchv(atlas, result, vec, i);
+}
+
+/*
+ * Search-result centric functions.
+ */
+size_t
+da_getpropcount(const da_deviceinfo_t *info)
+{
+ return info->propcount;
+}
+
+da_status_t
+da_getfirstprop(const da_deviceinfo_t *info, da_propid_t **propid)
+{
+ if (info->propcount == 0)
+ return DA_NOMORE;
+ *propid = &info->proplist[0];
+ return DA_OK;
+}
+
+da_status_t
+da_getnextprop(const da_deviceinfo_t *info, da_propid_t **propid)
+{
+ if (*propid - info->proplist >= info->propcount - 1)
+ return DA_NOMORE;
+ ++*propid;
+ return DA_OK;
+}
+
+void
+da_close(da_deviceinfo_t *sr)
+{
+}
+
+da_status_t
+da_getpropname(const da_deviceinfo_t *info, da_propid_t propid, const char **name)
+{
+ *name = "isRobot";
+ return DA_OK;
+}
+
+da_status_t
+da_getproptype(const da_deviceinfo_t *info, da_propid_t propid, da_type_t *type)
+{
+ *type = DA_TYPE_BOOLEAN;
+ return DA_OK;
+}
+
+da_status_t
+da_getpropinteger(const da_deviceinfo_t *info, da_propid_t property, long *vp)
+{
+ *vp = -1;
+ return DA_OK;
+}
+
+da_status_t
+da_getpropstring(const da_deviceinfo_t *info, da_propid_t property, const char **vp)
+{
+ *vp = NULL;
+ return DA_OK;
+}
+
+da_status_t
+da_getpropboolean(const da_deviceinfo_t *info, da_propid_t property, bool *vp)
+{
+ *vp = true;
+ return DA_OK;
+}
+
+const char *
+da_get_property_name(const da_atlas_t *atlas, da_propid_t property)
+{
+ return "isRobot";
+}
diff --git a/addons/deviceatlas/dummy/dac.h b/addons/deviceatlas/dummy/dac.h
new file mode 100644
index 0000000..bf166ae
--- /dev/null
+++ b/addons/deviceatlas/dummy/dac.h
@@ -0,0 +1,600 @@
+#ifndef MOBI_DA_DAC_H
+#define MOBI_DA_DAC_H
+
+/**
+ * @file dac.h
+ * @author Afilias Technologies
+ *
+ * @brief API main header file
+ */
+
+#include <sys/types.h>
+#include <limits.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#ifndef __cplusplus
+#ifndef true
+#ifdef HAVE_NO_BUILTIN__BOOL
+typedef int _Bool;
+#endif
+#define bool _Bool
+
+#define true 1
+#define false 0
+#endif
+#endif
+
+#define MOBI_DA_MAJOR 2
+#define MOBI_DA_MINOR 1
+#define MOBI_DA_DUMMY_LIBRARY 1
+
+
+/**
+ * @brief All values returned by the API have one of these types.
+ * da_getprop*() return data in the appropriate C type for the given da_type.
+ */
+enum da_type {
+ DA_TYPE_NONE,
+ DA_TYPE_BOOLEAN,
+ DA_TYPE_INTEGER,
+ DA_TYPE_NUMBER,
+ DA_TYPE_STRING,
+ DA_TYPE_ARRAY,
+ DA_TYPE_OBJECT,
+ DA_TYPE_NULL
+};
+
+/**
+ * Any method that returns a da_status may potentially fail for one of these reasons.
+ * XXX: Error reporting needs to be improved.
+ */
+enum da_status {
+ DA_OK, /* Success. */
+ DA_INVALID_JSON, /* The JSON format is invalid, or the content is unexpected in a given context. */
+ DA_OVERFLOW, /* Overflow occurred. Note this is used to indicate an unfinished string parse in JSON */
+ DA_FORMAT_ERROR, /* The data supplied is formatted incorrectly. */
+ DA_NOMEM, /* There was not enough space to complete the operation */
+ DA_SYS, /* A system error occurred - consult the OS for more details (eg, check errno) */
+ DA_NOTIMPL, /* This method is not implemented */
+ DA_NOTFOUND, /* The requested item was not found. */
+ DA_REGEXBAD, /* An invalid regex was provided. */
+ DA_NOMORE, /* Used to indicate the end of an iterator. */
+ DA_INVALID_COOKIE, /* Cookie value supplied was invalid */
+ DA_INVALID_TYPE, /* A value of an unexpected type was found. */
+ DA_INTERNAL_ERROR,
+ DA_STATUS_LAST /* Placeholder to indicate highest possible error value. (value will change as API matures) */
+};
+
+enum da_severity {
+ DA_SEV_FATAL, /* The operation will not continue, and the operation will return an error. */
+ DA_SEV_ERROR, /* An error occurred, but the API call will return at least some valid information */
+ DA_SEV_WARN, /* An unexpected event occurred, but the system dealt with it */
+ DA_SEV_INFO /* An informational message. */
+};
+/* Forward references to tagged types */
+struct atlas_image;
+struct da_atlas;
+struct da_deviceinfo;
+struct da_jsonparser;
+struct da_node;
+struct da_propset;
+union da_value;
+struct da_evidence;
+struct da_bitset;
+struct da_allocator;
+struct da_config;
+
+/**
+ * @brief Primary types of the interface.
+ * Primary types used by API client.
+ * Non-typedef structures and unions are considered private to the API.
+ *
+ */
+typedef enum da_severity da_severity_t; /* A severity for the error callback. */
+typedef enum da_status da_status_t; /* An error code - returned from most API calls. */
+typedef da_status_t (*da_setpos_fn)(void *ctx, off_t off); /* callback provided to API to rewind input stream */
+typedef enum da_type da_type_t; /* A value type (integer, string, etc) */
+
+/**
+ * @brief An operation on an atlas involves converting a set of evidence strings into a set of property/value pairs.
+ * The ID for a particular type of evidence is extract from the atlas (eg, for a specific HTTP header, use:
+ *
+ * da_evidence_id_t evidence = da_atlas_header_evidence_id(atlas, "User-Agent");
+ *
+ */
+typedef int da_evidence_id_t;
+
+/**
+ * @brief The search result encompasses a key/value set. Keys are handles retrieved via
+ * _either_ da_atlas_getpropid() or da_getpropid().
+ * Some search results may have keys not available when the atlas is opened (eg,
+ * when the name of the property itself is contained within the evidence)
+ * Such properties by necessity are given a "local" da_propid_t
+ *
+ * You can ensure any properties you are interested in get a global propid by
+ * passing a list of interesting named properties to da_atlas_open()
+ */
+typedef int da_propid_t;
+typedef size_t (*da_read_fn)(void *ctx, size_t maxlen, char *ptr);
+typedef struct da_atlas da_atlas_t;
+typedef struct da_deviceinfo da_deviceinfo_t;
+typedef struct da_evidence da_evidence_t;
+typedef struct da_jsonparser da_jsonparser_t;
+typedef struct da_node da_node_t;
+typedef struct da_property_decl da_property_decl_t;
+typedef struct da_propset da_propset_t;
+typedef struct da_config da_config_t;
+typedef void *(*da_alloc_fn)(void *ctx, size_t);
+typedef void (*da_free_fn)(void *ctx, void *);
+typedef void *(*da_realloc_fn)(void *ctx, void *, size_t);
+typedef void (*da_errorfunc_t)(da_severity_t severity, da_status_t status, const char *msg, va_list args);
+
+
+/* Manifest constants. */
+enum {
+ /*
+ * used as the initial guess for the compiled size of an atlas.
+ * If atlas sizes grow more beyond this, it can be expanded to avoid multiple scans of the data.
+ */
+ DA_INITIAL_MEMORY_ESTIMATE = 1024 * 1024 * 14
+};
+
+struct da_config {
+ unsigned int ua_props;
+ unsigned int lang_props;
+ unsigned int __reserved[14]; /* enough reserved keywords for future use */
+};
+
+/**
+ * Functional interface.
+ */
+
+/**
+ * @brief Initialize process to use the DA API.
+ */
+void da_init(void);
+
+
+/**
+ * @brief Release all resources used by the API
+ */
+void da_fini(void);
+
+/**
+ * @brief User-supplied callback to be invoked with information about an error.
+ * Note this may use thread-local storage etc to store the info on return from the current call
+ * It is guaranteed that an error-reporting function returning an error-code will have called
+ * this function at least once.
+ * @param callback function
+ */
+void da_seterrorfunc(da_errorfunc_t callback);
+
+/**
+ * @brief Given a specific HTTP header, return the associated ID for that header.
+ * When passing evidence to the API, its type is identified using its da_evidince_id_t.
+ * @param atlas atlas instance
+ * @param header_name Header's name
+ * @return evidence id
+ */
+da_evidence_id_t da_atlas_header_evidence_id(const da_atlas_t *atlas, const char *header_name);
+/**
+ * @brief Return the associated ID of the client side properties evidence
+ * @param atlas Atlas instance
+ * @return evidence id
+ */
+da_evidence_id_t da_atlas_clientprop_evidence_id(const da_atlas_t *atlas);
+/**
+ * @brief Return the associated ID of the accept language header evidence
+ * @param atlas Atlas instance
+ * @return evidence id
+ */
+da_evidence_id_t da_atlas_accept_language_evidence_id(const da_atlas_t *atlas);
+
+/**
+ * @brief readfn should present JSON content from ctx.
+ * atlasp points to an uninitialized da_atlas structure.
+ * Result is a compiled atlas at atlasp.
+ * Result is allocated via normal memory-allocation methods, malloc/calloc/realloc, so should be
+ * Free'd with free()
+ * XXX TODO: Change this to take a da_allocator
+ * @param ctx pointer given to read the json file
+ * @param readfn function pointer, set accordingly to the attended given pointer
+ * @param setposfn function pointer
+ * @param ptr Pointer dynamically allocated if the json parsing happened normally
+ * @param len size of the atlas image
+ * @return status of atlas compilation
+ */
+da_status_t da_atlas_compile(void *ctx, da_read_fn readfn, da_setpos_fn setposfn, void **ptr, size_t *len);
+
+/**
+ * @brief opens a previously compiled atlas for operations. extra_props will be available in calls to
+ * da_getpropid on the atlas, and if generated by the search, the ID will be consistent across
+ * different calls to search.
+ * Properties added by a search that are neither in the compiled atlas, nor in the extra_props list
+ * Are assigned an ID within the context that is not transferrable through different search results
+ * within the same atlas.
+ * @param atlas Atlas instance
+ * @param extra_props properties
+ * @param ptr given pointer from previously compiled atlas
+ * @param pos atlas image size
+ * @return status of atlas data opening
+ */
+da_status_t da_atlas_open(da_atlas_t *atlas, da_property_decl_t *extra_props, const void *ptr, size_t pos);
+
+/**
+ * @brief read from a mapped data which then replace da_atlas_compile call
+ *
+ * @param dumppath, anonymous if NULL
+ * @param map for anonymous, it is the responsibility of the caller to unmap it, ignored otherwise
+ * @param maplen for anonymous, it is the size of the mapped data, ignored otherwise
+ * @param ptr Pointer dynamically allocated if the mapping happened normally
+ * @param len size of the atlas image
+ * @return status of mapping
+ */
+da_status_t da_atlas_read_mapped(const char *path, void *m, void **p, size_t *l);
+/**
+ * @brief Release any resources associated with the atlas structure atlas, which was previously generated from
+ * da_read_atlas or da_compile_atlas.
+ * @param atlas instance
+ */
+void da_atlas_close(da_atlas_t *atlas);
+
+/**
+ * @brief Find device properties given a set of evidence.
+ * Search results are returned in da_deviceinfo_t, and must be cleaned using da_close
+ * "Evidence" is an array of length count, of string data tagged with an evidence ID.
+ * @param atlas Atlas instance
+ * @param info Device info
+ * @param ev Array of evidences
+ * @param count Number of evidence given
+ * @return status of the search
+ */
+da_status_t da_searchv(const da_atlas_t *atlas, da_deviceinfo_t *info, da_evidence_t *ev, size_t count);
+
+/**
+ * @brief As da_search, but unrolls the evidence array into variable arguments for simpler calling
+ * convention with known evidence types.
+ * varargs are pairs of (da_evidence_id, string), terminated with da_evidence_id DA_END
+ * @code da_search(&myAtlas, &deviceInfo, da_get_header_evidence_id("User-Agent"),
+ * "Mozilla/5.0 (Linux...", DA_END);
+ * @endcode
+ * @param atlas Atlas instance
+ * @param info given device info which holds on device properties
+ * @param pairs of evidence id / evidence value
+ * @return status of the search
+ */
+da_status_t da_search(const da_atlas_t *atlas, da_deviceinfo_t *info, ...);
+
+/**
+ * @brief After finishing with a search result, release resources associated with it.
+ * @param info Device info previously allocated by search functions
+ */
+void da_close(da_deviceinfo_t *info);
+
+/**
+ * @brief Given a property name (Eg, "displayWidth"), return the property ID associated with it for the
+ * specified atlas.
+ * @param atlas Atlas instance
+ * @param propname Property name
+ * @param propid Property id
+ * @return status of the property id search
+ */
+da_status_t da_atlas_getpropid(const da_atlas_t *atlas, const char *propname, da_propid_t *propid);
+
+/**
+ * @brief Given a property ID, return the type of that property.
+ * @code
+ * da_getproptype(&myAtlas, da_getpropid(&myAtlas, "displayWidth"), &propertyType);
+ * assert(propertyType == DA_TYPE_INT);
+ * @endcode
+ * @param atlas Atlas instance
+ * @param propid Property id
+ * @param type Type id of the property
+ * @return status of the type id search
+ */
+da_status_t da_atlas_getproptype(const da_atlas_t *atlas, da_propid_t propid, da_type_t *type);
+
+/**
+ * @brief Given a property ID, return the name of that property.
+ * @code
+ * da_atlas_getpropname(&myAtlas, da_getpropid(&myAtlas, "displayWidth"), &propertyName);
+ * assert(strcmp("displayWidth", propertyName) == 0);
+ * @endcode
+ * @param atlas Atlas instance
+ * @param propid property id
+ * @param propname property name returned
+ * @return status of the property name search
+ */
+da_status_t da_atlas_getpropname(const da_atlas_t *atlas, da_propid_t propid, const char **propname);
+
+
+/**
+ * @brief Given an atlas instance, return its counters + the builtins
+ * @code
+ * da_atlas_getpropcount(&myAtlas);
+ * @endcode
+ * @param atlas Atlas instance
+ * @return counters
+ */
+size_t da_atlas_getpropcount(const da_atlas_t *atlas);
+
+/**
+ * @brief Given an atlas instance, set the detection config
+ * @param atlas Atlas instance
+ * @param config instance
+ */
+void da_atlas_setconfig(da_atlas_t *atlas, da_config_t *config);
+
+/**
+ * @brief Given a search result, find the value of a specific property.
+ * @code
+ * long displayWidth; // width of display in pixels.
+ * da_getpropinteger(&deviceInfo, da_getpropid(&myAtlas, "displayWidth"), &displayWidth);
+ * @endcode
+ * String contents are owned by the search result, and are valid until the search is closed.
+ */
+/**
+ * @brief returns a property value as a string from a given string typed property id
+ * @param info Device info
+ * @param propid Property id
+ * @param value Value of the property
+ * @return status of property value search
+ */
+da_status_t da_getpropstring(const da_deviceinfo_t *info, da_propid_t propid, const char **value);
+/**
+ * @brief returns a property value as a long from a given long typed property id
+ * @param info Device info
+ * @param propid Property id
+ * @param value Value of the property
+ * @return status of property value search
+ */
+da_status_t da_getpropinteger(const da_deviceinfo_t *info, da_propid_t propid, long *value);
+/**
+ * @brief returns a property value as a boolean from a given boolean typed property id
+ * @param info Device info
+ * @param propid Property id
+ * @param value Value of the property
+ * @return status of property value search
+ */
+da_status_t da_getpropboolean(const da_deviceinfo_t *info, da_propid_t propid, bool *value);
+/**
+ * @brief returns a property value as a float from a given float typed property id
+ * @param info Device info
+ * @param propid Property id
+ * @param value Value of the property
+ * @return status of property value search
+ */
+da_status_t da_getpropfloat(const da_deviceinfo_t *info, da_propid_t propid, double *value);
+
+/**
+ * @brief Some properties may not be not known to the atlas before the search commences.
+ * Such properties cannot have a da_propid_t assigned to them on the atlas, but will
+ * have a local property assigned during search. The name and type of such properties
+ * can be discovered here.
+ *
+ * Properties that are used in the atlas source and properties specifically registered
+ * with da_atlas_open() will always be assigned to a property discovered during search.
+ * Therefore, if there are specific properties that you want to use, and are unsure
+ * if they are in your device atlas source, registering them with da_atlas_open will
+ * make access to them easier and more efficient
+ */
+/**
+ * @brief returns the type of a given device property from the search functions
+ * @param info Device info
+ * @param propid Property id
+ * @param type Type id
+ * @return status of property type search
+ */
+da_status_t da_getproptype(const da_deviceinfo_t *info, da_propid_t propid, da_type_t *type);
+/**
+ * @brief returns the name of a given device property from the search functions
+ * @param info Device info
+ * @param propid Property id
+ * @param propname Property name
+ * @return status of property type search
+ */
+da_status_t da_getpropname(const da_deviceinfo_t *info, da_propid_t propid, const char **propname);
+
+/**
+ * @brief da_getfirstprop/da_getnextprop provide iteration over all properties
+ * in a search result.
+ * Both will return DA_OK if there is a result available, and DA_NOMORE
+ * if the search is complete.
+ * @code
+ *
+ * da_propid_t *propidp;
+ * for (da_status_t status = da_getfirstprop(&result, &propidp);
+ * status == DA_OK;
+ * status = da_getnextprop(&result, &propidp)) {
+ * const char *propname;
+ * if (da_getpropname(&result, *propidp, &propname) == DA_OK)
+ * fprintf("found property %s\n", propname);
+ * }
+ * @endcode
+ */
+
+/**
+ * @brief returns the first property from device info
+ * @param info Device info
+ * @param propid Property
+ * @return status
+ */
+da_status_t da_getfirstprop(const da_deviceinfo_t *info, da_propid_t **propid);
+/**
+ * @brief device info properties iterator
+ * @param info Device info
+ * @param propid Property
+ * @return status
+ */
+da_status_t da_getnextprop(const da_deviceinfo_t *info, da_propid_t **propid);
+
+/**
+ * @brief Report an error, as per a report from the API to the user-callback.
+ * @param severity Severity level of the error
+ * @param fmt format error message
+ * @param va_list
+ * @return status
+ */
+da_status_t da_reporterror(da_status_t severity, const char *fmt, ...);
+
+/**
+ * @brief returns a textual description of the type "type".
+ * @param type Type id
+ * @return type name
+ */
+const char *da_typename(da_type_t type);
+
+/**
+ * @brief returns the version from the JSON in memory
+ * @param atlas
+ * @return version
+ */
+char *da_getdataversion(da_atlas_t *atlas);
+
+/**
+ * @brief returns the date creation's timestamp from the JSON in memory
+ * @param atlas
+ * @return version
+ */
+time_t da_getdatacreation(da_atlas_t *atlas);
+
+/**
+ * @brief returns the revision's number from the JSON in memory
+ * @param atlas
+ * @return version
+ */
+int da_getdatarevision(da_atlas_t *atlas);
+
+/**
+ * @brief returns the name of a global property
+ * @param atlas Atlas instance
+ * @param propid Property id
+ * @return property name
+ */
+const char *da_get_property_name(const da_atlas_t *atlas, da_propid_t propid);
+
+/**
+ * @brief returns the number of properties in a result.
+ * @param info Device info
+ * @return properties count
+ */
+size_t da_getpropcount(const da_deviceinfo_t *info);
+
+/*
+ * Details below should not be required for usage of the API
+ */
+
+/**
+ * @brief Represents a usable device atlas interface.
+ *
+ * No user servicable parts inside: access should
+ * be via the functional API.
+ */
+struct da_atlas {
+ const struct atlas_image *image;
+ struct header_evidence_entry *header_priorities;
+ size_t header_evidence_count;
+
+ struct pcre_regex_info *uar_regexes;
+ size_t uar_regex_count;
+
+ struct pcre_regex_info *replacement_regexes;
+ size_t replacement_regex_count;
+
+ da_evidence_id_t user_agent_evidence;
+ da_evidence_id_t clientprops_evidence;
+ da_evidence_id_t accept_language_evidence;
+ da_evidence_id_t next_evidence;
+
+ da_propset_t *properties;
+ da_propid_t id_propid;
+ da_propid_t id_proplang;
+ da_propid_t id_proplang_locale;
+
+ da_config_t config;
+
+ da_deviceinfo_t **cpr_props;
+ size_t cpr_count;
+};
+
+/* fixed constants. */
+enum {
+ DA_BUFSIZE = 16000
+};
+
+/**
+ * Represents a chunk of memory. See comments on da_deviceinfo.
+ * This is presented here to allow aggregation in da_deviceinfo:
+ * Not for public consumption.
+ */
+struct da_buf {
+ struct da_buf *next;
+ char *cur;
+ char *limit;
+ char buf[DA_BUFSIZE];
+};
+
+/**
+ * A callback interface for allocating memory from some source
+ * Not for public consumption.
+ */
+struct da_allocator {
+ da_alloc_fn alloc;
+ da_free_fn free;
+ da_realloc_fn realloc;
+ void *context;
+};
+
+
+/**
+ * Represents a search result
+ * Can be used to retrieve values of known properties discovered from the evidence,
+ * iterate over the properties with known values, and query property types that are
+ * local to this result.
+ *
+ * The atlas the search is carried out on must survive any da_deviceinfo results
+ * it provides.
+ */
+struct da_deviceinfo {
+ struct da_allocator allocator;
+ const da_atlas_t *atlas; /* reference to the atlas the search was carried out on. */
+ struct da_bitset *present; /* property received from tree */
+ struct da_bitset *localprop; /* property was received from UAR rule or CPR */
+ struct da_bitset *cprprop; /* property was received from CPR */
+ union da_value *properties; /* properties - indexed by property id. */
+ da_propid_t *proplist; /* list of properties present in this result. */
+ size_t propcount; /* size of proplist */
+ da_propset_t *local_types; /* property descriptors local to this search result. */
+
+ /**
+ * The per-deviceinfo heap is stored here. Allocations for data in the result
+ * come from the raw data in these buffers. The size of the fixed-size buffer
+ * built in to da_buf is sized such that all known search results will not
+ * require memory allocation via malloc()
+ */
+ struct da_buf *heap;
+ struct da_buf initial_heap;
+};
+
+/**
+ * Used to pass evidence to da_searchv()
+ */
+struct da_evidence {
+ da_evidence_id_t key;
+ char *value;
+};
+
+/**
+ * Used to pass properties the API intends to query to the da_atlas_open function
+ * This can be used to improve performance of lookup on properties well-known
+ * to the API user, but not present in the JSON database.
+ */
+struct da_property_decl {
+ const char *name;
+ da_type_t type;
+};
+
+
+#endif /* DEVATLAS_DAC_H */
diff --git a/addons/deviceatlas/dummy/dadwcom.c b/addons/deviceatlas/dummy/dadwcom.c
new file mode 100644
index 0000000..53c5fdf
--- /dev/null
+++ b/addons/deviceatlas/dummy/dadwcom.c
@@ -0,0 +1 @@
+#include <stdio.h>
diff --git a/addons/deviceatlas/dummy/dasch.c b/addons/deviceatlas/dummy/dasch.c
new file mode 100644
index 0000000..53c5fdf
--- /dev/null
+++ b/addons/deviceatlas/dummy/dasch.c
@@ -0,0 +1 @@
+#include <stdio.h>
diff --git a/addons/deviceatlas/dummy/json.c b/addons/deviceatlas/dummy/json.c
new file mode 100644
index 0000000..53c5fdf
--- /dev/null
+++ b/addons/deviceatlas/dummy/json.c
@@ -0,0 +1 @@
+#include <stdio.h>