WURFL InFuze for C: User Guide

Introduction

The WURFL C Application Programming Interface (API) is a high-performance and low-memory footprint mobile device detection software component written in C that can quickly and accurately detect over 500 capabilities of visiting devices. It can differentiate between portable mobile devices, desktop devices, SmartTVs and any other types of devices on which a web browser can be installed. The API library allows applications to perform real-time detection for a variety of uses, such as content adaptation, redirections, and data traffic analysis.

ScientiaMobile's WURFL C Module is also the core component for the Apache, Nginx, and Varnish-Cache Modules, offered by ScientiaMobile as separate products. Those modules allow you to add support for device detection in a variety of contexts, such as retrieving WURFL device data through the Apache Environment, FastCGI Environment, or data objects defined by Nginx and Varnish scripts. Other strategies may include injecting WURFL capabilities into HTTP Headers from your HTTP Proxy. This eliminates the need for application developers to maintain a WURFL library dependency in their code base.

API Features

  • Standard WURFL API (implements same logic as other standard WURFL APIs by ScientiaMobile)
  • Accurately detect over 500 capabilities
  • High performance & low memory footprint (as compared to other WURFL APIs)
  • Automatic caching previous detections for faster response times
  • Ability to adjust and configure different in-memory caching strategies
  • Support for WURFL 'patch files' (custom device description profiles that enrich WURFL)

Technical Overview

The WURFL InFuze for C API is also referred to as libwurfl, and is written in C. The installed libwurfl library consists of a header file, wurfl.h, and a dynamic link library which an application can link to.

When running the WURFL InFuze for C, libwurfl will read and parse the WURFL snapshot from run-time memory. If needed, you can also load WURFL Patch Files that extend or correct the WURFL repository with your own customizations of the device data.

The libwurfl C library has an in-memory cache technique to preserve the result of previous detections. This results in a great balance between accuracy of detection and fast response time in virtually all use cases. Finally, the strategies for caching are configurable (more later).

Supported Platforms

The libwurfl API is supported by multiple Linux distros such as Ubuntu, CentOS, RedHat, Fedora, Alpine and FreeBSD (other distros have similar build procedures). Other supported operating systems include Windows and Mac OS X. Please contact ScientiaMobile to inquire about specific platforms not listed here.

Installing libwurfl on Ubuntu/Debian

Once you have obtained the libwurfl deb package from ScientiaMobile, you can install it with:

sudo apt-get update
sudo dpkg -i libwurfl-W.X.Y.Z-x86_64.deb

For arm64/aarch64 architectures you should install with :

sudo apt-get update
sudo dpkg -i libwurfl-W.X.Y.Z-arm64.deb

Installing libwurfl on RedHat/Fedora/CentOS/Opensuse

Once you have obtained the libwurfl rpm package from ScientiaMobile, you can install it with:

sudo yum update
sudo rpm -i libwurfl-W.X.Y.Z-x86_64.rpm

For arm64/aarch64 architectures you should install with :

sudo yum update
sudo rpm -i libwurfl-W.X.Y.Z-aarch64.rpm

Include file are installed under /usr/include/wurfl and library in standard /usr/lib path so to compile code that uses libwurfl you will only have to add -lwurfl to your link step

Installing libwurfl on Alpine

Once you have obtained the libwurfl apk package from ScientiaMobile, you can install it with:

apk --allow-untrusted add libwurfl-W.X.Y.Z-r0-x86_64.apk

Since 1.12.2.0 the alpine package is signed so you might want to install the package public key in /etc/apk/keys taking it from : https://docs.scientiamobile.com/certs/support@scientiamobile.com-60eebab0.rsa.pub (just copy the downloaded file into /etc/apk/keys) After this you might install with :

apk add libwurfl-W.X.Y.Z-r0-x86_64.apk

Installing libwurfl on Mac OS X

Download the libwurfl-W.X.Y.Z-x86_64.pkg file from ScientiaMobile and install it by double clicking on it.

Installing libwurfl on Windows

Download the libwurfl-W.X.Y.Z-x64.msi file from ScientiaMobile and install it by double clicking on it.

Obtain the WURFL Repository file

In order for the WURFL C API library to work, you need a WURFL file installed on your system. The libwurfl C API library package comes with a recent evaluation copy of the WURFL file called wurfl.zip. The file is automatically installed to /usr/share/wurfl/ path, or is included with the Windows installer.

Updating the WURFL Repository

Commercial licensees of WURFL are granted access to "The Customer Vault", a personal virtual space containing purchased software licenses and weekly updated versions of the WURFL repository.

All licensed customers are given access to a direct download URL for obtaining the latest WURFL snapshot. This is either the weekly snapshot (released on Sunday night), or an Out-Of-Band snapshot that ScientiaMobile releases to customers between weekly snapshots in case of a significant improvement to the data (for example, if a high-profile device is released mid-week). The snapshots are licensed to your organization specifically as part of the agreement with ScientiaMobile.

To create a direct download URL, login to ScientiaMobile and click the "View Account" at the right end of the purchased product line. Then, click "View Archives" to download the latest WURFL snapshot in the form of a ZIP or a GZIP file. Those URLs are unique to you and must be kept private. You can also download one of the previous snapshots listed below the last one.

Customer Vault of WURFL snapshot

In addition to the weekly snapshots, the Vault contains a couple personal URLs to the latest update, that can be used to update one's WURFL installation programmatically. To implement this automation, please refer to the following instructions to setup the WURFL Updater class.

Getting Started

It's strongly recommended, as first thing, to download the latest version of DDR file:

// substitute https://data.scientiamobile.com/xxxxxxx/wurfl.zip with your data url from scientiamobile vault
// the second parameter is the destination folder where the zip file will be extracted. It must be writable by the process.
wurfl_error err = wurfl_download("https://data.scientiamobile.com/xxxxxxx/wurfl.zip", ".");

Using the WURFL InFuze for C is simple - all operations are performaned on a wurfl_handle, which is obtained typically during init phase:

wurfl_handle hWurfl = wurfl_create();

This operation returns wurfl_handle and creates an engine for further operations. The WURFL Engine is thread safe and is intentionally designed for sharing the same wurfl_handle across multiple threads.

In order to increase performance while processing real HTTP traffic, we suggest setting up an LRU cache in libwurfl. The LRU caching strategy will speed up lookup operations on processed User Agents by keeping them in an LRU map. The size of this map can be configured with:

wurfl_set_cache_provider(hwurfl, WURFL_CACHE_PROVIDER_LRU, "100000");

By default the cache will be set to 30000 entries which accounts for 7 to 10 MB of additional memory usage. Users are advised to size their cache generously (100,000 or more) to increase performance.

Important Note for Users of the old DOUBLE LRU Cache Provider (pre 1.9.1): for backwards compatibility, older configurations are still supported and will not generate errors or warnings, but internally the new SINGLE LRU Cache is adopted for better performance.

For more information, please see LRU Cache Mechanism.

Note: wurfl_set_cache_provider must be called before calling wurfl_load().

Before any device lookup you will need to load the Device Description Repository (DDR) file, named wurfl.zip :

// Define wurfl db
error = wurfl_set_root(hwurfl, "./wurfl.zip");
if (error != WURFL_OK) {
  printf("%s\n", wurfl_get_error_string(error));
  return -1;
}

// Loads wurfl db into memory
error = wurfl_load(hwurfl);
if (error != WURFL_OK) {
  printf("%s\n", wurfl_get_error_string(error));
  return -1;
}

After properly loading the WURFL instance, device detection can start. For this example, we will manually inject the User-Agent string of a popular Android device and proceed to detect the device (with some error-handling):

// Example of an User-Agent
const char *userAgent = "Mozilla/5.0 (Linux; U; Android 3.2; en-gb; K3108 Build/HTJ85B) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13";

wurfl_device_handle hdevice = wurfl_lookup_useragent(hwurfl, userAgent);

if (hdevice) {
    // DEVICE FOUND
    // ...
}

Once a device is detected, device capabilities can be retrieved as follows:

if (hdevice) {
    // DEVICE FOUND

    // Print out a capability
    printf("brand_name = %s\n", wurfl_device_get_static_cap(hdevice, "brand_name", &error));

}

After usage, please remember to destroy device instances and the WURFL engine itself to avoid memory leaks:

    ...
    wurfl_device_destroy(hdevice);
    ...
    wurfl_destroy(hwurfl);
    ...

Virtual Capabilities

Virtual capabilities are an important feature of the WURFL API that obtain values related to the requesting agent out of the HTTP request as a whole (as opposed to limiting itself to capabilities that are found in WURFL).

In order to compute its final returned value, a virtual capability may look at static (non-virtual) capabilities as well as parameters derived from the HTTP request at run-time. Virtual capabilities are useful to model aspects of the HTTP Client that are not easily captured through the finite number of profiles in WURFL.

// Print out a virtual capability
printf("is_smartphone = %s\n", wurfl_device_get_virtual_cap(hdevice, "is_smartphone", &error));

If your application needs to retrieve all static, and/or virtual, capabilities, you can use an enumerator to iterate through every static capability that was loaded into the runtime memory, and another enumerator to iterate through all the virtual capabilities. Thus, there is no need to provide a list of individual capability names.

if (hdevice) {
    // DEVICE FOUND
    // Iterate through all capabilities
    wurfl_enum_handle hdevicecaps = wurfl_enum_create(hdevice, WURFL_ENUM_STATIC_CAPABILITIES);

    while (wurfl_enum_is_valid(hdevicecaps)) {
        const char *capname = wurfl_enum_get_name(hdevicecaps);
        printf("%s = %s\n", capname, wurfl_device_get_static_cap(hdevice, capname, &error));

        wurfl_enum_move_next(hdevicecaps);
    }

    wurfl_enum_destroy(hdevicecaps);

    // Iterate through all virtual capabilities
    hdevicecaps = wurfl_enum_create(hdevice, WURFL_ENUM_VIRTUAL_CAPABILITIES);

    while (wurfl_enum_is_valid(hdevicecaps)) {
        const char *capname = wurfl_enum_get_name(hdevicecaps);
        printf("%s = %s\n", capname, wurfl_device_get_virtual_cap(hdevice, capname, &error));

        wurfl_enum_move_next(hdevicecaps);
    }

    wurfl_enum_destroy(hdevicecaps);
}

Code Example

This code allows you to create a WURFL Handler instance, define the path to the WURFL repository, and load the database into memory. For this example, we assume that the wurfl.zip file is located on the same path of the executable program file. However, the path name can be different depending on your requirements.

#include <stdio.h>
#include <wurfl/wurfl.h>

// lookup user agent
// cc -o lookup_ua lookup_ua.c -lwurfl

int main(int argc, char **argv)
{
    // Download the initial version of the wurfl.zip file for your licence
    // replace it with your account snapshot data wurfl

    char *snapshot_dataurl = "https://data.scientiamobile.com/xxxxx/wurfl.zip";

    wurfl_error err;
    printf("Downloading wurfl.zip from %s\n", snapshot_dataurl);

    if (( err = wurfl_download(snapshot_dataurl, ".")) != WURFL_OK)
    {
        // some error related to the folder your using to store wurfl.zip or the data url format is wrong
        printf("wurfl_download : %s\n", wurfl_get_error_string(err));
        return -1;
    }

    // Create wurfl handler
    wurfl_handle hwurfl = wurfl_create();

    // Define wurfl db
    wurfl_set_root(hwurfl, "wurfl.zip");

    // set lookup cache
    wurfl_set_cache_provider(hwurfl, WURFL_CACHE_PROVIDER_LRU, "200000");

    // Loads wurfl db into memory
    printf("Loading wurfl, api version %s\n", wurfl_get_api_version());

    if (( err = wurfl_load(hwurfl)) != WURFL_OK)
    {
        printf("%s\n", wurfl_get_error_string(err));
        return -1;
    }

    // initialize updater 

    if (( err = wurfl_updater_set_data_url(hwurfl, snapshot_dataurl)) != WURFL_OK)
    {
        printf("wurfl_updater_set_data_url : %s\n", wurfl_get_error_string(err));
        return -1;
    }

    // specify how frequent shall we check for updates
    err = wurfl_updater_set_data_frequency(hwurfl, WURFL_UPDATER_FREQ_DAILY);

    // start the updater : it will run in a separate thread and check daily for updates available to the wurfl.zip file
    wurfl_updater_start(hwurfl);

    char *userAgent = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36";

    printf("Lookup >%s<\n", userAgent);

    wurfl_device_handle hdevice = wurfl_lookup_useragent(hwurfl, userAgent);

    if (hdevice == NULL)
    {
        printf("wurfl_lookup_useragent returned NULL on >%s< ua, error %s\n", 
            userAgent, wurfl_get_error_message(hwurfl));
        return -1;
    }

    // Print out the wurfl_id, wurfl internal device identifier
    printf("wurfl_id = %s\n", wurfl_device_get_id(hdevice));

    // Print out a static capability
    printf("model_name = %s\n", wurfl_device_get_static_cap(hdevice, "model_name", &err));
    printf("brand_name = %s\n", wurfl_device_get_static_cap(hdevice, "brand_name", &err));
    printf("device_os = %s\n", wurfl_device_get_static_cap(hdevice, "device_os", &err));

    // Print out a virtual capability
    printf("complete_device_name = %s\n", wurfl_device_get_virtual_cap(hdevice, "complete_device_name", &err));
    printf("form_factor = %s\n", wurfl_device_get_virtual_cap(hdevice, "form_factor", &err));

    if ( wurfl_is_ua_frozen(hwurfl, userAgent) )
    {
        printf("UA %s is frozen. Sec-Ch-Ua headers are necessary for correct device identification.\n", userAgent);
    }

    wurfl_device_destroy(hdevice);
    wurfl_destroy(hwurfl);  
}

Compile and run this code (on a Linux/OSX box) with :

$ cc -o lookup_ua lookup_ua.c -lwurfl
$ ./lookup_ua
Downloading wurfl.zip from https://data.scientiamobile.com/xxxxx/wurfl.zip
Loading wurfl, api version 1.13.2.0
Lookup >Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36<
wurfl_id = generic_android_ver10_0
model_name = Android 10.0
brand_name = Generic
device_os = Android
complete_device_name = Generic Android 10.0
form_factor = Feature Phone
UA Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36 is frozen. Sec-Ch-Ua headers are necessary for correct device identification.
s$

On Windows, a default InFuze installation will create a C:\Program Files\ScientiaMobile\InFuze folder with relevant headers, libraries and data files. Please set the additional include/lib directories paths and dependencies in your Visual Studio project as you would do with any other Windows library.

wurfl_lookup function and http headers

For better accuracy in device detection and virtual capability computation you may use wurfl_lookup() which uses all HTTP headers needed by subsequent computations. To do it, a wurfl_important_header object is provided to lookup method, as shown in the example below:

#include <stdio.h>
#include <wurfl/wurfl.h>

// lookup http request headers
// cc -o lookup_headers lookup_headers.c -lwurfl

int main(int argc, char **argv)
{
    // Download the initial version of the wurfl.zip file for your licence
    // replace it with your account snapshot data wurfl

    char *snapshot_dataurl = "https://data.scientiamobile.com/xxxxx/wurfl.zip";

    wurfl_error err;
    printf("Downloading wurfl.zip from %s\n", snapshot_dataurl);

    if (( err = wurfl_download(snapshot_dataurl, ".")) != WURFL_OK)
    {
        // some error related to the folder your using to store wurfl.zip or the data url format is wrong
        printf("wurfl_download : %s\n", wurfl_get_error_string(err));
        return -1;
    }

    // Create wurfl handler
    wurfl_handle hwurfl = wurfl_create();

    // Define wurfl db
    wurfl_set_root(hwurfl, "wurfl.zip");

    // set lookup cache
    wurfl_set_cache_provider(hwurfl, WURFL_CACHE_PROVIDER_LRU, "200000");

    // Loads wurfl db into memory
    printf("Loading wurfl, api version %s\n", wurfl_get_api_version());

    if (( err = wurfl_load(hwurfl)) != WURFL_OK)
    {
        printf("%s\n", wurfl_get_error_string(err));
        return -1;
    }

    // initialize updater 

    if (( err = wurfl_updater_set_data_url(hwurfl, snapshot_dataurl)) != WURFL_OK)
    {
        printf("wurfl_updater_set_data_url : %s\n", wurfl_get_error_string(err));
        return -1;
    }

    // specify how frequent shall we check for updates
    err = wurfl_updater_set_data_frequency(hwurfl, WURFL_UPDATER_FREQ_DAILY);

    // start the updater : it will run in a separate thread and check for updates to the wurfl.zip file
    wurfl_updater_start(hwurfl);

    // ihh will hold the values for the various http headers needed for lookup
    wurfl_important_header_handle ihh = wurfl_important_header_create(hwurfl);

    // Set http headers values in User-Agent Client Hints case 
    // wurfl_important_header_set() will add the header to the ihh only if it is an interesting header for lookup.
    // To get the list of important headers (i.e. headers interesting for lookup), use either : 
    // const char **importantHeadersNames = wurfl_get_important_header_names();
    // which return an array of char * with all the names of the headers 
    // or
    // wurfl_important_header_enumerator_handle wurfl_get_important_header_enumerator(wurfl_handle hwurfl);
    // and relative methods to iterate over the header names : 
    // int wurfl_important_header_enumerator_is_valid(wurfl_important_header_enumerator_handle);
    // void wurfl_important_header_enumerator_move_next(wurfl_important_header_enumerator_handle);
    // const char *wurfl_important_header_enumerator_get_value(wurfl_important_header_enumerator_handle);
    // void wurfl_important_header_enumerator_destroy(wurfl_important_header_enumerator_handle);

    wurfl_important_header_set(ihh, "user-agent", "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36");
    wurfl_important_header_set(ihh, "sec-ch-ua", "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"");
    wurfl_important_header_set(ihh, "sec-ch-ua-arch", "");
    wurfl_important_header_set(ihh, "sec-ch-ua-bitness", "");
    wurfl_important_header_set(ihh, "sec-ch-ua-full-version", "131.0.6778.104");
    wurfl_important_header_set(ihh, "sec-ch-ua-full-version-list", "\"Google Chrome\";v=\"131.0.6778.104\", \"Chromium\";v=\"131.0.6778.104\", \"Not_A Brand\";v=\"24.0.0.0\"");
    wurfl_important_header_set(ihh, "sec-ch-ua-mobile", "?1");
    wurfl_important_header_set(ihh, "sec-ch-ua-model", "SM-M315F");
    wurfl_important_header_set(ihh, "sec-ch-ua-platform", "Android");
    wurfl_important_header_set(ihh, "sec-ch-ua-platform-version", "12.0.0");

    // lookup using se-ch-ua headers added to the ihh
    wurfl_device_handle hdevice = wurfl_lookup_with_important_header(hwurfl, ihh);

    if (hdevice == NULL)
    {
        printf("wurfl_lookup_with_important_header returned NULL, error %s\n", 
            wurfl_get_error_message(hwurfl));
        return -1;
    }

    // Print out the wurfl_id, wurfl internal device identifier
    printf("wurfl_id = %s\n", wurfl_device_get_id(hdevice));

    // Print out a static capability
    printf("model_name = %s\n", wurfl_device_get_static_cap(hdevice, "model_name", &err));
    printf("brand_name = %s\n", wurfl_device_get_static_cap(hdevice, "brand_name", &err));
    printf("device_os = %s\n", wurfl_device_get_static_cap(hdevice, "device_os", &err));

    // Print out a virtual capability
    printf("complete_device_name = %s\n", wurfl_device_get_virtual_cap(hdevice, "complete_device_name", &err));
    printf("form_factor = %s\n", wurfl_device_get_virtual_cap(hdevice, "form_factor", &err));

    wurfl_device_destroy(hdevice);
    wurfl_important_header_destroy(ihh);
    wurfl_destroy(hwurfl);  
}

IMPORTANT: Empty header values are treated as valid and those headers are not discarded. If you get headers from a request programmatically from a data source such as logs, DB data, spreadsheet, etc., please make sure that you DO NOT add headers with empty strings as values (this may also be the result of "casting" a NULL / NONE / NaN to a string):

Avoid:

wurfl_important_header_set(ihh, headerName, headerValue);

Use:

if (headerValue != NULL && headerValue[0] != '\0')
{
    wurfl_important_header_set(ihh, headerName, headerValue);
}

WURFL Updater features

Since 1.8.3.0, WURFL InFuze for C provides a set of new functions to allow for seamless update of the WURFL Snapshot (wurfl.zip or wurfl.xml.gz). Assuming your init code is as follows :

wurfl_handle hwurfl = wurfl_create();

// Define wurfl db
error = wurfl_set_root(hwurfl, "./wurfl.zip");
if (error != WURFL_OK) {
  printf("%s\n", wurfl_get_error_string(error));
  return -1;
}

If you wish to have your wurfl.zip file automatically updated when a new WURFL Snapshot is released, you will need to:

// substitute https://data.scientiamobile.com/xxxxx/wurfl.zip with your data URL from your ScientiaMobile Vault
error = wurfl_updater_set_data_url(hwurfl, "https://data.scientiamobile.com/xxxxx/wurfl.zip");
if (error != WURFL_OK) {
  printf("Updater cannot run : %s\n", wurfl_get_error_string(error));
  return -1;
}

// specify how frequent shall we check for updates
error = wurfl_updater_set_data_frequency(hwurfl, WURFL_UPDATER_FREQ_DAILY);

Now everything is set up and after loading your DDR:

// Loads wurfl db into memory
error = wurfl_load(c);
if (error != WURFL_OK) {
  printf("%s\n", wurfl_get_error_string(error));
  return -1;
}

You will want to start the updater thread:

wurfl_updater_start(hwurfl);

For better operations logging and to troubleshoot any problem you might have we suggest to enable logging (before the first set_data_url call ) :

wurfl_updater_set_log_path(hwurfl, "updater.log");

File "updater.log" will contain detailed information of all background operations.

Note: a wurfl.zip file must already be present in a writable path in order for the updater to check the file and determine whether or not it needs to update the file.


WURFL InFuze C++ Wrapper

A single-header object oriented C++ InFuze wrapper is available.

If interested, please check documentation here.


Note: Apache, NGINX, and Varnish-Cache are the trademarks of the respective trademark holders.

© 2024 ScientiaMobile Inc.
All Rights Reserved.

NOTICE: All information contained herein is, and remains the property of ScientiaMobile Incorporated and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to ScientiaMobile Incorporated and its suppliers and may be covered by U.S. and Foreign Patents, patents in process, and are protected by trade secret or copyright law. Dissemination of this information or reproduction of this material is strictly forbidden unless prior written permission is obtained from ScientiaMobile Incorporated.