/* * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "services/common/put_bytes/put_bytes.h" #include "services/common/comm_session/session_receive_router.h" #include "os/tick.h" #include "system/bootbits.h" #include "system/firmware_storage.h" #include "system/logging.h" #include "util/attributes.h" #include "util/net.h" #include #include "FreeRTOS.h" #include "semphr.h" #include "clar.h" #include #include "fake_events.h" #include "fake_pbl_malloc.h" #include "fake_new_timer.h" #include "fake_put_bytes_storage_mem.h" #include "fake_queue.h" #include "fake_rtc.h" #include "fake_session.h" #include "fake_spi_flash.h" #include "fake_system_task.h" #include "stubs_bt_lock.h" #include "stubs_freertos.h" #include "stubs_hexdump.h" #include "stubs_logging.h" #include "stubs_mutex.h" #include "stubs_passert.h" #include "stubs_pfs.h" #include "stubs_prompt.h" #include "stubs_serial.h" #include "stubs_task_watchdog.h" #include "stubs_tick.h" extern SemaphoreHandle_t put_bytes_get_semaphore(void); extern TimerID put_bytes_get_timer_id(void); extern uint32_t put_bytes_get_index(void); extern uint8_t prv_put_bytes_get_max_batched_pb_ops(void); extern const ReceiverImplementation g_put_bytes_receiver_impl; static const PebbleProtocolEndpoint s_put_bytes_endpoint = (const PebbleProtocolEndpoint) { .endpoint_id = 0xBEEF, .handler = NULL, .access_mask = PebbleProtocolAccessPrivate, .receiver_imp = &g_put_bytes_receiver_impl, .receiver_opt = NULL, }; // Fakes ////////////////////////////////////////////////////////// uint32_t s_boot_bits_orred; void boot_bit_set(BootBitValue bit) { s_boot_bits_orred |= bit; } static bool s_firmware_update_is_in_progress; bool firmware_update_is_in_progress(void) { return s_firmware_update_is_in_progress; } void psleep(int millis) { } void app_storage_get_file_name(char *name, size_t buf_length, AppInstallId app_id, PebbleTask task) { strcpy(name, "t"); } void bluetooth_analytics_handle_put_bytes_stats(bool successful, uint8_t type, uint32_t total_size, uint32_t elapsed_time_ms, const SlaveConnEventStats *orig_stats) { } bool bt_driver_analytics_get_conn_event_stats(SlaveConnEventStats *stats) { return false; } typedef enum { CmdInit = 0x01, CmdPut = 0x02, CmdCommit = 0x03, CmdAbort = 0x04, CmdInstall = 0x05, CmdInvalid = 0xff, } Cmd; typedef enum { ResponseAck = 0x01, ResponseNack = 0x02, } Response; // Send an INIT message typedef struct PACKED { Cmd cmd:8; uint32_t total_size; PutBytesObjectType type:8; union { struct { uint8_t index; char filename[]; }; uint32_t cookie; }; } InitRequest; typedef struct PACKED { Cmd cmd:8; uint32_t cookie; uint32_t payload_size; uint8_t payload[]; } PutRequest; typedef struct PACKED { Cmd cmd:8; uint32_t cookie; } InstallRequest; typedef struct PACKED { Cmd cmd:8; uint32_t cookie; } AbortRequest; typedef struct PACKED { Cmd cmd:8; uint32_t cookie; uint32_t crc; } CommitRequest; typedef struct PACKED { Response response:8; uint32_t cookie; } ResponseMsg; static int s_acks_received; static int s_nacks_received; static uint32_t s_last_response_cookie; static CommSession *s_session; // Helpers /////////////////////////////////////////////////////////// #define VALID_OBJECT_SIZE (4) #define PUT_BYTES_TIMEOUT_MS (30000) #define EXPECTED_CRC (0x12345678) #define EXPECTED_COOKIE (0xabcd1234) #define EXPECT_INIT_TIMEOUT_MS (1000) static void(*s_do_before_write)(void); static void prv_receive_data(CommSession *session, const uint8_t* data, size_t length) { Receiver *r = g_put_bytes_receiver_impl.prepare(session, &s_put_bytes_endpoint, length); if (r) { if (s_do_before_write) { s_do_before_write(); } g_put_bytes_receiver_impl.write(r, data, length); g_put_bytes_receiver_impl.finish(r); } else { PBL_LOG(LOG_LEVEL_ERROR, "No receiver returned!"); } } static void prv_receive_init(uint32_t total_size, PutBytesObjectType object_type) { InitRequest init_msg = (InitRequest) { .cmd = CmdInit, .total_size = htonl(total_size), .type = object_type, .cookie = htonl(1), }; prv_receive_data(s_session, (const uint8_t *) &init_msg, sizeof(init_msg)); } static void prv_receive_init_cookie(uint32_t total_size, PutBytesObjectType object_type, uint32_t cookie) { InitRequest init_msg = (InitRequest) { .cmd = CmdInit, .total_size = htonl(total_size), .type = object_type | (1 << 7), .cookie = htonl(cookie), }; prv_receive_data(s_session, (const uint8_t *) &init_msg, sizeof(init_msg)); } static void prv_receive_init_file(uint32_t total_size, const char *fn, size_t fn_len) { uint8_t buffer[sizeof(InitRequest) + fn_len]; InitRequest *init_msg = (InitRequest *)buffer; *init_msg = (InitRequest) { .cmd = CmdInit, .total_size = htonl(total_size), .type = ObjectFile, }; memcpy(&init_msg->filename[0], fn, fn_len); prv_receive_data(s_session, buffer, sizeof(buffer)); } static void prv_receive_put(uint32_t cookie, const uint8_t *payload, uint32_t payload_size) { uint8_t buffer[sizeof(PutRequest) + payload_size]; PutRequest *put_msg = (PutRequest *)buffer; *put_msg = (PutRequest) { .cmd = CmdPut, .cookie = htonl(cookie), .payload_size = htonl(payload_size), }; memcpy(&put_msg->payload[0], payload, payload_size); prv_receive_data(s_session, buffer, sizeof(buffer)); } static void prv_receive_commit(uint32_t cookie, uint32_t crc) { CommitRequest commit_msg = (CommitRequest) { .cmd = CmdCommit, .cookie = htonl(cookie), .crc = htonl(crc), }; prv_receive_data(s_session, (const uint8_t *)&commit_msg, sizeof(commit_msg)); } static void prv_receive_abort(uint32_t cookie) { AbortRequest abort_msg = (AbortRequest) { .cmd = CmdAbort, .cookie = htonl(cookie), }; prv_receive_data(s_session, (const uint8_t *) &abort_msg, sizeof(abort_msg)); } static void prv_receive_install(uint32_t cookie) { InstallRequest install_msg = (InstallRequest) { .cmd = CmdInstall, .cookie = htonl(cookie), }; prv_receive_data(s_session, (const uint8_t *) &install_msg, sizeof(install_msg)); } #define assert_ack_count(c) \ { \ fake_comm_session_process_send_next(); \ cl_assert_equal_i(s_acks_received, c); \ } #define assert_nack_count(c) \ { \ fake_comm_session_process_send_next(); \ cl_assert_equal_i(s_nacks_received, c); \ } #define assert_cleanup_event(object_type_, object_size_) \ PebbleEvent event = fake_event_get_last(); \ cl_assert_equal_i(event.type, PEBBLE_PUT_BYTES_EVENT); \ cl_assert_equal_i(event.put_bytes.type, PebblePutBytesEventTypeCleanup); \ cl_assert_equal_i(event.put_bytes.object_type, object_type_); \ cl_assert_equal_i(event.put_bytes.total_size, object_size_); \ cl_assert_equal_i(event.put_bytes.progress_percent, 0); \ cl_assert_equal_b(event.put_bytes.failed, true); \ static void prv_receive_init_fw_object(void) { prv_receive_init(VALID_OBJECT_SIZE, ObjectFirmware); fake_comm_session_process_send_next(); fake_system_task_callbacks_invoke_pending(); } static void prv_process_and_reset_test_counters(void) { fake_comm_session_process_send_next(); fake_system_task_callbacks_invoke_pending(); s_acks_received = 0; s_nacks_received = 0; } static void prv_receive_init_and_put_fw_object(void) { prv_receive_init(VALID_OBJECT_SIZE, ObjectFirmware); fake_comm_session_process_send_next(); fake_system_task_callbacks_invoke_pending(); const uint8_t chunk[] = { 0xaa, 0xbb, 0xcc, 0xdd }; prv_receive_put(s_last_response_cookie, chunk, sizeof(chunk)); prv_process_and_reset_test_counters(); } static void prv_receive_init_put_and_commit_fw_object(void) { prv_receive_init_and_put_fw_object(); prv_receive_commit(s_last_response_cookie, EXPECTED_CRC); prv_process_and_reset_test_counters(); } static void prv_receive_init_put_commit_and_install(PutBytesObjectType object_type) { prv_receive_init(VALID_OBJECT_SIZE, object_type); prv_process_and_reset_test_counters(); const uint8_t chunk[] = { 0xaa, 0xbb, 0xcc, 0xdd }; prv_receive_put(s_last_response_cookie, chunk, sizeof(chunk)); prv_process_and_reset_test_counters(); prv_receive_commit(s_last_response_cookie, EXPECTED_CRC); prv_process_and_reset_test_counters(); prv_receive_install(s_last_response_cookie); } // Tests /////////////////////////////////////////////////////////// static void prv_system_msg_sent_callback(uint16_t endpoint_id, const uint8_t* data, unsigned int data_length) { if (endpoint_id != 0xBEEF) { // Not the put bytes endpoint, ignore return; } // We should only be getting ACKs and NACKs back on this endpoint, which are both 5 bytes long cl_assert_equal_i(data_length, 5); ResponseMsg *response_msg = (ResponseMsg *)data; s_last_response_cookie = ntohl(response_msg->cookie); if (response_msg->response == ResponseAck) { ++s_acks_received; } else if (response_msg->response == ResponseNack) { ++s_nacks_received; } } void test_put_bytes__initialize(void) { fake_pb_storage_mem_reset(); fake_pb_storage_mem_set_crc(EXPECTED_CRC); fake_comm_session_init(); fake_event_reset_count(); Transport *transport = fake_transport_create(TransportDestinationSystem, NULL, prv_system_msg_sent_callback); s_session = fake_transport_set_connected(transport, true /* connected */); cl_assert_equal_p(comm_session_get_system_session(), s_session); prv_process_and_reset_test_counters(); s_last_response_cookie = 0; s_boot_bits_orred = 0; s_do_before_write = NULL; // Common for most tests: s_firmware_update_is_in_progress = true; fake_spi_flash_init(0, 0x1000000); put_bytes_init(); } void test_put_bytes__cleanup(void) { put_bytes_deinit(); fake_comm_session_cleanup(); fake_system_task_callbacks_cleanup(); fake_event_clear_last(); fake_pbl_malloc_check_net_allocs(); fake_pbl_malloc_clear_tracking(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // Misc static TickType_t prv_taking_too_long_yield_cb(QueueHandle_t queue) { return milliseconds_to_ticks(1000); } void test_put_bytes__lock_contention_upon_prepare_message(void) { // When the PutBytes lock is taken for a long time when a PutBytes message is prepared, // expect to receive a Nack: // Take and hold for a long time: xSemaphoreTake(put_bytes_get_semaphore(), portMAX_DELAY); fake_queue_set_yield_callback(put_bytes_get_semaphore(), prv_taking_too_long_yield_cb); prv_receive_init(4, ObjectFirmware); // Release it: xSemaphoreGive(put_bytes_get_semaphore()); fake_queue_set_yield_callback(put_bytes_get_semaphore(), NULL); assert_nack_count(1); } static void prv_hold_lock_before_write(void) { // Take and hold for a long time: xSemaphoreTake(put_bytes_get_semaphore(), portMAX_DELAY); fake_queue_set_yield_callback(put_bytes_get_semaphore(), prv_taking_too_long_yield_cb); } void test_put_bytes__lock_contention_upon_write_message(void) { // When the PutBytes lock is taken for a long time when a PutBytes message is written, // expect to receive a Nack: s_do_before_write = prv_hold_lock_before_write; prv_receive_init(4, ObjectFirmware); // Release it: xSemaphoreGive(put_bytes_get_semaphore()); fake_queue_set_yield_callback(put_bytes_get_semaphore(), NULL); assert_nack_count(1); } static void prv_cancel_before_write_second_message(void) { put_bytes_cancel(); } void test_put_bytes__cancel_between_prepare_and_finish(void) { // When the put_bytes_cancel() is called while the PutBytes message is written (between "prepare" // and "finish"), expect to receive a Nack: prv_receive_init(4, ObjectWatchApp); assert_ack_count(1); assert_nack_count(0); s_do_before_write = prv_cancel_before_write_second_message; const uint8_t payload[] = { 0xaa, 0xbb, 0xcc }; prv_receive_put(s_last_response_cookie, payload, sizeof(payload)); assert_nack_count(1); } void test_put_bytes__invalid_command_opcode(void) { uint8_t invalid_cmd[] = { CmdInvalid }; prv_receive_data(s_session, (const uint8_t *) invalid_cmd, sizeof(invalid_cmd)); // Messages with invalid command opcodes are NACK'd: assert_ack_count(0); assert_nack_count(1); } //////////////////////////////////////////////////////////////////////////////////////////////////// // Init Message void test_put_bytes__init_firmware(void) { prv_receive_init(VALID_OBJECT_SIZE, ObjectFirmware); // All good! assert_ack_count(1); assert_nack_count(0); // Expect "Start" event: PebbleEvent event = fake_event_get_last(); cl_assert_equal_i(event.type, PEBBLE_PUT_BYTES_EVENT); cl_assert_equal_i(event.put_bytes.type, PebblePutBytesEventTypeStart); cl_assert_equal_i(event.put_bytes.object_type, ObjectFirmware); cl_assert_equal_i(event.put_bytes.total_size, VALID_OBJECT_SIZE); cl_assert_equal_i(event.put_bytes.progress_percent, 0); cl_assert_equal_b(event.put_bytes.failed, false); } void test_put_bytes__init_while_already_busy(void) { prv_receive_init(VALID_OBJECT_SIZE, ObjectFirmware); prv_receive_init(VALID_OBJECT_SIZE, ObjectFirmware); assert_nack_count(1); } void test_put_bytes__init_too_large(void) { prv_receive_init(UINT_MAX, ObjectFirmware); // Fail due to massive total_size in our init message assert_ack_count(0); assert_nack_count(1); } void test_put_bytes__init_msg_incomplete(void) { const uint8_t incomplete_init_msg = CmdInit; prv_receive_data(s_session, (const uint8_t *) &incomplete_init_msg, sizeof(incomplete_init_msg)); assert_ack_count(0); assert_nack_count(1); } void test_put_bytes__init_invalid_object_type(void) { PutBytesObjectType invalid_object_type = 0xff; prv_receive_init(VALID_OBJECT_SIZE, invalid_object_type); // Fail due to massive total_size in our init message assert_ack_count(0); assert_nack_count(1); } void test_put_bytes__init_firmware_object_while_not_in_fw_update_mode(void) { s_firmware_update_is_in_progress = false; prv_receive_init(VALID_OBJECT_SIZE, ObjectFirmware); assert_ack_count(0); assert_nack_count(1); } void test_put_bytes__init_recovery_object_while_not_in_fw_update_mode(void) { s_firmware_update_is_in_progress = false; prv_receive_init(VALID_OBJECT_SIZE, ObjectRecovery); assert_ack_count(0); assert_nack_count(1); } void test_put_bytes__init_sys_resources_object_while_not_in_fw_update_mode(void) { s_firmware_update_is_in_progress = false; prv_receive_init(VALID_OBJECT_SIZE, ObjectSysResources); assert_ack_count(0); assert_nack_count(1); } void test_put_bytes__init_app_resources_okay_while_not_in_fw_update_mode(void) { s_firmware_update_is_in_progress = false; prv_receive_init_cookie(VALID_OBJECT_SIZE, ObjectAppResources, EXPECTED_COOKIE); assert_ack_count(1); assert_nack_count(0); cl_assert_equal_i(EXPECTED_COOKIE, put_bytes_get_index()); } void test_put_bytes__init_watch_app_okay_while_not_in_fw_update_mode(void) { s_firmware_update_is_in_progress = false; prv_receive_init_cookie(VALID_OBJECT_SIZE, ObjectWatchApp, EXPECTED_COOKIE); assert_ack_count(1); assert_nack_count(0); cl_assert_equal_i(EXPECTED_COOKIE, put_bytes_get_index()); } void test_put_bytes__init_file_okay_while_not_in_fw_update_mode(void) { s_firmware_update_is_in_progress = false; const char fn[] = "test.txt"; prv_receive_init_file(VALID_OBJECT_SIZE, fn, strlen(fn) + 1); assert_ack_count(1); assert_nack_count(0); } void test_put_bytes__init_worker_okay_while_not_in_fw_update_mode(void) { s_firmware_update_is_in_progress = false; prv_receive_init_cookie(VALID_OBJECT_SIZE, ObjectWatchWorker, EXPECTED_COOKIE); assert_ack_count(1); assert_nack_count(0); cl_assert_equal_i(EXPECTED_COOKIE, put_bytes_get_index()); } void test_put_bytes__init_nack_upon_oom(void) { fake_malloc_set_largest_free_block(1024); // PutBytes allocates ~2K prv_receive_init(1024 * 1024, ObjectFirmware); fake_malloc_set_largest_free_block(~0); assert_ack_count(0); assert_nack_count(1); } //////////////////////////////////////////////////////////////////////////////////////////////////// // Put Message void test_put_bytes__put_message_too_short(void) { prv_receive_init_fw_object(); prv_process_and_reset_test_counters(); const uint8_t incomplete_put_msg = CmdPut; prv_receive_data(s_session, (const uint8_t *) &incomplete_put_msg, sizeof(incomplete_put_msg)); assert_ack_count(0); assert_nack_count(1); } void test_put_bytes__put_message_length_field_too_long(void) { prv_receive_init_fw_object(); prv_process_and_reset_test_counters(); const size_t payload_size = 2; const uint8_t chunk[] = { 0xaa, 0xbb }; uint8_t buffer[sizeof(PutRequest) + payload_size]; PutRequest *put_msg = (PutRequest *)buffer; *put_msg = (PutRequest) { .cmd = CmdPut, .cookie = htonl(s_last_response_cookie), .payload_size = htonl(payload_size) + 1 /* one off! */, }; memcpy(&put_msg->payload[0], chunk, payload_size); prv_receive_data(s_session, buffer, sizeof(buffer)); assert_ack_count(0); assert_nack_count(1); } void test_put_bytes__invalid_session_cookie(void) { prv_receive_init_fw_object(); prv_process_and_reset_test_counters(); const uint8_t chunk[] = { 0xaa, 0xbb, 0xcc }; prv_receive_put(~s_last_response_cookie, chunk, sizeof(chunk)); assert_ack_count(0); assert_nack_count(1); } void test_put_bytes__not_in_fw_update_mode(void) { prv_receive_init_fw_object(); prv_process_and_reset_test_counters(); s_firmware_update_is_in_progress = false; const uint8_t chunk[] = { 0xaa, 0xbb, 0xcc }; prv_receive_put(s_last_response_cookie, chunk, sizeof(chunk)); assert_ack_count(0); assert_nack_count(1); } void test_put_bytes__previous_chunk_not_acked_yet(void) { uint8_t max_put_ops = prv_put_bytes_get_max_batched_pb_ops(); prv_receive_init(VALID_OBJECT_SIZE * max_put_ops, ObjectFirmware); prv_process_and_reset_test_counters(); const uint8_t chunk[] = { 0xaa, 0xbb, 0xcc }; uint8_t max_pb_ops = prv_put_bytes_get_max_batched_pb_ops(); for (int i = 0; i <= max_pb_ops; i++) { prv_receive_put(s_last_response_cookie, chunk, sizeof(chunk)); } assert_ack_count(max_pb_ops); assert_nack_count(1); } void test_put_bytes__chunk_too_large(void) { prv_receive_init_fw_object(); prv_process_and_reset_test_counters(); size_t chunk_size = 1024 * 1024; uint8_t *chunk = kernel_malloc(chunk_size); prv_receive_put(s_last_response_cookie, chunk, chunk_size); kernel_free(chunk); assert_ack_count(0); assert_nack_count(1); } void test_put_bytes__app_cancelled_before_chunk_got_processed(void) { prv_receive_init_cookie(VALID_OBJECT_SIZE, ObjectWatchApp, EXPECTED_COOKIE); prv_process_and_reset_test_counters(); const uint8_t chunk[] = { 0xaa, 0xbb, 0xcc }; prv_receive_put(s_last_response_cookie, chunk, sizeof(chunk)); put_bytes_cancel(); assert_cleanup_event(ObjectWatchApp, VALID_OBJECT_SIZE); if (prv_put_bytes_get_max_batched_pb_ops() > 1) { // With pre-acking, the put will have already been ack'ed and then a Nack will follow assert_ack_count(1); } else { assert_ack_count(0); } assert_nack_count(1); } void test_put_bytes__chunk_written_to_storage_and_progress_event_put(void) { prv_receive_init_fw_object(); prv_process_and_reset_test_counters(); const uint8_t chunk[] = { 0xaa, 0xbb, 0xcc }; prv_receive_put(s_last_response_cookie, chunk, sizeof(chunk)); assert_ack_count(1); assert_nack_count(0); fake_pb_storage_mem_assert_contents_written(chunk, sizeof(chunk)); PebbleEvent event = fake_event_get_last(); cl_assert_equal_i(event.type, PEBBLE_PUT_BYTES_EVENT); cl_assert_equal_i(event.put_bytes.type, PebblePutBytesEventTypeProgress); cl_assert_equal_i(event.put_bytes.object_type, ObjectFirmware); cl_assert_equal_i(event.put_bytes.bytes_transferred, sizeof(chunk)); cl_assert_equal_i(event.put_bytes.progress_percent, 100 * sizeof(chunk) / VALID_OBJECT_SIZE); cl_assert_equal_b(event.put_bytes.failed, false); } static uint32_t s_next_value_to_write; static void prv_cb_before_write(void) { prv_receive_put(s_last_response_cookie, (uint8_t *)&s_next_value_to_write, VALID_OBJECT_SIZE); } void test_put_bytes__receive_batched_messages(void) { uint8_t max_batched_ops = prv_put_bytes_get_max_batched_pb_ops(); int num_ops = 500; if (max_batched_ops < 2) { // This race condition is not possible if we aren't pre-Acking return; } prv_receive_init(VALID_OBJECT_SIZE * num_ops, ObjectFirmware); fake_comm_session_process_send_next(); fake_system_task_callbacks_invoke_pending(); uint8_t buffer[num_ops * VALID_OBJECT_SIZE]; for (size_t i = 0; i < sizeof(buffer); i += VALID_OBJECT_SIZE) { uint32_t towrite = i; memcpy(&buffer[i], &towrite, sizeof(towrite)); } // Make sure we can receive new data in the middle of a pb_storage_append operation for (int i = 0; i < num_ops; i += 2) { int idx = i * VALID_OBJECT_SIZE; prv_receive_put(s_last_response_cookie, &buffer[idx], VALID_OBJECT_SIZE); idx += VALID_OBJECT_SIZE; memcpy(&s_next_value_to_write, &buffer[idx], VALID_OBJECT_SIZE); fake_pb_storage_register_cb_before_write(prv_cb_before_write); fake_comm_session_process_send_next(); fake_system_task_callbacks_invoke_pending(); } fake_pb_storage_mem_assert_contents_written(buffer, sizeof(buffer)); } //////////////////////////////////////////////////////////////////////////////////////////////////// // Commit Message void test_put_bytes__commit_message_too_short(void) { prv_receive_init_and_put_fw_object(); const uint8_t incomplete_put_msg = CmdCommit; prv_receive_data(s_session, (const uint8_t *) &incomplete_put_msg, sizeof(incomplete_put_msg)); assert_ack_count(0); assert_nack_count(1); } void test_put_bytes__commit_message_sent_while_previous_put_was_not_acked_yet(void) { uint8_t max_put_ops = prv_put_bytes_get_max_batched_pb_ops(); prv_receive_init(VALID_OBJECT_SIZE * max_put_ops, ObjectFirmware); prv_process_and_reset_test_counters(); const uint8_t chunk[] = { 0xaa, 0xbb, 0xcc, 0xdd }; for (int i = 0; i < max_put_ops; i++) { prv_receive_put(s_last_response_cookie, chunk, sizeof(chunk)); } prv_receive_commit(s_last_response_cookie, EXPECTED_CRC); assert_ack_count(max_put_ops); // For the Put(s) assert_nack_count(1); // For the Commit } void test_put_bytes__commit_message_cookie_mismatch(void) { prv_receive_init_and_put_fw_object(); prv_receive_commit(~s_last_response_cookie, EXPECTED_CRC); assert_ack_count(0); assert_nack_count(1); } void test_put_bytes__commit_message_while_not_in_fw_update_mode(void) { prv_receive_init_and_put_fw_object(); s_firmware_update_is_in_progress = false; prv_receive_commit(s_last_response_cookie, EXPECTED_CRC); assert_ack_count(0); assert_nack_count(1); } void test_put_bytes__commit_message_crc_mismatch(void) { prv_receive_init_and_put_fw_object(); prv_receive_commit(s_last_response_cookie, ~EXPECTED_CRC); assert_ack_count(0); assert_nack_count(1); } void test_put_bytes__commit_message_fw_description_is_written(void) { prv_receive_init_and_put_fw_object(); prv_receive_commit(s_last_response_cookie, EXPECTED_CRC); fake_comm_session_process_send_next(); fake_system_task_callbacks_invoke_pending(); // Assert the FW description got written at the beginning of the storage: const FirmwareDescription fw_descr = { .description_length = sizeof(FirmwareDescription), .firmware_length = VALID_OBJECT_SIZE, .checksum = EXPECTED_CRC, }; fake_pb_storage_mem_assert_fw_description_written(&fw_descr); } //////////////////////////////////////////////////////////////////////////////////////////////////// // Abort Message void test_put_bytes__abort_message_too_short(void) { prv_receive_init_and_put_fw_object(); prv_process_and_reset_test_counters(); const uint8_t incomplete_abort_msg = CmdAbort; prv_receive_data(s_session, (const uint8_t *) &incomplete_abort_msg, sizeof(incomplete_abort_msg)); assert_ack_count(0); assert_nack_count(1); } void test_put_bytes__abort_message_cookie_mismatch(void) { prv_receive_init_and_put_fw_object(); prv_process_and_reset_test_counters(); prv_receive_abort(~s_last_response_cookie); assert_ack_count(0); assert_nack_count(1); } void test_put_bytes__abort_message_ok(void) { prv_receive_init_and_put_fw_object(); prv_process_and_reset_test_counters(); prv_receive_abort(s_last_response_cookie); assert_ack_count(1); assert_nack_count(0); assert_cleanup_event(ObjectFirmware, VALID_OBJECT_SIZE); } //////////////////////////////////////////////////////////////////////////////////////////////////// // Install Message void test_put_bytes__install_message_while_not_idle(void) { prv_receive_init(VALID_OBJECT_SIZE, ObjectFirmware); prv_process_and_reset_test_counters(); prv_receive_install(s_last_response_cookie); assert_ack_count(0); assert_nack_count(1); } void test_put_bytes__install_message_too_short(void) { prv_receive_init_put_and_commit_fw_object(); const uint8_t incomplete_install_msg = CmdInstall; prv_receive_data(s_session, (const uint8_t *) &incomplete_install_msg, sizeof(incomplete_install_msg)); assert_ack_count(0); assert_nack_count(1); } void test_put_bytes__install_message_while_not_in_fw_update_mode(void) { prv_receive_init_put_and_commit_fw_object(); s_firmware_update_is_in_progress = false; prv_receive_install(s_last_response_cookie); assert_ack_count(0); assert_nack_count(1); } void test_put_bytes__install_message_cookie_mismatch(void) { prv_receive_init_put_and_commit_fw_object(); prv_receive_install(~s_last_response_cookie); assert_ack_count(0); assert_nack_count(1); } void test_put_bytes__install_message_prf_boot_bit_set(void) { prv_receive_init_put_commit_and_install(ObjectRecovery); assert_ack_count(1); assert_nack_count(0); cl_assert_equal_i((s_boot_bits_orred & BOOT_BIT_NEW_PRF_AVAILABLE), BOOT_BIT_NEW_PRF_AVAILABLE); } void test_put_bytes__install_message_fw_and_sys_resources_boot_bits_set(void) { // Firmware object: prv_receive_init_put_commit_and_install(ObjectFirmware); assert_ack_count(1); assert_nack_count(0); // Expect boot bit not to be set yet: cl_assert_equal_i((s_boot_bits_orred & BOOT_BIT_NEW_FW_AVAILABLE), 0); // System Resources object: prv_receive_init_put_commit_and_install(ObjectSysResources); assert_ack_count(1); assert_nack_count(0); // Finally, expect both boot bits to be set at once: cl_assert_equal_i((s_boot_bits_orred & BOOT_BIT_NEW_FW_AVAILABLE), BOOT_BIT_NEW_FW_AVAILABLE); cl_assert_equal_i((s_boot_bits_orred & BOOT_BIT_NEW_SYSTEM_RESOURCES_AVAILABLE), BOOT_BIT_NEW_SYSTEM_RESOURCES_AVAILABLE); } //////////////////////////////////////////////////////////////////////////////////////////////////// // Timeouts void test_put_bytes__init_starts_timeout_timer(void) { prv_receive_init_fw_object(); TimerID timer_id = put_bytes_get_timer_id(); cl_assert_equal_b(true, stub_new_timer_is_scheduled(timer_id)); cl_assert_equal_i(PUT_BYTES_TIMEOUT_MS, stub_new_timer_timeout(timer_id)); } void test_put_bytes__put_chunk_restarts_timeout_timer(void) { prv_receive_init_fw_object(); // Stop the timer, so we can easily detect it gets restarted again: TimerID timer_id = put_bytes_get_timer_id(); new_timer_stop(timer_id); const uint8_t chunk[] = { 0xaa, 0xbb, 0xcc }; prv_receive_put(s_last_response_cookie, chunk, sizeof(chunk)); fake_system_task_callbacks_invoke_pending(); cl_assert_equal_b(true, stub_new_timer_is_scheduled(timer_id)); cl_assert_equal_i(PUT_BYTES_TIMEOUT_MS, stub_new_timer_timeout(timer_id)); } void test_put_bytes__after_timeout_cleanup_and_allow_init_again(void) { prv_receive_init_fw_object(); assert_ack_count(1); assert_nack_count(0); stub_new_timer_fire(put_bytes_get_timer_id()); fake_system_task_callbacks_invoke_pending(); cl_assert_equal_b(fake_pb_storage_mem_get_last_success(), false); assert_cleanup_event(ObjectFirmware, VALID_OBJECT_SIZE); // Send "Init" again: prv_receive_init_fw_object(); assert_ack_count(2); assert_nack_count(0); } //////////////////////////////////////////////////////////////////////////////////////////////////// // put_bytes_expect_init void test_put_bytes__expect_init_noop_while_not_idle(void) { cl_assert(EXPECT_INIT_TIMEOUT_MS != PUT_BYTES_TIMEOUT_MS); prv_receive_init_fw_object(); put_bytes_expect_init(EXPECT_INIT_TIMEOUT_MS); // The timer is still not overridden by the "expect_init" timer: cl_assert_equal_i(stub_new_timer_timeout(put_bytes_get_timer_id()), PUT_BYTES_TIMEOUT_MS); } void test_put_bytes__expect_init_no_event_when_init_received(void) { put_bytes_expect_init(EXPECT_INIT_TIMEOUT_MS); prv_receive_init_fw_object(); // The timer is overridden by the 30s Put Bytes timeout: cl_assert_equal_i(stub_new_timer_timeout(put_bytes_get_timer_id()), PUT_BYTES_TIMEOUT_MS); fake_event_reset_count(); stub_new_timer_fire(put_bytes_get_timer_id()); fake_system_task_callbacks_invoke_pending(); // Expect only "Cleanup" event: cl_assert_equal_i(fake_event_get_count(), 1); assert_cleanup_event(ObjectFirmware, VALID_OBJECT_SIZE); } void test_put_bytes__expect_init_event_upon_timeout(void) { put_bytes_expect_init(EXPECT_INIT_TIMEOUT_MS); stub_new_timer_fire(put_bytes_get_timer_id()); PebbleEvent event = fake_event_get_last(); cl_assert_equal_i(event.type, PEBBLE_PUT_BYTES_EVENT); cl_assert_equal_i(event.put_bytes.type, PebblePutBytesEventTypeInitTimeout); cl_assert_equal_i(event.put_bytes.object_type, ObjectUnknown); cl_assert_equal_i(event.put_bytes.total_size, 0); cl_assert_equal_i(event.put_bytes.progress_percent, 0); cl_assert_equal_b(event.put_bytes.failed, true); } //////////////////////////////////////////////////////////////////////////////////////////////////// // put_bytes_handle_remote_app_event void test_put_bytes__session_closed_after_fw_init(void) { prv_receive_init_fw_object(); PebbleCommSessionEvent app_event = { .is_open = false, .is_system = true }; // Close the BT session, have put_bytes react put_bytes_handle_comm_session_event(&app_event); fake_system_task_callbacks_invoke_pending(); assert_cleanup_event(ObjectFirmware, VALID_OBJECT_SIZE); } void test_put_bytes__session_closed_after_expect_init(void) { put_bytes_expect_init(EXPECT_INIT_TIMEOUT_MS); PebbleCommSessionEvent app_event = { .is_open = false, .is_system = true }; // Close the BT session, have put_bytes react put_bytes_handle_comm_session_event(&app_event); fake_system_task_callbacks_invoke_pending(); assert_cleanup_event(ObjectUnknown, 0); }