# DeviceAtlas Device API Usage C #
This document describes the DeviceAtlas API and how it is used.
The DeviceAtlas Device Identification API consists of two libraries. A core
library containing all of the main identification logic and a secondary
extension library to allow for easier integration into a web application
context.
## Library files ##
** DeviceAtlas Enterprise API (libda.so (or libda.dylib for macOs)) **
This is the main library and is responsible for loading the data file and
provides the logic for device identification. Device identification is achieved
by either passing individual identifiers (e.g. User-Agent) or by passing in a
`da_evidence_t` data structure along with optional Client-side properties.
This library does not depend on any third party libraries.
## 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 [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.
## Client Side Library - 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 devices are _not_
identifiable using only HTTP User-Agent data.
2. It allows for the collection of dynamic or changing properties.
When the client-side library 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 devices.
The client-side library may be included on a webpage by adding the following
snippet:
```Javascript
```
By default, the client-side library returns the data 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 Library](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 highly recommended 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 tested and are able to compile all
components:
- GCC (4.2 or above (5.x, 7.x and onwards are recommended, the 6.x versions could possibly impact
negatively the performances due to optimisations 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 a Makefile will be created.
```shell
% cmake .
% make
```
Note: Albeit not mandatory, for some contexts it is recommended to create a distinct build directory.
```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 builtin 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
```
Albeit 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
```
### Tips for potential performance improvements ###
- 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 above 550% performance increase on an Alpine container.
On the other hand the Linux glibc's allocator, in general, tends to be on part or to outperform them,
even compared to Google's `tcmalloc` for example, the outcome tends to be more dependent of the overall nature of the code.
- It is also compatible with the `BOLT` technology, on Linux in particular. Note that BOLT
works best after a proper profiling had 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, it had been noted a small performance increase below 5% with a binary size
increase of 500% ; a typical large C/C++ application has more chance to really benefits 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, it had been noted a performance increase around 5% with a 10% binary size decrease ;
benefits vary from cases to cases still.
More informations 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 impact negatively the performance in contrary, especially with gcc.
More informations can be found [here](https://gcc.gnu.org/projects/tree-ssa/vectorization.html)
or [here](https://llvm.org/docs/Vectorizers.html).
- At last, the `-march` flag set to the relevant target (especially with non generic arm hardwares) can
provide a slight performance boost in some cases.
### Tips for potential memory usage improvements ###
- 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, to some extent, the api's memory usage.
Three things need to be considered for that matter ; the value must be high enough for the number of properties
of that the current JSON data file used holds and the api inputs usage (e.g. client-hints ... client-side usage).
At last, decreasing it also lower the performance due to increased need of reallocating memory blocks for lookups.
### Usage Examples ###
The following sections shows how to call the DeviceApi. Please note that
errors 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 is to be
// a multiple of DA_MEMORY_MUL and to avoid unnecessary numerous
// 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 cache's number of entries
*/
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. DeviceAtlas expects the make/model string
in a specified format, this format adnd 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-n9005"};
status = da_searchv(&atlas, &device, &ev, 1);
...
```
#### Passing Client Side Data ####
The data collected by the DeviceAtlas Client-side Library is automatically
passed when a `HttpServletRequest` is used. If a `HttpServletRequest` object
is not available the client-side 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-G998B) 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 2024. All Rights Reserved. _
_ https://deviceatlas.com _