Files
gateway/components/gateway_knx/src/gateway_knx.cpp
T
Tony 2b779d5532 Add secure transport and OAM router runtime implementations
- Implement secure transport mechanisms in `gateway_knx_secure_transport.cpp` for handling secure sessions, including AES encryption, session key generation, and secure packet wrapping and unwrapping.
- Introduce `OamRouterRuntime` in `oam_router_runtime.cpp` to manage OAM router identity, individual addresses, and tunnel frame handling.
- Enhance secure session management with functions for session allocation, authentication, and secure packet processing.
- Ensure compatibility with existing KNXnet/IP protocols while adding support for secure communications.

Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-25 08:18:01 +08:00

375 lines
17 KiB
C++

#include "gateway_knx_private.hpp"
namespace gateway {
namespace {
GatewayKnxCloudRemoteConfig GatewayKnxCloudRemoteConfigFromValue(const DaliValue* value) {
GatewayKnxCloudRemoteConfig config;
if (value == nullptr || value->asObject() == nullptr) {
return config;
}
const auto& object = *value->asObject();
config.enabled = ObjectBoolAny(object, {"enabled", "cloudRemoteEnabled",
"cloud_remote_enabled"})
.value_or(config.enabled);
config.mode = ObjectStringAny(object, {"mode", "remoteMode", "remote_mode"})
.value_or(config.mode);
config.relay_endpoint = ObjectStringAny(object, {"relayEndpoint", "relay_endpoint",
"endpoint"})
.value_or(config.relay_endpoint);
config.mqtt_topic_prefix = ObjectStringAny(object, {"mqttTopicPrefix", "mqtt_topic_prefix",
"topicPrefix", "topic_prefix"})
.value_or(config.mqtt_topic_prefix);
config.auth_token_ref = ObjectStringAny(object, {"authTokenRef", "auth_token_ref",
"tokenRef", "token_ref"})
.value_or(config.auth_token_ref);
config.require_secure_tunnel = ObjectBoolAny(object, {"requireSecureTunnel",
"require_secure_tunnel"})
.value_or(config.require_secure_tunnel);
config.udp_punch_enabled = ObjectBoolAny(object, {"udpPunchEnabled", "udp_punch_enabled"})
.value_or(config.udp_punch_enabled);
return config;
}
DaliValue GatewayKnxCloudRemoteConfigToValue(const GatewayKnxCloudRemoteConfig& config) {
DaliValue::Object out;
out["enabled"] = config.enabled;
out["mode"] = config.mode;
out["relayEndpoint"] = config.relay_endpoint;
out["mqttTopicPrefix"] = config.mqtt_topic_prefix;
out["authTokenRef"] = config.auth_token_ref;
out["requireSecureTunnel"] = config.require_secure_tunnel;
out["udpPunchEnabled"] = config.udp_punch_enabled;
return DaliValue(std::move(out));
}
} // namespace
std::optional<GatewayKnxOamRouterConfig> GatewayKnxOamRouterConfigFromValue(
const DaliValue* value) {
if (value == nullptr || value->asObject() == nullptr) {
return std::nullopt;
}
const auto& object = *value->asObject();
GatewayKnxOamRouterConfig config;
config.enabled = ObjectBoolAny(object, {"enabled", "oamRouterEnabled",
"oam_router_enabled"})
.value_or(config.enabled);
config.ets_database_enabled = ObjectBoolAny(object, {"etsDatabaseEnabled",
"ets_database_enabled"})
.value_or(config.ets_database_enabled);
config.secure_tunnel_enabled = ObjectBoolAny(object, {"secureTunnelEnabled",
"secure_tunnel_enabled"})
.value_or(config.secure_tunnel_enabled);
config.secure_routing_enabled = ObjectBoolAny(object, {"secureRoutingEnabled",
"secure_routing_enabled"})
.value_or(config.secure_routing_enabled);
config.individual_address = static_cast<uint16_t>(std::clamp(
ObjectIntAny(object, {"individualAddress", "individual_address", "routerAddress",
"router_address"})
.value_or(config.individual_address),
0, 0xffff));
config.tunnel_address_base = static_cast<uint16_t>(std::clamp(
ObjectIntAny(object, {"tunnelAddressBase", "tunnel_address_base",
"tunnelBaseAddress", "tunnel_base_address"})
.value_or(config.tunnel_address_base),
0, 0xffff));
config.programming_button_gpio = std::clamp(
ObjectIntAny(object, {"programmingButtonGpio", "programming_button_gpio"})
.value_or(config.programming_button_gpio),
-1, 48);
config.programming_button_active_low =
ObjectBoolAny(object, {"programmingButtonActiveLow", "programming_button_active_low"})
.value_or(config.programming_button_active_low);
config.programming_led_gpio = std::clamp(
ObjectIntAny(object, {"programmingLedGpio", "programming_led_gpio"})
.value_or(config.programming_led_gpio),
-1, 48);
config.programming_led_active_high =
ObjectBoolAny(object, {"programmingLedActiveHigh", "programming_led_active_high"})
.value_or(config.programming_led_active_high);
config.cloud_remote = GatewayKnxCloudRemoteConfigFromValue(
ObjectValueAny(object, {"cloudRemote", "cloud_remote", "remoteAccess",
"remote_access"}));
return config;
}
DaliValue GatewayKnxOamRouterConfigToValue(const GatewayKnxOamRouterConfig& config) {
DaliValue::Object out;
out["enabled"] = config.enabled;
out["etsDatabaseEnabled"] = config.ets_database_enabled;
out["secureTunnelEnabled"] = config.secure_tunnel_enabled;
out["secureRoutingEnabled"] = config.secure_routing_enabled;
out["individualAddress"] = static_cast<int>(config.individual_address);
out["tunnelAddressBase"] = static_cast<int>(config.tunnel_address_base);
out["programmingButtonGpio"] = config.programming_button_gpio;
out["programmingButtonActiveLow"] = config.programming_button_active_low;
out["programmingLedGpio"] = config.programming_led_gpio;
out["programmingLedActiveHigh"] = config.programming_led_active_high;
out["cloudRemote"] = GatewayKnxCloudRemoteConfigToValue(config.cloud_remote);
return DaliValue(std::move(out));
}
std::optional<GatewayKnxConfig> GatewayKnxConfigFromValue(const DaliValue* value) {
if (value == nullptr || value->asObject() == nullptr) {
return std::nullopt;
}
const auto& object = *value->asObject();
GatewayKnxConfig config;
config.dali_router_enabled = ObjectBoolAny(object, {"daliRouterEnabled", "dali_router_enabled"})
.value_or(config.dali_router_enabled);
config.ip_router_enabled = ObjectBoolAny(object, {"ipRouterEnabled", "ip_router_enabled"})
.value_or(config.ip_router_enabled);
config.tunnel_enabled = ObjectBoolAny(object, {"tunnelEnabled", "tunnel_enabled"})
.value_or(config.tunnel_enabled);
config.multicast_enabled = ObjectBoolAny(object, {"multicastEnabled", "multicast_enabled"})
.value_or(config.multicast_enabled);
if (const auto mode = ObjectStringAny(object, {"mappingMode", "mapping_mode"})) {
config.mapping_mode = GatewayKnxMappingModeFromString(mode.value());
}
config.ets_database_enabled = ObjectBoolAny(object, {"etsDatabaseEnabled", "ets_database_enabled"})
.value_or(config.ets_database_enabled);
config.ets_associations = ParseEtsAssociations(object);
config.main_group = static_cast<uint8_t>(
std::clamp(ObjectIntAny(object, {"mainGroup", "main_group"}).value_or(config.main_group),
0, 31));
config.dali_bus_id = static_cast<uint8_t>(std::clamp(
ObjectIntAny(object, {"daliBusId", "dali_bus_id", "targetDaliBusId",
"target_dali_bus_id"})
.value_or(config.dali_bus_id),
0, 15));
config.udp_port = static_cast<uint16_t>(std::clamp(
ObjectIntAny(object, {"udpPort", "port", "udp_port"}).value_or(config.udp_port), 1,
65535));
config.multicast_address = ObjectStringAny(object, {"multicastAddress", "multicast_address"})
.value_or(config.multicast_address);
config.ip_interface_individual_address = static_cast<uint16_t>(std::clamp(
ObjectIntAny(object, {"ipInterfaceIndividualAddress",
"ip_interface_individual_address",
"ipInterfaceAddress",
"ip_interface_address"})
.value_or(config.ip_interface_individual_address),
0, 0xffff));
config.individual_address = static_cast<uint16_t>(std::clamp(
ObjectIntAny(object, {"individualAddress",
"individual_address",
"knxDaliGatewayIndividualAddress",
"knx_dali_gateway_individual_address",
"deviceIndividualAddress",
"device_individual_address"})
.value_or(config.individual_address),
0, 0xffff));
config.programming_button_gpio = std::clamp(
ObjectIntAny(object, {"programmingButtonGpio", "programming_button_gpio"})
.value_or(config.programming_button_gpio),
-1, 48);
config.programming_button_active_low =
ObjectBoolAny(object, {"programmingButtonActiveLow", "programming_button_active_low"})
.value_or(config.programming_button_active_low);
config.programming_led_gpio = std::clamp(
ObjectIntAny(object, {"programmingLedGpio", "programming_led_gpio"})
.value_or(config.programming_led_gpio),
-1, 48);
config.programming_led_active_high =
ObjectBoolAny(object, {"programmingLedActiveHigh", "programming_led_active_high"})
.value_or(config.programming_led_active_high);
if (const auto oam_router = GatewayKnxOamRouterConfigFromValue(
ObjectValueAny(object, {"oamRouter", "oam_router", "routerApplication",
"router_application"}))) {
config.oam_router = oam_router.value();
} else {
config.oam_router.enabled = ObjectBoolAny(object, {"oamRouterEnabled",
"oam_router_enabled"})
.value_or(config.oam_router.enabled);
}
const auto* tp_uart = getObjectValue(object, "tpUart");
if (tp_uart == nullptr) {
tp_uart = getObjectValue(object, "tp_uart");
}
if (tp_uart != nullptr && tp_uart->asObject() != nullptr) {
const auto& serial = *tp_uart->asObject();
config.tp_uart.uart_port = std::clamp(
ObjectIntAny(serial, {"uartPort", "uart_port"}).value_or(config.tp_uart.uart_port), -1,
2);
config.tp_uart.tx_pin = ObjectIntAny(serial, {"txPin", "tx_pin"}).value_or(config.tp_uart.tx_pin);
config.tp_uart.rx_pin = ObjectIntAny(serial, {"rxPin", "rx_pin"}).value_or(config.tp_uart.rx_pin);
config.tp_uart.baudrate = static_cast<uint32_t>(std::max(
1200, ObjectIntAny(serial, {"baudrate", "baud"}).value_or(config.tp_uart.baudrate)));
config.tp_uart.rx_buffer_size = static_cast<size_t>(std::max(
128, ObjectIntAny(serial, {"rxBufferSize", "rx_buffer_size"})
.value_or(static_cast<int>(config.tp_uart.rx_buffer_size))));
config.tp_uart.tx_buffer_size = static_cast<size_t>(std::max(
128, ObjectIntAny(serial, {"txBufferSize", "tx_buffer_size"})
.value_or(static_cast<int>(config.tp_uart.tx_buffer_size))));
config.tp_uart.startup_timeout_ms = static_cast<uint32_t>(std::max(
0, ObjectIntAny(serial, {"startupTimeoutMs", "startup_timeout_ms"})
.value_or(static_cast<int>(config.tp_uart.startup_timeout_ms))));
config.tp_uart.read_timeout_ms = static_cast<uint32_t>(std::max(
1, ObjectIntAny(serial, {"readTimeoutMs", "read_timeout_ms"})
.value_or(static_cast<int>(config.tp_uart.read_timeout_ms))));
config.tp_uart.nine_bit_mode = ObjectBoolAny(
serial, {"nineBitMode", "nine_bit_mode", "use9BitMode", "use_9_bit_mode"})
.value_or(config.tp_uart.nine_bit_mode);
}
return config;
}
DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& config) {
DaliValue::Object out;
out["daliRouterEnabled"] = config.dali_router_enabled;
out["ipRouterEnabled"] = config.ip_router_enabled;
out["tunnelEnabled"] = config.tunnel_enabled;
out["multicastEnabled"] = config.multicast_enabled;
out["etsDatabaseEnabled"] = config.ets_database_enabled;
out["mappingMode"] = GatewayKnxMappingModeToString(config.mapping_mode);
out["mainGroup"] = static_cast<int>(config.main_group);
out["daliBusId"] = static_cast<int>(config.dali_bus_id);
out["udpPort"] = static_cast<int>(config.udp_port);
out["multicastAddress"] = config.multicast_address;
out["ipInterfaceIndividualAddress"] =
static_cast<int>(config.ip_interface_individual_address);
out["individualAddress"] = static_cast<int>(config.individual_address);
out["programmingButtonGpio"] = config.programming_button_gpio;
out["programmingButtonActiveLow"] = config.programming_button_active_low;
out["programmingLedGpio"] = config.programming_led_gpio;
out["programmingLedActiveHigh"] = config.programming_led_active_high;
out["oamRouter"] = GatewayKnxOamRouterConfigToValue(config.oam_router);
DaliValue::Object serial;
serial["uartPort"] = config.tp_uart.uart_port;
serial["txPin"] = config.tp_uart.tx_pin;
serial["rxPin"] = config.tp_uart.rx_pin;
serial["baudrate"] = static_cast<int>(config.tp_uart.baudrate);
serial["rxBufferSize"] = static_cast<int>(config.tp_uart.rx_buffer_size);
serial["txBufferSize"] = static_cast<int>(config.tp_uart.tx_buffer_size);
serial["startupTimeoutMs"] = static_cast<int>(config.tp_uart.startup_timeout_ms);
serial["readTimeoutMs"] = static_cast<int>(config.tp_uart.read_timeout_ms);
serial["nineBitMode"] = config.tp_uart.nine_bit_mode;
out["tpUart"] = std::move(serial);
DaliValue::Array ets_associations;
ets_associations.reserve(config.ets_associations.size());
for (const auto& association : config.ets_associations) {
DaliValue::Object entry;
entry["groupAddress"] = static_cast<int>(association.group_address);
entry["groupObjectNumber"] = static_cast<int>(association.group_object_number);
ets_associations.emplace_back(std::move(entry));
}
out["etsAssociations"] = std::move(ets_associations);
return DaliValue(std::move(out));
}
bool GatewayKnxConfigUsesTpUart(const GatewayKnxConfig& config) {
return config.ip_router_enabled && config.tp_uart.uart_port >= 0;
}
const char* GatewayKnxMappingModeToString(GatewayKnxMappingMode mode) {
switch (mode) {
case GatewayKnxMappingMode::kEtsDatabase:
return "ets_database";
case GatewayKnxMappingMode::kGwReg1Direct:
return "gw_reg1_direct";
case GatewayKnxMappingMode::kManual:
return "manual";
case GatewayKnxMappingMode::kFormula:
default:
return "formula";
}
}
GatewayKnxMappingMode GatewayKnxMappingModeFromString(const std::string& value) {
const std::string normalized = NormalizeModeString(value);
if (normalized == "gwreg1direct" || normalized == "gwreg1" ||
normalized == "gwreg1channel" || normalized == "channelindex") {
return GatewayKnxMappingMode::kGwReg1Direct;
}
if (normalized == "manual" || normalized == "database" || normalized == "db") {
return GatewayKnxMappingMode::kManual;
}
if (normalized == "etsdatabase" || normalized == "ets" || normalized == "openknx") {
return GatewayKnxMappingMode::kEtsDatabase;
}
return GatewayKnxMappingMode::kFormula;
}
const char* GatewayKnxDataTypeToString(GatewayKnxDaliDataType data_type) {
switch (data_type) {
case GatewayKnxDaliDataType::kSwitch:
return "switch";
case GatewayKnxDaliDataType::kBrightness:
return "brightness";
case GatewayKnxDaliDataType::kBrightnessRelative:
return "brightness_relative";
case GatewayKnxDaliDataType::kColorTemperature:
return "color_temperature";
case GatewayKnxDaliDataType::kRgb:
return "rgb";
case GatewayKnxDaliDataType::kScene:
return "scene";
case GatewayKnxDaliDataType::kUnknown:
default:
return "unknown";
}
}
const char* GatewayKnxTargetKindToString(GatewayKnxDaliTargetKind kind) {
switch (kind) {
case GatewayKnxDaliTargetKind::kBroadcast:
return "broadcast";
case GatewayKnxDaliTargetKind::kShortAddress:
return "short_address";
case GatewayKnxDaliTargetKind::kGroup:
return "group";
case GatewayKnxDaliTargetKind::kNone:
default:
return "none";
}
}
std::optional<GatewayKnxDaliDataType> GatewayKnxDaliDataTypeForMiddleGroup(
uint8_t middle_group) {
switch (middle_group) {
case 1:
return GatewayKnxDaliDataType::kSwitch;
case 2:
return GatewayKnxDaliDataType::kBrightness;
case 3:
return GatewayKnxDaliDataType::kColorTemperature;
case 4:
return GatewayKnxDaliDataType::kRgb;
default:
return std::nullopt;
}
}
std::optional<GatewayKnxDaliTarget> GatewayKnxDaliTargetForSubgroup(uint8_t sub_group) {
if (sub_group == 0) {
return GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kBroadcast, 127};
}
if (sub_group >= 1 && sub_group <= 64) {
return GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kShortAddress,
static_cast<int>(sub_group - 1)};
}
if (sub_group >= 65 && sub_group <= 80) {
return GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kGroup,
static_cast<int>(sub_group - 65)};
}
return std::nullopt;
}
uint16_t GatewayKnxGroupAddress(uint8_t main_group, uint8_t middle_group,
uint8_t sub_group) {
return static_cast<uint16_t>(((main_group & 0x1f) << 11) |
((middle_group & 0x07) << 8) | sub_group);
}
std::string GatewayKnxGroupAddressString(uint16_t group_address) {
const int main = (group_address >> 11) & 0x1f;
const int middle = (group_address >> 8) & 0x07;
const int sub = group_address & 0xff;
return std::to_string(main) + "/" + std::to_string(middle) + "/" +
std::to_string(sub);
}
} // namespace gateway