Merge upstream develop into packet authentication policy
This commit is contained in:
@@ -815,6 +815,23 @@ static void reconcileAccelerometerThread(bool wasOn, bool nowOn, bool otherFeatu
|
||||
}
|
||||
#endif
|
||||
|
||||
// A "regenerate keys" client sends a blank SecurityConfig holding only the new private key, rather than the
|
||||
// config it read from us. Detect that shape - new private key, every other field at its proto default - so it
|
||||
// isn't mistaken for "and clear everything else".
|
||||
static bool isBareKeypairRotation(const meshtastic_Config_SecurityConfig &incoming,
|
||||
const meshtastic_Config_SecurityConfig ¤t)
|
||||
{
|
||||
if (incoming.private_key.size != 32)
|
||||
return false;
|
||||
if (current.private_key.size == 32 && memcmp(incoming.private_key.bytes, current.private_key.bytes, 32) == 0)
|
||||
return false;
|
||||
|
||||
return incoming.admin_key_count == 0 && !incoming.is_managed && !incoming.serial_enabled && !incoming.debug_log_api_enabled &&
|
||||
!incoming.admin_channel_enabled &&
|
||||
incoming.packet_signature_policy ==
|
||||
meshtastic_Config_SecurityConfig_PacketSignaturePolicy_PACKET_SIGNATURE_POLICY_COMPATIBLE;
|
||||
}
|
||||
|
||||
void AdminModule::handleSetConfig(const meshtastic_Config &c, bool fromOthers)
|
||||
{
|
||||
auto changes = SEGMENT_CONFIG;
|
||||
@@ -1104,6 +1121,16 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c, bool fromOthers)
|
||||
incoming.private_key = config.security.private_key;
|
||||
incoming.public_key = config.security.public_key;
|
||||
}
|
||||
// Rotating the keypair must not drop the admin keys - that locks the owner out of remote admin with no
|
||||
// recourse but a physical connection. Clearing admin keys still works via a SET that leaves the private
|
||||
// key alone and sends an empty list.
|
||||
if (isBareKeypairRotation(incoming, config.security)) {
|
||||
LOG_INFO("Security set is a bare keypair rotation; preserving remaining security config");
|
||||
meshtastic_Config_SecurityConfig rotated = config.security;
|
||||
rotated.public_key = incoming.public_key; // usually empty; derived from the private key below
|
||||
rotated.private_key = incoming.private_key;
|
||||
incoming = rotated;
|
||||
}
|
||||
#if MESHTASTIC_EXCLUDE_PKI || MESHTASTIC_EXCLUDE_XEDDSA
|
||||
if (incoming.packet_signature_policy !=
|
||||
meshtastic_Config_SecurityConfig_PacketSignaturePolicy_PACKET_SIGNATURE_POLICY_BALANCED) {
|
||||
|
||||
@@ -82,7 +82,6 @@ CannedMessageModule::CannedMessageModule()
|
||||
void CannedMessageModule::LaunchWithDestination(NodeNum newDest, uint8_t newChannel)
|
||||
{
|
||||
// Do NOT override explicit broadcast replies
|
||||
// Only reuse lastDest in LaunchRepeatDestination()
|
||||
|
||||
if (newDest == 0) {
|
||||
dest = NODENUM_BROADCAST;
|
||||
@@ -114,19 +113,9 @@ void CannedMessageModule::LaunchWithDestination(NodeNum newDest, uint8_t newChan
|
||||
LOG_DEBUG("[CannedMessage] LaunchWithDestination dest=0x%08x ch=%d", dest, channel);
|
||||
}
|
||||
|
||||
void CannedMessageModule::LaunchRepeatDestination()
|
||||
{
|
||||
if (!lastDestSet) {
|
||||
LaunchWithDestination(NODENUM_BROADCAST, 0);
|
||||
} else {
|
||||
LaunchWithDestination(lastDest, lastChannel);
|
||||
}
|
||||
}
|
||||
|
||||
void CannedMessageModule::LaunchFreetextWithDestination(NodeNum newDest, uint8_t newChannel)
|
||||
{
|
||||
// Do NOT override explicit broadcast replies
|
||||
// Only reuse lastDest in LaunchRepeatDestination()
|
||||
|
||||
if (newDest == 0) {
|
||||
dest = NODENUM_BROADCAST;
|
||||
@@ -308,12 +297,6 @@ void CannedMessageModule::updateDestinationSelectionList()
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if character input is currently allowed (used for search/freetext states)
|
||||
bool CannedMessageModule::isCharInputAllowed() const
|
||||
{
|
||||
return runState == CANNED_MESSAGE_RUN_STATE_FREETEXT || runState == CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION;
|
||||
}
|
||||
|
||||
static int getRowHeightForEmoteText(const char *text, int minimumHeight, int emoteSpacing = 2)
|
||||
{
|
||||
// Grow the row only when an emote is taller than the font.
|
||||
@@ -1437,13 +1420,6 @@ bool CannedMessageModule::shouldDraw()
|
||||
this->runState == CANNED_MESSAGE_RUN_STATE_EMOTE_PICKER);
|
||||
}
|
||||
|
||||
// Has the user defined any canned messages?
|
||||
// Expose publicly whether canned message module is ready for use
|
||||
bool CannedMessageModule::hasMessages()
|
||||
{
|
||||
return (this->messagesCount > 0);
|
||||
}
|
||||
|
||||
int CannedMessageModule::getNextIndex()
|
||||
{
|
||||
if (this->currentMessageIndex >= (this->messagesCount - 1)) {
|
||||
|
||||
@@ -55,7 +55,6 @@ class CannedMessageModule : public SinglePortModule, public Observable<const UIF
|
||||
CannedMessageModule();
|
||||
|
||||
void LaunchWithDestination(NodeNum, uint8_t newChannel = 0);
|
||||
void LaunchRepeatDestination();
|
||||
void LaunchFreetextWithDestination(NodeNum, uint8_t newChannel = 0);
|
||||
|
||||
// === Emote Picker navigation ===
|
||||
@@ -70,11 +69,9 @@ class CannedMessageModule : public SinglePortModule, public Observable<const UIF
|
||||
|
||||
// === State/UI ===
|
||||
bool shouldDraw();
|
||||
bool hasMessages();
|
||||
void resetSearch();
|
||||
void updateDestinationSelectionList();
|
||||
void drawDestinationSelectionScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
bool isCharInputAllowed() const;
|
||||
String drawWithCursor(String text, int cursor);
|
||||
|
||||
// === Emote Picker ===
|
||||
|
||||
@@ -46,6 +46,16 @@ const static DetectionSensorTriggerHandler handlers[_meshtastic_ModuleConfig_Det
|
||||
[meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_EITHER_EDGE_ACTIVE_HIGH] = detection_trigger_either_edge,
|
||||
};
|
||||
|
||||
// The configured trigger type arrives as an unvalidated protobuf enum, so a value outside the
|
||||
// generated range would index past the handler table. Fall back to the schema default instead.
|
||||
static meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType configuredTriggerType()
|
||||
{
|
||||
const uint32_t configured = (uint32_t)moduleConfig.detection_sensor.detection_trigger_type;
|
||||
if (configured > (uint32_t)_meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_MAX)
|
||||
return _meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_MIN;
|
||||
return (meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType)configured;
|
||||
}
|
||||
|
||||
int32_t DetectionSensorModule::runOnce()
|
||||
{
|
||||
/*
|
||||
@@ -89,8 +99,7 @@ int32_t DetectionSensorModule::runOnce()
|
||||
if (!Throttle::isWithinTimespanMs(lastSentToMesh,
|
||||
Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.minimum_broadcast_secs))) {
|
||||
bool isDetected = hasDetectionEvent();
|
||||
DetectionSensorTriggerVerdict verdict =
|
||||
handlers[moduleConfig.detection_sensor.detection_trigger_type](wasDetected, isDetected);
|
||||
DetectionSensorTriggerVerdict verdict = handlers[configuredTriggerType()](wasDetected, isDetected);
|
||||
wasDetected = isDetected;
|
||||
switch (verdict) {
|
||||
case DetectionSensorVerdictDetected:
|
||||
@@ -160,5 +169,5 @@ bool DetectionSensorModule::hasDetectionEvent()
|
||||
{
|
||||
bool currentState = digitalRead(moduleConfig.detection_sensor.monitor_pin);
|
||||
// LOG_DEBUG("Detection Sensor Module: Current state: %i", currentState);
|
||||
return (moduleConfig.detection_sensor.detection_trigger_type & 1) ? currentState : !currentState;
|
||||
return (configuredTriggerType() & 1) ? currentState : !currentState;
|
||||
}
|
||||
@@ -49,10 +49,21 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
|
||||
LOG_WARN("Invalid nodeInfo detected, is_licensed mismatch!");
|
||||
return true;
|
||||
}
|
||||
NodeNum sourceNum = getFrom(&mp);
|
||||
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(sourceNum);
|
||||
// Broadcasts only: senders never sign unicast NodeInfo, so dropping it would break exchanges
|
||||
// with signer nodes. Backstops ingress that skips Router's downgrade drop (e.g. decoded MQTT).
|
||||
if (node && nodeInfoLiteHasXeddsaSigned(node) && !mp.xeddsa_signed && isBroadcast(mp.to)) {
|
||||
LOG_WARN("Dropping unsigned NodeInfo broadcast from node 0x%08x that previously signed", sourceNum);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Coerce user.id to be derived from the node number
|
||||
snprintf(p.id, sizeof(p.id), "!%08x", getFrom(&mp));
|
||||
|
||||
bool hasChanged = nodeDB->updateUser(getFrom(&mp), p, mp.channel);
|
||||
// updateUser() refuses the identity write for a known signer sending unsigned (all unicast
|
||||
// NodeInfo), so the exchange above still proceeds but cannot spoof the stored name.
|
||||
bool hasChanged = nodeDB->updateUser(getFrom(&mp), p, mp.channel, mp.xeddsa_signed);
|
||||
|
||||
bool wasBroadcast = isBroadcast(mp.to);
|
||||
|
||||
|
||||
@@ -65,7 +65,6 @@ void OnScreenKeyboardModule::stop(bool callEmptyCallback)
|
||||
// Keep NotificationRenderer legacy pointers in sync
|
||||
NotificationRenderer::virtualKeyboard = nullptr;
|
||||
NotificationRenderer::textInputCallback = nullptr;
|
||||
clearPopup();
|
||||
if (callEmptyCallback && cb)
|
||||
cb("");
|
||||
}
|
||||
@@ -131,9 +130,6 @@ bool OnScreenKeyboardModule::draw(OLEDDisplay *display)
|
||||
display->fillRect(0, 0, display->getWidth(), display->getHeight());
|
||||
display->setColor(WHITE);
|
||||
keyboard->draw(display, 0, 0);
|
||||
|
||||
// Draw popup overlay if needed
|
||||
drawPopup(display);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -150,123 +146,6 @@ void OnScreenKeyboardModule::onCancel()
|
||||
stop(true);
|
||||
}
|
||||
|
||||
void OnScreenKeyboardModule::showPopup(const char *title, const char *content, uint32_t durationMs)
|
||||
{
|
||||
if (!title || !content)
|
||||
return;
|
||||
strncpy(popupTitle, title, sizeof(popupTitle) - 1);
|
||||
popupTitle[sizeof(popupTitle) - 1] = '\0';
|
||||
strncpy(popupMessage, content, sizeof(popupMessage) - 1);
|
||||
popupMessage[sizeof(popupMessage) - 1] = '\0';
|
||||
popupUntil = millis() + durationMs;
|
||||
popupVisible = true;
|
||||
}
|
||||
|
||||
void OnScreenKeyboardModule::clearPopup()
|
||||
{
|
||||
popupTitle[0] = '\0';
|
||||
popupMessage[0] = '\0';
|
||||
popupUntil = 0;
|
||||
popupVisible = false;
|
||||
}
|
||||
|
||||
void OnScreenKeyboardModule::drawPopupOverlay(OLEDDisplay *display)
|
||||
{
|
||||
// Only render the popup overlay (without drawing the keyboard)
|
||||
drawPopup(display);
|
||||
}
|
||||
|
||||
void OnScreenKeyboardModule::drawPopup(OLEDDisplay *display)
|
||||
{
|
||||
if (!popupVisible)
|
||||
return;
|
||||
if (millis() > popupUntil || popupMessage[0] == '\0') {
|
||||
popupVisible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Build lines and leverage NotificationRenderer inverted box drawing for consistent style
|
||||
constexpr uint16_t maxContentLines = 3;
|
||||
const bool hasTitle = popupTitle[0] != '\0';
|
||||
|
||||
display->setFont(FONT_SMALL);
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
const uint16_t maxWrapWidth = display->width() - 40;
|
||||
|
||||
auto wrapText = [&](const char *text, uint16_t availableWidth) -> std::vector<std::string> {
|
||||
std::vector<std::string> wrapped;
|
||||
std::string current;
|
||||
std::string word;
|
||||
const char *p = text;
|
||||
while (*p && wrapped.size() < maxContentLines) {
|
||||
while (*p && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
|
||||
if (*p == '\n') {
|
||||
if (!current.empty()) {
|
||||
wrapped.push_back(current);
|
||||
current.clear();
|
||||
if (wrapped.size() >= maxContentLines)
|
||||
break;
|
||||
}
|
||||
}
|
||||
++p;
|
||||
}
|
||||
if (!*p || wrapped.size() >= maxContentLines)
|
||||
break;
|
||||
word.clear();
|
||||
while (*p && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r')
|
||||
word += *p++;
|
||||
if (word.empty())
|
||||
continue;
|
||||
std::string test = current.empty() ? word : (current + " " + word);
|
||||
uint16_t w = display->getStringWidth(test.c_str(), test.length(), true);
|
||||
if (w <= availableWidth)
|
||||
current = test;
|
||||
else {
|
||||
if (!current.empty()) {
|
||||
wrapped.push_back(current);
|
||||
current = word;
|
||||
if (wrapped.size() >= maxContentLines)
|
||||
break;
|
||||
} else {
|
||||
current = word;
|
||||
while (current.size() > 1 &&
|
||||
display->getStringWidth(current.c_str(), current.length(), true) > availableWidth)
|
||||
current.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!current.empty() && wrapped.size() < maxContentLines)
|
||||
wrapped.push_back(current);
|
||||
return wrapped;
|
||||
};
|
||||
|
||||
std::vector<std::string> allLines;
|
||||
if (hasTitle)
|
||||
allLines.emplace_back(popupTitle);
|
||||
|
||||
char buf[sizeof(popupMessage)];
|
||||
strncpy(buf, popupMessage, sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
char *paragraph = strtok(buf, "\n");
|
||||
while (paragraph && allLines.size() < maxContentLines + (hasTitle ? 1 : 0)) {
|
||||
auto wrapped = wrapText(paragraph, maxWrapWidth);
|
||||
for (const auto &ln : wrapped) {
|
||||
if (allLines.size() >= maxContentLines + (hasTitle ? 1 : 0))
|
||||
break;
|
||||
allLines.push_back(ln);
|
||||
}
|
||||
paragraph = strtok(nullptr, "\n");
|
||||
}
|
||||
|
||||
std::vector<const char *> ptrs;
|
||||
for (const auto &ln : allLines)
|
||||
ptrs.push_back(ln.c_str());
|
||||
ptrs.push_back(nullptr);
|
||||
|
||||
// Use the standard notification box drawing from NotificationRenderer
|
||||
NotificationRenderer::drawNotificationBox(display, nullptr, ptrs.data(), allLines.size(), 0, 0);
|
||||
}
|
||||
|
||||
} // namespace graphics
|
||||
|
||||
#endif // HAS_SCREEN
|
||||
|
||||
@@ -25,11 +25,6 @@ class OnScreenKeyboardModule
|
||||
static bool processVirtualKeyboardInput(const InputEvent &event, VirtualKeyboard *keyboard);
|
||||
bool draw(OLEDDisplay *display);
|
||||
|
||||
void showPopup(const char *title, const char *content, uint32_t durationMs);
|
||||
void clearPopup();
|
||||
// Draw only the popup overlay (used when legacy virtualKeyboard draws the keyboard)
|
||||
void drawPopupOverlay(OLEDDisplay *display);
|
||||
|
||||
private:
|
||||
OnScreenKeyboardModule() = default;
|
||||
~OnScreenKeyboardModule();
|
||||
@@ -39,15 +34,8 @@ class OnScreenKeyboardModule
|
||||
void onSubmit(const std::string &text);
|
||||
void onCancel();
|
||||
|
||||
void drawPopup(OLEDDisplay *display);
|
||||
|
||||
VirtualKeyboard *keyboard = nullptr;
|
||||
std::function<void(const std::string &)> callback;
|
||||
|
||||
char popupTitle[64] = {0};
|
||||
char popupMessage[256] = {0};
|
||||
uint32_t popupUntil = 0;
|
||||
bool popupVisible = false;
|
||||
};
|
||||
|
||||
} // namespace graphics
|
||||
|
||||
@@ -55,7 +55,8 @@ void RoutingModule::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketI
|
||||
// Allow the caller to set want_ack on this ACK packet if it's important that the ACK be delivered reliably
|
||||
p->want_ack = ackWantsAck;
|
||||
|
||||
router->sendLocal(p); // we sometimes send directly to the local node
|
||||
if (router->sendLocal(p) == ERRNO_SHOULD_RELEASE) // we sometimes send directly to the local node
|
||||
packetPool.release(p);
|
||||
}
|
||||
|
||||
uint8_t RoutingModule::getHopLimitForResponse(const meshtastic_MeshPacket &mp)
|
||||
|
||||
@@ -10,11 +10,6 @@ float UnitConversions::MetersPerSecondToKnots(float metersPerSecond)
|
||||
return metersPerSecond * 1.94384;
|
||||
}
|
||||
|
||||
float UnitConversions::MetersPerSecondToMilesPerHour(float metersPerSecond)
|
||||
{
|
||||
return metersPerSecond * 2.23694;
|
||||
}
|
||||
|
||||
float UnitConversions::HectoPascalToInchesOfMercury(float hectoPascal)
|
||||
{
|
||||
return hectoPascal * 0.029529983071445;
|
||||
|
||||
@@ -7,7 +7,6 @@ class UnitConversions
|
||||
public:
|
||||
static float CelsiusToFahrenheit(float celsius);
|
||||
static float MetersPerSecondToKnots(float metersPerSecond);
|
||||
static float MetersPerSecondToMilesPerHour(float metersPerSecond);
|
||||
static float HectoPascalToInchesOfMercury(float hectoPascal);
|
||||
|
||||
// Bound a float before Arduino String(float) renders it: its fixed char[33] + dtostrf overflow
|
||||
|
||||
@@ -78,16 +78,6 @@ uint8_t sanitizePositionPrecision(uint8_t precision)
|
||||
return 32;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saturating increment for uint8_t counters.
|
||||
* Prevents overflow by capping at UINT8_MAX (255).
|
||||
*/
|
||||
inline void saturatingIncrement(uint8_t &counter)
|
||||
{
|
||||
if (counter < UINT8_MAX)
|
||||
counter++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a short human-readable name for common port numbers.
|
||||
* Falls back to "port:<N>" for unknown ports.
|
||||
@@ -224,19 +214,6 @@ meshtastic_TrafficManagementStats TrafficManagementModule::getStats() const
|
||||
return stats;
|
||||
}
|
||||
|
||||
void TrafficManagementModule::resetStats()
|
||||
{
|
||||
concurrency::LockGuard guard(&cacheLock);
|
||||
stats = meshtastic_TrafficManagementStats_init_zero;
|
||||
}
|
||||
|
||||
void TrafficManagementModule::recordRouterHopPreserved()
|
||||
{
|
||||
// router_preserve_hops: not suitable right now - removed from config until
|
||||
// the right heuristic for when to preserve vs. exhaust is clearer.
|
||||
(void)stats.router_hops_preserved;
|
||||
}
|
||||
|
||||
void TrafficManagementModule::incrementStat(uint32_t *field)
|
||||
{
|
||||
concurrency::LockGuard guard(&cacheLock);
|
||||
@@ -757,7 +734,7 @@ ProcessMessage TrafficManagementModule::handleReceived(const meshtastic_MeshPack
|
||||
meshtastic_User requester = meshtastic_User_init_zero;
|
||||
if (!unauthenticatedSigner &&
|
||||
pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_User_msg, &requester)) {
|
||||
nodeDB->updateUser(getFrom(&mp), requester, mp.channel);
|
||||
nodeDB->updateUser(getFrom(&mp), requester, mp.channel, mp.xeddsa_signed);
|
||||
}
|
||||
logAction("respond", &mp, "nodeinfo-cache");
|
||||
incrementStat(&stats.nodeinfo_cache_hits);
|
||||
|
||||
@@ -38,8 +38,6 @@ class TrafficManagementModule : public MeshModule, private concurrency::OSThread
|
||||
TrafficManagementModule &operator=(const TrafficManagementModule &) = delete;
|
||||
|
||||
meshtastic_TrafficManagementStats getStats() const;
|
||||
void resetStats();
|
||||
void recordRouterHopPreserved();
|
||||
|
||||
// Next-hop overflow cache (routing hint).
|
||||
// setNextHop: store a confirmed last-byte next hop for `dest`. Called by
|
||||
|
||||
Reference in New Issue
Block a user