blob: 3100496740e76f5f6289602c916658a8155a5eb9 [file] [log] [blame]
/*
*
* Copyright (c) 2020 Project CHIP Authors
* Copyright (c) 2019-2020 Google LLC.
* Copyright (c) 2018 Nest Labs, Inc.
*
* 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.
*/
/**
* @file
* Utilities for accessing persisted device configuration on
* platforms based on the Nordic nRF5 SDK.
*/
#include <atomic>
#include <platform/internal/CHIPDeviceLayerInternal.h>
#include <platform/nRF5/nRF5Config.h>
#include <core/CHIPEncoding.h>
#include <platform/internal/testing/ConfigUnitTest.h>
#include <support/logging/CHIPLogging.h>
#include <support/CodeUtils.h>
#include "FreeRTOS.h"
#include "semphr.h"
#include "fds.h"
#include "mem_manager.h"
namespace chip {
namespace DeviceLayer {
namespace Internal {
NRF5Config::FDSAsyncOp * volatile NRF5Config::sActiveAsyncOp;
SemaphoreHandle_t NRF5Config::sAsyncOpCompletionSem;
CHIP_ERROR NRF5Config::Init()
{
CHIP_ERROR err;
ret_code_t fdsRes;
// Create a semaphore to signal the completion of async FDS operations.
sAsyncOpCompletionSem = xSemaphoreCreateBinary();
VerifyOrExit(sAsyncOpCompletionSem != NULL, err = CHIP_ERROR_NO_MEMORY);
// Register an FDS event handler.
fdsRes = fds_register(HandleFDSEvent);
SuccessOrExit(err = MapFDSError(fdsRes));
// Initialize the FDS module.
{
FDSAsyncOp initOp(FDSAsyncOp::kInit);
err = DoAsyncFDSOp(initOp);
SuccessOrExit(err);
}
exit:
return err;
}
CHIP_ERROR NRF5Config::ReadConfigValue(Key key, bool & val)
{
CHIP_ERROR err;
fds_record_desc_t recDesc;
fds_flash_record_t rec;
uint32_t wordVal;
bool needClose = false;
err = OpenRecord(key, recDesc, rec);
SuccessOrExit(err);
needClose = true;
VerifyOrExit(rec.p_header->length_words == 1, err = CHIP_ERROR_INVALID_ARGUMENT);
wordVal = Encoding::LittleEndian::Get32((const uint8_t *)rec.p_data);
val = (wordVal != 0);
exit:
if (needClose)
{
fds_record_close(&recDesc);
}
return err;
}
CHIP_ERROR NRF5Config::ReadConfigValue(Key key, uint32_t & val)
{
CHIP_ERROR err;
fds_record_desc_t recDesc;
fds_flash_record_t rec;
bool needClose = false;
err = OpenRecord(key, recDesc, rec);
SuccessOrExit(err);
needClose = true;
VerifyOrExit(rec.p_header->length_words == 1, err = CHIP_ERROR_INVALID_ARGUMENT);
val = Encoding::LittleEndian::Get32((const uint8_t *)rec.p_data);
exit:
if (needClose)
{
fds_record_close(&recDesc);
}
return err;
}
CHIP_ERROR NRF5Config::ReadConfigValue(Key key, uint64_t & val)
{
CHIP_ERROR err;
fds_record_desc_t recDesc;
fds_flash_record_t rec;
bool needClose = false;
err = OpenRecord(key, recDesc, rec);
SuccessOrExit(err);
needClose = true;
VerifyOrExit(rec.p_header->length_words == 2, err = CHIP_ERROR_INVALID_ARGUMENT);
val = Encoding::LittleEndian::Get64((const uint8_t *)rec.p_data);
exit:
if (needClose)
{
fds_record_close(&recDesc);
}
return err;
}
CHIP_ERROR NRF5Config::ReadConfigValueStr(Key key, char * buf, size_t bufSize, size_t & outLen)
{
CHIP_ERROR err;
fds_flash_record_t rec;
fds_record_desc_t recDesc;
bool needClose = false;
const uint8_t * strEnd;
err = OpenRecord(key, recDesc, rec);
SuccessOrExit(err);
needClose = true;
strEnd = (const uint8_t *)memchr(rec.p_data, 0, rec.p_header->length_words * kFDSWordSize);
VerifyOrExit(strEnd != NULL, err = CHIP_ERROR_INVALID_ARGUMENT);
outLen = strEnd - (const uint8_t *)rec.p_data;
// NOTE: the caller is allowed to pass NULL for buf to query the length of the stored
// value.
if (buf != NULL)
{
VerifyOrExit(bufSize > outLen, err = CHIP_ERROR_BUFFER_TOO_SMALL);
memcpy(buf, rec.p_data, outLen + 1);
}
exit:
if (needClose)
{
fds_record_close(&recDesc);
}
return err;
}
CHIP_ERROR NRF5Config::ReadConfigValueBin(Key key, uint8_t * buf, size_t bufSize, size_t & outLen)
{
CHIP_ERROR err;
fds_flash_record_t rec;
fds_record_desc_t recDesc;
bool needClose = false;
err = OpenRecord(key, recDesc, rec);
SuccessOrExit(err);
needClose = true;
VerifyOrExit(rec.p_header->length_words >= 1, err = CHIP_ERROR_INVALID_ARGUMENT);
// First two bytes are length.
outLen = Encoding::LittleEndian::Get16((const uint8_t *)rec.p_data);
VerifyOrExit((rec.p_header->length_words * kFDSWordSize) >= (outLen + 2), err = CHIP_ERROR_INVALID_ARGUMENT);
// NOTE: the caller is allowed to pass NULL for buf to query the length of the stored
// value.
if (buf != NULL)
{
VerifyOrExit(bufSize >= outLen, err = CHIP_ERROR_BUFFER_TOO_SMALL);
memcpy(buf, ((const uint8_t *)rec.p_data) + 2, outLen);
}
exit:
if (needClose)
{
fds_record_close(&recDesc);
}
return err;
}
CHIP_ERROR NRF5Config::WriteConfigValue(Key key, bool val)
{
CHIP_ERROR err;
uint32_t storedVal = (val) ? 1 : 0;
FDSAsyncOp addOrUpdateOp(FDSAsyncOp::kAddOrUpdateRecord);
addOrUpdateOp.FileId = GetFileId(key);
addOrUpdateOp.RecordKey = GetRecordKey(key);
addOrUpdateOp.RecordData = (const uint8_t *)&storedVal;
addOrUpdateOp.RecordDataLengthWords = 1;
err = DoAsyncFDSOp(addOrUpdateOp);
SuccessOrExit(err);
ChipLogProgress(DeviceLayer, "FDS set: %04" PRIX16 "/%04" PRIX16 " = %s", GetFileId(key), GetRecordKey(key), val ? "true" : "false");
exit:
return err;
}
CHIP_ERROR NRF5Config::WriteConfigValue(Key key, uint32_t val)
{
CHIP_ERROR err;
FDSAsyncOp addOrUpdateOp(FDSAsyncOp::kAddOrUpdateRecord);
addOrUpdateOp.FileId = GetFileId(key);
addOrUpdateOp.RecordKey = GetRecordKey(key);
addOrUpdateOp.RecordData = (const uint8_t *)&val;
addOrUpdateOp.RecordDataLengthWords = 1;
err = DoAsyncFDSOp(addOrUpdateOp);
SuccessOrExit(err);
ChipLogProgress(DeviceLayer, "FDS set: 0x%04" PRIX16 "/0x%04" PRIX16 " = %" PRIu32 " (0x%" PRIX32 ")", GetFileId(key), GetRecordKey(key), val, val);
exit:
return err;
}
CHIP_ERROR NRF5Config::WriteConfigValue(Key key, uint64_t val)
{
CHIP_ERROR err;
FDSAsyncOp addOrUpdateOp(FDSAsyncOp::kAddOrUpdateRecord);
addOrUpdateOp.FileId = GetFileId(key);
addOrUpdateOp.RecordKey = GetRecordKey(key);
addOrUpdateOp.RecordData = (const uint8_t *)&val;
addOrUpdateOp.RecordDataLengthWords = 2;
err = DoAsyncFDSOp(addOrUpdateOp);
SuccessOrExit(err);
ChipLogProgress(DeviceLayer, "FDS set: 0x%04" PRIX16 "/0x%04" PRIX16 " = %" PRIu64 " (0x%" PRIX64 ")", GetFileId(key), GetRecordKey(key), val, val);
exit:
return err;
}
CHIP_ERROR NRF5Config::WriteConfigValueStr(Key key, const char * str)
{
return WriteConfigValueStr(key, str, (str != NULL) ? strlen(str) : 0);
}
CHIP_ERROR NRF5Config::WriteConfigValueStr(Key key, const char * str, size_t strLen)
{
CHIP_ERROR err;
uint8_t * storedVal = NULL;
if (str != NULL)
{
uint32_t storedValWords = FDSWords(strLen + 1);
storedVal = (uint8_t *)nrf_malloc(storedValWords * kFDSWordSize);
VerifyOrExit(storedVal != NULL, err = CHIP_ERROR_NO_MEMORY);
memcpy(storedVal, str, strLen);
storedVal[strLen] = 0;
FDSAsyncOp addOrUpdateOp(FDSAsyncOp::kAddOrUpdateRecord);
addOrUpdateOp.FileId = GetFileId(key);
addOrUpdateOp.RecordKey = GetRecordKey(key);
addOrUpdateOp.RecordData = storedVal;
addOrUpdateOp.RecordDataLengthWords = storedValWords;
err = DoAsyncFDSOp(addOrUpdateOp);
SuccessOrExit(err);
ChipLogProgress(DeviceLayer, "FDS set: 0x%04" PRIX16 "/0x%04" PRIX16 " = \"%s\"", GetFileId(key), GetRecordKey(key), (const char *)storedVal);
}
else
{
err = ClearConfigValue(key);
SuccessOrExit(err);
}
exit:
if (storedVal != NULL)
{
nrf_free(storedVal);
}
return err;
}
CHIP_ERROR NRF5Config::WriteConfigValueBin(Key key, const uint8_t * data, size_t dataLen)
{
CHIP_ERROR err;
uint8_t * storedVal = NULL;
if (data != NULL)
{
uint32_t storedValWords = FDSWords(dataLen + 2);
storedVal = (uint8_t *)nrf_malloc(storedValWords * kFDSWordSize);
VerifyOrExit(storedVal != NULL, err = CHIP_ERROR_NO_MEMORY);
// First two bytes encode the length.
Encoding::LittleEndian::Put16(storedVal, (uint16_t)dataLen);
memcpy(storedVal + 2, data, dataLen);
FDSAsyncOp addOrUpdateOp(FDSAsyncOp::kAddOrUpdateRecord);
addOrUpdateOp.FileId = GetFileId(key);
addOrUpdateOp.RecordKey = GetRecordKey(key);
addOrUpdateOp.RecordData = storedVal;
addOrUpdateOp.RecordDataLengthWords = storedValWords;
err = DoAsyncFDSOp(addOrUpdateOp);
SuccessOrExit(err);
ChipLogProgress(DeviceLayer, "FDS set: 0x%04" PRIX16 "/0x%04" PRIX16 " = (blob length %" PRId32 ")", GetFileId(key), GetRecordKey(key), dataLen);
}
else
{
err = ClearConfigValue(key);
SuccessOrExit(err);
}
exit:
if (storedVal != NULL)
{
nrf_free(storedVal);
}
return err;
}
CHIP_ERROR NRF5Config::ClearConfigValue(Key key)
{
CHIP_ERROR err;
FDSAsyncOp delOp(FDSAsyncOp::kDeleteRecordByKey);
delOp.FileId = GetFileId(key);
delOp.RecordKey = GetRecordKey(key);
err = DoAsyncFDSOp(delOp);
SuccessOrExit(err);
ChipLogProgress(DeviceLayer, "FDS delete: 0x%04" PRIX16 "/0x%04" PRIX16, GetFileId(key), GetRecordKey(key));
exit:
return err;
}
bool NRF5Config::ConfigValueExists(Key key)
{
ret_code_t fdsRes;
fds_record_desc_t recDesc;
fds_find_token_t findToken;
// Search for the requested record.
memset(&findToken, 0, sizeof(findToken));
fdsRes = fds_record_find(GetFileId(key), GetRecordKey(key), &recDesc, &findToken);
// Return true iff the record was found.
return fdsRes == NRF_SUCCESS;
}
CHIP_ERROR NRF5Config::FactoryResetConfig(void)
{
CHIP_ERROR err;
// Delete the chipConfig file and all records its contains.
{
FDSAsyncOp delOp(FDSAsyncOp::kDeleteFile);
delOp.FileId = kFileId_ChipConfig;
err = DoAsyncFDSOp(delOp);
SuccessOrExit(err);
ChipLogProgress(DeviceLayer, "FDS delete file: 0x%04" PRIX16, kFileId_ChipConfig);
}
// Force a GC
{
FDSAsyncOp gcOp(FDSAsyncOp::kGC);
err = DoAsyncFDSOp(gcOp);
SuccessOrExit(err);
}
exit:
return err;
}
#define CHIP_DEVICE_CONFIG_NRF5_FDS_ERROR_MIN 10000000
CHIP_ERROR NRF5Config::MapFDSError(ret_code_t fdsRes)
{
return (fdsRes == NRF_SUCCESS) ? CHIP_NO_ERROR : CHIP_DEVICE_CONFIG_NRF5_FDS_ERROR_MIN + fdsRes;
}
CHIP_ERROR NRF5Config::OpenRecord(NRF5Config::Key key, fds_record_desc_t & recDesc, fds_flash_record_t & rec)
{
CHIP_ERROR err;
ret_code_t fdsRes;
fds_find_token_t findToken;
// Search for the requested record. Return "CONFIG_NOT_FOUND" if it doesn't exist.
memset(&findToken, 0, sizeof(findToken));
fdsRes = fds_record_find(NRF5Config::GetFileId(key), NRF5Config::GetRecordKey(key), &recDesc, &findToken);
err = (fdsRes == FDS_ERR_NOT_FOUND) ? CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND : MapFDSError(fdsRes);
SuccessOrExit(err);
// Open the record for reading.
fdsRes = fds_record_open(&recDesc, &rec);
err = MapFDSError(fdsRes);
SuccessOrExit(err);
exit:
return err;
}
CHIP_ERROR NRF5Config::ForEachRecord(uint16_t fileId, uint16_t recordKey, ForEachRecordFunct funct)
{
CHIP_ERROR err = CHIP_NO_ERROR;;
ret_code_t fdsRes;
fds_find_token_t findToken;
fds_record_desc_t recDesc;
fds_flash_record_t rec;
bool needClose = false;
memset(&findToken, 0, sizeof(findToken));
while (true)
{
// Search for an occurrence of the requested record. If there are no more records, break the loop.
fdsRes = fds_record_find(fileId, recordKey, &recDesc, &findToken);
if (fdsRes == FDS_ERR_NOT_FOUND)
{
break;
}
err = MapFDSError(fdsRes);
SuccessOrExit(err);
// Open the record for reading.
fdsRes = fds_record_open(&recDesc, &rec);
err = MapFDSError(fdsRes);
SuccessOrExit(err);
needClose = true;
bool deleteRec = false;
// Invoke the caller's function.
err = funct(rec, deleteRec);
SuccessOrExit(err);
// Close the record.
needClose = false;
fdsRes = fds_record_close(&recDesc);
err = MapFDSError(fdsRes);
SuccessOrExit(err);
// Delete the record if requested.
if (deleteRec)
{
FDSAsyncOp delOp(FDSAsyncOp::kDeleteRecord);
delOp.FileId = fileId;
delOp.RecordKey = recordKey;
delOp.RecordDesc = recDesc;
err = DoAsyncFDSOp(delOp);
SuccessOrExit(err);
}
}
exit:
if (needClose)
{
fds_record_close(&recDesc);
}
return err;
}
CHIP_ERROR NRF5Config::DoAsyncFDSOp(FDSAsyncOp & asyncOp)
{
CHIP_ERROR err;
ret_code_t fdsRes;
fds_record_t rec;
bool gcPerformed = false;
// Keep trying to perform the requested op until there's a definitive success or failure...
while (true)
{
bool existingRecFound = false;
// If performing an AddOrUpdateRecord or DeleteRecordByKey, search for an existing record with the given key.
if (asyncOp.OpType == FDSAsyncOp::kAddOrUpdateRecord || asyncOp.OpType == FDSAsyncOp::kDeleteRecordByKey)
{
fds_find_token_t findToken;
memset(&findToken, 0, sizeof(findToken));
fdsRes = fds_record_find(asyncOp.FileId, asyncOp.RecordKey, &asyncOp.RecordDesc, &findToken);
VerifyOrExit(fdsRes == NRF_SUCCESS || fdsRes == FDS_ERR_NOT_FOUND, err = MapFDSError(fdsRes));
// Remember if we found an existing record.
existingRecFound = (fdsRes == NRF_SUCCESS);
}
// If adding or updating a record, prepare the FDS record structure with the record data.
if (asyncOp.OpType == FDSAsyncOp::kAddRecord ||
asyncOp.OpType == FDSAsyncOp::kUpdateRecord ||
asyncOp.OpType == FDSAsyncOp::kAddOrUpdateRecord)
{
memset(&rec, 0, sizeof(rec));
rec.file_id = asyncOp.FileId;
rec.key = asyncOp.RecordKey;
rec.data.p_data = asyncOp.RecordData;
rec.data.length_words = asyncOp.RecordDataLengthWords;
}
// Make the requested op the active op.
sActiveAsyncOp = &asyncOp;
// Initiate the requested FDS operation.
switch (asyncOp.OpType)
{
case FDSAsyncOp::kInit:
fdsRes = fds_init();
break;
case FDSAsyncOp::kAddOrUpdateRecord:
// Depending on whether an existing record was found, call fds_record_write or fds_record_update.
if (!existingRecFound)
{
case FDSAsyncOp::kAddRecord:
fdsRes = fds_record_write(NULL, &rec);
break;
}
else
{
case FDSAsyncOp::kUpdateRecord:
fdsRes = fds_record_update(&asyncOp.RecordDesc, &rec);
break;
}
case FDSAsyncOp::kDeleteRecordByKey:
// If performing a kDeleteRecordByKey and no matching record was found, simply return success.
if (!existingRecFound)
{
ExitNow(err = CHIP_NO_ERROR);
}
// fall through...
case FDSAsyncOp::kDeleteRecord:
fdsRes = fds_record_delete(&asyncOp.RecordDesc);
break;
case FDSAsyncOp::kDeleteFile:
fdsRes = fds_file_delete(asyncOp.FileId);
break;
case FDSAsyncOp::kGC:
fdsRes = fds_gc();
break;
case FDSAsyncOp::kWaitQueueSpaceAvailable:
// In this case, arrange to wait for any operation to complete, which coincides with
// space on the operation queue being available.
fdsRes = NRF_SUCCESS;
break;
default:
ChipDie();
}
// If the operation was queued successfully, wait for it to complete and retrieve the result.
// If the FreeRTOS scheduler is not running, poll the completion semaphore; otherwise wait
// indefinitely.
//
if (fdsRes == NRF_SUCCESS)
{
if (xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED)
{
while (xSemaphoreTake(sAsyncOpCompletionSem, 0) == pdFALSE)
;
}
else
{
xSemaphoreTake(sAsyncOpCompletionSem, portMAX_DELAY);
}
fdsRes = asyncOp.Result;
}
// Clear the active operation in case it wasn't done by the event handler.
sActiveAsyncOp = NULL;
// Return immediately if the operation completed successfully.
if (fdsRes == NRF_SUCCESS)
{
ExitNow(err = CHIP_NO_ERROR);
}
// If the operation failed for lack of flash space...
if (fdsRes == FDS_ERR_NO_SPACE_IN_FLASH)
{
// If we've already performed a garbage collection, fail immediately.
if (gcPerformed)
{
ExitNow(err = MapFDSError(fdsRes));
}
ChipLogProgress(DeviceLayer, "Initiating FDS GC to recover space");
// Request a garbage collection cycle and wait for it to complete.
FDSAsyncOp gcOp(FDSAsyncOp::kGC);
err = DoAsyncFDSOp(gcOp);
SuccessOrExit(err);
// Repeat the requested operation.
continue;
}
// If the write/update failed because the operation queue is full, wait for
// space to become available and then repeat the requested operation.
if (fdsRes == FDS_ERR_NO_SPACE_IN_QUEUES)
{
FDSAsyncOp waitOp(FDSAsyncOp::kWaitQueueSpaceAvailable);
err = DoAsyncFDSOp(waitOp);
SuccessOrExit(err);
continue;
}
// If the operation timed out, simply try it again.
if (fdsRes == FDS_ERR_OPERATION_TIMEOUT)
{
continue;
}
// Otherwise fail with an unrecoverable error.
ExitNow(err = MapFDSError(fdsRes));
}
exit:
return err;
}
void NRF5Config::HandleFDSEvent(const fds_evt_t * fdsEvent)
{
// Do nothing if there's no async operation active.
if (sActiveAsyncOp == NULL)
{
return;
}
// Check if the event applies to the active async operation.
switch (sActiveAsyncOp->OpType)
{
case FDSAsyncOp::kInit:
if (fdsEvent->id != FDS_EVT_INIT)
{
return;
}
break;
case FDSAsyncOp::kAddOrUpdateRecord:
case FDSAsyncOp::kAddRecord:
case FDSAsyncOp::kUpdateRecord:
// Ignore the event if its not a WRITE or UPDATE, or if its for a different file/record.
if ((fdsEvent->id != FDS_EVT_WRITE && fdsEvent->id != FDS_EVT_UPDATE) ||
fdsEvent->write.file_id != sActiveAsyncOp->FileId ||
fdsEvent->write.record_key != sActiveAsyncOp->RecordKey)
{
return;
}
break;
case FDSAsyncOp::kDeleteRecord:
case FDSAsyncOp::kDeleteRecordByKey:
// Ignore the event if its not a DEL_RECORD, or if its for a different file/record.
if (fdsEvent->id != FDS_EVT_DEL_RECORD ||
fdsEvent->del.record_id != sActiveAsyncOp->RecordDesc.record_id)
{
return;
}
break;
case FDSAsyncOp::kDeleteFile:
// Ignore the event if its not a DEL_FILE or its for a different file.
if (fdsEvent->id != FDS_EVT_DEL_FILE || fdsEvent->del.file_id != sActiveAsyncOp->FileId)
{
return;
}
break;
case FDSAsyncOp::kGC:
// Ignore the event if its not a GC.
if (fdsEvent->id != FDS_EVT_GC)
{
return;
}
break;
case FDSAsyncOp::kWaitQueueSpaceAvailable:
break;
}
// Capture the result.
sActiveAsyncOp->Result = fdsEvent->result;
// Mark the operation as complete.
sActiveAsyncOp = NULL;
// Signal the CHIP thread that the operation has completed.
#if defined(SOFTDEVICE_PRESENT) && SOFTDEVICE_PRESENT
// When using the Nodic SoftDevice, HandleFDSEvent() is called in a SoftDevice interrupt
// context. Therefore, we must use xSemaphoreGiveFromISR() to signal completion.
BaseType_t yieldRequired = xSemaphoreGiveFromISR(sAsyncOpCompletionSem, &yieldRequired);
// Yield to the next runnable task, but only if the FreeRTOS scheduler has been started.
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED && yieldRequired == pdTRUE)
{
portYIELD_FROM_ISR(yieldRequired);
}
#else // defined(SOFTDEVICE_PRESENT) || !SOFTDEVICE_PRESENT
// When NOT using the Nodic SoftDevice, HandleFDSEvent() is called in a non-interrupt
// context. Therefore, use xSemaphoreGive() to signal completion.
xSemaphoreGive(sAsyncOpCompletionSem);
#endif // !(defined(SOFTDEVICE_PRESENT) || !SOFTDEVICE_PRESENT)
}
void NRF5Config::RunConfigUnitTest()
{
CHIP_ERROR err;
// Run common unit test
::chip::DeviceLayer::Internal::RunConfigUnitTest<NRF5Config>();
// NRF Config Test 1 -- Force GC
{
const static uint8_t kTestData[] =
{
0xD5, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x30, 0x01, 0x08, 0x79, 0x55, 0x9F, 0x15, 0x1F, 0x66,
0x3D, 0x8F, 0x24, 0x02, 0x05, 0x37, 0x03, 0x27, 0x13, 0x02, 0x00, 0x00, 0xEE, 0xEE, 0x30, 0xB4,
0x18, 0x18, 0x26, 0x04, 0x80, 0x41, 0x1B, 0x23, 0x26, 0x05, 0x7F, 0xFF, 0xFF, 0x52, 0x37, 0x06,
0x27, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x30, 0xB4, 0x18, 0x18, 0x24, 0x07, 0x02, 0x26, 0x08,
0x25, 0x00, 0x5A, 0x23, 0x30, 0x0A, 0x39, 0x04, 0x9E, 0xC7, 0x77, 0xC5, 0xA4, 0x13, 0x31, 0xF7,
0x72, 0x2E, 0x27, 0xC2, 0x86, 0x3D, 0xC5, 0x2E, 0xD5, 0xD2, 0x3C, 0xCF, 0x7E, 0x06, 0xE3, 0x48,
0x53, 0x87, 0xE8, 0x4D, 0xB0, 0x27, 0x07, 0x58, 0x4A, 0x38, 0xB4, 0xF3, 0xB2, 0x47, 0x94, 0x45,
0x58, 0x65, 0x80, 0x08, 0x17, 0x6B, 0x8E, 0x4F, 0x07, 0x41, 0xA3, 0x3D, 0x5D, 0xCE, 0x76, 0x86,
0x35, 0x83, 0x29, 0x01, 0x18, 0x35, 0x82, 0x29, 0x01, 0x24, 0x02, 0x05, 0x18, 0x35, 0x84, 0x29,
0x01, 0x36, 0x02, 0x04, 0x02, 0x04, 0x01, 0x18, 0x18, 0x35, 0x81, 0x30, 0x02, 0x08, 0x42, 0xBD,
0x2C, 0x6B, 0x5B, 0x3A, 0x18, 0x16, 0x18, 0x35, 0x80, 0x30, 0x02, 0x08, 0x44, 0xE3, 0x40, 0x38,
0xA9, 0xD4, 0xB5, 0xA7, 0x18, 0x35, 0x0C, 0x30, 0x01, 0x19, 0x00, 0xA6, 0x5D, 0x54, 0xF5, 0xAE,
0x5D, 0x63, 0xEB, 0x69, 0xD8, 0xDB, 0xCB, 0xE2, 0x20, 0x0C, 0xD5, 0x6F, 0x43, 0x5E, 0x96, 0xA8,
0x54, 0xB2, 0x74, 0x30, 0x02, 0x19, 0x00, 0xE0, 0x37, 0x02, 0x8B, 0xB3, 0x04, 0x06, 0xDD, 0xBD,
0x28, 0xAA, 0xC4, 0xF1, 0xFF, 0xFB, 0xB1, 0xD4, 0x1C, 0x78, 0x40, 0xDA, 0x2C, 0xD8, 0x40, 0x18,
0x18,
};
uint8_t buf[512];
size_t dataLen;
for (int i = 0; i < 100; i++)
{
err = WriteConfigValueBin(kConfigKey_MfrDeviceCert, kTestData, sizeof(kTestData));
VerifyOrDie(err == CHIP_NO_ERROR);
vTaskDelay(pdMS_TO_TICKS(50));
}
err = ReadConfigValueBin(kConfigKey_MfrDeviceCert, buf, sizeof(buf), dataLen);
VerifyOrDie(err == CHIP_NO_ERROR);
VerifyOrDie(dataLen == sizeof(kTestData));
VerifyOrDie(memcmp(buf, kTestData, dataLen) == 0);
}
}
} // namespace Internal
} // namespace DeviceLayer
} // namespace chip