pebble/tests/fw/services/test_timeline_item.c
2025-01-27 11:38:16 -08:00

384 lines
16 KiB
C

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "services/normal/timeline/item.h"
#include "util/size.h"
#include "clar.h"
// Stubs
////////////////////////////////////
#include "fake_rtc.h"
#include "stubs_fonts.h"
#include "stubs_layout_layer.h"
#include "stubs_passert.h"
#include "stubs_logging.h"
#include "stubs_pbl_malloc.h"
#include "stubs_pebble_tasks.h"
#include "stubs_rand_ptr.h"
static uint8_t s_payload_complete[] = {
// Attribute 1
0x01, // Attribute ID - Title
0x11, 0x00, // Attribute Length
// Attribute text: "Test Notification"
0x54, 0x65, 0x73, 0x74, 0x20, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
0x6e,
// Attribute 2
0x02, // Attribute ID - Subtitle
0x08, 0x00, // Attribute Length
// Attribute text: "Subtitle"
'S', 'u', 'b', 't', 'i', 't', 'l', 'e',
// Attribute 3
0x03, // Attribute ID - Body
0x3f, 0x00, // Attribute Length
// Attribute text: "This is a test notification. Look at it and behold the awesome."
0x54, 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, 0x2e, 0x20, 0x4c, 0x6f, 0x6f,
0x6b, 0x20, 0x61, 0x74, 0x20, 0x69, 0x74, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x62, 0x65, 0x68, 0x6f,
0x6c, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x77, 0x65, 0x73, 0x6f, 0x6d, 0x65, 0x2e,
// Action 1
0x00, // Action ID
0x02, // Action Type - Pebble Protocol
0x01, // Number of action attributes
// Action Attributes
0x01, // Attribute ID - Title
0x07, 0x00, // Attribute Length
// Attribute text:
'D', 'i', 's', 'm', 'i', 's', 's',
// Action 2
0x01, // Action ID
0x02, // Action Type - Pebble Protocol
0x02, // Number of action attributes
// Action Attributes
0x01, // Attribute 1 ID - Title
0x04, 0x00, // Attribute 1 Length
// Attribute text:
'L', 'i', 'k', 'e',
0x07, // Attribute 2 ID - ANCS UID
0x01, 0x00, // Attribute 2 Length
// Attribute text: "Test"
0x01
};
void test_timeline_item__initialize(void) {
}
void test_timeline_item__cleanup(void) {
}
static const uint8_t s_serialized_attribute_list[] = {
0x01, // Attribute 1 ID - Title
0x04, 0x00, // Attribute 1 Length
// Attribute text:
'L', 'i', 'k', 'e',
0x02, // Attribute 1 ID - Title
0x03, 0x00, // Attribute 1 Length
// Attribute text:
'e', 'y', 'e',
};
static const uint8_t s_invalid_serialized_attribute_list[] = {
0x01, // Attribute 1 ID - Title
0x04, 0x00, // Attribute 1 Length
// Attribute text:
'L', 'i', 'k', 'e',
0x08, // Attribute 2 ID - String list
0x4e, 0x00, // Attribute 2 length
// Attribute content
0x74, 0x65, 0x73, 0x74, 0x00, 0xd0, 0x94, 0xd0, 0xb0, 0x00, 0xd0, 0x9d, 0xd0, 0xb5, 0xd1, 0x82,
0x00, 0xd0, 0x9e, 0xd0, 0x9a, 0x00, 0xd0, 0xa5, 0xd0, 0xb0, 0x2d, 0xd1, 0x85, 0xd0, 0xb0, 0x00,
0xd0, 0xa1, 0xd0, 0xbf, 0xd0, 0xb0, 0xd1, 0x81, 0xd0, 0xb8, 0xd0, 0xb1, 0xd0, 0xbe, 0x00, 0xd0,
0xa5, 0xd0, 0xbe, 0xd1, 0x80, 0xd0, 0xbe, 0xd1, 0x88, 0xd0, 0xbe, 0x00, 0xd0, 0x9e, 0xd1, 0x82,
0xd0, 0xbb, 0xd0, 0xb8, 0xd1, 0x87, 0xd0, 0xbd, 0xd0, 0xbe, 0x00, 0xd0, 0xa1, 0xd0, 0xba, 0xd0,
0xbe, 0xd1, 0x80, 0xd0, 0xbe, 0x20, 0xd0, 0xb1, 0xd1, 0x83, 0xd0, 0xb4, 0xd1,
};
void test_timeline_item__get_serialized_attributes_length(void) {
const uint8_t *cursor = s_serialized_attribute_list;
int32_t result = attribute_get_buffer_size_for_serialized_attributes(2,
&cursor, s_serialized_attribute_list + sizeof(s_serialized_attribute_list));
cl_assert(result == 9);
cursor = s_invalid_serialized_attribute_list;
result = attribute_get_buffer_size_for_serialized_attributes(3,
&cursor,
(uint8_t *)s_invalid_serialized_attribute_list + sizeof(s_invalid_serialized_attribute_list));
cl_assert(result < 0);
}
void test_timeline_item__deserialize_payload(void) {
size_t buf_size = 18 + 9 + 64 + 8 + 5 + 5;
char *buffer;
TimelineItem *item = timeline_item_create(3, 2, (uint8_t[]) {1, 2}, buf_size, (uint8_t **)&buffer);
timeline_item_deserialize_payload(item, buffer, buf_size, s_payload_complete, sizeof(s_payload_complete));
cl_assert_equal_i(item->attr_list.num_attributes, 3);
cl_assert_equal_i(item->attr_list.attributes[0].id, AttributeIdTitle);
cl_assert_equal_s(item->attr_list.attributes[0].cstring, "Test Notification");
cl_assert_equal_i(item->attr_list.attributes[1].id, AttributeIdSubtitle);
cl_assert_equal_s(item->attr_list.attributes[1].cstring, "Subtitle");
cl_assert_equal_i(item->attr_list.attributes[2].id, AttributeIdBody);
cl_assert_equal_s(item->attr_list.attributes[2].cstring, "This is a test notification. "
"Look at it and behold the awesome.");
cl_assert_equal_i(item->action_group.num_actions, 2);
cl_assert_equal_i(item->action_group.actions[0].id, 0);
cl_assert_equal_i(item->action_group.actions[0].type, TimelineItemActionTypeGeneric);
cl_assert_equal_i(item->action_group.actions[0].attr_list.num_attributes, 1);
cl_assert_equal_i(item->action_group.actions[0].attr_list.attributes[0].id, AttributeIdTitle);
cl_assert_equal_s(item->action_group.actions[0].attr_list.attributes[0].cstring, "Dismiss");
cl_assert_equal_i(item->action_group.actions[1].id, 1);
cl_assert_equal_i(item->action_group.actions[1].type, TimelineItemActionTypeGeneric);
cl_assert_equal_i(item->action_group.actions[1].attr_list.num_attributes, 2);
cl_assert_equal_i(item->action_group.actions[1].attr_list.attributes[0].id, AttributeIdTitle);
cl_assert_equal_s(item->action_group.actions[1].attr_list.attributes[0].cstring, "Like");
cl_assert_equal_i(item->action_group.actions[1].attr_list.attributes[1].id, AttributeIdAncsAction);
cl_assert_equal_i(item->action_group.actions[1].attr_list.attributes[1].uint8, 1);
timeline_item_destroy(item);
}
static Attribute action1_attributes[] = {
{.id = AttributeIdTitle, .cstring = "Dismiss"},
};
static Attribute action2_attributes[] = {
{.id = AttributeIdTitle, .cstring = "Like"},
{.id = AttributeIdAncsAction, .int8 = 1}
};
static Attribute attributes[] = {
{.id = AttributeIdTitle, .cstring = "Test Notification"},
{.id = AttributeIdSubtitle, .cstring = "Subtitle"},
{.id = AttributeIdBody, .cstring = "This is a test notification. "
"Look at it and behold the awesome."},
};
static TimelineItemAction actions[] = {
{.id = 0, .type = TimelineItemActionTypeGeneric, .attr_list = {.num_attributes = ARRAY_LENGTH(action1_attributes), .attributes = action1_attributes}},
{.id = 1, .type = TimelineItemActionTypeGeneric, .attr_list = {.num_attributes = ARRAY_LENGTH(action2_attributes), .attributes = action2_attributes}},
};
void test_timeline_item__serialize_payload(void) {
TimelineItem item = {
.attr_list.num_attributes = ARRAY_LENGTH(attributes),
.attr_list.attributes = attributes,
.action_group.num_actions = ARRAY_LENGTH(actions),
.action_group.actions = actions,
};
uint8_t *buffer = malloc(sizeof(s_payload_complete));
timeline_item_serialize_payload(&item, buffer, sizeof(s_payload_complete));
cl_assert(memcmp(buffer, s_payload_complete, sizeof(s_payload_complete)) == 0);
}
void test_timeline_item__string_list(void) {
StringList *list = malloc(20);
memset(list, 0, 20);
// no data
list->serialized_byte_length = 0;
cl_assert_equal_i(0, string_list_count(list));
list->serialized_byte_length = 3;
// 4 empty strings
cl_assert_equal_i(4, string_list_count(list));
cl_assert_equal_s("", string_list_get_at(list, 0));
cl_assert_equal_s("", string_list_get_at(list, 1));
cl_assert_equal_s("", string_list_get_at(list, 2));
cl_assert_equal_s("", string_list_get_at(list, 3));
// non-null-terminated string is treated as one string - this is the standard case
// please note that the string will only be terminated if there's another \0 following
// when deserializing the data, the deserializer will append the needed \0
list->serialized_byte_length = 3;
list->data[0] = 'a';
list->data[1] = 'b';
list->data[2] = 'c'; // end of data
list->data[3] = 'd';
list->data[4] = '\0';
cl_assert_equal_i(1, string_list_count(list));
cl_assert_equal_s("abcd", string_list_get_at(list, 0));
// 1 string (null terminated) => 2 strings, last is empty
list->serialized_byte_length = 3;
list->data[0] = 'a';
list->data[1] = 'b';
list->data[2] = '\0'; // end of data
list->data[3] = '\0';
cl_assert_equal_i(2, string_list_count(list));
cl_assert_equal_s("ab", string_list_get_at(list, 0));
cl_assert_equal_s("", string_list_get_at(list, 1));
// 2 strings (non-null terminated) - this is the standard case
list->serialized_byte_length = 4;
list->data[0] = 'a';
list->data[1] = 'b';
list->data[2] = '\0';
list->data[3] = 'c'; // end of data
list->data[4] = '\0';
cl_assert_equal_i(2, string_list_count(list));
cl_assert_equal_s("ab", string_list_get_at(list, 0));
cl_assert_equal_s("c", string_list_get_at(list, 1));
// 3 strings (last two are is empty)
list->serialized_byte_length = 4;
list->data[0] = 'a';
list->data[1] = 'b';
list->data[2] = '\0';
list->data[3] = '\0'; // end of data
list->data[4] = '\0';
cl_assert_equal_i(3, string_list_count(list));
cl_assert_equal_s("ab", string_list_get_at(list, 0));
cl_assert_equal_s("", string_list_get_at(list, 1));
cl_assert_equal_s("", string_list_get_at(list, 2));
cl_assert_equal_s(NULL, string_list_get_at(list, 3));
// 4 strings (first and last two are empty)
list->serialized_byte_length = 4;
list->data[0] = '\0';
list->data[1] = 'b';
list->data[2] = '\0';
list->data[3] = '\0'; // end of data
list->data[4] = '\0';
cl_assert_equal_i(4, string_list_count(list));
cl_assert_equal_s("", string_list_get_at(list, 0));
cl_assert_equal_s("b", string_list_get_at(list, 1));
cl_assert_equal_s("", string_list_get_at(list, 2));
cl_assert_equal_s("", string_list_get_at(list, 3));
// 2 strings (last is not terminated and will fall through) will return 2 strings
// when deserializing, the deserializer puts a \0 at the end
// this case demonstrates the problem with incorrectly initialized data
list->serialized_byte_length = 3;
list->data[0] = 'a';
list->data[1] = '\0';
list->data[2] = 'b'; // end of data
list->data[3] = 'c';
list->data[4] = '\0';
cl_assert_equal_i(2, string_list_count(list));
cl_assert_equal_s("a", string_list_get_at(list, 0));
cl_assert_equal_s("bc", string_list_get_at(list, 1));
}
static TimelineItemAction s_basic_action_list[] = {
{ .id = 0, .type = TimelineItemActionTypeGeneric },
{ .id = 1, .type = TimelineItemActionTypeHttp },
{ .id = 2, .type = TimelineItemActionTypeOpenPin },
};
void test_timeline_item__find_action_with_id(void) {
// Make sure we're resilient to NULL items
cl_assert_equal_p(timeline_item_find_action_with_id(NULL, 0), NULL);
// Make sure we can handle timeline items with no actions
TimelineItem item = {};
cl_assert_equal_p(timeline_item_find_action_with_id(&item, 0), NULL);
// Make sure we actually find the items we're looking for
item.action_group.num_actions = ARRAY_LENGTH(s_basic_action_list);
item.action_group.actions = s_basic_action_list;
cl_assert_equal_p(timeline_item_find_action_with_id(&item, 0), &s_basic_action_list[0]);
cl_assert_equal_p(timeline_item_find_action_with_id(&item, 1), &s_basic_action_list[1]);
cl_assert_equal_p(timeline_item_find_action_with_id(&item, 2), &s_basic_action_list[2]);
cl_assert_equal_p(timeline_item_find_action_with_id(&item, 3), NULL);
item.header.id = UUID_INVALID;
cl_assert_equal_p(timeline_item_find_action_with_id(&item, 0), NULL);
}
void test_timeline_item__find_action_by_type(void) {
// Make sure we're resilient to NULL items
cl_assert_equal_p(timeline_item_find_action_by_type(NULL, TimelineItemActionTypeGeneric), NULL);
// Make sure we can handle timeline items with no actions
TimelineItem item = {};
cl_assert_equal_p(timeline_item_find_action_by_type(&item, TimelineItemActionTypeGeneric), NULL);
// Make sure we actually find the items we're looking for
item.action_group.num_actions = ARRAY_LENGTH(s_basic_action_list);
item.action_group.actions = s_basic_action_list;
cl_assert_equal_p(timeline_item_find_action_by_type(&item, TimelineItemActionTypeGeneric),
&s_basic_action_list[0]);
cl_assert_equal_p(timeline_item_find_action_by_type(&item, TimelineItemActionTypeHttp),
&s_basic_action_list[1]);
cl_assert_equal_p(timeline_item_find_action_by_type(&item, TimelineItemActionTypeOpenPin),
&s_basic_action_list[2]);
cl_assert_equal_p(timeline_item_find_action_by_type(&item, TimelineItemActionTypeRemove), NULL);
item.header.id = UUID_INVALID;
cl_assert_equal_p(timeline_item_find_action_by_type(&item, 0), NULL);
}
void test_timeline_item__find_dismiss_action(void) {
// Make sure we're resilient to NULL items
cl_assert_equal_p(timeline_item_find_dismiss_action(NULL), NULL);
// Make sure we can handle timeline items with no actions
TimelineItem item = {};
cl_assert_equal_p(timeline_item_find_dismiss_action(&item), NULL);
// Copy the action list since it's easiest to just modify it
TimelineItemAction *action_list = malloc(sizeof(s_basic_action_list));
memcpy(action_list, s_basic_action_list, sizeof(s_basic_action_list));
item.action_group.num_actions = ARRAY_LENGTH(s_basic_action_list);
item.action_group.actions = action_list;
// Make sure we don't return anything if the action doesn't exist
cl_assert_equal_p(timeline_item_find_dismiss_action(&item), NULL);
// Make sure we find both dismiss and ancs negative actions
action_list[1].type = TimelineItemActionTypeDismiss;
cl_assert_equal_p(timeline_item_find_dismiss_action(&item), &action_list[1]);
action_list[1].type = TimelineItemActionTypeAncsNegative;
cl_assert_equal_p(timeline_item_find_dismiss_action(&item), &action_list[1]);
item.header.id = UUID_INVALID;
cl_assert_equal_p(timeline_item_find_dismiss_action(&item), NULL);
free(action_list);
}
void test_timeline_item__find_reply_action(void) {
// Make sure we're resilient to NULL items
cl_assert_equal_p(timeline_item_find_reply_action(NULL), NULL);
// Make sure we can handle timeline items with no actions
TimelineItem item = {};
cl_assert_equal_p(timeline_item_find_reply_action(&item), NULL);
// Copy the action list since it's easiest to just modify it
TimelineItemAction *action_list = malloc(sizeof(s_basic_action_list));
memcpy(action_list, s_basic_action_list, sizeof(s_basic_action_list));
item.action_group.num_actions = ARRAY_LENGTH(s_basic_action_list);
item.action_group.actions = action_list;
// Make sure we don't return anything if the action doesn't exist
cl_assert_equal_p(timeline_item_find_reply_action(&item), NULL);
// Make sure we find both response and ancs response actions
action_list[1].type = TimelineItemActionTypeResponse;
cl_assert_equal_p(timeline_item_find_reply_action(&item), &action_list[1]);
action_list[1].type = TimelineItemActionTypeAncsResponse;
cl_assert_equal_p(timeline_item_find_reply_action(&item), &action_list[1]);
item.header.id = UUID_INVALID;
cl_assert_equal_p(timeline_item_find_reply_action(&item), NULL);
free(action_list);
}