mirror of
https://github.com/google/pebble.git
synced 2025-03-23 12:12:19 +00:00
427 lines
16 KiB
C
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);
|
|
}
|