diff --git a/apps/gateway/sdkconfig.old b/apps/gateway/sdkconfig.old index 0d5c434..d4e3ee7 100644 --- a/apps/gateway/sdkconfig.old +++ b/apps/gateway/sdkconfig.old @@ -600,12 +600,12 @@ CONFIG_PARTITION_TABLE_MD5=y # # DALI Settings # -CONFIG_GATEWAY_CHANNEL_COUNT=2 +CONFIG_GATEWAY_CHANNEL_COUNT=1 # # Gateway Channel 1 # -CONFIG_GATEWAY_CHANNEL1_GW_ID=3 +CONFIG_GATEWAY_CHANNEL1_GW_ID=0 # CONFIG_GATEWAY_CHANNEL1_PHY_DISABLED is not set CONFIG_GATEWAY_CHANNEL1_PHY_NATIVE=y # CONFIG_GATEWAY_CHANNEL1_PHY_UART1 is not set @@ -619,17 +619,78 @@ CONFIG_GATEWAY_CHANNEL1_NATIVE_BAUDRATE=1200 # # Gateway Channel 2 # -CONFIG_GATEWAY_CHANNEL2_GW_ID=4 -# CONFIG_GATEWAY_CHANNEL2_PHY_DISABLED is not set -CONFIG_GATEWAY_CHANNEL2_PHY_NATIVE=y -# CONFIG_GATEWAY_CHANNEL2_PHY_UART1 is not set -# CONFIG_GATEWAY_CHANNEL2_PHY_UART2 is not set -CONFIG_GATEWAY_CHANNEL2_NATIVE_BUS_ID=1 -CONFIG_GATEWAY_CHANNEL2_NATIVE_TX_PIN=4 -CONFIG_GATEWAY_CHANNEL2_NATIVE_RX_PIN=3 -CONFIG_GATEWAY_CHANNEL2_NATIVE_BAUDRATE=1200 # end of Gateway Channel 2 +# +# Gateway Channel 3 +# +# end of Gateway Channel 3 + +# +# Gateway Channel 4 +# +# end of Gateway Channel 4 + +# +# Gateway Channel 5 +# +# end of Gateway Channel 5 + +# +# Gateway Channel 6 +# +# end of Gateway Channel 6 + +# +# Gateway Channel 7 +# +# end of Gateway Channel 7 + +# +# Gateway Channel 8 +# +# end of Gateway Channel 8 + +# +# Gateway Channel 9 +# +# end of Gateway Channel 9 + +# +# Gateway Channel 10 +# +# end of Gateway Channel 10 + +# +# Gateway Channel 11 +# +# end of Gateway Channel 11 + +# +# Gateway Channel 12 +# +# end of Gateway Channel 12 + +# +# Gateway Channel 13 +# +# end of Gateway Channel 13 + +# +# Gateway Channel 14 +# +# end of Gateway Channel 14 + +# +# Gateway Channel 15 +# +# end of Gateway Channel 15 + +# +# Gateway Channel 16 +# +# end of Gateway Channel 16 + # # Gateway Cache # @@ -644,10 +705,11 @@ CONFIG_GATEWAY_CACHE_OUTSIDE_BUS_FIRST=y # end of Gateway Cache # CONFIG_GATEWAY_ENABLE_DALI_BUS is not set +CONFIG_GATEWAY_CONTROLLER_TASK_STACK_SIZE=12288 # end of DALI Settings # -# Gateway Startup Services +# Connectivity Startup # CONFIG_GATEWAY_BLE_SUPPORTED=y CONFIG_GATEWAY_START_BLE_ENABLED=y @@ -661,6 +723,7 @@ CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC=60 CONFIG_GATEWAY_ETHERNET_SUPPORTED=y CONFIG_GATEWAY_START_ETHERNET_ENABLED=y CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE=y +# end of Connectivity Startup # # Gateway Wired Ethernet @@ -678,7 +741,15 @@ CONFIG_GATEWAY_ETHERNET_PHY_ADDR=1 CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE=4096 # end of Gateway Wired Ethernet +# +# Bridge Runtime +# CONFIG_GATEWAY_BRIDGE_SUPPORTED=y +# end of Bridge Runtime + +# +# Modbus Settings +# CONFIG_GATEWAY_MODBUS_BRIDGE_SUPPORTED=y # CONFIG_GATEWAY_START_MODBUS_BRIDGE_ENABLED is not set CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_TCP=y @@ -686,8 +757,14 @@ CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_TCP=y # CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII is not set CONFIG_GATEWAY_MODBUS_TCP_PORT=1502 CONFIG_GATEWAY_MODBUS_UNIT_ID=1 +# end of Modbus Settings + +# +# BACnet Settings +# CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED=y # CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED is not set +# end of BACnet Settings # # KNX Settings @@ -737,6 +814,9 @@ CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE=12288 CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY=5 # end of KNX Settings +# +# Cloud Settings +# CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y # CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set CONFIG_GATEWAY_CLOUD_TOPIC_PREFIX="devices" @@ -747,14 +827,29 @@ CONFIG_GATEWAY_CLOUD_LTE_UART_PORT=-1 CONFIG_GATEWAY_CLOUD_LTE_UART_TX_PIN=-1 CONFIG_GATEWAY_CLOUD_LTE_UART_RX_PIN=-1 CONFIG_GATEWAY_CLOUD_LTE_UART_BAUDRATE=115200 +# end of Cloud Settings + +# +# Bridge Task Settings +# CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE=6144 CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_PRIORITY=4 CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE=8192 CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY=5 +# end of Bridge Task Settings + +# +# USB Setup +# CONFIG_GATEWAY_USB_STARTUP_DEBUG_JTAG=y # CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL is not set +# end of USB Setup + +# +# UART0 Control +# # CONFIG_GATEWAY_485_CONTROL_ENABLED is not set -# end of Gateway Startup Services +# end of UART0 Control # # Gateway Network Services diff --git a/components/gateway_bridge/src/gateway_bridge.cpp b/components/gateway_bridge/src/gateway_bridge.cpp index c97c5e7..fe95e1d 100644 --- a/components/gateway_bridge/src/gateway_bridge.cpp +++ b/components/gateway_bridge/src/gateway_bridge.cpp @@ -70,6 +70,12 @@ constexpr uint8_t kDaliGroupRawMax = 0x9F; #define CONFIG_GATEWAY_KNX_INSTANCE_COUNT 1 #endif constexpr uint32_t kKnxEnabledInstanceCount = CONFIG_GATEWAY_KNX_INSTANCE_COUNT; +constexpr uint32_t kKnxOamInstanceId = 16; +#if defined(CONFIG_GATEWAY_KNX_OAM_ROUTER_SUPPORTED) +constexpr bool kKnxOamInstanceSupported = true; +#else +constexpr bool kKnxOamInstanceSupported = false; +#endif void ConfigureDaliCppLogging() { static bool configured = false; @@ -400,21 +406,49 @@ std::optional QueryInt(std::string_view query, std::string_view primary, } std::optional ValidKnxInstanceId(int value) { - if (value < 0 || static_cast(value) >= kKnxEnabledInstanceCount) { - return std::nullopt; + if (value >= 0) { + const auto instance_id = static_cast(value); + if (instance_id < kKnxEnabledInstanceCount || + (kKnxOamInstanceSupported && instance_id == kKnxOamInstanceId)) { + return instance_id; + } } - return static_cast(value); + return std::nullopt; } std::string KnxInstanceIdRangeMessage() { - return "KNX instanceId must be in range 0.." + - std::to_string(kKnxEnabledInstanceCount - 1); + std::string message = "KNX instanceId must be in range 0.." + + std::to_string(kKnxEnabledInstanceCount - 1); + if (kKnxOamInstanceSupported) { + message += " or " + std::to_string(kKnxOamInstanceId) + " for OAM"; + } + return message; } std::optional QueryKnxInstanceId(std::string_view query) { return ValidKnxInstanceId(QueryInt(query, "instanceId", "instance").value_or(0)); } +bool IsKnxOamInstanceId(uint32_t instance_id) { + return kKnxOamInstanceSupported && instance_id == kKnxOamInstanceId; +} + +cJSON* KnxAvailableInstanceIdsCjson(bool oam_enabled) { + cJSON* instances = cJSON_CreateArray(); + if (instances == nullptr) { + return nullptr; + } + for (uint32_t instance_id = 0; instance_id < kKnxEnabledInstanceCount; ++instance_id) { + cJSON_AddItemToArray(instances, + cJSON_CreateNumber(static_cast(instance_id))); + } + if (kKnxOamInstanceSupported && oam_enabled) { + cJSON_AddItemToArray(instances, + cJSON_CreateNumber(static_cast(kKnxOamInstanceId))); + } + return instances; +} + bool EraseOpenKnxRuntimeData(const std::string& nvs_namespace) { if (nvs_namespace.empty()) { return false; @@ -2401,9 +2435,19 @@ struct GatewayBridgeService::ChannelRuntime { } const auto effective_knx = knx_config.has_value() ? knx_config : service_config.default_knx_config; + const bool oam_instance_enabled = + effective_knx.has_value() && effective_knx->oam_router.enabled; cJSON_AddNumberToObject(knx_json, "instanceId", static_cast(instance_id)); cJSON_AddNumberToObject(knx_json, "enabledInstanceCount", static_cast(kKnxEnabledInstanceCount)); + cJSON* instance_ids_json = KnxAvailableInstanceIdsCjson(oam_instance_enabled); + if (instance_ids_json != nullptr) { + cJSON_AddItemToObject(knx_json, "availableInstanceIds", instance_ids_json); + } + if (kKnxOamInstanceSupported) { + cJSON_AddNumberToObject(knx_json, "oamInstanceId", + static_cast(kKnxOamInstanceId)); + } cJSON_AddBoolToObject(knx_json, "enabled", service_config.knx_enabled); cJSON_AddBoolToObject(knx_json, "startupEnabled", service_config.knx_startup_enabled); cJSON_AddBoolToObject(knx_json, "started", knx_started); @@ -2457,14 +2501,19 @@ struct GatewayBridgeService::ChannelRuntime { cJSON_AddStringToObject(security_json, "storage", "none"); #endif #if defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED) - const auto fdsk_info = openknx::LoadFactoryFdskInfoForInstance(instance_id); + const bool oam_instance = IsKnxOamInstanceId(instance_id); + const auto fdsk_info = oam_instance + ? openknx::LoadOamFactoryFdskInfo() + : openknx::LoadFactoryFdskInfoForInstance(instance_id); cJSON* fdsk_json = FactoryFdskInfoToCjson(fdsk_info, true); if (fdsk_json != nullptr) { cJSON_AddItemToObject(security_json, "factorySetupKey", fdsk_json); } cJSON* certificate_json = FactoryCertificateToCjson( - openknx::BuildFactoryCertificatePayloadForInstance(instance_id), false); + oam_instance ? openknx::BuildOamFactoryCertificatePayload() + : openknx::BuildFactoryCertificatePayloadForInstance(instance_id), + false); if (certificate_json != nullptr) { cJSON_AddItemToObject(security_json, "factoryCertificate", certificate_json); } @@ -4963,6 +5012,7 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost( return ErrorResponse(ESP_ERR_INVALID_ARG, message.c_str()); } + const bool reset_oam = IsKnxOamInstanceId(instance_id.value()); const bool reset_runtime = instance_id.value() == 0; const bool restart_router = reset_runtime && (runtime->knx_started || @@ -4975,7 +5025,9 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost( } std::string nvs_namespace = runtime->openKnxNamespace(); - if (instance_id.value() != 0) { + if (reset_oam) { + nvs_namespace = kGatewayKnxOamOpenKnxNamespace; + } else if (instance_id.value() != 0) { nvs_namespace += "_" + std::to_string(instance_id.value()); } if (!EraseOpenKnxRuntimeData(nvs_namespace)) { @@ -4983,7 +5035,11 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost( } openknx::FactoryFdskInfo info; - if (!openknx::ResetFactorySecurityForInstance(instance_id.value(), &info)) { + const bool security_reset = reset_oam + ? openknx::ResetOamFactoryFdskCache(&info) + : openknx::ResetFactorySecurityForInstance(instance_id.value(), + &info); + if (!security_reset) { return ErrorResponse(ESP_FAIL, "failed to restore KNX factory security key"); } diff --git a/components/gateway_bridge/src/security_storage.cpp b/components/gateway_bridge/src/security_storage.cpp index b196c8f..34e39c8 100644 --- a/components/gateway_bridge/src/security_storage.cpp +++ b/components/gateway_bridge/src/security_storage.cpp @@ -489,6 +489,16 @@ bool LoadFactoryFdsk(uint8_t* data, size_t len) { return LoadFactoryFdskForContext(kReg1Context, data, len); } +extern "C" bool knx_platform_get_fdsk_for_namespace(const char* nvs_namespace, + uint8_t* data, + size_t len) { + const std::string namespace_name = nvs_namespace == nullptr ? std::string() : nvs_namespace; + if (namespace_name.size() >= 4 && namespace_name.compare(namespace_name.size() - 4, 4, "_oam") == 0) { + return LoadFactoryFdskForContext(kOamContext, data, len); + } + return LoadFactoryFdsk(data, len); +} + FactoryFdskInfo LoadFactoryFdskInfo() { return LoadFactoryFdskInfoForContext(kReg1Context); } diff --git a/components/gateway_knx/include/ets_device_runtime.h b/components/gateway_knx/include/ets_device_runtime.h index b3a4ac0..c50dd58 100644 --- a/components/gateway_knx/include/ets_device_runtime.h +++ b/components/gateway_knx/include/ets_device_runtime.h @@ -23,6 +23,7 @@ class EtsDeviceRuntime { public: using CemiFrameSender = std::function; using CemiFrameReceiver = std::function; + using TpAckHandler = std::function; using GroupWriteHandler = std::function; using GroupObjectWriteHandler = std::function(CONFIG_GATEWAY_KNX_OAM_ROUTER_OEM_MANUFACTURER_ID); -inline constexpr uint16_t kOamRouterHardwareId = +inline constexpr uint16_t kOamRouterLegacyHardwareId = static_cast(CONFIG_GATEWAY_KNX_OAM_ROUTER_HARDWARE_ID); inline constexpr uint16_t kOamRouterApplicationNumber = static_cast(CONFIG_GATEWAY_KNX_OAM_ROUTER_APPLICATION_NUMBER); @@ -87,8 +87,15 @@ inline constexpr uint8_t kOamRouterApplicationVersion = inline constexpr uint8_t kOamRouterHardwareType[6] = { 0x00, 0x00, - static_cast((kOamRouterHardwareId >> 8) & 0xff), - static_cast(kOamRouterHardwareId & 0xff), + static_cast((kOamRouterApplicationNumber >> 8) & 0xff), + static_cast(kOamRouterApplicationNumber & 0xff), + kOamRouterApplicationVersion, + 0x00}; +inline constexpr uint8_t kOamRouterLegacyHardwareType[6] = { + 0x00, + 0x00, + static_cast((kOamRouterLegacyHardwareId >> 8) & 0xff), + static_cast(kOamRouterLegacyHardwareId & 0xff), kOamRouterApplicationVersion, 0x00}; inline constexpr uint8_t kOamRouterOrderNumber[10] = { diff --git a/components/gateway_knx/include/oam_router_runtime.h b/components/gateway_knx/include/oam_router_runtime.h index 5532900..141a1a6 100644 --- a/components/gateway_knx/include/oam_router_runtime.h +++ b/components/gateway_knx/include/oam_router_runtime.h @@ -38,6 +38,8 @@ class OamRouterRuntime { bool programmingMode() const; void setProgrammingMode(bool enabled); void toggleProgrammingMode(); + bool matchesSecureSyncSerial(CemiFrame& frame) const; + bool matchesRecentSecureToolAccess(CemiFrame& frame) const; EtsMemorySnapshot snapshot() const; DeviceObject* deviceObject(); @@ -60,6 +62,8 @@ class OamRouterRuntime { std::string nvs_namespace_; CemiFrameSender sender_; CemiFrameSender bus_frame_sender_; + mutable uint16_t recent_secure_tool_source_{0xffff}; + mutable int64_t recent_secure_tool_sync_us_{0}; #if defined(ENABLE_BAU091A_PERSONA) EspIdfPlatform platform_; Bau091A device_; diff --git a/components/gateway_knx/src/ets_device_runtime.cpp b/components/gateway_knx/src/ets_device_runtime.cpp index 8a3be11..e5eb68a 100644 --- a/components/gateway_knx/src/ets_device_runtime.cpp +++ b/components/gateway_knx/src/ets_device_runtime.cpp @@ -301,6 +301,16 @@ void EtsDeviceRuntime::setTpFrameReceiver(CemiFrameReceiver receiver) { } } +void EtsDeviceRuntime::setTpAckHandler(TpAckHandler handler) { + tp_ack_handler_ = std::move(handler); + if (auto* data_link_layer = device_.getDataLinkLayer()) { + data_link_layer->acknowledgeHandler([this](uint16_t destination, bool is_group_address) { + return tp_ack_handler_ ? tp_ack_handler_(destination, is_group_address) + : TPAckType::AckReqNone; + }); + } +} + void EtsDeviceRuntime::setNetworkInterface(esp_netif_t* netif) { platform_.networkInterface(netif); } diff --git a/components/gateway_knx/src/gateway_knx_router_lifecycle.cpp b/components/gateway_knx/src/gateway_knx_router_lifecycle.cpp index e1ba081..58b740f 100644 --- a/components/gateway_knx/src/gateway_knx_router_lifecycle.cpp +++ b/components/gateway_knx/src/gateway_knx_router_lifecycle.cpp @@ -256,7 +256,7 @@ esp_err_t GatewayKnxTpIpRouter::initializeRuntime() { ets_device_->deviceObject(), ets_device_->platform()); if (config_.oam_router.enabled) { oam_router_ = std::make_unique( - openknx_namespace_ + "_oam", config_.oam_router.individual_address, + kGatewayKnxOamOpenKnxNamespace, config_.oam_router.individual_address, config_.oam_router.tunnel_address_base); if (oam_router_->available()) { oam_router_->setProgrammingMode(oam_programming_mode_); @@ -276,8 +276,8 @@ esp_err_t GatewayKnxTpIpRouter::initializeRuntime() { ets_device_->tunnelClientAddress(), commissioning_only_); if (oam_router_ != nullptr) { ESP_LOGI(kTag, - "OAM router persona namespace=%s_oam configured=%d device=0x%04x tunnelClient=0x%04x secureTunnel=%d secureRouting=%d", - openknx_namespace_.c_str(), oam_router_->configured(), + "OAM router persona namespace=%s configured=%d device=0x%04x tunnelClient=0x%04x secureTunnel=%d secureRouting=%d", + kGatewayKnxOamOpenKnxNamespace, oam_router_->configured(), oam_router_->individualAddress(), oam_router_->tunnelClientAddress(), config_.oam_router.secure_tunnel_enabled, config_.oam_router.secure_routing_enabled); @@ -354,6 +354,17 @@ esp_err_t GatewayKnxTpIpRouter::initializeRuntime() { ets_device_->setTpFrameReceiver([this](const uint8_t* data, size_t len) { return handleOpenKnxTpIngressFrame(data, len); }); + ets_device_->setTpAckHandler([this](uint16_t destination, bool is_group_address) { + if (is_group_address || oam_router_ == nullptr) { + return TPAckType::AckReqNone; + } + const bool commissioning = !oam_router_->configured() || oam_router_->programmingMode(); + return destination == oam_router_->individualAddress() || + destination == oam_router_->tunnelClientAddress() || + (commissioning && destination == 0xffff) + ? TPAckType::AckReqAck + : TPAckType::AckReqNone; + }); if (oam_router_ != nullptr) { oam_router_->setBusFrameSender([this](const uint8_t* data, size_t len) { publishCloudCemiFrame(data, len); diff --git a/components/gateway_knx/src/gateway_knx_router_openknx.cpp b/components/gateway_knx/src/gateway_knx_router_openknx.cpp index 3aee299..cafdab8 100644 --- a/components/gateway_knx/src/gateway_knx_router_openknx.cpp +++ b/components/gateway_knx/src/gateway_knx_router_openknx.cpp @@ -34,7 +34,9 @@ bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t if (frame.valid()) { route_to_all_internal_instances = IsKnxBroadcastManagementRequest(frame); if (!route_to_oam && oam_router_ != nullptr && - MatchesOamRouterLocalIndividualAddress(frame, *oam_router_)) { + (MatchesOamRouterLocalIndividualAddress(frame, *oam_router_) || + oam_router_->matchesSecureSyncSerial(frame) || + oam_router_->matchesRecentSecureToolAccess(frame))) { route_to_oam = true; } } @@ -326,7 +328,10 @@ bool GatewayKnxTpIpRouter::handleOpenKnxTpIngressFrame(const uint8_t* data, size const bool broadcast_management = IsKnxBroadcastManagementRequest(frame); const bool addressed_to_oam = - oam_router_ != nullptr && MatchesOamRouterLocalIndividualAddress(frame, *oam_router_); + oam_router_ != nullptr && + (MatchesOamRouterLocalIndividualAddress(frame, *oam_router_) || + oam_router_->matchesSecureSyncSerial(frame) || + oam_router_->matchesRecentSecureToolAccess(frame)); if (!broadcast_management && !addressed_to_oam) { return false; } diff --git a/components/gateway_knx/src/gateway_knx_router_services.cpp b/components/gateway_knx/src/gateway_knx_router_services.cpp index 4a31c6b..5b6c50d 100644 --- a/components/gateway_knx/src/gateway_knx_router_services.cpp +++ b/components/gateway_knx/src/gateway_knx_router_services.cpp @@ -186,7 +186,10 @@ void GatewayKnxTpIpRouter::handleRoutingIndication(const uint8_t* packet_data, s const size_t cemi_len = frame.dataLength(); bool consumed_by_local_application = false; const bool addressed_to_oam = - oam_router_ != nullptr && MatchesOamRouterLocalIndividualAddress(frame, *oam_router_); + oam_router_ != nullptr && + (MatchesOamRouterLocalIndividualAddress(frame, *oam_router_) || + oam_router_->matchesSecureSyncSerial(frame) || + oam_router_->matchesRecentSecureToolAccess(frame)); const bool addressed_to_reg1 = ets_device_ != nullptr && MatchesOpenKnxLocalIndividualAddress(frame, *ets_device_); std::vector local_tunnel_frame; diff --git a/components/gateway_knx/src/oam_router_runtime.cpp b/components/gateway_knx/src/oam_router_runtime.cpp index 84e01b9..a58fc85 100644 --- a/components/gateway_knx/src/oam_router_runtime.cpp +++ b/components/gateway_knx/src/oam_router_runtime.cpp @@ -4,19 +4,52 @@ #include "esp_log.h" #include "esp_mac.h" +#include "esp_timer.h" #include "knx/cemi_server.h" #include "knx/property.h" #include +#include +#include #include #include #include +extern "C" bool knx_platform_get_fdsk_for_namespace(const char* nvs_namespace, + uint8_t* data, + size_t len) __attribute__((weak)); + namespace gateway::openknx { namespace { constexpr uint16_t kInvalidIndividualAddress = 0xffff; constexpr uint16_t kKnxUnconfiguredBroadcastAddress = 0xffff; +constexpr uint8_t kSecureDataPdu = 0; +constexpr uint8_t kSecureSyncRequest = 2; +constexpr uint8_t kSecureToolAccessFlag = 0x80; +constexpr size_t kKnxSerialLength = 6; +constexpr size_t kSecureApduScfOffset = 1; +constexpr size_t kSecureApduSerialOffset = 8; +constexpr size_t kSecureApduMinimumSyncRequestLength = + kSecureApduSerialOffset + kKnxSerialLength; +constexpr int64_t kSecureToolAccessRouteWindowUs = 120LL * 1000LL * 1000LL; +constexpr uint16_t kOamDeviceObjectVersion = 3; + +bool HardwareTypeEquals(const uint8_t* actual, const uint8_t* expected) { + return actual != nullptr && expected != nullptr && + std::memcmp(actual, expected, sizeof(knx_internal::kOamRouterHardwareType)) == 0; +} + +VersionCheckResult OamRouterVersionCheck(uint16_t manufacturer_id, + uint8_t* hardware_type, + uint16_t version) { + if (manufacturer_id != knx_internal::kOamRouterManufacturerId || + (!HardwareTypeEquals(hardware_type, knx_internal::kOamRouterHardwareType) && + !HardwareTypeEquals(hardware_type, knx_internal::kOamRouterLegacyHardwareType))) { + return FlashAllInvalid; + } + return version == kOamDeviceObjectVersion ? FlashValid : FlashTablesInvalid; +} bool IsUsableIndividualAddress(uint16_t address) { return address != 0 && address != kInvalidIndividualAddress; @@ -37,6 +70,21 @@ bool IsBroadcastManagementRequest(CemiFrame& frame) { } } +bool IsGroupBroadcastSecureToolAccess(CemiFrame& frame, uint8_t* service) { + if (frame.addressType() != GroupAddress || frame.destinationAddress() != 0x0000 || + frame.apdu().type() != SecureService || frame.apdu().length() <= kSecureApduScfOffset) { + return false; + } + const uint8_t scf = frame.apdu().data()[kSecureApduScfOffset]; + if ((scf & kSecureToolAccessFlag) == 0) { + return false; + } + if (service != nullptr) { + *service = scf & 0x07; + } + return true; +} + uint32_t OamBauNumberFromBaseMac() { uint8_t mac[6]{}; if (esp_efuse_mac_get_default(mac) != ESP_OK && esp_read_mac(mac, ESP_MAC_WIFI_STA) != ESP_OK) { @@ -59,6 +107,22 @@ void ApplyOamRouterIdentity(Bau091A& device) { property->write(knx_internal::kOamRouterProgramVersion); } } + +void ApplyOamFactoryFdsk(Bau091A& device, const std::string& nvs_namespace) { +#if defined(USE_DATASECURE) + if (knx_platform_get_fdsk_for_namespace == nullptr) { + return; + } + std::array fdsk{}; + if (knx_platform_get_fdsk_for_namespace(nvs_namespace.c_str(), fdsk.data(), fdsk.size())) { + device.factoryFdsk(fdsk.data(), fdsk.size()); + } + std::fill(fdsk.begin(), fdsk.end(), 0); +#else + (void)device; + (void)nvs_namespace; +#endif +} #endif } // namespace @@ -75,12 +139,15 @@ OamRouterRuntime::OamRouterRuntime(std::string nvs_namespace, { #if defined(ENABLE_BAU091A_PERSONA) platform_.outboundCemiFrameCallback(&OamRouterRuntime::HandleOutboundCemiFrame, this); + ApplyOamFactoryFdsk(device_, nvs_namespace_); ApplyOamRouterIdentity(device_); + device_.versionCheckCallback(&OamRouterVersionCheck); if (IsUsableIndividualAddress(fallback_individual_address)) { device_.deviceObject().individualAddress(fallback_individual_address); } ESP_LOGI("gateway_knx", "OAM OpenKNX loading memory namespace=%s", nvs_namespace_.c_str()); device_.readMemory(); + ApplyOamFactoryFdsk(device_, nvs_namespace_); ApplyOamRouterIdentity(device_); if (!IsUsableIndividualAddress(device_.deviceObject().individualAddress()) && IsUsableIndividualAddress(fallback_individual_address)) { @@ -174,6 +241,55 @@ void OamRouterRuntime::setProgrammingMode(bool enabled) { void OamRouterRuntime::toggleProgrammingMode() { setProgrammingMode(!programmingMode()); } +bool OamRouterRuntime::matchesSecureSyncSerial(CemiFrame& frame) const { +#if defined(ENABLE_BAU091A_PERSONA) && defined(USE_DATASECURE) + uint8_t service = 0; + if (!IsGroupBroadcastSecureToolAccess(frame, &service) || + frame.apdu().length() < kSecureApduMinimumSyncRequestLength) { + return false; + } + + if (service != kSecureSyncRequest) { + return false; + } + + const uint8_t* apdu_data = frame.apdu().data(); + const uint8_t* serial = apdu_data + kSecureApduSerialOffset; + const uint8_t* local_serial = + const_cast(device_).deviceObject().propertyData(PID_SERIAL_NUMBER); + const bool matches = local_serial != nullptr && + std::memcmp(serial, local_serial, kKnxSerialLength) == 0; + if (matches) { + recent_secure_tool_source_ = frame.sourceAddress(); + recent_secure_tool_sync_us_ = esp_timer_get_time(); + } + return matches; +#else + (void)frame; + return false; +#endif +} + +bool OamRouterRuntime::matchesRecentSecureToolAccess(CemiFrame& frame) const { +#if defined(ENABLE_BAU091A_PERSONA) && defined(USE_DATASECURE) + uint8_t service = 0; + if (!IsGroupBroadcastSecureToolAccess(frame, &service) || service != kSecureDataPdu || + recent_secure_tool_sync_us_ <= 0 || frame.sourceAddress() != recent_secure_tool_source_) { + return false; + } + const int64_t now = esp_timer_get_time(); + if (now < recent_secure_tool_sync_us_ || + now - recent_secure_tool_sync_us_ > kSecureToolAccessRouteWindowUs) { + return false; + } + recent_secure_tool_sync_us_ = now; + return true; +#else + (void)frame; + return false; +#endif +} + EtsMemorySnapshot OamRouterRuntime::snapshot() const { EtsMemorySnapshot out; #if defined(ENABLE_BAU091A_PERSONA) @@ -300,11 +416,18 @@ void OamRouterRuntime::loop() { bool OamRouterRuntime::HandleOutboundCemiFrame(CemiFrame& frame, void* context) { auto* self = static_cast(context); - if (self == nullptr || !self->sender_) { + if (self == nullptr) { return false; } - self->sender_(frame.data(), frame.dataLength()); - return true; + if (self->sender_) { + self->sender_(frame.data(), frame.dataLength()); + return true; + } + if (self->bus_frame_sender_) { + self->bus_frame_sender_(frame.data(), frame.dataLength()); + return true; + } + return false; } void OamRouterRuntime::EmitTunnelFrame(CemiFrame& frame, void* context) { @@ -348,6 +471,12 @@ bool OamRouterRuntime::shouldConsumeTunnelFrame(CemiFrame& frame) const { return dest == individualAddress() || dest == tunnelClientAddress() || (commissioning && dest == kKnxUnconfiguredBroadcastAddress); } + if (matchesSecureSyncSerial(frame)) { + return true; + } + if (matchesRecentSecureToolAccess(frame)) { + return true; + } if (IsBroadcastManagementRequest(frame)) { return true; } @@ -365,6 +494,12 @@ bool OamRouterRuntime::shouldConsumeBusFrame(CemiFrame& frame) const { if (IsBroadcastManagementRequest(frame)) { return true; } + if (matchesSecureSyncSerial(frame)) { + return true; + } + if (matchesRecentSecureToolAccess(frame)) { + return true; + } if (frame.addressType() != IndividualAddress) { return false; } diff --git a/knx b/knx index 29fa731..413c8f5 160000 --- a/knx +++ b/knx @@ -1 +1 @@ -Subproject commit 29fa7318f9d4e7389e455bb5d1e7bb80c174f4f5 +Subproject commit 413c8f5e26a1ea40022e34e0871dec51120582fd