pebble/tests/fakes/fake_session.c

503 lines
18 KiB
C
Raw Normal View History

/*
* 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;
}
}