diff --git a/apps/gateway/main/Kconfig.projbuild b/apps/gateway/main/Kconfig.projbuild index 66a11e0..c95c5f6 100644 --- a/apps/gateway/main/Kconfig.projbuild +++ b/apps/gateway/main/Kconfig.projbuild @@ -1,5 +1,7 @@ menu "Gateway App" +menu "DALI Settings" + config GATEWAY_CHANNEL_COUNT int "Gateway channel count" range 1 2 @@ -329,6 +331,8 @@ config GATEWAY_DALI_BAUDRATE help Runtime baudrate used when initializing the local DALI bus. +endmenu + menu "Gateway Startup Services" config GATEWAY_BLE_SUPPORTED @@ -620,6 +624,17 @@ config GATEWAY_START_BACNET_BRIDGE_ENABLED help Starts configured BACnet/IP object bindings at boot. Disabled by default so the UDP BACnet/IP port is opened only after provisioning or explicit runtime start. +menu "KNX Settings" + +config GATEWAY_KNX_INSTANCE_COUNT + int "Enabled KNX instance count" + range 1 16 + default 1 + help + Number of enabled KNX logical instances exposed by management APIs. + Instance 0 preserves the legacy serial/FDSK identity; each additional + instance derives its serial/FDSK from the base identity plus instance id. + config GATEWAY_KNX_BRIDGE_SUPPORTED bool "KNX to DALI bridge is supported" depends on GATEWAY_BRIDGE_SUPPORTED && (GATEWAY_WIFI_SUPPORTED || GATEWAY_ETHERNET_SUPPORTED) @@ -956,16 +971,16 @@ config GATEWAY_KNX_TP_UART_9BIT_MODE mode commonly described as 19200 baud 9-bit UART. Disable only for hardware wired for 8N1 host UART mode. - config GATEWAY_KNX_TP_FULL_IP_FORWARD - bool "Mirror all physical KNX TP telegrams to KNXnet/IP" - depends on GATEWAY_KNX_BRIDGE_SUPPORTED && GATEWAY_KNX_TP_UART_PORT >= 0 - default n - help - Mirrors physical KNX TP telegrams received from the TP-UART line back - out through KNXnet/IP tunnelling and multicast even when the gateway - runs the single-interface ETS device runtime. Enable this when ETS must - monitor or download other TP devices through the gateway's IP endpoint. - Leave it disabled to preserve the narrower default forwarding behavior. +config GATEWAY_KNX_TP_FULL_IP_FORWARD + bool "Mirror all physical KNX TP telegrams to KNXnet/IP" + depends on GATEWAY_KNX_BRIDGE_SUPPORTED && GATEWAY_KNX_TP_UART_PORT >= 0 + default n + help + Mirrors physical KNX TP telegrams received from the TP-UART line back + out through KNXnet/IP tunnelling and multicast even when the gateway + runs the single-interface ETS device runtime. Enable this when ETS must + monitor or download other TP devices through the gateway's IP endpoint. + Leave it disabled to preserve the narrower default forwarding behavior. config GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE int "KNX/IP bridge task stack bytes" @@ -979,6 +994,8 @@ config GATEWAY_BRIDGE_KNX_TASK_PRIORITY range 1 10 default 5 +endmenu + config GATEWAY_CLOUD_BRIDGE_SUPPORTED bool "MQTT cloud bridge is supported" depends on GATEWAY_BRIDGE_SUPPORTED && (GATEWAY_WIFI_SUPPORTED || GATEWAY_ETHERNET_SUPPORTED) diff --git a/apps/gateway/sdkconfig b/apps/gateway/sdkconfig index bd4e6b3..92c8d9c 100644 --- a/apps/gateway/sdkconfig +++ b/apps/gateway/sdkconfig @@ -596,6 +596,10 @@ CONFIG_PARTITION_TABLE_MD5=y # # Gateway App # + +# +# DALI Settings +# CONFIG_GATEWAY_CHANNEL_COUNT=2 # @@ -640,6 +644,7 @@ CONFIG_GATEWAY_CACHE_OUTSIDE_BUS_FIRST=y # end of Gateway Cache # CONFIG_GATEWAY_ENABLE_DALI_BUS is not set +# end of DALI Settings # # Gateway Startup Services @@ -683,6 +688,10 @@ CONFIG_GATEWAY_MODBUS_TCP_PORT=1502 CONFIG_GATEWAY_MODBUS_UNIT_ID=1 CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED=y # CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED is not set + +# +# KNX Settings +# CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED=y CONFIG_GATEWAY_START_KNX_BRIDGE_ENABLED=y CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED=y @@ -716,6 +725,8 @@ CONFIG_GATEWAY_KNX_TP_UART_9BIT_MODE=y CONFIG_GATEWAY_KNX_TP_FULL_IP_FORWARD=y CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE=12288 CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY=5 +# end of KNX Settings + CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y # CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE=6144 diff --git a/components/gateway_bridge/include/security_storage.h b/components/gateway_bridge/include/security_storage.h index c1bff20..434f631 100644 --- a/components/gateway_bridge/include/security_storage.h +++ b/components/gateway_bridge/include/security_storage.h @@ -40,10 +40,14 @@ struct IpSecureCredentialStatus { bool LoadFactoryFdsk(uint8_t* data, size_t len); FactoryFdskInfo LoadFactoryFdskInfo(); +bool LoadFactoryFdskForInstance(uint32_t instance_id, uint8_t* data, size_t len); +FactoryFdskInfo LoadFactoryFdskInfoForInstance(uint32_t instance_id); bool GenerateFactoryFdsk(FactoryFdskInfo* info = nullptr); bool WriteFactoryFdskHex(const std::string& hex_key, FactoryFdskInfo* info = nullptr); bool ResetFactoryFdskCache(FactoryFdskInfo* info = nullptr); +bool ResetFactorySecurityForInstance(uint32_t instance_id, FactoryFdskInfo* info = nullptr); FactoryCertificatePayload BuildFactoryCertificatePayload(); +FactoryCertificatePayload BuildFactoryCertificatePayloadForInstance(uint32_t instance_id); bool LoadOamFactoryFdsk(uint8_t* data, size_t len); FactoryFdskInfo LoadOamFactoryFdskInfo(); diff --git a/components/gateway_bridge/src/gateway_bridge.cpp b/components/gateway_bridge/src/gateway_bridge.cpp index 8d9f788..7444409 100644 --- a/components/gateway_bridge/src/gateway_bridge.cpp +++ b/components/gateway_bridge/src/gateway_bridge.cpp @@ -24,6 +24,8 @@ #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG #endif #include "esp_log.h" +#include "nvs.h" +#include "nvs_flash.h" #include "sdkconfig.h" #include "freertos/semphr.h" #include "lwip/inet.h" @@ -64,6 +66,10 @@ constexpr uint32_t kBacnetReliabilityCommunicationFailure = 12; constexpr const char* kModbusManagementPrefix = "@DALIGW"; constexpr uint8_t kDaliGroupRawMin = 0x80; constexpr uint8_t kDaliGroupRawMax = 0x9F; +#ifndef CONFIG_GATEWAY_KNX_INSTANCE_COUNT +#define CONFIG_GATEWAY_KNX_INSTANCE_COUNT 1 +#endif +constexpr uint32_t kKnxEnabledInstanceCount = CONFIG_GATEWAY_KNX_INSTANCE_COUNT; void ConfigureDaliCppLogging() { static bool configured = false; @@ -199,7 +205,7 @@ cJSON* FactoryCertificateToCjson(const openknx::FactoryCertificatePayload& certi return root; } -cJSON* IpSecureCredentialStatusToCjson( +[[maybe_unused]] cJSON* IpSecureCredentialStatusToCjson( const openknx::IpSecureCredentialStatus& status) { cJSON* root = cJSON_CreateObject(); if (root == nullptr) { @@ -393,6 +399,50 @@ std::optional QueryInt(std::string_view query, std::string_view primary, return ParseInt(QueryValue(query, fallback)); } +std::optional ValidKnxInstanceId(int value) { + if (value < 0 || static_cast(value) >= kKnxEnabledInstanceCount) { + return std::nullopt; + } + return static_cast(value); +} + +std::string KnxInstanceIdRangeMessage() { + return "KNX instanceId must be in range 0.." + + std::to_string(kKnxEnabledInstanceCount - 1); +} + +std::optional QueryKnxInstanceId(std::string_view query) { + return ValidKnxInstanceId(QueryInt(query, "instanceId", "instance").value_or(0)); +} + +bool EnsureBridgeNvsReady() { + const esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { + if (nvs_flash_erase() != ESP_OK) { + return false; + } + return nvs_flash_init() == ESP_OK; + } + return err == ESP_OK || err == ESP_ERR_INVALID_STATE; +} + +bool EraseOpenKnxEeprom(const std::string& nvs_namespace) { + if (nvs_namespace.empty() || !EnsureBridgeNvsReady()) { + return false; + } + nvs_handle_t handle = 0; + esp_err_t err = nvs_open(nvs_namespace.c_str(), NVS_READWRITE, &handle); + if (err != ESP_OK) { + return err == ESP_ERR_NVS_NOT_FOUND; + } + err = nvs_erase_key(handle, "eeprom"); + if (err == ESP_OK) { + err = nvs_commit(handle); + } + nvs_close(handle); + return err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND; +} + std::optional JsonIntAny(const cJSON* parent, const char* primary, const char* fallback) { auto value = JsonInt(parent, primary); if (value.has_value() || fallback == nullptr) { @@ -401,6 +451,10 @@ std::optional JsonIntAny(const cJSON* parent, const char* primary, const ch return JsonInt(parent, fallback); } +std::optional JsonKnxInstanceId(const cJSON* parent) { + return ValidKnxInstanceId(JsonIntAny(parent, "instanceId", "instance").value_or(0)); +} + bool ValidDaliAddress(int address) { return address >= 0 && address <= 127; } @@ -2229,7 +2283,7 @@ struct GatewayBridgeService::ChannelRuntime { return JsonOk(BridgeResultToCjson(result)); } - cJSON* knxStatusCjson() const { + cJSON* knxStatusCjson(uint32_t instance_id = 0) const { cJSON* knx_json = cJSON_CreateObject(); if (knx_json == nullptr) { return nullptr; @@ -2254,6 +2308,9 @@ struct GatewayBridgeService::ChannelRuntime { } const auto effective_knx = knx_config.has_value() ? knx_config : service_config.default_knx_config; + cJSON_AddNumberToObject(knx_json, "instanceId", static_cast(instance_id)); + cJSON_AddNumberToObject(knx_json, "enabledInstanceCount", + static_cast(kKnxEnabledInstanceCount)); 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); @@ -2307,13 +2364,14 @@ struct GatewayBridgeService::ChannelRuntime { cJSON_AddStringToObject(security_json, "storage", "none"); #endif #if defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED) - const auto fdsk_info = openknx::LoadFactoryFdskInfo(); + const auto fdsk_info = 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::BuildFactoryCertificatePayload(), false); + FactoryCertificateToCjson( + openknx::BuildFactoryCertificatePayloadForInstance(instance_id), false); if (certificate_json != nullptr) { cJSON_AddItemToObject(security_json, "factoryCertificate", certificate_json); } @@ -2322,27 +2380,6 @@ struct GatewayBridgeService::ChannelRuntime { cJSON_AddItemToObject(security_json, "failures", failures_json); } #endif - cJSON* oam_security_json = cJSON_CreateObject(); - if (oam_security_json != nullptr) { - cJSON* oam_fdsk_json = FactoryFdskInfoToCjson( - openknx::LoadOamFactoryFdskInfo(), false); - if (oam_fdsk_json != nullptr) { - cJSON_AddItemToObject(oam_security_json, "factorySetupKey", oam_fdsk_json); - } - cJSON* oam_certificate_json = FactoryCertificateToCjson( - openknx::BuildOamFactoryCertificatePayload(), false); - if (oam_certificate_json != nullptr) { - cJSON_AddItemToObject(oam_security_json, "factoryCertificate", - oam_certificate_json); - } - cJSON* oam_credentials_json = IpSecureCredentialStatusToCjson( - openknx::LoadOamIpSecureCredentialStatus()); - if (oam_credentials_json != nullptr) { - cJSON_AddItemToObject(oam_security_json, "ipSecureCredentials", - oam_credentials_json); - } - cJSON_AddItemToObject(security_json, "oamRouter", oam_security_json); - } cJSON_AddItemToObject(knx_json, "security", security_json); } if (effective_knx.has_value()) { @@ -2491,13 +2528,13 @@ struct GatewayBridgeService::ChannelRuntime { return root; } - GatewayBridgeHttpResponse knxStatusJson() const { + GatewayBridgeHttpResponse knxStatusJson(uint32_t instance_id = 0) const { cJSON* root = cJSON_CreateObject(); if (root == nullptr) { return ErrorResponse(ESP_ERR_NO_MEM, "failed to allocate KNX status JSON"); } cJSON_AddNumberToObject(root, "gatewayId", channel.gateway_id); - cJSON* knx_json = knxStatusCjson(); + cJSON* knx_json = knxStatusCjson(instance_id); if (knx_json == nullptr) { cJSON_Delete(root); return ErrorResponse(ESP_ERR_NO_MEM, "failed to allocate KNX status JSON"); @@ -3035,14 +3072,17 @@ struct GatewayBridgeService::ChannelRuntime { switch (point.generated_kind) { case GatewayModbusGeneratedKind::kShortOn: case GatewayModbusGeneratedKind::kShortRecallMax: + domain.markHostCommandFrame(channel.gateway_id, raw_command_address, DALI_CMD_RECALL_MAX); sent = domain.on(channel.gateway_id, point.short_address); mirrored_command = DALI_CMD_RECALL_MAX; break; case GatewayModbusGeneratedKind::kShortOff: + domain.markHostCommandFrame(channel.gateway_id, raw_command_address, DALI_CMD_OFF); sent = domain.off(channel.gateway_id, point.short_address); mirrored_command = DALI_CMD_OFF; break; case GatewayModbusGeneratedKind::kShortRecallMin: + domain.markHostCommandFrame(channel.gateway_id, raw_command_address, DALI_CMD_RECALL_MIN); sent = domain.sendRaw(channel.gateway_id, raw_command_address, DALI_CMD_RECALL_MIN); mirrored_command = DALI_CMD_RECALL_MIN; break; @@ -3065,6 +3105,8 @@ struct GatewayBridgeService::ChannelRuntime { if (value > 254) { return false; } + domain.markHostCommandFrame(channel.gateway_id, RawArcAddressFromDec(point.short_address), + static_cast(value)); if (domain.setBright(channel.gateway_id, point.short_address, value)) { cache.mirrorDaliCommand(channel.gateway_id, RawArcAddressFromDec(point.short_address), static_cast(value)); @@ -3072,8 +3114,10 @@ struct GatewayBridgeService::ChannelRuntime { } return false; case GatewayModbusGeneratedKind::kShortColorTemperature: + domain.markHostActivity(channel.gateway_id); return domain.setColTemp(channel.gateway_id, point.short_address, value); case GatewayModbusGeneratedKind::kShortGroupMask: + domain.markHostActivity(channel.gateway_id); if (domain.applyGroupMask(channel.gateway_id, point.short_address, value)) { cache.setDaliGroupMask(channel.gateway_id, static_cast(point.short_address), value); @@ -3120,6 +3164,7 @@ struct GatewayBridgeService::ChannelRuntime { domain_settings.max_level = current.max_level; domain_settings.fade_time = current.fade_time; domain_settings.fade_rate = current.fade_rate; + domain.markHostActivity(channel.gateway_id); if (domain.applyAddressSettings(channel.gateway_id, point.short_address, domain_settings)) { cache.setDaliSettings(channel.gateway_id, static_cast(point.short_address), current); @@ -4415,7 +4460,12 @@ GatewayBridgeHttpResponse GatewayBridgeService::handleGet( return JsonOk(runtime->statusCjson()); } if (action == "knx_status") { - return runtime->knxStatusJson(); + const auto instance_id = QueryKnxInstanceId(query); + if (!instance_id.has_value()) { + const std::string message = KnxInstanceIdRangeMessage(); + return ErrorResponse(ESP_ERR_INVALID_ARG, message.c_str()); + } + return runtime->knxStatusJson(instance_id.value()); } if (action == "config") { return runtime->configJson(); @@ -4762,6 +4812,61 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost( } return handleGet("status", gateway_id.value()); } + if (action == "knx_factory_reset") { + cJSON* body_root = body.empty() ? nullptr : cJSON_ParseWithLength(body.data(), body.size()); + if (body_root == nullptr) { + return ErrorResponse(ESP_ERR_INVALID_ARG, "KNX factory reset confirmation JSON is required"); + } + const char* confirm = JsonString(body_root, "confirm"); + const bool confirmed = confirm != nullptr && + std::string_view(confirm) == "reset-knx-factory-defaults"; + const auto instance_id = JsonKnxInstanceId(body_root); + cJSON_Delete(body_root); + if (!confirmed) { + return ErrorResponse(ESP_ERR_INVALID_ARG, "KNX factory reset confirmation is required"); + } + if (!instance_id.has_value()) { + const std::string message = KnxInstanceIdRangeMessage(); + return ErrorResponse(ESP_ERR_INVALID_ARG, message.c_str()); + } + + const bool reset_runtime = instance_id.value() == 0; + const bool restart_router = reset_runtime && + (runtime->knx_started || + (runtime->knx_router != nullptr && runtime->knx_router->started())); + if (restart_router) { + const esp_err_t stop_err = stopKnxEndpoint(runtime); + if (stop_err != ESP_OK) { + return ErrorResponse(stop_err, "failed to stop KNX/IP bridge before factory reset"); + } + } + + std::string nvs_namespace = runtime->openKnxNamespace(); + if (instance_id.value() != 0) { + nvs_namespace += "_" + std::to_string(instance_id.value()); + } + if (!EraseOpenKnxEeprom(nvs_namespace)) { + return ErrorResponse(ESP_FAIL, "failed to erase KNX runtime data"); + } + + openknx::FactoryFdskInfo info; + if (!openknx::ResetFactorySecurityForInstance(instance_id.value(), &info)) { + return ErrorResponse(ESP_FAIL, "failed to restore KNX factory security key"); + } + + if (restart_router) { + std::set used_serial_uarts; + collectUsedRuntimeResources(runtime->channel.gateway_id, nullptr, nullptr, &used_serial_uarts); + const esp_err_t start_err = startKnxEndpoint(runtime, &used_serial_uarts); + if (start_err != ESP_OK) { + return ErrorResponse(start_err, runtime->knx_last_error.empty() + ? "failed to restart KNX/IP bridge after factory reset" + : runtime->knx_last_error.c_str()); + } + } + + return runtime->knxStatusJson(instance_id.value()); + } if (action == "knx_security_read_factory_key") { #if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS) && \ defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED) diff --git a/components/gateway_bridge/src/security_storage.cpp b/components/gateway_bridge/src/security_storage.cpp index 44b1370..b196c8f 100644 --- a/components/gateway_bridge/src/security_storage.cpp +++ b/components/gateway_bridge/src/security_storage.cpp @@ -45,7 +45,7 @@ constexpr char kBase32Alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; constexpr char kHexAlphabet[] = "0123456789ABCDEF"; struct FactoryFdskContext { - const char* nvsNamespace; + std::string nvsNamespace; const char* productIdentity; const char* derivationLabel; uint16_t manufacturerId; @@ -55,7 +55,7 @@ struct FactoryFdskContext { bool clearOpenKnxCache; }; -constexpr FactoryFdskContext kReg1Context{ +const FactoryFdskContext kReg1Context{ kNamespace, kProductIdentity, kFdskDerivationLabel, @@ -65,7 +65,7 @@ constexpr FactoryFdskContext kReg1Context{ gateway::knx_internal::kReg1DaliSerialMacIncrement, true}; -constexpr FactoryFdskContext kOamContext{ +const FactoryFdskContext kOamContext{ kOamNamespace, kOamProductIdentity, kOamFdskDerivationLabel, @@ -77,6 +77,16 @@ constexpr FactoryFdskContext kOamContext{ extern "C" void knx_platform_clear_cached_fdsk() __attribute__((weak)); +FactoryFdskContext reg1ContextForInstance(uint32_t instance_id) { + FactoryFdskContext context = kReg1Context; + context.serialMacIncrement = gateway::knx_internal::kReg1DaliSerialMacIncrement + instance_id; + context.clearOpenKnxCache = instance_id == 0; + if (instance_id != 0) { + context.nvsNamespace = std::string(kNamespace) + "_" + std::to_string(instance_id); + } + return context; +} + std::string hexValue(uint32_t value, int width) { std::array buffer{}; std::snprintf(buffer.data(), buffer.size(), "%0*" PRIX32, width, value); @@ -213,7 +223,7 @@ void syncFactoryFdskToNvs(const FactoryFdskContext& context, const uint8_t* data size_t stored_size = stored.size(); nvs_handle_t handle = 0; - esp_err_t err = nvs_open(context.nvsNamespace, NVS_READWRITE, &handle); + esp_err_t err = nvs_open(context.nvsNamespace.c_str(), NVS_READWRITE, &handle); if (err != ESP_OK) { ESP_LOGW(kTag, "failed to open KNX security NVS namespace: %s", esp_err_to_name(err)); return; @@ -483,6 +493,16 @@ FactoryFdskInfo LoadFactoryFdskInfo() { return LoadFactoryFdskInfoForContext(kReg1Context); } +bool LoadFactoryFdskForInstance(uint32_t instance_id, uint8_t* data, size_t len) { + const auto context = reg1ContextForInstance(instance_id); + return LoadFactoryFdskForContext(context, data, len); +} + +FactoryFdskInfo LoadFactoryFdskInfoForInstance(uint32_t instance_id) { + const auto context = reg1ContextForInstance(instance_id); + return LoadFactoryFdskInfoForContext(context); +} + bool GenerateFactoryFdsk(FactoryFdskInfo* info) { return GenerateFactoryFdskForContext(kReg1Context, info); } @@ -495,10 +515,28 @@ bool ResetFactoryFdskCache(FactoryFdskInfo* info) { return ResetFactoryFdskCacheForContext(kReg1Context, info); } +bool ResetFactorySecurityForInstance(uint32_t instance_id, FactoryFdskInfo* info) { + const auto context = reg1ContextForInstance(instance_id); + if (ensureNvsReady()) { + nvs_handle_t handle = 0; + if (nvs_open(context.nvsNamespace.c_str(), NVS_READWRITE, &handle) == ESP_OK) { + nvs_erase_key(handle, kFactoryFdskKey); + nvs_commit(handle); + nvs_close(handle); + } + } + return ResetFactoryFdskCacheForContext(context, info); +} + FactoryCertificatePayload BuildFactoryCertificatePayload() { return BuildFactoryCertificatePayloadForContext(kReg1Context); } +FactoryCertificatePayload BuildFactoryCertificatePayloadForInstance(uint32_t instance_id) { + const auto context = reg1ContextForInstance(instance_id); + return BuildFactoryCertificatePayloadForContext(context); +} + bool LoadOamFactoryFdsk(uint8_t* data, size_t len) { return LoadFactoryFdskForContext(kOamContext, data, len); }