16 KiB
Feature Specification: Core Data Layer
Feature Branch: 012-core-data
Created: 2026-07-27
Status: Migrated
Input: Brownfield migration — reverse-engineered from existing core/data module
Summary
The Core Data Layer is the central orchestration hub of Meshtastic-Android. It provides concrete implementations of all repository and manager interfaces defined in core/repository, bridging the mesh radio transport layer (core/network) with the persistence layer (core/database) and the feature modules. The module handles mesh connection lifecycle, packet routing and processing, node management, session tracking, radio configuration, MQTT integration, firmware release data, XModem file transfer, and telemetry. All implementations reside in commonMain with Koin DI component scan.
Goals
- Centralized data orchestration — provide a single module with all
*Implclasses that feature modules depend on via interface-only injection. - Mesh connection lifecycle — manage the full connect → handshake → config complete → operational → disconnect lifecycle with status notifications.
- Packet processing pipeline — decode, route, and persist all
FromRadiopackets through a layered handler chain (packet → message → telemetry → admin → store-forward → neighbor). - Node management — maintain an in-memory node database backed by Room, with identity, position, telemetry, and hardware metadata.
- Session management — track per-node remote-admin sessions with TTL, passkey rotation, and automatic expiry aligned with firmware's 300s TTL.
Non-Goals
- Transport-level concerns (BLE, TCP, Serial) — handled by
core/network. - Database schema definitions or DAO interfaces — handled by
core/database. - Domain model definitions — handled by
core/model. - UI or ViewModel logic — handled by
feature/*modules. - Proto message definitions — handled by
core/proto(read-only upstream).
User Scenarios & Testing (mandatory)
User Story 1 — Mesh Connection Lifecycle (Priority: P1)
When the app connects to a Meshtastic radio, the data layer orchestrates the full handshake: device identification, configuration exchange, node DB loading, and transition to CONNECTED state. The connection manager coordinates with the radio interface, node manager, notifications, and analytics.
Why this priority: Every feature depends on an active mesh connection. Without a successful handshake, no data flows.
Independent Test: Can be validated by simulating a radio connection and verifying state transitions through Disconnected → DeviceSleep → Connected.
Acceptance Scenarios:
- Given a radio transport is available, When the connection manager starts, Then it transitions through
Disconnected → DeviceSleep → Connectedas handshake packets arrive. - Given a config-complete packet is received, When the handshake finishes, Then the node DB is saved, MQTT is started if configured, and connection state is set to
Connected. - Given the radio disconnects unexpectedly, When the transport reports disconnection, Then the connection manager transitions to
DeviceSleepand analytics are reported. - Given multiple connect/disconnect cycles occur, When each cycle completes, Then no coroutine leaks or state corruption occurs.
User Story 2 — Packet Processing Pipeline (Priority: P1)
When the radio receives a mesh packet, it flows through a pipeline of handlers: FromRadioPacketHandler dispatches to PacketHandler, which routes to MeshMessageProcessor, TelemetryPacketHandler, AdminPacketHandler, StoreForwardPacketHandler, and NeighborInfoHandler based on port number.
Why this priority: All mesh data (messages, telemetry, admin responses, traceroutes) enters the app through this pipeline.
Independent Test: Feed a FromRadio proto to handleFromRadio() and verify the correct handler processes it and persists the result.
Acceptance Scenarios:
- Given a
FromRadiopacket with aMESH_PACKETvariant, When processed byFromRadioPacketHandler, Then it is decoded and dispatched toPacketHandler. - Given a packet with
PortNum.TEXT_MESSAGE_APP, When routed byPacketHandler, ThenMeshMessageProcessorpersists it as aPacketentity and triggers notifications. - Given a packet with
PortNum.TELEMETRY_APP, When routed, ThenTelemetryPacketHandlerupdates the node's device/environment/power metrics. - Given a packet with
PortNum.ADMIN_APP, When routed, ThenAdminPacketHandlerprocesses admin responses and updates radio configuration. - Given a store-and-forward history packet, When processed, Then
StoreForwardPacketHandlerpersists it without duplicate notification.
User Story 3 — Node Management (Priority: P1)
The node manager maintains a reactive in-memory cache of all mesh nodes, synchronized with Room persistence. It handles node discovery, identity updates, position changes, and last-heard timestamps.
Why this priority: Node data is displayed across 6+ feature screens. Stale or missing node data degrades the entire UX.
Independent Test: Simulate node info packets and verify the node cache updates and Room persists correctly.
Acceptance Scenarios:
- Given a
NodeInfopacket is received, WhenNodeManagerprocesses it, Then the node is upserted in both the in-memory cache and Room database. - Given a node's user identity changes (new long_name/short_name), When the update is processed, Then the cached
Nodereflects the new identity immediately. - Given a node has not been heard for >15 minutes, When
isOnlineis evaluated, Then it returnsfalse. - Given
loadCachedNodeDB()is called on startup, Then all persisted nodes are loaded into the in-memory cache.
User Story 4 — Radio Configuration Management (Priority: P2)
The radio config repository manages the local copy of the connected device's configuration (LoRa, device, display, network, Bluetooth, position, power, security). Configuration changes are sent via admin packets and confirmed via response packets.
Why this priority: Settings screens depend on accurate config state. Stale config leads to user confusion.
Independent Test: Simulate config response packets and verify the repository state updates.
Acceptance Scenarios:
- Given a
Configadmin response is received, WhenMeshConfigHandlerprocesses it, Then the corresponding config flow inRadioConfigRepositoryis updated. - Given the user changes a config value, When the change is sent via
CommandSender, Then the admin packet is constructed and sent to the radio. - Given a
MeshConfigFlowManageris active, When config responses arrive, Then they are correlated with pending requests and the flow completes.
User Story 5 — Session Management for Remote Admin (Priority: P2)
The session manager tracks per-node remote-admin sessions, including passkey storage, TTL tracking, and automatic expiry detection. This enables the remote admin feature to maintain sessions across navigation without re-authenticating.
Why this priority: Remote admin is a power-user feature. Session management prevents passkey conflicts when administering multiple nodes.
Independent Test: Create sessions for multiple nodes and verify TTL expiry and passkey rotation.
Acceptance Scenarios:
- Given a remote-admin session is established with node A, When the passkey response arrives, Then
SessionManagerstores the passkey mapped to node A's num. - Given sessions exist for nodes A and B, When node B's session is accessed, Then node A's passkey is not overwritten (per-node isolation).
- Given a session is 240+ seconds old, When
statusFlow(nodeNum)is observed, Then it emitsExpired. - Given a session receives a refreshed passkey, When the refresh is processed, Then the TTL resets to 300s.
Edge Cases
- What happens when
FromRadiocontains an unknownpayloadVariant? It is logged and ignored. - What happens when the packet handler receives a packet with
from == 0? It is treated as from the local node. - What happens when MQTT reconnects mid-session?
MqttManagerImplre-subscribes to all configured topics. - What happens when
withDb()is called during a database switch? The connection-pool-closed exception is caught and retried once with the new DB instance.
Architecture
Key Components
| Component | File | Purpose |
|---|---|---|
MeshConnectionManagerImpl |
manager/MeshConnectionManagerImpl.kt |
Full mesh connection lifecycle: handshake, config exchange, state machine |
FromRadioPacketHandlerImpl |
manager/FromRadioPacketHandlerImpl.kt |
Top-level FromRadio dispatcher — routes to packet, config, nodeinfo handlers |
PacketHandlerImpl |
manager/PacketHandlerImpl.kt |
Per-portnum routing: text → message processor, telemetry → telemetry handler, etc. |
MeshMessageProcessorImpl |
manager/MeshMessageProcessorImpl.kt |
Message persistence, notification dispatch, contact-aware filtering |
TelemetryPacketHandlerImpl |
manager/TelemetryPacketHandlerImpl.kt |
Device/environment/power metric updates on nodes |
AdminPacketHandlerImpl |
manager/AdminPacketHandlerImpl.kt |
Admin response processing, config/module updates |
NodeManagerImpl |
manager/NodeManagerImpl.kt |
In-memory node cache + Room sync, identity updates, position tracking |
SessionManagerImpl |
manager/SessionManagerImpl.kt |
Per-node remote-admin session tracking with TTL and passkey rotation |
CommandSenderImpl |
manager/CommandSenderImpl.kt |
Constructs and sends admin/data packets to the radio |
MeshRouterImpl |
manager/MeshRouterImpl.kt |
Service action routing (send message, request position, traceroute, etc.) |
MeshConfigHandlerImpl |
manager/MeshConfigHandlerImpl.kt |
Config/module response processing and flow updates |
MeshConfigFlowManagerImpl |
manager/MeshConfigFlowManagerImpl.kt |
Coroutine-based config request/response correlation |
MqttManagerImpl |
manager/MqttManagerImpl.kt |
MQTT connection lifecycle management |
XModemManagerImpl |
manager/XModemManagerImpl.kt |
XModem file transfer protocol for firmware updates |
HistoryManagerImpl |
manager/HistoryManagerImpl.kt |
Sent-packet history for retry and deduplication |
MessageFilterImpl |
manager/MessageFilterImpl.kt |
Message filtering (mute, ignore, contact-level rules) |
NodeRepositoryImpl |
repository/NodeRepositoryImpl.kt |
Node CRUD, sort, filter, reactive flows |
PacketRepositoryImpl |
repository/PacketRepositoryImpl.kt |
Message/packet CRUD, paging, unread counts |
RadioConfigRepositoryImpl |
repository/RadioConfigRepositoryImpl.kt |
Radio config state flows (LoRa, device, display, etc.) |
FirmwareReleaseRepositoryImpl |
repository/FirmwareReleaseRepositoryImpl.kt |
Firmware release data from JSON + local cache |
DeviceHardwareRepositoryImpl |
repository/DeviceHardwareRepositoryImpl.kt |
Hardware catalog from JSON + local cache |
CoreDataModule |
di/CoreDataModule.kt |
Koin module with component scan + explicit providers |
Requirements (mandatory)
Functional Requirements
- FR-001: System MUST implement
MeshConnectionManagerinterface with full connect/handshake/disconnect lifecycle. - FR-002: System MUST process all
FromRadiopacket variants (MESH_PACKET, CONFIG_COMPLETE_ID, MY_INFO, NODE_INFO, METADATA, MQTTCLIENT_PROXY_MESSAGE, CLIENT_NOTIFICATION). - FR-003: System MUST route decoded mesh packets by
PortNumto the appropriate handler (text, telemetry, admin, traceroute, store-forward, neighbor-info, position). - FR-004: System MUST persist messages as
Packetentities in Room and emit notification events for new messages. - FR-005: System MUST maintain an in-memory
Nodecache synchronized with Room, supporting reactiveFlow<List<Node>>access. - FR-006: System MUST track per-node remote-admin sessions with 300s TTL, passkey storage, and automatic expiry.
- FR-007: System MUST manage radio configuration state as
StateFlowper config type (LoRa, device, display, network, Bluetooth, position, power, security). - FR-008: System MUST support XModem file transfer for firmware updates via
XModemManager. - FR-009: System MUST provide
CommandSenderfor constructing and sending admin/data packets to the radio. - FR-010: System MUST filter messages based on contact settings (muted, ignored, message-filtering disabled).
- FR-011: System MUST manage MQTT connection lifecycle (connect, subscribe, publish, disconnect) aligned with radio config.
- FR-012: System MUST provide switching data sources between real and mock node-info read/write sources.
- FR-013: System MUST handle traceroute snapshot persistence and overlay construction.
- FR-014: System MUST maintain firmware release and device hardware catalogs with JSON + local DB caching.
Non-Functional Requirements
- NFR-001: All implementations MUST reside in
commonMainsource set (Constitution §I). - NFR-002: All coroutines MUST use named dispatchers from
CoroutineDispatchers— noDispatchers.IOdirectly (Constitution §VII). - NFR-003: Error handling MUST use
safeCatching {}where applicable (Constitution §VII). - NFR-004: Session state MUST use
atomicfufor thread-safe access to thePersistentMap. - NFR-005: Database access via
withDb()MUST tolerate connection-pool-closed races during DB switching. - NFR-006: Node cache updates MUST be conflated to avoid overwhelming UI collectors during rapid mesh updates.
Source-Set Impact
| Source Set | Impact | Justification |
|---|---|---|
commonMain |
40 files (~10,500 LOC) | All manager and repository implementations |
commonTest |
19 files (~1,700 LOC) | Unit tests for managers and repositories |
androidMain |
0 files | No platform-specific code |
Privacy Assessment
- No PII, location data, or cryptographic keys logged — message content is never logged
- Node identifiers are anonymized in log output via
anonymize()utility - Proto submodule (
core/proto) not modified (read-only upstream)
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: All 7 repository implementations pass their corresponding unit tests.
- SC-002: Connection lifecycle state machine correctly transitions through all states for BLE, TCP, and Serial transports.
- SC-003: Packet processing pipeline routes all known
PortNumvalues to the correct handler. - SC-004: Session manager correctly isolates per-node passkeys — concurrent sessions for 2+ nodes do not interfere.
- SC-005:
NodeManager.loadCachedNodeDB()populates the in-memory cache from Room within 500ms for 100 nodes. - SC-006:
MessageFiltercorrectly suppresses notifications for muted/ignored contacts. - SC-007: XModem transfer handles NAK retries and completes a simulated firmware file.
- SC-008: Config flow manager correlates request/response pairs within firmware's response timeout.
- SC-009: All 19 existing test files pass with
allTeststarget. - SC-010:
MqttManagerreconnects within 5s after network recovery.
Assumptions
- All business logic resides in
commonMainsource set. - Koin DI with
@ComponentScanauto-discovers all@Single-annotated implementations. core/repositorydefines the interface contracts; this module provides the implementations.- Room database access is via
DatabaseProvider.withDb()— implementations never hold direct DAO references. - The
Clockdependency is injected for testability (noSystem.currentTimeMillis()calls).