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

427 lines
16 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 "applib/app_message/app_message_internal.h"
#include "clar.h"
#include "services/normal/app_message/app_message_sender.h"
#include "services/common/comm_session/session_internal.h"
#include "services/common/comm_session/protocol.h"
#include "services/common/comm_session/session.h"
#include "process_management/app_install_manager.h"
#include "services/normal/app_outbox_service.h"
#include "util/math.h"
#include "util/net.h"
extern const SessionSendJobImpl s_app_message_send_job_impl;
extern void comm_session_send_queue_cleanup(CommSession *session);
// Fakes & Stubs
////////////////////////////////////////////////////////////////////////////////////////////////////
#include "stubs_analytics.h"
#include "stubs_bt_lock.h"
#include "stubs_logging.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
static int s_app_install_timestamp_update_count;
void app_install_mark_prioritized(AppInstallId install_id, bool can_expire) {
++s_app_install_timestamp_update_count;
}
AppInstallId app_manager_get_current_app_id(void) {
return INSTALL_ID_INVALID;
}
static PebbleProcessMd s_process_md;
const PebbleProcessMd* app_manager_get_current_app_md(void) {
return &s_process_md;
}
static int s_consumed_count;
static AppOutboxStatus s_last_status_code;
void app_outbox_service_consume_message(AppOutboxMessage *message, AppOutboxStatus status) {
s_last_status_code = status;
++s_consumed_count;
kernel_free(message);
}
static AppOutboxMessageHandler s_outbox_message_handler;
static size_t s_service_data_size;
void app_outbox_service_register(AppOutboxServiceTag service_tag,
AppOutboxMessageHandler message_handler,
PebbleTask consumer_task,
size_t service_data_size) {
s_outbox_message_handler = message_handler;
s_service_data_size = service_data_size;
}
static bool s_is_message_cancelled;
bool app_outbox_service_is_message_cancelled(AppOutboxMessage *message) {
return s_is_message_cancelled;
}
void app_outbox_service_cleanup_all_pending_messages(void) {
s_is_message_cancelled = true;
}
void comm_session_analytics_inc_bytes_sent(CommSession *session, uint16_t length) {
}
static CommSession s_system_session;
static CommSession *s_system_session_ptr;
CommSession *comm_session_get_system_session(void) {
return s_system_session_ptr;
}
static CommSession s_app_session;
static CommSession *s_app_session_ptr;
CommSession *comm_session_get_current_app_session(void) {
if (s_process_md.allow_js) {
return comm_session_get_system_session();
}
return s_app_session_ptr;
}
bool comm_session_is_valid(const CommSession *session) {
if (!session) {
return false;
}
return (session == comm_session_get_current_app_session() ||
session == comm_session_get_system_session());
}
static int s_send_next_count = 0;
void comm_session_send_next(CommSession *session) {
++s_send_next_count;
}
void comm_session_set_responsiveness(CommSession *session, BtConsumer consumer,
ResponseTimeState state, uint16_t max_period_secs) {
}
void comm_session_sanitize_app_session(CommSession **session_in_out) {
CommSession *permitted_session = comm_session_get_current_app_session();
*session_in_out = ((!*session_in_out) ||
(*session_in_out == permitted_session)) ? permitted_session : NULL;
}
// Helpers
////////////////////////////////////////////////////////////////////////////////////////////////////
static void prv_send_outbox_raw_data(const uint8_t *data, size_t length) {
AppOutboxMessage *outbox_message = kernel_zalloc(sizeof(AppOutboxMessage) + s_service_data_size);
cl_assert(outbox_message);
outbox_message->data = data;
outbox_message->length = length;
s_outbox_message_handler(outbox_message);
}
static AppMessageAppOutboxData *prv_create_and_send_outbox_message(CommSession *session,
uint16_t endpoint_id,
const uint8_t *payload,
size_t payload_length) {
const size_t outbox_data_size = sizeof(AppMessageAppOutboxData) + payload_length;
AppMessageAppOutboxData *outbox_data = app_malloc(outbox_data_size);
cl_assert(outbox_data);
outbox_data->session = session;
outbox_data->endpoint_id = endpoint_id;
memcpy(outbox_data->payload, payload, payload_length);
prv_send_outbox_raw_data((const uint8_t *)outbox_data, outbox_data_size);
return outbox_data;
}
static void prv_process_send_queue(CommSession *session) {
cl_assert(session);
size_t length = comm_session_send_queue_get_length(session);
if (length) {
comm_session_send_queue_consume(session, length);
}
}
#define assert_consumed(expected_last_status, expected_consumed_count) \
{ \
cl_assert_equal_i(expected_last_status, s_last_status_code); \
cl_assert_equal_i(expected_consumed_count, s_consumed_count); \
}
#define assert_not_consumed() \
cl_assert_equal_i(0, s_consumed_count);
// Tests
////////////////////////////////////////////////////////////////////////////////////////////////////
#define DISALLOWED_ENDPOINT_ID (9000) // GetBytes
#define ALLOWED_ENDPOINT_ID (APP_MESSAGE_ENDPOINT_ID)
static const uint8_t TEST_PAYLOAD[] = {0xaa, 0xbb, 0xcc, 0xdd};
static uint8_t TEST_EXPECTED_PP_MSG[sizeof(PebbleProtocolHeader) + sizeof(TEST_PAYLOAD)];
void test_app_message_sender__initialize(void) {
s_system_session_ptr = &s_system_session;
s_app_session_ptr = &s_app_session;
s_send_next_count = 0;
s_outbox_message_handler = NULL;
s_service_data_size = 0;
s_is_message_cancelled = false;
s_last_status_code = AppOutboxStatusUserRangeEnd;
s_consumed_count = 0;
s_app_install_timestamp_update_count = 0;
s_process_md.allow_js = false;
PebbleProtocolHeader *header = (PebbleProtocolHeader *)TEST_EXPECTED_PP_MSG;
header->length = htons(sizeof(TEST_PAYLOAD));
header->endpoint_id = htons(ALLOWED_ENDPOINT_ID);
memcpy(TEST_EXPECTED_PP_MSG + sizeof(*header), TEST_PAYLOAD, sizeof(TEST_PAYLOAD));
app_message_sender_init();
cl_assert(s_outbox_message_handler);
}
void test_app_message_sender__cleanup(void) {
// Flush out to avoid other tests failing:
if (s_system_session_ptr) {
prv_process_send_queue(s_system_session_ptr);
}
if (s_app_session_ptr) {
prv_process_send_queue(s_app_session_ptr);
}
}
// Tests that exercise the sanity checking of the input from the app
////////////////////////////////////////////////////////////////////////////////////////////////////
void test_app_message_sender__outbox_data_too_short(void) {
// This is one byte too small, because the PP payload has to be at least one in length:
AppMessageAppOutboxData data = {};
prv_send_outbox_raw_data((const uint8_t *)&data, sizeof(data));
assert_consumed(AppMessageSenderErrorDataTooShort, 1);
}
void test_app_message_sender__disallowed_endpoint(void) {
AppMessageAppOutboxData *outbox_data =
prv_create_and_send_outbox_message(s_system_session_ptr, DISALLOWED_ENDPOINT_ID,
TEST_PAYLOAD, sizeof(TEST_PAYLOAD));
assert_consumed(AppMessageSenderErrorEndpointDisallowed, 1);
app_free(outbox_data);
}
void test_app_message_sender__system_session_but_not_js_app(void) {
AppMessageAppOutboxData *outbox_data =
prv_create_and_send_outbox_message(s_system_session_ptr, ALLOWED_ENDPOINT_ID,
TEST_PAYLOAD, sizeof(TEST_PAYLOAD));
assert_consumed(AppMessageSenderErrorDisconnected, 1);
app_free(outbox_data);
}
void test_app_message_sender__app_session_but_js_app(void) {
s_process_md.allow_js = true;
AppMessageAppOutboxData *outbox_data =
prv_create_and_send_outbox_message(s_app_session_ptr, ALLOWED_ENDPOINT_ID,
TEST_PAYLOAD, sizeof(TEST_PAYLOAD));
assert_consumed(AppMessageSenderErrorDisconnected, 1);
app_free(outbox_data);
}
void test_app_message_sender__no_sessions_connected(void) {
s_system_session_ptr = NULL;
s_app_session_ptr = NULL;
AppMessageAppOutboxData *outbox_data =
prv_create_and_send_outbox_message(NULL /* auto-select */, ALLOWED_ENDPOINT_ID,
TEST_PAYLOAD, sizeof(TEST_PAYLOAD));
assert_consumed(AppMessageSenderErrorDisconnected, 1);
app_free(outbox_data);
}
void test_app_message_sender__auto_select_not_js_app(void) {
AppMessageAppOutboxData *outbox_data =
prv_create_and_send_outbox_message(NULL /* auto-select */, ALLOWED_ENDPOINT_ID,
TEST_PAYLOAD, sizeof(TEST_PAYLOAD));
prv_process_send_queue(s_system_session_ptr);
assert_not_consumed();
prv_process_send_queue(s_app_session_ptr);
assert_consumed(AppMessageSenderErrorSuccess, 1);
app_free(outbox_data);
}
void test_app_message_sender__auto_select_js_app(void) {
s_process_md.allow_js = true;
AppMessageAppOutboxData *outbox_data =
prv_create_and_send_outbox_message(NULL /* auto-select */, ALLOWED_ENDPOINT_ID,
TEST_PAYLOAD, sizeof(TEST_PAYLOAD));
prv_process_send_queue(s_app_session_ptr);
assert_not_consumed();
prv_process_send_queue(s_system_session_ptr);
assert_consumed(AppMessageSenderErrorSuccess, 1);
app_free(outbox_data);
}
void test_app_message_sender__system_session_and_js_app(void) {
s_process_md.allow_js = true;
AppMessageAppOutboxData *outbox_data =
prv_create_and_send_outbox_message(s_system_session_ptr, ALLOWED_ENDPOINT_ID,
TEST_PAYLOAD, sizeof(TEST_PAYLOAD));
assert_not_consumed();
prv_process_send_queue(s_system_session_ptr);
assert_consumed(AppMessageSenderErrorSuccess, 1);
cl_assert_equal_i(s_app_install_timestamp_update_count, 1);
app_free(outbox_data);
}
// Tests that exercise interface towards the Send Queue
////////////////////////////////////////////////////////////////////////////////////////////////////
void test_app_message_sender__freed_but_not_sent_entirely(void) {
AppMessageAppOutboxData *outbox_data =
prv_create_and_send_outbox_message(NULL /* auto-select */, ALLOWED_ENDPOINT_ID,
TEST_PAYLOAD, sizeof(TEST_PAYLOAD));
size_t length = comm_session_send_queue_get_length(s_app_session_ptr);
comm_session_send_queue_consume(s_app_session_ptr, length - 1);
comm_session_send_queue_cleanup(s_app_session_ptr);
assert_consumed(AppMessageSenderErrorDisconnected, 1);
cl_assert_equal_i(s_app_install_timestamp_update_count, 0);
app_free(outbox_data);
}
void test_app_message_sender__byte_by_byte_consume(void) {
AppMessageAppOutboxData *outbox_data =
prv_create_and_send_outbox_message(NULL /* auto-select */, ALLOWED_ENDPOINT_ID,
TEST_PAYLOAD, sizeof(TEST_PAYLOAD));
size_t length = comm_session_send_queue_get_length(s_app_session_ptr);
cl_assert_equal_i(length, sizeof(PebbleProtocolHeader) + sizeof(TEST_PAYLOAD));
for (int i = 0; i < length; ++i) {
// Test the `length` implementation:
cl_assert_equal_i(length - i, comm_session_send_queue_get_length(s_app_session_ptr));
// Test the `read_pointer` implementation:
const uint8_t *read_pointer = NULL;
size_t length_available = comm_session_send_queue_get_read_pointer(s_app_session_ptr,
&read_pointer);
cl_assert(read_pointer);
cl_assert_equal_i(TEST_EXPECTED_PP_MSG[i], *read_pointer);
// Expect that the header and payload will be non-contiguous:
if (i < sizeof(PebbleProtocolHeader)) {
cl_assert_equal_i(sizeof(PebbleProtocolHeader) - i, length_available);
} else {
cl_assert_equal_i(length - i, length_available);
}
// Test the `copy` implementation:
uint8_t byte_out = 0xff;
cl_assert_equal_i(1, comm_session_send_queue_copy(s_app_session_ptr, 0 /* offset */,
1 /* length */, &byte_out));
cl_assert_equal_i(TEST_EXPECTED_PP_MSG[i], byte_out);
comm_session_send_queue_consume(s_app_session_ptr, 1 /* length */);
}
assert_consumed(AppMessageSenderErrorSuccess, 1);
cl_assert_equal_i(s_app_install_timestamp_update_count, 1);
app_free(outbox_data);
}
void test_app_message_sender__byte_by_byte_copy_with_offset(void) {
AppMessageAppOutboxData *outbox_data =
prv_create_and_send_outbox_message(NULL /* auto-select */, ALLOWED_ENDPOINT_ID,
TEST_PAYLOAD, sizeof(TEST_PAYLOAD));
size_t length = comm_session_send_queue_get_length(s_app_session_ptr);
cl_assert_equal_i(length, sizeof(PebbleProtocolHeader) + sizeof(TEST_PAYLOAD));
uint8_t bytes_out[length];
memset(bytes_out, 0xff, length);
// Consume byte by byte:
for (int c = 0; c < length; ++c) {
// Shift offset byte by byte:
for (int o = 0; o < (length - c); ++o) {
size_t length_to_copy = (length - c - o);
cl_assert_equal_i(length_to_copy, comm_session_send_queue_copy(s_app_session_ptr, o,
length_to_copy, bytes_out));
cl_assert_equal_i(0, memcmp(bytes_out, TEST_EXPECTED_PP_MSG + o + c, length_to_copy));
}
comm_session_send_queue_consume(s_app_session_ptr, 1 /* length */);
}
assert_consumed(AppMessageSenderErrorSuccess, 1);
cl_assert_equal_i(s_app_install_timestamp_update_count, 1);
app_free(outbox_data);
}
// Tests that deal with the edge case of app outbox messages getting cancelled,
// because the app that provides the buffer for the payload is quit while they are in the
// process of being sent out.
////////////////////////////////////////////////////////////////////////////////////////////////////
static void prv_quit_app_after_pp_msg_byte(uint32_t num_bytes) {
AppMessageAppOutboxData *outbox_data =
prv_create_and_send_outbox_message(NULL /* auto-select */, ALLOWED_ENDPOINT_ID,
TEST_PAYLOAD, sizeof(TEST_PAYLOAD));
size_t length = comm_session_send_queue_get_length(s_app_session_ptr);
uint8_t bytes_out[length];
memset(bytes_out, 0xff, length);
// Copy & consume one byte of the header -- note the header is 4 bytes total:
size_t first_length = num_bytes;
cl_assert_equal_i(first_length, comm_session_send_queue_copy(s_app_session_ptr, 0,
first_length, bytes_out));
comm_session_send_queue_consume(s_app_session_ptr, first_length);
// App quits with only one header byte consumed:
app_outbox_service_cleanup_all_pending_messages();
// Copy & consume the rest:
size_t second_length = (length - first_length);
cl_assert_equal_i(second_length, comm_session_send_queue_copy(s_app_session_ptr, 0,
second_length,
bytes_out + first_length));
comm_session_send_queue_consume(s_app_session_ptr, second_length);
// The message should be consumed now (to free the resources associated with it):
assert_consumed(AppMessageSenderErrorSuccess, 1);
// Expect at least the PebbleProtocol header or more to be intact:
size_t intact_size = MAX(num_bytes, sizeof(PebbleProtocolHeader));
cl_assert_equal_m(bytes_out, TEST_EXPECTED_PP_MSG, intact_size);
// Expect the remainder to be filled with zeroes:
for (int i = 0; i < (length - intact_size); ++i) {
cl_assert_equal_i(bytes_out[intact_size + i], 0x00);
}
app_free(outbox_data);
}
void test_app_message_sender__cancelled_message_in_flight_header_and_payload_not_finished(void) {
// Expect header to get sent out normally, then a payload with all zeroes
prv_quit_app_after_pp_msg_byte(sizeof(PebbleProtocolHeader) - 1);
}
void test_app_message_sender__cancelled_message_in_flight_payload_not_finished(void) {
// Expect remainder payload to be all zeroes
prv_quit_app_after_pp_msg_byte(sizeof(PebbleProtocolHeader) + 1);
}