mirror of
https://github.com/google/pebble.git
synced 2025-03-19 10:31:21 +00:00
645 lines
28 KiB
C
645 lines
28 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/kernel_le_client/ancs/ancs.h"
|
||
|
#include "comm/ble/kernel_le_client/ancs/ancs_util.h"
|
||
|
#include "comm/ble/kernel_le_client/ancs/ancs_definition.h"
|
||
|
|
||
|
#include "comm/ble/gap_le_connection.h"
|
||
|
#include "comm/ble/gap_le_task.h"
|
||
|
|
||
|
#include "services/common/evented_timer.h"
|
||
|
#include "services/common/regular_timer.h"
|
||
|
#include "services/normal/notifications/ancs/ancs_notifications.h"
|
||
|
#include "util/size.h"
|
||
|
|
||
|
#include "clar.h"
|
||
|
|
||
|
// Stubs
|
||
|
///////////////////////////////////////////////////////////
|
||
|
|
||
|
#include "stubs_analytics.h"
|
||
|
#include "stubs_ios_notif_pref_db.h"
|
||
|
#include "stubs_bt_stack.h"
|
||
|
#include "stubs_ble.h"
|
||
|
#include "stubs_pin_db.h"
|
||
|
#include "stubs_i18n.h"
|
||
|
#include "stubs_layout_layer.h"
|
||
|
#include "stubs_logging.h"
|
||
|
#include "stubs_mutex.h"
|
||
|
#include "stubs_passert.h"
|
||
|
#include "stubs_pebble_tasks.h"
|
||
|
#include "stubs_pebble_pairing_service.h"
|
||
|
#include "stubs_prompt.h"
|
||
|
#include "stubs_timeline.h"
|
||
|
#include "stubs_queue.h"
|
||
|
#include "stubs_rand_ptr.h"
|
||
|
#include "stubs_reconnect.h"
|
||
|
#include "stubs_reminder_db.h"
|
||
|
#include "stubs_reminders.h"
|
||
|
#include "stubs_serial.h"
|
||
|
#include "stubs_sleep.h"
|
||
|
#include "stubs_system_reset.h"
|
||
|
#include "stubs_task_watchdog.h"
|
||
|
#include "stubs_nexmo.h"
|
||
|
#include "stubs_codepoint.h"
|
||
|
#include "stubs_utf8.h"
|
||
|
|
||
|
void launcher_task_add_callback(void (*callback)(void *data), void *data) {
|
||
|
callback(data);
|
||
|
}
|
||
|
|
||
|
PebblePhoneCaller* phone_call_util_create_caller(const char *number, const char *name) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
// Fakes
|
||
|
///////////////////////////////////////////////////////////
|
||
|
|
||
|
#include "fake_events.h"
|
||
|
#include "fake_kernel_services_notifications.h"
|
||
|
#include "fake_new_timer.h"
|
||
|
#include "fake_notification_storage.h"
|
||
|
#include "fake_pbl_malloc.h"
|
||
|
#include "fake_spi_flash.h"
|
||
|
|
||
|
bool shell_prefs_get_language_english(void) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static bool s_block_event_callback = false;
|
||
|
EventedTimerID evented_timer_register(uint32_t timeout_ms,
|
||
|
bool repeating,
|
||
|
EventedTimerCallback callback,
|
||
|
void* callback_data) {
|
||
|
if (!s_block_event_callback) {
|
||
|
callback(callback_data);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// Test data
|
||
|
///////////////////////////////////////////////////////////
|
||
|
|
||
|
#include "ancs_test_data.h"
|
||
|
|
||
|
const uint32_t s_invalid_param_uid = 0x12;
|
||
|
const uint32_t s_get_wrong_data_uid = 0xee;
|
||
|
|
||
|
static BLECharacteristic s_characteristics[NumANCSCharacteristic] = { 1, 2, 3 };
|
||
|
|
||
|
// Helper Functions
|
||
|
///////////////////////////////////////////////////////////
|
||
|
|
||
|
static int s_num_requested_app_attributes;
|
||
|
static int s_num_requested_notif_attributes;
|
||
|
static int s_num_ds_notifications_received;
|
||
|
static bool s_gatt_client_op_write_should_fail_unlimited = false;
|
||
|
static bool s_gatt_client_op_write_should_fail_once = false;
|
||
|
|
||
|
static void prv_fake_receiving_ds_notification(size_t value_length, uint8_t *value) {
|
||
|
BLECharacteristic characteristic = s_characteristics[ANCSCharacteristicData];
|
||
|
ancs_handle_read_or_notification(characteristic, (const uint8_t *) value, value_length, 0);
|
||
|
}
|
||
|
|
||
|
static void prv_fake_receiving_ns_notification(size_t value_length, uint8_t *value) {
|
||
|
BLECharacteristic characteristic = s_characteristics[ANCSCharacteristicNotification];
|
||
|
ancs_handle_read_or_notification(characteristic, (const uint8_t *) value, value_length, 0);
|
||
|
}
|
||
|
|
||
|
static void prv_send_notification_with_event_flags(const uint8_t *ancs_notification_dict,
|
||
|
int event_flags) {
|
||
|
NSNotification ns_notification = {
|
||
|
.event_id = EventIDNotificationAdded,
|
||
|
.event_flags = event_flags,
|
||
|
.category_id = CategoryIDSocial,
|
||
|
.category_count = 1,
|
||
|
.uid = 1,
|
||
|
};
|
||
|
|
||
|
const uint32_t ancs_notification_dict_uid =
|
||
|
((GetNotificationAttributesMsg *)ancs_notification_dict)->notification_uid;
|
||
|
ns_notification.uid = ancs_notification_dict_uid;
|
||
|
prv_fake_receiving_ns_notification(sizeof(ns_notification), (uint8_t*) &ns_notification);
|
||
|
}
|
||
|
|
||
|
static void prv_send_notification(const uint8_t *ancs_notification_dict) {
|
||
|
prv_send_notification_with_event_flags(ancs_notification_dict, 0);
|
||
|
}
|
||
|
|
||
|
static uint8_t *prv_serialize_timeline_item(TimelineItem *item, size_t *size_out) {
|
||
|
size_t payload_size = timeline_item_get_serialized_payload_size(item);
|
||
|
*size_out = sizeof(SerializedTimelineItemHeader) + payload_size;
|
||
|
uint8_t *buffer = malloc(*size_out);
|
||
|
|
||
|
timeline_item_serialize_header(item, (SerializedTimelineItemHeader *)buffer);
|
||
|
timeline_item_serialize_payload(item, buffer + sizeof(SerializedTimelineItemHeader),
|
||
|
payload_size);
|
||
|
|
||
|
return buffer;
|
||
|
}
|
||
|
|
||
|
void prv_cmp_last_received_notification(TimelineItem *item) {
|
||
|
TimelineItem *notification = fake_notification_storage_get_last_notification();
|
||
|
size_t size1, size2;
|
||
|
|
||
|
// Clear out the id since it is auto-generated
|
||
|
memset(¬ification->header.id, 0, sizeof(notification->header.id));
|
||
|
|
||
|
uint8_t *buf1 = prv_serialize_timeline_item(notification, &size1);
|
||
|
uint8_t *buf2 = prv_serialize_timeline_item(item, &size2);
|
||
|
|
||
|
cl_assert_equal_i(size1, size2);
|
||
|
cl_assert_equal_m(buf1, buf2, size1);
|
||
|
|
||
|
free(buf1);
|
||
|
free(buf2);
|
||
|
}
|
||
|
|
||
|
// Called from inside prv_write_control_point_request.
|
||
|
// If this function is called we have requested a ds_notification
|
||
|
BTErrno gatt_client_op_write(BLECharacteristic characteristic,
|
||
|
const uint8_t *buffer,
|
||
|
size_t length,
|
||
|
GAPLEClient client) {
|
||
|
|
||
|
cl_assert_equal_i(characteristic, s_characteristics[ANCSCharacteristicControl]);
|
||
|
|
||
|
if (s_gatt_client_op_write_should_fail_once) {
|
||
|
s_gatt_client_op_write_should_fail_once = false;
|
||
|
return BTErrnoInvalidParameter;
|
||
|
}
|
||
|
|
||
|
if (s_gatt_client_op_write_should_fail_unlimited) {
|
||
|
return BTErrnoInvalidParameter;
|
||
|
}
|
||
|
|
||
|
const uint32_t comple_dict_uid = ((GetNotificationAttributesMsg*)s_complete_dict)->notification_uid;
|
||
|
const uint32_t chunked_dict_uid = ((GetNotificationAttributesMsg*)s_chunked_dict_part_one)->notification_uid;
|
||
|
const uint32_t message_size_attr_dict_uid = ((GetNotificationAttributesMsg*)s_message_size_attr_dict)->notification_uid;
|
||
|
const uint32_t invalid_dict_uid = ((GetNotificationAttributesMsg*)s_invalid_attribute_length)->notification_uid;
|
||
|
const uint32_t attribute_at_end_uid = ((GetNotificationAttributesMsg*)memory_with_attribute_id_at_end.attribute_data)->notification_uid;
|
||
|
const uint32_t loading_uid = ((GetNotificationAttributesMsg*)s_loading_response)->notification_uid;
|
||
|
const uint32_t no_content_uid = ((GetNotificationAttributesMsg*)s_this_message_has_no_content_response)->notification_uid;
|
||
|
const uint32_t multiple_complete_dict_uid = ((GetNotificationAttributesMsg*)s_multiple_complete_dicts)->notification_uid;
|
||
|
const uint32_t split_timestamp_uid = ((GetNotificationAttributesMsg*)s_split_timestamp_dict_part_one)->notification_uid;
|
||
|
const uint32_t message_dict_uid = ((GetNotificationAttributesMsg*)s_message_dict)->notification_uid;
|
||
|
const uint32_t app_name_title_dict_uid = ((GetNotificationAttributesMsg*)s_app_name_title_dict)->notification_uid;
|
||
|
const uint32_t unknown_app_message_dict_uid = ((GetNotificationAttributesMsg*)s_unknown_app_dict)->notification_uid;
|
||
|
const uint32_t unknown_app_unique_title_dict_uid = ((GetNotificationAttributesMsg*)s_unknown_app_unique_title_dict)->notification_uid;
|
||
|
const uint32_t mms_no_caption_dict_uid = ((GetNotificationAttributesMsg*)s_mms_no_caption_dict)->notification_uid;
|
||
|
const uint32_t mms_with_caption_dict_uid = ((GetNotificationAttributesMsg*)s_mms_with_caption_dict)->notification_uid;
|
||
|
|
||
|
const CPDSMessage *cmd_header = (const CPDSMessage *)buffer;
|
||
|
if (cmd_header->command_id == CommandIDGetAppAttributes) {
|
||
|
s_num_requested_app_attributes++;
|
||
|
|
||
|
if (strcmp((const char *)cmd_header->data, "com.tests.NotAnApp") == 0) {
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(s_fake_app_info_dict),
|
||
|
(uint8_t *)s_fake_app_info_dict);
|
||
|
} else {
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(s_message_app_info_dict),
|
||
|
(uint8_t *)s_message_app_info_dict);
|
||
|
}
|
||
|
return BTErrnoOK;
|
||
|
}
|
||
|
|
||
|
// else: notif request
|
||
|
uint32_t uid = ((GetNotificationAttributesMsg *)buffer)->notification_uid;
|
||
|
s_num_requested_notif_attributes++;
|
||
|
|
||
|
if (uid == comple_dict_uid) {
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(s_complete_dict), (uint8_t*) s_complete_dict);
|
||
|
s_num_ds_notifications_received++;
|
||
|
} else if (uid == chunked_dict_uid) {
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(s_chunked_dict_part_one), (uint8_t*) s_chunked_dict_part_one);
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(s_chunked_dict_part_two), (uint8_t*) s_chunked_dict_part_two);
|
||
|
s_num_ds_notifications_received += 2;
|
||
|
} else if (uid == message_size_attr_dict_uid) {
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(s_message_size_attr_dict), (uint8_t*) s_message_size_attr_dict);
|
||
|
s_num_ds_notifications_received++;
|
||
|
} else if (uid == attribute_at_end_uid) {
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(memory_with_attribute_id_at_end.attribute_data), (uint8_t*) memory_with_attribute_id_at_end.attribute_data);
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(memory_with_attribute_id_at_end_p2), (uint8_t*) memory_with_attribute_id_at_end_p2);
|
||
|
s_num_ds_notifications_received += 2;
|
||
|
} else if (uid == invalid_dict_uid) {
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(s_invalid_attribute_length), (uint8_t*) s_invalid_attribute_length);
|
||
|
s_num_ds_notifications_received++;
|
||
|
} else if (uid == loading_uid) {
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(s_loading_response),
|
||
|
(uint8_t*) s_loading_response);
|
||
|
s_num_ds_notifications_received++;
|
||
|
} else if (uid == no_content_uid) {
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(s_this_message_has_no_content_response),
|
||
|
(uint8_t*) s_this_message_has_no_content_response);
|
||
|
s_num_ds_notifications_received++;
|
||
|
} else if (uid == s_invalid_param_uid) {
|
||
|
ancs_handle_write_response(0, 0xA2);
|
||
|
s_num_ds_notifications_received++;
|
||
|
} else if (uid == multiple_complete_dict_uid) {
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(s_multiple_complete_dicts), (uint8_t*) s_multiple_complete_dicts);
|
||
|
s_num_ds_notifications_received += 3;
|
||
|
} else if (uid == split_timestamp_uid) {
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(s_split_timestamp_dict_part_one), (uint8_t*) s_split_timestamp_dict_part_one);
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(s_split_timestamp_dict_part_two), (uint8_t*) s_split_timestamp_dict_part_two);
|
||
|
s_num_ds_notifications_received += 2;
|
||
|
} else if (uid == message_dict_uid) {
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(s_message_dict), (uint8_t*) s_message_dict);
|
||
|
s_num_ds_notifications_received++;
|
||
|
} else if (uid == app_name_title_dict_uid) {
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(s_app_name_title_dict), (uint8_t *)s_app_name_title_dict);
|
||
|
s_num_ds_notifications_received++;
|
||
|
} else if (uid == unknown_app_message_dict_uid) {
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(s_unknown_app_dict), (uint8_t *)s_unknown_app_dict);
|
||
|
s_num_ds_notifications_received++;
|
||
|
} else if (uid == unknown_app_unique_title_dict_uid) {
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(s_unknown_app_unique_title_dict), (uint8_t *)s_unknown_app_unique_title_dict);
|
||
|
s_num_ds_notifications_received++;
|
||
|
} else if (uid == mms_no_caption_dict_uid) {
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(s_mms_no_caption_dict), (uint8_t *)s_mms_no_caption_dict);
|
||
|
s_num_ds_notifications_received++;
|
||
|
} else if (uid == mms_with_caption_dict_uid) {
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(s_mms_with_caption_dict), (uint8_t *)s_mms_with_caption_dict);
|
||
|
s_num_ds_notifications_received++;
|
||
|
} else if (uid == s_get_wrong_data_uid) {
|
||
|
// We wanted a notification attributes message, but got a app attributes message...
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(s_message_app_info_dict),
|
||
|
(uint8_t *)s_message_app_info_dict);
|
||
|
s_num_ds_notifications_received++;
|
||
|
}
|
||
|
|
||
|
return BTErrnoOK;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Tests
|
||
|
///////////////////////////////////////////////////////////
|
||
|
|
||
|
#define TEST_START FILESYSTEM_FILE_TEST_SPACE_BEGIN
|
||
|
#define TEST_SIZE (FILESYSTEM_FILE_TEST_SPACE_END - \
|
||
|
FILESYSTEM_FILE_TEST_SPACE_BEGIN)
|
||
|
|
||
|
void test_ancs__initialize(void) {
|
||
|
s_block_event_callback = false;
|
||
|
regular_timer_init();
|
||
|
s_num_requested_notif_attributes = 0;
|
||
|
s_num_requested_app_attributes = 0;
|
||
|
s_num_ds_notifications_received = 0;
|
||
|
s_gatt_client_op_write_should_fail_once = false;
|
||
|
s_gatt_client_op_write_should_fail_unlimited = false;
|
||
|
fake_kernel_services_notifications_reset();
|
||
|
fake_notification_storage_reset();
|
||
|
fake_event_init();
|
||
|
|
||
|
ancs_create();
|
||
|
ancs_handle_service_discovered(s_characteristics);
|
||
|
}
|
||
|
|
||
|
void test_ancs__cleanup(void) {
|
||
|
ancs_destroy();
|
||
|
cl_assert_equal_i(regular_timer_seconds_count(), 0);
|
||
|
cl_assert_equal_i(regular_timer_minutes_count(), 0);
|
||
|
regular_timer_deinit();
|
||
|
}
|
||
|
|
||
|
// Janky black box smoke-test to exercise the ANCS message re-assembly state
|
||
|
// machine
|
||
|
void test_ancs__should_handle_small_and_large_messages(void) {
|
||
|
// Get 4 complete notifications
|
||
|
prv_send_notification((uint8_t *)&s_complete_dict);
|
||
|
prv_send_notification((uint8_t *)&s_complete_dict);
|
||
|
prv_send_notification((uint8_t *)&s_complete_dict);
|
||
|
prv_send_notification((uint8_t *)&s_complete_dict);
|
||
|
cl_assert_equal_i(s_num_requested_notif_attributes, 4);
|
||
|
cl_assert_equal_i(s_num_ds_notifications_received, 4);
|
||
|
cl_assert_equal_i(fake_kernel_services_notifications_ancs_notifications_count(), 4);
|
||
|
|
||
|
// Get 4 2-part notifications
|
||
|
prv_send_notification((uint8_t *)&s_chunked_dict_part_one);
|
||
|
prv_send_notification((uint8_t *)&s_chunked_dict_part_one);
|
||
|
prv_send_notification((uint8_t *)&s_chunked_dict_part_one);
|
||
|
prv_send_notification((uint8_t *)&s_chunked_dict_part_one);
|
||
|
cl_assert_equal_i(s_num_requested_notif_attributes, 4 + 4);
|
||
|
cl_assert_equal_i(s_num_ds_notifications_received, 4 + 2*4);
|
||
|
cl_assert_equal_i(fake_kernel_services_notifications_ancs_notifications_count(), 4 + 4);
|
||
|
|
||
|
// Some alternating complete / 2-part notifications
|
||
|
prv_send_notification((uint8_t *)&s_complete_dict);
|
||
|
prv_send_notification((uint8_t *)&s_chunked_dict_part_one);
|
||
|
prv_send_notification((uint8_t *)&s_complete_dict);
|
||
|
prv_send_notification((uint8_t *)&s_chunked_dict_part_one);
|
||
|
cl_assert_equal_i(s_num_requested_notif_attributes, 8 + 4);
|
||
|
cl_assert_equal_i(s_num_ds_notifications_received, 12 + 1 + 2 + 1 + 2);
|
||
|
cl_assert_equal_i(fake_kernel_services_notifications_ancs_notifications_count(), 4 + 4 + 4);
|
||
|
|
||
|
// Send a "corrupted" notification.
|
||
|
prv_send_notification((uint8_t *)&s_invalid_attribute_length);
|
||
|
cl_assert_equal_i(s_num_requested_notif_attributes, 12 + 1);
|
||
|
cl_assert_equal_i(s_num_ds_notifications_received, 18 + 1);
|
||
|
// No increment:
|
||
|
cl_assert_equal_i(fake_kernel_services_notifications_ancs_notifications_count(), 4 + 4 + 4);
|
||
|
}
|
||
|
|
||
|
void test_ancs__should_handle_message_size_attribtue(void) {
|
||
|
prv_send_notification((uint8_t *)&s_message_size_attr_dict);
|
||
|
cl_assert_equal_i(s_num_requested_notif_attributes, 1);
|
||
|
cl_assert_equal_i(s_num_ds_notifications_received, 1);
|
||
|
cl_assert_equal_i(fake_kernel_services_notifications_ancs_notifications_count(), 1);
|
||
|
}
|
||
|
|
||
|
void test_ancs__should_filter_out_loading_messages_from_mail_app(void) {
|
||
|
// Get notification for which we'll get a "Loading..." response:
|
||
|
prv_send_notification((uint8_t *)&s_loading_response);
|
||
|
cl_assert_equal_i(s_num_requested_notif_attributes, 1);
|
||
|
cl_assert_equal_i(s_num_ds_notifications_received, 1);
|
||
|
// Assert it got filtered out:
|
||
|
cl_assert_equal_i(fake_kernel_services_notifications_ancs_notifications_count(), 0);
|
||
|
|
||
|
// Get notification for which we'll get a "This message has no content." response:
|
||
|
prv_send_notification((uint8_t *)&s_this_message_has_no_content_response);
|
||
|
cl_assert_equal_i(s_num_requested_notif_attributes, 2);
|
||
|
cl_assert_equal_i(s_num_ds_notifications_received, 2);
|
||
|
// Assert it got filtered out:
|
||
|
cl_assert_equal_i(fake_kernel_services_notifications_ancs_notifications_count(), 0);
|
||
|
}
|
||
|
|
||
|
void test_ancs__should_filter_out_duplicate_messages(void) {
|
||
|
// With an empty db, new notifications should be added as usual
|
||
|
prv_send_notification((uint8_t *)&s_complete_dict);
|
||
|
cl_assert_equal_i(fake_kernel_services_notifications_ancs_notifications_count(), 1);
|
||
|
cl_assert_equal_i(fake_notification_storage_get_store_count(), 1);
|
||
|
cl_assert_equal_i(fake_notification_storage_get_remove_count(), 0);
|
||
|
|
||
|
// We should reject any notification that matches and has the exact same uid
|
||
|
uint32_t uid = ((GetNotificationAttributesMsg *)&s_complete_dict)->notification_uid;
|
||
|
fake_notification_storage_set_existing_ancs_notification(&(Uuid)UUID_SYSTEM, uid);
|
||
|
prv_send_notification((uint8_t *)&s_complete_dict);
|
||
|
cl_assert_equal_i(fake_kernel_services_notifications_ancs_notifications_count(), 1);
|
||
|
cl_assert_equal_i(fake_notification_storage_get_store_count(), 1);
|
||
|
cl_assert_equal_i(fake_notification_storage_get_remove_count(), 0);
|
||
|
|
||
|
// If there's a notification that matches with a different uid, we update the notification by
|
||
|
// removing and then storing again (we don't send a NotificationAdded event)
|
||
|
fake_notification_storage_set_existing_ancs_notification(&(Uuid)UUID_SYSTEM, UINT32_MAX);
|
||
|
prv_send_notification((uint8_t *)&s_complete_dict);
|
||
|
cl_assert_equal_i(fake_kernel_services_notifications_ancs_notifications_count(), 1);
|
||
|
cl_assert_equal_i(fake_notification_storage_get_store_count(), 2);
|
||
|
cl_assert_equal_i(fake_notification_storage_get_remove_count(), 1);
|
||
|
}
|
||
|
|
||
|
void test_ancs__should_handle_split_timestamp_messages(void) {
|
||
|
prv_send_notification((uint8_t *)&s_split_timestamp_dict_part_one);
|
||
|
|
||
|
cl_assert_equal_i(s_num_requested_notif_attributes, 1);
|
||
|
cl_assert_equal_i(s_num_ds_notifications_received, 2);
|
||
|
cl_assert_equal_i(fake_kernel_services_notifications_ancs_notifications_count(), 1);
|
||
|
}
|
||
|
|
||
|
|
||
|
void test_ancs__attribute_at_end(void) {
|
||
|
prv_send_notification((uint8_t *)&memory_with_attribute_id_at_end.attribute_data);
|
||
|
cl_assert_equal_i(s_num_requested_notif_attributes, 1 );
|
||
|
cl_assert_equal_i(s_num_ds_notifications_received, 2);
|
||
|
}
|
||
|
|
||
|
void test_ancs__app_name_cache(void) {
|
||
|
prv_send_notification((uint8_t *)&s_message_dict);
|
||
|
prv_send_notification((uint8_t *)&s_message_dict);
|
||
|
|
||
|
cl_assert_equal_i(s_num_requested_notif_attributes, 2);
|
||
|
// should have gotten cached the second time around
|
||
|
cl_assert_equal_i(s_num_requested_app_attributes, 1);
|
||
|
cl_assert_equal_i(s_num_ds_notifications_received, 2);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
void test_ancs__ancs_invalid_param(void) {
|
||
|
|
||
|
NSNotification ns_notification = {
|
||
|
.event_id = EventIDNotificationAdded,
|
||
|
.event_flags = 0,
|
||
|
.category_id = CategoryIDSocial,
|
||
|
.category_count = 1,
|
||
|
.uid = 0,
|
||
|
};
|
||
|
|
||
|
const uint32_t comple_dict_uid = ((GetNotificationAttributesMsg*)s_complete_dict)->notification_uid;
|
||
|
|
||
|
ns_notification.uid = s_invalid_param_uid;
|
||
|
// This will return with an error ANCS_INVALID_PARAM
|
||
|
// Should not get re-requested
|
||
|
prv_fake_receiving_ns_notification(sizeof(ns_notification), (uint8_t*) &ns_notification);
|
||
|
cl_assert_equal_i(s_num_requested_notif_attributes, 1 );
|
||
|
cl_assert_equal_i(s_num_ds_notifications_received, 1);
|
||
|
|
||
|
ns_notification.uid = comple_dict_uid;
|
||
|
prv_fake_receiving_ns_notification(sizeof(ns_notification), (uint8_t*) &ns_notification);
|
||
|
cl_assert_equal_i(s_num_requested_notif_attributes, 2);
|
||
|
cl_assert_equal_i(s_num_ds_notifications_received, 2);
|
||
|
|
||
|
ns_notification.uid = s_invalid_param_uid;
|
||
|
// This will return with an error ANCS_INVALID_PARAM
|
||
|
// Should not get re-requested
|
||
|
prv_fake_receiving_ns_notification(sizeof(ns_notification), (uint8_t*) &ns_notification);
|
||
|
cl_assert_equal_i(s_num_requested_notif_attributes, 3);
|
||
|
cl_assert_equal_i(s_num_ds_notifications_received, 3);
|
||
|
|
||
|
ns_notification.uid = s_invalid_param_uid;
|
||
|
// This will return with an error ANCS_INVALID_PARAM
|
||
|
// Should not get re-requested
|
||
|
prv_fake_receiving_ns_notification(sizeof(ns_notification), (uint8_t*) &ns_notification);
|
||
|
cl_assert_equal_i(s_num_requested_notif_attributes, 4);
|
||
|
cl_assert_equal_i(s_num_ds_notifications_received, 4);
|
||
|
|
||
|
ns_notification.uid = comple_dict_uid;
|
||
|
prv_fake_receiving_ns_notification(sizeof(ns_notification), (uint8_t*) &ns_notification);
|
||
|
cl_assert_equal_i(s_num_requested_notif_attributes, 5);
|
||
|
cl_assert_equal_i(s_num_ds_notifications_received, 5);
|
||
|
|
||
|
}
|
||
|
|
||
|
extern ANCSClientState prv_get_state(void);
|
||
|
extern void prv_check_ancs_alive(void);
|
||
|
|
||
|
void test_ancs__alive_check_disconnection(void) {
|
||
|
prv_check_ancs_alive();
|
||
|
// check we're in the alive check state and we sent a single request
|
||
|
cl_assert_equal_i(prv_get_state(), ANCSClientStateAliveCheck);
|
||
|
cl_assert_equal_i(s_num_requested_notif_attributes, 1);
|
||
|
// simulate a disconnection/reconnection
|
||
|
ancs_handle_service_removed(s_characteristics, NumANCSCharacteristic);
|
||
|
ancs_handle_service_discovered(s_characteristics);
|
||
|
// we should be back in the Idle state
|
||
|
cl_assert_equal_i(prv_get_state(), ANCSClientStateIdle);
|
||
|
// Make sure we can still receive notifications
|
||
|
prv_send_notification((uint8_t *)&s_complete_dict);
|
||
|
cl_assert_equal_i(s_num_requested_notif_attributes, 2);
|
||
|
cl_assert_equal_i(s_num_ds_notifications_received, 1);
|
||
|
cl_assert_equal_i(fake_kernel_services_notifications_ancs_notifications_count(), 1);
|
||
|
}
|
||
|
|
||
|
void test_ancs__notification_dismissal(void) {
|
||
|
NSNotification ns_notification = {
|
||
|
.event_id = EventIDNotificationRemoved,
|
||
|
.event_flags = 0,
|
||
|
.category_id = CategoryIDSocial,
|
||
|
.category_count = 1,
|
||
|
};
|
||
|
|
||
|
// Notification removal without DIS service - notification shouldn't be acted upon
|
||
|
prv_fake_receiving_ns_notification(sizeof(ns_notification), (uint8_t*) &ns_notification);
|
||
|
cl_assert_equal_i(fake_kernel_services_notifications_acted_upon_count(), 0);
|
||
|
|
||
|
// DIS service / iOS 9+ detected - enabling notification dismissal
|
||
|
ancs_handle_ios9_or_newer_detected();
|
||
|
prv_fake_receiving_ns_notification(sizeof(ns_notification), (uint8_t*) &ns_notification);
|
||
|
cl_assert_equal_i(fake_kernel_services_notifications_acted_upon_count(), 1);
|
||
|
}
|
||
|
|
||
|
void test_ancs__notification_parsing(void) {
|
||
|
// Test a recognized app with a duplicated title
|
||
|
// Run multiple times to make sure we're not corrupting the app name cache
|
||
|
for (int i = 0; i < 4; ++i) {
|
||
|
prv_send_notification((uint8_t *)&s_app_name_title_dict);
|
||
|
prv_cmp_last_received_notification(&s_app_name_title_parsed_item);
|
||
|
}
|
||
|
|
||
|
// Test an unrecognized app with a duplicated title
|
||
|
prv_send_notification((uint8_t *)&s_unknown_app_dict);
|
||
|
prv_cmp_last_received_notification(&s_unknown_app_parsed_item);
|
||
|
|
||
|
// Make sure both apps attributes were requested (Messages and FakeApp)
|
||
|
cl_assert_equal_i(s_num_requested_app_attributes, 2);
|
||
|
|
||
|
// Test a recognized app with a unique title
|
||
|
prv_send_notification((uint8_t *)&s_message_dict);
|
||
|
prv_cmp_last_received_notification(&s_message_parsed_item);
|
||
|
|
||
|
// Test an unrecognized app with a unique title
|
||
|
prv_send_notification((uint8_t *)&s_unknown_app_unique_title_dict);
|
||
|
prv_cmp_last_received_notification(&s_unknown_app_unique_title_parsed_item);
|
||
|
|
||
|
// Test an MMS without a caption
|
||
|
prv_send_notification_with_event_flags((uint8_t *)&s_mms_no_caption_dict, EventFlagMultiMedia);
|
||
|
prv_cmp_last_received_notification(&s_mms_no_caption_parsed_item);
|
||
|
|
||
|
// Test an MMS with a caption
|
||
|
prv_send_notification_with_event_flags((uint8_t *)&s_mms_with_caption_dict, EventFlagMultiMedia);
|
||
|
prv_cmp_last_received_notification(&s_mms_with_caption_parsed_item);
|
||
|
|
||
|
// Test a third party notification with the MultiMedia EventFlag
|
||
|
prv_send_notification_with_event_flags((uint8_t *)&s_unknown_app_unique_title_dict, EventFlagMultiMedia);
|
||
|
prv_cmp_last_received_notification(&s_unknown_app_unique_title_parsed_item);
|
||
|
}
|
||
|
|
||
|
// Make sure we send an ANCS_DISCONNECTED event whenever our session goes away
|
||
|
void test_ancs__disconnection(void) {
|
||
|
// Simulate a disconnection/reconnection
|
||
|
ancs_handle_service_removed(s_characteristics, NumANCSCharacteristic);
|
||
|
ancs_handle_service_discovered(s_characteristics);
|
||
|
cl_assert_equal_i(fake_event_get_last().type, PEBBLE_ANCS_DISCONNECTED_EVENT);
|
||
|
fake_event_clear_last();
|
||
|
|
||
|
// If we unexpectedly register another session, make sure we send the event
|
||
|
ancs_handle_service_discovered(s_characteristics);
|
||
|
cl_assert_equal_i(fake_event_get_last().type, PEBBLE_ANCS_DISCONNECTED_EVENT);
|
||
|
fake_event_clear_last();
|
||
|
|
||
|
ancs_invalidate_all_references();
|
||
|
cl_assert_equal_i(fake_event_get_last().type, PEBBLE_ANCS_DISCONNECTED_EVENT);
|
||
|
|
||
|
// Make sure that losing BT altogether sends the event
|
||
|
ancs_destroy();
|
||
|
cl_assert_equal_i(fake_event_get_last().type, PEBBLE_ANCS_DISCONNECTED_EVENT);
|
||
|
fake_event_clear_last();
|
||
|
}
|
||
|
|
||
|
void test_ancs__unrequested_notifications(void) {
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(s_complete_dict), (uint8_t*) s_complete_dict);
|
||
|
cl_assert_equal_i(fake_kernel_services_notifications_ancs_notifications_count(), 0);
|
||
|
|
||
|
prv_fake_receiving_ds_notification(ARRAY_LENGTH(s_message_app_info_dict),
|
||
|
(uint8_t *)s_message_app_info_dict);
|
||
|
cl_assert_equal_i(fake_kernel_services_notifications_ancs_notifications_count(), 0);
|
||
|
}
|
||
|
|
||
|
void test_ancs__handle_unexpected_notifications(void) {
|
||
|
NSNotification ns_notification = {
|
||
|
.event_id = EventIDNotificationAdded,
|
||
|
.event_flags = 0,
|
||
|
.category_id = CategoryIDSocial,
|
||
|
.category_count = 1,
|
||
|
.uid = s_get_wrong_data_uid,
|
||
|
};
|
||
|
prv_fake_receiving_ns_notification(sizeof(ns_notification), (uint8_t*) &ns_notification);
|
||
|
cl_assert_equal_i(fake_kernel_services_notifications_ancs_notifications_count(), 0);
|
||
|
|
||
|
// And make sure we get to a state where we can handle more messages
|
||
|
prv_send_notification((uint8_t *)&s_complete_dict);
|
||
|
cl_assert_equal_i(s_num_requested_notif_attributes, 2);
|
||
|
cl_assert_equal_i(s_num_ds_notifications_received, 2);
|
||
|
cl_assert_equal_i(fake_kernel_services_notifications_ancs_notifications_count(), 1);
|
||
|
}
|
||
|
|
||
|
void test_ancs__get_notif_attributes_retry(void) {
|
||
|
s_gatt_client_op_write_should_fail_once = true;
|
||
|
|
||
|
prv_send_notification((uint8_t *)&s_complete_dict);
|
||
|
// We will be successful on the retry (second attempt)
|
||
|
cl_assert_equal_i(fake_kernel_services_notifications_ancs_notifications_count(), 1);
|
||
|
|
||
|
s_gatt_client_op_write_should_fail_unlimited = true;
|
||
|
prv_send_notification((uint8_t *)&s_complete_dict);
|
||
|
// The retry fails and we give up on this one
|
||
|
cl_assert_equal_i(fake_kernel_services_notifications_ancs_notifications_count(), 1);
|
||
|
|
||
|
// And make sure we get to a state where we can handle more messages
|
||
|
s_gatt_client_op_write_should_fail_unlimited = false;
|
||
|
prv_send_notification((uint8_t *)&s_complete_dict);
|
||
|
cl_assert_equal_i(fake_kernel_services_notifications_ancs_notifications_count(), 2);
|
||
|
}
|
||
|
|
||
|
void test_ancs__reset_after_retry(void) {
|
||
|
s_block_event_callback = true;
|
||
|
s_gatt_client_op_write_should_fail_once = true;
|
||
|
prv_send_notification((uint8_t *)&s_complete_dict);
|
||
|
ancs_invalidate_all_references();
|
||
|
cl_assert_equal_i(prv_get_state(), ANCSClientStateIdle);
|
||
|
}
|
||
|
|
||
|
// No Longer Supported
|
||
|
//void test_ancs__should_handle_response_with_multiple_notifications(void) {
|
||
|
//
|
||
|
// NSNotification ns_notification = {
|
||
|
// .event_id = EventIDNotificationAdded,
|
||
|
// .event_flags = 0,
|
||
|
// .category_id = CategoryIDSocial,
|
||
|
// .category_count = 1,
|
||
|
// .uid = 0,
|
||
|
// };
|
||
|
//
|
||
|
// const uint32_t multiple_complete_dict_uid = ((GetNotificationAttributesMsg*)s_multiple_complete_dicts)->notification_uid;
|
||
|
// ns_notification.uid = multiple_complete_dict_uid;
|
||
|
// prv_fake_receiving_ns_notification(sizeof(ns_notification), (uint8_t*) &ns_notification);
|
||
|
// cl_assert_equal_i(s_num_requested_notif_attributes, 1);
|
||
|
// cl_assert_equal_i(s_num_ds_notifications_received, 3);
|
||
|
//
|
||
|
// // The last one was a phone notification but I changed it so it no longer is
|
||
|
// cl_assert_equal_i(fake_kernel_services_notifications_ancs_notifications_count(), 3);
|
||
|
//}
|
||
|
|