Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

View file

@ -0,0 +1,240 @@
/*
* 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.h"
#include "applib/app_message/app_message_internal.h"
#include "kernel/pbl_malloc.h"
#include "process_state/app_state/app_state.h"
#include "services/common/comm_session/protocol.h"
#include "syscall/syscall.h"
#include "system/logging.h"
// -------- Initialization ---------------------------------------------------------------------- //
void app_message_init(void) {
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
*app_message_ctx = (const AppMessageCtx) {};
}
// -------- Pebble Protocol Handlers ------------------------------------------------------------ //
static bool prv_has_invalid_header_length(size_t length) {
if (length < sizeof(AppMessageHeader)) {
PBL_LOG(LOG_LEVEL_ERROR, "Too short");
return true;
}
return false;
}
//! The new implementation uses up to 72 bytes more stack space than the previous implementation.
//! Might need to do some extra work to get a "thinner" stack, if this causes issues.
//! Executes on App task.
void app_message_app_protocol_msg_callback(CommSession *session,
const uint8_t* data, size_t length,
AppInboxConsumerInfo *consumer_info) {
if (prv_has_invalid_header_length(length)) {
return;
}
AppMessageHeader *message = (AppMessageHeader *) data;
switch (message->command) {
case CMD_PUSH:
// Incoming message:
app_message_inbox_receive(session, (AppMessagePush *) message, length, consumer_info);
return;
case CMD_REQUEST:
// Incoming request for an update push:
// TODO PBL-1636: decide to implement CMD_REQUEST, or remove it
return;
case CMD_ACK:
case CMD_NACK:
// Received ACK/NACK in response to previously pushed update:
app_message_out_handle_ack_nack_received(message);
return;
default:
PBL_LOG(LOG_LEVEL_ERROR, "Unknown Cmd 0x%x", message->command);
return;
}
}
//! Executes on KernelBG, sends back NACK on behalf of the app if it is not able to do so.
//! Note that app_message_receiver_dropped_handler will also get called on the App task,
//! to report the number of missed messages.
void app_message_app_protocol_system_nack_callback(CommSession *session,
const uint8_t* data, size_t length) {
if (prv_has_invalid_header_length(length)) {
return;
}
AppMessageHeader *message = (AppMessageHeader *) data;
if (message->command != CMD_PUSH) {
return;
}
app_message_inbox_send_ack_nack_reply(session, message->transaction_id, CMD_NACK);
}
// -------- Developer Interface ----------------------------------------------------------------- //
void *app_message_get_context(void) {
return app_state_get_app_message_ctx()->inbox.user_context;
}
void *app_message_set_context(void *context) {
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
void *retval = app_message_ctx->inbox.user_context;
app_message_ctx->inbox.user_context = context;
app_message_ctx->outbox.user_context = context;
return retval;
}
AppMessageInboxReceived app_message_register_inbox_received(
AppMessageInboxReceived received_callback) {
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
AppMessageInboxReceived retval = app_message_ctx->inbox.received_callback;
app_message_ctx->inbox.received_callback = received_callback;
return retval;
}
AppMessageInboxDropped app_message_register_inbox_dropped(AppMessageInboxDropped dropped_callback) {
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
AppMessageInboxDropped retval = app_message_ctx->inbox.dropped_callback;
app_message_ctx->inbox.dropped_callback = dropped_callback;
return retval;
}
AppMessageOutboxSent app_message_register_outbox_sent(AppMessageOutboxSent sent_callback) {
AppMessageOutboxSent retval = app_state_get_app_message_ctx()->outbox.sent_callback;
app_state_get_app_message_ctx()->outbox.sent_callback = sent_callback;
return retval;
}
AppMessageOutboxFailed app_message_register_outbox_failed(AppMessageOutboxFailed failed_callback) {
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
AppMessageOutboxFailed retval = app_message_ctx->outbox.failed_callback;
app_message_ctx->outbox.failed_callback = failed_callback;
return retval;
}
void app_message_deregister_callbacks(void) {
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
app_message_ctx->inbox.received_callback = NULL;
app_message_ctx->inbox.dropped_callback = NULL;
app_message_ctx->inbox.user_context = NULL;
app_message_ctx->outbox.sent_callback = NULL;
app_message_ctx->outbox.failed_callback = NULL;
app_message_ctx->outbox.user_context = NULL;
}
static bool prv_supports_8k(void) {
if (!sys_app_pp_has_capability(CommSessionAppMessage8kSupport)) {
return false;
}
const Version app_sdk_version = sys_get_current_app_sdk_version();
const Version sdk_version_8k_messages_enabled = (const Version) { 0x05, 0x3f };
return (version_compare(sdk_version_8k_messages_enabled, app_sdk_version) <= 0);
}
uint32_t app_message_inbox_size_maximum(void) {
if (prv_supports_8k()) {
// New behavior, allow up to one large 8K byte array per message:
return (APP_MSG_8K_DICT_SIZE);
} else {
// Legacy behavior:
if (sys_get_current_app_is_js_allowed()) {
return (COMM_PRIVATE_MAX_INBOUND_PAYLOAD_SIZE - APP_MSG_HDR_OVRHD_SIZE);
} else {
return (COMM_PUBLIC_MAX_INBOUND_PAYLOAD_SIZE - APP_MSG_HDR_OVRHD_SIZE);
}
}
}
uint32_t app_message_outbox_size_maximum(void) {
if (prv_supports_8k()) {
return (APP_MSG_8K_DICT_SIZE);
} else {
// Legacy behavior:
return (APP_MESSAGE_OUTBOX_SIZE_MINIMUM + APP_MSG_HDR_OVRHD_SIZE);
}
}
AppMessageResult app_message_open(const uint32_t size_inbound, const uint32_t size_outbound) {
// We're making this assumption in this file; here's as good a place to check it as any.
// It's probably not super-bad if this isn't true, but we'll have type casts between different
// sizes without over/underflow verification.
#ifndef UNITTEST
_Static_assert(sizeof(size_t) == sizeof(uint32_t), "sizeof(size_t) != sizeof(uint32_t)");
#endif
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
if (app_message_ctx->outbox.phase != OUT_CLOSED ||
app_message_ctx->inbox.is_open) {
return APP_MSG_INVALID_STATE; // Already open
}
AppMessageResult result = app_message_outbox_open(&app_message_ctx->outbox, size_outbound);
if (APP_MSG_OK != result) {
return result;
}
result = app_message_inbox_open(&app_message_ctx->inbox, size_inbound);
if (APP_MSG_OK != result) {
app_message_outbox_close(&app_message_ctx->outbox);
return result;
}
return APP_MSG_OK;
}
void app_message_close(void) {
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
// TODO PBL-1634: handle the the return status when this function returns status.
// For now, continue to ignore failure.
app_message_outbox_close(&app_message_ctx->outbox);
app_message_inbox_close(&app_message_ctx->inbox);
app_message_deregister_callbacks();
}
// -------- Testing Interface (only) ------------------------------------------------------------ //
AppTimer *app_message_ack_timer_id(void) {
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
return app_message_ctx->outbox.ack_nack_timer;
}
bool app_message_is_accepting_inbound(void) {
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
return app_message_ctx->inbox.is_open;
}
bool app_message_is_accepting_outbound(void) {
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
return (app_message_ctx->outbox.phase == OUT_ACCEPTING);
}
bool app_message_is_closed_inbound(void) {
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
return (!app_message_ctx->inbox.is_open);
}
bool app_message_is_closed_outbound(void) {
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
return (app_message_ctx->outbox.phase == OUT_CLOSED);
}

View file

@ -0,0 +1,418 @@
/*
* 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.
*/
#pragma once
#include "util/dict.h"
#include "util/list.h"
//! @addtogroup Foundation
//! @{
//! @addtogroup AppMessage
//!
//!
//!
//! \brief Bi-directional communication between phone apps and Pebble watchapps
//!
//! AppMessage is a bi-directional messaging subsystem that enables communication between phone apps
//! and Pebble watchapps. This is accomplished by allowing phone and watchapps to exchange arbitrary
//! sets of key/value pairs. The key/value pairs are stored in the form of a Dictionary, the layout
//! of which is left for the application developer to define.
//!
//! AppMessage implements a push-oriented messaging protocol, enabling your app to call functions and
//! methods to push messages from Pebble to phone and vice versa. The protocol is symmetric: both Pebble
//! and the phone can send messages. All messages are acknowledged. In this context, there is no
//! client-server model, as such.
//!
//! During the sending phase, one side initiates the communication by transferring a dictionary over the air.
//! The other side then receives this message and is given an opportunity to perform actions on that data.
//! As soon as possible, the other side is expected to reply to the message with a simple acknowledgment
//! that the message was received successfully.
//!
//! PebbleKit JavaScript provides you with a set of standard JavaScript APIs that let your app receive messages
//! from the watch, make HTTP requests, and send new messages to the watch. AppMessage APIs are used to send and
//! receive data. A Pebble watchapp can use the resources of the connected phone to fetch information from web services,
//! send information to web APIs, or store login credentials. On the JavaScript side, you communicate
//! with Pebble via a Pebble object exposed in the namespace.
//!
//! Messages always need to get either ACKnowledged or "NACK'ed," that is, not acknowledged.
//! If not, messages will result in a time-out failure. The AppMessage subsystem takes care of this implicitly.
//! In the phone libraries, this step is a bit more explicit.
//!
//! The Pebble watch interfaces make a distinction between the Inbox and the Outbox calls. The Inbox
//! receives messages from the phone on the watch; the Outbox sends messages from the watch to the phone.
//! These two buffers can be managed separately.
//!
//! <h4>Warning</h4>
//! A critical constraint of AppMessage is that messages are limited in size. An ingoing (outgoing) message
//! larger than the inbox (outbox) will not be transmitted and will generate an error. You can choose your
//! inbox and outbox size when you call app_message_open().
//!
//! Pebble SDK provides a static minimum guaranteed size (APP_MESSAGE_INBOX_SIZE_MINIMUM and APP_MESSAGE_OUTBOX_SIZE_MINIMUM).
//! Requesting a buffer of the minimum guaranteed size (or smaller) is always guaranteed to succeed on all
//! Pebbles in this SDK version or higher, and with every phone.
//!
//! In some context, Pebble might be able to provide your application with larger inbox/outbox.
//! You can call app_message_inbox_size_maximum() and app_message_outbox_size_maximum() in your code to get
//! the largest possible value you can use.
//!
//! To always get the largest buffer available, follow this best practice:
//!
//! app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum())
//!
//! AppMessage uses your application heap space. That means that the sizes you pick for the AppMessage
//! inbox and outbox buffers are important in optimizing your apps performance. The more you use for
//! AppMessage, the less space youll have for the rest of your app.
//!
//! To register callbacks, you should call app_message_register_inbox_received(), app_message_register_inbox_dropped(),
//! app_message_register_outbox_sent(), app_message_register_outbox_failed().
//!
//! Pebble recommends that you call them before app_message_open() to ensure you do not miss a message
//! arriving between starting AppMessage and registering the callback. You can set a context that will be passed
//! to all the callbacks with app_message_set_context().
//!
//! In circumstances that may not be ideal, when using AppMessage several types of errors may occur.
//! For example:
//!
//! * The send cant start because the system state won't allow for a success. Several reasons
//! you're unable to perform a send: A send() is already occurring (only one is possible at a time) or Bluetooth
//! is not enabled or connected.
//! * The send and receive occur, but the receiver cant accept the message. For instance, there is no app
//! that receives such a message.
//! * The send occurs, but the receiver either does not actually receive the message or cant handle it
//! in a timely fashion.
//! * In the case of a dropped message, the phone sends a message to the watchapp, while there is still
//! an unprocessed message in the Inbox.
//!
//! Other errors are possible and described by AppMessageResult. A client of the AppMessage interface
//! should use the result codes to be more robust in the face of communication problems either in the field or while debugging.
//!
//! Refer to the \htmlinclude app-phone-communication.html for a conceptual overview and code usage.
//!
//! For code examples, refer to the SDK Examples that directly use App Message. These include:
//! * <a href="https://github.com/pebble-examples/pebblekit-js-weather">
//! pebblekit-js-weather</a>
//! * <a href="https://github.com/pebble-examples/pebblekit-js-quotes">
//! pebblekit-js-quotes</a>
//! @{
//!
//! AppMessage is a messaging subsystem that allows phone and watch applications
//! to exchange arbitrary sets of key-value pairs. The key value pairs are
//! stored in the form of a Dictionary, the layout of which is left for the
//! application developer to define.
//!
//! <h3>Communication Model</h3>
//!
//! AppMessage is a simple send-receive-reply protocol. The protocol is symmetric: both the watch and phone can start
//! sending a message and expect a reply from the other side.
//!
//! In the sending phase, one side initiates the communication by transferring a dictionary over the air. The other
//! side then receives this message and is given an opportunity to perform actions on that data. As soon as possible,
//! the other side is expected to reply to the message with a simple acknowledgement that the message was received
//! successfully.
//!
//! In non-ideal circumstances, several errors may occur. For example:
//! * The send can't start as the system state won't allow for a success.
//! * The send and receive occur, but the receiver cannot accept the message (for example, there is no app that receives such
//! a message).
//! * The send occurs, but the receiver either does not actually receive the message or can't handle it in a timely
//! fashion.
//!
//! Other errors are possible, described by \ref AppMessageResult. A client of the AppMessage interface
//! can use the result codes to be more robust in the face of communication problems either in the field or while
//! debugging.
//!
//! The watch interfaces make a distinction between the Inbox and the Outbox. The Inbox receives messages from the
//! phone on the watch; the Outbox sends messages from the watch to the phone. These two objects can be managed
//! separately.
//!
//! \note Messages are actually addressed by the UUID of the watch and phone apps. This is done automatically by the
//! system for the convenience of the client. However, this does require that both the watch and phone apps
//! share their UUID. AppMessage is not capable of 1:N or M:N communication at this time, and is merely 1:1.
//!
//! \sa AppMessageResult
// -------- Defines, Enumerations, and Structures ------------------------------------------------------------------ //
//! As long as the firmware maintains its current major version, inboxes of this size or smaller will be allowed.
//!
//! \sa app_message_inbox_size_maximum()
//! \sa APP_MESSAGE_OUTBOX_SIZE_MINIMUM
//!
#define APP_MESSAGE_INBOX_SIZE_MINIMUM 124 /* bytes */
//! As long as the firmware maintains its current major version, outboxes of this size or smaller will be allowed.
//!
//! \sa app_message_outbox_size_maximum()
//! \sa APP_MESSAGE_INBOX_SIZE_MINIMUM
//!
#define APP_MESSAGE_OUTBOX_SIZE_MINIMUM 636 /* bytes */
//! AppMessage result codes.
typedef enum {
//! (0) All good, operation was successful.
APP_MSG_OK = 0,
//! (2) The other end did not confirm receiving the sent data with an (n)ack in time.
APP_MSG_SEND_TIMEOUT = 1 << 1,
//! (4) The other end rejected the sent data, with a "nack" reply.
APP_MSG_SEND_REJECTED = 1 << 2,
//! (8) The other end was not connected.
APP_MSG_NOT_CONNECTED = 1 << 3,
//! (16) The local application was not running.
APP_MSG_APP_NOT_RUNNING = 1 << 4,
//! (32) The function was called with invalid arguments.
APP_MSG_INVALID_ARGS = 1 << 5,
//! (64) There are pending (in or outbound) messages that need to be processed first before
//! new ones can be received or sent.
APP_MSG_BUSY = 1 << 6,
//! (128) The buffer was too small to contain the incoming message.
//! @internal
//! @see \ref app_message_open()
APP_MSG_BUFFER_OVERFLOW = 1 << 7,
//! (512) The resource had already been released.
APP_MSG_ALREADY_RELEASED = 1 << 9,
//! (1024) The callback was already registered.
APP_MSG_CALLBACK_ALREADY_REGISTERED = 1 << 10,
//! (2048) The callback could not be deregistered, because it had not been registered before.
APP_MSG_CALLBACK_NOT_REGISTERED = 1 << 11,
//! (4096) The system did not have sufficient application memory to
//! perform the requested operation.
APP_MSG_OUT_OF_MEMORY = 1 << 12,
//! (8192) App message was closed.
APP_MSG_CLOSED = 1 << 13,
//! (16384) An internal OS error prevented AppMessage from completing an operation.
APP_MSG_INTERNAL_ERROR = 1 << 14,
//! (32768) The function was called while App Message was not in the appropriate state.
APP_MSG_INVALID_STATE = 1 << 15,
} AppMessageResult;
//! Called after an incoming message is received.
//!
//! \param[in] iterator
//! The dictionary iterator to the received message. Never NULL. Note that the iterator cannot be modified or
//! saved off. The library may need to re-use the buffered space where this message is supplied. Returning from
//! the callback indicates to the library that the received message contents are no longer needed or have already
//! been externalized outside its buffering space and iterator.
//!
//! \param[in] context
//! Pointer to application data as specified when registering the callback.
//!
typedef void (*AppMessageInboxReceived)(DictionaryIterator *iterator, void *context);
//! Called after an incoming message is dropped.
//!
//! \param[in] result
//! The reason why the message was dropped. Some possibilities include \ref APP_MSG_BUSY and
//! \ref APP_MSG_BUFFER_OVERFLOW.
//!
//! \param[in] context
//! Pointer to application data as specified when registering the callback.
//!
//! Note that you can call app_message_outbox_begin() from this handler to prepare a new message.
//! This will invalidate the previous dictionary iterator; do not use it after calling app_message_outbox_begin().
//!
typedef void (*AppMessageInboxDropped)(AppMessageResult reason, void *context);
//! Called after an outbound message has been sent and the reply has been received.
//!
//! \param[in] iterator
//! The dictionary iterator to the sent message. The iterator will be in the final state that was sent. Note that
//! the iterator cannot be modified or saved off as the library will re-open the dictionary with dict_begin() after
//! this callback returns.
//!
//! \param[in] context
//! Pointer to application data as specified when registering the callback.
//!
typedef void (*AppMessageOutboxSent)(DictionaryIterator *iterator, void *context);
//! Called after an outbound message has not been sent successfully.
//!
//! \param[in] iterator
//! The dictionary iterator to the sent message. The iterator will be in the final state that was sent. Note that
//! the iterator cannot be modified or saved off as the library will re-open the dictionary with dict_begin() after
//! this callback returns.
//!
//! \param[in] result
//! The result of the operation. Some possibilities for the value include \ref APP_MSG_SEND_TIMEOUT,
//! \ref APP_MSG_SEND_REJECTED, \ref APP_MSG_NOT_CONNECTED, \ref APP_MSG_APP_NOT_RUNNING, and the combination
//! `(APP_MSG_NOT_CONNECTED | APP_MSG_APP_NOT_RUNNING)`.
//!
//! \param context
//! Pointer to application data as specified when registering the callback.
//!
//! Note that you can call app_message_outbox_begin() from this handler to prepare a new message.
//! This will invalidate the previous dictionary iterator; do not use it after calling app_message_outbox_begin().
//!
typedef void (*AppMessageOutboxFailed)(DictionaryIterator *iterator, AppMessageResult reason, void *context);
// -------- AppMessage Callbacks ----------------------------------------------------------------------------------- //
//! Gets the context that will be passed to all AppMessage callbacks.
//!
//! \return The current context on record.
//!
void *app_message_get_context(void);
//! Sets the context that will be passed to all AppMessage callbacks.
//!
//! \param[in] context The context that will be passed to all AppMessage callbacks.
//!
//! \return The previous context that was on record.
//!
void *app_message_set_context(void *context);
//! Registers a function that will be called after any Inbox message is received successfully.
//!
//! Only one callback may be registered at a time. Each subsequent call to this function will replace the previous
//! callback. The callback is optional; setting it to NULL will deregister the current callback and no function will
//! be called anymore.
//!
//! \param[in] received_callback The callback that will be called going forward; NULL to not have a callback.
//!
//! \return The previous callback (or NULL) that was on record.
//!
AppMessageInboxReceived app_message_register_inbox_received(AppMessageInboxReceived received_callback);
//! Registers a function that will be called after any Inbox message is received but dropped by the system.
//!
//! Only one callback may be registered at a time. Each subsequent call to this function will replace the previous
//! callback. The callback is optional; setting it to NULL will deregister the current callback and no function will
//! be called anymore.
//!
//! \param[in] dropped_callback The callback that will be called going forward; NULL to not have a callback.
//!
//! \return The previous callback (or NULL) that was on record.
//!
AppMessageInboxDropped app_message_register_inbox_dropped(AppMessageInboxDropped dropped_callback);
//! Registers a function that will be called after any Outbox message is sent and an ACK reply occurs in a timely
//! fashion.
//!
//! Only one callback may be registered at a time. Each subsequent call to this function will replace the previous
//! callback. The callback is optional; setting it to NULL will deregister the current callback and no function will
//! be called anymore.
//!
//! \param[in] sent_callback The callback that will be called going forward; NULL to not have a callback.
//!
//! \return The previous callback (or NULL) that was on record.
//!
AppMessageOutboxSent app_message_register_outbox_sent(AppMessageOutboxSent sent_callback);
//! Registers a function that will be called after any Outbox message is not sent with a timely ACK reply.
//! The call to \ref app_message_outbox_send() must have succeeded.
//!
//! Only one callback may be registered at a time. Each subsequent call to this function will replace the previous
//! callback. The callback is optional; setting it to NULL will deregister the current callback and no function will
//! be called anymore.
//!
//! \param[in] failed_callback The callback that will be called going forward; NULL to not have a callback.
//!
//! \return The previous callback (or NULL) that was on record.
//!
AppMessageOutboxFailed app_message_register_outbox_failed(AppMessageOutboxFailed failed_callback);
//! Deregisters all callbacks and their context.
//!
void app_message_deregister_callbacks(void);
// -------- AppMessage Lifecycle ----------------------------------------------------------------------------------- //
//! Programatically determine the inbox size maximum in the current configuration.
//!
//! \return The inbox size maximum on this firmware.
//!
//! \sa APP_MESSAGE_INBOX_SIZE_MINIMUM
//! \sa app_message_outbox_size_maximum()
//!
uint32_t app_message_inbox_size_maximum(void);
//! Programatically determine the outbox size maximum in the current configuration.
//!
//! \return The outbox size maximum on this firmware.
//!
//! \sa APP_MESSAGE_OUTBOX_SIZE_MINIMUM
//! \sa app_message_inbox_size_maximum()
//!
uint32_t app_message_outbox_size_maximum(void);
//! Open AppMessage to transfers.
//!
//! Use \ref dict_calc_buffer_size_from_tuplets() or \ref dict_calc_buffer_size() to estimate the size you need.
//!
//! \param[in] size_inbound The required size for the Inbox buffer
//! \param[in] size_outbound The required size for the Outbox buffer
//!
//! \return A result code such as \ref APP_MSG_OK or \ref APP_MSG_OUT_OF_MEMORY.
//!
//! \note It is recommended that if the Inbox will be used, that at least the Inbox callbacks should be registered
//! before this call. Otherwise it is possible for an Inbox message to be NACK'ed without being seen by the
//! application.
//!
AppMessageResult app_message_open(const uint32_t size_inbound, const uint32_t size_outbound);
//! Close AppMessage to further transfers.
//!
void app_message_close(void);
// -------- AppMessage Inbox --------------------------------------------------------------------------------------- //
// Note: the Inbox has no direct functions, only callbacks.
// -------- AppMessage Outbox -------------------------------------------------------------------------------------- //
//! Begin writing to the Outbox's Dictionary buffer.
//!
//! \param[out] iterator Location to write the DictionaryIterator pointer. This will be NULL on failure.
//!
//! \return A result code, including but not limited to \ref APP_MSG_OK, \ref APP_MSG_INVALID_ARGS or
//! \ref APP_MSG_BUSY.
//!
//! \note After a successful call, one can add values to the dictionary using functions like \ref dict_write_data()
//! and friends.
//!
//! \sa Dictionary
//!
AppMessageResult app_message_outbox_begin(DictionaryIterator **iterator);
//! Sends the outbound dictionary.
//!
//! \return A result code, including but not limited to \ref APP_MSG_OK or \ref APP_MSG_BUSY. The APP_MSG_OK code does
//! not mean that the message was sent successfully, but only that the start of processing was successful.
//! Since this call is asynchronous, callbacks provide the final result instead.
//!
//! \sa AppMessageOutboxSent
//! \sa AppMessageOutboxFailed
//!
AppMessageResult app_message_outbox_send(void);
//! @} // end addtogroup AppMessage
//! @} // end addtogroup Foundation

View file

@ -0,0 +1,120 @@
/*
* 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 "applib/app_message/app_message_receiver.h"
#include "process_state/app_state/app_state.h"
#include "system/logging.h"
#include "syscall/syscall.h"
AppMessageResult app_message_inbox_open(AppMessageCtxInbox *inbox, size_t size_inbound) {
const size_t size_maximum = app_message_inbox_size_maximum();
if (size_inbound > size_maximum) {
// Truncate if it's more than the max:
size_inbound = size_maximum;
} else if (size_inbound == size_maximum) {
APP_LOG(LOG_LEVEL_INFO, "app_message_open() called with app_message_inbox_size_maximum().");
APP_LOG(LOG_LEVEL_INFO,
"This consumes %"PRIu32" bytes of heap memory, potentially more in the future!",
(uint32_t)size_maximum);
}
if (size_inbound == 0) {
return APP_MSG_OK;
}
// Add extra space needed for protocol overhead:
if (!app_message_receiver_open(size_inbound + APP_MSG_HDR_OVRHD_SIZE)) {
return APP_MSG_OUT_OF_MEMORY;
}
inbox->is_open = true;
return APP_MSG_OK;
}
void app_message_inbox_close(AppMessageCtxInbox *inbox) {
app_message_receiver_close();
inbox->is_open = false;
}
void app_message_inbox_send_ack_nack_reply(CommSession *session, const uint8_t transaction_id,
AppMessageCmd cmd) {
const AppMessageAck nack_message = (const AppMessageAck) {
.header = {
.command = cmd,
.transaction_id = transaction_id,
},
};
// Just use a syscall to enqueue the message using kernel heap.
// We could use app_outbox, but then we'd need to allocate the message on the app heap and I'm
// afraid this might break apps, especially if the mobile app is misbehaving and avalanching the
// app with messages that need to be (n)ack'd.
sys_app_pp_send_data(session, APP_MESSAGE_ENDPOINT_ID,
(const uint8_t *) &nack_message, sizeof(nack_message));
}
void app_message_inbox_handle_dropped_messages(uint32_t num_drops) {
// Taking a shortcut here. We used to report either APP_MSG_BUFFER_OVERFLOW or APP_MSG_BUSY back
// to the app. With the new the Receiver / AppInbox system, there are different reasons why
// messages get dropped. Just map everything to "APP_MSG_BUSY":
AppMessageCtx *app_message_ctx = app_state_get_app_message_ctx();
AppMessageCtxInbox *inbox = &app_message_ctx->inbox;
const bool is_open_and_has_handler = (inbox->is_open && inbox->dropped_callback);
for (uint32_t i = 0; i < num_drops; ++i) {
if (is_open_and_has_handler) {
inbox->dropped_callback(APP_MSG_BUSY, inbox->user_context);
}
}
}
static bool prv_is_app_with_uuid_running(const Uuid *uuid) {
Uuid app_uuid = {};
sys_get_app_uuid(&app_uuid);
return uuid_equal(&app_uuid, uuid);
}
void app_message_inbox_receive(CommSession *session, AppMessagePush *push_message, size_t length,
AppInboxConsumerInfo *consumer_info) {
// Test if the data is long enough to contain a push message:
if (length < sizeof(AppMessagePush)) {
PBL_LOG(LOG_LEVEL_ERROR, "Too short");
return;
}
AppMessageCtxInbox *inbox = &app_state_get_app_message_ctx()->inbox;
const uint8_t transaction_id = push_message->header.transaction_id;
// Verify UUID for app-bound messages:
if (!prv_is_app_with_uuid_running(&push_message->uuid)) {
app_message_inbox_send_ack_nack_reply(session, transaction_id, CMD_NACK);
sys_app_pp_app_message_analytics_count_drop();
return;
}
DictionaryIterator iterator;
const uint16_t dict_size = (length - APP_MSG_HDR_OVRHD_SIZE);
// TODO PBL-1639: Maybe do some sanity checking on the dict structure?
dict_read_begin_from_buffer(&iterator, (const uint8_t *) &push_message->dictionary, dict_size);
if (inbox->received_callback) {
inbox->received_callback(&iterator, inbox->user_context);
}
// Mark data as consumed...
app_inbox_consume(consumer_info);
// ... only then send the ACK:
app_message_inbox_send_ack_nack_reply(session, transaction_id, CMD_ACK);
}

View file

@ -0,0 +1,156 @@
/*
* 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.
*/
#pragma once
#include "applib/app_message/app_message.h"
#include "applib/app_timer.h"
#include "services/normal/app_message/app_message_sender.h"
#include "util/attributes.h"
#include "util/uuid.h"
typedef struct CommSession CommSession;
#define ACK_NACK_TIME_OUT_MS (10000)
#define APP_MESSAGE_ENDPOINT_ID (0x30)
typedef enum {
CMD_PUSH = 0x01,
CMD_REQUEST = 0x02,
CMD_ACK = 0xff,
CMD_NACK = 0x7f,
} AppMessageCmd;
typedef struct PACKED {
AppMessageCmd command:8;
uint8_t transaction_id;
} AppMessageHeader;
//! The actual wire format of an app message message
typedef struct PACKED {
AppMessageHeader header;
Uuid uuid;
Dictionary dictionary; //!< Variable length!
} AppMessagePush;
// AppMessageHeader and Uuid size should be opaque to user of API
#define APP_MSG_HDR_OVRHD_SIZE (offsetof(AppMessagePush, dictionary))
#define APP_MSG_8K_DICT_SIZE (sizeof(Dictionary) + sizeof(Tuple) + (8 * 1024))
typedef struct PACKED {
AppMessageHeader header;
} AppMessageAck;
// For a diagram of the state machine:
// https://pebbletechnology.atlassian.net/wiki/pages/editpage.action?pageId=91914242
typedef enum AppMessagePhaseOut {
//! The App Message Outbox is not enabled. To enable it, call app_message_open().
OUT_CLOSED = 0,
//! The dictionary writing can be "started" by calling app_message_outbox_begin()
OUT_ACCEPTING,
//! app_message_outbox_begin() has been called and the dictionary can be written and then sent.
OUT_WRITING,
//! app_message_outbox_send() has been called. The ack/nack timeout timer has been set and
//! we're awaiting an ack/nack on the sent message AND the callback from the AppOutbox subsystem
//! that the data has been consumed. These 2 things happen in parallel, the order in which they
//! happen is undefined.
OUT_AWAITING_REPLY_AND_OUTBOX_CALLBACK,
//! We're still awaiting the AppMessage ack/nack, but the AppOutbox subsystem has indicated that
//! the data has been consumed.
OUT_AWAITING_REPLY,
//! We're still awaiting the callback from the AppOutbox subsystem to indicate the data has been
//! consumed, but we have already received the AppMessage ack/nack. This state is possible because
//! acking at a lower layer (i.e. PPoGATT) can happen with a slight delay.
OUT_AWAITING_OUTBOX_CALLBACK,
} AppMessagePhaseOut;
typedef struct AppMessageCtxInbox {
bool is_open;
void *user_context;
AppMessageInboxReceived received_callback;
AppMessageInboxDropped dropped_callback;
} AppMessageCtxInbox;
typedef struct AppMessageCtxOutbox {
DictionaryIterator iterator;
size_t transmission_size_limit;
AppMessageAppOutboxData *app_outbox_message;
AppMessageOutboxSent sent_callback;
AppMessageOutboxFailed failed_callback;
void *user_context;
AppTimer *ack_nack_timer;
struct PACKED {
AppMessagePhaseOut phase:8;
uint8_t transaction_id;
uint16_t not_ready_throttle_ms; // used for throttling app task when outbox is not ready
AppMessageResult result:16;
};
} AppMessageCtxOutbox;
typedef struct AppMessageCtx {
AppMessageCtxInbox inbox;
AppMessageCtxOutbox outbox;
} AppMessageCtx;
_Static_assert(sizeof(AppMessageCtx) <= 112,
"AppMessageCtx must not exceed 112 bytes!");
typedef struct {
CommSession *session;
//! To give us some room for future changes. This structure ends up in a buffer that is sized by
//! the app, so we can't easily increase the size of this once shipped.
uint8_t padding[8];
uint8_t data[];
} AppMessageReceiverHeader;
#ifndef UNITTEST
_Static_assert(sizeof(AppMessageReceiverHeader) == 12,
"The size of AppMessageReceiverHeader cannot grow beyond 12 bytes!");
#endif
void app_message_init(void);
AppMessageResult app_message_inbox_open(AppMessageCtxInbox *inbox, size_t size_inbound);
void app_message_inbox_close(AppMessageCtxInbox *inbox);
typedef struct AppInboxConsumerInfo AppInboxConsumerInfo;
void app_message_inbox_receive(CommSession *session, AppMessagePush *push_message, size_t length,
AppInboxConsumerInfo *consumer_info);
AppMessageResult app_message_outbox_open(AppMessageCtxOutbox *outbox, size_t size_outbound);
void app_message_outbox_close(AppMessageCtxOutbox *outbox);
void app_message_out_handle_ack_nack_received(const AppMessageHeader *header);
void app_message_inbox_send_ack_nack_reply(CommSession *session, const uint8_t transaction_id,
AppMessageCmd cmd);
void app_message_inbox_handle_dropped_messages(uint32_t num_drops);
void app_message_app_protocol_msg_callback(CommSession *session,
const uint8_t* data, size_t length,
AppInboxConsumerInfo *consumer_info);
void app_message_app_protocol_system_nack_callback(CommSession *session,
const uint8_t* data, size_t length);

View file

@ -0,0 +1,316 @@
/*
* 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/applib_malloc.auto.h"
#include "applib/app_message/app_message_internal.h"
#include "applib/app_outbox.h"
#include "kernel/pbl_malloc.h"
#include "process_state/app_state/app_state.h"
#include "services/common/comm_session/session.h"
#include "syscall/syscall.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/math.h"
static void prv_outbox_prepare(AppMessageCtxOutbox *outbox);
static uint16_t prv_get_next_transaction_id(AppMessageCtxOutbox *outbox) {
return ++(outbox->transaction_id);
}
static void prv_transition_to_accepting(AppMessageCtxOutbox *outbox) {
outbox->phase = OUT_ACCEPTING;
if (outbox->result == APP_MSG_OK) {
if (outbox->sent_callback) {
outbox->sent_callback(&outbox->iterator, outbox->user_context);
}
} else {
if (outbox->failed_callback) {
outbox->failed_callback(&outbox->iterator, outbox->result, outbox->user_context);
}
}
}
static void prv_handle_nack_or_ack_timeout(AppMessageCtxOutbox *outbox,
AppMessageResult result) {
outbox->result = result;
if (outbox->phase == OUT_AWAITING_REPLY) {
prv_transition_to_accepting(outbox);
} else if (outbox->phase == OUT_AWAITING_REPLY_AND_OUTBOX_CALLBACK) {
outbox->phase = OUT_AWAITING_OUTBOX_CALLBACK;
} else {
WTF;
}
}
static void prv_handle_outbox_error_cb(void *data) {
AppMessageResult result = (AppMessageResult)(uintptr_t) data;
_Static_assert(sizeof(result) <= sizeof(data), "AppMessageResult expected to fit in void *");
AppMessageCtxOutbox *outbox = &app_state_get_app_message_ctx()->outbox;
if (outbox->phase != OUT_AWAITING_REPLY_AND_OUTBOX_CALLBACK) {
APP_LOG(LOG_LEVEL_ERROR, "Outbox failure, but unexpected state: %u", outbox->phase);
return;
}
// If app_message_outbox_handle_app_outbox_message_sent() has been called with an error,
// don't wait for an (N)ACK (it won't ever come), but finish right away:
outbox->result = result;
prv_transition_to_accepting(outbox);
}
//! Use sys_current_process_schedule_callback to maximize the stack space available to the
//! app's failed_callback.
static void prv_handle_outbox_error_async(AppMessageResult result) {
sys_current_process_schedule_callback(prv_handle_outbox_error_cb, (void *)(uintptr_t)result);
}
AppMessageResult app_message_outbox_open(AppMessageCtxOutbox *outbox, size_t size_outbound) {
const size_t size_maximum = app_message_outbox_size_maximum();
if (size_outbound > size_maximum) {
// Truncate if it's more than the max:
size_outbound = size_maximum;
} else if (size_outbound == size_maximum) {
APP_LOG(LOG_LEVEL_INFO, "app_message_open() called with app_message_outbox_size_maximum().");
APP_LOG(LOG_LEVEL_INFO,
"This consumes %"PRIu32" bytes of heap memory, potentially more in the future!",
(uint32_t)size_maximum);
}
if (size_outbound == 0) {
return APP_MSG_OK;
}
// Extra space needed by App Message protocol...:
size_outbound += APP_MSG_HDR_OVRHD_SIZE;
// ... and extra space header for app outbox message (not counting towards the transmission size):
outbox->app_outbox_message = applib_zalloc(sizeof(AppMessageAppOutboxData) + size_outbound);
if (outbox->app_outbox_message == NULL) {
return APP_MSG_OUT_OF_MEMORY;
}
outbox->transmission_size_limit = size_outbound;
outbox->transaction_id = 0;
prv_outbox_prepare(outbox);
outbox->phase = OUT_ACCEPTING;
return APP_MSG_OK;
}
static void prv_outbox_prepare(AppMessageCtxOutbox *outbox) {
AppMessagePush *push = (AppMessagePush *)outbox->app_outbox_message->payload;
dict_write_begin(&outbox->iterator,
(uint8_t *)&push->dictionary,
outbox->transmission_size_limit - APP_MSG_HDR_OVRHD_SIZE);
}
static void prv_stop_timer(AppMessageCtxOutbox *outbox) {
if (outbox->ack_nack_timer) {
app_timer_cancel(outbox->ack_nack_timer);
outbox->ack_nack_timer = NULL;
}
}
void app_message_outbox_close(AppMessageCtxOutbox *outbox) {
// Verify outbox phase.
if (outbox->phase == OUT_CLOSED) {
return;
}
// Cancel any outstanding timer.
prv_stop_timer(outbox);
outbox->transmission_size_limit = 0;
applib_free(outbox->app_outbox_message);
outbox->app_outbox_message = NULL;
// Finish by moving to the next phase.
outbox->phase = OUT_CLOSED;
}
static void prv_throttle(AppMessageCtxOutbox *outbox) {
if (outbox->not_ready_throttle_ms == 0) {
outbox->not_ready_throttle_ms = 1;
} else {
outbox->not_ready_throttle_ms = MIN(outbox->not_ready_throttle_ms * 2, 100 /*ms*/);
}
sys_psleep(outbox->not_ready_throttle_ms);
}
static bool prv_is_message_pending(AppMessagePhaseOut phase) {
return (phase == OUT_AWAITING_REPLY_AND_OUTBOX_CALLBACK ||
phase == OUT_AWAITING_REPLY ||
phase == OUT_AWAITING_OUTBOX_CALLBACK);
}
static bool prv_is_awaiting_ack(AppMessagePhaseOut phase) {
return (phase == OUT_AWAITING_REPLY_AND_OUTBOX_CALLBACK ||
phase == OUT_AWAITING_REPLY);
}
AppMessageResult app_message_outbox_begin(DictionaryIterator **iterator) {
AppMessageCtxOutbox *outbox = &app_state_get_app_message_ctx()->outbox;
if (iterator == NULL) {
return APP_MSG_INVALID_ARGS;
}
AppMessagePhaseOut phase = outbox->phase;
*iterator = NULL;
if (prv_is_message_pending(phase)) {
PBL_LOG(LOG_LEVEL_ERROR, "Can't call app_message_outbox_begin() now, wait for sent_callback!");
// See https://pebbletechnology.atlassian.net/browse/PBL-10146
// Workaround for apps that sit in a while() loop waiting on app_message_outbox_begin().
// Sleep a little longer each time we get a consecutive poll that returns failure.
prv_throttle(outbox);
return APP_MSG_BUSY;
} else if (phase == OUT_WRITING) {
PBL_LOG(LOG_LEVEL_ERROR,
"Must call app_message_outbox_send() before calling app_message_outbox_begin() again!");
return APP_MSG_INVALID_STATE;
} else if (phase == OUT_CLOSED) {
PBL_LOG(LOG_LEVEL_ERROR,
"Must call app_message_open() before calling app_message_outbox_begin()!");
return APP_MSG_INVALID_STATE;
}
// Reset the send state (dictionary, counters, etc.)
// We do this here, as this function is only called when we begin a new outbox,
// so the state should always be clean when we return successfully.
prv_outbox_prepare(outbox);
*iterator = &outbox->iterator;
outbox->phase = OUT_WRITING;
outbox->result = APP_MSG_OK;
return APP_MSG_OK;
}
static void ack_nack_timer_callback(void *data) {
AppMessageCtxOutbox *outbox = &app_state_get_app_message_ctx()->outbox;
outbox->ack_nack_timer = NULL;
if (!prv_is_awaiting_ack(outbox->phase)) {
// Reply was received and handled in the mean time, or app message was closed.
return;
}
prv_handle_nack_or_ack_timeout(outbox, APP_MSG_SEND_TIMEOUT);
}
void app_message_outbox_handle_app_outbox_message_sent(AppOutboxStatus status, void *cb_ctx) {
AppMessageCtxOutbox *outbox = &app_state_get_app_message_ctx()->outbox;
AppMessageSenderError e = (AppMessageSenderError)status;
if (e != AppMessageSenderErrorSuccess) {
if (e != AppMessageSenderErrorDisconnected) {
PBL_LOG(LOG_LEVEL_ERROR, "App message corrupted outbox? %"PRIu8, (uint8_t)e);
}
// Sleep a bit to prevent apps that hammer app_message_outbox_begin() when disconnected to
// become battery hogs:
prv_throttle(outbox);
prv_stop_timer(outbox);
// Just report any error as "not connected" to the app.
prv_handle_outbox_error_async(APP_MSG_NOT_CONNECTED);
} else {
// Only stop throttling if outbox message was consumed successfully:
outbox->not_ready_throttle_ms = 0;
if (outbox->phase == OUT_AWAITING_REPLY_AND_OUTBOX_CALLBACK) {
outbox->phase = OUT_AWAITING_REPLY;
return;
}
if (outbox->phase == OUT_AWAITING_OUTBOX_CALLBACK) {
prv_transition_to_accepting(outbox);
return;
}
}
}
AppMessageResult app_message_outbox_send(void) {
AppMessageCtxOutbox *outbox = &app_state_get_app_message_ctx()->outbox;
if (prv_is_message_pending(outbox->phase)) {
PBL_LOG(LOG_LEVEL_ERROR, "Can't call app_message_outbox_send() now, wait for sent_callback!");
return APP_MSG_BUSY;
}
if (outbox->phase != OUT_WRITING) {
return APP_MSG_INVALID_STATE;
}
const size_t transmission_size = dict_write_end(&outbox->iterator) + APP_MSG_HDR_OVRHD_SIZE;
if (transmission_size > outbox->transmission_size_limit) {
return APP_MSG_BUFFER_OVERFLOW;
}
uint8_t transaction_id = prv_get_next_transaction_id(outbox);
AppMessageAppOutboxData *app_outbox_message = outbox->app_outbox_message;
AppMessagePush *transmission = (AppMessagePush *)app_outbox_message->payload;
transmission->header.command = CMD_PUSH;
transmission->header.transaction_id = transaction_id;
sys_get_app_uuid(&transmission->uuid);
outbox->phase = OUT_AWAITING_REPLY_AND_OUTBOX_CALLBACK;
app_outbox_message->session = NULL;
app_outbox_message->endpoint_id = APP_MESSAGE_ENDPOINT_ID;
PBL_ASSERTN(!outbox->ack_nack_timer);
outbox->ack_nack_timer = app_timer_register(ACK_NACK_TIME_OUT_MS,
ack_nack_timer_callback, NULL);
app_outbox_send((const uint8_t *)app_outbox_message,
sizeof(AppMessageAppOutboxData) + transmission_size,
app_message_outbox_handle_app_outbox_message_sent, NULL);
return APP_MSG_OK;
}
void app_message_out_handle_ack_nack_received(const AppMessageHeader *header) {
AppMessageCtxOutbox *outbox = &app_state_get_app_message_ctx()->outbox;
if (!prv_is_awaiting_ack(outbox->phase)) {
PBL_LOG(LOG_LEVEL_ERROR, "Received (n)ack, but was not expecting one");
return;
}
if (outbox->transaction_id != header->transaction_id) {
PBL_LOG(LOG_LEVEL_ERROR, "Tx ID mismatch: %"PRIu8" != %"PRIu8,
outbox->transaction_id, header->transaction_id);
return;
}
prv_stop_timer(outbox);
if (header->command == CMD_NACK) {
prv_handle_nack_or_ack_timeout(outbox, APP_MSG_SEND_REJECTED);
return;
}
if (outbox->phase == OUT_AWAITING_REPLY_AND_OUTBOX_CALLBACK) {
outbox->phase = OUT_AWAITING_OUTBOX_CALLBACK;
return;
}
// phase == OUT_AWAITING_REPLY, because of !prv_is_awaiting_ack() check above.
prv_transition_to_accepting(outbox);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Unit test interfaces
AppTimer *app_message_outbox_get_ack_nack_timer(void) {
AppMessageCtxOutbox *outbox = &app_state_get_app_message_ctx()->outbox;
return outbox ? outbox->ack_nack_timer : NULL;
}

View file

@ -0,0 +1,70 @@
/*
* 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_receiver.h"
#include "applib/app_message/app_message_internal.h"
#include "applib/app_inbox.h"
#include "process_state/app_state/app_state.h"
#include "system/logging.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
// All these functions execute on App Task
void app_message_receiver_message_handler(const uint8_t *data, size_t length,
AppInboxConsumerInfo *consumer_info) {
AppMessageReceiverHeader *message = (AppMessageReceiverHeader *)data;
app_message_app_protocol_msg_callback(message->session, message->data,
length - sizeof(AppMessageReceiverHeader), consumer_info);
}
void app_message_receiver_dropped_handler(uint32_t num_dropped_messages) {
app_message_inbox_handle_dropped_messages(num_dropped_messages);
}
bool app_message_receiver_open(size_t buffer_size) {
AppInbox **app_message_inbox = app_state_get_app_message_inbox();
if (*app_message_inbox) {
PBL_LOG(LOG_LEVEL_INFO, "App PP receiver already open, not opening again");
return true;
}
// Make sure that at least one message of `buffer_size` will fit, by adding the header size:
// Allocate overhead for 1 (N)ACK + 1 Push message:
static const uint32_t min_num_messages = 2;
size_t final_buffer_size =
(sizeof(AppMessageReceiverHeader) * min_num_messages) + buffer_size + sizeof(AppMessageAck);
AppInbox *inbox = app_inbox_create_and_register(final_buffer_size, min_num_messages,
app_message_receiver_message_handler,
app_message_receiver_dropped_handler);
if (!inbox) {
// No logging needed, the inner calls log themselves already
return false;
}
*app_message_inbox = inbox;
return true;
}
void app_message_receiver_close(void) {
AppInbox **inbox = app_state_get_app_message_inbox();
if (!(*inbox)) {
PBL_LOG(LOG_LEVEL_INFO, "App PP receiver already closed");
return;
}
app_inbox_destroy_and_deregister(*inbox);
*inbox = NULL;
}

View file

@ -0,0 +1,23 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
bool app_message_receiver_open(size_t buffer_size);
void app_message_receiver_close(void);