mirror of
https://github.com/google/pebble.git
synced 2025-04-30 15:21:41 -04:00
617 lines
20 KiB
C
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;
|
|
}
|