2021-11-30 14:51:24 +01:00

3206 lines
118 KiB
C

/*
* AWS IoT Over-the-air Update v3.2.0
* Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* @file ota_utest.c
* @brief Unit tests for functions in OTA agent.
*/
/* Standard includes. */
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
/* 3rdparty includes. */
#include <unistd.h>
#include "unity.h"
/* OTA includes. */
#include "ota_appversion32.h"
#include "ota.h"
#include "ota_private.h"
#include "ota_mqtt_private.h"
#include "ota_http_private.h"
#include "ota_interface_private.h"
/* test includes. */
#include "utest_helpers.h"
/* Job document for testing. */
#define OTA_TEST_FILE_SIZE 10240
#define OTA_TEST_FILE_NUM_BLOCKS ( OTA_TEST_FILE_SIZE / OTA_FILE_BLOCK_SIZE + 1 )
#define OTA_TEST_DUPLICATE_NUM_BLOCKS 3
#define OTA_TEST_FILE_SIZE_STR "10240"
#define JOB_DOC_A "{\"clientToken\":\"0:testclient\",\"timestamp\":1602795143,\"execution\":{\"jobId\":\"AFR_OTA-testjob20\",\"status\":\"QUEUED\",\"queuedAt\":1602795128,\"lastUpdatedAt\":1602795128,\"versionNumber\":1,\"executionNumber\":1,\"jobDocument\":{\"afr_ota\":{\"protocols\":[\"MQTT\"],\"streamname\":\"AFR_OTA-XYZ\",\"files\":[{\"filepath\":\"/test/demo\",\"filesize\":" OTA_TEST_FILE_SIZE_STR ",\"fileid\":0,\"certfile\":\"test.crt\",\"sig-sha256-ecdsa\":\"MEQCIF2QDvww1G/kpRGZ8FYvQrok1bSZvXjXefRk7sqNcyPTAiB4dvGt8fozIY5NC0vUDJ2MY42ZERYEcrbwA4n6q7vrBg==\"}] }}}}"
#define JOB_DOC_B "{\"clientToken\":\"0:testclient\",\"timestamp\":1602795143,\"execution\":{\"jobId\":\"AFR_OTA-testjob21\",\"status\":\"QUEUED\",\"queuedAt\":1602795128,\"lastUpdatedAt\":1602795128,\"versionNumber\":1,\"executionNumber\":1,\"jobDocument\":{\"afr_ota\":{\"protocols\":[\"MQTT\"],\"streamname\":\"AFR_OTA-XYZ\",\"files\":[{\"filepath\":\"/test/demo\",\"filesize\":" OTA_TEST_FILE_SIZE_STR ",\"fileid\":0,\"certfile\":\"test.crt\",\"sig-sha256-ecdsa\":\"MEQCIF2QDvww1G/kpRGZ8FYvQrok1bSZvXjXefRk7sqNcyPTAiB4dvGt8fozIY5NC0vUDJ2MY42ZERYEcrbwA4n6q7vrBg==\"}] }}}}"
#define JOB_DOC_SELF_TEST "{\"clientToken\":\"0:testclient\",\"timestamp\":1602795143,\"execution\":{\"jobId\":\"AFR_OTA-testjob20\",\"status\":\"IN_PROGRESS\",\"statusDetails\":{\"self_test\":\"ready\",\"updatedBy\":\"0x1000000\"},\"queuedAt\":1602795128,\"lastUpdatedAt\":1602795128,\"versionNumber\":1,\"executionNumber\":1,\"jobDocument\":{\"afr_ota\":{\"protocols\":[\"MQTT\"],\"streamname\":\"AFR_OTA-XYZ\",\"files\":[{\"filepath\":\"/test/demo\",\"filesize\":" OTA_TEST_FILE_SIZE_STR ",\"fileid\":0,\"certfile\":\"test.crt\",\"sig-sha256-ecdsa\":\"MEQCIF2QDvww1G/kpRGZ8FYvQrok1bSZvXjXefRk7sqNcyPTAiB4dvGt8fozIY5NC0vUDJ2MY42ZERYEcrbwA4n6q7vrBg==\"}] }}}}"
#define JOB_DOC_SELF_TEST_FILE_TYPE "{\"clientToken\":\"0:testclient\",\"timestamp\":1602795143,\"execution\":{\"jobId\":\"AFR_OTA-testjob20\",\"status\":\"IN_PROGRESS\",\"statusDetails\":{\"self_test\":\"ready\",\"updatedBy\":\"0x1000000\"},\"queuedAt\":1602795128,\"lastUpdatedAt\":1602795128,\"versionNumber\":1,\"executionNumber\":1,\"jobDocument\":{\"afr_ota\":{\"protocols\":[\"MQTT\"],\"streamname\":\"AFR_OTA-XYZ\",\"files\":[{\"filepath\":\"/test/demo\",\"filesize\":" OTA_TEST_FILE_SIZE_STR ",\"fileid\":0,\"certfile\":\"test.crt\",\"fileType\":255,\"sig-sha256-ecdsa\":\"MEQCIF2QDvww1G/kpRGZ8FYvQrok1bSZvXjXefRk7sqNcyPTAiB4dvGt8fozIY5NC0vUDJ2MY42ZERYEcrbwA4n6q7vrBg==\"}] }}}}"
#define JOB_DOC_SELF_TEST_SAME_VERSION "{\"clientToken\":\"0:testclient\",\"timestamp\":1602795143,\"execution\":{\"jobId\":\"AFR_OTA-testjob20\",\"status\":\"IN_PROGRESS\",\"statusDetails\":{\"self_test\":\"ready\",\"updatedBy\":\"0x1000001\"},\"queuedAt\":1602795128,\"lastUpdatedAt\":1602795128,\"versionNumber\":1,\"executionNumber\":1,\"jobDocument\":{\"afr_ota\":{\"protocols\":[\"MQTT\"],\"streamname\":\"AFR_OTA-XYZ\",\"files\":[{\"filepath\":\"/test/demo\",\"filesize\":" OTA_TEST_FILE_SIZE_STR ",\"fileid\":0,\"certfile\":\"test.crt\",\"sig-sha256-ecdsa\":\"MEQCIF2QDvww1G/kpRGZ8FYvQrok1bSZvXjXefRk7sqNcyPTAiB4dvGt8fozIY5NC0vUDJ2MY42ZERYEcrbwA4n6q7vrBg==\"}] }}}}"
#define JOB_DOC_SELF_TEST_DOWNGRADE "{\"clientToken\":\"0:testclient\",\"timestamp\":1602795143,\"execution\":{\"jobId\":\"AFR_OTA-testjob20\",\"status\":\"IN_PROGRESS\",\"statusDetails\":{\"self_test\":\"ready\",\"updatedBy\":\"0x2000000\"},\"queuedAt\":1602795128,\"lastUpdatedAt\":1602795128,\"versionNumber\":1,\"executionNumber\":1,\"jobDocument\":{\"afr_ota\":{\"protocols\":[\"MQTT\"],\"streamname\":\"AFR_OTA-XYZ\",\"files\":[{\"filepath\":\"/test/demo\",\"filesize\":" OTA_TEST_FILE_SIZE_STR ",\"fileid\":0,\"certfile\":\"test.crt\",\"sig-sha256-ecdsa\":\"MEQCIF2QDvww1G/kpRGZ8FYvQrok1bSZvXjXefRk7sqNcyPTAiB4dvGt8fozIY5NC0vUDJ2MY42ZERYEcrbwA4n6q7vrBg==\"}] }}}}"
#define JOB_DOC_HTTP "{\"clientToken\":\"0:testclient\",\"timestamp\":1602795143,\"execution\":{\"jobId\":\"AFR_OTA-testjob22\",\"status\":\"QUEUED\",\"queuedAt\":1602795128,\"lastUpdatedAt\":1602795128,\"versionNumber\":1,\"executionNumber\":1,\"jobDocument\":{\"afr_ota\":{\"protocols\":[\"HTTP\"],\"files\":[{\"filepath\":\"/test/demo\",\"filesize\":" OTA_TEST_FILE_SIZE_STR ",\"fileid\":0,\"certfile\":\"test.crt\",\"update_data_url\":\"https://dummy-url.com/ota.bin\",\"auth_scheme\":\"aws.s3.presigned\",\"sig-sha256-ecdsa\":\"MEQCIF2QDvww1G/kpRGZ8FYvQrok1bSZvXjXefRk7sqNcyPTAiB4dvGt8fozIY5NC0vUDJ2MY42ZERYEcrbwA4n6q7vrBg==\"}] }}}}"
#define JOB_DOC_ONE_BLOCK "{\"clientToken\":\"0:testclient\",\"timestamp\":1602795143,\"execution\":{\"jobId\":\"AFR_OTA-testjob22\",\"status\":\"QUEUED\",\"queuedAt\":1602795128,\"lastUpdatedAt\":1602795128,\"versionNumber\":1,\"executionNumber\":1,\"jobDocument\":{\"afr_ota\":{\"protocols\":[\"HTTP\"],\"files\":[{\"filepath\":\"/test/demo\",\"filesize\": \"1024\" ,\"fileid\":0,\"certfile\":\"test.crt\",\"update_data_url\":\"https://dummy-url.com/ota.bin\",\"auth_scheme\":\"aws.s3.presigned\",\"sig-sha256-ecdsa\":\"MEQCIF2QDvww1G/kpRGZ8FYvQrok1bSZvXjXefRk7sqNcyPTAiB4dvGt8fozIY5NC0vUDJ2MY42ZERYEcrbwA4n6q7vrBg==\"}] }}}}"
#define JOB_DOC_INVALID "not a json"
#define JOB_DOC_INVALID_PROTOCOL "{\"clientToken\":\"0:testclient\",\"timestamp\":1602795143,\"execution\":{\"jobId\":\"AFR_OTA-testjob20\",\"status\":\"QUEUED\",\"queuedAt\":1602795128,\"lastUpdatedAt\":1602795128,\"versionNumber\":1,\"executionNumber\":1,\"jobDocument\":{\"afr_ota\":{\"protocols\":[\"XYZ\"],\"streamname\":\"AFR_OTA-XYZ\",\"files\":[{\"filepath\":\"/test/demo\",\"filesize\":" OTA_TEST_FILE_SIZE_STR ",\"fileid\":0,\"certfile\":\"test.crt\",\"sig-sha256-ecdsa\":\"MEQCIF2QDvww1G/kpRGZ8FYvQrok1bSZvXjXefRk7sqNcyPTAiB4dvGt8fozIY5NC0vUDJ2MY42ZERYEcrbwA4n6q7vrBg==\"}] }}}}"
#define JOB_DOC_INVALID_BASE64_KEY "{\"clientToken\":\"0:testclient\",\"timestamp\":1602795143,\"execution\":{\"jobId\":\"AFR_OTA-testjob20\",\"status\":\"QUEUED\",\"queuedAt\":1602795128,\"lastUpdatedAt\":1602795128,\"versionNumber\":1,\"executionNumber\":1,\"jobDocument\":{\"afr_ota\":{\"protocols\":[\"MQTT\"],\"streamname\":\"AFR_OTA-XYZ\",\"files\":[{\"filepath\":\"/test/demo\",\"filesize\":" OTA_TEST_FILE_SIZE_STR ",\"fileid\":0,\"certfile\":\"test.crt\",\"sig-sha256-ecdsa\":\"Zg===\"}] }}}}"
#define JOB_DOC_MISSING_KEY "{\"clientToken\":\"0:testclient\",\"timestamp\":1602795143,\"execution\":{\"jobId\":\"AFR_OTA-testjob20\",\"status\":\"QUEUED\",\"queuedAt\":1602795128,\"lastUpdatedAt\":1602795128,\"versionNumber\":1,\"executionNumber\":1,\"jobDocument\":{\"afr_ota\":{\"protocols\":[\"MQTT\"],\"streamname\":\"AFR_OTA-XYZ\",\"files\":[{\"filepath\":\"/test/demo\",\"fileid\":0,\"certfile\":\"test.crt\",\"sig-sha256-ecdsa\":\"MEQCIF2QDvww1G/kpRGZ8FYvQrok1bSZvXjXefRk7sqNcyPTAiB4dvGt8fozIY5NC0vUDJ2MY42ZERYEcrbwA4n6q7vrBg==\"}] }}}}"
#define JOB_DOC_INVALID_NUMBER_NAN "{\"clientToken\":\"0:testclient\",\"timestamp\":1602795143,\"execution\":{\"jobId\":\"AFR_OTA-testjob20\",\"status\":\"QUEUED\",\"queuedAt\":1602795128,\"lastUpdatedAt\":1602795128,\"versionNumber\":1,\"executionNumber\":1,\"jobDocument\":{\"afr_ota\":{\"protocols\":[\"MQTT\"],\"streamname\":\"AFR_OTA-XYZ\",\"files\":[{\"filepath\":\"/test/demo\",\"filesize\": \"NaN\",\"fileid\":\"NaN\",\"certfile\":\"test.crt\",\"sig-sha256-ecdsa\":\"MEQCIF2QDvww1G/kpRGZ8FYvQrok1bSZvXjXefRk7sqNcyPTAiB4dvGt8fozIY5NC0vUDJ2MY42ZERYEcrbwA4n6q7vrBg==\"}] }}}}"
#define JOB_DOC_INVALID_NUMBER_VAL "{\"clientToken\":\"0:testclient\",\"timestamp\":1602795143,\"execution\":{\"jobId\":\"AFR_OTA-testjob20\",\"status\":\"QUEUED\",\"queuedAt\":1602795128,\"lastUpdatedAt\":1602795128,\"versionNumber\":1,\"executionNumber\":1,\"jobDocument\":{\"afr_ota\":{\"protocols\":[\"MQTT\"],\"streamname\":\"AFR_OTA-XYZ\",\"files\":[{\"filepath\":\"/test/demo\",\"filesize\": 19223372036854775808,\"fileid\":\"NaN\",\"certfile\":\"test.crt\",\"sig-sha256-ecdsa\":\"MEQCIF2QDvww1G/kpRGZ8FYvQrok1bSZvXjXefRk7sqNcyPTAiB4dvGt8fozIY5NC0vUDJ2MY42ZERYEcrbwA4n6q7vrBg==\"}] }}}}"
#define JOB_DOC_SERVERFILE_ID "{\"clientToken\":\"0:testclient\",\"timestamp\":1602795143,\"execution\":{\"jobId\":\"AFR_OTA-testjob20\",\"status\":\"IN_PROGRESS\",\"statusDetails\":{\"self_test\":\"ready\",\"updatedBy\":\"0x1000000\"},\"queuedAt\":1602795128,\"lastUpdatedAt\":1602795128,\"versionNumber\":1,\"executionNumber\":1,\"jobDocument\":{\"afr_ota\":{\"protocols\":[\"MQTT\"],\"streamname\":\"AFR_OTA-XYZ\",\"files\":[{\"filepath\":\"/test/demo\",\"filesize\":" OTA_TEST_FILE_SIZE_STR ",\"fileid\":1,\"certfile\":\"test.crt\",\"sig-sha256-ecdsa\":\"MEQCIF2QDvww1G/kpRGZ8FYvQrok1bSZvXjXefRk7sqNcyPTAiB4dvGt8fozIY5NC0vUDJ2MY42ZERYEcrbwA4n6q7vrBg==\"}] }}}}"
#define JOB_DOC_MISSING_JOB_DOC "{\"clientToken\":\"0:testclient\",\"timestamp\":1602795143,\"execution\":{\"jobId\":\"AFR_OTA-testjob20\",\"status\":\"QUEUED\",\"queuedAt\":1602795128,\"lastUpdatedAt\":1602795128,\"versionNumber\":1,\"executionNumber\":1}}"
#define JOB_DOC_MISSING_JOB_ID "{\"clientToken\":\"0:testclient\",\"timestamp\":1602795143,\"execution\":{\"status\":\"QUEUED\",\"queuedAt\":1602795128,\"lastUpdatedAt\":1602795128,\"versionNumber\":1,\"executionNumber\":1,\"jobDocument\":{\"afr_ota\":{\"protocols\":[\"MQTT\"],\"streamname\":\"AFR_OTA-XYZ\",\"files\":[{\"filepath\":\"/test/demo\",\"filesize\":" OTA_TEST_FILE_SIZE_STR ",\"fileid\":0,\"certfile\":\"test.crt\",\"sig-sha256-ecdsa\":\"MEQCIF2QDvww1G/kpRGZ8FYvQrok1bSZvXjXefRk7sqNcyPTAiB4dvGt8fozIY5NC0vUDJ2MY42ZERYEcrbwA4n6q7vrBg==\"}] }}}}"
#define JOB_DOC_INVALID_JOB_ID "{\"clientToken\":\"0:testclient\",\"timestamp\":1602795143,\"execution\":{\"jobId\":\"InvalidJobIdExceedingAllowedJobIdLengthInvalidJobIdExceedingAllowedJobIdLengthInvalidJobIdExceedingAllowedJobIdLength\",\"status\":\"QUEUED\",\"queuedAt\":1602795128,\"lastUpdatedAt\":1602795128,\"versionNumber\":1,\"executionNumber\":1,\"jobDocument\":{\"afr_ota\":{\"protocols\":[\"MQTT\"],\"streamname\":\"AFR_OTA-XYZ\",\"files\":[{\"filepath\":\"/test/demo\",\"filesize\":" OTA_TEST_FILE_SIZE_STR ",\"fileid\":0,\"certfile\":\"test.crt\",\"sig-sha256-ecdsa\":\"MEQCIF2QDvww1G/kpRGZ8FYvQrok1bSZvXjXefRk7sqNcyPTAiB4dvGt8fozIY5NC0vUDJ2MY42ZERYEcrbwA4n6q7vrBg==\"}] }}}}"
#define JOB_DOC_DIFFERENT_FILE_TYPE "{\"clientToken\":\"0:testclient\",\"timestamp\":1602795143,\"execution\":{\"jobId\":\"AFR_OTA-testjob20\",\"status\":\"QUEUED\",\"queuedAt\":1602795128,\"lastUpdatedAt\":1602795128,\"versionNumber\":1,\"executionNumber\":1,\"jobDocument\":{\"afr_ota\":{\"protocols\":[\"MQTT\"],\"streamname\":\"AFR_OTA-XYZ\",\"files\":[{\"filepath\":\"/test/demo\",\"filesize\":" OTA_TEST_FILE_SIZE_STR ",\"fileid\":0,\"certfile\":\"test.crt\",\"fileType\":2,\"sig-sha256-ecdsa\":\"MEQCIF2QDvww1G/kpRGZ8FYvQrok1bSZvXjXefRk7sqNcyPTAiB4dvGt8fozIY5NC0vUDJ2MY42ZERYEcrbwA4n6q7vrBg==\"}] }}}}"
/* OTA application buffer size. */
#define OTA_UPDATE_FILE_PATH_SIZE 100
#define OTA_CERT_FILE_PATH_SIZE 100
#define OTA_STREAM_NAME_SIZE 50
#define OTA_INVALID_STREAM_NAME_SIZE 5 /* Size insufficient to hold stream name used in the default job document (AFR_OTA-testjob20). */
#define OTA_DECODE_MEMORY_SIZE OTA_FILE_BLOCK_SIZE
#define OTA_FILE_BITMAP_SIZE 50
#define OTA_UPDATE_URL_SIZE 100
#define OTA_AUTH_SCHEME_SIZE 50
#define OTA_APP_BUFFER_SIZE \
( OTA_UPDATE_FILE_PATH_SIZE + \
OTA_CERT_FILE_PATH_SIZE + \
OTA_STREAM_NAME_SIZE + \
OTA_DECODE_MEMORY_SIZE + \
OTA_FILE_BITMAP_SIZE + \
OTA_UPDATE_URL_SIZE + \
OTA_AUTH_SCHEME_SIZE )
#define min( x, y ) ( x < y ? x : y )
#define OTA_NUM_MSG_Q_ENTRIES 20
/* Firmware version. */
const AppVersion32_t appFirmwareVersion =
{
.u.x.major = 1,
.u.x.minor = 0,
.u.x.build = 1,
};
/* OTA code signing signature algorithm. */
const char OTA_JsonFileSignatureKey[ OTA_FILE_SIG_KEY_STR_MAX_LENGTH ] = "sig-sha256-ecdsa";
/* OTA client name. */
static const char * pOtaDefaultClientId = "ota_utest";
/* OTA job doc. */
static const char * pOtaJobDoc = NULL;
/* OTA interface. */
static OtaInterfaces_t otaInterfaces;
/* OTA image state. */
static OtaPalImageState_t palImageState = OtaPalImageStateUnknown;
static bool resetCalled = false;
/* OTA application buffer. */
static OtaAppBuffer_t pOtaAppBuffer;
static uint8_t pUserBuffer[ OTA_APP_BUFFER_SIZE ];
/* OTA Event. */
static OtaEventMsg_t otaEventQueue[ OTA_NUM_MSG_Q_ENTRIES ];
static OtaEventMsg_t * otaEventQueueEnd = otaEventQueue;
static OtaEventData_t eventBuffer;
static bool eventIgnore;
/* OTA File handle and buffer. */
static FILE * pOtaFileHandle = NULL;
static uint8_t pOtaFileBuffer[ OTA_TEST_FILE_SIZE ];
/* 2 seconds default wait time for OTA state machine transition. */
static const int otaDefaultWait = 0;
/* Flag to unsubscribe to topics after ota shutdown. */
static const uint8_t unsubscribeFlag = 1;
/* ========================================================================== */
/* Global static variable defined in ota.c for managing the state machine. */
extern OtaAgentContext_t otaAgent;
/* Global static variable defined in ota.c for managing the data interface
* protocol function pointers. */
extern OtaDataInterface_t otaDataInterface;
/* Global static variable defined in ota.c for managing the control interface
* protocol function pointers. */
extern OtaControlInterface_t otaControlInterface;
/* The OTA job document model. */
extern const JsonDocParam_t * otaJobDocModelParamStructure;
/* Static function defined in ota.c for processing events. */
extern void receiveAndProcessOtaEvent( void );
/* Static state machine function handlers under test defined in ota.c. */
extern OtaErr_t initFileHandler( const OtaEventData_t * pEventData );
extern OtaErr_t requestDataHandler( const OtaEventData_t * pEventData );
extern OtaErr_t requestJobHandler( const OtaEventData_t * pEventData );
extern OtaErr_t processDataHandler( const OtaEventData_t * pEventData );
extern OtaErr_t resumeHandler( const OtaEventData_t * pEventData );
extern OtaErr_t jobNotificationHandler( const OtaEventData_t * pEventData );
extern OtaErr_t shutdownHandler( const OtaEventData_t * pEventData );
/* Static helper functions under test defined in ota.c. */
extern OtaErr_t setImageStateWithReason( OtaImageState_t stateToSet,
uint32_t reasonToSet );
extern bool otaClose( OtaFileContext_t * const pFileContext );
extern bool validateDataBlock( const OtaFileContext_t * pFileContext,
uint32_t blockIndex,
uint32_t blockSize );
extern DocParseErr_t initDocModel( JsonDocModel_t * pDocModel,
const JsonDocParam_t * pBodyDef,
void * contextBaseAddr,
uint32_t contextSize,
uint16_t numJobParams );
extern OtaFileContext_t * parseJobDoc( const JsonDocParam_t * pJsonDoc,
uint16_t numJobParams,
const char * pJson,
uint32_t messageLength,
bool * pUpdateJob );
extern DocParseErr_t validateJSON( const char * pJson,
uint32_t messageLength );
extern IngestResult_t ingestDataBlockCleanup( OtaFileContext_t * pFileContext,
OtaPalStatus_t * pCloseResult );
extern OtaJobParseErr_t verifyActiveJobStatus( OtaFileContext_t * pFileContext,
OtaFileContext_t ** pFinalFile,
bool * pUpdateJob );
/* ========================================================================== */
/* ====================== Unit test helper functions ======================== */
/* ========================================================================== */
static void * mockMallocAlwaysFail( size_t size )
{
( void ) size;
return NULL;
}
static OtaOsStatus_t mockOSEventReset( OtaEventContext_t * unused )
{
otaEventQueueEnd = otaEventQueue;
eventIgnore = false;
( void ) unused;
return OtaOsSuccess;
}
/* Allow an event to be sent only once, after that ignore all incoming event. Useful to make sure
* internal OTA handler are not able to send any event. */
static OtaOsStatus_t mockOSEventSendThenStop( OtaEventContext_t * unused_1,
const void * pEventMsg,
uint32_t unused_2 )
{
( void ) unused_1;
( void ) unused_2;
if( otaEventQueueEnd >= otaEventQueue + OTA_NUM_MSG_Q_ENTRIES )
{
return OtaOsEventQueueSendFailed;
}
if( !eventIgnore )
{
const OtaEventMsg_t * pOtaEvent = pEventMsg;
otaEventQueueEnd->eventId = pOtaEvent->eventId;
otaEventQueueEnd->pEventData = pOtaEvent->pEventData;
otaEventQueueEnd++;
eventIgnore = true;
}
return OtaOsSuccess;
}
/* A variant of mockOSEventSendThenStop, but return failure after first event sent. */
static OtaOsStatus_t mockOSEventSendThenFail( OtaEventContext_t * unused_1,
const void * pEventMsg,
uint32_t unused_2 )
{
OtaOsStatus_t err = OtaOsSuccess;
( void ) unused_1;
( void ) unused_2;
if( eventIgnore )
{
err = OtaOsEventQueueSendFailed;
}
else
{
err = mockOSEventSendThenStop( unused_1, pEventMsg, unused_2 );
}
return err;
}
/* Allow events to be sent any number of times. */
static OtaOsStatus_t mockOSEventSend( OtaEventContext_t * unused_1,
const void * pEventMsg,
uint32_t unused_2 )
{
( void ) unused_1;
( void ) unused_2;
if( otaEventQueueEnd >= otaEventQueue + OTA_NUM_MSG_Q_ENTRIES )
{
return OtaOsEventQueueSendFailed;
}
const OtaEventMsg_t * pOtaEvent = pEventMsg;
otaEventQueueEnd->eventId = pOtaEvent->eventId;
otaEventQueueEnd->pEventData = pOtaEvent->pEventData;
otaEventQueueEnd++;
return OtaOsSuccess;
}
/* Ignore all incoming events and return fail. */
static OtaOsStatus_t mockOSEventSendAlwaysFail( OtaEventContext_t * unused_1,
const void * pEventMsg,
uint32_t unused_2 )
{
( void ) unused_1;
( void ) pEventMsg;
( void ) unused_2;
return OtaOsEventQueueSendFailed;
}
static OtaOsStatus_t mockOSEventReceive( OtaEventContext_t * unused_1,
void * pEventMsg,
uint32_t unused_2 )
{
OtaOsStatus_t err = OtaOsSuccess;
OtaEventMsg_t * pOtaEvent = pEventMsg;
size_t currQueueSize = otaEventQueueEnd - otaEventQueue;
( void ) unused_1;
( void ) unused_2;
if( otaEventQueueEnd != otaEventQueue )
{
pOtaEvent->eventId = otaEventQueue[ 0 ].eventId;
pOtaEvent->pEventData = otaEventQueue[ 0 ].pEventData;
memmove( otaEventQueue, otaEventQueue + 1, sizeof( OtaEventMsg_t ) * ( currQueueSize - 1 ) );
otaEventQueueEnd--;
}
else
{
err = OtaOsEventQueueReceiveFailed;
}
return err;
}
static OtaOsStatus_t stubOSTimerStart( OtaTimerId_t timerId,
const char * const pTimerName,
const uint32_t timeout,
OtaTimerCallback_t callback )
{
( void ) timerId;
( void ) pTimerName;
( void ) timeout;
( void ) callback;
return OtaOsSuccess;
}
static OtaOsStatus_t mockOSTimerInvokeCallback( OtaTimerId_t timerId,
const char * const pTimerName,
const uint32_t timeout,
OtaTimerCallback_t callback )
{
callback( timerId );
( void ) timeout;
( void ) pTimerName;
return OtaOsSuccess;
}
static OtaOsStatus_t mockOSTimerStartAlwaysFail( OtaTimerId_t unused_1,
const char * const unused_2,
const uint32_t unused_3,
OtaTimerCallback_t unused_4 )
{
( void ) unused_1;
( void ) unused_2;
( void ) unused_3;
( void ) unused_4;
return OtaOsTimerStartFailed;
}
static OtaOsStatus_t stubOSTimerStop( OtaTimerId_t timerId )
{
( void ) timerId;
return OtaOsSuccess;
}
static OtaOsStatus_t stubOSTimerDelete( OtaTimerId_t timerId )
{
( void ) timerId;
return OtaOsSuccess;
}
static OtaMqttStatus_t stubMqttSubscribe( const char * unused_1,
uint16_t unused_2,
uint8_t unused_3 )
{
( void ) unused_1;
( void ) unused_2;
( void ) unused_3;
return OtaMqttSuccess;
}
static OtaMqttStatus_t stubMqttSubscribeAlwaysFail( const char * unused_1,
uint16_t unused_2,
uint8_t unused_3 )
{
( void ) unused_1;
( void ) unused_2;
( void ) unused_3;
return OtaMqttSubscribeFailed;
}
static OtaMqttStatus_t stubMqttPublish( const char * const unused_1,
uint16_t unused_2,
const char * unused_3,
uint32_t unused_4,
uint8_t unused_5 )
{
( void ) unused_1;
( void ) unused_2;
( void ) unused_3;
( void ) unused_4;
( void ) unused_5;
return OtaMqttSuccess;
}
OtaErr_t mockControlInterfaceRequestJobAlwaysFail( OtaAgentContext_t * unused )
{
( void ) unused;
return OtaErrRequestJobFailed;
}
OtaErr_t mockControlInterfaceUpdateJobAlwaysFail( OtaAgentContext_t * unused1,
OtaJobStatus_t unused2,
int32_t unused3,
int32_t unused4 )
{
( void ) unused1;
( void ) unused2;
( void ) unused3;
( void ) unused4;
return OtaErrUpdateJobStatusFailed;
}
OtaErr_t mockDataInterfaceInitFileTransferAlwaysFail( OtaAgentContext_t * unused )
{
( void ) unused;
return OtaErrInitFileTransferFailed;
}
OtaErr_t mockDataInitFileTransferAlwaysSucceed( OtaAgentContext_t * unused )
{
( void ) unused;
return OtaErrNone;
}
static OtaMqttStatus_t stubMqttPublishAlwaysFail( const char * const unused_1,
uint16_t unused_2,
const char * unused_3,
uint32_t unused_4,
uint8_t unused_5 )
{
( void ) unused_1;
( void ) unused_2;
( void ) unused_3;
( void ) unused_4;
( void ) unused_5;
return OtaMqttPublishFailed;
}
static OtaMqttStatus_t stubMqttUnsubscribe( const char * unused_1,
uint16_t unused_2,
uint8_t unused_3 )
{
( void ) unused_1;
( void ) unused_2;
( void ) unused_3;
return OtaMqttSuccess;
}
static OtaMqttStatus_t stubMqttUnsubscribeAlwaysFail( const char * unused_1,
uint16_t unused_2,
uint8_t unused_3 )
{
( void ) unused_1;
( void ) unused_2;
( void ) unused_3;
return OtaMqttUnsubscribeFailed;
}
static OtaHttpStatus_t stubHttpInit( char * url )
{
( void ) url;
return OtaHttpSuccess;
}
static OtaHttpStatus_t mockHttpInitAlwaysFail( char * url )
{
( void ) url;
return OtaHttpInitFailed;
}
static OtaHttpStatus_t stubHttpRequest( uint32_t rangeStart,
uint32_t rangeEnd )
{
( void ) rangeEnd;
( void ) rangeStart;
return OtaHttpSuccess;
}
static OtaHttpStatus_t mockHttpRequestAlwaysFail( uint32_t rangeStart,
uint32_t rangeEnd )
{
( void ) rangeEnd;
( void ) rangeStart;
return OtaHttpRequestFailed;
}
static OtaHttpStatus_t stubHttpDeinit()
{
return OtaHttpSuccess;
}
static OtaHttpStatus_t stubHttpDeinitAlwaysFail()
{
return OtaHttpDeinitFailed;
}
OtaPalStatus_t mockPalAbort( OtaFileContext_t * const pFileContext )
{
( void ) pFileContext;
return OTA_PAL_COMBINE_ERR( OtaPalSuccess, 0 );
}
OtaPalStatus_t mockPalCreateFileForRx( OtaFileContext_t * const pFileContext )
{
pOtaFileHandle = ( FILE * ) pOtaFileBuffer;
pFileContext->pFile = pOtaFileHandle;
return OTA_PAL_COMBINE_ERR( OtaPalSuccess, 0 );
}
OtaPalStatus_t mockPalCreateNullFileForRx( OtaFileContext_t * const pFileContext )
{
pFileContext->pFile = NULL;
return OTA_PAL_COMBINE_ERR( OtaPalSuccess, 0 );
}
OtaPalStatus_t mockPalCreateFileForRxAlwaysFail( OtaFileContext_t * const pFileContext )
{
( void ) pFileContext;
return OTA_PAL_COMBINE_ERR( OtaPalRxFileCreateFailed, 0 );
}
OtaPalStatus_t mockPalCloseFile( OtaFileContext_t * const pFileContext )
{
( void ) pFileContext;
return OTA_PAL_COMBINE_ERR( OtaPalSuccess, 0 );
}
OtaPalStatus_t mockPalCloseFileAlwaysFail( OtaFileContext_t * const pFileContext )
{
( void ) pFileContext;
return OTA_PAL_COMBINE_ERR( OtaPalFileClose, 0 );
}
OtaPalStatus_t mockPalCloseFileSigCheckFail( OtaFileContext_t * const pFileContext )
{
( void ) pFileContext;
return OTA_PAL_COMBINE_ERR( OtaPalSignatureCheckFailed, 0 );
}
int16_t mockPalWriteBlock( OtaFileContext_t * const pFileContext,
uint32_t offset,
uint8_t * const pData,
uint32_t blockSize )
{
( void ) pFileContext;
if( offset >= OTA_TEST_FILE_SIZE )
{
TEST_ASSERT_TRUE_MESSAGE( false, "Offset is bigger than test file buffer." );
}
memcpy( pOtaFileBuffer + offset, pData, blockSize );
return blockSize;
}
int16_t mockPalWriteBlockAlwaysFail( OtaFileContext_t * const unused1,
uint32_t unused2,
uint8_t * const unused3,
uint32_t unused4 )
{
( void ) unused1;
( void ) unused2;
( void ) unused3;
( void ) unused4;
return -1;
}
OtaPalStatus_t mockPalActivate( OtaFileContext_t * const pFileContext )
{
( void ) pFileContext;
return OTA_PAL_COMBINE_ERR( OtaPalSuccess, 0 );
}
OtaPalStatus_t mockPalActivateReturnFail( OtaFileContext_t * const pFileContext )
{
( void ) pFileContext;
return OTA_PAL_COMBINE_ERR( OtaPalActivateFailed, 0 );
}
OtaPalStatus_t mockPalResetDevice( OtaFileContext_t * const pFileContext )
{
( void ) pFileContext;
resetCalled = true;
return OTA_PAL_COMBINE_ERR( OtaPalSuccess, 0 );
}
OtaPalStatus_t mockPalSetPlatformImageState( OtaFileContext_t * const pFileContext,
OtaImageState_t eState )
{
( void ) pFileContext;
switch( eState )
{
case OtaImageStateTesting:
palImageState = OtaPalImageStatePendingCommit;
break;
case OtaImageStateAccepted:
palImageState = OtaPalImageStateValid;
break;
default:
palImageState = OtaPalImageStateInvalid;
break;
}
return OTA_PAL_COMBINE_ERR( OtaPalSuccess, 0 );
}
OtaPalStatus_t mockPalSetPlatformImageStateAlwaysFail( OtaFileContext_t * const pFileContext,
OtaImageState_t eState )
{
( void ) pFileContext;
( void ) eState;
return OTA_PAL_COMBINE_ERR( OtaPalBadImageState, 0 );
}
OtaPalImageState_t mockPalGetPlatformImageState( OtaFileContext_t * const pFileContext )
{
( void ) pFileContext;
return palImageState;
}
OtaPalImageState_t mockPalGetPlatformImageStateAlwaysInvalid( OtaFileContext_t * const pFileContext )
{
( void ) pFileContext;
return OtaPalImageStateInvalid;
}
OtaPalImageState_t mockPalGetPlatformImageStateAlwaysPendingCommit( OtaFileContext_t * const pFileContext )
{
( void ) pFileContext;
return OtaPalImageStatePendingCommit;
}
static void mockAppCallback( OtaJobEvent_t event,
const void * pData )
{
OtaJobDocument_t * jobDoc = NULL;
( void ) pData;
if( event == OtaJobEventStartTest )
{
OTA_SetImageState( OtaImageStateAccepted );
}
if( event == OtaJobEventParseCustomJob )
{
jobDoc = ( OtaJobDocument_t * ) pData;
jobDoc->parseErr = OtaJobParseErrNone;
}
}
static void mockAppCallbackCustomParsingFails( OtaJobEvent_t event,
const void * pData )
{
OtaJobDocument_t * jobDoc = NULL;
( void ) pData;
if( event == OtaJobEventStartTest )
{
OTA_SetImageState( OtaImageStateAccepted );
}
if( event == OtaJobEventParseCustomJob )
{
jobDoc = ( OtaJobDocument_t * ) pData;
jobDoc->parseErr = OtaJobParseErrUnknown;
}
}
/* Set default OTA OS interface to mockOSEventSendThenStop. This allows us to easily control the
* state machine transition by blocking any event in OTA internal handlers. */
static void otaInterfaceDefault()
{
otaInterfaces.os.event.init = mockOSEventReset;
otaInterfaces.os.event.send = mockOSEventSendThenStop;
otaInterfaces.os.event.recv = mockOSEventReceive;
otaInterfaces.os.event.deinit = mockOSEventReset;
otaInterfaces.os.timer.start = stubOSTimerStart;
otaInterfaces.os.timer.stop = stubOSTimerStop;
otaInterfaces.os.timer.delete = stubOSTimerDelete;
otaInterfaces.os.mem.malloc = malloc;
otaInterfaces.os.mem.free = free;
otaInterfaces.mqtt.subscribe = stubMqttSubscribe;
otaInterfaces.mqtt.publish = stubMqttPublish;
otaInterfaces.mqtt.unsubscribe = stubMqttUnsubscribe;
otaInterfaces.http.init = stubHttpInit;
otaInterfaces.http.deinit = stubHttpDeinit;
otaInterfaces.http.request = stubHttpRequest;
otaInterfaces.pal.abort = mockPalAbort;
otaInterfaces.pal.createFile = mockPalCreateFileForRx;
otaInterfaces.pal.closeFile = mockPalCloseFile;
otaInterfaces.pal.writeBlock = mockPalWriteBlock;
otaInterfaces.pal.activate = mockPalActivate;
otaInterfaces.pal.reset = mockPalResetDevice;
otaInterfaces.pal.setPlatformImageState = mockPalSetPlatformImageState;
otaInterfaces.pal.getPlatformImageState = mockPalGetPlatformImageState;
}
static void otaAppBufferDefault()
{
pOtaAppBuffer.pUpdateFilePath = pUserBuffer;
pOtaAppBuffer.updateFilePathsize = OTA_UPDATE_FILE_PATH_SIZE;
pOtaAppBuffer.pCertFilePath = pOtaAppBuffer.pUpdateFilePath + pOtaAppBuffer.updateFilePathsize;
pOtaAppBuffer.certFilePathSize = OTA_CERT_FILE_PATH_SIZE;
pOtaAppBuffer.pStreamName = pOtaAppBuffer.pCertFilePath + pOtaAppBuffer.certFilePathSize;
pOtaAppBuffer.streamNameSize = OTA_STREAM_NAME_SIZE;
pOtaAppBuffer.pDecodeMemory = pOtaAppBuffer.pStreamName + pOtaAppBuffer.streamNameSize;
pOtaAppBuffer.decodeMemorySize = OTA_DECODE_MEMORY_SIZE;
pOtaAppBuffer.pFileBitmap = pOtaAppBuffer.pDecodeMemory + pOtaAppBuffer.decodeMemorySize;
pOtaAppBuffer.fileBitmapSize = OTA_FILE_BITMAP_SIZE;
pOtaAppBuffer.pUrl = pOtaAppBuffer.pStreamName + pOtaAppBuffer.streamNameSize;
pOtaAppBuffer.urlSize = OTA_UPDATE_URL_SIZE;
pOtaAppBuffer.pAuthScheme = pOtaAppBuffer.pUrl + pOtaAppBuffer.urlSize;
pOtaAppBuffer.authSchemeSize = OTA_AUTH_SCHEME_SIZE;
}
/* Helper function for processing all elements in the queue if there are any. */
static void processEntireQueue()
{
if( otaEventQueueEnd >= otaEventQueue + OTA_NUM_MSG_Q_ENTRIES )
{
return;
}
while( otaEventQueueEnd != otaEventQueue )
{
receiveAndProcessOtaEvent();
}
}
static void otaInit( const char * pClientID,
OtaAppCallback_t appCallback )
{
OTA_Init( &pOtaAppBuffer,
&otaInterfaces,
( const uint8_t * ) pClientID,
appCallback );
}
static void otaInitDefault()
{
otaInit( pOtaDefaultClientId, mockAppCallback );
}
static void otaDeinit()
{
mockOSEventReset( NULL );
OTA_Shutdown( otaDefaultWait, unsubscribeFlag );
processEntireQueue();
}
static void otaReceiveJobDocument()
{
TEST_ASSERT_NOT_EQUAL( NULL, pOtaJobDoc );
size_t job_doc_len = strlen( pOtaJobDoc );
OtaEventMsg_t otaEvent = { 0 };
/* Parse success would create the file, let it invoke our mock when creating file. */
otaEvent.eventId = OtaAgentEventReceivedJobDocument;
otaEvent.pEventData = &eventBuffer;
memcpy( otaEvent.pEventData->data, pOtaJobDoc, job_doc_len );
otaEvent.pEventData->dataLength = job_doc_len;
OTA_SignalEvent( &otaEvent );
}
/* Jump to any state in OTA agent. The event send interface must be set to
* mockOSEventSendThenStop to prevent any OTA internal state transitions. */
static void otaGoToState( OtaState_t state )
{
OtaEventMsg_t otaEvent = { 0 };
if( state == OTA_GetState() )
{
return;
}
if( OtaAgentStateStopped == OTA_GetState() )
{
otaInitDefault();
}
/* Default to the MQTT job doc. */
if( pOtaJobDoc == NULL )
{
pOtaJobDoc = JOB_DOC_A;
}
switch( state )
{
case OtaAgentStateInit:
/* Nothing needs to be done here since we should either be in init state already or
* we are in other running states. */
break;
case OtaAgentStateReady:
otaAgent.state = OtaAgentStateReady;
break;
case OtaAgentStateRequestingJob:
otaGoToState( OtaAgentStateReady );
otaEvent.eventId = OtaAgentEventStart;
OTA_SignalEvent( &otaEvent );
receiveAndProcessOtaEvent();
break;
case OtaAgentStateWaitingForJob:
otaGoToState( OtaAgentStateRequestingJob );
otaEvent.eventId = OtaAgentEventRequestJobDocument;
OTA_SignalEvent( &otaEvent );
receiveAndProcessOtaEvent();
break;
case OtaAgentStateCreatingFile:
otaGoToState( OtaAgentStateWaitingForJob );
otaReceiveJobDocument();
receiveAndProcessOtaEvent();
break;
case OtaAgentStateRequestingFileBlock:
otaGoToState( OtaAgentStateCreatingFile );
otaEvent.eventId = OtaAgentEventCreateFile;
OTA_SignalEvent( &otaEvent );
receiveAndProcessOtaEvent();
break;
case OtaAgentStateWaitingForFileBlock:
otaGoToState( OtaAgentStateRequestingFileBlock );
otaEvent.eventId = OtaAgentEventRequestFileBlock;
OTA_SignalEvent( &otaEvent );
receiveAndProcessOtaEvent();
break;
case OtaAgentStateSuspended:
otaGoToState( OtaAgentStateReady );
OTA_Suspend();
receiveAndProcessOtaEvent();
break;
default:
break;
}
mockOSEventReset( NULL );
}
/* ========================================================================== */
/* ================ Unit test setup and tear down functions ================= */
/* ========================================================================== */
void setUp()
{
TEST_ASSERT_EQUAL( OtaAgentStateStopped, OTA_GetState() );
otaInterfaceDefault();
otaAppBufferDefault();
}
void tearDown()
{
palImageState = OtaPalImageStateUnknown;
resetCalled = false;
pOtaJobDoc = NULL;
pOtaFileHandle = NULL;
memset( pOtaFileBuffer, 0, OTA_TEST_FILE_SIZE );
otaInterfaceDefault();
otaDeinit();
TEST_ASSERT_EQUAL( OtaAgentStateStopped, OTA_GetState() );
}
/* ========================================================================== */
/* =============================== Unit tests =============================== */
/* ========================================================================== */
void test_OTA_InitWhenStopped()
{
otaGoToState( OtaAgentStateInit );
TEST_ASSERT_EQUAL( OtaAgentStateInit, OTA_GetState() );
}
void test_OTA_InitWhenReady()
{
otaGoToState( OtaAgentStateReady );
TEST_ASSERT_EQUAL( OtaAgentStateReady, OTA_GetState() );
/* Calling init again should remain in ready state. */
otaInitDefault();
TEST_ASSERT_EQUAL( OtaAgentStateReady, OTA_GetState() );
/* Explicitly test NULL client name and NULL complete callback. */
otaInit( NULL, NULL );
TEST_ASSERT_EQUAL( OtaAgentStateReady, OTA_GetState() );
}
void test_OTA_InitWithNullName()
{
/* Explicitly test NULL client name. OTA agent should remain in stopped state. */
otaInit( NULL, mockAppCallback );
TEST_ASSERT_EQUAL( OtaAgentStateStopped, OTA_GetState() );
}
void test_OTA_InitWithNameTooLong()
{
/* OTA does not accept name longer than 64. Explicitly test long client name. */
char long_name[ 100 ] = { 0 };
memset( long_name, 1, sizeof( long_name ) - 1 );
otaInit( long_name, mockAppCallback );
TEST_ASSERT_EQUAL( OtaAgentStateStopped, OTA_GetState() );
}
void test_OTA_InitNullAppBuffers()
{
/* Test for having NULL pointers but valid sizes. */
pOtaAppBuffer.pUpdateFilePath = NULL;
pOtaAppBuffer.updateFilePathsize = OTA_UPDATE_FILE_PATH_SIZE;
pOtaAppBuffer.pCertFilePath = NULL;
pOtaAppBuffer.certFilePathSize = OTA_CERT_FILE_PATH_SIZE;
pOtaAppBuffer.pStreamName = NULL;
pOtaAppBuffer.streamNameSize = OTA_STREAM_NAME_SIZE;
pOtaAppBuffer.pDecodeMemory = NULL;
pOtaAppBuffer.decodeMemorySize = OTA_DECODE_MEMORY_SIZE;
pOtaAppBuffer.pFileBitmap = NULL;
pOtaAppBuffer.fileBitmapSize = OTA_FILE_BITMAP_SIZE;
pOtaAppBuffer.pUrl = NULL;
pOtaAppBuffer.urlSize = OTA_UPDATE_URL_SIZE;
pOtaAppBuffer.pAuthScheme = NULL;
pOtaAppBuffer.authSchemeSize = OTA_AUTH_SCHEME_SIZE;
OTA_Init( &pOtaAppBuffer,
&otaInterfaces,
( const uint8_t * ) pOtaDefaultClientId,
mockAppCallback );
TEST_ASSERT_EQUAL( OtaAgentStateInit, OTA_GetState() );
}
void test_OTA_InitZeroAppBufferSizes()
{
/* Test for having valid pointers with zero sizes. */
pOtaAppBuffer.pUpdateFilePath = pUserBuffer;
pOtaAppBuffer.updateFilePathsize = 0;
pOtaAppBuffer.pCertFilePath = pUserBuffer;
pOtaAppBuffer.certFilePathSize = 0;
pOtaAppBuffer.pStreamName = pUserBuffer;
pOtaAppBuffer.streamNameSize = 0;
pOtaAppBuffer.pDecodeMemory = pUserBuffer;
pOtaAppBuffer.decodeMemorySize = 0;
pOtaAppBuffer.pFileBitmap = pUserBuffer;
pOtaAppBuffer.fileBitmapSize = 0;
pOtaAppBuffer.pUrl = pUserBuffer;
pOtaAppBuffer.urlSize = 0;
pOtaAppBuffer.pAuthScheme = pUserBuffer;
pOtaAppBuffer.authSchemeSize = 0;
OTA_Init( &pOtaAppBuffer,
&otaInterfaces,
( const uint8_t * ) pOtaDefaultClientId,
mockAppCallback );
TEST_ASSERT_EQUAL( OtaAgentStateInit, OTA_GetState() );
}
void test_OTA_ShutdownWithDelay()
{
otaGoToState( OtaAgentStateReady );
OTA_Shutdown( 2000, 0 );
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateStopped, OTA_GetState() );
}
void test_OTA_ShutdownWhenStopped()
{
/* Calling shutdown when already stopped should have no effect. */
OTA_Shutdown( otaDefaultWait, unsubscribeFlag );
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateStopped, OTA_GetState() );
}
void test_OTA_ShutdownFailToSendEvent()
{
otaGoToState( OtaAgentStateReady );
TEST_ASSERT_EQUAL( OtaAgentStateReady, OTA_GetState() );
/* Set the event send interface to a mock function that always fail. */
otaInterfaces.os.event.send = mockOSEventSendAlwaysFail;
/* Shutdown should now fail and OTA agent should remain in ready state. */
OTA_Shutdown( otaDefaultWait, unsubscribeFlag );
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateReady, OTA_GetState() );
}
void test_OTA_ShutdownTwiceBeforeProcessing()
{
otaInterfaces.os.event.send = mockOSEventSend;
otaGoToState( OtaAgentStateReady );
OTA_Shutdown( 0, 0 );
OTA_Shutdown( 0, 0 );
receiveAndProcessOtaEvent();
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateStopped, OTA_GetState() );
}
void test_OTA_CloseNullInput()
{
TEST_ASSERT_EQUAL( false, otaClose( NULL ) );
}
void test_OTA_StartWhenReady()
{
OtaEventMsg_t otaEvent = { 0 };
/* Let the PAL says it's not in self test.*/
palImageState = OtaPalImageStateValid;
otaGoToState( OtaAgentStateReady );
TEST_ASSERT_EQUAL( OtaAgentStateReady, OTA_GetState() );
otaEvent.eventId = OtaAgentEventStart;
OTA_SignalEvent( &otaEvent );
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateRequestingJob, OTA_GetState() );
}
void test_OTA_StartFailedWhenReady()
{
OtaEventMsg_t otaEvent = { 0 };
/* Let the PAL says it's not in self test.*/
palImageState = OtaPalImageStateValid;
otaGoToState( OtaAgentStateReady );
TEST_ASSERT_EQUAL( OtaAgentStateReady, OTA_GetState() );
/* Set the event send interface to a mock function that fails after first event sent. */
otaInterfaces.os.event.send = mockOSEventSendThenFail;
/* The event handler should fail, so OTA agent should remain in OtaAgentStateReady state. */
otaEvent.eventId = OtaAgentEventStart;
OTA_SignalEvent( &otaEvent );
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateReady, OTA_GetState() );
}
void test_OTA_SuspendWhenStopped()
{
/* Calling suspend when stopped should return an error. */
TEST_ASSERT_NOT_EQUAL( OtaErrNone, OTA_Suspend() );
/* OTA agent should remain in stopped state. */
TEST_ASSERT_EQUAL( OtaAgentStateStopped, OTA_GetState() );
}
void test_OTA_SuspendWhenReady()
{
otaGoToState( OtaAgentStateReady );
TEST_ASSERT_EQUAL( OtaAgentStateReady, OTA_GetState() );
TEST_ASSERT_EQUAL( OtaErrNone, OTA_Suspend() );
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateSuspended, OTA_GetState() );
}
void test_OTA_SuspendFailedWhenReady()
{
otaGoToState( OtaAgentStateReady );
TEST_ASSERT_EQUAL( OtaAgentStateReady, OTA_GetState() );
/* Set the event send interface to a mock function that always fail. */
otaInterfaces.os.event.send = mockOSEventSendAlwaysFail;
/* Suspend should fail and OTA agent should remain in ready state. */
TEST_ASSERT_EQUAL( OtaErrSignalEventFailed, OTA_Suspend() );
TEST_ASSERT_EQUAL( OtaAgentStateReady, OTA_GetState() );
}
void test_OTA_ResumeWhenStopped()
{
/* Calling resume when stopped should return an error. */
TEST_ASSERT_NOT_EQUAL( OtaErrNone, OTA_Resume() );
/* OTA agent should remain in stopped state. */
TEST_ASSERT_EQUAL( OtaAgentStateStopped, OTA_GetState() );
}
void test_OTA_ResumeWhenSuspended()
{
otaGoToState( OtaAgentStateSuspended );
TEST_ASSERT_EQUAL( OtaAgentStateSuspended, OTA_GetState() );
TEST_ASSERT_EQUAL( OtaErrNone, OTA_Resume() );
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateRequestingJob, OTA_GetState() );
}
void test_OTA_ResumeWhenReady()
{
otaGoToState( OtaAgentStateReady );
TEST_ASSERT_EQUAL( OtaAgentStateReady, OTA_GetState() );
/* Calling resume when OTA agent is not suspend state. This should be an unexpected event and
* the agent should remain in ready state. */
TEST_ASSERT_EQUAL( OtaErrNone, OTA_Resume() );
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateReady, OTA_GetState() );
}
void test_OTA_ResumeFailedWhenSuspended()
{
otaGoToState( OtaAgentStateSuspended );
TEST_ASSERT_EQUAL( OtaAgentStateSuspended, OTA_GetState() );
/* Set the event send interface to a mock function that always fail. */
otaInterfaces.os.event.send = mockOSEventSendAlwaysFail;
/* Resume should fail and OTA agent should remain in suspend state. */
TEST_ASSERT_EQUAL( OtaErrSignalEventFailed, OTA_Resume() );
TEST_ASSERT_EQUAL( OtaAgentStateSuspended, OTA_GetState() );
}
void test_OTA_Statistics()
{
otaGoToState( OtaAgentStateReady );
TEST_ASSERT_EQUAL( OtaAgentStateReady, OTA_GetState() );
TEST_ASSERT_EQUAL( OtaErrInvalidArg, OTA_GetStatistics( NULL ) );
OtaAgentStatistics_t statistics = { 0 };
TEST_ASSERT_EQUAL( OtaErrNone, OTA_GetStatistics( &statistics ) );
TEST_ASSERT_EQUAL( 0, statistics.otaPacketsReceived );
TEST_ASSERT_EQUAL( 0, statistics.otaPacketsQueued );
TEST_ASSERT_EQUAL( 0, statistics.otaPacketsProcessed );
TEST_ASSERT_EQUAL( 0, statistics.otaPacketsDropped );
}
void test_OTA_CheckForUpdate()
{
otaGoToState( OtaAgentStateRequestingJob );
TEST_ASSERT_EQUAL( OtaAgentStateRequestingJob, OTA_GetState() );
TEST_ASSERT_EQUAL( OtaErrNone, OTA_CheckForUpdate() );
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
void test_OTA_CheckForUpdateFailToSendEvent()
{
otaGoToState( OtaAgentStateRequestingJob );
TEST_ASSERT_EQUAL( OtaAgentStateRequestingJob, OTA_GetState() );
/* Set the event send interface to a mock function that always fail. */
otaInterfaces.os.event.send = mockOSEventSendAlwaysFail;
/* Check for update should fail and OTA agent should remain in requesting job state. */
TEST_ASSERT_EQUAL( OtaErrSignalEventFailed, OTA_CheckForUpdate() );
TEST_ASSERT_EQUAL( OtaAgentStateRequestingJob, OTA_GetState() );
}
void test_OTA_ActivateNewImage()
{
otaGoToState( OtaAgentStateReady );
TEST_ASSERT_EQUAL( OtaAgentStateReady, OTA_GetState() );
/* Activate image simply calls the PAL implementation and return its return value. */
TEST_ASSERT_EQUAL( OtaErrNone, OTA_ActivateNewImage() );
otaInterfaces.pal.activate = mockPalActivateReturnFail;
TEST_ASSERT_EQUAL( OtaErrActivateFailed, OTA_ActivateNewImage() );
}
/* OTA pal function pointers should be NULL when OTA agent stopped. Calling OTA_ActivateNewImage
* should fail. */
void test_OTA_ActivateNewImageWhenStopped()
{
TEST_ASSERT_NOT_EQUAL( OtaErrNone, OTA_ActivateNewImage() );
}
void test_OTA_ActivateNewImageNullPalActivate()
{
/* Have all other interfaces besides the activate function of the PAL
* interface. */
otaInterfaces.pal.activate = NULL;
/* Initialize the OTA Agent to set the interfaces before trying to
* activate the new image. */
OTA_Init( &pOtaAppBuffer,
&otaInterfaces,
( const uint8_t * ) pOtaDefaultClientId,
mockAppCallback );
TEST_ASSERT_EQUAL( OtaAgentStateInit, OTA_GetState() );
TEST_ASSERT_EQUAL( OtaErrActivateFailed, OTA_ActivateNewImage() );
}
void test_OTA_ImageStateAbortWithActiveJob()
{
otaGoToState( OtaAgentStateWaitingForFileBlock );
/* Calling abort with an active job would make OTA agent transit to waiting for job state. */
TEST_ASSERT_EQUAL( OtaErrNone, OTA_SetImageState( OtaImageStateAborted ) );
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
void test_OTA_ImageStateAbortWithNoJob()
{
otaGoToState( OtaAgentStateReady );
TEST_ASSERT_EQUAL( OtaAgentStateReady, OTA_GetState() );
/* Set the event send interface to a mock function that allows events to be sent continuously
* since setting image state to abort would send an user abort event in the handler. */
otaInterfaces.os.event.send = mockOSEventSend;
/* Calling abort without an active job would fail. OTA agent should remain in ready state. */
TEST_ASSERT_EQUAL( OtaErrNone, OTA_SetImageState( OtaImageStateAborted ) );
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateReady, OTA_GetState() );
}
void test_OTA_ImageStateAbortFailToSendEvent()
{
otaGoToState( OtaAgentStateReady );
TEST_ASSERT_EQUAL( OtaAgentStateReady, OTA_GetState() );
/* Set the event send interface to a mock function that always fail. */
otaInterfaces.os.event.send = mockOSEventSendAlwaysFail;
TEST_ASSERT_EQUAL( OtaErrSignalEventFailed, OTA_SetImageState( OtaImageStateAborted ) );
TEST_ASSERT_EQUAL( OtaAgentStateReady, OTA_GetState() );
}
void test_OTA_ImageStateAbortUpdateStatusFail()
{
/* Allow event to be sent continuously so that retries can work. */
otaInterfaces.os.event.send = mockOSEventSend;
otaGoToState( OtaAgentStateWaitingForFileBlock );
/* Successfully send the event to abort the image. */
TEST_ASSERT_EQUAL( OtaErrNone, OTA_SetImageState( OtaImageStateAborted ) );
/* Process the event to abort the image and fail to update the job status. */
otaControlInterface.updateJobStatus = mockControlInterfaceUpdateJobAlwaysFail;
receiveAndProcessOtaEvent();
/* Test that the image state will be set regardless of whether or not the
* job status was updated successfully. */
TEST_ASSERT_EQUAL( OtaImageStateAborted, OTA_GetImageState() );
}
void test_OTA_ImageStateRjectWithActiveJob()
{
otaGoToState( OtaAgentStateWaitingForFileBlock );
TEST_ASSERT_EQUAL( OtaErrNone, OTA_SetImageState( OtaImageStateRejected ) );
TEST_ASSERT_EQUAL( OtaImageStateRejected, OTA_GetImageState() );
}
void test_OTA_ImageStateRjectWithNoJob()
{
otaGoToState( OtaAgentStateReady );
TEST_ASSERT_EQUAL( OtaAgentStateReady, OTA_GetState() );
TEST_ASSERT_EQUAL( OtaErrNoActiveJob, OTA_SetImageState( OtaImageStateRejected ) );
TEST_ASSERT_EQUAL( OtaImageStateRejected, OTA_GetImageState() );
}
void test_OTA_ImageStateAcceptWithActiveJob()
{
otaGoToState( OtaAgentStateWaitingForFileBlock );
TEST_ASSERT_EQUAL( OtaErrNone, OTA_SetImageState( OtaImageStateAccepted ) );
TEST_ASSERT_EQUAL( OtaImageStateAccepted, OTA_GetImageState() );
}
void test_OTA_ImageStateAcceptWithNoJob()
{
otaGoToState( OtaAgentStateReady );
TEST_ASSERT_EQUAL( OtaAgentStateReady, OTA_GetState() );
TEST_ASSERT_EQUAL( OtaErrNoActiveJob, OTA_SetImageState( OtaImageStateAccepted ) );
TEST_ASSERT_EQUAL( OtaImageStateAccepted, OTA_GetImageState() );
}
void test_OTA_ImageStateInvalidState()
{
TEST_ASSERT_EQUAL( OtaErrInvalidArg, OTA_SetImageState( -1 ) );
}
void test_OTA_ImageStatePalFail()
{
otaInterfaces.pal.setPlatformImageState = mockPalSetPlatformImageStateAlwaysFail;
otaGoToState( OtaAgentStateReady );
TEST_ASSERT_EQUAL( OtaErrNoActiveJob, OTA_SetImageState( OtaImageStateAccepted ) );
TEST_ASSERT_EQUAL( OtaImageStateRejected, OTA_GetImageState() );
}
void test_OTA_ImageStateWithReasonPalFail()
{
OtaImageState_t stateToSet = OtaImageStateAccepted;
OtaErr_t error;
otaGoToState( OtaAgentStateReady );
otaInterfaces.pal.setPlatformImageState = mockPalSetPlatformImageStateAlwaysFail;
error = setImageStateWithReason( stateToSet, OtaErrImageStateMismatch );
TEST_ASSERT_EQUAL( OtaErrNoActiveJob, error );
}
void test_OTA_RequestJobDocumentRetryFail()
{
OtaEventMsg_t otaEvent = { 0 };
uint32_t i = 0;
otaGoToState( OtaAgentStateReady );
TEST_ASSERT_EQUAL( OtaAgentStateReady, OTA_GetState() );
/* Let MQTT publish fail so request job will also fail. */
otaInterfaces.mqtt.publish = stubMqttPublishAlwaysFail;
/* Let timer invoke callback directly. */
otaInterfaces.os.timer.start = mockOSTimerInvokeCallback;
/* Allow event to be sent continuously so that retries can work. */
otaInterfaces.os.event.send = mockOSEventSend;
/* Place the OTA Agent into the state for requesting a job. */
otaEvent.eventId = OtaAgentEventStart;
OTA_SignalEvent( &otaEvent );
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateRequestingJob, OTA_GetState() );
/* Fail the maximum number of attempts to request a job document. */
for( i = 0U; i < otaconfigMAX_NUM_REQUEST_MOMENTUM; ++i )
{
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateRequestingJob, OTA_GetState() );
}
/* Attempt to request another job document after failing the maximum number
* of times, triggering a shutdown event. */
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateRequestingJob, OTA_GetState() );
/* Shutdown after processing the shutdown event. */
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateStopped, OTA_GetState() );
}
void test_OTA_ProcessJobDocumentInvalidJson()
{
pOtaJobDoc = JOB_DOC_INVALID;
otaGoToState( OtaAgentStateWaitingForJob );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
otaReceiveJobDocument();
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
/**
* @brief Test that the job is rejected if the file key signature cannot be
* decoded.
*/
void test_OTA_ProcessJobDocumentInvalidBase64Key()
{
pOtaJobDoc = JOB_DOC_INVALID_BASE64_KEY;
otaGoToState( OtaAgentStateWaitingForJob );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
otaReceiveJobDocument();
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
/**
* @brief Test that the job is rejected if a required key(here filesize)
* is missing from the job document.
*/
void test_OTA_ProcessJobDocumentMissingRequiredKey()
{
pOtaJobDoc = JOB_DOC_MISSING_KEY;
otaGoToState( OtaAgentStateWaitingForJob );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
otaReceiveJobDocument();
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
/**
* @brief Test that the job is rejected if a field that is expected
* to be an integer is NaN.
*/
void test_OTA_ProcessJobDocumentInvalidNum()
{
pOtaJobDoc = JOB_DOC_INVALID_NUMBER_VAL;
otaGoToState( OtaAgentStateWaitingForJob );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
otaReceiveJobDocument();
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
/**
* @brief Test that the job is rejected if a field that is expected
* to be an integer is greater than the maximum allowed number for
* string to int conversion (LONG_MAX).
*/
void test_OTA_ProcessJobDocumentNumOverflow()
{
pOtaJobDoc = JOB_DOC_INVALID_NUMBER_NAN;
otaGoToState( OtaAgentStateWaitingForJob );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
otaReceiveJobDocument();
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
void test_OTA_ProcessCustomJobAppCallbackFails()
{
pOtaJobDoc = JOB_DOC_MISSING_KEY;
otaInit( pOtaDefaultClientId, mockAppCallbackCustomParsingFails );
TEST_ASSERT_EQUAL( OtaAgentStateInit, OTA_GetState() );
otaGoToState( OtaAgentStateCreatingFile );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
void test_OTA_ProcessCustomJobFailsInvalidKeys()
{
/* Custom job parsing fails with key 'jobDocument' missing. */
pOtaJobDoc = JOB_DOC_MISSING_JOB_DOC;
otaGoToState( OtaAgentStateCreatingFile );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
/* Custom job parsing fails with key 'jobId' missing. */
pOtaJobDoc = JOB_DOC_MISSING_JOB_ID;
otaGoToState( OtaAgentStateCreatingFile );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
/* Custom job parsing fails with value of 'jobId' exceeding max length(72). */
pOtaJobDoc = JOB_DOC_INVALID_JOB_ID;
otaGoToState( OtaAgentStateCreatingFile );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
void test_OTA_RejectWhileAborted()
{
pOtaJobDoc = JOB_DOC_INVALID;
otaGoToState( OtaAgentStateWaitingForJob );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
otaInterfaces.pal.setPlatformImageState = mockPalSetPlatformImageStateAlwaysFail;
otaReceiveJobDocument();
receiveAndProcessOtaEvent();
/* Test that the OTA Agent is able to recover after failing to reject a job
* due to already having aborted the job. It is not necessary for the call
* to setPlatformImageState to fail for this behavior to happen, but it is
* required to reach the branch that checks for this scenario. */
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
void test_OTA_ProcessJobDocumentInvalidProtocol()
{
pOtaJobDoc = JOB_DOC_INVALID_PROTOCOL;
otaGoToState( OtaAgentStateWaitingForJob );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
otaReceiveJobDocument();
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateCreatingFile, OTA_GetState() );
}
void test_OTA_ProcessJobDocumentInvalidProtocolPalFails()
{
pOtaJobDoc = JOB_DOC_INVALID_PROTOCOL;
otaInterfaces.pal.setPlatformImageState = mockPalSetPlatformImageStateAlwaysFail;
otaGoToState( OtaAgentStateWaitingForJob );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
otaReceiveJobDocument();
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateCreatingFile, OTA_GetState() );
}
void test_OTA_ProcessJobDocumentValidJson()
{
pOtaJobDoc = JOB_DOC_A;
otaGoToState( OtaAgentStateWaitingForJob );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
otaReceiveJobDocument();
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateCreatingFile, OTA_GetState() );
}
void test_OTA_ProcessJobDocumentPalCreateFileFail()
{
otaInterfaces.pal.createFile = mockPalCreateFileForRxAlwaysFail;
otaGoToState( OtaAgentStateWaitingForJob );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
otaReceiveJobDocument();
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
void test_OTA_ProcessJobDocumentBitmapMallocFail()
{
pOtaAppBuffer.pFileBitmap = NULL;
otaInterfaces.os.mem.malloc = mockMallocAlwaysFail;
otaGoToState( OtaAgentStateWaitingForJob );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
otaReceiveJobDocument();
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
void test_OTA_ProcessJobDocumentEventSendFail( void )
{
pOtaJobDoc = JOB_DOC_A;
otaGoToState( OtaAgentStateWaitingForJob );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
otaReceiveJobDocument();
otaInterfaces.os.event.send = mockOSEventSendAlwaysFail;
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
static void otaInitFileTransfer()
{
OtaEventMsg_t otaEvent = { 0 };
otaGoToState( OtaAgentStateCreatingFile );
TEST_ASSERT_EQUAL( OtaAgentStateCreatingFile, OTA_GetState() );
otaEvent.eventId = OtaAgentEventCreateFile;
OTA_SignalEvent( &otaEvent );
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateRequestingFileBlock, OTA_GetState() );
}
void test_OTA_InitFileTransferMqtt()
{
pOtaJobDoc = JOB_DOC_A;
otaInitFileTransfer();
}
void test_OTA_InitFileTransferHttp()
{
pOtaJobDoc = JOB_DOC_HTTP;
otaInitFileTransfer();
}
void test_OTA_InitFileTransferRetryFail()
{
uint32_t i = 0U;
/* Use HTTP data transfer so we can intentionally fail the init transfer. */
pOtaJobDoc = JOB_DOC_HTTP;
otaGoToState( OtaAgentStateWaitingForJob );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
/* Let http init fail so init file transfer will also fail. */
otaInterfaces.http.init = mockHttpInitAlwaysFail;
/* Let timer invoke callback directly. */
otaInterfaces.os.timer.start = mockOSTimerInvokeCallback;
/* Allow event to be sent continuously so that retries can work. */
otaInterfaces.os.event.send = mockOSEventSend;
/* After receiving a valid job document, OTA agent should first transit to creating file state.
* Then keep calling init file transfer until failed, which should then shutdown itself. */
otaReceiveJobDocument();
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateCreatingFile, OTA_GetState() );
/* Make the maximum number of failures based on the momentum parameter. */
for( i = 0U; i < otaconfigMAX_NUM_REQUEST_MOMENTUM; ++i )
{
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateCreatingFile, OTA_GetState() );
}
/* Make another attempt after already reaching the maximum number of
* allowable attempts, which generates a shutdown event. */
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateCreatingFile, OTA_GetState() );
/* Shutdown the OTA Agent after processing the event. */
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateStopped, OTA_GetState() );
}
static void otaRequestFileBlock()
{
OtaEventMsg_t otaEvent = { 0 };
otaGoToState( OtaAgentStateRequestingFileBlock );
TEST_ASSERT_EQUAL( OtaAgentStateRequestingFileBlock, OTA_GetState() );
otaEvent.eventId = OtaAgentEventRequestFileBlock;
OTA_SignalEvent( &otaEvent );
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForFileBlock, OTA_GetState() );
}
void test_OTA_RequestFileBlockMqtt()
{
pOtaJobDoc = JOB_DOC_A;
otaRequestFileBlock();
}
void test_OTA_RequestFileBlockHttp()
{
pOtaJobDoc = JOB_DOC_HTTP;
otaRequestFileBlock();
}
void test_OTA_RequestFileBlockHttpOneBlock()
{
pOtaJobDoc = JOB_DOC_ONE_BLOCK;
otaRequestFileBlock();
}
void test_OTA_RequestFileBlockTimerFails()
{
OtaEventMsg_t otaEvent = { 0 };
/* Set the event send functionality to always succeed. */
otaInterfaces.os.event.send = mockOSEventSend;
/* Prepare the OTA Agent to request a block. */
otaGoToState( OtaAgentStateRequestingFileBlock );
TEST_ASSERT_EQUAL( OtaAgentStateRequestingFileBlock, OTA_GetState() );
otaEvent.eventId = OtaAgentEventRequestFileBlock;
OTA_SignalEvent( &otaEvent );
/* Set the request timer to fail when attempting to start. */
otaInterfaces.os.timer.start = mockOSTimerStartAlwaysFail;
/* Process the event to request a block. */
receiveAndProcessOtaEvent();
/* Process the event to shut down. */
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateStopped, OTA_GetState() );
}
void test_OTA_RequestFileBlockRetryFail()
{
uint32_t i = 0;
/* Use HTTP data transfer so we can intentionally fail the file block request. */
pOtaJobDoc = JOB_DOC_HTTP;
otaGoToState( OtaAgentStateWaitingForJob );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
/* Let http request fail so request file block will also fail. */
otaInterfaces.http.request = mockHttpRequestAlwaysFail;
/* Let timer invoke callback directly. */
otaInterfaces.os.timer.start = mockOSTimerInvokeCallback;
/* Allow event to be sent continuously so that retries can work. */
otaInterfaces.os.event.send = mockOSEventSend;
/* After receiving a valid job document and starts file transfer, OTA agent should first transit
* to requesting file block state. Then keep calling request file block until failed. */
/* Receive the job document. */
otaReceiveJobDocument();
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateCreatingFile, OTA_GetState() );
/* Successfully initialize the file transfer. */
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateRequestingFileBlock, OTA_GetState() );
/* Request a file block and fail the maximum number of times. */
for( i = 0; i < otaconfigMAX_NUM_REQUEST_MOMENTUM; ++i )
{
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateRequestingFileBlock, OTA_GetState() );
}
/* The timer triggers as we check for the number of attempts so far. The
* number of attempts is at the maximum already so we also create a
* shutdown event. */
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateRequestingFileBlock, OTA_GetState() );
/* Process the timer event. */
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateRequestingFileBlock, OTA_GetState() );
/* Process the shutdown event. */
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateStopped, OTA_GetState() );
}
void test_OTA_ReceiveFileBlockEmpty()
{
OtaEventMsg_t otaEvent = { 0 };
otaGoToState( OtaAgentStateWaitingForFileBlock );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForFileBlock, OTA_GetState() );
/* Set the event send interface to a mock function that allows events to be sent continuously.
* This is required because decode failure would cause OtaAgentEventCloseFile event to be sent
* within the OTA event handler and we want it to be processed. */
otaInterfaces.os.event.send = mockOSEventSend;
otaEvent.eventId = OtaAgentEventReceivedFileBlock;
otaEvent.pEventData = &eventBuffer;
otaEvent.pEventData->dataLength = 0;
OTA_SignalEvent( &otaEvent );
/* Process the event for receiving the block to trigger digesting it. */
receiveAndProcessOtaEvent();
/* Process the event generated after failing to decode the block. The
* expected result is that we close the file and begin waiting for a new
* job document. */
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
void test_OTA_ReceiveFileBlockTooLarge()
{
OtaEventMsg_t otaEvent = { 0 };
pOtaJobDoc = JOB_DOC_HTTP;
otaGoToState( OtaAgentStateWaitingForFileBlock );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForFileBlock, OTA_GetState() );
otaInterfaces.os.event.send = mockOSEventSend;
otaEvent.eventId = OtaAgentEventReceivedFileBlock;
otaEvent.pEventData = &eventBuffer;
otaEvent.pEventData->dataLength = OTA_FILE_BLOCK_SIZE + 1;
OTA_SignalEvent( &otaEvent );
/* Process the event for receiving the block to trigger digesting it. */
receiveAndProcessOtaEvent();
/* Process the event generated after failing to decode the block. */
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
void test_OTA_ReceiveLastFileBlockTooSmall()
{
OtaEventMsg_t otaEvent = { 0 };
pOtaJobDoc = JOB_DOC_ONE_BLOCK;
otaGoToState( OtaAgentStateWaitingForFileBlock );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForFileBlock, OTA_GetState() );
otaInterfaces.os.event.send = mockOSEventSend;
otaEvent.eventId = OtaAgentEventReceivedFileBlock;
otaEvent.pEventData = &eventBuffer;
otaEvent.pEventData->dataLength = OTA_FILE_BLOCK_SIZE - 1;
OTA_SignalEvent( &otaEvent );
/* Process the event to receive the invalid block. */
receiveAndProcessOtaEvent();
/* Process the event generated after receiving an invalid block. */
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
void test_OTA_ReceiveWithNullFile()
{
OtaEventMsg_t otaEvent = { 0 };
const uint32_t dataSize = 1024;
pOtaJobDoc = JOB_DOC_ONE_BLOCK;
otaInterfaces.pal.createFile = mockPalCreateNullFileForRx;
otaInterfaces.os.event.send = mockOSEventSend;
otaGoToState( OtaAgentStateWaitingForFileBlock );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForFileBlock, OTA_GetState() );
otaEvent.eventId = OtaAgentEventReceivedFileBlock;
otaEvent.pEventData = &eventBuffer;
otaEvent.pEventData->dataLength = dataSize;
OTA_SignalEvent( &otaEvent );
/* Process the event to receive the block */
receiveAndProcessOtaEvent();
/* Process the event generated after receiving a valid block while the
* Agent has a NULL file pointer. */
receiveAndProcessOtaEvent();
/* Having a NULL file pointer means that the data blocks can not be saved.
* This is treated as a failure for the job as a whole. Ensure that after
* failing the current job because the file pointer is NULL, the OTA Agent
* goes back to the waiting for a job state. */
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
void test_OTA_ReceiveWriteBlockFail()
{
OtaEventMsg_t otaEvent = { 0 };
const uint32_t dataSize = 1024;
pOtaJobDoc = JOB_DOC_ONE_BLOCK;
otaInterfaces.pal.writeBlock = mockPalWriteBlockAlwaysFail;
otaGoToState( OtaAgentStateWaitingForFileBlock );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForFileBlock, OTA_GetState() );
otaInterfaces.os.event.send = mockOSEventSend;
otaEvent.eventId = OtaAgentEventReceivedFileBlock;
otaEvent.pEventData = &eventBuffer;
otaEvent.pEventData->dataLength = dataSize;
OTA_SignalEvent( &otaEvent );
/* Process the event to receive the invalid block. */
receiveAndProcessOtaEvent();
/* Process the event generated after receiving an invalid block. */
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
void test_OTA_ReceiveFileBlockCompleteMqtt()
{
OtaEventMsg_t otaEvent;
OtaEventData_t eventBuffers[ OTA_TEST_FILE_NUM_BLOCKS * OTA_TEST_DUPLICATE_NUM_BLOCKS ];
uint8_t pFileBlock[ OTA_FILE_BLOCK_SIZE ] = { 0 };
uint8_t pStreamingMessage[ OTA_FILE_BLOCK_SIZE * 2 ] = { 0 };
size_t streamingMessageSize = 0;
int remainingBytes = OTA_TEST_FILE_SIZE;
int idx = 0;
int dupIdx = 0;
otaGoToState( OtaAgentStateWaitingForFileBlock );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForFileBlock, OTA_GetState() );
/* Set the event send interface to a mock function that allows events to be sent continuously
* because we're receiving multiple blocks in this test. */
otaInterfaces.os.event.send = mockOSEventSend;
/* Fill the file block. */
for( idx = 0; idx < ( int ) sizeof( pFileBlock ); idx++ )
{
pFileBlock[ idx ] = idx % UINT8_MAX;
}
/* Send blocks. */
idx = 0;
while( remainingBytes >= 0 )
{
/* Intentionally send duplicate blocks to test if we are handling it correctly. */
for( dupIdx = 0; dupIdx < OTA_TEST_DUPLICATE_NUM_BLOCKS; dupIdx++ )
{
/* Construct a AWS IoT streaming message. */
createOtaStreamingMessage(
pStreamingMessage,
sizeof( pStreamingMessage ),
idx,
pFileBlock,
min( ( uint32_t ) remainingBytes, OTA_FILE_BLOCK_SIZE ),
&streamingMessageSize,
true );
otaEvent.eventId = OtaAgentEventReceivedFileBlock;
otaEvent.pEventData = &eventBuffers[ idx * OTA_TEST_DUPLICATE_NUM_BLOCKS + dupIdx ];
memcpy( otaEvent.pEventData->data, pStreamingMessage, streamingMessageSize );
otaEvent.pEventData->dataLength = streamingMessageSize;
OTA_SignalEvent( &otaEvent );
}
idx++;
remainingBytes -= OTA_FILE_BLOCK_SIZE;
}
/* Process all of the events for receiving an MQTT message. */
processEntireQueue();
/* OTA agent should complete the update and go back to waiting for job state. */
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
/* Check if received complete file. */
for( idx = 0; idx < OTA_TEST_FILE_SIZE; ++idx )
{
TEST_ASSERT_EQUAL( pFileBlock[ idx % sizeof( pFileBlock ) ], pOtaFileBuffer[ idx ] );
}
}
void test_OTA_ReceiveFileBlockCompleteDynamicBufferMqtt()
{
memset( &pOtaAppBuffer, 0, sizeof( pOtaAppBuffer ) );
test_OTA_ReceiveFileBlockCompleteMqtt();
}
void test_OTA_ReceiveFileBlockCompleteDifferentFileTypeMqtt()
{
pOtaJobDoc = JOB_DOC_DIFFERENT_FILE_TYPE;
test_OTA_ReceiveFileBlockCompleteMqtt();
}
void test_OTA_ReceiveFileBlockMallocFail()
{
uint8_t pStreamingMessage[ OTA_FILE_BLOCK_SIZE * 2 ] = { 0 };
uint8_t pFileBlock[ OTA_FILE_BLOCK_SIZE ] = { 0 };
size_t streamingMessageSize = 0;
OtaEventData_t eventBuffer;
OtaEventMsg_t otaEvent = { 0 };
otaInterfaces.os.event.send = mockOSEventSend;
/* Pass an invalid values for pDecodeMemory and decodeMemorySize so the
* OTA Agent dynamically allocates the buffer instead of using one provided
* by the user. */
pOtaAppBuffer.pDecodeMemory = NULL;
pOtaAppBuffer.decodeMemorySize = 0;
/* Get into the state before receiving a data block. */
otaGoToState( OtaAgentStateWaitingForFileBlock );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForFileBlock, OTA_GetState() );
/* Create and send the data block. */
createOtaStreamingMessage(
pStreamingMessage,
sizeof( pStreamingMessage ),
0,
pFileBlock,
OTA_FILE_BLOCK_SIZE,
&streamingMessageSize,
true );
otaEvent.eventId = OtaAgentEventReceivedFileBlock;
otaEvent.pEventData = &eventBuffer;
memcpy( otaEvent.pEventData->data, pStreamingMessage, streamingMessageSize );
otaEvent.pEventData->dataLength = streamingMessageSize;
OTA_SignalEvent( &otaEvent );
/* Set malloc to fail and receive the block. */
otaInterfaces.os.mem.malloc = mockMallocAlwaysFail;
receiveAndProcessOtaEvent();
/* Receive the event for closing the file after the failure. */
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
void test_OTA_DroppedFileBlock()
{
OtaEventMsg_t otaEvent = { 0 };
otaInterfaces.os.event.send = mockOSEventSend;
/* Get ready to receive a block. */
otaGoToState( OtaAgentStateWaitingForFileBlock );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForFileBlock, OTA_GetState() );
/* No blocks have been received or dropped yet. */
TEST_ASSERT_EQUAL( 0, otaAgent.statistics.otaPacketsDropped );
/* Prepare an event as if we are receiving a data block. */
otaEvent.eventId = OtaAgentEventReceivedFileBlock;
otaEvent.pEventData = &eventBuffer;
otaEvent.pEventData->dataLength = 0;
/* Set the interface to fail sending the data block. */
otaInterfaces.os.event.send = mockOSEventSendAlwaysFail;
/* Simulate the application receiving a data block and failing to send it
* to the OTA Agent. */
TEST_ASSERT_EQUAL( false, OTA_SignalEvent( &otaEvent ) );
TEST_ASSERT_EQUAL( 1, otaAgent.statistics.otaPacketsDropped );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForFileBlock, OTA_GetState() );
}
void test_OTA_ReceiveFileBlockCompleteHttp()
{
OtaEventMsg_t otaEvent;
OtaEventData_t eventBuffers[ OTA_TEST_FILE_NUM_BLOCKS ];
uint8_t pFileBlock[ OTA_FILE_BLOCK_SIZE ] = { 0 };
int remainingBytes = OTA_TEST_FILE_SIZE;
int fileBlockSize = 0;
int idx = 0;
pOtaJobDoc = JOB_DOC_HTTP;
otaGoToState( OtaAgentStateWaitingForFileBlock );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForFileBlock, OTA_GetState() );
/* Set the event send interface to a mock function that allows events to be sent continuously
* because we're receiving multiple blocks in this test. */
otaInterfaces.os.event.send = mockOSEventSend;
/* Fill the file block. */
for( idx = 0; idx < ( int ) sizeof( pFileBlock ); idx++ )
{
pFileBlock[ idx ] = idx % UINT8_MAX;
}
idx = 0;
while( remainingBytes >= 0 )
{
fileBlockSize = min( ( uint32_t ) remainingBytes, OTA_FILE_BLOCK_SIZE );
otaEvent.eventId = OtaAgentEventReceivedFileBlock;
otaEvent.pEventData = &eventBuffers[ idx ];
memcpy( otaEvent.pEventData->data, pFileBlock, fileBlockSize );
otaEvent.pEventData->dataLength = fileBlockSize;
OTA_SignalEvent( &otaEvent );
idx++;
remainingBytes -= OTA_FILE_BLOCK_SIZE;
}
/* OTA agent should complete the update and go back to waiting for job state. */
processEntireQueue();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
/* Check if received complete file. */
for( idx = 0; idx < OTA_TEST_FILE_SIZE; ++idx )
{
TEST_ASSERT_EQUAL( pFileBlock[ idx % sizeof( pFileBlock ) ], pOtaFileBuffer[ idx ] );
}
}
void test_OTA_ReceiveFileBlockCompleteDynamicBufferHttp()
{
memset( &pOtaAppBuffer, 0, sizeof( pOtaAppBuffer ) );
test_OTA_ReceiveFileBlockCompleteHttp();
}
/**
* @brief Test that extractAndStoreArray fails if device does not have sufficient
* memory to allocate the string/array (here streamname).
*/
void test_OTA_ExtractArrayMemAllocFails()
{
otaInterfaces.os.mem.malloc = mockMallocAlwaysFail;
pOtaAppBuffer.streamNameSize = 0;
/* Try to reach state OtaAgentStateCreatingFile, which would require the device
* to receive a job document and allocate resources and store the parameters.
* Insufficient memory causes the job to fail and state reverts to OtaAgentStateWaitingForJob.
*/
otaGoToState( OtaAgentStateCreatingFile );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
/**
* @brief Test that extractAndStoreArray fails if user buffer is insufficient
* to allocate the string/array (here streamname).
*/
void test_OTA_ExtractArrayInsufficientBuffer()
{
pOtaAppBuffer.streamNameSize = OTA_INVALID_STREAM_NAME_SIZE;
/* Try to reach state OtaAgentStateCreatingFile, which would require the device
* to receive a job document and allocate resources and store the parameters.
* Insufficient memory causes the job to fail and state reverts to OtaAgentStateWaitingForJob.
*/
otaGoToState( OtaAgentStateCreatingFile );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
void test_OTA_ProcessJobDocumentFileIdNotZero()
{
pOtaJobDoc = JOB_DOC_SERVERFILE_ID;
/* Set the event send interface to a mock function that allows events to be sent continuously.
* This is to complete the self test process. */
otaInterfaces.os.event.send = mockOSEventSend;
otaGoToState( OtaAgentStateWaitingForJob );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
otaReceiveJobDocument();
receiveAndProcessOtaEvent();
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
TEST_ASSERT_EQUAL( OtaImageStateAccepted, OTA_GetImageState() );
}
static void invokeSelfTestHandler()
{
pOtaJobDoc = JOB_DOC_SELF_TEST;
/* Set the event send interface to a mock function that allows events to be sent continuously.
* This is to complete the self test process. */
otaInterfaces.os.event.send = mockOSEventSend;
otaGoToState( OtaAgentStateWaitingForJob );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
otaReceiveJobDocument();
receiveAndProcessOtaEvent();
receiveAndProcessOtaEvent();
}
void test_OTA_SelfTestJob()
{
invokeSelfTestHandler();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
TEST_ASSERT_EQUAL( OtaImageStateAccepted, OTA_GetImageState() );
}
void test_OTA_SelfTestJobEventSendFail()
{
pOtaJobDoc = JOB_DOC_SELF_TEST;
otaGoToState( OtaAgentStateWaitingForJob );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
/* Set the event send interface to a mock function that always fails to
* send the event. */
otaReceiveJobDocument();
otaInterfaces.os.event.send = mockOSEventSendAlwaysFail;
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
void test_OTA_SelfTestJobNonSelfTestPlatform()
{
/* Let the PAL always says it's not in self test. */
otaInterfaces.pal.getPlatformImageState = mockPalGetPlatformImageStateAlwaysInvalid;
invokeSelfTestHandler();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
TEST_ASSERT_EQUAL( OtaImageStateRejected, OTA_GetImageState() );
}
void test_OTA_NonSelfTestJobSelfTestPlatform()
{
/* Let the PAL always says it's in self test. */
otaInterfaces.pal.getPlatformImageState = mockPalGetPlatformImageStateAlwaysPendingCommit;
otaGoToState( OtaAgentStateCreatingFile );
TEST_ASSERT_EQUAL( OtaAgentStateCreatingFile, OTA_GetState() );
}
void test_OTA_SelfTestJobDowngrade()
{
pOtaJobDoc = JOB_DOC_SELF_TEST_DOWNGRADE;
otaGoToState( OtaAgentStateWaitingForJob );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
otaReceiveJobDocument();
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( true, resetCalled );
}
void test_OTA_SelfTestJobSameVersion()
{
pOtaJobDoc = JOB_DOC_SELF_TEST_SAME_VERSION;
otaGoToState( OtaAgentStateWaitingForJob );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
otaReceiveJobDocument();
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( true, resetCalled );
}
void test_OTA_SelfTestJobNonFirmwareFileType()
{
pOtaJobDoc = JOB_DOC_SELF_TEST_FILE_TYPE;
otaInterfaces.os.event.send = mockOSEventSend;
otaGoToState( OtaAgentStateWaitingForJob );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
otaReceiveJobDocument();
receiveAndProcessOtaEvent();
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
TEST_ASSERT_EQUAL( OtaImageStateAccepted, OTA_GetImageState() );
}
void test_OTA_StartWithSelfTest()
{
/* Directly set pal image state to pending commit, pretending we're in self test. */
palImageState = OtaPalImageStatePendingCommit;
/* Let timer start to invoke callback directly. */
otaInterfaces.os.timer.start = mockOSTimerInvokeCallback;
/* Start OTA agent. */
otaGoToState( OtaAgentStateWaitingForJob );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
TEST_ASSERT_EQUAL( true, resetCalled );
}
void test_OTA_ReceiveNewJobDocWhileInProgress()
{
pOtaJobDoc = JOB_DOC_A;
otaGoToState( OtaAgentStateWaitingForFileBlock );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForFileBlock, OTA_GetState() );
/* Reset the event queue so that we can send the next event. */
mockOSEventReset( NULL );
/* Sending another job document should cause OTA agent to abort current update. */
pOtaJobDoc = JOB_DOC_B;
otaReceiveJobDocument();
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateRequestingJob, OTA_GetState() );
}
static void refreshWithJobDoc( const char * initJobDoc,
const char * newJobDoc )
{
OtaEventMsg_t otaEvent = { 0 };
pOtaJobDoc = initJobDoc;
otaGoToState( OtaAgentStateWaitingForFileBlock );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForFileBlock, OTA_GetState() );
/* Set the event send interface to a mock function that allows events to be sent continuously.
* We need this to go through the process of refreshing job doc. */
otaInterfaces.os.event.send = mockOSEventSend;
/* First send request job doc event while we're in progress, this should make OTA agent to
* to request job doc again and transit to waiting for job state. */
otaEvent.eventId = OtaAgentEventRequestJobDocument;
OTA_SignalEvent( &otaEvent );
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
/* Now send the new job doc, OTA agent should abort current job and start the new job. */
pOtaJobDoc = newJobDoc;
otaReceiveJobDocument();
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateCreatingFile, OTA_GetState() );
/* Request file block. */
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateRequestingFileBlock, OTA_GetState() );
/* Wait for the file block. */
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForFileBlock, OTA_GetState() );
}
void test_OTA_RefreshWithSameJobDoc()
{
refreshWithJobDoc( JOB_DOC_A, JOB_DOC_A );
}
void test_OTA_RefreshWithInvalidJobDoc()
{
OtaEventMsg_t otaEvent = { 0 };
pOtaJobDoc = JOB_DOC_A;
otaGoToState( OtaAgentStateWaitingForFileBlock );
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForFileBlock, OTA_GetState() );
/* Set the event send interface to a mock function that allows events to be sent continuously.
* We need this to go through the process of refreshing job doc. */
otaInterfaces.os.event.send = mockOSEventSend;
/* Send the request job document event while the OTA Agent is already in the process of
* downloading a file. The OTA Agent should cancel the current job and begin waiting
* for the next job document. */
otaEvent.eventId = OtaAgentEventRequestJobDocument;
OTA_SignalEvent( &otaEvent );
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
/* Now send the new job doc, OTA agent should abort current job and start the new job. */
pOtaJobDoc = JOB_DOC_MISSING_KEY;
otaReceiveJobDocument();
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateWaitingForJob, OTA_GetState() );
}
void test_OTA_RefreshWithDifferentJobDoc()
{
refreshWithJobDoc( JOB_DOC_A, JOB_DOC_B );
}
void test_OTA_RefreshWithSameJobDocHttpDynamicBuffer()
{
memset( &pOtaAppBuffer, 0, sizeof( pOtaAppBuffer ) );
refreshWithJobDoc( JOB_DOC_HTTP, JOB_DOC_HTTP );
}
void test_OTA_UnexpectedEventReceiveJobDoc()
{
OtaEventMsg_t otaEvent = { 0 };
otaGoToState( OtaAgentStateSuspended );
TEST_ASSERT_EQUAL( OtaAgentStateSuspended, OTA_GetState() );
otaEvent.eventId = OtaAgentEventReceivedJobDocument;
OTA_SignalEvent( &otaEvent );
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateSuspended, OTA_GetState() );
}
void test_OTA_UnexpectedEventReceiveFileBlock()
{
OtaEventMsg_t otaEvent = { 0 };
otaGoToState( OtaAgentStateSuspended );
TEST_ASSERT_EQUAL( OtaAgentStateSuspended, OTA_GetState() );
otaEvent.eventId = OtaAgentEventReceivedFileBlock;
OTA_SignalEvent( &otaEvent );
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateSuspended, OTA_GetState() );
}
void test_OTA_UnexpectedEventOthers()
{
OtaEventMsg_t otaEvent = { 0 };
otaGoToState( OtaAgentStateSuspended );
TEST_ASSERT_EQUAL( OtaAgentStateSuspended, OTA_GetState() );
otaEvent.eventId = OtaAgentEventStart;
OTA_SignalEvent( &otaEvent );
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateSuspended, OTA_GetState() );
}
void test_OTA_ReceiveFileBlockCompleteMqttSigCheckFail()
{
otaInterfaces.pal.closeFile = mockPalCloseFileSigCheckFail;
test_OTA_ReceiveFileBlockCompleteMqtt();
}
void test_OTA_ReceiveFileBlockCompleteMqttFailtoClose()
{
otaInterfaces.pal.closeFile = mockPalCloseFileAlwaysFail;
test_OTA_ReceiveFileBlockCompleteMqtt();
}
void test_OTA_EventProcessingTask_ExitOnAbort()
{
OtaEventMsg_t otaEvent = { 0 };
otaGoToState( OtaAgentStateReady );
otaEvent.eventId = OtaAgentEventShutdown;
OTA_SignalEvent( &otaEvent );
OTA_EventProcessingTask( NULL );
/* Test that the OTA_EventProcessingTask aborts correctly after receiving
* and event to shutdown the OTA Agent. */
TEST_ASSERT_EQUAL( OtaAgentStateStopped, OTA_GetState() );
}
/* ========================================================================== */
/* ====================== OTA MQTT and HTTP Unit Tests ====================== */
/* ========================================================================== */
/* Test that mqtt cleanup fails with unsubscribe failure. */
void test_OTA_MQTT_CleanupFailed()
{
OtaEventMsg_t otaEvent = { 0 };
otaAgent.unsubscribeOnShutdown = 1;
otaGoToState( OtaAgentStateRequestingJob );
TEST_ASSERT_EQUAL( OtaAgentStateRequestingJob, OTA_GetState() );
otaInterfaces.mqtt.unsubscribe = stubMqttUnsubscribeAlwaysFail;
otaEvent.eventId = OtaAgentEventShutdown;
OTA_SignalEvent( &otaEvent );
receiveAndProcessOtaEvent();
TEST_ASSERT_EQUAL( OtaAgentStateStopped, OTA_GetState() );
}
/* Test that requestFileBlock_Mqtt fails if the Encoding fails. */
void test_OTA_MQTT_EncodingFailed()
{
OtaErr_t err = OtaErrNone;
otaInitDefault();
/* Explicitly set BitMap to NULL for the encoding to fail. */
OtaFileContext_t * pFileContext = &( otaAgent.fileContext );
pFileContext->pRxBlockBitmap = NULL;
err = requestFileBlock_Mqtt( &otaAgent );
TEST_ASSERT_EQUAL( OtaErrFailedToEncodeCbor, err );
}
/* Test that requestFileBlock_Mqtt fails if the Publish fails. */
void test_OTA_MQTT_RequestFileFailed()
{
OtaErr_t err = OtaErrNone;
otaInitDefault();
otaInterfaces.mqtt.publish = stubMqttPublishAlwaysFail;
err = requestFileBlock_Mqtt( &otaAgent );
TEST_ASSERT_EQUAL( OtaErrRequestFileBlockFailed, err );
}
/* Test that requestJob_Mqtt fails if the Subscribe fails. */
void test_OTA_MQTT_JobSubscribingFailed()
{
OtaErr_t err = OtaErrNone;
otaInitDefault();
otaInterfaces.mqtt.subscribe = stubMqttSubscribeAlwaysFail;
err = requestJob_Mqtt( &otaAgent );
TEST_ASSERT_EQUAL( OtaErrRequestJobFailed, err );
}
/* Test that initFileTransfer_Mqtt fails if the Subscribe fails. */
void test_OTA_MQTT_InitFileTransferSubscribeFailed()
{
OtaErr_t err = OtaErrNone;
otaInitDefault();
otaInterfaces.mqtt.subscribe = stubMqttSubscribeAlwaysFail;
err = initFileTransfer_Mqtt( &otaAgent );
TEST_ASSERT_EQUAL( OtaErrInitFileTransferFailed, err );
}
/* Test that updateJobStatus_Mqtt fails if the Publish fails. */
void test_OTA_MQTT_UpdateStatusFailed()
{
OtaErr_t err = OtaErrNone;
otaInitDefault();
otaInterfaces.mqtt.publish = stubMqttPublishAlwaysFail;
err = updateJobStatus_Mqtt( &otaAgent, JobStatusSucceeded, 0, 0 );
TEST_ASSERT_EQUAL( OtaErrUpdateJobStatusFailed, err );
}
/* Test data cleanup fails with HTTP deinit failure*/
void test_OTA_HTTP_cleanupFailed()
{
OtaErr_t err = OtaErrNone;
otaInitDefault();
otaInterfaces.http.deinit = stubHttpDeinitAlwaysFail;
err = cleanupData_Http( &otaAgent );
TEST_ASSERT_EQUAL( OtaErrCleanupDataFailed, err );
}
/* ========================================================================== */
/* ======================= OTA StrError Unit Tests ========================== */
/* ========================================================================== */
/**
* @brief Test OTA_Err_strerror returns correct strings.
*/
void test_OTA_Err_strerror( void )
{
OtaErr_t err;
const char * str = NULL;
err = OtaErrNone;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrNone", str );
err = OtaErrUninitialized;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrUninitialized", str );
err = OtaErrPanic;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrPanic", str );
err = OtaErrInvalidArg;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrInvalidArg", str );
err = OtaErrAgentStopped;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrAgentStopped", str );
err = OtaErrSignalEventFailed;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrSignalEventFailed", str );
err = OtaErrRequestJobFailed;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrRequestJobFailed", str );
err = OtaErrInitFileTransferFailed;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrInitFileTransferFailed", str );
err = OtaErrRequestFileBlockFailed;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrRequestFileBlockFailed", str );
err = OtaErrCleanupControlFailed;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrCleanupControlFailed", str );
err = OtaErrCleanupDataFailed;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrCleanupDataFailed", str );
err = OtaErrUpdateJobStatusFailed;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrUpdateJobStatusFailed", str );
err = OtaErrJobParserError;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrJobParserError", str );
err = OtaErrInvalidDataProtocol;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrInvalidDataProtocol", str );
err = OtaErrMomentumAbort;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrMomentumAbort", str );
err = OtaErrDowngradeNotAllowed;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrDowngradeNotAllowed", str );
err = OtaErrSameFirmwareVersion;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrSameFirmwareVersion", str );
err = OtaErrImageStateMismatch;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrImageStateMismatch", str );
err = OtaErrNoActiveJob;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrNoActiveJob", str );
err = OtaErrUserAbort;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrUserAbort", str );
err = OtaErrFailedToEncodeCbor;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrFailedToEncodeCbor", str );
err = OtaErrFailedToDecodeCbor;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrFailedToDecodeCbor", str );
err = OtaErrActivateFailed;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "OtaErrActivateFailed", str );
err = OtaErrActivateFailed + 1;
str = OTA_Err_strerror( err );
TEST_ASSERT_EQUAL_STRING( "InvalidErrorCode", str );
}
/**
* @brief Test OTA_OsStatus_strerror returns correct strings.
*/
void test_OTA_OsStatus_strerror( void )
{
OtaOsStatus_t status;
const char * str = NULL;
status = OtaOsSuccess;
str = OTA_OsStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaOsSuccess", str );
status = OtaOsEventQueueCreateFailed;
str = OTA_OsStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaOsEventQueueCreateFailed", str );
status = OtaOsEventQueueSendFailed;
str = OTA_OsStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaOsEventQueueSendFailed", str );
status = OtaOsEventQueueReceiveFailed;
str = OTA_OsStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaOsEventQueueReceiveFailed", str );
status = OtaOsEventQueueDeleteFailed;
str = OTA_OsStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaOsEventQueueDeleteFailed", str );
status = OtaOsTimerCreateFailed;
str = OTA_OsStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaOsTimerCreateFailed", str );
status = OtaOsTimerStartFailed;
str = OTA_OsStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaOsTimerStartFailed", str );
status = OtaOsTimerRestartFailed;
str = OTA_OsStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaOsTimerRestartFailed", str );
status = OtaOsTimerStopFailed;
str = OTA_OsStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaOsTimerStopFailed", str );
status = OtaOsTimerDeleteFailed;
str = OTA_OsStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaOsTimerDeleteFailed", str );
status = OtaOsTimerDeleteFailed + 1;
str = OTA_OsStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "InvalidErrorCode", str );
}
/**
* @brief Test OTA_PalStatus_strerror returns correct strings.
*/
void test_OTA_PalStatus_strerror( void )
{
OtaPalMainStatus_t status;
const char * str = NULL;
status = OtaPalSuccess;
str = OTA_PalStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaPalSuccess", str );
status = OtaPalUninitialized;
str = OTA_PalStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaPalUninitialized", str );
status = OtaPalOutOfMemory;
str = OTA_PalStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaPalOutOfMemory", str );
status = OtaPalNullFileContext;
str = OTA_PalStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaPalNullFileContext", str );
status = OtaPalSignatureCheckFailed;
str = OTA_PalStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaPalSignatureCheckFailed", str );
status = OtaPalRxFileCreateFailed;
str = OTA_PalStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaPalRxFileCreateFailed", str );
status = OtaPalRxFileTooLarge;
str = OTA_PalStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaPalRxFileTooLarge", str );
status = OtaPalBootInfoCreateFailed;
str = OTA_PalStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaPalBootInfoCreateFailed", str );
status = OtaPalBadSignerCert;
str = OTA_PalStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaPalBadSignerCert", str );
status = OtaPalBadImageState;
str = OTA_PalStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaPalBadImageState", str );
status = OtaPalAbortFailed;
str = OTA_PalStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaPalAbortFailed", str );
status = OtaPalRejectFailed;
str = OTA_PalStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaPalRejectFailed", str );
status = OtaPalCommitFailed;
str = OTA_PalStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaPalCommitFailed", str );
status = OtaPalActivateFailed;
str = OTA_PalStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaPalActivateFailed", str );
status = OtaPalFileAbort;
str = OTA_PalStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaPalFileAbort", str );
status = OtaPalFileClose;
str = OTA_PalStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaPalFileClose", str );
status = OtaPalFileClose + 1;
str = OTA_PalStatus_strerror( status );
TEST_ASSERT_EQUAL_STRING( "InvalidErrorCode", str );
}
/**
* @brief Test OTA_JobParse_strerror returns correct strings.
*/
void test_OTA_JobParse_strerror( void )
{
OtaJobParseErr_t status;
const char * str = NULL;
status = OtaJobParseErrUnknown;
str = OTA_JobParse_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaJobParseErrUnknown", str );
status = OtaJobParseErrNone;
str = OTA_JobParse_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaJobParseErrNone", str );
status = OtaJobParseErrNullJob;
str = OTA_JobParse_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaJobParseErrNullJob", str );
status = OtaJobParseErrUpdateCurrentJob;
str = OTA_JobParse_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaJobParseErrUpdateCurrentJob", str );
status = OtaJobParseErrZeroFileSize;
str = OTA_JobParse_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaJobParseErrZeroFileSize", str );
status = OtaJobParseErrNonConformingJobDoc;
str = OTA_JobParse_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaJobParseErrNonConformingJobDoc", str );
status = OtaJobParseErrBadModelInitParams;
str = OTA_JobParse_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaJobParseErrBadModelInitParams", str );
status = OtaJobParseErrNoContextAvailable;
str = OTA_JobParse_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaJobParseErrNoContextAvailable", str );
status = OtaJobParseErrNoActiveJobs;
str = OTA_JobParse_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaJobParseErrNoActiveJobs", str );
status = OtaJobParseErrNoActiveJobs + 1;
str = OTA_JobParse_strerror( status );
TEST_ASSERT_EQUAL_STRING( "InvalidErrorCode", str );
}
/**
* @brief Test OTA_MQTT_strerror returns correct strings.
*/
void test_OTA_MQTT_strerror( void )
{
OtaMqttStatus_t status;
const char * str = NULL;
status = OtaMqttSuccess;
str = OTA_MQTT_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaMqttSuccess", str );
status = OtaMqttPublishFailed;
str = OTA_MQTT_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaMqttPublishFailed", str );
status = OtaMqttSubscribeFailed;
str = OTA_MQTT_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaMqttSubscribeFailed", str );
status = OtaMqttUnsubscribeFailed;
str = OTA_MQTT_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaMqttUnsubscribeFailed", str );
status = OtaMqttUnsubscribeFailed + 1;
str = OTA_MQTT_strerror( status );
TEST_ASSERT_EQUAL_STRING( "InvalidErrorCode", str );
}
/**
* @brief Test OTA_HTTP_strerror returns correct strings.
*/
void test_OTA_HTTP_strerror( void )
{
OtaHttpStatus_t status;
const char * str = NULL;
status = OtaHttpSuccess;
str = OTA_HTTP_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaHttpSuccess", str );
status = OtaHttpInitFailed;
str = OTA_HTTP_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaHttpInitFailed", str );
status = OtaHttpDeinitFailed;
str = OTA_HTTP_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaHttpDeinitFailed", str );
status = OtaHttpRequestFailed;
str = OTA_HTTP_strerror( status );
TEST_ASSERT_EQUAL_STRING( "OtaHttpRequestFailed", str );
status = OtaHttpRequestFailed + 1;
str = OTA_HTTP_strerror( status );
TEST_ASSERT_EQUAL_STRING( "InvalidErrorCode", str );
}
/* ========================================================================== */
/* ================== OTA State Machine Handler Unit Tests ================== */
/* ========================================================================== */
/**
* @brief Test that initFileHandler returns the proper error when the timer
* fails to start.
*/
void test_OTA_initFileHandler_TimerFails( void )
{
OtaEventMsg_t otaEvent = { 0 };
/* Initialize the OTA interfaces so they are not NULL. */
otaGoToState( OtaAgentStateReady );
/* Fail to initialize the file transfer so the timer is started. */
otaDataInterface.initFileTransfer = mockDataInterfaceInitFileTransferAlwaysFail;
/* Fail to start the timer. */
otaInterfaces.os.timer.start = mockOSTimerStartAlwaysFail;
TEST_ASSERT_EQUAL( OtaErrInitFileTransferFailed, initFileHandler( otaEvent.pEventData ) );
}
/**
* @brief Test that initFileHandler returns the proper error when the OTA event
* send functionality fails.
*/
void test_OTA_initFileHandler_EventSendFails( void )
{
OtaEventMsg_t otaEvent = { 0 };
/* Initialize the OTA interfaces so they are not NULL. */
otaGoToState( OtaAgentStateReady );
/* Test failing while trying to send the shutdown event after failing
* to initialize the file. */
/* Fail to initialize the file transfer so the timer is started. */
otaDataInterface.initFileTransfer = mockDataInterfaceInitFileTransferAlwaysFail;
/* Simulate reaching the maximum number of attempts before considering
* the attempt to be a failure. */
otaAgent.requestMomentum = otaconfigMAX_NUM_REQUEST_MOMENTUM;
/* Fail to send the OTA event. */
otaInterfaces.os.event.send = mockOSEventSendAlwaysFail;
TEST_ASSERT_EQUAL( OtaErrSignalEventFailed, initFileHandler( otaEvent.pEventData ) );
/* Test failing while trying to send the request block event after
* successfully initializing the file. */
/* Succeed with the file initialization to then attempt to send the event
* for requesting a block. */
otaDataInterface.initFileTransfer = mockDataInitFileTransferAlwaysSucceed;
/* Fail to send the OTA event. */
otaInterfaces.os.event.send = mockOSEventSendAlwaysFail;
TEST_ASSERT_EQUAL( OtaErrSignalEventFailed, initFileHandler( otaEvent.pEventData ) );
}
/**
* @brief Test that requestDataHandler returns the proper error when the OTA
* event send functionality fails.
*/
void test_OTA_requestDataHandler_EventSendFails( void )
{
OtaEventMsg_t otaEvent = { 0 };
/* Initialize the OTA interfaces so they are not NULL. */
otaGoToState( OtaAgentStateReady );
/* File context has a non-zero number of blocks remaining. */
otaAgent.fileContext.blocksRemaining = 1U;
/* Simulate reaching the maximum number of attempts before considering
* the attempt to be a failure. In this scenario, the handler will attempt
* to send a shutdown event to the OTA Agent.*/
otaAgent.requestMomentum = otaconfigMAX_NUM_REQUEST_MOMENTUM;
/* Fail to send the OTA event. */
otaInterfaces.os.event.send = mockOSEventSendAlwaysFail;
TEST_ASSERT_EQUAL( OtaErrSignalEventFailed, requestDataHandler( otaEvent.pEventData ) );
}
/**
* @brief Test that requestJobHandler returns the proper error when the timer
* start functionality fails.
*/
void test_OTA_requestJobHandler_TimerFails( void )
{
OtaEventMsg_t otaEvent = { 0 };
/* Initialize the OTA interfaces so they are not NULL. */
otaGoToState( OtaAgentStateReady );
/* Fail requesting the job document. */
otaControlInterface.requestJob = mockControlInterfaceRequestJobAlwaysFail;
/* Fail to start the request timer. */
otaInterfaces.os.timer.start = mockOSTimerStartAlwaysFail;
TEST_ASSERT_EQUAL( OtaErrRequestJobFailed, requestJobHandler( otaEvent.pEventData ) );
}
/**
* @brief Test that requestJobHandler returns the proper error when the OTA
* event send functionality fails.
*/
void test_OTA_requestJobHandler_EventSendFails( void )
{
OtaEventMsg_t otaEvent = { 0 };
/* Initialize the OTA interfaces so they are not NULL. */
otaGoToState( OtaAgentStateReady );
/* Fail requesting the job document. */
otaControlInterface.requestJob = mockControlInterfaceRequestJobAlwaysFail;
/* Simulate reaching the maximum number of attempts before considering
* the attempt to be a failure. */
otaAgent.requestMomentum = otaconfigMAX_NUM_REQUEST_MOMENTUM;
/* Fail to send the OTA event. */
otaInterfaces.os.event.send = mockOSEventSendAlwaysFail;
TEST_ASSERT_EQUAL( OtaErrSignalEventFailed, requestJobHandler( otaEvent.pEventData ) );
}
/**
* @brief Test that processDataHandler safely handles receiving invalid events.
*/
void test_OTA_processDataHandler_InvalidEvent( void )
{
/* Initialize the OTA interfaces so they are not NULL. */
otaGoToState( OtaAgentStateReady );
/* Test that passing NULL event data does not cause a segmentation fault.
* The expected return is OtaErrNone because the error return value of this
* handler represents the success of updating the job document when there
* is an issue processing the block. */
TEST_ASSERT_EQUAL( OtaErrNone, processDataHandler( NULL ) );
}
/**
* @brief Test that resumeHandler returns the proper error when the OTA event
* send functionality fails.
*/
void test_OTA_resumeHandler_EventSendFails()
{
/* Initialize the OTA interfaces so they are not NULL. */
otaGoToState( OtaAgentStateSuspended );
/* Fail to send the OTA event. */
otaInterfaces.os.event.send = mockOSEventSendAlwaysFail;
TEST_ASSERT_EQUAL( OtaErrSignalEventFailed, resumeHandler( NULL ) );
}
void test_OTA_jobNotificationHandler_EventSendFails()
{
/* Initialize the OTA interfaces so they are not NULL. */
otaGoToState( OtaAgentStateWaitingForFileBlock );
otaInterfaces.os.event.send = mockOSEventSendAlwaysFail;
TEST_ASSERT_EQUAL( OtaErrSignalEventFailed, jobNotificationHandler( NULL ) );
}
/**
* @brief Test that shutdownHandler safely handles being called without the
* control and data interfaces being set.
*/
void test_OTA_shutdownHandler_NullInterface()
{
otaGoToState( OtaAgentStateReady );
otaDataInterface.cleanup = NULL;
otaDataInterface.decodeFileBlock = NULL;
otaDataInterface.initFileTransfer = NULL;
otaDataInterface.requestFileBlock = NULL;
otaControlInterface.cleanup = NULL;
otaControlInterface.requestJob = NULL;
otaControlInterface.updateJobStatus = NULL;
TEST_ASSERT_EQUAL( OtaErrNone, shutdownHandler( NULL ) );
}
/* ========================================================================== */
/* ======================== OTA Interface Unit Tests ======================== */
/* ========================================================================== */
/**
* @brief Test that setDataInterface sets the data interface when given valid
* inputs.
*/
void test_OTA_setDataInterface_ValidInput( void )
{
OtaDataInterface_t dataInterface = { NULL, NULL, NULL, NULL };
uint8_t pProtocol[ OTA_PROTOCOL_BUFFER_SIZE ] = { 0 };
memcpy( pProtocol, "[\"MQTT\"]", sizeof( "[\"MQTT\"]" ) );
TEST_ASSERT_EQUAL( OtaErrNone, setDataInterface( &dataInterface, pProtocol ) );
TEST_ASSERT_EQUAL( initFileTransfer_Mqtt, dataInterface.initFileTransfer );
TEST_ASSERT_EQUAL( requestFileBlock_Mqtt, dataInterface.requestFileBlock );
TEST_ASSERT_EQUAL( decodeFileBlock_Mqtt, dataInterface.decodeFileBlock );
TEST_ASSERT_EQUAL( cleanupData_Mqtt, dataInterface.cleanup );
memcpy( pProtocol, "[\"HTTP\"]", sizeof( "[\"HTTP\"]" ) );
memset( &dataInterface, 0, sizeof( dataInterface ) );
TEST_ASSERT_EQUAL( OtaErrNone, setDataInterface( &dataInterface, pProtocol ) );
TEST_ASSERT_EQUAL( initFileTransfer_Http, dataInterface.initFileTransfer );
TEST_ASSERT_EQUAL( requestDataBlock_Http, dataInterface.requestFileBlock );
TEST_ASSERT_EQUAL( decodeFileBlock_Http, dataInterface.decodeFileBlock );
TEST_ASSERT_EQUAL( cleanupData_Http, dataInterface.cleanup );
memcpy( pProtocol, "[\"MQTT\",\"HTTP\"]", sizeof( "[\"MQTT\",\"HTTP\"]" ) );
memset( &dataInterface, 0, sizeof( dataInterface ) );
TEST_ASSERT_EQUAL( OtaErrNone, setDataInterface( &dataInterface, pProtocol ) );
TEST_ASSERT_NOT_EQUAL( NULL, dataInterface.initFileTransfer );
TEST_ASSERT_NOT_EQUAL( NULL, dataInterface.requestFileBlock );
TEST_ASSERT_NOT_EQUAL( NULL, dataInterface.decodeFileBlock );
TEST_ASSERT_NOT_EQUAL( NULL, dataInterface.cleanup );
memcpy( pProtocol, "[\"HTTP\",\"MQTT\"]", sizeof( "[\"HTTP\",\"MQTT\"]" ) );
memset( &dataInterface, 0, sizeof( dataInterface ) );
TEST_ASSERT_EQUAL( OtaErrNone, setDataInterface( &dataInterface, pProtocol ) );
TEST_ASSERT_NOT_EQUAL( NULL, dataInterface.initFileTransfer );
TEST_ASSERT_NOT_EQUAL( NULL, dataInterface.requestFileBlock );
TEST_ASSERT_NOT_EQUAL( NULL, dataInterface.decodeFileBlock );
TEST_ASSERT_NOT_EQUAL( NULL, dataInterface.cleanup );
}
/**
* @brief Test that setDataInterface returns an error and does not set the data
* interface when provided with an invalid input from a job document.
*/
void test_OTA_setDataInterface_InvalidInput( void )
{
OtaDataInterface_t dataInterface = { NULL, NULL, NULL, NULL };
uint8_t pProtocol[ OTA_PROTOCOL_BUFFER_SIZE ] = { 0 };
memcpy( pProtocol, "invalid_protocol", sizeof( "invalid_protocol" ) );
TEST_ASSERT_EQUAL( OtaErrInvalidDataProtocol, setDataInterface( &dataInterface, pProtocol ) );
TEST_ASSERT_EQUAL( NULL, dataInterface.initFileTransfer );
TEST_ASSERT_EQUAL( NULL, dataInterface.requestFileBlock );
TEST_ASSERT_EQUAL( NULL, dataInterface.decodeFileBlock );
TEST_ASSERT_EQUAL( NULL, dataInterface.cleanup );
memcpy( pProtocol, "junkMQTT", sizeof( "junkMQTT" ) );
TEST_ASSERT_EQUAL( OtaErrInvalidDataProtocol, setDataInterface( &dataInterface, pProtocol ) );
TEST_ASSERT_EQUAL( NULL, dataInterface.initFileTransfer );
TEST_ASSERT_EQUAL( NULL, dataInterface.requestFileBlock );
TEST_ASSERT_EQUAL( NULL, dataInterface.decodeFileBlock );
TEST_ASSERT_EQUAL( NULL, dataInterface.cleanup );
memcpy( pProtocol, "HTTPjunk", sizeof( "HTTPjunk" ) );
TEST_ASSERT_EQUAL( OtaErrInvalidDataProtocol, setDataInterface( &dataInterface, pProtocol ) );
TEST_ASSERT_EQUAL( NULL, dataInterface.initFileTransfer );
TEST_ASSERT_EQUAL( NULL, dataInterface.requestFileBlock );
TEST_ASSERT_EQUAL( NULL, dataInterface.decodeFileBlock );
TEST_ASSERT_EQUAL( NULL, dataInterface.cleanup );
}
/* ========================================================================== */
/* ==================== OTA Private Function Unit Tests ===================== */
/* ========================================================================== */
void test_OTA_initDocModelFail()
{
DocParseErr_t parseError = DocParseErrNone;
JsonDocModel_t otaJobDocModel;
parseError = initDocModel( NULL,
otaJobDocModelParamStructure,
( void * ) &( otaAgent.fileContext ),
( uint32_t ) sizeof( OtaFileContext_t ),
OTA_NUM_JOB_PARAMS );
TEST_ASSERT_EQUAL( DocParseErrNullModelPointer, parseError );
parseError = initDocModel( &otaJobDocModel,
NULL,
( void * ) &( otaAgent.fileContext ),
( uint32_t ) sizeof( OtaFileContext_t ),
OTA_NUM_JOB_PARAMS );
TEST_ASSERT_EQUAL( DocParseErrNullBodyPointer, parseError );
parseError = initDocModel( &otaJobDocModel,
otaJobDocModelParamStructure,
( void * ) &( otaAgent.fileContext ),
( uint32_t ) sizeof( OtaFileContext_t ),
OTA_DOC_MODEL_MAX_PARAMS + 1 );
TEST_ASSERT_EQUAL( DocParseErrTooManyParams, parseError );
}
void test_OTA_parseJobFailsNullJsonDocument()
{
OtaFileContext_t * pContext = NULL;
bool updateJob = false;
otaInitDefault();
pContext = parseJobDoc( NULL, 0, JOB_DOC_A, strlen( JOB_DOC_A ), &updateJob );
TEST_ASSERT_NULL( pContext );
TEST_ASSERT_EQUAL( false, updateJob );
}
void test_OTA_extractParameterFailInvalidJobDocModel()
{
OtaFileContext_t * pContext;
bool updateJob = false;
JsonDocParam_t otaCustomJobDocModelParamStructure[ 1 ] =
{
{ OTA_JSON_JOB_ID_KEY, OTA_JOB_PARAM_REQUIRED, *otaAgent.fileContext.pJobName, otaAgent.fileContext.jobNameMaxSize, UINT16_MAX },
};
/* The document structure has an invalid value for ModelParamType_t. */
otaInitDefault();
pContext = parseJobDoc( otaCustomJobDocModelParamStructure, 1, JOB_DOC_A, strlen( JOB_DOC_A ), &updateJob );
TEST_ASSERT_NULL( pContext );
TEST_ASSERT_EQUAL( false, updateJob );
}
void test_OTA_validateJSONFailNullJson()
{
DocParseErr_t err = DocParseErrNone;
err = validateJSON( NULL, 0 );
TEST_ASSERT_EQUAL( DocParseErrNullDocPointer, err );
}
void test_OTA_validateDataBlockInputSize()
{
OtaFileContext_t fileContext = { 0 };
/* Test for when the block received is the final block. */
fileContext.fileSize = OTA_FILE_BLOCK_SIZE;
/* Block size is too small. */
TEST_ASSERT_EQUAL( false, validateDataBlock( &fileContext, 0, 0 ) );
/* Block size is the expected size. */
TEST_ASSERT_EQUAL( true, validateDataBlock( &fileContext, 0, OTA_FILE_BLOCK_SIZE ) );
/* Block size is larger than the expected size. */
TEST_ASSERT_EQUAL( false, validateDataBlock( &fileContext, 0, OTA_FILE_BLOCK_SIZE + 1 ) );
/* Test for when the block is not the final block. */
fileContext.fileSize = OTA_FILE_BLOCK_SIZE * 2;
/* Block size is too small. */
TEST_ASSERT_EQUAL( false, validateDataBlock( &fileContext, 0, 0 ) );
/* Block size is the expected size. */
TEST_ASSERT_EQUAL( true, validateDataBlock( &fileContext, 0, OTA_FILE_BLOCK_SIZE ) );
/* Block size is larger than the expected size. */
TEST_ASSERT_EQUAL( false, validateDataBlock( &fileContext, 0, OTA_FILE_BLOCK_SIZE + 1 ) );
}
void test_ingestDataBlockCleanup_NullFile()
{
OtaFileContext_t fileContext = { 0 };
OtaPalStatus_t closeStatus = { 0 };
otaGoToState( OtaAgentStateReady );
fileContext.pRxBlockBitmap = NULL;
fileContext.blocksRemaining = 0;
fileContext.pFile = NULL;
TEST_ASSERT_EQUAL( IngestResultBadFileHandle, ingestDataBlockCleanup( &fileContext, &closeStatus ) );
}
void test_verifyActiveJobStatus_NullJobName()
{
OtaFileContext_t fileContext = { 0 };
OtaFileContext_t finalFile = { 0 };
OtaFileContext_t * pFinalFile = &finalFile;
bool shouldUpdate = false;
TEST_ASSERT_EQUAL( OtaJobParseErrNullJob, verifyActiveJobStatus( &fileContext, &pFinalFile, &shouldUpdate ) );
}
void test_verifyActiveJobStatus_NullUpdateURL()
{
OtaFileContext_t fileContext = { 0 };
OtaFileContext_t finalFile = { 0 };
OtaFileContext_t * pFinalFile = &finalFile;
bool shouldUpdate = false;
/* Set the job names to be the same to simulate receiving a job doc with the same ID. */
( void ) memcpy( otaAgent.pActiveJobName, "jobName", sizeof( "jobName" ) );
fileContext.pJobName = ( uint8_t * ) "jobName";
/* Set the pUpdateUrlPath to NULL. */
otaAgent.fileContext.pUpdateUrlPath = NULL;
TEST_ASSERT_EQUAL( OtaJobParseErrUpdateCurrentJob, verifyActiveJobStatus( &fileContext, &pFinalFile, &shouldUpdate ) );
}
void test_verifyActiveJobStatus_NullCleanupInterface()
{
OtaFileContext_t fileContext = { 0 };
OtaFileContext_t finalFile = { 0 };
OtaFileContext_t * pFinalFile = &finalFile;
bool shouldUpdate = false;
otaGoToState( OtaAgentStateReady );
/* Make it so that the job document names do not match. This simulates
* receiving a new job document. In this scenario, the OTA Agent will drop
* the old job. */
fileContext.pJobName = ( uint8_t * ) "jobName";
memset( otaAgent.pActiveJobName, 0, OTA_JOB_ID_MAX_SIZE );
/* Set the data interface for cleanup to NULL. */
otaDataInterface.cleanup = NULL;
/* The verifyActiveJobStatus function is expected to safely avoid calling
* the cleanup function if it is not defined. */
TEST_ASSERT_EQUAL( OtaJobParseErrNone, verifyActiveJobStatus( &fileContext, &pFinalFile, &shouldUpdate ) );
}