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

356 lines
13 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 "clar.h"
#include "applib/app_inbox.h"
#include "applib/app_message/app_message_internal.h"
#include "applib/app_message/app_message_receiver.h"
#include "comm/bt_conn_mgr.h"
#include "services/common/comm_session/session_receive_router.h"
#include "kernel/events.h"
#include "process_management/app_install_types.h"
extern const ReceiverImplementation g_app_message_receiver_implementation;
static const ReceiverImplementation *s_rcv_imp = &g_app_message_receiver_implementation;
#define MAX_HEADER_SIZE (sizeof(AppMessageHeader))
#define BUFFER_SIZE (sizeof(AppMessagePush))
////////////////////////////////////////////////////////////////////////////////////////////////////
// Fakes & Stubs
#include "fake_kernel_malloc.h"
#include "fake_system_task.h"
#include "stubs_analytics.h"
#include "stubs_logging.h"
#include "stubs_mutex.h"
#include "stubs_passert.h"
#include "stubs_syscall_internal.h"
bool process_manager_send_event_to_process(PebbleTask task, PebbleEvent* e) {
cl_assert_equal_i(PEBBLE_CALLBACK_EVENT, e->type);
// Use fake_system_task as mock implementation:
system_task_add_callback(e->callback.callback, e->callback.data);
return true;
}
static void prv_process_events(void) {
fake_system_task_callbacks_invoke_pending();
}
static AppInbox *s_app_message_inbox;
AppInbox **app_state_get_app_message_inbox(void) {
return &s_app_message_inbox;
}
static bool s_communication_timestamp_updated;
void app_install_mark_prioritized(AppInstallId install_id, bool can_expire) {
s_communication_timestamp_updated = true;
}
AppInstallId app_manager_get_current_app_id(void) {
return INSTALL_ID_INVALID;
}
static uint8_t s_app_message_pp_buffer[BUFFER_SIZE];
static size_t s_app_message_pp_received_length;
void app_message_app_protocol_msg_callback(CommSession *session,
const uint8_t* data, size_t length,
AppInboxConsumerInfo *consumer_info) {
cl_assert(length <= BUFFER_SIZE);
memcpy(s_app_message_pp_buffer, data, length);
s_app_message_pp_received_length = length;
}
static void prv_protocol_msg_callback(CommSession *session,
const uint8_t* data, size_t length) {
app_message_app_protocol_msg_callback(session, data, length, NULL);
}
void app_message_inbox_handle_dropped_messages(uint32_t num_drops) {
}
void comm_session_set_responsiveness(CommSession *session, BtConsumer consumer,
ResponseTimeState state, uint16_t max_period_secs) {
}
static bool s_kernel_receiver_available;
static Receiver *s_kernel_receiver;
static bool s_kernel_receiver_is_receiving;
static uint8_t s_kernel_receiver_buffer[MAX_HEADER_SIZE];
static off_t s_kernel_receiver_buffer_idx;
static bool s_kernel_receiver_finish_called;
static bool s_kernel_receiver_cleanup_called;
static Receiver *prv_default_kernel_receiver_prepare(CommSession *session,
const PebbleProtocolEndpoint *endpoint,
size_t total_payload_size) {
if (!s_kernel_receiver_available) {
return NULL;
}
s_kernel_receiver_is_receiving = true;
cl_assert_equal_p(endpoint->handler, app_message_app_protocol_system_nack_callback);
cl_assert(total_payload_size <= MAX_HEADER_SIZE);
return s_kernel_receiver;
}
static void prv_default_kernel_receiver_write(Receiver *receiver,
const uint8_t *data, size_t length) {
cl_assert_equal_p(receiver, s_kernel_receiver);
memcpy(s_kernel_receiver_buffer + s_kernel_receiver_buffer_idx, data, length);
s_kernel_receiver_buffer_idx += length;
cl_assert(s_kernel_receiver_buffer_idx <= MAX_HEADER_SIZE);
}
static void prv_default_kernel_receiver_finish(Receiver *receiver) {
cl_assert_equal_p(receiver, s_kernel_receiver);
s_kernel_receiver_is_receiving = false;
s_kernel_receiver_finish_called = true;
}
static void prv_default_kernel_receiver_cleanup(Receiver *receiver) {
cl_assert_equal_p(receiver, s_kernel_receiver);
s_kernel_receiver_is_receiving = false;
s_kernel_receiver_cleanup_called = true;
}
const ReceiverImplementation g_default_kernel_receiver_implementation = {
.prepare = prv_default_kernel_receiver_prepare,
.write = prv_default_kernel_receiver_write,
.finish = prv_default_kernel_receiver_finish,
.cleanup = prv_default_kernel_receiver_cleanup,
};
void app_message_app_protocol_system_nack_callback(CommSession *session,
const uint8_t* data, size_t length) {
}
void test_dropped_handler(uint32_t num_dropped_messages) {}
void test_message_handler(const uint8_t *data, size_t length,
AppInboxConsumerInfo *consumer_info) {}
void test_alt_message_handler(const uint8_t *data, size_t length,
AppInboxConsumerInfo *consumer_info) {}
void test_alt_dropped_handler(uint32_t num_dropped_messages) {}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Tests
static CommSession *s_session = (CommSession *)0xaabbccdd;
void test_app_message_receiver__initialize(void) {
s_kernel_receiver_available = true;
s_kernel_receiver = (Receiver *)0xffaaffaa;
s_kernel_receiver_is_receiving = false;
s_kernel_receiver_finish_called = false;
s_kernel_receiver_cleanup_called = false;
s_kernel_receiver_buffer_idx = 0;
memset(s_kernel_receiver_buffer, 0, sizeof(s_kernel_receiver_buffer));
s_app_message_pp_received_length = 0;
memset(s_app_message_pp_buffer, 0, sizeof(s_app_message_pp_buffer));
fake_kernel_malloc_init();
fake_kernel_malloc_enable_stats(true);
s_communication_timestamp_updated = false;
}
void test_app_message_receiver__cleanup(void) {
fake_system_task_callbacks_cleanup();
fake_kernel_malloc_deinit();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Forwarding to default system receiver to nack the message
static const AppMessagePush s_push = {
.header = {
.command = CMD_PUSH,
.transaction_id = 0xa5,
},
};
static const PebbleProtocolEndpoint s_app_message_endpoint = (const PebbleProtocolEndpoint) {
.endpoint_id = APP_MESSAGE_ENDPOINT_ID,
.handler = prv_protocol_msg_callback,
.access_mask = PebbleProtocolAccessAny,
.receiver_imp = &g_app_message_receiver_implementation,
.receiver_opt = NULL,
};
void test_app_message_receiver__receive_push_but_inbox_not_opened(void) {
Receiver *r = s_rcv_imp->prepare(s_session, &s_app_message_endpoint,
sizeof(AppMessagePush));
cl_assert(r != NULL);
cl_assert_equal_b(true, s_kernel_receiver_is_receiving);
s_rcv_imp->write(r, (const uint8_t *)&s_push, sizeof(s_push));
// Expect only up to MAX_HEADER_SIZE bytes has been written:
cl_assert_equal_i(s_kernel_receiver_buffer_idx, MAX_HEADER_SIZE);
// Check that the header is received correctly:
cl_assert_equal_m(s_kernel_receiver_buffer, &s_push, MAX_HEADER_SIZE);
s_rcv_imp->finish(r);
prv_process_events();
cl_assert_equal_b(false, s_kernel_receiver_is_receiving);
cl_assert_equal_b(true, s_kernel_receiver_finish_called);
}
void test_app_message_receiver__receive_push_but_inbox_not_opened_then_cleanup(void) {
Receiver *r = s_rcv_imp->prepare(s_session, &s_app_message_endpoint,
sizeof(AppMessagePush));
s_rcv_imp->write(r, (const uint8_t *)&s_push, sizeof(s_push));
s_rcv_imp->cleanup(r);
cl_assert_equal_b(false, s_kernel_receiver_is_receiving);
cl_assert_equal_b(true, s_kernel_receiver_cleanup_called);
}
void test_app_message_receiver__receive_push_but_inbox_not_opened_kernel_oom(void) {
fake_kernel_malloc_set_largest_free_block(0);
Receiver *r = s_rcv_imp->prepare(s_session, &s_app_message_endpoint,
sizeof(AppMessagePush));
cl_assert_equal_p(r, NULL);
cl_assert_equal_b(false, s_kernel_receiver_is_receiving);
}
void test_app_message_receiver__receive_push_but_inbox_not_opened_no_kernel_receiver(void) {
fake_kernel_malloc_mark();
s_kernel_receiver_available = false;
Receiver *r = s_rcv_imp->prepare(s_session, &s_app_message_endpoint,
sizeof(AppMessagePush));
cl_assert_equal_p(r, NULL);
cl_assert_equal_b(false, s_kernel_receiver_is_receiving);
fake_kernel_malloc_mark_assert_equal();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Normal flow: writing message to app message inbox
static Receiver *prv_create_inbox_prepare_and_write(void) {
cl_assert_equal_b(true, app_message_receiver_open(sizeof(AppMessagePush)));
Receiver *r = s_rcv_imp->prepare(s_session, &s_app_message_endpoint,
sizeof(AppMessagePush));
cl_assert(r != NULL);
s_rcv_imp->write(r, (const uint8_t *)&s_push, sizeof(s_push));
return r;
}
static void prv_destroy_inbox(void) {
app_message_receiver_close();
}
void test_app_message_receiver__receive_push(void) {
Receiver *r = prv_create_inbox_prepare_and_write();
s_rcv_imp->finish(r);
prv_process_events();
cl_assert_equal_b(false, s_kernel_receiver_is_receiving);
cl_assert_equal_b(false, s_kernel_receiver_finish_called);
cl_assert_equal_m(&s_push, s_app_message_pp_buffer, sizeof(s_push));
cl_assert_equal_i(s_app_message_pp_received_length, sizeof(s_push));
cl_assert_equal_b(true, s_communication_timestamp_updated);
prv_destroy_inbox();
}
void test_app_message_receiver__receive_push_then_cleanup(void) {
Receiver *r = prv_create_inbox_prepare_and_write();
s_rcv_imp->cleanup(r);
cl_assert_equal_b(false, s_kernel_receiver_is_receiving);
cl_assert_equal_b(false, s_kernel_receiver_finish_called);
cl_assert_equal_i(s_app_message_pp_received_length, 0);
cl_assert_equal_b(true, s_communication_timestamp_updated);
prv_destroy_inbox();
}
void test_app_message_receiver__receive_push_buffer_overflow(void) {
cl_assert_equal_b(true, app_message_receiver_open(sizeof(AppMessagePush)));
// Write an ACK, we should be able to fit one (N)ACK in addition to the Push message:
AppMessageAck ack = {};
Receiver *r = s_rcv_imp->prepare(s_session, &s_app_message_endpoint,
sizeof(ack));
cl_assert(r != NULL);
s_rcv_imp->write(r, (const uint8_t *)&ack, sizeof(ack));
s_rcv_imp->finish(r);
cl_assert_equal_b(false, s_kernel_receiver_finish_called);
cl_assert_equal_b(true, s_kernel_receiver_cleanup_called);
s_app_message_pp_received_length = 0;
s_kernel_receiver_buffer_idx = 0;
// Write a Push:
r = s_rcv_imp->prepare(s_session, &s_app_message_endpoint,
sizeof(AppMessagePush));
cl_assert(r != NULL);
s_rcv_imp->write(r, (const uint8_t *)&s_push, sizeof(s_push));
// Write some more, doesn't fit in the buffer:
s_rcv_imp->write(r, (const uint8_t *)&s_push, sizeof(s_push));
s_rcv_imp->finish(r);
prv_process_events();
// Header fwd to default system receiver should have finished, so it can be nacked:
cl_assert_equal_b(true, s_kernel_receiver_finish_called);
// Only Ack is received:
cl_assert_equal_i(s_app_message_pp_received_length, sizeof(AppMessageAck));
prv_destroy_inbox();
}
// Covers race as described here: PBL-41464
// 1. A part of a big app message is being received, causing it to get received in chunks.
// It's not received completely yet.
// 2. app_message_outbox_closed is called.
// 3. app_message_outbox_open is called, resetting the receiver state.
// 4. Next chunk comes in. We used to assert: the "writer" (receiver state) was not NULL.
// Fix: just eat the message and fail the app message by letting the KernelReceiver NACK it.
void test_app_message_receiver__receive_multi_chunk_push_while_open_close_toggle(void) {
cl_assert_equal_b(true, app_message_receiver_open(sizeof(AppMessagePush)));
Receiver *r = s_rcv_imp->prepare(s_session, &s_app_message_endpoint,
sizeof(AppMessagePush));
cl_assert(r != NULL);
// Receive only first byte of the push message:
s_rcv_imp->write(r, (const uint8_t *)&s_push, 1);
// Close app message and open again:
app_message_receiver_close();
cl_assert_equal_b(true, app_message_receiver_open(sizeof(AppMessagePush)));
// Receive the remainder of the push message:
s_rcv_imp->write(r, ((const uint8_t *)&s_push) + 1, sizeof(s_push) - 1);
s_rcv_imp->finish(r);
// Header fwd to default system receiver should have finished, so it can be nacked:
prv_process_events();
cl_assert_equal_b(false, s_kernel_receiver_is_receiving);
cl_assert_equal_b(true, s_kernel_receiver_finish_called);
prv_destroy_inbox();
}