pebble/tests/fw/services/test_put_bytes.c
2025-01-27 11:38:16 -08:00

1030 lines
31 KiB
C

/*
* 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 <bluetooth/conn_event_stats.h>
#include "FreeRTOS.h"
#include "semphr.h"
#include "clar.h"
#include <limits.h>
#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);
}