Device Identity

Modern devices are often composed of multiple hardware components, such as embedded boards and peripherals, that can fail, be replaced, or become temporarily unavailable. This creates a fundamental challenge: how to preserve the identity and other essential information of a device when its individual parts are not permanent. The CriticalDataService addresses this problem by ensuring that small but essential pieces of data, such as a device serial number or provisioning information, persist across hardware changes.

The service works by distributing critical data across multiple hardware components, referred to as sources. Each source stores a copy of the same data using whatever limited persistent storage it has available. Because the data is replicated across multiple locations, the system can recover the correct information even if one or more components are replaced or fail. When a new board is introduced, it can be repopulated with the correct data from the remaining sources, ensuring continuity of device identity.

To maintain consistency, the CriticalDataService uses a voting algorithm. On startup, the service collects data from all available sources and evaluates it to determine the correct version. Each source contributes a vote, optionally weighted by its level of trust. Corrupted or invalid data is automatically discarded, and once a consensus is reached, the resolved dataset is written back to all sources to restore redundancy. This process ensures that all components eventually store the same, correct data.

Critical data is structured as key-value pairs, where each value is stored in JSON format. All entries are part of a single shared dataset, allowing multiple services to store their data within the same system. Keys follow a namespaced naming convention (for example, service.feature) to prevent collisions between services. The system does not enforce a global structure, so each service is responsible for defining and managing its own data format.

Critical data is meant for small, essential data that remains the same across boots cycles. The total available space is constrained by the smallest participating source, and in some cases, a source may only be able to store a signature of the data rather than the full dataset.

Critical data may not be immediately available when the system starts. Some sources depend on hardware initialization and may become ready asynchronously. Once sources are available, CritcalDataService evaluates the data, and makes the correct critical data available. Applications can monitor this process using a readiness indicator or register for callbacks to be notified when the data becomes available or if it cannot be resolved.

Each source is responsible for persisting and retrieving the raw data as a byte array, which means that the source must track the length of data along with the data itself. The data provided by the CriticalDataService is compressed and contains a CRC32 check for corruption, so sources need not duplicate this.

Sources are typically added to a BeanContext, which automatically autowires them into the CriticalDataService. Sources are dynamically added as they are created, or become ready if they implement Ready. Even after the correct data has been determined, newly available sources must notify the service when they are ready so that the correct critical data can be replicated back to the source.

The voting mechanism is central to the reliability of the system. Each source contributes a weighted vote, and the dataset with the highest combined weight is selected as the correct version. Proper configuration of source weights is important to avoid ties and ensure meaningful consensus, particularly in systems with a small number of sources. In general, having multiple independent sources improves the robustness of the system, while too few sources reduces the effectiveness of the voting process.

Finally, the CriticalDataListener that allows applications to react to changes in critical data. Callbacks are triggered when data becomes available, is updated, or cannot be resolved. This enables applications to integrate seamlessly with the lifecycle of critical data and respond appropriately to changes in system state.