/* * 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 "kernel/pbl_malloc.h" #include "os/mutex.h" #include "process_management/process_manager.h" #include "services/normal/app_message/app_message_sender.h" #include "services/normal/app_outbox_service.h" #include "syscall/syscall.h" #include "syscall/syscall_internal.h" #include "system/logging.h" #include "system/passert.h" #include "util/list.h" static PebbleRecursiveMutex *s_app_outbox_mutex; typedef struct { AppOutboxMessage *head; AppOutboxMessageHandler message_handler; size_t consumer_data_length; PebbleTask consumer_task; } AppOutboxConsumer; //! Array with the consuming kernel services that have been registered at run-time: static AppOutboxConsumer s_app_outbox_consumer[NumAppOutboxServiceTag]; //////////////////////////////////////////////////////////////////////////////////////////////////// // Declarations of permitted senders: typedef struct { AppOutboxSentHandler sent_handler; size_t max_length; uint32_t max_pending_messages; } AppOutboxSenderDef; extern void app_message_outbox_handle_app_outbox_message_sent(AppOutboxStatus status, void *cb_ctx); #ifdef UNITTEST extern void test_app_outbox_sent_handler(AppOutboxStatus status, void *cb_ctx); #endif //! Constant array defining the allowed handlers and their restrictions: static const AppOutboxSenderDef s_app_outbox_sender_defs[] = { [AppOutboxServiceTagAppMessageSender] = { .sent_handler = app_message_outbox_handle_app_outbox_message_sent, .max_length = (sizeof(AppMessageAppOutboxData) + APP_MSG_HDR_OVRHD_SIZE + APP_MSG_8K_DICT_SIZE), .max_pending_messages = 1, }, #ifdef UNITTEST [AppOutboxServiceTagUnitTest] = { .sent_handler = test_app_outbox_sent_handler, .max_length = 1, .max_pending_messages = 2, }, #endif }; //////////////////////////////////////////////////////////////////////////////////////////////////// // Syscalls static const AppOutboxSenderDef *prv_find_def_and_tag_by_handler(AppOutboxSentHandler sent_handler, AppOutboxServiceTag *tag_out) { for (AppOutboxServiceTag tag = 0; tag < NumAppOutboxServiceTag; ++tag) { if (s_app_outbox_sender_defs[tag].sent_handler == sent_handler) { if (tag_out) { *tag_out = tag; } return &s_app_outbox_sender_defs[tag]; } } if (tag_out) { *tag_out = AppOutboxServiceTagInvalid; } return NULL; } static void app_outbox_service_send(const uint8_t *data, size_t length, AppOutboxSentHandler sent_handler, void *cb_ctx); DEFINE_SYSCALL(void, sys_app_outbox_send, const uint8_t *data, size_t length, AppOutboxSentHandler sent_handler, void *cb_ctx) { if (PRIVILEGE_WAS_ELEVATED) { // Check that data is in app space: syscall_assert_userspace_buffer(data, length); } const AppOutboxSenderDef *def = prv_find_def_and_tag_by_handler(sent_handler, NULL); if (!def) { PBL_LOG(LOG_LEVEL_ERROR, "AppOutbox sent_handler not allowed <%p>", sent_handler); syscall_failed(); } const size_t max_length = def->max_length; if (length > max_length) { PBL_LOG(LOG_LEVEL_ERROR, "AppOutbox max_length exceeded %"PRIu32" vs %"PRIu32, (uint32_t)length, (uint32_t)max_length); syscall_failed(); } app_outbox_service_send(data, length, sent_handler, cb_ctx); } //////////////////////////////////////////////////////////////////////////////////////////////////// // Helpers static void prv_lock(void) { // Using one "global" lock for all app outboxes. // If needed, we could easily give each app outbox its own mutex, but it seems overkill right now. mutex_lock_recursive(s_app_outbox_mutex); } static void prv_unlock(void) { mutex_unlock_recursive(s_app_outbox_mutex); } static AppOutboxConsumer *prv_consumer_for_tag(AppOutboxServiceTag tag) { if (tag == AppOutboxServiceTagInvalid) { return NULL; } AppOutboxConsumer *consumer = &s_app_outbox_consumer[tag]; if (consumer->message_handler == NULL) { return NULL; } return consumer; } static void prv_schedule_sent_handler(AppOutboxSentHandler sent_handler, void *cb_ctx, AppOutboxStatus status) { if (!sent_handler) { return; } PebbleEvent event = { .type = PEBBLE_APP_OUTBOX_SENT_EVENT, .app_outbox_sent = { .sent_handler = sent_handler, .cb_ctx = cb_ctx, .status = status, }, }; process_manager_send_event_to_process(PebbleTask_App, &event); } //! @note This executes on App Task static void prv_schedule_consumer_message_handler(AppOutboxConsumer *consumer, AppOutboxMessage *message) { void (*callback)(void *) = (__typeof__(callback))consumer->message_handler; PebbleEvent event = { .type = PEBBLE_APP_OUTBOX_MSG_EVENT, .app_outbox_msg = { .callback = callback, .data = message, }, }; sys_send_pebble_event_to_kernel(&event); } static uint32_t prv_num_pending_messages(const AppOutboxConsumer *consumer) { return list_count((ListNode *)consumer->head); } static AppOutboxConsumer *prv_find_consumer_with_message(const AppOutboxMessage *message) { AppOutboxMessage *head = (AppOutboxMessage *)list_get_head((ListNode *)message); for (AppOutboxServiceTag tag = 0; tag < NumAppOutboxServiceTag; ++tag) { if (s_app_outbox_consumer[tag].head == head) { return &s_app_outbox_consumer[tag]; } } return NULL; } void prv_cleanup_pending_messages(AppOutboxConsumer *consumer, bool should_call_sent_handler) { AppOutboxMessage *message = consumer->head; consumer->head = NULL; while (message) { if (should_call_sent_handler) { prv_schedule_sent_handler(message->sent_handler, message->cb_ctx, AppOutboxStatusConsumerDoesNotExist); } AppOutboxMessage *next = (AppOutboxMessage *)message->node.next; message->node = (ListNode) {}; // Don't free it, it's the responsibility of the consumer to eventually call // app_outbox_service_consume_message(), which will free the message! message = next; } } //////////////////////////////////////////////////////////////////////////////////////////////////// // Exported functions void app_outbox_service_register(AppOutboxServiceTag tag, AppOutboxMessageHandler message_handler, PebbleTask consumer_task, size_t consumer_data_length) { prv_lock(); { PBL_ASSERTN(!prv_consumer_for_tag(tag)); AppOutboxConsumer *consumer = &s_app_outbox_consumer[tag]; consumer->message_handler = message_handler; consumer->consumer_data_length = consumer_data_length; consumer->consumer_task = consumer_task; } prv_unlock(); } void app_outbox_service_unregister(AppOutboxServiceTag service_tag) { prv_lock(); { prv_cleanup_pending_messages(&s_app_outbox_consumer[service_tag], true /* should_call_sent_handler */); s_app_outbox_consumer[service_tag].message_handler = NULL; } prv_unlock(); } //! @note This executes on App Task //! Should only get called through the syscall, sys_app_outbox_send static void app_outbox_service_send(const uint8_t *data, size_t length, AppOutboxSentHandler sent_handler, void *cb_ctx) { AppOutboxStatus status = AppOutboxStatusSuccess; prv_lock(); { AppOutboxServiceTag tag; const AppOutboxSenderDef *def = prv_find_def_and_tag_by_handler(sent_handler, &tag); AppOutboxConsumer *consumer = prv_consumer_for_tag(tag); if (!consumer) { status = AppOutboxStatusConsumerDoesNotExist; goto finally; } if (prv_num_pending_messages(consumer) >= def->max_pending_messages) { status = AppOutboxStatusOutOfResources; goto finally; } const size_t consumer_data_length = consumer->consumer_data_length; AppOutboxMessage *message = (AppOutboxMessage *)kernel_zalloc(sizeof(AppOutboxMessage) + consumer_data_length); if (!message) { status = AppOutboxStatusOutOfMemory; goto finally; } *message = (AppOutboxMessage) { .data = data, .length = length, .sent_handler = sent_handler, .cb_ctx = cb_ctx, }; consumer->head = (AppOutboxMessage *)list_prepend((ListNode *)consumer->head, (ListNode *)message); prv_schedule_consumer_message_handler(consumer, message); } finally: if (AppOutboxStatusSuccess != status) { prv_schedule_sent_handler(sent_handler, cb_ctx, status); } prv_unlock(); } bool app_outbox_service_is_message_cancelled(AppOutboxMessage *message) { prv_lock(); bool cancelled = !prv_find_consumer_with_message(message); prv_unlock(); return cancelled; } void app_outbox_service_consume_message(AppOutboxMessage *message, AppOutboxStatus status) { prv_lock(); { if (app_outbox_service_is_message_cancelled(message)) { // Don't call the sent_handler goto finally; } AppOutboxConsumer *consumer = prv_find_consumer_with_message(message); PBL_ASSERTN(consumer); list_remove(&message->node, (ListNode **)&consumer->head, NULL); prv_schedule_sent_handler(message->sent_handler, message->cb_ctx, status); } finally: kernel_free(message); prv_unlock(); } void app_outbox_service_cleanup_all_pending_messages(void) { prv_lock(); for (AppOutboxServiceTag tag = 0; tag < NumAppOutboxServiceTag; ++tag) { AppOutboxConsumer *consumer = &s_app_outbox_consumer[tag]; prv_cleanup_pending_messages(consumer, false /* should_call_sent_handler */); } prv_unlock(); } void app_outbox_service_cleanup_event(PebbleEvent *event) { if (event->type != PEBBLE_APP_OUTBOX_MSG_EVENT) { return; } // Call consume directly to clean up the message, it's not valid anyway: app_outbox_service_consume_message((AppOutboxMessage *)event->app_outbox_msg.data, AppOutboxStatusSuccess /* ignored */); } void app_outbox_service_init(void) { s_app_outbox_mutex = mutex_create_recursive(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // Unit Test Interfaces void app_outbox_service_deinit(void) { app_outbox_service_cleanup_all_pending_messages(); memset(&s_app_outbox_consumer, 0, sizeof(s_app_outbox_consumer)); mutex_destroy((PebbleMutex *)s_app_outbox_mutex); s_app_outbox_mutex = NULL; } uint32_t app_outbox_service_max_pending_messages(AppOutboxServiceTag tag) { return s_app_outbox_sender_defs[tag].max_pending_messages; } uint32_t app_outbox_service_max_message_length(AppOutboxServiceTag tag) { return s_app_outbox_sender_defs[tag].max_length; }