mirror of
https://github.com/google/pebble
synced 2026-10-17 08:44:11 +02:00
Import of the watch repository from Pebble
This commit is contained in:
@@ -0,0 +1,681 @@
|
||||
/*
|
||||
* 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 <stdint.h>
|
||||
|
||||
#include "resource/timeline_resource_ids.auto.h"
|
||||
#include "util/attributes.h"
|
||||
|
||||
// Chunked dictionary 1/2 sent by ANCS (w/header)
|
||||
// 00 97 00 00 00 00 13 00 63 6f 6d 2e 61 70 70 6c ........ com.appl
|
||||
// 65 2e 4d 6f 62 69 6c 65 53 4d 53 01 10 00 72 6a e.Mobile SMS...rj
|
||||
// 63 61 73 65 40 67 6d 61 69 6c 2e 63 6f 6d 02 00 case@gma il.com..
|
||||
// 00 03 c8 00 54 68 69 73 20 69 73 20 61 20 76 65 ....This is a ve
|
||||
// 72 79 20 63 6f 6d 70 6c 69 63 61 74 65 64 20 63 ry compl icated c
|
||||
// 61 73 65 2c 20 4d 61 75 64 65 2e 20 59 6f 75 20 ase, Mau de. You
|
||||
// 6b 6e 6f 77 2c 20 61 20 6c 6f 74 74 61 20 69 6e know, a lotta in
|
||||
// 73 2c 20 6c 6f 74 74 61 20 6f 75 74 73 2c 20 6c s, lotta outs, l
|
||||
// 6f 74 74 61 20 77 68 61 74 2d 68 61 76 65 2d 79 otta wha t-have-y
|
||||
// 6f 75 27 73 2e 20 41 6e 64 2c 20 ou's. An d,
|
||||
// NB header has been removed
|
||||
static const uint8_t s_chunked_dict_part_one[] = {
|
||||
0x00, 0x97, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c,
|
||||
0x65, 0x2e, 0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x53, 0x4d, 0x53, 0x01, 0x10, 0x00, 0x72, 0x6a,
|
||||
0x63, 0x61, 0x73, 0x65, 0x40, 0x67, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x02, 0x00,
|
||||
0x00, 0x03, 0xc8, 0x00, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x76, 0x65,
|
||||
0x72, 0x79, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x20, 0x63,
|
||||
0x61, 0x73, 0x65, 0x2c, 0x20, 0x4d, 0x61, 0x75, 0x64, 0x65, 0x2e, 0x20, 0x59, 0x6f, 0x75, 0x20,
|
||||
0x6b, 0x6e, 0x6f, 0x77, 0x2c, 0x20, 0x61, 0x20, 0x6c, 0x6f, 0x74, 0x74, 0x61, 0x20, 0x69, 0x6e,
|
||||
0x73, 0x2c, 0x20, 0x6c, 0x6f, 0x74, 0x74, 0x61, 0x20, 0x6f, 0x75, 0x74, 0x73, 0x2c, 0x20, 0x6c,
|
||||
0x6f, 0x74, 0x74, 0x61, 0x20, 0x77, 0x68, 0x61, 0x74, 0x2d, 0x68, 0x61, 0x76, 0x65, 0x2d, 0x79,
|
||||
0x6f, 0x75, 0x27, 0x73, 0x2e, 0x20, 0x41, 0x6e, 0x64, 0x2c, 0x20
|
||||
};
|
||||
|
||||
// Chunked dictionary 2/2 sent by ANCS
|
||||
// 75 68 2c 20 6c 6f 74 74 61 20 73 74 72 61 6e 64 uh, lott a strand
|
||||
// 73 20 74 6f 20 6b 65 65 70 20 69 6e 20 6d 79 20 s to kee p in my
|
||||
// 68 65 61 64 2c 20 6d 61 6e 2e 20 4c 6f 74 74 61 head, ma n. Lotta
|
||||
// 20 73 74 72 61 6e 64 73 20 69 6e 20 6f 6c 64 20 strands in old
|
||||
// 44 75 64 65 72 27 73 20 68 65 61 64 2e 20 4c 75 Duder's head. Lu
|
||||
// 63 6b 69 6c 79 20 49 27 6d 20 61 64 68 65 72 69 ckily I' m adheri
|
||||
// 6e n
|
||||
static const uint8_t s_chunked_dict_part_two[] = {
|
||||
0x75, 0x68, 0x2c, 0x20, 0x6c, 0x6f, 0x74, 0x74, 0x61, 0x20, 0x73, 0x74, 0x72, 0x61, 0x6e, 0x64,
|
||||
0x73, 0x20, 0x74, 0x6f, 0x20, 0x6b, 0x65, 0x65, 0x70, 0x20, 0x69, 0x6e, 0x20, 0x6d, 0x79, 0x20,
|
||||
0x68, 0x65, 0x61, 0x64, 0x2c, 0x20, 0x6d, 0x61, 0x6e, 0x2e, 0x20, 0x4c, 0x6f, 0x74, 0x74, 0x61,
|
||||
0x20, 0x73, 0x74, 0x72, 0x61, 0x6e, 0x64, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x6f, 0x6c, 0x64, 0x20,
|
||||
0x44, 0x75, 0x64, 0x65, 0x72, 0x27, 0x73, 0x20, 0x68, 0x65, 0x61, 0x64, 0x2e, 0x20, 0x4c, 0x75,
|
||||
0x63, 0x6b, 0x69, 0x6c, 0x79, 0x20, 0x49, 0x27, 0x6d, 0x20, 0x61, 0x64, 0x68, 0x65, 0x72, 0x69,
|
||||
0x6e, /* date */ 0x05, 0x0f, 0x00, 0x32, 0x30, 0x31,
|
||||
0x33, 0x31, 0x31, 0x30, 0x35, 0x54, 0x31, 0x38, 0x31, 0x39, 0x32, 0x38
|
||||
};
|
||||
|
||||
// Complete dictionary sent by ANCS
|
||||
// 00 98 00 00 00 00 13 00 63 6f 6d 2e 61 70 70 6c ........ com.appl
|
||||
// 65 2e 4d 6f 62 69 6c 65 53 4d 53 01 10 00 72 6a e.Mobile SMS...rj
|
||||
// 63 61 73 65 40 67 6d 61 69 6c 2e 63 6f 6d 02 00 case@gma il.com..
|
||||
// 00 03 26 00 43 61 72 65 66 75 6c 2c 20 6d 61 6e ..&.Care ful, man
|
||||
// 2c 20 74 68 65 72 65 27 73 20 61 20 62 65 76 65 , there' s a beve
|
||||
// 72 61 67 65 20 68 65 72 65 21 rage her e! // FIXME 4th
|
||||
// attr has been appended
|
||||
// Has header removed:
|
||||
static const uint8_t s_complete_dict[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c,
|
||||
0x65, 0x2e, 0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x53, 0x4d, 0x53, 0x01, 0x10, 0x00, 0x72, 0x6a,
|
||||
0x63, 0x61, 0x73, 0x65, 0x40, 0x67, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x02, 0x00,
|
||||
0x00, 0x03, 0x26, 0x00, 0x43, 0x61, 0x72, 0x65, 0x66, 0x75, 0x6c, 0x2c, 0x20, 0x6d, 0x61, 0x6e,
|
||||
0x2c, 0x20, 0x74, 0x68, 0x65, 0x72, 0x65, 0x27, 0x73, 0x20, 0x61, 0x20, 0x62, 0x65, 0x76, 0x65,
|
||||
0x72, 0x61, 0x67, 0x65, 0x20, 0x68, 0x65, 0x72, 0x65, 0x21, /* date */ 0x05, 0x0f, 0x00, 0x32, 0x30, 0x31,
|
||||
0x33, 0x31, 0x31, 0x30, 0x35, 0x54, 0x31, 0x38, 0x31, 0x39, 0x32, 0x38
|
||||
};
|
||||
|
||||
|
||||
//! 00 3c 00 00 00 05 0f 00 32 30 31 36 30 33 30 31 ........ 20160301
|
||||
//! 54 31 34 31 35 32 33 01 08 00 4f 76 65 72 63 61 T141523. ..Overca
|
||||
//! 73 74 02 00 00 03 2b 00 54 68 65 20 4d 6f 74 68 st....+. The Moth
|
||||
//! 20 50 6f 64 63 61 73 74 3a 20 4d 6f 6e 69 63 61 Podcast : Monica
|
||||
//! 20 43 68 65 72 75 2d 4d 70 61 6d 62 61 77 61 73 Cheru-M pambawas
|
||||
//! e2 80 a6 04 03 00 31 31 33 ......11 3
|
||||
static const uint8_t s_message_size_attr_dict[] = {
|
||||
0x00, 0x3c, 0x00, 0x00, 0x00, 0x05, 0x0f, 0x00, 0x32, 0x30, 0x31, 0x36, 0x30, 0x33, 0x30, 0x31,
|
||||
0x54, 0x31, 0x34, 0x31, 0x35, 0x32, 0x33, 0x01, 0x08, 0x00, 0x4f, 0x76, 0x65, 0x72, 0x63, 0x61,
|
||||
0x73, 0x74, 0x02, 0x00, 0x00, 0x03, 0x2b, 0x00, 0x54, 0x68, 0x65, 0x20, 0x4d, 0x6f, 0x74, 0x68,
|
||||
0x20, 0x50, 0x6f, 0x64, 0x63, 0x61, 0x73, 0x74, 0x3a, 0x20, 0x4d, 0x6f, 0x6e, 0x69, 0x63, 0x61,
|
||||
0x20, 0x43, 0x68, 0x65, 0x72, 0x75, 0x2d, 0x4d, 0x70, 0x61, 0x6d, 0x62, 0x61, 0x77, 0x61, 0x73,
|
||||
0xe2, 0x80, 0xa6, 0x04, 0x03, 0x00, 0x31, 0x31, 0x33,
|
||||
/* adding an app_id because it didnt' come with one */
|
||||
0x00, 0x01, 0x00, 0x50,
|
||||
};
|
||||
|
||||
//! Complete dict for a messages app notification
|
||||
//! 00 1a 00 00 00 06 00 00 07 05 00 43 6c 65 61 72 ........ ...Clear
|
||||
//! 00 13 00 63 6f 6d 2e 61 70 70 6c 65 2e 4d 6f 62 ...com.a pple.Mob
|
||||
//! 69 6c 65 53 4d 53 01 13 00 46 72 61 6e 63 6f 69 ileSMS.. .Francoi
|
||||
//! 73 20 42 61 6c 64 61 73 73 61 72 69 02 00 00 03 s Baldas sari....
|
||||
//! 1b 00 74 68 69 73 20 69 73 20 61 20 74 65 73 74 ..this i s a test
|
||||
//! 20 6e 6f 74 69 66 69 63 61 74 69 6f 6e 05 0f 00 notific ation...
|
||||
//! 32 30 31 35 30 36 31 35 54 31 36 31 38 33 37 20150615 T161837
|
||||
static const uint8_t s_message_dict[] = {
|
||||
0x00, 0x1a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x05, 0x00, 0x43, 0x6c, 0x65, 0x61, 0x72,
|
||||
0x00, 0x13, 0x00, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x4d, 0x6f, 0x62,
|
||||
0x69, 0x6c, 0x65, 0x53, 0x4d, 0x53, 0x01, 0x13, 0x00, 0x46, 0x72, 0x61, 0x6e, 0x63, 0x6f, 0x69,
|
||||
0x73, 0x20, 0x42, 0x61, 0x6c, 0x64, 0x61, 0x73, 0x73, 0x61, 0x72, 0x69, 0x02, 0x00, 0x00, 0x03,
|
||||
0x1b, 0x00, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74,
|
||||
0x20, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x05, 0x0f, 0x00,
|
||||
0x32, 0x30, 0x31, 0x35, 0x30, 0x36, 0x31, 0x35, 0x54, 0x31, 0x36, 0x31, 0x38, 0x33, 0x37
|
||||
};
|
||||
|
||||
static TimelineItem s_message_parsed_item = {
|
||||
.header = {
|
||||
.id = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
.ancs_uid = 0x1a,
|
||||
.timestamp = 1434385117,
|
||||
.duration = 0,
|
||||
.type = TimelineItemTypeNotification,
|
||||
.layout = LayoutIdNotification,
|
||||
.ancs_notif = 1
|
||||
},
|
||||
.attr_list = (AttributeList) {
|
||||
.num_attributes = 6,
|
||||
.attributes = (Attribute[6]) {
|
||||
{ .id = AttributeIdSender, .cstring = "Francois Baldassari" },
|
||||
{ .id = AttributeIdTitle, .cstring = "Francois Baldassari" },
|
||||
{ .id = AttributeIdBody, .cstring = "this is a test notification" },
|
||||
{ .id = AttributeIdiOSAppIdentifier, .cstring = "com.apple.MobileSMS" },
|
||||
{ .id = AttributeIdIconTiny, .uint32 = TIMELINE_RESOURCE_GENERIC_SMS },
|
||||
{ .id = AttributeIdBgColor, .uint8 = GColorIslamicGreenARGB8 }
|
||||
}
|
||||
},
|
||||
.action_group = (TimelineItemActionGroup) {
|
||||
.num_actions = 1,
|
||||
.actions = (TimelineItemAction[1]) {
|
||||
{
|
||||
.id = 0,
|
||||
.type = TimelineItemActionTypeAncsNegative,
|
||||
.attr_list = (AttributeList) {
|
||||
.num_attributes = 2,
|
||||
.attributes = (Attribute[2]) {
|
||||
{ .id = AttributeIdAncsAction, .uint8 = ActionIDNegative},
|
||||
{ .id = AttributeIdTitle, .cstring = "Dismiss" }}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//! Complete dict for the messages app attributes
|
||||
//! 01 63 6f 6d 2e 61 70 70 6c 65 2e 4d 6f 62 69 6c .com.app le.Mobil
|
||||
//! 65 53 4d 53 00 00 08 00 4d 65 73 73 61 67 65 73 eSMS.... Messages
|
||||
|
||||
static const uint8_t s_message_app_info_dict[] = {
|
||||
0x01, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x4d, 0x6f, 0x62, 0x69, 0x6c,
|
||||
0x65, 0x53, 0x4d, 0x53, 0x00, 0x00, 0x08, 0x00, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73
|
||||
};
|
||||
|
||||
//! Complete dict for the unrecognized fake app attributes
|
||||
//! 01 63 6f 6d 2e 74 65 73 74 73 2e 4e 6f 74 41 6e .com.tes ts.NotAn
|
||||
//! 41 70 70 00 00 07 00 46 61 6b 65 41 70 70 App....F akeApp
|
||||
|
||||
static const uint8_t s_fake_app_info_dict[] = {
|
||||
0x01, 0x63, 0x6f, 0x6d, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x4e, 0x6f, 0x74, 0x41, 0x6e,
|
||||
0x41, 0x70, 0x70, 0x00, 0x00, 0x07, 0x00, 0x46, 0x61, 0x6b, 0x65, 0x41, 0x70, 0x70,
|
||||
};
|
||||
|
||||
// Complete app dictionary sent by ANCS
|
||||
// 01 63 6f 6d 2e 67 65 74 70 65 62 62 6c 65 2e 70 .com.get pebble.p
|
||||
// 65 62 62 6c 65 74 69 6d 65 2e 65 6e 74 65 72 70 ebbletim e.enterp
|
||||
// 72 69 73 65 00 00 0e 00 50 65 62 62 6c 65 20 54 rise.... Pebble T
|
||||
// 69 6d 65 20 ce b2 ime ..
|
||||
static const uint8_t s_app_attr_dict[] = {
|
||||
0x01, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x65, 0x74, 0x70, 0x65, 0x62, 0x62, 0x6c, 0x65, 0x2e, 0x70,
|
||||
0x65, 0x62, 0x62, 0x6c, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70,
|
||||
0x72, 0x69, 0x73, 0x65, 0x00, 0x00, 0x0e, 0x00, 0x50, 0x65, 0x62, 0x62, 0x6c, 0x65, 0x20, 0x54,
|
||||
0x69, 0x6d, 0x65, 0x20, 0xce, 0xb2
|
||||
};
|
||||
|
||||
static const uint8_t s_missing_last_attribute[] = {
|
||||
0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c,
|
||||
0x65, 0x2e, 0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x53, 0x4d, 0x53, 0x01, 0x10, 0x00, 0x72, 0x6a,
|
||||
0x63, 0x61, 0x73, 0x65, 0x40, 0x67, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x02, 0x00,
|
||||
0x00, 0x03, 0x26, 0x00, 0x43, 0x61, 0x72, 0x65, 0x66, 0x75, 0x6c, 0x2c, 0x20, 0x6d, 0x61, 0x6e,
|
||||
0x2c, 0x20, 0x74, 0x68, 0x65, 0x72, 0x65, 0x27, 0x73, 0x20, 0x61, 0x20, 0x62, 0x65, 0x76, 0x65,
|
||||
0x72, 0x61, 0x67, 0x65, 0x20, 0x68, 0x65, 0x72, 0x65, 0x21
|
||||
};
|
||||
|
||||
// Length of attribute too long
|
||||
static const uint8_t s_invalid_attribute_length[] = {
|
||||
0x00, 0x94, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c,
|
||||
0x65, 0x2e, 0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x53, 0x4d, 0x53, 0x01, 0x29, 0x00, 0x72, 0x6a,
|
||||
0x63, 0x61, 0x73, 0x65, 0x40, 0x67, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x43, 0x61, 0x72, 0x65, 0x66, 0x75, 0x6c, 0x2c, 0x20, 0x6d, 0x61, 0x6e,
|
||||
0x2c, 0x20, 0x74, 0x68, 0x65, 0x72, 0x65, 0x00, 0x73, 0x20, 0x61, 0x20, 0x62, 0x65, 0x76, 0x65,
|
||||
0x72, 0x61, 0x67, 0x65, 0x20, 0x68, 0x65, 0x72, 0x65, 0x21, 0x02, 0x00, 0x00, 0x03, 0x26, 0x00,
|
||||
0x43, 0x61, 0x72, 0x65, 0x66, 0x75, 0x6c, 0x2c, 0x20, 0x6d, 0x61, 0x6e, 0x2c, 0x20, 0x74, 0x68,
|
||||
0x65, 0x72, 0x65, 0x27, 0x73, 0x20, 0x61, 0x20, 0x62, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65,
|
||||
0x20, 0x68, 0x65, 0x72, 0x65, 0x21, 0x05, 0x0f, 0x00, 0x32, 0x30, 0x31, 0x33, 0x31, 0x31, 0x30,
|
||||
0x35, 0x54, 0x31, 0x38, 0x31, 0x39, 0x32, 0x38
|
||||
};
|
||||
|
||||
// Chunked dictionary 1/2 sent by ANCS (w/header)
|
||||
// 00 0d 00 00 00 00 1b 00 63 6f 6d 2e 6b 69 63 6b ........ com.kick
|
||||
// 73 74 61 72 74 65 72 2e 6b 69 63 6b 73 74 61 72 starter. kickstar
|
||||
// 74 65 72 01 0b 00 4b 69 63 6b 73 74 61 72 74 65 ter...Ki ckstarte
|
||||
// 72 02 00 00 03 56 00 52 65 73 70 6f 6e 73 65 20 r....V.R esponse
|
||||
// 6e 65 65 64 65 64 21 20 47 65 74 20 79 6f 75 72 needed! Get your
|
||||
// 20 72 65 77 61 72 64 20 66 6f 72 20 62 61 63 6b reward for back
|
||||
// 69 6e 67 20 50 65 62 62 6c 65 3a 20 45 2d 50 61 ing Pebb le: E-Pa
|
||||
// 70 65 72 79 20 57 61 74 63 68 20 66 6f 72 20 69 pery Wat ch for i
|
||||
// 4f 53 20 61 6e 64 20 44 72 6f 69 64 2e 05 0f 00 OS and D roid....
|
||||
// 32 30 31 34 30 36 31 30 54 32 32 20140610 T22
|
||||
static const uint8_t s_split_timestamp_dict_part_one[] = {
|
||||
0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x63, 0x6f, 0x6d, 0x2e, 0x6b, 0x69, 0x63, 0x6b,
|
||||
0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x72, 0x2e, 0x6b, 0x69, 0x63, 0x6b, 0x73, 0x74, 0x61, 0x72,
|
||||
0x74, 0x65, 0x72, 0x01, 0x0b, 0x00, 0x4b, 0x69, 0x63, 0x6b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65,
|
||||
0x72, 0x02, 0x00, 0x00, 0x03, 0x56, 0x00, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x20,
|
||||
0x6e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x21, 0x20, 0x47, 0x65, 0x74, 0x20, 0x79, 0x6f, 0x75, 0x72,
|
||||
0x20, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x62, 0x61, 0x63, 0x6b,
|
||||
0x69, 0x6e, 0x67, 0x20, 0x50, 0x65, 0x62, 0x62, 0x6c, 0x65, 0x3a, 0x20, 0x45, 0x2d, 0x50, 0x61,
|
||||
0x70, 0x65, 0x72, 0x79, 0x20, 0x57, 0x61, 0x74, 0x63, 0x68, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x69,
|
||||
0x4f, 0x53, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x44, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x05, 0x0f, 0x00,
|
||||
0x32, 0x30, 0x31, 0x34, 0x30, 0x36, 0x31, 0x30, 0x54, 0x32, 0x32
|
||||
};
|
||||
|
||||
// Chunked dictionary 2/2 sent by ANCS
|
||||
// 32 30 34 38 2048
|
||||
static const uint8_t s_split_timestamp_dict_part_two[] = {
|
||||
0x32, 0x30, 0x34, 0x38
|
||||
};
|
||||
|
||||
static const uint8_t s_invalid_attribute_length_fixed[] = {
|
||||
0x00, 0x94, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c,
|
||||
0x65, 0x2e, 0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x53, 0x4d, 0x53, 0x01, 0x10, 0x00, 0x72, 0x6a,
|
||||
0x63, 0x61, 0x73, 0x65, 0x40, 0x67, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x02, 0x00,
|
||||
0x00, 0x03, 0x26, 0x00, 0x43, 0x61, 0x72, 0x65, 0x66, 0x75, 0x6c, 0x2c, 0x20, 0x6d, 0x61, 0x6e,
|
||||
0x2c, 0x20, 0x74, 0x68, 0x65, 0x72, 0x65, 0x27, 0x73, 0x20, 0x61, 0x20, 0x62, 0x65, 0x76, 0x65,
|
||||
0x72, 0x61, 0x67, 0x65, 0x20, 0x68, 0x65, 0x72, 0x65, 0x21, 0x05, 0x0f, 0x00, 0x32, 0x30, 0x31,
|
||||
0x33, 0x31, 0x31, 0x30, 0x35, 0x54, 0x31, 0x38, 0x31, 0x39, 0x32, 0x38
|
||||
};
|
||||
|
||||
|
||||
// 00 22 00 00 00 07 07 00 44 69 73 6d 69 73 73 00 ."...... Dismiss.
|
||||
// 14 00 63 6f 6d 2e 61 70 70 6c 65 2e 6d 6f 62 69 ..com.ap ple.mobi
|
||||
// 6c 65 6d 61 69 6c 01 15 00 41 61 72 6f 6e 20 48 lemail.. .Aaron H
|
||||
// 65 63 6b 6d 61 6e 6e 20 28 4a 49 52 41 29 02 28 eckmann (JIRA).(
|
||||
// 00 5b 4a 49 52 41 5d 20 28 50 42 4c 2d 38 37 37 .[JIRA] (PBL-877
|
||||
// 36 29 20 45 72 72 6f 72 20 73 63 72 65 65 6e 20 6) Error screen
|
||||
// 61 66 74 65 72 20 6c 6f 67 03 1c 00 54 68 69 73 after lo g...This
|
||||
// 20 6d 65 73 73 61 67 65 20 68 61 73 20 6e 6f 20 message has no
|
||||
// 63 6f 6e 74 65 6e 74 2e 05 0f 00 32 30 31 34 30 content. ...20140
|
||||
// 39 32 36 54 31 30 34 33 30 30 926T1043 00
|
||||
static const uint8_t s_this_message_has_no_content_response[] = {
|
||||
0x00, 0x22, 0x00, 0x00, 0x00, 0x07, 0x07, 0x00, 0x44, 0x69, 0x73, 0x6d, 0x69, 0x73, 0x73, 0x00,
|
||||
0x14, 0x00, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x6d, 0x6f, 0x62, 0x69,
|
||||
0x6c, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x01, 0x15, 0x00, 0x41, 0x61, 0x72, 0x6f, 0x6e, 0x20, 0x48,
|
||||
0x65, 0x63, 0x6b, 0x6d, 0x61, 0x6e, 0x6e, 0x20, 0x28, 0x4a, 0x49, 0x52, 0x41, 0x29, 0x02, 0x28,
|
||||
0x00, 0x5b, 0x4a, 0x49, 0x52, 0x41, 0x5d, 0x20, 0x28, 0x50, 0x42, 0x4c, 0x2d, 0x38, 0x37, 0x37,
|
||||
0x36, 0x29, 0x20, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x20, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20,
|
||||
0x61, 0x66, 0x74, 0x65, 0x72, 0x20, 0x6c, 0x6f, 0x67, 0x03, 0x1c, 0x00, 0x54, 0x68, 0x69, 0x73,
|
||||
0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x68, 0x61, 0x73, 0x20, 0x6e, 0x6f, 0x20,
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2e, 0x05, 0x0f, 0x00, 0x32, 0x30, 0x31, 0x34, 0x30,
|
||||
0x39, 0x32, 0x36, 0x54, 0x31, 0x30, 0x34, 0x33, 0x30, 0x30
|
||||
};
|
||||
|
||||
// 00 19 00 00 00 07 07 00 44 69 73 6d 69 73 73 00 ........ Dismiss.
|
||||
// 14 00 63 6f 6d 2e 61 70 70 6c 65 2e 6d 6f 62 69 ..com.ap ple.mobi
|
||||
// 6c 65 6d 61 69 6c 01 05 00 4d 61 72 74 79 02 04 lemail.. .Marty..
|
||||
// 00 74 65 73 74 03 0a 00 4c 6f 61 64 69 6e 67 e2 .test... Loading.
|
||||
// 80 a6 05 0f 00 32 30 31 34 30 39 32 39 54 31 35 .....201 40929T15
|
||||
// 31 35 31 30 1510
|
||||
static const uint8_t s_loading_response[] = {
|
||||
0x00, 0x19, 0x00, 0x00, 0x00, 0x07, 0x07, 0x00, 0x44, 0x69, 0x73, 0x6d, 0x69, 0x73, 0x73, 0x00,
|
||||
0x14, 0x00, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x6d, 0x6f, 0x62, 0x69,
|
||||
0x6c, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x01, 0x05, 0x00, 0x4d, 0x61, 0x72, 0x74, 0x79, 0x02, 0x04,
|
||||
0x00, 0x74, 0x65, 0x73, 0x74, 0x03, 0x0a, 0x00, 0x4c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0xe2,
|
||||
0x80, 0xa6, 0x05, 0x0f, 0x00, 0x32, 0x30, 0x31, 0x34, 0x30, 0x39, 0x32, 0x39, 0x54, 0x31, 0x35,
|
||||
0x31, 0x35, 0x31, 0x30,
|
||||
};
|
||||
|
||||
// 00 aa 00 00 00 07 07 00 44 69 73 6d 69 73 73 00 ........ Dismiss.
|
||||
// 14 00 63 6f 6d 2e 61 70 70 6c 65 2e 6d 6f 62 69 ..com.ap ple.mobi
|
||||
// 6c 65 6d 61 69 6c 01 16 00 63 61 6c 65 6e 64 61 lemail.. .calenda
|
||||
// 72 40 63 73 61 69 6c 2e 6d 69 74 2e 65 64 75 02 r@csail. mit.edu.
|
||||
// 28 00 54 41 4c 4b 3a 20 54 75 65 73 64 61 79 20 (.TALK: Tuesday
|
||||
// 31 30 2d 32 38 2d 32 30 31 34 20 53 63 69 65 6e 10-28-20 14 Scien
|
||||
// 74 69 66 69 63 20 55 74 6f 70 03 c8 00 53 63 69 tific Ut op...Sci
|
||||
// 65 6e 74 69 66 69 63 20 55 74 6f 70 69 61 3a 20 entific Utopia:
|
||||
// 49 6d 70 72 6f 76 69 6e 67 20 4f 70 65 6e 6e 65 Improvin g Openne
|
||||
// 73 73 20 61 6e 64 20 52 65 70 72 6f 64 75 63 69 ss and R eproduci
|
||||
// 62 69 6c 69 74 79 20 69 6e 20 53 63 69 65 6e 74 bility i n Scient
|
||||
// 69 66 69 63 20 52 65 73 65 61 72 63 68 20 53 70 ific Res earch Sp
|
||||
// 65 61 6b 65 72 3a 20 42 72 69 61 6e 20 4e 6f 73 eaker: B rian Nos
|
||||
// 65 6b 2c 20 55 6e 69 76 65 72 73 69 74 79 20 6f ek, Univ ersity o
|
||||
// 66 20 56 69 72 67 69 6e 69 61 20 61 6e 64 20 43 f Virgin ia and C
|
||||
// 65 6e 74 65 72 20 66 6f 72 20 4f 70 65 6e 20 53 enter fo r Open S
|
||||
// 63 69 65 6e 63 65 20 53 70 65 61 6b 65 72 20 41 cience S peaker A
|
||||
// 66 66 69 6c 69 61 74 69 6f 6e 3a 20 55 6e 69 76 ffiliati on: Univ
|
||||
// 65 72 73 69 74 79 20 6f 66 20 56 69 72 67 69 6e ersity o f Virgin
|
||||
// 69 61 20 48 6f 05 ia Ho.
|
||||
typedef struct PACKED s_sample_memory_layout {
|
||||
const uint8_t attribute_data[310];
|
||||
const uint8_t out_of_bounds_memory[6];
|
||||
} s_sample_memory_layout;
|
||||
|
||||
s_sample_memory_layout memory_with_attribute_id_at_end = {
|
||||
.attribute_data = {
|
||||
0x00, 0xaa, 0x00, 0x00, 0x00, 0x07, 0x07, 0x00, 0x44, 0x69, 0x73, 0x6d, 0x69, 0x73, 0x73, 0x00,
|
||||
0x14, 0x00, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x6d, 0x6f, 0x62, 0x69,
|
||||
0x6c, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x01, 0x16, 0x00, 0x63, 0x61, 0x6c, 0x65, 0x6e, 0x64, 0x61,
|
||||
0x72, 0x40, 0x63, 0x73, 0x61, 0x69, 0x6c, 0x2e, 0x6d, 0x69, 0x74, 0x2e, 0x65, 0x64, 0x75, 0x02,
|
||||
0x28, 0x00, 0x54, 0x41, 0x4c, 0x4b, 0x3a, 0x20, 0x54, 0x75, 0x65, 0x73, 0x64, 0x61, 0x79, 0x20,
|
||||
0x31, 0x30, 0x2d, 0x32, 0x38, 0x2d, 0x32, 0x30, 0x31, 0x34, 0x20, 0x53, 0x63, 0x69, 0x65, 0x6e,
|
||||
0x74, 0x69, 0x66, 0x69, 0x63, 0x20, 0x55, 0x74, 0x6f, 0x70, 0x03, 0xc8, 0x00, 0x53, 0x63, 0x69,
|
||||
0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x20, 0x55, 0x74, 0x6f, 0x70, 0x69, 0x61, 0x3a, 0x20,
|
||||
0x49, 0x6d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x4f, 0x70, 0x65, 0x6e, 0x6e, 0x65,
|
||||
0x73, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x52, 0x65, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x69,
|
||||
0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x20, 0x69, 0x6e, 0x20, 0x53, 0x63, 0x69, 0x65, 0x6e, 0x74,
|
||||
0x69, 0x66, 0x69, 0x63, 0x20, 0x52, 0x65, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x20, 0x53, 0x70,
|
||||
0x65, 0x61, 0x6b, 0x65, 0x72, 0x3a, 0x20, 0x42, 0x72, 0x69, 0x61, 0x6e, 0x20, 0x4e, 0x6f, 0x73,
|
||||
0x65, 0x6b, 0x2c, 0x20, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x69, 0x74, 0x79, 0x20, 0x6f,
|
||||
0x66, 0x20, 0x56, 0x69, 0x72, 0x67, 0x69, 0x6e, 0x69, 0x61, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x43,
|
||||
0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x4f, 0x70, 0x65, 0x6e, 0x20, 0x53,
|
||||
0x63, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x20, 0x53, 0x70, 0x65, 0x61, 0x6b, 0x65, 0x72, 0x20, 0x41,
|
||||
0x66, 0x66, 0x69, 0x6c, 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x55, 0x6e, 0x69, 0x76,
|
||||
0x65, 0x72, 0x73, 0x69, 0x74, 0x79, 0x20, 0x6f, 0x66, 0x20, 0x56, 0x69, 0x72, 0x67, 0x69, 0x6e,
|
||||
0x69, 0x61, 0x20, 0x48, 0x6f, 0x05,
|
||||
},
|
||||
.out_of_bounds_memory = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
},
|
||||
};
|
||||
|
||||
static const uint8_t memory_with_attribute_id_at_end_p2[] = {
|
||||
0x0f, 0x00, 0x32, 0x30, 0x31, 0x33, 0x31, 0x31, 0x30, 0x35, 0x54, 0x31, 0x38, 0x31, 0x39, 0x32, 0x38
|
||||
};
|
||||
|
||||
|
||||
// 00 74 00 00 00 06 06 00 41 6e 73 77 65 72 07 07 .t...... Answer..
|
||||
// 00 44 65 63 6c 69 6e 65 00 15 00 63 6f 6d 2e 61 .Decline ...com.a
|
||||
// 70 70 6c 65 2e 6d 6f 62 69 6c 65 70 68 6f 6e 65 pple.mob ilephone
|
||||
// 01 03 00 49 6d 6d 02 06 00 6d 6f 62 69 6c 65 03 ...Imm.. .mobile.
|
||||
// 0d 00 49 6e 63 6f 6d 69 6e 67 20 43 61 6c 6c 05 ..Incomi ng Call.
|
||||
// 00 00 00 75 00 00 00 07 07 00 44 69 73 6d 69 73 ...u.... ..Dismis
|
||||
// 73 00 13 00 63 6f 6d 2e 61 70 70 6c 65 2e 6d 6f s...com. apple.mo
|
||||
// 62 69 6c 65 63 61 6c 01 11 00 48 61 69 6c 65 79 bilecal. ..Hailey
|
||||
// 27 73 20 70 72 61 63 74 69 63 65 02 00 00 03 0a 's pract ice.....
|
||||
// 00 52 65 64 20 4d 6f 72 74 6f 6e 05 0f 00 32 30 .Red Mor ton...20
|
||||
// 31 34 31 30 32 38 54 31 36 30 30 30 30 00 76 00 141028T1 60000.v.
|
||||
// 00 00 06 06 00 41 6e 73 77 65 72 07 07 00 44 65 .....Ans wer...De
|
||||
// 63 6c 69 6e 65 00 15 00 63 6f 6d 2e 61 70 70 6c cline... com.appl
|
||||
// 65 2e 6d 6f 62 69 6c 65 70 68 6f 6e 58 01 03 00 e.mobile phonX... //phone -> phonX for testing
|
||||
// 49 6d 6d 02 06 00 6d 6f 62 69 6c 65 03 0d 00 49 Imm...mo bile...I
|
||||
// 6e 63 6f 6d 69 6e 67 20 43 61 6c 6c 05 00 00 ncoming Call...
|
||||
|
||||
static const uint8_t s_multiple_complete_dicts[] = {
|
||||
// Dict 1
|
||||
0x00, 0x74, 0x00, 0x00, 0x00, 0x06, 0x06, 0x00, 0x41, 0x6e, 0x73, 0x77, 0x65, 0x72, 0x07, 0x07,
|
||||
0x00, 0x44, 0x65, 0x63, 0x6c, 0x69, 0x6e, 0x65, 0x00, 0x15, 0x00, 0x63, 0x6f, 0x6d, 0x2e, 0x61,
|
||||
0x70, 0x70, 0x6c, 0x65, 0x2e, 0x6d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x70, 0x68, 0x6f, 0x6e, 0x65,
|
||||
0x01, 0x03, 0x00, 0x49, 0x6d, 0x6d, 0x02, 0x06, 0x00, 0x6d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x03,
|
||||
0x0d, 0x00, 0x49, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x20, 0x43, 0x61, 0x6c, 0x6c, 0x05,
|
||||
0x00, 0x00,
|
||||
|
||||
// Dict 2
|
||||
0x00, 0x75, 0x00, 0x00, 0x00, 0x07, 0x07, 0x00, 0x44, 0x69, 0x73, 0x6d, 0x69, 0x73,
|
||||
0x73, 0x00, 0x13, 0x00, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x6d, 0x6f,
|
||||
0x62, 0x69, 0x6c, 0x65, 0x63, 0x61, 0x6c, 0x01, 0x11, 0x00, 0x48, 0x61, 0x69, 0x6c, 0x65, 0x79,
|
||||
0x27, 0x73, 0x20, 0x70, 0x72, 0x61, 0x63, 0x74, 0x69, 0x63, 0x65, 0x02, 0x00, 0x00, 0x03, 0x0a,
|
||||
0x00, 0x52, 0x65, 0x64, 0x20, 0x4d, 0x6f, 0x72, 0x74, 0x6f, 0x6e, 0x05, 0x0f, 0x00, 0x32, 0x30,
|
||||
0x31, 0x34, 0x31, 0x30, 0x32, 0x38, 0x54, 0x31, 0x36, 0x30, 0x30, 0x30, 0x30,
|
||||
|
||||
//Dict 3
|
||||
0x00, 0x76, 0x00,
|
||||
0x00, 0x00, 0x06, 0x06, 0x00, 0x41, 0x6e, 0x73, 0x77, 0x65, 0x72, 0x07, 0x07, 0x00, 0x44, 0x65,
|
||||
0x63, 0x6c, 0x69, 0x6e, 0x65, 0x00, 0x15, 0x00, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c,
|
||||
0x65, 0x2e, 0x6d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x70, 0x68, 0x6f, 0x6e, 0x64, 0x01, 0x03, 0x00,
|
||||
0x49, 0x6d, 0x6d, 0x02, 0x06, 0x00, 0x6d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x03, 0x0d, 0x00, 0x49,
|
||||
0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x20, 0x43, 0x61, 0x6c, 0x6c, 0x05, 0x00, 0x00
|
||||
};
|
||||
|
||||
//! Complete dict for a messages app notification with a duplicated title
|
||||
//! 00 1b 00 00 00 06 00 00 07 05 00 43 6c 65 61 72 ........ ...Clear
|
||||
//! 00 13 00 63 6f 6d 2e 61 70 70 6c 65 2e 4d 6f 62 ...com.a pple.Mob
|
||||
//! 69 6c 65 53 4d 53 01 08 00 4D 65 73 73 61 67 65 ileSMS.. .Message
|
||||
//! 73 02 00 00 03 1b 00 74 68 69 73 20 69 73 20 61 s......t his is a
|
||||
//! 20 74 65 73 74 20 6e 6f 74 69 66 69 63 61 74 69 test no tificati
|
||||
//! 6f 6e 05 0f 00 32 30 31 35 30 36 31 35 54 31 36 on...201 50615T16
|
||||
//! 31 38 33 37 1837
|
||||
|
||||
static const uint8_t s_app_name_title_dict[] = {
|
||||
0x00, 0x1b, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x05, 0x00, 0x43, 0x6c, 0x65, 0x61, 0x72,
|
||||
0x00, 0x13, 0x00, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x4d, 0x6f, 0x62,
|
||||
0x69, 0x6c, 0x65, 0x53, 0x4d, 0x53, 0x01, 0x08, 0x00, 0x4D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
||||
0x73, 0x02, 0x00, 0x00, 0x03, 0x1b, 0x00, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61,
|
||||
0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x05, 0x0f, 0x00, 0x32, 0x30, 0x31, 0x35, 0x30, 0x36, 0x31, 0x35, 0x54, 0x31, 0x36,
|
||||
0x31, 0x38, 0x33, 0x37
|
||||
};
|
||||
|
||||
static TimelineItem s_app_name_title_parsed_item = {
|
||||
.header = {
|
||||
.id = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
.ancs_uid = 0x1b,
|
||||
.timestamp = 1434385117,
|
||||
.duration = 0,
|
||||
.type = TimelineItemTypeNotification,
|
||||
.layout = LayoutIdNotification,
|
||||
.ancs_notif = 1
|
||||
},
|
||||
.attr_list = (AttributeList) {
|
||||
.num_attributes = 4,
|
||||
.attributes = (Attribute[4]) {
|
||||
{ .id = AttributeIdBody, .cstring = "this is a test notification" },
|
||||
{ .id = AttributeIdiOSAppIdentifier, .cstring = "com.apple.MobileSMS" },
|
||||
{ .id = AttributeIdIconTiny, .uint32 = TIMELINE_RESOURCE_GENERIC_SMS },
|
||||
{ .id = AttributeIdBgColor, .uint8 = GColorIslamicGreenARGB8 },
|
||||
}
|
||||
},
|
||||
.action_group = (TimelineItemActionGroup) {
|
||||
.num_actions = 1,
|
||||
.actions = (TimelineItemAction[1]) {
|
||||
{
|
||||
.id = 0,
|
||||
.type = TimelineItemActionTypeAncsNegative,
|
||||
.attr_list = (AttributeList) {
|
||||
.num_attributes = 2,
|
||||
.attributes = (Attribute[2]) {
|
||||
{ .id = AttributeIdAncsAction, .uint8 = ActionIDNegative},
|
||||
{ .id = AttributeIdTitle, .cstring = "Dismiss" }}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//! Complete dict for a notification from an unrecognized app with a duplicated title
|
||||
//! 00 1c 00 00 00 06 04 00 4c 69 6b 65 07 05 00 43 ........ Like...C
|
||||
//! 6c 65 61 72 00 12 00 63 6f 6d 2e 74 65 73 74 73 lear...com.t ests
|
||||
//! 2e 4e 6f 74 41 6e 41 70 70 01 07 00 46 61 6b 65 .NotAnApp... Fake
|
||||
//! 41 70 70 02 00 00 03 1b 00 74 68 69 73 20 69 73 App......thi s is
|
||||
//! 20 61 20 74 65 73 74 20 6e 6f 74 69 66 69 63 61 a test noti fica
|
||||
//! 74 69 6f 6e 05 0f 00 32 30 31 35 30 36 31 35 54 tion...20150 615T
|
||||
//! 31 36 31 38 33 37 161837
|
||||
|
||||
static const uint8_t s_unknown_app_dict[] = {
|
||||
0x00, 0x1c, 0x00, 0x00, 0x00, 0x06, 0x04, 0x00, 0x4c, 0x69, 0x6b, 0x65, 0x07, 0x05, 0x00, 0x43,
|
||||
0x6c, 0x65, 0x61, 0x72, 0x00, 0x12, 0x00, 0x63, 0x6f, 0x6d, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73,
|
||||
0x2e, 0x4e, 0x6f, 0x74, 0x41, 0x6e, 0x41, 0x70, 0x70, 0x01, 0x07, 0x00, 0x46, 0x61, 0x6b, 0x65,
|
||||
0x41, 0x70, 0x70, 0x02, 0x00, 0x00, 0x03, 0x1b, 0x00, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73,
|
||||
0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x05, 0x0f, 0x00, 0x32, 0x30, 0x31, 0x35, 0x30, 0x36, 0x31, 0x35, 0x54,
|
||||
0x31, 0x36, 0x31, 0x38, 0x33, 0x37
|
||||
};
|
||||
|
||||
static TimelineItem s_unknown_app_parsed_item = {
|
||||
.header = {
|
||||
.id = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
.ancs_uid = 0x1c,
|
||||
.timestamp = 1434385117,
|
||||
.duration = 0,
|
||||
.type = TimelineItemTypeNotification,
|
||||
.layout = LayoutIdNotification,
|
||||
.ancs_notif = 1
|
||||
},
|
||||
.attr_list = (AttributeList) {
|
||||
.num_attributes = 4,
|
||||
.attributes = (Attribute[4]) {
|
||||
{ .id = AttributeIdAppName, .cstring = "FakeApp" },
|
||||
{ .id = AttributeIdBody, .cstring = "this is a test notification" },
|
||||
{ .id = AttributeIdiOSAppIdentifier, .cstring = "com.tests.NotAnApp" },
|
||||
{ .id = AttributeIdIconTiny, .uint32 = TIMELINE_RESOURCE_NOTIFICATION_GENERIC },
|
||||
}
|
||||
},
|
||||
.action_group = (TimelineItemActionGroup) {
|
||||
.num_actions = 2,
|
||||
.actions = (TimelineItemAction[2]) {
|
||||
{
|
||||
.id = 0,
|
||||
.type = TimelineItemActionTypeAncsPositive,
|
||||
.attr_list = (AttributeList) {
|
||||
.num_attributes = 2,
|
||||
.attributes = (Attribute[2]) {
|
||||
{ .id = AttributeIdAncsAction, .uint8 = ActionIDPositive},
|
||||
{ .id = AttributeIdTitle, .cstring = "Like" }}
|
||||
}
|
||||
},
|
||||
{
|
||||
.id = 0,
|
||||
.type = TimelineItemActionTypeAncsNegative,
|
||||
.attr_list = (AttributeList) {
|
||||
.num_attributes = 2,
|
||||
.attributes = (Attribute[2]) {
|
||||
{ .id = AttributeIdAncsAction, .uint8 = ActionIDNegative},
|
||||
{ .id = AttributeIdTitle, .cstring = "Dismiss" }}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//! Complete dict for a notification from an unrecognized app with a unique title
|
||||
//! 00 1d 00 00 00 06 00 00 07 05 00 43 6c 65 61 72 ........ ...Clear
|
||||
//! 00 12 00 63 6f 6d 2e 74 65 73 74 73 2e 4e 6f 74 ...com.t ests.Not
|
||||
//! 41 6e 41 70 70 01 0d 00 4d 65 73 73 61 67 65 20 AnApp... Message
|
||||
//! 54 69 74 6c 65 02 00 00 03 1b 00 74 68 69 73 20 Title... ...this
|
||||
//! 69 73 20 61 20 74 65 73 74 20 6e 6f 74 69 66 69 is a tes t notifi
|
||||
//! 63 61 74 69 6f 6e 05 0f 00 32 30 31 35 30 36 31 cation.. .2015061
|
||||
//! 35 54 31 36 31 38 33 37 5T161837
|
||||
|
||||
static const uint8_t s_unknown_app_unique_title_dict[] = {
|
||||
0x00, 0x1d, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x05, 0x00, 0x43, 0x6c, 0x65, 0x61, 0x72,
|
||||
0x00, 0x12, 0x00, 0x63, 0x6f, 0x6d, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x4e, 0x6f, 0x74,
|
||||
0x41, 0x6e, 0x41, 0x70, 0x70, 0x01, 0x0d, 0x00, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20,
|
||||
0x54, 0x69, 0x74, 0x6c, 0x65, 0x02, 0x00, 0x00, 0x03, 0x1b, 0x00, 0x74, 0x68, 0x69, 0x73, 0x20,
|
||||
0x69, 0x73, 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69,
|
||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x05, 0x0f, 0x00, 0x32, 0x30, 0x31, 0x35, 0x30, 0x36, 0x31,
|
||||
0x35, 0x54, 0x31, 0x36, 0x31, 0x38, 0x33, 0x37
|
||||
};
|
||||
|
||||
static TimelineItem s_unknown_app_unique_title_parsed_item = {
|
||||
.header = {
|
||||
.id = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
.ancs_uid = 0x1d,
|
||||
.timestamp = 1434385117,
|
||||
.duration = 0,
|
||||
.type = TimelineItemTypeNotification,
|
||||
.layout = LayoutIdNotification,
|
||||
.ancs_notif = 1
|
||||
},
|
||||
.attr_list = (AttributeList) {
|
||||
.num_attributes = 5,
|
||||
.attributes = (Attribute[5]) {
|
||||
{ .id = AttributeIdTitle, .cstring = "Message Title" },
|
||||
{ .id = AttributeIdAppName, .cstring = "FakeApp" },
|
||||
{ .id = AttributeIdBody, .cstring = "this is a test notification" },
|
||||
{ .id = AttributeIdiOSAppIdentifier, .cstring = "com.tests.NotAnApp" },
|
||||
{ .id = AttributeIdIconTiny, .uint32 = TIMELINE_RESOURCE_NOTIFICATION_GENERIC },
|
||||
}
|
||||
},
|
||||
.action_group = (TimelineItemActionGroup) {
|
||||
.num_actions = 1,
|
||||
.actions = (TimelineItemAction[1]) {
|
||||
{
|
||||
.id = 0,
|
||||
.type = TimelineItemActionTypeAncsNegative,
|
||||
.attr_list = (AttributeList) {
|
||||
.num_attributes = 2,
|
||||
.attributes = (Attribute[2]) {
|
||||
{ .id = AttributeIdAncsAction, .uint8 = ActionIDNegative},
|
||||
{ .id = AttributeIdTitle, .cstring = "Dismiss" }}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//! Complete dict for an MMS with an image
|
||||
//! 00 1f 00 00 00 06 00 00 07 05 00 43 6c 65 61 72 .4.........Clear
|
||||
//! 00 13 00 63 6f 6d 2e 61 70 70 6c 65 2e 4d 6f 62 ...com.apple.Mob
|
||||
//! 69 6c 65 53 4d 53 01 08 00 48 75 79 20 54 72 61 ileSMS...Huy Tra
|
||||
//! 6e 02 00 00 03 00 00 05 0f 00 32 30 31 36 30 31 n.........201601
|
||||
//! 31 31 54 31 38 34 38 35 32 11T184852
|
||||
|
||||
static const uint8_t s_mms_no_caption_dict[] = {
|
||||
0x00, 0x1f, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x05, 0x00, 0x43, 0x6c, 0x65, 0x61, 0x72,
|
||||
0x00, 0x13, 0x00, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x4d, 0x6f, 0x62,
|
||||
0x69, 0x6c, 0x65, 0x53, 0x4d, 0x53, 0x01, 0x08, 0x00, 0x48, 0x75, 0x79, 0x20, 0x54, 0x72, 0x61,
|
||||
0x6e, 0x02, 0x00, 0x00, 0x03, 0x00, 0x00, 0x05, 0x0f, 0x00, 0x32, 0x30, 0x31, 0x36, 0x30, 0x31,
|
||||
0x31, 0x31, 0x54, 0x31, 0x38, 0x34, 0x38, 0x35, 0x32
|
||||
};
|
||||
|
||||
static TimelineItem s_mms_no_caption_parsed_item = {
|
||||
.header = {
|
||||
.id = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
.ancs_uid = 0x1f,
|
||||
.timestamp = 1452538132,
|
||||
.duration = 0,
|
||||
.type = TimelineItemTypeNotification,
|
||||
.layout = LayoutIdNotification,
|
||||
.ancs_notif = 1
|
||||
},
|
||||
.attr_list = (AttributeList) {
|
||||
.num_attributes = 5,
|
||||
.attributes = (Attribute[5]) {
|
||||
{ .id = AttributeIdSender, .cstring = "Huy Tran" },
|
||||
{ .id = AttributeIdBody, .cstring = "Huy Tran sent an attachment 🎁" },
|
||||
{ .id = AttributeIdiOSAppIdentifier, .cstring = "com.apple.MobileSMS" },
|
||||
{ .id = AttributeIdIconTiny, .uint32 = TIMELINE_RESOURCE_GENERIC_SMS },
|
||||
{ .id = AttributeIdBgColor, .uint8 = GColorIslamicGreenARGB8 }
|
||||
}
|
||||
},
|
||||
.action_group = (TimelineItemActionGroup) {
|
||||
.num_actions = 1,
|
||||
.actions = (TimelineItemAction[1]) {
|
||||
{
|
||||
.id = 0,
|
||||
.type = TimelineItemActionTypeAncsNegative,
|
||||
.attr_list = (AttributeList) {
|
||||
.num_attributes = 2,
|
||||
.attributes = (Attribute[2]) {
|
||||
{ .id = AttributeIdAncsAction, .uint8 = ActionIDNegative },
|
||||
{ .id = AttributeIdTitle, .cstring = "Dismiss" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//! Complete dict for an MMS with an image and caption
|
||||
//! 00 20 00 00 00 06 00 00 07 05 00 43 6c 65 61 72 .'...... ...Clear
|
||||
//! 00 13 00 63 6f 6d 2e 61 70 70 6c 65 2e 4d 6f 62 ...com.a pple.Mob
|
||||
//! 69 6c 65 53 4d 53 01 08 00 48 75 79 20 54 72 61 ileSMS.. .Huy Tra
|
||||
//! 6e 02 00 00 03 10 00 20 43 68 65 63 6b 20 74 68 n...... Check th
|
||||
//! 69 73 20 6f 75 74 21 05 0f 00 32 30 31 36 30 31 is out!. ..201601
|
||||
//! 31 31 54 32 31 30 34 31 35 11T21041 5
|
||||
|
||||
static const uint8_t s_mms_with_caption_dict[] = {
|
||||
0x00, 0x20, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x05, 0x00, 0x43, 0x6c, 0x65, 0x61, 0x72,
|
||||
0x00, 0x13, 0x00, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x4d, 0x6f, 0x62,
|
||||
0x69, 0x6c, 0x65, 0x53, 0x4d, 0x53, 0x01, 0x08, 0x00, 0x48, 0x75, 0x79, 0x20, 0x54, 0x72, 0x61,
|
||||
0x6e, 0x02, 0x00, 0x00, 0x03, 0x10, 0x00, 0x20, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x20, 0x74, 0x68,
|
||||
0x69, 0x73, 0x20, 0x6f, 0x75, 0x74, 0x21, 0x05, 0x0f, 0x00, 0x32, 0x30, 0x31, 0x36, 0x30, 0x31,
|
||||
0x31, 0x31, 0x54, 0x32, 0x31, 0x30, 0x34, 0x31, 0x35
|
||||
};
|
||||
|
||||
static TimelineItem s_mms_with_caption_parsed_item = {
|
||||
.header = {
|
||||
.id = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
.ancs_uid = 0x20,
|
||||
.timestamp = 1452546255,
|
||||
.duration = 0,
|
||||
.type = TimelineItemTypeNotification,
|
||||
.layout = LayoutIdNotification,
|
||||
.ancs_notif = 1
|
||||
},
|
||||
.attr_list = (AttributeList) {
|
||||
.num_attributes = 5,
|
||||
.attributes = (Attribute[5]) {
|
||||
{ .id = AttributeIdSender, .cstring = "Huy Tran" },
|
||||
{ .id = AttributeIdBody, .cstring = "Huy Tran sent an attachment 🎁\n\"Check this out!\"" },
|
||||
{ .id = AttributeIdiOSAppIdentifier, .cstring = "com.apple.MobileSMS" },
|
||||
{ .id = AttributeIdIconTiny, .uint32 = TIMELINE_RESOURCE_GENERIC_SMS },
|
||||
{ .id = AttributeIdBgColor, .uint8 = GColorIslamicGreenARGB8 }
|
||||
}
|
||||
},
|
||||
.action_group = (TimelineItemActionGroup) {
|
||||
.num_actions = 1,
|
||||
.actions = (TimelineItemAction[1]) {
|
||||
{
|
||||
.id = 0,
|
||||
.type = TimelineItemActionTypeAncsNegative,
|
||||
.attr_list = (AttributeList) {
|
||||
.num_attributes = 2,
|
||||
.attributes = (Attribute[2]) {
|
||||
{ .id = AttributeIdAncsAction, .uint8 = ActionIDNegative },
|
||||
{ .id = AttributeIdTitle, .cstring = "Dismiss" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,720 @@
|
||||
/*
|
||||
* 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/ams/ams.h"
|
||||
|
||||
#include "comm/bt_conn_mgr.h"
|
||||
|
||||
#include "services/normal/music.h"
|
||||
#include "services/normal/music_internal.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
// Stubs & Fakes
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#include "fake_events.h"
|
||||
#include "fake_gatt_client_operations.h"
|
||||
#include "fake_gatt_client_subscriptions.h"
|
||||
#include "fake_pebble_tasks.h"
|
||||
#include "fake_rtc.h"
|
||||
|
||||
#include "stubs_app_install_manager.h"
|
||||
#include "stubs_app_manager.h"
|
||||
#include "stubs_bt_lock.h"
|
||||
#include "stubs_hexdump.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_mutex.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_pbl_malloc.h"
|
||||
#include "stubs_serial.h"
|
||||
#include "stubs_tick.h"
|
||||
|
||||
void analytics_event_ams(uint8_t type, int32_t aux_info) {
|
||||
}
|
||||
|
||||
struct {
|
||||
ResponseTimeState state;
|
||||
uint16_t max_period_secs;
|
||||
} s_conn_mgr_states[NumBtConsumer];
|
||||
|
||||
void conn_mgr_set_ble_conn_response_time(GAPLEConnection *hdl, BtConsumer consumer,
|
||||
ResponseTimeState state, uint16_t max_period_secs) {
|
||||
s_conn_mgr_states[consumer].state = state;
|
||||
s_conn_mgr_states[consumer].max_period_secs = max_period_secs;
|
||||
}
|
||||
|
||||
GAPLEConnection *gap_le_connection_by_device(const BTDeviceInternal *device) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
BTDeviceInternal gatt_client_characteristic_get_device(BLECharacteristic characteristic_ref) {
|
||||
return (BTDeviceInternal) {
|
||||
.address.octets = {
|
||||
0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Tests: Disover AMS
|
||||
///////////////////////////////////////////////////////////
|
||||
#define NUM_AMS_INSTANCES 2
|
||||
static BLECharacteristic s_characteristics[NUM_AMS_INSTANCES][NumAMSCharacteristic] = {
|
||||
// AMS instance one:
|
||||
[0] = {
|
||||
[AMSCharacteristicRemoteCommand] = 1,
|
||||
[AMSCharacteristicEntityUpdate] = 2,
|
||||
[AMSCharacteristicEntityAttribute] = 3,
|
||||
},
|
||||
// AMS instance two:
|
||||
[1] = {
|
||||
[AMSCharacteristicRemoteCommand] = 4,
|
||||
[AMSCharacteristicEntityUpdate] = 5,
|
||||
[AMSCharacteristicEntityAttribute] = 6,
|
||||
},
|
||||
};
|
||||
|
||||
static const BLECharacteristic s_unknown_characteristic = 999;
|
||||
|
||||
static void prv_assert_can_handle_characteristics(uint32_t instance_idx, bool expect_can_handle) {
|
||||
for (AMSCharacteristic c = 0; c < NumAMSCharacteristic; ++c) {
|
||||
cl_assert_equal_b(ams_can_handle_characteristic(s_characteristics[instance_idx][c]),
|
||||
expect_can_handle);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_discover_ams(uint8_t num_instances) {
|
||||
cl_assert(num_instances <= 2);
|
||||
if (num_instances) {
|
||||
for (int i = 0; i < num_instances && i < NUM_AMS_INSTANCES; i++) {
|
||||
ams_handle_service_discovered(&s_characteristics[i][0]);
|
||||
}
|
||||
} else {
|
||||
ams_invalidate_all_references();
|
||||
}
|
||||
}
|
||||
|
||||
void test_ams__cannot_handle_any_characteristic_after_destroy(void) {
|
||||
prv_discover_ams(1);
|
||||
ams_destroy();
|
||||
prv_assert_can_handle_characteristics(0, false /* expect_can_handle */);
|
||||
}
|
||||
|
||||
void test_ams__cannot_handle_unknown_characteristic(void) {
|
||||
prv_discover_ams(1);
|
||||
cl_assert_equal_b(ams_can_handle_characteristic(s_unknown_characteristic), false);
|
||||
}
|
||||
|
||||
void test_ams__discover_of_ams_should_subscribe_to_entity_update_characteristic(void) {
|
||||
// Pass in 2 instances, it should be able to cope with this
|
||||
prv_discover_ams(2 /* num_instances */);
|
||||
|
||||
// Assert ams.c can now handle the characteristic reference for the first instance:
|
||||
prv_assert_can_handle_characteristics(0, true /* expect_can_handle */);
|
||||
|
||||
// Assert ams.c can not handle the characteristic reference for the second instance:
|
||||
prv_assert_can_handle_characteristics(1, false /* expect_can_handle */);
|
||||
|
||||
// The first instance is expected to be used.
|
||||
BLECharacteristic entity_update = s_characteristics[0][AMSCharacteristicEntityUpdate];
|
||||
fake_gatt_client_subscriptions_assert_subscribe(entity_update,
|
||||
BLESubscriptionNotifications, GAPLEClientKernel);
|
||||
}
|
||||
|
||||
void test_ams__connect_to_music_service_upon_subscribing_entity_update_characteristic(void) {
|
||||
prv_discover_ams(1 /* num_instances */);
|
||||
// Not connected yet (still need to subscribe):
|
||||
cl_assert_equal_s(music_get_connected_server_debug_name(), NULL);
|
||||
|
||||
// Simulate successful subscription:
|
||||
BLECharacteristic entity_update = s_characteristics[0][AMSCharacteristicEntityUpdate];
|
||||
ams_handle_subscribe(entity_update, BLESubscriptionNotifications, BLEGATTErrorSuccess);
|
||||
|
||||
// Music service should be connected now:
|
||||
cl_assert_equal_s(music_get_connected_server_debug_name(), ams_music_server_debug_name());
|
||||
|
||||
// Rediscovery will disconnect music service (until resubscribed):
|
||||
ams_invalidate_all_references();
|
||||
prv_discover_ams(1 /* num_instances */);
|
||||
cl_assert_equal_s(music_get_connected_server_debug_name(), NULL);
|
||||
}
|
||||
|
||||
void test_ams__dont_connect_music_service_if_subscribe_entity_update_characteristic_fails(void) {
|
||||
prv_discover_ams(1 /* num_instances */);
|
||||
|
||||
// Simulate failed subscription:
|
||||
BLECharacteristic entity_update = s_characteristics[0][AMSCharacteristicEntityUpdate];
|
||||
ams_handle_subscribe(entity_update, BLESubscriptionNotifications, BLEGATTErrorUnlikelyError);
|
||||
|
||||
// Not connected because subscription failed:
|
||||
cl_assert_equal_s(music_get_connected_server_debug_name(), NULL);
|
||||
}
|
||||
|
||||
void test_ams__update_characteristics_ams_not_found(void) {
|
||||
// AMS Found:
|
||||
prv_discover_ams(1 /* num_instances */);
|
||||
// AMS Disappeared:
|
||||
prv_discover_ams(0 /* num_instances */);
|
||||
// Verify ams.c cannot handle the previous characteristics any more:
|
||||
prv_assert_can_handle_characteristics(0, false /* expect_can_handle */);
|
||||
}
|
||||
|
||||
// Tests: Register for Entity Updates
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
static const uint8_t s_register_player_entity[] = {0x00,
|
||||
// Apple bug #21283910
|
||||
// See ams.c, prv_get_registration_cmd_for_entity
|
||||
// 0x00,
|
||||
0x01, 0x02};
|
||||
|
||||
static const uint8_t s_register_queue_entity[] = {0x01, 0x00, 0x01, 0x02, 0x03};
|
||||
|
||||
static const uint8_t s_register_track_entity[] = {0x02, 0x00, 0x01, 0x02, 0x03};
|
||||
|
||||
void test_ams__register_for_entity_updates(void) {
|
||||
prv_discover_ams(1 /* num_instances */);
|
||||
|
||||
cl_assert_equal_b(ams_is_registered_for_all_entity_updates(), false);
|
||||
|
||||
// Simulate successful subscription:
|
||||
BLECharacteristic entity_update = s_characteristics[0][AMSCharacteristicEntityUpdate];
|
||||
ams_handle_subscribe(entity_update, BLESubscriptionNotifications, BLEGATTErrorSuccess);
|
||||
|
||||
// Expect to have written the command to register for the Player entity:
|
||||
fake_gatt_client_op_assert_write(entity_update, s_register_player_entity,
|
||||
sizeof(s_register_player_entity), GAPLEClientKernel,
|
||||
true /* is_response_required */);
|
||||
ams_handle_write_response(entity_update, BLEGATTErrorSuccess);
|
||||
|
||||
cl_assert_equal_b(ams_is_registered_for_all_entity_updates(), false);
|
||||
|
||||
// Expect to have written the command to register for the Queue entity:
|
||||
fake_gatt_client_op_assert_write(entity_update, s_register_queue_entity,
|
||||
sizeof(s_register_queue_entity), GAPLEClientKernel,
|
||||
true /* is_response_required */);
|
||||
ams_handle_write_response(entity_update, BLEGATTErrorSuccess);
|
||||
|
||||
cl_assert_equal_b(ams_is_registered_for_all_entity_updates(), false);
|
||||
|
||||
// Expect to have written the command to register for the Track entity:
|
||||
fake_gatt_client_op_assert_write(entity_update, s_register_track_entity,
|
||||
sizeof(s_register_track_entity), GAPLEClientKernel,
|
||||
true /* is_response_required */);
|
||||
ams_handle_write_response(entity_update, BLEGATTErrorSuccess);
|
||||
|
||||
cl_assert_equal_b(ams_is_registered_for_all_entity_updates(), true);
|
||||
|
||||
// After GATT re-discovery, the registration needs to happen again
|
||||
ams_invalidate_all_references();
|
||||
prv_discover_ams(1 /* num_instances */);
|
||||
cl_assert_equal_b(ams_is_registered_for_all_entity_updates(), false);
|
||||
}
|
||||
|
||||
void test_ams__register_for_entity_updates_retry_if_out_of_resources(void) {
|
||||
prv_discover_ams(1 /* num_instances */);
|
||||
|
||||
// Simulate not having enough resources to process the request:
|
||||
fake_gatt_client_op_set_write_return_value(BTErrnoNotEnoughResources);
|
||||
|
||||
// Simulate successful subscription:
|
||||
BLECharacteristic entity_update = s_characteristics[0][AMSCharacteristicEntityUpdate];
|
||||
ams_handle_subscribe(entity_update, BLESubscriptionNotifications, BLEGATTErrorSuccess);
|
||||
|
||||
// Nothing should have been written because out of resources:
|
||||
fake_gatt_client_op_assert_no_write();
|
||||
|
||||
// Resources come available again:
|
||||
fake_gatt_client_op_set_write_return_value(BTErrnoOK);
|
||||
|
||||
// Simulate processing the callback to retry:
|
||||
cl_assert(s_launcher_task_callback != NULL);
|
||||
s_launcher_task_callback(s_launcher_task_callback_data);
|
||||
|
||||
// Expect to have written the command to register for the Player entity:
|
||||
fake_gatt_client_op_assert_write(entity_update, s_register_player_entity,
|
||||
sizeof(s_register_player_entity), GAPLEClientKernel,
|
||||
true /* is_response_required */);
|
||||
ams_handle_write_response(entity_update, BLEGATTErrorSuccess);
|
||||
}
|
||||
|
||||
static const MusicServerImplementation s_dummy_server_implementation = {};
|
||||
static void prv_set_dummy_server_connected(bool connected) {
|
||||
music_set_connected_server(&s_dummy_server_implementation,
|
||||
connected /* connected */);
|
||||
}
|
||||
|
||||
void test_ams__dont_register_if_another_music_server_is_already_connected(void) {
|
||||
prv_discover_ams(1 /* num_instances */);
|
||||
// Not connected yet (still need to subscribe):
|
||||
cl_assert_equal_s(music_get_connected_server_debug_name(), NULL);
|
||||
|
||||
prv_set_dummy_server_connected(true /* connected */);
|
||||
|
||||
// Simulate successful subscription:
|
||||
BLECharacteristic entity_update = s_characteristics[0][AMSCharacteristicEntityUpdate];
|
||||
ams_handle_subscribe(entity_update, BLESubscriptionNotifications, BLEGATTErrorSuccess);
|
||||
|
||||
// Nothing should have been written, because there's already a music server connected.
|
||||
fake_gatt_client_op_assert_no_write();
|
||||
|
||||
// Clean up after ourselves:
|
||||
prv_set_dummy_server_connected(false /* connected */);
|
||||
}
|
||||
|
||||
// Tests: Sending Remote Commands
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
static void prv_connect_ams(void) {
|
||||
prv_discover_ams(1 /* num_instances */);
|
||||
|
||||
// Simulate successful subscription:
|
||||
BLECharacteristic entity_update = s_characteristics[0][AMSCharacteristicEntityUpdate];
|
||||
ams_handle_subscribe(entity_update, BLESubscriptionNotifications, BLEGATTErrorSuccess);
|
||||
|
||||
// Simulate successful write responses (for the Entity Update registration write requests):
|
||||
ams_handle_write_response(entity_update, BLEGATTErrorSuccess);
|
||||
ams_handle_write_response(entity_update, BLEGATTErrorSuccess);
|
||||
ams_handle_write_response(entity_update, BLEGATTErrorSuccess);
|
||||
|
||||
cl_assert_equal_b(ams_is_registered_for_all_entity_updates(), true);
|
||||
|
||||
fake_gatt_client_op_clear_write_list();
|
||||
}
|
||||
|
||||
static uint8_t prv_ams_command_for_music_command(MusicCommand command) {
|
||||
switch (command) {
|
||||
case MusicCommandPlay:
|
||||
return 0x00;
|
||||
case MusicCommandPause:
|
||||
return 0x01;
|
||||
case MusicCommandTogglePlayPause:
|
||||
return 0x02;
|
||||
case MusicCommandNextTrack:
|
||||
return 0x03;
|
||||
case MusicCommandPreviousTrack:
|
||||
return 0x04;
|
||||
case MusicCommandVolumeUp:
|
||||
return 0x05;
|
||||
case MusicCommandVolumeDown:
|
||||
return 0x06;
|
||||
case MusicCommandAdvanceRepeatMode:
|
||||
return 0x07;
|
||||
case MusicCommandAdvanceShuffleMode:
|
||||
return 0x08;
|
||||
case MusicCommandSkipForward:
|
||||
return 0x09;
|
||||
case MusicCommandSkipBackward:
|
||||
return 0x0A;
|
||||
case MusicCommandLike:
|
||||
return 0x0B;
|
||||
case MusicCommandDislike:
|
||||
return 0x0C;
|
||||
case MusicCommandBookmark:
|
||||
return 0x0D;
|
||||
default:
|
||||
cl_assert(false);
|
||||
return 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
void test_ams__send_remote_command(void) {
|
||||
prv_connect_ams();
|
||||
|
||||
// Exercise all MusicCommand types (currently they are all supported):
|
||||
for (MusicCommand music_cmd = 0; music_cmd < NumMusicCommand; ++music_cmd) {
|
||||
music_command_send(music_cmd);
|
||||
|
||||
BLECharacteristic remote_command = s_characteristics[0][AMSCharacteristicRemoteCommand];
|
||||
const uint8_t ams_cmd = prv_ams_command_for_music_command(music_cmd);
|
||||
fake_gatt_client_op_assert_write(remote_command, &ams_cmd, sizeof(ams_cmd),
|
||||
GAPLEClientKernel, true /* is_response_required */);
|
||||
|
||||
// Simulate receiving the response:
|
||||
ams_handle_write_response(remote_command, BLEGATTErrorSuccess);
|
||||
}
|
||||
|
||||
// Invalid/Unsupported command:
|
||||
music_command_send(NumMusicCommand);
|
||||
fake_gatt_client_op_assert_no_write();
|
||||
}
|
||||
|
||||
void test_ams__send_remote_command_non_kernel_main_task(void) {
|
||||
prv_connect_ams();
|
||||
|
||||
// Simulate Music app calling music_command_send():
|
||||
stub_pebble_tasks_set_current(PebbleTask_App);
|
||||
music_command_send(MusicCommandPlay);
|
||||
|
||||
// Process the KernelMain callback:
|
||||
cl_assert(s_launcher_task_callback != NULL);
|
||||
stub_pebble_tasks_set_current(PebbleTask_KernelMain);
|
||||
s_launcher_task_callback(s_launcher_task_callback_data);
|
||||
|
||||
BLECharacteristic remote_command = s_characteristics[0][AMSCharacteristicRemoteCommand];
|
||||
const uint8_t ams_cmd = prv_ams_command_for_music_command(MusicCommandPlay);
|
||||
fake_gatt_client_op_assert_write(remote_command, &ams_cmd, sizeof(ams_cmd),
|
||||
GAPLEClientKernel, true /* is_response_required */);
|
||||
}
|
||||
|
||||
void test_ams__send_remote_command_non_kernel_main_task_then_disconnect(void) {
|
||||
prv_connect_ams();
|
||||
|
||||
// Simulate Music app calling music_command_send():
|
||||
stub_pebble_tasks_set_current(PebbleTask_App);
|
||||
music_command_send(MusicCommandPlay);
|
||||
|
||||
// Simulate disconnecting:
|
||||
ams_destroy();
|
||||
|
||||
// Process the KernelMain callback:
|
||||
cl_assert(s_launcher_task_callback != NULL);
|
||||
stub_pebble_tasks_set_current(PebbleTask_KernelMain);
|
||||
s_launcher_task_callback(s_launcher_task_callback_data);
|
||||
|
||||
// No crashes, no writes
|
||||
fake_gatt_client_op_assert_no_write();
|
||||
}
|
||||
|
||||
// Tests: music_needs_user_to_start_playback_on_phone
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
void test_ams__music_needs_user_to_start_playback_on_phone(void) {
|
||||
prv_connect_ams();
|
||||
|
||||
// No metadata received yet, user needs to start playback on phone
|
||||
cl_assert_equal_b(music_needs_user_to_start_playback_on_phone(), true);
|
||||
|
||||
// Received metadata
|
||||
// cl_assert_equal_b(music_needs_user_to_start_playback_on_phone(), false);
|
||||
|
||||
// Metadata cleared
|
||||
cl_assert_equal_b(music_needs_user_to_start_playback_on_phone(), true);
|
||||
}
|
||||
|
||||
// Tests: music_request_..._latency
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
void test_ams__music_request_reduced_latency(void) {
|
||||
prv_connect_ams();
|
||||
|
||||
music_request_reduced_latency(true /* reduced_latency */);
|
||||
cl_assert_equal_i(s_conn_mgr_states[BtConsumerMusicServiceIndefinite].state,
|
||||
ResponseTimeMiddle);
|
||||
cl_assert_equal_i(s_conn_mgr_states[BtConsumerMusicServiceIndefinite].max_period_secs,
|
||||
MAX_PERIOD_RUN_FOREVER);
|
||||
|
||||
music_request_reduced_latency(false /* reduced_latency */);
|
||||
cl_assert_equal_i(s_conn_mgr_states[BtConsumerMusicServiceIndefinite].state,
|
||||
ResponseTimeMax);
|
||||
}
|
||||
|
||||
void test_ams__music_request_low_latency_for_period(void) {
|
||||
prv_connect_ams();
|
||||
|
||||
const uint32_t period_s = 1234;
|
||||
music_request_low_latency_for_period(period_s * 1000);
|
||||
cl_assert_equal_i(s_conn_mgr_states[BtConsumerMusicServiceMomentary].state,
|
||||
ResponseTimeMin);
|
||||
cl_assert_equal_i(s_conn_mgr_states[BtConsumerMusicServiceMomentary].max_period_secs,
|
||||
period_s);
|
||||
}
|
||||
|
||||
// Tests: Receiving Player updates (the happy paths)
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
static void prv_receive_entity_update(const uint8_t *update, uint16_t update_length) {
|
||||
BLECharacteristic entity_update = s_characteristics[0][AMSCharacteristicEntityUpdate];
|
||||
ams_handle_read_or_notification(entity_update, update, update_length, BLEGATTErrorSuccess);
|
||||
}
|
||||
|
||||
void test_ams__receive_player_name_update(void) {
|
||||
prv_connect_ams();
|
||||
|
||||
// 0000 00 00 00 4d 75 73 69 63 ...Music
|
||||
uint8_t player_name_update[] = {
|
||||
0x00, 0x00, 0x00, 0x4d, 0x75, 0x73, 0x69, 0x63,
|
||||
};
|
||||
prv_receive_entity_update(player_name_update, sizeof(player_name_update));
|
||||
|
||||
char player_name[MUSIC_BUFFER_LENGTH];
|
||||
cl_assert_equal_b(music_get_player_name(player_name), true);
|
||||
cl_assert_equal_s(player_name, "Music");
|
||||
}
|
||||
|
||||
void test_ams__receive_player_playback_info_update(void) {
|
||||
prv_connect_ams();
|
||||
|
||||
// Receive: playing, 200% playback rate, elapsed time 184.755s
|
||||
// 0000 00 01 00 31 2c 32 2e 30 2c 31 38 34 2e 37 35 35 ...1,2.0 ,184.755
|
||||
uint8_t playback_info_update[] = {
|
||||
0x00, 0x01, 0x00, 0x31, 0x2c, 0x32, 0x2e, 0x30,
|
||||
0x2c, 0x31, 0x38, 0x34, 0x2e, 0x37, 0x35, 0x35,
|
||||
};
|
||||
prv_receive_entity_update(playback_info_update, sizeof(playback_info_update));
|
||||
|
||||
cl_assert_equal_i(music_get_playback_state(), MusicPlayStatePlaying);
|
||||
|
||||
// music_get_pos relies on having a sensible track duration, so simulate receiving this too:
|
||||
// 02 03 00 33 31 39 2e 35 30 37 ...319.5 07
|
||||
uint8_t track_duration_update[] = {
|
||||
0x02, 0x03, 0x00, 0x33, 0x31, 0x39, 0x2e, 0x35,
|
||||
0x30, 0x37,
|
||||
};
|
||||
prv_receive_entity_update(track_duration_update, sizeof(track_duration_update));
|
||||
|
||||
uint32_t track_pos_ms;
|
||||
uint32_t track_duration_ms;
|
||||
music_get_pos(&track_pos_ms, &track_duration_ms);
|
||||
cl_assert_equal_i(track_pos_ms, 184755);
|
||||
cl_assert_equal_i(track_duration_ms, 319507);
|
||||
|
||||
cl_assert_equal_i(music_get_playback_rate_percent(), 200);
|
||||
}
|
||||
|
||||
void test_ams__receive_player_playback_info_update_nulls(void) {
|
||||
prv_connect_ams();
|
||||
|
||||
// Receive: paused, empty string, empty string
|
||||
// 0000 00 01 00 30 2c 2c ...0,,
|
||||
uint8_t playback_info_update[] = {
|
||||
0x00, 0x01, 0x00, 0x30, 0x2c, 0x2c,
|
||||
};
|
||||
prv_receive_entity_update(playback_info_update, sizeof(playback_info_update));
|
||||
|
||||
cl_assert_equal_i(music_get_playback_state(), MusicPlayStatePaused);
|
||||
|
||||
uint32_t track_pos_ms;
|
||||
uint32_t track_duration_ms;
|
||||
music_get_pos(&track_pos_ms, &track_duration_ms);
|
||||
cl_assert_equal_i(track_pos_ms, 0);
|
||||
cl_assert_equal_i(track_duration_ms, 0);
|
||||
|
||||
cl_assert_equal_i(music_get_playback_rate_percent(), 0);
|
||||
}
|
||||
|
||||
void test_ams__receive_player_volume_update(void) {
|
||||
prv_connect_ams();
|
||||
|
||||
// Receive volume of 0.604925
|
||||
// 00 02 00 30 2e 36 30 34 39 32 35 ...0.604 925
|
||||
uint8_t volume_update[] = {
|
||||
0x00, 0x02, 0x00, 0x30, 0x2e, 0x36, 0x30, 0x34,
|
||||
0x39, 0x32, 0x35,
|
||||
};
|
||||
prv_receive_entity_update(volume_update, sizeof(volume_update));
|
||||
|
||||
cl_assert_equal_i(music_get_volume_percent(), 60);
|
||||
}
|
||||
|
||||
// Tests: Receiving Player updates (the unhappy paths)
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
static void prv_assert_initial_playback_state(void) {
|
||||
cl_assert_equal_i(music_get_playback_state(), MusicPlayStateUnknown);
|
||||
|
||||
uint32_t track_pos_ms;
|
||||
uint32_t track_duration_ms;
|
||||
music_get_pos(&track_pos_ms, &track_duration_ms);
|
||||
cl_assert_equal_i(track_pos_ms, 0);
|
||||
cl_assert_equal_i(track_duration_ms, 0);
|
||||
|
||||
cl_assert_equal_i(music_get_playback_rate_percent(), 0);
|
||||
}
|
||||
|
||||
void test_ams__receive_non_numeric_player_playback_info_update(void) {
|
||||
prv_connect_ams();
|
||||
|
||||
// Receive: 'A', 'B.0' playback rate, elapsed time 184.755s
|
||||
// 0000 00 01 00 41 2c 42 2e 30 2c 31 38 34 2e 37 35 35 ...A,B.0 ,184.755
|
||||
uint8_t nan_playback_info_update[] = {
|
||||
0x00, 0x01, 0x00, 0x41, 0x2c, 0x42, 0x2e, 0x30,
|
||||
0x2c, 0x31, 0x38, 0x34, 0x2e, 0x37, 0x35, 0x35,
|
||||
};
|
||||
prv_receive_entity_update(nan_playback_info_update, sizeof(nan_playback_info_update));
|
||||
|
||||
prv_assert_initial_playback_state();
|
||||
}
|
||||
|
||||
void test_ams__receive_incomplete_csv_list_player_playback_info_update(void) {
|
||||
prv_connect_ams();
|
||||
|
||||
// Receive: playing, 200% playback rate
|
||||
// 0000 00 01 00 31 2c 32 2e 30 ...1,2.0
|
||||
uint8_t incomplete_playback_info_update[] = {
|
||||
0x00, 0x01, 0x00, 0x31, 0x2c, 0x32, 0x2e, 0x30,
|
||||
};
|
||||
prv_receive_entity_update(incomplete_playback_info_update,
|
||||
sizeof(incomplete_playback_info_update));
|
||||
|
||||
prv_assert_initial_playback_state();
|
||||
}
|
||||
|
||||
void test_ams__receive_malformed_player_volume_update(void) {
|
||||
prv_connect_ams();
|
||||
|
||||
cl_assert_equal_i(music_get_volume_percent(), 0);
|
||||
|
||||
// Receive volume of 0.604925
|
||||
// 00 02 00 30 2e 41 30 34 39 32 35 ...0.A04 925
|
||||
uint8_t volume_update[] = {
|
||||
0x00, 0x02, 0x00, 0x30, 0x2e, 0x41, 0x30, 0x34,
|
||||
0x39, 0x32, 0x35,
|
||||
};
|
||||
prv_receive_entity_update(volume_update, sizeof(volume_update));
|
||||
|
||||
cl_assert_equal_i(music_get_volume_percent(), 0);
|
||||
}
|
||||
|
||||
// Tests: Receiving Track updates
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
void test_ams__receive_track_artist_update(void) {
|
||||
prv_connect_ams();
|
||||
|
||||
// 0000 02 00 00 4d 69 6c 65 73 20 44 61 76 69 73 ...Miles Davis
|
||||
uint8_t track_artist_update[] = {
|
||||
0x02, 0x00, 0x00, 0x4d, 0x69, 0x6c, 0x65, 0x73,
|
||||
0x20, 0x44, 0x61, 0x76, 0x69, 0x73,
|
||||
};
|
||||
prv_receive_entity_update(track_artist_update, sizeof(track_artist_update));
|
||||
|
||||
char artist[MUSIC_BUFFER_LENGTH];
|
||||
music_get_now_playing(NULL, artist, NULL);
|
||||
|
||||
cl_assert_equal_s(artist, "Miles Davis");
|
||||
}
|
||||
|
||||
void test_ams__receive_track_title_update(void) {
|
||||
prv_connect_ams();
|
||||
|
||||
// 0000 02 02 00 53 6f 20 57 68 61 74 ...So Wh at
|
||||
uint8_t track_title_update[] = {
|
||||
0x02, 0x02, 0x00, 0x53, 0x6f, 0x20, 0x57, 0x68,
|
||||
0x61, 0x74,
|
||||
};
|
||||
prv_receive_entity_update(track_title_update, sizeof(track_title_update));
|
||||
|
||||
char title[MUSIC_BUFFER_LENGTH];
|
||||
music_get_now_playing(title, NULL, NULL);
|
||||
|
||||
cl_assert_equal_s(title, "So What");
|
||||
}
|
||||
|
||||
void test_ams__receive_track_album_update(void) {
|
||||
prv_connect_ams();
|
||||
|
||||
// 0000 02 01 00 4b 69 6e 64 20 4f 66 20 42 6c 75 65 20 ...Kind Of Blue
|
||||
// 0010 28 4c 65 67 61 63 79 20 45 64 69 74 69 6f 6e 29 (Legacy Edition)
|
||||
uint8_t track_album_update[] = {
|
||||
0x02, 0x01, 0x00, 0x4b, 0x69, 0x6e, 0x64, 0x20,
|
||||
0x4f, 0x66, 0x20, 0x42, 0x6c, 0x75, 0x65, 0x20,
|
||||
0x28, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x20,
|
||||
0x45, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x29,
|
||||
};
|
||||
prv_receive_entity_update(track_album_update, sizeof(track_album_update));
|
||||
|
||||
char album[MUSIC_BUFFER_LENGTH];
|
||||
music_get_now_playing(NULL, NULL, album);
|
||||
|
||||
cl_assert_equal_s(album, "Kind Of Blue (Legacy Edition)");
|
||||
}
|
||||
|
||||
// Tests: Music service capabilities
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
void test_ams__supported_capabilities(void) {
|
||||
prv_connect_ams();
|
||||
|
||||
// music_is_progress_reporting_supported() relies on a valid track duration
|
||||
uint8_t track_duration_update[] = {
|
||||
0x02, 0x03, 0x00, 0x33, 0x31, 0x39, 0x2e, 0x35,
|
||||
0x30, 0x37,
|
||||
};
|
||||
prv_receive_entity_update(track_duration_update, sizeof(track_duration_update));
|
||||
|
||||
cl_assert_equal_b(music_is_playback_state_reporting_supported(), true);
|
||||
cl_assert_equal_b(music_is_progress_reporting_supported(), true);
|
||||
cl_assert_equal_b(music_is_volume_reporting_supported(), true);
|
||||
cl_assert_equal_b(music_needs_user_to_start_playback_on_phone(), true);
|
||||
for (MusicCommand cmd = 0; cmd < NumMusicCommand; ++cmd) {
|
||||
cl_assert_equal_b(music_is_command_supported(cmd), true);
|
||||
}
|
||||
|
||||
cl_assert_equal_b(music_is_command_supported(NumMusicCommand), false);
|
||||
}
|
||||
|
||||
// Tests: Create & Destroy
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
void test_ams__create_again_trips_assert(void) {
|
||||
cl_assert_passert(ams_create());
|
||||
}
|
||||
|
||||
void test_ams__create_works_again_after_destroy(void) {
|
||||
ams_destroy();
|
||||
ams_create();
|
||||
// No assert hit.
|
||||
}
|
||||
|
||||
void test_ams__destroy_after_destroy_is_fine(void) {
|
||||
ams_destroy();
|
||||
ams_destroy();
|
||||
// No assert hit.
|
||||
}
|
||||
|
||||
void test_ams__destroy_disconnects_from_music_service(void) {
|
||||
prv_discover_ams(1 /* num_instances */);
|
||||
|
||||
// Simulate successful subscription:
|
||||
BLECharacteristic entity_update = s_characteristics[0][AMSCharacteristicEntityUpdate];
|
||||
ams_handle_subscribe(entity_update, BLESubscriptionNotifications, BLEGATTErrorSuccess);
|
||||
|
||||
ams_destroy();
|
||||
cl_assert_equal_s(music_get_connected_server_debug_name(), NULL);
|
||||
}
|
||||
|
||||
// Test setup
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
void test_ams__initialize(void) {
|
||||
s_launcher_task_callback = NULL;
|
||||
memset(s_conn_mgr_states, 0, sizeof(s_conn_mgr_states));
|
||||
fake_rtc_init(1234, 5678);
|
||||
fake_event_init();
|
||||
fake_gatt_client_subscriptions_init();
|
||||
fake_gatt_client_op_init();
|
||||
stub_pebble_tasks_set_current(PebbleTask_KernelMain);
|
||||
ams_create();
|
||||
cl_assert_equal_s(music_get_connected_server_debug_name(), NULL);
|
||||
}
|
||||
|
||||
void test_ams__cleanup(void) {
|
||||
ams_destroy();
|
||||
fake_gatt_client_op_deinit();
|
||||
fake_gatt_client_subscriptions_deinit();
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* 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/ams/ams_util.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
// Stubs
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#include "stubs_logging.h"
|
||||
|
||||
// Helpers
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
static char s_results[10][32];
|
||||
static uint32_t s_results_lengths[10];
|
||||
static uint8_t s_results_count;
|
||||
static uint32_t s_max_results_count;
|
||||
|
||||
static bool prv_result_callback(const char *value, uint32_t value_length,
|
||||
uint32_t index, void *context) {
|
||||
strcpy(s_results[s_results_count], value);
|
||||
s_results_lengths[s_results_count] = value_length;
|
||||
cl_assert_equal_i(index, s_results_count);
|
||||
++s_results_count;
|
||||
|
||||
return (s_max_results_count > s_results_count);
|
||||
}
|
||||
|
||||
// Tests
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
void test_ams_util__initialize(void) {
|
||||
memset(s_results, 0, sizeof(s_results));
|
||||
s_results_count = 0;
|
||||
s_max_results_count = ~0;
|
||||
}
|
||||
|
||||
void test_ams_util__cleanup(void) {
|
||||
}
|
||||
|
||||
// ams_util_float_string_parse() tests
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#define assert_float_parse(in, mul, succeeds, expected_result) \
|
||||
{ \
|
||||
const char *input = (const char *)in; \
|
||||
const uint32_t input_length = input ? sizeof(in) : 0; \
|
||||
const uint32_t multiplier = mul; \
|
||||
int32_t result = 0; \
|
||||
cl_assert_equal_b(succeeds, \
|
||||
ams_util_float_string_parse((const char *)in, input_length, multiplier, &result)); \
|
||||
if (succeeds) { \
|
||||
cl_assert_equal_i(result, expected_result); \
|
||||
} \
|
||||
}
|
||||
|
||||
void test_ams_util__float_string_parse_negative_number(void) {
|
||||
// "-1" * 3
|
||||
assert_float_parse("-1", 3, true, -1 * 3);
|
||||
}
|
||||
|
||||
void test_ams_util__float_string_parse_only_minus_sign(void) {
|
||||
// "-" * 3
|
||||
assert_float_parse("-", 3, false, 0);
|
||||
}
|
||||
|
||||
void test_ams_util__float_string_parse_negative_number_nothing_before_separator(void) {
|
||||
// "-.1" * 30
|
||||
assert_float_parse("-.1", 30, true, -3);
|
||||
}
|
||||
|
||||
void test_ams_util__float_string_parse_multiple_minusses(void) {
|
||||
// "--.1" * 30
|
||||
assert_float_parse("--.1", 30, false, 0);
|
||||
}
|
||||
|
||||
void test_ams_util__float_string_parse_null(void) {
|
||||
// NULL * 3
|
||||
assert_float_parse(NULL, 3, false, 0);
|
||||
}
|
||||
|
||||
void test_ams_util__float_string_parse_not_zero_terminated(void) {
|
||||
uint8_t buffer[] = {'1'};
|
||||
assert_float_parse(buffer, 3, true, 3);
|
||||
}
|
||||
|
||||
void test_ams_util__float_string_parse_null_in_the_middle(void) {
|
||||
uint8_t buffer[] = {'1', 0, '2'};
|
||||
assert_float_parse(buffer, 3, true, 3);
|
||||
}
|
||||
|
||||
void test_ams_util__float_string_parse_empty_string(void) {
|
||||
// "" * 3
|
||||
assert_float_parse("", 3, false, 0);
|
||||
}
|
||||
|
||||
void test_ams_util__float_string_parse_not_a_number(void) {
|
||||
// "hello" * 3
|
||||
assert_float_parse("hello", 3, false, 0);
|
||||
// " " * 3
|
||||
assert_float_parse(" ", 3, false, 0);
|
||||
}
|
||||
|
||||
void test_ams_util__float_string_parse_no_fraction(void) {
|
||||
// "42" * 3
|
||||
assert_float_parse("42", 3, true, 42 * 3);
|
||||
}
|
||||
|
||||
void test_ams_util__float_string_parse_separator_but_no_fraction(void) {
|
||||
// "21." * 3
|
||||
assert_float_parse("21.", 3, true, 21 * 3);
|
||||
}
|
||||
|
||||
void test_ams_util__float_string_parse_comma_decimal_separator(void) {
|
||||
// "1.23456" * 3 is approx 3.7036800000000003, round up to 4
|
||||
assert_float_parse("1,23456", 3, true, 4);
|
||||
}
|
||||
|
||||
void test_ams_util__float_string_parse_period_decimal_separator(void) {
|
||||
// "1.23456" * 3 is approx 3.7036800000000003, round up to 4
|
||||
assert_float_parse("1.23456", 3, true, 4);
|
||||
}
|
||||
|
||||
void test_ams_util__float_string_parse_round_down(void) {
|
||||
// "0.74995" * 2 is approx 1.4999, round down to 1
|
||||
assert_float_parse("0.74995", 2, true, 1);
|
||||
}
|
||||
|
||||
void test_ams_util__float_string_parse_multiple_separators(void) {
|
||||
// "1.2.2" * 3
|
||||
assert_float_parse("1.2.2", 2, false, 0);
|
||||
}
|
||||
|
||||
void test_ams_util__float_string_positive_multiplier(void) {
|
||||
// "1.654321" * 33 is approx 54.592593, round up to 55
|
||||
assert_float_parse("1.654321", 33, true, 55);
|
||||
}
|
||||
|
||||
void test_ams_util__float_string_negative_multiplier(void) {
|
||||
// "1.987622" * -33 is approx -65.591526, round to -66
|
||||
assert_float_parse("1.987622", -33, true, -66);
|
||||
}
|
||||
|
||||
void test_ams_util__float_string_parse_overflow_positive(void) {
|
||||
// fails:
|
||||
// "2147483648" * 1
|
||||
assert_float_parse("2147483648", 1, false, 0);
|
||||
|
||||
// succeeds:
|
||||
// "2147483647" * 1
|
||||
assert_float_parse("2147483647", 1, true, 2147483647);
|
||||
}
|
||||
|
||||
void test_ams_util__float_string_parse_overflow_negative(void) {
|
||||
// fails:
|
||||
// "-2147483649" * 1
|
||||
assert_float_parse("-2147483649", 1, false, 0);
|
||||
|
||||
// succeeds:
|
||||
// "-2147483648" * 1
|
||||
assert_float_parse("-2147483648", 1, true, -2147483648);
|
||||
assert_float_parse("2147483648", -1, true, -2147483648);
|
||||
}
|
||||
|
||||
// ams_util_csv_parse() tests
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#define assert_result(idx, val, len); \
|
||||
cl_assert_equal_i(len, s_results_lengths[idx]); \
|
||||
cl_assert_equal_i(memcmp(val, s_results[idx], len), 0);
|
||||
|
||||
void test_ams_util__csv_empty_string(void) {
|
||||
const char empty_string[] = "";
|
||||
const uint8_t count = ams_util_csv_parse(empty_string, sizeof(empty_string), NULL,
|
||||
prv_result_callback);
|
||||
cl_assert_equal_i(s_results_count, 1);
|
||||
cl_assert_equal_i(count, 1);
|
||||
cl_assert_equal_s(empty_string, s_results[0]);
|
||||
}
|
||||
|
||||
void test_ams_util__csv_empty_values(void) {
|
||||
const char one_value[] = ",";
|
||||
const uint8_t count = ams_util_csv_parse(one_value, sizeof(one_value), NULL,
|
||||
prv_result_callback);
|
||||
cl_assert_equal_i(s_results_count, 2);
|
||||
cl_assert_equal_i(count, 2);
|
||||
assert_result(0, "", 0);
|
||||
assert_result(1, "", 0);
|
||||
}
|
||||
|
||||
void test_ams_util__csv_null(void) {
|
||||
const uint8_t count = ams_util_csv_parse(NULL, 0, NULL,
|
||||
prv_result_callback);
|
||||
cl_assert_equal_i(s_results_count, 0);
|
||||
cl_assert_equal_i(count, 0);
|
||||
}
|
||||
|
||||
void test_ams_util__csv_one_value(void) {
|
||||
const char one_value[] = "A";
|
||||
const uint8_t count = ams_util_csv_parse(one_value, sizeof(one_value), NULL,
|
||||
prv_result_callback);
|
||||
cl_assert_equal_i(s_results_count, 1);
|
||||
cl_assert_equal_i(count, 1);
|
||||
cl_assert_equal_s(one_value, s_results[0]);
|
||||
}
|
||||
|
||||
void test_ams_util__csv_multiple_values(void) {
|
||||
const char multi_values[] = "A,B,C";
|
||||
const uint8_t count = ams_util_csv_parse(multi_values, sizeof(multi_values), NULL,
|
||||
prv_result_callback);
|
||||
cl_assert_equal_i(s_results_count, 3);
|
||||
cl_assert_equal_i(count, 3);
|
||||
assert_result(0, "A", 1);
|
||||
assert_result(1, "B", 1);
|
||||
assert_result(2, "C", 1);
|
||||
}
|
||||
|
||||
void test_ams_util__csv_stop_after_one_value(void) {
|
||||
const char multi_values[] = "A,B,C";
|
||||
s_max_results_count = 1;
|
||||
const uint8_t count = ams_util_csv_parse(multi_values, sizeof(multi_values), NULL,
|
||||
prv_result_callback);
|
||||
cl_assert_equal_i(s_results_count, 1);
|
||||
cl_assert_equal_i(count, 1);
|
||||
assert_result(0, "A", 1);
|
||||
}
|
||||
|
||||
void test_ams_util__csv_null_in_the_middle(void) {
|
||||
const char null_middle_value[] = "A\x00 BCD,1234";
|
||||
cl_assert(sizeof(null_middle_value) > 2);
|
||||
const uint8_t count = ams_util_csv_parse(null_middle_value, sizeof(null_middle_value), NULL,
|
||||
prv_result_callback);
|
||||
cl_assert_equal_i(s_results_count, 1);
|
||||
cl_assert_equal_i(count, 1);
|
||||
assert_result(0, "A", 1);
|
||||
}
|
||||
|
||||
void test_ams_util__csv_buffer_not_zero_terminated(void) {
|
||||
const char one_value[] = "ABCDEF";
|
||||
cl_assert(sizeof(one_value) > 2);
|
||||
const uint8_t count = ams_util_csv_parse(one_value,
|
||||
sizeof(one_value) - 1 /* omit zero teminator */, NULL,
|
||||
prv_result_callback);
|
||||
cl_assert_equal_i(s_results_count, 1);
|
||||
cl_assert_equal_i(count, 1);
|
||||
assert_result(0, "ABCDEF", 6);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,644 @@
|
||||
/*
|
||||
* 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);
|
||||
//}
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* 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 "applib/persist_map.h"
|
||||
#include "applib/persist_private.h"
|
||||
#include "comm/ble/app_profiles/ancs_app_storage.h"
|
||||
#include "drivers/crc.h"
|
||||
#include "kernel/services/file.h"
|
||||
#include "system/filesystem.h"
|
||||
#include "system/logging.h"
|
||||
#include "util/size.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
// Stubs
|
||||
////////////////////////////////////
|
||||
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_serial.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_mutex.h"
|
||||
#include "stubs_system_reset.h"
|
||||
#include "stubs_task_watchdog.h"
|
||||
|
||||
// Fakes
|
||||
////////////////////////////////////
|
||||
|
||||
#include "fake_session.h"
|
||||
#include "fake_spi_flash.h"
|
||||
#include "fake_system_task.h"
|
||||
#include "fake_new_timer.h"
|
||||
#include "fake_pbl_malloc.h"
|
||||
|
||||
// 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_app_storage__initialize(void) {
|
||||
fake_spi_flash_init(TEST_START, TEST_SIZE);
|
||||
file_system_format();
|
||||
file_system_reset();
|
||||
ancs_app_storage_init();
|
||||
}
|
||||
|
||||
void test_ancs_app_storage__cleanup(void) {
|
||||
ancs_app_storage_deinit();
|
||||
}
|
||||
|
||||
void test_ancs_app_storage__one_app(void) {
|
||||
ANCSAppData app_data = {
|
||||
.bundle_id = "com.getpebble.PebbleApp",
|
||||
.display_name = "Pebble",
|
||||
.flags = ANCSAppIsBlocked,
|
||||
};
|
||||
app_data.is_meta_changed = true;
|
||||
ancs_app_storage_save(&app_data);
|
||||
|
||||
ANCSAppData app_data_out = { 0 };
|
||||
ancs_app_storage_load(app_data.bundle_id, &app_data_out);
|
||||
|
||||
cl_assert_equal_s(app_data.bundle_id, app_data_out.bundle_id);
|
||||
cl_assert_equal_i(app_data.flags, app_data_out.flags);
|
||||
cl_assert_equal_s(app_data.display_name, app_data_out.display_name);
|
||||
|
||||
ancs_app_destroy_buffer(&app_data_out);
|
||||
}
|
||||
|
||||
void test_ancs_app_storage__overwrite(void) {
|
||||
ANCSAppData app_data = {
|
||||
.bundle_id = "com.getpebble.PebbleApp",
|
||||
.display_name = "Pebble",
|
||||
.flags = ANCSAppIsBlocked,
|
||||
};
|
||||
app_data.is_meta_changed = true;
|
||||
ancs_app_storage_save(&app_data);
|
||||
|
||||
app_data.display_name = "Pebble 2";
|
||||
app_data.is_meta_changed = true;
|
||||
ancs_app_storage_save(&app_data);
|
||||
|
||||
ANCSAppData app_data_out = { 0 };
|
||||
ancs_app_storage_load(app_data.bundle_id, &app_data_out);
|
||||
|
||||
cl_assert_equal_s(app_data.bundle_id, app_data_out.bundle_id);
|
||||
cl_assert_equal_i(app_data.flags, app_data_out.flags);
|
||||
cl_assert_equal_s(app_data.display_name, app_data_out.display_name);
|
||||
|
||||
ancs_app_destroy_buffer(&app_data_out);
|
||||
}
|
||||
|
||||
static uint32_t get_key(const char* bundle_id) {
|
||||
return legacy_defective_checksum_memory(bundle_id, strlen(bundle_id));
|
||||
}
|
||||
|
||||
void test_ancs_app_storage__hash_collisions(void) {
|
||||
ANCSAppData app_data = {
|
||||
.flags = ANCSAppFlagNone,
|
||||
};
|
||||
|
||||
// Courtesy of http://programmers.stackexchange.com/questions/49550/which-hashing-algorithm-is-best-for-uniqueness-and-speed
|
||||
char* collide_pairs[][2] = {
|
||||
{ "codding", "gnu" },
|
||||
{ "exhibiters", "schlager" },
|
||||
};
|
||||
|
||||
for (unsigned int i = 0; i < ARRAY_LENGTH(collide_pairs); ++i) {
|
||||
for (unsigned int j = 0; j < 2; ++j) {
|
||||
char* name = collide_pairs[i][j];
|
||||
app_data.bundle_id = name;
|
||||
app_data.display_name = name;
|
||||
app_data.is_meta_changed = true;
|
||||
uint32_t key = get_key(name);
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "name: %s, key: %u", name, (unsigned) key);
|
||||
ancs_app_storage_save(&app_data);
|
||||
}
|
||||
}
|
||||
|
||||
ANCSAppData app_data_out = { 0 };
|
||||
for (unsigned int i = 0; i < ARRAY_LENGTH(collide_pairs); ++i) {
|
||||
for (unsigned int j = 0; j < 2; ++j) {
|
||||
char* name = collide_pairs[i][j];
|
||||
app_data.bundle_id = name;
|
||||
app_data.display_name = name;
|
||||
ancs_app_storage_load(name, &app_data_out);
|
||||
|
||||
cl_assert_equal_s(app_data.bundle_id, app_data_out.bundle_id);
|
||||
cl_assert_equal_i(app_data.flags, app_data_out.flags);
|
||||
cl_assert_equal_s(app_data.display_name, app_data_out.display_name);
|
||||
|
||||
ancs_app_destroy_buffer(&app_data_out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test_ancs_app_storage__iter(void) {
|
||||
ANCSAppData apps[] = {
|
||||
{ .bundle_id = "com.apple.MobileSMS", .display_name = "Messages" },
|
||||
{ .bundle_id = "com.apple.facetime", .display_name = "FaceTime" },
|
||||
{ .bundle_id = "com.facebook.Messenger", .display_name = "Facebook" },
|
||||
{ .bundle_id = "com.atebits.Tweetie2", .display_name = "Twitter" },
|
||||
{ .bundle_id = "com.apple.mobilecal", .display_name = "Calender" },
|
||||
{ .bundle_id = "com.blackberry.bbm1", .display_name = "BBM" },
|
||||
{ .bundle_id = "net.whatsapp.WhatsApp", .display_name = "WhatsApp" },
|
||||
{ .bundle_id = "com.toyopagroup.picaboo", .display_name = "Snapchat" },
|
||||
{ .bundle_id = "com.kik.chat", .display_name = "Kik Chat" },
|
||||
{ .bundle_id = "com.apple.mobilemail", .display_name = "Mail" },
|
||||
{ .bundle_id = "com.yahoo.Aerogram", .display_name = "YMail" },
|
||||
{ .bundle_id = "co.inboxapp.inbox", .display_name = "Inbox" },
|
||||
{ .bundle_id = "com.google.Gmail", .display_name = "Gmail" },
|
||||
};
|
||||
|
||||
for (unsigned int i = 0; i < ARRAY_LENGTH(apps); ++i) {
|
||||
apps[i].is_meta_changed = true;
|
||||
apps[i].is_local_changed = true;
|
||||
ancs_app_storage_save(&apps[i]);
|
||||
}
|
||||
|
||||
ANCSAppData app_data_out = { 0 };
|
||||
for (unsigned int i = 0; i < ARRAY_LENGTH(apps); ++i) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "i: %d, name: %s", i, apps[i].bundle_id);
|
||||
ancs_app_storage_load(apps[i].bundle_id, &app_data_out);
|
||||
|
||||
cl_assert_equal_s(apps[i].bundle_id, app_data_out.bundle_id);
|
||||
cl_assert_equal_i(apps[i].flags, app_data_out.flags);
|
||||
cl_assert_equal_s(apps[i].display_name, app_data_out.display_name);
|
||||
|
||||
ancs_app_destroy_buffer(&app_data_out);
|
||||
}
|
||||
|
||||
ancs_app_storage_iter_begin();
|
||||
for (unsigned int i = 0; ancs_app_storage_next(&app_data_out); ++i) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "i: %d, name: %s, name_out: %s", i, apps[i].bundle_id, app_data_out.bundle_id);
|
||||
cl_assert_equal_s(apps[i].bundle_id, app_data_out.bundle_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* 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_types.h"
|
||||
#include "comm/ble/kernel_le_client/ancs/ancs_util.h"
|
||||
|
||||
#include "util/buffer.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
||||
// Stubs
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#include "stubs_analytics.h"
|
||||
#include "stubs_ble.h"
|
||||
#include "stubs_bt_stack.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_serial.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_queue.h"
|
||||
#include "stubs_mutex.h"
|
||||
#include "stubs_system_reset.h"
|
||||
#include "stubs_task_watchdog.h"
|
||||
#include "stubs_pbl_malloc.h"
|
||||
#include "stubs_reconnect.h"
|
||||
|
||||
// Test data
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#include "ancs_test_data.h"
|
||||
|
||||
// Tests
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
void test_ancs_util__should_parse_complete_notif_attr_dict(void) {
|
||||
bool error = false;
|
||||
cl_assert(ancs_util_is_complete_notif_attr_response(s_complete_dict, sizeof(s_complete_dict), &error));
|
||||
cl_assert(!error);
|
||||
}
|
||||
|
||||
void test_ancs_util__should_identify_missing_attribute(void) {
|
||||
bool error = false;
|
||||
cl_assert(!ancs_util_is_complete_notif_attr_response(s_missing_last_attribute, sizeof(s_missing_last_attribute),
|
||||
&error));
|
||||
cl_assert(!error);
|
||||
}
|
||||
|
||||
void test_ancs_util__should_identify_invalid_attr_length(void) {
|
||||
bool error = false;
|
||||
cl_assert(!ancs_util_is_complete_notif_attr_response(s_invalid_attribute_length, sizeof(s_invalid_attribute_length),
|
||||
&error));
|
||||
cl_assert(error);
|
||||
}
|
||||
|
||||
void test_ancs_util__should_parse_incomplete_last_attribute(void) {
|
||||
bool error = false;
|
||||
cl_assert(!ancs_util_is_complete_notif_attr_response(s_chunked_dict_part_one, sizeof(s_chunked_dict_part_one),
|
||||
&error));
|
||||
cl_assert(!error);
|
||||
}
|
||||
|
||||
void test_ancs_util__should_not_parse_malformed_notif_attr_dict(void) {
|
||||
bool error = false;
|
||||
cl_assert(!ancs_util_is_complete_notif_attr_response(s_chunked_dict_part_two, sizeof(s_chunked_dict_part_two),
|
||||
&error));
|
||||
cl_assert(error);
|
||||
}
|
||||
|
||||
void test_ancs_util__should_extract_dict_from_buffer(void) {
|
||||
static const char* expected_message = "This is a very complicated case, Maude. You know, a lotta ins, lotta outs, lotta what-have-you's. And, uh, lotta strands to keep in my head, man. Lotta strands in old Duder's head. Luckily I'm adherin";
|
||||
|
||||
int bytes_written;
|
||||
Buffer* b = buffer_create(500);
|
||||
bytes_written = buffer_add(b, s_chunked_dict_part_one, sizeof(s_chunked_dict_part_one));
|
||||
cl_assert_equal_i(bytes_written, sizeof(s_chunked_dict_part_one));
|
||||
|
||||
bytes_written = buffer_add(b, s_chunked_dict_part_two, sizeof(s_chunked_dict_part_two));
|
||||
cl_assert_equal_i(bytes_written, sizeof(s_chunked_dict_part_two));
|
||||
|
||||
bool error = false;
|
||||
cl_assert(ancs_util_is_complete_notif_attr_response(b->data, b->bytes_written, &error));
|
||||
cl_assert(!error);
|
||||
|
||||
ANCSAttribute* attr_ptrs[NUM_FETCHED_NOTIF_ATTRIBUTES];
|
||||
|
||||
// Only pass the attribute data to ancs_util_get_attr_ptrs:
|
||||
const GetNotificationAttributesMsg *msg =
|
||||
(const GetNotificationAttributesMsg*) b->data;
|
||||
const size_t attributes_length = b->bytes_written - sizeof(*msg);
|
||||
cl_assert(ancs_util_get_attr_ptrs(msg->attributes_data, attributes_length, s_fetched_notif_attributes,
|
||||
NUM_FETCHED_NOTIF_ATTRIBUTES, attr_ptrs, &error));
|
||||
cl_assert(!error);
|
||||
|
||||
ANCSAttribute* message = (ANCSAttribute*) attr_ptrs[3];
|
||||
cl_assert(strncmp((char*) message->value, expected_message, message->length) == 0);
|
||||
|
||||
free(b);
|
||||
}
|
||||
|
||||
|
||||
void test_ancs_util__should_detect_duplicate_message(void) {
|
||||
#if(0)
|
||||
// Extract the dict
|
||||
static const char* expected_message = "This is a very complicated case, Maude. You know, a lotta ins, lotta outs, lotta what-have-you's. And, uh, lotta strands to keep in my head, man. Lotta strands in old Duder's head. Luckily I'm adherin";
|
||||
|
||||
ancs_util_reset_notification_cache();
|
||||
|
||||
Buffer* b = buffer_create(500);
|
||||
cl_assert_equal_i(buffer_add(b, s_chunked_dict_part_one, sizeof(s_chunked_dict_part_one)), sizeof(s_chunked_dict_part_one));
|
||||
cl_assert_equal_i(buffer_add(b, s_chunked_dict_part_two, sizeof(s_chunked_dict_part_two)), sizeof(s_chunked_dict_part_two));
|
||||
|
||||
bool error = false;
|
||||
cl_assert(ancs_util_is_complete_notif_attr_response(b->data, b->bytes_written, &error));
|
||||
cl_assert(!error);
|
||||
|
||||
ANCSAttribute *attr_ptrs[NUM_FETCHED_NOTIF_ATTRIBUTES];
|
||||
|
||||
const GetNotificationAttributesMsg *msg =
|
||||
(const GetNotificationAttributesMsg*) b->data;
|
||||
const size_t attributes_length = b->bytes_written - sizeof(*msg);
|
||||
cl_assert(ancs_util_get_attr_ptrs(msg->attributes_data, attributes_length, s_fetched_notif_attributes,
|
||||
NUM_FETCHED_NOTIF_ATTRIBUTES, attr_ptrs, &error));
|
||||
cl_assert(!error);
|
||||
|
||||
ANCSAttribute* message = (ANCSAttribute*) attr_ptrs[3];
|
||||
cl_assert(strncmp((char*) message->value, expected_message, message->length) == 0);
|
||||
|
||||
// Ensure the dupe is detected
|
||||
bool was_added_to_cache = ancs_util_cache_notification(
|
||||
attr_ptrs[FetchedAttributeIndexAppID],
|
||||
attr_ptrs[FetchedAttributeIndexTitle],
|
||||
attr_ptrs[FetchedAttributeIndexSubtitle],
|
||||
attr_ptrs[FetchedAttributeIndexMessage],
|
||||
attr_ptrs[FetchedAttributeIndexDate]
|
||||
);
|
||||
|
||||
cl_assert(was_added_to_cache);
|
||||
|
||||
was_added_to_cache = ancs_util_cache_notification(
|
||||
attr_ptrs[FetchedAttributeIndexAppID],
|
||||
attr_ptrs[FetchedAttributeIndexTitle],
|
||||
attr_ptrs[FetchedAttributeIndexSubtitle],
|
||||
attr_ptrs[FetchedAttributeIndexMessage],
|
||||
attr_ptrs[FetchedAttributeIndexDate]
|
||||
);
|
||||
|
||||
cl_assert(was_added_to_cache == false);
|
||||
|
||||
// Ensure the dupe is no longer detected
|
||||
ancs_util_reset_notification_cache();
|
||||
was_added_to_cache = ancs_util_cache_notification(
|
||||
attr_ptrs[FetchedAttributeIndexAppID],
|
||||
attr_ptrs[FetchedAttributeIndexTitle],
|
||||
attr_ptrs[FetchedAttributeIndexSubtitle],
|
||||
attr_ptrs[FetchedAttributeIndexMessage],
|
||||
attr_ptrs[FetchedAttributeIndexDate]
|
||||
);
|
||||
|
||||
cl_assert(was_added_to_cache);
|
||||
|
||||
free(b);
|
||||
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* 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 "comm/ble/gap_le_connection.h"
|
||||
#include "comm/bt_conn_mgr.h"
|
||||
#include "comm/bt_conn_mgr_impl.h"
|
||||
#include "services/common/regular_timer.h"
|
||||
|
||||
// Fakes
|
||||
#include "fake_gap_le_connect_params.h"
|
||||
#include "fake_new_timer.h"
|
||||
#include "fake_rtc.h"
|
||||
#include "fake_system_task.h"
|
||||
#include "fake_pbl_malloc.h"
|
||||
|
||||
// Stubs
|
||||
#include "stubs_bluetopia_interface.h"
|
||||
#include "stubs_bt_lock.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_mutex.h"
|
||||
#include "stubs_passert.h"
|
||||
|
||||
extern void conn_mgr_handle_desired_state_granted(GAPLEConnection *hdl,
|
||||
ResponseTimeState granted_state);
|
||||
|
||||
// Stubs
|
||||
/////
|
||||
bool gap_le_connection_is_valid(const GAPLEConnection *conn) {
|
||||
return (conn != NULL);
|
||||
}
|
||||
|
||||
GAPLEConnection *gap_le_connection_any(void) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void prv_regular_timer_spend_seconds(uint32_t seconds) {
|
||||
for (uint32_t i = 0; i < seconds; ++i) {
|
||||
fake_rtc_increment_ticks(RTC_TICKS_HZ);
|
||||
regular_timer_fire_seconds(1);
|
||||
|
||||
// bt_conn_mgr offloads the callback to KernelBG
|
||||
fake_system_task_callbacks_invoke_pending();
|
||||
}
|
||||
}
|
||||
|
||||
void launcher_task_add_callback(void (*callback)(void *data), void *data) {
|
||||
callback(data);
|
||||
}
|
||||
|
||||
// Tests
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
static int s_granted_count;
|
||||
static GAPLEConnection s_hdl;
|
||||
|
||||
void test_bt_conn_mgr__initialize(void) {
|
||||
s_granted_count = 0;
|
||||
fake_rtc_init(0, 0);
|
||||
regular_timer_init();
|
||||
fake_gap_le_connect_params_init();
|
||||
s_hdl.conn_mgr_info = bt_conn_mgr_info_init();
|
||||
}
|
||||
|
||||
void test_bt_conn_mgr__cleanup(void) {
|
||||
regular_timer_deinit();
|
||||
}
|
||||
|
||||
void test_bt_conn_mgr__ble_latency_mgr(void) {
|
||||
// 1 consumer at fastest rate should result in fastest rate getting scheduled
|
||||
conn_mgr_set_ble_conn_response_time(
|
||||
&s_hdl, BtConsumerLeServiceDiscovery, ResponseTimeMin, 100);
|
||||
|
||||
uint16_t secs_to_wait;
|
||||
ResponseTimeState state;
|
||||
|
||||
state = conn_mgr_get_latency_for_le_connection(&s_hdl, &secs_to_wait);
|
||||
cl_assert_equal_i(state, ResponseTimeMin);
|
||||
cl_assert_equal_i(secs_to_wait, 100);
|
||||
cl_assert_equal_i(fake_gap_le_connect_params_get_last_requested(), ResponseTimeMin);
|
||||
|
||||
// another consumer at lower rate should not have any effect
|
||||
fake_gap_le_connect_params_reset_last_requested();
|
||||
conn_mgr_set_ble_conn_response_time(
|
||||
&s_hdl, BtConsumerUnitTests, ResponseTimeMiddle, 30);
|
||||
state = conn_mgr_get_latency_for_le_connection(&s_hdl, &secs_to_wait);
|
||||
cl_assert_equal_i(state, ResponseTimeMin);
|
||||
cl_assert_equal_i(secs_to_wait, 100);
|
||||
cl_assert_equal_i(fake_gap_le_connect_params_get_last_requested(), ResponseTimeInvalid);
|
||||
|
||||
// removing the fastest consumer should result in the next fastest being scheduled, but only
|
||||
// after an "inactivity timeout":
|
||||
conn_mgr_set_ble_conn_response_time(
|
||||
&s_hdl, BtConsumerLeServiceDiscovery, ResponseTimeMax, 0);
|
||||
|
||||
state = conn_mgr_get_latency_for_le_connection(&s_hdl, &secs_to_wait);
|
||||
cl_assert_equal_i(state, ResponseTimeMin);
|
||||
cl_assert_equal_i(secs_to_wait, BT_CONN_MGR_INACTIVITY_TIMEOUT_SECS);
|
||||
cl_assert_equal_i(fake_gap_le_connect_params_get_last_requested(), ResponseTimeInvalid);
|
||||
|
||||
prv_regular_timer_spend_seconds(BT_CONN_MGR_INACTIVITY_TIMEOUT_SECS);
|
||||
|
||||
state = conn_mgr_get_latency_for_le_connection(&s_hdl, &secs_to_wait);
|
||||
cl_assert_equal_i(state, ResponseTimeMiddle);
|
||||
cl_assert_equal_i(secs_to_wait, 30 - BT_CONN_MGR_INACTIVITY_TIMEOUT_SECS);
|
||||
cl_assert_equal_i(fake_gap_le_connect_params_get_last_requested(), ResponseTimeMiddle);
|
||||
|
||||
// removing all consumers we should fall back to slowest interval, but only
|
||||
// after an "inactivity timeout":
|
||||
fake_gap_le_connect_params_reset_last_requested();
|
||||
conn_mgr_set_ble_conn_response_time(
|
||||
&s_hdl, BtConsumerUnitTests, ResponseTimeMax, 0);
|
||||
|
||||
prv_regular_timer_spend_seconds(BT_CONN_MGR_INACTIVITY_TIMEOUT_SECS);
|
||||
state = conn_mgr_get_latency_for_le_connection(&s_hdl, &secs_to_wait);
|
||||
cl_assert_equal_i(state, ResponseTimeMax);
|
||||
cl_assert_equal_i(fake_gap_le_connect_params_get_last_requested(), ResponseTimeMax);
|
||||
|
||||
// if nothing else is scheduled, middle rate should get picked up right away
|
||||
conn_mgr_set_ble_conn_response_time(
|
||||
&s_hdl, BtConsumerUnitTests, ResponseTimeMiddle, 30);
|
||||
state = conn_mgr_get_latency_for_le_connection(&s_hdl, &secs_to_wait);
|
||||
cl_assert_equal_i(state, ResponseTimeMiddle);
|
||||
cl_assert_equal_i(secs_to_wait, 30);
|
||||
cl_assert_equal_i(fake_gap_le_connect_params_get_last_requested(), ResponseTimeMiddle);
|
||||
|
||||
// higher rate should take over lower rate already scheduled
|
||||
conn_mgr_set_ble_conn_response_time(
|
||||
&s_hdl, BtConsumerLeServiceDiscovery, ResponseTimeMin, 25);
|
||||
state = conn_mgr_get_latency_for_le_connection(&s_hdl, &secs_to_wait);
|
||||
cl_assert_equal_i(state, ResponseTimeMin);
|
||||
cl_assert_equal_i(secs_to_wait, 25);
|
||||
cl_assert_equal_i(fake_gap_le_connect_params_get_last_requested(), ResponseTimeMin);
|
||||
|
||||
// two requests at same high rate, longest time should be selected as timeout
|
||||
fake_gap_le_connect_params_reset_last_requested();
|
||||
conn_mgr_set_ble_conn_response_time(
|
||||
&s_hdl, BtConsumerUnitTests, ResponseTimeMin, 250);
|
||||
state = conn_mgr_get_latency_for_le_connection(&s_hdl, &secs_to_wait);
|
||||
cl_assert_equal_i(state, ResponseTimeMin);
|
||||
cl_assert_equal_i(secs_to_wait, 250);
|
||||
cl_assert_equal_i(fake_gap_le_connect_params_get_last_requested(), ResponseTimeInvalid);
|
||||
|
||||
bt_conn_mgr_info_deinit(&s_hdl.conn_mgr_info);
|
||||
}
|
||||
|
||||
static void prv_granted_handler(void) {
|
||||
++s_granted_count;
|
||||
}
|
||||
|
||||
void test_bt_conn_mgr__granted_handler_request_max_no_existing_node(void) {
|
||||
fake_gap_le_connect_params_set_actual_state(ResponseTimeMax);
|
||||
conn_mgr_set_ble_conn_response_time_ext(&s_hdl, BtConsumerLeServiceDiscovery, ResponseTimeMax, 1,
|
||||
prv_granted_handler);
|
||||
// Expect granted handler to be called immediately:
|
||||
cl_assert_equal_i(s_granted_count, 1);
|
||||
}
|
||||
|
||||
void test_bt_conn_mgr__granted_handler_request_existing(void) {
|
||||
fake_gap_le_connect_params_set_actual_state(ResponseTimeMax);
|
||||
conn_mgr_set_ble_conn_response_time_ext(&s_hdl, BtConsumerLeServiceDiscovery, ResponseTimeMin, 1,
|
||||
prv_granted_handler);
|
||||
cl_assert_equal_i(s_granted_count, 0);
|
||||
|
||||
// Simulate that the requested state takes effect:
|
||||
fake_gap_le_connect_params_set_actual_state(ResponseTimeMin);
|
||||
conn_mgr_handle_desired_state_granted(&s_hdl, ResponseTimeMin);
|
||||
cl_assert_equal_i(s_granted_count, 1);
|
||||
|
||||
conn_mgr_set_ble_conn_response_time_ext(&s_hdl, BtConsumerLeServiceDiscovery, ResponseTimeMin, 1,
|
||||
prv_granted_handler);
|
||||
cl_assert_equal_i(s_granted_count, 2);
|
||||
|
||||
conn_mgr_set_ble_conn_response_time_ext(&s_hdl, BtConsumerLeServiceDiscovery, ResponseTimeMiddle,
|
||||
1, prv_granted_handler);
|
||||
cl_assert_equal_i(s_granted_count, 3);
|
||||
|
||||
conn_mgr_set_ble_conn_response_time_ext(&s_hdl, BtConsumerLeServiceDiscovery, ResponseTimeMax,
|
||||
1, prv_granted_handler);
|
||||
cl_assert_equal_i(s_granted_count, 4);
|
||||
}
|
||||
|
||||
void test_bt_conn_mgr__request_max_time_while_no_requests_are_running(void) {
|
||||
uint16_t secs_to_wait;
|
||||
ResponseTimeState state;
|
||||
|
||||
// Always start off with ResponseTimeMax:
|
||||
state = conn_mgr_get_latency_for_le_connection(&s_hdl, &secs_to_wait);
|
||||
cl_assert_equal_i(state, ResponseTimeMax);
|
||||
|
||||
// Requesting ResponseTimeMax should have no effect:
|
||||
conn_mgr_set_ble_conn_response_time(&s_hdl, BtConsumerLeServiceDiscovery, ResponseTimeMax, 1);
|
||||
state = conn_mgr_get_latency_for_le_connection(&s_hdl, &secs_to_wait);
|
||||
cl_assert_equal_i(state, ResponseTimeMax);
|
||||
|
||||
// Not even after waiting 10 seconds:
|
||||
prv_regular_timer_spend_seconds(10);
|
||||
state = conn_mgr_get_latency_for_le_connection(&s_hdl, &secs_to_wait);
|
||||
cl_assert_equal_i(state, ResponseTimeMax);
|
||||
|
||||
bt_conn_mgr_info_deinit(&s_hdl.conn_mgr_info);
|
||||
}
|
||||
@@ -0,0 +1,955 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
@@ -0,0 +1,871 @@
|
||||
/*
|
||||
* 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 "bluetooth/gap_le_connect.h"
|
||||
#include "comm/ble/gap_le_connect.h"
|
||||
#include "comm/ble/gap_le_connection.h"
|
||||
#include "comm/ble/gap_le_task.h"
|
||||
|
||||
#include "kernel/events.h"
|
||||
#include "services/common/analytics/analytics.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
#include <bluetooth/bonding_sync.h>
|
||||
#include <bluetooth/sm_types.h>
|
||||
#include <btutil/bt_device.h>
|
||||
|
||||
// Fakes
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#include "fake_events.h"
|
||||
#include "fake_GAPAPI.h"
|
||||
#include "fake_bluetooth_persistent_storage.h"
|
||||
#include "fake_HCIAPI.h"
|
||||
#include "fake_new_timer.h"
|
||||
#include "fake_pbl_malloc.h"
|
||||
#include "fake_system_task.h"
|
||||
|
||||
// Stubs
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#include "stubs_bluetopia_interface.h"
|
||||
#include "stubs_bt_lock.h"
|
||||
#include "stubs_gap_le_advert.h"
|
||||
#include "stubs_bluetooth_analytics.h"
|
||||
#include "stubs_gatt_client_discovery.h"
|
||||
#include "stubs_gatt_client_subscriptions.h"
|
||||
#include "stubs_hexdump.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_mutex.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_pebble_pairing_service.h"
|
||||
#include "stubs_regular_timer.h"
|
||||
#include "stubs_shared_prf_storage.h"
|
||||
|
||||
// Note:
|
||||
// The unit tests for "Pebble as Master" are disabled because role switching is not implemented yet,
|
||||
// and the FW is currently "hard-wired" to be slave as a precautionary measure to prevent it from
|
||||
// trying to connect as master. See PBL-20368.
|
||||
|
||||
void bt_driver_cb_handle_create_bonding(const BleBonding *bonding,
|
||||
const BTDeviceAddress *addr) {
|
||||
}
|
||||
|
||||
void cc2564A_bad_le_connection_complete_handle(unsigned int stack_id,
|
||||
const GAP_LE_Current_Connection_Parameters_t *params) {
|
||||
}
|
||||
|
||||
const GAP_LE_Pairing_Capabilities_t* gap_le_pairing_capabilities(void) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void gap_le_device_name_request(uintptr_t stack_id, GAPLEConnection *connection) {
|
||||
}
|
||||
|
||||
void gatt_service_changed_server_cleanup_by_connection(GAPLEConnection *connection) {
|
||||
}
|
||||
|
||||
void bt_driver_handle_le_conn_params_update_event(
|
||||
const BleConnectionUpdateCompleteEvent *event) {
|
||||
}
|
||||
|
||||
typedef struct PairingUserConfirmationCtx PairingUserConfirmationCtx;
|
||||
|
||||
void bt_driver_pebble_pairing_service_handle_status_change(const GAPLEConnection *connection) {
|
||||
}
|
||||
|
||||
void bt_driver_cb_pairing_confirm_handle_request(const PairingUserConfirmationCtx *ctx,
|
||||
const char *device_name,
|
||||
const char *confirmation_token) {
|
||||
}
|
||||
|
||||
void bt_driver_cb_pairing_confirm_handle_completed(const PairingUserConfirmationCtx *ctx,
|
||||
bool success) {
|
||||
}
|
||||
|
||||
void launcher_task_add_callback(void (*callback)(void *data), void *data) {
|
||||
callback(data);
|
||||
}
|
||||
|
||||
void analytics_event_bt_error(AnalyticsEvent type, uint32_t error) {
|
||||
}
|
||||
|
||||
void bluetooth_analytics_handle_connection_disconnection_event(
|
||||
AnalyticsEvent type, uint8_t reason, const BleRemoteVersionInfo *vers_info) {
|
||||
}
|
||||
|
||||
void analytics_inc(AnalyticsMetric metric, AnalyticsClient client) {
|
||||
}
|
||||
|
||||
// Helpers
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
static BTDeviceInternal prv_dummy_device(uint8_t octet) {
|
||||
BTDeviceAddress address = {
|
||||
.octets = {
|
||||
[0] = octet,
|
||||
[1] = octet,
|
||||
[2] = octet,
|
||||
[3] = octet,
|
||||
[4] = octet,
|
||||
[5] = octet,
|
||||
},
|
||||
};
|
||||
BTDevice device = bt_device_init_with_address(address, true /* is_random */);
|
||||
return *(BTDeviceInternal *)(&device);
|
||||
}
|
||||
|
||||
static BTBondingID prv_add_bonding_for_fake_resolvable_device(void) {
|
||||
BTDeviceInternal identity_device = {};
|
||||
const SMIdentityResolvingKey *irk = (const SMIdentityResolvingKey *) fake_GAPAPI_get_fake_irk();
|
||||
BleBonding bonding = {
|
||||
.pairing_info = {
|
||||
.identity = identity_device,
|
||||
.irk = *irk,
|
||||
.is_remote_identity_info_valid = true,
|
||||
},
|
||||
.is_gateway = true,
|
||||
};
|
||||
bt_driver_handle_host_added_bonding(&bonding);
|
||||
return fake_bt_persistent_storage_add(irk, &identity_device, "Dummy", true /* is_gateway */);
|
||||
}
|
||||
|
||||
static void prv_assert_no_event(void) {
|
||||
PebbleEvent event = fake_event_get_last();
|
||||
cl_assert_equal_i(event.type, PEBBLE_NULL_EVENT);
|
||||
}
|
||||
|
||||
static void prv_fake_connect(const BTDeviceInternal *device, bool is_master) {
|
||||
// Simulate getting a Connection Complete event for the device from Bluetopia:
|
||||
fake_gap_put_connection_event(HCI_ERROR_CODE_SUCCESS,
|
||||
is_master, device);
|
||||
cl_assert_equal_b(gap_le_connection_is_connected(device), true);
|
||||
}
|
||||
|
||||
static void prv_fake_disconnect(const BTDeviceInternal *device, bool is_master) {
|
||||
fake_gap_put_disconnection_event(HCI_ERROR_CODE_SUCCESS,
|
||||
HCI_ERROR_CODE_CONNECTION_TERMINATED_BY_LOCAL_HOST,
|
||||
is_master,
|
||||
device);
|
||||
cl_assert_equal_b(gap_le_connection_is_connected(device), false);
|
||||
}
|
||||
|
||||
static void prv_assert_client_event(const BTDeviceInternal *device,
|
||||
bool connected,
|
||||
PebbleTaskBitset client_tasks,
|
||||
uint8_t hci_reason) {
|
||||
// Verify the Pebble event:
|
||||
PebbleEvent event = fake_event_get_last();
|
||||
cl_assert_equal_i(event.type, PEBBLE_BLE_CONNECTION_EVENT);
|
||||
// Event should only go to app:
|
||||
cl_assert_equal_i(event.task_mask, (PebbleTaskBitset) ~(client_tasks));
|
||||
const PebbleBLEConnectionEvent *conn_event = &event.bluetooth.le.connection;
|
||||
const BTDeviceInternal event_device = PebbleEventToBTDeviceInternal(conn_event);
|
||||
const bool is_same_device = bt_device_equal(&event_device.opaque,
|
||||
&device->opaque);
|
||||
cl_assert_equal_b(is_same_device, true);
|
||||
cl_assert_equal_b(conn_event->connected, connected);
|
||||
cl_assert_equal_i(conn_event->hci_reason, hci_reason);
|
||||
}
|
||||
|
||||
// Tests
|
||||
///////////////////////////////////////////////////////////
|
||||
extern void gap_le_connect_bluetopia_connection_callback(
|
||||
unsigned int stack_id, GAP_LE_Event_Data_t* event_data, unsigned long CallbackParameter);
|
||||
void test_gap_le_connect__initialize(void) {
|
||||
fake_GAPAPI_init();
|
||||
|
||||
// Register slave connection event callback for tests involving Pebble as slave:
|
||||
// This normally happens in gap_le_advert.c. Taking a shortcut to avoid dragging in more code.
|
||||
GAP_LE_Advertising_Enable(1, TRUE, NULL, NULL, gap_le_connect_bluetopia_connection_callback, 0);
|
||||
fake_event_init();
|
||||
fake_bt_persistent_storage_reset();
|
||||
gap_le_connection_init();
|
||||
gap_le_connect_init();
|
||||
}
|
||||
|
||||
void test_gap_le_connect__cleanup(void) {
|
||||
// Cancel all connection intents:
|
||||
for (GAPLEClient c = 0; c < GAPLEClientNum; ++c) {
|
||||
gap_le_connect_cancel_all(c);
|
||||
}
|
||||
|
||||
gap_le_connect_deinit();
|
||||
|
||||
cl_assert_equal_b(gap_le_connect_has_pending_create_connection(), false);
|
||||
cl_assert_equal_i(gap_le_connect_connection_intents_count(), 0);
|
||||
|
||||
gap_le_connection_deinit();
|
||||
|
||||
cl_assert_equal_b(fake_HCIAPI_whitelist_error_count(), 0);
|
||||
fake_HCIAPI_deinit();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Parameter / Bounds checking
|
||||
|
||||
void test_gap_le_connect__register_max_intents(void) {
|
||||
for (int i = 0; i < GAP_LE_CONNECT_MASTER_MAX_CONNECTION_INTENTS + 1; ++i) {
|
||||
BTDeviceInternal device = prv_dummy_device(i);
|
||||
BTErrno e = gap_le_connect_connect(&device,
|
||||
true /* auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
|
||||
if (i == GAP_LE_CONNECT_MASTER_MAX_CONNECTION_INTENTS) {
|
||||
// When the limit is reached, expect "not enough resources" error:
|
||||
cl_assert_equal_i(e, BTErrnoNotEnoughResources);
|
||||
} else {
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
const bool registered = gap_le_connect_has_connection_intent(&device,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_b(registered, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test_gap_le_connect__register_null_device(void) {
|
||||
BTErrno e = gap_le_connect_connect(NULL,
|
||||
true /* auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoInvalidParameter);
|
||||
}
|
||||
|
||||
void test_gap_le_connect__unregister_null_device(void) {
|
||||
BTErrno e = gap_le_connect_cancel(NULL, GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoInvalidParameter);
|
||||
}
|
||||
|
||||
void test_gap_le_connect__register_invalid_bonding(void) {
|
||||
BTErrno e = gap_le_connect_connect_by_bonding(BT_BONDING_ID_INVALID,
|
||||
true /* auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoInvalidParameter);
|
||||
}
|
||||
|
||||
void test_gap_le_connect__register_non_existing_bonding(void) {
|
||||
BTErrno e = gap_le_connect_connect_by_bonding(~0,
|
||||
true /* auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoInvalidParameter);
|
||||
}
|
||||
|
||||
void test_gap_le_connect__unregister_invalid_bonding(void) {
|
||||
BTErrno e = gap_le_connect_cancel_by_bonding(BT_BONDING_ID_INVALID, GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoInvalidParameter);
|
||||
}
|
||||
|
||||
void test_gap_le_connect__unregister_non_existing_bonding(void) {
|
||||
BTErrno e = gap_le_connect_cancel_by_bonding(~0, GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoInvalidParameter);
|
||||
}
|
||||
|
||||
void test_gap_le_connect__register_is_already_registered_for_same_client(void) {
|
||||
BTErrno e;
|
||||
bool registered;
|
||||
BTDeviceInternal device = prv_dummy_device(1);
|
||||
|
||||
// Register connection intent:
|
||||
e = gap_le_connect_connect(&device,
|
||||
true /* auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
registered = gap_le_connect_has_connection_intent(&device, GAPLEClientApp);
|
||||
cl_assert_equal_b(registered, true);
|
||||
|
||||
// Try registering the device again as same client:
|
||||
e = gap_le_connect_connect(&device,
|
||||
true /* auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoInvalidState);
|
||||
|
||||
// Should still be registered from the first call:
|
||||
registered = gap_le_connect_has_connection_intent(&device, GAPLEClientApp);
|
||||
cl_assert_equal_b(registered, true);
|
||||
}
|
||||
|
||||
void test_gap_le_connect__register_same_device_and_bonding(void) {
|
||||
// Test that it is possible to have 2 intents for the same device, when registering one intent
|
||||
// using the resolvable address and one with a bonding. Pebble will not try to collate these,
|
||||
// because there are many addresses that resolve to the same bonding. The current implementation
|
||||
// uses one address or one bonding per intent.
|
||||
BTErrno e;
|
||||
bool registered;
|
||||
BTDeviceInternal device = *fake_GAPAPI_get_device_resolving_to_fake_irk();
|
||||
BTBondingID bonding_id = prv_add_bonding_for_fake_resolvable_device();
|
||||
|
||||
// Register connection intent:
|
||||
e = gap_le_connect_connect(&device,
|
||||
true /* auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
registered = gap_le_connect_has_connection_intent(&device, GAPLEClientApp);
|
||||
cl_assert_equal_b(registered, true);
|
||||
|
||||
// Register another connection intent using the bonding:
|
||||
e = gap_le_connect_connect_by_bonding(bonding_id,
|
||||
true /* auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
registered = gap_le_connect_has_connection_intent_for_bonding(bonding_id, GAPLEClientApp);
|
||||
cl_assert_equal_b(registered, true);
|
||||
}
|
||||
|
||||
void test_gap_le_connect__register_two_clients_same_device(void) {
|
||||
BTErrno e;
|
||||
bool registered;
|
||||
BTDeviceInternal device = prv_dummy_device(1);
|
||||
|
||||
// Register connection intent:
|
||||
e = gap_le_connect_connect(&device,
|
||||
true /* auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
registered = gap_le_connect_has_connection_intent(&device, GAPLEClientApp);
|
||||
cl_assert_equal_b(registered, true);
|
||||
|
||||
// Try registering the device again for different client:
|
||||
e = gap_le_connect_connect(&device,
|
||||
true /* auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
// Assert registrations:
|
||||
registered = gap_le_connect_has_connection_intent(&device, GAPLEClientApp);
|
||||
cl_assert_equal_b(registered, true);
|
||||
registered = gap_le_connect_has_connection_intent(&device, GAPLEClientKernel);
|
||||
cl_assert_equal_b(registered, true);
|
||||
|
||||
// Only one registration (co-owned by the 2 clients):
|
||||
cl_assert_equal_i(gap_le_connect_connection_intents_count(), 1);
|
||||
}
|
||||
|
||||
void test_gap_le_connect__unregister_unknown_device(void) {
|
||||
BTDeviceInternal device = prv_dummy_device(1);
|
||||
BTErrno e = gap_le_connect_cancel(&device, GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoInvalidParameter);
|
||||
}
|
||||
|
||||
void test_gap_le_connect__unregister_unowned_intent(void) {
|
||||
BTDeviceInternal device = prv_dummy_device(1);
|
||||
BTErrno e;
|
||||
|
||||
// Register connection intent owned by kernel:
|
||||
e = gap_le_connect_connect(&device,
|
||||
true /* auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
// Unregister connection intent owned by app:
|
||||
e = gap_le_connect_cancel(&device, GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoInvalidParameter);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Virtual (dis)connection events
|
||||
|
||||
void __disabled_test_gap_le_connect__connection_event_for_registered_client(void) {
|
||||
BTDeviceInternal device = prv_dummy_device(1);
|
||||
|
||||
// Register connection intent:
|
||||
BTErrno e = gap_le_connect_connect(&device,
|
||||
true /* auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
// Device isn't connected. Verify no event was caused as a result of the
|
||||
// registration.
|
||||
prv_assert_no_event();
|
||||
|
||||
// Connect & verify the client task gets the event:
|
||||
prv_fake_connect(&device, true /* is_master*/);
|
||||
prv_assert_client_event(&device, true /* connected */, (1 << PebbleTask_App),
|
||||
HCI_ERROR_CODE_SUCCESS);
|
||||
|
||||
// Disconnect & verify the client task gets the event:
|
||||
prv_fake_disconnect(&device, true /* is_master */);
|
||||
prv_assert_client_event(&device, false /* connected */, (1 << PebbleTask_App),
|
||||
HCI_ERROR_CODE_CONNECTION_TERMINATED_BY_LOCAL_HOST);
|
||||
}
|
||||
|
||||
void test_gap_le_connect__connection_event_for_registered_client_by_bonding(void) {
|
||||
BTDeviceInternal device = *fake_GAPAPI_get_device_resolving_to_fake_irk();
|
||||
BTBondingID bonding_id = prv_add_bonding_for_fake_resolvable_device();
|
||||
|
||||
// Register connection intent:
|
||||
BTErrno e = gap_le_connect_connect_by_bonding(bonding_id,
|
||||
true /* auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
// Device isn't connected. Verify no event was caused as a result of the
|
||||
// registration.
|
||||
prv_assert_no_event();
|
||||
|
||||
// Connect & verify the client task gets the event:
|
||||
prv_fake_connect(&device, false /* is_master*/);
|
||||
prv_assert_client_event(&device, true /* connected */, (1 << PebbleTask_App),
|
||||
HCI_ERROR_CODE_SUCCESS);
|
||||
|
||||
// Disconnect & verify the client task gets the event:
|
||||
prv_fake_disconnect(&device, false /* is_master */);
|
||||
prv_assert_client_event(&device, false /* connected */, (1 << PebbleTask_App),
|
||||
HCI_ERROR_CODE_CONNECTION_TERMINATED_BY_LOCAL_HOST);
|
||||
}
|
||||
|
||||
void __disabled_test_gap_le_connect__register_for_already_connected_device(void) {
|
||||
BTErrno e;
|
||||
BTDeviceInternal device = prv_dummy_device(1);
|
||||
|
||||
// Register connection intent for kernel:
|
||||
e = gap_le_connect_connect(&device,
|
||||
true /* auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
// Simulate getting a Connection Complete event for the device from Bluetopia:
|
||||
prv_fake_connect(&device, true /* is_master*/);
|
||||
|
||||
// Verify the kernel task got a (virtual) connection event:
|
||||
prv_assert_client_event(&device, true /* connected */, (1 << PebbleTask_KernelMain),
|
||||
HCI_ERROR_CODE_SUCCESS);
|
||||
|
||||
// Register connection intent for app:
|
||||
e = gap_le_connect_connect(&device,
|
||||
true /* auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
// Verify (only) the app task got a (virtual) connection event:
|
||||
prv_assert_client_event(&device, true /* connected */, (1 << PebbleTask_App),
|
||||
HCI_ERROR_CODE_SUCCESS);
|
||||
}
|
||||
|
||||
void test_gap_le_connect__register_for_already_connected_bonding(void) {
|
||||
BTErrno e;
|
||||
BTDeviceInternal device = *fake_GAPAPI_get_device_resolving_to_fake_irk();
|
||||
BTBondingID bonding_id = prv_add_bonding_for_fake_resolvable_device();
|
||||
|
||||
// Register connection intent for kernel:
|
||||
e = gap_le_connect_connect_by_bonding(bonding_id,
|
||||
true /* auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
// Simulate getting a Connection Complete event for the device from Bluetopia:
|
||||
prv_fake_connect(&device, false /* is_master*/);
|
||||
|
||||
// Verify the kernel task got a (virtual) connection event:
|
||||
prv_assert_client_event(&device, true /* connected */, (1 << PebbleTask_KernelMain),
|
||||
HCI_ERROR_CODE_SUCCESS);
|
||||
|
||||
// Register connection intent for app:
|
||||
e = gap_le_connect_connect_by_bonding(bonding_id,
|
||||
true /* auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
// Verify (only) the app task got a (virtual) connection event:
|
||||
prv_assert_client_event(&device, true /* connected */, (1 << PebbleTask_App),
|
||||
HCI_ERROR_CODE_SUCCESS);
|
||||
}
|
||||
|
||||
void __disabled_test_gap_le_connect__disconnection_event_upon_airplane_mode(void) {
|
||||
BTDeviceInternal device = prv_dummy_device(1);
|
||||
|
||||
// Register connection intent:
|
||||
BTErrno e = gap_le_connect_connect(&device,
|
||||
true /* auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
prv_fake_connect(&device, true /* is_master*/);
|
||||
|
||||
// Air-plane mode:
|
||||
gap_le_connect_deinit();
|
||||
|
||||
// Verify (only) the app task got a (virtual) connection event:
|
||||
prv_assert_client_event(&device, false /* connected */, (1 << PebbleTask_App),
|
||||
GAPLEConnectHCIReasonExtensionAirPlaneMode);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Auto-reconnect Tests
|
||||
|
||||
void __disabled_test_gap_le_connect__single_client_no_autoreconnect(void) {
|
||||
BTDeviceInternal device = prv_dummy_device(1);
|
||||
|
||||
// Register connection intent for app:
|
||||
gap_le_connect_connect(&device,
|
||||
false /* no auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
|
||||
prv_fake_connect(&device, true /* is_master*/);
|
||||
|
||||
// Verify the app task got a (virtual) connection event:
|
||||
prv_assert_client_event(&device, true /* connected */, (1 << PebbleTask_App),
|
||||
HCI_ERROR_CODE_SUCCESS);
|
||||
|
||||
prv_fake_disconnect(&device, true /* is_master */);
|
||||
|
||||
// Verify the app task got a (virtual) disconnection event:
|
||||
prv_assert_client_event(&device, false /* connected */, (1 << PebbleTask_App),
|
||||
HCI_ERROR_CODE_CONNECTION_TERMINATED_BY_LOCAL_HOST);
|
||||
|
||||
// Verify that the connection intent has been removed after disconnection:
|
||||
cl_assert_equal_i(gap_le_connect_connection_intents_count(), 0);
|
||||
}
|
||||
|
||||
void __disabled_test_gap_le_connect__two_clients_one_without_autoreconnect(void) {
|
||||
BTDeviceInternal device = prv_dummy_device(1);
|
||||
|
||||
// Register auto-reconnecting connection intent for kernel:
|
||||
gap_le_connect_connect(&device,
|
||||
true /* auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientKernel);
|
||||
|
||||
prv_fake_connect(&device, true /* is_master*/);
|
||||
|
||||
// Register one-shot connection intent for app:
|
||||
gap_le_connect_connect(&device,
|
||||
false /* no auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
|
||||
prv_fake_disconnect(&device, true /* is_master */);
|
||||
|
||||
// Verify both app task and kernel got the (virtual) disconnection event:
|
||||
prv_assert_client_event(&device, false /* connected */,
|
||||
(1 << PebbleTask_App) | (1 << PebbleTask_KernelMain),
|
||||
HCI_ERROR_CODE_CONNECTION_TERMINATED_BY_LOCAL_HOST);
|
||||
|
||||
// Verify that the connection intent is still there for the Kernel:
|
||||
cl_assert_equal_i(gap_le_connect_connection_intents_count(), 1);
|
||||
cl_assert_equal_b(gap_le_connect_has_connection_intent(&device, GAPLEClientKernel), true);
|
||||
cl_assert_equal_b(gap_le_connect_has_connection_intent(&device, GAPLEClientApp), false);
|
||||
cl_assert_equal_b(gap_le_connect_has_pending_create_connection(), true);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Cancel Connect (as Master)
|
||||
|
||||
void __disabled_test_gap_le_connect__cancel_connect(void) {
|
||||
BTDeviceInternal device = prv_dummy_device(1);
|
||||
|
||||
// Register connection intent for app:
|
||||
gap_le_connect_connect(&device,
|
||||
false /* no auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
|
||||
BTErrno e = gap_le_connect_cancel(&device, GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
// The LE Cancel Create Connection command is always followed by an event that
|
||||
// is sent by the BT Controller. Simulate this event:
|
||||
fake_gap_le_put_cancel_create_event(&device, true /* is_master */);
|
||||
|
||||
prv_assert_no_event();
|
||||
|
||||
// Verify there are no more intents:
|
||||
cl_assert_equal_i(gap_le_connect_connection_intents_count(), 0);
|
||||
cl_assert_equal_b(gap_le_connect_has_pending_create_connection(), false);
|
||||
}
|
||||
|
||||
void __disabled_test_gap_le_connect__disconnection_event_upon_cancel_connect(void) {
|
||||
BTDeviceInternal device = prv_dummy_device(1);
|
||||
|
||||
// Register connection intent for app:
|
||||
gap_le_connect_connect(&device,
|
||||
false /* no auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
|
||||
prv_fake_connect(&device, false /* is_master*/);
|
||||
|
||||
BTErrno e = gap_le_connect_cancel(&device, GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
// The LE Cancel Create Connection command is always followed by an event that
|
||||
// is sent by the BT Controller. Simulate this event:
|
||||
fake_gap_le_put_cancel_create_event(&device, true /* is_master */);
|
||||
|
||||
// Verify the app task got a (virtual) disconnection event:
|
||||
prv_assert_client_event(&device, false /* connected */, (1 << PebbleTask_App),
|
||||
GAPLEConnectHCIReasonExtensionCancelConnect);
|
||||
// Verify there are no more intents:
|
||||
cl_assert_equal_i(gap_le_connect_connection_intents_count(), 0);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Cancel Connect by Bonding (as Slave)
|
||||
|
||||
void test_gap_le_connect__slave_cancel_connect_by_bonding(void) {
|
||||
BTBondingID bonding_id = prv_add_bonding_for_fake_resolvable_device();
|
||||
|
||||
// Register connection intent for app:
|
||||
gap_le_connect_connect_by_bonding(bonding_id,
|
||||
false /* no auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
|
||||
BTErrno e = gap_le_connect_cancel_by_bonding(bonding_id, GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
prv_assert_no_event();
|
||||
|
||||
// Verify there are no more intents:
|
||||
cl_assert_equal_i(gap_le_connect_connection_intents_count(), 0);
|
||||
cl_assert_equal_b(gap_le_connect_has_pending_create_connection(), false);
|
||||
}
|
||||
|
||||
void test_gap_le_connect__slave_disconnection_event_upon_cancel_connect_by_bonding(void) {
|
||||
BTDeviceInternal device = *fake_GAPAPI_get_device_resolving_to_fake_irk();
|
||||
BTBondingID bonding_id = prv_add_bonding_for_fake_resolvable_device();
|
||||
|
||||
prv_fake_connect(&device, false /* is_master*/);
|
||||
|
||||
// Register connection intent for app:
|
||||
gap_le_connect_connect_by_bonding(bonding_id,
|
||||
false /* no auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
|
||||
BTErrno e = gap_le_connect_cancel_by_bonding(bonding_id, GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
// Verify the app task got a (virtual) disconnection event:
|
||||
prv_assert_client_event(&device, false /* connected */, (1 << PebbleTask_App),
|
||||
GAPLEConnectHCIReasonExtensionCancelConnect);
|
||||
// Verify there are no more intents:
|
||||
cl_assert_equal_i(gap_le_connect_connection_intents_count(), 0);
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// Pairing
|
||||
|
||||
void __disabled_test_gap_le_connect__one_shot_intent_removed_when_disconnected_before_encrpt(void) {
|
||||
BTDeviceInternal device = prv_dummy_device(1);
|
||||
|
||||
// Register connection one-shot intent, with pairing required:
|
||||
gap_le_connect_connect(&device,
|
||||
false /* no auto_reconnect */,
|
||||
true /* is_pairing_required */,
|
||||
GAPLEClientKernel);
|
||||
|
||||
// Expect intent:
|
||||
cl_assert_equal_b(gap_le_connect_has_connection_intent(&device, GAPLEClientKernel), true);
|
||||
|
||||
prv_fake_connect(&device, true /* is_master*/);
|
||||
prv_fake_disconnect(&device, true /* is_master */);
|
||||
|
||||
// Expect intent to be removed:
|
||||
cl_assert_equal_b(gap_le_connect_has_connection_intent(&device, GAPLEClientKernel), false);
|
||||
}
|
||||
|
||||
void test_gap_le_connect__connection_event_only_after_encrypted_if_encryption_required(void) {
|
||||
BTDeviceInternal device = *fake_GAPAPI_get_device_resolving_to_fake_irk();
|
||||
BTBondingID bonding_id = prv_add_bonding_for_fake_resolvable_device();
|
||||
|
||||
// Register connection intent for app:
|
||||
gap_le_connect_connect_by_bonding(bonding_id,
|
||||
true /* no auto_reconnect */,
|
||||
true /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
|
||||
prv_fake_connect(&device, false /* is_master*/);
|
||||
|
||||
// Verify the app task got NO (virtual) connection event, the link is not encrypted yet:
|
||||
// TODO: legacy PEBBLE_BT_CONNECTION_EVENT is still emitted, see gap_le_connect.c
|
||||
// prv_put_legacy_connection_event.
|
||||
//
|
||||
// prv_assert_no_event();
|
||||
|
||||
fake_event_clear_last();
|
||||
fake_GAPAPI_set_encrypted_for_device(&device);
|
||||
fake_GAPAPI_put_encryption_change_event(true /* encrypted */, GAP_LE_PAIRING_STATUS_NO_ERROR,
|
||||
false /* is_master */, &device);
|
||||
|
||||
// Verify the app task got a (virtual) connection event:
|
||||
prv_assert_client_event(&device, true /* connected */, (1 << PebbleTask_App),
|
||||
HCI_ERROR_CODE_SUCCESS);
|
||||
}
|
||||
|
||||
void test_gap_le_connect__add_intent_requiring_pairing_after_connected_and_encrypted(void) {
|
||||
BTDeviceInternal device = *fake_GAPAPI_get_device_resolving_to_fake_irk();
|
||||
BTBondingID bonding_id = prv_add_bonding_for_fake_resolvable_device();
|
||||
|
||||
prv_fake_connect(&device, false /* is_master*/);
|
||||
fake_GAPAPI_set_encrypted_for_device(&device);
|
||||
fake_GAPAPI_put_encryption_change_event(true /* encrypted */, GAP_LE_PAIRING_STATUS_NO_ERROR,
|
||||
false /* is_master */, &device);
|
||||
fake_event_clear_last();
|
||||
|
||||
const BleAddressAndIRKChange e = {
|
||||
.device = device,
|
||||
.is_address_updated = true,
|
||||
.new_device = device,
|
||||
.is_resolved = true,
|
||||
.irk = *(const SMIdentityResolvingKey *) fake_GAPAPI_get_fake_irk(),
|
||||
};
|
||||
bt_driver_handle_le_connection_handle_update_address_and_irk(&e);
|
||||
|
||||
gap_le_connection_by_device(&device);
|
||||
|
||||
|
||||
// Register connection intent for app:
|
||||
gap_le_connect_connect_by_bonding(bonding_id,
|
||||
true /* no auto_reconnect */,
|
||||
true /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
|
||||
// Verify the app task got a (virtual) connection event:
|
||||
prv_assert_client_event(&device, true /* connected */, (1 << PebbleTask_App),
|
||||
HCI_ERROR_CODE_SUCCESS);
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Handling Bonding Changes
|
||||
|
||||
void test_gap_le_connect__removed_bonding_while_connected(void) {
|
||||
BTDeviceInternal device = *fake_GAPAPI_get_device_resolving_to_fake_irk();
|
||||
BTBondingID bonding_id = prv_add_bonding_for_fake_resolvable_device();
|
||||
|
||||
// Register connection intent for app:
|
||||
gap_le_connect_connect_by_bonding(bonding_id,
|
||||
false /* no auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
|
||||
prv_fake_connect(&device, false /* is_master*/);
|
||||
|
||||
// Verify the app task got a (virtual) connection event:
|
||||
prv_assert_client_event(&device, true /* connected */, (1 << PebbleTask_App),
|
||||
HCI_ERROR_CODE_SUCCESS);
|
||||
|
||||
// Simulate "bonding will delete" callback:
|
||||
gap_le_connect_handle_bonding_change(bonding_id, BtPersistBondingOpWillDelete);
|
||||
|
||||
// Verify the app task got a (virtual) disconnection event:
|
||||
prv_assert_client_event(&device, false /* connected */, (1 << PebbleTask_App),
|
||||
GAPLEConnectHCIReasonExtensionUserRemovedBonding);
|
||||
// Verify there are no more intents:
|
||||
cl_assert_equal_i(gap_le_connect_connection_intents_count(), 0);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// BT Controller White-list management
|
||||
|
||||
void __disabled_test_gap_le_connect__whitelist_add_when_disconnected(void) {
|
||||
BTDeviceInternal device = prv_dummy_device(1);
|
||||
|
||||
// Register connection intent for app:
|
||||
gap_le_connect_connect(&device,
|
||||
false /* no auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
|
||||
// Not connected yet, so expect to be added to white-list:
|
||||
cl_assert_equal_b(fake_HCIAPI_whitelist_contains(&device), true);
|
||||
|
||||
prv_fake_connect(&device, true /* is_master*/);
|
||||
|
||||
// Connected, so expect to be removed from white-list:
|
||||
cl_assert_equal_b(fake_HCIAPI_whitelist_contains(&device), false);
|
||||
}
|
||||
|
||||
void __disabled_test_gap_le_connect__whitelist_add_when_connected(void) {
|
||||
BTDeviceInternal device = prv_dummy_device(1);
|
||||
|
||||
// Register connection intent for app:
|
||||
gap_le_connect_connect(&device,
|
||||
false /* no auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientKernel);
|
||||
|
||||
prv_fake_connect(&device, true /* is_master*/);
|
||||
|
||||
// Register connection intent for app:
|
||||
gap_le_connect_connect(&device,
|
||||
false /* no auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
|
||||
// Connected, so expect to be removed from white-list:
|
||||
cl_assert_equal_b(fake_HCIAPI_whitelist_contains(&device), false);
|
||||
}
|
||||
|
||||
void __disabled_test_gap_le_connect__whitelist_remove_when_connected(void) {
|
||||
BTDeviceInternal device = prv_dummy_device(1);
|
||||
|
||||
// Register connection intent for app:
|
||||
gap_le_connect_connect(&device,
|
||||
false /* no auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
prv_fake_connect(&device, true /* is_master*/);
|
||||
|
||||
gap_le_connect_cancel(&device, GAPLEClientApp);
|
||||
|
||||
// Connected, so expect to be removed from white-list:
|
||||
cl_assert_equal_b(fake_HCIAPI_whitelist_contains(&device), false);
|
||||
}
|
||||
|
||||
void __disabled_test_gap_le_connect__whitelist_repopulated_on_init(void) {
|
||||
BTDeviceInternal device = prv_dummy_device(1);
|
||||
gap_le_connect_connect(&device,
|
||||
false /* no auto_reconnect */,
|
||||
false /* is_pairing_required */,
|
||||
GAPLEClientApp);
|
||||
|
||||
gap_le_connect_deinit();
|
||||
|
||||
// Connection intents survive air-plane mode:
|
||||
cl_assert_equal_i(gap_le_connect_connection_intents_count(), 1);
|
||||
|
||||
// "Reset" BT Controller:
|
||||
fake_HCIAPI_deinit();
|
||||
cl_assert_equal_i(fake_HCIAPI_whitelist_count(), 0);
|
||||
|
||||
gap_le_connect_init();
|
||||
|
||||
// Not connected yet, so expect to be added to white-list:
|
||||
cl_assert_equal_b(fake_HCIAPI_whitelist_contains(&device), true);
|
||||
cl_assert_equal_i(fake_HCIAPI_whitelist_count(), 1);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* 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 <bluetooth/gatt.h>
|
||||
|
||||
#include "comm/ble/gatt_client_accessors.h"
|
||||
#include "comm/ble/gatt_client_discovery.h"
|
||||
#include "comm/ble/gatt_service_changed.h"
|
||||
#include "comm/ble/gap_le_connection.h"
|
||||
|
||||
#include "kernel/events.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
#include <btutil/bt_device.h>
|
||||
#include <btutil/bt_uuid.h>
|
||||
|
||||
// Fakes
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#include "fake_GAPAPI.h"
|
||||
#include "fake_GATTAPI.h"
|
||||
#include "fake_GATTAPI_test_vectors.h"
|
||||
#include "fake_events.h"
|
||||
#include "fake_new_timer.h"
|
||||
#include "fake_system_task.h"
|
||||
#include "fake_pbl_malloc.h"
|
||||
|
||||
// Stubs
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#include "stubs_bluetopia_interface.h"
|
||||
#include "stubs_bt_driver_gatt.h"
|
||||
#include "stubs_bt_lock.h"
|
||||
#include "stubs_gatt_client_subscriptions.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_mutex.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_prompt.h"
|
||||
#include "stubs_rand_ptr.h"
|
||||
#include "stubs_regular_timer.h"
|
||||
|
||||
void core_dump_reset(bool is_forced) {
|
||||
}
|
||||
|
||||
void launcher_task_add_callback(void (*callback)(void *data), void *data) {
|
||||
callback(data);
|
||||
}
|
||||
|
||||
uint16_t gaps_get_starting_att_handle(void) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
// Helpers
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#define TEST_GATT_CONNECTION_ID (1234)
|
||||
|
||||
static BTDeviceInternal prv_dummy_device(uint8_t octet) {
|
||||
BTDeviceAddress address = {
|
||||
.octets = {
|
||||
[0] = octet,
|
||||
[1] = octet,
|
||||
[2] = octet,
|
||||
[3] = octet,
|
||||
[4] = octet,
|
||||
[5] = octet,
|
||||
},
|
||||
};
|
||||
BTDevice device = bt_device_init_with_address(address, true /* is_random */);
|
||||
return *(BTDeviceInternal *)(&device);
|
||||
}
|
||||
|
||||
static BTDeviceInternal prv_connected_dummy_device(uint8_t octet) {
|
||||
BTDeviceInternal device = prv_dummy_device(octet);
|
||||
gap_le_connection_add(&device, NULL, true /* local_is_master */);
|
||||
GAPLEConnection *connection = gap_le_connection_by_device(&device);
|
||||
connection->gatt_connection_id = TEST_GATT_CONNECTION_ID;
|
||||
return device;
|
||||
}
|
||||
|
||||
static void prv_mock_put_service_discovery_events(void) {
|
||||
// Simulate discovery of Blood Pressure service:
|
||||
fake_gatt_put_discovery_indication_blood_pressure_service(TEST_GATT_CONNECTION_ID);
|
||||
fake_gatt_put_discovery_indication_health_thermometer_service(TEST_GATT_CONNECTION_ID);
|
||||
fake_gatt_put_discovery_indication_random_128bit_uuid_service(TEST_GATT_CONNECTION_ID);
|
||||
fake_gatt_put_discovery_complete_event(GATT_SERVICE_DISCOVERY_STATUS_SUCCESS,
|
||||
TEST_GATT_CONNECTION_ID);
|
||||
}
|
||||
|
||||
// Tests
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
void test_gatt_client_accessors__initialize(void) {
|
||||
fake_gatt_init();
|
||||
fake_event_init();
|
||||
gap_le_connection_init();
|
||||
}
|
||||
|
||||
void test_gatt_client_accessors__cleanup(void) {
|
||||
gap_le_connection_deinit();
|
||||
}
|
||||
|
||||
void test_gatt_client_accessors__copy_service_refs(void) {
|
||||
BTDeviceInternal device = prv_connected_dummy_device(1);
|
||||
|
||||
// Start discovery:
|
||||
cl_assert_equal_i(gatt_client_discovery_discover_all(&device), BTErrnoOK);
|
||||
prv_mock_put_service_discovery_events();
|
||||
|
||||
const Service *bp_service = fake_gatt_get_blood_pressure_service();
|
||||
const Service *thermo_service = fake_gatt_get_health_thermometer_service();
|
||||
const Service *random_128bit_service = fake_gatt_get_random_128bit_uuid_service();
|
||||
const Service *services[] = {
|
||||
bp_service,
|
||||
thermo_service,
|
||||
random_128bit_service,
|
||||
};
|
||||
const uint8_t num_services = 3;
|
||||
|
||||
// Test gatt_client_copy_service_refs():
|
||||
BLEService service_refs[num_services];
|
||||
uint8_t num_found_services = gatt_client_copy_service_refs(&device, service_refs, num_services);
|
||||
cl_assert_equal_i(num_found_services, num_services);
|
||||
|
||||
for (uint8_t s = 0; s < num_found_services; ++s) {
|
||||
BLEService service_ref = service_refs[s];
|
||||
const Service *expected_service = services[s];
|
||||
|
||||
// Test gatt_client_service_get_uuid():
|
||||
const Uuid uuid = gatt_client_service_get_uuid(service_ref);
|
||||
cl_assert(uuid_equal(&uuid, &expected_service->uuid));
|
||||
|
||||
// Test gatt_client_service_get_device():
|
||||
BTDeviceInternal returned_device = gatt_client_service_get_device(service_ref);
|
||||
cl_assert(bt_device_equal(&returned_device.opaque, &device.opaque));
|
||||
|
||||
// Check Characteristics:
|
||||
const uint8_t num_characteristics = expected_service->num_characteristics;
|
||||
BLECharacteristic characteristic_refs[num_characteristics];
|
||||
// Test gatt_client_service_get_characteristics():
|
||||
const uint8_t num_found_characteristics =
|
||||
gatt_client_service_get_characteristics(service_ref, characteristic_refs, num_characteristics);
|
||||
cl_assert_equal_i(num_characteristics, num_found_characteristics);
|
||||
|
||||
for (uint8_t c = 0; c < num_found_characteristics; ++c) {
|
||||
BLECharacteristic characteristic_ref = characteristic_refs[c];
|
||||
const Characteristic *expected_characteristic = &expected_service->characteristics[c];
|
||||
|
||||
// Test gatt_client_characteristic_get_uuid():
|
||||
const Uuid uuid = gatt_client_characteristic_get_uuid(characteristic_ref);
|
||||
cl_assert(uuid_equal(&uuid, &expected_characteristic->uuid));
|
||||
|
||||
// Test gatt_client_characteristic_get_properties():
|
||||
cl_assert_equal_i(gatt_client_characteristic_get_properties(characteristic_ref),
|
||||
expected_characteristic->properties);
|
||||
|
||||
// Test gatt_client_characteristic_get_service():
|
||||
cl_assert_equal_i(gatt_client_characteristic_get_service(characteristic_ref), service_ref);
|
||||
|
||||
// Test gatt_client_characteristic_get_device():
|
||||
BTDeviceInternal returned_device = gatt_client_characteristic_get_device(characteristic_ref);
|
||||
cl_assert(bt_device_equal(&returned_device.opaque, &device.opaque));
|
||||
|
||||
// Test gatt_client_characteristic_get_descriptors():
|
||||
const uint8_t num_descriptors = expected_characteristic->num_descriptors;
|
||||
BLEDescriptor descriptor_refs[num_descriptors];
|
||||
const uint8_t num_found_descriptors =
|
||||
gatt_client_characteristic_get_descriptors(characteristic_ref, descriptor_refs, num_descriptors);
|
||||
cl_assert_equal_i(num_descriptors, num_found_descriptors);
|
||||
|
||||
for (uint8_t d = 0; d < num_descriptors; ++d) {
|
||||
const Descriptor *expected_descriptor = &expected_characteristic->descriptors[d];
|
||||
const BLEDescriptor descriptor_ref = descriptor_refs[d];
|
||||
|
||||
// Test gatt_client_descriptor_get_uuid():
|
||||
const Uuid uuid = gatt_client_descriptor_get_uuid(descriptor_ref);
|
||||
cl_assert(uuid_equal(&uuid, &expected_descriptor->uuid));
|
||||
|
||||
// Test gatt_client_descriptor_get_characteristic():
|
||||
cl_assert_equal_i(gatt_client_descriptor_get_characteristic(descriptor_ref), characteristic_ref);
|
||||
}
|
||||
}
|
||||
|
||||
// Test gatt_client_service_get_included_services():
|
||||
const uint8_t num_inc_services = expected_service->num_included_services;
|
||||
BLEService inc_service_refs[num_inc_services];
|
||||
const uint8_t num_found_included_services =
|
||||
gatt_client_service_get_included_services(service_ref, inc_service_refs, num_inc_services);
|
||||
cl_assert_equal_i(num_inc_services, num_found_included_services);
|
||||
|
||||
for (uint8_t i = 0; i < num_inc_services; ++i) {
|
||||
const Service *expected_inc_service = expected_service->included_services[i];
|
||||
BLEService inc_service = inc_service_refs[i];
|
||||
|
||||
// Only check the Service UUID:
|
||||
const Uuid uuid = gatt_client_service_get_uuid(inc_service);
|
||||
cl_assert(uuid_equal(&uuid, &expected_inc_service->uuid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test_gatt_client_accessors__copy_service_refs_matching(void) {
|
||||
BTDeviceInternal device = prv_connected_dummy_device(1);
|
||||
|
||||
// Start discovery:
|
||||
cl_assert_equal_i(gatt_client_discovery_discover_all(&device), BTErrnoOK);
|
||||
prv_mock_put_service_discovery_events();
|
||||
|
||||
const uint8_t num_services = 1;
|
||||
|
||||
// Test gatt_client_copy_service_refs():
|
||||
BLEService service_refs[num_services];
|
||||
const Service *bp_service = fake_gatt_get_blood_pressure_service();
|
||||
uint8_t num_found_services = gatt_client_copy_service_refs_matching_uuid(&device, service_refs,
|
||||
num_services,
|
||||
&bp_service->uuid);
|
||||
cl_assert_equal_i(num_found_services, num_services);
|
||||
|
||||
// Test that the UUID matches the Blood Pressure UUID:
|
||||
const Uuid uuid = gatt_client_service_get_uuid(service_refs[0]);
|
||||
cl_assert(uuid_equal(&uuid, &bp_service->uuid));
|
||||
}
|
||||
|
||||
void test_gatt_client_accessors__get_characteristics_matching_uuids(void) {
|
||||
BTDeviceInternal device = prv_connected_dummy_device(1);
|
||||
|
||||
// Start discovery:
|
||||
cl_assert_equal_i(gatt_client_discovery_discover_all(&device), BTErrnoOK);
|
||||
prv_mock_put_service_discovery_events();
|
||||
|
||||
const Service *bp_service = fake_gatt_get_blood_pressure_service();
|
||||
|
||||
// Get the reference to the Blood Pressure service:
|
||||
const uint8_t num_services = 1;
|
||||
BLEService service_refs[num_services];
|
||||
gatt_client_copy_service_refs_matching_uuid(&device, service_refs,
|
||||
num_services, &bp_service->uuid);
|
||||
|
||||
Uuid matching_uuids[3];
|
||||
matching_uuids[0] = bp_service->characteristics[1].uuid;
|
||||
matching_uuids[1] = bt_uuid_expand_16bit(0xffff); // not expected to match
|
||||
matching_uuids[2] = bp_service->characteristics[0].uuid;
|
||||
|
||||
BLECharacteristic characteristics[3];
|
||||
const uint8_t found =
|
||||
gatt_client_service_get_characteristics_matching_uuids(service_refs[0], characteristics,
|
||||
matching_uuids, 3);
|
||||
cl_assert_equal_i(found, 2);
|
||||
|
||||
// Expect the order of the matching_uuids array is preserved:
|
||||
cl_assert(uuid_equal(&matching_uuids[0], &bp_service->characteristics[1].uuid));
|
||||
cl_assert(uuid_equal(&matching_uuids[2], &bp_service->characteristics[0].uuid));
|
||||
|
||||
// Expect the 0xffff UUID to return "no match":
|
||||
cl_assert_equal_i(characteristics[1], BLE_CHARACTERISTIC_INVALID);
|
||||
}
|
||||
|
||||
extern uint8_t gatt_client_copy_service_refs_by_discovery_generation(
|
||||
const BTDeviceInternal *device, BLEService services_out[],
|
||||
uint8_t num_services, uint8_t discovery_gen);
|
||||
extern void gatt_client_discovery_discover_range(GAPLEConnection *connection,
|
||||
ATTHandleRange *hdl_range);
|
||||
|
||||
void test_gatt_client_accessors__get_service_refs_by_discovery_gen(void) {
|
||||
BTDeviceInternal device = prv_connected_dummy_device(1);
|
||||
|
||||
cl_assert_equal_i(gatt_client_discovery_discover_all(&device), BTErrnoOK);
|
||||
fake_gatt_put_discovery_indication_blood_pressure_service(TEST_GATT_CONNECTION_ID);
|
||||
fake_gatt_put_discovery_complete_event(GATT_SERVICE_DISCOVERY_STATUS_SUCCESS, TEST_GATT_CONNECTION_ID);
|
||||
|
||||
ATTHandleRange range = {
|
||||
.start = 0x1,
|
||||
.end = 0xC000,
|
||||
};
|
||||
|
||||
gatt_client_discovery_discover_range(gap_le_connection_by_device(&device), &range);
|
||||
fake_gatt_put_discovery_indication_health_thermometer_service(TEST_GATT_CONNECTION_ID);
|
||||
fake_gatt_put_discovery_indication_random_128bit_uuid_service(TEST_GATT_CONNECTION_ID);
|
||||
fake_gatt_put_discovery_complete_event(GATT_SERVICE_DISCOVERY_STATUS_SUCCESS, TEST_GATT_CONNECTION_ID);
|
||||
|
||||
|
||||
const Service *bp_service = fake_gatt_get_blood_pressure_service();
|
||||
const Service *thermo_service = fake_gatt_get_health_thermometer_service();
|
||||
const Service *random_128bit_service = fake_gatt_get_random_128bit_uuid_service();
|
||||
|
||||
BLEService service_refs_out[3];
|
||||
|
||||
// Only the BP service should be part of the first generation
|
||||
uint8_t refs_out = gatt_client_copy_service_refs_by_discovery_generation(
|
||||
&device, service_refs_out, 3, 0);
|
||||
cl_assert_equal_i(1, refs_out);
|
||||
const Uuid uuid = gatt_client_service_get_uuid(service_refs_out[0]);
|
||||
cl_assert(uuid_equal(&uuid, &bp_service->uuid));
|
||||
|
||||
// Thermo & Random 128 bit service should be part of the second gen
|
||||
refs_out = gatt_client_copy_service_refs_by_discovery_generation(
|
||||
&device, service_refs_out, 3, 1);
|
||||
cl_assert_equal_i(2, refs_out);
|
||||
const Uuid uuid1 = gatt_client_service_get_uuid(service_refs_out[0]);
|
||||
const Uuid uuid2 = gatt_client_service_get_uuid(service_refs_out[0]);
|
||||
cl_assert(uuid_equal(&uuid1, &thermo_service->uuid) ||
|
||||
uuid_equal(&uuid1, &random_128bit_service->uuid));
|
||||
cl_assert(uuid_equal(&uuid2, &thermo_service->uuid) ||
|
||||
uuid_equal(&uuid2, &random_128bit_service->uuid));
|
||||
}
|
||||
@@ -0,0 +1,520 @@
|
||||
/*
|
||||
* 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/gatt_client_discovery.h"
|
||||
#include "comm/ble/gatt_service_changed.h"
|
||||
#include "comm/ble/gap_le_connection.h"
|
||||
#include "comm/ble/gap_le_task.h"
|
||||
|
||||
#include "kernel/events.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
#include <btutil/bt_device.h>
|
||||
|
||||
// Fakes
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#include "fake_GAPAPI.h"
|
||||
#include "fake_GATTAPI.h"
|
||||
#include "fake_GATTAPI_test_vectors.h"
|
||||
#include "fake_events.h"
|
||||
#include "fake_new_timer.h"
|
||||
#include "fake_pbl_malloc.h"
|
||||
#include "fake_system_task.h"
|
||||
|
||||
// Stubs
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#include "stubs_bluetopia_interface.h"
|
||||
#include "stubs_bt_driver_gatt.h"
|
||||
#include "stubs_bt_lock.h"
|
||||
#include "stubs_gatt_client_subscriptions.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_mutex.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_rand_ptr.h"
|
||||
#include "stubs_regular_timer.h"
|
||||
|
||||
void core_dump_reset(bool is_forced) {
|
||||
}
|
||||
|
||||
void launcher_task_add_callback(void (*callback)(void *data), void *data) {
|
||||
callback(data);
|
||||
}
|
||||
|
||||
uint16_t gaps_get_starting_att_handle(void) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
// Helpers
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
extern TimerID bt_driver_gatt_get_watchdog_timer_id(void);
|
||||
|
||||
#define TEST_GATT_CONNECTION_ID (1234)
|
||||
|
||||
static BTDeviceInternal prv_dummy_device(uint8_t octet) {
|
||||
BTDeviceAddress address = {
|
||||
.octets = {
|
||||
[0] = octet,
|
||||
[1] = octet,
|
||||
[2] = octet,
|
||||
[3] = octet,
|
||||
[4] = octet,
|
||||
[5] = octet,
|
||||
},
|
||||
};
|
||||
BTDevice device = bt_device_init_with_address(address, true /* is_random */);
|
||||
return *(BTDeviceInternal *)(&device);
|
||||
}
|
||||
|
||||
static BTDeviceInternal prv_connected_dummy_device(uint8_t octet) {
|
||||
BTDeviceInternal device = prv_dummy_device(octet);
|
||||
gap_le_connection_add(&device, NULL, true /* local_is_master */);
|
||||
GAPLEConnection *connection = gap_le_connection_by_device(&device);
|
||||
connection->gatt_connection_id = TEST_GATT_CONNECTION_ID;
|
||||
return device;
|
||||
}
|
||||
|
||||
static void prv_assert_no_event(void) {
|
||||
PebbleEvent event = fake_event_get_last();
|
||||
cl_assert_equal_i(event.type, PEBBLE_NULL_EVENT);
|
||||
}
|
||||
|
||||
static void prv_assert_event(const BTDeviceInternal *device, BTErrno status) {
|
||||
PebbleEvent event = fake_event_get_last();
|
||||
cl_assert_equal_i(event.type, PEBBLE_BLE_GATT_CLIENT_EVENT);
|
||||
cl_assert_equal_i(event.bluetooth.le.gatt_client_service.subtype,
|
||||
PebbleBLEGATTClientEventTypeServiceChange);
|
||||
cl_assert_equal_i(event.bluetooth.le.gatt_client_service.info->status,
|
||||
status);
|
||||
const BTDeviceInternal event_device = event.bluetooth.le.gatt_client_service.info->device;
|
||||
const bool equal_devices = bt_device_equal(&device->opaque,
|
||||
&event_device.opaque);
|
||||
cl_assert_equal_b(equal_devices, true);
|
||||
|
||||
// clear the event
|
||||
fake_event_clear_last();
|
||||
fake_event_reset_count();
|
||||
}
|
||||
|
||||
static void prv_simulate_and_assert_discovery_of_one_service(const BTDeviceInternal *device) {
|
||||
// Simulate discovery of Blood Pressure service:
|
||||
fake_gatt_put_discovery_indication_blood_pressure_service(TEST_GATT_CONNECTION_ID);
|
||||
fake_gatt_put_discovery_complete_event(GATT_SERVICE_DISCOVERY_STATUS_SUCCESS,
|
||||
TEST_GATT_CONNECTION_ID);
|
||||
prv_assert_event(device, BTErrnoOK);
|
||||
}
|
||||
|
||||
// Tests
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
void test_gatt_client_discovery__initialize(void) {
|
||||
fake_gatt_init();
|
||||
fake_event_init();
|
||||
gap_le_connection_init();
|
||||
}
|
||||
|
||||
void test_gatt_client_discovery__cleanup(void) {
|
||||
gap_le_connection_deinit();
|
||||
|
||||
// make sure we haven't leaked any memory!
|
||||
fake_pbl_malloc_check_net_allocs();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Edge cases
|
||||
|
||||
void test_gatt_client_discovery__not_connected(void) {
|
||||
BTDeviceInternal device = prv_dummy_device(1);
|
||||
cl_assert_equal_i(gatt_client_discovery_discover_all(&device),
|
||||
BTErrnoInvalidParameter);
|
||||
cl_assert_equal_b(fake_gatt_is_service_discovery_running(), false);
|
||||
}
|
||||
|
||||
void test_gatt_client_discovery__already_in_progress(void) {
|
||||
BTDeviceInternal device = prv_connected_dummy_device(1);
|
||||
|
||||
// Start discovery for device:
|
||||
cl_assert_equal_i(gatt_client_discovery_discover_all(&device), BTErrnoOK);
|
||||
cl_assert_equal_b(fake_gatt_is_service_discovery_running(), true);
|
||||
|
||||
// Start again (expect to fail):
|
||||
cl_assert_equal_i(gatt_client_discovery_discover_all(&device),
|
||||
BTErrnoInvalidState);
|
||||
|
||||
// take down the connection and a disconnection event should be emitted
|
||||
gap_le_connection_remove(&device);
|
||||
prv_assert_event(&device, BTErrnoServiceDiscoveryDisconnected);
|
||||
}
|
||||
|
||||
void test_gatt_client_discovery__event_is_sent_when_already_discovered(void) {
|
||||
BTDeviceInternal device = prv_connected_dummy_device(1);
|
||||
|
||||
// Start discovery:
|
||||
cl_assert_equal_i(gatt_client_discovery_discover_all(&device), BTErrnoOK);
|
||||
cl_assert_equal_b(fake_gatt_is_service_discovery_running(), true);
|
||||
|
||||
// Simulate discovery of 1 service:
|
||||
fake_gatt_put_discovery_indication_blood_pressure_service(TEST_GATT_CONNECTION_ID);
|
||||
fake_gatt_put_discovery_complete_event(GATT_SERVICE_DISCOVERY_STATUS_SUCCESS,
|
||||
TEST_GATT_CONNECTION_ID);
|
||||
cl_assert_equal_b(fake_gatt_is_service_discovery_running(), false);
|
||||
prv_assert_event(&device, BTErrnoOK);
|
||||
|
||||
fake_event_clear_last();
|
||||
|
||||
// Start discovery again, expect not to run (already discovered):
|
||||
cl_assert_equal_i(gatt_client_discovery_discover_all(&device), BTErrnoOK);
|
||||
cl_assert_equal_b(fake_gatt_is_service_discovery_running(), false);
|
||||
|
||||
// Expect event:
|
||||
prv_assert_event(&device, BTErrnoOK);
|
||||
}
|
||||
|
||||
void test_gatt_client_discovery__disconnected_during_discovery(void) {
|
||||
BTDeviceInternal device = prv_connected_dummy_device(1);
|
||||
|
||||
// Start discovery:
|
||||
cl_assert_equal_i(gatt_client_discovery_discover_all(&device), BTErrnoOK);
|
||||
// Simulate disconnection:
|
||||
gap_le_connection_remove(&device);
|
||||
// Process racing discovery indication:
|
||||
fake_gatt_put_discovery_indication_blood_pressure_service(TEST_GATT_CONNECTION_ID);
|
||||
// Bluetopia's GATT module does *NOT* emit a service discovery completion event for disconnections
|
||||
|
||||
// Test that our API *does* emit an service discovery event with "disconnected" reason:
|
||||
prv_assert_event(&device, BTErrnoServiceDiscoveryDisconnected);
|
||||
}
|
||||
|
||||
void test_gatt_client_discovery__complete_error(void) {
|
||||
BTDeviceInternal device = prv_connected_dummy_device(1);
|
||||
|
||||
// Start discovery:
|
||||
cl_assert_equal_i(gatt_client_discovery_discover_all(&device), BTErrnoOK);
|
||||
// Simulate getting one service indication...
|
||||
fake_gatt_put_discovery_indication_blood_pressure_service(TEST_GATT_CONNECTION_ID);
|
||||
// ... then a failure:
|
||||
fake_gatt_put_discovery_complete_event(GATT_SERVICE_DISCOVERY_STATUS_RESPONSE_TIMEOUT,
|
||||
TEST_GATT_CONNECTION_ID);
|
||||
// Expect event with error status and 0 services:
|
||||
prv_assert_event(&device,
|
||||
BTErrnoWithBluetopiaError(GATT_SERVICE_DISCOVERY_STATUS_RESPONSE_TIMEOUT));
|
||||
// Expect service discovery to be stopped:
|
||||
cl_assert_equal_b(fake_gatt_is_service_discovery_running(), false);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// Watchdog timeout tests
|
||||
|
||||
static void prv_fire_watchdog_timeouts(const BTDeviceInternal *device, int retries) {
|
||||
for (int i = 0; i <= retries; ++i) {
|
||||
const int start_count = fake_gatt_is_service_discovery_start_count();
|
||||
const int stop_count = fake_gatt_is_service_discovery_stop_count();
|
||||
|
||||
// Fire the watchdog timer:
|
||||
const TimerID watchdog_timer = bt_driver_gatt_get_watchdog_timer_id();
|
||||
stub_new_timer_fire(watchdog_timer);
|
||||
|
||||
// Check whether GATT_Stop_Service_Discovery has been called:
|
||||
cl_assert_equal_i(stop_count + 1, fake_gatt_is_service_discovery_stop_count());
|
||||
|
||||
if (i < GATT_CLIENT_DISCOVERY_MAX_RETRY) {
|
||||
// Check whether GATT_Start_Service_Discovery has been called, except for the last iteration:
|
||||
cl_assert_equal_i(start_count + 1, fake_gatt_is_service_discovery_start_count());
|
||||
// No client event:
|
||||
prv_assert_no_event();
|
||||
} else {
|
||||
// Last iteration: expect event with error status and 0 services:
|
||||
prv_assert_event(device, BTErrnoServiceDiscoveryTimeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test_gatt_client_discovery__watchdog_error_out_after_max_retries(void) {
|
||||
BTDeviceInternal device = prv_connected_dummy_device(1);
|
||||
|
||||
// Start discovery:
|
||||
cl_assert_equal_i(gatt_client_discovery_discover_all(&device), BTErrnoOK);
|
||||
prv_fire_watchdog_timeouts(&device, GATT_CLIENT_DISCOVERY_MAX_RETRY);
|
||||
}
|
||||
|
||||
void test_gatt_client_discovery__watchdog_retry_counter_not_affecting_successive_process(void) {
|
||||
BTDeviceInternal device = prv_connected_dummy_device(1);
|
||||
|
||||
// Start discovery:
|
||||
cl_assert_equal_i(gatt_client_discovery_discover_all(&device), BTErrnoOK);
|
||||
prv_fire_watchdog_timeouts(&device, GATT_CLIENT_DISCOVERY_MAX_RETRY);
|
||||
|
||||
// Make sure the previous retry counter doesn't affect any new discovery process:
|
||||
fake_event_clear_last();
|
||||
cl_assert_equal_i(gatt_client_discovery_discover_all(&device), BTErrnoOK);
|
||||
|
||||
// Fire watchdog one less time than the maximum:
|
||||
prv_fire_watchdog_timeouts(&device, GATT_CLIENT_DISCOVERY_MAX_RETRY - 1);
|
||||
|
||||
// Finally make the Bluetopia discovery events come in:
|
||||
prv_simulate_and_assert_discovery_of_one_service(&device);
|
||||
}
|
||||
|
||||
void test_gatt_client_discovery__watchdog_race_with_stopping(void) {
|
||||
BTDeviceInternal device = prv_connected_dummy_device(1);
|
||||
|
||||
// Start discovery:
|
||||
cl_assert_equal_i(gatt_client_discovery_discover_all(&device), BTErrnoOK);
|
||||
|
||||
// Make Bluetopia's GATT_Stop_Service_Discovery fail:
|
||||
// (Service discovery has finished in the mean time, disconnected, ...)
|
||||
fake_gatt_set_stop_return_value(BTGATT_ERROR_INVALID_PARAMETER);
|
||||
|
||||
// Fire the watchdog timer:
|
||||
const TimerID watchdog_timer = bt_driver_gatt_get_watchdog_timer_id();
|
||||
stub_new_timer_fire(watchdog_timer);
|
||||
|
||||
// No event should be generated, because the finishing / disconnecting / ... should cause
|
||||
// Bluetopia to call back to prv_handle_bluetopia_discovery_event() and therefore the normal
|
||||
// path will be taken.
|
||||
prv_assert_no_event();
|
||||
|
||||
// take down the connection and a disconnection event should be emitted
|
||||
gap_le_connection_remove(&device);
|
||||
prv_assert_event(&device, BTErrnoServiceDiscoveryDisconnected);
|
||||
}
|
||||
|
||||
void test_gatt_client_discovery__watchdog_race_with_restarting(void) {
|
||||
BTDeviceInternal device = prv_connected_dummy_device(1);
|
||||
|
||||
// Start discovery:
|
||||
cl_assert_equal_i(gatt_client_discovery_discover_all(&device), BTErrnoOK);
|
||||
|
||||
// Make Bluetopia's GATT_Start_Service_Discovery fail:
|
||||
// (Disconnected in the mean time, ...)
|
||||
fake_gatt_set_start_return_value(BTGATT_ERROR_INVALID_PARAMETER);
|
||||
|
||||
// Fire the watchdog timer:
|
||||
const TimerID watchdog_timer = bt_driver_gatt_get_watchdog_timer_id();
|
||||
stub_new_timer_fire(watchdog_timer);
|
||||
|
||||
// Stopping did not fail, but restarting did. In this case we need to generate an event that
|
||||
// the discovery process failed. The error from GATT_Start_Service_Discovery is expected to be
|
||||
// passed in the event.
|
||||
prv_assert_event(&device, BTErrnoWithBluetopiaError(BTGATT_ERROR_INVALID_PARAMETER));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// Re-discovery
|
||||
|
||||
extern BTErrno gatt_client_discovery_rediscover_all(const BTDeviceInternal *device);
|
||||
|
||||
void test_gatt_client_discovery__rediscover_not_already_running(void) {
|
||||
BTDeviceInternal device = prv_connected_dummy_device(1);
|
||||
|
||||
// Start discovery:
|
||||
cl_assert_equal_i(gatt_client_discovery_discover_all(&device), BTErrnoOK);
|
||||
prv_simulate_and_assert_discovery_of_one_service(&device);
|
||||
|
||||
GAPLEConnection *connection = gap_le_connection_by_gatt_id(TEST_GATT_CONNECTION_ID);
|
||||
// Expect one service, Blood Pressure:
|
||||
cl_assert_equal_i(list_count(&connection->gatt_remote_services->node), 1);
|
||||
|
||||
fake_event_clear_last();
|
||||
|
||||
// Re-discovery:
|
||||
cl_assert_equal_i(gatt_client_discovery_rediscover_all(&device), BTErrnoOK);
|
||||
|
||||
// Expect "Database Changed" event:
|
||||
prv_assert_event(&device, BTErrnoServiceDiscoveryDatabaseChanged);
|
||||
// Expect all services nodes to be cleaned up:
|
||||
cl_assert_equal_i(list_count(&connection->gatt_remote_services->node), 0);
|
||||
|
||||
// Put one, expect one:
|
||||
prv_simulate_and_assert_discovery_of_one_service(&device);
|
||||
cl_assert_equal_i(list_count(&connection->gatt_remote_services->node), 1);
|
||||
}
|
||||
|
||||
void test_gatt_client_discovery__rediscover_already_running(void) {
|
||||
BTDeviceInternal device = prv_connected_dummy_device(1);
|
||||
|
||||
// Start discovery:
|
||||
cl_assert_equal_i(gatt_client_discovery_discover_all(&device), BTErrnoOK);
|
||||
// Put one service, but do not finish...
|
||||
fake_gatt_put_discovery_indication_blood_pressure_service(TEST_GATT_CONNECTION_ID);
|
||||
prv_assert_no_event();
|
||||
cl_assert_equal_b(fake_gatt_is_service_discovery_running(), true);
|
||||
const int stop_count_before_rediscovery = fake_gatt_is_service_discovery_stop_count();
|
||||
|
||||
// Re-discovery:
|
||||
cl_assert_equal_i(gatt_client_discovery_rediscover_all(&device), BTErrnoOK);
|
||||
|
||||
// Assert the previous process has been stopped:
|
||||
cl_assert_equal_i(stop_count_before_rediscovery + 1, fake_gatt_is_service_discovery_stop_count());
|
||||
|
||||
// Expect "Database Changed" event:
|
||||
prv_assert_event(&device, BTErrnoServiceDiscoveryDatabaseChanged);
|
||||
|
||||
// Put one, expect one:
|
||||
prv_simulate_and_assert_discovery_of_one_service(&device);
|
||||
}
|
||||
|
||||
extern void gatt_client_discovery_discover_range(GAPLEConnection *connection,
|
||||
ATTHandleRange *hdl_range);
|
||||
|
||||
void test_gatt_client_discovery__multiple_jobs_pending(void) {
|
||||
BTDeviceInternal device = prv_connected_dummy_device(1);
|
||||
GAPLEConnection *connection = gap_le_connection_by_device(&device);
|
||||
|
||||
ATTHandleRange range = {
|
||||
.start = 0x1,
|
||||
.end = 0x3000
|
||||
};
|
||||
ATTHandleRange range_alt = {
|
||||
.start = 0x3001,
|
||||
.end = 0x4000
|
||||
};
|
||||
|
||||
cl_assert_equal_b(fake_gatt_is_service_discovery_running(), false);
|
||||
|
||||
// start a discovery job, pretend nothing is found
|
||||
gatt_client_discovery_discover_range(connection, &range);
|
||||
cl_assert_equal_b(fake_gatt_is_service_discovery_running(), true);
|
||||
// pend up another service discovery job
|
||||
gatt_client_discovery_discover_range(connection, &range);
|
||||
cl_assert_equal_i(1, fake_gatt_is_service_discovery_start_count());
|
||||
|
||||
fake_gatt_put_discovery_complete_event(GATT_SERVICE_DISCOVERY_STATUS_SUCCESS,
|
||||
TEST_GATT_CONNECTION_ID);
|
||||
// Nothing was found so we should just have a completion event
|
||||
cl_assert_equal_i(1, fake_event_get_count());
|
||||
|
||||
prv_assert_event(&device, BTErrnoOK);
|
||||
cl_assert_equal_i(2, fake_gatt_is_service_discovery_start_count());
|
||||
|
||||
// next job should be in progress
|
||||
cl_assert_equal_b(fake_gatt_is_service_discovery_running(), true);
|
||||
// BP service was discovered
|
||||
fake_gatt_put_discovery_indication_blood_pressure_service(TEST_GATT_CONNECTION_ID);
|
||||
// start another job, should be pended
|
||||
gatt_client_discovery_discover_range(connection, &range);
|
||||
fake_gatt_put_discovery_complete_event(GATT_SERVICE_DISCOVERY_STATUS_SUCCESS,
|
||||
TEST_GATT_CONNECTION_ID);
|
||||
|
||||
// Since we are restarting discovery over the same range and discovered one
|
||||
// service two events should be generated, one about the discovery complete
|
||||
// and one invalidating the just discovered service
|
||||
cl_assert_equal_i(2, fake_event_get_count());
|
||||
prv_assert_event(&device, BTErrnoOK);
|
||||
cl_assert_equal_i(3, fake_gatt_is_service_discovery_start_count());
|
||||
|
||||
fake_gatt_put_discovery_indication_blood_pressure_service(TEST_GATT_CONNECTION_ID);
|
||||
|
||||
gatt_client_discovery_discover_range(connection, &range_alt);
|
||||
|
||||
// BP service should have been discovered
|
||||
fake_gatt_put_discovery_complete_event(GATT_SERVICE_DISCOVERY_STATUS_SUCCESS,
|
||||
TEST_GATT_CONNECTION_ID);
|
||||
// Only one event should have been pended since we were not rediscovering the
|
||||
// same handle range
|
||||
cl_assert_equal_i(1, fake_event_get_count());
|
||||
prv_assert_event(&device, BTErrnoOK);
|
||||
|
||||
// Nothing discovered for final query
|
||||
fake_gatt_put_discovery_complete_event(GATT_SERVICE_DISCOVERY_STATUS_SUCCESS,
|
||||
TEST_GATT_CONNECTION_ID);
|
||||
prv_assert_event(&device, BTErrnoOK);
|
||||
|
||||
cl_assert_equal_i(4, fake_gatt_is_service_discovery_start_count());
|
||||
cl_assert_equal_b(fake_gatt_is_service_discovery_running(), false);
|
||||
}
|
||||
|
||||
void test_gatt_client_discovery__partial_and_full_discovery_jobs_intermixed(void) {
|
||||
BTDeviceInternal device = prv_connected_dummy_device(1);
|
||||
GAPLEConnection *connection = gap_le_connection_by_device(&device);
|
||||
|
||||
// queue up a few jobs - note only one should be running at any time
|
||||
ATTHandleRange range = {
|
||||
.start = 0x1,
|
||||
.end = 0x3000
|
||||
};
|
||||
for (int i = 0; i < 10; i++) {
|
||||
gatt_client_discovery_discover_range(connection, &range);
|
||||
}
|
||||
|
||||
// kick off a full discovery
|
||||
cl_assert_equal_i(gatt_client_discovery_rediscover_all(&device), BTErrnoOK);
|
||||
|
||||
// Assert the previous process has been stopped:
|
||||
cl_assert_equal_i(1, fake_gatt_is_service_discovery_stop_count());
|
||||
|
||||
// Expect "Database Changed" event:
|
||||
prv_assert_event(&device, BTErrnoServiceDiscoveryDatabaseChanged);
|
||||
|
||||
prv_simulate_and_assert_discovery_of_one_service(&device);
|
||||
|
||||
// None of the batched up jobs should have run
|
||||
cl_assert_equal_i(2, fake_gatt_is_service_discovery_start_count());
|
||||
|
||||
// nothing should still be running
|
||||
cl_assert_equal_b(fake_gatt_is_service_discovery_running(), false);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// Test vectors
|
||||
|
||||
static void prv_assert_blood_pressure_service(const GATTService *service) {
|
||||
const Service *bp_service = fake_gatt_get_blood_pressure_service();
|
||||
|
||||
const uint16_t service_handle = bp_service->handle;
|
||||
cl_assert_equal_i(service->att_handle, service_handle);
|
||||
cl_assert_equal_b(uuid_equal(&service->uuid, &bp_service->uuid), true);
|
||||
cl_assert_equal_i(service->num_att_handles_included_services, bp_service->num_included_services);
|
||||
cl_assert_equal_i(service->num_characteristics, bp_service->num_characteristics);
|
||||
|
||||
const GATTCharacteristic *characteristic_one = service->characteristics;
|
||||
const Characteristic *expected_characteristic1 = &bp_service->characteristics[0];
|
||||
cl_assert_equal_i(characteristic_one->att_handle_offset, expected_characteristic1->handle - service_handle);
|
||||
cl_assert_equal_i(characteristic_one->num_descriptors, expected_characteristic1->num_descriptors);
|
||||
cl_assert_equal_i(characteristic_one->descriptors[0].att_handle_offset,
|
||||
expected_characteristic1->descriptors[0].handle - service_handle);
|
||||
cl_assert_equal_i(characteristic_one->properties, expected_characteristic1->properties);
|
||||
cl_assert_equal_b(uuid_equal(&characteristic_one->uuid, &expected_characteristic1->uuid), true);
|
||||
|
||||
// Second characteristic is tacked right after the first one:
|
||||
const GATTCharacteristic *characteristic_two =
|
||||
(const GATTCharacteristic *) &characteristic_one->descriptors[1];
|
||||
const Characteristic *expected_characteristic2 = &bp_service->characteristics[1];
|
||||
cl_assert_equal_i(characteristic_two->att_handle_offset, expected_characteristic2->handle - service_handle);
|
||||
cl_assert_equal_i(characteristic_two->num_descriptors, expected_characteristic2->num_descriptors);
|
||||
cl_assert_equal_i(characteristic_two->descriptors[0].att_handle_offset,
|
||||
expected_characteristic2->descriptors[0].handle - service_handle);
|
||||
cl_assert_equal_i(characteristic_two->properties, expected_characteristic2->properties);
|
||||
cl_assert_equal_b(uuid_equal(&characteristic_two->uuid, &expected_characteristic2->uuid), true);
|
||||
}
|
||||
|
||||
void test_gatt_client_discovery__single_blood_pressure_service(void) {
|
||||
BTDeviceInternal device = prv_connected_dummy_device(1);
|
||||
|
||||
// Start discovery:
|
||||
cl_assert_equal_i(gatt_client_discovery_discover_all(&device), BTErrnoOK);
|
||||
|
||||
prv_simulate_and_assert_discovery_of_one_service(&device);
|
||||
|
||||
GAPLEConnection *connection = gap_le_connection_by_gatt_id(TEST_GATT_CONNECTION_ID);
|
||||
// Expect one service, Blood Pressure:
|
||||
cl_assert_equal_i(list_count(&connection->gatt_remote_services->node), 1);
|
||||
const GATTService *service = connection->gatt_remote_services->service;
|
||||
prv_assert_blood_pressure_service(service);
|
||||
}
|
||||
@@ -0,0 +1,942 @@
|
||||
/*
|
||||
* 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 <bluetooth/gatt.h>
|
||||
|
||||
#include "comm/ble/gatt_client_subscriptions.h"
|
||||
#include "comm/ble/gap_le_connection.h"
|
||||
#include "comm/ble/gap_le_task.h"
|
||||
#include "comm/ble/gatt_service_changed.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
#include <btutil/bt_device.h>
|
||||
#include <btutil/bt_uuid.h>
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "semphr.h"
|
||||
|
||||
// Fakes
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#include "fake_events.h"
|
||||
#include "fake_pbl_malloc.h"
|
||||
#include "fake_GATTAPI.h"
|
||||
#include "fake_GATTAPI_test_vectors.h"
|
||||
#include "fake_new_timer.h"
|
||||
#include "fake_queue.h"
|
||||
#include "fake_system_task.h"
|
||||
|
||||
#include "stubs_regular_timer.h"
|
||||
|
||||
static BTErrno s_write_descriptor_cccd_result;
|
||||
static BLEDescriptor s_last_cccd_ref;
|
||||
static uint16_t s_last_cccd_value;
|
||||
|
||||
BTErrno gatt_client_op_write_descriptor_cccd(BLEDescriptor cccd, const uint16_t *value) {
|
||||
s_last_cccd_ref = cccd;
|
||||
s_last_cccd_value = *value;
|
||||
return s_write_descriptor_cccd_result;
|
||||
}
|
||||
|
||||
// FIXME: PBL-23945
|
||||
void fake_kernel_malloc_mark(void) { }
|
||||
void fake_kernel_malloc_mark_assert_equal(void) { }
|
||||
|
||||
// Stubs
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#include "stubs_analytics.h"
|
||||
#include "stubs_bluetopia_interface.h"
|
||||
#include "stubs_bt_driver_gatt.h"
|
||||
#include "stubs_bt_lock.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_mutex.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_rand_ptr.h"
|
||||
#include "stubs_tick.h"
|
||||
|
||||
void core_dump_reset(bool is_forced) {
|
||||
}
|
||||
|
||||
void launcher_task_add_callback(void (*callback)(void *data), void *data) {
|
||||
callback(data);
|
||||
}
|
||||
|
||||
uint16_t gaps_get_starting_att_handle(void) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
// Helpers
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
extern void gatt_client_subscriptions_handle_server_notification(GAPLEConnection *connection,
|
||||
uint16_t att_handle,
|
||||
const uint8_t *att_value,
|
||||
uint16_t att_length);
|
||||
|
||||
extern void gatt_client_subscriptions_handle_write_cccd_response(BLEDescriptor cccd,
|
||||
BLEGATTError error);
|
||||
|
||||
extern uint16_t gatt_client_characteristic_get_handle_and_connection(
|
||||
BLECharacteristic characteristic_ref,
|
||||
GAPLEConnection **connection);
|
||||
|
||||
extern SemaphoreHandle_t gatt_client_subscription_get_semaphore(void);
|
||||
extern void gatt_client_subscription_cleanup(void);
|
||||
|
||||
#define TEST_GATT_CONNECTION_ID (1234)
|
||||
#define BOGUS_CHARACTERISTIC ((BLECharacteristic) 888)
|
||||
#define BOGUS_ATT_HANDLE ((uint16_t) 0xffff)
|
||||
|
||||
static BTDeviceInternal s_device;
|
||||
static GAPLEConnection *s_connection;
|
||||
static uint16_t s_handle;
|
||||
|
||||
static BTDeviceInternal prv_dummy_device(uint8_t octet) {
|
||||
BTDeviceAddress address = {
|
||||
.octets = {
|
||||
[0] = octet,
|
||||
[1] = octet,
|
||||
[2] = octet,
|
||||
[3] = octet,
|
||||
[4] = octet,
|
||||
[5] = octet,
|
||||
},
|
||||
};
|
||||
BTDevice device = bt_device_init_with_address(address, true /* is_random */);
|
||||
return *(BTDeviceInternal *)(&device);
|
||||
}
|
||||
|
||||
static BTDeviceInternal prv_connected_dummy_device(uint8_t octet) {
|
||||
BTDeviceInternal device = prv_dummy_device(octet);
|
||||
gap_le_connection_add(&device, NULL, true /* local_is_master */);
|
||||
s_connection = gap_le_connection_by_device(&device);
|
||||
s_connection->gatt_connection_id = TEST_GATT_CONNECTION_ID;
|
||||
return device;
|
||||
}
|
||||
|
||||
static void prv_assert_no_event(void) {
|
||||
PebbleEvent event = fake_event_get_last();
|
||||
cl_assert_equal_i(event.type, PEBBLE_NULL_EVENT);
|
||||
}
|
||||
|
||||
static void prv_assert_subscription_event(BLECharacteristic characteristic,
|
||||
BLESubscription subscription_type,
|
||||
BLEGATTError error, bool kernel, bool app) {
|
||||
PebbleEvent event = fake_event_get_last();
|
||||
cl_assert_equal_i(event.type, PEBBLE_BLE_GATT_CLIENT_EVENT);
|
||||
cl_assert_equal_i(event.bluetooth.le.gatt_client.subtype,
|
||||
PebbleBLEGATTClientEventTypeCharacteristicSubscribe);
|
||||
cl_assert_equal_i(event.bluetooth.le.gatt_client.subscription_type, subscription_type);
|
||||
cl_assert_equal_i(event.bluetooth.le.gatt_client.object_ref, characteristic);
|
||||
cl_assert_equal_i(event.bluetooth.le.gatt_client.gatt_error, error);
|
||||
PebbleTaskBitset task_mask = ~0;
|
||||
if (kernel) {
|
||||
task_mask &= ~gap_le_pebble_task_bit_for_client(GAPLEClientKernel);
|
||||
}
|
||||
if (app) {
|
||||
task_mask &= ~gap_le_pebble_task_bit_for_client(GAPLEClientApp);
|
||||
}
|
||||
cl_assert_equal_i(event.task_mask, task_mask);
|
||||
}
|
||||
|
||||
static void prv_assert_notification_event_ext(BLECharacteristic characteristic,
|
||||
const uint8_t *value, uint16_t assert_value_length,
|
||||
bool kernel, bool app, bool should_consume) {
|
||||
PebbleEvent event = fake_event_get_last();
|
||||
cl_assert_equal_i(event.type, PEBBLE_BLE_GATT_CLIENT_EVENT);
|
||||
cl_assert_equal_i(event.bluetooth.le.gatt_client.subtype,
|
||||
PebbleBLEGATTClientEventTypeNotification);
|
||||
PebbleTaskBitset task_mask = ~0;
|
||||
if (kernel) {
|
||||
task_mask &= ~gap_le_pebble_task_bit_for_client(GAPLEClientKernel);
|
||||
}
|
||||
if (app) {
|
||||
task_mask &= ~gap_le_pebble_task_bit_for_client(GAPLEClientApp);
|
||||
}
|
||||
cl_assert_equal_i(event.task_mask, task_mask);
|
||||
|
||||
BLECharacteristic characteristic_out;
|
||||
uint8_t *buffer = (uint8_t *) malloc(assert_value_length);
|
||||
memset(buffer, 0, assert_value_length);
|
||||
if (kernel) {
|
||||
GATTBufferedNotificationHeader header = {};
|
||||
gatt_client_subscriptions_get_notification_header(GAPLEClientKernel, &header);
|
||||
cl_assert_equal_i(header.value_length, assert_value_length);
|
||||
cl_assert_equal_i(header.characteristic, characteristic);
|
||||
|
||||
if (should_consume) {
|
||||
uint16_t value_length = assert_value_length;
|
||||
gatt_client_subscriptions_consume_notification(&characteristic_out, buffer, &value_length,
|
||||
GAPLEClientKernel, NULL);
|
||||
cl_assert_equal_i(memcmp(buffer, value, value_length), 0);
|
||||
cl_assert_equal_i(characteristic_out, characteristic);
|
||||
}
|
||||
}
|
||||
memset(buffer, 0, assert_value_length);
|
||||
if (app) {
|
||||
GATTBufferedNotificationHeader header = {};
|
||||
gatt_client_subscriptions_get_notification_header(GAPLEClientApp, &header);
|
||||
cl_assert_equal_i(header.value_length, assert_value_length);
|
||||
cl_assert_equal_i(header.characteristic, characteristic);
|
||||
|
||||
if (should_consume) {
|
||||
uint16_t value_length = assert_value_length;
|
||||
gatt_client_subscriptions_consume_notification(&characteristic_out, buffer, &value_length,
|
||||
GAPLEClientApp, NULL);
|
||||
cl_assert_equal_i(memcmp(buffer, value, value_length), 0);
|
||||
cl_assert_equal_i(characteristic_out, characteristic);
|
||||
}
|
||||
}
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
static void prv_assert_notification_event(BLECharacteristic characteristic,
|
||||
const uint8_t *value, uint16_t assert_value_length,
|
||||
bool kernel, bool app) {
|
||||
prv_assert_notification_event_ext(characteristic, value, assert_value_length, kernel, app,
|
||||
true /* should_consume */);
|
||||
}
|
||||
|
||||
static void prv_simulate_and_assert_discovery_of_one_service(const BTDeviceInternal *device) {
|
||||
// Simulate discovery of Blood Pressure service:
|
||||
fake_gatt_put_discovery_indication_blood_pressure_service(TEST_GATT_CONNECTION_ID);
|
||||
// Simulate discovery of random 128-bit service:
|
||||
fake_gatt_put_discovery_indication_random_128bit_uuid_service(TEST_GATT_CONNECTION_ID);
|
||||
fake_gatt_put_discovery_complete_event(GATT_SERVICE_DISCOVERY_STATUS_SUCCESS,
|
||||
TEST_GATT_CONNECTION_ID);
|
||||
}
|
||||
|
||||
static BLECharacteristic prv_get_indicatable_characteristic(void) {
|
||||
// The Blood Pressure Service UUID:
|
||||
Uuid service_uuid = bt_uuid_expand_16bit(0x1810);
|
||||
BLEService service;
|
||||
uint8_t num_copied = gatt_client_copy_service_refs_matching_uuid(&s_device, &service, 1,
|
||||
&service_uuid);
|
||||
cl_assert_equal_i(num_copied, 1);
|
||||
|
||||
// UUID for indicatable Pressure Measurement characteristic:
|
||||
Uuid characteristic_uuid = bt_uuid_expand_16bit(0x2a35);
|
||||
BLECharacteristic characteristic;
|
||||
num_copied = gatt_client_service_get_characteristics_matching_uuids(service, &characteristic,
|
||||
&characteristic_uuid, 1);
|
||||
cl_assert_equal_i(num_copied, 1);
|
||||
|
||||
GAPLEConnection *connection;
|
||||
s_handle = gatt_client_characteristic_get_handle_and_connection(characteristic, &connection);
|
||||
|
||||
return characteristic;
|
||||
}
|
||||
|
||||
static void prv_confirm_cccd_write(BLEGATTError error) {
|
||||
gatt_client_subscriptions_handle_write_cccd_response(s_last_cccd_ref, error);
|
||||
}
|
||||
|
||||
// Tests
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
void test_gatt_client_subscriptions__initialize(void) {
|
||||
s_write_descriptor_cccd_result = BTErrnoOK;
|
||||
|
||||
fake_pbl_malloc_clear_tracking();
|
||||
fake_event_init();
|
||||
gap_le_connection_init();
|
||||
gatt_client_subscription_boot();
|
||||
|
||||
// Prepare connected device with Blood Pressure GATT service discovered:
|
||||
s_device = prv_connected_dummy_device(1);
|
||||
cl_assert_equal_i(gatt_client_discovery_discover_all(&s_device), BTErrnoOK);
|
||||
prv_simulate_and_assert_discovery_of_one_service(&s_device);
|
||||
|
||||
fake_event_clear_last();
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__cleanup(void) {
|
||||
for (GAPLEClient c = 0; c < GAPLEClientNum; ++c) {
|
||||
gatt_client_subscriptions_cleanup_by_client(c);
|
||||
}
|
||||
gap_le_connection_deinit();
|
||||
gatt_client_subscription_cleanup();
|
||||
|
||||
fake_pbl_malloc_check_net_allocs();
|
||||
fake_pbl_malloc_clear_tracking();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// gatt_client_subscriptions_subscribe
|
||||
|
||||
void test_gatt_client_subscriptions__subscribe_invalid_characteristic(void) {
|
||||
fake_kernel_malloc_mark();
|
||||
BTErrno e = gatt_client_subscriptions_subscribe(BOGUS_CHARACTERISTIC,
|
||||
BLESubscriptionAny, GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoInvalidParameter);
|
||||
fake_kernel_malloc_mark_assert_equal();
|
||||
prv_assert_no_event();
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__subscribe_no_cccd(void) {
|
||||
// The random 128-bit Service UUID:
|
||||
Uuid service_uuid = UuidMake(0xF7, 0x68, 0x09, 0x5B, 0x1B, 0xFA, 0x4F, 0x63,
|
||||
0x97, 0xEE, 0xFD, 0xED, 0xAC, 0x66, 0xF9, 0xB0);
|
||||
BLEService service;
|
||||
uint8_t num_copied = gatt_client_copy_service_refs_matching_uuid(&s_device, &service, 1,
|
||||
&service_uuid);
|
||||
cl_assert_equal_i(num_copied, 1);
|
||||
|
||||
// UUID for Characteristic that has no CCCD:
|
||||
Uuid characteristic_uuid = UuidMake(0xF7, 0x68, 0x09, 0x5B, 0x1B, 0xFA, 0x4F, 0x63,
|
||||
0x97, 0xEE, 0xFD, 0xED, 0xAC, 0x66, 0xF9, 0xB1);
|
||||
BLECharacteristic characteristic;
|
||||
num_copied = gatt_client_service_get_characteristics_matching_uuids(service, &characteristic,
|
||||
&characteristic_uuid, 1);
|
||||
cl_assert_equal_i(num_copied, 1);
|
||||
|
||||
fake_kernel_malloc_mark();
|
||||
// Try to subscribe to the non-subscribe-able characteristic:
|
||||
BTErrno e = gatt_client_subscriptions_subscribe(characteristic,
|
||||
BLESubscriptionAny, GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoInvalidParameter);
|
||||
|
||||
fake_kernel_malloc_mark_assert_equal();
|
||||
prv_assert_no_event();
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__subscribe_unsupported_subscription_type(void) {
|
||||
BTErrno e;
|
||||
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
||||
|
||||
fake_kernel_malloc_mark();
|
||||
// Try to subscribe for notifications to the indicatable (but not notify-able) characteristic:
|
||||
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionNotifications,
|
||||
GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoInvalidParameter);
|
||||
fake_kernel_malloc_mark_assert_equal();
|
||||
prv_assert_no_event();
|
||||
|
||||
// Try to subscribe for indications to the indicatable characteristic:
|
||||
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__subscribe_already_subscribed(void) {
|
||||
BTErrno e;
|
||||
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
||||
|
||||
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
fake_event_clear_last();
|
||||
fake_kernel_malloc_mark();
|
||||
// Subscribe again:
|
||||
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoInvalidState);
|
||||
fake_kernel_malloc_mark_assert_equal();
|
||||
prv_assert_no_event();
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__unsubscribe_pending_subscription(void) {
|
||||
BTErrno e;
|
||||
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
||||
|
||||
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
fake_event_clear_last();
|
||||
fake_kernel_malloc_mark();
|
||||
// Un-subscribe, while subscribing process is still pending:
|
||||
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionNone, GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoInvalidState);
|
||||
fake_kernel_malloc_mark_assert_equal();
|
||||
prv_assert_no_event();
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__subscribe_oom_for_subscription_allocation(void) {
|
||||
fake_kernel_malloc_mark();
|
||||
fake_malloc_set_largest_free_block(sizeof(GATTClientSubscriptionNode) - 1);
|
||||
BTErrno e = gatt_client_subscriptions_subscribe(prv_get_indicatable_characteristic(),
|
||||
BLESubscriptionIndications,
|
||||
GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoNotEnoughResources);
|
||||
fake_kernel_malloc_mark_assert_equal();
|
||||
prv_assert_no_event();
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__subscribe_oom_for_buffer_allocation(void) {
|
||||
fake_kernel_malloc_mark();
|
||||
fake_malloc_set_largest_free_block(sizeof(GATTClientSubscriptionNode));
|
||||
BTErrno e = gatt_client_subscriptions_subscribe(prv_get_indicatable_characteristic(),
|
||||
BLESubscriptionIndications,
|
||||
GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoNotEnoughResources);
|
||||
fake_kernel_malloc_mark_assert_equal();
|
||||
prv_assert_no_event();
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__subscribe_cccd_write_error(void) {
|
||||
s_write_descriptor_cccd_result = -1;
|
||||
|
||||
fake_kernel_malloc_mark();
|
||||
// Try to subscribe for indications to the indicatable characteristic:
|
||||
BTErrno e = gatt_client_subscriptions_subscribe(prv_get_indicatable_characteristic(),
|
||||
BLESubscriptionIndications,
|
||||
GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, -1);
|
||||
fake_kernel_malloc_mark_assert_equal();
|
||||
prv_assert_no_event();
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__unsubscribe_no_clients_subscribed(void) {
|
||||
fake_kernel_malloc_mark();
|
||||
BTErrno e;
|
||||
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
||||
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionNone, GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoInvalidState);
|
||||
fake_kernel_malloc_mark_assert_equal();
|
||||
prv_assert_no_event();
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__unsubscribe_not_subscribed_but_other_client_is(void) {
|
||||
BTErrno e;
|
||||
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
||||
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications, GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
fake_kernel_malloc_mark();
|
||||
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionNone, GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoInvalidState);
|
||||
fake_kernel_malloc_mark_assert_equal();
|
||||
prv_assert_no_event();
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__subscribe_first_subscriber(void) {
|
||||
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
||||
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
cl_assert_equal_i(s_last_cccd_value, BLESubscriptionIndications);
|
||||
|
||||
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
||||
prv_assert_subscription_event(characteristic, BLESubscriptionIndications, BLEGATTErrorSuccess,
|
||||
true /* kernel */, false /* app */);
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__subscribe_not_first_subscriber(void) {
|
||||
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
||||
// Subscribe kernel:
|
||||
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
cl_assert_equal_i(s_last_cccd_value, BLESubscriptionIndications);
|
||||
|
||||
// Simulate getting confirmation from remote:
|
||||
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
||||
fake_event_clear_last();
|
||||
|
||||
// Subscribe app:
|
||||
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientApp);
|
||||
// App should get event immediately:
|
||||
prv_assert_subscription_event(characteristic, BLESubscriptionIndications, BLEGATTErrorSuccess,
|
||||
false /* kernel */, true /* app */);
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__two_subscribers_before_cccd_write_response(void) {
|
||||
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
||||
// Subscribe kernel:
|
||||
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
cl_assert_equal_i(s_last_cccd_value, BLESubscriptionIndications);
|
||||
|
||||
s_last_cccd_value = ~0;
|
||||
|
||||
// Subscribe app:
|
||||
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
// Should not be written again, because BLESubscriptionIndications was already written:
|
||||
cl_assert_equal_i(s_last_cccd_value, (uint16_t) ~0);
|
||||
|
||||
fake_event_clear_last();
|
||||
|
||||
// Simulate getting confirmation from remote:
|
||||
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
||||
|
||||
// Expect one event for both Kernel + App:
|
||||
prv_assert_subscription_event(characteristic, BLESubscriptionIndications, BLEGATTErrorSuccess,
|
||||
true /* kernel */, true /* app */);
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__unsubscribe_last_subscriber(void) {
|
||||
BTErrno e;
|
||||
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
||||
|
||||
fake_kernel_malloc_mark();
|
||||
|
||||
// Subscribe kernel:
|
||||
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
// Simulate getting confirmation from remote:
|
||||
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
||||
fake_event_clear_last();
|
||||
|
||||
// Unsubscribe kernel:
|
||||
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionNone, GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
// Kernel should get event immediately:
|
||||
prv_assert_subscription_event(characteristic, BLESubscriptionNone, BLEGATTErrorSuccess,
|
||||
true /* kernel */, false /* app */);
|
||||
|
||||
// CCCD value should be 0 now:
|
||||
cl_assert_equal_i(s_last_cccd_value, 0);
|
||||
|
||||
// Verify everything is cleaned up:
|
||||
fake_kernel_malloc_mark_assert_equal();
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__unsubscribe_not_last_subscriber(void) {
|
||||
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
||||
// Subscribe kernel:
|
||||
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
cl_assert_equal_i(s_last_cccd_value, BLESubscriptionIndications);
|
||||
|
||||
// Simulate getting confirmation from remote:
|
||||
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
||||
fake_event_clear_last();
|
||||
|
||||
// Subscribe app:
|
||||
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientApp);
|
||||
|
||||
s_last_cccd_value = ~0;
|
||||
|
||||
// Unsubscribe kernel:
|
||||
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionNone, GAPLEClientKernel);
|
||||
// Kernel should get event immediately:
|
||||
prv_assert_subscription_event(characteristic, BLESubscriptionNone, BLEGATTErrorSuccess,
|
||||
true /* kernel */, false /* app */);
|
||||
|
||||
// CCCD value should not be written, because app is still subscribed:
|
||||
cl_assert_equal_i(s_last_cccd_value, (uint16_t) ~0);
|
||||
}
|
||||
|
||||
|
||||
void test_gatt_client_subscriptions__subscribe_failed_cccd_write(void) {
|
||||
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
||||
|
||||
fake_kernel_malloc_mark();
|
||||
|
||||
// Subscribe kernel:
|
||||
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
cl_assert_equal_i(s_last_cccd_value, BLESubscriptionIndications);
|
||||
|
||||
// Simulate getting confirmation from remote:
|
||||
prv_confirm_cccd_write(BLEGATTErrorInvalidHandle);
|
||||
|
||||
// Expect kernel event with 'BLESubscriptionNone' type and error bubbled up:
|
||||
prv_assert_subscription_event(characteristic, BLESubscriptionNone, BLEGATTErrorInvalidHandle,
|
||||
true /* kernel */, false /* app */);
|
||||
|
||||
// Verify everything is cleaned up:
|
||||
fake_kernel_malloc_mark_assert_equal();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// gatt_client_subscriptions_handle_server_notification &
|
||||
// gatt_client_subscriptions_consume_notification
|
||||
|
||||
void test_gatt_client_subscriptions__notification_but_no_subscribers(void) {
|
||||
const uint8_t value = 0xAA;
|
||||
gatt_client_subscriptions_handle_server_notification(s_connection, s_handle,
|
||||
&value, sizeof(value));
|
||||
prv_assert_no_event();
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__cccd_write_confirmation_but_no_subscription(void) {
|
||||
s_last_cccd_ref = 1;
|
||||
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
||||
|
||||
// This used to cause a crash:
|
||||
// https://pebbletechnology.atlassian.net/browse/PBL-23455
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__notification_single_subscriber(void) {
|
||||
// Subscribe app:
|
||||
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
||||
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
// Simulate getting confirmation from remote:
|
||||
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
||||
fake_event_clear_last();
|
||||
|
||||
// Nothing to be read before getting the notification:
|
||||
bool has_notification = gatt_client_subscriptions_get_notification_header(GAPLEClientApp,
|
||||
NULL);
|
||||
cl_assert_equal_b(has_notification, false);
|
||||
|
||||
const uint8_t value[] = {0xAA, 0xBB, 0xCC};
|
||||
gatt_client_subscriptions_handle_server_notification(s_connection, s_handle,
|
||||
value, sizeof(value));
|
||||
|
||||
prv_assert_notification_event(characteristic, value, sizeof(value),
|
||||
false /* kernel */, true /* app */);
|
||||
|
||||
// Nothing to be read after "consuming" it:
|
||||
has_notification = gatt_client_subscriptions_get_notification_header(GAPLEClientApp, NULL);
|
||||
cl_assert_equal_b(has_notification, false);
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__zero_length_notification(void) {
|
||||
// Subscribe app:
|
||||
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
||||
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
// Simulate getting confirmation from remote:
|
||||
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
||||
fake_event_clear_last();
|
||||
|
||||
gatt_client_subscriptions_handle_server_notification(s_connection, s_handle, NULL, 0);
|
||||
|
||||
prv_assert_notification_event(characteristic, NULL, 0, false /* kernel */, true /* app */);
|
||||
|
||||
// Nothing to be read after "consuming" it:
|
||||
bool has_notification = gatt_client_subscriptions_get_notification_header(GAPLEClientApp, NULL);
|
||||
cl_assert_equal_b(has_notification, false);
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__notification_app_and_kernel_subscribers(void) {
|
||||
// Subscribe app:
|
||||
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
||||
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
// Subscribe kernel:
|
||||
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
// Simulate getting confirmation from remote:
|
||||
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
||||
fake_event_clear_last();
|
||||
fake_event_reset_count();
|
||||
|
||||
const uint8_t value[] = {0xAA, 0xBB, 0xCC};
|
||||
gatt_client_subscriptions_handle_server_notification(s_connection, s_handle,
|
||||
value, sizeof(value));
|
||||
cl_assert_equal_i(fake_event_get_count(), 1);
|
||||
|
||||
// Send another notification, before reading out the previous one:
|
||||
gatt_client_subscriptions_handle_server_notification(s_connection, s_handle,
|
||||
value, sizeof(value));
|
||||
// Only one event should have been put on the queue:
|
||||
cl_assert_equal_i(fake_event_get_count(), 1);
|
||||
|
||||
// Assert 2 events can be read out:
|
||||
prv_assert_notification_event(characteristic, value, sizeof(value),
|
||||
true /* kernel */, true /* app */);
|
||||
prv_assert_notification_event(characteristic, value, sizeof(value),
|
||||
true /* kernel */, true /* app */);
|
||||
|
||||
// Send the 3rd notification:
|
||||
gatt_client_subscriptions_handle_server_notification(s_connection, s_handle,
|
||||
value, sizeof(value));
|
||||
cl_assert_equal_i(fake_event_get_count(), 2);
|
||||
prv_assert_notification_event(characteristic, value, sizeof(value),
|
||||
true /* kernel */, true /* app */);
|
||||
}
|
||||
|
||||
static TickType_t prv_taking_too_long_to_consume_yield_cb(QueueHandle_t queue) {
|
||||
return milliseconds_to_ticks(1000);
|
||||
}
|
||||
|
||||
static TickType_t prv_consume_in_time_yield_cb(QueueHandle_t queue) {
|
||||
// Consume while BT task is waiting for buffer to be freed up:
|
||||
BLECharacteristic characteristic_out;
|
||||
uint8_t *value_out = (uint8_t *) malloc(GATT_CLIENT_SUBSCRIPTIONS_BUFFER_SIZE);
|
||||
uint16_t value_length = GATT_CLIENT_SUBSCRIPTIONS_BUFFER_SIZE;
|
||||
gatt_client_subscriptions_consume_notification(&characteristic_out, value_out, &value_length,
|
||||
GAPLEClientApp, NULL);
|
||||
free(value_out);
|
||||
return milliseconds_to_ticks(5);
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__notification_buffer_full(void) {
|
||||
// Subscribe app:
|
||||
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
||||
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
// Simulate getting confirmation from remote:
|
||||
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
||||
fake_event_clear_last();
|
||||
|
||||
// We had a bug at some point where the header size was not taken into account correctly, take
|
||||
// a value that won't fit by 1 byte, so this bug does not happen again:
|
||||
const size_t too_big = GATT_CLIENT_SUBSCRIPTIONS_BUFFER_SIZE -
|
||||
sizeof(GATTBufferedNotificationHeader) + 1;
|
||||
uint8_t *value = (uint8_t *) malloc(too_big);
|
||||
memset(value, 0x55, too_big);
|
||||
gatt_client_subscriptions_handle_server_notification(s_connection, s_handle,
|
||||
value, too_big);
|
||||
prv_assert_no_event();
|
||||
|
||||
// Receive a GATT notification that is supposed to fill up the buffer entirely:
|
||||
const size_t fill_entirely_size = (GATT_CLIENT_SUBSCRIPTIONS_BUFFER_SIZE -
|
||||
sizeof(GATTBufferedNotificationHeader));
|
||||
gatt_client_subscriptions_handle_server_notification(s_connection, s_handle,
|
||||
value, fill_entirely_size);
|
||||
prv_assert_notification_event_ext(characteristic, value, fill_entirely_size,
|
||||
false /* kernel */, true /* app */, false /* should_consume */);
|
||||
|
||||
// Receive another GATT notification. Won't fit until consumed. Consuming is taking to long:
|
||||
fake_queue_set_yield_callback(gatt_client_subscription_get_semaphore(),
|
||||
prv_taking_too_long_to_consume_yield_cb);
|
||||
fake_event_clear_last();
|
||||
gatt_client_subscriptions_handle_server_notification(s_connection, s_handle,
|
||||
value, 1 /* one byte */);
|
||||
// Data will be dropped, no event :(
|
||||
prv_assert_no_event();
|
||||
|
||||
// Receive another GATT notification. Won't fit until consumed.
|
||||
// Consuming is happening before the timeout hits (in the yield callback):
|
||||
fake_queue_set_yield_callback(gatt_client_subscription_get_semaphore(),
|
||||
prv_consume_in_time_yield_cb);
|
||||
gatt_client_subscriptions_handle_server_notification(s_connection, s_handle,
|
||||
value, 1 /* one byte */);
|
||||
prv_assert_notification_event(characteristic, value, 1 /* one byte */,
|
||||
false /* kernel */, true /* app */);
|
||||
|
||||
free(value);
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__consume_but_too_small_buffer(void) {
|
||||
// Subscribe app:
|
||||
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
||||
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
// Simulate getting confirmation from remote:
|
||||
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
||||
fake_event_clear_last();
|
||||
|
||||
const uint8_t value[] = {0xAA, 0xBB, 0xCC};
|
||||
gatt_client_subscriptions_handle_server_notification(s_connection, s_handle,
|
||||
value, sizeof(value));
|
||||
|
||||
BLECharacteristic handle_out = ~0;
|
||||
uint8_t out[1];
|
||||
uint16_t value_length = sizeof(out);
|
||||
bool has_more = true;
|
||||
const uint16_t next_length =
|
||||
gatt_client_subscriptions_consume_notification(&handle_out, out, &value_length,
|
||||
GAPLEClientApp, &has_more);
|
||||
cl_assert_equal_i(handle_out, BLE_CHARACTERISTIC_INVALID);
|
||||
cl_assert_equal_i(value_length, 0);
|
||||
// Notification will be eaten, regardless of whether it was copied:
|
||||
cl_assert_equal_b(has_more, false);
|
||||
cl_assert_equal_i(next_length, 0);
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__consume_but_nothing_in_buffer(void) {
|
||||
// Subscribe app:
|
||||
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
||||
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
// Simulate getting confirmation from remote:
|
||||
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
||||
fake_event_clear_last();
|
||||
|
||||
BLECharacteristic handle_out = ~0;
|
||||
uint8_t out[1];
|
||||
uint16_t value_length = sizeof(out);
|
||||
bool has_more = true;
|
||||
const uint16_t next_length =
|
||||
gatt_client_subscriptions_consume_notification(&handle_out, out, &value_length,
|
||||
GAPLEClientApp, &has_more);
|
||||
cl_assert_equal_i(handle_out, BLE_CHARACTERISTIC_INVALID);
|
||||
cl_assert_equal_i(value_length, 0);
|
||||
cl_assert_equal_b(has_more, false);
|
||||
cl_assert_equal_i(next_length, 0);
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__consume_but_buffer_client_buffer_null(void) {
|
||||
bool has_more = true;
|
||||
BLECharacteristic handle_out = ~0;
|
||||
uint8_t out[1];
|
||||
uint16_t value_length = sizeof(out);
|
||||
|
||||
const uint16_t next_length =
|
||||
gatt_client_subscriptions_consume_notification(&handle_out, out, &value_length,
|
||||
GAPLEClientApp, &has_more);
|
||||
|
||||
cl_assert_equal_i(next_length, 0);
|
||||
cl_assert_equal_b(has_more, false);
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__notification_consume_without_notification(void) {
|
||||
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
||||
|
||||
uint16_t value_length;
|
||||
GATTBufferedNotificationHeader header = {
|
||||
.characteristic = BLE_CHARACTERISTIC_INVALID,
|
||||
.value_length = 0,
|
||||
};
|
||||
bool has_more = gatt_client_subscriptions_get_notification_header(GAPLEClientKernel,
|
||||
&header);
|
||||
cl_assert_equal_i(header.value_length, 0);
|
||||
cl_assert_equal_i(header.characteristic, BLE_CHARACTERISTIC_INVALID);
|
||||
cl_assert_equal_b(has_more, false);
|
||||
|
||||
BLECharacteristic characteristic_out = ~0;
|
||||
uint8_t value = 0xff;
|
||||
value_length = sizeof(value);
|
||||
gatt_client_subscriptions_consume_notification(&characteristic_out, &value, &value_length,
|
||||
GAPLEClientKernel, &has_more);
|
||||
// Expect untouched:
|
||||
cl_assert_equal_i(characteristic_out, ~0);
|
||||
cl_assert_equal_i(value, 0xff);
|
||||
cl_assert_equal_b(has_more, false);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// gatt_client_subscriptions_cleanup_by_client
|
||||
|
||||
void test_gatt_client_subscriptions__cleanup_by_client(void) {
|
||||
fake_kernel_malloc_mark();
|
||||
|
||||
// Subscribe app:
|
||||
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
||||
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
gatt_client_subscriptions_cleanup_by_client(GAPLEClientApp);
|
||||
prv_assert_no_event();
|
||||
|
||||
fake_kernel_malloc_mark_assert_equal();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// gatt_client_subscriptions_cleanup_by_connection
|
||||
|
||||
bool gatt_client_get_event_pending_state(GAPLEClient);
|
||||
|
||||
static void prv_pend_events_to_kernel_and_app(void) {
|
||||
// fake pend an event to the kernel
|
||||
gatt_client_subscriptions_reschedule(GAPLEClientKernel);
|
||||
cl_assert_equal_b(gatt_client_get_event_pending_state(GAPLEClientKernel), true);
|
||||
fake_event_clear_last();
|
||||
|
||||
// fake pend an event to the app
|
||||
gatt_client_subscriptions_reschedule(GAPLEClientApp);
|
||||
cl_assert_equal_b(gatt_client_get_event_pending_state(GAPLEClientApp), true);
|
||||
fake_event_clear_last();
|
||||
}
|
||||
|
||||
static void prv_assert_no_pending_events_to_kernel_and_app(void) {
|
||||
cl_assert_equal_b(gatt_client_get_event_pending_state(GAPLEClientKernel), false);
|
||||
cl_assert_equal_b(gatt_client_get_event_pending_state(GAPLEClientApp), false);
|
||||
}
|
||||
|
||||
void test_gatt_client_subscriptions__cleanup_by_connection(void) {
|
||||
fake_kernel_malloc_mark();
|
||||
|
||||
// Subscribe app and kernel:
|
||||
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
||||
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
prv_pend_events_to_kernel_and_app();
|
||||
|
||||
gatt_client_subscriptions_cleanup_by_connection(s_connection, false /* should_unsubscribe */);
|
||||
prv_assert_no_event();
|
||||
|
||||
// there should be no more subscriptions
|
||||
cl_assert(s_connection->gatt_subscriptions == NULL);
|
||||
prv_assert_no_pending_events_to_kernel_and_app();
|
||||
|
||||
fake_kernel_malloc_mark_assert_equal();
|
||||
}
|
||||
|
||||
extern void gatt_client_subscription_cleanup_by_att_handle_range(
|
||||
struct GAPLEConnection *connection, ATTHandleRange *range);
|
||||
|
||||
void test_gatt_client_subscriptions__cleanup_by_att_handle_range(void) {
|
||||
fake_kernel_malloc_mark();
|
||||
|
||||
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
||||
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientApp);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
||||
GAPLEClientKernel);
|
||||
cl_assert_equal_i(e, BTErrnoOK);
|
||||
|
||||
cl_assert(s_connection->gatt_subscriptions != NULL);
|
||||
|
||||
ATTHandleRange range;
|
||||
fake_gatt_get_bp_att_handle_range(&range.start, &range.end);
|
||||
|
||||
ATTHandleRange bogus_range = {
|
||||
.start = range.end + 1,
|
||||
.end = range.end + 5
|
||||
};
|
||||
|
||||
prv_pend_events_to_kernel_and_app();
|
||||
|
||||
// should have no effect since service is not in this range
|
||||
gatt_client_subscription_cleanup_by_att_handle_range(s_connection, &bogus_range);
|
||||
prv_assert_no_event();
|
||||
cl_assert(s_connection->gatt_subscriptions != NULL);
|
||||
|
||||
// should actually remove everything
|
||||
gatt_client_subscription_cleanup_by_att_handle_range(s_connection, &range);
|
||||
prv_assert_no_event();
|
||||
|
||||
// there should be no more subscriptions
|
||||
cl_assert(s_connection->gatt_subscriptions == NULL);
|
||||
prv_assert_no_pending_events_to_kernel_and_app();
|
||||
|
||||
fake_kernel_malloc_mark_assert_equal();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// TODO: Write tests that exercise applib/bluetooth/ble_client.c
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* 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/gatt_service_changed.h"
|
||||
#include "comm/ble/gap_le_connection.h"
|
||||
|
||||
#include "kernel/events.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
#include <btutil/bt_device.h>
|
||||
|
||||
// Fakes
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#include "fake_GAPAPI.h"
|
||||
#include "fake_GATTAPI.h"
|
||||
#include "fake_GATTAPI_test_vectors.h"
|
||||
#include "fake_pbl_malloc.h"
|
||||
#include "fake_new_timer.h"
|
||||
#include "fake_rtc.h"
|
||||
#include "fake_system_task.h"
|
||||
|
||||
// Stubs
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#include "stubs_bluetopia_interface.h"
|
||||
#include "stubs_bt_driver_gatt.h"
|
||||
#include "stubs_bt_lock.h"
|
||||
#include "stubs_events.h"
|
||||
#include "stubs_gatt_client_subscriptions.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_mutex.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_prompt.h"
|
||||
#include "stubs_rand_ptr.h"
|
||||
#include "stubs_regular_timer.h"
|
||||
|
||||
extern bool prv_contains_service_changed_characteristic(
|
||||
GAPLEConnection *connection,
|
||||
const GATT_Service_Discovery_Indication_Data_t *event);
|
||||
|
||||
void core_dump_reset(bool is_forced) {
|
||||
}
|
||||
|
||||
static GAPLEConnection s_connection;
|
||||
|
||||
GAPLEConnection *gap_le_connection_by_device(const BTDeviceInternal *addr) {
|
||||
return &s_connection;
|
||||
}
|
||||
|
||||
GAPLEConnection *gap_le_connection_by_addr(const BTDeviceAddress *addr) {
|
||||
return &s_connection;
|
||||
}
|
||||
|
||||
GAPLEConnection *gap_le_connection_by_gatt_id(unsigned int connection_id) {
|
||||
return &s_connection;
|
||||
}
|
||||
|
||||
bool gap_le_connection_is_valid(const GAPLEConnection *conn) {
|
||||
return true;
|
||||
}
|
||||
|
||||
GAPLEConnection *gap_le_connection_any(void) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint16_t gaps_get_starting_att_handle(void) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
GAPLEConnection *gatt_client_characteristic_get_connection(BLECharacteristic characteristic_ref) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
BLEService gatt_client_att_handle_get_service(
|
||||
GAPLEConnection *connection, uint16_t att_handle, const GATTServiceNode **service_node_out) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t gatt_client_copy_service_refs_by_discovery_generation(
|
||||
const BTDeviceInternal *device, BLEService services_out[],
|
||||
uint8_t num_services, uint8_t discovery_gen) { return 0;}
|
||||
|
||||
void gatt_client_service_get_all_characteristics_and_descriptors(
|
||||
GAPLEConnection *connection, GATTService *service,
|
||||
BLECharacteristic *characteristic_hdls_out,
|
||||
BLEDescriptor *descriptor_hdls_out) { }
|
||||
|
||||
void launcher_task_add_callback(void (*callback)(void *data), void *data) {
|
||||
callback(data);
|
||||
}
|
||||
|
||||
// FIXME: PBL-23945
|
||||
void fake_kernel_malloc_mark(void) { }
|
||||
void fake_kernel_malloc_mark_assert_equal(void) { }
|
||||
|
||||
// Helpers
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#define TEST_GATT_CONNECTION_ID (1234)
|
||||
#define TEST_BT_STACK_ID (1)
|
||||
|
||||
typedef enum {
|
||||
Unknown,
|
||||
Handled,
|
||||
Unhandled,
|
||||
} HandleResult;
|
||||
|
||||
static HandleResult s_last_handle_discovery_result;
|
||||
|
||||
static void prv_bluetopia_service_discovery_cb(unsigned int stack_id,
|
||||
GATT_Service_Discovery_Event_Data_t *event,
|
||||
unsigned long callback_param) {
|
||||
cl_assert_equal_i(stack_id, TEST_BT_STACK_ID);
|
||||
if (event->Event_Data_Type == etGATT_Service_Discovery_Indication) {
|
||||
const GATT_Service_Discovery_Indication_Data_t *indication =
|
||||
event->Event_Data.GATT_Service_Discovery_Indication_Data;
|
||||
cl_assert_equal_i(s_connection.gatt_connection_id, TEST_GATT_CONNECTION_ID);
|
||||
cl_assert_equal_i(indication->ConnectionID, TEST_GATT_CONNECTION_ID);
|
||||
|
||||
s_last_handle_discovery_result =
|
||||
prv_contains_service_changed_characteristic(&s_connection, indication) ? Handled : Unhandled;
|
||||
}
|
||||
}
|
||||
|
||||
// Tests
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
void test_gatt_service_changed_client__initialize(void) {
|
||||
s_last_handle_discovery_result = Unknown;
|
||||
fake_gatt_init();
|
||||
s_connection = (GAPLEConnection) {
|
||||
.gatt_connection_id = TEST_GATT_CONNECTION_ID,
|
||||
.gatt_service_changed_att_handle = 0,
|
||||
};
|
||||
GATT_Start_Service_Discovery_Handle_Range(TEST_BT_STACK_ID, TEST_GATT_CONNECTION_ID, NULL, 0, NULL,
|
||||
prv_bluetopia_service_discovery_cb, 0);
|
||||
}
|
||||
|
||||
void test_gatt_service_changed_client__cleanup(void) {
|
||||
}
|
||||
|
||||
// Discovery
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
void test_gatt_service_changed_client__handle_non_gatt_profile_service(void) {
|
||||
fake_gatt_put_discovery_indication_blood_pressure_service(TEST_GATT_CONNECTION_ID);
|
||||
cl_assert_equal_i(s_last_handle_discovery_result, Unhandled);
|
||||
}
|
||||
|
||||
void test_gatt_service_changed_client__handle_gatt_profile_service(void) {
|
||||
fake_gatt_put_discovery_indication_gatt_profile_service(TEST_GATT_CONNECTION_ID,
|
||||
true /* has_service_changed_characteristic */);
|
||||
cl_assert_equal_i(s_last_handle_discovery_result, Handled);
|
||||
|
||||
// Verify the CCCD of the Service Changed characteristic has been written:
|
||||
cl_assert_equal_i(fake_gatt_write_last_written_handle(),
|
||||
fake_gatt_gatt_profile_service_service_changed_cccd_att_handle());
|
||||
|
||||
// Simulate getting a Write Response confirmation for the written CCCD:
|
||||
fake_gatt_put_write_response_for_last_write();
|
||||
|
||||
// Today we don't really do anything upon getting the confirmation
|
||||
}
|
||||
|
||||
void test_gatt_service_changed_client__handle_gatt_profile_service_missing_service_changed(void) {
|
||||
fake_gatt_put_discovery_indication_gatt_profile_service(TEST_GATT_CONNECTION_ID,
|
||||
false /* has_service_changed_characteristic */);
|
||||
cl_assert_equal_i(s_last_handle_discovery_result, Handled);
|
||||
}
|
||||
|
||||
// Characteristic Value Indications
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
void test_gatt_service_changed_client__handle_indication_non_service_changed(void) {
|
||||
fake_gatt_put_discovery_indication_gatt_profile_service(TEST_GATT_CONNECTION_ID,
|
||||
true /* has_service_changed_characteristic */);
|
||||
const uint8_t value;
|
||||
const bool handled = gatt_service_changed_client_handle_indication(&s_connection, 0xfffe,
|
||||
&value, sizeof(value));
|
||||
cl_assert_equal_b(handled, false);
|
||||
}
|
||||
|
||||
void test_gatt_service_changed_client__handle_indication_service_changed(void) {
|
||||
fake_gatt_put_discovery_indication_gatt_profile_service(TEST_GATT_CONNECTION_ID,
|
||||
true /* has_service_changed_characteristic */);
|
||||
const uint16_t att_handle = fake_gatt_gatt_profile_service_service_changed_att_handle();
|
||||
|
||||
fake_kernel_malloc_mark();
|
||||
|
||||
const int start_count_before_indication = fake_gatt_is_service_discovery_start_count();
|
||||
|
||||
const uint16_t handle_range[2] = {
|
||||
[0] = 0x1,
|
||||
[1] = 0xfffe,
|
||||
};
|
||||
const bool handled = gatt_service_changed_client_handle_indication(&s_connection, att_handle,
|
||||
(const uint8_t *) handle_range, sizeof(uint16_t) * 2);
|
||||
// Re-discovery is trigger on KernelBG:
|
||||
fake_system_task_callbacks_invoke_pending();
|
||||
|
||||
// The KernelBG trip uses kernel_malloc, make sure it's cleaning up properly:
|
||||
fake_kernel_malloc_mark_assert_equal();
|
||||
cl_assert_equal_b(handled, true);
|
||||
|
||||
// Expect service discovery to be started once more:
|
||||
cl_assert_equal_i(start_count_before_indication + 1,
|
||||
fake_gatt_is_service_discovery_start_count());
|
||||
}
|
||||
|
||||
void test_gatt_service_changed_client__handle_indication_service_changed_malformatted(void) {
|
||||
fake_gatt_put_discovery_indication_gatt_profile_service(TEST_GATT_CONNECTION_ID,
|
||||
true /* has_service_changed_characteristic */);
|
||||
const uint16_t att_handle = fake_gatt_gatt_profile_service_service_changed_att_handle();
|
||||
|
||||
const uint16_t handle_range[1] = {
|
||||
[0] = 0x1,
|
||||
};
|
||||
const bool handled = gatt_service_changed_client_handle_indication(&s_connection, att_handle,
|
||||
(const uint8_t *) handle_range, sizeof(uint16_t) * 1);
|
||||
cl_assert_equal_b(handled, true);
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* 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/gatt_service_changed.h"
|
||||
#include "comm/ble/gap_le_connection.h"
|
||||
#include "bluetopia_interface.h"
|
||||
|
||||
#include "kernel/events.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
#include <btutil/bt_device.h>
|
||||
|
||||
extern void gatt_service_changed_server_init(void);
|
||||
|
||||
// Fakes
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#include "fake_GAPAPI.h"
|
||||
#include "fake_GATTAPI.h"
|
||||
#include "fake_GATTAPI_test_vectors.h"
|
||||
#include "fake_pbl_malloc.h"
|
||||
#include "fake_new_timer.h"
|
||||
#include "fake_rtc.h"
|
||||
#include "fake_system_task.h"
|
||||
|
||||
// Stubs
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#include "stubs_bluetopia_interface.h"
|
||||
#include "stubs_bt_driver_gatt.h"
|
||||
#include "stubs_bt_driver_gatt_client_discovery.h"
|
||||
#include "stubs_bt_lock.h"
|
||||
#include "stubs_events.h"
|
||||
#include "stubs_gatt_client_subscriptions.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_mutex.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_prompt.h"
|
||||
#include "stubs_rand_ptr.h"
|
||||
#include "stubs_regular_timer.h"
|
||||
|
||||
uint16_t gaps_get_starting_att_handle(void) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
BLEService gatt_client_att_handle_get_service(
|
||||
GAPLEConnection *connection, uint16_t att_handle, const GATTServiceNode **service_node_out) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t gatt_client_copy_service_refs_by_discovery_generation(
|
||||
const BTDeviceInternal *device, BLEService services_out[],
|
||||
uint8_t num_services, uint8_t discovery_gen) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void gatt_client_service_get_all_characteristics_and_descriptors(
|
||||
GAPLEConnection *connection, GATTService *service,
|
||||
BLECharacteristic *characteristic_hdls_out,
|
||||
BLEDescriptor *descriptor_hdls_out) {
|
||||
}
|
||||
|
||||
void launcher_task_add_callback(void (*callback)(void *data), void *data) {
|
||||
callback(data);
|
||||
}
|
||||
|
||||
// Helpers
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
static const BTDeviceInternal s_device = {
|
||||
.address = {
|
||||
.octets = {
|
||||
1, 2, 3, 4, 5, 6,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static uint32_t s_connection_id = 1;
|
||||
|
||||
static GAPLEConnection *s_connection;
|
||||
|
||||
static void prv_cccd_write(bool is_subscribing) {
|
||||
GattServerSubscribeEvent event = {
|
||||
.connection_id = s_connection_id,
|
||||
.dev_address = s_device.address,
|
||||
.is_subscribing = is_subscribing,
|
||||
};
|
||||
bt_driver_cb_gatt_service_changed_server_subscribe(&event);
|
||||
}
|
||||
|
||||
static void prv_process_pending_callbacks(GAPLEConnection *connection) {
|
||||
if (connection && connection->gatt_service_changed_indication_timer) {
|
||||
stub_new_timer_fire(connection->gatt_service_changed_indication_timer);
|
||||
}
|
||||
fake_system_task_callbacks_invoke_pending();
|
||||
}
|
||||
|
||||
#define prv_expect_service_changed_indication_api_call_count(expected_count) \
|
||||
{ \
|
||||
prv_process_pending_callbacks(s_connection); \
|
||||
cl_assert_equal_i(fake_gatt_get_service_changed_indication_count(), expected_count); \
|
||||
}
|
||||
|
||||
// Tests
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
void test_gatt_service_changed_server__initialize(void) {
|
||||
gatt_service_changed_server_init();
|
||||
fake_gatt_init();
|
||||
gap_le_connection_init();
|
||||
gap_le_connection_add(&s_device, NULL, false /* local_is_master */);
|
||||
s_connection = gap_le_connection_by_device(&s_device);
|
||||
cl_assert(s_connection);
|
||||
s_connection->gatt_connection_id = s_connection_id;
|
||||
}
|
||||
|
||||
void test_gatt_service_changed_server__cleanup(void) {
|
||||
if (s_connection) {
|
||||
gap_le_connection_remove(&s_device);
|
||||
s_connection = NULL;
|
||||
}
|
||||
gap_le_connection_deinit();
|
||||
stub_new_timer_cleanup();
|
||||
}
|
||||
|
||||
void test_gatt_service_changed_server__unsubscribe(void) {
|
||||
prv_cccd_write(false /* is_subscribing */);
|
||||
|
||||
prv_expect_service_changed_indication_api_call_count(0);
|
||||
}
|
||||
|
||||
void test_gatt_service_changed_server__subscribe_event_but_no_connection(void) {
|
||||
gap_le_connection_remove(&s_device);
|
||||
s_connection = NULL;
|
||||
|
||||
prv_cccd_write(true /* is_subscribing */);
|
||||
|
||||
prv_expect_service_changed_indication_api_call_count(0);
|
||||
}
|
||||
|
||||
void test_gatt_service_changed_server__subscribe_fw_not_updated(void) {
|
||||
prv_cccd_write(true /* is_subscribing */);
|
||||
prv_expect_service_changed_indication_api_call_count(0);
|
||||
}
|
||||
|
||||
void test_gatt_service_changed_server__resubscribe_indication_already_pending(void) {
|
||||
gatt_service_changed_server_handle_fw_update();
|
||||
|
||||
prv_cccd_write(true /* is_subscribing */);
|
||||
prv_cccd_write(true /* is_subscribing */);
|
||||
|
||||
prv_expect_service_changed_indication_api_call_count(1);
|
||||
}
|
||||
|
||||
void test_gatt_service_changed_server__reconnect_resubscribe_stop_sending_after_n_times(void) {
|
||||
gatt_service_changed_server_handle_fw_update();
|
||||
|
||||
gap_le_connection_remove(&s_device);
|
||||
s_connection = NULL;
|
||||
|
||||
static const int max_times = 5;
|
||||
|
||||
for (int i = 0; i < max_times + 1; ++i) {
|
||||
gap_le_connection_add(&s_device, NULL, false /* local_is_master */);
|
||||
s_connection = gap_le_connection_by_device(&s_device);
|
||||
cl_assert(s_connection);
|
||||
s_connection->gatt_connection_id = s_connection_id;
|
||||
|
||||
prv_cccd_write(true /* is_subscribing */);
|
||||
if (i < max_times) {
|
||||
prv_expect_service_changed_indication_api_call_count(i + 1);
|
||||
} else {
|
||||
prv_expect_service_changed_indication_api_call_count(max_times);
|
||||
}
|
||||
|
||||
gap_le_connection_remove(&s_device);
|
||||
s_connection = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void test_gatt_service_changed_server__disconnect_during_delay(void) {
|
||||
gatt_service_changed_server_handle_fw_update();
|
||||
prv_cccd_write(true /* is_subscribing */);
|
||||
|
||||
// Grab the timer ID:
|
||||
TimerID t = s_connection->gatt_service_changed_indication_timer;
|
||||
s_connection->gatt_service_changed_indication_timer = TIMER_INVALID_ID;
|
||||
|
||||
// Simulate disconnection:
|
||||
gap_le_connection_remove(&s_device);
|
||||
s_connection = NULL;
|
||||
|
||||
// Timer fires:
|
||||
stub_new_timer_fire(t);
|
||||
|
||||
prv_expect_service_changed_indication_api_call_count(0);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* 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 "comm/ble/gap_le_advert.h"
|
||||
#include "comm/ble/gap_le_task.h"
|
||||
#include "comm/ble/gatt_client_subscriptions.h"
|
||||
#include "comm/ble/kernel_le_client/kernel_le_client.h"
|
||||
#include "comm/ble/kernel_le_client/test/test_definition.h"
|
||||
#include "kernel/events.h"
|
||||
#include "util/size.h"
|
||||
|
||||
// Stubs
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "fake_system_task.h"
|
||||
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_pbl_malloc.h"
|
||||
#include "stubs_rand_ptr.h"
|
||||
#include "stubs_rtc.h"
|
||||
|
||||
void ams_create(void) {
|
||||
}
|
||||
|
||||
void ams_destroy(void) {
|
||||
}
|
||||
|
||||
void ancs_create(void) {
|
||||
}
|
||||
|
||||
void ancs_destroy(void) {
|
||||
}
|
||||
|
||||
void app_launch_handle_disconnection(void) {
|
||||
}
|
||||
|
||||
BTBondingID bt_persistent_storage_get_ble_ancs_bonding(void) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool bt_persistent_storage_is_ble_ancs_bonding(BTBondingID bonding) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void gap_le_advert_unschedule_job_types(GAPLEAdvertisingJobTag *tag_types, size_t num_types) {
|
||||
}
|
||||
|
||||
void gap_le_connect_cancel_all(GAPLEClient client) {
|
||||
}
|
||||
|
||||
BTErrno gap_le_connect_cancel_by_bonding(BTBondingID bonding_id, GAPLEClient client) {
|
||||
return BTErrnoOK;
|
||||
}
|
||||
|
||||
BTErrno gap_le_connect_connect_by_bonding(BTBondingID bonding_id, bool auto_reconnect,
|
||||
bool is_pairing_required, GAPLEClient client) {
|
||||
return BTErrnoOK;
|
||||
}
|
||||
|
||||
void gap_le_slave_reconnect_start(void) {
|
||||
}
|
||||
|
||||
void gap_le_slave_reconnect_stop(void) {
|
||||
}
|
||||
|
||||
BTErrno gatt_client_discovery_discover_all(const BTDeviceInternal *device) {
|
||||
return BTErrnoOK;
|
||||
}
|
||||
|
||||
uint16_t gatt_client_subscriptions_consume_notification(BLECharacteristic *characteristic_ref_out,
|
||||
uint8_t *value_out,
|
||||
uint16_t *value_length_in_out,
|
||||
GAPLEClient client, bool *has_more_out) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool gatt_client_subscriptions_get_notification_header(GAPLEClient client,
|
||||
GATTBufferedNotificationHeader *header_out) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void gatt_client_subscriptions_reschedule(GAPLEClient c) {
|
||||
}
|
||||
|
||||
void launcher_task_add_callback(CallbackEventCallback callback, void *data) {
|
||||
// Use fake_system_task as mock:
|
||||
system_task_add_callback(callback, data);
|
||||
}
|
||||
|
||||
void ppogatt_create(void) {
|
||||
}
|
||||
|
||||
void ppogatt_destroy(void) {
|
||||
}
|
||||
|
||||
void ppogatt_handle_buffer_empty(void) {
|
||||
}
|
||||
|
||||
void bt_driver_reconnect_try_now(bool ignore_paused) {
|
||||
}
|
||||
|
||||
void gatt_client_op_cleanup(GAPLEClient client) {
|
||||
}
|
||||
|
||||
void ppogatt_reset_disconnect_counter(void) {
|
||||
}
|
||||
|
||||
// Fakes & Helpers
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static const BTDeviceInternal s_test_device = {
|
||||
.address = (const BTDeviceAddress) {
|
||||
.octets = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
|
||||
},
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
TestServiceInstanceComplete = 1,
|
||||
TestServiceInstanceIncomplete = 2,
|
||||
TestServiceInstanceUnsupported = 3,
|
||||
} TestServiceInstance;
|
||||
|
||||
static BLEService s_service_handles[] = {
|
||||
TestServiceInstanceComplete,
|
||||
TestServiceInstanceIncomplete,
|
||||
TestServiceInstanceUnsupported,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
TestCharacteristicInstanceCompleteOne = 11,
|
||||
TestCharacteristicInstanceCompleteTwo = 12,
|
||||
TestCharacteristicInstanceIncompleteOne = 21,
|
||||
TestCharacteristicInstanceUnsupported = 33,
|
||||
} TestCharacteristicInstance;
|
||||
|
||||
Uuid gatt_client_service_get_uuid(BLEService service_ref) {
|
||||
switch (service_ref) {
|
||||
case TestServiceInstanceComplete:
|
||||
case TestServiceInstanceIncomplete:
|
||||
return s_test_service_uuid;
|
||||
|
||||
case TestServiceInstanceUnsupported:
|
||||
default:
|
||||
return UUID_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t gatt_client_service_get_characteristics_matching_uuids(BLEService service_ref,
|
||||
BLECharacteristic characteristics_out[],
|
||||
const Uuid matching_characteristic_uuids[],
|
||||
uint8_t num_characteristics) {
|
||||
cl_assert_equal_i(num_characteristics, TestCharacteristicCount);
|
||||
switch (service_ref) {
|
||||
case TestServiceInstanceComplete:
|
||||
characteristics_out[0] = TestCharacteristicInstanceCompleteOne;
|
||||
characteristics_out[1] = TestCharacteristicInstanceCompleteTwo;
|
||||
return 2;
|
||||
case TestServiceInstanceIncomplete:
|
||||
characteristics_out[0] = TestCharacteristicInstanceIncompleteOne;
|
||||
return 1;
|
||||
case TestCharacteristicInstanceUnsupported:
|
||||
characteristics_out[0] = TestCharacteristicInstanceUnsupported;
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int s_read_responses_consumed_count;
|
||||
void gatt_client_consume_read_response(uintptr_t object_ref,
|
||||
uint8_t value_out[],
|
||||
uint16_t value_length,
|
||||
GAPLEClient client) {
|
||||
++s_read_responses_consumed_count;
|
||||
}
|
||||
|
||||
static int s_services_discovered_count;
|
||||
void test_client_handle_service_discovered(BLECharacteristic *characteristics) {
|
||||
++s_services_discovered_count;
|
||||
}
|
||||
|
||||
void test_client_invalidate_all_references(void) {
|
||||
|
||||
}
|
||||
|
||||
void test_client_handle_service_removed(BLECharacteristic *characteristics,
|
||||
uint8_t num_characteristics) {
|
||||
|
||||
}
|
||||
|
||||
static bool s_can_handle_characteristic;
|
||||
bool test_client_can_handle_characteristic(BLECharacteristic characteristic) {
|
||||
return s_can_handle_characteristic;
|
||||
}
|
||||
|
||||
void test_client_handle_write_response(BLECharacteristic characteristic, BLEGATTError error) {
|
||||
|
||||
}
|
||||
|
||||
void test_client_handle_subscribe(BLECharacteristic characteristic,
|
||||
BLESubscription subscription_type, BLEGATTError error) {
|
||||
|
||||
}
|
||||
|
||||
void test_client_handle_read_or_notification(BLECharacteristic characteristic, const uint8_t *value,
|
||||
size_t value_length, BLEGATTError error) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Tests
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void test_kernel_le_client__initialize(void) {
|
||||
s_services_discovered_count = 0;
|
||||
s_read_responses_consumed_count = 0;
|
||||
s_can_handle_characteristic = false;
|
||||
kernel_le_client_init();
|
||||
}
|
||||
|
||||
void test_kernel_le_client__cleanup(void) {
|
||||
kernel_le_client_deinit();
|
||||
fake_system_task_callbacks_cleanup();
|
||||
}
|
||||
|
||||
void test_kernel_le_client__read_response_consumed_even_if_client_is_gone(void) {
|
||||
// Simulate the client goes away:
|
||||
s_can_handle_characteristic = false;
|
||||
|
||||
PebbleEvent e = (PebbleEvent) {
|
||||
.type = PEBBLE_BLE_GATT_CLIENT_EVENT,
|
||||
.bluetooth.le.gatt_client = {
|
||||
.object_ref = TestCharacteristicInstanceCompleteOne,
|
||||
.value_length = 1,
|
||||
.gatt_error = BLEGATTErrorSuccess,
|
||||
.subtype = PebbleBLEGATTClientEventTypeCharacteristicRead,
|
||||
},
|
||||
};
|
||||
|
||||
kernel_le_client_handle_event(&e);
|
||||
|
||||
cl_assert_equal_i(s_read_responses_consumed_count, 1);
|
||||
|
||||
// When value_length is zero, the read response should NOT be consumed:
|
||||
e.bluetooth.le.gatt_client.value_length = 0;
|
||||
s_read_responses_consumed_count = 0;
|
||||
kernel_le_client_handle_event(&e);
|
||||
|
||||
cl_assert_equal_i(s_read_responses_consumed_count, 0);
|
||||
}
|
||||
|
||||
void test_kernel_le_client__service_added(void) {
|
||||
uint8_t num_services_added = ARRAY_LENGTH(s_service_handles);
|
||||
PebbleBLEGATTClientServiceEventInfo *info =
|
||||
kernel_malloc(sizeof(PebbleBLEGATTClientServiceEventInfo) +
|
||||
(num_services_added * sizeof(BLEService)));
|
||||
|
||||
*info = (PebbleBLEGATTClientServiceEventInfo) {
|
||||
.status = BTErrnoOK,
|
||||
.type = PebbleServicesAdded,
|
||||
.device = s_test_device,
|
||||
};
|
||||
info->services_added_data.num_services_added = num_services_added;
|
||||
memcpy(info->services_added_data.services, s_service_handles, sizeof(s_service_handles));
|
||||
|
||||
PebbleEvent e = (PebbleEvent) {
|
||||
.type = PEBBLE_BLE_GATT_CLIENT_EVENT,
|
||||
.bluetooth.le.gatt_client_service = {
|
||||
.info = info,
|
||||
.subtype = PebbleBLEGATTClientEventTypeServiceChange,
|
||||
},
|
||||
};
|
||||
|
||||
kernel_le_client_handle_event(&e);
|
||||
|
||||
// Found one complete service instance:
|
||||
cl_assert_equal_i(s_services_discovered_count, 1);
|
||||
|
||||
kernel_free(info);
|
||||
}
|
||||
|
||||
// FIXME: PBL-27751: Improve test coverage of kernel_le_client.c
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,182 @@
|
||||
from waftools.pebble_test import clar
|
||||
import waftools.ragel
|
||||
|
||||
def build(bld):
|
||||
clar(bld,
|
||||
sources_ant_glob = "src/fw/comm/ble/kernel_le_client/ams/ams_util.c "
|
||||
"src/fw/comm/ble/kernel_le_client/ams/ams.c "
|
||||
"src/fw/services/normal/music.c "
|
||||
"tests/fakes/fake_events.c "
|
||||
"tests/fakes/fake_gatt_client_operations.c "
|
||||
"tests/fakes/fake_gatt_client_subscriptions.c "
|
||||
"tests/fakes/fake_rtc.c",
|
||||
test_sources_ant_glob = "test_ams.c")
|
||||
|
||||
clar(bld,
|
||||
sources_ant_glob = "src/fw/comm/ble/kernel_le_client/ams/ams_util.c",
|
||||
test_sources_ant_glob = "test_ams_util.c")
|
||||
|
||||
# TODO: the bt_driver calls here should really be a fake and not rely on
|
||||
# the cc2564 module
|
||||
clar(bld,
|
||||
sources_ant_glob = "src/fw/comm/ble/gap_le_advert.c "
|
||||
"src/fw/comm/ble/gap_le_connection.c "
|
||||
"src/fw/services/common/regular_timer.c "
|
||||
"src/fw/comm/internals/bt_conn_mgr.c "
|
||||
"tests/fakes/fake_GAPAPI.c "
|
||||
"tests/fakes/fake_rtc.c "
|
||||
"tests/fakes/fake_HCIAPI.c "
|
||||
"tests/fakes/fake_gap_le_connect_params.c ",
|
||||
test_sources_ant_glob = "test_gap_le_advert.c",
|
||||
override_includes=['dummy_board'])
|
||||
|
||||
clar(bld,
|
||||
sources_ant_glob = "src/fw/comm/internals/bt_conn_mgr.c "
|
||||
"src/fw/services/common/regular_timer.c "
|
||||
"tests/fakes/fake_gap_le_connect_params.c "
|
||||
"tests/fakes/fake_rtc.c ",
|
||||
test_sources_ant_glob = "test_bt_conn_mgr.c")
|
||||
|
||||
clar(bld,
|
||||
sources_ant_glob = "src/fw/util/rand/rand.c "
|
||||
"src/fw/vendor/tinymt32/tinymt32.c "
|
||||
"src/fw/comm/ble/gap_le_connection.c "
|
||||
"src/fw/comm/ble/gatt_client_accessors.c "
|
||||
"src/fw/comm/ble/gatt_client_discovery.c "
|
||||
"src/fw/comm/ble/gatt_service_changed.c "
|
||||
"src/fw/comm/internals/bt_conn_mgr.c "
|
||||
"tests/fakes/fake_events.c "
|
||||
"tests/fakes/fake_rtc.c "
|
||||
"tests/fakes/fake_GATTAPI.c "
|
||||
"tests/fakes/fake_GATTAPI_test_vectors.c "
|
||||
"tests/fakes/fake_gap_le_connect_params.c ",
|
||||
test_sources_ant_glob = "test_gatt_client_accessors.c")
|
||||
|
||||
clar(bld,
|
||||
sources_ant_glob = "src/fw/util/rand/rand.c "
|
||||
"src/fw/vendor/tinymt32/tinymt32.c "
|
||||
"src/fw/comm/ble/gap_le_connection.c "
|
||||
"src/fw/comm/ble/gatt_client_accessors.c "
|
||||
"src/fw/comm/ble/gatt_client_discovery.c "
|
||||
"src/fw/comm/ble/gatt_service_changed.c "
|
||||
"src/fw/comm/internals/bt_conn_mgr.c "
|
||||
"tests/fakes/fake_gap_le_connect_params.c "
|
||||
"tests/fakes/fake_events.c "
|
||||
"tests/fakes/fake_rtc.c "
|
||||
"tests/fakes/fake_GATTAPI.c "
|
||||
"tests/fakes/fake_GATTAPI_test_vectors.c ",
|
||||
test_sources_ant_glob = "test_gatt_client_discovery.c")
|
||||
|
||||
clar(bld,
|
||||
sources_ant_glob = "src/fw/util/rand/rand.c "
|
||||
"src/fw/vendor/tinymt32/tinymt32.c "
|
||||
"src/fw/comm/ble/gap_le_connection.c "
|
||||
"src/fw/comm/ble/gap_le_task.c "
|
||||
"src/fw/comm/ble/gatt_client_accessors.c "
|
||||
"src/fw/comm/ble/gatt_client_discovery.c "
|
||||
"src/fw/comm/ble/gatt_client_subscriptions.c "
|
||||
"src/fw/comm/ble/gatt_service_changed.c "
|
||||
"src/fw/comm/internals/bt_conn_mgr.c "
|
||||
"tests/fakes/fake_gap_le_connect_params.c "
|
||||
"tests/fakes/fake_events.c "
|
||||
"tests/fakes/fake_GATTAPI.c "
|
||||
"tests/fakes/fake_GATTAPI_test_vectors.c "
|
||||
"tests/fakes/fake_queue.c "
|
||||
"tests/fakes/fake_rtc.c ",
|
||||
test_sources_ant_glob = "test_gatt_client_subscriptions.c")
|
||||
|
||||
clar(bld,
|
||||
sources_ant_glob = "src/fw/util/rand/rand.c "
|
||||
"src/fw/vendor/tinymt32/tinymt32.c "
|
||||
"src/fw/comm/ble/gatt_client_discovery.c "
|
||||
"src/fw/comm/ble/gatt_service_changed.c "
|
||||
"src/fw/comm/internals/bt_conn_mgr.c "
|
||||
"tests/fakes/fake_gap_le_connect_params.c "
|
||||
"tests/fakes/fake_GATTAPI.c "
|
||||
"tests/fakes/fake_GATTAPI_test_vectors.c "
|
||||
"tests/fakes/fake_rtc.c",
|
||||
test_sources_ant_glob = "test_gatt_service_changed_client.c")
|
||||
|
||||
clar(bld,
|
||||
sources_ant_glob = "src/fw/util/rand/rand.c "
|
||||
"src/fw/vendor/tinymt32/tinymt32.c "
|
||||
"src/fw/comm/ble/gap_le_connection.c "
|
||||
"src/fw/comm/ble/gatt_client_discovery.c "
|
||||
"src/fw/comm/ble/gatt_service_changed.c "
|
||||
"src/fw/comm/internals/bt_conn_mgr.c "
|
||||
"tests/fakes/fake_gap_le_connect_params.c "
|
||||
"tests/fakes/fake_GATTAPI.c "
|
||||
"tests/fakes/fake_GATTAPI_test_vectors.c "
|
||||
"tests/fakes/fake_rtc.c",
|
||||
test_sources_ant_glob="test_gatt_service_changed_server.c")
|
||||
|
||||
clar(bld,
|
||||
sources_ant_glob = "src/fw/comm/ble/gap_le_connect.c "
|
||||
"src/fw/comm/ble/gap_le_connection.c "
|
||||
"src/fw/comm/ble/gap_le_task.c "
|
||||
"src/fw/comm/internals/bt_conn_mgr.c "
|
||||
"tests/fakes/fake_events.c "
|
||||
"tests/fakes/fake_GAPAPI.c "
|
||||
"tests/fakes/fake_bluetooth_persistent_storage.c "
|
||||
"tests/fakes/fake_gap_le_connect_params.c "
|
||||
"tests/fakes/fake_HCIAPI.c "
|
||||
"tests/fakes/fake_rtc.c ",
|
||||
test_sources_ant_glob = "test_gap_le_connect.c",
|
||||
override_includes=['dummy_board'])
|
||||
|
||||
clar(bld,
|
||||
sources_ant_glob = "src/fw/util/time/mktime.c " \
|
||||
"src/fw/util/buffer.c " \
|
||||
"src/fw/util/time/time.c " \
|
||||
"src/fw/comm/ble/kernel_le_client/ancs/ancs_util.c " \
|
||||
"tests/fakes/fake_rtc.c",
|
||||
test_sources_ant_glob = "test_ancs_util.c")
|
||||
|
||||
clar(bld,
|
||||
sources_ant_glob = "src/fw/util/buffer.c " \
|
||||
"src/fw/util/pstring.c " \
|
||||
"src/fw/util/time/mktime.c " \
|
||||
"src/fw/util/time/time.c "\
|
||||
"src/fw/comm/ble/kernel_le_client/ancs/ancs_util.c " \
|
||||
"src/fw/comm/ble/kernel_le_client/ancs/ancs.c " \
|
||||
"src/fw/comm/ble/kernel_le_client/ancs/ancs_app_name_storage.c " \
|
||||
"src/fw/services/common/regular_timer.c " \
|
||||
"src/fw/util/rand/rand.c " \
|
||||
"src/fw/vendor/tinymt32/tinymt32.c " \
|
||||
"tests/fakes/fake_events.c " \
|
||||
"tests/fakes/fake_kernel_services_notifications.c " \
|
||||
"tests/fakes/fake_gatt_client_subscriptions.c " \
|
||||
"tests/fakes/fake_rtc.c " \
|
||||
"src/fw/services/normal/notifications/ancs/ancs_notifications.c " \
|
||||
" src/fw/services/normal/notifications/ancs/ancs_notifications_util.c " \
|
||||
"src/fw/services/normal/notifications/ancs/ancs_item.c " \
|
||||
"src/fw/services/normal/notifications/ancs/ancs_filtering.c " \
|
||||
"src/fw/services/normal/notifications/ancs/ancs_phone_call.c " \
|
||||
"src/fw/services/normal/timeline/item.c " \
|
||||
"src/fw/services/normal/timeline/attributes_actions.c " \
|
||||
"src/fw/services/normal/timeline/attribute_group.c " \
|
||||
"src/fw/services/normal/timeline/attribute.c",
|
||||
test_sources_ant_glob = "test_ancs.c")
|
||||
|
||||
clar(bld,
|
||||
sources_ant_glob="src/fw/comm/ble/kernel_le_client/kernel_le_client.c "
|
||||
"src/fw/util/rand/rand.c "
|
||||
"src/fw/vendor/tinymt32/tinymt32.c",
|
||||
test_sources_ant_glob="test_kernel_le_client.c")
|
||||
|
||||
for ppogatt_version in [ 0, 1]:
|
||||
clar(bld,
|
||||
sources_ant_glob = "src/fw/system/hexdump.c " \
|
||||
"src/fw/comm/ble/kernel_le_client/ppogatt/ppogatt.c " \
|
||||
"src/fw/services/common/regular_timer.c " \
|
||||
"src/fw/util/rand/rand.c "
|
||||
"src/fw/vendor/tinymt32/tinymt32.c "
|
||||
"tests/fakes/fake_gatt_client_operations.c " \
|
||||
"tests/fakes/fake_gatt_client_subscriptions.c " \
|
||||
"tests/fakes/fake_rtc.c " \
|
||||
"tests/fakes/fake_session.c",
|
||||
defines=["USE_PPOGATT_VERSION=%d" % ppogatt_version],
|
||||
test_name='test_ppogatt_v%d' % ppogatt_version,
|
||||
test_sources_ant_glob = "test_ppogatt.c")
|
||||
|
||||
# vim:filetype=python
|
||||
Reference in New Issue
Block a user