fix: enhance KNX OAM router functionality and security features
Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
+108
-13
@@ -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
|
||||
|
||||
@@ -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<int> QueryInt(std::string_view query, std::string_view primary,
|
||||
}
|
||||
|
||||
std::optional<uint32_t> ValidKnxInstanceId(int value) {
|
||||
if (value < 0 || static_cast<uint32_t>(value) >= kKnxEnabledInstanceCount) {
|
||||
return std::nullopt;
|
||||
if (value >= 0) {
|
||||
const auto instance_id = static_cast<uint32_t>(value);
|
||||
if (instance_id < kKnxEnabledInstanceCount ||
|
||||
(kKnxOamInstanceSupported && instance_id == kKnxOamInstanceId)) {
|
||||
return instance_id;
|
||||
}
|
||||
}
|
||||
return static_cast<uint32_t>(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<uint32_t> 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<double>(instance_id)));
|
||||
}
|
||||
if (kKnxOamInstanceSupported && oam_enabled) {
|
||||
cJSON_AddItemToArray(instances,
|
||||
cJSON_CreateNumber(static_cast<double>(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<double>(instance_id));
|
||||
cJSON_AddNumberToObject(knx_json, "enabledInstanceCount",
|
||||
static_cast<double>(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<double>(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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ class EtsDeviceRuntime {
|
||||
public:
|
||||
using CemiFrameSender = std::function<void(const uint8_t* data, size_t len)>;
|
||||
using CemiFrameReceiver = std::function<bool(const uint8_t* data, size_t len)>;
|
||||
using TpAckHandler = std::function<TPAckType(uint16_t destination, bool is_group_address)>;
|
||||
using GroupWriteHandler = std::function<void(uint16_t group_address, const uint8_t* data,
|
||||
size_t len)>;
|
||||
using GroupObjectWriteHandler = std::function<void(uint16_t group_object_number,
|
||||
@@ -64,6 +65,7 @@ class EtsDeviceRuntime {
|
||||
void setGroupObjectWriteHandler(GroupObjectWriteHandler handler);
|
||||
void setBusFrameSender(CemiFrameSender sender);
|
||||
void setTpFrameReceiver(CemiFrameReceiver receiver);
|
||||
void setTpAckHandler(TpAckHandler handler);
|
||||
void setNetworkInterface(esp_netif_t* netif);
|
||||
bool hasTpUart() const;
|
||||
bool enableTpUart(bool enabled = true);
|
||||
@@ -124,6 +126,7 @@ class EtsDeviceRuntime {
|
||||
CemiFrameSender sender_;
|
||||
CemiFrameSender bus_frame_sender_;
|
||||
CemiFrameReceiver tp_frame_receiver_;
|
||||
TpAckHandler tp_ack_handler_;
|
||||
GroupWriteHandler group_write_handler_;
|
||||
GroupObjectWriteHandler group_object_write_handler_;
|
||||
FunctionPropertyHandler command_handler_;
|
||||
|
||||
@@ -33,6 +33,7 @@ class TpuartUartInterface;
|
||||
|
||||
constexpr uint16_t kGatewayKnxDefaultUdpPort = 3671;
|
||||
constexpr const char* kGatewayKnxDefaultMulticastAddress = "224.0.23.12";
|
||||
constexpr const char* kGatewayKnxOamOpenKnxNamespace = "openknx_oam";
|
||||
constexpr uint32_t kGatewayKnxDefaultTpBaudrate = 19200;
|
||||
constexpr uint32_t kGatewayKnxDefaultTpStartupTimeoutMs = 2000;
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ inline constexpr uint32_t kOamRouterSerialMacIncrement = kDaliMaxKnxInstanceCoun
|
||||
inline constexpr uint16_t kOamRouterDeviceDescriptor = 0x091A;
|
||||
inline constexpr uint16_t kOamRouterManufacturerId =
|
||||
static_cast<uint16_t>(CONFIG_GATEWAY_KNX_OAM_ROUTER_OEM_MANUFACTURER_ID);
|
||||
inline constexpr uint16_t kOamRouterHardwareId =
|
||||
inline constexpr uint16_t kOamRouterLegacyHardwareId =
|
||||
static_cast<uint16_t>(CONFIG_GATEWAY_KNX_OAM_ROUTER_HARDWARE_ID);
|
||||
inline constexpr uint16_t kOamRouterApplicationNumber =
|
||||
static_cast<uint16_t>(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<uint8_t>((kOamRouterHardwareId >> 8) & 0xff),
|
||||
static_cast<uint8_t>(kOamRouterHardwareId & 0xff),
|
||||
static_cast<uint8_t>((kOamRouterApplicationNumber >> 8) & 0xff),
|
||||
static_cast<uint8_t>(kOamRouterApplicationNumber & 0xff),
|
||||
kOamRouterApplicationVersion,
|
||||
0x00};
|
||||
inline constexpr uint8_t kOamRouterLegacyHardwareType[6] = {
|
||||
0x00,
|
||||
0x00,
|
||||
static_cast<uint8_t>((kOamRouterLegacyHardwareId >> 8) & 0xff),
|
||||
static_cast<uint8_t>(kOamRouterLegacyHardwareId & 0xff),
|
||||
kOamRouterApplicationVersion,
|
||||
0x00};
|
||||
inline constexpr uint8_t kOamRouterOrderNumber[10] = {
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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::OamRouterRuntime>(
|
||||
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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<uint8_t> local_tunnel_frame;
|
||||
|
||||
@@ -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 <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
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<uint8_t, 16> 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<Bau091A&>(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<OamRouterRuntime*>(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;
|
||||
}
|
||||
|
||||
+1
-1
Submodule knx updated: 29fa7318f9...413c8f5e26
Reference in New Issue
Block a user