pebble/tests/fw/comm/test_ancs.c

645 lines
28 KiB
C
Raw Permalink Normal View History

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "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(&notification->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);
//}