# 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
```
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 (or llvm) 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
```
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=` 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=` e.g. `CMAKE_INSTALL_PREFIX=/opt/` to install
headers in `/opt/include`, libraries in `/opt/lib`.
- `CMAKE_GENERATOR=` 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
% 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=` 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
// 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 _