diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index bc660170c..e3f076ce0 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -8,7 +8,7 @@ "features": { "ghcr.io/devcontainers/features/python:1": { "installTools": true, - "version": "3.13" + "version": "3.14" } }, "customizations": { diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 0a43c3079..46916bf29 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -8,15 +8,15 @@ plugins: uri: https://github.com/trunk-io/plugins lint: enabled: - - checkov@3.2.486 - - renovate@41.157.0 + - checkov@3.2.489 + - renovate@41.169.1 - prettier@3.6.2 - - trufflehog@3.90.11 + - trufflehog@3.90.12 - yamllint@1.37.1 - bandit@1.8.6 - trivy@0.67.2 - taplo@0.10.0 - - ruff@0.14.1 + - ruff@0.14.3 - isort@7.0.0 - markdownlint@0.45.0 - oxipng@9.1.5 diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index f3fd00de7..bce06f907 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -2,7 +2,7 @@ [portduino_base] platform = # renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop - https://github.com/meshtastic/platform-native/archive/d3f6e339534233c7217818867368767590ce549e.zip + https://github.com/meshtastic/platform-native/archive/f566d364204416cdbf298e349213f7d551f793d9.zip framework = arduino build_src_filter = diff --git a/platformio.ini b/platformio.ini index 80f65a9e7..09c924f47 100644 --- a/platformio.ini +++ b/platformio.ini @@ -176,7 +176,7 @@ lib_deps = # renovate: datasource=custom.pio depName=Adafruit LTR390 Library packageName=adafruit/library/Adafruit LTR390 Library adafruit/Adafruit LTR390 Library@1.1.2 # renovate: datasource=custom.pio depName=Adafruit PCT2075 packageName=adafruit/library/Adafruit PCT2075 - adafruit/Adafruit PCT2075@1.0.5 + adafruit/Adafruit PCT2075@1.0.6 # renovate: datasource=custom.pio depName=DFRobot_BMM150 packageName=dfrobot/library/DFRobot_BMM150 dfrobot/DFRobot_BMM150@1.0.0 # renovate: datasource=custom.pio depName=Adafruit_TSL2561 packageName=adafruit/library/Adafruit TSL2561 diff --git a/protobufs b/protobufs index bf149bbdc..7654db2e2 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit bf149bbdcce45ba7cd8643db7cb25e5c8815072b +Subproject commit 7654db2e2d1834aebde40090a9b74162ad1048ae diff --git a/src/input/TDeckProKeyboard.cpp b/src/input/TDeckProKeyboard.cpp index 098e0804a..eeafe4949 100644 --- a/src/input/TDeckProKeyboard.cpp +++ b/src/input/TDeckProKeyboard.cpp @@ -57,7 +57,7 @@ static unsigned char TDeckProTapMap[_TCA8418_NUM_KEYS][5] = { {0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}, {0x20, 0x00, 0x00}, - {0x00, 0x00, 0x00}, + {0x00, 0x00, '0'}, {0x00, 0x00, 0x00} // R_Shift, sym, space, mic, L_Shift }; diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index df9aece0a..443cc8eb7 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1638,13 +1638,32 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact) // If should_ignore is set, // we need to clear the public key and other cruft, in addition to setting the node as ignored info->is_ignored = true; + info->is_favorite = false; info->has_device_metrics = false; info->has_position = false; info->user.public_key.size = 0; info->user.public_key.bytes[0] = 0; } else { - info->last_heard = getValidTime(RTCQualityNTP); - info->is_favorite = true; + /* Clients are sending add_contact before every text message DM (because clients may hold a larger node database with + * public keys than the radio holds). However, we don't want to update last_heard just because we sent someone a DM! + */ + + /* "Boring old nodes" are the first to be evicted out of the node database when full. This includes a newly-zeroed + * nodeinfo because it has: !is_favorite && last_heard==0. To keep this from happening when we addFromContact, we set the + * new node as a favorite, and we leave last_heard alone (even if it's zero). + */ + if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) { + // Special case for CLIENT_BASE: is_favorite has special meaning, and we don't want to automatically set it + // without the user doing so deliberately. We don't normally expect users to use a CLIENT_BASE to send DMs or to add + // contacts, but we should make sure it doesn't auto-favorite in case they do. Instead, as a workaround, we'll set + // last_heard to now, so that the add_contact node doesn't immediately get evicted. + info->last_heard = getTime(); + } else { + // Normal case: set is_favorite to prevent expiration. + // last_heard will remain as-is (or remain 0 if this entry wasn't in the nodeDB). + info->is_favorite = true; + } + // As the clients will begin sending the contact with DMs, we want to strictly check if the node is manually verified if (contact.manually_verified) { info->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 5cf8bfa7d..05f47d7f4 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -479,6 +479,11 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p) LOG_ERROR("Invalid protobufs in received mesh packet id=0x%08x (bad psk?)!", p->id); } else if (decodedtmp.portnum == meshtastic_PortNum_UNKNOWN_APP) { LOG_ERROR("Invalid portnum (bad psk?)!"); +#if !(MESHTASTIC_EXCLUDE_PKI) + } else if (!owner.is_licensed && isToUs(p) && decodedtmp.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) { + LOG_WARN("Rejecting legacy DM"); + return DecodeState::DECODE_FAILURE; +#endif } else { p->decoded = decodedtmp; p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index 7cc896292..a542cf29c 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -272,8 +272,9 @@ typedef struct _meshtastic_AdminMessage { int32_t shutdown_seconds; /* Tell the node to factory reset config; all device state and configuration will be returned to factory defaults; BLE bonds will be preserved. */ int32_t factory_reset_config; - /* Tell the node to reset the nodedb. */ - int32_t nodedb_reset; + /* Tell the node to reset the nodedb. + When true, favorites are preserved through reset. */ + bool nodedb_reset; }; /* The node generates this key and sends it with any get_x_response packets. The client MUST include the same key with any set_x commands. Key expires after 300 seconds. @@ -459,7 +460,7 @@ X(a, STATIC, ONEOF, BOOL, (payload_variant,exit_simulator,exit_simulato X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_seconds,reboot_seconds), 97) \ X(a, STATIC, ONEOF, INT32, (payload_variant,shutdown_seconds,shutdown_seconds), 98) \ X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_config,factory_reset_config), 99) \ -X(a, STATIC, ONEOF, INT32, (payload_variant,nodedb_reset,nodedb_reset), 100) \ +X(a, STATIC, ONEOF, BOOL, (payload_variant,nodedb_reset,nodedb_reset), 100) \ X(a, STATIC, SINGULAR, BYTES, session_passkey, 101) #define meshtastic_AdminMessage_CALLBACK NULL #define meshtastic_AdminMessage_DEFAULT NULL diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 059af57ae..0da44cce0 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -284,6 +284,10 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_T_WATCH_ULTRA = 114, /* Elecrow ThinkNode M3 */ meshtastic_HardwareModel_THINKNODE_M3 = 115, + /* RAK WISMESH_TAP_V2 with ESP32-S3 CPU */ + meshtastic_HardwareModel_WISMESH_TAP_V2 = 116, + /* RAK3401 */ + meshtastic_HardwareModel_RAK3401 = 117, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index d300ff53b..24fb8f1f9 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -104,9 +104,18 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta (config.security.admin_key[2].size == 32 && memcmp(mp.public_key.bytes, config.security.admin_key[2].bytes, 32) == 0)) { LOG_INFO("PKC admin payload with authorized sender key"); + + // Automatically favorite the node that is using the admin key auto remoteNode = nodeDB->getMeshNode(mp.from); if (remoteNode && !remoteNode->is_favorite) { - remoteNode->is_favorite = true; + if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) { + // Special case for CLIENT_BASE: is_favorite has special meaning, and we don't want to automatically set it + // without the user doing so deliberately. + LOG_INFO("PKC admin valid, but not auto-favoriting node %x because role==CLIENT_BASE", mp.from); + } else { + LOG_INFO("PKC admin valid. Auto-favoriting node %x", mp.from); + remoteNode->is_favorite = true; + } } } else { myReply = allocErrorResponse(meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED, &mp); diff --git a/src/modules/StoreForwardModule.cpp b/src/modules/StoreForwardModule.cpp index 72ac99118..b8a710bf5 100644 --- a/src/modules/StoreForwardModule.cpp +++ b/src/modules/StoreForwardModule.cpp @@ -204,6 +204,10 @@ void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp) this->packetHistory[this->packetHistoryTotalCount].payload_size = p.payload.size; this->packetHistory[this->packetHistoryTotalCount].rx_rssi = mp.rx_rssi; this->packetHistory[this->packetHistoryTotalCount].rx_snr = mp.rx_snr; + this->packetHistory[this->packetHistoryTotalCount].hop_start = mp.hop_start; + this->packetHistory[this->packetHistoryTotalCount].hop_limit = mp.hop_limit; + this->packetHistory[this->packetHistoryTotalCount].via_mqtt = mp.via_mqtt; + this->packetHistory[this->packetHistoryTotalCount].transport_mechanism = mp.transport_mechanism; memcpy(this->packetHistory[this->packetHistoryTotalCount].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN); this->packetHistoryTotalCount++; @@ -256,6 +260,10 @@ meshtastic_MeshPacket *StoreForwardModule::preparePayload(NodeNum dest, uint32_t p->decoded.emoji = (uint32_t)this->packetHistory[i].emoji; p->rx_rssi = this->packetHistory[i].rx_rssi; p->rx_snr = this->packetHistory[i].rx_snr; + p->hop_start = this->packetHistory[i].hop_start; + p->hop_limit = this->packetHistory[i].hop_limit; + p->via_mqtt = this->packetHistory[i].via_mqtt; + p->transport_mechanism = (meshtastic_MeshPacket_TransportMechanism)this->packetHistory[i].transport_mechanism; // Let's assume that if the server received the S&F request that the client is in range. // TODO: Make this configurable. diff --git a/src/modules/StoreForwardModule.h b/src/modules/StoreForwardModule.h index 25836eded..148568e1b 100644 --- a/src/modules/StoreForwardModule.h +++ b/src/modules/StoreForwardModule.h @@ -21,6 +21,10 @@ struct PacketHistoryStruct { pb_size_t payload_size; int32_t rx_rssi; float rx_snr; + uint8_t hop_start; + uint8_t hop_limit; + bool via_mqtt; + uint8_t transport_mechanism; }; class StoreForwardModule : private concurrency::OSThread, public ProtobufModule