diff --git a/cmake/protobuf.cmake b/cmake/protobuf.cmake index 86237314..ce7a6ebf 100644 --- a/cmake/protobuf.cmake +++ b/cmake/protobuf.cmake @@ -1,7 +1,8 @@ message(STATUS "Fetching protobufs ...") FetchContent_Declare( Protobuf - GIT_REPOSITORY "https://github.com/meshtastic/protobufs" - GIT_TAG v2.7.8 + GIT_REPOSITORY "https://github.com/RCGV1/protobufs-fork" + # Packet authenticity policy and XEdDSA capability from protobufs#983. + GIT_TAG 19ab2d2711050cb462f0f57507f75b47bfa6832c ) FetchContent_MakeAvailable(Protobuf) diff --git a/docs/validation/packet-auth/confirm-strict.png b/docs/validation/packet-auth/confirm-strict.png new file mode 100644 index 00000000..4551568d Binary files /dev/null and b/docs/validation/packet-auth/confirm-strict.png differ diff --git a/docs/validation/packet-auth/dropdown-open.png b/docs/validation/packet-auth/dropdown-open.png new file mode 100644 index 00000000..eb85c6ef Binary files /dev/null and b/docs/validation/packet-auth/dropdown-open.png differ diff --git a/docs/validation/packet-auth/modal-balanced.png b/docs/validation/packet-auth/modal-balanced.png new file mode 100644 index 00000000..4276edbf Binary files /dev/null and b/docs/validation/packet-auth/modal-balanced.png differ diff --git a/generated/ui_240x320/ui_tabview_settings.c b/generated/ui_240x320/ui_tabview_settings.c index b1830ca4..fd9541be 100644 --- a/generated/ui_240x320/ui_tabview_settings.c +++ b/generated/ui_240x320/ui_tabview_settings.c @@ -1,4 +1,17 @@ #include "ui.h" +#include "graphics/view/TFT/PacketAuthUI.h" +#include "lv_i18n.h" +#include "styles.h" + +lv_obj_t *ui_PacketAuthButton; +lv_obj_t *ui_PacketAuthLabel; +lv_obj_t *ui_PacketAuthPanel; +lv_obj_t *ui_PacketAuthDropdown; +lv_obj_t *ui_PacketAuthDescription; +lv_obj_t *ui_PacketAuthWarning; +lv_obj_t *ui_PacketAuthOkButton; +lv_obj_t *ui_PacketAuthOkLabel; +lv_obj_t *ui_PacketAuthCancelButton; lv_obj_t * ui_AdvancedSettingsPanel; lv_obj_t * ui_SettingsTabView; @@ -747,4 +760,73 @@ void create_tabview_settings() lv_obj_set_align(ui_RemoteHardwareLabel, LV_ALIGN_LEFT_MID); lv_label_set_long_mode(ui_RemoteHardwareLabel, LV_LABEL_LONG_DOT); lv_label_set_text(ui_RemoteHardwareLabel, "Remote Hardware"); -} \ No newline at end of file + + ui_PacketAuthButton = lv_btn_create(objects.tab_page_basic_settings); + lv_obj_set_size(ui_PacketAuthButton, lv_pct(95), 30); + lv_obj_add_flag(ui_PacketAuthButton, LV_OBJ_FLAG_HIDDEN | LV_OBJ_FLAG_SCROLL_ON_FOCUS); + add_style_settings_button_style(ui_PacketAuthButton); + lv_obj_set_style_shadow_width(ui_PacketAuthButton, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + ui_PacketAuthLabel = lv_label_create(ui_PacketAuthButton); + lv_obj_set_width(ui_PacketAuthLabel, lv_pct(100)); + lv_label_set_long_mode(ui_PacketAuthLabel, LV_LABEL_LONG_DOT); + lv_label_set_text(ui_PacketAuthLabel, _("Packet authenticity: Balanced")); + lv_obj_set_style_align(ui_PacketAuthLabel, LV_ALIGN_CENTER, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_PacketAuthPanel = lv_obj_create(objects.main_screen); + // Keep enough vertical room for the expanded selector, wrapped copy, and + // actions. 236px fits the 240px landscape display with a small margin. + lv_obj_set_size(ui_PacketAuthPanel, lv_pct(90), 236); + lv_obj_set_style_align(ui_PacketAuthPanel, LV_ALIGN_CENTER, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_add_flag(ui_PacketAuthPanel, LV_OBJ_FLAG_HIDDEN); + lv_obj_clear_flag(ui_PacketAuthPanel, LV_OBJ_FLAG_SCROLLABLE); + add_style_settings_panel_style(ui_PacketAuthPanel); + lv_obj_set_style_border_width(ui_PacketAuthPanel, 2, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_color(ui_PacketAuthPanel, lv_color_hex(0x216ad8), LV_PART_MAIN | LV_STATE_DEFAULT); + // This is a modal settings panel; keep the form beneath it from bleeding through its text. + lv_obj_set_style_bg_opa(ui_PacketAuthPanel, LV_OPA_COVER, LV_PART_MAIN | LV_STATE_DEFAULT); + + lv_obj_t *title = lv_label_create(ui_PacketAuthPanel); + lv_label_set_text(title, _("Packet authenticity")); + lv_obj_set_style_align(title, LV_ALIGN_TOP_MID, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_PacketAuthDropdown = lv_dropdown_create(ui_PacketAuthPanel); + lv_dropdown_set_options(ui_PacketAuthDropdown, _("Balanced\nCompatible\nStrict")); + lv_obj_set_pos(ui_PacketAuthDropdown, 0, 25); + lv_obj_set_size(ui_PacketAuthDropdown, lv_pct(100), 30); + // The default theme adds generous line spacing, causing the list to + // cover the description and action buttons. Compact the list so all + // three choices stay inside the modal's reserved selector area. + lv_obj_t *packetAuthList = lv_dropdown_get_list(ui_PacketAuthDropdown); + lv_obj_set_style_text_line_space(packetAuthList, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(packetAuthList, 2, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(packetAuthList, 2, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_PacketAuthDescription = lv_label_create(ui_PacketAuthPanel); + // Leave the selector's expanded list clear of the explanatory copy. + lv_obj_set_pos(ui_PacketAuthDescription, 0, 112); + lv_obj_set_width(ui_PacketAuthDescription, lv_pct(100)); + lv_label_set_long_mode(ui_PacketAuthDescription, LV_LABEL_LONG_WRAP); + lv_label_set_text(ui_PacketAuthDescription, _("Prefer signed packets (recommended).")); + + ui_PacketAuthWarning = lv_label_create(ui_PacketAuthPanel); + lv_obj_set_pos(ui_PacketAuthWarning, 0, 98); + lv_obj_set_width(ui_PacketAuthWarning, lv_pct(100)); + lv_label_set_long_mode(ui_PacketAuthWarning, LV_LABEL_LONG_WRAP); + lv_label_set_text(ui_PacketAuthWarning, _("Unsigned broadcasts filtered.")); + lv_obj_set_style_text_color(ui_PacketAuthWarning, lv_color_hex(0xeb533f), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_add_flag(ui_PacketAuthWarning, LV_OBJ_FLAG_HIDDEN); + + ui_PacketAuthOkButton = lv_btn_create(ui_PacketAuthPanel); + lv_obj_set_size(ui_PacketAuthOkButton, lv_pct(47), 30); + lv_obj_set_style_align(ui_PacketAuthOkButton, LV_ALIGN_BOTTOM_LEFT, LV_PART_MAIN | LV_STATE_DEFAULT); + ui_PacketAuthOkLabel = lv_label_create(ui_PacketAuthOkButton); + lv_label_set_text(ui_PacketAuthOkLabel, _("Save")); + lv_obj_center(ui_PacketAuthOkLabel); + + ui_PacketAuthCancelButton = lv_btn_create(ui_PacketAuthPanel); + lv_obj_set_size(ui_PacketAuthCancelButton, lv_pct(47), 30); + lv_obj_set_style_align(ui_PacketAuthCancelButton, LV_ALIGN_BOTTOM_RIGHT, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_t *cancelLabel = lv_label_create(ui_PacketAuthCancelButton); + lv_label_set_text(cancelLabel, _("Cancel")); + lv_obj_center(cancelLabel); +} diff --git a/generated/ui_320x240/ui_tabview_settings.c b/generated/ui_320x240/ui_tabview_settings.c index b1830ca4..fd9541be 100644 --- a/generated/ui_320x240/ui_tabview_settings.c +++ b/generated/ui_320x240/ui_tabview_settings.c @@ -1,4 +1,17 @@ #include "ui.h" +#include "graphics/view/TFT/PacketAuthUI.h" +#include "lv_i18n.h" +#include "styles.h" + +lv_obj_t *ui_PacketAuthButton; +lv_obj_t *ui_PacketAuthLabel; +lv_obj_t *ui_PacketAuthPanel; +lv_obj_t *ui_PacketAuthDropdown; +lv_obj_t *ui_PacketAuthDescription; +lv_obj_t *ui_PacketAuthWarning; +lv_obj_t *ui_PacketAuthOkButton; +lv_obj_t *ui_PacketAuthOkLabel; +lv_obj_t *ui_PacketAuthCancelButton; lv_obj_t * ui_AdvancedSettingsPanel; lv_obj_t * ui_SettingsTabView; @@ -747,4 +760,73 @@ void create_tabview_settings() lv_obj_set_align(ui_RemoteHardwareLabel, LV_ALIGN_LEFT_MID); lv_label_set_long_mode(ui_RemoteHardwareLabel, LV_LABEL_LONG_DOT); lv_label_set_text(ui_RemoteHardwareLabel, "Remote Hardware"); -} \ No newline at end of file + + ui_PacketAuthButton = lv_btn_create(objects.tab_page_basic_settings); + lv_obj_set_size(ui_PacketAuthButton, lv_pct(95), 30); + lv_obj_add_flag(ui_PacketAuthButton, LV_OBJ_FLAG_HIDDEN | LV_OBJ_FLAG_SCROLL_ON_FOCUS); + add_style_settings_button_style(ui_PacketAuthButton); + lv_obj_set_style_shadow_width(ui_PacketAuthButton, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + ui_PacketAuthLabel = lv_label_create(ui_PacketAuthButton); + lv_obj_set_width(ui_PacketAuthLabel, lv_pct(100)); + lv_label_set_long_mode(ui_PacketAuthLabel, LV_LABEL_LONG_DOT); + lv_label_set_text(ui_PacketAuthLabel, _("Packet authenticity: Balanced")); + lv_obj_set_style_align(ui_PacketAuthLabel, LV_ALIGN_CENTER, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_PacketAuthPanel = lv_obj_create(objects.main_screen); + // Keep enough vertical room for the expanded selector, wrapped copy, and + // actions. 236px fits the 240px landscape display with a small margin. + lv_obj_set_size(ui_PacketAuthPanel, lv_pct(90), 236); + lv_obj_set_style_align(ui_PacketAuthPanel, LV_ALIGN_CENTER, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_add_flag(ui_PacketAuthPanel, LV_OBJ_FLAG_HIDDEN); + lv_obj_clear_flag(ui_PacketAuthPanel, LV_OBJ_FLAG_SCROLLABLE); + add_style_settings_panel_style(ui_PacketAuthPanel); + lv_obj_set_style_border_width(ui_PacketAuthPanel, 2, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_color(ui_PacketAuthPanel, lv_color_hex(0x216ad8), LV_PART_MAIN | LV_STATE_DEFAULT); + // This is a modal settings panel; keep the form beneath it from bleeding through its text. + lv_obj_set_style_bg_opa(ui_PacketAuthPanel, LV_OPA_COVER, LV_PART_MAIN | LV_STATE_DEFAULT); + + lv_obj_t *title = lv_label_create(ui_PacketAuthPanel); + lv_label_set_text(title, _("Packet authenticity")); + lv_obj_set_style_align(title, LV_ALIGN_TOP_MID, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_PacketAuthDropdown = lv_dropdown_create(ui_PacketAuthPanel); + lv_dropdown_set_options(ui_PacketAuthDropdown, _("Balanced\nCompatible\nStrict")); + lv_obj_set_pos(ui_PacketAuthDropdown, 0, 25); + lv_obj_set_size(ui_PacketAuthDropdown, lv_pct(100), 30); + // The default theme adds generous line spacing, causing the list to + // cover the description and action buttons. Compact the list so all + // three choices stay inside the modal's reserved selector area. + lv_obj_t *packetAuthList = lv_dropdown_get_list(ui_PacketAuthDropdown); + lv_obj_set_style_text_line_space(packetAuthList, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(packetAuthList, 2, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(packetAuthList, 2, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_PacketAuthDescription = lv_label_create(ui_PacketAuthPanel); + // Leave the selector's expanded list clear of the explanatory copy. + lv_obj_set_pos(ui_PacketAuthDescription, 0, 112); + lv_obj_set_width(ui_PacketAuthDescription, lv_pct(100)); + lv_label_set_long_mode(ui_PacketAuthDescription, LV_LABEL_LONG_WRAP); + lv_label_set_text(ui_PacketAuthDescription, _("Prefer signed packets (recommended).")); + + ui_PacketAuthWarning = lv_label_create(ui_PacketAuthPanel); + lv_obj_set_pos(ui_PacketAuthWarning, 0, 98); + lv_obj_set_width(ui_PacketAuthWarning, lv_pct(100)); + lv_label_set_long_mode(ui_PacketAuthWarning, LV_LABEL_LONG_WRAP); + lv_label_set_text(ui_PacketAuthWarning, _("Unsigned broadcasts filtered.")); + lv_obj_set_style_text_color(ui_PacketAuthWarning, lv_color_hex(0xeb533f), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_add_flag(ui_PacketAuthWarning, LV_OBJ_FLAG_HIDDEN); + + ui_PacketAuthOkButton = lv_btn_create(ui_PacketAuthPanel); + lv_obj_set_size(ui_PacketAuthOkButton, lv_pct(47), 30); + lv_obj_set_style_align(ui_PacketAuthOkButton, LV_ALIGN_BOTTOM_LEFT, LV_PART_MAIN | LV_STATE_DEFAULT); + ui_PacketAuthOkLabel = lv_label_create(ui_PacketAuthOkButton); + lv_label_set_text(ui_PacketAuthOkLabel, _("Save")); + lv_obj_center(ui_PacketAuthOkLabel); + + ui_PacketAuthCancelButton = lv_btn_create(ui_PacketAuthPanel); + lv_obj_set_size(ui_PacketAuthCancelButton, lv_pct(47), 30); + lv_obj_set_style_align(ui_PacketAuthCancelButton, LV_ALIGN_BOTTOM_RIGHT, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_t *cancelLabel = lv_label_create(ui_PacketAuthCancelButton); + lv_label_set_text(cancelLabel, _("Cancel")); + lv_obj_center(cancelLabel); +} diff --git a/include/graphics/common/MeshtasticView.h b/include/graphics/common/MeshtasticView.h index 1cabd974..7560a070 100644 --- a/include/graphics/common/MeshtasticView.h +++ b/include/graphics/common/MeshtasticView.h @@ -76,7 +76,7 @@ class MeshtasticView : public DeviceGUI virtual bool setupUIConfig(const meshtastic_DeviceUIConfig &uiconfig) { return true; } virtual void setMyInfo(uint32_t nodeNum); virtual void setDeviceMetaData(int hw_model, const char *version, bool has_bluetooth, bool has_wifi, bool has_eth, - bool can_shutdown); + bool can_shutdown, bool has_xeddsa); virtual void addOrUpdateNode(uint32_t nodeNum, uint8_t channel, uint32_t lastHeard, eRole role, bool hasKey, bool viaMqtt); virtual void addOrUpdateNode(uint32_t nodeNum, uint8_t channel, uint32_t lastHeard, const meshtastic_User &cfg); virtual void addNode(uint32_t nodeNum, uint8_t channel, const char *userShort, const char *userLong, uint32_t lastHeard, diff --git a/include/graphics/view/TFT/PacketAuthPolicy.h b/include/graphics/view/TFT/PacketAuthPolicy.h new file mode 100644 index 00000000..59709c66 --- /dev/null +++ b/include/graphics/view/TFT/PacketAuthPolicy.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +namespace PacketAuthPolicy +{ +enum class Selection : uint8_t { + Balanced = 0, + Compatible = 1, + Strict = 2, +}; + +constexpr Selection fromWireValue(uint32_t value) +{ + return value <= static_cast(Selection::Strict) ? static_cast(value) : Selection::Balanced; +} + +constexpr uint32_t toWireValue(Selection selection) +{ + return static_cast(selection); +} + +constexpr bool requiresStrictConfirmation(Selection current, Selection requested) +{ + return requested == Selection::Strict && current != Selection::Strict; +} + +constexpr const char *label(Selection selection) +{ + switch (selection) { + case Selection::Compatible: + return "Compatible"; + case Selection::Strict: + return "Strict"; + case Selection::Balanced: + default: + return "Balanced"; + } +} +} // namespace PacketAuthPolicy diff --git a/include/graphics/view/TFT/PacketAuthUI.h b/include/graphics/view/TFT/PacketAuthUI.h new file mode 100644 index 00000000..31aae5c1 --- /dev/null +++ b/include/graphics/view/TFT/PacketAuthUI.h @@ -0,0 +1,21 @@ +#pragma once + +#include "lvgl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern lv_obj_t *ui_PacketAuthButton; +extern lv_obj_t *ui_PacketAuthLabel; +extern lv_obj_t *ui_PacketAuthPanel; +extern lv_obj_t *ui_PacketAuthDropdown; +extern lv_obj_t *ui_PacketAuthDescription; +extern lv_obj_t *ui_PacketAuthWarning; +extern lv_obj_t *ui_PacketAuthOkButton; +extern lv_obj_t *ui_PacketAuthOkLabel; +extern lv_obj_t *ui_PacketAuthCancelButton; + +#ifdef __cplusplus +} +#endif diff --git a/include/graphics/view/TFT/TFTView_320x240.h b/include/graphics/view/TFT/TFTView_320x240.h index c2787374..b4cafc8c 100644 --- a/include/graphics/view/TFT/TFTView_320x240.h +++ b/include/graphics/view/TFT/TFTView_320x240.h @@ -21,8 +21,8 @@ class TFTView_320x240 : public MeshtasticView // methods to update view void setMyInfo(uint32_t nodeNum) override; - void setDeviceMetaData(int hw_model, const char *version, bool has_bluetooth, bool has_wifi, bool has_eth, - bool can_shutdown) override; + void setDeviceMetaData(int hw_model, const char *version, bool has_bluetooth, bool has_wifi, bool has_eth, bool can_shutdown, + bool has_xeddsa) override; void addOrUpdateNode(uint32_t nodeNum, uint8_t channel, uint32_t lastHeard, const meshtastic_User &cfg) override; void addNode(uint32_t nodeNum, uint8_t channel, const char *userShort, const char *userLong, uint32_t lastHeard, eRole role, bool hasKey, bool unmessagable) override; @@ -98,6 +98,7 @@ class TFTView_320x240 : public MeshtasticView eLanguage, eScreenTimeout, eScreenLock, + ePacketAuthPolicy, eScreenBrightness, eTheme, eInputControl, @@ -255,6 +256,8 @@ class TFTView_320x240 : public MeshtasticView void clearChatHistory(void); void showLoRaFrequency(const meshtastic_Config_LoRaConfig &cfg); void setBellText(bool banner, bool sound); + void updatePacketAuthAvailability(void); + void closePacketAuthPanel(void); void setChannelName(const meshtastic_Channel &ch); uint32_t timestamp(char *buf, uint32_t time, bool update); void updateLocationMap(uint32_t objects); @@ -322,6 +325,10 @@ class TFTView_320x240 : public MeshtasticView static void ui_event_calibration_button(lv_event_t *e); static void ui_event_timeout_button(lv_event_t *e); static void ui_event_screen_lock_button(lv_event_t *e); + static void ui_event_packet_auth_button(lv_event_t *e); + static void ui_event_packet_auth_dropdown(lv_event_t *e); + static void ui_event_packet_auth_ok(lv_event_t *e); + static void ui_event_packet_auth_cancel(lv_event_t *e); static void ui_event_input_button(lv_event_t *e); static void ui_event_alert_button(lv_event_t *e); static void ui_event_backup_button(lv_event_t *e); @@ -421,6 +428,8 @@ class TFTView_320x240 : public MeshtasticView static uint32_t pinKeys; // number of keys pressed (lock screen) static bool screenLocked; // screen lock active static bool screenUnlockRequest; // screen unlock request (via button) + bool hasXeddsa = false; // connected firmware can verify packet signatures + bool strictConfirmationPending = false; // Strict requires a second, explicit confirmation uint32_t selectedHops; // remember selected choice bool chooseNodeSignalScanner; // chose a target node for signal scanner bool chooseNodeTraceRoute; // chose a target node for trace route @@ -444,4 +453,4 @@ class TFTView_320x240 : public MeshtasticView }; meshtastic_DeviceProfile_full db{}; // full copy of the node's configuration db (except nodeinfos) plus ui data -}; \ No newline at end of file +}; diff --git a/source/comms/ethernet/EthClient.cpp b/source/comms/ethernet/EthClient.cpp index 8c272261..c6a9df0b 100644 --- a/source/comms/ethernet/EthClient.cpp +++ b/source/comms/ethernet/EthClient.cpp @@ -64,7 +64,7 @@ bool EthClient::connect(void) { if (clientStatus != eConnected) { setConnectionStatus(eConnecting, "Connecting..."); - if (client->connect(server, SERVER_PORT)) { + if (client->connect(server, serverPort)) { ILOG_INFO("EthClient connected!"); setConnectionStatus(eConnected, "Connected!"); } else { diff --git a/source/graphics/TFT/TFTView_320x240.cpp b/source/graphics/TFT/TFTView_320x240.cpp index 72f662e8..662e6bdd 100644 --- a/source/graphics/TFT/TFTView_320x240.cpp +++ b/source/graphics/TFT/TFTView_320x240.cpp @@ -11,6 +11,8 @@ #include "graphics/map/MapPanel.h" #include "graphics/map/TileProvider.h" #include "graphics/map/URLService.h" +#include "graphics/view/TFT/PacketAuthPolicy.h" +#include "graphics/view/TFT/PacketAuthUI.h" #include "graphics/view/TFT/Themes.h" #include "images.h" #include "input/InputDriver.h" @@ -84,6 +86,45 @@ constexpr lv_color_t colorMidGray = LV_COLOR_HEX(0x808080); constexpr lv_color_t colorDarkGray = LV_COLOR_HEX(0x303030); constexpr lv_color_t colorMesh = LV_COLOR_HEX(0x67ea94); +namespace +{ +const char *packetAuthLabel(PacketAuthPolicy::Selection policy) +{ + switch (policy) { + case PacketAuthPolicy::Selection::Compatible: + return _("Compatible"); + case PacketAuthPolicy::Selection::Strict: + return _("Strict"); + case PacketAuthPolicy::Selection::Balanced: + default: + return _("Balanced"); + } +} + +void setPacketAuthButtonLabel(PacketAuthPolicy::Selection policy) +{ + char label[48]; + lv_snprintf(label, sizeof(label), _("Packet authenticity: %s"), packetAuthLabel(policy)); + lv_label_set_text(ui_PacketAuthLabel, label); +} + +// The packet-auth panel contains copy that can wrap differently across the +// 240x320 and 320x240 layouts. Reflow the warning below the measured +// description instead of relying on fixed coordinates that can collide with +// the action buttons. +void layoutPacketAuthPanel(void) +{ + if (!ui_PacketAuthDescription || !ui_PacketAuthWarning) { + return; + } + + lv_obj_update_layout(ui_PacketAuthDescription); + const lv_coord_t descriptionBottom = lv_obj_get_y(ui_PacketAuthDescription) + lv_obj_get_height(ui_PacketAuthDescription); + lv_obj_set_y(ui_PacketAuthWarning, descriptionBottom + 6); + lv_obj_update_layout(ui_PacketAuthWarning); +} +} // namespace + // children index of nodepanel lv objects (see addNode) enum NodePanelIdx { node_img_idx, @@ -776,6 +817,10 @@ void TFTView_320x240::ui_events_init(void) lv_obj_add_event_cb(objects.basic_settings_channel_button, ui_event_channel_button, LV_EVENT_CLICKED, NULL); lv_obj_add_event_cb(objects.basic_settings_timeout_button, ui_event_timeout_button, LV_EVENT_CLICKED, NULL); lv_obj_add_event_cb(objects.basic_settings_screen_lock_button, ui_event_screen_lock_button, LV_EVENT_CLICKED, NULL); + lv_obj_add_event_cb(ui_PacketAuthButton, ui_event_packet_auth_button, LV_EVENT_CLICKED, NULL); + lv_obj_add_event_cb(ui_PacketAuthDropdown, ui_event_packet_auth_dropdown, LV_EVENT_VALUE_CHANGED, NULL); + lv_obj_add_event_cb(ui_PacketAuthOkButton, ui_event_packet_auth_ok, LV_EVENT_CLICKED, NULL); + lv_obj_add_event_cb(ui_PacketAuthCancelButton, ui_event_packet_auth_cancel, LV_EVENT_CLICKED, NULL); lv_obj_add_event_cb(objects.basic_settings_brightness_button, ui_event_brightness_button, LV_EVENT_CLICKED, NULL); lv_obj_add_event_cb(objects.basic_settings_theme_button, ui_event_theme_button, LV_EVENT_CLICKED, NULL); lv_obj_add_event_cb(objects.basic_settings_calibration_button, ui_event_calibration_button, LV_EVENT_CLICKED, NULL); @@ -1912,6 +1957,125 @@ void TFTView_320x240::ui_event_screen_lock_button(lv_event_t *e) } } +void TFTView_320x240::ui_event_packet_auth_button(lv_event_t *e) +{ + if (lv_event_get_code(e) != LV_EVENT_CLICKED || THIS->activeSettings != eNone || !THIS->hasXeddsa || + !THIS->db.config.has_security || THIS->state == MeshtasticView::eDisconnected) { + return; + } + + PacketAuthPolicy::Selection policy = PacketAuthPolicy::fromWireValue(THIS->db.config.security.packet_signature_policy); + lv_dropdown_set_selected(ui_PacketAuthDropdown, PacketAuthPolicy::toWireValue(policy)); + THIS->strictConfirmationPending = false; + lv_label_set_text(ui_PacketAuthOkLabel, _("Save")); + lv_obj_add_flag(ui_PacketAuthWarning, LV_OBJ_FLAG_HIDDEN); + ui_event_packet_auth_dropdown(e); + + lv_obj_clear_flag(ui_PacketAuthPanel, LV_OBJ_FLAG_HIDDEN); + THIS->disablePanel(objects.controller_panel); + THIS->disablePanel(objects.tab_page_basic_settings); + THIS->activeSettings = ePacketAuthPolicy; + lv_group_focus_obj(ui_PacketAuthDropdown); +} + +void TFTView_320x240::ui_event_packet_auth_dropdown(lv_event_t *e) +{ + PacketAuthPolicy::Selection policy = PacketAuthPolicy::fromWireValue(lv_dropdown_get_selected(ui_PacketAuthDropdown)); + THIS->strictConfirmationPending = false; + lv_label_set_text(ui_PacketAuthOkLabel, _("Save")); + + switch (policy) { + case PacketAuthPolicy::Selection::Compatible: + lv_label_set_text(ui_PacketAuthDescription, _("Accept unsigned packets for maximum compatibility.")); + lv_obj_add_flag(ui_PacketAuthWarning, LV_OBJ_FLAG_HIDDEN); + break; + case PacketAuthPolicy::Selection::Strict: + lv_label_set_text(ui_PacketAuthDescription, _("Require verified signatures for remote mesh packets.")); + lv_obj_clear_flag(ui_PacketAuthWarning, LV_OBJ_FLAG_HIDDEN); + break; + case PacketAuthPolicy::Selection::Balanced: + default: + lv_label_set_text(ui_PacketAuthDescription, _("Prefer signed packets (recommended).")); + lv_obj_add_flag(ui_PacketAuthWarning, LV_OBJ_FLAG_HIDDEN); + break; + } + + // Strict mode adds a warning line. Pull the description up once the + // selector closes so the warning has a clear gap above the actions; keep + // the lower position while the selector is open so its list has room. + lv_obj_set_y(ui_PacketAuthDescription, policy == PacketAuthPolicy::Selection::Strict ? 76 : 112); + layoutPacketAuthPanel(); +} + +void TFTView_320x240::ui_event_packet_auth_ok(lv_event_t *e) +{ + if (lv_event_get_code(e) != LV_EVENT_CLICKED || THIS->activeSettings != ePacketAuthPolicy) { + return; + } + + if (!THIS->hasXeddsa || !THIS->db.config.has_security || THIS->state == MeshtasticView::eDisconnected) { + THIS->closePacketAuthPanel(); + return; + } + + PacketAuthPolicy::Selection current = PacketAuthPolicy::fromWireValue(THIS->db.config.security.packet_signature_policy); + PacketAuthPolicy::Selection requested = PacketAuthPolicy::fromWireValue(lv_dropdown_get_selected(ui_PacketAuthDropdown)); + if (PacketAuthPolicy::requiresStrictConfirmation(current, requested) && !THIS->strictConfirmationPending) { + THIS->strictConfirmationPending = true; + lv_label_set_text(ui_PacketAuthOkLabel, _("Confirm Strict")); + lv_group_focus_obj(ui_PacketAuthOkButton); + return; + } + + if (requested != current) { + meshtastic_Config_SecurityConfig security = THIS->db.config.security; + security.packet_signature_policy = + static_cast(PacketAuthPolicy::toWireValue(requested)); + if (!THIS->controller->sendConfig(meshtastic_Config_SecurityConfig{security}, THIS->ownNode)) { + lv_label_set_text(ui_PacketAuthDescription, _("Unable to save while disconnected.")); + return; + } + THIS->db.config.security = security; + setPacketAuthButtonLabel(requested); + } + + THIS->closePacketAuthPanel(); +} + +void TFTView_320x240::ui_event_packet_auth_cancel(lv_event_t *e) +{ + if (lv_event_get_code(e) == LV_EVENT_CLICKED && THIS->activeSettings == ePacketAuthPolicy) { + THIS->closePacketAuthPanel(); + } +} + +void TFTView_320x240::closePacketAuthPanel(void) +{ + lv_obj_add_flag(ui_PacketAuthPanel, LV_OBJ_FLAG_HIDDEN); + strictConfirmationPending = false; + enablePanel(objects.controller_panel); + enablePanel(objects.tab_page_basic_settings); + activeSettings = eNone; + if (hasXeddsa && state != MeshtasticView::eDisconnected) { + lv_group_focus_obj(ui_PacketAuthButton); + } +} + +void TFTView_320x240::updatePacketAuthAvailability(void) +{ + const bool available = hasXeddsa && db.config.has_security && state != MeshtasticView::eDisconnected; + if (available) { + lv_obj_clear_flag(ui_PacketAuthButton, LV_OBJ_FLAG_HIDDEN); + lv_obj_clear_state(ui_PacketAuthButton, LV_STATE_DISABLED); + } else { + lv_obj_add_flag(ui_PacketAuthButton, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_state(ui_PacketAuthButton, LV_STATE_DISABLED); + if (activeSettings == ePacketAuthPolicy) { + closePacketAuthPanel(); + } + } +} + void TFTView_320x240::ui_event_input_button(lv_event_t *e) { lv_event_code_t event_code = lv_event_get_code(e); @@ -4769,8 +4933,10 @@ void TFTView_320x240::setMyInfo(uint32_t nodeNum) } void TFTView_320x240::setDeviceMetaData(int hw_model, const char *version, bool has_bluetooth, bool has_wifi, bool has_eth, - bool can_shutdown) + bool can_shutdown, bool has_xeddsa) { + hasXeddsa = has_xeddsa; + updatePacketAuthAvailability(); } void TFTView_320x240::addOrUpdateNode(uint32_t nodeNum, uint8_t channel, uint32_t lastHeard, const meshtastic_User &cfg) @@ -5774,6 +5940,7 @@ void TFTView_320x240::notifyConnected(const char *info) THIS->controller->setConfigRequested(true); } state = MeshtasticView::eRunning; + updatePacketAuthAvailability(); } } @@ -5785,7 +5952,12 @@ void TFTView_320x240::notifyDisconnected(const char *info) if (state == MeshtasticView::eRunning) { messageAlert(_("Disconnected!"), true); } - state = MeshtasticView::eDisconnected; + } + state = MeshtasticView::eDisconnected; + hasXeddsa = false; + db.config.has_security = false; + if (screensInitialised) { + updatePacketAuthAvailability(); } } @@ -6265,6 +6437,8 @@ void TFTView_320x240::updateSecurityConfig(const meshtastic_Config_SecurityConfi { db.config.security = cfg; db.config.has_security = true; + setPacketAuthButtonLabel(PacketAuthPolicy::fromWireValue(cfg.packet_signature_policy)); + updatePacketAuthAvailability(); // display public key in qr code label char buf[64]; diff --git a/source/graphics/common/MeshtasticView.cpp b/source/graphics/common/MeshtasticView.cpp index 92a584c2..cbe097ea 100644 --- a/source/graphics/common/MeshtasticView.cpp +++ b/source/graphics/common/MeshtasticView.cpp @@ -50,7 +50,7 @@ bool MeshtasticView::sleep(int16_t pin) void MeshtasticView::setMyInfo(uint32_t nodeNum) {} void MeshtasticView::setDeviceMetaData(int hw_model, const char *version, bool has_bluetooth, bool has_wifi, bool has_eth, - bool can_shutdown) + bool can_shutdown, bool has_xeddsa) { } diff --git a/source/graphics/common/ViewController.cpp b/source/graphics/common/ViewController.cpp index 451aa191..bdfae0c7 100644 --- a/source/graphics/common/ViewController.cpp +++ b/source/graphics/common/ViewController.cpp @@ -874,7 +874,7 @@ bool ViewController::handleFromRadio(const meshtastic_FromRadio &from) case meshtastic_FromRadio_metadata_tag: { const meshtastic_DeviceMetadata &meta = from.metadata; view->setDeviceMetaData(meta.hw_model, meta.firmware_version, meta.hasBluetooth, meta.hasWifi, meta.hasEthernet, - meta.canShutdown); + meta.canShutdown, meta.has_xeddsa); break; } case meshtastic_FromRadio_config_complete_id_tag: { diff --git a/tests/test_PacketAuthPolicy.cpp b/tests/test_PacketAuthPolicy.cpp new file mode 100644 index 00000000..c67a20ce --- /dev/null +++ b/tests/test_PacketAuthPolicy.cpp @@ -0,0 +1,23 @@ +#include "graphics/view/TFT/PacketAuthPolicy.h" +#include + +using PacketAuthPolicy::Selection; + +TEST_CASE("packet authenticity wire values map to UI selections") +{ + CHECK(PacketAuthPolicy::fromWireValue(0) == Selection::Balanced); + CHECK(PacketAuthPolicy::fromWireValue(1) == Selection::Compatible); + CHECK(PacketAuthPolicy::fromWireValue(2) == Selection::Strict); + CHECK(PacketAuthPolicy::fromWireValue(99) == Selection::Balanced); + CHECK(PacketAuthPolicy::toWireValue(Selection::Balanced) == 0); + CHECK(PacketAuthPolicy::toWireValue(Selection::Compatible) == 1); + CHECK(PacketAuthPolicy::toWireValue(Selection::Strict) == 2); +} + +TEST_CASE("only a transition into Strict requires confirmation") +{ + CHECK(PacketAuthPolicy::requiresStrictConfirmation(Selection::Balanced, Selection::Strict)); + CHECK(PacketAuthPolicy::requiresStrictConfirmation(Selection::Compatible, Selection::Strict)); + CHECK_FALSE(PacketAuthPolicy::requiresStrictConfirmation(Selection::Strict, Selection::Strict)); + CHECK_FALSE(PacketAuthPolicy::requiresStrictConfirmation(Selection::Strict, Selection::Balanced)); +}