mirror of
https://github.com/google/pebble.git
synced 2025-03-20 19:01:21 +00:00
955 lines
32 KiB
C
955 lines
32 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 "comm/ble/gap_le_advert.h"
|
|
#include "comm/ble/gap_le_connection.h"
|
|
#include "services/common/regular_timer.h"
|
|
#include "util/size.h"
|
|
|
|
#include "clar.h"
|
|
|
|
// Fakes
|
|
///////////////////////////////////////////////////////////
|
|
|
|
#include "fake_GAPAPI.h"
|
|
#include "fake_new_timer.h"
|
|
#include "fake_rtc.h"
|
|
#include "fake_system_task.h"
|
|
#include "fake_pbl_malloc.h"
|
|
|
|
// Stubs
|
|
///////////////////////////////////////////////////////////
|
|
|
|
#include "stubs_analytics.h"
|
|
#include "stubs_bluetopia_interface.h"
|
|
#include "stubs_bt_lock.h"
|
|
#include "stubs_gatt_client_discovery.h"
|
|
#include "stubs_gatt_client_subscriptions.h"
|
|
#include "stubs_logging.h"
|
|
#include "stubs_mutex.h"
|
|
#include "stubs_passert.h"
|
|
#include "stubs_prompt.h"
|
|
|
|
void gap_le_connect_bluetopia_connection_callback(uint32_t stack_id,
|
|
GAP_LE_Event_Data_t* event_data,
|
|
uint32_t CallbackParameter) {
|
|
}
|
|
|
|
bool static s_is_connected_as_slave = false;
|
|
|
|
bool gap_le_connect_is_connected_as_slave(void) {
|
|
return s_is_connected_as_slave;
|
|
}
|
|
|
|
void ble_legacy_discovery_enable(uint32_t timeout_secs) {
|
|
}
|
|
|
|
void gap_le_slave_reconnect_stop(void) {
|
|
}
|
|
|
|
void gap_le_slave_reconnect_start(void) {
|
|
}
|
|
|
|
void gatt_service_changed_server_cleanup_by_connection(GAPLEConnection *connection) {
|
|
}
|
|
|
|
int HCI_LE_Set_Advertise_Enable(unsigned int BluetoothStackID, ...) {
|
|
return (0);
|
|
}
|
|
|
|
void launcher_task_add_callback(void (*callback)(void *data), void *data) {
|
|
callback(data);
|
|
}
|
|
|
|
static uint32_t s_unscheduled_cb_count;
|
|
static void * s_unscheduled_cb_data = "Callback Data";
|
|
static GAPLEAdvertisingJobRef s_unscheduled_job;
|
|
static bool s_unscheduled_completed;
|
|
|
|
static void unscheduled_callback(GAPLEAdvertisingJobRef job,
|
|
bool completed,
|
|
void *cb_data) {
|
|
s_unscheduled_job = job;
|
|
++s_unscheduled_cb_count;
|
|
s_unscheduled_completed = completed;
|
|
cl_assert_equal_p(cb_data, s_unscheduled_cb_data);
|
|
}
|
|
|
|
void test_gap_le_advert__initialize(void) {
|
|
fake_GAPAPI_init();
|
|
|
|
s_unscheduled_cb_count = 0;
|
|
s_unscheduled_job = NULL;
|
|
s_unscheduled_completed = false;
|
|
|
|
// This bypasses the work-around for the CC2564 advertising bug, that pauses the round-robinning
|
|
// through scheduled advertisment jobs:
|
|
s_is_connected_as_slave = true;
|
|
|
|
regular_timer_init();
|
|
gap_le_advert_init();
|
|
}
|
|
|
|
void test_gap_le_advert__cleanup(void) {
|
|
gap_le_advert_deinit();
|
|
|
|
// Make sure deinit did disable advertising and clean up timer:
|
|
cl_assert(!gap_le_is_advertising_enabled());
|
|
cl_assert_equal_i(regular_timer_seconds_count(), 0);
|
|
|
|
regular_timer_deinit();
|
|
}
|
|
|
|
//! Helper to assert whether a piece of ad_data is set to the controller.
|
|
//! For convenience, C strings are used (while in reality it can be arbitrary
|
|
//! binary data).
|
|
#define assert_ad_data(ad_data_cstring) \
|
|
{ \
|
|
Advertising_Data_t ad_data_out; \
|
|
const size_t data_length = strlen(ad_data_cstring) + 1; \
|
|
cl_assert_equal_i(gap_le_get_advertising_data(&ad_data_out), data_length); \
|
|
cl_assert_equal_s((const char *) &ad_data_out, ad_data_cstring); \
|
|
}
|
|
|
|
//! Helper to create BLEAdData from C strings.
|
|
//! In reality, people will use ble_ad_create() and the ble_ad_set_* functions,
|
|
//! but that's part of another test.
|
|
static BLEAdData *create_ad(const char ad_data[],
|
|
const char scan_resp_data[]) {
|
|
const size_t ad_data_length = ad_data ? strlen(ad_data) + 1 : 0;
|
|
const size_t scan_resp_data_length =
|
|
scan_resp_data ? strlen(scan_resp_data) + 1 : 0;
|
|
|
|
BLEAdData *ad = (BLEAdData *) malloc(sizeof(BLEAdData) +
|
|
ad_data_length +
|
|
scan_resp_data_length);
|
|
ad->ad_data_length = ad_data_length;
|
|
ad->scan_resp_data_length = scan_resp_data_length;
|
|
if (ad_data_length) {
|
|
strncpy((char *) ad->data,
|
|
ad_data, ad_data_length);
|
|
}
|
|
if (scan_resp_data_length) {
|
|
strncpy((char *) ad->data + ad_data_length,
|
|
scan_resp_data, scan_resp_data_length);
|
|
}
|
|
return ad;
|
|
}
|
|
|
|
void test_gap_le_advert__single_job(void) {
|
|
const char ad_data[] = "ad data";
|
|
const char scan_resp_data[] = "scan resp data";
|
|
BLEAdData *ad = create_ad(ad_data, scan_resp_data);
|
|
|
|
GAPLEAdvertisingJobTerm advert_term = {
|
|
.min_interval_slots = 160,
|
|
.max_interval_slots = 241,
|
|
.duration_secs = 10,
|
|
};
|
|
GAPLEAdvertisingJobRef job;
|
|
job = gap_le_advert_schedule(ad,
|
|
&advert_term,
|
|
sizeof(advert_term)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert(job);
|
|
|
|
// Since there was nothing scheduled, expect that the ad data is set to the
|
|
// controller immediately:
|
|
cl_assert(gap_le_is_advertising_enabled());
|
|
|
|
// Check that the ad data that is passed to the controller is the same that
|
|
// was given when calling the API:
|
|
assert_ad_data(ad_data);
|
|
|
|
// Check that the scan resp data that is passed to the controller is the same
|
|
// that was given when calling the API:
|
|
Scan_Response_Data_t scan_resp_data_out;
|
|
cl_assert_equal_i(gap_le_get_scan_response_data(&scan_resp_data_out),
|
|
sizeof(scan_resp_data));
|
|
cl_assert(memcmp(&scan_resp_data_out, scan_resp_data,
|
|
sizeof(scan_resp_data)) == 0);
|
|
|
|
// Expect one regular timer to be running for advertisements:
|
|
cl_assert_equal_i(regular_timer_seconds_count(), 1);
|
|
// Unschedule callback should not have been called:
|
|
cl_assert_equal_i(s_unscheduled_cb_count, 0);
|
|
|
|
// Unschedule and expect not to be advertising any more:
|
|
gap_le_advert_unschedule(job);
|
|
cl_assert(!gap_le_is_advertising_enabled());
|
|
|
|
// Unschedule callback should have been called once:
|
|
cl_assert_equal_i(s_unscheduled_cb_count, 1);
|
|
cl_assert_equal_p(s_unscheduled_job, job);
|
|
cl_assert_equal_b(s_unscheduled_completed, false);
|
|
|
|
// Expect no advertisement timer
|
|
cl_assert_equal_i(regular_timer_seconds_count(), 0);
|
|
|
|
free(ad);
|
|
}
|
|
|
|
void test_gap_le_advert__single_job_multiple_terms_silence_and_loop_around(void) {
|
|
BLEAdData *ad = create_ad("yo", NULL);
|
|
|
|
GAPLEAdvertisingJobTerm advert_terms[] =
|
|
{
|
|
{
|
|
.min_interval_slots = 160,
|
|
.max_interval_slots = 240,
|
|
.duration_secs = 1,
|
|
},
|
|
{
|
|
.min_interval_slots = 320,
|
|
.max_interval_slots = 480,
|
|
.duration_secs = 1,
|
|
},
|
|
{
|
|
.min_interval_slots = GAPLE_ADVERTISING_SILENCE_INTERVAL_SLOTS,
|
|
.max_interval_slots = GAPLE_ADVERTISING_SILENCE_INTERVAL_SLOTS,
|
|
.duration_secs = 1,
|
|
},
|
|
{
|
|
.duration_secs = GAPLE_ADVERTISING_DURATION_LOOP_AROUND,
|
|
.loop_around_index = 1,
|
|
},
|
|
};
|
|
GAPLEAdvertisingJobRef job;
|
|
job = gap_le_advert_schedule(ad,
|
|
advert_terms,
|
|
sizeof(advert_terms)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert(job);
|
|
|
|
// First term:
|
|
cl_assert(gap_le_is_advertising_enabled());
|
|
assert_ad_data("yo");
|
|
gap_le_assert_advertising_interval(advert_terms[0].min_interval_slots,
|
|
advert_terms[0].max_interval_slots);
|
|
|
|
regular_timer_fire_seconds(1);
|
|
|
|
// Second term:
|
|
cl_assert(gap_le_is_advertising_enabled());
|
|
assert_ad_data("yo");
|
|
gap_le_assert_advertising_interval(advert_terms[1].min_interval_slots,
|
|
advert_terms[1].max_interval_slots);
|
|
|
|
regular_timer_fire_seconds(1);
|
|
|
|
// Silent term:
|
|
cl_assert(!gap_le_is_advertising_enabled());
|
|
|
|
regular_timer_fire_seconds(1);
|
|
|
|
// Looped around to second term (index==1):
|
|
cl_assert(gap_le_is_advertising_enabled());
|
|
assert_ad_data("yo");
|
|
gap_le_assert_advertising_interval(advert_terms[1].min_interval_slots,
|
|
advert_terms[1].max_interval_slots);
|
|
|
|
cl_assert_equal_i(s_unscheduled_cb_count, 0);
|
|
}
|
|
|
|
void test_gap_le_advert__single_job_multiple_terms(void) {
|
|
const char ad_data[] = "ad data";
|
|
const char scan_resp_data[] = "scan resp data";
|
|
BLEAdData *ad = create_ad(ad_data, scan_resp_data);
|
|
|
|
GAPLEAdvertisingJobTerm advert_terms[3] =
|
|
{
|
|
{
|
|
.min_interval_slots = 160,
|
|
.max_interval_slots = 240,
|
|
.duration_secs = 4,
|
|
},
|
|
{
|
|
.min_interval_slots = 320,
|
|
.max_interval_slots = 400,
|
|
.duration_secs = 4,
|
|
},
|
|
{
|
|
.min_interval_slots = 640,
|
|
.max_interval_slots = 800,
|
|
.duration_secs = 4,
|
|
},
|
|
};
|
|
GAPLEAdvertisingJobRef job;
|
|
job = gap_le_advert_schedule(ad,
|
|
advert_terms,
|
|
sizeof(advert_terms)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert(job);
|
|
|
|
// Since there was nothing scheduled, expect that the ad data is set to the
|
|
// controller immediately:
|
|
cl_assert(gap_le_is_advertising_enabled());
|
|
|
|
// Check that the ad data that is passed to the controller is the same that
|
|
// was given when calling the API:
|
|
assert_ad_data(ad_data);
|
|
|
|
// Check that the scan resp data that is passed to the controller is the same
|
|
// that was given when calling the API:
|
|
Scan_Response_Data_t scan_resp_data_out;
|
|
cl_assert_equal_i(gap_le_get_scan_response_data(&scan_resp_data_out),
|
|
sizeof(scan_resp_data));
|
|
cl_assert(memcmp(&scan_resp_data_out, scan_resp_data,
|
|
sizeof(scan_resp_data)) == 0);
|
|
|
|
// Expect one regular timer to be running for adverts:
|
|
cl_assert_equal_i(regular_timer_seconds_count(), 1);
|
|
// Unschedule callback should not have been called:
|
|
cl_assert_equal_i(s_unscheduled_cb_count, 0);
|
|
|
|
// Make sure the all the terms in the job are run:
|
|
|
|
for (int term = 0; term < ARRAY_LENGTH(advert_terms); ++term) {
|
|
for (int second_tick = 0; second_tick < 4; ++second_tick) {
|
|
cl_assert_equal_i(s_unscheduled_cb_count, 0);
|
|
assert_ad_data("ad data");
|
|
gap_le_assert_advertising_interval(advert_terms[term].min_interval_slots,
|
|
advert_terms[term].max_interval_slots);
|
|
regular_timer_fire_seconds(1);
|
|
}
|
|
}
|
|
|
|
cl_assert(!gap_le_is_advertising_enabled());
|
|
cl_assert_equal_i(s_unscheduled_cb_count, 1);
|
|
cl_assert_equal_p(s_unscheduled_job, job);
|
|
|
|
// Expect no advertisement timer
|
|
cl_assert_equal_i(regular_timer_seconds_count(), 0);
|
|
|
|
free(ad);
|
|
}
|
|
|
|
void test_gap_le_advert__job_round_robin(void) {
|
|
GAPLEAdvertisingJobTerm advert_term = {
|
|
.min_interval_slots = 160,
|
|
.max_interval_slots = 320,
|
|
.duration_secs = GAPLE_ADVERTISING_DURATION_INFINITE,
|
|
};
|
|
|
|
// Schedule infinite job "A":
|
|
BLEAdData *ad_a = create_ad("A", NULL);
|
|
GAPLEAdvertisingJobRef infinite_job_a;
|
|
infinite_job_a = gap_le_advert_schedule(ad_a,
|
|
&advert_term, sizeof(advert_term)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert(infinite_job_a);
|
|
assert_ad_data("A");
|
|
|
|
// Schedule infinite job "B":
|
|
BLEAdData *ad_b = create_ad("B", NULL);
|
|
GAPLEAdvertisingJobRef infinite_job_b;
|
|
infinite_job_b = gap_le_advert_schedule(ad_b,
|
|
&advert_term, sizeof(advert_term)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert(infinite_job_b);
|
|
assert_ad_data("B");
|
|
|
|
// Round-robin 10 times:
|
|
for (int i = 0; i < 10; ++i) {
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("A");
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("B");
|
|
}
|
|
|
|
// Introduce non-infinite job "C" for 10 seconds:
|
|
advert_term.duration_secs = 10;
|
|
BLEAdData *ad_c = create_ad("C", NULL);
|
|
GAPLEAdvertisingJobRef infinite_job_c;
|
|
infinite_job_c = gap_le_advert_schedule(ad_c,
|
|
&advert_term, sizeof(advert_term)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert(infinite_job_c);
|
|
assert_ad_data("C");
|
|
|
|
// Round-robin 5 times:
|
|
for (int i = 0; i < 5; ++i) {
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("A");
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("B");
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("C");
|
|
}
|
|
|
|
// Introduce a second non-infinite job "D" for 10 seconds:
|
|
BLEAdData *ad_d = create_ad("D", NULL);
|
|
GAPLEAdvertisingJobRef infinite_job_d;
|
|
infinite_job_d = gap_le_advert_schedule(ad_d,
|
|
&advert_term, sizeof(advert_term)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
// It should get immediate air-time for one cycle:
|
|
cl_assert(infinite_job_d);
|
|
assert_ad_data("D");
|
|
|
|
// Round-robin 4 times:
|
|
for (int i = 0; i < 4; ++i) {
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("A");
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("B");
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("C");
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("D");
|
|
}
|
|
|
|
// Schedule yet another infinite job "E".
|
|
BLEAdData *ad_e = create_ad("E", NULL);
|
|
advert_term.duration_secs = GAPLE_ADVERTISING_DURATION_INFINITE;
|
|
GAPLEAdvertisingJobRef infinite_job_e;
|
|
infinite_job_e = gap_le_advert_schedule(ad_e,
|
|
&advert_term, sizeof(advert_term)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert(infinite_job_e);
|
|
// Infinite jobs are equal in priority to finite jobs, so it should get immediate air-time for one cycle:
|
|
assert_ad_data("E");
|
|
|
|
// No jobs should been have been unscheduled:
|
|
cl_assert_equal_i(s_unscheduled_cb_count, 0);
|
|
|
|
// This is the last round for "C":
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("A");
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("B");
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("C");
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("D");
|
|
|
|
// One job ("C") should been have been unscheduled:
|
|
cl_assert_equal_i(s_unscheduled_cb_count, 1);
|
|
|
|
|
|
// Round-robin 5 times:
|
|
for (int i = 0; i < 5; ++i) {
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("E");
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("A");
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("B");
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("D");
|
|
}
|
|
|
|
// "D" should be done now, so expect only infinite jobs to get air-time again:
|
|
for (int i = 0; i < 10; ++i) {
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("E");
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("A");
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("B");
|
|
|
|
// Jobs "C" and "D" should have been unscheduled, infinite jobs should never
|
|
// get unscheduled:
|
|
cl_assert_equal_i(s_unscheduled_cb_count, 2);
|
|
cl_assert_equal_b(s_unscheduled_completed, true);
|
|
}
|
|
|
|
free(ad_a);
|
|
free(ad_b);
|
|
free(ad_c);
|
|
free(ad_d);
|
|
}
|
|
|
|
// Tests that the adv data is set when a job goes from a silent term to a non-silent one, and
|
|
// another job's data was previously advertised.
|
|
void test_gap_le_advert__data_set_after_silent_term(void) {
|
|
GAPLEAdvertisingJobTerm advert_terms_a[] =
|
|
{
|
|
{
|
|
.min_interval_slots = 160,
|
|
.max_interval_slots = 320,
|
|
.duration_secs = 1,
|
|
},
|
|
};
|
|
|
|
GAPLEAdvertisingJobTerm advert_terms_b[] =
|
|
{
|
|
{
|
|
.min_interval_slots = GAPLE_ADVERTISING_SILENCE_INTERVAL_SLOTS,
|
|
.max_interval_slots = GAPLE_ADVERTISING_SILENCE_INTERVAL_SLOTS,
|
|
.duration_secs = 1,
|
|
},
|
|
{
|
|
.min_interval_slots = 32,
|
|
.max_interval_slots = 64,
|
|
.duration_secs = 1,
|
|
},
|
|
};
|
|
|
|
// Schedule job "A":
|
|
BLEAdData *ad_a = create_ad("A", NULL);
|
|
GAPLEAdvertisingJobRef job_a;
|
|
job_a = gap_le_advert_schedule(ad_a,
|
|
advert_terms_a,
|
|
sizeof(advert_terms_a)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert(job_a);
|
|
assert_ad_data("A");
|
|
regular_timer_fire_seconds(1);
|
|
cl_assert_equal_b(gap_le_is_advertising_enabled(), false);
|
|
cl_assert_equal_p(s_unscheduled_job, job_a);
|
|
|
|
// Schedule job "B":
|
|
BLEAdData *ad_b = create_ad("B", NULL);
|
|
GAPLEAdvertisingJobRef job_b;
|
|
job_b = gap_le_advert_schedule(ad_b,
|
|
advert_terms_b,
|
|
sizeof(advert_terms_b)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
|
|
cl_assert(job_b);
|
|
cl_assert_equal_b(gap_le_is_advertising_enabled(), false);
|
|
|
|
regular_timer_fire_seconds(1);
|
|
cl_assert_equal_b(gap_le_is_advertising_enabled(), true);
|
|
assert_ad_data("B");
|
|
}
|
|
|
|
void test_gap_le_advert__round_robin_two_jobs_incl_silent_terms(void) {
|
|
GAPLEAdvertisingJobTerm advert_terms_a[] =
|
|
{
|
|
{
|
|
.min_interval_slots = 160,
|
|
.max_interval_slots = 320,
|
|
.duration_secs = 1,
|
|
},
|
|
{
|
|
.min_interval_slots = 480,
|
|
.max_interval_slots = 960,
|
|
.duration_secs = 1,
|
|
},
|
|
{
|
|
.min_interval_slots = GAPLE_ADVERTISING_SILENCE_INTERVAL_SLOTS,
|
|
.max_interval_slots = GAPLE_ADVERTISING_SILENCE_INTERVAL_SLOTS,
|
|
.duration_secs = 1,
|
|
},
|
|
{
|
|
.min_interval_slots = 256,
|
|
.max_interval_slots = 512,
|
|
.duration_secs = 1,
|
|
},
|
|
};
|
|
|
|
GAPLEAdvertisingJobTerm advert_terms_b[] =
|
|
{
|
|
{
|
|
.min_interval_slots = GAPLE_ADVERTISING_SILENCE_INTERVAL_SLOTS,
|
|
.max_interval_slots = GAPLE_ADVERTISING_SILENCE_INTERVAL_SLOTS,
|
|
.duration_secs = 1,
|
|
},
|
|
{
|
|
.min_interval_slots = 32,
|
|
.max_interval_slots = 64,
|
|
.duration_secs = 1,
|
|
},
|
|
{
|
|
.min_interval_slots = GAPLE_ADVERTISING_SILENCE_INTERVAL_SLOTS,
|
|
.max_interval_slots = GAPLE_ADVERTISING_SILENCE_INTERVAL_SLOTS,
|
|
.duration_secs = 2,
|
|
},
|
|
{
|
|
.min_interval_slots = 960,
|
|
.max_interval_slots = 1240,
|
|
.duration_secs = 1,
|
|
},
|
|
};
|
|
|
|
// Schedule job "A":
|
|
BLEAdData *ad_a = create_ad("A", NULL);
|
|
GAPLEAdvertisingJobRef job_a;
|
|
job_a = gap_le_advert_schedule(ad_a,
|
|
advert_terms_a, sizeof(advert_terms_a)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert(job_a);
|
|
assert_ad_data("A");
|
|
|
|
// Schedule job "B":
|
|
BLEAdData *ad_b = create_ad("B", NULL);
|
|
GAPLEAdvertisingJobRef job_b;
|
|
job_b = gap_le_advert_schedule(ad_b,
|
|
advert_terms_b, sizeof(advert_terms_b)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
|
|
// Even though B is newer, expect "A" to be scheduled still, because B's first term is silent:
|
|
cl_assert(job_b);
|
|
cl_assert_equal_b(gap_le_is_advertising_enabled(), true);
|
|
assert_ad_data("A");
|
|
gap_le_assert_advertising_interval(advert_terms_a[0].min_interval_slots,
|
|
advert_terms_a[0].max_interval_slots);
|
|
|
|
|
|
// After A's first term, expect that B's second term will follow. B's first term was silent, so
|
|
// this "ran" at the same time as A's first term.
|
|
regular_timer_fire_seconds(1);
|
|
cl_assert_equal_b(gap_le_is_advertising_enabled(), true);
|
|
assert_ad_data("B");
|
|
gap_le_assert_advertising_interval(advert_terms_b[1].min_interval_slots,
|
|
advert_terms_b[1].max_interval_slots);
|
|
|
|
// Expect A's second term. B's third term (silent) will "run" now too.
|
|
regular_timer_fire_seconds(1);
|
|
cl_assert_equal_b(gap_le_is_advertising_enabled(), true);
|
|
assert_ad_data("A");
|
|
gap_le_assert_advertising_interval(advert_terms_a[1].min_interval_slots,
|
|
advert_terms_a[1].max_interval_slots);
|
|
|
|
// Expect silence. B's third (silent) is 2 secs, so one more to go and A's third term is silent.
|
|
regular_timer_fire_seconds(1);
|
|
cl_assert_equal_b(gap_le_is_advertising_enabled(), false);
|
|
|
|
// Expect B's fourth term now.
|
|
regular_timer_fire_seconds(1);
|
|
cl_assert_equal_b(gap_le_is_advertising_enabled(), true);
|
|
assert_ad_data("B");
|
|
gap_le_assert_advertising_interval(advert_terms_b[3].min_interval_slots,
|
|
advert_terms_b[3].max_interval_slots);
|
|
|
|
regular_timer_fire_seconds(1);
|
|
|
|
// Expect B to be done:
|
|
cl_assert_equal_i(s_unscheduled_cb_count, 1);
|
|
cl_assert_equal_p(s_unscheduled_job, job_b);
|
|
cl_assert_equal_b(s_unscheduled_completed, true);
|
|
|
|
// Expect A's fourth term:
|
|
assert_ad_data("A");
|
|
cl_assert_equal_b(gap_le_is_advertising_enabled(), true);
|
|
gap_le_assert_advertising_interval(advert_terms_a[3].min_interval_slots,
|
|
advert_terms_a[3].max_interval_slots);
|
|
|
|
regular_timer_fire_seconds(1);
|
|
|
|
// Expect A to be done as well:
|
|
cl_assert_equal_i(s_unscheduled_cb_count, 2);
|
|
cl_assert_equal_p(s_unscheduled_job, job_a);
|
|
cl_assert_equal_b(s_unscheduled_completed, true);
|
|
}
|
|
|
|
|
|
void test_gap_le_advert__job_round_robin_multiple_terms(void) {
|
|
GAPLEAdvertisingJobTerm advert_terms[2] =
|
|
{
|
|
{
|
|
.min_interval_slots = 160,
|
|
.max_interval_slots = 320,
|
|
.duration_secs = 5,
|
|
},
|
|
{
|
|
.min_interval_slots = 480,
|
|
.max_interval_slots = 620,
|
|
.duration_secs = 5,
|
|
},
|
|
};
|
|
|
|
// Schedule job "A":
|
|
BLEAdData *ad_a = create_ad("A", NULL);
|
|
GAPLEAdvertisingJobRef job_a;
|
|
job_a = gap_le_advert_schedule(ad_a,
|
|
advert_terms, sizeof(advert_terms)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert(job_a);
|
|
assert_ad_data("A");
|
|
|
|
// Schedule job "B":
|
|
BLEAdData *ad_b = create_ad("B", NULL);
|
|
GAPLEAdvertisingJobRef job_b;
|
|
job_b = gap_le_advert_schedule(ad_b,
|
|
advert_terms, sizeof(advert_terms)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert(job_b);
|
|
assert_ad_data("B");
|
|
|
|
// Round-robin. Each term is 5 seconds, each job is 10 seconds:
|
|
for (int i = 0; i < 9; ++i) {
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("A");
|
|
cl_assert_equal_i(s_unscheduled_cb_count, 0);
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("B");
|
|
cl_assert_equal_i(s_unscheduled_cb_count, 0);
|
|
}
|
|
|
|
// Unschedule job "B", air job "A"
|
|
regular_timer_fire_seconds(1);
|
|
assert_ad_data("A");
|
|
cl_assert_equal_i(s_unscheduled_cb_count, 1);
|
|
cl_assert_equal_p(s_unscheduled_job, job_b);
|
|
cl_assert_equal_b(s_unscheduled_completed, true);
|
|
|
|
// Unschedule job "A"
|
|
regular_timer_fire_seconds(1);
|
|
cl_assert_equal_i(s_unscheduled_cb_count, 2);
|
|
cl_assert_equal_p(s_unscheduled_job, job_a);
|
|
cl_assert_equal_b(s_unscheduled_completed, true);
|
|
|
|
cl_assert(!gap_le_is_advertising_enabled());
|
|
|
|
free(ad_a);
|
|
free(ad_b);
|
|
}
|
|
|
|
void test_gap_le_advert__expiring_job(void) {
|
|
// Test that a job is expired correctly after the set duration:
|
|
uint16_t seconds_left = 5;
|
|
GAPLEAdvertisingJobTerm advert_term = {
|
|
.min_interval_slots = 160,
|
|
.max_interval_slots = 320,
|
|
.duration_secs = seconds_left,
|
|
};
|
|
|
|
BLEAdData *ad = create_ad(NULL, NULL);
|
|
GAPLEAdvertisingJobRef job;
|
|
job = gap_le_advert_schedule(ad, &advert_term, sizeof(advert_term)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert(job);
|
|
|
|
// No jobs should been have been unscheduled:
|
|
cl_assert_equal_i(s_unscheduled_cb_count, 0);
|
|
|
|
while (seconds_left) {
|
|
cl_assert(gap_le_is_advertising_enabled());
|
|
|
|
regular_timer_fire_seconds(1);
|
|
--seconds_left;
|
|
}
|
|
|
|
cl_assert(!gap_le_is_advertising_enabled());
|
|
cl_assert_equal_i(regular_timer_seconds_count(), 0);
|
|
|
|
// The job should been have been unscheduled:
|
|
cl_assert_equal_i(s_unscheduled_cb_count, 1);
|
|
cl_assert_equal_p(s_unscheduled_job, job);
|
|
cl_assert_equal_b(s_unscheduled_completed, true);
|
|
|
|
free(ad);
|
|
}
|
|
|
|
void test_gap_le_advert__invalid_params(void) {
|
|
GAPLEAdvertisingJobRef job;
|
|
BLEAdData *ad = create_ad(NULL, NULL);
|
|
|
|
GAPLEAdvertisingJobTerm advert_term = {
|
|
.min_interval_slots = 31,
|
|
.max_interval_slots = 31,
|
|
.duration_secs = 1,
|
|
};
|
|
|
|
// Minimum interval boundary (no scan resp):
|
|
job = gap_le_advert_schedule(ad, &advert_term, sizeof(advert_term)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert_equal_p(job, NULL);
|
|
|
|
advert_term.min_interval_slots = 32;
|
|
advert_term.max_interval_slots = 32;
|
|
job = gap_le_advert_schedule(ad, &advert_term, sizeof(advert_term)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert(job);
|
|
|
|
// Minimum interval boundary (with scan resp):
|
|
char scan_resp_data[] = "scan resp data";
|
|
BLEAdData *ad_scannable = create_ad(NULL, scan_resp_data);
|
|
advert_term.min_interval_slots = 159;
|
|
advert_term.max_interval_slots = 159;
|
|
job = gap_le_advert_schedule(ad_scannable,
|
|
&advert_term, sizeof(advert_term)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert_equal_p(job, NULL);
|
|
|
|
advert_term.min_interval_slots = 160;
|
|
advert_term.max_interval_slots = 160;
|
|
job = gap_le_advert_schedule(ad_scannable,
|
|
&advert_term, sizeof(advert_term)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert(job);
|
|
|
|
// Max < Min:
|
|
advert_term.min_interval_slots = 200;
|
|
advert_term.max_interval_slots = 32;
|
|
job = gap_le_advert_schedule(ad, &advert_term, sizeof(advert_term)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert_equal_p(job, NULL);
|
|
|
|
// Loop-around in the first term:
|
|
advert_term.min_interval_slots = 200;
|
|
advert_term.max_interval_slots = 200;
|
|
advert_term.duration_secs = GAPLE_ADVERTISING_DURATION_LOOP_AROUND;
|
|
job = gap_le_advert_schedule(ad, &advert_term, sizeof(advert_term)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert_equal_p(job, NULL);
|
|
|
|
// Forever silent term:
|
|
advert_term.min_interval_slots = GAPLE_ADVERTISING_SILENCE_INTERVAL_SLOTS;
|
|
advert_term.max_interval_slots = GAPLE_ADVERTISING_SILENCE_INTERVAL_SLOTS;
|
|
advert_term.duration_secs = GAPLE_ADVERTISING_DURATION_INFINITE;
|
|
job = gap_le_advert_schedule(ad, &advert_term, sizeof(advert_term)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert_equal_p(job, NULL);
|
|
|
|
// No terms:
|
|
job = gap_le_advert_schedule(ad, NULL, 0,
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert_equal_p(job, NULL);
|
|
|
|
// No ad data:
|
|
advert_term.min_interval_slots = 200;
|
|
advert_term.max_interval_slots = 200;
|
|
advert_term.duration_secs = 1;
|
|
job = gap_le_advert_schedule(NULL, &advert_term,
|
|
sizeof(advert_term)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert_equal_p(job, NULL);
|
|
|
|
free(ad);
|
|
free(ad_scannable);
|
|
}
|
|
|
|
void test_gap_le_advert__unschedule_non_existent(void) {
|
|
// Unscheduling non-existent job should be fine, should not crash:
|
|
gap_le_advert_unschedule((GAPLEAdvertisingJobRef)(uintptr_t) 0x1234);
|
|
|
|
// Unschedule callback should not have been called:
|
|
cl_assert_equal_i(s_unscheduled_cb_count, 0);
|
|
}
|
|
|
|
void test_gap_le_advert__deinit_unschedules(void) {
|
|
BLEAdData *ad = create_ad(NULL, NULL);
|
|
|
|
GAPLEAdvertisingJobTerm advert_term = {
|
|
.min_interval_slots = 160,
|
|
.max_interval_slots = 320,
|
|
.duration_secs = 10,
|
|
};
|
|
GAPLEAdvertisingJobRef job;
|
|
job = gap_le_advert_schedule(ad, &advert_term, sizeof(advert_term)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert(job);
|
|
gap_le_advert_deinit();
|
|
cl_assert_equal_i(s_unscheduled_cb_count, 1);
|
|
cl_assert_equal_p(s_unscheduled_job, job);
|
|
cl_assert_equal_b(s_unscheduled_completed, false);
|
|
cl_assert_equal_i(regular_timer_seconds_count(), 0);
|
|
free(ad);
|
|
}
|
|
|
|
void test_gap_le_advert__cant_schedule_after_deinit(void) {
|
|
gap_le_advert_deinit();
|
|
BLEAdData *ad = create_ad(NULL, NULL);
|
|
GAPLEAdvertisingJobTerm advert_term = {
|
|
.min_interval_slots = 160,
|
|
.max_interval_slots = 320,
|
|
.duration_secs = 10,
|
|
};
|
|
GAPLEAdvertisingJobRef job;
|
|
job = gap_le_advert_schedule(ad, &advert_term, sizeof(advert_term)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert_equal_p(job, NULL);
|
|
cl_assert_equal_i(regular_timer_seconds_count(), 0);
|
|
}
|
|
|
|
void test_gap_le_advert__continue_after_slave_connection(void) {
|
|
BLEAdData *ad = create_ad(NULL, NULL);
|
|
GAPLEAdvertisingJobTerm advert_term = {
|
|
.min_interval_slots = 160,
|
|
.max_interval_slots = 320,
|
|
.duration_secs = 10,
|
|
};
|
|
GAPLEAdvertisingJobRef job;
|
|
job = gap_le_advert_schedule(ad, &advert_term, sizeof(advert_term)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, 0);
|
|
cl_assert_equal_b(gap_le_is_advertising_enabled(), true);
|
|
|
|
// Simulate stopping advertising because of inbound connection:
|
|
gap_le_set_advertising_disabled();
|
|
s_is_connected_as_slave = true;
|
|
|
|
// Call the connection handler:
|
|
gap_le_advert_handle_connect_as_slave();
|
|
// we should have stopped advertising for reconnection
|
|
cl_assert_equal_b(gap_le_is_advertising_enabled(), false);
|
|
|
|
// Trigger the advertising to start up again. Since we still have an advertisement job set,
|
|
// it should continue.
|
|
regular_timer_fire_seconds(1);
|
|
cl_assert_equal_b(gap_le_is_advertising_enabled(), true);
|
|
|
|
free(ad);
|
|
}
|
|
|
|
void test_gap_le_advert__unschedule_job_types(void) {
|
|
BLEAdData *ad = create_ad(NULL, NULL);
|
|
GAPLEAdvertisingJobTerm advert_term = {
|
|
.min_interval_slots = 160,
|
|
.max_interval_slots = 320,
|
|
.duration_secs = 10,
|
|
};
|
|
GAPLEAdvertisingJobRef job_a;
|
|
job_a = gap_le_advert_schedule(ad, &advert_term, sizeof(advert_term)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, GAPLEAdvertisingJobTagDiscovery);
|
|
|
|
GAPLEAdvertisingJobTag tag = GAPLEAdvertisingJobTagDiscovery;
|
|
gap_le_advert_unschedule_job_types(&tag, 1);
|
|
|
|
// make sure we can remove a tag when it is the only one scheduled
|
|
cl_assert(s_unscheduled_job == job_a);
|
|
cl_assert_equal_i(s_unscheduled_cb_count, 1);
|
|
|
|
|
|
// add back the job we just unscheduled
|
|
job_a = gap_le_advert_schedule(ad, &advert_term, sizeof(advert_term)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, GAPLEAdvertisingJobTagDiscovery);
|
|
|
|
GAPLEAdvertisingJobRef job_b;
|
|
job_b = gap_le_advert_schedule(ad, &advert_term, sizeof(advert_term)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, GAPLEAdvertisingJobTagReconnection);
|
|
|
|
|
|
GAPLEAdvertisingJobRef job_c;
|
|
job_c = gap_le_advert_schedule(ad, &advert_term, sizeof(advert_term)/sizeof(GAPLEAdvertisingJobTerm),
|
|
unscheduled_callback, s_unscheduled_cb_data, GAPLEAdvertisingJobTagReconnection);
|
|
|
|
// run some Ad cycling
|
|
for (int i = 0; i < 3; i++) {
|
|
regular_timer_fire_seconds(1);
|
|
}
|
|
|
|
cl_assert_equal_b(gap_le_is_advertising_enabled(), true);
|
|
|
|
// make sure we can remove a few jobs with the same advertisement type
|
|
tag = GAPLEAdvertisingJobTagReconnection;
|
|
gap_le_advert_unschedule_job_types(&tag, 1);
|
|
|
|
// should have 3 jobs unscheduled at this point and 1 still running
|
|
cl_assert_equal_i(s_unscheduled_cb_count, 3);
|
|
cl_assert_equal_b(gap_le_is_advertising_enabled(), true);
|
|
|
|
// make sure after we unschedule all the jobs no more are running
|
|
tag = GAPLEAdvertisingJobTagDiscovery;
|
|
gap_le_advert_unschedule_job_types(&tag, 1);
|
|
cl_assert_equal_i(s_unscheduled_cb_count, 4);
|
|
cl_assert_equal_b(gap_le_is_advertising_enabled(), false);
|
|
|
|
free(ad);
|
|
}
|