Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions meshtastic/admin.options
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,11 @@
*HamParameters.call_sign max_size:8
*HamParameters.short_name max_size:5
*NodeRemoteHardwarePinsResponse.node_remote_hardware_pins max_count:16

# Bounded client-app metadata store. app_id is ASCII [a-z0-9._-]{1,32}, so
# 33 covers the 32-byte max plus the nanopb null terminator. Payload cap is
# 512 bytes per the v1 RFC. TODO(maintainer): confirm sizes during review.
*ClientAppData.app_id max_size:33
*ClientAppData.payload max_size:512
*AdminMessage.get_client_app_data_request max_size:33
*AdminMessage.delete_client_app_data_request max_size:33
94 changes: 94 additions & 0 deletions meshtastic/admin.proto
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,39 @@ message AdminMessage {
* Parameters and sensor configuration
*/
SensorConfig sensor_config = 103;

/*
* Write a bounded local client-app metadata record. Local-only by
* default: the firmware refuses set_client_app_data from non-local
* senders. See top-level ClientAppData for the namespaced-not-owned
* caveat: any admin-capable client may overwrite any app_id, so
* callers must treat stored payloads as untrusted and recoverable.
* Validation failures (bad app_id, oversize payload, no slot free)
* are reported via meshtastic_Routing_Error_BAD_REQUEST.
* TODO(maintainer): confirm field-number allocation for 104..107.
*/
ClientAppData set_client_app_data = 104;

/*
* Read a stored client-app metadata record by app_id. The firmware
* replies with get_client_app_data_response: a populated record on
* hit, or a record with empty app_id to signal NOT_FOUND.
*/
string get_client_app_data_request = 105;

/*
* Stored client-app metadata in response to get_client_app_data_request.
* If app_id is empty, no record exists for the requested key.
*/
ClientAppData get_client_app_data_response = 106;

/*
* Delete a stored client-app metadata record by app_id. Local-only by
* default. Returns Routing_Error_NONE on success (record removed) or
* Routing_Error_BAD_REQUEST on invalid app_id. Deleting a missing
* app_id is treated as success (idempotent).
*/
string delete_client_app_data_request = 107;
}
}

Expand Down Expand Up @@ -760,3 +793,64 @@ message SHTXX_config {
*/
optional uint32 set_accuracy = 1;
}

/*
* Optional, bounded, local-node-only storage for opaque, app-owned metadata
* that a companion application can ask its locally-connected node to persist
* on its behalf. Not a filesystem, not a database, and not arbitrary NVRAM.
* It is a tiny fixed-slot table for a few small records.
*
* Intended to give clients an explicit, firmware-bounded place to store
* non-secret convenience metadata, without overloading user-visible
* fields like long_name or unrelated configuration surfaces.
*
* IMPORTANT: namespaced, not owned. The firmware enforces shape, payload
* size, and record-count limits, but does NOT authenticate which companion
* application is making a write request. app_id prevents accidental name
* collisions between apps, NOT malicious or intentional overwrites: any
* admin-capable client may overwrite or delete any app_id. Callers must
* therefore treat stored payloads as untrusted, optional, and recoverable.
*
* Do NOT store secrets, identity keys, session keys, paid-entitlement
* state, trust authority, blocklists, or any data used to make security,
* routing, authentication, or purchase decisions. Treat this as a
* convenience cache for non-critical app state only.
*
* Firmware never interprets `payload`, never broadcasts these records over
* LoRa, never includes them in NodeInfo, and never relays them via MQTT.
* Records are local-node-only, survive reboot, and are cleared by factory
* reset.
*
* Client guidance: gracefully fall back to app-local storage when the
* firmware does not support this feature; version your payloads via the
* `version` field below; keep payloads small (well under the 512-byte cap)
* to leave headroom for other apps.
*/
message ClientAppData {
/*
* Namespacing key. Must match `^[a-z0-9._-]{1,32}$`.
* Convention examples: "meshtastic-ios",
* "meshtastic-android", "thirdparty.example".
*/
string app_id = 1;

/*
* Application-defined schema version for `payload`. The firmware does not
* interpret this; clients use it to migrate or reject stale payloads.
*/
uint32 version = 2;

/*
* Opaque app-owned bytes, max 512. Firmware never inspects or interprets
* the contents.
*/
bytes payload = 3;

/*
* Unix epoch seconds, set by the firmware on every successful write.
* May be 0 if the firmware does not yet have a valid wall-clock time.
* Useful for clients to detect that another admin-capable client has
* overwritten or deleted-then-recreated the record since the last read.
*/
fixed32 updated_at = 4;
}
8 changes: 8 additions & 0 deletions meshtastic/localonly.options
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# nanopb sizing for local-only persistence wrappers.
# LocalConfig and LocalModuleConfig contain only nested message types whose
# sizing is governed by their own .options files, so they have no entries
# here.

# Bounded client-app metadata store: max 4 records per node by default.
# TODO(maintainer): confirm record cap during review.
*LocalClientAppData.records max_count:4
23 changes: 23 additions & 0 deletions meshtastic/localonly.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ syntax = "proto3";

package meshtastic;

import "meshtastic/admin.proto";
import "meshtastic/config.proto";
import "meshtastic/module_config.proto";

Expand Down Expand Up @@ -153,3 +154,25 @@ message LocalModuleConfig {
*/
uint32 version = 8;
}

/*
* On-disk wrapper for the bounded local client-app metadata store.
* Persisted by the firmware to /prefs/clientappdata.proto. Never sent over
* the wire and never broadcast. See ClientAppData (admin.proto) for the
* namespaced-not-owned caveat that governs how clients should treat the
* stored payloads.
*/
message LocalClientAppData {
/*
* The set of stored records. Bounded by localonly.options to a small
* number (initially 4). Firmware enforces uniqueness on app_id.
*/
repeated ClientAppData records = 1;

/*
* A version integer used to invalidate old save files when we make
* incompatible changes. Set at build time by NodeDB.cpp in the device
* code, mirroring LocalConfig.version / LocalModuleConfig.version.
*/
uint32 version = 2;
}