mirror of
https://github.com/google/pebble.git
synced 2025-03-21 19:31:20 +00:00
503 lines
18 KiB
C
503 lines
18 KiB
C
|
/*
|
||
|
* Copyright 2024 Google LLC
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
#include "fake_session.h"
|
||
|
|
||
|
#include "comm/bt_lock.h"
|
||
|
|
||
|
#include "kernel/pbl_malloc.h"
|
||
|
#include "system/logging.h"
|
||
|
#include "services/common/comm_session/protocol.h"
|
||
|
#include "services/common/comm_session/session_send_buffer.h"
|
||
|
#include "services/common/system_task.h"
|
||
|
#include "util/circular_buffer.h"
|
||
|
#include "system/hexdump.h"
|
||
|
|
||
|
#include "clar_asserts.h"
|
||
|
|
||
|
#include "util/list.h"
|
||
|
|
||
|
#include <string.h>
|
||
|
|
||
|
extern void fake_system_task_callbacks_invoke_pending(void);
|
||
|
|
||
|
typedef struct CommSession {
|
||
|
ListNode node;
|
||
|
Transport *transport;
|
||
|
const TransportImplementation *transport_imp;
|
||
|
bool is_send_next_call_pending;
|
||
|
TransportDestination destination;
|
||
|
uint8_t *temp_write_buffer;
|
||
|
uint16_t endpoint_id;
|
||
|
uint16_t bytes_written;
|
||
|
uint16_t max_out_payload_length;
|
||
|
CircularBuffer send_buffer;
|
||
|
uint8_t storage[1024];
|
||
|
} CommSession;
|
||
|
|
||
|
static CommSession *s_session_head;
|
||
|
|
||
|
static int s_session_close_call_count;
|
||
|
static int s_session_open_call_count;
|
||
|
|
||
|
bool comm_session_is_valid(const CommSession *session) {
|
||
|
return list_contains((ListNode *) s_session_head, &session->node);
|
||
|
}
|
||
|
|
||
|
static bool prv_find_session_is_system_filter(ListNode *found_node, void *data) {
|
||
|
const CommSessionType requested_type = (const bool) (uintptr_t) data;
|
||
|
const TransportDestination destination = ((const CommSession *) found_node)->destination;
|
||
|
switch (requested_type) {
|
||
|
case CommSessionTypeApp:
|
||
|
return destination == TransportDestinationApp || destination == TransportDestinationHybrid;
|
||
|
case CommSessionTypeSystem:
|
||
|
return destination == TransportDestinationSystem || destination == TransportDestinationHybrid;
|
||
|
default:
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool comm_session_has_capability(CommSession *session, CommSessionCapability capability){
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
CommSession * comm_session_get_by_type(CommSessionType type) {
|
||
|
// TODO: This is not going to fly with multiple app sessions
|
||
|
CommSession *session;
|
||
|
bt_lock();
|
||
|
{
|
||
|
session = (CommSession *) list_find((ListNode *) s_session_head,
|
||
|
prv_find_session_is_system_filter,
|
||
|
(void *) (uintptr_t) type);
|
||
|
}
|
||
|
bt_unlock();
|
||
|
return session;
|
||
|
}
|
||
|
|
||
|
CommSession* comm_session_get_system_session(void) {
|
||
|
// TODO: What if Pebble App is connected via iSPP *and* PPoGATT ?
|
||
|
return comm_session_get_by_type(CommSessionTypeSystem);
|
||
|
}
|
||
|
|
||
|
CommSession* comm_session_get_current_app_session(void) {
|
||
|
// TODO: What if App is connected via iSPP *and* PPoGATT ?
|
||
|
return comm_session_get_by_type(CommSessionTypeApp);
|
||
|
}
|
||
|
|
||
|
void comm_session_close(CommSession *session, CommSessionCloseReason reason) {
|
||
|
cl_assert(list_contains((const ListNode *) s_session_head, &session->node));
|
||
|
if (session->temp_write_buffer) {
|
||
|
kernel_free(session->temp_write_buffer);
|
||
|
}
|
||
|
list_remove(&session->node, (ListNode **) &s_session_head, NULL);
|
||
|
kernel_free(session);
|
||
|
++s_session_close_call_count;
|
||
|
}
|
||
|
|
||
|
void comm_session_receive_router_write(CommSession *session,
|
||
|
const uint8_t *received_data,
|
||
|
size_t num_bytes_to_copy) {
|
||
|
PBL_LOG(LOG_LEVEL_DEBUG, "Received Data:");
|
||
|
PBL_HEXDUMP(LOG_LEVEL_DEBUG, received_data, num_bytes_to_copy);
|
||
|
}
|
||
|
|
||
|
bool comm_session_send_data(CommSession *session, uint16_t endpoint_id,
|
||
|
const uint8_t *data, size_t length, uint32_t timeout_ms) {
|
||
|
SendBuffer *sb = comm_session_send_buffer_begin_write(session, endpoint_id, length, timeout_ms);
|
||
|
if (!sb) {
|
||
|
return false;
|
||
|
}
|
||
|
comm_session_send_buffer_write(sb, data, length);
|
||
|
comm_session_send_buffer_end_write(sb);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
CommSession * comm_session_open(Transport *transport, const TransportImplementation *implementation,
|
||
|
TransportDestination destination) {
|
||
|
++s_session_open_call_count;
|
||
|
|
||
|
CommSession *session = kernel_malloc(sizeof(CommSession));
|
||
|
memset(session, 0, sizeof(*session));
|
||
|
*session = (const CommSession) {
|
||
|
.transport = transport,
|
||
|
.transport_imp = implementation,
|
||
|
.destination = destination,
|
||
|
.max_out_payload_length = COMM_MAX_OUTBOUND_PAYLOAD_SIZE,
|
||
|
};
|
||
|
|
||
|
const size_t max_pp_msg_size = session->max_out_payload_length + sizeof(PebbleProtocolHeader);
|
||
|
// If this fails, you need to bump up the size of the storage[] array in the fake CommSession
|
||
|
cl_assert(sizeof(session->storage) >= max_pp_msg_size);
|
||
|
circular_buffer_init(&session->send_buffer, session->storage, max_pp_msg_size);
|
||
|
|
||
|
s_session_head = (CommSession *) list_prepend((ListNode *) s_session_head, &session->node);
|
||
|
return session;
|
||
|
}
|
||
|
|
||
|
size_t comm_session_send_queue_get_length(const CommSession *session) {
|
||
|
cl_assert(list_contains((const ListNode *) s_session_head, &session->node));
|
||
|
return circular_buffer_get_read_space_remaining(&session->send_buffer);
|
||
|
}
|
||
|
|
||
|
size_t comm_session_send_queue_copy(CommSession *session, uint32_t start_off, size_t length,
|
||
|
uint8_t *data_out) {
|
||
|
cl_assert(data_out);
|
||
|
cl_assert(list_contains((const ListNode *) s_session_head, &session->node));
|
||
|
return circular_buffer_copy_offset(&session->send_buffer, start_off, data_out, length);
|
||
|
}
|
||
|
|
||
|
void comm_session_send_queue_consume(CommSession *session, size_t length) {
|
||
|
circular_buffer_consume(&session->send_buffer, length);
|
||
|
}
|
||
|
|
||
|
static void prv_send_next_kernel_bg_cb(void *data) {
|
||
|
CommSession *session = (CommSession *) data;
|
||
|
if (!list_contains((const ListNode *) s_session_head, (const ListNode *) session)) {
|
||
|
// Session closed in the mean time
|
||
|
return;
|
||
|
}
|
||
|
// Flip the flag before the send_next callback, so it can schedule again if needed.
|
||
|
session->is_send_next_call_pending = false;
|
||
|
|
||
|
// Kick the transport to send out the next bytes from the send buffer
|
||
|
const size_t read_space = comm_session_send_queue_get_length(session);
|
||
|
if (read_space) {
|
||
|
session->transport_imp->send_next(session->transport);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void comm_session_send_next(CommSession *session) {
|
||
|
if (session->is_send_next_call_pending) {
|
||
|
return;
|
||
|
}
|
||
|
system_task_add_callback(prv_send_next_kernel_bg_cb, session);
|
||
|
session->is_send_next_call_pending = true;
|
||
|
}
|
||
|
|
||
|
bool prv_filter_by_transport_callback(ListNode *node, void *data) {
|
||
|
return (((CommSession *) node)->transport == data);
|
||
|
}
|
||
|
|
||
|
CommSession * prv_find_session_by_transport(Transport *transport) {
|
||
|
return (CommSession *) list_find((ListNode *) s_session_head,
|
||
|
prv_filter_by_transport_callback, transport);
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// Send buffer fakes
|
||
|
|
||
|
size_t comm_session_send_buffer_get_max_payload_length(const CommSession *session) {
|
||
|
size_t max_length = 0;
|
||
|
if (comm_session_is_valid(session)) {
|
||
|
max_length = session->send_buffer.buffer_size - sizeof(PebbleProtocolHeader);
|
||
|
}
|
||
|
return max_length;
|
||
|
}
|
||
|
|
||
|
SendBuffer * comm_session_send_buffer_begin_write(CommSession *session, uint16_t endpoint_id,
|
||
|
size_t required_free_length,
|
||
|
uint32_t timeout_ms) {
|
||
|
if (!session) {
|
||
|
return NULL;
|
||
|
}
|
||
|
if (!comm_session_is_valid(session)) {
|
||
|
return NULL;
|
||
|
}
|
||
|
if (required_free_length + sizeof(PebbleProtocolHeader) >
|
||
|
circular_buffer_get_write_space_remaining(&session->send_buffer)) {
|
||
|
return NULL;
|
||
|
}
|
||
|
if (session->temp_write_buffer) {
|
||
|
// Already writing, fake doesn't support multiple tasks trying to write at the same time
|
||
|
return NULL;
|
||
|
}
|
||
|
session->temp_write_buffer = (uint8_t *) kernel_malloc(session->max_out_payload_length);
|
||
|
session->bytes_written = 0;
|
||
|
session->endpoint_id = endpoint_id;
|
||
|
return (SendBuffer *) session;
|
||
|
}
|
||
|
|
||
|
bool comm_session_send_buffer_write(SendBuffer *sb, const uint8_t *data, size_t length) {
|
||
|
CommSession *session = (CommSession *) sb;
|
||
|
cl_assert(session);
|
||
|
cl_assert(session->temp_write_buffer);
|
||
|
cl_assert(length + session->bytes_written <= session->max_out_payload_length);
|
||
|
|
||
|
memcpy(session->temp_write_buffer + session->bytes_written, data, length);
|
||
|
session->bytes_written += length;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void comm_session_send_buffer_end_write(SendBuffer *sb) {
|
||
|
CommSession *session = (CommSession *) sb;
|
||
|
cl_assert(session);
|
||
|
cl_assert(session->temp_write_buffer);
|
||
|
|
||
|
const PebbleProtocolHeader pp_header = {
|
||
|
.length = session->bytes_written,
|
||
|
.endpoint_id = session->endpoint_id,
|
||
|
};
|
||
|
|
||
|
circular_buffer_write(&session->send_buffer, (const uint8_t *) &pp_header, sizeof(pp_header));
|
||
|
circular_buffer_write(&session->send_buffer, session->temp_write_buffer, session->bytes_written);
|
||
|
|
||
|
kernel_free(session->temp_write_buffer);
|
||
|
session->temp_write_buffer = NULL;
|
||
|
session->endpoint_id = ~0;
|
||
|
session->bytes_written = 0;
|
||
|
}
|
||
|
|
||
|
static uint32_t s_responsiveness_max_period_s;
|
||
|
static bool s_responsiveness_latency_is_reduced;
|
||
|
static ResponsivenessGrantedHandler s_last_responsiveness_granted_handler;
|
||
|
|
||
|
void comm_session_set_responsiveness(
|
||
|
CommSession *session, BtConsumer consumer, ResponseTimeState state, uint16_t max_period_secs) {
|
||
|
comm_session_set_responsiveness_ext(session, consumer, state, max_period_secs, NULL);
|
||
|
}
|
||
|
|
||
|
void comm_session_set_responsiveness_ext(CommSession *session, BtConsumer consumer,
|
||
|
ResponseTimeState state, uint16_t max_period_secs,
|
||
|
ResponsivenessGrantedHandler granted_handler) {
|
||
|
|
||
|
s_responsiveness_max_period_s = max_period_secs;
|
||
|
|
||
|
if (state == ResponseTimeMiddle) {
|
||
|
s_responsiveness_latency_is_reduced = true;
|
||
|
} else if (state == ResponseTimeMax) {
|
||
|
s_responsiveness_latency_is_reduced = false;
|
||
|
}
|
||
|
|
||
|
s_last_responsiveness_granted_handler = granted_handler;
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// Session related functions
|
||
|
|
||
|
ResponsivenessGrantedHandler fake_comm_session_get_last_responsiveness_granted_handler(void) {
|
||
|
return s_last_responsiveness_granted_handler;
|
||
|
}
|
||
|
|
||
|
int fake_comm_session_open_call_count(void) {
|
||
|
return s_session_open_call_count;
|
||
|
}
|
||
|
|
||
|
int fake_comm_session_close_call_count(void) {
|
||
|
return s_session_close_call_count;
|
||
|
}
|
||
|
|
||
|
void fake_comm_session_process_send_next(void) {
|
||
|
CommSession *session = s_session_head;
|
||
|
while (session) {
|
||
|
CommSession *next = (CommSession *) session->node.next;
|
||
|
comm_session_send_next(session);
|
||
|
session = next;
|
||
|
}
|
||
|
fake_system_task_callbacks_invoke_pending();
|
||
|
}
|
||
|
|
||
|
uint32_t fake_comm_session_get_responsiveness_max_period(void) {
|
||
|
return s_responsiveness_max_period_s;
|
||
|
}
|
||
|
|
||
|
uint32_t fake_comm_session_is_latency_reduced(void) {
|
||
|
return s_responsiveness_latency_is_reduced;
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// Transport mock
|
||
|
|
||
|
typedef struct {
|
||
|
ListNode node;
|
||
|
uint16_t endpoint_id;
|
||
|
size_t length;
|
||
|
uint8_t data[];
|
||
|
} DataNode;
|
||
|
|
||
|
typedef struct {
|
||
|
ListNode node;
|
||
|
TransportDestination destination;
|
||
|
FakeTransportSentCallback sent_cb;
|
||
|
Uuid app_uuid;
|
||
|
CommSession *session;
|
||
|
|
||
|
//! When no sent_cb is used, data is appended to this list
|
||
|
DataNode *sent_data;
|
||
|
} FakeTransport;
|
||
|
|
||
|
static FakeTransport *s_fake_transport_head;
|
||
|
|
||
|
static void prv_fake_transport_send_next(Transport *transport) {
|
||
|
FakeTransport *fake_transport = (FakeTransport *) transport;
|
||
|
cl_assert_equal_b(list_contains((const ListNode *) s_fake_transport_head,
|
||
|
(const ListNode *) fake_transport), true);
|
||
|
CommSession *session = fake_transport->session;
|
||
|
PebbleProtocolHeader pp_header;
|
||
|
uint8_t *buffer = kernel_malloc(1024);
|
||
|
while (circular_buffer_copy(&session->send_buffer,
|
||
|
(uint8_t *) &pp_header, sizeof(pp_header)) == sizeof(pp_header)) {
|
||
|
circular_buffer_copy_offset(&session->send_buffer, sizeof(pp_header), buffer, pp_header.length);
|
||
|
if (fake_transport->sent_cb) {
|
||
|
fake_transport->sent_cb(pp_header.endpoint_id, buffer, pp_header.length);
|
||
|
} else {
|
||
|
PBL_LOG(LOG_LEVEL_DEBUG, "Sending Data to PP endpoint %u (0x%x):",
|
||
|
pp_header.endpoint_id, pp_header.endpoint_id);
|
||
|
PBL_HEXDUMP(LOG_LEVEL_DEBUG, buffer, pp_header.length);
|
||
|
|
||
|
DataNode *data_node = kernel_malloc(sizeof(DataNode) + pp_header.length);
|
||
|
list_init(&data_node->node);
|
||
|
data_node->endpoint_id = pp_header.endpoint_id;
|
||
|
data_node->length = pp_header.length;
|
||
|
memcpy(data_node->data, buffer, pp_header.length);
|
||
|
fake_transport->sent_data = (DataNode *) list_prepend(&fake_transport->sent_data->node,
|
||
|
&data_node->node);
|
||
|
}
|
||
|
circular_buffer_consume(&session->send_buffer, sizeof(pp_header) + pp_header.length);
|
||
|
}
|
||
|
kernel_free(buffer);
|
||
|
}
|
||
|
|
||
|
static void prv_fake_transport_reset(Transport *transport) {
|
||
|
cl_assert_(false, "Not implemented: prv_fake_transport_reset");
|
||
|
}
|
||
|
|
||
|
static const TransportImplementation s_fake_transport_implementation = {
|
||
|
.send_next = prv_fake_transport_send_next,
|
||
|
.reset = prv_fake_transport_reset,
|
||
|
};
|
||
|
|
||
|
Transport *fake_transport_create(TransportDestination destination,
|
||
|
const Uuid *app_uuid,
|
||
|
FakeTransportSentCallback sent_cb) {
|
||
|
if (app_uuid == NULL) {
|
||
|
cl_assert_(TransportDestinationSystem == destination ||
|
||
|
TransportDestinationHybrid == TransportDestinationSystem,
|
||
|
"When passing NULL app_uuid, the destination can only be System or Hybrid");
|
||
|
} else {
|
||
|
cl_assert_(TransportDestinationSystem == destination ||
|
||
|
TransportDestinationHybrid == TransportDestinationSystem,
|
||
|
"When passing an app_uuid, the destination can only be App or Hybrid");
|
||
|
}
|
||
|
FakeTransport *transport = (FakeTransport *) kernel_malloc(sizeof(FakeTransport));
|
||
|
*transport = (const FakeTransport) {
|
||
|
.destination = destination,
|
||
|
.sent_cb = sent_cb,
|
||
|
};
|
||
|
if (app_uuid) {
|
||
|
transport->app_uuid = *app_uuid;
|
||
|
}
|
||
|
s_fake_transport_head = (FakeTransport *) list_prepend((ListNode *) s_fake_transport_head,
|
||
|
&transport->node);
|
||
|
return (Transport *) transport;
|
||
|
}
|
||
|
|
||
|
CommSession *fake_transport_set_connected(Transport *transport, bool connected) {
|
||
|
FakeTransport *fake_transport = (FakeTransport *) transport;
|
||
|
if (connected) {
|
||
|
cl_assert_equal_p(fake_transport->session, NULL);
|
||
|
fake_transport->session = comm_session_open(transport, &s_fake_transport_implementation,
|
||
|
fake_transport->destination);
|
||
|
return fake_transport->session;
|
||
|
} else {
|
||
|
cl_assert(fake_transport->session);
|
||
|
comm_session_close(fake_transport->session, 0);
|
||
|
fake_transport->session = NULL;
|
||
|
return NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void fake_transport_set_sent_cb(Transport *transport, FakeTransportSentCallback sent_cb) {
|
||
|
cl_assert(transport);
|
||
|
FakeTransport *fake_transport = (FakeTransport *) transport;
|
||
|
fake_transport->sent_cb = sent_cb;
|
||
|
}
|
||
|
|
||
|
void fake_transport_assert_sent(Transport *transport, uint16_t index, uint16_t endpoint_id,
|
||
|
const uint8_t data[], size_t length) {
|
||
|
cl_assert(transport);
|
||
|
FakeTransport *fake_transport = (FakeTransport *)transport;
|
||
|
DataNode *data_node = fake_transport->sent_data;
|
||
|
for (uint16_t i = 0; i <= index; ++i) {
|
||
|
cl_assert_(data_node, "Sent out too few packets");
|
||
|
|
||
|
if (i == index) {
|
||
|
cl_assert_equal_i(data_node->endpoint_id, endpoint_id);
|
||
|
cl_assert_equal_i(data_node->length, length);
|
||
|
cl_assert_equal_m(data_node->data, data, length);
|
||
|
}
|
||
|
|
||
|
data_node = (DataNode *) data_node->node.next;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void fake_transport_assert_nothing_sent(Transport *transport) {
|
||
|
cl_assert(transport);
|
||
|
FakeTransport *fake_transport = (FakeTransport *)transport;
|
||
|
DataNode *data_node = fake_transport->sent_data;
|
||
|
cl_assert_equal_p(data_node, NULL);
|
||
|
}
|
||
|
|
||
|
void fake_transport_destroy(Transport *transport) {
|
||
|
FakeTransport *fake_transport = (FakeTransport *) transport;
|
||
|
cl_assert(transport);
|
||
|
cl_assert_equal_b(list_contains((const ListNode *)s_fake_transport_head,
|
||
|
(const ListNode *)fake_transport), true);
|
||
|
if (fake_transport->session) {
|
||
|
// Causes clean up of CommSession:
|
||
|
fake_transport_set_connected((Transport *)fake_transport, false /* connected */);
|
||
|
}
|
||
|
list_remove((ListNode *) fake_transport, (ListNode **) &s_fake_transport_head, NULL);
|
||
|
DataNode *data_node = fake_transport->sent_data;
|
||
|
while (data_node) {
|
||
|
DataNode *next_data_node = (DataNode *)data_node->node.next;
|
||
|
kernel_free(data_node);
|
||
|
data_node = next_data_node;
|
||
|
}
|
||
|
kernel_free(fake_transport);
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// Transport helper functions
|
||
|
|
||
|
bool fake_comm_session_send_buffer_write_raw_by_transport(Transport *transport,
|
||
|
const uint8_t *data, size_t length) {
|
||
|
CommSession *session = prv_find_session_by_transport(transport);
|
||
|
cl_assert(session);
|
||
|
return circular_buffer_write(&session->send_buffer, data, length);
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// Fake life cycle
|
||
|
|
||
|
void fake_comm_session_init(void) {
|
||
|
cl_assert_(s_fake_transport_head == NULL,
|
||
|
"Didn't clean up the fake transports? \
|
||
|
Call fake_comm_session_cleanup() if you don't want to clean them up manually.");
|
||
|
|
||
|
s_session_close_call_count = 0;
|
||
|
s_session_open_call_count = 0;
|
||
|
s_last_responsiveness_granted_handler = NULL;
|
||
|
}
|
||
|
|
||
|
void fake_comm_session_cleanup(void) {
|
||
|
FakeTransport *fake_transport = s_fake_transport_head;
|
||
|
while (fake_transport) {
|
||
|
FakeTransport *next = (FakeTransport *) fake_transport->node.next;
|
||
|
fake_transport_destroy((Transport *) fake_transport);
|
||
|
fake_transport = next;
|
||
|
}
|
||
|
}
|