|
Device Generator 1.0.3
Devices, browsers, user agents and IP addresses that fit together for C++23
|
device-generator draws the device a person uses to reach a service: platform, maker and model, OS version, browser and version, the User-Agent and Accept-Language headers, the screen and an IP address. Every part agrees with the others and with the reference date. This guide covers everything beyond the README.
Copy dasmig/devicegen.hpp, dasmig/core.hpp and dasmig/core/ into your include path, and either the resources/ folder or dasmig/devicegen_data.hpp (see Compiled-in Data). Build with -std=c++23 (GCC 14+, Clang 18+) or /std:c++latest (MSVC 19.38+).
dvg::instance() loads the lite tier on first use, from the compiled-in copy or from a resources/ folder found next to the program (resources/, ../resources, device-generator/resources, ../device-generator/resources). Independent instances start empty:
lite covers the 195 sovereign states with the 64 largest IP blocks per country; full covers 245 territories with all 143,000 IPv4 blocks and 70,000 IPv6 prefixes and loads in about 0.2 s. Both have the same models, versions and shares. countries() lists the codes a request can name, platforms() the platform keys.
| Field | Default | Meaning |
|---|---|---|
country | drawn by internet users | ISO 3166-1 alpha-2 code, any case |
platform | drawn by the country's shares | android-phone, ios-phone, windows-desktop, macos-desktop, ipad, android-tablet, linux-desktop, chromeos-desktop, other (user-generator's keys) |
kind | any | device_kind::phone, tablet, desktop or other; narrows the platform draw |
browser | drawn by the OS's shares | chrome, safari, edge, firefox, samsung, opera, yandex; a drawn country and platform are ones that have it |
locale | empty | BCP 47 tag for Accept-Language; empty leaves the header empty |
reference_date | 2023-07-01 | the day the device is seen; from 2021-01-01 |
ip | ip_version::any | any (IPv6 with the country's IPv6 share), v4, v6 |
safe | false | documentation addresses only |
weighted | true | false draws uniformly among what is possible |
Fixing a field never changes the other fields' draws: get_device({.country = d.country_code}, d.seed()) gives the same device as get_device({}, d.seed()). A platform fixes the kind and the OS; platform = "other" gives a device with no browser and no user agent (the platform exists so that every user-generator platform can be passed in).
| Member | Example |
|---|---|
country_code | BR |
platform, platform_label, kind | ios-phone, iPhone, phone |
maker, model | Apple, iPhone 15; Windows, Linux and ChromeOS computers have a maker and no model |
os, os_version | iOS, 17.5; Android, Windows and macOS versions are majors (14, 11, 10.15); Linux and ChromeOS have none |
browser, browser_version, engine | chrome, 126, WebKit (every iOS browser is WebKit) |
user_agent | the User-Agent header |
accept_language | pt-BR,pt;q=0.9,en;q=0.8 |
sec_ch_ua, sec_ch_ua_mobile, sec_ch_ua_platform | "Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99", ?0, "Windows": the client hints Chrome and Edge 96 and later send outside iOS; empty otherwise |
screen | {width, height, pixel_ratio} in CSS pixels, portrait for phones and tablets |
ip, ip_version, reserved | 2804:38a:96bb:5ece:67b6:b52a:7139:f2d1, 6, false |
to_string() (and operator<<) gives a one-line summary; device::browser_name("samsung") gives Samsung Internet.
The reference date decides which models exist and which OS and browser versions are out, and moves maker and browser shares along their trends (shares in the data are those of 2025; Huawei phones outside China are about three times as common in 2021). Versions spread after release and fade once the next version is out, so soon after a release most devices are still on the previous one; a device stays on the last version it can run (an iPhone 8 on iOS 16, Chrome 109 on Windows 7). The user agent follows the browser's format at that version: Chrome on Android sends its model before Chrome 110 and Android 10; K from 110, and iOS 26 and later send the frozen iPhone OS 18_6.
Dates before 2021-01-01 throw std::invalid_argument. A date after the newest release in the data (2026-10-27 in this version) is treated as that date, so the newest versions keep their shares instead of fading.
Addresses are drawn from the blocks the regional registries list for the country, weighted by size; special-purpose ranges are never drawn, and IPv4 addresses avoid .0 and .255. IPv6 addresses keep the registered prefix and draw the rest. A block's country is where it is registered, not necessarily where it is used.
safe = true draws only from the documentation ranges (RFC 5737: 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24; RFC 3849: 2001:db8::/32) and sets reserved. A country with no registered block (Kosovo in the shipped data) needs safe.
The platform keys are user-generator's, so a user's device can be drawn to match the account:
user-generator itself does not include device-generator yet; an opt-in device section is planned.
get_device(request, seed) is a pure function of the request, the seed and the data, and is const; get_device(request) draws a seed from the instance's engine. device::seed() gives the seed back. The same request, seed and data give byte-identical output on every compiler, standard library and CPU, checked by the known-answer tests.
Each of the sixteen tables (dasmig::device_table) takes layers:
In the default override mode a row replaces the loaded row with the same key and new keys are added; layer_mode::add refuses existing keys; layer_mode::replace replaces the table; a disabled column set to 1 removes a key. A layer is checked before it is applied, and a bad one leaves the data unchanged. layers(table) lists the layers in effect; load() drops them all. The columns are described in doc/data.md.
to_json() writes every field and a signature with the seed and data_hash(), the hash of the loaded tables and layers:
to_json(core::json_writer&) writes into a larger document.
Define DASMIG_DEVICE_EMBED before including the header (or pass -DDASMIG_DEVICE_EMBED) to build the lite tier into the program from dasmig/devicegen_data.hpp (649 KB of source). load(dataset::lite) then needs no files, and its output equals the files'. make embed regenerates the header after the data changes.
Instances are independent. The seeded get_device(request, seed) is const and safe to call from several threads on one instance; the unseeded overload, load() and extend() change the instance and need external synchronisation.
Every getter has batch forms: get_device_batch returns a std::vector, and for_each_device streams the items to a sink in index order without holding the batch.
i is exactly get_device(request, core::item_seed(batch_seed, first + i)). A range can be generated directly (first = 1000 starts at item 1000), two batches that follow each other equal one, and the output is the same for any threads (0 means the hardware count).core::batch_options has count, first, threads and unique. The request describes one item; the options describe the batch..unique = core::unique_rule::by<device>(key) regenerates an item whose key already appeared, with a deterministic retry seed, and throws std::runtime_error naming the item when the attempts run out.core::jsonl_writer writes one to_json() per line, core::csv_writer flattens each result's JSON into dotted columns (screen.width). Any callable (std::uint64_t index, device&) works.About 9 µs a device on one thread (110,000 a second, lite, clang -O2).
| Exception | When |
|---|---|
std::runtime_error | A generation call before data is loaded |
std::invalid_argument | A reference date before 2021-01-01 or not a valid date; an unknown country or platform; a kind that contradicts the platform; a browser that does not run on the platform or the OS version by the date; a country without registered addresses and no safe; a locale that is not a BCP 47 tag (a language of 2 to 8 letters, then subtags of 1 to 8 letters or digits, separated by - or _) |
dasmig::core::tsv_error | A malformed table or layer (message gives file, line and column); the data is unchanged |
std::invalid_argument from extend(path) | A file whose name is not one of the tables |