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>
This commit is contained in:
Tony
2026-05-25 08:18:01 +08:00
parent 0467179f70
commit 2b779d5532
22 changed files with 2665 additions and 77 deletions
@@ -0,0 +1,278 @@
#include "oam_router_runtime.h"
#include "gateway_knx_internal.h"
#include "esp_log.h"
#include "esp_mac.h"
#include "knx/cemi_server.h"
#include "knx/property.h"
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <utility>
namespace gateway::openknx {
namespace {
constexpr uint16_t kInvalidIndividualAddress = 0xffff;
constexpr uint16_t kKnxUnconfiguredBroadcastAddress = 0xffff;
bool IsUsableIndividualAddress(uint16_t address) {
return address != 0 && address != kInvalidIndividualAddress;
}
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) {
return 0;
}
uint32_t suffix = (static_cast<uint32_t>(mac[2]) << 24) |
(static_cast<uint32_t>(mac[3]) << 16) |
(static_cast<uint32_t>(mac[4]) << 8) |
static_cast<uint32_t>(mac[5]);
return suffix + knx_internal::kOamRouterSerialMacIncrement;
}
#if defined(ENABLE_BAU091A_PERSONA)
void ApplyOamRouterIdentity(Bau091A& device) {
device.deviceObject().manufacturerId(knx_internal::kOamRouterManufacturerId);
device.deviceObject().bauNumber(OamBauNumberFromBaseMac());
device.deviceObject().hardwareType(knx_internal::kOamRouterHardwareType);
device.deviceObject().orderNumber(knx_internal::kOamRouterOrderNumber);
if (auto* property = device.parameters().property(PID_PROG_VERSION); property != nullptr) {
property->write(knx_internal::kOamRouterProgramVersion);
}
}
#endif
} // namespace
OamRouterRuntime::OamRouterRuntime(std::string nvs_namespace,
uint16_t fallback_individual_address,
uint16_t tunnel_client_address)
: nvs_namespace_(std::move(nvs_namespace))
#if defined(ENABLE_BAU091A_PERSONA)
,
platform_(nullptr, nvs_namespace_.c_str()),
device_(platform_)
#endif
{
#if defined(ENABLE_BAU091A_PERSONA)
platform_.outboundCemiFrameCallback(&OamRouterRuntime::HandleOutboundCemiFrame, this);
ApplyOamRouterIdentity(device_);
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();
ApplyOamRouterIdentity(device_);
if (!IsUsableIndividualAddress(device_.deviceObject().individualAddress()) &&
IsUsableIndividualAddress(fallback_individual_address)) {
device_.deviceObject().individualAddress(fallback_individual_address);
}
#ifdef USE_CEMI_SERVER
if (auto* server = device_.getCemiServer()) {
server->clientAddress(IsUsableIndividualAddress(tunnel_client_address)
? tunnel_client_address
: DefaultTunnelClientAddress(
device_.deviceObject().individualAddress()));
server->deviceAddressPropertiesTargetClient(false);
server->tunnelFrameCallback(&OamRouterRuntime::EmitTunnelFrame, this);
}
#endif
uint8_t program_version[5]{};
if (auto* property = device_.parameters().property(PID_PROG_VERSION); property != nullptr) {
property->read(program_version);
}
ESP_LOGI("gateway_knx",
"OAM router runtime namespace=%s configured=%d manufacturer=0x%04x mask=0x%04x device=0x%04x tunnelClient=0x%04x progVersion=%02X %02X %02X %02X %02X",
nvs_namespace_.c_str(), device_.configured(), device_.deviceObject().manufacturerId(),
device_.deviceObject().maskVersion(), device_.deviceObject().individualAddress(),
tunnelClientAddress(), program_version[0], program_version[1], program_version[2],
program_version[3], program_version[4]);
#else
(void)fallback_individual_address;
(void)tunnel_client_address;
#endif
}
OamRouterRuntime::~OamRouterRuntime() {
#if defined(ENABLE_BAU091A_PERSONA)
platform_.outboundCemiFrameCallback(nullptr, nullptr);
#ifdef USE_CEMI_SERVER
if (auto* server = device_.getCemiServer()) {
server->tunnelFrameCallback(nullptr, nullptr);
}
#endif
#endif
}
bool OamRouterRuntime::available() const {
#if defined(ENABLE_BAU091A_PERSONA)
return true;
#else
return false;
#endif
}
uint16_t OamRouterRuntime::individualAddress() const {
#if defined(ENABLE_BAU091A_PERSONA)
return const_cast<Bau091A&>(device_).deviceObject().individualAddress();
#else
return 0xffff;
#endif
}
uint16_t OamRouterRuntime::tunnelClientAddress() const {
#if defined(ENABLE_BAU091A_PERSONA) && defined(USE_CEMI_SERVER)
if (auto* server = const_cast<Bau091A&>(device_).getCemiServer()) {
return server->clientAddress();
}
#endif
return DefaultTunnelClientAddress(individualAddress());
}
bool OamRouterRuntime::configured() const {
#if defined(ENABLE_BAU091A_PERSONA)
return const_cast<Bau091A&>(device_).configured();
#else
return false;
#endif
}
bool OamRouterRuntime::programmingMode() const {
#if defined(ENABLE_BAU091A_PERSONA)
return const_cast<Bau091A&>(device_).deviceObject().progMode();
#else
return false;
#endif
}
void OamRouterRuntime::setProgrammingMode(bool enabled) {
#if defined(ENABLE_BAU091A_PERSONA)
device_.deviceObject().progMode(enabled);
#else
(void)enabled;
#endif
}
void OamRouterRuntime::toggleProgrammingMode() { setProgrammingMode(!programmingMode()); }
EtsMemorySnapshot OamRouterRuntime::snapshot() const {
EtsMemorySnapshot out;
#if defined(ENABLE_BAU091A_PERSONA)
auto& device = const_cast<Bau091A&>(device_);
out.configured = device.configured();
out.individual_address = device.deviceObject().individualAddress();
#endif
return out;
}
DeviceObject* OamRouterRuntime::deviceObject() {
#if defined(ENABLE_BAU091A_PERSONA)
return &device_.deviceObject();
#else
return nullptr;
#endif
}
Platform* OamRouterRuntime::platform() {
#if defined(ENABLE_BAU091A_PERSONA)
return &platform_;
#else
return nullptr;
#endif
}
void OamRouterRuntime::setNetworkInterface(esp_netif_t* netif) {
#if defined(ENABLE_BAU091A_PERSONA)
platform_.networkInterface(netif);
#else
(void)netif;
#endif
}
bool OamRouterRuntime::handleTunnelFrame(const uint8_t* data, size_t len,
CemiFrameSender sender) {
#if defined(ENABLE_BAU091A_PERSONA) && defined(USE_CEMI_SERVER)
auto* server = device_.getCemiServer();
if (server == nullptr || data == nullptr || len < 2) {
return false;
}
std::vector<uint8_t> frame_data(data, data + len);
CemiFrame frame(frame_data.data(), static_cast<uint16_t>(frame_data.size()));
if (!shouldConsumeTunnelFrame(frame)) {
return false;
}
sender_ = std::move(sender);
server->frameReceived(frame);
loop();
sender_ = nullptr;
return true;
#else
(void)data;
(void)len;
(void)sender;
return false;
#endif
}
void OamRouterRuntime::loop() {
#if defined(ENABLE_BAU091A_PERSONA)
device_.loop();
#endif
}
bool OamRouterRuntime::HandleOutboundCemiFrame(CemiFrame& frame, void* context) {
auto* self = static_cast<OamRouterRuntime*>(context);
if (self == nullptr || !self->sender_) {
return false;
}
self->sender_(frame.data(), frame.dataLength());
return true;
}
void OamRouterRuntime::EmitTunnelFrame(CemiFrame& frame, void* context) {
auto* self = static_cast<OamRouterRuntime*>(context);
if (self == nullptr || !self->sender_) {
return;
}
self->sender_(frame.data(), frame.dataLength());
}
uint16_t OamRouterRuntime::DefaultTunnelClientAddress(uint16_t individual_address) {
if (!IsUsableIndividualAddress(individual_address)) {
return 0x1102;
}
const uint16_t line_base = individual_address & 0xff00;
uint16_t device = static_cast<uint16_t>((individual_address & 0x00ff) + 1);
if (device == 0 || device > 0xff) {
device = 2;
}
return static_cast<uint16_t>(line_base | device);
}
bool OamRouterRuntime::shouldConsumeTunnelFrame(CemiFrame& frame) const {
switch (frame.messageCode()) {
case M_PropRead_req:
case M_PropWrite_req:
case M_Reset_req:
case M_FuncPropCommand_req:
case M_FuncPropStateRead_req:
return true;
case L_data_req: {
const uint16_t dest = frame.destinationAddress();
const bool commissioning = !configured() || programmingMode();
if (frame.addressType() == IndividualAddress) {
return dest == individualAddress() || dest == tunnelClientAddress() ||
(commissioning && dest == kKnxUnconfiguredBroadcastAddress);
}
return false;
}
default:
return false;
}
}
} // namespace gateway::openknx