Bare Metal

Port the Hubble Device SDK to a bare metal environment — one without a traditional real-time operating system (RTOS) such as FreeRTOS or Zephyr.

Requirements

  • Device key (generated when you register a new device to your organization through the Hubble Cloud API).

  • A counter source for EID rotation and key derivation — either device uptime (recommended for bare metal, no clock required) or current Unix time. See Counter Source below.

  • Encryption library or custom implementation.

Overview

Bare metal implementations run directly on the microcontroller hardware, often using vendor-provided Hardware Abstraction Layers (HALs) or custom timing infrastructure. This guide outlines how to compile a basic Bluetooth® Low Energy (BLE) beacon in a bare metal environment that advertises on the Hubble Terrestrial Network.

Porting the Hubble Device SDK to a new platform requires

  • Including Hubble source files and cryptographic implementation.

  • Choosing a counter source (device uptime or Unix time).

  • Specific configuration defines.

  • Implementing two abstraction layers: system abstraction and cryptographic abstraction.

Counter Source

The SDK derives a rotating time counter that drives EID rotation, key derivation, and BLE address generation. You select where that counter comes from at compile time. The two sources are mutually exclusive — defining both produces a build error.

Counter Source

Define

Requires a clock?

Best for

Device uptime (recommended for bare metal)

CONFIG_HUBBLE_COUNTER_SOURCE_DEVICE_UPTIME

No

Devices without a real-time clock (RTC), reliable time source, or a way to provision Unix time.

Unix time

CONFIG_HUBBLE_COUNTER_SOURCE_UNIX_TIME

Yes

Devices with an RTC or network time synchronization.

Unix Time

In Unix time mode the counter is derived from the current Unix Epoch time, so the device must know the wall-clock time before it can generate valid advertisements. hubble_init() takes the current Unix time in milliseconds since the Unix epoch:

  • A value of 0 is invalid and returns an error.

  • Update the time later with hubble_time_set() (for example, after an RTC read or a network time sync).

/* Unix time mode — provide current Unix time in milliseconds */
uint64_t unix_time_ms = 1705881600000;
int ret = hubble_init(unix_time_ms, master_key);

Source Files

To build the Hubble Device SDK, include the following source files in your project. Ensure your build system includes the SDK’s include/ directory in the header search path.

File

Purpose

src/hubble.c

Core SDK initialization and counter source (device uptime / Unix time) management.

src/hubble_ble.c

BLE advertisement packet generation.

src/crypto/mbedtls.c or src/crypto/psa.c

Cryptographic implementation (choose one, or implement and port your own).

Your port file(s)

Platform-specific implementations of sys.h and optionally crypto.h (see Abstraction Layers below).

Configuration Defines

Hubble Device SDK uses compile-time configuration defines. Create a configuration header or pass these as compiler flags.

Required Defines

/* Select the key size. Define exactly one of the following.
 * This choice selects the AES-128 vs AES-256 cipher. */
#define CONFIG_HUBBLE_NETWORK_KEY_128       /* 128-bit keys */
/* #define CONFIG_HUBBLE_NETWORK_KEY_256 */ /* 256-bit keys */

/* Enable BLE network functionality */
#define CONFIG_HUBBLE_BLE_NETWORK

Define exactly one of CONFIG_HUBBLE_NETWORK_KEY_128 or CONFIG_HUBBLE_NETWORK_KEY_256. If neither is defined the build fails with a compile error.

Counter Source Defines

Select exactly one counter source (see Counter Source). Defining both produces a build error; defining neither falls back to the SDK default of Unix time.

/* Recommended for bare metal: derive the counter from device uptime.
 * No real-time clock or time provisioning required. */
#define CONFIG_HUBBLE_COUNTER_SOURCE_DEVICE_UPTIME

/* Alternative: derive the counter from Unix time (requires a clock). */
/* #define CONFIG_HUBBLE_COUNTER_SOURCE_UNIX_TIME */

The EID rotation period controls how often the counter advances. Currently only daily rotation (86400 seconds) is supported for backend compatibility.

/* EID rotation period in seconds (currently only 86400 / daily is supported) */
#define CONFIG_HUBBLE_EID_ROTATION_PERIOD_SEC 86400

Optional Defines

/* Strict nonce reuse checking. This is ENABLED BY DEFAULT in the SDK
 * (the Kconfig default is y). A bare-metal build has no Kconfig
 * defaults, so define it explicitly to keep the check active.
 * Leaving it out disables the check, which is strongly discouraged
 * outside of testing. */
#define CONFIG_HUBBLE_NETWORK_SECURITY_ENFORCE_NONCE_CHECK

/* Enable custom sequence counter implementation */
#define CONFIG_HUBBLE_NETWORK_SEQUENCE_NONCE_CUSTOM

Abstraction Layers

Two abstraction layers must be implemented to port Hubble Device SDK:

  1. System Abstraction (sys.h) — Provides timing, logging, and optional sequence counter functionality.

  2. Cryptographic Abstraction (crypto.h) — Provides AES-CTR encryption, AES-CMAC authentication, and secure memory handling.

System Abstraction

sys.h

The system abstraction layer is defined in include/hubble/port/sys.h. You must implement the following functions:

  • hubble_uptime_get()

  • hubble_log()

  • (Optional) hubble_sequence_counter_get()

hubble_uptime_get

uint64_t hubble_uptime_get(void);

Returns the system uptime in milliseconds since boot, and must provide monotonically increasing values. This function underpins both counter sources: in device uptime mode it is the sole input to the time counter, and in Unix time mode it is used alongside the provisioned timestamp to track Unix time between syncs.

Requirements

  • Return value in milliseconds.

  • Must handle timer overflow/wraparound correctly.

  • Timing precision requirement: ±500 ppm (0.05%) or better.

  • The SDK expects this function to be called from a single execution context. Thread safety is not required for bare metal implementations.

Additional Context for Bare Metal Implementation

Most microcontrollers provide a hardware timer or system tick counter. Your implementation should:

  • Read the hardware timer/counter value.

  • Track overflows to extend beyond the native timer width.

  • Convert to milliseconds based on the timer frequency.

Example Implementation Pattern

#include <hubble/port/sys.h>

/* Example using a 32-bit hardware timer at known frequency */
uint64_t hubble_uptime_get(void)
{
    static uint64_t overflow_count = 0;
    static uint32_t last_ticks = 0;

    uint32_t current_ticks = your_hardware_timer_get();

    /* Detect overflow (assumes function called frequently enough) */
    if (current_ticks < last_ticks) {
        overflow_count += (1ULL << 32);
    }
    last_ticks = current_ticks;

    uint64_t total_ticks = current_ticks + overflow_count;

    /* Convert to milliseconds */
    return (total_ticks * 1000ULL) / YOUR_TIMER_FREQUENCY_HZ;
}

Note

For a real-world example using the Nordic SoftDevice with app_timer see the hubble-reference-nordic-softdevice implementation on GitHub.

hubble_log

int hubble_log(enum hubble_log_level level, const char *format, ...);

Logs a formatted message at the specified severity level. This function is application-specific and should integrate with your project’s logging infrastructure.

Log Levels

enum hubble_log_level {
    HUBBLE_LOG_DEBUG,
    HUBBLE_LOG_INFO,
    HUBBLE_LOG_WARNING,
    HUBBLE_LOG_ERROR,
};

Additional Context for Bare Metal Implementation

  • Minimal/No-op: Return 0 without output (suitable for production with size constraints).

  • UART/Serial: Format and transmit via UART.

  • RTT/SWO: Use debug probe logging (J-Link RTT, SWO, etc.).

  • Vendor Logging: Integrate with your MCU vendor’s logging framework.

Example: Minimal Implementation

int hubble_log(enum hubble_log_level level, const char *format, ...)
{
    (void)level;
    (void)format;
    return 0;
}

Example: UART Implementation

#include <hubble/port/sys.h>
#include <stdarg.h>
#include <stdio.h>

int hubble_log(enum hubble_log_level level, const char *format, ...)
{
    static const char *level_names[] = {"DBG", "INF", "WRN", "ERR"};
    char buffer[128];
    va_list args;

    int offset = snprintf(buffer, sizeof(buffer), "[%s] ", level_names[level]);

    va_start(args, format);
    int len = vsnprintf(buffer + offset, sizeof(buffer) - offset, format, args);
    va_end(args);

    if (len > 0) {
        your_uart_write(buffer, offset + len);
    }

    return 0;
}

hubble_sequence_counter_get

uint16_t hubble_sequence_counter_get(void);

Optional function returns a 10-bit sequence counter (0–1023) used for key rotation, nonce generation, and BLE address derivation. The counter should increment with each BLE advertisement and wrap from 1023 back to 0.

Default Behavior

If you do not define CONFIG_HUBBLE_NETWORK_SEQUENCE_NONCE_CUSTOM, the SDK provides a default implementation that maintains a simple incrementing counter in RAM. This is suitable for most bare metal applications.

When to Implement Custom

Using hubble_sequence_counter_get is optional. Implement this function if you need:

  • Persistence across device resets (requires non-volatile storage)

  • Synchronization with external systems

  • Custom counter management logic

Example: Custom Implementation with Non-Volatile Storage (NVS)

#define CONFIG_HUBBLE_NETWORK_SEQUENCE_NONCE_CUSTOM

uint16_t hubble_sequence_counter_get(void)
{
    static uint16_t counter = 0;
    static bool initialized = false;

    if (!initialized) {
        /* Load from non-volatile storage on first call */
        counter = your_nvs_read_u16("hubble_seq");
        initialized = true;
    }

    uint16_t current = counter;
    counter = (counter + 1) & HUBBLE_BLE_MAX_SEQ_COUNTER;  /* 0x3FF = 1023 */

    /* Persist periodically or on significant changes */
    your_nvs_write_u16("hubble_seq", counter);

    return current;
}

Cryptographic Abstraction

crypto.h

The cryptographic abstraction layer is defined in include/hubble/port/crypto.h. The SDK requires AES-CTR encryption and AES-CMAC authentication.

Using a Library

The Hubble Device SDK includes ready-to-use implementations for common cryptographic libraries. To use a provided implementation, include the matching source file in your build and link against its underlying crypto library.

Note

In Kconfig-based builds the crypto backend is chosen with the CONFIG_HUBBLE_NETWORK_CRYPTO_MBEDTLS / CONFIG_HUBBLE_NETWORK_CRYPTO_PSA / CONFIG_HUBBLE_NETWORK_CRYPTO_CUSTOM choice (CONFIG_HUBBLE_NETWORK_CRYPTO_PSA is the default), and the build system compiles the matching file for you. The crypto source files themselves do not test that symbol, so a bare-metal build makes the same selection simply by compiling exactly one of src/crypto/mbedtls.c, src/crypto/psa.c, or your own implementation. You do not need to define CONFIG_HUBBLE_NETWORK_CRYPTO_* for the source file to build. Note that both mbedtls.c and psa.c select the AES cipher from the CONFIG_HUBBLE_NETWORK_KEY_128 / CONFIG_HUBBLE_NETWORK_KEY_256 choice, so that define must still be set as described under Required Defines.

Library

Source File

Detail

Mbed TLS

src/crypto/mbedtls.c

Widely supported, good balance of size and features.

PSA Crypto API

src/crypto/psa.c

ARM’s standard crypto API, supports hardware acceleration.

For most bare metal projects, we recommend using Mbed TLS or the PSA Crypto API.

  • Both are actively maintained and widely audited.

  • Support hardware acceleration on many platforms.

  • Provide configurable builds to minimize code size.

  • Available from most MCU vendors’ SDKs.

Tip

We particularly recommend PSA Crypto API if your platform supports it, as it provides a standardized interface that can leverage hardware crypto accelerators when available.

An alternative is wolfSSL. This option offers small footprint optimizations (--enable-smallstack, --enable-minimal) and professional support. However it may require a commercial license for some use cases.

We do not recommend TinyCrypt. While some developers have used TinyCrypt due to its small size, we do not officially support it and recommend against its use.

  • No longer maintained: The project has been abandoned by Intel.

  • Limited side-channel protections: TinyCrypt implements only generic timing-attack countermeasures, making it potentially vulnerable on platforms where side-channel attacks are a concern.

  • Known vulnerabilities: Static analysis has identified issues including integer overflows (CWE-190) and invalid pointer arithmetic (CWE-823).

  • Limited algorithm support: Only supports AES-128, not AES-256.

Note

If code size is a critical constraint, consider using Mbed TLS with a minimal configuration or the PSA Crypto API with hardware acceleration.

Custom Implementation

If you need to implement the crypto layer yourself (for example, to use hardware crypto peripherals directly), implement the Hubble Cryptographic Functions API (see the Core API reference).

Complete Example

Below is a complete example of a minimal bare metal port:

/* hubble_port_bare_metal.c */

#include <hubble/port/sys.h>
#include <hubble/port/crypto.h>
#include <stdarg.h>
#include <string.h>

/* Include your platform headers */
#include "your_hardware_timer.h"
#include "your_uart.h"

/*
 * System Abstraction
 */

uint64_t hubble_uptime_get(void)
{
    static uint64_t overflow_ticks = 0;
    static uint32_t last_ticks = 0;

    uint32_t current = hardware_timer_get_ticks();

    if (current < last_ticks) {
        overflow_ticks += 0x100000000ULL;
    }
    last_ticks = current;

    return ((current + overflow_ticks) * 1000ULL) / TIMER_FREQ_HZ;
}

int hubble_log(enum hubble_log_level level, const char *format, ...)
{
    /* Implement based on your logging needs, or leave as no-op */
    (void)level;
    (void)format;
    return 0;
}

/*
 * Note: hubble_sequence_counter_get() uses the SDK's default
 * implementation unless CONFIG_HUBBLE_NETWORK_SEQUENCE_NONCE_CUSTOM
 * is defined.
 */

Warning

For cryptographic functions, link against src/crypto/mbedtls.c or src/crypto/psa.c, or provide your own implementation.

Validation

Test your port by:

  1. Verifying hubble_uptime_get() returns monotonically increasing values.

  2. Calling hubble_init() with your key and the appropriate initial value for your counter source:

    • Device uptime mode: an initial counter value (0 to start at epoch 0, or a saved counter to resume).

    • Unix time mode: a known, non-zero Unix timestamp in milliseconds.

  3. Generating advertisements with hubble_ble_advertise_get() and verifying output length.

  4. Checking logs (if implemented) for any error messages.

In your main loop, use hubble_ble_advertise_expiration_get() to decide when to regenerate the advertisement. It returns the number of milliseconds until the current advertisement expires, which is the natural signal for a bare-metal loop to call hubble_ble_advertise_get() again and refresh the payload in step with the EID rotation period.

Troubleshooting

Linker errors for crypto functions

  • Ensure you’ve included either src/crypto/mbedtls.c, src/crypto/psa.c, or your own crypto implementation.

  • Verify the crypto library (Mbed TLS, PSA, etc.) is properly linked.

“CONFIG_HUBBLE_NETWORK_KEY_256 or CONFIG_HUBBLE_NETWORK_KEY_128 must be defined” error

  • Define exactly one of CONFIG_HUBBLE_NETWORK_KEY_128 or CONFIG_HUBBLE_NETWORK_KEY_256 in your build or config header.

“Cannot define both … COUNTER_SOURCE …” build error

  • The two counter sources are mutually exclusive. Define only CONFIG_HUBBLE_COUNTER_SOURCE_DEVICE_UPTIME or CONFIG_HUBBLE_COUNTER_SOURCE_UNIX_TIME, not both.

“Unix Epoch time not initialized” warning

  • This appears in Unix time mode when no valid time has been set. Call hubble_init() with a non-zero Unix timestamp, or call hubble_time_set() once a time source is available.

  • If your device has no clock, use device uptime mode (CONFIG_HUBBLE_COUNTER_SOURCE_DEVICE_UPTIME) instead, which needs no time provisioning.

Timer overflow causing time jumps

  • Ensure hubble_uptime_get() is called frequently enough to detect overflows.

  • For 32-bit timers at 1 MHz, overflow occurs every ~71 minutes.

Nonce reuse warnings

  • If you see “Re-using same nonce is insecure!” warnings, ensure your sequence counter is incrementing properly.

  • These warnings come from CONFIG_HUBBLE_NETWORK_SECURITY_ENFORCE_NONCE_CHECK, which is enabled by default. In a bare-metal build without Kconfig defaults, define it explicitly to keep the check active (see Optional Defines).