pebble/src/fw/services/normal/app_inbox_service.c
Josh Soref e3c66a23c7 spelling: dereference
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2025-01-28 21:32:35 -05:00

617 lines
20 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 "app_inbox_service.h"
#include "kernel/pbl_malloc.h"
#include "kernel/pebble_tasks.h"
#include "process_management/process_manager.h"
#include "os/mutex.h"
#include "syscall/syscall_internal.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/buffer.h"
#include "util/list.h"
typedef struct AppInboxNode {
ListNode node;
AppInboxServiceTag tag;
AppInboxMessageHandler message_handler;
AppInboxDroppedHandler dropped_handler;
PebbleTask event_handler_task;
//! Indicates whether there is a writer.
//! The writer can set it to anything they want, mostly for debugging purposes.
void *writer;
bool write_failed;
bool has_pending_event;
uint32_t num_failed;
uint32_t num_success;
struct {
//! The size of `storage`.
size_t size;
//! The positive offset relative relative to write_index, up until which the current
//! (incomplete) message has been written.
size_t current_offset;
//! Index after which the current message should get written.
//! If this index is non-zero, there are completed message(s) in the buffer.
size_t write_index;
///! Pointer to the beginning of the storage.
uint8_t *storage;
} buffer;
} AppInboxNode;
typedef struct AppInboxConsumerInfo {
AppInboxServiceTag tag;
AppInboxMessageHandler message_handler;
AppInboxDroppedHandler dropped_handler;
uint32_t num_failed;
uint32_t num_success;
uint8_t *it;
uint8_t *end;
} AppInboxConsumerInfo;
_Static_assert(sizeof(AppInboxServiceTag) <= sizeof(void *),
"AppInboxServiceTag should fit inside a void *");
static AppInboxNode *s_app_inbox_head;
static PebbleRecursiveMutex *s_app_inbox_mutex;
////////////////////////////////////////////////////////////////////////////////////////////////////
// Declarations of permitted handlers:
extern void app_message_receiver_message_handler(const uint8_t *data, size_t length,
AppInboxConsumerInfo *consumer_info);
extern void app_message_receiver_dropped_handler(uint32_t num_dropped_messages);
#ifdef UNITTEST
extern void test_message_handler(const uint8_t *data, size_t length,
AppInboxConsumerInfo *consumer_info);
extern void test_dropped_handler(uint32_t num_dropped_messages);
extern void test_alt_message_handler(const uint8_t *data, size_t length,
AppInboxConsumerInfo *consumer_info);
extern void test_alt_dropped_handler(uint32_t num_dropped_messages);
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////
// Syscalls
static AppInboxServiceTag prv_tag_for_event_handlers(const AppInboxMessageHandler message_handler,
const AppInboxDroppedHandler dropped_handler) {
static const struct {
AppInboxMessageHandler message_handler;
AppInboxDroppedHandler dropped_handler;
} s_event_handler_map[] = {
[AppInboxServiceTagAppMessageReceiver] = {
.message_handler = app_message_receiver_message_handler,
.dropped_handler = app_message_receiver_dropped_handler,
},
#ifdef UNITTEST
[AppInboxServiceTagUnitTest] = {
.message_handler = test_message_handler,
.dropped_handler = test_dropped_handler,
},
[AppInboxServiceTagUnitTestAlt] = {
.message_handler = test_alt_message_handler,
.dropped_handler = test_alt_dropped_handler,
}
#endif
};
for (AppInboxServiceTag tag = 0; tag < NumAppInboxServiceTag; ++tag) {
if (s_event_handler_map[tag].message_handler == message_handler &&
s_event_handler_map[tag].dropped_handler == dropped_handler) {
return tag;
}
}
return AppInboxServiceTagInvalid;
}
DEFINE_SYSCALL(bool, sys_app_inbox_service_register, uint8_t *storage, size_t storage_size,
AppInboxMessageHandler message_handler, AppInboxDroppedHandler dropped_handler) {
if (PRIVILEGE_WAS_ELEVATED) {
syscall_assert_userspace_buffer(storage, storage_size);
}
const AppInboxServiceTag service_tag = prv_tag_for_event_handlers(message_handler,
dropped_handler);
if (AppInboxServiceTagInvalid == service_tag) {
PBL_LOG(LOG_LEVEL_ERROR, "AppInbox event handlers not allowed <0x%"PRIx32", 0x%"PRIx32">",
// Ugh.. no more format signature slots free for %p %p...
(uint32_t)(uintptr_t)message_handler, (uint32_t)(uintptr_t)dropped_handler);
syscall_failed();
}
return app_inbox_service_register(storage, storage_size,
message_handler, dropped_handler, service_tag);
}
DEFINE_SYSCALL(uint32_t, sys_app_inbox_service_unregister, uint8_t *storage) {
// No check is needed on the value of `storage `, we're not going to dereference it.
return app_inbox_service_unregister_by_storage(storage);
}
static bool prv_get_consumer_info(AppInboxServiceTag tag, AppInboxConsumerInfo *info_in_out);
DEFINE_SYSCALL(bool, sys_app_inbox_service_get_consumer_info,
AppInboxServiceTag tag, AppInboxConsumerInfo *info_out) {
if (PRIVILEGE_WAS_ELEVATED) {
if (info_out) {
syscall_assert_userspace_buffer(info_out, sizeof(*info_out));
}
}
return prv_get_consumer_info(tag, info_out);
}
static void prv_consume(AppInboxConsumerInfo *consumer_info);
DEFINE_SYSCALL(void, sys_app_inbox_service_consume, AppInboxConsumerInfo *consumer_info) {
if (PRIVILEGE_WAS_ELEVATED) {
syscall_assert_userspace_buffer(consumer_info, sizeof(*consumer_info));
}
prv_consume(consumer_info);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
static void prv_lock(void) {
// Using one "global" lock for all app inboxes.
// If needed, we could easily give each app inbox its own mutex, but it seems overkill right now.
mutex_lock_recursive(s_app_inbox_mutex);
}
static void prv_unlock(void) {
mutex_unlock_recursive(s_app_inbox_mutex);
}
static bool prv_list_filter_by_storage(ListNode *found_node, void *data) {
return ((AppInboxNode *)found_node)->buffer.storage == (uint8_t *)data;
}
static AppInboxNode *prv_find_inbox_by_storage(uint8_t *storage) {
return (AppInboxNode *) list_find((ListNode *)s_app_inbox_head,
prv_list_filter_by_storage, storage);
}
static bool prv_list_filter_by_tag(ListNode *found_node, void *data) {
return ((AppInboxNode *)found_node)->tag == (AppInboxServiceTag)(uintptr_t)data;
}
static AppInboxNode *prv_find_inbox_by_tag(AppInboxServiceTag tag) {
return (AppInboxNode *) list_find((ListNode *)s_app_inbox_head,
prv_list_filter_by_tag, (void *)(uintptr_t)tag);
}
static AppInboxNode *prv_find_inbox_by_tag_and_log_if_not_found(AppInboxServiceTag tag) {
AppInboxNode *inbox = prv_find_inbox_by_tag(tag);
if (!inbox) {
PBL_LOG(LOG_LEVEL_ERROR, "No AppInbox for tag <%d>", tag);
}
return inbox;
}
//! We don't report "number of messages consumed", because that would force the system to parse
//! the contents of the (app space) buffer, which might have been corrupted by the app.
//! Note that it's in theory possible for a misbehaving app to pass in a consumed_up_to_ptr that is
//! mid-way in a message. If it does so, it won't crash the kernel, but it will result in delivery
//! of broken messages to the app, but it won't be our fault...
static void prv_consume(AppInboxConsumerInfo *consumer_info) {
prv_lock();
{
AppInboxNode *inbox = prv_find_inbox_by_tag_and_log_if_not_found(consumer_info->tag);
if (!inbox) {
goto unlock;
}
uint8_t *const consumed_up_to_ptr = consumer_info->it;
uint8_t * const completed_messages_end = (inbox->buffer.storage + inbox->buffer.write_index);
if (consumed_up_to_ptr < inbox->buffer.storage ||
consumed_up_to_ptr > completed_messages_end) {
PBL_LOG(LOG_LEVEL_ERROR, "Out of bounds");
goto unlock;
}
const size_t bytes_consumed = (consumed_up_to_ptr - inbox->buffer.storage);
if (0 == bytes_consumed) {
goto unlock;
}
uint8_t * const partial_message_end = completed_messages_end + inbox->buffer.current_offset;
const size_t remaining_size = partial_message_end - consumed_up_to_ptr;
consumer_info->it = inbox->buffer.storage;
consumer_info->end = inbox->buffer.storage + remaining_size;
if (remaining_size) {
// New data has been written in the mean-time, move it all to the front of the buffer:
memmove(inbox->buffer.storage, consumed_up_to_ptr, remaining_size);
}
inbox->buffer.write_index -= bytes_consumed;
}
unlock:
prv_unlock();
}
static bool prv_get_consumer_info(AppInboxServiceTag tag, AppInboxConsumerInfo *info_out) {
if (!info_out) {
return false;
}
bool success = false;
prv_lock();
{
AppInboxNode *inbox = prv_find_inbox_by_tag_and_log_if_not_found(tag);
if (!inbox) {
goto unlock;
}
*info_out = (const AppInboxConsumerInfo) {
.tag = tag,
.message_handler = inbox->message_handler,
.dropped_handler = inbox->dropped_handler,
.num_failed = inbox->num_failed,
.num_success = inbox->num_success,
.it = inbox->buffer.storage,
.end = inbox->buffer.storage + inbox->buffer.write_index,
};
// Also mark that there is no event pending any more:
inbox->has_pending_event = false;
// Reset counters because the info is communicated to app and it's about to consume the data.
inbox->num_failed = 0;
inbox->num_success = 0;
success = true;
}
unlock:
prv_unlock();
return success;
}
//! @note Executes on app task, therefore we need to go through syscalls to access AppInbox!
static void prv_callback_event_handler(void *ctx) {
AppInboxServiceTag tag = (AppInboxServiceTag)(uintptr_t)ctx;
AppInboxConsumerInfo info = {};
size_t num_message_consumed = 0;
if (!sys_app_inbox_service_get_consumer_info(tag, &info)) {
// Inbox wasn't there any more
return;
}
if (!info.message_handler) {
// Shouldn't ever happen, but better not PBL_ASSERTN on app task
PBL_LOG(LOG_LEVEL_ERROR, "No AppInbox message handler!");
return;
}
if (!info.num_success && !info.num_failed) {
// Shouldn't ever happen, but better not PBL_ASSERTN on app task
PBL_LOG(LOG_LEVEL_ERROR, "Got callback, but zero messages!?");
// fall-through
}
// These conditions are redundant, just for safety:
while ((num_message_consumed < info.num_success) && (info.it < info.end)) {
AppInboxMessageHeader *msg = (AppInboxMessageHeader *)info.it;
// Increment now so that if the message_handler calls into sys_app_inbox_service_consume(),
// it will be pointing *after* the message that is just handled:
info.it += (sizeof(AppInboxMessageHeader) + msg->length);
// Check for safety, just in case the app has corrupted the buffer in the mean time:
if (msg->data + msg->length <= info.end) {
info.message_handler(msg->data, msg->length, &info);
} else {
PBL_LOG(LOG_LEVEL_ERROR, "Corrupted AppInbox message!");
}
++num_message_consumed;
}
if (info.num_failed) {
if (info.dropped_handler) {
info.dropped_handler(info.num_failed);
} else {
PBL_LOG(LOG_LEVEL_ERROR, "Dropped %"PRIu32" messages but no dropped_handler",
info.num_failed);
}
}
// Report back up to which byte we've consumed the data.
sys_app_inbox_service_consume(&info);
}
bool app_inbox_service_register(uint8_t *storage, size_t storage_size,
AppInboxMessageHandler message_handler,
AppInboxDroppedHandler dropped_handler, AppInboxServiceTag tag) {
AppInboxNode *new_node = (AppInboxNode *)kernel_zalloc(sizeof(AppInboxNode));
if (!new_node) {
PBL_LOG(LOG_LEVEL_ERROR, "Not enough memory to allocate AppInboxNode");
return false;
}
prv_lock();
{
bool has_error = false;
if (prv_find_inbox_by_storage(storage)) {
PBL_LOG(LOG_LEVEL_ERROR, "AppInbox already registered for storage <%p>", storage);
has_error = true;
}
// This check effectively caps the kernel RAM impact of this service,
// so it's not possible to abuse the syscall and cause kernel OOM.
if (prv_find_inbox_by_tag(tag)) {
PBL_LOG(LOG_LEVEL_ERROR, "AppInbox already registered for tag <%d>", tag);
has_error = true;
}
if (has_error) {
kernel_free(new_node);
new_node = NULL;
} else {
new_node->tag = tag;
new_node->message_handler = message_handler;
new_node->dropped_handler = dropped_handler;
new_node->event_handler_task = pebble_task_get_current();
new_node->buffer.storage = storage;
new_node->buffer.size = storage_size;
s_app_inbox_head = (AppInboxNode *)list_prepend((ListNode *)s_app_inbox_head,
(ListNode *)new_node);
}
}
prv_unlock();
return (new_node != NULL);
}
uint32_t app_inbox_service_unregister_by_storage(uint8_t *storage) {
uint32_t num_messages_lost = 0;
prv_lock();
{
AppInboxNode *node = prv_find_inbox_by_storage(storage);
if (node) {
list_remove((ListNode *)node, (ListNode **)&s_app_inbox_head, NULL);
num_messages_lost = node->num_failed + node->num_success + (node->writer ? 1 : 0);
kernel_free(node);
}
}
prv_unlock();
return num_messages_lost;
}
void app_inbox_service_unregister_all(void) {
prv_lock();
{
AppInboxNode *node = s_app_inbox_head;
while (node) {
AppInboxNode *next = (AppInboxNode *) node->node.next;
kernel_free(node);
node = next;
}
s_app_inbox_head = NULL;
}
prv_unlock();
}
static bool prv_is_inbox_being_written(AppInboxNode *inbox) {
return (inbox->writer != NULL);
}
static size_t prv_get_space_remaining(AppInboxNode *inbox) {
return (inbox->buffer.size - inbox->buffer.write_index - inbox->buffer.current_offset);
}
bool prv_check_space_remaining(AppInboxNode *inbox, size_t required_free_length) {
const size_t space_remaining = prv_get_space_remaining(inbox);
if (required_free_length > space_remaining) {
PBL_LOG(LOG_LEVEL_ERROR, "Dropping data, not enough space %"PRIu32" vs %"PRIu32,
(uint32_t)required_free_length, (uint32_t)space_remaining);
return false;
}
return true;
}
static void prv_send_event_if_needed(AppInboxNode *inbox) {
if (!inbox || inbox->has_pending_event) {
return;
}
PebbleEvent event = {
.type = PEBBLE_CALLBACK_EVENT,
.callback = {
.callback = prv_callback_event_handler,
.data = (void *)(uintptr_t) inbox->tag,
},
};
const bool is_event_enqueued = process_manager_send_event_to_process(inbox->event_handler_task,
&event);
if (!is_event_enqueued) {
PBL_LOG(LOG_LEVEL_ERROR, "Event queue full");
}
inbox->has_pending_event = is_event_enqueued;
}
static void prv_mark_failed_if_no_writer(AppInboxNode *inbox) {
if (!inbox->writer) {
// See PBL-41464
// App message has been reset (closed and opened again) while a message was being received.
// Fail it because our state got lost.
inbox->write_failed = true;
}
}
bool app_inbox_service_begin(AppInboxServiceTag tag, size_t required_free_length, void *writer) {
if (!writer) {
return false;
}
bool success = false;
prv_lock();
{
AppInboxNode *inbox = prv_find_inbox_by_tag_and_log_if_not_found(tag);
if (!inbox) {
goto unlock;
}
if (prv_is_inbox_being_written(inbox)) {
++inbox->num_failed;
PBL_LOG(LOG_LEVEL_ERROR, "Dropping data, already written by <%p>", inbox->writer);
// Don't send event here, when the current write finishes, the drop(s) will be reported too.
goto unlock;
}
if (!prv_check_space_remaining(inbox, required_free_length + sizeof(AppInboxMessageHeader))) {
++inbox->num_failed;
// If it doesn't fit, send event immediately, we don't know when the next write will happen.
prv_send_event_if_needed(inbox);
goto unlock;
}
inbox->writer = writer;
inbox->write_failed = false;
// Leave space at the beginning for the header, which we'll write in the end
inbox->buffer.current_offset = sizeof(AppInboxMessageHeader);
success = true;
}
unlock:
prv_unlock();
return success;
}
bool app_inbox_service_write(AppInboxServiceTag tag, const uint8_t *data, size_t length) {
bool success = false;
prv_lock();
{
AppInboxNode *inbox = prv_find_inbox_by_tag_and_log_if_not_found(tag);
if (!inbox) {
goto unlock;
}
prv_mark_failed_if_no_writer(inbox);
if (inbox->write_failed) {
goto unlock;
}
if (!prv_check_space_remaining(inbox, length)) {
inbox->write_failed = true;
goto unlock;
}
memcpy(inbox->buffer.storage + inbox->buffer.write_index + inbox->buffer.current_offset,
data, length);
inbox->buffer.current_offset += length;
success = true;
}
unlock:
prv_unlock();
return success;
}
static void prv_finish(AppInboxNode *inbox) {
inbox->writer = NULL;
inbox->buffer.current_offset = 0;
}
void app_inbox_service_init(void) {
s_app_inbox_mutex = mutex_create_recursive();
}
bool app_inbox_service_end(AppInboxServiceTag tag) {
bool success = false;
prv_lock();
{
AppInboxNode *inbox = prv_find_inbox_by_tag_and_log_if_not_found(tag);
if (!inbox) {
goto unlock;
}
prv_mark_failed_if_no_writer(inbox);
if (inbox->write_failed) {
++inbox->num_failed;
} else {
const AppInboxMessageHeader header = (const AppInboxMessageHeader) {
.length = inbox->buffer.current_offset - sizeof(AppInboxMessageHeader),
// Fill with something that might aid debugging one day:
.padding = { 0xaa, 0xaa, 0xaa, 0xaa },
};
memcpy(inbox->buffer.storage + inbox->buffer.write_index, &header, sizeof(header));
inbox->buffer.write_index += inbox->buffer.current_offset;
++inbox->num_success;
success = true;
}
prv_finish(inbox);
prv_send_event_if_needed(inbox);
}
unlock:
prv_unlock();
return success;
}
void app_inbox_service_cancel(AppInboxServiceTag tag) {
prv_lock();
{
AppInboxNode *inbox = prv_find_inbox_by_tag_and_log_if_not_found(tag);
if (!inbox) {
goto unlock;
}
prv_finish(inbox);
}
unlock:
prv_unlock();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Unit Test Interfaces
bool app_inbox_service_has_inbox_for_tag(AppInboxServiceTag tag) {
bool has_inbox;
prv_lock();
has_inbox = (prv_find_inbox_by_tag(tag) != NULL);
prv_unlock();
return has_inbox;
}
bool app_inbox_service_has_inbox_for_storage(uint8_t *storage) {
bool has_inbox;
prv_lock();
has_inbox = (prv_find_inbox_by_storage(storage) != NULL);
prv_unlock();
return has_inbox;
}
bool app_inbox_service_is_being_written_for_tag(AppInboxServiceTag tag) {
bool is_written = false;
prv_lock();
AppInboxNode *inbox = prv_find_inbox_by_tag(tag);
if (inbox) {
is_written = (inbox->writer != NULL);
}
prv_unlock();
return is_written;
}
uint32_t app_inbox_service_num_failed_for_tag(AppInboxServiceTag tag) {
uint32_t num_failed = 0;
prv_lock();
AppInboxNode *inbox = prv_find_inbox_by_tag(tag);
if (inbox) {
num_failed = inbox->num_failed;
}
prv_unlock();
return num_failed;
}
uint32_t app_inbox_service_num_success_for_tag(AppInboxServiceTag tag) {
uint32_t num_success = 0;
prv_lock();
AppInboxNode *inbox = prv_find_inbox_by_tag(tag);
if (inbox) {
num_success = inbox->num_success;
}
prv_unlock();
return num_success;
}