mirror of
https://github.com/google/pebble.git
synced 2025-06-05 17:53:11 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
240
src/fw/applib/app_message/app_message.c
Normal file
240
src/fw/applib/app_message/app_message.c
Normal 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);
|
||||
}
|
418
src/fw/applib/app_message/app_message.h
Normal file
418
src/fw/applib/app_message/app_message.h
Normal 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 app’s performance. The more you use for
|
||||
//! AppMessage, the less space you’ll 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 can’t 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 can’t 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 can’t 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
|
120
src/fw/applib/app_message/app_message_inbox.c
Normal file
120
src/fw/applib/app_message/app_message_inbox.c
Normal 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);
|
||||
}
|
156
src/fw/applib/app_message/app_message_internal.h
Normal file
156
src/fw/applib/app_message/app_message_internal.h
Normal 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);
|
316
src/fw/applib/app_message/app_message_outbox.c
Normal file
316
src/fw/applib/app_message/app_message_outbox.c
Normal 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;
|
||||
}
|
70
src/fw/applib/app_message/app_message_receiver.c
Normal file
70
src/fw/applib/app_message/app_message_receiver.c
Normal 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;
|
||||
}
|
23
src/fw/applib/app_message/app_message_receiver.h
Normal file
23
src/fw/applib/app_message/app_message_receiver.h
Normal 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);
|
Loading…
Add table
Add a link
Reference in a new issue