Skip to content

Commit 0a0661a

Browse files
committed
fota: handle FOTA_DOWNLOAD_REJECTED
If an invalid image is received during FOTA, the nRF Cloud FOTA poll library will notify the application with the FOTA_DOWNLOAD_REJECTED event. This commit adds handling for this event in the FOTA and main modules to: 1. notify the user that the update has been rejected and 2. allow future FOTA attempts. Signed-off-by: Simen S. Røstad <[email protected]>
1 parent 1edb37d commit 0a0661a

File tree

8 files changed

+158
-3
lines changed

8 files changed

+158
-3
lines changed

.github/workflows/compliance.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
steps:
1616
- name: Installation
1717
run: |
18-
apt-get update && apt-get install --no-install-recommends -y libmagic1 git
18+
apt-get update && apt-get install --no-install-recommends -y libmagic1 git gcc build-essential
1919
pip install west gitlint
2020
2121
- name: Checkout the code

app/src/main.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1683,6 +1683,8 @@ static enum smf_state_result fota_run(void *o)
16831683
switch (msg) {
16841684
case FOTA_DOWNLOAD_CANCELED:
16851685
__fallthrough;
1686+
case FOTA_DOWNLOAD_REJECTED:
1687+
__fallthrough;
16861688
case FOTA_DOWNLOAD_TIMED_OUT:
16871689
__fallthrough;
16881690
case FOTA_DOWNLOAD_FAILED:

app/src/modules/fota/fota.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,11 @@ static void fota_status(enum nrf_cloud_fota_status status, const char *const sta
192192

193193
evt = FOTA_DOWNLOAD_CANCELED;
194194
break;
195+
case NRF_CLOUD_FOTA_REJECTED:
196+
LOG_WRN("Firmware update rejected");
197+
198+
evt = FOTA_DOWNLOAD_REJECTED;
199+
break;
195200
case NRF_CLOUD_FOTA_TIMED_OUT:
196201
LOG_WRN("Firmware download timed out");
197202

@@ -386,6 +391,8 @@ static enum smf_state_result state_downloading_update_run(void *obj)
386391
return SMF_EVENT_HANDLED;
387392
case FOTA_DOWNLOAD_CANCELED:
388393
__fallthrough;
394+
case FOTA_DOWNLOAD_REJECTED:
395+
__fallthrough;
389396
case FOTA_DOWNLOAD_TIMED_OUT:
390397
__fallthrough;
391398
case FOTA_DOWNLOAD_FAILED:

app/src/modules/fota/fota.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ enum fota_msg_type {
4444
/* Event notified when the FOTA download has been canceled. */
4545
FOTA_DOWNLOAD_CANCELED,
4646

47+
/* Event notified when the FOTA update has been rejected by the device,
48+
* for example wrong image signature or incompatible image.
49+
*/
50+
FOTA_DOWNLOAD_REJECTED,
51+
4752
/* Input message types */
4853

4954
/* Request to poll cloud for any available firmware updates. */

tests/module/fota/src/fota_module_test.c

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,102 @@ void test_fota_module_should_fail_on_cancellation(void)
222222
event_expect(FOTA_DOWNLOAD_CANCELED);
223223
}
224224

225+
void test_fota_module_should_fail_on_rejection(void)
226+
{
227+
/* Given */
228+
nrf_cloud_fota_poll_process_fake.return_val = 0;
229+
230+
/* 1. Poll for update */
231+
event_send(FOTA_POLL_REQUEST);
232+
event_expect(FOTA_POLL_REQUEST);
233+
234+
/* 2. Downloading update */
235+
invoke_nrf_cloud_fota_callback_stub_status(NRF_CLOUD_FOTA_DOWNLOADING);
236+
event_expect(FOTA_DOWNLOADING_UPDATE);
237+
238+
/* 3. Download rejected */
239+
invoke_nrf_cloud_fota_callback_stub_status(NRF_CLOUD_FOTA_REJECTED);
240+
event_expect(FOTA_DOWNLOAD_REJECTED);
241+
}
242+
243+
void test_fota_module_should_restart_after_cancellation(void)
244+
{
245+
/* Given */
246+
nrf_cloud_fota_poll_process_fake.return_val = 0;
247+
nrf_cloud_fota_poll_update_apply_fake.return_val = 0;
248+
249+
/* 1. Poll for first update */
250+
event_send(FOTA_POLL_REQUEST);
251+
event_expect(FOTA_POLL_REQUEST);
252+
253+
/* 2. Downloading update */
254+
invoke_nrf_cloud_fota_callback_stub_status(NRF_CLOUD_FOTA_DOWNLOADING);
255+
event_expect(FOTA_DOWNLOADING_UPDATE);
256+
257+
/* 3. Download canceled */
258+
invoke_nrf_cloud_fota_callback_stub_status(NRF_CLOUD_FOTA_CANCELED);
259+
event_expect(FOTA_DOWNLOAD_CANCELED);
260+
261+
/* 4. Poll for second update - should work after cancellation */
262+
event_send(FOTA_POLL_REQUEST);
263+
event_expect(FOTA_POLL_REQUEST);
264+
265+
/* 5. Downloading second update */
266+
invoke_nrf_cloud_fota_callback_stub_status(NRF_CLOUD_FOTA_DOWNLOADING);
267+
event_expect(FOTA_DOWNLOADING_UPDATE);
268+
269+
/* 6. Download succeeded, validation needed */
270+
invoke_nrf_cloud_fota_callback_stub_status(NRF_CLOUD_FOTA_FMFU_VALIDATION_NEEDED);
271+
event_expect(FOTA_IMAGE_APPLY_NEEDED);
272+
273+
/* 7. Apply image */
274+
event_send(FOTA_IMAGE_APPLY);
275+
event_expect(FOTA_IMAGE_APPLY);
276+
277+
/* 8. Reboot needed */
278+
invoke_nrf_cloud_fota_callback_stub_reboot(FOTA_REBOOT_SUCCESS);
279+
event_expect(FOTA_SUCCESS_REBOOT_NEEDED);
280+
}
281+
282+
void test_fota_module_should_restart_after_rejection(void)
283+
{
284+
/* Given */
285+
nrf_cloud_fota_poll_process_fake.return_val = 0;
286+
nrf_cloud_fota_poll_update_apply_fake.return_val = 0;
287+
288+
/* 1. Poll for first update */
289+
event_send(FOTA_POLL_REQUEST);
290+
event_expect(FOTA_POLL_REQUEST);
291+
292+
/* 2. Downloading update */
293+
invoke_nrf_cloud_fota_callback_stub_status(NRF_CLOUD_FOTA_DOWNLOADING);
294+
event_expect(FOTA_DOWNLOADING_UPDATE);
295+
296+
/* 3. Download rejected */
297+
invoke_nrf_cloud_fota_callback_stub_status(NRF_CLOUD_FOTA_REJECTED);
298+
event_expect(FOTA_DOWNLOAD_REJECTED);
299+
300+
/* 4. Poll for second update - should work after rejection */
301+
event_send(FOTA_POLL_REQUEST);
302+
event_expect(FOTA_POLL_REQUEST);
303+
304+
/* 5. Downloading second update */
305+
invoke_nrf_cloud_fota_callback_stub_status(NRF_CLOUD_FOTA_DOWNLOADING);
306+
event_expect(FOTA_DOWNLOADING_UPDATE);
307+
308+
/* 6. Download succeeded, validation needed */
309+
invoke_nrf_cloud_fota_callback_stub_status(NRF_CLOUD_FOTA_FMFU_VALIDATION_NEEDED);
310+
event_expect(FOTA_IMAGE_APPLY_NEEDED);
311+
312+
/* 7. Apply image */
313+
event_send(FOTA_IMAGE_APPLY);
314+
event_expect(FOTA_IMAGE_APPLY);
315+
316+
/* 8. Reboot needed */
317+
invoke_nrf_cloud_fota_callback_stub_reboot(FOTA_REBOOT_SUCCESS);
318+
event_expect(FOTA_SUCCESS_REBOOT_NEEDED);
319+
}
320+
225321
/* This is required to be added to each test. That is because unity's
226322
* main may return nonzero, while zephyr's main currently must
227323
* return 0 in all cases (other values are reserved).

tests/module/main/src/main.c

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,51 @@ void test_timer_cancellation_during_fota(void)
797797
send_cloud_disconnected();
798798
}
799799

800+
void test_timer_rejection_during_fota_rejected(void)
801+
{
802+
int err;
803+
struct storage_msg storage_msg = {
804+
.type = STORAGE_MODE_BUFFER,
805+
};
806+
807+
/* Switch to buffer mode first */
808+
err = zbus_chan_pub(&STORAGE_CHAN, &storage_msg, K_SECONDS(1));
809+
TEST_ASSERT_EQUAL(0, err);
810+
811+
expect_storage_event(STORAGE_MODE_BUFFER);
812+
813+
/* Start normal operation */
814+
send_cloud_connected();
815+
816+
/* We would have received cloud and location events, but they should be ignored in this
817+
* test.
818+
*/
819+
purge_cloud_events();
820+
purge_location_events();
821+
822+
/* Trigger FOTA */
823+
send_fota_msg(FOTA_DOWNLOADING_UPDATE);
824+
expect_fota_event(FOTA_DOWNLOADING_UPDATE);
825+
826+
/* During FOTA, no timer-based events should occur - verify by waiting multiple
827+
* intervals.
828+
*/
829+
expect_no_events(CONFIG_APP_CLOUD_UPDATE_INTERVAL_SECONDS * 5);
830+
831+
/* Reject FOTA and return to normal operation */
832+
send_fota_msg(FOTA_DOWNLOAD_REJECTED);
833+
expect_fota_event(FOTA_DOWNLOAD_REJECTED);
834+
835+
/* Should resume normal timer-based operation */
836+
k_sleep(K_SECONDS(CONFIG_APP_CLOUD_UPDATE_INTERVAL_SECONDS));
837+
expect_storage_event(STORAGE_BATCH_REQUEST);
838+
expect_cloud_event(CLOUD_SHADOW_GET_DELTA);
839+
expect_fota_event(FOTA_POLL_REQUEST);
840+
841+
/* Cleanup */
842+
send_cloud_disconnected();
843+
}
844+
800845
void test_multiple_cloud_data_send_intervals(void)
801846
{
802847
/* Connect and complete initial sampling */

tests/state_machines/source_of_truth/fota.puml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ state STATE_RUNNING {
2828

2929
STATE_DOWNLOADING_UPDATE --> STATE_WAITING_FOR_IMAGE_APPLY: FOTA_IMAGE_APPLY_NEEDED
3030
STATE_DOWNLOADING_UPDATE --> STATE_REBOOT_PENDING: FOTA_SUCCESS_REBOOT_NEEDED
31-
STATE_DOWNLOADING_UPDATE --> STATE_WAITING_FOR_POLL_REQUEST: FOTA_DOWNLOAD_CANCELED || FOTA_DOWNLOAD_TIMED_OUT || FOTA_DOWNLOAD_FAILED
31+
STATE_DOWNLOADING_UPDATE --> STATE_WAITING_FOR_POLL_REQUEST: FOTA_DOWNLOAD_CANCELED || FOTA_DOWNLOAD_TIMED_OUT || FOTA_DOWNLOAD_FAILED || FOTA_DOWNLOAD_REJECTED
3232

3333
STATE_WAITING_FOR_IMAGE_APPLY --> STATE_IMAGE_APPLYING: FOTA_IMAGE_APPLY
3434

tests/state_machines/source_of_truth/main.puml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,5 @@ state STATE_FOTA {
7171

7272
[*] --> STATE_RUNNING
7373
STATE_RUNNING --> STATE_FOTA: FOTA_DOWNLOADING_UPDATE
74-
STATE_FOTA --> STATE_RUNNING[H*]: FOTA_DOWNLOAD_CANCELED || FOTA_DOWNLOAD_TIMED_OUT || FOTA_DOWNLOAD_FAILED
74+
STATE_FOTA --> STATE_RUNNING[H*]: FOTA_DOWNLOAD_CANCELED || FOTA_DOWNLOAD_TIMED_OUT || FOTA_DOWNLOAD_FAILED || FOTA_DOWNLOAD_REJECTED
7575
@enduml

0 commit comments

Comments
 (0)