diff --git a/meshtastic/admin.proto b/meshtastic/admin.proto index e2e1eed4..06dd7e08 100644 --- a/meshtastic/admin.proto +++ b/meshtastic/admin.proto @@ -581,6 +581,44 @@ message LockdownAuth { * the locked state. Always honoured regardless of current lock state. */ bool lock_now = 4; + + /* + * Optional per-boot uptime cap on the unlocked session, in seconds. + * 0 = unlimited (token-only enforcement, suitable for unattended + * tower / infrastructure nodes). + * + * When non-zero, the firmware arms an uptime timer at unlock. On + * each expiry, while there is still boot-count budget, the firmware + * decrements the on-flash boot count in place, revokes per- + * connection admin auth (clients must re-authenticate to see + * content), re-engages the screen lock, and re-arms the timer + * without rebooting. Mesh routing keeps running across session + * boundaries; only when the boot-count budget reaches zero does + * the device hard-lock and reboot. + * + * Total exposure ceiling = ((resolved boot count) + 1) * max_session_seconds. + * The +1 accounts for the initial passphrase-unlocked session + * itself, since boots_remaining is the number of subsequent + * session rolls (each consuming one boot from the rollback ledger). + * The resolved boot count is the value the firmware writes into the + * token at unlock time: the client-supplied boots_remaining when + * non-zero, otherwise the firmware default (TOKEN_DEFAULT_BOOTS). + * Note that boots_remaining == 0 in this message means "use firmware + * default", NOT "zero boots" — a client computing the ceiling for + * display should mirror that resolution rather than multiplying the + * raw request value. + * + * The cap is persisted in the token, so it survives token-based + * auto-unlock across reboots. Explicit operator Lock Now still + * deletes the token and forces passphrase re-entry. + * + * Uses millis() (CPU uptime), not wall-clock time, so the cap is + * immune to GPS spoofing, RTC backup-battery removal, and Faraday + * cage isolation — none of those move the uptime counter. The only + * way to reset the session clock is a reboot, which costs a boot + * from the on-flash, HMAC-bound counter. + */ + uint32 max_session_seconds = 5; } /* diff --git a/meshtastic/config.proto b/meshtastic/config.proto index 3abe6292..8f554d0f 100644 --- a/meshtastic/config.proto +++ b/meshtastic/config.proto @@ -965,6 +965,24 @@ message Config { * ITU Region 3 Amateur Radio 2m band (144-148 MHz) */ ITU3_2M = 33; + + /* + * ITU Region 1 Amateur Radio 70cm band (430-440 MHz) + */ + ITU1_70CM = 34; + + /* + * ITU Region 2 Amateur Radio 70cm band (420-450 MHz) + * Note: Some countries do not allocate 420-430 MHz or 440-450 MHz. + * Check local law! + */ + ITU2_70CM = 35; + + /* + * ITU Region 3 Amateur Radio 70cm band (430-450 MHz) + * Note: Some countries do not allocate 440-450 MHz. Check local law! + */ + ITU3_70CM = 36; } /* diff --git a/meshtastic/deviceonly.options b/meshtastic/deviceonly.options index d6aae0c4..21e81276 100644 --- a/meshtastic/deviceonly.options +++ b/meshtastic/deviceonly.options @@ -12,6 +12,18 @@ *NodeInfoLite.hops_away int_size:8 *NodeInfoLite.next_hop int_size:8 +# Flattened user fields. long_name was 40 on UserLite; trimmed to 25 here so the +# slim header gives back ~15 B/node of RAM. Names that exceeded 24 chars on the +# wire (max 40) get truncated on store, with sanitizeUtf8 cleaning up partial +# multi-byte sequences at the boundary. +*NodeInfoLite.long_name max_size:25 +*NodeInfoLite.short_name max_size:5 +*NodeInfoLite.public_key max_size:32 +# hw_model + role used to be 32-bit enums inside UserLite. We pin them to one +# byte each on the slim header to claw back the alignment overhead. +*NodeInfoLite.hw_model int_size:8 +*NodeInfoLite.role int_size:8 + *UserLite.long_name max_size:40 *UserLite.short_name max_size:5 *UserLite.public_key max_size:32 # public key diff --git a/meshtastic/deviceonly.proto b/meshtastic/deviceonly.proto index b7c377a8..64e44ea5 100644 --- a/meshtastic/deviceonly.proto +++ b/meshtastic/deviceonly.proto @@ -50,6 +50,11 @@ message PositionLite { * TODO: REPLACE */ Position.LocSource location_source = 5; + + /* + * Indicates the bits of precision set by the sending node + */ + uint32 precision_bits = 6; } message UserLite { @@ -107,16 +112,11 @@ message NodeInfoLite { */ uint32 num = 1; - /* - * The user info for this node - */ - UserLite user = 2; - - /* - * This position data. Note: before 1.2.14 we would also store the last time we've heard from this node in position.time, that is no longer true. - * Position.time now indicates the last time we received a POSITION from that node. - */ - PositionLite position = 3; + // Tag 2 was UserLite user; flattened into tags 14..18 (presence via + // NODEINFO_BITFIELD_HAS_USER). + // Tag 3 was PositionLite position; moved to NodeDatabase.positions. + reserved 2, 3; + reserved "user", "position"; /* * Returns the Signal-to-noise ratio (SNR) of the last received message, @@ -128,20 +128,19 @@ message NodeInfoLite { * Set to indicate the last time we received a packet from this node */ fixed32 last_heard = 5; - /* - * The latest device metrics for the node. - */ - DeviceMetrics device_metrics = 6; + + // Tag 6 was DeviceMetrics device_metrics; moved to NodeDatabase.telemetry. + reserved 6; + reserved "device_metrics"; /* * local channel index we heard that node on. Only populated if its not the default channel. */ uint32 channel = 7; - /* - * True if we witnessed the node over MQTT instead of LoRA transport - */ - bool via_mqtt = 8; + // Tags 8/10/11 were via_mqtt/is_favorite/is_ignored bools; packed into bitfield. + reserved 8, 10, 11; + reserved "via_mqtt", "is_favorite", "is_ignored"; /* * Number of hops away from us this node is (0 if direct neighbor) @@ -149,28 +148,42 @@ message NodeInfoLite { optional uint32 hops_away = 9; /* - * True if node is in our favorites list - * Persists between NodeDB internal clean ups + * Last byte of the node number of the node that should be used as the next hop to reach this node. */ - bool is_favorite = 10; + uint32 next_hop = 12; /* - * True if node is in our ignored list - * Persists between NodeDB internal clean ups + * Bitfield for storing booleans. See NODEINFO_BITFIELD_* in src/mesh/NodeDB.h. */ - bool is_ignored = 11; + uint32 bitfield = 13; + + // Flattened user fields (formerly UserLite). macaddr dropped (deprecated 1.2.11). /* - * Last byte of the node number of the node that should be used as the next hop to reach this node. + * A full name for this user, i.e. "Kevin Hester". */ - uint32 next_hop = 12; + string long_name = 14; /* - * Bitfield for storing booleans. - * LSB 0 is_key_manually_verified - * LSB 1 is_muted + * A VERY short name, ideally two characters or an emoji. + * Suitable for a tiny OLED screen. */ - uint32 bitfield = 13; + string short_name = 15; + + /* + * Hardware model the user's device is running. + */ + HardwareModel hw_model = 16; + + /* + * The user's role in the mesh. + */ + Config.DeviceConfig.Role role = 17; + + /* + * The public key of the user's device, for PKI-based encrypted DMs. + */ + bytes public_key = 18; } /* @@ -236,6 +249,29 @@ message DeviceState { repeated NodeRemoteHardwarePin node_remote_hardware_pins = 13; } +// Satellite per-node entries; stored alongside the slim NodeInfoLite so nodes +// that never report don't pay the embedded cost. + +message NodePositionEntry { + uint32 num = 1; + PositionLite position = 2; +} + +message NodeTelemetryEntry { + uint32 num = 1; + DeviceMetrics device_metrics = 2; +} + +message NodeEnvironmentEntry { + uint32 num = 1; + EnvironmentMetrics environment_metrics = 2; +} + +message NodeStatusEntry { + uint32 num = 1; + StatusMessage status = 2; +} + message NodeDatabase { /* * A version integer used to invalidate old save files when we make @@ -248,6 +284,13 @@ message NodeDatabase { * New lite version of NodeDB to decrease memory footprint */ repeated NodeInfoLite nodes = 2 [(nanopb).callback_datatype = "std::vector"]; + + // Per-NodeNum satellite arrays. Constrained platforms (e.g. STM32WL) omit + // these via MESHTASTIC_EXCLUDE_*DB build flags. + repeated NodePositionEntry positions = 3 [(nanopb).callback_datatype = "std::vector"]; + repeated NodeTelemetryEntry telemetry = 4 [(nanopb).callback_datatype = "std::vector"]; + repeated NodeStatusEntry status = 5 [(nanopb).callback_datatype = "std::vector"]; + repeated NodeEnvironmentEntry environment = 6 [(nanopb).callback_datatype = "std::vector"]; } /* diff --git a/meshtastic/deviceonly_legacy.options b/meshtastic/deviceonly_legacy.options new file mode 100644 index 00000000..e23758f5 --- /dev/null +++ b/meshtastic/deviceonly_legacy.options @@ -0,0 +1,7 @@ +# nanopb options for the legacy migration descriptor. +# Mirrors deviceonly.options sizing for the NodeInfoLite_Legacy message so old +# /prefs/nodes.proto saves parse with the same byte layout. + +*NodeInfoLite_Legacy.channel int_size:8 +*NodeInfoLite_Legacy.hops_away int_size:8 +*NodeInfoLite_Legacy.next_hop int_size:8 diff --git a/meshtastic/deviceonly_legacy.proto b/meshtastic/deviceonly_legacy.proto new file mode 100644 index 00000000..a4bb0240 --- /dev/null +++ b/meshtastic/deviceonly_legacy.proto @@ -0,0 +1,50 @@ +syntax = "proto3"; + +package meshtastic; + +/* trunk-ignore(buf-lint/COMPILE) */ +import "meshtastic/deviceonly.proto"; +import "meshtastic/telemetry.proto"; +import "nanopb.proto"; + +option csharp_namespace = "Meshtastic.Protobufs"; +option go_package = "github.com/meshtastic/go/generated"; +option java_outer_classname = "DeviceOnlyLegacy"; +option java_package = "org.meshtastic.proto"; +option swift_prefix = ""; +option (nanopb_fileopt).include = ""; + +/* + * Legacy NodeInfoLite descriptor used only to decode pre-split + * /prefs/nodes.proto saves during the v24 -> v25 migration boot. + * This preserves the original NodeInfoLite-compatible field numbers needed + * to parse old wire bytes cleanly, including user (2), position (3), + * device_metrics (6), and the legacy-only compatibility fields via_mqtt (8), + * is_favorite (10), and is_ignored (11). Steady-state code does not use + * this struct; it is dropped after migration completes. This file should be + * removed once DEVICESTATE_MIN_VER advances past 24. + */ +message NodeInfoLite_Legacy { + uint32 num = 1; + UserLite user = 2; + PositionLite position = 3; + float snr = 4; + fixed32 last_heard = 5; + DeviceMetrics device_metrics = 6; + uint32 channel = 7; + bool via_mqtt = 8; + optional uint32 hops_away = 9; + bool is_favorite = 10; + bool is_ignored = 11; + uint32 next_hop = 12; + uint32 bitfield = 13; +} + +/* + * Legacy NodeDatabase shape: one repeated array of fat NodeInfoLite_Legacy + * with no satellite position/telemetry arrays. + */ +message NodeDatabase_Legacy { + uint32 version = 1; + repeated NodeInfoLite_Legacy nodes = 2 [(nanopb).callback_datatype = "std::vector"]; +} diff --git a/meshtastic/localonly.proto b/meshtastic/localonly.proto index 2a6c7cac..4f846e53 100644 --- a/meshtastic/localonly.proto +++ b/meshtastic/localonly.proto @@ -146,6 +146,11 @@ message LocalModuleConfig { */ ModuleConfig.TAKConfig tak = 17; + /* + * MeshBeacon Config + */ + ModuleConfig.MeshBeaconConfig mesh_beacon = 18; + /* * A version integer used to invalidate old save files when we make * incompatible changes This integer is set at build time and is private to diff --git a/meshtastic/mesh.options b/meshtastic/mesh.options index 7d3f8fea..27fc61f4 100644 --- a/meshtastic/mesh.options +++ b/meshtastic/mesh.options @@ -19,6 +19,7 @@ # outside of this envelope *Data.payload max_size:233 *Data.bitfield int_size:8 +*Data.xeddsa_signature max_size:64 *NodeInfo.channel int_size:8 *NodeInfo.hops_away int_size:8 diff --git a/meshtastic/mesh.proto b/meshtastic/mesh.proto index 9527ec8c..fe2304ea 100644 --- a/meshtastic/mesh.proto +++ b/meshtastic/mesh.proto @@ -899,8 +899,8 @@ enum HardwareModel { HELTEC_MESH_NODE_T1 = 133; /* - * B&Q Consulting Station G3: TBD - */ + * B&Q Consulting Station G3: TBD + */ STATION_G3 = 134; /* @@ -1210,6 +1210,11 @@ message Data { * Bitfield for extra flags. First use is to indicate that user approves the packet being uploaded to MQTT. */ optional uint32 bitfield = 9; + + /* + * XEdDSA signature for the payload + */ + bytes xeddsa_signature = 10; } /* @@ -1789,6 +1794,11 @@ message MeshPacket { * Indicates which transport mechanism this packet arrived over */ TransportMechanism transport_mechanism = 21; + + /* + * Indicates whether the packet has a valid signature + */ + bool xeddsa_signed = 22; } /* @@ -1925,6 +1935,13 @@ message NodeInfo { * Persistes between NodeDB internal clean ups */ bool is_muted = 13; + + /* + * True if node is signing its packets via XEdDSA + * Persists between NodeDB internal clean ups + * LSB 1 of the bitfield + */ + bool has_xeddsa_signed = 14; } /* diff --git a/meshtastic/mesh_beacon.options b/meshtastic/mesh_beacon.options new file mode 100644 index 00000000..cd4099cd --- /dev/null +++ b/meshtastic/mesh_beacon.options @@ -0,0 +1,3 @@ +*MeshBeacon.message max_size:101 +*MeshBeacon.offer_channel.name max_size:12 +*MeshBeacon.offer_channel.psk max_size:32 diff --git a/meshtastic/mesh_beacon.proto b/meshtastic/mesh_beacon.proto new file mode 100644 index 00000000..c6005ab7 --- /dev/null +++ b/meshtastic/mesh_beacon.proto @@ -0,0 +1,42 @@ +syntax = "proto3"; + +package meshtastic; + +import "meshtastic/channel.proto"; +import "meshtastic/config.proto"; + +option csharp_namespace = "Meshtastic.Protobufs"; +option go_package = "github.com/meshtastic/go/generated"; +option java_outer_classname = "MeshBeaconProtos"; +option java_package = "org.meshtastic.proto"; +option swift_prefix = ""; + +/* + * Payload for MESH_BEACON_APP packets. + * Periodically broadcast by nodes in beacon mode. + * Listeners deliver the text message to the local inbox and cache any offered + * channel/preset for the client app to act on — the firmware never auto-applies them. + */ +message MeshBeacon { + /* + * Human-readable beacon message. Max 100 bytes enforced by firmware on send. + */ + string message = 1; + + /* + * Optional channel (name + PSK) being advertised to listening clients. + * A client app may offer to switch the user to this channel; firmware never applies it automatically. + */ + ChannelSettings offer_channel = 2; + + /* + * Optional region being advertised alongside offer_preset. + */ + Config.LoRaConfig.RegionCode offer_region = 3; + + /* + * Optional modem preset being advertised. + * Combined with offer_region, tells a client "there is a mesh on this preset/region". + */ + optional Config.LoRaConfig.ModemPreset offer_preset = 4; +} diff --git a/meshtastic/module_config.options b/meshtastic/module_config.options index c6158bef..1a4efaa9 100644 --- a/meshtastic/module_config.options +++ b/meshtastic/module_config.options @@ -29,3 +29,9 @@ *DetectionSensorConfig.detection_trigger_type max_size:8 *StatusMessageConfig.node_status max_size:80 + +*MeshBeaconConfig.broadcast_message max_size:101 +*MeshBeaconConfig.broadcast_offer_channel.name max_size:12 +*MeshBeaconConfig.broadcast_offer_channel.psk max_size:32 +*MeshBeaconConfig.broadcast_on_channel.name max_size:12 +*MeshBeaconConfig.broadcast_on_channel.psk max_size:32 diff --git a/meshtastic/module_config.proto b/meshtastic/module_config.proto index a134f74c..d74de6ca 100644 --- a/meshtastic/module_config.proto +++ b/meshtastic/module_config.proto @@ -3,6 +3,8 @@ syntax = "proto3"; package meshtastic; import "meshtastic/atak.proto"; +import "meshtastic/channel.proto"; +import "meshtastic/config.proto"; option csharp_namespace = "Meshtastic.Protobufs"; option go_package = "github.com/meshtastic/go/generated"; @@ -857,6 +859,84 @@ message ModuleConfig { string node_status = 1; } + /* + * MeshBeacon module config + */ + message MeshBeaconConfig { + /* + * Enable receiving MESH_BEACON_APP packets from other nodes. + * The text portion is delivered to the local message inbox. + * Offered channel/preset are stored for the client app to act on. + */ + bool listen_enabled = 1; + + /* + * Enable periodically broadcasting MESH_BEACON_APP packets from this node. + */ + bool broadcast_enabled = 2; + + /* + * Optional: node ID to send beacon messages AS. + * When set, the `from` field of outgoing beacon packets is set to this node ID, + * making beacons appear to originate from that node. + * When unset (0), beacons are sent as the local node. + * A remote admin can only set this field to their own node ID. + */ + uint32 broadcast_send_as_node = 3; + + /* + * Message to include in each beacon broadcast. Max 100 bytes enforced by firmware. + */ + string broadcast_message = 4; + + /* + * Optional channel (name + PSK) to advertise in the MeshBeacon offer_channel field. + */ + ChannelSettings broadcast_offer_channel = 5; + + /* + * Optional region to advertise in the MeshBeacon offer_region field. + */ + Config.LoRaConfig.RegionCode broadcast_offer_region = 6; + + /* + * Optional modem preset to advertise in the MeshBeacon offer_preset field. + */ + optional Config.LoRaConfig.ModemPreset broadcast_offer_preset = 7; + + /* + * Channel settings (name + PSK) to use when sending beacons. + * If unset, beacons go out on the primary channel. + */ + ChannelSettings broadcast_on_channel = 8; + + /* + * Region to use when sending beacons on broadcast_on_preset. + */ + Config.LoRaConfig.RegionCode broadcast_on_region = 9; + + /* + * Modem preset to use when sending beacons. + * If different from current config, the radio is temporarily switched for TX. + */ + optional Config.LoRaConfig.ModemPreset broadcast_on_preset = 10; + + /* + * How often to broadcast, in seconds. Min 3600 (1 h), max 259200 (72 h). Default 3600. + */ + uint32 broadcast_interval_secs = 11; + + /* + * When true and both broadcast_message and offer content (preset/channel/region) are present, + * the broadcaster splits them into two separate packets instead of one combined MESH_BEACON_APP: + * - Packet A: MESH_BEACON_APP carrying only the offer fields (no text) on the beacon radio config. + * - Packet B: TEXT_MESSAGE_APP carrying only the broadcast_message on the normal radio config. + * This ensures nodes that only decode TEXT_MESSAGE_APP can still receive the human-readable text. + */ + bool broadcast_legacy_split = 12; + + } + /* * TODO: REPLACE */ @@ -940,6 +1020,11 @@ message ModuleConfig { * TAK team/role configuration for TAK_TRACKER */ TAKConfig tak = 16; + + /* + * MeshBeacon module config + */ + MeshBeaconConfig mesh_beacon = 17; } /* diff --git a/meshtastic/portnums.proto b/meshtastic/portnums.proto index 61412cfe..5ebe26fb 100644 --- a/meshtastic/portnums.proto +++ b/meshtastic/portnums.proto @@ -155,6 +155,14 @@ enum PortNum { */ NODE_STATUS_APP = 36; + /* + * Beacon module broadcast packets. + * ENCODING: protobuf (MeshBeacon) + * Periodically broadcast by nodes in beacon mode; received by nodes with listen_enabled. + * Carries a text message plus optional channel/preset offers for client apps. + */ + MESH_BEACON_APP = 37; + /* * Provides a hardware serial interface to send and receive from the Meshtastic network. * Connect to the RX/TX pins of a device with 38400 8N1. Packets received from the Meshtastic