# DeviceAtlas Device C API # This document describes the DeviceAtlas API and how it is used. The DeviceAtlas API consists of one core library containing all of the main identification logic. ## Library Files ## ** DeviceAtlas Enterprise API, `libda.so` (`libda.dylib` on macOS) ** This is the main library and is responsible for loading the data file and implements the logic for device identification. Device identification is achieved by either passing individual identifiers (e.g. a User-Agent string) or by passing in a `da_evidence_t` data structure along with optional Client Side Component properties. This library does not depend on any third-party libraries. Depending on your compiler and operating system, a handful of different versions of the same library might be built, e.g. libda-omp.so if OpenMP is supported. ## Data File ## The DeviceAtlas API relies on a device data file to function. To download data file please visit [DeviceAtlas Enterprise Data File Download](https://deviceatlas.com/getJSON). For a full list of the available properties please see the [DeviceAtlas properties](https://deviceatlas.com/properties) page. > Note: The 3.x API is only compatible with the version 3 data file which is downloadable from the DeviceAtlas website. The version 2 data file is only compatible with the production 2x APIs and is no longer supported. ## Client Side Component - Apple Device Identification ## In addition to the server-side API, an optional client-side JavaScript library is available. This library is important for two reasons: 1. It facilitates identification of Apple devices. Apple device models are _not_ identifiable using only HTTP User-Agent data due to policy decisions by Apple. 2. It facilitates the collection of User-Agent Client Hints from browsers and allows for the collection of dynamic or changing properties. When the client-side Component is embedded in a web page it collects additional properties from the client's web browser. This data is then sent back to the server and must be passed to the DeviceAtlas API along with the HTTP headers. The combination of the client data and the server-side data allow for accurate identification of Apple device models. The Client Side Component can also assist with handling User-Agent Client Hints. User-Agent Client Hints are now a critical requirement for accurate device identification on the web. In cases where it is difficult or impossible to collect User-Agent Client Hints from visiting devices the Client Side Component can be utilised to collect these instead. You can read more about User-Agent Client Hints on our website: [User-Agent Client Hints](https://deviceatlas.com/resources/user-agent-client-hints). The client-side library may be included on a webpage by adding the following snippet: ```JavaScript <!-- Adding the DeviceAtlas Client Side Component to get client side properties --> <script type="text/javascript" src="https://cs.deviceatlas-cdn.com/dacs.js" async></script> ``` By default, the Client Side Component returns the data to the server via a cookie. Alternatively, the client data may be returned via AJAX and passed to the server-side API manually. For additional information, please see the [Client Side Component](https://docs.deviceatlas.com/apis/clientside/latest/README.ClientSide.html) documentation. ## Setup & Usage ## The usage of the DeviceAtlas server-side library is straightforward. In all cases, the data file must be loaded before any device identification calls are made. > **Note:** It is important to ensure the data file is only loaded once. This is particularly important for batch processing and web applications. ### Requirements ### - A C compiler (C++ compiler for the wrapper) - CMake 2.8 (or above). The following compilers have been successfully tested for compiling all components: - GCC 4.2 or above, version 5.x, 7.x and onwards are recommended. The 6.x versions could possibly negatively impact the performance due to optimisation bugs. - Clang 3.x or above. - TinyCC 0.9 or above. ### Build ### > **Note:** the API is compatible with ASLR with PIE enabled; modern operating systems have ASLR enabled by default for security risk mitigation purpose and does not impact the API's performance. #### Ubuntu/Debian #### ```shell % sudo apt-get install gcc (or clang) cmake ``` #### RedHat/CentOS #### ```shell % sudo dnf install gcc (or clang) cmake ``` #### macOS #### ```shell % brew install gcc (or clang; not necessary if clang from Xcode is already installed) cmake ``` **Note:** on ARM64 architecture, `libatomic_ops` is required in addition. ```shell % brew install libatomic_ops ``` #### FreeBSD ### ```shell sudo pkg install gcc<any version> (or llvm<any version>) if another compiler different from the base system is preferred ``` ### Cmake ### In order to build the API, cmake needs to be called as first step, pointing to the root folder of the API so that a Makefile will be created. ```shell % cmake . % make ``` Note: Though not mandatory, it is recommended to create a distinct build directory especially from medium to large projects. ```shell % mkdir build % cd build % cmake <path to the C API root folder> ``` Without passing any option to cmake, it builds a Release version and for the install target, the install prefix is `/usr/local` by default. However, there are available options to fit your needs. #### Relevant built-in options for the API #### - `CMAKE_BUILD_TYPE=<Release|RelWithDebInfo|MinSizeRel|Debug>` which is the optimisation compiler level which ranges from -O3 to -O0 on unixes, depending on the need for performance and debug infos level. - `CMAKE_INSTALL_PREFIX=<root path for the install target>` e.g. `CMAKE_INSTALL_PREFIX=/opt/` to install headers in `/opt/include`, libraries in `/opt/lib`. - `CMAKE_GENERATOR=<generator name>` on unixes, by default, is `Unix Makefiles`, however `Ninja` build is also supported. - `CMAKE_C(XX)_COMPILER=` to override, respectively, the C/C++ compiler, by default it is `cc/c++`. - `CMAKE_C(XX)_FLAGS=` to override, respectively, the C/C++ compiler build flags. - `CMAKE_(SHARED/EXE)_LINKER_FLAGS=` to override, respectively, the libraries/executables linker flags. #### Custom DeviceAtlas options #### The following flags are available in order to fit particular use cases and needs. - `APINOCACHE (ON/OFF)`, ON to disable the cache support, OFF by default. - `APITOCONST (ON/OFF)`, ON to build the API with input data as const, OFF by default. - `APILTO (ON/OFF)`, ON to build the API with Link Time Optimisation, OFF by default. - `APIGPU (ON/OFF)`, ON to build CUDA/HIP GPU support, OFF by default. - `APIRUST (ON/OFF)`, ON to build a subset of the API implemented in rust, OFF by default. #### Install/Uninstall the API (Optional) #### ```shell % cd <path to the extracted API archive> % sudo make install % sudo ldconfig ``` Though cmake does not provide a direct way to uninstall, it creates an `install_manifest.txt` file which can serve this purpose. ```shell % cat install_manifest.txt | (sudo) xargs rm ``` ### Performance Improvement Tips ### - The API is compatible with the vast majority of the popular custom allocators to be linked (or preloaded) with, allocators such as `jemalloc`, Microsoft's `mimalloc` and `snmalloc` for example. The performance varies from operating systems and their respective libC, however it is known that the aforementioned allocators perform better on macOS and especially on Linux/MUSL distributions such as Alpine. With a basic application with jemalloc, it had been noted an average of 55% performance increase on macOS and over 550% performance increase on an Alpine container. On the other hand, the Linux glibc allocator, in general, tends to be on par or to outperform them, even compared to Google's `tcmalloc` for example. Overall, the ultimate outcome tends to be more dependent of the overall nature of the code than the allocator used. - It is also compatible with the `BOLT` technology, on Linux in particular. Note that BOLT works best after a proper profiling has been done with the Linux's `perf` toolset; BOLT also produces a significantly higher binary in size after call graphs reordering. With a basic application, a small performance increase (< 5%) with a binary size increase of 500% has been observed; a typical large C/C++ application is more likely to benefit from it. More informations can be found [here](https://github.com/llvm/llvm-project/blob/main/bolt/README.md). - The `Link Time Optimization` technology can benefit applications to some extent too. With a basic application, a performance increase around 5% with a 10% binary size decrease has been observed, though benefits vary from case to case. More information can be found [here](https://gcc.gnu.org/wiki/LinkTimeOptimization) or [here](https://llvm.org/docs/LinkTimeOptimization.html). - The `loop vectorization` feature is enabled by default, both by clang and gcc implicitly with optimised builds. However, it depends of the nature of the overall code and other factors (e.g. memory alignment) as tree vectorization can negatively impact the performance in comparison with gcc in particular. More informations can be found [here](https://gcc.gnu.org/projects/tree-ssa/vectorization.html) or [here](https://llvm.org/docs/Vectorizers.html). - Finally, the `-march` flag set to the relevant target (especially with non-generic ARM hardwares) can provide a slight performance boost in some cases. ### Memory Usage Improvement Tips ### - While using memory pools to override API heap allocations brings its benefits, it also adds a reasonable but noticeable memory usage overhead. - It is possible to pass the `DA_EFFECTIVE_BUFSIZE=<value>` preprocessor (or using the cmake `APIBUFSIZE` option) to change the `DA_BUFSIZE` value, thus possibly decreasing the API's memory usage to some extent. The following points need to be considered in this case: 1. The value must be high enough for the number of properties in the current JSON data file; 2. It must be high enough for API inputs usage e.g. User-Agent Client Hints, Client Side Component data; 3. Finally, decreasing it will also lower performance due to an increased need for reallocating memory blocks for lookups. ### Usage Examples ### The following sections shows how to call the DeviceApi. Please note that error handling has been omitted for brevity. #### Detection via User-Agent #### Lookup the properties by passing an user-agent to the API: ```c #include <dac.h> // Optional // By default the API sets a sane memory threshold for the JSON parsing // and mapping to memory. However, it is possible to override the value // if it needs to fit specific constraints. The value should be // a multiple of DA_MEMORY_MUL and to avoid numerous unnecessary // I/O operations, being rounded up slighty. size_t da_user_hint = 3 * DA_MEMORY_MUL; static size_t filereader(void *ctx, size_t count, char *buf) { return fread(buf, 1, count, ctx); } static da_status_t fileseeker(void *ctx, off_t pos) { return fseek(ctx, pos, SEEK_SET) != -1 ? DA_OK : DA_SYS; } ... da_atlas_t atlas; da_status_t status; void *ptr; size_t size; FILE *json = fopen("/path/to/datafile.json", "r"); if (!json) { fprintf(stderr, "fread failed: %s\n", jsonpath); exit(EXIT_FAILURE); } da_init(); if ((status = da_atlas_compile(json, filereader, fileseeker, &ptr, &size)) != DA_OK) { fprintf(stderr, "da_atlas_compile failed\n"); goto dafini; } /** * Or when the binary file dumped previously by da_atlas_dump_mapped * or from in RAM data */ ... const char *binarypath = "data.bin"; void *m; if ((status = da_atlas_read_mapped(binarypath, m, &ptr, &size)) != DA_OK) { fprintf(stderr, "da_atlas_read_mapped failed\n"); goto daclose; } ... da_property_decl_t extraprops[] = {{ 0, 0 }}; if ((status = da_atlas_open(&atlas, extraprops, ptr, size)) != DA_OK) { fprintf(stderr, "da_atlas_open failed\n"); goto daclose; } /** * Optional: dumping the API memory state into a binary file */ ... const char *binaryPath = "./data.bin"; void *bin; size_t binlen; if ((status = da_atlas_dump_mapped(binarypath, &bin, &binlen, ptr, size)) != DA_OK) { fprintf(stderr, "da_atlas_dump_mapped failed\n"); goto daclose; } /** * Optional: setting the number of entries in the cache */ atlas.config.cache_size = 10000; ... da_evidence_t ev = {.key = da_atlas_header_evidence_id(&atlas, "user-agent"), .value = "Mozilla/5.0 (Linux; Android 11; SM-G998B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Mobile Safari/537.36"}; if ((status = da_searchv(&atlas, &device, &ev, 1)) == DA_OK) { for (status = da_getfirstprop(&device, &curprop); status == DA_OK; status = da_getnextprop(&device, &curprop)) { da_getpropname(&device, *curprop, &curpropname); ... da_getproptype(&device, *curprop, &curtype); ... switch (curtype) { case DA_TYPE_NUMBER: case DA_TYPE_INTEGER: { long curvalue; da_getpropinteger(&device, *curprop, &curvalue); sprintf(propbuf, "%ld", curvalue); break; } case DA_TYPE_BOOLEAN: { bool curvalue; da_getpropboolean(&device, *curprop, &curvalue); sprintf(propbuf, "%d", curvalue); break; } case DA_TYPE_STRING: { const char *curvalue; size_t curvaluelen; da_getpropstring(&device, *curprop, &curvalue); curvaluelen = strlen(curvalue); if (curvaluelen > sizeof(propbuf) - 1) curvaluelen = sizeof(propbuf) - 1; strncpy(propbuf, curvalue, curvaluelen); propbuf[curvaluelen] = 0; break; } case DA_TYPE_ARRAY: case DA_TYPE_NONE: default: break; } ... or ... da_type_t curtype; struct da_propent ent; uintptr_t val; if (da_getpropval(&device, *curprop, &val, &ent) == DA_OK) { switch (ent.type) { case DA_TYPE_NUMBER: case DA_TYPE_INTEGER: { sprintf(propbuf, "%ld", (int)val); break; } case DA_TYPE_BOOLEAN: { sprintf(propbuf, "%d", (bool)val); break; } case DA_TYPE_STRING: { size_t curvaluelen; const char *curval = (const char *)val; curvaluelen = strlen(curvalue); if (curvaluelen > sizeof(propbuf) - 1) curvaluelen = sizeof(propbuf) - 1; strncpy(propbuf, curvalue, curvaluelen); propbuf[curvaluelen] = 0; break; } } ... printf("%s => %s\n", ent.name, propbuf); ... } } da_close(&device); da_atlas_close(&atlas); ``` #### Detection via Make/Model #### Lookup the properties by passing a make/model string to the API e.g. the make/model string obtained from a mobile device OS. DeviceAtlas expects the make/model string in a specified format. This format and how to obtain the make/model string can be found under the "Expected string format for DeviceAtlas lookup" section [here](https://deviceatlas.com/resources/getting-started-enterprise-for-apps). ```c ... da_evidence_t ev = {.key = da_atlas_header_evidence_id(&atlas, "make-model"), .value = " SAMSUNG SM-S901B"}; status = da_searchv(&atlas, &device, &ev, 1); ... ``` #### Passing Client Side Component Data to the API #### The data collected by the DeviceAtlas Client Side Component is automatically passed when a `HttpServletRequest` is used. If a `HttpServletRequest` object is not available the Client Side Component data can be passed manually as a second parameter to the `getProperties()` method. ```c ... da_evidence_t ev[2]; ev[0].key = da_atlas_header_evidence_id(&atlas, "user-Agent"); ev[0].value = "Mozilla/5.0 (Linux; Android 12; SM-S901B) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/102.0.0.0 Mobile Safari/537.36"; ev[1].key = da_atlas_clientprop_evidence_id(&atlas); ev[1].value = "scsVersion:2.1|bcookieSupport:1|bcss.animations:1|bcss.columns:1|bcss.transforms:1|bcss.transitions:1|sdeviceAspectRatio:1795/1010" ... ``` #### Additional Examples #### Please find more complete examples in the /Examples directory. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - _ Copyright (c) DeviceAtlas Limited 2025. All Rights Reserved. _ _ https://deviceatlas.com _ <!-- HTML+JS for document formatting when opened in browser --> <div class="btn-group" id="main-menu" style="float:right"><a class="btn dropdown-toggle" data-toggle="dropdown" href="#">Menu<span class="caret"></span></a><ul class="dropdown-menu"><li><a href="README.html">Main</a></li><li class="disabled"><a href="README.DeviceApi.html">Device C API</a></li><li><a href="README.ClientHints.html">Client Hints Support</a></li><li><a href="README.CarrierApi.html">Carrier Identification API</a></li><li><a href="README.Upgrade.html">Device API Upgrade</a></li><li><a href="README.Cpp.html">Device API C++</a></li><li><a href="README.Nginx.html">NGINX Module</a></li><li><a href="README.Apache2.html">Apache2 Module</a></li><li><a href="README.JsonConverter.html">Device Identification API C JSONConverter</a></li><li><a href="README.Scheduler.html">Device Identification API C file monitor and scheduler</a></li><li><a href="README.Go-DeviceApi.html">Device API Usage Go</a></li><li><a href="README.Go-Upgrade.html">Device API Upgrade Go</a></li><li><a href="README.Rust-DeviceApi.html">Device API Usage Rust</a></li><li><a href="README.Gpu.html">Device API and GPU/CPU interaction</a></li><li class="divider"></li><li><a href="./ApiDocs/index.html">C ApiDocs</a></li><li><a href="./ApiCppDocs/index.html">C ++ interface ApiDocs</a></li><li><a href="./Go-ApiDocs/carrier.html">Go ApiDocs Carrier</a></li><li><a href="./Go-ApiDocs/device.html">Go ApiDocs Device</a></li></ul></div>