Import of the watch repository from Pebble

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

View file

@ -0,0 +1,272 @@
/*
* 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 "services/common/comm_session/session_receive_router.h"
#include "services/common/system_task.h"
#include "clar.h"
#include <string.h>
// Stubs
///////////////////////////////////////////////////////////
#include "stubs_logging.h"
#include "stubs_passert.h"
typedef void (*CallbackEventCallback)(void *data);
static int s_kernel_main_schedule_count;
void launcher_task_add_callback(CallbackEventCallback callback, void *data) {
++s_kernel_main_schedule_count;
system_task_add_callback(callback, data);
}
// Fakes
///////////////////////////////////////////////////////////
#include "../../fakes/fake_pbl_malloc.h"
#include "../../fakes/fake_system_task.h"
#define FAKE_COMM_SESSION (CommSession *)1
// Helpers
///////////////////////////////////////////////////////////
extern const ReceiverImplementation g_default_kernel_receiver_implementation;
extern const PebbleTask g_default_kernel_receiver_opt_bg;
extern const PebbleTask g_default_kernel_receiver_opt_main;
typedef enum {
HandlerA = 0,
HandlerB,
HandlerC,
NumHandlers
} FakeProtocolHandlers;
static int s_handler_call_count[NumHandlers] = { 0 };
typedef struct {
size_t len;
uint8_t *buf;
} ExpectedData;
static ExpectedData s_expected_data;
static void prv_set_expected_data(void *data, size_t len) {
s_expected_data.buf = data;
s_expected_data.len = len;
}
static void prv_assert_data_matches_expected(const void *data, size_t len) {
cl_assert_equal_i(len, s_expected_data.len);
cl_assert(memcmp(s_expected_data.buf, data, len) == 0);
}
static void prv_assert_no_handler_calls(void) {
for (int i = 0; i < NumHandlers; i++) {
cl_assert_equal_i(s_handler_call_count[i], 0);
}
}
static void prv_endpoint_handler_a(
CommSession *session, const uint8_t *data, size_t length) {
s_handler_call_count[HandlerA]++;
prv_assert_data_matches_expected(data, length);
}
static void prv_endpoint_handler_b(
CommSession *session, const uint8_t *data, size_t length) {
s_handler_call_count[HandlerB]++;
prv_assert_data_matches_expected(data, length);
}
static void prv_endpoint_handler_c(
CommSession *session, const uint8_t *data, size_t length) {
s_handler_call_count[HandlerC]++;
prv_assert_data_matches_expected(data, length);
}
static const PebbleProtocolEndpoint s_endpoints[NumHandlers] = {
[0] = {
.handler = prv_endpoint_handler_a,
.receiver_opt = &g_default_kernel_receiver_opt_bg,
},
[1] = {
.handler = prv_endpoint_handler_b,
.receiver_opt = &g_default_kernel_receiver_opt_bg,
},
[2] = {
.handler = prv_endpoint_handler_c,
.receiver_opt = &g_default_kernel_receiver_opt_main,
},
};
// Tests
///////////////////////////////////////////////////////////
void test_default_kernel_receiver__cleanup(void) {
memset(s_handler_call_count, 0x0, sizeof(s_handler_call_count));
s_kernel_main_schedule_count = 0;
}
//! With one event in flight, walk through the prepare, write, finish happy
//! path. Ensure that the endpoint handler CB is run from kernel BG and that
//! we don't leak memory
void test_default_kernel_receiver__prepare_write_finish_single(void) {
char *data = "helloworld";
Receiver *receiver = g_default_kernel_receiver_implementation.prepare(
FAKE_COMM_SESSION, &s_endpoints[0], strlen(data));
cl_assert(receiver != NULL);
g_default_kernel_receiver_implementation.write(receiver, (uint8_t *)data, strlen(data));
prv_assert_no_handler_calls();
g_default_kernel_receiver_implementation.finish(receiver);
// CBs shouldn't immediately execute since they are pended on KernelBG
prv_assert_no_handler_calls();
prv_set_expected_data(data, strlen(data));
fake_system_task_callbacks_invoke_pending();
cl_assert_equal_i(s_handler_call_count[HandlerA], 1);
cl_assert_equal_i(fake_pbl_malloc_num_net_allocs(), 0);
}
//! Multiple sessions should be able to be transmitting messages concurrently
void test_default_kernel_receiver__prepare_write_finish_multiple_sessions(void) {
Receiver *receiver[NumHandlers];
CommSession *session[NumHandlers] = {
(CommSession *)1,
(CommSession *)2,
(CommSession *)3,
};
char *data[NumHandlers] = {
"Session 1 Data!!",
"This is Session 2 Data!",
"Session 3"
};
for (int i = 0; i < NumHandlers; i++) {
receiver[i] = g_default_kernel_receiver_implementation.prepare(
session[i], &s_endpoints[i], strlen(data[i]));
cl_assert(receiver[i] != NULL);
for (int j = 0; j < strlen(data[i]); j++) {
g_default_kernel_receiver_implementation.write(
receiver[i], (uint8_t *)&data[i][j], 1);
}
}
prv_assert_no_handler_calls();
for (int i = NumHandlers - 1; i >= 0; i--) {
int kernel_main_schedule_count_before = s_kernel_main_schedule_count;
g_default_kernel_receiver_implementation.finish(receiver[i]);
cl_assert_equal_i(s_handler_call_count[i], 0);
bool should_execute_on_kernel_main =
(s_endpoints[i].receiver_opt == &g_default_kernel_receiver_opt_main);
if (should_execute_on_kernel_main) {
cl_assert_equal_i(kernel_main_schedule_count_before + 1, s_kernel_main_schedule_count);
}
prv_set_expected_data(data[i], strlen(data[i]));
fake_system_task_callbacks_invoke_pending();
cl_assert_equal_i(s_handler_call_count[i], 1);
}
cl_assert_equal_i(fake_pbl_malloc_num_net_allocs(), 0);
}
//! It's possible the same session can receiver multiple messages before any
//! are processed on kernel BG. Make sure they do not interfere with one
//! another
void test_default_kernel_receiver__same_session_batched(void) {
const int batch_num = 10;
char data = 'a';
for (int i = 0; i < batch_num; i++) {
Receiver *receiver = g_default_kernel_receiver_implementation.prepare(
FAKE_COMM_SESSION, &s_endpoints[0], 1);
cl_assert(receiver != NULL);
g_default_kernel_receiver_implementation.write(
receiver, (uint8_t *)&data, 1);
g_default_kernel_receiver_implementation.finish(receiver);
data += 1;
}
prv_assert_no_handler_calls();
char expected_data = 'a';
for (int i = 0; i < batch_num; i++) {
prv_set_expected_data(&expected_data, 1);
fake_system_task_callbacks_invoke(1);
cl_assert_equal_i(s_handler_call_count[HandlerA], i + 1);
expected_data += 1;
}
cl_assert_equal_i(fake_pbl_malloc_num_net_allocs(), 0);
}
//! Make sure that if cleanup runs and we are partially through updating a
//! buffer the callback does not get called
void test_default_kernel_receiver__receiver_cleanup(void) {
char *data = "cleanup test!";
Receiver *receiver = g_default_kernel_receiver_implementation.prepare(
FAKE_COMM_SESSION, &s_endpoints[0], strlen(data));
cl_assert(receiver != NULL);
g_default_kernel_receiver_implementation.write(receiver, (uint8_t *)data, strlen(data));
prv_assert_no_handler_calls();
g_default_kernel_receiver_implementation.cleanup(receiver);
fake_system_task_callbacks_invoke_pending();
prv_assert_no_handler_calls();
cl_assert_equal_i(fake_pbl_malloc_num_net_allocs(), 0);
}
//! Test the case where a callback has been pended to kernelBG and we get a cleanup
void test_default_kernel_receiver__race_condition(void) {
char *data = "cleanup but finish ran first!";
Receiver *receiver = g_default_kernel_receiver_implementation.prepare(
FAKE_COMM_SESSION, &s_endpoints[0], strlen(data));
cl_assert(receiver != NULL);
g_default_kernel_receiver_implementation.write(receiver, (uint8_t *)data, strlen(data));
prv_assert_no_handler_calls();
g_default_kernel_receiver_implementation.finish(receiver);
g_default_kernel_receiver_implementation.cleanup(receiver);
// our msg should not have been freed since it was offloaded to kernelBG
cl_assert(fake_pbl_malloc_num_net_allocs() != 0);
prv_set_expected_data(data, strlen(data));
fake_system_task_callbacks_invoke_pending();
cl_assert_equal_i(s_handler_call_count[HandlerA], 1);
cl_assert_equal_i(fake_pbl_malloc_num_net_allocs(), 0);
}

View file

@ -0,0 +1,110 @@
/*
* 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 "services/common/comm_session/meta_endpoint.h"
#include "clar.h"
// Stubs
///////////////////////////////////////////////////////////
#include "stubs_bt_lock.h"
#include "stubs_hexdump.h"
#include "stubs_logging.h"
#include "stubs_passert.h"
#include "stubs_print.h"
// Fakes
///////////////////////////////////////////////////////////
#include "fake_kernel_malloc.h"
#include "fake_session.h"
#include "fake_system_task.h"
static Transport *s_transport;
static CommSession *s_session;
// Helpers
///////////////////////////////////////////////////////////
static void prv_process_and_assert_sent(const uint8_t data[], uint16_t length) {
const uint16_t meta_endpoint_id = 0;
fake_system_task_callbacks_invoke_pending();
fake_comm_session_process_send_next();
fake_transport_assert_sent(s_transport, 0 /* index */, meta_endpoint_id, data, length);
}
// Tests
///////////////////////////////////////////////////////////
void test_meta_endpoint__initialize(void) {
fake_kernel_malloc_init();
fake_kernel_malloc_mark();
fake_comm_session_init();
s_transport = fake_transport_create(TransportDestinationSystem, NULL, NULL);
s_session = fake_transport_set_connected(s_transport, true);
}
void test_meta_endpoint__cleanup(void) {
// Check for leaks:
fake_kernel_malloc_mark_assert_equal();
fake_kernel_malloc_deinit();
fake_comm_session_cleanup();
}
void test_meta_endpoint__send_meta_corrupted_message(void) {
const MetaResponseInfo meta_response_info = {
.session = s_session,
.payload = {
.error_code = MetaResponseCodeCorruptedMessage,
},
};
meta_endpoint_send_response_async(&meta_response_info);
const uint8_t expected_payload[] = { 0xd0 };
prv_process_and_assert_sent(expected_payload, sizeof(expected_payload));
}
void test_meta_endpoint__send_meta_disallowed_message(void) {
const MetaResponseInfo meta_response_info = {
.session = s_session,
.payload = {
.error_code = MetaResponseCodeDisallowed,
.endpoint_id = 0xabcd,
},
};
meta_endpoint_send_response_async(&meta_response_info);
const uint8_t expected_payload[] = { 0xdd, 0xab, 0xcd };
prv_process_and_assert_sent(expected_payload, sizeof(expected_payload));
}
void test_meta_endpoint__send_meta_unhandled_message(void) {
const MetaResponseInfo meta_response_info = {
.session = s_session,
.payload = {
.error_code = MetaResponseCodeUnhandled,
.endpoint_id = 0x1234,
},
};
meta_endpoint_send_response_async(&meta_response_info);
const uint8_t expected_payload[] = { 0xdc, 0x12, 0x34 };
prv_process_and_assert_sent(expected_payload, sizeof(expected_payload));
}

View file

@ -0,0 +1,458 @@
/*
* 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 "BSCAPI.h"
#include "clar.h"
#include "bluetooth/bt_driver_comm.h"
#include "services/common/comm_session/session.h"
#include "services/common/comm_session/session_remote_version.h"
#include "services/common/comm_session/session_send_buffer.h"
#include "services/common/comm_session/session_transport.h"
#include "kernel/events.h"
extern void comm_session_set_capabilities(CommSession *session,
CommSessionCapability capability_flags);
extern bool comm_session_is_valid(const CommSession *session);
extern void comm_session_init(void);
extern void comm_session_deinit(void);
extern void comm_session_send_next_immediately(CommSession *session);
// Stubs
///////////////////////////////////////////////////////////
#include "stubs_analytics.h"
#include "stubs_bt_lock.h"
#include "stubs_bt_stack.h"
#include "stubs_logging.h"
#include "stubs_passert.h"
#include "stubs_rand_ptr.h"
#include "stubs_syscall_internal.h"
void comm_session_analytics_open_session(CommSession *session) {
}
void comm_session_analytics_close_session(CommSession *session, CommSessionCloseReason reason) {
}
void comm_session_receive_router_cleanup(CommSession *session) {
}
void comm_session_send_queue_cleanup(CommSession *session) {
}
void bt_persistent_storage_set_cached_system_capabilities(
const PebbleProtocolCapabilities *capabilities) {
}
static uint16_t s_send_queue_length;
size_t comm_session_send_queue_get_length(const CommSession *session) {
return s_send_queue_length;
}
void fake_session_send_queue_set_length(uint32_t length) {
s_send_queue_length = length;
}
static bool s_dls_private_handle_disconnect_called;
void dls_private_handle_disconnect(void *data) {
s_dls_private_handle_disconnect_called = true;
}
static bool s_comm_session_event_put;
void event_put(PebbleEvent* event) {
if (event->type == PEBBLE_COMM_SESSION_EVENT &&
event->bluetooth.comm_session_event.is_system) {
s_comm_session_event_put = true;
};
}
void app_launch_trigger(void) {
}
void session_remote_version_start_requests(CommSession *session) {
}
static int s_send_next_count;
static void prv_send_next(Transport *transport) {
++s_send_next_count;
}
static int s_close_count;
Transport *s_last_closed_transport;
static void prv_close(Transport *transport) {
++s_close_count;
s_last_closed_transport = transport;
}
static int s_reset_count;
static void prv_reset(Transport *transport) {
++s_reset_count;
}
typedef enum {
TransportIDNull,
TransportID1,
TransportID2,
TransportID3,
NumTransportID,
} TransportID;
static Uuid *s_transport_uuid[NumTransportID];
static const Uuid *prv_get_uuid(Transport *transport) {
TransportID i = (uintptr_t)transport;
cl_assert(i >= TransportID1 && i <= TransportID3);
return s_transport_uuid[i];
}
static CommSessionTransportType prv_get_type(struct Transport *transport) {
return CommSessionTransportType_QEMU;
}
static const TransportImplementation s_transport_imp = {
.send_next = prv_send_next,
.close = prv_close,
.reset = prv_reset,
.get_uuid = prv_get_uuid,
.get_type = prv_get_type,
};
// Fakes
///////////////////////////////////////////////////////////
#include "fake_kernel_malloc.h"
#include "fake_session_send_buffer.h"
#include "fake_system_task.h"
#include "fake_app_manager.h"
static void prv_system_task_cb(void *data) {
CommSession *session = (CommSession *)data;
bt_driver_run_send_next_job(session, true);
}
bool bt_driver_comm_schedule_send_next_job(CommSession *data) {
// Implement this API in this test suite using the fake_system_task:
system_task_add_callback(prv_system_task_cb, data);
return true;
}
static bool s_bt_driver_comm_is_current_task_send_next_task;
bool bt_driver_comm_is_current_task_send_next_task(void) {
return s_bt_driver_comm_is_current_task_send_next_task;
}
// Tests
///////////////////////////////////////////////////////////
void test_session__initialize(void) {
stub_app_init();
comm_session_init();
fake_kernel_malloc_init();
fake_kernel_malloc_mark();
fake_session_send_buffer_init();
s_send_queue_length = 0;
memset(s_transport_uuid, 0, sizeof(s_transport_uuid));
s_send_next_count = 0;
s_reset_count = 0;
s_close_count = 0;
s_last_closed_transport = NULL;
s_dls_private_handle_disconnect_called = false;
s_comm_session_event_put = false;
s_bt_driver_comm_is_current_task_send_next_task = false;
}
void test_session__cleanup(void) {
// Check for leaks:
fake_kernel_malloc_mark_assert_equal();
fake_kernel_malloc_deinit();
fake_system_task_callbacks_cleanup();
}
void test_session__get_system_session_disconnected_returns_null(void) {
cl_assert_equal_p(comm_session_get_system_session(), NULL);
}
void test_session__get_app_session_disconnected_returns_null(void) {
cl_assert_equal_p(comm_session_get_current_app_session(), NULL);
}
void test_session__send_data_returns_false_for_null_session(void) {
const uint16_t endpoint_id = 1234;
uint8_t data[] = {1, 2, 3, 4};
cl_assert_equal_b(comm_session_send_data(NULL, endpoint_id,
data, sizeof(data),
COMM_SESSION_DEFAULT_TIMEOUT), false);
}
void test_session__basic_open_close(void) {
Transport *transport = (Transport *) TransportID1;
CommSession *session = comm_session_open(transport, &s_transport_imp,
TransportDestinationSystem);
cl_assert(session);
cl_assert_equal_b(comm_session_is_valid(session), true);
comm_session_close(session, CommSessionCloseReason_UnderlyingDisconnection);
cl_assert_equal_b(comm_session_is_valid(session), false);
}
void test_session__get_type_system(void) {
Transport *transport = (Transport *) TransportID1;
CommSession *session = comm_session_open(transport, &s_transport_imp,
TransportDestinationSystem);
cl_assert_equal_i(comm_session_get_type(session), CommSessionTypeSystem);
cl_assert_equal_p(comm_session_get_system_session(), session);
cl_assert_equal_p(comm_session_get_by_type(CommSessionTypeSystem), session);
comm_session_close(session, CommSessionCloseReason_UnderlyingDisconnection);
fake_system_task_callbacks_invoke_pending(); // get dls called
cl_assert_equal_b(s_dls_private_handle_disconnect_called, true);
cl_assert_equal_b(s_comm_session_event_put, true);
}
void test_session__get_type_app(void) {
Transport *transport = (Transport *) TransportID1;
CommSession *session = comm_session_open(transport, &s_transport_imp,
TransportDestinationApp);
cl_assert_equal_i(comm_session_get_type(session), CommSessionTypeApp);
cl_assert_equal_p(comm_session_get_current_app_session(), session);
cl_assert_equal_p(comm_session_get_by_type(CommSessionTypeApp), session);
comm_session_close(session, CommSessionCloseReason_UnderlyingDisconnection);
fake_system_task_callbacks_invoke_pending(); // get dls called
cl_assert_equal_b(s_dls_private_handle_disconnect_called, false);
cl_assert_equal_b(s_comm_session_event_put, false);
}
void test_session__last_system_session_wins(void) {
Transport *system_transport = (Transport *) TransportID1;
CommSession *system_session = comm_session_open(system_transport, &s_transport_imp,
TransportDestinationSystem);
cl_assert_equal_i(s_close_count, 0);
Transport *system_transport2 = (Transport *) TransportID2;
CommSession *system_session2 = comm_session_open(system_transport2, &s_transport_imp,
TransportDestinationSystem);
cl_assert(system_session2);
cl_assert_equal_p(s_last_closed_transport, system_transport);
cl_assert_equal_i(s_close_count, 1);
comm_session_close(system_session2, CommSessionCloseReason_UnderlyingDisconnection);
// The transport's .close is supposed to call this.
// The stub in this test doens't, so clean up manually:
comm_session_close(system_session, CommSessionCloseReason_UnderlyingDisconnection);
}
void test_session__get_app_session_multiple(void) {
Transport *system_transport = (Transport *) TransportID1;
CommSession *system_session = comm_session_open(system_transport, &s_transport_imp,
TransportDestinationSystem);
Uuid legacy_app_uuid = {
0xff, 0xc5, 0x24, 0x01, 0x4d, 0xbe, 0x40, 0x8b,
0xb7, 0x3a, 0x0e, 0x80, 0xef, 0x09, 0xaf, 0x74};
// Legacy transport (iAP) isn't aware of the app UUID, so don't set anything:
Transport *legacy_transport = (Transport *) TransportID2;
CommSession *legacy_app_session = comm_session_open(legacy_transport, &s_transport_imp,
TransportDestinationApp);
Uuid modern_app_uuid = {
0x04, 0xc5, 0x24, 0x01, 0x4d, 0xbe, 0x40, 0x8b,
0xb7, 0x3a, 0x0e, 0x80, 0xef, 0x09, 0xaf, 0x74};
Transport *modern_transport = (Transport *) TransportID3;
s_transport_uuid[TransportID3] = &modern_app_uuid;
CommSession *modern_app_session = comm_session_open(modern_transport, &s_transport_imp,
TransportDestinationApp);
stub_app_set_uuid(legacy_app_uuid);
cl_assert_equal_p(comm_session_get_current_app_session(), legacy_app_session);
stub_app_set_uuid(modern_app_uuid);
cl_assert_equal_p(comm_session_get_current_app_session(), modern_app_session);
stub_app_set_uuid((Uuid)UUID_INVALID);
cl_assert_equal_p(comm_session_get_current_app_session(), NULL);
stub_app_set_uuid((Uuid)UUID_SYSTEM);
cl_assert_equal_p(comm_session_get_current_app_session(), NULL);
comm_session_close(system_session, CommSessionCloseReason_UnderlyingDisconnection);
comm_session_close(legacy_app_session, CommSessionCloseReason_UnderlyingDisconnection);
comm_session_close(modern_app_session, CommSessionCloseReason_UnderlyingDisconnection);
}
void test_session__assert_if_deinit_and_transport_did_not_clean_up_properly(void) {
Transport *transport = (Transport *) TransportID1;
CommSession *session = comm_session_open(transport, &s_transport_imp,
TransportDestinationSystem);
cl_assert_equal_b(comm_session_is_valid(session), true);
// Expect assert when Transport didn't clean up after itself:
cl_assert_passert(comm_session_deinit());
comm_session_close(session, CommSessionCloseReason_UnderlyingDisconnection);
}
void test_session__send_next_deduping(void) {
Transport *transport = (Transport *) TransportID1;
CommSession *session = comm_session_open(transport, &s_transport_imp,
TransportDestinationSystem);
cl_assert_equal_b(comm_session_is_valid(session), true);
fake_session_send_queue_set_length(1234);
cl_assert_equal_i(fake_system_task_count_callbacks(), 0);
comm_session_send_next(session);
cl_assert_equal_i(fake_system_task_count_callbacks(), 1);
comm_session_send_next(session);
cl_assert_equal_i(fake_system_task_count_callbacks(), 1);
comm_session_send_next(session);
cl_assert_equal_i(fake_system_task_count_callbacks(), 1);
comm_session_send_next(session);
cl_assert_equal_i(fake_system_task_count_callbacks(), 1);
comm_session_send_next(session);
cl_assert_equal_i(fake_system_task_count_callbacks(), 1);
cl_assert_equal_i(s_send_next_count, 0);
fake_system_task_callbacks_invoke_pending();
cl_assert_equal_i(fake_system_task_count_callbacks(), 0);
cl_assert_equal_i(s_send_next_count, 1);
comm_session_send_next(session);
cl_assert_equal_i(fake_system_task_count_callbacks(), 1);
comm_session_send_next(session);
cl_assert_equal_i(fake_system_task_count_callbacks(), 1);
cl_assert_equal_i(s_send_next_count, 1);
fake_system_task_callbacks_invoke_pending();
cl_assert_equal_i(s_send_next_count, 2);
comm_session_close(session, CommSessionCloseReason_UnderlyingDisconnection);
}
void test_session__send_next_not_called_when_session_closed_in_mean_time(void) {
Transport *transport = (Transport *) TransportID1;
CommSession *session = comm_session_open(transport, &s_transport_imp,
TransportDestinationSystem);
cl_assert_equal_b(comm_session_is_valid(session), true);
fake_session_send_queue_set_length(1234);
cl_assert_equal_i(fake_system_task_count_callbacks(), 0);
comm_session_send_next(session);
comm_session_close(session, CommSessionCloseReason_UnderlyingDisconnection);
fake_system_task_callbacks_invoke_pending();
cl_assert_equal_i(fake_system_task_count_callbacks(), 0);
cl_assert_equal_i(s_send_next_count, 0);
}
static bool prv_schedule_send_next(CommSession *session) {
return true;
}
static bool s_is_current_task_schedule_task = false;
static bool prv_is_current_task_schedule_task(Transport *transport) {
return s_is_current_task_schedule_task;
}
void test_session__transport_send_next_task(void) {
TransportImplementation transport_imp = s_transport_imp;
transport_imp.schedule = prv_schedule_send_next;
transport_imp.is_current_task_schedule_task = prv_is_current_task_schedule_task;
extern bool comm_session_is_current_task_send_next_task(CommSession *session);
Transport *transport = (Transport *) TransportID1;
CommSession *session = comm_session_open(transport, &transport_imp,
TransportDestinationSystem);
s_is_current_task_schedule_task = true;
cl_assert_equal_b(comm_session_is_current_task_send_next_task(session), true);
s_is_current_task_schedule_task = false;
cl_assert_equal_b(comm_session_is_current_task_send_next_task(session), false);
comm_session_close(session, CommSessionCloseReason_UnderlyingDisconnection);
transport_imp.schedule = NULL;
transport_imp.is_current_task_schedule_task = NULL;
session = comm_session_open(transport, &transport_imp, TransportDestinationSystem);
s_bt_driver_comm_is_current_task_send_next_task = true;
cl_assert_equal_b(comm_session_is_current_task_send_next_task(session), true);
s_bt_driver_comm_is_current_task_send_next_task = false;
cl_assert_equal_b(comm_session_is_current_task_send_next_task(session), false);
comm_session_close(session, CommSessionCloseReason_UnderlyingDisconnection);
}
void test_session__reset_valid_session(void) {
Transport *transport = (Transport *) TransportID1;
CommSession *session = comm_session_open(transport, &s_transport_imp,
TransportDestinationSystem);
cl_assert_equal_b(comm_session_is_valid(session), true);
cl_assert_equal_i(s_reset_count, 0);
comm_session_reset(session);
cl_assert_equal_i(s_reset_count, 1);
comm_session_close(session, CommSessionCloseReason_UnderlyingDisconnection);
}
void test_session__reset_invalid_session(void) {
CommSession *invalid_session = (CommSession *) TransportID1;
cl_assert_equal_b(comm_session_is_valid(invalid_session), false);
cl_assert_equal_i(s_reset_count, 0);
comm_session_reset(invalid_session);
cl_assert_equal_i(s_reset_count, 0);
}
extern bool comm_session_send_next_is_scheduled(CommSession *session);
void test_session__send_next_is_schedule_flag_not_unset_after_immediate_call(void){
Transport *transport = (Transport *) TransportID1;
CommSession *session = comm_session_open(transport, &s_transport_imp,
TransportDestinationSystem);
cl_assert_equal_b(comm_session_is_valid(session), true);
comm_session_send_next(session);
cl_assert_equal_b(comm_session_send_next_is_scheduled(session), true);
// Calling comm_session_send_next_immediately should NOT result in the flag to get unset
comm_session_send_next_immediately(session);
cl_assert_equal_b(comm_session_send_next_is_scheduled(session), true);
fake_system_task_callbacks_invoke_pending();
cl_assert_equal_b(comm_session_send_next_is_scheduled(session), false);
comm_session_close(session, CommSessionCloseReason_UnderlyingDisconnection);
}
void test_session__capabilities(void) {
Transport *transport = (Transport *) TransportID1;
CommSession *session = comm_session_open(transport, &s_transport_imp,
TransportDestinationSystem);
for (int i = 0; i < (sizeof(int) * 8); ++i) {
CommSessionCapability capability = (1 << i);
cl_assert_equal_b(false, comm_session_has_capability(session, capability));
}
comm_session_set_capabilities(session, ~0);
for (int i = 0; i < (sizeof(int) * 8); ++i) {
CommSessionCapability capability = (1 << i);
cl_assert_equal_b(true, comm_session_has_capability(session, capability));
}
comm_session_close(session, CommSessionCloseReason_UnderlyingDisconnection);
}

View file

@ -0,0 +1,382 @@
/*
* 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 "BSCAPI.h"
#include "clar.h"
#include "kernel/events.h"
#include "services/common/comm_session/meta_endpoint.h"
#include "services/common/comm_session/session_receive_router.h"
#include "services/common/comm_session/session_remote_version.h"
#include "services/common/comm_session/session_transport.h"
#include "services/common/comm_session/test_endpoint_ids.h"
#include "system/logging.h"
// Stubs
///////////////////////////////////////////////////////////
#include "stubs_analytics.h"
#include "stubs_bt_lock.h"
#include "stubs_bt_stack.h"
#include "stubs_events.h"
#include "stubs_hexdump.h"
#include "stubs_logging.h"
#include "stubs_passert.h"
#include "stubs_rand_ptr.h"
#include "stubs_syscall_internal.h"
void app_launch_trigger(void) {
}
bool bt_driver_comm_schedule_send_next_job(CommSession *data) {
return true;
}
bool bt_driver_comm_is_current_task_send_next_task(void) {
return false;
}
void comm_session_analytics_inc_bytes_received(CommSession *session, uint16_t length) {
}
void comm_session_analytics_open_session(CommSession *session) {
}
void comm_session_analytics_close_session(CommSession *session, CommSessionCloseReason reason) {
}
void comm_session_send_queue_cleanup(CommSession *session) {
}
size_t comm_session_send_queue_get_length(const CommSession *session) {
return 0;
}
void dls_private_handle_disconnect(void *data) {
}
void session_remote_version_start_requests(CommSession *session) {
}
void bt_persistent_storage_set_cached_system_capabilities(
const PebbleProtocolCapabilities *capabilities) {
}
// Fakes
///////////////////////////////////////////////////////////
#include "fake_kernel_malloc.h"
#include "fake_session_send_buffer.h"
#include "fake_system_task.h"
#include "fake_app_manager.h"
MetaResponseInfo s_last_meta_response_info;
void meta_endpoint_send_response_async(const MetaResponseInfo *meta_response_info) {
s_last_meta_response_info = *meta_response_info;
}
#define assert_meta_response_sent(e) \
cl_assert_equal_i(e, s_last_meta_response_info.payload.error_code)
// Helpers
///////////////////////////////////////////////////////////
#define PP_MSG_BYTES(endpoint_id, length, ...) \
{ \
length & 0xff, length >> 8, \
endpoint_id && 0xff, endpoint_id >> 8, \
__VA_ARGS__ \
}
#define RECEIVE(...) \
{ \
const uint8_t partial_data[] = { __VA_ARGS__ }; \
comm_session_receive_router_write(s_session, partial_data, sizeof(partial_data)); \
}
static void prv_send_next(Transport *transport);
static void prv_reset(Transport *transport);
static void prv_set_connection_responsiveness(Transport *transport,
BtConsumer consumer,
ResponseTimeState state,
uint16_t max_period_secs,
ResponsivenessGrantedHandler granted_handler);
CommSession *s_session;
Transport *s_transport = (Transport *) ~0;
const TransportImplementation s_transport_implementation = {
.send_next = prv_send_next,
.reset = prv_reset,
.set_connection_responsiveness = prv_set_connection_responsiveness,
};
static void prv_send_next(Transport *transport) {
}
static void prv_reset(Transport *transport) {
}
static void prv_set_connection_responsiveness(Transport *transport,
BtConsumer consumer,
ResponseTimeState state,
uint16_t max_period_secs,
ResponsivenessGrantedHandler granted_handler) {
}
// Referenced from protocol_endppints_table.auto.h override header:
///////////////////////////////////////////////////////////
typedef enum {
TestEndpointPrivate,
TestEndpointPublic,
TestEndpointAny,
NumTestEndpoint,
} TestEndpoint;
static int s_protocol_callback_counts[NumTestEndpoint];
void private_test_protocol_msg_callback(CommSession *session,
const uint8_t* data, size_t length) {
++s_protocol_callback_counts[TestEndpointPrivate];
}
void public_test_protocol_msg_callback(CommSession *session,
const uint8_t* data, size_t length) {
++s_protocol_callback_counts[TestEndpointPublic];
}
void any_test_protocol_msg_callback(CommSession *session,
const uint8_t* data, size_t length) {
++s_protocol_callback_counts[TestEndpointAny];
}
static struct {
int foo;
} s_test_receiver_ctx;
static int s_prepare_count = 0;
static int s_finish_count = 0;
static int s_cleanup_count = 0;
static uint8_t s_write_buffer[1024];
static uint16_t s_write_length;
static bool s_prepare_return_null = false;
static Receiver * prv_system_test_receiver_prepare(CommSession *session,
const PebbleProtocolEndpoint *endpoint,
size_t total_msg_length) {
++s_prepare_count;
if (s_prepare_return_null) {
return NULL;
}
return (Receiver *) &s_test_receiver_ctx;
}
static void prv_system_test_receiver_write(Receiver *receiver,
const uint8_t *data, size_t length) {
cl_assert(s_write_length + length < sizeof(s_write_buffer));
memcpy(s_write_buffer + s_write_length, data, length);
s_write_length += length;
PBL_LOG(LOG_LEVEL_DEBUG, "Wrote %zu bytes", length);
}
static void prv_system_test_receiver_finish(Receiver *receiver) {
++s_finish_count;
}
static void prv_system_test_receiver_cleanup(Receiver *receiver) {
++s_cleanup_count;
}
const ReceiverImplementation g_system_test_receiver_imp = {
.prepare = prv_system_test_receiver_prepare,
.write = prv_system_test_receiver_write,
.finish = prv_system_test_receiver_finish,
.cleanup = prv_system_test_receiver_cleanup,
};
// Tests
///////////////////////////////////////////////////////////
void test_session_receive_router__initialize(void) {
s_session = comm_session_open(s_transport, &s_transport_implementation,
TransportDestinationSystem);
memset(s_protocol_callback_counts, 0, sizeof(s_protocol_callback_counts));
s_prepare_count = 0;
s_finish_count = 0;
s_cleanup_count = 0;
s_write_length = 0;
s_last_meta_response_info = (const MetaResponseInfo) {};
s_prepare_return_null = false;
}
void test_session_receive_router__cleanup(void) {
if (s_session) {
comm_session_close(s_session, CommSessionCloseReason_UnderlyingDisconnection);
s_session = NULL;
}
}
void test_session_receive_router__header_byte_by_byte(void) {
// Expect callback to "prepare" only after complete Pebble Protocol header has been received.
// Length high byte (big endian!)
RECEIVE(0x00);
cl_assert_equal_i(s_prepare_count, 0);
// Length low byte (big endian!)
RECEIVE(0x01);
cl_assert_equal_i(s_prepare_count, 0);
// Endpoint low byte (big endian!)
RECEIVE(PRIVATE_TEST_ENDPOINT_ID >> 8);
cl_assert_equal_i(s_prepare_count, 0);
// Endpoint low byte (big endian!)
RECEIVE(PRIVATE_TEST_ENDPOINT_ID & 0xff);
cl_assert_equal_i(s_prepare_count, 1);
}
void test_session_receive_router__unhandled_endpoint(void) {
// Expect "Unhandled" meta message to be replied to a message for an unknown endpoint.
// The message should get eaten and not interfere with whatever comes next.
// Length: 1, Endpoint ID: NON_EXISTENT_ENDPOINT_ID, Payload: 0x55
RECEIVE(0x00, 0x01, NON_EXISTENT_ENDPOINT_ID >> 8, NON_EXISTENT_ENDPOINT_ID & 0xff, 0x55);
cl_assert_equal_i(s_prepare_count, 0);
assert_meta_response_sent(MetaResponseCodeUnhandled);
// Length: 1, Endpoint ID: OTHER_NON_EXISTENT_ENDPOINT_ID, Payload: 0x55
RECEIVE(0x00, 0x01,
OTHER_NON_EXISTENT_ENDPOINT_ID >> 8, OTHER_NON_EXISTENT_ENDPOINT_ID & 0xff, 0x55);
cl_assert_equal_i(s_prepare_count, 0);
assert_meta_response_sent(MetaResponseCodeUnhandled);
// Length: 1, Endpoint ID: PRIVATE_TEST_ENDPOINT_ID, Payload: 0xaa
RECEIVE(0x00, 0x01, PRIVATE_TEST_ENDPOINT_ID >> 8, PRIVATE_TEST_ENDPOINT_ID & 0xff, 0xaa);
cl_assert_equal_i(s_prepare_count, 1);
}
void test_session_receive_router__unhandled_and_supported_concat(void) {
// Expect "Unhandled" meta message to be replied to a message for an unknown endpoint,
// the message should get eaten even if a supported message immediately follows.
// Length: 1, Endpoint ID: NON_EXISTENT_ENDPOINT_ID, Payload: 0x55
RECEIVE(0x00, 0x01, NON_EXISTENT_ENDPOINT_ID >> 8, NON_EXISTENT_ENDPOINT_ID & 0xff, 0x55,
0x00, 0x01, PRIVATE_TEST_ENDPOINT_ID >> 8, PRIVATE_TEST_ENDPOINT_ID & 0xff, 0xaa);
assert_meta_response_sent(MetaResponseCodeUnhandled);
cl_assert_equal_i(s_prepare_count, 1);
}
void test_session_receive_router__system_disallowed_endpoint(void) {
// Expect "Disallowed" meta message to be replied to a message for an endpoint that is disallowed
// by use over a system session.
// The message should get eaten and not interfere with whatever comes next.
// Length: 1, Endpoint ID: PUBLIC_TEST_ENDPOINT_ID, Payload: 0x55
RECEIVE(0x00, 0x01, PUBLIC_TEST_ENDPOINT_ID >> 8, PUBLIC_TEST_ENDPOINT_ID & 0xff, 0x55);
cl_assert_equal_i(s_prepare_count, 0);
assert_meta_response_sent(MetaResponseCodeDisallowed);
// Length: 1, Endpoint ID: PRIVATE_TEST_ENDPOINT_ID, Payload: 0xaa
RECEIVE(0x00, 0x01, PRIVATE_TEST_ENDPOINT_ID >> 8, PRIVATE_TEST_ENDPOINT_ID & 0xff, 0xaa);
cl_assert_equal_i(s_prepare_count, 1);
}
void test_session_receive_router__app_disallowed_endpoint(void) {
// Expect "Disallowed" meta message to be replied to a message for an endpoint that is disallowed
// by use over a app session.
// The message should get eaten and not interfere with whatever comes next.
comm_session_close(s_session, CommSessionCloseReason_UnderlyingDisconnection);
s_session = comm_session_open(s_transport, &s_transport_implementation,
TransportDestinationApp);
// Length: 1, Endpoint ID: PUBLIC_TEST_ENDPOINT_ID, Payload: 0xaa
RECEIVE(0x00, 0x01, PRIVATE_TEST_ENDPOINT_ID >> 8, PRIVATE_TEST_ENDPOINT_ID & 0xff, 0xaa);
cl_assert_equal_i(s_prepare_count, 0);
assert_meta_response_sent(MetaResponseCodeDisallowed);
// Length: 1, Endpoint ID: PUBLIC_TEST_ENDPOINT_ID, Payload: 0x55
RECEIVE(0x00, 0x01, PUBLIC_TEST_ENDPOINT_ID >> 8, PUBLIC_TEST_ENDPOINT_ID & 0xff, 0x55);
cl_assert_equal_i(s_prepare_count, 1);
}
void test_session_receive_router__ignore_message_if_no_receiver_could_be_prepared(void) {
// Expect an inbound message to be skipped/ignored if no Receiver could be prepared.
// The message should get eaten and not interfere with whatever comes next.
s_prepare_return_null = true;
// Length: 1, Endpoint ID: PRIVATE_TEST_ENDPOINT_ID, Payload: 0xaa
RECEIVE(0x00, 0x01, PRIVATE_TEST_ENDPOINT_ID >> 8, PRIVATE_TEST_ENDPOINT_ID & 0xff, 0xaa);
cl_assert_equal_i(s_prepare_count, 1);
cl_assert_equal_i(s_finish_count, 0);
s_prepare_return_null = false;
// Length: 1, Endpoint ID: PRIVATE_TEST_ENDPOINT_ID, Payload: 0xaa
RECEIVE(0x00, 0x01, PRIVATE_TEST_ENDPOINT_ID >> 8, PRIVATE_TEST_ENDPOINT_ID & 0xff, 0xaa);
cl_assert_equal_i(s_prepare_count, 2);
cl_assert_equal_i(s_finish_count, 1);
}
void test_session_receive_router__cleanup_receiver_if_session_is_closed(void) {
// Expect that when a partial message has been received, but then the session gets closed,
// that "cleanup" is called.
// Length: 1, Endpoint ID: PRIVATE_TEST_ENDPOINT_ID, Payload: not received yet
RECEIVE(0x00, 0x01, PRIVATE_TEST_ENDPOINT_ID >> 8, PRIVATE_TEST_ENDPOINT_ID & 0xff);
cl_assert_equal_i(s_prepare_count, 1);
comm_session_close(s_session, CommSessionCloseReason_UnderlyingDisconnection);
s_session = NULL;
cl_assert_equal_i(s_cleanup_count, 1);
}
void test_session_receive_router__payload_in_pieces(void) {
// Expect that when a message's payload is received in pieces, the complete payload will be
// written and finish will only be called after the whole payload has been received.
const uint8_t expected_payload[] = { 0xaa, 0xbb, 0xcc, 0xdd, 0xee };
// Length: 5, Endpoint ID: PRIVATE_TEST_ENDPOINT_ID, Payload: 0xaa (byte 1)
RECEIVE(0x00, sizeof(expected_payload),
PRIVATE_TEST_ENDPOINT_ID >> 8, PRIVATE_TEST_ENDPOINT_ID & 0xff,
0xaa);
cl_assert_equal_i(s_write_length, 1);
cl_assert_equal_i(s_finish_count, 0);
// Payload bytes 2, 3 and 4
RECEIVE(0xbb, 0xcc, 0xdd);
cl_assert_equal_i(s_write_length, 4);
cl_assert_equal_i(s_finish_count, 0);
// Last payload byte
RECEIVE(0xee,
// New message partial header: Length: 5, Endpoint ID: PRIVATE_TEST_ENDPOINT_ID
0x00, 0x01, PRIVATE_TEST_ENDPOINT_ID >> 8);
cl_assert_equal_i(s_finish_count, 1);
cl_assert_equal_i(s_write_length, sizeof(expected_payload));
cl_assert_equal_m(s_write_buffer, expected_payload, sizeof(expected_payload));
}

View file

@ -0,0 +1,171 @@
/*
* 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 "clar.h"
#include "services/common/bluetooth/bluetooth_persistent_storage.h"
#include "services/common/comm_session/session_internal.h"
#include "services/common/comm_session/session_remote_os.h"
#include "services/common/comm_session/session_remote_version.h"
#include "util/net.h"
static CommSession s_session;
extern void session_remote_version_protocol_msg_callback(CommSession *session,
const uint8_t *data, size_t length);
// Fakes & Stubs
////////////////////////////////////////////////////////////////////////////////////////////////////
#include "fake_pbl_malloc.h"
#include "fake_events.h"
#include "fake_new_timer.h"
#include "stubs_bt_lock.h"
#include "stubs_logging.h"
#include "stubs_mutex.h"
#include "stubs_passert.h"
#include "stubs_rtc.h"
void bt_driver_reconnect_notify_platform_bitfield(uint32_t p) {}
static bool s_session_is_system;
bool comm_session_is_system(CommSession *session) {
return s_session_is_system;
}
static bool s_session_is_valid;
bool comm_session_is_valid(const CommSession *session) {
return (session == &s_session) && s_session_is_valid;
}
static bool s_data_sent;
bool comm_session_send_data(CommSession *session, uint16_t endpoint_id,
const uint8_t* data, size_t length, uint32_t timeout_ms) {
// The request is just a single 0x00 byte to endpoint 0x11:
cl_assert_equal_i(endpoint_id, 0x11);
cl_assert_equal_i(length, 1);
cl_assert_equal_i(*data, 0x00 /* Command ID 'Request' */);
s_data_sent = true;
return true;
}
CommSessionCapability s_capability_flags;
void comm_session_set_capabilities(CommSession *session, CommSessionCapability capability_flags) {
cl_assert_equal_p(session, &s_session);
s_capability_flags = capability_flags;
}
BTBondingID bt_persistent_storage_store_bt_classic_pairing(BTDeviceAddress *address, SM128BitKey *link_key,
char *name, uint8_t *platform_bits) {
return 1;
}
// Helpers
////////////////////////////////////////////////////////////////////////////////////////////////////
static void (*s_launcher_task_callback)(void *data);
static void *s_launcher_task_callback_data;
void launcher_task_add_callback(void (*callback)(void *data), void *data) {
// Simple fake, can only handle one call
cl_assert_equal_p(s_launcher_task_callback, NULL);
s_launcher_task_callback = callback;
s_launcher_task_callback_data = data;
}
static void prv_process_and_assert_sent_request_data(bool expect_request_data_sent) {
s_data_sent = false;
s_launcher_task_callback(s_launcher_task_callback_data);
cl_assert_equal_b(s_data_sent, expect_request_data_sent);
}
static void prv_receive_v3_response(uint8_t major, uint8_t minor, uint8_t bugfix,
CommSessionCapability protocol_capabilities) {
union {
CommSessionCapability protocol_capabilities;
uint8_t byte[8]; // Little-endian!
} capabilities;
capabilities.protocol_capabilities = protocol_capabilities;
uint8_t response_data[] = {
0x01, // Command ID 'Response'
0x00, 0x00, 0x00, 0x00, // Deprecated library version
0x00, 0x00, 0x00, 0x00, // Deprecated capabilities
0x00, 0x00, 0x00, 0x00, // Platform (OS) bitfield
0x02, // Response version
major, minor, bugfix,
capabilities.byte[0],
capabilities.byte[1],
capabilities.byte[2],
capabilities.byte[3],
capabilities.byte[4],
capabilities.byte[5],
capabilities.byte[7],
};
session_remote_version_protocol_msg_callback(&s_session, response_data, sizeof(response_data));
}
// Tests
////////////////////////////////////////////////////////////////////////////////////////////////////
#define MAX_ATTEMPTS (3)
void test_session_remote_version__initialize(void) {
fake_event_init();
s_session = (CommSession) {};
s_data_sent = false;
s_session_is_valid = true;
s_session_is_system = true;
s_capability_flags = 0;
session_remote_version_start_requests(&s_session);
}
void test_session_remote_version__cleanup(void) {
s_launcher_task_callback = NULL;
s_launcher_task_callback_data = NULL;
fake_pbl_malloc_clear_tracking();
}
void test_session_remote_version__receive_invalid_msg(void) {
uint8_t invalid_msg = 0xff;
session_remote_version_protocol_msg_callback(&s_session, &invalid_msg, sizeof(invalid_msg));
cl_assert_equal_i(fake_event_get_count(), 0);
}
static const CommSessionCapability s_expected_capabilities =
(CommSessionAppMessage8kSupport |
CommSessionVoiceApiSupport);
void test_session_remote_version__system_session(void) {
s_session_is_system = true;
prv_receive_v3_response(3, 2, 1, s_expected_capabilities);
// Triggers PEBBLE_REMOTE_APP_INFO_EVENT:
cl_assert_equal_i(fake_event_get_count(), 1);
PebbleEvent e = fake_event_get_last();
cl_assert_equal_i(e.type, PEBBLE_REMOTE_APP_INFO_EVENT);
cl_assert_equal_i(s_capability_flags, s_expected_capabilities);
}
void test_session_remote_version__app_session(void) {
s_session_is_system = false;
prv_receive_v3_response(3, 2, 1, s_expected_capabilities);
// Triggers no event:
cl_assert_equal_i(fake_event_get_count(), 0);
cl_assert_equal_i(s_capability_flags, s_expected_capabilities);
}

View file

@ -0,0 +1,377 @@
/*
* 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 "services/common/comm_session/default_kernel_sender.h"
#include "services/common/comm_session/session_send_buffer.h"
#include "services/common/comm_session/session_transport.h"
#include "services/common/comm_session/session_internal.h"
#include "services/common/comm_session/session_send_queue.h"
#include "services/common/comm_session/protocol.h"
#include "util/net.h"
#include "util/size.h"
#include "FreeRTOS.h"
#include "semphr.h"
#include "clar.h"
extern SendBuffer * comm_session_send_buffer_create(bool is_system);
extern void comm_session_send_buffer_destroy(SendBuffer *sb);
extern SemaphoreHandle_t comm_session_send_buffer_write_semaphore(void);
extern T_STATIC const SessionSendJobImpl s_default_kernel_send_job_impl;
extern void comm_default_kernel_sender_deinit(void);
extern void comm_session_send_queue_cleanup(CommSession *session);
// Stubs
///////////////////////////////////////////////////////////
#include "stubs_bt_lock.h"
#include "stubs_logging.h"
#include "stubs_passert.h"
#include "stubs_analytics.h"
void comm_session_analytics_inc_bytes_sent(CommSession *session, uint16_t length) {
}
// Fakes
///////////////////////////////////////////////////////////
#include "fake_kernel_malloc.h"
#include "fake_queue.h"
#include "fake_rtc.h"
static CommSession s_session;
static CommSession *s_valid_session;
static void prv_cleanup_send_buffer(SendBuffer *sb) {
s_default_kernel_send_job_impl.free((SessionSendQueueJob *)sb);
}
bool comm_session_is_valid(const CommSession *session) {
if (!session) {
return false;
}
return (s_valid_session == session);
}
static int s_send_next_count = 0;
void comm_session_send_next(CommSession *session) {
++s_send_next_count;
}
void comm_session_send_next_immediately(CommSession *session) {
// Pretend to send out all the data:
size_t read_space = comm_session_send_queue_get_length(session);
comm_session_send_queue_consume(session, read_space);
}
static bool s_is_current_task_send_next_task = false;
bool comm_session_is_current_task_send_next_task(CommSession *session) {
return s_is_current_task_send_next_task;
}
// Tests
///////////////////////////////////////////////////////////
static const uint16_t ENDPOINT_ID = 1234;
static const uint32_t TIMEOUT_MS = 500;
void test_session_send_buffer__initialize(void) {
s_is_current_task_send_next_task = false;
s_session = (const CommSession) {};
fake_kernel_malloc_init();
fake_kernel_malloc_enable_stats(true);
fake_kernel_malloc_mark();
s_send_next_count = 0;
comm_default_kernel_sender_init();
}
void test_session_send_buffer__cleanup(void) {
comm_default_kernel_sender_deinit();
// Check for leaks:
fake_kernel_malloc_mark_assert_equal();
fake_kernel_malloc_deinit();
}
void test_session_send_buffer__null_session(void) {
cl_assert_equal_p(NULL, comm_session_send_buffer_begin_write(NULL, ENDPOINT_ID, 1, TIMEOUT_MS));
}
void test_session_send_buffer__begin_write_with_more_than_max_payload(void) {
s_valid_session = &s_session;
size_t max_length = comm_session_send_buffer_get_max_payload_length(&s_session);
SendBuffer *write_sb = comm_session_send_buffer_begin_write(&s_session, ENDPOINT_ID,
max_length + 1,
TIMEOUT_MS);
cl_assert_equal_p(write_sb, NULL);
}
TickType_t prv_session_closed_yield_cb(QueueHandle_t handle) {
if (s_valid_session) {
comm_session_send_queue_cleanup(s_valid_session);
s_valid_session = NULL;
}
return 10;
}
TickType_t prv_receive_but_no_bytes_freed_yield_cb(QueueHandle_t handle) {
fake_rtc_increment_ticks(100);
xSemaphoreGive(handle);
return 100;
}
void test_session_send_buffer__not_enough_space_in_time(void) {
s_valid_session = &s_session;
// Fill the send buffer completely:
const size_t max_length = comm_session_send_buffer_get_max_payload_length(&s_session);
SendBuffer *write_sb = comm_session_send_buffer_begin_write(&s_session, ENDPOINT_ID,
max_length /* required_free_length */,
TIMEOUT_MS);
cl_assert(write_sb);
uint8_t fake_data[max_length];
memset(fake_data, 0, max_length);
comm_session_send_buffer_write(write_sb, fake_data, max_length);
comm_session_send_buffer_end_write(write_sb);
// Set a yield callback that gives the semph in time but does not clear out the send buffer:
SemaphoreHandle_t write_semph = comm_session_send_buffer_write_semaphore();
fake_queue_set_yield_callback(write_semph, prv_receive_but_no_bytes_freed_yield_cb);
// Try to begin writing again, requesting only one byte:
SendBuffer *write_sb2 = comm_session_send_buffer_begin_write(&s_session, ENDPOINT_ID,
1 /* required_free_length */,
TIMEOUT_MS);
cl_assert_equal_p(write_sb2, NULL);
prv_cleanup_send_buffer(write_sb);
}
void test_session_send_buffer__multiple_smaller_messages(void) {
s_valid_session = &s_session;
// This length excludes the sizeof(PebbleProtocolHeader).
size_t bytes_free = comm_session_send_buffer_get_max_payload_length(&s_session);
bytes_free += sizeof(PebbleProtocolHeader);
const int num_sbs = 1 + (bytes_free / (sizeof(PebbleProtocolHeader) + 1 /* payload_length */));
SendBuffer *write_sb[num_sbs];
memset(write_sb, 0, sizeof(write_sb));
for (int i = 0; bytes_free > 0 && i < ARRAY_LENGTH(write_sb); ++i) {
size_t payload_length = 1;
bytes_free -= sizeof(PebbleProtocolHeader) + payload_length;
// If we cannot fit another message after this one, increment the length to use up the space:
if (bytes_free <= (sizeof(PebbleProtocolHeader) + payload_length)) {
payload_length += bytes_free;
bytes_free = 0;
}
write_sb[i] = comm_session_send_buffer_begin_write(&s_session, ENDPOINT_ID,
payload_length, TIMEOUT_MS);
uint8_t fake_data[payload_length];
memset(fake_data, 0, payload_length);
comm_session_send_buffer_write(write_sb[i], fake_data, payload_length);
comm_session_send_buffer_end_write(write_sb[i]);
}
// Can't write another message:
cl_assert_equal_p(NULL, comm_session_send_buffer_begin_write(&s_session, ENDPOINT_ID,
1 /* length */, TIMEOUT_MS));
for (int i = 0; i < ARRAY_LENGTH(write_sb); ++i) {
if (!write_sb[i]) {
break;
}
prv_cleanup_send_buffer(write_sb[i]);
}
}
void test_session_send_buffer__not_enough_space_kernel_bg(void) {
s_valid_session = &s_session;
// Fill the send buffer completely:
const size_t max_length = comm_session_send_buffer_get_max_payload_length(&s_session);
SendBuffer *write_sb = comm_session_send_buffer_begin_write(&s_session, ENDPOINT_ID,
max_length /* required_free_length */,
TIMEOUT_MS);
uint8_t fake_data[max_length];
memset(fake_data, 0, max_length);
comm_session_send_buffer_write(write_sb, fake_data, max_length);
comm_session_send_buffer_end_write(write_sb);
// Pretend the current task is the same task that processes "send_next".
// Pretend to execute a callback that was scheduled already before the previous write caused
// a "send_next" callback to be scheduled.
s_is_current_task_send_next_task = true;
// Set a yield callback that gives the semph in time but does not clear out the send buffer:
SemaphoreHandle_t write_semph = comm_session_send_buffer_write_semaphore();
fake_queue_set_yield_callback(write_semph, prv_receive_but_no_bytes_freed_yield_cb);
// Try to begin writing again, requesting only one byte:
SendBuffer *write_sb2 = comm_session_send_buffer_begin_write(&s_session, ENDPOINT_ID,
1 /* required_free_length */,
TIMEOUT_MS);
// Because the ..._begin_write() call happened from the BT02 task, expect the data to be
// sent out immediately (we'd timeout or deadlock if an infinite timeout was set)
cl_assert(write_sb2);
comm_session_send_buffer_end_write(write_sb2);
// write_sb is already cleaned up because it got sent out
prv_cleanup_send_buffer(write_sb2);
}
void test_session_send_buffer__writing_but_then_session_closed(void) {
s_valid_session = &s_session;
// Fill the send buffer completely:
const size_t max_length = comm_session_send_buffer_get_max_payload_length(&s_session);
SendBuffer *write_sb = comm_session_send_buffer_begin_write(&s_session, ENDPOINT_ID,
max_length /* required_free_length */,
TIMEOUT_MS);
uint8_t fake_data[max_length];
memset(fake_data, 0, max_length);
comm_session_send_buffer_write(write_sb, fake_data, max_length);
comm_session_send_buffer_end_write(write_sb);
// Set a yield callback that gives the semph in time but closes the session:
SemaphoreHandle_t write_semph = comm_session_send_buffer_write_semaphore();
fake_queue_set_yield_callback(write_semph, prv_session_closed_yield_cb);
// Try to begin writing again, requesting only one byte:
write_sb = comm_session_send_buffer_begin_write(&s_session, ENDPOINT_ID,
1 /* required_free_length */,
TIMEOUT_MS);
cl_assert_equal_p(write_sb, NULL);
// ..send_buffer_destroy() is already called in the yield cb
}
void test_session_send_buffer__write_beyond_available_space(void) {
s_valid_session = &s_session;
// Fill the send buffer completely:
const size_t max_length = comm_session_send_buffer_get_max_payload_length(&s_session);
SendBuffer *write_sb = comm_session_send_buffer_begin_write(&s_session, ENDPOINT_ID,
max_length /* required_free_length */,
TIMEOUT_MS);
uint8_t fake_data[max_length];
memset(fake_data, 0, max_length);
cl_assert_equal_b(comm_session_send_buffer_write(write_sb, fake_data, max_length), true);
// Try writing another byte (expect false returned):
cl_assert_equal_b(comm_session_send_buffer_write(write_sb, fake_data, max_length), false);
comm_session_send_buffer_end_write(write_sb);
prv_cleanup_send_buffer(write_sb);
}
void test_session_send_buffer__send_queue_interface(void) {
s_valid_session = &s_session;
// Fill the send buffer completely:
const size_t max_payload_length = comm_session_send_buffer_get_max_payload_length(&s_session);
SendBuffer *write_sb = comm_session_send_buffer_begin_write(&s_session, ENDPOINT_ID,
max_payload_length /* required_free_length */,
TIMEOUT_MS);
uint8_t fake_data_payload[max_payload_length];
for (int i = 0; i < max_payload_length; ++i) {
fake_data_payload[i] = i % 0xff;
}
// Write in two parts:
size_t second_write_length = max_payload_length - (max_payload_length / 2);
cl_assert_equal_b(comm_session_send_buffer_write(write_sb,
fake_data_payload,
max_payload_length - second_write_length), true);
cl_assert_equal_b(comm_session_send_buffer_write(write_sb,
fake_data_payload + second_write_length,
second_write_length), true);
cl_assert_equal_i(s_send_next_count, 0);
comm_session_send_buffer_end_write(write_sb);
// Expect comm_session_send_next to be called to trigger the transport:
cl_assert_equal_i(s_send_next_count, 1);
// Exercise the transport interface:
const SessionSendQueueJob *job = (const SessionSendQueueJob *)write_sb;
// ..._get_read_space_remaining():
size_t expected_bytes_incl_pebble_protocol_header =
max_payload_length + sizeof(PebbleProtocolHeader);
size_t length = s_default_kernel_send_job_impl.get_length(job);
cl_assert_equal_i(length, expected_bytes_incl_pebble_protocol_header);
// ..._copy():
uint8_t pp_data_out[expected_bytes_incl_pebble_protocol_header];
size_t bytes_copied =
s_default_kernel_send_job_impl.copy(job, 0,
expected_bytes_incl_pebble_protocol_header,
pp_data_out);
cl_assert_equal_i(bytes_copied, expected_bytes_incl_pebble_protocol_header);
PebbleProtocolHeader *header = (PebbleProtocolHeader *) pp_data_out;
cl_assert_equal_i(header->length, htons(max_payload_length));
cl_assert_equal_i(header->endpoint_id, htons(ENDPOINT_ID));
cl_assert_equal_i(memcmp(pp_data_out + sizeof(PebbleProtocolHeader),
fake_data_payload, max_payload_length), 0);
// ..._copy() with offset:
int offset = 2;
bytes_copied =
s_default_kernel_send_job_impl.copy(job, offset,
expected_bytes_incl_pebble_protocol_header,
pp_data_out);
cl_assert_equal_i(bytes_copied, expected_bytes_incl_pebble_protocol_header - offset);
header = (PebbleProtocolHeader *) (pp_data_out - offset);
cl_assert_equal_i(header->endpoint_id, htons(ENDPOINT_ID));
cl_assert_equal_i(memcmp(pp_data_out + sizeof(PebbleProtocolHeader) - offset,
fake_data_payload, max_payload_length - offset), 0);
// ..._get_read_pointer():
uint16_t bytes_read = 0;
uint16_t read_space;
const uint8_t *data_out;
while ((read_space = s_default_kernel_send_job_impl.get_read_pointer(job, &data_out))) {
PebbleProtocolHeader *header = (PebbleProtocolHeader *) data_out;
if (bytes_read == 0) {
cl_assert(read_space >= sizeof(PebbleProtocolHeader));
cl_assert_equal_i(header->length, htons(max_payload_length));
cl_assert_equal_i(header->endpoint_id, htons(ENDPOINT_ID));
cl_assert_equal_i(memcmp(data_out + sizeof(PebbleProtocolHeader),
fake_data_payload,
read_space - sizeof(PebbleProtocolHeader)), 0);
} else {
cl_assert_equal_i(memcmp(data_out,
fake_data_payload + bytes_read - sizeof(PebbleProtocolHeader),
read_space), 0);
}
s_default_kernel_send_job_impl.consume(job, read_space);
bytes_read += read_space;
}
cl_assert_equal_i(bytes_read, expected_bytes_incl_pebble_protocol_header);
cl_assert_equal_i(comm_session_send_queue_get_length(s_valid_session), 0);
prv_cleanup_send_buffer(write_sb);
}

View file

@ -0,0 +1,303 @@
/*
* 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 "clar.h"
#include "services/common/comm_session/session_internal.h"
#include "services/common/comm_session/session_send_queue.h"
#include "util/math.h"
extern void comm_session_send_queue_cleanup(CommSession *session);
// Fakes & Stubs
////////////////////////////////////////////////////////////////////////////////////////////////////
#include "fake_kernel_malloc.h"
#include "stubs_bt_lock.h"
#include "stubs_logging.h"
#include "stubs_passert.h"
static CommSession s_session;
static CommSession *s_valid_session;
void comm_session_analytics_inc_bytes_sent(CommSession *session, uint16_t length) {
}
bool comm_session_is_valid(const CommSession *session) {
if (!session) {
return false;
}
return (s_valid_session == session);
}
void comm_session_send_next(CommSession *session) {
}
// Helpers
////////////////////////////////////////////////////////////////////////////////////////////////////
typedef struct {
SessionSendQueueJob job;
size_t consumed_length;
size_t length;
uint8_t data[];
} TestSendJob;
static size_t prv_get_length(const TestSendJob *sb) {
return (sb->length - sb->consumed_length);
}
static const uint8_t *prv_get_read_pointer(const TestSendJob *sb) {
return (sb->data + sb->consumed_length);
}
static size_t prv_send_job_impl_get_length(const SessionSendQueueJob *send_job) {
return prv_get_length((TestSendJob *)send_job);
}
size_t prv_send_job_impl_copy(const SessionSendQueueJob *send_job, int start_offset,
size_t length, uint8_t *data_out) {
TestSendJob *sb = (TestSendJob *)send_job;
const size_t length_remaining = prv_get_length(sb);
const size_t length_after_offset = (length_remaining - start_offset);
const size_t length_to_copy = MIN(length_after_offset, length);
memcpy(data_out, prv_get_read_pointer(sb) + start_offset, length_to_copy);
return length_to_copy;
}
size_t prv_send_job_impl_get_read_pointer(const SessionSendQueueJob *send_job,
const uint8_t **data_out) {
TestSendJob *sb = (TestSendJob *)send_job;
*data_out = prv_get_read_pointer(sb);
return prv_get_length(sb);
}
void prv_send_job_impl_consume(const SessionSendQueueJob *send_job, size_t length) {
TestSendJob *sb = (TestSendJob *)send_job;
sb->consumed_length += length;
}
static int s_free_count;
void prv_send_job_impl_free(SessionSendQueueJob *send_job) {
kernel_free(send_job);
++s_free_count;
}
static const SessionSendJobImpl s_test_job_impl = {
.get_length = prv_send_job_impl_get_length,
.copy = prv_send_job_impl_copy,
.get_read_pointer = prv_send_job_impl_get_read_pointer,
.consume = prv_send_job_impl_consume,
.free = prv_send_job_impl_free,
};
SessionSendQueueJob *prv_create_test_job(const uint8_t *data, size_t length) {
TestSendJob *job = kernel_malloc(sizeof(TestSendJob) + length);
cl_assert(job);
*job = (const TestSendJob) {
.job = {
.impl = &s_test_job_impl,
},
.length = length,
};
if (length && data) {
memcpy(job->data, data, length);
}
return (SessionSendQueueJob *)job;
}
// Tests
////////////////////////////////////////////////////////////////////////////////////////////////////
static const uint8_t TEST_DATA[] = {0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
void test_session_send_queue__initialize(void) {
s_valid_session = &s_session;
s_free_count = 0;
fake_kernel_malloc_init();
fake_kernel_malloc_enable_stats(true);
fake_kernel_malloc_mark();
s_session = (const CommSession) {};
}
void test_session_send_queue__cleanup(void) {
if (s_valid_session) {
comm_session_send_queue_cleanup(s_valid_session);
}
// Check for leaks:
fake_kernel_malloc_mark_assert_equal();
fake_kernel_malloc_deinit();
}
void test_session_send_queue__get_length_returns_summed_length_of_all_jobs(void) {
cl_assert_equal_i(0, comm_session_send_queue_get_length(s_valid_session));
int num_jobs = 3;
for (int i = 0; i < num_jobs; ++i) {
SessionSendQueueJob *job = prv_create_test_job(TEST_DATA, sizeof(TEST_DATA));
comm_session_send_queue_add_job(s_valid_session, &job);
cl_assert(job);
cl_assert_equal_i((i + 1) * sizeof(TEST_DATA),
comm_session_send_queue_get_length(s_valid_session));
}
}
static void prv_add_jobs(int num_jobs) {
for (int i = 0; i < num_jobs; ++i) {
SessionSendQueueJob *job = prv_create_test_job(TEST_DATA, sizeof(TEST_DATA));
comm_session_send_queue_add_job(s_valid_session, &job);
cl_assert(job);
}
}
void test_session_send_queue__copy_empty_queue(void) {
uint8_t data_out[2];
cl_assert_equal_i(0,
comm_session_send_queue_copy(s_valid_session, 0, sizeof(data_out), data_out));
}
void test_session_send_queue__copy_less_than_head_job_zero_offset(void) {
int num_jobs = 3;
prv_add_jobs(num_jobs);
uint8_t data_out[2];
memset(data_out, 0, sizeof(data_out));
cl_assert_equal_i(sizeof(data_out),
comm_session_send_queue_copy(s_valid_session, 0, sizeof(data_out), data_out));
cl_assert_equal_m(data_out, TEST_DATA, sizeof(data_out));
}
void test_session_send_queue__copy_less_than_head_job_with_offset_shorter_than_job(void) {
int num_jobs = 3;
prv_add_jobs(num_jobs);
uint8_t data_out[1];
memset(data_out, 0, sizeof(data_out));
int offset = 1;
cl_assert_equal_i(sizeof(data_out),
comm_session_send_queue_copy(s_valid_session, offset,
sizeof(data_out), data_out));
cl_assert_equal_m(data_out, TEST_DATA + offset, sizeof(data_out));
}
void test_session_send_queue__copy_less_than_head_job_with_offset_longer_than_job(void) {
int num_jobs = 3;
prv_add_jobs(num_jobs);
uint8_t data_out[sizeof(TEST_DATA) - 1];
memset(data_out, 0, sizeof(data_out));
int offset = sizeof(TEST_DATA) + 1;
cl_assert_equal_i(sizeof(data_out),
comm_session_send_queue_copy(s_valid_session, offset,
sizeof(data_out), data_out));
cl_assert_equal_m(data_out, TEST_DATA + (offset % sizeof(TEST_DATA)), sizeof(data_out));
}
void test_session_send_queue__copy_overlapping_multiple_jobs_with_offset(void) {
int num_jobs = 3;
prv_add_jobs(num_jobs);
uint8_t data_out[2 * sizeof(TEST_DATA)];
memset(data_out, 0, sizeof(data_out));
int offset = 1;
cl_assert_equal_i(sizeof(data_out),
comm_session_send_queue_copy(s_valid_session, offset,
sizeof(data_out), data_out));
for (int i = 0; i < 2; ++i) {
cl_assert_equal_m(data_out + (i * sizeof(TEST_DATA)),
TEST_DATA + offset, sizeof(TEST_DATA) - offset);
cl_assert_equal_m(data_out + ((i + 1) * sizeof(TEST_DATA)) - offset,
TEST_DATA, offset);
}
}
void test_session_send_queue__get_read_pointer(void) {
cl_assert_equal_i(s_free_count, 0);
int num_jobs = 3;
prv_add_jobs(num_jobs);
const uint8_t *data_out = NULL;
for (int consumed = 0; consumed < sizeof(TEST_DATA); ++consumed) {
cl_assert_equal_i(comm_session_send_queue_get_read_pointer(s_valid_session, &data_out),
sizeof(TEST_DATA) - consumed);
cl_assert_equal_m(data_out, TEST_DATA + consumed, sizeof(TEST_DATA) - consumed);
comm_session_send_queue_consume(s_valid_session, 1);
}
// Expect head to be free'd:
cl_assert_equal_i(s_free_count, 1);
// Next job can be read:
cl_assert_equal_i(comm_session_send_queue_get_read_pointer(s_valid_session, &data_out),
sizeof(TEST_DATA));
cl_assert_equal_m(data_out, TEST_DATA, sizeof(TEST_DATA));
}
void test_session_send_queue__get_read_pointer_no_jobs(void) {
const uint8_t *data_out = NULL;
cl_assert_equal_i(0, comm_session_send_queue_get_read_pointer(s_valid_session, &data_out));
}
void test_session_send_queue__consume_more_than_one_job(void) {
int num_jobs = 3;
prv_add_jobs(num_jobs);
size_t consumed = sizeof(TEST_DATA) + 1;
comm_session_send_queue_consume(s_valid_session, consumed);
// Expect head to be free'd:
cl_assert_equal_i(s_free_count, 1);
cl_assert_equal_i((num_jobs * sizeof(TEST_DATA)) - consumed,
comm_session_send_queue_get_length(s_valid_session));
}
void test_session_send_queue__consume_all(void) {
int num_jobs = 3;
prv_add_jobs(num_jobs);
comm_session_send_queue_consume(s_valid_session, UINT32_MAX);
cl_assert_equal_i(s_free_count, num_jobs);
}
void test_session_send_queue__cleanup_calls_free_on_all_jobs(void) {
cl_assert_equal_i(s_free_count, 0);
// Add a couple jobs:
int num_jobs = 3;
prv_add_jobs(num_jobs);
// When the session is disconnected, comm_session_send_queue_cleanup() is called:
comm_session_send_queue_cleanup(s_valid_session);
cl_assert_equal_i(s_free_count, num_jobs);
}
void test_session_send_queue__session_closed_when_add_is_called(void) {
s_valid_session = NULL;
SessionSendQueueJob *job = prv_create_test_job(TEST_DATA, sizeof(TEST_DATA));
comm_session_send_queue_add_job(s_valid_session, &job);
cl_assert(!job);
cl_assert_equal_i(s_free_count, 1);
}

View file

@ -0,0 +1,64 @@
from waftools.pebble_test import clar
def build(bld):
clar(bld,
sources_ant_glob=(
"src/fw/services/common/comm_session/meta_endpoint.c "
"tests/fakes/fake_session.c "
),
test_sources_ant_glob="test_meta_endpoint.c")
clar(bld,
sources_ant_glob=(
"src/fw/util/rand/rand.c "
"src/fw/vendor/tinymt32/tinymt32.c "
"src/fw/services/common/comm_session/session.c "
"tests/fakes/fake_session_send_buffer.c"
),
test_sources_ant_glob="test_session.c",
override_includes=['dummy_board'])
clar(bld,
sources_ant_glob=(
"src/fw/services/common/comm_session/default_kernel_sender.c "
"src/fw/services/common/comm_session/session_send_queue.c "
"tests/fakes/fake_queue.c "
"tests/fakes/fake_rtc.c"
),
test_sources_ant_glob="test_session_send_buffer.c")
clar(bld,
sources_ant_glob=(
"src/fw/services/common/comm_session/session_send_queue.c "
),
test_sources_ant_glob="test_session_send_queue.c")
clar(bld,
sources_ant_glob=(
"src/fw/util/rand/rand.c "
"src/fw/vendor/tinymt32/tinymt32.c "
"src/fw/services/common/comm_session/session.c "
"src/fw/services/common/comm_session/session_receive_router.c "
"tests/fakes/fake_session_send_buffer.c "
),
test_sources_ant_glob="test_session_receive_router.c",
override_includes=['dummy_board', 'pp_endpoints'])
clar(bld,
sources_ant_glob=(
"src/fw/services/common/comm_session/session_remote_version.c "
"tests/fakes/fake_events.c "
),
test_sources_ant_glob="test_session_remote_version.c",
override_includes=['dummy_board'])
clar(bld,
sources_ant_glob=(
"src/fw/services/common/comm_session/default_kernel_receiver.c "
),
test_sources_ant_glob="test_default_kernel_receiver.c",
override_includes=['dummy_board'])
# vim:filetype=python