[SILABS] Multi-chip OTA work (#32349)
* Inital multi OTA work WIP
* fix wifi build
* cleanup and code review comments
* Restyled by clang-format
* Restyled by gn
* Restyled by gn
* code review comments
* Restyled by clang-format
---------
Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/examples/platform/silabs/OTAConfig.cpp b/examples/platform/silabs/OTAConfig.cpp
index adc6f23..6305af0 100644
--- a/examples/platform/silabs/OTAConfig.cpp
+++ b/examples/platform/silabs/OTAConfig.cpp
@@ -17,6 +17,7 @@
*/
#include "OTAConfig.h"
+#include "silabs_utils.h"
#include <app/server/Server.h>
#ifndef SIWX_917
@@ -81,7 +82,6 @@
chip::DefaultOTARequestorStorage gRequestorStorage;
chip::DeviceLayer::DefaultOTARequestorDriver gRequestorUser;
chip::BDXDownloader gDownloader;
-chip::OTAImageProcessorImpl gImageProcessor;
void OTAConfig::Init()
{
@@ -93,12 +93,23 @@
// Periodic query timeout must be set prior to requestor being initialized
gRequestorUser.SetPeriodicQueryTimeout(OTA_PERIODIC_TIMEOUT);
- gRequestorUser.Init(&gRequestorCore, &gImageProcessor);
- gImageProcessor.SetOTAImageFile("test.txt");
- gImageProcessor.SetOTADownloader(&gDownloader);
+#if CHIP_DEVICE_CONFIG_ENABLE_MULTI_OTA_REQUESTOR
+ auto & imageProcessor = chip::OTAMultiImageProcessorImpl::GetDefaultInstance();
+#else
+ auto & imageProcessor = chip::OTAImageProcessorImpl::GetDefaultInstance();
+#endif
+
+ gRequestorUser.Init(&gRequestorCore, &imageProcessor);
+
+ CHIP_ERROR err = imageProcessor.Init(&gDownloader);
+ if (err != CHIP_NO_ERROR)
+ {
+ SILABS_LOG("Image processor init failed");
+ assert(err == CHIP_NO_ERROR);
+ }
// Connect the Downloader and Image Processor objects
- gDownloader.SetImageProcessorDelegate(&gImageProcessor);
+ gDownloader.SetImageProcessorDelegate(&imageProcessor);
// Initialize and interconnect the Requestor and Image Processor objects -- END
}
diff --git a/examples/platform/silabs/OTAConfig.h b/examples/platform/silabs/OTAConfig.h
index 2d5dbcf..2b7ed9a 100644
--- a/examples/platform/silabs/OTAConfig.h
+++ b/examples/platform/silabs/OTAConfig.h
@@ -22,7 +22,12 @@
#include <app/clusters/ota-requestor/DefaultOTARequestor.h>
#include <app/clusters/ota-requestor/DefaultOTARequestorDriver.h>
#include <app/clusters/ota-requestor/DefaultOTARequestorStorage.h>
+
+#if CHIP_DEVICE_CONFIG_ENABLE_MULTI_OTA_REQUESTOR
+#include <platform/silabs/multi-ota/OTAMultiImageProcessorImpl.h>
+#else
#include <platform/silabs/OTAImageProcessorImpl.h>
+#endif
class OTAConfig
{
diff --git a/examples/platform/silabs/efr32/BUILD.gn b/examples/platform/silabs/efr32/BUILD.gn
index 02f1317..8b7b1eb 100644
--- a/examples/platform/silabs/efr32/BUILD.gn
+++ b/examples/platform/silabs/efr32/BUILD.gn
@@ -180,6 +180,10 @@
defines += [ "HEAP_MONITORING" ]
}
+ if (chip_enable_multi_ota_requestor) {
+ defines += [ "CHIP_DEVICE_CONFIG_ENABLE_MULTI_OTA_REQUESTOR=1" ]
+ }
+
ldflags = [ "-Wl,--no-warn-rwx-segment" ]
}
diff --git a/scripts/tools/silabs/ota/README.md b/scripts/tools/silabs/ota/README.md
new file mode 100644
index 0000000..d30dd45
--- /dev/null
+++ b/scripts/tools/silabs/ota/README.md
@@ -0,0 +1,71 @@
+---
+orphan: true
+---
+
+# Silabs OTA image tool
+
+## Overview
+
+This tool can generate an OTA image in the `|OTA standard header|TLV1|...|TLVn|`
+format. The payload contains data in standard TLV format (not Matter TLV
+format). During OTA transfer, these TLV can span across multiple BDX blocks,
+thus the `OTAImageProcessorImpl` instance should take this into account.
+
+## Supported platforms
+
+- EFR32 -
+
+## Usage
+
+This is a wrapper over standard `ota_image_tool.py`, so the options for `create`
+are also available here:
+
+```
+python3 ./scripts/tools/silabs/ota/ota_image_tool.py create -v 0xDEAD -p 0xBEEF -vn 50000 -vs "1.0" -da sha256
+```
+
+followed by \*_custom options_- and a positional argument (should be last) that
+specifies the output file. Please see the `create_ota_images.sh` for some
+reference commands.
+
+The list of **custom options**:
+
+```
+# Application options
+--app-input-file --> Path to the application binary.
+--app-version --> Application version. It's part of the descriptor and
+ can be different than the OTA image header version: -vn.
+--app-version-str --> Application version string. Same as above.
+--app-build-date --> Application build date. Same as above.
+
+# SSBL options
+--bl-input-file --> Path to the SSBL binary.
+--bl-version --> SSBL version.
+--bl-version-str --> SSBL version string.
+--bl-build-date --> SSBL build date.
+
+# Factory data options
+--factory-data --> If set, enables the generation of factory data.
+--cert_declaration --> Certification Declaration.
+--dac_cert --> DAC certificate.
+--dac_key --> DAC private key.
+--pai_cert --> PAI certificate.
+
+# Custom TLV options
+--json --> Path to a JSON file following ota_payload.schema
+```
+
+Please note that the options above are separated into four categories:
+application, bootloader, factory data and custom TLV (`--json` option). If no
+descriptor options are specified for app/SSBL, the script will use the default
+values (`50000`, `"50000-default"`, `"2023-01-01"`). The descriptor feature is
+optional, TLV processors having the option to register a callback for descriptor
+processing.
+
+## Custom payload
+
+When defining a custom processor, a user is able to also specify the custom
+format of the TLV by creating a JSON file based on the `ota_payload.schema`. The
+tool offers support for describing multiple TLV in the same JSON file. Please
+see the `examples/ota_max_entries_example.json` for a multi-app + SSBL example.
+Option `--json` must be used to specify the path to the JSON file.
diff --git a/scripts/tools/silabs/ota/crypto_utils.py b/scripts/tools/silabs/ota/crypto_utils.py
new file mode 100755
index 0000000..dbab140
--- /dev/null
+++ b/scripts/tools/silabs/ota/crypto_utils.py
@@ -0,0 +1,487 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2023 Project CHIP Authors
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""
+A pure python (slow) implementation of rijndael with a decent interface
+
+To include -
+
+from rijndael import rijndael
+
+To do a key setup -
+
+r = rijndael(key, block_size = 16)
+
+key must be a string of length 16, 24, or 32
+blocksize must be 16, 24, or 32. Default is 16
+
+To use -
+
+ciphertext = r.encrypt(plaintext)
+plaintext = r.decrypt(ciphertext)
+
+If any strings are of the wrong length a ValueError is thrown
+"""
+# ported from the Java reference code by Bram Cohen, April 2001
+# this code is public domain, unless someone makes
+# an intellectual property claim against the reference
+# code, in which case it can be made public domain by
+# deleting all the comments and renaming all the variables
+
+import copy
+import logging
+import struct
+
+shifts = [[[0, 0], [1, 3], [2, 2], [3, 1]],
+ [[0, 0], [1, 5], [2, 4], [3, 3]],
+ [[0, 0], [1, 7], [3, 5], [4, 4]]]
+
+# [keysize][block_size]
+num_rounds = {16: {16: 10, 24: 12, 32: 14}, 24: {16: 12, 24: 12, 32: 14}, 32: {16: 14, 24: 14, 32: 14}}
+
+A = [[1, 1, 1, 1, 1, 0, 0, 0],
+ [0, 1, 1, 1, 1, 1, 0, 0],
+ [0, 0, 1, 1, 1, 1, 1, 0],
+ [0, 0, 0, 1, 1, 1, 1, 1],
+ [1, 0, 0, 0, 1, 1, 1, 1],
+ [1, 1, 0, 0, 0, 1, 1, 1],
+ [1, 1, 1, 0, 0, 0, 1, 1],
+ [1, 1, 1, 1, 0, 0, 0, 1]]
+
+# produce log and alog tables, needed for multiplying in the
+# field GF(2^m) (generator = 3)
+alog = [1]
+for i in range(255):
+ j = (alog[-1] << 1) ^ alog[-1]
+ if j & 0x100 != 0:
+ j ^= 0x11B
+ alog.append(j)
+
+log = [0] * 256
+for i in range(1, 255):
+ log[alog[i]] = i
+
+
+# multiply two elements of GF(2^m)
+def mul(a, b):
+ if a == 0 or b == 0:
+ return 0
+ return alog[(log[a & 0xFF] + log[b & 0xFF]) % 255] # noqa: F821
+
+
+# substitution box based on F^{-1}(x)
+box = [[0] * 8 for i in range(256)]
+box[1][7] = 1
+for i in range(2, 256):
+ j = alog[255 - log[i]]
+ for t in range(8):
+ box[i][t] = (j >> (7 - t)) & 0x01
+
+B = [0, 1, 1, 0, 0, 0, 1, 1]
+
+# affine transform: box[i] <- B + A*box[i]
+cox = [[0] * 8 for i in range(256)]
+for i in range(256):
+ for t in range(8):
+ cox[i][t] = B[t]
+ for j in range(8):
+ cox[i][t] ^= A[t][j] * box[i][j]
+
+# S-boxes and inverse S-boxes
+S = [0] * 256
+Si = [0] * 256
+for i in range(256):
+ S[i] = cox[i][0] << 7
+ for t in range(1, 8):
+ S[i] ^= cox[i][t] << (7-t)
+ Si[S[i] & 0xFF] = i
+
+# T-boxes
+G = [[2, 1, 1, 3],
+ [3, 2, 1, 1],
+ [1, 3, 2, 1],
+ [1, 1, 3, 2]]
+
+AA = [[0] * 8 for i in range(4)]
+
+for i in range(4):
+ for j in range(4):
+ AA[i][j] = G[i][j]
+ AA[i][i+4] = 1
+
+for i in range(4):
+ pivot = AA[i][i]
+ if pivot == 0:
+ t = i + 1
+ while AA[t][i] == 0 and t < 4:
+ t += 1
+ assert t != 4, 'G matrix must be invertible'
+ for j in range(8):
+ AA[i][j], AA[t][j] = AA[t][j], AA[i][j]
+ pivot = AA[i][i]
+ for j in range(8):
+ if AA[i][j] != 0:
+ AA[i][j] = alog[(255 + log[AA[i][j] & 0xFF] - log[pivot & 0xFF]) % 255]
+ for t in range(4):
+ if i != t:
+ for j in range(i+1, 8):
+ AA[t][j] ^= mul(AA[i][j], AA[t][i])
+ AA[t][i] = 0
+
+iG = [[0] * 4 for i in range(4)]
+
+for i in range(4):
+ for j in range(4):
+ iG[i][j] = AA[i][j + 4]
+
+
+def mul4(a, bs):
+ if a == 0:
+ return 0
+ r = 0
+ for b in bs:
+ r <<= 8
+ if b != 0:
+ r = r | mul(a, b) # noqa: F821
+ return r
+
+
+T1 = []
+T2 = []
+T3 = []
+T4 = []
+T5 = []
+T6 = []
+T7 = []
+T8 = []
+U1 = []
+U2 = []
+U3 = []
+U4 = []
+
+for t in range(256):
+ s = S[t]
+ T1.append(mul4(s, G[0]))
+ T2.append(mul4(s, G[1]))
+ T3.append(mul4(s, G[2]))
+ T4.append(mul4(s, G[3]))
+
+ s = Si[t]
+ T5.append(mul4(s, iG[0]))
+ T6.append(mul4(s, iG[1]))
+ T7.append(mul4(s, iG[2]))
+ T8.append(mul4(s, iG[3]))
+
+ U1.append(mul4(t, iG[0]))
+ U2.append(mul4(t, iG[1]))
+ U3.append(mul4(t, iG[2]))
+ U4.append(mul4(t, iG[3]))
+
+# round constants
+rcon = [1]
+r = 1
+for t in range(1, 30):
+ r = mul(2, r)
+ rcon.append(r)
+
+del A
+del AA
+del pivot
+del B
+del G
+del box
+del log
+del alog
+del i
+del j
+del r
+del s
+del t
+del mul
+del mul4
+del cox
+del iG
+
+
+class rijndael:
+ def __init__(self, key, block_size=16):
+ if block_size != 16 and block_size != 24 and block_size != 32:
+ raise ValueError('Invalid block size: ' + str(block_size))
+ if len(key) != 16 and len(key) != 24 and len(key) != 32:
+ raise ValueError('Invalid key size: ' + str(len(key)))
+ self.block_size = block_size
+
+ ROUNDS = num_rounds[len(key)][block_size]
+ BC = int(block_size / 4)
+
+ # encryption round keys
+ Ke = [[0] * BC for i in range(ROUNDS + 1)]
+ # decryption round keys
+ Kd = [[0] * BC for i in range(ROUNDS + 1)]
+ ROUND_KEY_COUNT = (ROUNDS + 1) * BC
+ KC = int(len(key) / 4)
+
+ # copy user material bytes into temporary ints
+ tk = []
+ for i in range(0, KC):
+ tk.append((key[i * 4] << 24) | (key[i * 4 + 1] << 16) |
+ (key[i * 4 + 2] << 8) | key[i * 4 + 3])
+
+ # copy values into round key arrays
+ t = 0
+ j = 0
+ while j < KC and t < ROUND_KEY_COUNT:
+ Ke[int(t / BC)][t % BC] = tk[j]
+ Kd[ROUNDS - int(t / BC)][t % BC] = tk[j]
+ j += 1
+ t += 1
+ tt = 0
+ rconpointer = 0
+ while t < ROUND_KEY_COUNT:
+ # extrapolate using phi (the round key evolution function)
+ tt = tk[KC - 1]
+ tk[0] ^= (S[(tt >> 16) & 0xFF] & 0xFF) << 24 ^ \
+ (S[(tt >> 8) & 0xFF] & 0xFF) << 16 ^ \
+ (S[tt & 0xFF] & 0xFF) << 8 ^ \
+ (S[(tt >> 24) & 0xFF] & 0xFF) ^ \
+ (rcon[rconpointer] & 0xFF) << 24
+ rconpointer += 1
+ if KC != 8:
+ for i in range(1, KC):
+ tk[i] ^= tk[i-1]
+ else:
+ for i in range(1, KC / 2):
+ tk[i] ^= tk[i-1]
+ tt = tk[KC / 2 - 1]
+ tk[KC / 2] ^= (S[tt & 0xFF] & 0xFF) ^ \
+ (S[(tt >> 8) & 0xFF] & 0xFF) << 8 ^ \
+ (S[(tt >> 16) & 0xFF] & 0xFF) << 16 ^ \
+ (S[(tt >> 24) & 0xFF] & 0xFF) << 24
+ for i in range(KC / 2 + 1, KC):
+ tk[i] ^= tk[i-1]
+ # copy values into round key arrays
+ j = 0
+ while j < KC and t < ROUND_KEY_COUNT:
+ Ke[int(t / BC)][t % BC] = tk[j]
+ Kd[ROUNDS - int(t / BC)][t % BC] = tk[j]
+ j += 1
+ t += 1
+ # inverse MixColumn where needed
+ for r in range(1, ROUNDS):
+ for j in range(BC):
+ tt = Kd[r][j]
+ Kd[r][j] = U1[(tt >> 24) & 0xFF] ^ \
+ U2[(tt >> 16) & 0xFF] ^ \
+ U3[(tt >> 8) & 0xFF] ^ \
+ U4[tt & 0xFF]
+ self.Ke = Ke
+ self.Kd = Kd
+
+ def encrypt(self, plaintext):
+ if len(plaintext) != self.block_size:
+ raise ValueError('wrong block length, expected ' + str(self.block_size) + ' got ' + str(len(plaintext)))
+ Ke = self.Ke
+
+ BC = int(self.block_size / 4)
+ ROUNDS = len(Ke) - 1
+ if BC == 4:
+ SC = 0
+ elif BC == 6:
+ SC = 1
+ else:
+ SC = 2
+ s1 = shifts[SC][1][0]
+ s2 = shifts[SC][2][0]
+ s3 = shifts[SC][3][0]
+ a = [0] * BC
+ # temporary work array
+ t = []
+ # plaintext to ints + key
+ for i in range(BC):
+ t.append((ord(plaintext[i * 4]) << 24 |
+ ord(plaintext[i * 4 + 1]) << 16 |
+ ord(plaintext[i * 4 + 2]) << 8 |
+ ord(plaintext[i * 4 + 3])) ^ Ke[0][i])
+ # apply round transforms
+ for r in range(1, ROUNDS):
+ for i in range(BC):
+ a[i] = (T1[(t[i] >> 24) & 0xFF] ^
+ T2[(t[(i + s1) % BC] >> 16) & 0xFF] ^
+ T3[(t[(i + s2) % BC] >> 8) & 0xFF] ^
+ T4[t[(i + s3) % BC] & 0xFF]) ^ Ke[r][i]
+ t = copy.copy(a)
+ # last round is special
+ result = []
+ for i in range(BC):
+ tt = Ke[ROUNDS][i]
+ result.append((S[(t[i] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)
+ result.append((S[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)
+ result.append((S[(t[(i + s2) % BC] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF)
+ result.append((S[t[(i + s3) % BC] & 0xFF] ^ tt) & 0xFF)
+ return ''.join(list(map(chr, result)))
+
+ def decrypt(self, ciphertext):
+ if len(ciphertext) != self.block_size:
+ raise ValueError('wrong block length, expected ' + str(self.block_size) + ' got ' + str(len(ciphertext)))
+ Kd = self.Kd
+
+ BC = int(self.block_size / 4)
+ ROUNDS = len(Kd) - 1
+ if BC == 4:
+ SC = 0
+ elif BC == 6:
+ SC = 1
+ else:
+ SC = 2
+ s1 = shifts[SC][1][1]
+ s2 = shifts[SC][2][1]
+ s3 = shifts[SC][3][1]
+ a = [0] * BC
+ # temporary work array
+ t = [0] * BC
+ # ciphertext to ints + key
+ for i in range(BC):
+ t[i] = (ord(ciphertext[i * 4]) << 24 |
+ ord(ciphertext[i * 4 + 1]) << 16 |
+ ord(ciphertext[i * 4 + 2]) << 8 |
+ ord(ciphertext[i * 4 + 3])) ^ Kd[0][i]
+ # apply round transforms
+ for r in range(1, ROUNDS):
+ for i in range(BC):
+ a[i] = (T5[(t[i] >> 24) & 0xFF] ^
+ T6[(t[(i + s1) % BC] >> 16) & 0xFF] ^
+ T7[(t[(i + s2) % BC] >> 8) & 0xFF] ^
+ T8[t[(i + s3) % BC] & 0xFF]) ^ Kd[r][i]
+ t = copy.copy(a)
+ # last round is special
+ result = []
+ for i in range(BC):
+ tt = Kd[ROUNDS][i]
+ result.append((Si[(t[i] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)
+ result.append((Si[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)
+ result.append((Si[(t[(i + s2) % BC] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF)
+ result.append((Si[t[(i + s3) % BC] & 0xFF] ^ tt) & 0xFF)
+ return ''.join(map(chr, result))
+
+
+def encryptFlashData(nonce, key, data, imageLen):
+ encyptedBlock = ''
+ if (imageLen % 16) != 0:
+ for x in range(16 - (imageLen % 16)):
+ data = data + bytes([255])
+ imageLen = len(data)
+
+ r = rijndael(key, block_size=16)
+
+ for x in range(int(imageLen / 16)):
+ # use nonce value to create encrypted chunk
+ encryptNonce = ''
+ for i in nonce:
+ tempString = "%08x" % i
+ y = 0
+ while y < 8:
+ encryptNonce = encryptNonce + chr(int(tempString[y:y+2], 16))
+ y = y + 2
+ encChunk = r.encrypt(encryptNonce)
+
+ # increment the nonce value
+ if (nonce[3] == 0xffffffff):
+ nonce[3] = 0
+ else:
+ nonce[3] += 1
+
+ # xor encypted junk with data chunk
+ chunk = data[x*16:(x+1)*16] # Read 16 byte chucks. 128 bits
+
+ lchunk = chunk
+ lencChunk = list(map(ord, encChunk))
+
+ loutChunk = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ for i in range(16):
+ loutChunk[i] = lchunk[i] ^ lencChunk[i]
+ encyptedBlock = encyptedBlock + chr(lchunk[i] ^ lencChunk[i])
+
+ return (encyptedBlock)
+
+
+def aParsePassKeyString(sPassKey):
+ lstu32Passkey = [0, 0, 0, 0]
+
+ try:
+ lstStrPassKey = sPassKey.split(",")
+
+ except Exception:
+ sPassKey = "0x00000000, 0x00000000, 0x00000000, 0x00000000"
+ lstStrPassKey = sPassKey.split(",")
+
+ if len(lstStrPassKey) == 4:
+ for i in range(4):
+ if "0x" in lstStrPassKey[i]:
+ lstu32Passkey[i] = int(lstStrPassKey[i], 16)
+ else:
+ lstu32Passkey[i] = int(lstStrPassKey[i], 10)
+
+ logging.info(f"\t-key: {lstu32Passkey[0]}, {lstu32Passkey[1]}, {lstu32Passkey[2]}, {lstu32Passkey[3]}")
+ abEncryptKey = struct.pack(">LLLL", lstu32Passkey[0],
+ lstu32Passkey[1],
+ lstu32Passkey[2],
+ lstu32Passkey[3])
+ return abEncryptKey
+
+
+def aParseNonce(sNonceValue):
+ lstu32Nonce = [0, 0, 0, 0]
+
+ try:
+ lstStrNonce = sNonceValue.split(",")
+
+ except Exception:
+ sNonceValue = "0x00000000, 0x00000000, 0x00000000, 0x00000000"
+ lstStrNonce = sNonceValue.split(",")
+
+ if len(lstStrNonce) == 4:
+ for i in range(4):
+ if "0x" in lstStrNonce[i]:
+ lstu32Nonce[i] = int(lstStrNonce[i], 16)
+ else:
+ lstu32Nonce[i] = int(lstStrNonce[i], 10)
+
+ logging.info(f"Nonce : {lstu32Nonce[0]}, {lstu32Nonce[1]}, {lstu32Nonce[2]}, {lstu32Nonce[3]}")
+
+ return lstu32Nonce
+
+
+def encryptData(sSrcData, sPassKey, aPassIv):
+
+ sKeyString = sPassKey.strip()
+ assert len(sKeyString) == 32, 'the length of encryption key should be equal to 32'
+ sPassString = "0x" + sKeyString[:8] + ',' + "0x" + sKeyString[8:16] + \
+ ',' + "0x" + sKeyString[16:24] + ',' + "0x" + sKeyString[24:32]
+ aPassKey = aParsePassKeyString(sPassString)
+
+ sIvString = aPassIv.strip()
+ sPassString = "0x" + sIvString[:8] + ',' + "0x" + sIvString[8:16] + \
+ ',' + "0x" + sIvString[16:24] + ',' + "0x" + sIvString[24:32]
+ aNonce = aParseNonce(sPassString)
+
+ logging.info("Started Encrypting with key[{}] ......".format(sPassKey))
+
+ encryptedData = encryptFlashData(aNonce, aPassKey, sSrcData, len(sSrcData))
+
+ logging.info("Done")
+
+ return encryptedData
diff --git a/scripts/tools/silabs/ota/ota_image_tool.py b/scripts/tools/silabs/ota/ota_image_tool.py
new file mode 100755
index 0000000..64715d7
--- /dev/null
+++ b/scripts/tools/silabs/ota/ota_image_tool.py
@@ -0,0 +1,390 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2023 Project CHIP Authors
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+'''This file should contain a way to generate custom OTA payloads.
+
+The format of the custom payload is the following:
+| Total size of TLVs | TLV1 | ... | TLVn |
+
+The OTA payload can then be used to generate an OTA image file, which
+will be parsed by the OTA image processor. The total size of TLVs is
+needed as input for a TLVReader.
+
+Currently, this script only supports Certification Declaration update,
+but it could be modified to support all factory data fields.
+'''
+
+import argparse
+import glob
+import json
+import logging
+import os
+import sys
+
+import crypto_utils
+import jsonschema
+
+sys.path.insert(0, os.path.join(
+ os.path.dirname(__file__), '../factory_data_generator'))
+sys.path.insert(0, os.path.join(
+ os.path.dirname(__file__), '../../../../src/controller/python'))
+sys.path.insert(0, os.path.join(
+ os.path.dirname(__file__), '../../../../src/app/'))
+
+import ota_image_tool # noqa: E402 isort:skip
+from chip.tlv import TLVWriter # noqa: E402 isort:skip
+from custom import CertDeclaration, DacCert, DacPKey, PaiCert # noqa: E402 isort:skip
+from default import InputArgument # noqa: E402 isort:skip
+from generate import set_logger # noqa: E402 isort:skip
+
+OTA_APP_TLV_TEMP = os.path.join(os.path.dirname(__file__), "ota_temp_app_tlv.bin")
+OTA_BOOTLOADER_TLV_TEMP = os.path.join(os.path.dirname(__file__), "ota_temp_ssbl_tlv.bin")
+OTA_FACTORY_TLV_TEMP = os.path.join(os.path.dirname(__file__), "ota_temp_factory_tlv.bin")
+
+INITIALIZATION_VECTOR = "00000010111213141516171800000000"
+
+
+class TAG:
+ APPLICATION = 1
+ BOOTLOADER = 2
+ FACTORY_DATA = 3
+
+
+def write_to_temp(path: str, payload: bytearray):
+ with open(path, "wb") as _handle:
+ _handle.write(payload)
+
+ logging.info(f"Data payload size for {path.split('/')[-1]}: {len(payload)}")
+
+
+def generate_header(tag: int, length: int):
+ header = bytearray(tag.to_bytes(4, "little"))
+ header += bytearray(length.to_bytes(4, "little"))
+ return header
+
+
+def generate_factory_data(args: object):
+ """
+ Generate custom OTA payload from InputArgument derived objects. The payload is
+ written in a temporary file that will be appended to args.input_files.
+ """
+ fields = dict()
+
+ if args.dac_key is not None:
+ args.dac_key.generate_private_key(args.dac_key_password)
+
+ data = [obj for key, obj in vars(args).items() if isinstance(obj, InputArgument)]
+ for arg in sorted(data, key=lambda x: x.key()):
+ fields.update({arg.key(): arg.encode()})
+
+ if fields:
+ writer = TLVWriter()
+ writer.put(None, fields)
+ logging.info(f"factory data encryption enable: {args.enc_enable}")
+ if args.enc_enable:
+ enc_factory_data = crypto_utils.encryptData(writer.encoding, args.input_ota_key, INITIALIZATION_VECTOR)
+ enc_factory_data1 = bytes([ord(x) for x in enc_factory_data])
+ payload = generate_header(TAG.FACTORY_DATA, len(enc_factory_data1))
+ payload += enc_factory_data1
+ else:
+ payload = generate_header(TAG.FACTORY_DATA, len(writer.encoding))
+ payload += writer.encoding
+
+ write_to_temp(OTA_FACTORY_TLV_TEMP, payload)
+
+ return [OTA_FACTORY_TLV_TEMP]
+
+
+def generate_descriptor(version: int, versionStr: str, buildDate: str):
+ """
+ Generate descriptor as bytearray for app/SSBL payload.
+ """
+ v = version if version is not None else 50000
+ vs = versionStr if versionStr is not None else "50000-default"
+ bd = buildDate if buildDate is not None else "2023-01-01"
+
+ logging.info(f"\t-version: {v}")
+ logging.info(f"\t-version str: {vs}")
+ logging.info(f"\t-build date: {bd}")
+
+ v = v.to_bytes(4, "little")
+ vs = bytearray(vs, "ascii") + bytearray(64 - len(vs))
+ bd = bytearray(bd, "ascii") + bytearray(64 - len(bd))
+
+ return v + vs + bd
+
+
+def generate_app(args: object):
+ """
+ Generate app payload with descriptor. If a certain option is not specified, use the default values.
+ """
+ logging.info("App descriptor information:")
+
+ descriptor = generate_descriptor(args.app_version, args.app_version_str, args.app_build_date)
+ logging.info(f"App encryption enable: {args.enc_enable}")
+ if args.enc_enable:
+ inputFile = open(args.app_input_file, "rb")
+ enc_file = crypto_utils.encryptData(inputFile.read(), args.input_ota_key, INITIALIZATION_VECTOR)
+ enc_file1 = bytes([ord(x) for x in enc_file])
+ file_size = len(enc_file1)
+ payload = generate_header(TAG.APPLICATION, len(descriptor) + file_size) + descriptor + enc_file1
+ else:
+ file_size = os.path.getsize(args.app_input_file)
+ logging.info(f"file size: {file_size}")
+ payload = generate_header(TAG.APPLICATION, len(descriptor) + file_size) + descriptor
+
+ write_to_temp(OTA_APP_TLV_TEMP, payload)
+ if args.enc_enable:
+ return [OTA_APP_TLV_TEMP]
+ else:
+ return [OTA_APP_TLV_TEMP, args.app_input_file]
+
+
+def generate_bootloader(args: object):
+ """
+ Generate SSBL payload with descriptor. If a certain option is not specified, use the default values.
+ """
+ logging.info("SSBL descriptor information:")
+
+ descriptor = generate_descriptor(args.bl_version, args.bl_version_str, args.bl_build_date)
+ logging.info(f"Bootloader encryption enable: {args.enc_enable}")
+ if args.enc_enable:
+ inputFile = open(args.bl_input_file, "rb")
+ enc_file = crypto_utils.encryptData(inputFile.read(), args.input_ota_key, INITIALIZATION_VECTOR)
+ enc_file1 = bytes([ord(x) for x in enc_file])
+ file_size = len(enc_file1)
+ payload = generate_header(TAG.BOOTLOADER, len(descriptor) + file_size) + descriptor + enc_file1
+ else:
+ file_size = os.path.getsize(args.bl_input_file)
+ logging.info(f"file size: {file_size}")
+ payload = generate_header(TAG.BOOTLOADER, len(descriptor) + file_size) + descriptor
+
+ write_to_temp(OTA_BOOTLOADER_TLV_TEMP, payload)
+ if args.enc_enable:
+ return [OTA_BOOTLOADER_TLV_TEMP]
+ else:
+ return [OTA_BOOTLOADER_TLV_TEMP, args.bl_input_file]
+
+
+def validate_json(data: str):
+ with open(os.path.join(os.path.dirname(__file__), 'ota_payload.schema'), 'r') as fd:
+ payload_schema = json.load(fd)
+
+ try:
+ jsonschema.validate(instance=data, schema=payload_schema)
+ logging.info("JSON data is valid")
+ except jsonschema.exceptions.ValidationError as err:
+ logging.error(f"JSON data is invalid: {err}")
+ sys.exit(1)
+
+
+def generate_custom_tlvs(data):
+ """
+ Generate custom OTA payload from a JSON object following a predefined schema.
+ The payload is written in a temporary file that will be appended to args.input_files.
+ """
+ input_files = []
+
+ payload = bytearray()
+ descriptor = bytearray()
+ iteration = 0
+ for entry in data["inputs"]:
+ if "descriptor" in entry:
+ for field in entry["descriptor"]:
+ if isinstance(field["value"], str):
+ descriptor += bytearray(field["value"], "ascii") + bytearray(field["length"] - len(field["value"]))
+ elif isinstance(field["value"], int):
+ descriptor += bytearray(field["value"].to_bytes(field["length"], "little"))
+ file_size = os.path.getsize(entry["path"])
+ payload = generate_header(entry["tag"], len(descriptor) + file_size) + descriptor
+
+ temp_output = os.path.join(os.path.dirname(__file__), "ota_temp_custom_tlv_" + str(iteration) + ".bin")
+ write_to_temp(temp_output, payload)
+
+ input_files += [temp_output, entry["path"]]
+ iteration += 1
+ descriptor = bytearray()
+
+ return input_files
+
+
+def show_payload(args: object):
+ """
+ Parse and present OTA custom payload in human-readable form.
+ """
+ # TODO: implement to show current TLVs
+ pass
+
+
+def create_image(args: object):
+ ota_image_tool.validate_header_attributes(args)
+
+ input_files = list()
+
+ if args.json:
+ with open(args.json, 'r') as fd:
+ data = json.load(fd)
+ validate_json(data)
+ input_files += generate_custom_tlvs(data)
+
+ if args.factory_data:
+ input_files += generate_factory_data(args)
+
+ if args.bl_input_file:
+ input_files += generate_bootloader(args)
+
+ if args.app_input_file:
+ input_files += generate_app(args)
+
+ if len(input_files) == 0:
+ print("Please specify an input option.")
+ sys.exit(1)
+
+ logging.info("Input files used:")
+ [logging.info(f"\t- {_file}") for _file in input_files]
+
+ args.input_files = input_files
+ ota_image_tool.generate_image(args)
+
+ for filename in glob.glob(os.path.dirname(__file__) + "/ota_temp_*"):
+ os.remove(filename)
+ if args.enc_enable:
+ for filename in glob.glob(os.path.dirname(__file__) + "/enc_ota_temp_*"):
+ os.remove(filename)
+
+
+def main():
+ """
+ This function is a modified version of ota_image_tool.py main function.
+
+ The wrapper version defines a new set of args, which are used to generate
+ TLV data that will be embedded in the final OTA payload.
+ """
+
+ def any_base_int(s): return int(s, 0)
+
+ set_logger()
+ parser = argparse.ArgumentParser(
+ description='Matter OTA (Over-the-air update) image utility', fromfile_prefix_chars='@')
+ subcommands = parser.add_subparsers(
+ dest='subcommand', title='valid subcommands', required=True)
+
+ create_parser = subcommands.add_parser('create', help='Create OTA image')
+ create_parser.add_argument('-v', '--vendor-id', type=any_base_int,
+ required=True, help='Vendor ID')
+ create_parser.add_argument('-p', '--product-id', type=any_base_int,
+ required=True, help='Product ID')
+ create_parser.add_argument('-vn', '--version', type=any_base_int,
+ required=True, help='Software version (numeric)')
+ create_parser.add_argument('-vs', '--version-str', required=True,
+ help='Software version (string)')
+ create_parser.add_argument('-da', '--digest-algorithm', choices=ota_image_tool.DIGEST_ALL_ALGORITHMS,
+ required=True, help='Digest algorithm')
+ create_parser.add_argument('-mi', '--min-version', type=any_base_int,
+ help='Minimum software version that can be updated to this image')
+ create_parser.add_argument('-ma', '--max-version', type=any_base_int,
+ help='Maximum software version that can be updated to this image')
+ create_parser.add_argument('-rn', '--release-notes',
+ help='Release note URL')
+
+ create_parser.add_argument('-app', "--app-input-file",
+ help='Path to application input file')
+ create_parser.add_argument('--app-version', type=any_base_int,
+ help='Application Software version (numeric)')
+ create_parser.add_argument('--app-version-str', type=str,
+ help='Application Software version (string)')
+ create_parser.add_argument('--app-build-date', type=str,
+ help='Application build date (string)')
+
+ create_parser.add_argument('-bl', '--bl-input-file',
+ help='Path to input bootloader image payload file')
+ create_parser.add_argument('--bl-version', type=any_base_int,
+ help='Bootloader Software version (numeric)')
+ create_parser.add_argument('--bl-version-str', type=str,
+ help='Bootloader Software version (string)')
+ create_parser.add_argument('--bl-build-date', type=str,
+ help='Bootloader build date (string)')
+
+ # Factory data specific arguments. Will be used to generate the TLV payload.
+ create_parser.add_argument('-fd', '--factory-data', action='store_true',
+ help='If found, enable factory data payload generation.')
+ create_parser.add_argument("--cert_declaration", type=CertDeclaration,
+ help="[path] Path to Certification Declaration in DER format")
+ create_parser.add_argument("--dac_cert", type=DacCert,
+ help="[path] Path to DAC certificate in DER format")
+ create_parser.add_argument("--dac_key", type=DacPKey,
+ help="[path] Path to DAC key in DER format")
+ create_parser.add_argument("--dac_key_password", type=str,
+ help="[path] Password to decode DAC Key if available")
+ create_parser.add_argument("--pai_cert", type=PaiCert,
+ help="[path] Path to PAI certificate in DER format")
+
+ # Path to input JSON file which describes custom TLVs.
+ create_parser.add_argument('--json', help="[path] Path to the JSON describing custom TLVs")
+
+ create_parser.add_argument('--enc_enable', action="store_true", help='enable ota encryption')
+ create_parser.add_argument('--input_ota_key', type=str, default="1234567890ABCDEFA1B2C3D4E5F6F1B4",
+ help='Input OTA Encryption KEY (string:16Bytes)')
+
+ create_parser.add_argument('-i', '--input_files', default=list(),
+ help='Path to input image payload file')
+ create_parser.add_argument('output_file', help='Path to output image file')
+
+ show_parser = subcommands.add_parser('show', help='Show OTA image info')
+ show_parser.add_argument('image_file', help='Path to OTA image file')
+
+ extract_tool = subcommands.add_parser('extract', help='Remove the OTA header from an image file')
+ extract_tool.add_argument('image_file', help='Path to OTA image file with header')
+ extract_tool.add_argument('output_file', help='Path to put the output file (no header)')
+
+ change_tool = subcommands.add_parser('change_header', help='Change the specified values in the header')
+ change_tool.add_argument('-v', '--vendor-id', type=any_base_int,
+ help='Vendor ID')
+ change_tool.add_argument('-p', '--product-id', type=any_base_int,
+ help='Product ID')
+ change_tool.add_argument('-vn', '--version', type=any_base_int,
+ help='Software version (numeric)')
+ change_tool.add_argument('-vs', '--version-str',
+ help='Software version (string)')
+ change_tool.add_argument('-da', '--digest-algorithm', choices=ota_image_tool.DIGEST_ALL_ALGORITHMS,
+ help='Digest algorithm')
+ change_tool.add_argument('-mi', '--min-version', type=any_base_int,
+ help='Minimum software version that can be updated to this image')
+ change_tool.add_argument('-ma', '--max-version', type=any_base_int,
+ help='Maximum software version that can be updated to this image')
+ change_tool.add_argument(
+ '-rn', '--release-notes', help='Release note URL')
+ change_tool.add_argument('image_file',
+ help='Path to input OTA file')
+ change_tool.add_argument('output_file', help='Path to output OTA file')
+
+ args = parser.parse_args()
+
+ if args.subcommand == 'create':
+ create_image(args)
+ elif args.subcommand == 'show':
+ ota_image_tool.show_header(args)
+ show_payload(args)
+ elif args.subcommand == 'extract':
+ ota_image_tool.remove_header(args)
+ elif args.subcommand == 'change_header':
+ ota_image_tool.update_header_args(args)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/tools/silabs/ota/ota_payload.schema b/scripts/tools/silabs/ota/ota_payload.schema
new file mode 100644
index 0000000..bceacf7
--- /dev/null
+++ b/scripts/tools/silabs/ota/ota_payload.schema
@@ -0,0 +1,67 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "Custom_OTA_TLV_schema",
+ "description": "A representation of custom OTA payload with variable number of TLVs",
+ "type": "object",
+ "required": [
+ "inputs"
+ ],
+ "properties": {
+ "inputs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "tag",
+ "path"
+ ],
+ "properties": {
+ "tag": {
+ "type": "integer",
+ "description": "TLV's tag value used to select a parser"
+ },
+ "descriptor": {
+ "type": "array",
+ "description": "Metadata of the TLV value field (C struct)",
+ "items": {
+ "$ref": "#/$defs/field"
+ }
+ },
+ "path": {
+ "type": "string",
+ "description": "System path to the binary"
+ }
+ }
+ }
+ }
+ },
+ "$defs": {
+ "field": {
+ "type": "object",
+ "required": [
+ "name",
+ "length",
+ "value"
+ ],
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "length": {
+ "type": "integer",
+ "description": "Number of bytes occupied in memory"
+ },
+ "value": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "integer"
+ }
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/platform/silabs/OTAImageProcessorImpl.h b/src/platform/silabs/OTAImageProcessorImpl.h
index 46b78b0..5598b67 100644
--- a/src/platform/silabs/OTAImageProcessorImpl.h
+++ b/src/platform/silabs/OTAImageProcessorImpl.h
@@ -40,7 +40,8 @@
CHIP_ERROR ConfirmCurrentImage() override;
void SetOTADownloader(OTADownloader * downloader) { mDownloader = downloader; }
- void SetOTAImageFile(const char * imageFile) { mImageFile = imageFile; }
+ CHIP_ERROR Init(OTADownloader * downloader);
+ static OTAImageProcessorImpl & GetDefaultInstance();
private:
//////////// Actual handlers for the OTAImageProcessorInterface ///////////////
@@ -68,7 +69,6 @@
MutableByteSpan mBlock;
OTADownloader * mDownloader;
OTAImageHeaderParser mHeaderParser;
- const char * mImageFile = nullptr;
static constexpr size_t kAlignmentBytes = 64;
// Intermediate, word-aligned buffer for writing to the bootloader storage.
// Bootloader storage API requires the buffer size to be a multiple of 4.
diff --git a/src/platform/silabs/SiWx917/OTAImageProcessorImpl.cpp b/src/platform/silabs/SiWx917/OTAImageProcessorImpl.cpp
index 7e58113..d641bfa 100644
--- a/src/platform/silabs/SiWx917/OTAImageProcessorImpl.cpp
+++ b/src/platform/silabs/SiWx917/OTAImageProcessorImpl.cpp
@@ -38,6 +38,7 @@
#define SL_STATUS_FW_UPDATE_DONE SL_STATUS_SI91X_NO_AP_FOUND
uint8_t flag = RPS_HEADER;
+static chip::OTAImageProcessorImpl gImageProcessor;
namespace chip {
@@ -48,6 +49,15 @@
uint16_t OTAImageProcessorImpl::writeBufOffset = 0;
uint8_t OTAImageProcessorImpl::writeBuffer[kAlignmentBytes] __attribute__((aligned(4))) = { 0 };
+CHIP_ERROR OTAImageProcessorImpl::Init(OTADownloader * downloader)
+{
+ ReturnErrorCodeIf(downloader == nullptr, CHIP_ERROR_INVALID_ARGUMENT);
+
+ gImageProcessor.SetOTADownloader(downloader);
+
+ return CHIP_NO_ERROR;
+}
+
CHIP_ERROR OTAImageProcessorImpl::PrepareDownload()
{
DeviceLayer::PlatformMgr().ScheduleWork(HandlePrepareDownload, reinterpret_cast<intptr_t>(this));
@@ -386,4 +396,9 @@
return CHIP_NO_ERROR;
}
+OTAImageProcessorImpl & OTAImageProcessorImpl::GetDefaultInstance()
+{
+ return gImageProcessor;
+}
+
} // namespace chip
diff --git a/src/platform/silabs/efr32/BUILD.gn b/src/platform/silabs/efr32/BUILD.gn
index 83198d1..25b8e9f 100644
--- a/src/platform/silabs/efr32/BUILD.gn
+++ b/src/platform/silabs/efr32/BUILD.gn
@@ -17,6 +17,7 @@
import("${chip_root}/build/chip/buildconfig_header.gni")
import("${chip_root}/src/crypto/crypto.gni")
import("${chip_root}/src/platform/device.gni")
+import("${chip_root}/third_party/silabs/efr32_sdk.gni")
import("${chip_root}/third_party/silabs/silabs_board.gni")
silabs_platform_dir = "${chip_root}/src/platform/silabs"
@@ -77,7 +78,17 @@
sources += [ "BLEManagerImpl.cpp" ]
}
- if (chip_enable_ota_requestor) {
+ if (chip_enable_multi_ota_requestor) {
+ sources += [
+ "${silabs_platform_dir}/multi-ota/OTAMultiImageProcessorImpl.cpp",
+ "${silabs_platform_dir}/multi-ota/OTAMultiImageProcessorImpl.h",
+ "${silabs_platform_dir}/multi-ota/OTATlvProcessor.cpp",
+ "${silabs_platform_dir}/multi-ota/OTATlvProcessor.h",
+ "${silabs_platform_dir}/multi-ota/efr32/OTAFirmwareProcessor.cpp",
+ "${silabs_platform_dir}/multi-ota/efr32/OTAFirmwareProcessor.h",
+ "${silabs_platform_dir}/multi-ota/efr32/OTAHooks.cpp",
+ ]
+ } else if (chip_enable_ota_requestor) {
sources += [
"${silabs_platform_dir}/OTAImageProcessorImpl.h",
"OTAImageProcessorImpl.cpp",
diff --git a/src/platform/silabs/efr32/OTAImageProcessorImpl.cpp b/src/platform/silabs/efr32/OTAImageProcessorImpl.cpp
index a2544f8..598ee5a 100644
--- a/src/platform/silabs/efr32/OTAImageProcessorImpl.cpp
+++ b/src/platform/silabs/efr32/OTAImageProcessorImpl.cpp
@@ -33,6 +33,8 @@
/// No error, operation OK
#define SL_BOOTLOADER_OK 0L
+static chip::OTAImageProcessorImpl gImageProcessor;
+
namespace chip {
// Define static memebers
@@ -41,6 +43,15 @@
uint16_t OTAImageProcessorImpl::writeBufOffset = 0;
uint8_t OTAImageProcessorImpl::writeBuffer[kAlignmentBytes] __attribute__((aligned(4))) = { 0 };
+CHIP_ERROR OTAImageProcessorImpl::Init(OTADownloader * downloader)
+{
+ ReturnErrorCodeIf(downloader == nullptr, CHIP_ERROR_INVALID_ARGUMENT);
+
+ gImageProcessor.SetOTADownloader(downloader);
+
+ return CHIP_NO_ERROR;
+}
+
CHIP_ERROR OTAImageProcessorImpl::PrepareDownload()
{
DeviceLayer::PlatformMgr().ScheduleWork(HandlePrepareDownload, reinterpret_cast<intptr_t>(this));
@@ -400,4 +411,9 @@
return CHIP_NO_ERROR;
}
+OTAImageProcessorImpl & OTAImageProcessorImpl::GetDefaultInstance()
+{
+ return gImageProcessor;
+}
+
} // namespace chip
diff --git a/src/platform/silabs/multi-ota/OTAMultiImageProcessorImpl.cpp b/src/platform/silabs/multi-ota/OTAMultiImageProcessorImpl.cpp
new file mode 100644
index 0000000..147dcda
--- /dev/null
+++ b/src/platform/silabs/multi-ota/OTAMultiImageProcessorImpl.cpp
@@ -0,0 +1,457 @@
+/*
+ *
+ * Copyright (c) 2021-2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <app/clusters/ota-requestor/OTADownloader.h>
+#include <app/clusters/ota-requestor/OTARequestorInterface.h>
+#include <lib/support/BufferReader.h>
+#include <platform/DiagnosticDataProvider.h>
+#include <platform/internal/CHIPDeviceLayerInternal.h>
+#include <platform/internal/GenericConfigurationManagerImpl.h>
+
+#include <platform/silabs/multi-ota/OTAMultiImageProcessorImpl.h>
+
+using namespace chip::DeviceLayer;
+using namespace ::chip::DeviceLayer::Internal;
+
+static chip::OTAMultiImageProcessorImpl gImageProcessor;
+
+extern "C" {
+#include "btl_interface.h"
+#include "em_bus.h" // For CORE_CRITICAL_SECTION
+#if SL_WIFI
+#include "spi_multiplex.h"
+#endif // SL_WIFI
+}
+
+namespace chip {
+
+CHIP_ERROR OTAMultiImageProcessorImpl::Init(OTADownloader * downloader)
+{
+ ReturnErrorCodeIf(downloader == nullptr, CHIP_ERROR_INVALID_ARGUMENT);
+
+ gImageProcessor.SetOTADownloader(downloader);
+
+ OtaHookInit();
+
+ return CHIP_NO_ERROR;
+}
+
+void OTAMultiImageProcessorImpl::Clear()
+{
+ mHeaderParser.Clear();
+ mAccumulator.Clear();
+ mParams.totalFileBytes = 0;
+ mParams.downloadedBytes = 0;
+ mCurrentProcessor = nullptr;
+
+ ReleaseBlock();
+}
+
+CHIP_ERROR OTAMultiImageProcessorImpl::PrepareDownload()
+{
+ DeviceLayer::PlatformMgr().ScheduleWork(HandlePrepareDownload, reinterpret_cast<intptr_t>(this));
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR OTAMultiImageProcessorImpl::Finalize()
+{
+ DeviceLayer::PlatformMgr().ScheduleWork(HandleFinalize, reinterpret_cast<intptr_t>(this));
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR OTAMultiImageProcessorImpl::Apply()
+{
+ DeviceLayer::PlatformMgr().ScheduleWork(HandleApply, reinterpret_cast<intptr_t>(this));
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR OTAMultiImageProcessorImpl::Abort()
+{
+ DeviceLayer::PlatformMgr().ScheduleWork(HandleAbort, reinterpret_cast<intptr_t>(this));
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR OTAMultiImageProcessorImpl::ProcessBlock(ByteSpan & block)
+{
+ if ((block.data() == nullptr) || block.empty())
+ {
+ return CHIP_ERROR_INVALID_ARGUMENT;
+ }
+
+ // Store block data for HandleProcessBlock to access
+ CHIP_ERROR err = SetBlock(block);
+ if (err != CHIP_NO_ERROR)
+ {
+ ChipLogError(SoftwareUpdate, "Cannot set block data: %" CHIP_ERROR_FORMAT, err.Format());
+ }
+
+ DeviceLayer::PlatformMgr().ScheduleWork(HandleProcessBlock, reinterpret_cast<intptr_t>(this));
+ return CHIP_NO_ERROR;
+}
+
+void OTAMultiImageProcessorImpl::HandlePrepareDownload(intptr_t context)
+{
+ auto * imageProcessor = reinterpret_cast<OTAMultiImageProcessorImpl *>(context);
+
+ VerifyOrReturn(imageProcessor != nullptr, ChipLogError(SoftwareUpdate, "ImageProcessor context is null"));
+
+ VerifyOrReturn(imageProcessor->mDownloader != nullptr, ChipLogError(SoftwareUpdate, "mDownloader is null"));
+
+ ChipLogProgress(SoftwareUpdate, "HandlePrepareDownload: started");
+
+ CORE_CRITICAL_SECTION(bootloader_init();)
+
+ imageProcessor->mParams.downloadedBytes = 0;
+
+ imageProcessor->mHeaderParser.Init();
+ imageProcessor->mAccumulator.Init(sizeof(OTATlvHeader));
+ imageProcessor->mDownloader->OnPreparedForDownload(CHIP_NO_ERROR);
+}
+
+CHIP_ERROR OTAMultiImageProcessorImpl::ProcessHeader(ByteSpan & block)
+{
+ OTAImageHeader header;
+ ReturnErrorOnFailure(mHeaderParser.AccumulateAndDecode(block, header));
+
+ mParams.totalFileBytes = header.mPayloadSize;
+ mHeaderParser.Clear();
+ ChipLogError(SoftwareUpdate, "Processed header successfully");
+
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR OTAMultiImageProcessorImpl::ProcessPayload(ByteSpan & block)
+{
+ CHIP_ERROR status = CHIP_NO_ERROR;
+
+ while (true)
+ {
+ if (!mCurrentProcessor)
+ {
+ ReturnErrorOnFailure(mAccumulator.Accumulate(block));
+ ByteSpan tlvHeader{ mAccumulator.data(), sizeof(OTATlvHeader) };
+ ReturnErrorOnFailure(SelectProcessor(tlvHeader));
+ ReturnErrorOnFailure(mCurrentProcessor->Init());
+ }
+
+ status = mCurrentProcessor->Process(block);
+ if (status == CHIP_OTA_CHANGE_PROCESSOR)
+ {
+ mAccumulator.Clear();
+ mAccumulator.Init(sizeof(OTATlvHeader));
+
+ mCurrentProcessor = nullptr;
+
+ // If the block size is 0, it means that the processed data was a multiple of
+ // received BDX block size (e.g. 8 blocks of 1024 bytes were transferred).
+ // After state for selecting next processor is reset, a request for fetching next
+ // data must be sent.
+ if (block.size() == 0)
+ {
+ status = CHIP_NO_ERROR;
+ break;
+ }
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return status;
+}
+
+CHIP_ERROR OTAMultiImageProcessorImpl::SelectProcessor(ByteSpan & block)
+{
+ OTATlvHeader header;
+ Encoding::LittleEndian::Reader reader(block.data(), sizeof(header));
+
+ ReturnErrorOnFailure(reader.Read32(&header.tag).StatusCode());
+ ReturnErrorOnFailure(reader.Read32(&header.length).StatusCode());
+
+ auto pair = mProcessorMap.find(header.tag);
+ if (pair == mProcessorMap.end())
+ {
+ ChipLogError(SoftwareUpdate, "There is no registered processor for tag: %lu", header.tag);
+ return CHIP_OTA_PROCESSOR_NOT_REGISTERED;
+ }
+
+ ChipLogDetail(SoftwareUpdate, "Selected processor with tag: %lu", pair->first);
+ mCurrentProcessor = pair->second;
+ mCurrentProcessor->SetLength(header.length);
+ mCurrentProcessor->SetWasSelected(true);
+
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR OTAMultiImageProcessorImpl::RegisterProcessor(uint32_t tag, OTATlvProcessor * processor)
+{
+ auto pair = mProcessorMap.find(tag);
+ if (pair != mProcessorMap.end())
+ {
+ ChipLogError(SoftwareUpdate, "A processor for tag %lu is already registered.", tag);
+ return CHIP_OTA_PROCESSOR_ALREADY_REGISTERED;
+ }
+
+ mProcessorMap.insert({ tag, processor });
+
+ return CHIP_NO_ERROR;
+}
+
+void OTAMultiImageProcessorImpl::HandleAbort(intptr_t context)
+{
+ ChipLogError(SoftwareUpdate, "OTA was aborted");
+ auto * imageProcessor = reinterpret_cast<OTAMultiImageProcessorImpl *>(context);
+ if (imageProcessor != nullptr)
+ {
+ imageProcessor->AbortAllProcessors();
+ }
+ imageProcessor->Clear();
+}
+
+void OTAMultiImageProcessorImpl::HandleProcessBlock(intptr_t context)
+{
+ auto * imageProcessor = reinterpret_cast<OTAMultiImageProcessorImpl *>(context);
+
+ VerifyOrReturn(imageProcessor != nullptr, ChipLogError(SoftwareUpdate, "ImageProcessor context is null"));
+
+ VerifyOrReturn(imageProcessor->mDownloader != nullptr, ChipLogError(SoftwareUpdate, "mDownloader is null"));
+
+ CHIP_ERROR status;
+ auto block = ByteSpan(imageProcessor->mBlock.data(), imageProcessor->mBlock.size());
+
+ if (imageProcessor->mHeaderParser.IsInitialized())
+ {
+ status = imageProcessor->ProcessHeader(block);
+ if (status != CHIP_NO_ERROR)
+ {
+ imageProcessor->HandleStatus(status);
+ }
+ }
+
+ status = imageProcessor->ProcessPayload(block);
+ imageProcessor->HandleStatus(status);
+}
+
+void OTAMultiImageProcessorImpl::HandleStatus(CHIP_ERROR status)
+{
+ if (status == CHIP_NO_ERROR || status == CHIP_ERROR_BUFFER_TOO_SMALL)
+ {
+ mParams.downloadedBytes += mBlock.size();
+ FetchNextData(0);
+ }
+ else if (status == CHIP_OTA_FETCH_ALREADY_SCHEDULED)
+ {
+ mParams.downloadedBytes += mBlock.size();
+ }
+ else
+ {
+ ChipLogError(SoftwareUpdate, "Image update canceled. Failed to process OTA block: %s", ErrorStr(status));
+ GetRequestorInstance()->CancelImageUpdate();
+ }
+}
+
+void OTAMultiImageProcessorImpl::AbortAllProcessors()
+{
+ ChipLogError(SoftwareUpdate, "All selected processors will call abort action");
+
+ for (auto const & pair : mProcessorMap)
+ {
+ if (pair.second->WasSelected())
+ {
+ pair.second->Clear();
+ pair.second->SetWasSelected(false);
+ }
+ }
+}
+
+bool OTAMultiImageProcessorImpl::IsFirstImageRun()
+{
+ OTARequestorInterface * requestor = chip::GetRequestorInstance();
+ if (requestor == nullptr)
+ {
+ return false;
+ }
+
+ return requestor->GetCurrentUpdateState() == OTARequestorInterface::OTAUpdateStateEnum::kApplying;
+}
+
+CHIP_ERROR OTAMultiImageProcessorImpl::ConfirmCurrentImage()
+{
+ uint32_t currentVersion;
+ uint32_t targetVersion;
+
+ OTARequestorInterface * requestor = chip::GetRequestorInstance();
+ ReturnErrorCodeIf(requestor == nullptr, CHIP_ERROR_INTERNAL);
+
+ targetVersion = requestor->GetTargetVersion();
+ ReturnErrorOnFailure(DeviceLayer::ConfigurationMgr().GetSoftwareVersion(currentVersion));
+ if (currentVersion != targetVersion)
+ {
+ ChipLogError(SoftwareUpdate, "Current sw version %lu is different than the expected sw version = %lu", currentVersion,
+ targetVersion);
+ return CHIP_ERROR_INCORRECT_STATE;
+ }
+
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR OTAMultiImageProcessorImpl::SetBlock(ByteSpan & block)
+{
+ if (block.empty())
+ {
+ return CHIP_NO_ERROR;
+ }
+
+ if (mBlock.size() < block.size())
+ {
+ if (!mBlock.empty())
+ {
+ ReleaseBlock();
+ }
+ uint8_t * mBlock_ptr = static_cast<uint8_t *>(chip::Platform::MemoryAlloc(block.size()));
+ if (mBlock_ptr == nullptr)
+ {
+ return CHIP_ERROR_NO_MEMORY;
+ }
+ mBlock = MutableByteSpan(mBlock_ptr, block.size());
+ }
+
+ CHIP_ERROR err = CopySpanToMutableSpan(block, mBlock);
+ if (err != CHIP_NO_ERROR)
+ {
+ ChipLogError(SoftwareUpdate, "Cannot copy block data: %" CHIP_ERROR_FORMAT, err.Format());
+ return err;
+ }
+ return CHIP_NO_ERROR;
+}
+
+void OTAMultiImageProcessorImpl::HandleFinalize(intptr_t context)
+{
+ ChipLogError(SoftwareUpdate, "HandleFinalize begin");
+ CHIP_ERROR error = CHIP_NO_ERROR;
+ auto * imageProcessor = reinterpret_cast<OTAMultiImageProcessorImpl *>(context);
+ if (imageProcessor == nullptr)
+ {
+ return;
+ }
+
+ error = imageProcessor->ProcessFinalize();
+
+ imageProcessor->mParams.downloadedBytes += imageProcessor->mBlock.size();
+
+ imageProcessor->ReleaseBlock();
+
+ if (error != CHIP_NO_ERROR)
+ {
+ ChipLogError(SoftwareUpdate, "ProcessFinalize() error");
+ imageProcessor->mDownloader->EndDownload(CHIP_ERROR_WRITE_FAILED);
+ return;
+ }
+
+ ChipLogProgress(SoftwareUpdate, "OTA image downloaded successfully");
+}
+
+CHIP_ERROR OTAMultiImageProcessorImpl::ProcessFinalize()
+{
+ for (auto const & pair : this->mProcessorMap)
+ {
+ pair.second->FinalizeAction();
+ }
+ return CHIP_NO_ERROR;
+}
+
+void OTAMultiImageProcessorImpl::HandleApply(intptr_t context)
+{
+ CHIP_ERROR error = CHIP_NO_ERROR;
+ auto * imageProcessor = reinterpret_cast<OTAMultiImageProcessorImpl *>(context);
+
+ ChipLogProgress(SoftwareUpdate, "HandleApply: started");
+
+ // Force KVS to store pending keys such as data from StoreCurrentUpdateInfo()
+ chip::DeviceLayer::PersistedStorage::KeyValueStoreMgrImpl().ForceKeyMapSave();
+
+ if (imageProcessor == nullptr)
+ {
+ return;
+ }
+
+ for (auto const & pair : imageProcessor->mProcessorMap)
+ {
+ if (pair.second->WasSelected())
+ {
+ error = pair.second->ApplyAction();
+ if (error != CHIP_NO_ERROR)
+ {
+ ChipLogError(SoftwareUpdate, "Apply action for tag %d processor failed.", (uint8_t) pair.first);
+ // Revert all previously applied actions if current apply action fails.
+ // Reset image processor and requestor states.
+ imageProcessor->AbortAllProcessors();
+ imageProcessor->Clear();
+ GetRequestorInstance()->Reset();
+
+ return;
+ }
+ }
+ }
+
+ for (auto const & pair : imageProcessor->mProcessorMap)
+ {
+ pair.second->Clear();
+ pair.second->SetWasSelected(false);
+ }
+
+ imageProcessor->mAccumulator.Clear();
+
+ ChipLogProgress(SoftwareUpdate, "HandleApply: Finished");
+
+ // TODO: check where to put this
+ // ConfigurationManagerImpl().StoreSoftwareUpdateCompleted();
+
+ // This reboots the device
+ CORE_CRITICAL_SECTION(bootloader_rebootAndInstall();)
+}
+
+CHIP_ERROR OTAMultiImageProcessorImpl::ReleaseBlock()
+{
+ if (mBlock.data() != nullptr)
+ {
+ chip::Platform::MemoryFree(mBlock.data());
+ }
+
+ mBlock = MutableByteSpan();
+ return CHIP_NO_ERROR;
+}
+
+void OTAMultiImageProcessorImpl::FetchNextData(uint32_t context)
+{
+ auto * imageProcessor = &OTAMultiImageProcessorImpl::GetDefaultInstance();
+ SystemLayer().ScheduleLambda([imageProcessor] {
+ if (imageProcessor->mDownloader)
+ {
+ imageProcessor->mDownloader->FetchNextData();
+ }
+ });
+}
+
+OTAMultiImageProcessorImpl & OTAMultiImageProcessorImpl::GetDefaultInstance()
+{
+ return gImageProcessor;
+}
+
+} // namespace chip
diff --git a/src/platform/silabs/multi-ota/OTAMultiImageProcessorImpl.h b/src/platform/silabs/multi-ota/OTAMultiImageProcessorImpl.h
new file mode 100644
index 0000000..3e9cf0d
--- /dev/null
+++ b/src/platform/silabs/multi-ota/OTAMultiImageProcessorImpl.h
@@ -0,0 +1,99 @@
+/*
+ *
+ * Copyright (c) 2021-2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <app/clusters/ota-requestor/OTADownloader.h>
+#include <app/clusters/ota-requestor/OTARequestorInterface.h>
+#include <include/platform/CHIPDeviceLayer.h>
+#include <include/platform/OTAImageProcessor.h>
+#include <lib/core/OTAImageHeader.h>
+#include <map>
+#include <platform/silabs/multi-ota/OTATlvProcessor.h>
+
+/*
+ * This hook is called at the end of OTAMultiImageProcessorImpl::Init.
+ * It should generally register the OTATlvProcessor instances.
+ */
+
+namespace chip {
+
+class OTAMultiImageProcessorImpl : public OTAImageProcessorInterface
+{
+public:
+ using ProviderLocation = chip::OTARequestorInterface::ProviderLocationType;
+
+ CHIP_ERROR Init(OTADownloader * downloader);
+ CHIP_ERROR OtaHookInit();
+ static CHIP_ERROR ProcessDescriptor(void * descriptor);
+ void Clear();
+
+ //////////// OTAImageProcessorInterface Implementation ///////////////
+ CHIP_ERROR PrepareDownload() override;
+ CHIP_ERROR Finalize() override;
+ CHIP_ERROR Apply() override;
+ CHIP_ERROR Abort() override;
+ CHIP_ERROR ProcessBlock(ByteSpan & block) override;
+ bool IsFirstImageRun() override;
+ CHIP_ERROR ConfirmCurrentImage() override;
+
+ void SetOTADownloader(OTADownloader * downloader) { mDownloader = downloader; }
+
+ CHIP_ERROR ProcessHeader(ByteSpan & block);
+ CHIP_ERROR ProcessPayload(ByteSpan & block);
+ CHIP_ERROR ProcessFinalize();
+ CHIP_ERROR SelectProcessor(ByteSpan & block);
+ CHIP_ERROR RegisterProcessor(uint32_t tag, OTATlvProcessor * processor);
+
+ static void FetchNextData(uint32_t context);
+ static OTAMultiImageProcessorImpl & GetDefaultInstance();
+
+private:
+ //////////// Actual handlers for the OTAImageProcessorInterface ///////////////
+ static void HandlePrepareDownload(intptr_t context);
+ static void HandleFinalize(intptr_t context);
+ static void HandleApply(intptr_t context);
+ static void HandleAbort(intptr_t context);
+ static void HandleProcessBlock(intptr_t context);
+
+ void HandleStatus(CHIP_ERROR status);
+
+ /**
+ * Called to allocate memory for mBlock if necessary and set it to block
+ */
+ CHIP_ERROR SetBlock(ByteSpan & block);
+
+ /**
+ * Called to release allocated memory for mBlock
+ */
+ CHIP_ERROR ReleaseBlock();
+
+ /**
+ * Call AbortAction for all processors that were used
+ */
+ void AbortAllProcessors();
+
+ MutableByteSpan mBlock;
+ OTADownloader * mDownloader;
+ OTAImageHeaderParser mHeaderParser;
+ OTATlvProcessor * mCurrentProcessor = nullptr;
+ OTADataAccumulator mAccumulator;
+ std::map<uint32_t, OTATlvProcessor *> mProcessorMap;
+};
+
+} // namespace chip
diff --git a/src/platform/silabs/multi-ota/OTATlvProcessor.cpp b/src/platform/silabs/multi-ota/OTATlvProcessor.cpp
new file mode 100644
index 0000000..a5da7ea
--- /dev/null
+++ b/src/platform/silabs/multi-ota/OTATlvProcessor.cpp
@@ -0,0 +1,171 @@
+/*
+ *
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <lib/core/TLV.h>
+#include <lib/support/BufferReader.h>
+#include <platform/internal/CHIPDeviceLayerInternal.h>
+
+#include <platform/silabs/multi-ota/OTAMultiImageProcessorImpl.h>
+#include <platform/silabs/multi-ota/OTATlvProcessor.h>
+#if OTA_ENCRYPTION_ENABLE
+#include "OtaUtils.h"
+#include "rom_aes.h"
+#endif
+namespace chip {
+
+#if OTA_ENCRYPTION_ENABLE
+constexpr uint8_t au8Iv[] = { 0x00, 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x00, 0x00, 0x00, 0x00 };
+#endif
+CHIP_ERROR OTATlvProcessor::Process(ByteSpan & block)
+{
+ CHIP_ERROR status = CHIP_NO_ERROR;
+ uint32_t bytes = chip::min(mLength - mProcessedLength, static_cast<uint32_t>(block.size()));
+ ByteSpan relevantData = block.SubSpan(0, bytes);
+
+ status = ProcessInternal(relevantData);
+ if (!IsError(status))
+ {
+ mProcessedLength += bytes;
+ block = block.SubSpan(bytes);
+ if (mProcessedLength == mLength)
+ {
+ status = ExitAction();
+ if (!IsError(status))
+ {
+ // If current block was processed fully and the block still contains data, it
+ // means that the block contains another TLV's data and the current processor
+ // should be changed by OTAMultiImageProcessorImpl.
+ return CHIP_OTA_CHANGE_PROCESSOR;
+ }
+ }
+ }
+
+ return status;
+}
+
+void OTATlvProcessor::ClearInternal()
+{
+ mLength = 0;
+ mProcessedLength = 0;
+ mWasSelected = false;
+#if OTA_ENCRYPTION_ENABLE
+ mIVOffset = 0;
+#endif
+}
+
+bool OTATlvProcessor::IsError(CHIP_ERROR & status)
+{
+ return status != CHIP_NO_ERROR && status != CHIP_ERROR_BUFFER_TOO_SMALL && status != CHIP_OTA_FETCH_ALREADY_SCHEDULED;
+}
+
+void OTADataAccumulator::Init(uint32_t threshold)
+{
+ mThreshold = threshold;
+ mBufferOffset = 0;
+ mBuffer.Alloc(mThreshold);
+}
+
+void OTADataAccumulator::Clear()
+{
+ mThreshold = 0;
+ mBufferOffset = 0;
+ mBuffer.Free();
+}
+
+CHIP_ERROR OTADataAccumulator::Accumulate(ByteSpan & block)
+{
+ uint32_t numBytes = chip::min(mThreshold - mBufferOffset, static_cast<uint32_t>(block.size()));
+ memcpy(&mBuffer[mBufferOffset], block.data(), numBytes);
+ mBufferOffset += numBytes;
+ block = block.SubSpan(numBytes);
+
+ if (mBufferOffset < mThreshold)
+ {
+ return CHIP_ERROR_BUFFER_TOO_SMALL;
+ }
+
+ return CHIP_NO_ERROR;
+}
+
+#if OTA_ENCRYPTION_ENABLE
+CHIP_ERROR OTATlvProcessor::vOtaProcessInternalEncryption(MutableByteSpan & block)
+{
+ uint8_t iv[16];
+ uint8_t key[kOTAEncryptionKeyLength];
+ uint8_t dataOut[16] = { 0 };
+ uint32_t u32IVCount;
+ uint32_t Offset = 0;
+ uint8_t data;
+ tsReg128 sKey;
+ aesContext_t Context;
+
+ memcpy(iv, au8Iv, sizeof(au8Iv));
+
+ u32IVCount = (((uint32_t) iv[12]) << 24) | (((uint32_t) iv[13]) << 16) | (((uint32_t) iv[14]) << 8) | (iv[15]);
+ u32IVCount += (mIVOffset >> 4);
+
+ iv[12] = (uint8_t) ((u32IVCount >> 24) & 0xff);
+ iv[13] = (uint8_t) ((u32IVCount >> 16) & 0xff);
+ iv[14] = (uint8_t) ((u32IVCount >> 8) & 0xff);
+ iv[15] = (uint8_t) (u32IVCount & 0xff);
+
+ if (Encoding::HexToBytes(OTA_ENCRYPTION_KEY, strlen(OTA_ENCRYPTION_KEY), key, kOTAEncryptionKeyLength) !=
+ kOTAEncryptionKeyLength)
+ {
+ // Failed to convert the OTAEncryptionKey string to octstr type value
+ return CHIP_ERROR_INVALID_STRING_LENGTH;
+ }
+
+ ByteSpan KEY = ByteSpan(key);
+ Encoding::LittleEndian::Reader reader_key(KEY.data(), KEY.size());
+ ReturnErrorOnFailure(reader_key.Read32(&sKey.u32register0)
+ .Read32(&sKey.u32register1)
+ .Read32(&sKey.u32register2)
+ .Read32(&sKey.u32register3)
+ .StatusCode());
+
+ while (Offset + 16 <= block.size())
+ {
+ /*Encrypt the IV*/
+ Context.mode = AES_MODE_ECB_ENCRYPT;
+ Context.pSoftwareKey = (uint32_t *) &sKey;
+ AES_128_ProcessBlocks(&Context, (uint32_t *) &iv[0], (uint32_t *) &dataOut[0], 1);
+
+ /* Decrypt a block of the buffer */
+ for (uint8_t i = 0; i < 16; i++)
+ {
+ data = block[Offset + i] ^ dataOut[i];
+ memcpy(&block[Offset + i], &data, sizeof(uint8_t));
+ }
+
+ /* increment the IV for the next block */
+ u32IVCount++;
+
+ iv[12] = (uint8_t) ((u32IVCount >> 24) & 0xff);
+ iv[13] = (uint8_t) ((u32IVCount >> 16) & 0xff);
+ iv[14] = (uint8_t) ((u32IVCount >> 8) & 0xff);
+ iv[15] = (uint8_t) (u32IVCount & 0xff);
+
+ Offset += 16; /* increment the buffer offset */
+ mIVOffset += 16;
+ }
+
+ return CHIP_NO_ERROR;
+}
+#endif
+} // namespace chip
diff --git a/src/platform/silabs/multi-ota/OTATlvProcessor.h b/src/platform/silabs/multi-ota/OTATlvProcessor.h
new file mode 100644
index 0000000..9e8e56a
--- /dev/null
+++ b/src/platform/silabs/multi-ota/OTATlvProcessor.h
@@ -0,0 +1,159 @@
+/*
+ *
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <lib/core/Optional.h>
+#include <lib/support/ScopedBuffer.h>
+#include <lib/support/Span.h>
+
+namespace chip {
+
+#define CHIP_ERROR_TLV_PROCESSOR(e) \
+ ChipError(ChipError::Range::kLastRange, ((uint8_t) ChipError::Range::kLastRange << 3) | e, __FILE__, __LINE__)
+
+#define CHIP_OTA_TLV_CONTINUE_PROCESSING CHIP_ERROR_TLV_PROCESSOR(0x01)
+#define CHIP_OTA_CHANGE_PROCESSOR CHIP_ERROR_TLV_PROCESSOR(0x02)
+#define CHIP_OTA_PROCESSOR_NOT_REGISTERED CHIP_ERROR_TLV_PROCESSOR(0x03)
+#define CHIP_OTA_PROCESSOR_ALREADY_REGISTERED CHIP_ERROR_TLV_PROCESSOR(0x04)
+#define CHIP_OTA_PROCESSOR_CLIENT_INIT CHIP_ERROR_TLV_PROCESSOR(0x05)
+#define CHIP_OTA_PROCESSOR_MAKE_ROOM CHIP_ERROR_TLV_PROCESSOR(0x06)
+#define CHIP_OTA_PROCESSOR_PUSH_CHUNK CHIP_ERROR_TLV_PROCESSOR(0x07)
+#define CHIP_OTA_PROCESSOR_IMG_AUTH CHIP_ERROR_TLV_PROCESSOR(0x08)
+#define CHIP_OTA_FETCH_ALREADY_SCHEDULED CHIP_ERROR_TLV_PROCESSOR(0x09)
+#define CHIP_OTA_PROCESSOR_IMG_COMMIT CHIP_ERROR_TLV_PROCESSOR(0x0A)
+#define CHIP_OTA_PROCESSOR_CB_NOT_REGISTERED CHIP_ERROR_TLV_PROCESSOR(0x0B)
+#define CHIP_OTA_PROCESSOR_EEPROM_OFFSET CHIP_ERROR_TLV_PROCESSOR(0x0C)
+#define CHIP_OTA_PROCESSOR_EXTERNAL_STORAGE CHIP_ERROR_TLV_PROCESSOR(0x0D)
+#define CHIP_OTA_PROCESSOR_START_IMAGE CHIP_ERROR_TLV_PROCESSOR(0x0E)
+#define SL_GENERIC_OTA_ERROR CHIP_ERROR_TLV_PROCESSOR(0x0E)
+
+// Descriptor constants
+inline constexpr size_t kVersionStringSize = 64;
+inline constexpr size_t kBuildDateSize = 64;
+
+/**
+ * Used alongside RegisterDescriptorCallback to register
+ * a custom descriptor processing function with a certain
+ * TLV processor.
+ */
+typedef CHIP_ERROR (*ProcessDescriptor)(void * descriptor);
+
+struct OTATlvHeader
+{
+ uint32_t tag;
+ uint32_t length;
+};
+
+/**
+ * This class defines an interface for a Matter TLV processor.
+ * Instances of derived classes can be registered as processors
+ * in OTAMultiImageProcessorImpl. Based on the TLV type, a certain
+ * processor is used to process subsequent blocks until the number
+ * of bytes found in the metadata is processed. In case a block contains
+ * data from two different TLVs, the processor should ensure the remaining
+ * data is returned in the block passed as input.
+ * The default processors: application, SSBL and factory data are registered
+ * in OTAMultiImageProcessorImpl::Init through OtaHookInit.
+ * Applications should use OTAMultiImageProcessorImpl::RegisterProcessor
+ * to register additional processors.
+ */
+class OTATlvProcessor
+{
+public:
+ virtual ~OTATlvProcessor() {}
+
+ virtual CHIP_ERROR Init() = 0;
+ virtual CHIP_ERROR Clear() = 0;
+ virtual CHIP_ERROR ApplyAction() = 0;
+ virtual CHIP_ERROR FinalizeAction() = 0;
+ virtual CHIP_ERROR ExitAction() { return CHIP_NO_ERROR; }
+
+ CHIP_ERROR Process(ByteSpan & block);
+ void RegisterDescriptorCallback(ProcessDescriptor callback) { mCallbackProcessDescriptor = callback; }
+ void SetLength(uint32_t length) { mLength = length; }
+ void SetWasSelected(bool selected) { mWasSelected = selected; }
+ bool WasSelected() { return mWasSelected; }
+#if OTA_ENCRYPTION_ENABLE
+ CHIP_ERROR vOtaProcessInternalEncryption(MutableByteSpan & block);
+#endif
+
+protected:
+ /**
+ * @brief Process custom TLV payload
+ *
+ * The method takes subsequent chunks of the Matter OTA image file and processes them.
+ * If more image chunks are needed, CHIP_ERROR_BUFFER_TOO_SMALL error is returned.
+ * Other error codes indicate that an error occurred during processing. Fetching
+ * next data is scheduled automatically by OTAMultiImageProcessorImpl if the return value
+ * is neither an error code, nor CHIP_OTA_FETCH_ALREADY_SCHEDULED (which implies the
+ * scheduling is done inside ProcessInternal or will be done in the future, through a
+ * callback).
+ *
+ * @param block Byte span containing a subsequent Matter OTA image chunk. When the method
+ * returns CHIP_NO_ERROR, the byte span is used to return a remaining part
+ * of the chunk, not used by current TLV processor.
+ *
+ * @retval CHIP_NO_ERROR Block was processed successfully.
+ * @retval CHIP_ERROR_BUFFER_TOO_SMALL Provided buffers are insufficient to decode some
+ * metadata (e.g. a descriptor).
+ * @retval CHIP_OTA_FETCH_ALREADY_SCHEDULED Should be returned if ProcessInternal schedules
+ * fetching next data (e.g. through a callback).
+ * @retval Error code Something went wrong. Current OTA process will be
+ * canceled.
+ */
+ virtual CHIP_ERROR ProcessInternal(ByteSpan & block) = 0;
+
+ void ClearInternal();
+
+ bool IsError(CHIP_ERROR & status);
+
+#if OTA_ENCRYPTION_ENABLE
+ /*ota decryption*/
+ uint32_t mIVOffset = 0;
+ /* Expected byte size of the OTAEncryptionKeyLength */
+ static constexpr size_t kOTAEncryptionKeyLength = 16;
+#endif
+ uint32_t mLength = 0;
+ uint32_t mProcessedLength = 0;
+ bool mWasSelected = false;
+ ProcessDescriptor mCallbackProcessDescriptor = nullptr;
+};
+
+/**
+ * This class can be used to accumulate data until a given threshold.
+ * Should be used by OTATlvProcessor derived classes if they need
+ * metadata accumulation (e.g. for custom header decoding).
+ */
+class OTADataAccumulator
+{
+public:
+ void Init(uint32_t threshold);
+ void Clear();
+ CHIP_ERROR Accumulate(ByteSpan & block);
+
+ inline uint8_t * data() { return mBuffer.Get(); }
+ inline uint32_t GetThreshold() { return mThreshold; }
+
+private:
+ uint32_t mThreshold;
+ uint32_t mBufferOffset;
+ Platform::ScopedMemoryBuffer<uint8_t> mBuffer;
+};
+
+} // namespace chip
diff --git a/src/platform/silabs/multi-ota/efr32/OTAFirmwareProcessor.cpp b/src/platform/silabs/multi-ota/efr32/OTAFirmwareProcessor.cpp
new file mode 100644
index 0000000..fdf4c3d
--- /dev/null
+++ b/src/platform/silabs/multi-ota/efr32/OTAFirmwareProcessor.cpp
@@ -0,0 +1,224 @@
+/*
+ *
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <platform/internal/CHIPDeviceLayerInternal.h>
+#include <platform/silabs/multi-ota/OTAMultiImageProcessorImpl.h>
+#include <platform/silabs/multi-ota/efr32/OTAFirmwareProcessor.h>
+
+#include <app/clusters/ota-requestor/OTARequestorInterface.h>
+
+extern "C" {
+#include "btl_interface.h"
+#include "em_bus.h" // For CORE_CRITICAL_SECTION
+#if SL_WIFI
+#include "spi_multiplex.h"
+#endif // SL_WIFI
+}
+
+/// No error, operation OK
+#define SL_BOOTLOADER_OK 0L
+// TODO: more descriptive error codes
+#define SL_OTA_ERROR 1L
+
+namespace chip {
+
+// Define static memebers
+uint8_t OTAFirmwareProcessor::mSlotId = 0;
+uint32_t OTAFirmwareProcessor::mWriteOffset = 0;
+uint16_t OTAFirmwareProcessor::writeBufOffset = 0;
+uint8_t OTAFirmwareProcessor::writeBuffer[kAlignmentBytes] __attribute__((aligned(4))) = { 0 };
+
+CHIP_ERROR OTAFirmwareProcessor::Init()
+{
+ ReturnErrorCodeIf(mCallbackProcessDescriptor == nullptr, CHIP_OTA_PROCESSOR_CB_NOT_REGISTERED);
+ mAccumulator.Init(sizeof(Descriptor));
+#if OTA_ENCRYPTION_ENABLE
+ mUnalignmentNum = 0;
+#endif
+
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR OTAFirmwareProcessor::Clear()
+{
+ OTATlvProcessor::ClearInternal();
+ mAccumulator.Clear();
+ mDescriptorProcessed = false;
+#if OTA_ENCRYPTION_ENABLE
+ mUnalignmentNum = 0;
+#endif
+
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR OTAFirmwareProcessor::ProcessInternal(ByteSpan & block)
+{
+ uint32_t err = SL_BOOTLOADER_OK;
+ if (!mDescriptorProcessed)
+ {
+ ReturnErrorOnFailure(ProcessDescriptor(block));
+ }
+
+ uint32_t blockReadOffset = 0;
+ while (blockReadOffset < block.size())
+ {
+ writeBuffer[writeBufOffset] = *((block.data()) + blockReadOffset);
+ writeBufOffset++;
+ blockReadOffset++;
+ if (writeBufOffset == kAlignmentBytes)
+ {
+ writeBufOffset = 0;
+#if SL_BTLCTRL_MUX
+ err = sl_wfx_host_pre_bootloader_spi_transfer();
+ if (err != SL_STATUS_OK)
+ {
+ ChipLogError(SoftwareUpdate, "sl_wfx_host_pre_bootloader_spi_transfer() error: %ld", err);
+ return;
+ }
+#endif // SL_BTLCTRL_MUX
+ CORE_CRITICAL_SECTION(err = bootloader_eraseWriteStorage(mSlotId, mWriteOffset, writeBuffer, kAlignmentBytes);)
+#if SL_BTLCTRL_MUX
+ err = sl_wfx_host_post_bootloader_spi_transfer();
+ if (err != SL_STATUS_OK)
+ {
+ ChipLogError(SoftwareUpdate, "sl_wfx_host_post_bootloader_spi_transfer() error: %ld", err);
+ return;
+ }
+#endif // SL_BTLCTRL_MUX
+ if (err)
+ {
+ ChipLogError(SoftwareUpdate, "bootloader_eraseWriteStorage() error: %ld", err);
+ // TODO: add this somewhere
+ // imageProcessor->mDownloader->EndDownload(CHIP_ERROR_WRITE_FAILED);
+ // TODO: Replace CHIP_ERROR_CANCELLED with new error statement
+ return CHIP_ERROR_CANCELLED;
+ }
+ mWriteOffset += kAlignmentBytes;
+ }
+ }
+
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR OTAFirmwareProcessor::ProcessDescriptor(ByteSpan & block)
+{
+ ReturnErrorOnFailure(mAccumulator.Accumulate(block));
+ ReturnErrorOnFailure(mCallbackProcessDescriptor(static_cast<void *>(mAccumulator.data())));
+
+ mDescriptorProcessed = true;
+ mAccumulator.Clear();
+
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR OTAFirmwareProcessor::ApplyAction()
+{
+ uint32_t err = SL_BOOTLOADER_OK;
+
+#if SL_BTLCTRL_MUX
+ err = sl_wfx_host_pre_bootloader_spi_transfer();
+ if (err != SL_STATUS_OK)
+ {
+ ChipLogError(SoftwareUpdate, "sl_wfx_host_pre_bootloader_spi_transfer() error: %ld", err);
+ return SL_GENERIC_OTA_ERROR;
+ }
+#endif // SL_BTLCTRL_MUX
+ CORE_CRITICAL_SECTION(err = bootloader_verifyImage(mSlotId, NULL);)
+ if (err != SL_BOOTLOADER_OK)
+ {
+ ChipLogError(SoftwareUpdate, "bootloader_verifyImage() error: %ld", err);
+ // Call the OTARequestor API to reset the state
+ GetRequestorInstance()->CancelImageUpdate();
+#if SL_BTLCTRL_MUX
+ err = sl_wfx_host_post_bootloader_spi_transfer();
+ if (err != SL_STATUS_OK)
+ {
+ ChipLogError(SoftwareUpdate, "sl_wfx_host_post_bootloader_spi_transfer() error: %ld", err);
+ return SL_GENERIC_OTA_ERROR;
+ }
+#endif // SL_BTLCTRL_MUX
+ return SL_GENERIC_OTA_ERROR;
+ }
+
+ CORE_CRITICAL_SECTION(err = bootloader_setImageToBootload(mSlotId);)
+ if (err != SL_BOOTLOADER_OK)
+ {
+ ChipLogError(SoftwareUpdate, "bootloader_setImageToBootload() error: %ld", err);
+ // Call the OTARequestor API to reset the state
+ GetRequestorInstance()->CancelImageUpdate();
+#if SL_BTLCTRL_MUX
+ err = sl_wfx_host_post_bootloader_spi_transfer();
+ if (err != SL_STATUS_OK)
+ {
+ ChipLogError(SoftwareUpdate, "sl_wfx_host_post_bootloader_spi_transfer() error: %ld", err);
+ return SL_GENERIC_OTA_ERROR;
+ }
+#endif // SL_BTLCTRL_MUX
+ return SL_GENERIC_OTA_ERROR;
+ }
+
+#if SL_BTLCTRL_MUX
+ err = sl_wfx_host_post_bootloader_spi_transfer();
+ if (err != SL_STATUS_OK)
+ {
+ ChipLogError(SoftwareUpdate, "sl_wfx_host_post_bootloader_spi_transfer() error: %ld", err);
+ return SL_GENERIC_OTA_ERROR;
+ }
+#endif // SL_BTLCTRL_MUX
+ // This reboots the device
+ // CORE_CRITICAL_SECTION(bootloader_rebootAndInstall();)
+
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR OTAFirmwareProcessor::FinalizeAction()
+{
+ uint32_t err = SL_BOOTLOADER_OK;
+
+ // Pad the remainder of the write buffer with zeros and write it to bootloader storage
+ if (writeBufOffset != 0)
+ {
+
+ while (writeBufOffset != kAlignmentBytes)
+ {
+ writeBuffer[writeBufOffset] = 0;
+ writeBufOffset++;
+ }
+#if SL_BTLCTRL_MUX
+ err = sl_wfx_host_pre_bootloader_spi_transfer();
+ if (err != SL_STATUS_OK)
+ {
+ ChipLogError(SoftwareUpdate, "sl_wfx_host_pre_bootloader_spi_transfer() error: %ld", err);
+ return SL_GENERIC_OTA_ERROR;
+ }
+#endif // SL_BTLCTRL_MUX
+ CORE_CRITICAL_SECTION(err = bootloader_eraseWriteStorage(mSlotId, mWriteOffset, writeBuffer, kAlignmentBytes);)
+#if SL_BTLCTRL_MUX
+ err = sl_wfx_host_post_bootloader_spi_transfer();
+ if (err != SL_STATUS_OK)
+ {
+ ChipLogError(SoftwareUpdate, "sl_wfx_host_post_bootloader_spi_transfer() error: %ld", err);
+ return SL_GENERIC_OTA_ERROR;
+ }
+#endif // SL_BTLCTRL_MUX
+ }
+
+ return err ? CHIP_ERROR_WRITE_FAILED : CHIP_NO_ERROR;
+}
+
+} // namespace chip
diff --git a/src/platform/silabs/multi-ota/efr32/OTAFirmwareProcessor.h b/src/platform/silabs/multi-ota/efr32/OTAFirmwareProcessor.h
new file mode 100644
index 0000000..c383e74
--- /dev/null
+++ b/src/platform/silabs/multi-ota/efr32/OTAFirmwareProcessor.h
@@ -0,0 +1,59 @@
+/*
+ *
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <lib/support/Span.h>
+#include <platform/silabs/multi-ota/OTATlvProcessor.h>
+
+namespace chip {
+
+class OTAFirmwareProcessor : public OTATlvProcessor
+{
+public:
+ struct Descriptor
+ {
+ uint32_t version;
+ char versionString[kVersionStringSize];
+ char buildDate[kBuildDateSize];
+ };
+
+ CHIP_ERROR Init() override;
+ CHIP_ERROR Clear() override;
+ CHIP_ERROR ApplyAction() override;
+ CHIP_ERROR FinalizeAction() override;
+
+private:
+ CHIP_ERROR ProcessInternal(ByteSpan & block) override;
+ CHIP_ERROR ProcessDescriptor(ByteSpan & block);
+
+ OTADataAccumulator mAccumulator;
+ bool mDescriptorProcessed = false;
+#if OTA_ENCRYPTION_ENABLE
+ uint32_t mUnalignmentNum;
+#endif
+ static constexpr size_t kAlignmentBytes = 64;
+ static uint32_t mWriteOffset; // End of last written block
+ static uint8_t mSlotId; // Bootloader storage slot
+ // Bootloader storage API requires the buffer size to be a multiple of 4.
+ static uint8_t writeBuffer[kAlignmentBytes] __attribute__((aligned(4)));
+ // Offset indicates how far the write buffer has been filled
+ static uint16_t writeBufOffset;
+};
+
+} // namespace chip
diff --git a/src/platform/silabs/multi-ota/efr32/OTAHooks.cpp b/src/platform/silabs/multi-ota/efr32/OTAHooks.cpp
new file mode 100644
index 0000000..cddb669
--- /dev/null
+++ b/src/platform/silabs/multi-ota/efr32/OTAHooks.cpp
@@ -0,0 +1,44 @@
+/*
+ *
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <include/platform/CHIPDeviceLayer.h>
+#include <platform/silabs/multi-ota/OTAMultiImageProcessorImpl.h>
+
+#include <app/clusters/ota-requestor/OTARequestorInterface.h>
+
+#include <platform/silabs/multi-ota/efr32/OTAFirmwareProcessor.h>
+
+CHIP_ERROR chip::OTAMultiImageProcessorImpl::ProcessDescriptor(void * descriptor)
+{
+ auto desc = static_cast<chip::OTAFirmwareProcessor::Descriptor *>(descriptor);
+ ChipLogDetail(SoftwareUpdate, "Descriptor: %ld, %s, %s", desc->version, desc->versionString, desc->buildDate);
+
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR chip::OTAMultiImageProcessorImpl::OtaHookInit()
+{
+ static chip::OTAFirmwareProcessor sApplicationProcessor;
+
+ sApplicationProcessor.RegisterDescriptorCallback(ProcessDescriptor);
+
+ auto & imageProcessor = chip::OTAMultiImageProcessorImpl::GetDefaultInstance();
+ ReturnErrorOnFailure(imageProcessor.RegisterProcessor(1, &sApplicationProcessor));
+
+ return CHIP_NO_ERROR;
+}
diff --git a/third_party/silabs/efr32_sdk.gni b/third_party/silabs/efr32_sdk.gni
index 55934ae..9a2a1ec 100644
--- a/third_party/silabs/efr32_sdk.gni
+++ b/third_party/silabs/efr32_sdk.gni
@@ -77,6 +77,9 @@
# Use SLC generated files
slc_reuse_files = false
+
+ # Multi-chip OTA
+ chip_enable_multi_ota_requestor = false
}
examples_plat_dir = "${chip_root}/examples/platform/silabs/efr32"
silabs_plat_efr32_wifi_dir = "${chip_root}/src/platform/silabs/efr32/wifi"