diff --git a/samples/siwx91x_ota/CMakeLists.txt b/samples/siwx91x_ota/CMakeLists.txt new file mode 100644 index 0000000..a118582 --- /dev/null +++ b/samples/siwx91x_ota/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (c) 2025 Silicon Laboratories Inc. +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(siwx91x_ota) + +target_sources(app PRIVATE src/main.c src/ca-cert_der.c) + +# Ensure the directory for the generated file exists +set(gen_dir ${ZEPHYR_BINARY_DIR}/include/generated/) +file(MAKE_DIRECTORY ${gen_dir}) diff --git a/samples/siwx91x_ota/Kconfig b/samples/siwx91x_ota/Kconfig new file mode 100644 index 0000000..d00d9ec --- /dev/null +++ b/samples/siwx91x_ota/Kconfig @@ -0,0 +1,39 @@ +# Config options for OTA application +# +# Copyright (c) 2025 Silicon Laboratories Inc. +# SPDX-License-Identifier: Apache-2.0 + +mainmenu "SiWx91x OTA Configuration" + +menu "SiWx91x OTA application options" + +config OTA_WIFI_SSID + string "WiFi SSID" + default "your_ssid" + help + WiFi SSID for the network to connect to. + +config OTA_WIFI_PSK + string "WiFi PSK" + default "your_psk" + help + WiFi PSK (password) for the network to connect to. + +config OTA_IP_PROTOCOL_SELECTION + int "IP protocol selection" + default 0 + range 0 1 + help + Select IP protocol for OTA connection. + 0: IPv4 (default) + 1: IPv6 + +config OTA_UPDATE_URL + string "OTA update URL" + default "http://example.com:8080/firmware.rps" + help + The full URL for the OTA firmware update. + +endmenu + +source "Kconfig.zephyr" diff --git a/samples/siwx91x_ota/README.rst b/samples/siwx91x_ota/README.rst new file mode 100644 index 0000000..e4e10ec --- /dev/null +++ b/samples/siwx91x_ota/README.rst @@ -0,0 +1,64 @@ +# Copyright (c) 2025 Silicon Laboratories Inc. +# SPDX-License-Identifier: Apache-2.0 + +.. zephyr:code-sample:: siwx91x_otas + :name: HTTP OTA Firmware Update on SiWx917 + :relevant-api: wifi + + Demonstrates HTTP/HTTPS OTA firmware update using SiWx917 on Zephyr. + +Overview +******** + +Application demonstrates how to perform HTTP/HTTPS OTA firmware updates on the +SiWx917 platform using Zephyr RTOS. It connects to a Wi-Fi network, establishes +a secure HTTPS connection using a CA certificate, and downloads firmware +updates from a remote server. The application showcases secure connectivity and +OTA update mechanisms for IoT devices. + +Requirements +************ + +* SiWx917 development board with Wi-Fi support +* HTTP server + +Configurations +************** + +The following configurations can be modified in ``prj.conf``: + +* Wi-Fi Settings + * ``CONFIG_OTA_WIFI_SSID`` - Network name + * ``CONFIG_OTA_WIFI_PSK`` - Network password + * ``CONFIG_OTA_IP_PROTOCOL_SELECTION`` - Select IPv4 or IPv6 + * ``CONFIG_OTA_UPDATE_URL`` - OTA update URL + +.. _signed image generation: + https://docs.zephyrproject.org/latest/kconfig.html#CONFIG_SIWX91X_SIGN_KEY + +Building and Running +******************** + +1. Configure required settings +2. Build and Flash + + .. code-block:: console + + west build -b siwx917_rb4338a siwx917_ota -p + west flash + +3. Run HTTP/HTTPS server + +Test the Application +******************** + +1. After flashing the SiWx91x, the device will scan for the specified AP and + attempt to connect if found. +2. Once connected, the SiWx91x will initiate an HTTP/S connection to the specified + server and download the firmware binary. +3. The OTA update process will be logged to the serial console. + +Note +**** + +This application is not for production. diff --git a/samples/siwx91x_ota/prj.conf b/samples/siwx91x_ota/prj.conf new file mode 100644 index 0000000..ab4fd15 --- /dev/null +++ b/samples/siwx91x_ota/prj.conf @@ -0,0 +1,77 @@ +# Copyright (c) 2025 Silicon Laboratories Inc. +# SPDX-License-Identifier: Apache-2.0 + +# Network Stack Configuration +CONFIG_NETWORKING=y +CONFIG_NET_MGMT=y +CONFIG_NET_TCP=y +CONFIG_NET_CONNECTION_MANAGER=y + +# IPv4/IPv6 Support +CONFIG_NET_IPV4=y +CONFIG_NET_IPV6=y +CONFIG_NET_ARP=y +CONFIG_NET_DHCPV4=y +CONFIG_NET_DHCPV6=y + +# WiFi Configuration +CONFIG_WIFI=y + +# Memory and Threading +CONFIG_MAIN_STACK_SIZE=4098 +CONFIG_INIT_STACKS=y +CONFIG_HEAP_MEM_POOL_SIZE=8192 + +# CMSIS Configuration +CONFIG_CMSIS_V2_MUTEX_MAX_COUNT=10 +CONFIG_CMSIS_V2_EVT_FLAGS_MAX_COUNT=10 + +# Network Parameters +CONFIG_NET_TCP_MAX_RECV_WINDOW_SIZE=10240 + +# Socket Support +CONFIG_REQUIRES_FULL_LIBC=y +CONFIG_NET_SOCKETS=y + +# HTTP Client Configuration +CONFIG_HTTP_CLIENT=y +CONFIG_HTTP_PARSER_URL=y +CONFIG_NET_MGMT_EVENT=y + +# TLS Security Configuration +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_BUILTIN=y +CONFIG_MBEDTLS_ENABLE_HEAP=y +CONFIG_MBEDTLS_HEAP_SIZE=60000 +CONFIG_TLS_CREDENTIALS=y +CONFIG_NET_SOCKETS_SOCKOPT_TLS=y +CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=8000 + +# TLS Protocol and Cipher Configuration +CONFIG_MBEDTLS_TLS_VERSION_1_2=y +CONFIG_MBEDTLS_SHA256=y +CONFIG_MBEDTLS_CIPHER_AES_ENABLED=y +CONFIG_MBEDTLS_CIPHER_ALL_ENABLED=y +CONFIG_MBEDTLS_CIPHER_CHACHA20_ENABLED=y +CONFIG_MBEDTLS_POLY1305=y + +CONFIG_ASSERT=y + +# DNS configuration +CONFIG_DNS_RESOLVER=y + +# for SLAAC configuration +CONFIG_NET_IPV6_ND=y +CONFIG_NET_IPV6_RA_RDNSS=y +CONFIG_NET_IPV6_NBR_CACHE=y +CONFIG_NET_IPV6_MLD=y +CONFIG_NET_IPV6_DAD=y + +# Wi-Fi configuration +CONFIG_OTA_WIFI_SSID="" +CONFIG_OTA_WIFI_PSK="" +CONFIG_OTA_IP_PROTOCOL_SELECTION=1 + +# OTA Configuration +CONFIG_SIWX91X_FIRMWARE_UPGRADE=y +CONFIG_REBOOT=y diff --git a/samples/siwx91x_ota/sample.yaml b/samples/siwx91x_ota/sample.yaml new file mode 100644 index 0000000..f4ef7b1 --- /dev/null +++ b/samples/siwx91x_ota/sample.yaml @@ -0,0 +1,19 @@ +# Copyright (c) 2025 Silicon Laboratories Inc. +# SPDX-License-Identifier: Apache-2.0 + +sample: + name: HTTP/HTTPS OTAF + description: HTTP/HTTPS OTA firmware update application for SiWx917 +tests: + sample.net.http_otaf: + harness: net + platform_allow: + - siwx917_rb4338a + tags: + - net + - wifi + - http + - tls + - ota + integration_platforms: + - siwx917_rb4338a diff --git a/samples/siwx91x_ota/src/ca-cert_der.c b/samples/siwx91x_ota/src/ca-cert_der.c new file mode 100644 index 0000000..f5f0169 --- /dev/null +++ b/samples/siwx91x_ota/src/ca-cert_der.c @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2025 Silicon Laboratories Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +const unsigned char ca_cert_der[] = { + 0x30, 0x82, 0x04, 0xff, 0x30, 0x82, 0x03, 0xe7, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x14, + 0x6b, 0x9b, 0x70, 0xc6, 0xf1, 0xa3, 0x94, 0x65, 0x19, 0xa1, 0x08, 0x58, 0xef, 0xa7, 0x8d, + 0x2b, 0x7a, 0x83, 0xc1, 0xda, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, 0x94, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, + 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, + 0x0c, 0x07, 0x4d, 0x6f, 0x6e, 0x74, 0x61, 0x6e, 0x61, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, + 0x55, 0x04, 0x07, 0x0c, 0x07, 0x42, 0x6f, 0x7a, 0x65, 0x6d, 0x61, 0x6e, 0x31, 0x11, 0x30, + 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x08, 0x53, 0x61, 0x77, 0x74, 0x6f, 0x6f, 0x74, + 0x68, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x0a, 0x43, 0x6f, 0x6e, + 0x73, 0x75, 0x6c, 0x74, 0x69, 0x6e, 0x67, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, + 0x03, 0x0c, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x6f, 0x6c, 0x66, 0x73, 0x73, 0x6c, 0x2e, + 0x63, 0x6f, 0x6d, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x09, 0x01, 0x16, 0x10, 0x69, 0x6e, 0x66, 0x6f, 0x40, 0x77, 0x6f, 0x6c, 0x66, 0x73, + 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x34, 0x31, 0x32, 0x31, + 0x38, 0x32, 0x31, 0x32, 0x35, 0x32, 0x39, 0x5a, 0x17, 0x0d, 0x32, 0x37, 0x30, 0x39, 0x31, + 0x34, 0x32, 0x31, 0x32, 0x35, 0x32, 0x39, 0x5a, 0x30, 0x81, 0x94, 0x31, 0x0b, 0x30, 0x09, + 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, + 0x55, 0x04, 0x08, 0x0c, 0x07, 0x4d, 0x6f, 0x6e, 0x74, 0x61, 0x6e, 0x61, 0x31, 0x10, 0x30, + 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x07, 0x42, 0x6f, 0x7a, 0x65, 0x6d, 0x61, 0x6e, + 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x08, 0x53, 0x61, 0x77, 0x74, + 0x6f, 0x6f, 0x74, 0x68, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x0a, + 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x74, 0x69, 0x6e, 0x67, 0x31, 0x18, 0x30, 0x16, 0x06, + 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x6f, 0x6c, 0x66, 0x73, + 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x10, 0x69, 0x6e, 0x66, 0x6f, 0x40, 0x77, 0x6f, + 0x6c, 0x66, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, + 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xbf, 0x0c, 0xca, + 0x2d, 0x14, 0xb2, 0x1e, 0x84, 0x42, 0x5b, 0xcd, 0x38, 0x1f, 0x4a, 0xf2, 0x4d, 0x75, 0x10, + 0xf1, 0xb6, 0x35, 0x9f, 0xdf, 0xca, 0x7d, 0x03, 0x98, 0xd3, 0xac, 0xde, 0x03, 0x66, 0xee, + 0x2a, 0xf1, 0xd8, 0xb0, 0x7d, 0x6e, 0x07, 0x54, 0x0b, 0x10, 0x98, 0x21, 0x4d, 0x80, 0xcb, + 0x12, 0x20, 0xe7, 0xcc, 0x4f, 0xde, 0x45, 0x7d, 0xc9, 0x72, 0x77, 0x32, 0xea, 0xca, 0x90, + 0xbb, 0x69, 0x52, 0x10, 0x03, 0x2f, 0xa8, 0xf3, 0x95, 0xc5, 0xf1, 0x8b, 0x62, 0x56, 0x1b, + 0xef, 0x67, 0x6f, 0xa4, 0x10, 0x41, 0x95, 0xad, 0x0a, 0x9b, 0xe3, 0xa5, 0xc0, 0xb0, 0xd2, + 0x70, 0x76, 0x50, 0x30, 0x5b, 0xa8, 0xe8, 0x08, 0x2c, 0x7c, 0xed, 0xa7, 0xa2, 0x7a, 0x8d, + 0x38, 0x29, 0x1c, 0xac, 0xc7, 0xed, 0xf2, 0x7c, 0x95, 0xb0, 0x95, 0x82, 0x7d, 0x49, 0x5c, + 0x38, 0xcd, 0x77, 0x25, 0xef, 0xbd, 0x80, 0x75, 0x53, 0x94, 0x3c, 0x3d, 0xca, 0x63, 0x5b, + 0x9f, 0x15, 0xb5, 0xd3, 0x1d, 0x13, 0x2f, 0x19, 0xd1, 0x3c, 0xdb, 0x76, 0x3a, 0xcc, 0xb8, + 0x7d, 0xc9, 0xe5, 0xc2, 0xd7, 0xda, 0x40, 0x6f, 0xd8, 0x21, 0xdc, 0x73, 0x1b, 0x42, 0x2d, + 0x53, 0x9c, 0xfe, 0x1a, 0xfc, 0x7d, 0xab, 0x7a, 0x36, 0x3f, 0x98, 0xde, 0x84, 0x7c, 0x05, + 0x67, 0xce, 0x6a, 0x14, 0x38, 0x87, 0xa9, 0xf1, 0x8c, 0xb5, 0x68, 0xcb, 0x68, 0x7f, 0x71, + 0x20, 0x2b, 0xf5, 0xa0, 0x63, 0xf5, 0x56, 0x2f, 0xa3, 0x26, 0xd2, 0xb7, 0x6f, 0xb1, 0x5a, + 0x17, 0xd7, 0x38, 0x99, 0x08, 0xfe, 0x93, 0x58, 0x6f, 0xfe, 0xc3, 0x13, 0x49, 0x08, 0x16, + 0x0b, 0xa7, 0x4d, 0x67, 0x00, 0x52, 0x31, 0x67, 0x23, 0x4e, 0x98, 0xed, 0x51, 0x45, 0x1d, + 0xb9, 0x04, 0xd9, 0x0b, 0xec, 0xd8, 0x28, 0xb3, 0x4b, 0xbd, 0xed, 0x36, 0x79, 0x02, 0x03, + 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x45, 0x30, 0x82, 0x01, 0x41, 0x30, 0x1d, 0x06, 0x03, + 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x27, 0x8e, 0x67, 0x11, 0x74, 0xc3, 0x26, 0x1d, + 0x3f, 0xed, 0x33, 0x63, 0xb3, 0xa4, 0xd8, 0x1d, 0x30, 0xe5, 0xe8, 0xd5, 0x30, 0x81, 0xd4, + 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x81, 0xcc, 0x30, 0x81, 0xc9, 0x80, 0x14, 0x27, 0x8e, + 0x67, 0x11, 0x74, 0xc3, 0x26, 0x1d, 0x3f, 0xed, 0x33, 0x63, 0xb3, 0xa4, 0xd8, 0x1d, 0x30, + 0xe5, 0xe8, 0xd5, 0xa1, 0x81, 0x9a, 0xa4, 0x81, 0x97, 0x30, 0x81, 0x94, 0x31, 0x0b, 0x30, + 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, + 0x03, 0x55, 0x04, 0x08, 0x0c, 0x07, 0x4d, 0x6f, 0x6e, 0x74, 0x61, 0x6e, 0x61, 0x31, 0x10, + 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x07, 0x42, 0x6f, 0x7a, 0x65, 0x6d, 0x61, + 0x6e, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x08, 0x53, 0x61, 0x77, + 0x74, 0x6f, 0x6f, 0x74, 0x68, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, + 0x0a, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x74, 0x69, 0x6e, 0x67, 0x31, 0x18, 0x30, 0x16, + 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x6f, 0x6c, 0x66, + 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x09, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x10, 0x69, 0x6e, 0x66, 0x6f, 0x40, 0x77, + 0x6f, 0x6c, 0x66, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x82, 0x14, 0x6b, 0x9b, 0x70, + 0xc6, 0xf1, 0xa3, 0x94, 0x65, 0x19, 0xa1, 0x08, 0x58, 0xef, 0xa7, 0x8d, 0x2b, 0x7a, 0x83, + 0xc1, 0xda, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, + 0xff, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x15, 0x30, 0x13, 0x82, 0x0b, 0x65, + 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x87, 0x04, 0x7f, 0x00, 0x00, + 0x01, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, + 0x03, 0x02, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, + 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x77, 0x3b, 0x3d, 0x66, 0x74, 0xbc, 0x97, 0xfe, + 0x40, 0x16, 0xe6, 0xba, 0xa5, 0xd5, 0xd1, 0x84, 0x08, 0x89, 0x69, 0x4f, 0x88, 0x0d, 0x57, + 0xa9, 0xef, 0x8c, 0xc3, 0x97, 0x52, 0xc8, 0xbd, 0x8b, 0xa2, 0x49, 0x3b, 0xb7, 0xf7, 0x5d, + 0x1e, 0xd6, 0x14, 0x7f, 0xb2, 0x80, 0x33, 0xda, 0xa0, 0x8a, 0xd3, 0xe1, 0x2f, 0xd5, 0xbc, + 0x33, 0x9f, 0xea, 0x5a, 0x72, 0x24, 0xe5, 0xf8, 0xb8, 0x4b, 0xb3, 0xdf, 0x62, 0x90, 0x3b, + 0xa8, 0x21, 0xef, 0x27, 0x42, 0x75, 0xbc, 0x60, 0x02, 0x8e, 0x37, 0x35, 0x99, 0xeb, 0xa3, + 0x28, 0xf2, 0x65, 0x4c, 0xff, 0x7a, 0xf8, 0x8e, 0xcc, 0x23, 0x6d, 0xe5, 0x6a, 0xfe, 0x22, + 0x5a, 0xd9, 0xb2, 0x4f, 0x47, 0xc7, 0xe0, 0xae, 0x98, 0xef, 0x94, 0xac, 0xb6, 0x4f, 0x61, + 0x81, 0x29, 0x8e, 0xe1, 0x79, 0x2c, 0x46, 0xfc, 0xe9, 0x1a, 0xc3, 0x96, 0x1f, 0x19, 0x93, + 0x64, 0x2e, 0x9f, 0x37, 0x72, 0xc5, 0xe4, 0x93, 0x4e, 0x61, 0x5f, 0x38, 0x8e, 0xae, 0xe8, + 0x39, 0x19, 0xe6, 0x97, 0xa8, 0x91, 0xd4, 0x23, 0x7e, 0x1e, 0xd2, 0xd0, 0x53, 0xec, 0xcc, + 0xac, 0xa0, 0x1d, 0xd0, 0xb7, 0xdd, 0xb1, 0xb7, 0x01, 0x2e, 0x96, 0xcd, 0x85, 0x27, 0xe0, + 0xe7, 0x47, 0xe2, 0xc1, 0xc1, 0x00, 0xf6, 0x94, 0xdf, 0x77, 0xe7, 0xfa, 0xc6, 0xef, 0x8a, + 0xc0, 0x7c, 0x67, 0xbc, 0xff, 0xa0, 0x7c, 0x94, 0x3b, 0x7d, 0x86, 0x42, 0xaf, 0x3d, 0x83, + 0x31, 0xee, 0x2a, 0x3b, 0x7b, 0xf0, 0x2c, 0x9e, 0x6f, 0xe9, 0xc4, 0x07, 0x81, 0x24, 0xda, + 0x05, 0x70, 0x4d, 0xdd, 0x09, 0xae, 0x9e, 0x72, 0xb8, 0x21, 0x0e, 0x8c, 0xb2, 0xab, 0xaa, + 0x4c, 0x49, 0x10, 0xf7, 0x76, 0xf9, 0xb5, 0x0d, 0x6c, 0x20, 0xd3, 0xdf, 0x7a, 0x06, 0x32, + 0x8d, 0x29, 0x1f, 0x28, 0x1d, 0x8d, 0x26, 0x33}; +unsigned int ca_cert_der_len = 1283; diff --git a/samples/siwx91x_ota/src/main.c b/samples/siwx91x_ota/src/main.c new file mode 100644 index 0000000..37c2f42 --- /dev/null +++ b/samples/siwx91x_ota/src/main.c @@ -0,0 +1,990 @@ +/* + * Copyright (c) 2025 Silicon Laboratories Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Running this application under net offload mode */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "firmware_upgradation.h" +#include "sl_si91x_types.h" +#include "sl_utility.h" +#include "sl_wifi.h" + +/* Event masks */ +#define WIFI_SHELL_MGMT_EVENTS_COMMON \ + (NET_EVENT_WIFI_SCAN_RESULT | NET_EVENT_WIFI_SCAN_DONE | NET_EVENT_WIFI_CONNECT_RESULT | \ + NET_EVENT_WIFI_DISCONNECT_RESULT) + +/* Network configuration */ +#define CMD_WAIT_TIME_MS 180000 /**< Command timeout in milliseconds */ +#define MAX_RECV_BUF_LEN 1460 /**< Maximum receive buffer length */ +#define HTTP_MSG_Q_MAX_SIZE 16 /**< HTTP message queue size */ + +/* Firmware update configuration */ +#define CHUNK_SIZE (10 * 1024) /**< Download chunk size */ +#define FW_CHUNK_SIZE 1024 /**< Firmware write chunk size */ +#define FW_CHUNK_DOWNLOAD_TIMEOUT_MS 30000 /**< Firmware chunk download timeout */ +#define HTTP_RESP_TIMEOUT_MS 30000 /**< HTTP response timeout */ + +/* Retry configuration */ +#define MAX_RETRIES 3 /**< Maximum retry attempts */ + +/* TLS configuration */ +#define TLS_TAG_CA_CERTIFICATE 1 /**< CA certificate security tag */ + +/* Event flags */ +#define WLAN_EVENT_READY BIT(0) +#define DHCP_EVENT_READY BIT(1) +#define OTA_EVENT_READY BIT(2) + +/* OTA update state machine states */ +enum ota_state { + OTA_STATE_CONNECT, /**< Connect to Wi-Fi network */ + OTA_STATE_IP_CONFIG, /**< Get IP configuration */ + OTA_STATE_SERVER_CONNECT, /**< Connect to OTA server */ + OTA_STATE_DOWNLOAD, /**< Download firmware image */ + OTA_STATE_PROCESS /**< Process downloaded firmware */ +}; + +/* Message for HTTP data */ +typedef struct { + uint32_t length; + uint8_t buffer[FW_CHUNK_SIZE]; +} msg_t; + +struct http_parse_data { + char schema[6]; + char host[100]; + char path[100]; + char port[6]; + bool is_tls_enabled; +}; + +struct app_ctx { + struct net_if *iface; + enum wifi_security_type security_type; + struct k_event events; + struct net_mgmt_event_callback dhcp_mgmt_cb; + struct net_mgmt_event_callback wlan_mgmt_cb; + struct net_mgmt_event_callback l4_cb; + volatile enum ota_state state; + volatile bool ipv6_addr_config_done; + int sock; + struct http_parse_data http_parse_data_st; + uint8_t http_recv_buf[MAX_RECV_BUF_LEN]; + uint32_t ota_image_size; + int ota_range_start_byte; + int ota_range_end_byte; + msg_t msg_c; + bool http_response_error; + char __aligned(4) http_data_q_buffer[HTTP_MSG_Q_MAX_SIZE * sizeof(msg_t)]; + struct k_msgq http_data_q; + uint8_t retry_count; +}; + +extern unsigned char ca_cert_der[]; +extern unsigned int ca_cert_der_len; + +static void ota_application_start(struct app_ctx *ctx); +static void ota_start_dhcpv4_client(struct net_if *iface, void *user_data); +static void ota_wifi_mgmt_event_handler(struct net_mgmt_event_callback *cb, uint64_t mgmt_event, + struct net_if *iface); +static void ota_handle_wifi_disconnect_result(struct net_mgmt_event_callback *cb); +static void ota_handle_wifi_connect_result(struct net_mgmt_event_callback *cb); +static void ota_handle_wifi_scan_done(struct net_mgmt_event_callback *cb); +static void ota_handle_wifi_scan_result(struct net_mgmt_event_callback *cb); +static int ota_load_firmware(struct app_ctx *ctx); +static void ota_dhcp_callback_handler(struct net_mgmt_event_callback *cb, uint64_t mgmt_event, + struct net_if *iface); + +/* + * @brief Handles IPv6 connectivity events + * + * Processes Layer 4 connectivity events for IPv6, displaying network information + * when an IPv6 connection is established. + * + * @param cb Pointer to the event callback structure + * @param event The network event to handle + * @param iface Network interface that generated the event + */ +static void ota_l4_event_handler(struct net_mgmt_event_callback *cb, uint64_t event, + struct net_if *iface) +{ + int i = 0; + char ipv6_address[NET_IPV6_ADDR_LEN]; + char link_local[NET_IPV6_ADDR_LEN]; + struct app_ctx *ctx = CONTAINER_OF(cb, struct app_ctx, l4_cb); + + if (event == NET_EVENT_IPV6_ADDR_ADD) { + printf("Network connectivity established and IPv6 address assigned\n"); + for (i = 0; i < NET_IF_MAX_IPV6_ADDR; i++) { + + net_addr_ntop(AF_INET6, &iface->config.ip.ipv6->unicast[i].address.in6_addr, + link_local, sizeof(link_local)); + if (!strncmp(link_local, "fe80", 4)) { + printf("IPv6 Link local Address:%s\n", link_local); + } else { + memcpy(ipv6_address, link_local, sizeof(link_local)); + printf("IPv6 global address:%s\n", ipv6_address); + } + } + ctx->ipv6_addr_config_done = true; + } +} + +/* + * @brief Starts DHCPv4 client on the given network interface + * + * @param iface Network interface where DHCP client should be started + * @param user_data User data (unused) + */ +static void ota_start_dhcpv4_client(struct net_if *iface, void *user_data) +{ + ARG_UNUSED(user_data); + + printf("Start on %s: index=%d\n", net_if_get_device(iface)->name, + net_if_get_by_iface(iface)); + net_dhcpv4_start(iface); +} + +/* + * @brief Handles DHCPv4 address assignment events + * + * Processes events when IPv4 addresses are assigned via DHCP, printing + * the assigned network configuration. + * + * @param cb Pointer to the event callback structure + * @param mgmt_event The network management event being handled + * @param iface Network interface that generated the event + */ +static void ota_dhcp_callback_handler(struct net_mgmt_event_callback *cb, uint64_t mgmt_event, + struct net_if *iface) +{ + int i = 0; + char buf[NET_IPV4_ADDR_LEN]; + struct app_ctx *ctx = CONTAINER_OF(cb, struct app_ctx, dhcp_mgmt_cb); + + if (mgmt_event == NET_EVENT_IPV4_ADDR_ADD) { + for (i = 0; i < NET_IF_MAX_IPV4_ADDR; i++) { + if (iface->config.ip.ipv4->unicast[i].ipv4.addr_type != NET_ADDR_DHCP) { + continue; + } + + printf("Address[%d]: %s", net_if_get_by_iface(iface), + net_addr_ntop( + AF_INET, + &iface->config.ip.ipv4->unicast[i].ipv4.address.in_addr, buf, + sizeof(buf))); + printf(" Subnet[%d]: %s", net_if_get_by_iface(iface), + net_addr_ntop(AF_INET, &iface->config.ip.ipv4->unicast[i].netmask, + buf, sizeof(buf))); + printf(" Router[%d]: %s", net_if_get_by_iface(iface), + net_addr_ntop(AF_INET, &iface->config.ip.ipv4->gw, buf, + sizeof(buf))); + printf(" Lease time[%d]: %u seconds\n", net_if_get_by_iface(iface), + iface->config.dhcpv4.lease_time); + + k_event_post(&ctx->events, DHCP_EVENT_READY); + } + } +} + +/* + * @brief Callback for HTTP response processing + * + * Processes received HTTP response data in chunks and queues them for firmware update. + * Handles error conditions and manages data fragmentation. + * + * @param rsp Response structure containing received data + * @param final_data Flag indicating if this is the final fragment + * @param user_data User context data (pointer to app_ctx) + * @return 0 on success, negative error code on failure + */ +static int ota_http_response_cb(struct http_response *rsp, enum http_final_call final_data, + void *user_data) +{ + struct app_ctx *ctx = (struct app_ctx *)user_data; + size_t offset = 0; + static uint8_t staging_buf[FW_CHUNK_SIZE]; + static size_t staging_len; + static msg_t msg_p; + + ctx->http_response_error = false; + + /* Check for HTTP errors */ + if (rsp->http_status_code >= 400) { + printf("HTTP error: %d %s\n", rsp->http_status_code, rsp->http_status); + ctx->http_response_error = true; + return -ENODATA; + } + + /* Check for empty response */ + if (!rsp->body_frag_len) { + printf("Warning: Empty response fragment received\n"); + if (final_data) { + ctx->http_response_error = true; + } + return -ENODATA; + } + + if (final_data == HTTP_DATA_MORE) { + printf("Partial data received (%zd bytes)\n", rsp->body_frag_len); + } else if (final_data == HTTP_DATA_FINAL) { + printf("All data received (%zd bytes)\n", rsp->body_frag_len); + } + + /* Copy received data into staging buffer and push 1024-byte chunks */ + while (offset < rsp->body_frag_len) { + size_t space_left = FW_CHUNK_SIZE - staging_len; + size_t to_copy = (rsp->body_frag_len - offset < space_left) + ? (rsp->body_frag_len - offset) + : space_left; + + memcpy(staging_buf + staging_len, rsp->body_frag_start + offset, to_copy); + staging_len += to_copy; + offset += to_copy; + + /* If we have a full chunk, push to queue */ + if (staging_len == FW_CHUNK_SIZE) { + msg_p.length = FW_CHUNK_SIZE; + memcpy(msg_p.buffer, staging_buf, FW_CHUNK_SIZE); + if (k_msgq_put(&ctx->http_data_q, &msg_p, K_NO_WAIT)) { + printf("Unable to queue the data\n"); + ctx->http_response_error = true; + return -errno; + } + staging_len = 0; /* Reset for next chunk */ + } + } + /* If this is the final data and there are leftover bytes, push them as the last chunk */ + if (final_data && staging_len > 0) { + msg_p.length = staging_len; + memcpy(msg_p.buffer, staging_buf, staging_len); + if (k_msgq_put(&ctx->http_data_q, &msg_p, K_NO_WAIT)) { + printf("Unable to queue the data\n"); + ctx->http_response_error = true; + return -errno; + } + staging_len = 0; + } + if (final_data || ctx->http_response_error) { + k_event_post(&ctx->events, OTA_EVENT_READY); + } + return 0; +} + +/* + * @brief Initialize WiFi scanning + * @param ctx Pointer to application context structure + * @return int 0 on success, negative error code on failure + */ +static int ota_wifi_start_scan(struct app_ctx *ctx) +{ + struct wifi_scan_params params = {}; + + params.dwell_time_active = 99; + if (net_mgmt(NET_REQUEST_WIFI_SCAN, ctx->iface, ¶ms, sizeof(params))) { + return -errno; + } + if (k_event_wait(&ctx->events, WLAN_EVENT_READY, false, K_MSEC(CMD_WAIT_TIME_MS)) == 0) { + printf("WiFi scan failed or timed out\n"); + return -errno; + } + return 0; +} + +/* + * @brief Connect to WiFi network + * + * @param ssid Network SSID + * @param pwd Network password + * @param security Security type + * @return int 0 on success, negative error code on failure + */ +static int ota_wifi_connect(const char *ssid, const char *pwd, struct app_ctx *ctx) +{ + int ret; + struct wifi_connect_req_params cnx_params = {.channel = WIFI_CHANNEL_ANY, + .band = 0, + .security = ctx->security_type, + .psk_length = strlen(pwd), + .psk = (uint8_t *)pwd, + .ssid_length = strlen(ssid), + .ssid = (uint8_t *)ssid}; + + if (!ssid || !pwd) { + printf("Invalid connection parameters\n"); + return -EINVAL; + } + + /* Request connection */ + ret = net_mgmt(NET_REQUEST_WIFI_CONNECT, ctx->iface, &cnx_params, + sizeof(struct wifi_connect_req_params)); + if (ret) { + printf("Connection request failed with error: %d\n", ret); + return -errno; + } + printf("Connecting to network: %s\n", ssid); + if (k_event_wait(&ctx->events, WLAN_EVENT_READY, false, K_MSEC(CMD_WAIT_TIME_MS)) == 0) { + printf("WiFi connect failed or timed out\n"); + return -errno; + } + return 0; +} + +/* + * @brief Configure IP networking + * @param ctx Pointer to application context structure + * @return int 0 on success, negative error code on failure + */ +static int ota_configure_ip(struct app_ctx *ctx) +{ + printf("Configuring IP networking...\n"); + + if (IS_ENABLED(CONFIG_NET_IPV4)) { + net_if_foreach(ota_start_dhcpv4_client, NULL); + if (k_event_wait(&ctx->events, DHCP_EVENT_READY, false, K_MSEC(CMD_WAIT_TIME_MS)) == + 0) { + printf("IP configuration failed or timed out\n"); + return -errno; + } + } + return 0; +} + +/* + * @brief Parses URL into host, path and port components + * + * Extracts the different components of the OTA update URL to prepare + * for connecting to the server. + * + * @param http_parse_data_st Pointer to structure to store the parsed URL components + * @return 0 on success, negative error code on failure + */ +int ota_parse_url(struct http_parse_data *http_parse_data_st) +{ + struct http_parser_url parser; + char *full_url = CONFIG_OTA_UPDATE_URL; + size_t data_len; + int ret = 0; + + http_parser_url_init(&parser); + ret = http_parser_parse_url(full_url, strlen(full_url), 0, &parser); + __ASSERT(ret == 0, "URL parsing failed"); + + /* Get the schema, host, port and path info from the parsed URL */ + if (parser.field_set & (1 << UF_SCHEMA)) { + data_len = parser.field_data[UF_SCHEMA].len; + memcpy(http_parse_data_st->schema, &full_url[parser.field_data[UF_SCHEMA].off], + data_len); + http_parse_data_st->schema[data_len] = '\0'; + http_parse_data_st->is_tls_enabled = + (strcmp(http_parse_data_st->schema, "https") == 0) ? 1 : 0; + } else { + __ASSERT(parser.field_set & BIT(UF_SCHEMA), "Schema field missing in URL"); + } + if (parser.field_set & (1 << UF_HOST)) { + data_len = parser.field_data[UF_HOST].len; + memcpy(http_parse_data_st->host, &full_url[parser.field_data[UF_HOST].off], + data_len); + http_parse_data_st->host[data_len] = '\0'; + } else { + __ASSERT(parser.field_set & BIT(UF_HOST), "Host field missing in URL"); + } + + if (parser.field_set & (1 << UF_PATH)) { + data_len = parser.field_data[UF_PATH].len; + memcpy(http_parse_data_st->path, &full_url[parser.field_data[UF_PATH].off], + data_len); + http_parse_data_st->path[data_len] = '\0'; + } else { + __ASSERT(parser.field_set & BIT(UF_PATH), "Path field missing in URL"); + } + + if (parser.field_set & (1 << UF_PORT)) { + data_len = parser.field_data[UF_PORT].len; + memcpy(http_parse_data_st->port, &full_url[parser.field_data[UF_PORT].off], + data_len); + http_parse_data_st->port[data_len] = '\0'; + } else { + strcpy(http_parse_data_st->port, http_parse_data_st->is_tls_enabled ? "443" : "80"); + } + printf("Retrieve http%s://%s:%s%s\n", http_parse_data_st->is_tls_enabled ? "s" : "", + http_parse_data_st->host, http_parse_data_st->port, http_parse_data_st->path); + return 0; +} + +/* + * @brief Connect to OTA server + * + * @param ctx Pointer to application context structure + * @return int 0 on success, negative error code on failure + */ +static int ota_connect_to_server(struct app_ctx *ctx) +{ + int ret; + struct zsock_addrinfo *res; + int *sock_ptr = &ctx->sock; + struct http_parse_data http_parse_data_st = ctx->http_parse_data_st; + struct zsock_addrinfo hints = { + .ai_family = (CONFIG_OTA_IP_PROTOCOL_SELECTION == 0) ? AF_INET : AF_INET6, + .ai_socktype = SOCK_STREAM}; + + ret = zsock_getaddrinfo(http_parse_data_st.host, http_parse_data_st.port, &hints, &res); + if (ret != 0) { + printf("Address resolution failed: %d\n", ret); + return ret; + } + __ASSERT(IS_ENABLED(CONFIG_NET_SOCKETS_SOCKOPT_TLS), + "Application was built without support for TLS"); + + if (http_parse_data_st.is_tls_enabled) { + sec_tag_t sec_tag_list[] = { + TLS_TAG_CA_CERTIFICATE, + }; + + printf("Creating TLS socket\n"); + *sock_ptr = zsock_socket(res->ai_family, res->ai_socktype, IPPROTO_TLS_1_2); + if (*sock_ptr < 0) { + goto error; + } + + ret = zsock_setsockopt(*sock_ptr, SOL_TLS, TLS_SEC_TAG_LIST, sec_tag_list, + sizeof(sec_tag_list)); + if (ret < 0) { + goto error; + } + + /* NOTE: The customer must not proceed to production with this.*/ + ret = zsock_setsockopt(*sock_ptr, SOL_TLS, TLS_HOSTNAME, NULL, 0); + if (ret < 0) { + goto error; + } + } else { + *sock_ptr = zsock_socket(res->ai_family, res->ai_socktype, IPPROTO_TCP); + if (*sock_ptr < 0) { + goto error; + } + } + + /* Connect to the server */ + ret = zsock_connect(*sock_ptr, res->ai_addr, res->ai_addrlen); + if (ret < 0) { + printf("Connection failed (%d): %s\n", -errno, strerror(errno)); + *sock_ptr = -1; + goto error; + } + + printf("Connected to %s:%s\n", http_parse_data_st.host, http_parse_data_st.port); + zsock_freeaddrinfo(res); + return *sock_ptr; + +error: + if (*sock_ptr >= 0) { + zsock_close(*sock_ptr); + *sock_ptr = -1; + } + zsock_freeaddrinfo(res); + return -errno; +} + +/* + * @brief Download firmware chunk from server + * + * @param ctx Pointer to application context + * @return int 0 on success, negative error code on failure + */ +static int ota_download_firmware_chunk(struct app_ctx *ctx) +{ + int ret = 0; + char range_header[64]; + const char *headers[2] = { range_header, NULL }; + struct http_request req = {.method = HTTP_GET, + .url = ctx->http_parse_data_st.path, + .header_fields = headers, + .host = ctx->http_parse_data_st.host, + .protocol = "HTTP/1.1", + .response = ota_http_response_cb, + .recv_buf = ctx->http_recv_buf, + .recv_buf_len = sizeof(ctx->http_recv_buf)}; + + printf("Downloading bytes %d-%d...\n", ctx->ota_range_start_byte, ctx->ota_range_end_byte); + + /* Prepare headers */ + snprintf(range_header, sizeof(range_header), "Range: bytes=%d-%d\r\n", + ctx->ota_range_start_byte, ctx->ota_range_end_byte); + + /* Send request */ + ret = http_client_req(ctx->sock, &req, HTTP_RESP_TIMEOUT_MS, ctx); + if (ret < 0) { + printf("HTTP request failed: status=%s, requested range=%d-%d\n", strerror(-ret), + ctx->ota_range_start_byte, ctx->ota_range_end_byte); + } + return ret; +} + +/* + * @brief Updates the HTTP range header for the next chunk download + * + * Calculates the byte range for the next chunk to be downloaded based on + * current progress and total firmware size. + * + * @param ctx Pointer to application context + * @return true if the full image has been received, false otherwise + */ +bool ota_update_http_range_header(struct app_ctx *ctx) +{ + bool received_full_img = false; + + if (ctx->ota_range_end_byte == (ctx->ota_image_size - 1)) { + printf("received full image\n"); + received_full_img = true; + return received_full_img; + } + ctx->ota_range_end_byte = ctx->ota_range_start_byte + CHUNK_SIZE - 1; + if (ctx->ota_range_end_byte > ctx->ota_image_size) { + ctx->ota_range_end_byte = ctx->ota_image_size - 1; + } + return received_full_img; +} + +/* + * @brief Process firmware update data packets + * + * @param otaf_image Pointer to firmware image data + * @param length Length of the data in bytes + * @return int Status code (0 on success, negative error code on failure) + */ +int ota_load_firmware(struct app_ctx *ctx) +{ + static bool first_packet = true; + uint32_t status = 0; + uint8_t *otaf_image = ctx->msg_c.buffer; + uint16_t length = ctx->msg_c.length; + + if (!otaf_image || length == 0) { + __ASSERT(0, "Invalid firmware data parameters\n"); + } + + /* Handle the first packet (header processing) */ + if (first_packet) { + uint8_t ota_header_size = sizeof(sl_si91x_firmware_header_t); + + /* Get firmware size from header */ + status = sl_wifi_get_firmware_size((void *)ctx->msg_c.buffer, &ctx->ota_image_size); + if (status != SL_STATUS_OK) { + printf("Unable to fetch firmware size. Status: 0x%x\n", status); + zsock_close(ctx->sock); + return status; + } + printf("Firmware size: %u bytes\n", ctx->ota_image_size); + + /* Process firmware header */ + status = sl_si91x_fwup_start(otaf_image); + if (status != SL_STATUS_OK) { + printf("Failed to load firmware header (0x%x)\n", status); + return status; + } + + /* Load first chunk (after header) */ + status = sl_si91x_fwup_load((otaf_image + ota_header_size), + (length - ota_header_size)); + if (status != SL_STATUS_OK) { + printf("Failed to load first firmware chunk (0x%x)\n", status); + return status; + } + first_packet = false; + return SL_STATUS_OK; + } + + /* Handle subsequent packets */ + status = sl_si91x_fwup_load(otaf_image, length); + + /* Check if firmware update is completed */ + if (status == SL_STATUS_SI91X_FW_UPDATE_DONE) { + zsock_close(ctx->sock); + k_sleep(K_MSEC(3000)); + printf("Firmware update completed. Reboot.\n"); + sys_reboot(); + first_packet = true; + return SL_STATUS_OK; + } else if (status != SL_STATUS_OK) { + printf("Fail to load the firmware chunk: 0x%x\n", status); + return status; + } + return SL_STATUS_OK; +} + +/* + * @brief Process downloaded firmware chunk + * @param ctx Pointer to application context + * @return int 0 if more chunks needed, 1 if complete, negative on error + */ +static int ota_process_firmware_chunk(struct app_ctx *ctx) +{ + int status; + int req_start; + + printf("Processing firmware data...\n"); + + /* Wait for HTTP response to be fully received */ + if (k_event_wait(&ctx->events, OTA_EVENT_READY, false, + K_MSEC(FW_CHUNK_DOWNLOAD_TIMEOUT_MS)) == 0) { + printf("Firmware download failed or Semaphore timed out\n"); + return -errno; + } + + /* Check if there was an error in the HTTP response */ + if (ctx->http_response_error) { + printf("Error occurred during HTTP download, skipping processing\n"); + return -EPROTO; + } + req_start = ctx->ota_range_start_byte; + + /* Process all queued data chunks */ + while (k_msgq_get(&ctx->http_data_q, &ctx->msg_c, K_NO_WAIT) == 0) { + ctx->ota_range_start_byte = ctx->ota_range_start_byte + ctx->msg_c.length; + status = ota_load_firmware(ctx); + if (status != SL_STATUS_OK) { + printf("Fw load failed for requested range=%d-%d\n", req_start, + ctx->ota_range_end_byte); + ctx->ota_range_start_byte = req_start; + return -EIO; + } + } + printf("Fw load success for requested range=%d-%d\n", req_start, ctx->ota_range_end_byte); + return 0; +} + +/* + * @brief Prints the current firmware version + * + * Retrieves and displays the current firmware version information + * from the device. + */ +void ota_print_firmware_version(void) +{ + int ret = 0; + sl_wifi_firmware_version_t version = {}; + + /* Get initial firmware version */ + ret = sl_wifi_get_firmware_version(&version); + if (ret == SL_STATUS_OK) { + print_firmware_version(&version); + } else { + printf("Failed to get firmware version: 0x%x\n", ret); + } +} + +/* + * @brief Clean up resources used by the OTA update process + * + * @param ctx Pointer to application context structure containing resources to clean up + */ +void ota_cleanup_resources(struct app_ctx *ctx) +{ + if (ctx->sock >= 0) { + zsock_close(ctx->sock); + ctx->sock = -1; + } + k_msgq_purge(&ctx->http_data_q); + ctx->http_response_error = false; +} + +/* + * @brief Handles retry logic for OTA operations + * + * Manages retry attempts for various OTA operations, with appropriate + * cleanup between attempts. + * + * @param ctx Pointer to application context + * @param operation_name Name of the operation being retried for logging + * @param cleanup_socket Whether to clean up socket resources before retrying + * @return true if retry should continue, false if max retries exceeded + */ +static bool ota_handle_retry(struct app_ctx *ctx, const char *operation_name, bool cleanup_socket) +{ + /* Clean up socket and message queue if needed */ + if (cleanup_socket && ctx->sock >= 0) { + ota_cleanup_resources(ctx); + } + if (++ctx->retry_count > MAX_RETRIES) { + printf("Maximum retries exceeded, aborting OTA\n"); + ctx->retry_count = 0; + return false; + } + printf("Retrying %s (%d/%d)...\n", operation_name, ctx->retry_count, MAX_RETRIES); + k_sleep(K_MSEC(1000)); + return true; +} + +/* + * @brief Main OTA application state machine + * @param ctx Pointer to application context + */ +static void ota_application_start(struct app_ctx *ctx) +{ + int ret = 0; + int firmware_status = 0; + char *ssid = CONFIG_OTA_WIFI_SSID; + char *pwd = CONFIG_OTA_WIFI_PSK; + bool ota_complete = false; + bool tls_certificate_added = false; + + printf("OTA Application Started\n"); + ota_print_firmware_version(); + + /* Main state machine loop */ + while (1) { + switch (ctx->state) { + case OTA_STATE_CONNECT: + ota_wifi_start_scan(ctx); + ret = ota_wifi_connect(ssid, pwd, ctx); + if (ret < 0) { + if (!ota_handle_retry(ctx, "Wi-Fi connection", false)) { + return; + } + break; + } + break; + + case OTA_STATE_IP_CONFIG: + ret = ota_configure_ip(ctx); + if (ret < 0) { + if (!ota_handle_retry(ctx, "IP configuration", false)) { + return; + } + break; + } + ctx->state = OTA_STATE_SERVER_CONNECT; + break; + + case OTA_STATE_SERVER_CONNECT: + if (IS_ENABLED(CONFIG_NET_IPV6) && !ctx->ipv6_addr_config_done) { + k_sleep(K_MSEC(10)); + break; + } + + /* Parse URL for OTA server */ + if (ota_parse_url(&ctx->http_parse_data_st)) { + printf("Failed to parse OTA URL, check configuration\n"); + if (!ota_handle_retry(ctx, "URL parsing", false)) { + return; + } + break; + } + + if (ctx->http_parse_data_st.is_tls_enabled && !tls_certificate_added) { + if (tls_credential_add(TLS_TAG_CA_CERTIFICATE, + TLS_CREDENTIAL_CA_CERTIFICATE, ca_cert_der, + ca_cert_der_len)) { + __ASSERT(0, "Failed to register CA certificate"); + } + tls_certificate_added = true; + } + + ret = ota_connect_to_server(ctx); + if (ret < 0) { + if (!ota_handle_retry(ctx, "server connection", true)) { + return; + } + break; + } + ctx->state = OTA_STATE_DOWNLOAD; + break; + + case OTA_STATE_DOWNLOAD: + ret = ota_download_firmware_chunk(ctx); + if (ret < 0) { + if (!ota_handle_retry(ctx, "download", true)) { + return; + } + ctx->state = OTA_STATE_SERVER_CONNECT; + break; + } + ctx->state = OTA_STATE_PROCESS; + break; + + case OTA_STATE_PROCESS: + firmware_status = ota_process_firmware_chunk(ctx); + if (firmware_status < 0) { + printf("OTA update failed during processing\n"); + ctx->state = (ctx->sock < 0) ? OTA_STATE_SERVER_CONNECT + : OTA_STATE_DOWNLOAD; + const char *operation = + (ctx->sock < 0) ? "server connection" : "download"; + + if (!ota_handle_retry(ctx, operation, true)) { + return; + } + break; + } + + /* Update range and check if complete */ + ota_complete = ota_update_http_range_header(ctx); + if (ota_complete) { + printf("OTA update completed successfully\n"); + ota_cleanup_resources(ctx); + return; + } + /* Get next chunk if OTA not complete*/ + ctx->state = OTA_STATE_DOWNLOAD; + break; + } + } +} + +/** + * @brief Converts MAC address to string format + * + * @param mac Pointer to MAC address bytes + * @param mac_len Length of MAC address (must be 6) + * @param buf Buffer to store the string representation + * @param buf_len Length of the buffer (must be at least 18 bytes) + * @return Pointer to the buffer containing the string on success, NULL on failure + */ +static char *ota_mac_to_string(const uint8_t *mac, uint8_t mac_len, char *buf, size_t buf_len) +{ + if (!mac || mac_len != 6 || !buf || buf_len < 18) { + return NULL; + } + snprintf(buf, buf_len, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], + mac[4], mac[5]); + return buf; +} + +static void ota_handle_wifi_scan_result(struct net_mgmt_event_callback *cb) +{ + const struct wifi_scan_result *entry = (const struct wifi_scan_result *)cb->info; + uint8_t mac_string_buf[sizeof("xx:xx:xx:xx:xx:xx")]; + uint8_t ssid_print[WIFI_SSID_MAX_LEN + 1]; + static uint16_t scan_result_count; + struct app_ctx *ctx = CONTAINER_OF(cb, struct app_ctx, wlan_mgmt_cb); + + if (strncmp(entry->ssid, CONFIG_OTA_WIFI_SSID, entry->ssid_length) == 0) { + ctx->security_type = entry->security; + } + + if (!scan_result_count) { + printf("%-4s | %-32s %-5s | %-13s | %-4s | %-15s | %-17s | %-8s\n", "Num", "SSID", + "(len)", "Chan (Band)", "RSSI", "Security", "BSSID", "MFP"); + } + + strncpy(ssid_print, entry->ssid, sizeof(ssid_print) - 1); + ssid_print[sizeof(ssid_print) - 1] = '\0'; + + printf("%-4d | %-32s %-5u | %-4u (%-6s) | %-4d | %-15s | %-17s | %-8s\n", + scan_result_count++, ssid_print, entry->ssid_length, entry->channel, + wifi_band_txt(entry->band), entry->rssi, wifi_security_txt(entry->security), + ((entry->mac_length) ? ota_mac_to_string(entry->mac, WIFI_MAC_ADDR_LEN, + mac_string_buf, sizeof(mac_string_buf)) + : ""), + wifi_mfp_txt(entry->mfp)); +} + +static void ota_handle_wifi_scan_done(struct net_mgmt_event_callback *cb) +{ + const struct wifi_status *status = (const struct wifi_status *)cb->info; + struct app_ctx *ctx = CONTAINER_OF(cb, struct app_ctx, wlan_mgmt_cb); + + if (status->status) { + printf("Scan request failed (%d)\n", status->status); + } else { + k_event_post(&ctx->events, WLAN_EVENT_READY); + } +} + +static void ota_handle_wifi_connect_result(struct net_mgmt_event_callback *cb) +{ + const struct wifi_status *status = (const struct wifi_status *)cb->info; + struct app_ctx *ctx = CONTAINER_OF(cb, struct app_ctx, wlan_mgmt_cb); + + if (status->status) { + printf("Connection request failed (%d)\n", status->status); + } else { + printf("Connected to Wi-Fi\n"); + k_event_post(&ctx->events, WLAN_EVENT_READY); + ctx->state = OTA_STATE_IP_CONFIG; + } +} + +static void ota_handle_wifi_disconnect_result(struct net_mgmt_event_callback *cb) +{ + const struct wifi_status *status = (const struct wifi_status *)cb->info; + struct app_ctx *ctx = CONTAINER_OF(cb, struct app_ctx, wlan_mgmt_cb); + + if (status->status) { + printf("Disconnection request failed (%d)\n", status->status); + } else { + printf("Disconnection reason: %d\n", status->disconn_reason); + + /* Check if disconnection occurred during OTA process */ + if (ctx->state > OTA_STATE_IP_CONFIG) { + printf("WiFi disconnected during OTA update, initiating reconnection\n"); + ota_cleanup_resources(ctx); + + /* Set state to reconnect */ + ctx->state = OTA_STATE_CONNECT; + } + k_event_post(&ctx->events, WLAN_EVENT_READY); + } +} + +static void ota_wifi_mgmt_event_handler(struct net_mgmt_event_callback *cb, uint64_t mgmt_event, + struct net_if *iface) +{ + + switch (mgmt_event) { + case NET_EVENT_WIFI_SCAN_RESULT: + ota_handle_wifi_scan_result(cb); + break; + case NET_EVENT_WIFI_SCAN_DONE: + ota_handle_wifi_scan_done(cb); + break; + case NET_EVENT_WIFI_CONNECT_RESULT: + ota_handle_wifi_connect_result(cb); + break; + case NET_EVENT_WIFI_DISCONNECT_RESULT: + ota_handle_wifi_disconnect_result(cb); + break; + default: + break; + } +} + +int main(void) +{ + static struct app_ctx app_ctx = { + .events = Z_EVENT_INITIALIZER(app_ctx.events), + .http_data_q = Z_MSGQ_INITIALIZER(app_ctx.http_data_q, app_ctx.http_data_q_buffer, + sizeof(msg_t), HTTP_MSG_Q_MAX_SIZE), + .sock = -1, + .ota_range_end_byte = (CHUNK_SIZE - 1), + .state = OTA_STATE_CONNECT, + .retry_count = 0}; + + app_ctx.iface = net_if_get_first_wifi(); + + net_mgmt_init_event_callback(&app_ctx.wlan_mgmt_cb, ota_wifi_mgmt_event_handler, + WIFI_SHELL_MGMT_EVENTS_COMMON); + net_mgmt_add_event_callback(&app_ctx.wlan_mgmt_cb); + + net_mgmt_init_event_callback(&app_ctx.dhcp_mgmt_cb, ota_dhcp_callback_handler, + NET_EVENT_IPV4_ADDR_ADD); + net_mgmt_add_event_callback(&app_ctx.dhcp_mgmt_cb); + net_mgmt_init_event_callback(&app_ctx.l4_cb, ota_l4_event_handler, NET_EVENT_IPV6_ADDR_ADD); + net_mgmt_add_event_callback(&app_ctx.l4_cb); + + ota_application_start(&app_ctx); + return 0; +}