/* * 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/app_glance.h" #include "drivers/rtc.h" #include "kernel/pbl_malloc.h" #include "resource/resource_ids.auto.h" #include "services/normal/app_glances/app_glance_service.h" #include "services/normal/blob_db/app_glance_db.h" #include "services/normal/blob_db/app_glance_db_private.h" #include "services/normal/filesystem/pfs.h" #include "util/uuid.h" // Fakes //////////////////////////////////////////////////////////////// #include "fake_settings_file.h" #include "fake_events.h" // Stubs //////////////////////////////////////////////////////////////// #include "stubs_logging.h" #include "stubs_mutex.h" #include "stubs_passert.h" #include "stubs_pbl_malloc.h" status_t pfs_remove(const char *name) { fake_settings_file_reset(); return S_SUCCESS; } static bool s_app_cache_entry_exists = true; bool app_cache_entry_exists(AppInstallId app_id) { return s_app_cache_entry_exists; } static int s_launch_count = 0; status_t app_cache_app_launched(AppInstallId app_id) { s_launch_count++; return S_SUCCESS; } static AppInstallId s_app_install_id = 1; AppInstallId app_install_get_id_for_uuid(const Uuid *uuid) { if (!uuid) { return INSTALL_ID_INVALID; } return s_app_install_id; } bool app_install_id_from_system(AppInstallId id) { return (id < INSTALL_ID_INVALID); } bool app_install_id_from_app_db(AppInstallId id) { return (id > INSTALL_ID_INVALID); } #define APP_GLANCE_TEST_UUID \ (UuidMake(0x3d, 0xc6, 0xb9, 0x4c, 0x4, 0x2, 0x48, 0xf4, \ 0xbe, 0x14, 0x81, 0x17, 0xf1, 0xa, 0xa9, 0xc4)) static const uint8_t s_app_glance_basic[] = { // Version APP_GLANCE_DB_CURRENT_VERSION, // Creation time 0x14, 0x13, 0x4E, 0x57, // 1464734484 (Tue, 31 May 2016 22:41:24 GMT) // Slice 1 0x22, 0x00, // Total size 0x00, // AppGlanceSliceType - AppGlanceSliceType_IconAndSubtitle 0x03, // Number of attributes // Slice Attributes 0x25, // Attribute ID - AttributeIdTimestamp 0x04, 0x00, // Attribute Length // Slice expiration time: 0x94, 0x64, 0x4F, 0x57, // 1464820884 (Wed, 1 June 2016 22:41:24 GMT) 0x30, // Attribute ID - AttributeIdIcon 0x04, 0x00, // Attribute Length // Slice icon resource ID: 0x69, 0x00, 0x00, 0x00, // 0x2F, // Attribute ID - AttributeIdSubtitleTemplateString 0x0D, 0x00, // Attribute Length // Slice subtitle: 'T', 'e', 's', 't', ' ', 's', 'u', 'b', 't', 'i', 't', 'l', 'e', }; // Note that `APP_GLANCE_DB_MAX_SLICES_PER_GLANCE` is reduced for the unit tests! static const uint8_t s_app_glance_with_too_many_slices[] = { // Version APP_GLANCE_DB_CURRENT_VERSION, // Creation time 0x14, 0x13, 0x4E, 0x57, // 1464734484 (Tue, 31 May 2016 22:41:24 GMT) // Slice 1 0x0B, 0x00, // Total size 0x00, // AppGlanceSliceType - AppGlanceSliceType_IconAndSubtitle 0x01, // Number of attributes // Slice Attributes 0x25, // Attribute ID - AttributeIdTimestamp 0x04, 0x00, // Attribute Length // Slice expiration time: 0x94, 0x64, 0x4F, 0x57, // 1464820884 (Wed, 1 June 2016 22:41:24 GMT) // Slice 2 0x0B, 0x00, // Total size 0x00, // AppGlanceSliceType - AppGlanceSliceType_IconAndSubtitle 0x01, // Number of attributes // Slice Attributes 0x25, // Attribute ID - AttributeIdTimestamp 0x04, 0x00, // Attribute Length // Slice expiration time: 0x95, 0x64, 0x4F, 0x57, // 1464820884 (Wed, 1 June 2016 22:41:25 GMT) // Slice 3 0x0B, 0x00, // Total size 0x00, // AppGlanceSliceType - AppGlanceSliceType_IconAndSubtitle 0x01, // Number of attributes // Slice Attributes 0x25, // Attribute ID - AttributeIdTimestamp 0x04, 0x00, // Attribute Length // Slice expiration time: 0x96, 0x64, 0x4F, 0x57, // 1464820884 (Wed, 1 June 2016 22:41:26 GMT) }; static const uint8_t s_app_glance_with_invalid_slice_total_sizes[] = { // Version APP_GLANCE_DB_CURRENT_VERSION, // Creation time 0x14, 0x13, 0x4E, 0x57, // 1464734484 (Tue, 31 May 2016 22:41:24 GMT) // Slice 1 (valid) 0x0B, 0x00, // Total size 0x00, // AppGlanceSliceType - AppGlanceSliceType_IconAndSubtitle 0x01, // Number of attributes // Slice Attributes 0x25, // Attribute ID - AttributeIdTimestamp 0x04, 0x00, // Attribute Length // Slice expiration time: 0x94, 0x64, 0x4F, 0x57, // 1464820884 (Wed, 1 June 2016 22:41:24 GMT) // Slice 2 (invalid total_size) 0xFF, 0x00, // Total size 0x00, // AppGlanceSliceType - AppGlanceSliceType_IconAndSubtitle 0x01, // Number of attributes // Slice Attributes 0x25, // Attribute ID - AttributeIdTimestamp 0x04, 0x00, // Attribute Length // Slice expiration time: 0x95, 0x64, 0x4F, 0x57, // 1464820884 (Wed, 1 June 2016 22:41:25 GMT) }; // Setup //////////////////////////////////////////////////////////////// void test_app_glance_db__initialize(void) { s_app_cache_entry_exists = true; s_app_install_id = 1; s_launch_count = 0; fake_event_init(); fake_settings_file_reset(); app_glance_db_init(); } void app_glance_db_deinit(void); void test_app_glance_db__cleanup(void) { app_glance_db_deinit(); } // Blob Tests //////////////////////////////////////////////////////////////// void test_app_glance_db__blob_insertion_with_invalid_key_or_val_length_fails(void) { // Invalid key length should fail const size_t invalid_key_length = 1337; cl_assert_equal_i(app_glance_db_insert((uint8_t *)&APP_GLANCE_TEST_UUID, invalid_key_length, (uint8_t *)&s_app_glance_basic, sizeof(s_app_glance_basic)), E_INVALID_ARGUMENT); // Invalid val length should fail const size_t invalid_val_size = sizeof(SerializedAppGlanceHeader) - 1; cl_assert_equal_i(app_glance_db_insert((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE, (uint8_t *)&s_app_glance_basic, invalid_val_size), E_INVALID_ARGUMENT); } void test_app_glance_db__basic_glance_blob_insert_and_read(void) { const size_t glance_size = sizeof(s_app_glance_basic); cl_assert_equal_i(app_glance_db_insert((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE, (uint8_t *)&s_app_glance_basic, glance_size), S_SUCCESS); cl_assert_equal_i(app_glance_db_get_len((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE), glance_size); uint8_t *glance_out = kernel_malloc(glance_size); cl_assert_equal_i(app_glance_db_read((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE, glance_out, glance_size), S_SUCCESS); cl_assert_equal_m(glance_out, (uint8_t *)s_app_glance_basic, glance_size); kernel_free(glance_out); } void test_app_glance_db__blob_read_with_invalid_key_length_or_null_val_out_fails(void) { // Call the basic glance blob insert test to insert the basic glance blob test_app_glance_db__basic_glance_blob_insert_and_read(); const size_t glance_size = sizeof(s_app_glance_basic); uint8_t glance_out[glance_size]; // Trying to read the basic glance blob back with an invalid key length should fail const size_t invalid_key_length = 1337; cl_assert_equal_i(app_glance_db_read((uint8_t *)&APP_GLANCE_TEST_UUID, invalid_key_length, glance_out, glance_size), E_INVALID_ARGUMENT); // Trying to read the basic glance blob back with a NULL glance_out argument should fail uint8_t *invalid_glance_out = NULL; cl_assert_equal_i(app_glance_db_read((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE, invalid_glance_out, glance_size), E_INVALID_ARGUMENT); } void test_app_glance_db__basic_glance_blob_delete(void) { // Call the basic glance blob insert test to insert the basic glance blob test_app_glance_db__basic_glance_blob_insert_and_read(); // Delete the basic glance blob cl_assert_equal_i(app_glance_db_delete((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE), S_SUCCESS); const size_t glance_size = sizeof(s_app_glance_basic); uint8_t glance_out[glance_size]; // Trying to read the basic glance blob now should fail because it should no longer exist cl_assert_equal_i(app_glance_db_read((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE, glance_out, glance_size), E_DOES_NOT_EXIST); } void test_app_glance_db__delete_non_existing_blob_does_nothing(void) { // Trying to delete a glance that is not actually in the database should do nothing cl_assert_equal_i(app_glance_db_delete((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE), S_SUCCESS); } void test_app_glance_db__delete_blob_with_invalid_key_length_fails(void) { // Call the basic glance blob insert test to insert the basic glance blob test_app_glance_db__basic_glance_blob_insert_and_read(); // Trying to delete the basic glance blob with an invalid key length should fail const size_t invalid_key_length = 1337; cl_assert_equal_i(app_glance_db_delete((uint8_t *)&APP_GLANCE_TEST_UUID, invalid_key_length), E_INVALID_ARGUMENT); } void test_app_glance_db__glance_blob_with_older_creation_time_than_existing_not_inserted(void) { // Insert the first glance blob SerializedAppGlanceHeader app_glance_1 = (SerializedAppGlanceHeader) { .version = APP_GLANCE_DB_CURRENT_VERSION, .creation_time = 1464734484, // Tue, 31 May 2016 22:41:24 GMT }; const size_t glance_1_size = sizeof(app_glance_1); cl_assert_equal_i(app_glance_db_insert((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE, (uint8_t *)&app_glance_1, glance_1_size), S_SUCCESS); cl_assert_equal_i(app_glance_db_get_len((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE), glance_1_size); // Try to insert a different glance blob with an older creation time; this should fail SerializedAppGlanceHeader app_glance_2 = (SerializedAppGlanceHeader) { .version = APP_GLANCE_DB_CURRENT_VERSION, .creation_time = 1464648084, // Mon, 30 May 2016 22:41:24 GMT }; const size_t glance_2_size = sizeof(app_glance_2); cl_assert_equal_i(app_glance_db_insert((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE, (uint8_t *)&app_glance_2, glance_2_size), E_INVALID_ARGUMENT); } void test_app_glance_db__glance_blob_with_too_many_slices_inserted_but_trimmed(void) { const size_t original_glance_size = sizeof(s_app_glance_with_too_many_slices); const size_t excess_slices_size = 11; const size_t trimmed_glance_size = original_glance_size - excess_slices_size; // Insert the glance blob with too many slices; this should succeed cl_assert_equal_i(app_glance_db_insert((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE, (uint8_t *)&s_app_glance_with_too_many_slices, original_glance_size), S_SUCCESS); // But the length we read back should be trimmed of the excess slices cl_assert_equal_i(app_glance_db_get_len((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE), trimmed_glance_size); // The glance blob read back from the database should match everything up to where we trimmed uint8_t glance_out[trimmed_glance_size]; cl_assert_equal_i(app_glance_db_read((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE, glance_out, trimmed_glance_size), S_SUCCESS); cl_assert_equal_m(glance_out, (uint8_t *)s_app_glance_with_too_many_slices, trimmed_glance_size); } static void prv_check_invalid_version_code_blob_not_inserted(uint8_t version) { const SerializedAppGlanceHeader app_glance = (SerializedAppGlanceHeader) { .version = version, }; cl_assert_equal_i(app_glance_db_insert((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE, (uint8_t *)&app_glance, sizeof(app_glance)), E_INVALID_ARGUMENT); } void test_app_glance_db__lower_version_blob_not_inserted(void) { for (uint8_t version = 0; version < APP_GLANCE_DB_CURRENT_VERSION; version++) { prv_check_invalid_version_code_blob_not_inserted(version); } } void test_app_glance_db__higher_version_not_blob_inserted(void) { prv_check_invalid_version_code_blob_not_inserted(APP_GLANCE_DB_CURRENT_VERSION + 1); } static status_t prv_insert_dummy_glance_blob_with_size(uint16_t blob_size) { const uint8_t dummy_app_glance[] = { // Version APP_GLANCE_DB_CURRENT_VERSION, // Creation time 0x14, 0x13, 0x4E, 0x57, // 1464734484 (Tue, 31 May 2016 22:41:24 GMT) // Slice 1 (uint8_t)(blob_size & 0xFF), (uint8_t)(blob_size >> 8), // Total size }; return app_glance_db_insert((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE, (uint8_t *)&dummy_app_glance, sizeof(dummy_app_glance)); } void test_app_glance_db__check_too_small_blob_not_inserted(void) { cl_assert_equal_i(prv_insert_dummy_glance_blob_with_size(APP_GLANCE_DB_SLICE_MIN_SIZE - 1), E_INVALID_ARGUMENT); } void test_app_glance_db__check_too_large_blob_not_inserted(void) { cl_assert_equal_i(prv_insert_dummy_glance_blob_with_size(APP_GLANCE_DB_SLICE_MAX_SIZE + 1), E_INVALID_ARGUMENT); } void test_app_glance_db__check_invalid_slice_total_sizes_blob_not_inserted(void) { cl_assert_equal_i(app_glance_db_insert((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE, (uint8_t *)&s_app_glance_with_invalid_slice_total_sizes, sizeof(s_app_glance_with_invalid_slice_total_sizes)), E_INVALID_ARGUMENT); } status_t app_glance_db_insert_stale(const uint8_t *key, int key_len, const uint8_t *val, int val_len); void test_app_glance_db__read_stale_glance_blob(void) { // Force the insertion of a stale glance blob (outdated version) const SerializedAppGlanceHeader app_glance = (SerializedAppGlanceHeader) { .version = APP_GLANCE_DB_CURRENT_VERSION - 1, }; cl_assert_equal_i(app_glance_db_insert_stale((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE, (uint8_t *)&app_glance, sizeof(app_glance)), S_SUCCESS); // Verify that trying to read the blob back fails due to it not existing SerializedAppGlanceHeader glance_out = {}; cl_assert_equal_i(app_glance_db_read((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE, (uint8_t *)&glance_out, sizeof(SerializedAppGlanceHeader)), E_DOES_NOT_EXIST); } void test_app_glance_db__glance_blob_with_slice_missing_expiration_time_gets_default_value(void) { const uint8_t app_glance_with_slice_missing_expiration_time[] = { // Version APP_GLANCE_DB_CURRENT_VERSION, // Creation time 0x14, 0x13, 0x4E, 0x57, // 1464734484 (Tue, 31 May 2016 22:41:24 GMT) // Slice 1 0x1B, 0x00, // Total size 0x00, // AppGlanceSliceType - AppGlanceSliceType_IconAndSubtitle 0x02, // Number of attributes // Slice Attributes 0x30, // Attribute ID - AttributeIdIcon 0x04, 0x00, // Attribute Length // Slice icon resource ID: 0x69, 0x00, 0x00, 0x00, // 0x2F, // Attribute ID - AttributeIdSubtitleTemplateString 0x0D, 0x00, // Attribute Length // Slice subtitle: 'T', 'e', 's', 't', ' ', 's', 'u', 'b', 't', 'i', 't', 'l', 'e', }; const size_t app_glance_with_slice_missing_expiration_time_size = sizeof(app_glance_with_slice_missing_expiration_time); cl_assert_equal_i(app_glance_db_insert((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE, (uint8_t *)&app_glance_with_slice_missing_expiration_time, app_glance_with_slice_missing_expiration_time_size), S_SUCCESS); AppGlance read_back_glance = {}; cl_assert_equal_i(app_glance_db_read_glance(&APP_GLANCE_TEST_UUID, &read_back_glance), S_SUCCESS); cl_assert_equal_i(read_back_glance.slices[0].expiration_time, APP_GLANCE_SLICE_NO_EXPIRATION); } // Glance Tests //////////////////////////////////////////////////////////////// void test_app_glance_db__basic_glance_insert_and_read(void) { const AppGlance glance = (AppGlance) { .num_slices = 2, .slices = { { .expiration_time = 1464734484, // (Tue, 31 May 2016 22:41:24 GMT) .type = AppGlanceSliceType_IconAndSubtitle, .icon_and_subtitle = { .icon_resource_id = RESOURCE_ID_SETTINGS_ICON_AIRPLANE, .template_string = "Test subtitle", }, }, { .expiration_time = 1465579430, // (Fri, 10 Jun 2016 17:23:50 GMT) .type = AppGlanceSliceType_IconAndSubtitle, .icon_and_subtitle = { .icon_resource_id = RESOURCE_ID_SETTINGS_ICON_BLUETOOTH, .template_string = "Test subtitle 2", }, }, }, }; cl_assert_equal_i(app_glance_db_insert_glance(&APP_GLANCE_TEST_UUID, &glance), S_SUCCESS); AppGlance read_back_glance = {}; cl_assert_equal_i(app_glance_db_read_glance(&APP_GLANCE_TEST_UUID, &read_back_glance), S_SUCCESS); cl_assert_equal_m(&glance, &read_back_glance, sizeof(AppGlance)); } void test_app_glance_db__reading_nonexistent_glance_returns_does_not_exist(void) { AppGlance glance = {}; cl_assert_equal_i(app_glance_db_read_glance(&UUID_INVALID, &glance), E_DOES_NOT_EXIST); } void test_app_glance_db__reading_glance_with_invalid_arguments_fails(void) { // NULL UUID fails AppGlance glance_out = {}; cl_assert_equal_i(app_glance_db_read_glance(NULL, &glance_out), E_INVALID_ARGUMENT); // NULL glance_out fails cl_assert_equal_i(app_glance_db_read_glance(&APP_GLANCE_TEST_UUID, NULL), E_INVALID_ARGUMENT); } void test_app_glance_db__inserting_glance_with_invalid_arguments_fails(void) { // NULL UUID fails const AppGlance glance = {}; cl_assert_equal_i(app_glance_db_insert_glance(NULL, &glance), E_INVALID_ARGUMENT); // NULL glance fails cl_assert_equal_i(app_glance_db_insert_glance(&APP_GLANCE_TEST_UUID, NULL), E_INVALID_ARGUMENT); // Glance with too many slices fails const AppGlance glance_with_too_many_slices = (AppGlance) { .num_slices = 1337, }; cl_assert(glance_with_too_many_slices.num_slices > APP_GLANCE_DB_MAX_SLICES_PER_GLANCE); cl_assert_equal_i(app_glance_db_insert_glance(&APP_GLANCE_TEST_UUID, &glance_with_too_many_slices), E_INVALID_ARGUMENT); // Glance containing a slice with an invalid type fails const AppGlance glance_containing_slice_with_invalid_type = (AppGlance) { .num_slices = 1, .slices = { { .expiration_time = 1464734484, // (Tue, 31 May 2016 22:41:24 GMT) .type = (AppGlanceSliceType)200, }, }, }; cl_assert(glance_containing_slice_with_invalid_type.slices[0].type >= AppGlanceSliceTypeCount); cl_assert_equal_i(app_glance_db_insert_glance(&APP_GLANCE_TEST_UUID, &glance_containing_slice_with_invalid_type), E_INVALID_ARGUMENT); } void test_app_glance_db__read_glance_creation_time(void) { time_t time_out; // Reading the creation time of a glance that doesn't exist should return "does not exist" cl_assert_equal_i(app_glance_db_read_creation_time(&APP_GLANCE_TEST_UUID, &time_out), E_DOES_NOT_EXIST); // Insert a glance and check that the creation time we read back matches test_app_glance_db__basic_glance_blob_insert_and_read(); cl_assert_equal_i(app_glance_db_read_creation_time(&APP_GLANCE_TEST_UUID, &time_out), S_SUCCESS); cl_assert_equal_i(time_out, 1464734484); } void test_app_glance_db__read_glance_creation_time_with_invalid_arguments_fails(void) { // NULL UUID fails time_t time_out; cl_assert_equal_i(app_glance_db_read_creation_time(NULL, &time_out), E_INVALID_ARGUMENT); // NULL time_out fails cl_assert_equal_i(app_glance_db_read_creation_time(&APP_GLANCE_TEST_UUID, NULL), E_INVALID_ARGUMENT); } void test_app_glance_db__empty_glance_insert_after_basic_glance_insert_succeeds(void) { // Call the basic glance insert test to insert the basic glance test_app_glance_db__basic_glance_insert_and_read(); // Let some time pass so the creation time of this next glance insertion is newer rtc_set_time(rtc_get_time() + 10); // Try inserting an empty glance; this should succeed and clear the glance AppGlance empty_glance = {}; cl_assert_equal_i(app_glance_db_insert_glance(&APP_GLANCE_TEST_UUID, &empty_glance), S_SUCCESS); AppGlance read_back_glance = {}; cl_assert_equal_i(app_glance_db_read_glance(&APP_GLANCE_TEST_UUID, &read_back_glance), S_SUCCESS); cl_assert_equal_m(&empty_glance, &read_back_glance, sizeof(AppGlance)); } void test_app_glance_db__insert_no_app_installed(void) { s_app_install_id = INSTALL_ID_INVALID; cl_assert_equal_i(app_glance_db_insert((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE, s_app_glance_basic, sizeof(s_app_glance_basic)), E_DOES_NOT_EXIST); } void test_app_glance_db__insert_app_not_in_cache(void) { s_app_install_id = 10; s_app_cache_entry_exists = false; cl_assert_equal_i(app_glance_db_insert((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE, s_app_glance_basic, sizeof(s_app_glance_basic)), S_SUCCESS); cl_assert_equal_i(fake_event_get_count(), 1); PebbleEvent e = fake_event_get_last(); cl_assert_equal_i(e.type, PEBBLE_APP_FETCH_REQUEST_EVENT); cl_assert(!e.app_fetch_request.with_ui); cl_assert_equal_i(e.app_fetch_request.id, 10); } void test_app_glance_db__insert_app_in_cache(void) { cl_assert_equal_i(app_glance_db_insert((uint8_t *)&APP_GLANCE_TEST_UUID, UUID_SIZE, s_app_glance_basic, sizeof(s_app_glance_basic)), S_SUCCESS); cl_assert_equal_i(s_launch_count, 1); }