Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

363
tests/fakes/fake_GAPAPI.c Normal file
View file

@ -0,0 +1,363 @@
/*
* 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 "fake_GAPAPI.h"
#include "bluetopia_interface.h"
#include <string.h>
#include "clar_asserts.h"
static bool s_is_le_advertising_enabled;
static GAP_LE_Event_Callback_t s_le_adv_connection_event_callback;
static unsigned long s_le_adv_connection_callback_param;
static uint16_t s_min_advertising_interval_slots;
static uint16_t s_max_advertising_interval_slots;
void gap_le_set_advertising_disabled(void) {
s_is_le_advertising_enabled = false;
s_min_advertising_interval_slots = 0;
s_max_advertising_interval_slots = 0;
}
int GAP_LE_Advertising_Disable(unsigned int BluetoothStackID) {
s_is_le_advertising_enabled = false;
s_le_adv_connection_event_callback = NULL;
s_le_adv_connection_callback_param = 0;
s_min_advertising_interval_slots = 0;
s_max_advertising_interval_slots = 0;
return 0;
}
int GAP_LE_Advertising_Enable(unsigned int BluetoothStackID,
Boolean_t EnableScanResponse,
GAP_LE_Advertising_Parameters_t *GAP_LE_Advertising_Parameters,
GAP_LE_Connectability_Parameters_t *GAP_LE_Connectability_Parameters,
GAP_LE_Event_Callback_t GAP_LE_Event_Callback,
unsigned long CallbackParameter) {
s_is_le_advertising_enabled = true;
s_le_adv_connection_event_callback = GAP_LE_Event_Callback;
s_le_adv_connection_callback_param = CallbackParameter;
if (GAP_LE_Advertising_Parameters) {
// Convert from ms to slots:
s_min_advertising_interval_slots =
(GAP_LE_Advertising_Parameters->Advertising_Interval_Min * 16) / 10;
s_max_advertising_interval_slots =
(GAP_LE_Advertising_Parameters->Advertising_Interval_Max * 16) / 10;
} else {
s_min_advertising_interval_slots = 0;
s_max_advertising_interval_slots = 0;
}
return 0;
}
void gap_le_assert_advertising_interval(uint16_t expected_min_slots, uint16_t expected_max_slots) {
cl_assert_equal_i(s_min_advertising_interval_slots, expected_min_slots);
cl_assert_equal_i(s_max_advertising_interval_slots, expected_max_slots);
}
bool gap_le_is_advertising_enabled(void) {
return s_is_le_advertising_enabled;
}
static Advertising_Data_t s_ad_data;
static unsigned int s_ad_data_length;
int GAP_LE_Set_Advertising_Data(unsigned int BluetoothStackID,
unsigned int Length,
Advertising_Data_t *Advertising_Data) {
memcpy(&s_ad_data, Advertising_Data, Length);
s_ad_data_length = Length;
return 0;
}
unsigned int gap_le_get_advertising_data(Advertising_Data_t *ad_data_out) {
*ad_data_out = s_ad_data;
return s_ad_data_length;
}
static Scan_Response_Data_t s_scan_resp_data;
static unsigned int s_scan_resp_data_length;
int GAP_LE_Set_Scan_Response_Data(unsigned int BluetoothStackID,
unsigned int Length,
Scan_Response_Data_t *Scan_Response_Data) {
memcpy(&s_scan_resp_data, Scan_Response_Data, Length);
s_scan_resp_data_length = Length;
return 0;
}
unsigned int gap_le_get_scan_response_data(Scan_Response_Data_t *scan_resp_data_out) {
*scan_resp_data_out = s_scan_resp_data;
return s_scan_resp_data_length;
}
static GAP_LE_Event_Callback_t s_le_create_connection_event_callback;
static unsigned long s_le_create_connection_callback_param;
int GAP_LE_Create_Connection(unsigned int BluetoothStackID,
unsigned int ScanInterval,
unsigned int ScanWindow,
GAP_LE_Filter_Policy_t InitatorFilterPolicy,
GAP_LE_Address_Type_t RemoteAddressType,
BD_ADDR_t *RemoteDevice,
GAP_LE_Address_Type_t LocalAddressType,
GAP_LE_Connection_Parameters_t *ConnectionParameters,
GAP_LE_Event_Callback_t GAP_LE_Event_Callback,
unsigned long CallbackParameter) {
s_le_create_connection_event_callback = GAP_LE_Event_Callback;
s_le_create_connection_callback_param = CallbackParameter;
return 0;
}
void prv_fake_gap_le_create_connection_event_put(GAP_LE_Event_Data_t *event) {
cl_assert(s_le_create_connection_event_callback != NULL);
s_le_create_connection_event_callback(1, event, s_le_create_connection_callback_param);
}
void prv_fake_gap_le_adv_connection_event_put(GAP_LE_Event_Data_t *event) {
cl_assert(s_le_adv_connection_event_callback != NULL);
s_le_adv_connection_event_callback(1, event, s_le_adv_connection_callback_param);
}
void fake_gap_put_connection_event(uint8_t status,
bool is_master,
const BTDeviceInternal *device) {
GAP_LE_Connection_Complete_Event_Data_t event_data =
(GAP_LE_Connection_Complete_Event_Data_t) {
.Status = status,
.Master = is_master,
.Peer_Address_Type = device->is_random_address ? latRandom : latPublic,
.Peer_Address = BTDeviceAddressToBDADDR(device->address),
};
GAP_LE_Event_Data_t event = (GAP_LE_Event_Data_t) {
.Event_Data_Type = etLE_Connection_Complete,
.Event_Data_Size = sizeof(GAP_LE_Connection_Complete_Event_Data_t),
.Event_Data.GAP_LE_Connection_Complete_Event_Data = &event_data,
};
if (is_master) {
prv_fake_gap_le_create_connection_event_put(&event);
} else {
prv_fake_gap_le_adv_connection_event_put(&event);
}
}
void fake_gap_put_disconnection_event(uint8_t status, uint8_t reason,
bool is_master,
const BTDeviceInternal *device) {
GAP_LE_Disconnection_Complete_Event_Data_t event_data =
(GAP_LE_Disconnection_Complete_Event_Data_t) {
.Status = status,
.Reason = reason,
.Peer_Address_Type = device->is_random_address ? latRandom : latPublic,
.Peer_Address = BTDeviceAddressToBDADDR(device->address),
};
GAP_LE_Event_Data_t event = (GAP_LE_Event_Data_t) {
.Event_Data_Type = etLE_Disconnection_Complete,
.Event_Data_Size = sizeof(GAP_LE_Disconnection_Complete_Event_Data_t),
.Event_Data.GAP_LE_Disconnection_Complete_Event_Data = &event_data,
};
if (is_master) {
prv_fake_gap_le_create_connection_event_put(&event);
} else {
prv_fake_gap_le_adv_connection_event_put(&event);
}
}
void fake_GAPAPI_put_encryption_change_event(bool encrypted, uint8_t status, bool is_master,
const BTDeviceInternal *device) {
GAP_LE_Encryption_Change_Event_Data_t event_data =
(GAP_LE_Encryption_Change_Event_Data_t) {
.BD_ADDR = BTDeviceAddressToBDADDR(device->address),
.Encryption_Change_Status = status,
.Encryption_Mode = encrypted ? emEnabled : emDisabled,
};
GAP_LE_Event_Data_t event = (GAP_LE_Event_Data_t) {
.Event_Data_Type = etLE_Encryption_Change,
.Event_Data_Size = sizeof(GAP_LE_Encryption_Change_Event_Data_t),
.Event_Data.GAP_LE_Encryption_Change_Event_Data = &event_data,
};
if (is_master) {
prv_fake_gap_le_create_connection_event_put(&event);
} else {
prv_fake_gap_le_adv_connection_event_put(&event);
}
}
int GAP_LE_Cancel_Create_Connection(unsigned int BluetoothStackID) {
return 0;
}
// Puts the event that the BT Controller will emit after a succesfull
// GAP_LE_Cancel_Create_Connection call.
void fake_gap_le_put_cancel_create_event(const BTDeviceInternal *device, bool is_master) {
fake_gap_put_connection_event(HCI_ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER,
is_master,
device);
}
int GAP_LE_Disconnect(unsigned int BluetoothStackID,
BD_ADDR_t BD_ADDR) {
return 0;
}
int GAP_LE_Pair_Remote_Device(unsigned int BluetoothStackID,
BD_ADDR_t BD_ADDR,
GAP_LE_Pairing_Capabilities_t *Capabilities,
GAP_LE_Event_Callback_t GAP_LE_Event_Callback,
unsigned long CallbackParameter) {
return 0;
}
// -------------------------------------------------------------------------------------------------
// Bluetopia's Security Manager API
int GAP_LE_Authentication_Response(unsigned int BluetoothStackID, BD_ADDR_t BD_ADDR,
GAP_LE_Authentication_Response_Information_t *GAP_LE_Authentication_Information) {
return 0;
}
int GAP_LE_Diversify_Function(unsigned int BluetoothStackID, Encryption_Key_t *Key, Word_t DIn,
Word_t RIn, Encryption_Key_t *Result) {
return 0;
}
int GAP_LE_Generate_Long_Term_Key(unsigned int BluetoothStackID, Encryption_Key_t *DHK,
Encryption_Key_t *ER, Long_Term_Key_t *LTK_Result,
Word_t *DIV_Result, Word_t *EDIV_Result,
Random_Number_t *Rand_Result) {
return 0;
}
static BD_ADDR_t s_encrypted_device;
int GAP_LE_Query_Encryption_Mode(unsigned int BluetoothStackID, BD_ADDR_t BD_ADDR,
GAP_Encryption_Mode_t *GAP_Encryption_Mode) {
*GAP_Encryption_Mode = (COMPARE_BD_ADDR(s_encrypted_device, BD_ADDR)) ? emEnabled : emDisabled;
return 0;
}
void fake_GAPAPI_set_encrypted_for_device(const BTDeviceInternal *device) {
s_encrypted_device = BTDeviceAddressToBDADDR(device->address);
}
int GAP_LE_Regenerate_Long_Term_Key(unsigned int BluetoothStackID, Encryption_Key_t *DHK,
Encryption_Key_t *ER, Word_t EDIV, Random_Number_t *Rand,
Long_Term_Key_t *LTK_Result) {
return 0;
}
int GAP_LE_Register_Remote_Authentication(unsigned int BluetoothStackID, GAP_LE_Event_Callback_t GAP_LE_Event_Callback, unsigned long CallbackParameter) {
return 0;
}
int GAP_LE_Un_Register_Remote_Authentication(unsigned int BluetoothStackID) {
return 0;
}
int GAP_LE_Request_Security(unsigned int BluetoothStackID, BD_ADDR_t BD_ADDR,
GAP_LE_Bonding_Type_t Bonding_Type, Boolean_t MITM,
GAP_LE_Event_Callback_t GAP_LE_Event_Callback,
unsigned long CallbackParameter) {
return 0;
}
int GAP_LE_Set_Pairability_Mode(unsigned int BluetoothStackID,
GAP_LE_Pairability_Mode_t PairableMode) {
return 0;
}
int GAP_LE_Generate_Resolvable_Address(unsigned int BluetoothStackID, Encryption_Key_t *IRK,
BD_ADDR_t *ResolvableAddress_Result) {
return 0;
}
int GAP_LE_Set_Random_Address(unsigned int BluetoothStackID, BD_ADDR_t RandomAddress) {
return 0;
}
int GAP_Query_Local_BD_ADDR(unsigned int BluetoothStackID, BD_ADDR_t *BD_ADDR) {
return 0;
}
static const Encryption_Key_t s_fake_irk = {
0xaa,
0xaa,
};
static const BD_ADDR_t s_resolving_bd_addr = {
0xaa, 0xff, 0xff, 0xff, 0xff,
0x7f /* 6th byte: bit 6 set, bit 7 unset indicates "resolvable private address" */
};
static const BD_ADDR_t s_not_resolving_bd_addr = {
0xff,
};
const Encryption_Key_t *fake_GAPAPI_get_fake_irk(void) {
return &s_fake_irk;
}
const BD_ADDR_t *fake_GAPAPI_get_bd_addr_not_resolving_to_fake_irk(void) {
return &s_not_resolving_bd_addr;
}
const BTDeviceInternal *fake_GAPAPI_get_device_not_resolving_to_fake_irk(void) {
static BTDeviceInternal s_not_resolving_device;
s_not_resolving_device = (const BTDeviceInternal) {
.address = BDADDRToBTDeviceAddress(s_not_resolving_bd_addr),
.is_random_address = true,
};
return &s_not_resolving_device;
}
const BD_ADDR_t *fake_GAPAPI_get_bd_addr_resolving_to_fake_irk(void) {
return &s_resolving_bd_addr;
}
const BTDeviceInternal *fake_GAPAPI_get_device_resolving_to_fake_irk(void) {
static BTDeviceInternal s_resolving_device;
s_resolving_device = (const BTDeviceInternal) {
.address = BDADDRToBTDeviceAddress(s_resolving_bd_addr),
.is_random_address = true,
};
return &s_resolving_device;
}
Boolean_t GAP_LE_Resolve_Address(unsigned int BluetoothStackID, Encryption_Key_t *IRK,
BD_ADDR_t ResolvableAddress) {
return COMPARE_BD_ADDR(ResolvableAddress, s_resolving_bd_addr) &&
COMPARE_ENCRYPTION_KEY(*IRK, s_fake_irk);
}
void fake_GAPAPI_init(void) {
memset(&s_encrypted_device, 0, sizeof(s_encrypted_device));
s_is_le_advertising_enabled = false;
s_le_adv_connection_event_callback = NULL;
s_le_adv_connection_callback_param = 0;
memset(&s_ad_data, 0, sizeof(s_ad_data));
s_ad_data_length = 0;
s_le_create_connection_event_callback = NULL;
s_le_create_connection_callback_param = 0;
memset(&s_scan_resp_data, 0, sizeof(s_scan_resp_data));
s_scan_resp_data_length = 0;
}

58
tests/fakes/fake_GAPAPI.h Normal file
View file

@ -0,0 +1,58 @@
/*
* 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.
*/
#pragma once
#include "GAPAPI.h"
#include <bluetooth/bluetooth_types.h>
#include <stdbool.h>
#include <stdint.h>
//! Provided to simulate stopping advertising because of an inbound connection.
void gap_le_set_advertising_disabled(void);
bool gap_le_is_advertising_enabled(void);
void gap_le_assert_advertising_interval(uint16_t expected_min_slots, uint16_t expected_max_slots);
unsigned int gap_le_get_advertising_data(Advertising_Data_t *ad_data_out);
unsigned int gap_le_get_scan_response_data(Scan_Response_Data_t *scan_resp_data_out);
void fake_gap_put_connection_event(uint8_t status, bool is_master, const BTDeviceInternal *device);
void fake_gap_put_disconnection_event(uint8_t status, uint8_t reason, bool is_master,
const BTDeviceInternal *device);
void fake_GAPAPI_put_encryption_change_event(bool encrypted, uint8_t status, bool is_master,
const BTDeviceInternal *device);
void fake_gap_le_put_cancel_create_event(const BTDeviceInternal *device, bool is_master);
void fake_GAPAPI_set_encrypted_for_device(const BTDeviceInternal *device);
const Encryption_Key_t *fake_GAPAPI_get_fake_irk(void);
const BD_ADDR_t *fake_GAPAPI_get_bd_addr_not_resolving_to_fake_irk(void);
const BTDeviceInternal *fake_GAPAPI_get_device_not_resolving_to_fake_irk(void);
const BD_ADDR_t *fake_GAPAPI_get_bd_addr_resolving_to_fake_irk(void);
const BTDeviceInternal *fake_GAPAPI_get_device_resolving_to_fake_irk(void);
void fake_GAPAPI_init(void);

191
tests/fakes/fake_GATTAPI.c Normal file
View file

@ -0,0 +1,191 @@
/*
* 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 "fake_GATTAPI.h"
#include "clar_asserts.h"
#include <string.h>
static GATT_Connection_Event_Callback_t s_connection_event_callback;
static unsigned int s_stack_id;
static unsigned long s_connection_callback_param;
static int s_start_count;
static int s_stop_count;
static int s_start_ret_val;
static int s_stop_ret_val;
static int s_service_changed_indication_count;
struct FakeGATTServiceDiscoveryContext {
bool is_running;
unsigned int stack_id;
unsigned int connection_id;
unsigned int num_of_uuids;
GATT_UUID_t *uuids;
GATT_Service_Discovery_Event_Callback_t callback;
unsigned long callback_param;
} s_service_discovery_ctx;
int GATT_Initialize(unsigned int BluetoothStackID,
unsigned long Flags,
GATT_Connection_Event_Callback_t ConnectionEventCallback,
unsigned long CallbackParameter) {
s_stack_id = BluetoothStackID;
s_connection_event_callback = ConnectionEventCallback;
s_connection_callback_param = CallbackParameter;
return 0;
}
int GATT_Cleanup(unsigned int BluetoothStackID) {
return 0;
}
int GATT_Start_Service_Discovery_Handle_Range(unsigned int stack_id,
unsigned int connection_id,
GATT_Attribute_Handle_Group_t *DiscoveryHandleRange,
unsigned int NumberOfUUID,
GATT_UUID_t *UUIDList,
GATT_Service_Discovery_Event_Callback_t ServiceDiscoveryCallback,
unsigned long CallbackParameter) {
s_service_discovery_ctx = (struct FakeGATTServiceDiscoveryContext) {
.is_running = true,
.stack_id = stack_id,
.connection_id = connection_id,
.num_of_uuids = NumberOfUUID,
.uuids = UUIDList,
.callback = ServiceDiscoveryCallback,
.callback_param = CallbackParameter
};
++s_start_count;
return s_start_ret_val;
}
int GATT_Stop_Service_Discovery(unsigned int BluetoothStackID, unsigned int ConnectionID) {
s_service_discovery_ctx.is_running = false;
++s_stop_count;
return s_stop_ret_val;
}
bool fake_gatt_is_service_discovery_running(void) {
return s_service_discovery_ctx.is_running;
}
int fake_gatt_is_service_discovery_start_count(void) {
return s_start_count;
}
int fake_gatt_is_service_discovery_stop_count(void) {
return s_stop_count;
}
void fake_gatt_set_start_return_value(int ret_value) {
s_start_ret_val = ret_value;
}
void fake_gatt_set_stop_return_value(int ret_value) {
s_stop_ret_val = ret_value;
}
void fake_gatt_put_service_discovery_event(GATT_Service_Discovery_Event_Data_t *event) {
cl_assert_equal_b(s_service_discovery_ctx.is_running, true);
if (event->Event_Data_Type == etGATT_Service_Discovery_Complete) {
s_service_discovery_ctx.is_running = false;
}
s_service_discovery_ctx.callback(s_service_discovery_ctx.stack_id,
event,
s_service_discovery_ctx.callback_param);
}
void fake_gatt_init(void) {
memset(&s_service_discovery_ctx, 0,
sizeof(struct FakeGATTServiceDiscoveryContext));
s_stack_id = 0;
s_connection_callback_param = 0;
s_connection_event_callback = NULL;
s_start_count = 0;
s_stop_count = 0;
s_start_ret_val = 0;
s_stop_ret_val = 0;
s_service_changed_indication_count = 0;
}
int GATT_Service_Changed_CCCD_Read_Response(unsigned int BluetoothStackID,
unsigned int TransactionID,
Word_t CCCD) {
return 0;
}
int GATT_Service_Changed_Indication(unsigned int BluetoothStackID,
unsigned int ConnectionID,
GATT_Service_Changed_Data_t *Service_Changed_Data) {
++s_service_changed_indication_count;
return 1; // fake transaction ID
}
int fake_gatt_get_service_changed_indication_count(void) {
return s_service_changed_indication_count;
}
int GATT_Service_Changed_Read_Response(unsigned int BluetoothStackID,
unsigned int TransactionID,
GATT_Service_Changed_Data_t *Service_Changed_Data) {
return 0;
}
static uint16_t s_write_request_length;
static GATT_Client_Event_Callback_t s_write_cb;
static unsigned long s_write_cb_param;
static unsigned int s_write_connection_id;
static unsigned int s_write_stack_id;
static uint16_t s_write_handle;
int GATT_Write_Request(unsigned int BluetoothStackID, unsigned int ConnectionID,
Word_t AttributeHandle, Word_t AttributeLength, void *AttributeValue,
GATT_Client_Event_Callback_t ClientEventCallback,
unsigned long CallbackParameter) {
s_write_handle = AttributeHandle;
s_write_request_length = AttributeLength;
s_write_cb = ClientEventCallback;
s_write_cb_param = CallbackParameter;
s_write_connection_id = ConnectionID;
s_write_stack_id = BluetoothStackID;
return 1;
}
uint16_t fake_gatt_write_last_written_handle(void) {
return s_write_handle;
}
void fake_gatt_put_write_response_for_last_write(void) {
cl_assert_(s_write_cb, "GATT_Write_Request need to be called first!");
GATT_Write_Response_Data_t data = {
.ConnectionID = s_write_connection_id,
.TransactionID = 1,
.ConnectionType = gctLE,
// .RemoteDevice // TODO
.BytesWritten = s_write_request_length,
};
GATT_Client_Event_Data_t event = {
.Event_Data_Type = etGATT_Client_Write_Response,
.Event_Data_Size = sizeof(GATT_Write_Response_Data_t),
.Event_Data.GATT_Write_Response_Data = &data,
};
s_write_cb(s_write_stack_id, &event, s_write_cb_param);
s_write_cb = NULL;
}

View file

@ -0,0 +1,48 @@
/*
* 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.
*/
#pragma once
#include "GATTAPI.h"
#include <stdbool.h>
#include <stdint.h>
bool fake_gatt_is_service_discovery_running(void);
//! @return Number of times GATT_Start_Service_Discovery has been called since fake_gatt_init()
int fake_gatt_is_service_discovery_start_count(void);
//! @return Number of times GATT_Stop_Service_Discovery has been called since fake_gatt_init()
int fake_gatt_is_service_discovery_stop_count(void);
//! Sets the value that the GATT_Start_Service_Discovery fake should return
//! @note fake_gatt_init() will reset this to 0
void fake_gatt_set_start_return_value(int ret_value);
//! Sets the value that the GATT_Stop_Service_Discovery fake should return
//! @note fake_gatt_init() will reset this to 0
void fake_gatt_set_stop_return_value(int ret_value);
int fake_gatt_get_service_changed_indication_count(void);
void fake_gatt_put_service_discovery_event(GATT_Service_Discovery_Event_Data_t *event);
uint16_t fake_gatt_write_last_written_handle(void);
void fake_gatt_put_write_response_for_last_write(void);
void fake_gatt_init(void);

View file

@ -0,0 +1,475 @@
/*
* 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 "fake_GATTAPI_test_vectors.h"
#include "fake_GATTAPI.h"
#include <btutil/bt_uuid.h>
void fake_gatt_put_discovery_complete_event(uint8_t status,
unsigned int connection_id) {
GATT_Service_Discovery_Complete_Data_t data =
(GATT_Service_Discovery_Complete_Data_t) {
.ConnectionID = connection_id,
.Status = status,
};
GATT_Service_Discovery_Event_Data_t event =
(GATT_Service_Discovery_Event_Data_t) {
.Event_Data_Type = etGATT_Service_Discovery_Complete,
.Event_Data_Size = GATT_SERVICE_DISCOVERY_COMPLETE_DATA_SIZE,
.Event_Data = {
.GATT_Service_Discovery_Complete_Data = &data,
},
};
fake_gatt_put_service_discovery_event(&event);
}
void fake_gatt_put_discovery_indication_health_thermometer_service(unsigned int connection_id) {
GATT_Characteristic_Descriptor_Information_t cccd1 = {
.Characteristic_Descriptor_Handle = 0x15,
.Characteristic_Descriptor_UUID = {
.UUID_Type = guUUID_16,
.UUID = {
.UUID_16 = {
.UUID_Byte0 = 0x02,
.UUID_Byte1 = 0x29,
},
},
}
};
GATT_Characteristic_Information_t characteristics[1] = {
[0] = {
.Characteristic_UUID = {
.UUID_Type = guUUID_16,
.UUID = {
.UUID_16 = {
.UUID_Byte0 = 0x1c,
.UUID_Byte1 = 0x2a,
},
},
},
.Characteristic_Handle = 0x13,
.Characteristic_Properties = 0x2,
.NumberOfDescriptors = 0x1,
.DescriptorList = &cccd1,
},
};
GATT_Service_Discovery_Indication_Data_t data = {
.ConnectionID = connection_id,
.ServiceInformation = {
.Service_Handle = 0x11,
.End_Group_Handle = 0x15,
.UUID = {
.UUID_Type = guUUID_16,
.UUID = {
.UUID_16 = {
.UUID_Byte0 = 0x09,
.UUID_Byte1 = 0x18,
},
},
}
},
.NumberOfCharacteristics = 0x1,
.CharacteristicInformationList = characteristics,
};
GATT_Service_Discovery_Event_Data_t event = {
.Event_Data_Type = etGATT_Service_Discovery_Indication,
.Event_Data_Size = GATT_SERVICE_DISCOVERY_INDICATION_DATA_SIZE,
.Event_Data = {
.GATT_Service_Discovery_Indication_Data = &data,
},
};
fake_gatt_put_service_discovery_event(&event);
}
static Service s_health_thermometer_service;
const Service * fake_gatt_get_health_thermometer_service(void) {
s_health_thermometer_service = (const Service) {
.uuid = bt_uuid_expand_16bit(0x1809),
.handle = 0x11,
.num_characteristics = 1,
.characteristics = {
[0] = {
.uuid = bt_uuid_expand_16bit(0x2a1c),
.properties = 0x02,
.handle = 0x13,
.num_descriptors = 1,
.descriptors = {
[0] = {
.uuid = bt_uuid_expand_16bit(0x2902),
.handle = 0x15,
},
},
},
}
};
return &s_health_thermometer_service;
}
void fake_gatt_put_discovery_indication_blood_pressure_service(
unsigned int connection_id) {
GATT_Characteristic_Descriptor_Information_t cccd1 = {
.Characteristic_Descriptor_Handle = 0x05,
.Characteristic_Descriptor_UUID = {
.UUID_Type = guUUID_16,
.UUID = {
.UUID_16 = {
.UUID_Byte0 = 0x02,
.UUID_Byte1 = 0x29,
},
},
}
};
GATT_Characteristic_Descriptor_Information_t cccd2 = {
.Characteristic_Descriptor_Handle = 0x09,
.Characteristic_Descriptor_UUID = {
.UUID_Type = guUUID_16,
.UUID = {
.UUID_16 = {
.UUID_Byte0 = 0x02,
.UUID_Byte1 = 0x29,
},
},
}
};
GATT_Characteristic_Information_t characteristics[2] = {
[0] = {
.Characteristic_UUID = {
.UUID_Type = guUUID_16,
.UUID = {
.UUID_16 = {
.UUID_Byte0 = 0x35,
.UUID_Byte1 = 0x2a,
},
},
},
.Characteristic_Handle = 0x3,
.Characteristic_Properties = 0x20,
.NumberOfDescriptors = 0x1,
.DescriptorList = &cccd1,
},
[1] = {
.Characteristic_UUID = {
.UUID_Type = guUUID_16,
.UUID = {
.UUID_16 = {
.UUID_Byte0 = 0x49,
.UUID_Byte1 = 0x2a,
},
},
},
.Characteristic_Handle = 0x7,
.Characteristic_Properties = 0x2,
.NumberOfDescriptors = 0x1,
.DescriptorList = &cccd2,
},
};
// Including Health Thermometer Service as "Included Service":
GATT_Service_Information_t inc_service_list = {
.Service_Handle = 0x11,
.End_Group_Handle = 0x15,
.UUID = {
.UUID_Type = guUUID_16,
.UUID = {
.UUID_16 = {
.UUID_Byte0 = 0x09,
.UUID_Byte1 = 0x18,
},
},
}
};
GATT_Service_Discovery_Indication_Data_t data = {
.ConnectionID = connection_id,
.ServiceInformation = {
.Service_Handle = 0x1,
.End_Group_Handle = 0x9,
.UUID = {
.UUID_Type = guUUID_16,
.UUID = {
.UUID_16 = {
.UUID_Byte0 = 0x10,
.UUID_Byte1 = 0x18,
},
},
}
},
.NumberOfIncludedService = 0x1,
.IncludedServiceList = &inc_service_list,
.NumberOfCharacteristics = 0x2,
.CharacteristicInformationList = characteristics,
};
GATT_Service_Discovery_Event_Data_t event = {
.Event_Data_Type = etGATT_Service_Discovery_Indication,
.Event_Data_Size = GATT_SERVICE_DISCOVERY_INDICATION_DATA_SIZE,
.Event_Data = {
.GATT_Service_Discovery_Indication_Data = &data,
},
};
fake_gatt_put_service_discovery_event(&event);
}
static Service s_blood_pressure_service;
#define BP_START_ATT_HANDLE 0x1
#define BP_END_ATT_HANDLE 0x9
const Service * fake_gatt_get_blood_pressure_service(void) {
s_blood_pressure_service = (const Service) {
.uuid = bt_uuid_expand_16bit(0x1810),
.handle = BP_START_ATT_HANDLE,
.num_characteristics = 2,
.characteristics = {
[0] = {
.uuid = bt_uuid_expand_16bit(0x2a35),
.properties = 0x20, // Indicatable
.handle = 0x3,
.num_descriptors = 1,
.descriptors = {
[0] = {
.uuid = bt_uuid_expand_16bit(0x2902),
.handle = 0x05,
},
},
},
[1] = {
.uuid = bt_uuid_expand_16bit(0x2a49),
.properties = 0x02,
.handle = 0x7,
.num_descriptors = 1,
.descriptors = {
[0] = {
.uuid = bt_uuid_expand_16bit(0x2902),
.handle = BP_END_ATT_HANDLE,
},
},
},
},
.num_included_services = 1,
.included_services = {
[0] = &s_health_thermometer_service,
}
};
return &s_blood_pressure_service;
}
void fake_gatt_get_bp_att_handle_range(uint16_t *start, uint16_t *end) {
*start = BP_START_ATT_HANDLE;
*end = BP_END_ATT_HANDLE;
}
static Service s_random_128bit_service;
#define RANDOM_S_START_ATT_HANDLE 0x17
#define RANDOM_S_END_ATT_HANDLE 0x25
void fake_gatt_put_discovery_indication_random_128bit_uuid_service(unsigned int connection_id) {
GATT_Characteristic_Descriptor_Information_t cccd1 = {
.Characteristic_Descriptor_Handle = 0x21,
.Characteristic_Descriptor_UUID = {
.UUID_Type = guUUID_128,
.UUID = {
.UUID_128 = { 0xB2, 0xF9, 0x66, 0xAC, 0xED, 0xFD, 0xEE, 0x97, 0x63, 0x4F, 0xFA, 0x1B, 0x5B, 0x09, 0x68, 0xF7 },
},
}
};
GATT_Characteristic_Descriptor_Information_t cccd2 = {
.Characteristic_Descriptor_Handle = RANDOM_S_END_ATT_HANDLE,
.Characteristic_Descriptor_UUID = {
.UUID_Type = guUUID_128,
.UUID = {
.UUID_128 = { 0xB4, 0xF9, 0x66, 0xAC, 0xED, 0xFD, 0xEE, 0x97, 0x63, 0x4F, 0xFA, 0x1B, 0x5B, 0x09, 0x68, 0xF7 },
},
}
};
GATT_Characteristic_Information_t characteristics[2] = {
[0] = {
.Characteristic_UUID = {
.UUID_Type = guUUID_128,
.UUID = {
.UUID_128 = { 0xB1, 0xF9, 0x66, 0xAC, 0xED, 0xFD, 0xEE, 0x97, 0x63, 0x4F, 0xFA, 0x1B, 0x5B, 0x09, 0x68, 0xF7 },
},
},
.Characteristic_Handle = 0x19,
.Characteristic_Properties = 0x2,
.NumberOfDescriptors = 0x1,
.DescriptorList = &cccd1,
},
[1] = {
.Characteristic_UUID = {
.UUID_Type = guUUID_128,
.UUID = {
.UUID_128 = { 0xB3, 0xF9, 0x66, 0xAC, 0xED, 0xFD, 0xEE, 0x97, 0x63, 0x4F, 0xFA, 0x1B, 0x5B, 0x09, 0x68, 0xF7 },
},
},
.Characteristic_Handle = 0x23,
.Characteristic_Properties = 0x2,
.NumberOfDescriptors = 0x1,
.DescriptorList = &cccd2,
},
};
GATT_Service_Discovery_Indication_Data_t data = {
.ConnectionID = connection_id,
.ServiceInformation = {
.Service_Handle = RANDOM_S_START_ATT_HANDLE,
.End_Group_Handle = 0x9,
.UUID = {
.UUID_Type = guUUID_128,
.UUID = {
.UUID_128 = { 0xB0, 0xF9, 0x66, 0xAC, 0xED, 0xFD, 0xEE, 0x97, 0x63, 0x4F, 0xFA, 0x1B, 0x5B, 0x09, 0x68, 0xF7 },
},
},
},
.NumberOfCharacteristics = 0x2,
.CharacteristicInformationList = characteristics,
};
GATT_Service_Discovery_Event_Data_t event = {
.Event_Data_Type = etGATT_Service_Discovery_Indication,
.Event_Data_Size = GATT_SERVICE_DISCOVERY_INDICATION_DATA_SIZE,
.Event_Data = {
.GATT_Service_Discovery_Indication_Data = &data,
},
};
fake_gatt_put_service_discovery_event(&event);
}
const Service * fake_gatt_get_random_128bit_uuid_service(void) {
s_random_128bit_service = (const Service) {
.uuid = UuidMake(0xF7, 0x68, 0x09, 0x5B, 0x1B, 0xFA, 0x4F, 0x63, 0x97, 0xEE, 0xFD, 0xED, 0xAC, 0x66, 0xF9, 0xB0),
.handle = 0x01,
.num_characteristics = 2,
.characteristics = {
[0] = {
.uuid = UuidMake(0xF7, 0x68, 0x09, 0x5B, 0x1B, 0xFA, 0x4F, 0x63, 0x97, 0xEE, 0xFD, 0xED, 0xAC, 0x66, 0xF9, 0xB1),
.properties = 0x02,
.handle = 0x3,
.num_descriptors = 1,
.descriptors = {
[0] = {
.uuid = UuidMake(0xF7, 0x68, 0x09, 0x5B, 0x1B, 0xFA, 0x4F, 0x63, 0x97, 0xEE, 0xFD, 0xED, 0xAC, 0x66, 0xF9, 0xB2),
.handle = 0x05,
},
},
},
[1] = {
.uuid = UuidMake(0xF7, 0x68, 0x09, 0x5B, 0x1B, 0xFA, 0x4F, 0x63, 0x97, 0xEE, 0xFD, 0xED, 0xAC, 0x66, 0xF9, 0xB3),
.properties = 0x02,
.handle = 0x7,
.num_descriptors = 1,
.descriptors = {
[0] = {
.uuid = UuidMake(0xF7, 0x68, 0x09, 0x5B, 0x1B, 0xFA, 0x4F, 0x63, 0x97, 0xEE, 0xFD, 0xED, 0xAC, 0x66, 0xF9, 0xB4),
.handle = 0x09,
},
},
},
},
};
return &s_random_128bit_service;
}
void fake_gatt_put_discovery_indication_gatt_profile_service(unsigned int connection_id,
bool has_service_changed_characteristic) {
GATT_Characteristic_Descriptor_Information_t cccd1 = {
.Characteristic_Descriptor_Handle = 0x05,
.Characteristic_Descriptor_UUID = {
.UUID_Type = guUUID_16,
.UUID = {
.UUID_16 = {
.UUID_Byte0 = 0x02,
.UUID_Byte1 = 0x29,
},
},
}
};
GATT_Characteristic_Information_t characteristics[1] = {
[0] = {
.Characteristic_UUID = {
.UUID_Type = guUUID_16,
.UUID = {
.UUID_16 = {
.UUID_Byte0 = 0x05,
.UUID_Byte1 = 0x2a,
},
},
},
.Characteristic_Handle = 0x3,
.Characteristic_Properties = 0x20,
.NumberOfDescriptors = 1,
.DescriptorList = &cccd1,
},
};
GATT_Service_Discovery_Indication_Data_t data = {
.ConnectionID = connection_id,
.ServiceInformation = {
.Service_Handle = 0x1,
.End_Group_Handle = 0x5,
.UUID = {
.UUID_Type = guUUID_16,
.UUID = {
.UUID_16 = {
.UUID_Byte0 = 0x01,
.UUID_Byte1 = 0x18,
},
},
}
},
.NumberOfIncludedService = 0,
.IncludedServiceList = NULL,
.NumberOfCharacteristics = has_service_changed_characteristic ? 1 : 0,
.CharacteristicInformationList = has_service_changed_characteristic ? characteristics : NULL,
};
GATT_Service_Discovery_Event_Data_t event = {
.Event_Data_Type = etGATT_Service_Discovery_Indication,
.Event_Data_Size = GATT_SERVICE_DISCOVERY_INDICATION_DATA_SIZE,
.Event_Data = {
.GATT_Service_Discovery_Indication_Data = &data,
},
};
fake_gatt_put_service_discovery_event(&event);
}
uint16_t fake_gatt_gatt_profile_service_service_changed_att_handle(void) {
return 3; // .Characteristic_Handle = 0x3,
}
uint16_t fake_gatt_gatt_profile_service_service_changed_cccd_att_handle(void) {
return 5; // .Characteristic_Descriptor_Handle = 0x05,
}

View file

@ -0,0 +1,101 @@
/*
* 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.
*/
#pragma once
#include "util/uuid.h"
#include <stdint.h>
// These structures are only used in the unit tests:
typedef struct {
Uuid uuid;
uint16_t handle;
} Descriptor;
typedef struct {
Uuid uuid;
uint8_t properties;
uint16_t handle;
uint8_t num_descriptors;
Descriptor descriptors[3];
} Characteristic;
typedef struct Service {
Uuid uuid;
uint16_t handle;
uint8_t num_characteristics;
Characteristic characteristics[3];
uint8_t num_included_services;
struct Service *included_services[2];
} Service;
//! Simulates receiving the Bluetopia service discovery complete event
void fake_gatt_put_discovery_complete_event(uint8_t status,
unsigned int connection_id);
// Health Thermometer Service 0x1809 : 0x11
// Temperature Measurement 0x2a1c : 0x13 (properties=0x02)
// CCCD 0x2902 : 0x15
//! Simulates receiving the Bluetopia service discovery indication event
void fake_gatt_put_discovery_indication_health_thermometer_service(
unsigned int connection_id);
//! Returns the Service data structure that can be used for reference
const Service * fake_gatt_get_health_thermometer_service(void);
// Blood Pressure Service 0x1810 : 0x01
// Pressure Characteristic 0x2a35 : 0x03 (properties=0x20)
// CCCD 0x2902 : 0x05
// Feature Characteristic 0x2a49 : 0x07 (properties=0x02)
// CCCD 0x2902 : 0x09
// Included Services : Points to the fake Health Thermometer Service
//! Simulates receiving the Bluetopia service discovery indication event
void fake_gatt_put_discovery_indication_blood_pressure_service(
unsigned int connection_id);
//! Returns the Service data structure that can be used for reference
const Service * fake_gatt_get_blood_pressure_service(void);
// Service F768095B-1BFA-4F63-97EE-FDEDAC66F9B0 : 0x17
// Char1 F768095B-1BFA-4F63-97EE-FDEDAC66F9B1 : 0x19 (properties=0x02)
// Desc1 F768095B-1BFA-4F63-97EE-FDEDAC66F9B2 : 0x21
// Char2 F768095B-1BFA-4F63-97EE-FDEDAC66F9B3 : 0x23 (properties=0x02)
// Desc2 F768095B-1BFA-4F63-97EE-FDEDAC66F9B4 : 0x25
void fake_gatt_put_discovery_indication_random_128bit_uuid_service(
unsigned int connection_id);
// Returns the starting ATT handle (Service, 0x1) and ending ATT handle (Desc2 0x09)
// for the BP service
void fake_gatt_get_bp_att_handle_range(uint16_t *start, uint16_t *end);
const Service * fake_gatt_get_random_128bit_uuid_service(void);
//! Simulates receiving the Bluetopia service discovery indication event
void fake_gatt_put_discovery_indication_gatt_profile_service(unsigned int connection_id,
bool has_service_changed_characteristic);
uint16_t fake_gatt_gatt_profile_service_service_changed_att_handle(void);
uint16_t fake_gatt_gatt_profile_service_service_changed_cccd_att_handle(void);

151
tests/fakes/fake_HCIAPI.c Normal file
View file

@ -0,0 +1,151 @@
/*
* 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 "fake_HCIAPI.h"
#include "bluetopia_interface.h"
#include "HCIAPI.h"
#include "util/list.h"
#include <stdlib.h>
typedef struct {
ListNode node;
Byte_t Address_Type;
BD_ADDR_t Address;
} WhitelistEntry;
static WhitelistEntry *s_head;
static uint32_t s_whitelist_error_count;
#define MAX_CC2564_WHITELIST_ENTRIES (25)
int HCI_LE_Read_Advertising_Channel_Tx_Power(unsigned int BluetoothStackID,
Byte_t *StatusResult,
Byte_t *Transmit_Power_LevelResult) {
*Transmit_Power_LevelResult = -55;
return 0;
}
static bool prv_whitelist_filter(ListNode *found_node, void *data) {
const WhitelistEntry *entry1 = (WhitelistEntry *) found_node;
const WhitelistEntry *entry2 = (WhitelistEntry *) data;
return COMPARE_BD_ADDR(entry1->Address, entry2->Address) &&
entry1->Address_Type == entry2->Address_Type;
}
static WhitelistEntry * prv_find_whitelist_entry(const WhitelistEntry *model) {
return (WhitelistEntry *) list_find(&s_head->node,
prv_whitelist_filter,
(void *) model);
}
int HCI_LE_Rand(unsigned int BluetoothStackID, Byte_t *StatusResult,
Random_Number_t *Random_NumberResult) {
uint8_t *data = (uint8_t *) Random_NumberResult;
for (int i = 0; i < sizeof(*Random_NumberResult); ++i) {
data[i] = i;
}
*StatusResult = 0;
return 0;
}
int HCI_LE_Add_Device_To_White_List(unsigned int BluetoothStackID,
Byte_t Address_Type,
BD_ADDR_t Address,
Byte_t *StatusResult) {
const uint32_t count = list_count(&s_head->node);
if (count > MAX_CC2564_WHITELIST_ENTRIES) {
++s_whitelist_error_count;
return -1;
}
const WhitelistEntry model = {
.Address_Type = Address_Type,
.Address = Address,
};
{
WhitelistEntry *e = prv_find_whitelist_entry(&model);
// Already present
if (e) {
++s_whitelist_error_count;
return -1;
}
}
WhitelistEntry *e = (WhitelistEntry *) malloc(sizeof(WhitelistEntry));
*e = (const WhitelistEntry) {
.Address_Type = Address_Type,
.Address = Address,
};
s_head = (WhitelistEntry *) list_prepend(&s_head->node, &e->node);
return 0;
}
int HCI_LE_Remove_Device_From_White_List(unsigned int BluetoothStackID,
Byte_t Address_Type,
BD_ADDR_t Address,
Byte_t *StatusResult) {
const WhitelistEntry model = {
.Address_Type = Address_Type,
.Address = Address,
};
WhitelistEntry *e = prv_find_whitelist_entry(&model);
if (e) {
list_remove(&e->node, (ListNode **) &s_head, NULL);
free(e);
return 0;
} else {
// Doesn't exist
++s_whitelist_error_count;
return -1;
}
}
bool fake_HCIAPI_whitelist_contains(const BTDeviceInternal *device) {
const WhitelistEntry model = {
.Address_Type = device->is_random_address ? 0x01 : 0x00,
.Address = BTDeviceAddressToBDADDR(device->address),
};
return (prv_find_whitelist_entry(&model) != NULL);
}
uint32_t fake_HCIAPI_whitelist_count(void) {
return list_count(&s_head->node);
}
uint32_t fake_HCIAPI_whitelist_error_count(void) {
return s_whitelist_error_count;
}
void fake_HCIAPI_deinit(void) {
WhitelistEntry *e = s_head;
while (e) {
WhitelistEntry *next = (WhitelistEntry *) e->node.next;
free(e);
e = next;
}
s_head = NULL;
s_whitelist_error_count = 0;
}
void cc2564A_advert_no_sleep_wa(void) {
}

30
tests/fakes/fake_HCIAPI.h Normal file
View file

@ -0,0 +1,30 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <bluetooth/bluetooth_types.h>
bool fake_HCIAPI_whitelist_contains(const BTDeviceInternal *device);
uint32_t fake_HCIAPI_whitelist_count(void);
uint32_t fake_HCIAPI_whitelist_error_count(void);
void fake_HCIAPI_deinit(void);

View file

@ -0,0 +1,109 @@
/*
* 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 "fake_accel_service.h"
#include <stdlib.h>
#include <time.h>
#include "applib/accel_service_private.h"
#include "system/passert.h"
static AccelDataHandler s_handler;
static AccelRawDataHandler s_raw_handler;
static uint32_t s_samples_per_update;
#define ACCEL_SESSION_REF ((AccelServiceState *)1)
void accel_data_service_subscribe(uint32_t samples_per_update, AccelDataHandler handler) {
PBL_ASSERTN(!s_raw_handler);
s_handler = handler;
s_samples_per_update = samples_per_update;
}
void accel_raw_data_service_subscribe(uint32_t samples_per_update, AccelRawDataHandler handler) {
PBL_ASSERTN(!s_handler);
s_raw_handler = handler;
s_samples_per_update = samples_per_update;
}
void accel_data_service_unsubscribe(void) {
s_handler = NULL;
s_raw_handler = NULL;
}
int accel_service_set_sampling_rate(AccelSamplingRate rate) {
return 0;
}
void fake_accel_service_invoke_callbacks(AccelData *data, uint32_t num_samples) {
if (s_handler) {
s_handler(data, num_samples);
}
if (s_raw_handler) {
AccelRawData raw_data[num_samples];
for (int i = 0; i < num_samples; i++) {
raw_data[i].x = data[i].x;
raw_data[i].y = data[i].y;
raw_data[i].z = data[i].z;
}
uint64_t timestamp = data[0].timestamp;
s_raw_handler(raw_data, num_samples, timestamp);
}
}
AccelServiceState * accel_session_create(void) {
return ACCEL_SESSION_REF;
}
void accel_session_delete(AccelServiceState *session) {
}
void accel_session_data_subscribe(AccelServiceState *session, uint32_t samples_per_update,
AccelDataHandler handler) {
s_handler = handler;
s_samples_per_update = samples_per_update;
}
void accel_session_raw_data_subscribe(
AccelServiceState *session, AccelSamplingRate sampling_rate, uint32_t samples_per_update,
AccelRawDataHandler handler) {
s_raw_handler = handler;
s_samples_per_update = samples_per_update;
}
void accel_session_data_unsubscribe(AccelServiceState *session) {
s_handler = NULL;
s_raw_handler = NULL;
}
int accel_session_set_sampling_rate(AccelServiceState *session, AccelSamplingRate rate) {
return 0;
}
int accel_session_set_samples_per_update(AccelServiceState *session, uint32_t samples_per_update) {
PBL_ASSERTN(session == ACCEL_SESSION_REF);
s_samples_per_update = samples_per_update;
return 0;
}

View file

@ -0,0 +1,24 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "applib/accel_service.h"
void fake_accel_service_invoke_callbacks(AccelData *data, uint32_t num_samples);

View file

@ -0,0 +1,57 @@
/*
* 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 "fake_accessory.h"
#include "clar_asserts.h"
#include "services/normal/accessory/smartstrap_comms.h"
#include <string.h>
#define BUFFER_LENGTH 200
static uint8_t s_buffer[BUFFER_LENGTH];
static int s_buffer_index = 0;
static bool s_did_send_byte = false;
void accessory_disable_input(void) {
}
void accessory_enable_input(void) {
}
void accessory_use_dma(bool use_dma) {
}
void accessory_send_byte(uint8_t data) {
cl_assert(s_buffer_index < BUFFER_LENGTH);
s_buffer[s_buffer_index++] = data;
s_did_send_byte = true;
}
void accessory_send_stream(AccessoryDataStreamCallback callback, void *context) {
s_buffer_index = 0;
memset(s_buffer, 0, BUFFER_LENGTH);
while (callback(context)) {
cl_assert(s_did_send_byte);
}
}
void fake_accessory_get_buffer(uint8_t **buffer, int *length) {
*buffer = s_buffer;
*length = s_buffer_index;
}

View file

@ -0,0 +1,32 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
void accessory_enable_input(void);
void accessory_disable_input(void);
void accessory_use_dma(bool use_dma);
void accessory_send_byte(uint8_t data);
typedef bool (*AccessoryDataStreamCallback)(void *context);
void accessory_send_stream(AccessoryDataStreamCallback callback, void *context);
void fake_accessory_get_buffer(uint8_t **buffer, int *length);

615
tests/fakes/fake_aes.c Normal file
View file

@ -0,0 +1,615 @@
/*
* 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 "aes.h"
//! @file fake_aes.c This file implements the aes_128_encrypt_block() helper in software, which in
//! real life uses Dialog's AES hardware block.
// Public-domain AES software implementation.
// From: https://github.com/kokke/tiny-AES128-C
#define ECB (1)
#define CBC (0)
/*
This is an implementation of the AES128 algorithm, specifically ECB and CBC mode.
The implementation is verified against the test vectors in:
National Institute of Standards and Technology Special Publication 800-38A 2001 ED
ECB-AES128
----------
plain-text:
6bc1bee22e409f96e93d7e117393172a
ae2d8a571e03ac9c9eb76fac45af8e51
30c81c46a35ce411e5fbc1191a0a52ef
f69f2445df4f9b17ad2b417be66c3710
key:
2b7e151628aed2a6abf7158809cf4f3c
resulting cipher
3ad77bb40d7a3660a89ecaf32466ef97
f5d3d58503b9699de785895a96fdbaaf
43b1cd7f598ece23881b00e3ed030688
7b0c785e27e8ad3f8223207104725dd4
NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0)
You should pad the end of the string with zeros if this is not the case.
*/
/*****************************************************************************/
/* Includes: */
/*****************************************************************************/
#include <stdint.h>
#include <string.h> // CBC mode, for memset
/*****************************************************************************/
/* Defines: */
/*****************************************************************************/
// The number of columns comprising a state in AES. This is a constant in AES. Value=4
#define Nb 4
// The number of 32 bit words in a key.
#define Nk 4
// Key length in bytes [128 bit]
#define KEYLEN 16
// The number of rounds in AES Cipher.
#define Nr 10
// jcallan@github points out that declaring Multiply as a function
// reduces code size considerably with the Keil ARM compiler.
// See this link for more information: https://github.com/kokke/tiny-AES128-C/pull/3
#ifndef MULTIPLY_AS_A_FUNCTION
#define MULTIPLY_AS_A_FUNCTION 0
#endif
/*****************************************************************************/
/* Private variables: */
/*****************************************************************************/
// state - array holding the intermediate results during decryption.
typedef uint8_t state_t[4][4];
static state_t* state;
// The array that stores the round keys.
static uint8_t RoundKey[176];
// The Key input to the AES Program
static const uint8_t* Key;
#if defined(CBC) && CBC
// Initial Vector used only for CBC mode
static uint8_t* Iv;
#endif
// The lookup-tables are marked const so they can be placed in read-only storage instead of RAM
// The numbers below can be computed dynamically trading ROM for RAM -
// This can be useful in (embedded) bootloader applications, where ROM is often limited.
static const uint8_t sbox[256] = {
//0 1 2 3 4 5 6 7 8 9 A B C D E F
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 };
static const uint8_t rsbox[256] =
{ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d };
// The round constant word array, Rcon[i], contains the values given by
// x to th e power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8)
// Note that i starts at 1, not 0).
static const uint8_t Rcon[255] = {
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39,
0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8,
0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef,
0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b,
0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20,
0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63,
0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb };
/*****************************************************************************/
/* Private functions: */
/*****************************************************************************/
static uint8_t getSBoxValue(uint8_t num)
{
return sbox[num];
}
static uint8_t getSBoxInvert(uint8_t num)
{
return rsbox[num];
}
// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states.
static void KeyExpansion(void)
{
uint32_t i, j, k;
uint8_t tempa[4]; // Used for the column/row operations
// The first round key is the key itself.
for(i = 0; i < Nk; ++i)
{
RoundKey[(i * 4) + 0] = Key[(i * 4) + 0];
RoundKey[(i * 4) + 1] = Key[(i * 4) + 1];
RoundKey[(i * 4) + 2] = Key[(i * 4) + 2];
RoundKey[(i * 4) + 3] = Key[(i * 4) + 3];
}
// All other round keys are found from the previous round keys.
for(; (i < (Nb * (Nr + 1))); ++i)
{
for(j = 0; j < 4; ++j)
{
tempa[j]=RoundKey[(i-1) * 4 + j];
}
if (i % Nk == 0)
{
// This function rotates the 4 bytes in a word to the left once.
// [a0,a1,a2,a3] becomes [a1,a2,a3,a0]
// Function RotWord()
{
k = tempa[0];
tempa[0] = tempa[1];
tempa[1] = tempa[2];
tempa[2] = tempa[3];
tempa[3] = k;
}
// SubWord() is a function that takes a four-byte input word and
// applies the S-box to each of the four bytes to produce an output word.
// Function Subword()
{
tempa[0] = getSBoxValue(tempa[0]);
tempa[1] = getSBoxValue(tempa[1]);
tempa[2] = getSBoxValue(tempa[2]);
tempa[3] = getSBoxValue(tempa[3]);
}
tempa[0] = tempa[0] ^ Rcon[i/Nk];
}
else if (Nk > 6 && i % Nk == 4)
{
// Function Subword()
{
tempa[0] = getSBoxValue(tempa[0]);
tempa[1] = getSBoxValue(tempa[1]);
tempa[2] = getSBoxValue(tempa[2]);
tempa[3] = getSBoxValue(tempa[3]);
}
}
RoundKey[i * 4 + 0] = RoundKey[(i - Nk) * 4 + 0] ^ tempa[0];
RoundKey[i * 4 + 1] = RoundKey[(i - Nk) * 4 + 1] ^ tempa[1];
RoundKey[i * 4 + 2] = RoundKey[(i - Nk) * 4 + 2] ^ tempa[2];
RoundKey[i * 4 + 3] = RoundKey[(i - Nk) * 4 + 3] ^ tempa[3];
}
}
// This function adds the round key to state.
// The round key is added to the state by an XOR function.
static void AddRoundKey(uint8_t round)
{
uint8_t i,j;
for(i=0;i<4;++i)
{
for(j = 0; j < 4; ++j)
{
(*state)[i][j] ^= RoundKey[round * Nb * 4 + i * Nb + j];
}
}
}
// The SubBytes Function Substitutes the values in the
// state matrix with values in an S-box.
static void SubBytes(void)
{
uint8_t i, j;
for(i = 0; i < 4; ++i)
{
for(j = 0; j < 4; ++j)
{
(*state)[j][i] = getSBoxValue((*state)[j][i]);
}
}
}
// The ShiftRows() function shifts the rows in the state to the left.
// Each row is shifted with different offset.
// Offset = Row number. So the first row is not shifted.
static void ShiftRows(void)
{
uint8_t temp;
// Rotate first row 1 columns to left
temp = (*state)[0][1];
(*state)[0][1] = (*state)[1][1];
(*state)[1][1] = (*state)[2][1];
(*state)[2][1] = (*state)[3][1];
(*state)[3][1] = temp;
// Rotate second row 2 columns to left
temp = (*state)[0][2];
(*state)[0][2] = (*state)[2][2];
(*state)[2][2] = temp;
temp = (*state)[1][2];
(*state)[1][2] = (*state)[3][2];
(*state)[3][2] = temp;
// Rotate third row 3 columns to left
temp = (*state)[0][3];
(*state)[0][3] = (*state)[3][3];
(*state)[3][3] = (*state)[2][3];
(*state)[2][3] = (*state)[1][3];
(*state)[1][3] = temp;
}
static uint8_t xtime(uint8_t x)
{
return ((x<<1) ^ (((x>>7) & 1) * 0x1b));
}
// MixColumns function mixes the columns of the state matrix
static void MixColumns(void)
{
uint8_t i;
uint8_t Tmp,Tm,t;
for(i = 0; i < 4; ++i)
{
t = (*state)[i][0];
Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3] ;
Tm = (*state)[i][0] ^ (*state)[i][1] ; Tm = xtime(Tm); (*state)[i][0] ^= Tm ^ Tmp ;
Tm = (*state)[i][1] ^ (*state)[i][2] ; Tm = xtime(Tm); (*state)[i][1] ^= Tm ^ Tmp ;
Tm = (*state)[i][2] ^ (*state)[i][3] ; Tm = xtime(Tm); (*state)[i][2] ^= Tm ^ Tmp ;
Tm = (*state)[i][3] ^ t ; Tm = xtime(Tm); (*state)[i][3] ^= Tm ^ Tmp ;
}
}
// Multiply is used to multiply numbers in the field GF(2^8)
#if MULTIPLY_AS_A_FUNCTION
static uint8_t Multiply(uint8_t x, uint8_t y)
{
return (((y & 1) * x) ^
((y>>1 & 1) * xtime(x)) ^
((y>>2 & 1) * xtime(xtime(x))) ^
((y>>3 & 1) * xtime(xtime(xtime(x)))) ^
((y>>4 & 1) * xtime(xtime(xtime(xtime(x))))));
}
#else
#define Multiply(x, y) \
( ((y & 1) * x) ^ \
((y>>1 & 1) * xtime(x)) ^ \
((y>>2 & 1) * xtime(xtime(x))) ^ \
((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ \
((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))) \
#endif
// MixColumns function mixes the columns of the state matrix.
// The method used to multiply may be difficult to understand for the inexperienced.
// Please use the references to gain more information.
static void InvMixColumns(void)
{
int i;
uint8_t a,b,c,d;
for(i=0;i<4;++i)
{
a = (*state)[i][0];
b = (*state)[i][1];
c = (*state)[i][2];
d = (*state)[i][3];
(*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09);
(*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d);
(*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b);
(*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e);
}
}
// The SubBytes Function Substitutes the values in the
// state matrix with values in an S-box.
static void InvSubBytes(void)
{
uint8_t i,j;
for(i=0;i<4;++i)
{
for(j=0;j<4;++j)
{
(*state)[j][i] = getSBoxInvert((*state)[j][i]);
}
}
}
static void InvShiftRows(void)
{
uint8_t temp;
// Rotate first row 1 columns to right
temp=(*state)[3][1];
(*state)[3][1]=(*state)[2][1];
(*state)[2][1]=(*state)[1][1];
(*state)[1][1]=(*state)[0][1];
(*state)[0][1]=temp;
// Rotate second row 2 columns to right
temp=(*state)[0][2];
(*state)[0][2]=(*state)[2][2];
(*state)[2][2]=temp;
temp=(*state)[1][2];
(*state)[1][2]=(*state)[3][2];
(*state)[3][2]=temp;
// Rotate third row 3 columns to right
temp=(*state)[0][3];
(*state)[0][3]=(*state)[1][3];
(*state)[1][3]=(*state)[2][3];
(*state)[2][3]=(*state)[3][3];
(*state)[3][3]=temp;
}
// Cipher is the main function that encrypts the PlainText.
static void Cipher(void)
{
uint8_t round = 0;
// Add the First round key to the state before starting the rounds.
AddRoundKey(0);
// There will be Nr rounds.
// The first Nr-1 rounds are identical.
// These Nr-1 rounds are executed in the loop below.
for(round = 1; round < Nr; ++round)
{
SubBytes();
ShiftRows();
MixColumns();
AddRoundKey(round);
}
// The last round is given below.
// The MixColumns function is not here in the last round.
SubBytes();
ShiftRows();
AddRoundKey(Nr);
}
static void InvCipher(void)
{
uint8_t round=0;
// Add the First round key to the state before starting the rounds.
AddRoundKey(Nr);
// There will be Nr rounds.
// The first Nr-1 rounds are identical.
// These Nr-1 rounds are executed in the loop below.
for(round=Nr-1;round>0;round--)
{
InvShiftRows();
InvSubBytes();
AddRoundKey(round);
InvMixColumns();
}
// The last round is given below.
// The MixColumns function is not here in the last round.
InvShiftRows();
InvSubBytes();
AddRoundKey(0);
}
static void BlockCopy(uint8_t* output, const uint8_t* input)
{
uint8_t i;
for (i=0;i<KEYLEN;++i)
{
output[i] = input[i];
}
}
/*****************************************************************************/
/* Public functions: */
/*****************************************************************************/
#if defined(ECB) && ECB
void AES128_ECB_encrypt(const uint8_t* input, const uint8_t* key, uint8_t* output)
{
// Copy input to output, and work in-memory on output
BlockCopy(output, input);
state = (state_t*)output;
Key = key;
KeyExpansion();
// The next function call encrypts the PlainText with the Key using AES algorithm.
Cipher();
}
void AES128_ECB_decrypt(const uint8_t* input, const uint8_t* key, uint8_t *output)
{
// Copy input to output, and work in-memory on output
BlockCopy(output, input);
state = (state_t*)output;
// The KeyExpansion routine must be called before encryption.
Key = key;
KeyExpansion();
InvCipher();
}
#endif // #if defined(ECB) && ECB
#if defined(CBC) && CBC
static void XorWithIv(uint8_t* buf)
{
uint8_t i;
for(i = 0; i < KEYLEN; ++i)
{
buf[i] ^= Iv[i];
}
}
void AES128_CBC_encrypt_buffer(uint8_t* output, uint8_t* input, uint32_t length, const uint8_t* key, const uint8_t* iv)
{
uintptr_t i;
uint8_t remainders = length % KEYLEN; /* Remaining bytes in the last non-full block */
BlockCopy(output, input);
state = (state_t*)output;
// Skip the key expansion if key is passed as 0
if(0 != key)
{
Key = key;
KeyExpansion();
}
if(iv != 0)
{
Iv = (uint8_t*)iv;
}
for(i = 0; i < length; i += KEYLEN)
{
XorWithIv(input);
BlockCopy(output, input);
state = (state_t*)output;
Cipher();
Iv = output;
input += KEYLEN;
output += KEYLEN;
}
if(remainders)
{
BlockCopy(output, input);
memset(output + remainders, 0, KEYLEN - remainders); /* add 0-padding */
state = (state_t*)output;
Cipher();
}
}
void AES128_CBC_decrypt_buffer(uint8_t* output, uint8_t* input, uint32_t length, const uint8_t* key, const uint8_t* iv)
{
uintptr_t i;
uint8_t remainders = length % KEYLEN; /* Remaining bytes in the last non-full block */
BlockCopy(output, input);
state = (state_t*)output;
// Skip the key expansion if key is passed as 0
if(0 != key)
{
Key = key;
KeyExpansion();
}
// If iv is passed as 0, we continue to encrypt without re-setting the Iv
if(iv != 0)
{
Iv = (uint8_t*)iv;
}
for(i = 0; i < length; i += KEYLEN)
{
BlockCopy(output, input);
state = (state_t*)output;
InvCipher();
XorWithIv(output);
Iv = input;
input += KEYLEN;
output += KEYLEN;
}
if(remainders)
{
BlockCopy(output, input);
memset(output+remainders, 0, KEYLEN - remainders); /* add 0-padding */
state = (state_t*)output;
InvCipher();
}
}
#endif // #if defined(CBC) && CBC
bool aes_128_encrypt_block(const uint8_t key[AES_128_KEY_SIZE],
const uint8_t plain_text_block[AES_128_BLOCK_SIZE],
uint8_t cipher_text_block_out[AES_128_BLOCK_SIZE]) {
AES128_ECB_encrypt(plain_text_block, key, cipher_text_block_out);
return true;
}

View file

@ -0,0 +1,274 @@
/*
* 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.
*/
// Need to use the real struct because the product code accesses the structure directly. It would
// be nice to instead create a dummy with only the fields we need, but oh well.
#include "applib/ui/animation_private.h"
#include <stdarg.h>
//! List of all animations that were created in the current test in order of creation.
ListNode *s_animations;
// Fake implementations of the real animation interface.
/////////////////////////////////////////////////////////////
Animation *animation_create(void) {
AnimationPrivate *animation = malloc(sizeof(AnimationPrivate));
*animation = (AnimationPrivate) {};
if (!s_animations) {
s_animations = (ListNode *)animation;
} else {
list_append(s_animations, (ListNode *)animation);
}
return (Animation *)animation;
}
static Animation *prv_create_from_array(Animation **animation_array, size_t array_len) {
AnimationPrivate *parent = (AnimationPrivate *)animation_create();
parent->first_child = (AnimationPrivate *)animation_array[0];
for (int i = 0; i < (int)array_len; i++) {
AnimationPrivate *child = (AnimationPrivate *)animation_array[i];
child->parent = parent;
if (i + 1 < (int)array_len) {
child->sibling = (AnimationPrivate *)animation_array[i + 1];
}
}
return (Animation *)parent;
}
static Animation *prv_create_from_vararg(Animation *animation_a, Animation *animation_b,
Animation *animation_c, va_list args) {
Animation *animation_array[ANIMATION_MAX_CREATE_VARGS];
size_t array_len = 0;
animation_array[array_len++] = animation_a;
animation_array[array_len++] = animation_b;
if (animation_c) {
animation_array[array_len++] = animation_c;
while (array_len < ANIMATION_MAX_CREATE_VARGS) {
void *arg = va_arg(args, void *);
if (arg == NULL) {
break;
}
animation_array[array_len++] = arg;
}
}
return prv_create_from_array(animation_array, array_len);
}
Animation *WEAK animation_sequence_create(Animation *animation_a, Animation *animation_b,
Animation *animation_c, ...) {
va_list args;
va_start(args, animation_c);
Animation *animation = prv_create_from_vararg(animation_a, animation_b, animation_c, args);
va_end(args);
return animation;
}
Animation *WEAK animation_sequence_create_from_array(Animation **animation_array,
uint32_t array_len) {
return prv_create_from_array(animation_array, array_len);
}
Animation *WEAK animation_spawn_create(Animation *animation_a, Animation *animation_b,
Animation *animation_c, ...) {
va_list args;
va_start(args, animation_c);
Animation *animation = prv_create_from_vararg(animation_a, animation_b, animation_c, args);
va_end(args);
return animation;
}
Animation *WEAK animation_spawn_create_from_array(Animation **animation_array,
uint32_t array_len) {
return prv_create_from_array(animation_array, array_len);
}
typedef void (*AnimationEachCallback)(AnimationPrivate *animation, uintptr_t context);
static void prv_each(AnimationPrivate *animation, AnimationEachCallback callback,
uintptr_t context) {
if (animation->first_child) {
prv_each(animation->first_child, callback, context);
}
if (animation->sibling) {
prv_each(animation->sibling, callback, context);
}
callback(animation, context);
}
static void prv_free(AnimationPrivate *animation, uintptr_t context) {
list_remove(&animation->list_node, NULL, NULL);
free(animation);
}
bool animation_destroy(Animation *animation) {
prv_each((AnimationPrivate *)animation, prv_free, (uintptr_t)NULL);
return true;
}
bool animation_set_implementation(Animation *animation_h,
const AnimationImplementation *implementation) {
AnimationPrivate *animation = (AnimationPrivate *)animation_h;
if (!animation) {
return false;
}
animation->implementation = implementation;
return true;
}
bool animation_is_scheduled(Animation *animation_h) {
AnimationPrivate *animation = (AnimationPrivate *)animation_h;
if (!animation) {
return false;
}
return animation->scheduled;
}
uint32_t animation_get_duration(Animation *animation, bool include_delay, bool include_play_count) {
if (!animation) {
return 0;
}
return ((AnimationPrivate *)animation)->duration_ms;
}
static void prv_call_started(AnimationPrivate *animation, uintptr_t UNUSED context) {
if (animation->implementation && animation->implementation->setup) {
animation->implementation->setup((Animation *)animation);
}
if (animation->handlers.started) {
animation->handlers.started((Animation *)animation, animation->context);
}
}
static void prv_call_scheduled(AnimationPrivate *animation, uintptr_t scheduled) {
animation->scheduled = scheduled;
}
bool animation_schedule(Animation *animation_h) {
AnimationPrivate *animation = (AnimationPrivate *)animation_h;
if (!animation) {
return false;
}
if (!animation->scheduled) {
prv_each(animation, prv_call_scheduled, true);
// If your test is failing, build out this fake so that this is an async start
prv_each(animation, prv_call_started, (uintptr_t)NULL);
}
return true;
}
bool animation_set_elapsed(Animation *animation_h, uint32_t elapsed_ms) {
AnimationPrivate *animation = (AnimationPrivate *)animation_h;
if (!animation) {
return false;
}
if (animation->duration_ms <= elapsed_ms) {
animation->is_completed = true;
animation_unschedule(animation_h);
}
return true;
}
bool animation_get_elapsed(Animation *animation_h, int32_t *elapsed_ms) {
AnimationPrivate *animation= (AnimationPrivate *)animation_h;
if (!animation) {
return false;
}
if (elapsed_ms) {
*elapsed_ms = animation->is_completed ? animation->duration_ms : 0;
}
return true;
}
bool animation_set_handlers(Animation *animation_h, AnimationHandlers callbacks, void *context) {
AnimationPrivate *animation = (AnimationPrivate *)animation_h;
if (!animation) {
return false;
}
animation->handlers = callbacks;
animation->context = context;
return true;
}
void *animation_get_context(Animation *animation_h) {
AnimationPrivate *animation = (AnimationPrivate *)animation_h;
if (!animation) {
return false;
}
return animation->context;
}
static void prv_call_update(AnimationPrivate *animation, uintptr_t progress) {
if (animation->implementation && animation->implementation->update) {
animation->implementation->update((Animation *)animation, progress);
}
}
static void prv_call_stopped(AnimationPrivate *animation, uintptr_t finished) {
if (animation->handlers.stopped) {
animation->handlers.stopped((Animation *)animation, finished, animation->context);
}
if (animation->implementation && animation->implementation->teardown) {
animation->implementation->teardown((Animation *)animation);
}
}
bool animation_unschedule(Animation *animation_h) {
AnimationPrivate *animation = (AnimationPrivate *)animation_h;
if (!animation) {
return false;
}
if (animation->scheduled) {
prv_each(animation, prv_call_scheduled, false);
prv_each(animation, prv_call_update, ANIMATION_NORMALIZED_MAX);
prv_each(animation, prv_call_stopped, animation->is_completed);
}
return true;
}
// Interface for unit tests to query the fake animation state
/////////////////////////////////////////////////////////////
Animation *fake_animation_get_first_animation(void) {
return (Animation *)s_animations;
}
Animation *fake_animation_get_next_animation(Animation *animation) {
return (Animation *)((AnimationPrivate *)animation)->list_node.next;
}
void fake_animation_cleanup(void) {
ListNode *iter = s_animations;
while (iter) {
ListNode *current = iter;
iter = iter->next;
free(current);
}
s_animations = NULL;
}
void fake_animation_complete(Animation *animation) {
animation_schedule(animation);
const uint32_t duration =
animation_get_duration(animation, false /* delay */, true /* play_count */);
animation_set_elapsed(animation, duration);
animation_unschedule(animation);
}

View file

@ -0,0 +1,39 @@
/*
* 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.
*/
//! @file fake_animation.h
//!
//! Simple fake of the Animation code. Not intended to be a complete drop in replacement, but good
//! enough for some simple tests.
#include "applib/ui/animation_private.h"
//! @return A pointer to the first animation that was created since we last called
//! fake_animation_cleanup
Animation * fake_animation_get_first_animation(void);
//! @return The next animation after the supplied animation. Animations form a link list based on
//! creation time, and that list can be walked by combining this function with
//! fake_animation_get_first_animation
Animation * fake_animation_get_next_animation(Animation *animation);
//! Cleans up all fake animation state. Use between tests to ensure a clean slate.
void fake_animation_cleanup(void);
//! Runs an animation to completion by scheduling it, setting its elapsed to its duration, and then
//! unscheduling it.
//! @param animation The animation to run to completion.
void fake_animation_complete(Animation *animation);

View file

@ -0,0 +1,25 @@
/*
* 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.
*/
#pragma once
void* app_malloc(size_t bytes) {
return malloc(bytes);
}
void app_free(void* ptr) {
free(ptr);
}

View file

@ -0,0 +1,147 @@
/*
* 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.
*/
#pragma once
#include "process_management/pebble_process_md.h"
#include "process_management/app_manager.h"
#include "system/passert.h"
#include "util/list.h"
#include <string.h>
#define TEST_UUID UuidMake(0xF9, 0xC6, 0xEB, 0xE4, 0x06, 0xCD, 0x46, 0xF1, 0xB1, 0x51, 0x24, 0x08, 0x74, 0xD2, 0x07, 0x73)
static bool s_is_app_running = true;
static AppInstallId s_app_install_id = INSTALL_ID_INVALID;
static PebbleProcessMdSystem s_app_md = {
.common.uuid = TEST_UUID,
.name = "Test App"
};
const PebbleProcessMd* app_manager_get_current_app_md(void) {
if (s_is_app_running) {
return (const PebbleProcessMd*)&s_app_md;
} else {
static PebbleProcessMd s_invalid_md = {
.uuid = UUID_INVALID,
};
return &s_invalid_md;
}
}
AppInstallId app_manager_get_current_app_id(void) {
return s_is_app_running ? s_app_install_id : INSTALL_ID_INVALID;
}
const PebbleProcessMd* sys_process_manager_get_current_process_md(void) {
return app_manager_get_current_app_md();
}
bool sys_process_manager_get_current_process_uuid(Uuid *uuid_out) {
if (!s_is_app_running) {
return false;
}
*uuid_out = app_manager_get_current_app_md()->uuid;
return true;
}
ResAppNum app_manager_get_current_resource_num(void) {
return 1;
}
static ProcessContext s_app_task_context;
ProcessContext* app_manager_get_task_context(void) {
return &s_app_task_context;
}
typedef struct {
ListNode node;
void (*callback)(void *);
void *data;
} CallbackNode;
static ListNode *s_app_task_callback_head = NULL;
void app_task_add_callback(void (*callback)(void *data), void *data) {
CallbackNode *node = (CallbackNode *) malloc(sizeof(CallbackNode));
list_init(&node->node);
node->callback = callback;
node->data = data;
s_app_task_callback_head = list_prepend(s_app_task_callback_head, &node->node);
}
AppInstallId app_install_get_id_for_uuid(const Uuid *uuid) {
return s_app_install_id;
}
void app_install_set_is_communicating(const AppInstallId install_id, const bool is_communicating) {
}
////////////////////////////////////
// Stub manipulation:
//
void stub_app_set_js(bool is_js) {
((PebbleProcessMd *) &s_app_md)->allow_js = is_js;
}
void stub_app_set_uuid(Uuid uuid) {
s_app_md.common.uuid = uuid;
}
void stub_app_set_install_id(AppInstallId install_id) {
s_app_install_id = install_id;
}
void stub_app_task_callbacks_invoke_pending(void) {
// Start at tail ("oldest" callback):
CallbackNode *node = (CallbackNode *) list_get_tail(s_app_task_callback_head);
while (node) {
CallbackNode *prev = (CallbackNode *) list_get_prev(&node->node);
if (node->callback) {
node->callback(node->data);
}
list_remove(&node->node, &s_app_task_callback_head, NULL);
free(node);
node = prev;
}
PBL_ASSERTN(s_app_task_callback_head == NULL);
}
void stub_app_task_callbacks_cleanup(void) {
CallbackNode *node = (CallbackNode *) s_app_task_callback_head;
while (node) {
CallbackNode *next = (CallbackNode *) list_get_next(&node->node);
list_remove(&node->node, &s_app_task_callback_head, NULL);
free(node);
node = next;
}
s_app_install_id = INSTALL_ID_INVALID;
PBL_ASSERTN(s_app_task_callback_head == NULL);
}
void stub_app_set_is_running(const bool is_running) {
s_is_app_running = is_running;
}
void stub_app_init(void) {
stub_app_set_uuid(TEST_UUID);
stub_app_set_is_running(true);
}

View file

@ -0,0 +1,31 @@
/*
* 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 "fake_graphics_context.h"
#include "applib/ui/content_indicator.h"
#include "kernel/ui/kernel_ui.h"
#include "stubs_app_state.h"
void fake_app_state_init(void) {
// Setup graphics context
fake_graphics_context_init();
s_app_state_framebuffer = fake_graphics_context_get_framebuffer();
// Setup content indicator
ContentIndicatorsBuffer *buffer = content_indicator_get_current_buffer();
content_indicator_init_buffer(buffer);
}

View file

@ -0,0 +1,162 @@
/*
* 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.
*/
#pragma once
#include "applib/app_timer.h"
#include "system/logging.h"
#include <string.h>
typedef struct FakeAppTimer {
uint32_t timeout_ms;
bool repeating;
AppTimerCallback callback;
void* callback_data;
struct FakeAppTimer *next;
uint32_t timer_id;
} FakeAppTimer;
static FakeAppTimer *s_fake_app_timer_head;
static uint32_t s_fake_app_timer_next_id;
static FakeAppTimer *prv_find_fake_app_timer_by_timer_id(uint32_t timer_id) {
for (FakeAppTimer *node = s_fake_app_timer_head; node != NULL; node = node->next) {
if (node->timer_id == timer_id) {
return node;
}
}
return NULL;
}
void fake_app_timer_init(void) {
s_fake_app_timer_head = NULL;
s_fake_app_timer_next_id = 0;
}
void fake_app_timer_deinit(void) {
FakeAppTimer *timer = s_fake_app_timer_head;
while (timer) {
FakeAppTimer *next = timer->next;
app_timer_cancel((AppTimer *)timer);
timer = next;
}
}
uint32_t fake_app_timer_get_timeout(AppTimer *timer) {
FakeAppTimer *fake_timer = prv_find_fake_app_timer_by_timer_id((uintptr_t)timer);
if (fake_timer) {
return fake_timer->timeout_ms;
}
return 0;
}
bool fake_app_timer_is_scheduled(AppTimer *timer) {
return (prv_find_fake_app_timer_by_timer_id((uintptr_t)timer) != NULL);
}
AppTimer* app_timer_register(uint32_t timeout_ms, AppTimerCallback callback, void* callback_data) {
FakeAppTimer *fake_timer = malloc(sizeof(FakeAppTimer));
*fake_timer = (FakeAppTimer) {
.timeout_ms = timeout_ms,
.callback = callback,
.callback_data = callback_data,
.next = s_fake_app_timer_head,
.timer_id = ++s_fake_app_timer_next_id,
};
s_fake_app_timer_head = fake_timer;
return (AppTimer *)(uintptr_t)fake_timer->timer_id;
}
AppTimer* app_timer_register_repeatable(uint32_t timeout_ms,
AppTimerCallback callback,
void* callback_data,
bool repeating) {
FakeAppTimer *fake_timer = malloc(sizeof(FakeAppTimer));
*fake_timer = (FakeAppTimer) {
.timeout_ms = timeout_ms,
.repeating = repeating,
.callback = callback,
.callback_data = callback_data,
.next = s_fake_app_timer_head,
.timer_id = ++s_fake_app_timer_next_id,
};
s_fake_app_timer_head = fake_timer;
return (AppTimer *)(uintptr_t)fake_timer->timer_id;
}
bool app_timer_reschedule(AppTimer *timer, uint32_t new_timeout_ms) {
FakeAppTimer *fake_timer = prv_find_fake_app_timer_by_timer_id((uintptr_t)timer);
if (fake_timer) {
fake_timer->timeout_ms = new_timeout_ms;
return true;
}
return false;
}
static void prv_unlink_and_free_timer(FakeAppTimer *timer) {
FakeAppTimer *prev_timer = s_fake_app_timer_head;
if (timer == s_fake_app_timer_head) {
// The timer is the head
s_fake_app_timer_head = timer->next;
} else {
// Not the head, find the previous one:
while (prev_timer && prev_timer != timer &&
prev_timer->next && prev_timer->next != timer) {
prev_timer = prev_timer->next;
}
if (!prev_timer) {
PBL_LOG(LOG_LEVEL_ERROR, "Tried to unlink and free non-existing timer %p", timer);
return;
}
prev_timer->next = timer->next;
}
free(timer);
}
void app_timer_cancel(AppTimer *timer) {
FakeAppTimer *fake_timer = prv_find_fake_app_timer_by_timer_id((uintptr_t)timer);
if (fake_timer) {
prv_unlink_and_free_timer(fake_timer);
}
}
bool app_timer_trigger(AppTimer *timer) {
FakeAppTimer *fake_timer = prv_find_fake_app_timer_by_timer_id((uintptr_t)timer);
if (fake_timer) {
AppTimerCallback callback = fake_timer->callback;
void *data = fake_timer->callback_data;
if (!fake_timer->repeating) {
prv_unlink_and_free_timer(fake_timer);
}
callback(data);
return true;
}
return false;
}
void *app_timer_get_data(AppTimer *timer) {
FakeAppTimer *fake_timer = prv_find_fake_app_timer_by_timer_id((uintptr_t)timer);
if (fake_timer) {
return fake_timer->callback_data;
} else {
return NULL;
}
}

View file

@ -0,0 +1,50 @@
/*
* 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 "applib/applib_resource_private.h"
#include "fake_resource_syscalls.h"
bool applib_resource_track_mmapped(const void *bytes) {
return false;
}
bool applib_resource_is_mmapped(const void *bytes) {
return false;
}
bool applib_resource_munmap(const void *bytes) {
return false;
}
bool applib_resource_munmap_all() {
return false;
}
void applib_resource_munmap_or_free(void *bytes) {
free(bytes);
}
void *applib_resource_mmap_or_load(ResAppNum app_num, uint32_t resource_id,
size_t offset, size_t num_bytes, bool used_aligned) {
uint8_t *result = malloc(num_bytes + (used_aligned ? 7 : 0));
if (!result || sys_resource_load_range(app_num, resource_id, offset,
result, num_bytes) != num_bytes) {
free(result);
return NULL;
}
return result;
}

View file

@ -0,0 +1,62 @@
/*
* 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 "fake_battery.h"
#include "kernel/events.h"
static int s_millivolts = 0;
static bool s_usb_connected = false;
static bool s_charging = false;
void fake_battery_init(int millivolts, bool usb_connected, bool charging) {
s_millivolts = millivolts;
s_usb_connected = usb_connected;
s_charging = charging;
}
void fake_battery_set_millivolts(int millivolts) {
s_millivolts = millivolts;
}
void fake_battery_set_connected(bool usb_connected) {
s_usb_connected = usb_connected;
// Trigger a connection event!
PebbleEvent event = {
.type = PEBBLE_BATTERY_CONNECTION_EVENT,
.battery_connection = {
.is_connected = usb_connected,
}
};
event_put(&event);
}
void fake_battery_set_charging(bool charging) {
s_charging = charging;
}
int battery_get_millivolts(void) {
return s_millivolts;
}
bool battery_is_usb_connected(void) {
return s_usb_connected;
}
bool battery_charge_controller_thinks_we_are_charging(void) {
return s_charging;
}

View file

@ -0,0 +1,27 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
void fake_battery_init(int millivolts, bool usb_connected, bool charging);
void fake_battery_set_millivolts(int millivolts);
void fake_battery_set_charging(bool charging);
void fake_battery_set_connected(bool usb_connected);

83
tests/fakes/fake_blobdb.c Normal file
View file

@ -0,0 +1,83 @@
/*
* 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_asserts.h"
#include <stddef.h>
#include <stdbool.h>
#include "test_db.h"
#include "services/normal/blob_db/api.h"
static BlobDBId s_blobdb_id = BlobDBIdTest;
void fake_blob_db_set_id(BlobDBId id) {
s_blobdb_id = id;
}
void blob_db_init_dbs(void) {
test_db_init();
}
void blob_db_get_dirty_dbs(uint8_t *ids, uint8_t *num_ids) {
bool is_dirty = false;
test_db_is_dirty(&is_dirty);
if (is_dirty) {
ids[0] = s_blobdb_id;
*num_ids = 1;
} else {
*num_ids = 0;
}
}
status_t blob_db_insert(BlobDBId db_id,
const uint8_t *key, int key_len, const uint8_t *val, int val_len) {
cl_assert(db_id == s_blobdb_id);
return test_db_insert(key, key_len, val, val_len);
}
int blob_db_get_len(BlobDBId db_id,
const uint8_t *key, int key_len) {
cl_assert(db_id == s_blobdb_id);
return test_db_get_len(key, key_len);
}
status_t blob_db_read(BlobDBId db_id,
const uint8_t *key, int key_len, uint8_t *val_out, int val_len) {
cl_assert(db_id == s_blobdb_id);
return test_db_read(key, key_len, val_out, val_len);
}
status_t blob_db_delete(BlobDBId db_id,
const uint8_t *key, int key_len) {
cl_assert(db_id == s_blobdb_id);
return test_db_delete(key, key_len);
}
status_t blob_db_flush(BlobDBId db_id) {
cl_assert(db_id == s_blobdb_id);
return test_db_flush();
}
BlobDBDirtyItem *blob_db_get_dirty_list(BlobDBId db_id) {
cl_assert(db_id == s_blobdb_id);
return test_db_get_dirty_list();
}
status_t blob_db_mark_synced(BlobDBId db_id, uint8_t *key, int key_len) {
cl_assert(db_id == s_blobdb_id);
return test_db_mark_synced(key, key_len);
}

21
tests/fakes/fake_blobdb.h Normal file
View file

@ -0,0 +1,21 @@
/*
* 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.
*/
#pragma once
#include "services/normal/blob_db/api.h"
void fake_blob_db_set_id(BlobDBId id);

View file

@ -0,0 +1,120 @@
/*
* 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 "fake_bluetooth_persistent_storage.h"
#include "util/list.h"
#include <btutil/bt_device.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
ListNode node;
BTBondingID id;
SMIdentityResolvingKey irk;
bool is_public_address;
BTDeviceInternal device;
char name[BT_DEVICE_NAME_BUFFER_SIZE];
bool is_gateway;
} FakeBonding;
static FakeBonding *s_head;
static BTBondingID s_next_id = 1;
bool bt_persistent_storage_is_gateway(const BTBondingID bonding) {
return true;
}
static BTBondingID prv_next_id(void) {
return s_next_id++;
}
static bool prv_find_by_id(ListNode *found_node, void *data) {
BTBondingID bonding_id = (BTBondingID) data;
const FakeBonding *bonding = (const FakeBonding *) found_node;
return (bonding->id == bonding_id);
}
bool bt_persistent_storage_get_ble_pairing_by_id(BTBondingID id,
SMIdentityResolvingKey *IRK_out,
BTDeviceInternal *device_out,
char name[BT_DEVICE_NAME_BUFFER_SIZE]) {
FakeBonding *bonding = (FakeBonding *) list_find(&s_head->node, prv_find_by_id,
(void *) (uintptr_t) id);
if (!bonding) {
return false;
}
if (IRK_out) {
*IRK_out = bonding->irk;
}
if (device_out) {
*device_out = bonding->device;
}
if (name) {
strncpy(name, bonding->name, BT_DEVICE_NAME_BUFFER_SIZE);
}
return true;
}
BTBondingID fake_bt_persistent_storage_add(const SMIdentityResolvingKey *irk,
const BTDeviceInternal *device,
const char name[BT_DEVICE_NAME_BUFFER_SIZE],
bool is_gateway) {
FakeBonding *bonding = (FakeBonding *) malloc(sizeof(FakeBonding));
*bonding = (const FakeBonding) {
.id = prv_next_id(),
.irk = *irk,
.device = *device,
.is_gateway = is_gateway,
};
strncpy(bonding->name, name, BT_DEVICE_NAME_BUFFER_SIZE);
s_head = (FakeBonding *) list_prepend(&s_head->node, &bonding->node);
return bonding->id;
}
BTBondingID bt_persistent_storage_store_ble_pairing(const SMPairingInfo *pairing_info, bool is_gateway,
const char *device_name, bool requires_address_pinning, uint8_t flags) {
const SMIdentityResolvingKey *IRK = pairing_info->is_remote_identity_info_valid ?
&pairing_info->irk : NULL;
const BTDeviceInternal *device = pairing_info->is_remote_identity_info_valid ?
&pairing_info->identity : NULL;
if (!device_name) {
device_name = "Device";
}
return fake_bt_persistent_storage_add(IRK, device, device_name, is_gateway);
}
void fake_bt_persistent_storage_reset(void) {
FakeBonding *bonding = s_head;
while (bonding) {
FakeBonding *next = (FakeBonding *) bonding->node.next;
free(bonding);
bonding = next;
}
s_head = NULL;
s_next_id = 1;
}
bool bt_persistent_storage_get_root_key(SMRootKeyType key_type, SM128BitKey *key_out) {
return true;
}
void bt_persistent_storage_set_root_keys(SM128BitKey *keys_in) {
return;
}

View file

@ -0,0 +1,26 @@
/*
* 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.
*/
#pragma once
#include "services/common/bluetooth/bluetooth_persistent_storage.h"
void fake_bt_persistent_storage_reset(void);
BTBondingID fake_bt_persistent_storage_add(const SMIdentityResolvingKey *irk,
const BTDeviceInternal *device,
const char name[BT_DEVICE_NAME_BUFFER_SIZE],
bool is_gateway);

View file

@ -0,0 +1,85 @@
/*
* 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.
*/
#pragma once
#include "util/list.h"
#include "kernel/pbl_malloc.h"
#include "system/passert.h"
#include <bluetooth/bonding_sync.h>
#include <bluetooth/sm_types.h>
#include <btutil/sm_util.h>
typedef struct {
ListNode node;
BleBonding bonding;
} BLEBondingNode;
static BLEBondingNode *s_ble_bonding_head;
void bonding_sync_add_bonding(const BleBonding *bonding) {
BLEBondingNode *node = (BLEBondingNode *) kernel_malloc_check(sizeof(BLEBondingNode));
*node = (BLEBondingNode) {
.bonding = *bonding,
};
s_ble_bonding_head = (BLEBondingNode *) list_prepend((ListNode *)s_ble_bonding_head,
(ListNode *)node);
}
void bt_driver_handle_host_added_bonding(const BleBonding *bonding) {
bonding_sync_add_bonding(bonding);
}
static bool prv_list_find_cb(ListNode *found_list_node, void *data) {
const BleBonding *bonding = (const BleBonding *)data;
const BLEBondingNode *found_node = (const BLEBondingNode *)found_list_node;
const BleBonding *found_bonding = &found_node->bonding;
return sm_is_pairing_info_equal_identity(&bonding->pairing_info, &found_bonding->pairing_info);
}
static void prv_remove_node(BLEBondingNode *node) {
list_remove((ListNode *)node, (ListNode **)&s_ble_bonding_head, NULL);
kernel_free(node);
}
bool bonding_sync_contains_pairing_info(const SMPairingInfo *pairing_info, bool is_gateway) {
BleBonding bonding = {
.is_gateway = is_gateway,
.pairing_info = *pairing_info,
};
BLEBondingNode *found_node = (BLEBondingNode *)list_find((ListNode *)s_ble_bonding_head,
prv_list_find_cb, (void *)&bonding);
return (found_node != NULL);
}
void bt_driver_handle_host_removed_bonding(const BleBonding *bonding) {
BLEBondingNode *found_node = (BLEBondingNode *)list_find((ListNode *)s_ble_bonding_head,
prv_list_find_cb, (void *)bonding);
PBL_ASSERTN(found_node);
prv_remove_node(found_node);
}
void bonding_sync_init(void) {
PBL_ASSERTN(!s_ble_bonding_head);
}
void bonding_sync_deinit(void) {
while (s_ble_bonding_head) {
prv_remove_node(s_ble_bonding_head);
}
}

View file

@ -0,0 +1,33 @@
/*
* 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 <inttypes.h>
#include <stdbool.h>
#include "fake_bootbits.h"
static uint32_t s_bootbits = 0;
void boot_bit_clear(BootBitValue bit) {
s_bootbits &= ~bit;
}
bool boot_bit_test(BootBitValue bit) {
return (s_bootbits & bit);
}
void fake_boot_bit_set(BootBitValue bit) {
s_bootbits |= bit;
}

View file

@ -0,0 +1,21 @@
/*
* 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.
*/
#pragma once
#include "system/bootbits.h"
void fake_boot_bit_set(BootBitValue bit);

126
tests/fakes/fake_clock.c Normal file
View file

@ -0,0 +1,126 @@
/*
* 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/common/i18n/i18n.h"
#include "util/attributes.h"
#include "util/math.h"
#include "stubs_i18n.h"
#include <inttypes.h>
#include <memory.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
WEAK const char *string_strip_leading_whitespace(const char *string) {
const char *result_string = string;
while (*result_string != '\0') {
if (*result_string != ' ' &&
*result_string != '\n') {
break;
}
result_string++;
}
return result_string;
}
WEAK int time_util_get_num_hours(int hours, bool is24h) {
return is24h ? hours : (hours + 12 - 1) % 12 + 1;
}
WEAK bool clock_is_24h_style() {
return false;
}
static size_t prv_format_time(char *buffer, int buf_size, const char *format, time_t timestamp) {
struct tm time_tm;
localtime_r(&timestamp, &time_tm);
const size_t ret_val = strftime(buffer, buf_size, i18n_get(format, buffer), &time_tm);
i18n_free(format, buffer);
return ret_val;
}
size_t clock_get_time_number(char *number_buffer, size_t number_buffer_size, time_t timestamp) {
const size_t written =
prv_format_time(number_buffer, number_buffer_size,
(clock_is_24h_style() ? i18n_noop("%R") : i18n_noop("%l:%M")), timestamp);
const char *number_buffer_ptr = string_strip_leading_whitespace(number_buffer);
memmove(number_buffer,
number_buffer_ptr,
number_buffer_size - (number_buffer_ptr - number_buffer));
return written - (number_buffer_ptr - number_buffer);
}
size_t clock_get_time_word(char *buffer, size_t buffer_size, time_t timestamp) {
if (clock_is_24h_style()) {
buffer[0] = '\0';
return 0;
} else {
return prv_format_time(buffer, buffer_size, i18n_noop("%p"), timestamp);
}
}
size_t clock_format_time(char *buffer, uint8_t size, int16_t hours, int16_t minutes,
bool add_space) {
if (size == 0 || buffer == NULL) {
return 0;
}
bool is24h = clock_is_24h_style();
const char *format;
// [INTL] you want to have layout resources that specify time formatting,
// and be able to set a default one for each locale.
if (is24h) {
format = "%u:%02u";
} else {
if (hours < 12) {
format = add_space ? "%u:%02u AM" : "%u:%02uAM";
} else {
format = add_space ? "%u:%02u PM" : "%u:%02uPM";
}
}
return sniprintf(buffer, size, format, time_util_get_num_hours(hours, is24h), minutes);
}
size_t clock_copy_time_string_timestamp(char *buffer, uint8_t size, time_t timestamp) {
struct tm time;
localtime_r(&timestamp, &time);
return clock_format_time(buffer, size, time.tm_hour, time.tm_min, true);
}
void clock_copy_time_string(char *buffer, uint8_t size) {
time_t t = 0;
clock_copy_time_string_timestamp(buffer, size, t);
}
size_t clock_get_date(char *buffer, int buf_size, time_t timestamp) {
return prv_format_time(buffer, buf_size, i18n_noop("%m/%d"), timestamp);
}
size_t clock_get_day_date(char *buffer, int buf_size, time_t timestamp) {
return prv_format_time(buffer, buf_size, i18n_noop("%d"), timestamp);
}
void clock_hour_and_minute_add(int *hour, int *minute, int delta_minutes) {
const int new_minutes = positive_modulo(*hour * MINUTES_PER_HOUR + *minute + delta_minutes,
MINUTES_PER_DAY);
*hour = new_minutes / MINUTES_PER_HOUR;
*minute = new_minutes % MINUTES_PER_HOUR;
}

View file

@ -0,0 +1,25 @@
/*
* 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.
*/
#pragma once
#include "applib/ui/content_indicator_private.h"
static ContentIndicatorsBuffer s_content_indicators_buffer;
ContentIndicatorsBuffer *content_indicator_get_current_buffer(void) {
return &s_content_indicators_buffer;
}

41
tests/fakes/fake_cron.h Normal file
View file

@ -0,0 +1,41 @@
/*
* 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.
*/
#pragma once
#include <pebbleos/cron.h>
#include "clar_asserts.h"
static CronJob *s_job = NULL;
time_t cron_job_schedule(CronJob *job) {
s_job = job;
return 0;
}
bool cron_job_unschedule(CronJob *job) {
s_job = NULL;
return true;
}
void fake_cron_job_fire(void) {
cl_assert(s_job != NULL);
CronJob *job = s_job;
s_job = NULL;
job->cb(job, job->cb_data);
}

View file

@ -0,0 +1,27 @@
/*
* 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 "fake_display.h"
void display_init(void) {}
void display_clear(void) {}
void display_update(NextRowCallback nrcb, UpdateCompleteCallback uccb) {}
void display_enter_static(void) {}
void display_pulse_vcom(void) {}

View file

@ -0,0 +1,54 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#define DISP_BYTES_LINE DISP_COLS / 8
// Bytes_per_line + 1 byte for the line address + 1 byte for a null trailer + 1 optional byte for a write command
#define DISP_DMA_BUFFER_SIZE (DISP_BYTES_LINE + 3)
typedef struct {
uint8_t address;
uint8_t* data;
} DisplayRow;
typedef bool(*NextRowCallback)(DisplayRow* row);
typedef void(*UpdateCompleteCallback)(void);
typedef enum {
DISPLAY_STATE_IDLE,
DISPLAY_STATE_WRITING
} DisplayState;
typedef struct {
DisplayState state;
NextRowCallback get_next_row;
UpdateCompleteCallback complete;
} DisplayContext;
void display_init(void);
void display_clear(void);
void display_update(NextRowCallback nrcb, UpdateCompleteCallback uccb);
void display_enter_static(void);
void display_pulse_vcom(void);

View file

@ -0,0 +1,49 @@
/*
* 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.
*/
#pragma once
#include "clar_asserts.h"
#include "applib/event_service_client.h"
#include "fake_events.h"
static EventServiceInfo s_event_handler[PEBBLE_NUM_EVENTS];
void event_service_client_subscribe(EventServiceInfo *service_info) {
cl_assert_equal_p(s_event_handler[service_info->type].handler, NULL);
s_event_handler[service_info->type] = *service_info;
}
void event_service_client_unsubscribe(EventServiceInfo *service_info) {
s_event_handler[service_info->type] = (EventServiceInfo) {};
}
void fake_event_service_init(void) {
memset(s_event_handler, sizeof(s_event_handler), 0);
}
void fake_event_service_handle_last(void) {
PebbleEvent event = fake_event_get_last();
EventServiceInfo *service_info = &s_event_handler[event.type];
cl_assert(service_info->handler);
service_info->handler(&event, service_info->context);
}
EventServiceInfo *fake_event_service_get_info(PebbleEventType type) {
return &s_event_handler[type];
}

View file

@ -0,0 +1,77 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
#include "services/common/evented_timer.h"
typedef struct {
uint32_t timeout_ms;
EventedTimerCallback callback;
void *callback_data;
bool repeating;
} FakeEventedTimer;
EventedTimerID evented_timer_register(uint32_t timeout_ms, bool repeating,
EventedTimerCallback callback, void* callback_data) {
FakeEventedTimer *fake_timer = malloc(sizeof(FakeEventedTimer));
fake_timer->timeout_ms = timeout_ms;
fake_timer->callback = callback;
fake_timer->callback_data = callback_data;
fake_timer->repeating = repeating;
return (EventedTimerID)fake_timer;
}
bool evented_timer_reschedule(EventedTimerID timer_id, uint32_t new_timeout_ms) {
if (timer_id) {
FakeEventedTimer *fake_timer = (FakeEventedTimer*)timer_id;
fake_timer->timeout_ms = new_timeout_ms;
return true;
}
return false;
}
EventedTimerID evented_timer_register_or_reschedule(EventedTimerID timer_id, uint32_t timeout_ms,
EventedTimerCallback callback, void *data) {
if (timer_id && evented_timer_reschedule(timer_id, timeout_ms)) {
return timer_id;
} else {
return evented_timer_register(timeout_ms, false, callback, data);
}
}
void evented_timer_cancel(EventedTimerID timer_id) {
if (timer_id) {
FakeEventedTimer *fake_timer = (FakeEventedTimer*)timer_id;
free(fake_timer);
}
}
bool fake_evented_timer_trigger(EventedTimerID timer_id) {
if (timer_id) {
FakeEventedTimer *fake_timer = (FakeEventedTimer*)timer_id;
fake_timer->callback(fake_timer->callback_data);
if (!fake_timer->repeating) {
free(fake_timer);
}
return true;
}
return false;
}

91
tests/fakes/fake_events.c Normal file
View file

@ -0,0 +1,91 @@
/*
* 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 "fake_events.h"
#include "kernel/pbl_malloc.h"
#include "freertos_types.h"
#include "projdefs.h"
static PebbleEvent s_last_pebble_event;
static uint32_t s_fake_event_count = 0;
static FakeEventCallback s_fake_event_cb = NULL;
WEAK void **fake_event_get_buffer(PebbleEvent *event) {
switch (event->type) {
case PEBBLE_BLE_GATT_CLIENT_EVENT:
if (event->bluetooth.le.gatt_client.subtype == PebbleBLEGATTClientEventTypeServiceChange) {
return (void **)(&event->bluetooth.le.gatt_client_service.info);
}
break;
default:
break; // Nothing to do!
}
return NULL;
}
void event_put(PebbleEvent* event) {
fake_event_clear_last();
s_last_pebble_event = *event;
++s_fake_event_count;
if (s_fake_event_cb) {
s_fake_event_cb(event);
}
}
bool event_put_isr(PebbleEvent* event) {
return false;
}
QueueHandle_t event_kernel_to_kernel_event_queue(void) {
return (NULL);
}
BaseType_t event_queue_cleanup_and_reset(QueueHandle_t queue) {
return pdPASS;
}
void fake_event_init(void) {
fake_event_reset_count();
fake_event_clear_last();
}
PebbleEvent fake_event_get_last(void) {
return s_last_pebble_event;
}
void fake_event_clear_last(void) {
void **buf = fake_event_get_buffer(&s_last_pebble_event);
if (buf && *buf) {
kernel_free(*buf);
*buf = NULL;
}
s_last_pebble_event = (PebbleEvent) {};
}
void fake_event_reset_count(void) {
s_fake_event_count = 0;
}
uint32_t fake_event_get_count(void) {
return s_fake_event_count;
}
void fake_event_set_callback(FakeEventCallback cb) {
s_fake_event_cb = cb;
}

35
tests/fakes/fake_events.h Normal file
View file

@ -0,0 +1,35 @@
/*
* 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.
*/
#pragma once
#include "kernel/events.h"
void fake_event_init(void);
PebbleEvent fake_event_get_last(void);
void fake_event_clear_last(void);
void fake_event_reset_count(void);
uint32_t fake_event_get_count(void);
void **fake_event_get_buffer(PebbleEvent *event);
typedef void (*FakeEventCallback)(PebbleEvent *event);
void fake_event_set_callback(FakeEventCallback cb);

View file

@ -0,0 +1,77 @@
/*
* 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 "drivers/flash.h"
#include "flash_region/flash_region.h"
#include <stdint.h>
static void prv_erase_optimal_range(uint32_t min_start, uint32_t max_start,
uint32_t min_end, uint32_t max_end) {
// We want to erase the sector that starts immediately below max_start but after min_start. If no sector
// boundary exists between the two, we need to start erasing sectors after min_start and backfill with
// subsector erases.
int32_t sector_start = (max_start & SECTOR_ADDR_MASK);
int32_t subsector_start = (max_start & SUBSECTOR_ADDR_MASK);
if (sector_start < (int32_t) min_start) {
sector_start += SECTOR_SIZE_BYTES;
}
// We want to erase ending after min_end but before max_end. If that ends running past the end of max_end,
// we need to erase starting with the sector before and fill in with subsector erases.
int32_t sector_end = ((min_end - 1) & SECTOR_ADDR_MASK) + SECTOR_SIZE_BYTES;
int32_t subsector_end = ((min_end - 1) & SUBSECTOR_ADDR_MASK) + SUBSECTOR_SIZE_BYTES;
if (sector_end > (int32_t) max_end) {
sector_end -= SECTOR_SIZE_BYTES;
}
int erase_count = 0;
// Do the upkeep immediately just in case we've spent awhile running without feeding the
// watchdog before doing this erase operation.
if (sector_start < sector_end) {
// Now erase the leading subsectors...
for (int32_t i = subsector_start; i < sector_start; i += SUBSECTOR_SIZE_BYTES) {
flash_erase_subsector_blocking(i);
}
// Erase the full sectors...
for (int32_t i = sector_start; i < sector_end; i += SECTOR_SIZE_BYTES) {
flash_erase_sector_blocking(i);
}
// Erase the trailing subsectors
for (int32_t i = sector_end; i < subsector_end; i += SUBSECTOR_SIZE_BYTES) {
flash_erase_subsector_blocking(i);
}
} else {
// Can't erase any full sectors, just erase subsectors the whole way.
for (int32_t i = subsector_start; i < subsector_end; i += SUBSECTOR_SIZE_BYTES) {
flash_erase_subsector_blocking(i);
}
}
}
void flash_region_erase_optimal_range(uint32_t min_start, uint32_t max_start,
uint32_t min_end, uint32_t max_end) {
prv_erase_optimal_range(min_start, max_start, min_end, max_end);
}
void flash_region_erase_optimal_range_no_watchdog(uint32_t min_start, uint32_t max_start,
uint32_t min_end, uint32_t max_end) {
prv_erase_optimal_range(min_start, max_start, min_end, max_end);
}

130
tests/fakes/fake_fonts.c Normal file
View file

@ -0,0 +1,130 @@
/*
* 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 "fake_fonts.h"
#include "resource/resource_ids.auto.h"
#include "font_resource_keys.auto.h"
#include "applib/fonts/fonts_private.h"
#include "applib/graphics/text_resources.h"
#include "util/math.h"
#include "util/size.h"
#include <string.h>
#include "clar_asserts.h"
typedef struct {
const char *key;
uint32_t handle;
FontInfo font_info;
} FontHelper;
static FontHelper s_font_helpers[] = {
{.key = FONT_KEY_GOTHIC_14, .handle = RESOURCE_ID_GOTHIC_14},
{.key = FONT_KEY_GOTHIC_14_BOLD, .handle = RESOURCE_ID_GOTHIC_14_BOLD},
{.key = FONT_KEY_GOTHIC_18, .handle = RESOURCE_ID_GOTHIC_18},
{.key = FONT_KEY_GOTHIC_18_BOLD, .handle = RESOURCE_ID_GOTHIC_18_BOLD},
{.key = FONT_KEY_GOTHIC_24_BOLD, .handle = RESOURCE_ID_GOTHIC_24_BOLD},
{.key = FONT_KEY_DROID_SERIF_28_BOLD, .handle = RESOURCE_ID_GOTHIC_28_BOLD},
{.key = FONT_KEY_LECO_20_BOLD_NUMBERS, .handle = RESOURCE_ID_LECO_20_BOLD_NUMBERS},
{.key = FONT_KEY_LECO_26_BOLD_NUMBERS_AM_PM, .handle = RESOURCE_ID_LECO_26_BOLD_NUMBERS_AM_PM},
{.key = FONT_KEY_LECO_32_BOLD_NUMBERS, .handle = RESOURCE_ID_LECO_32_BOLD_NUMBERS},
{.key = FONT_KEY_LECO_36_BOLD_NUMBERS, .handle = RESOURCE_ID_LECO_36_BOLD_NUMBERS},
{.key = FONT_KEY_LECO_38_BOLD_NUMBERS, .handle = RESOURCE_ID_LECO_38_BOLD_NUMBERS},
{.key = FONT_KEY_GOTHIC_14_EMOJI, .handle = RESOURCE_ID_GOTHIC_14_EMOJI},
{.key = FONT_KEY_GOTHIC_18_EMOJI, .handle = RESOURCE_ID_GOTHIC_18_EMOJI},
{.key = FONT_KEY_GOTHIC_24, .handle = RESOURCE_ID_GOTHIC_24},
{.key = FONT_KEY_GOTHIC_24_EMOJI, .handle = RESOURCE_ID_GOTHIC_24_EMOJI},
{.key = FONT_KEY_GOTHIC_28, .handle = RESOURCE_ID_GOTHIC_28},
{.key = FONT_KEY_GOTHIC_28_EMOJI, .handle = RESOURCE_ID_GOTHIC_28_EMOJI},
{.key = FONT_KEY_GOTHIC_28_BOLD, .handle = RESOURCE_ID_GOTHIC_28_BOLD},
{.key = FONT_KEY_GOTHIC_36, .handle = RESOURCE_ID_GOTHIC_36},
{.key = FONT_KEY_GOTHIC_36_BOLD, .handle = RESOURCE_ID_GOTHIC_36_BOLD},
#if (PLATFORM_SNOWY || PLATFORM_SPALDING)
{.key = FONT_KEY_AGENCY_FB_36_NUMBERS_AM_PM, .handle = RESOURCE_ID_AGENCY_FB_36_NUMBERS_AM_PM },
{.key = FONT_KEY_AGENCY_FB_60_NUMBERS_AM_PM, .handle = RESOURCE_ID_AGENCY_FB_60_NUMBERS_AM_PM },
{.key = FONT_KEY_AGENCY_FB_60_THIN_NUMBERS_AM_PM, .handle = RESOURCE_ID_AGENCY_FB_60_THIN_NUMBERS_AM_PM },
#elif PLATFORM_ROBERT
{.key = FONT_KEY_AGENCY_FB_46_NUMBERS_AM_PM, .handle = RESOURCE_ID_AGENCY_FB_46_NUMBERS_AM_PM },
{.key = FONT_KEY_AGENCY_FB_88_NUMBERS_AM_PM, .handle = RESOURCE_ID_AGENCY_FB_88_NUMBERS_AM_PM },
{.key = FONT_KEY_AGENCY_FB_88_THIN_NUMBERS_AM_PM, .handle = RESOURCE_ID_AGENCY_FB_88_THIN_NUMBERS_AM_PM },
#endif
// add more here as we need more fonts from this module
};
static FontHelper *prv_font_helper_from_font_key(const char *font_key) {
for (int i = 0; i < ARRAY_LENGTH(s_font_helpers); i++) {
if (!strcmp(font_key, s_font_helpers[i].key)) {
return &s_font_helpers[i];
}
}
return NULL;
}
static GFont prv_get_font(const char *font_key) {
FontHelper *font_helper = prv_font_helper_from_font_key(font_key);
cl_assert_(font_helper, font_key);
FontInfo *result = &font_helper->font_info;
if (!result->loaded) {
bool init_result = text_resources_init_font(0, font_helper->handle, 0, result);
cl_assert_(init_result, font_key);
}
return (GFont)result;
}
static const struct {
const char *key_name;
uint8_t min_height;
} s_emoji_fonts[] = {
// Keep this sorted in descending order
{ FONT_KEY_GOTHIC_28_EMOJI, 28 },
{ FONT_KEY_GOTHIC_24_EMOJI, 24 },
{ FONT_KEY_GOTHIC_18_EMOJI, 18 },
{ FONT_KEY_GOTHIC_14_EMOJI, 14 },
};
FontInfo *fonts_get_system_emoji_font_for_size(unsigned int font_height) {
for (uint32_t i = 0; i < ARRAY_LENGTH(s_emoji_fonts); i++) {
if (font_height >= s_emoji_fonts[i].min_height) {
return prv_get_font(s_emoji_fonts[i].key_name);
}
}
// Didn't find a suitable emoji font
return NULL;
}
GFont fonts_get_system_font(const char *font_key) {
return prv_get_font(font_key);
}
GFont system_resource_get_font(const char *font_key) {
return prv_get_font(font_key);
}
uint8_t fonts_get_font_height(GFont font) {
return font->max_height;
}
int16_t fonts_get_font_cap_offset(GFont font) {
if (!font) {
return 0;
}
// FIXME PBL-25709: Actually use font-specific caps and also provide function for baseline offsets
return (int16_t)(((int16_t)font->max_height) * 22 / 100);
}

27
tests/fakes/fake_fonts.h Normal file
View file

@ -0,0 +1,27 @@
/*
* 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.
*/
#pragma once
#include "applib/fonts/fonts.h"
GFont fonts_get_system_font(const char *font_key);
uint8_t fonts_get_font_height(GFont font);
GFont system_resource_get_font(const char *font_key);
FontInfo *fonts_get_system_emoji_font_for_size(unsigned int font_height);

View file

@ -0,0 +1,125 @@
/*
* 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 "framebuffer.h"
#include "drivers/display.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/bitset.h"
#include <stdint.h>
#include <string.h>
const int FrameBuffer_MaxX = DISP_COLS;
const int FrameBuffer_MaxY = DISP_ROWS;
static FrameBuffer* s_current_framebuffer;
static uint8_t s_current_flush_line;
static void flush_complete(void);
static bool flush_get_next_line(DisplayRow* row);
static void framebuffer_reset_dirty_lines(FrameBuffer* f);
uint32_t* framebuffer_get_line(FrameBuffer* f, uint8_t y) {
PBL_ASSERT(y < FrameBuffer_MaxY, "Y coordinate is outside of framebuffer dimensions");
return f->buffer + (y * FRAMEBUFFER_WORDS_PER_ROW);
}
void framebuffer_clear(FrameBuffer* f) {
memset(f->buffer, 0xffffffff, FRAMEBUFFER_SIZE_BYTES);
memset(f->dirty_lines, 0xff, DISP_ROWS / 8);
f->is_dirty = true;
f->is_cleared = false;
}
void framebuffer_clear_line(FrameBuffer* f, uint8_t y) {
uint32_t* line = framebuffer_get_line(f, y);
memset(line, 0xffffffff, FRAMEBUFFER_SIZE_BYTES);
bitset8_set(f->dirty_lines, y);
f->is_dirty = true;
}
void framebuffer_mark_dirty_rect(FrameBuffer* f, GRect rect) {
const uint16_t y_start = rect.origin.y;
const uint16_t y_end = y_start + rect.size.h;
for (uint16_t y = y_start; y < y_end; ++y) {
bitset8_update(f->dirty_lines, y, is_dirty);
}
f->is_dirty = true;
}
void framebuffer_set_line(FrameBuffer* f, uint8_t y, const uint32_t* buffer) {
memcpy(framebuffer_get_line(f, y), buffer, FRAMEBUFFER_WORDS_PER_ROW);
bitset8_set(f->dirty_lines, y);
f->is_dirty = true;
}
void framebuffer_set_lines(FrameBuffer* f, uint8_t y, const uint8_t num_lines, const uint32_t* buffer) {
uint32_t* line = framebuffer_get_line(f, y);
memcpy(line, buffer, num_lines * FRAMEBUFFER_WORDS_PER_ROW);
const GRect dirty_rect = GRect(0, y, DISP_COLS, num_lines);
framebuffer_mark_dirty_rect(f, dirty_rect);
}
void framebuffer_flush(FrameBuffer* f) {
// If the framebuffer hasn't been cleared but it is dirty, issue a
// display command to blank the screen.
if (f->is_cleared) {
display_clear();
f->is_cleared = false;
}
if (!f->is_dirty) {
return;
}
s_current_framebuffer = f;
s_current_flush_line = 0;
display_update(&flush_get_next_line, &flush_complete);
}
static void flush_complete(void) {
s_current_flush_line = 0;
framebuffer_reset_dirty_lines(s_current_framebuffer);
}
static bool flush_get_next_line(DisplayRow* row) {
while (s_current_flush_line < DISP_ROWS) {
if (bitset8_get(s_current_framebuffer->dirty_lines, s_current_flush_line)) {
row->address = s_current_flush_line;
row->data = framebuffer_get_line(s_current_framebuffer, s_current_flush_line);
s_current_flush_line++;
return true;
}
s_current_flush_line++;
}
return false;
}
static void framebuffer_reset_dirty(FrameBuffer* f) {
memset(f->dirty_lines, 0x00, DISP_ROWS / 8);
f->is_dirty = false;
f->is_cleared = false;
}

53
tests/fakes/fake_gap_le.c Normal file
View file

@ -0,0 +1,53 @@
/*
* 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 "fake_gap_le.h"
#include <stdint.h>
bool gap_le_start_scan(void) {
return true;
}
bool gap_le_stop_scan(void) {
return true;
}
static bool s_gap_le_is_initialized = false;
void gap_le_init(uint32_t stack_id) {
s_gap_le_is_initialized = true;
}
bool gap_le_deinit(uint32_t stack_id) {
s_gap_le_is_initialized = false;
return true;
}
void gap_le_connect_params_request_low_power(unsigned int stack_id, const BD_ADDR_t* addr) {
}
bool fake_gap_le_is_initialized(void) {
return s_gap_le_is_initialized;
}
void sm_set_pairable(uint32_t stack_id, bool is_pairable) {
}
void gap_le_request_power_saving_connection_params(unsigned int stack_id, const BD_ADDR_t* addr) {
}

21
tests/fakes/fake_gap_le.h Normal file
View file

@ -0,0 +1,21 @@
/*
* 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.
*/
#pragma once
#import <stdbool.h>
bool fake_gap_le_is_initialized(void);

View file

@ -0,0 +1,62 @@
/*
* 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 "fake_gap_le_connect_params.h"
#include "GAPAPI.h"
static ResponseTimeState s_last_requested_desired_state;
void gap_le_connect_params_request(GAPLEConnection *connection,
ResponseTimeState desired_state) {
s_last_requested_desired_state = desired_state;
}
void gap_le_connect_params_setup_connection(GAPLEConnection *connection) {
}
void gap_le_connect_params_cleanup_by_connection(GAPLEConnection *connection) {
}
void gap_le_connect_params_handle_update(unsigned int stack_id,
const GAP_LE_Connection_Parameter_Updated_Event_Data_t *event) {
}
void gap_le_connect_params_handle_connection_parameter_update_response(
const GAP_LE_Connection_Parameter_Update_Response_Event_Data_t *event_data) {
}
static ResponseTimeState s_actual_state;
ResponseTimeState gap_le_connect_params_get_actual_state(GAPLEConnection *connection) {
return s_actual_state;
}
void fake_gap_le_connect_params_init(void) {
s_last_requested_desired_state = ResponseTimeInvalid;
s_actual_state = ResponseTimeInvalid;
}
ResponseTimeState fake_gap_le_connect_params_get_last_requested(void) {
return s_last_requested_desired_state;
}
void fake_gap_le_connect_params_reset_last_requested(void) {
s_last_requested_desired_state = ResponseTimeInvalid;
}
void fake_gap_le_connect_params_set_actual_state(ResponseTimeState actual_state) {
s_actual_state = actual_state;
}

View file

@ -0,0 +1,27 @@
/*
* 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.
*/
#pragma once
#include "comm/ble/gap_le_connect_params.h"
void fake_gap_le_connect_params_init(void);
ResponseTimeState fake_gap_le_connect_params_get_last_requested(void);
void fake_gap_le_connect_params_reset_last_requested(void);
void fake_gap_le_connect_params_set_actual_state(ResponseTimeState actual_state);

View file

@ -0,0 +1,24 @@
/*
* 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/gap_le_connection.h"
void gatt_client_discovery_cleanup_by_connection(GAPLEConnection *connection) { }
void gatt_client_subscription_cleanup_by_att_handle_range(
struct GAPLEConnection *connection, ATTHandleRange *range) { }

View file

@ -0,0 +1,220 @@
/*
* 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 "fake_gatt_client_operations.h"
#include "comm/ble/gatt_client_operations.h"
#include "util/list.h"
#include "clar_asserts.h"
#include <string.h>
typedef struct {
ListNode node;
BLECharacteristic characteristic;
GAPLEClient client;
} Read;
static Read *s_read_head;
static BTErrno s_read_return_value;
BTErrno gatt_client_op_read(BLECharacteristic characteristic,
GAPLEClient client) {
if (s_read_return_value != BTErrnoOK) {
return s_read_return_value;
}
Read *read = malloc(sizeof(Read));
*read = (const Read) {
.characteristic = characteristic,
.client = client,
};
if (s_read_head) {
list_append((ListNode *)s_read_head, &read->node);
} else {
s_read_head = read;
}
return BTErrnoOK;
}
void gatt_client_consume_read_response(uintptr_t object_ref,
uint8_t value_out[],
uint16_t value_length,
GAPLEClient client) {
}
typedef struct {
ListNode node;
BLECharacteristic characteristic;
GAPLEClient client;
uint8_t *value;
size_t value_length;
bool is_response_required;
} Write;
static Write *s_write_head;
static BTErrno s_write_return_value;
static BTErrno fake_gatt_client_write(BLECharacteristic characteristic,
const uint8_t *value,
size_t value_length,
GAPLEClient client,
bool is_response_required) {
if (s_write_return_value != BTErrnoOK) {
return s_write_return_value;
}
Write *write = malloc(sizeof(Write));
uint8_t *buffer;
if (value_length) {
cl_assert(value);
buffer = malloc(value_length);
memcpy(buffer, value, value_length);
} else {
cl_assert_equal_p(value, NULL);
buffer = NULL;
}
*write = (const Write) {
.characteristic = characteristic,
.client = client,
.is_response_required = is_response_required,
.value = buffer,
.value_length = value_length,
};
if (s_write_head) {
list_append((ListNode *)s_write_head, &write->node);
} else {
s_write_head = write;
}
return BTErrnoOK;
}
BTErrno gatt_client_op_write(BLECharacteristic characteristic,
const uint8_t *value,
size_t value_length,
GAPLEClient client) {
return fake_gatt_client_write(characteristic, value, value_length, client,
true /* is_response_required */);
}
BTErrno gatt_client_op_write_without_response(BLECharacteristic characteristic,
const uint8_t *value,
size_t value_length,
GAPLEClient client) {
return fake_gatt_client_write(characteristic, value, value_length, client,
false /* is_response_required */);
}
BTErrno gatt_client_op_write_descriptor(BLEDescriptor descriptor,
const uint8_t *value,
size_t value_length,
GAPLEClient client) {
return BTErrnoOK;
}
BTErrno gatt_client_op_read_descriptor(BLEDescriptor descriptor,
GAPLEClient client) {
return BTErrnoOK;
}
BTErrno gatt_client_op_write_descriptor_cccd(BLEDescriptor cccd, const uint16_t *value) {
return BTErrnoOK;
}
void gatt_client_op_cleanup(GAPLEClient client) {
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Fake Manipulation
void fake_gatt_client_op_init(void) {
s_read_return_value = BTErrnoOK;
s_write_return_value = BTErrnoOK;
}
void fake_gatt_client_op_deinit(void) {
Read *read = s_read_head;
while (read) {
Read *next = (Read *) read->node.next;
free(read);
read = next;
}
s_read_head = NULL;
fake_gatt_client_op_clear_write_list();
}
void fake_gatt_client_op_set_read_return_value(BTErrno e) {
s_read_return_value = e;
}
void fake_gatt_client_op_assert_read(BLECharacteristic characteristic,
GAPLEClient client) {
if (s_read_head) {
cl_assert_equal_i(characteristic, s_read_head->characteristic);
cl_assert_equal_i(client, s_read_head->client);
} else {
cl_assert_(false, "No gatt_client_op_read() has happened at all");
}
Read *old_head = s_read_head;
s_read_head = (Read *) list_pop_head(&s_read_head->node);
free(old_head);
}
void fake_gatt_client_op_set_write_return_value(BTErrno e) {
s_write_return_value = e;
}
void fake_gatt_client_op_clear_write_list(void) {
Write *write = s_write_head;
while (write) {
Write *next = (Write *) write->node.next;
free(write->value);
free(write);
write = next;
}
s_write_head = NULL;
}
void fake_gatt_client_op_assert_no_write(void) {
cl_assert_equal_p(s_write_head, NULL);
}
static void fake_gatt_client_op_assert_write_failed(void) {
cl_assert_(false, "No gatt_client_op_write() or "
"gatt_client_op_write_without_response() has happened at all");
}
void fake_gatt_client_op_assert_write(BLECharacteristic characteristic,
const uint8_t *value, size_t value_length,
GAPLEClient client, bool is_response_required) {
if (s_write_head) {
cl_assert_equal_i(characteristic, s_write_head->characteristic);
cl_assert_equal_i(s_write_head->value_length, value_length);
cl_assert_equal_i(memcmp(value, s_write_head->value, value_length), 0);
cl_assert_equal_i(client, s_write_head->client);
cl_assert_equal_b(is_response_required, s_write_head->is_response_required);
} else {
fake_gatt_client_op_assert_write_failed();
}
Write *old_write = s_write_head;
s_write_head = (Write *) list_pop_head(&s_write_head->node);
free(old_write->value);
free(old_write);
}

View file

@ -0,0 +1,40 @@
/*
* 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.
*/
#pragma once
#include "applib/bluetooth/ble_client.h"
#include "comm/ble/gap_le_task.h"
void fake_gatt_client_op_init(void);
void fake_gatt_client_op_deinit(void);
void fake_gatt_client_op_set_read_return_value(BTErrno e);
void fake_gatt_client_op_assert_read(BLECharacteristic characteristic,
GAPLEClient client);
void fake_gatt_client_op_set_write_return_value(BTErrno e);
void fake_gatt_client_op_clear_write_list(void);
void fake_gatt_client_op_assert_no_write(void);
void fake_gatt_client_op_assert_write(BLECharacteristic characteristic,
const uint8_t *value, size_t value_length,
GAPLEClient client, bool is_response_required);

View file

@ -0,0 +1,102 @@
/*
* 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 "fake_gatt_client_subscriptions.h"
#include "clar_asserts.h"
typedef struct {
ListNode node;
BLECharacteristic characteristic;
BLESubscription subscription_type;
GAPLEClient client;
} Subscribe;
static Subscribe *s_subscribe_head;
static BTErrno s_subscribe_return_value;
BTErrno gatt_client_subscriptions_subscribe(BLECharacteristic characteristic,
BLESubscription subscription_type,
GAPLEClient client) {
Subscribe *subscribe = malloc(sizeof(Subscribe));
*subscribe = (const Subscribe) {
.characteristic = characteristic,
.subscription_type = subscription_type,
.client = client,
};
if (s_subscribe_head) {
list_append((ListNode *)s_subscribe_head, &subscribe->node);
} else {
s_subscribe_head = subscribe;
}
return s_subscribe_return_value;
}
bool gatt_client_subscriptions_get_notification_header(GAPLEClient client,
GATTBufferedNotificationHeader *header_out) {
return false;
}
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;
}
void gatt_client_subscriptions_cleanup_by_client(GAPLEClient client) {
}
void gatt_client_subscriptions_cleanup_by_connection(struct GAPLEConnection *connection,
bool should_unsubscribe) {
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Fake Manipulation
void fake_gatt_client_subscriptions_init(void) {
s_subscribe_return_value = BTErrnoOK;
}
void fake_gatt_client_subscriptions_deinit(void) {
Subscribe *subscribe = s_subscribe_head;
while (subscribe) {
Subscribe *next = (Subscribe *) subscribe->node.next;
free(subscribe);
subscribe = next;
}
s_subscribe_head = NULL;
}
void fake_gatt_client_subscriptions_set_subscribe_return_value(BTErrno e) {
s_subscribe_return_value = e;
}
void fake_gatt_client_subscriptions_assert_subscribe(BLECharacteristic characteristic,
BLESubscription subscription_type,
GAPLEClient client) {
if (s_subscribe_head) {
cl_assert_equal_i(characteristic, s_subscribe_head->characteristic);
cl_assert_equal_i(subscription_type, s_subscribe_head->subscription_type);
cl_assert_equal_i(client, s_subscribe_head->client);
} else {
cl_assert_(false, "No gatt_client_subscriptions_subscribe() has happened at all");
}
s_subscribe_head = (Subscribe *) list_pop_head(&s_subscribe_head->node);
}

View file

@ -0,0 +1,29 @@
/*
* 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.
*/
#pragma once
#include "comm/ble/gatt_client_subscriptions.h"
void fake_gatt_client_subscriptions_init(void);
void fake_gatt_client_subscriptions_deinit(void);
void fake_gatt_client_subscriptions_set_subscribe_return_value(BTErrno e);
void fake_gatt_client_subscriptions_assert_subscribe(BLECharacteristic characteristic,
BLESubscription subscription_type,
GAPLEClient client);

View file

@ -0,0 +1,46 @@
/*
* 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 "fake_gbitmap_get_data_row.h"
#include "applib/graphics/gtypes.h"
#include <stdint.h>
bool s_fake_data_row_handling = false;
bool s_fake_data_row_handling_disable_vertical_flip = false;
extern GBitmapDataRowInfo prv_gbitmap_get_data_row_info(const GBitmap *bitmap, uint16_t y);
// Overrides the same function in gbitmap.c
GBitmapDataRowInfo gbitmap_get_data_row_info(const GBitmap *bitmap, uint16_t y) {
// If fake data row handling is enabled, clip the row to a diamond mask
if (s_fake_data_row_handling) {
const int16_t diamond_offset =
ABS((bitmap->bounds.size.w / 2) - (y * bitmap->bounds.size.w / bitmap->bounds.size.h));
const int16_t min_x = bitmap->bounds.origin.x + diamond_offset;
// vertically flip unless disabled
if (!s_fake_data_row_handling_disable_vertical_flip) {
y = bitmap->bounds.size.h - y - 1;
}
return (GBitmapDataRowInfo){
.data = prv_gbitmap_get_data_row_info(bitmap, y).data,
.min_x = min_x,
.max_x = grect_get_max_x(&bitmap->bounds) - diamond_offset - 1,
};
}
return prv_gbitmap_get_data_row_info(bitmap, y);
}

View file

@ -0,0 +1,22 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
extern bool s_fake_data_row_handling;
extern bool s_fake_data_row_handling_disable_vertical_flip;

View file

@ -0,0 +1,36 @@
/*
* 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 "applib/graphics/gtypes.h"
#include "upng.h"
bool gbitmap_png_data_is_png(uint8_t *data, size_t data_size) {return false;}
GBitmap* gbitmap_create_from_png_data(const uint8_t *png_data, size_t png_data_size) {return NULL;}
bool gbitmap_init_with_png_data(GBitmap *bitmap, const uint8_t *data, size_t data_size) {return true;}
uint8_t gbitmap_png_load_palette(upng_t *upng, GColor8 **palette) {return 0;}
bool gbitmap_png_is_format_supported(upng_t *upng) {return true;}
int32_t gbitmap_png_get_transparent_gray_value(upng_t *upng) {return -1;}
int32_t png_seek_chunk_in_resource(uint32_t resource_id, uint32_t offset,
bool seek_framedata, bool *found_actl) {return 0;}
int32_t png_seek_chunk_in_resource_system(ResAppNum app_num, uint32_t resource_id, uint32_t offset,
bool seek_framedata, bool *found_actl) {return 0;}

View file

@ -0,0 +1,46 @@
/*
* 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.
*/
#define FAKE_GRAPHICS_CONTEXT_C (1)
#include "fake_graphics_context.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/framebuffer.h"
extern GContext *s_app_state_get_graphics_context;
static GContext s_ctx;
static FrameBuffer s_fb;
GContext *graphics_context_get_current_context(void) {
return &s_ctx;
}
GContext *fake_graphics_context_get_context(void) {
return &s_ctx;
}
FrameBuffer *fake_graphics_context_get_framebuffer(void) {
return &s_fb;
}
void fake_graphics_context_init(void) {
framebuffer_init(&s_fb, &(GSize) {DISP_COLS, DISP_ROWS});
framebuffer_clear(&s_fb);
graphics_context_init(&s_ctx, &s_fb, GContextInitializationMode_App);
s_app_state_get_graphics_context = &s_ctx;
}

View file

@ -0,0 +1,39 @@
/*
* 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.
*/
#pragma once
#include "applib/graphics/graphics.h"
#include "applib/graphics/framebuffer.h"
#if !FAKE_GRAPHICS_CONTEXT_C
#include "fw/graphics/util.h"
#include "stubs_app_state.h"
#endif
GContext *fake_graphics_context_get_context(void);
FrameBuffer *fake_graphics_context_get_framebuffer(void);
void fake_graphics_context_init(void);
#define FAKE_GRAPHICS_CONTEXT_CHECK_DEST_BITMAP(name) ({ \
cl_check(gbitmap_pbi_eq(&fake_graphics_context_get_context()->dest_bitmap, name)); \
})
#define FAKE_GRAPHICS_CONTEXT_CHECK_DEST_BITMAP_FILE() ({ \
cl_check(gbitmap_pbi_eq(&fake_graphics_context_get_context()->dest_bitmap, TEST_PBI_FILE)); \
})

View file

@ -0,0 +1,192 @@
/*
* 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.
*/
#pragma once
#include <inttypes.h>
#include <string.h>
#include "util/list.h"
#include "clar_asserts.h"
// Simple pass-through implementation of kernel_malloc/free that attempts to
// protect against buffer overruns in tested code by adding a canary value to
// the beginning and end of the allocated block, and verifying the value on
// freeing of the block. It won't catch *all* memory errors of course, like
// writing way outside of your bounds, or use-after-free, or neglecting to free.
// But it should catch some of the simpler cases.
static const uint32_t s_malloc_canary = 0x54761F34;
static uint64_t s_largest_free_block_bytes = ~0;
static uint64_t s_heap_mark;
static bool s_stats_enabled = false;
typedef struct {
ListNode node;
size_t size;
void *ptr;
} Allocation;
Allocation *s_head;
void* kernel_malloc(size_t bytes) {
if (bytes > s_largest_free_block_bytes) {
return NULL;
}
char* memory = malloc(bytes + 12);
memcpy(memory, &bytes, 4);
memcpy(memory + 4, &s_malloc_canary, 4);
memcpy(memory + bytes + 8, &s_malloc_canary, 4);
void* ptr = memory + 8;
if (s_stats_enabled) {
Allocation *a = (Allocation *) malloc(sizeof(Allocation));
*a = (const Allocation) {
.size = bytes,
.ptr = ptr,
};
s_head = (Allocation *) list_prepend((ListNode *) s_head, &a->node);
}
return ptr;
}
void* kernel_zalloc(size_t bytes) {
void *ptr = kernel_malloc(bytes);
if (ptr) {
memset(ptr, 0, bytes);
}
return ptr;
}
void* kernel_zalloc_check(size_t bytes) {
return kernel_zalloc(bytes);
}
void* kernel_malloc_check(size_t bytes) {
return kernel_malloc(bytes);
}
char* kernel_strdup(const char* s) {
char *r = kernel_malloc_check(strlen(s) + 1);
if (!r) {
return NULL;
}
strcpy(r, s);
return r;
}
char* kernel_strdup_check(const char* s) {
return kernel_strdup(s);
}
static bool prv_find_allocation_filter_cb(ListNode *found_node, void *data) {
Allocation *a = (Allocation *) found_node;
return (a->ptr == data);
}
// Split into its own function to make it easy to set a breakpoint on it when debugging
// using `./waf test --debug_test`
static void prv_double_free_assert(Allocation *a) {
cl_assert_(a != NULL, "Couldn't find allocation! Double free?");
}
void kernel_free(void* ptr) {
if (ptr == NULL) {
return;
}
if (s_stats_enabled) {
Allocation *a = (Allocation *) list_find((ListNode *) s_head,
prv_find_allocation_filter_cb, ptr);
prv_double_free_assert(a);
list_remove(&a->node, (ListNode **) &s_head, NULL);
free(a);
}
char* memory = (char*)ptr - 8;
uint32_t canary_start = -1;
uint32_t canary_end = -1;
uint32_t canary_length = -1;
memcpy(&canary_length, memory, 4);
memcpy(&canary_start, memory + 4, 4);
memcpy(&canary_end, memory + canary_length + 8, 4);
cl_assert(canary_start == s_malloc_canary);
cl_assert(canary_length != -1);
cl_assert(canary_end == s_malloc_canary);
free(memory);
}
//! Enables or disables the tracking of allocations
void fake_kernel_malloc_enable_stats(bool enable) {
s_stats_enabled = enable;
}
//! Returns the number of bytes allocated on the kernel heap.
//! @note Call fake_kernel_malloc_enable_stats(true) before using this.
uint64_t fake_kernel_malloc_get_total_bytes_allocated(void) {
uint64_t bytes_allocated = 0;
Allocation *a = s_head;
while (a) {
bytes_allocated += a->size;
a = (Allocation *) a->node.next;
}
return bytes_allocated;
}
//! Makes successive kernel_malloc() fail for sizes above the number of bytes specified.
void fake_kernel_malloc_set_largest_free_block(uint64_t bytes) {
s_largest_free_block_bytes = bytes;
}
//! Marks the current, total bytes allocated.
//! @see fake_kernel_malloc_mark_assert_equal
void fake_kernel_malloc_mark(void) {
s_heap_mark = fake_kernel_malloc_get_total_bytes_allocated();
}
//! Asserts that the total bytes allocated is the same as the last time fake_kernel_malloc_mark()
//! was called.
void fake_kernel_malloc_mark_assert_equal(void) {
cl_assert_equal_i(s_heap_mark, fake_kernel_malloc_get_total_bytes_allocated());
}
void fake_kernel_malloc_init(void) {
s_largest_free_block_bytes = ~0;
s_heap_mark = 0;
s_head = NULL;
}
void fake_kernel_malloc_deinit(void) {
Allocation *a = s_head;
while (a) {
Allocation *next = (Allocation *) a->node.next;
free(a);
a = next;
}
s_head = NULL;
}

View file

@ -0,0 +1,58 @@
/*
* 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 "fake_kernel_services_notifications.h"
#include "services/normal/notifications/notification_storage.h"
static uint32_t s_ancs_count = 0;
static uint32_t s_acted_upon_count = 0;
void notifications_handle_notification_added(Uuid *id) {
++s_ancs_count;
}
void notifications_handle_notification_removed(Uuid *id) {
--s_ancs_count;
}
void notifications_handle_notification_acted_upon(Uuid *id) {
++s_acted_upon_count;
return;
}
void notifications_handle_notification_action_result() {
}
void notifications_add_notification(TimelineItem *notification) {
notification_storage_store(notification);
++s_ancs_count;
}
void fake_kernel_services_notifications_reset(void) {
s_ancs_count = 0;
s_acted_upon_count = 0;
}
uint32_t fake_kernel_services_notifications_ancs_notifications_count(void) {
return s_ancs_count;
}
uint32_t fake_kernel_services_notifications_acted_upon_count(void) {
return s_acted_upon_count;
}

View file

@ -0,0 +1,39 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
#include "services/normal/timeline/item.h"
void notifications_handle_ancs_message(TimelineItem *notification);
void notifications_handle_ancs_notification_removed(uint32_t ancs_uid);
void notifications_add_notification(TimelineItem *notification);
////////////////////////////////////////////////////////////////////////////////////////////////////
// Fake manipulation:
//! Resets the fake (i.e. ANCS count)
void fake_kernel_services_notifications_reset(void);
//! @return Number of times notifications_handle_ancs_message() was called.
uint32_t fake_kernel_services_notifications_ancs_notifications_count(void);
//! @return Number of times notifications_handle_notification_acted_upon() was called.
uint32_t fake_kernel_services_notifications_acted_upon_count(void);

18
tests/fakes/fake_light.c Normal file
View file

@ -0,0 +1,18 @@
/*
* 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.
*/
void light_enable_interaction(void) {
}

View file

@ -0,0 +1,81 @@
/*
* 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.
*/
#pragma once
#include <clar.h>
#include <regex.h>
#include <stdio.h>
#include <stdarg.h>
#include <util/string.h>
// make sure we can unit-test log output
#define CUSTOM_LOG_INTERNAL
const char **s_log_internal__expected;
const char **s_log_internal__expected_regex;
static void log_internal(uint8_t log_level, const char* src_filename, int src_line_number,
const char* fmt, va_list args) {
// this implementation for log_internal constructs the logged string inside of a static buffer
// so we can compare it against some test expectation
printf("%s:%d> ", GET_FILE_NAME(src_filename), src_line_number);
static char buffer[256];
const int written = vsprintf(buffer, fmt, args);
cl_assert(written < sizeof(buffer)); // there's no vsnprintf()
buffer[written] = '\0';
// compare log message against array of expectations
if (s_log_internal__expected) {
if (*s_log_internal__expected == NULL) {
cl_assert_equal_s("Did not expect another logged string, but got", buffer);
cl_fail("Should only happen if the log statement exactly matches the message above.");
}
cl_assert_equal_s(*s_log_internal__expected, buffer);
s_log_internal__expected++;
} else if (s_log_internal__expected_regex) {
if (*s_log_internal__expected_regex == NULL) {
cl_assert_equal_s("Did not expect another logged string, but got", buffer);
cl_fail("Should only happen if the log statement exactly matches the message above.");
}
regex_t regex = {};
// Compile regex:
cl_assert_equal_i(0, regcomp(&regex, *s_log_internal__expected_regex, REG_EXTENDED));
// Match regex:
const int rv = regexec(&regex, buffer, 0, NULL, 0);
if (rv) {
// Check REG_... #defines in regex.h for what these values mean.
char msgbuf[256];
char regexerr[128];
regerror(rv, &regex, regexerr, sizeof(regexerr));
sprintf(msgbuf, "Regex match failed (rv=%i): %s\n \"%s\" didn't match pattern \"%s\"",
rv, regexerr, buffer, *s_log_internal__expected_regex);
cl_fail(msgbuf);
}
regfree(&regex);
s_log_internal__expected_regex++;
}
printf("%s", buffer);
printf("\n");
}

View file

@ -0,0 +1,34 @@
/*
* 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 "drivers/mpu.h"
// Everything but NULL + 256
const MpuRegion s_fake_app_region = { .region_num = 9, .enabled = true, .base_address = 256,
.size = 0xFFFFFFFF - (1 << 8), .priv_read = true, .priv_write = true, .user_read = true, .user_write = true };
const MpuRegion* memory_layout_get_app_region(void) {
return &s_fake_app_region;
}
bool memory_layout_is_pointer_in_region(const MpuRegion *region, const void *ptr) {
uintptr_t p = (uintptr_t) ptr;
return (p >= region->base_address && p < (region->base_address + region->size));
}
bool memory_layout_is_buffer_in_region(const MpuRegion *region, const void *buf, size_t length) {
return memory_layout_is_pointer_in_region(region, buf) && memory_layout_is_pointer_in_region(region, buf + length);
}

195
tests/fakes/fake_mutex.h Normal file
View file

@ -0,0 +1,195 @@
/*
* 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.
*/
#pragma once
#include "system/passert.h"
#include "util/list.h"
#include <os/mutex.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
typedef struct FakePebbleMutex {
ListNode node;
uint8_t lock_count;
bool recursive;
} FakePebbleMutex;
static FakePebbleMutex *s_mutex_list;
static bool s_asserts_disabled;
static bool s_assert_triggered;
//
// Helpers
//
static void *prv_mutex_create(bool is_recursive) {
FakePebbleMutex *mutex = malloc(sizeof(FakePebbleMutex));
*mutex = (FakePebbleMutex) {
.recursive = is_recursive,
};
s_mutex_list = (FakePebbleMutex *)list_prepend((ListNode *)s_mutex_list, (ListNode *)mutex);
return mutex;
}
static bool prv_list_foreach_assert_unlocked(ListNode *node, void *context) {
FakePebbleMutex *mutex = (FakePebbleMutex *)node;
bool *failed = context;
if (mutex->lock_count != 0) {
// If this is failing, set your breakpoint here to find out which mutex
printf("Mutex (0x%lx) was not unlocked when fake_mutex_assert_all_unlocked called\n",
(unsigned long)mutex);
s_assert_triggered = true;
cl_assert(s_asserts_disabled);
*failed = true;
}
return true;
}
//
// Fake Mutex API
//
void fake_mutex_reset(bool assert_all_unlocked) {
ListNode *iter = (ListNode *)s_mutex_list;
while (iter) {
ListNode *next = iter->next;
if (assert_all_unlocked) {
FakePebbleMutex *mutex = (FakePebbleMutex *)iter;
cl_assert_equal_i(0, mutex->lock_count);
}
free(iter);
iter = next;
}
s_mutex_list = NULL;
s_asserts_disabled = false;
s_assert_triggered = false;
}
void fake_mutex_assert_all_unlocked(void) {
bool failed;
list_foreach((ListNode *)s_mutex_list, prv_list_foreach_assert_unlocked, &failed);
}
//
// Mutex API
//
PebbleMutex *mutex_create(void) {
const bool is_recursive = false;
return prv_mutex_create(is_recursive);
}
void mutex_destroy(PebbleMutex *handle) {
FakePebbleMutex *mutex = (FakePebbleMutex *)handle;
list_remove((ListNode *)mutex, (ListNode **)&s_mutex_list, NULL);
}
void mutex_lock(PebbleMutex *handle) {
FakePebbleMutex *mutex = (FakePebbleMutex *)handle;
if (mutex->lock_count != 0) {
s_assert_triggered = true;
cl_assert_(s_asserts_disabled, "mutex_lock called with mutex that was already locked");
}
mutex->lock_count++;
}
bool mutex_lock_with_timeout(PebbleMutex *handle, uint32_t timeout_ms) {
mutex_lock(handle);
return true;
}
void mutex_lock_with_lr(PebbleMutex *handle, uint32_t myLR) {
mutex_lock(handle);
}
void mutex_unlock(PebbleMutex *handle) {
FakePebbleMutex *mutex = (FakePebbleMutex *)handle;
if (mutex->lock_count != 1) {
s_assert_triggered = true;
cl_assert_(s_asserts_disabled, "mutex_unlock called with mutex that was not locked");
}
mutex->lock_count--;
}
PebbleRecursiveMutex * mutex_create_recursive(void) {
const bool is_recursive = true;
return prv_mutex_create(is_recursive);
}
void mutex_lock_recursive(PebbleRecursiveMutex *handle) {
FakePebbleMutex *mutex = (FakePebbleMutex *)handle;
mutex->lock_count++;
}
bool mutex_lock_recursive_with_timeout(PebbleRecursiveMutex *handle, uint32_t timeout_ms) {
mutex_lock_recursive(handle);
return true;
}
bool mutex_lock_recursive_with_timeout_and_lr(PebbleRecursiveMutex *handle,
uint32_t timeout_ms,
uint32_t LR) {
mutex_lock_recursive(handle);
return true;
}
//! Tests if a given mutex is owned by the current task. Useful for
//! ensuring locks are held when they should be.
bool mutex_is_owned_recursive(PebbleRecursiveMutex *handle) {
return true;
}
void mutex_unlock_recursive(PebbleRecursiveMutex *handle) {
FakePebbleMutex *mutex = (FakePebbleMutex *)handle;
if (mutex->lock_count == 0) {
s_assert_triggered = true;
cl_assert_(s_asserts_disabled,
"mutex_unlock_recursive called when lock count not greater than 0");
}
mutex->lock_count--;
}
//! @return true if the calling task owns the mutex
void mutex_assert_held_by_curr_task(PebbleMutex *handle, bool is_held) {
return;
}
//! @return true if the calling task owns the mutex
void mutex_assert_recursive_held_by_curr_task(PebbleRecursiveMutex *handle, bool is_held) {
return;
}
// PRIVATE: Only used for testing this module
void fake_mutex_set_should_assert(bool should) {
s_asserts_disabled = !should;
}
bool fake_mutex_get_assert_triggered(void) {
return s_assert_triggered;
}
bool fake_mutex_all_unlocked(void) {
bool failed;
s_asserts_disabled = true;
list_foreach((ListNode *)s_mutex_list, prv_list_foreach_assert_unlocked, &failed);
s_asserts_disabled = false;
return !failed;
}

View file

@ -0,0 +1,320 @@
/*
* 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.
*/
#pragma once
#include "fake_pbl_malloc.h"
#include "services/common/new_timer/new_timer.h"
#include "util/list.h"
#include "drivers/rtc.h"
#include "system/passert.h"
#include <stdio.h>
// Structure of a timer
typedef struct StubTimer {
//! Entry into either the running timers (s_running_timers) list or the idle timers
//! (s_idle_timers)
ListNode list_node;
TimerID id; //<! ID assigned to this timer
//! client provided callback function and argument
NewTimerCallback cb;
void* cb_data;
//! The tick value when this timer will expire (in milliseconds). If the timer isn't currently
//! running (scheduled) this value will be zero.
uint32_t timeout_ms;
//! True if this timer should automatically be rescheduled for period_ms from now
bool repeating;
uint32_t period_ms;
//! True if this timer is currently having its callback executed.
bool executing;
//! Set by the delete function of client tries to delete a timer currently executing it's callback
bool defer_delete;
} StubTimer;
// =============================================================================================
// Stubs
static ListNode *s_running_timers = NULL;
static ListNode *s_idle_timers = NULL;
// Call counters
static int s_num_new_timer_create_calls = 0;
static int s_num_new_timer_start_calls = 0;
static int s_num_new_timer_stop_calls = 0;
static int s_num_new_timer_delete_calls = 0;
static int s_num_new_timer_schedule_calls = 0;
// Last parameters
static TimerID s_new_timer_start_param_timer_id;
static uint32_t s_new_timer_start_param_timeout_ms;
static NewTimerCallback s_new_timer_start_param_cb;
static void * s_new_timer_start_param_cb_data;
// Debug utility
/*
static void prv_print_idle_list(char* title) {
printf("%s IDLE LIST:\n", title);
StubTimer *node = (StubTimer *) s_idle_timers;
while (node) {
StubTimer *next = (StubTimer *) list_get_next(&node->list_node);
printf(" %p\n", node);
if (node == next) {
printf("RECURSIVE!!!\n");
return;
}
node = next;
}
printf("\n");
}
*/
static bool prv_id_list_filter(ListNode* node, void* data) {
StubTimer* timer = (StubTimer*)node;
return timer->id == (uint32_t) data;
}
static StubTimer* prv_find_timer(TimerID timer_id)
{
// Look for this timer in either the running or idle list
ListNode* node = list_find(s_running_timers, prv_id_list_filter, (void*)(intptr_t)timer_id);
if (!node) {
node = list_find(s_idle_timers, prv_id_list_filter, (void*)(intptr_t)timer_id);
}
return (StubTimer *)node;
}
static int prv_timer_expire_compare_func(void* a, void* b) {
return (((StubTimer*)b)->timeout_ms - ((StubTimer*)a)->timeout_ms);
}
static int stub_new_timer_create(void) {
StubTimer *timer = (StubTimer *) kernel_malloc(sizeof(StubTimer));
static int s_next_timer_id = 1;
*timer = (StubTimer) {
.id = s_next_timer_id++,
};
s_idle_timers = list_insert_before(s_idle_timers, &timer->list_node);
return timer->id;
}
////////////////////////////////////
// Stub manipulation:
//
bool stub_new_timer_start(TimerID timer_id, uint32_t timeout_ms, NewTimerCallback cb, void *cb_data,
uint32_t flags) {
StubTimer* timer = prv_find_timer(timer_id);
// Remove it from its current list
if (list_contains(s_running_timers, &timer->list_node)) {
list_remove(&timer->list_node, &s_running_timers /* &head */, NULL /* &tail */);
} else {
list_remove(&timer->list_node, &s_idle_timers /* &head */, NULL /* &tail */);
}
// Set timer variables
timer->cb = cb;
timer->cb_data = cb_data;
timer->timeout_ms = timeout_ms;
timer->repeating = flags & TIMER_START_FLAG_REPEATING;
timer->period_ms = timeout_ms;
// Insert into sorted order in the running list
s_running_timers = list_sorted_add(s_running_timers, &timer->list_node,
prv_timer_expire_compare_func, true);
return true;
}
bool stub_new_timer_stop(TimerID timer_id) {
StubTimer* timer = prv_find_timer(timer_id);
// Move it to the idle list if it's currently running
if (list_contains(s_running_timers, &timer->list_node)) {
list_remove(&timer->list_node, &s_running_timers /* &head */, NULL /* &tail */);
s_idle_timers = list_insert_before(s_idle_timers, &timer->list_node);
}
// Clear the repeating flag so that if they call this method from a callback it won't get
// rescheduled
timer->repeating = false;
timer->timeout_ms = 0;
return !timer->executing;
}
void stub_new_timer_delete(TimerID timer_id) {
StubTimer* timer = prv_find_timer(timer_id);
// Automatically stop it if it it's not stopped already
if (list_contains(s_running_timers, &timer->list_node)) {
timer->timeout_ms = 0;
list_remove(&timer->list_node, &s_running_timers /* &head */, NULL /* &tail */);
s_idle_timers = list_insert_before(s_idle_timers, &timer->list_node);
}
timer->repeating = false; // In case it's currently executing, make sure we don't reschedule it
if (!timer->executing) {
list_remove(&timer->list_node, &s_idle_timers /* &head */, NULL /* &tail */);
kernel_free(timer);
} else {
timer->defer_delete = true;
}
}
bool stub_new_timer_is_scheduled(TimerID timer_id) {
StubTimer* timer = prv_find_timer(timer_id);
if (timer == NULL) {
return false;
}
return list_contains(s_running_timers, &timer->list_node);
}
uint32_t stub_new_timer_timeout(TimerID timer_id) {
StubTimer* timer = prv_find_timer(timer_id);
if (timer == NULL) {
return false;
}
return timer->timeout_ms;
}
// Mark the timer as executing. This prevents it from getting deleted. In the real implementation,
// it would get deleted after it's callback returned
void stub_new_timer_set_executing(TimerID timer_id, bool set) {
StubTimer* timer = prv_find_timer(timer_id);
PBL_ASSERTN(timer != NULL);
timer->executing = true;
}
void * stub_new_timer_callback_data(TimerID timer_id) {
StubTimer* timer = prv_find_timer(timer_id);
if (timer == NULL) {
return false;
}
return timer->cb_data;
}
bool stub_new_timer_fire(TimerID timer_id) {
StubTimer* timer = prv_find_timer(timer_id);
if (timer == NULL) {
return false;
}
if (list_contains(s_running_timers, &timer->list_node)) {
list_remove(&timer->list_node, &s_running_timers /* &head */, NULL /* &tail */);
s_idle_timers = list_insert_before(s_idle_timers, &timer->list_node);
} else {
printf("WARNING: Attempted to fire a non-running timer\n");
return false;
}
timer->timeout_ms = 0;
timer->executing = true;
timer->cb(timer->cb_data);
timer->executing = false;
if (timer->defer_delete) {
// Timer was deleted from the callback, clean it up now:
list_remove(&timer->list_node, &s_idle_timers /* &head */, NULL /* &tail */);
kernel_free(timer);
return true;
}
if (timer->repeating && timer->timeout_ms == 0) {
stub_new_timer_start(timer_id, timer->period_ms, timer->cb, timer->cb_data,
TIMER_START_FLAG_REPEATING);
}
return true;
}
void stub_new_timer_cleanup(void) {
StubTimer *node = (StubTimer *) s_running_timers;
while (node) {
StubTimer *next = (StubTimer *) list_get_next(&node->list_node);
list_remove(&node->list_node, &s_running_timers, NULL);
kernel_free(node);
node = next;
}
PBL_ASSERTN(s_running_timers == NULL);
node = (StubTimer *) s_idle_timers;
while (node) {
StubTimer *next = (StubTimer *) list_get_next(&node->list_node);
list_remove(&node->list_node, &s_idle_timers, NULL);
kernel_free(node);
node = next;
}
PBL_ASSERTN(s_idle_timers == NULL);
}
TimerID stub_new_timer_get_next(void) {
StubTimer *timer = (StubTimer *) list_get_head(s_running_timers);
return timer ? timer->id : TIMER_INVALID_ID;
}
void stub_new_timer_invoke(int num_to_invoke) {
TimerID timer = stub_new_timer_get_next();
while (timer != TIMER_INVALID_ID && num_to_invoke--) {
stub_new_timer_fire(timer);
timer = stub_new_timer_get_next();
}
}
// =============================================================================================
// Fakes
// Create a new timer
TimerID new_timer_create(void) {
s_num_new_timer_create_calls++;
return stub_new_timer_create();
}
// Start a timer
bool new_timer_start(TimerID timer_id, uint32_t timeout_ms, NewTimerCallback cb, void *cb_data,
uint32_t flags) {
s_num_new_timer_start_calls++;
s_new_timer_start_param_timer_id = timer_id;
s_new_timer_start_param_timeout_ms = timeout_ms;
s_new_timer_start_param_cb = cb;
s_new_timer_start_param_cb_data = cb_data;
return stub_new_timer_start(timer_id, timeout_ms, cb, cb_data, flags);
}
// Stop a timer
bool new_timer_stop(TimerID timer_id) {
s_num_new_timer_stop_calls++;
return stub_new_timer_stop(timer_id);
}
// Delete a timer
void new_timer_delete(TimerID timer_id) {
s_num_new_timer_delete_calls++;
stub_new_timer_delete(timer_id);
}
bool new_timer_scheduled(TimerID timer, uint32_t *expire_ms_p) {
s_num_new_timer_schedule_calls++;
return stub_new_timer_is_scheduled(timer);
}

View file

@ -0,0 +1,125 @@
/*
* 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"
static TimelineItem s_last_stored_notification = {};
static int s_notification_store_count = 0;
static int s_notification_remove_count = 0;
static TimelineItem s_existing_ancs_notification = {
.header = (CommonTimelineItemHeader) {
.id = UUID_INVALID,
.ancs_uid = 0
}
};
extern T_STATIC bool prv_deep_copy_attributes_actions(AttributeList *attr_list,
TimelineItemActionGroup *action_group,
TimelineItem *item_out);
void fake_notification_storage_reset(void) {
s_notification_store_count = 0;
s_notification_remove_count = 0;
s_existing_ancs_notification = (TimelineItem) {
.header = (CommonTimelineItemHeader) {
.id = UUID_INVALID,
.ancs_uid = 0
}
};
}
TimelineItem *fake_notification_storage_get_last_notification(void) {
return &s_last_stored_notification;
}
int fake_notification_storage_get_store_count(void) {
return s_notification_store_count;
}
int fake_notification_storage_get_remove_count(void) {
return s_notification_remove_count;
}
void fake_notification_storage_set_existing_ancs_notification(Uuid *uuid, uint32_t ancs_uid) {
s_existing_ancs_notification = (TimelineItem) {
.header = (CommonTimelineItemHeader) {
.id = *uuid,
.ancs_uid = ancs_uid
}
};
}
void notification_storage_init(void) {
}
void notification_storage_lock(void) {
}
void notification_storage_unlock(void) {
}
void notification_storage_store(TimelineItem *notification) {
++s_notification_store_count;
// Copy notification into our last stored buffer
timeline_item_free_allocated_buffer(&s_last_stored_notification);
s_last_stored_notification = *notification;
if (!prv_deep_copy_attributes_actions(&notification->attr_list, &notification->action_group,
&s_last_stored_notification)) {
s_last_stored_notification = (TimelineItem){};
}
}
bool notification_storage_notification_exists(const Uuid *id) {
return false;
}
size_t notification_storage_get_len(const Uuid *uuid) {
return 0;
}
bool notification_storage_get(const Uuid *id, TimelineItem *item_out) {
return false;
}
void notification_storage_set_status(const Uuid *id, uint8_t status) {
}
bool notification_storage_get_status(const Uuid *id, uint8_t *status) {
return true;
}
void notification_storage_remove(const Uuid *id) {
++s_notification_remove_count;
}
bool notification_storage_find_ancs_notification_id(uint32_t ancs_uid, Uuid *uuid_out) {
*uuid_out = s_existing_ancs_notification.header.id;
return (s_existing_ancs_notification.header.ancs_uid == ancs_uid);
}
bool notification_storage_find_ancs_notification_by_timestamp(
TimelineItem *notification, CommonTimelineItemHeader *header_out) {
if (uuid_is_invalid(&s_existing_ancs_notification.header.id)) {
return false;
}
*header_out = s_existing_ancs_notification.header;
return true;
}
void notification_storage_rewrite(void (*iter_callback)(TimelineItem *notification,
SerializedTimelineItemHeader *header, void *data), void *data) {
}

62
tests/fakes/fake_otp.c Normal file
View file

@ -0,0 +1,62 @@
/*
* 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 "fake_otp.h"
#include "drivers/otp.h"
#include "system/passert.h"
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
char s_otp_buffer[512];
uint8_t s_otp_locks[16];
void fake_otp_reset(void) {
memset(s_otp_buffer, 0xff, sizeof(s_otp_buffer));
memset(s_otp_locks, 0xff, sizeof(s_otp_locks));
}
char * otp_get_slot(const uint8_t index) {
PBL_ASSERTN(index < NUM_OTP_SLOTS);
return (char * const) (s_otp_buffer + (32 * index));
}
uint8_t * otp_get_lock(const uint8_t index) {
PBL_ASSERTN(index < NUM_OTP_SLOTS);
return (uint8_t * const) (s_otp_locks + index);
}
bool otp_is_locked(const uint8_t index) {
return (*otp_get_lock(index) == 0);
}
OtpWriteResult otp_write_slot(const uint8_t index, const char *value) {
if (otp_is_locked(index)) {
return OtpWriteFailAlreadyWritten;
}
// Write the value
char *slot = otp_get_slot(index);
strcpy(slot, value);
// Lock the OTP sector
uint8_t *lock = otp_get_lock(index);
*lock = 0;
return OtpWriteSuccess;
}

19
tests/fakes/fake_otp.h Normal file
View file

@ -0,0 +1,19 @@
/*
* 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.
*/
#pragma once
void fake_otp_reset(void);

View file

@ -0,0 +1,238 @@
/*
* 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.
*/
#pragma once
#include <util/heap.h>
#include "util/list.h"
#include "util/math.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
typedef struct {
ListNode list_node;
size_t bytes;
void *ptr;
void *lr;
} PointerListNode;
static PointerListNode *s_pointer_list = NULL;
static bool prv_pointer_list_filter(ListNode *node, void *ptr) {
return ((PointerListNode *)node)->ptr == ptr;
}
static void prv_pointer_list_add(void *ptr, size_t bytes, void *lr) {
PointerListNode *node = malloc(sizeof(PointerListNode));
list_init(&node->list_node);
node->ptr = ptr;
node->bytes = bytes;
node->lr = lr;
s_pointer_list = (PointerListNode *)list_prepend((ListNode *)s_pointer_list, &node->list_node);
}
static void prv_pointer_list_remove(void *ptr) {
ListNode *node = list_find((ListNode *)s_pointer_list, prv_pointer_list_filter, ptr);
if (!node && ptr) {
printf("*** INVALID FREE: %p\n", ptr);
cl_fail("Pointer has not been alloc'd (maybe a double free?)");
}
list_remove(node, (ListNode **)&s_pointer_list, NULL);
free(node);
}
static size_t s_max_size_allowed = ~0;
static Heap s_heap;
Heap *task_heap_get_for_current_task(void) {
return &s_heap;
}
static void *malloc_and_track(size_t bytes, void *lr) {
if (bytes >= s_max_size_allowed) {
return NULL;
}
void *rt = malloc(bytes);
prv_pointer_list_add(rt, bytes, lr);
return rt;
}
static void *calloc_and_track(int n, size_t bytes, void *lr) {
if ((bytes * n) >= s_max_size_allowed) {
return NULL;
}
void *rt = calloc(n, bytes);
prv_pointer_list_add(rt, bytes, lr);
return rt;
}
void fake_malloc_set_largest_free_block(size_t bytes) {
s_max_size_allowed = bytes;
}
static void free_and_track(void *ptr) {
prv_pointer_list_remove(ptr);
free(ptr);
}
void *realloc_and_track(void *ptr, size_t bytes, void *lr) {
void *new_ptr = malloc_and_track(bytes, lr);
if (new_ptr && ptr) {
ListNode *node = list_find((ListNode *)s_pointer_list, prv_pointer_list_filter, ptr);
cl_assert(node);
memcpy(new_ptr, ptr, MIN(((PointerListNode*)node)->bytes, bytes));
free_and_track(ptr);
}
return new_ptr;
}
int fake_pbl_malloc_num_net_allocs(void) {
return list_count((ListNode *)s_pointer_list);
}
void fake_pbl_malloc_check_net_allocs(void) {
if (fake_pbl_malloc_num_net_allocs() > 0) {
ListNode *node = (ListNode *)s_pointer_list;
while (node) {
PointerListNode *ptr_node = (PointerListNode *)node;
printf("Still allocated: %p (%zu bytes, lr %p)\n",
ptr_node->ptr, ptr_node->bytes, ptr_node->lr);
node = list_get_next(node);
}
}
cl_assert_equal_i(fake_pbl_malloc_num_net_allocs(), 0);
}
void fake_pbl_malloc_clear_tracking(void) {
while (s_pointer_list) {
ListNode *new_head = list_pop_head((ListNode *)s_pointer_list);
free(s_pointer_list);
s_pointer_list = (PointerListNode *)new_head;
}
s_max_size_allowed = ~0;
}
void *task_malloc(size_t bytes) {
return malloc_and_track(bytes, __builtin_return_address(0));
}
void *task_malloc_check(size_t bytes) {
return malloc_and_track(bytes, __builtin_return_address(0));
}
void *task_realloc(void *ptr, size_t bytes) {
return realloc_and_track(ptr, bytes, __builtin_return_address(0));
}
void *task_zalloc(size_t bytes) {
void *ptr = task_malloc(bytes);
if (ptr) {
memset(ptr, 0, bytes);
}
return ptr;
}
void *task_zalloc_check(size_t bytes) {
void *ptr = task_malloc_check(bytes);
memset(ptr, 0, bytes);
return ptr;
}
void *task_calloc(size_t count, size_t size) {
return calloc_and_track(count, size, __builtin_return_address(0));
}
void *task_calloc_check(size_t count, size_t size) {
return calloc_and_track(count, size, __builtin_return_address(0));
}
void task_free(void *ptr) {
free_and_track(ptr);
}
void *applib_zalloc(size_t bytes) {
return calloc_and_track(1, bytes, __builtin_return_address(0));
}
void applib_free(void *ptr) {
free_and_track(ptr);
}
void *app_malloc(size_t bytes) {
return malloc_and_track(bytes, __builtin_return_address(0));
}
void *app_malloc_check(size_t bytes) {
return malloc_and_track(bytes, __builtin_return_address(0));
}
void app_free(void *ptr) {
free_and_track(ptr);
}
void *kernel_malloc(size_t bytes) {
return malloc_and_track(bytes, __builtin_return_address(0));
}
void *kernel_zalloc(size_t bytes) {
return calloc_and_track(1, bytes, __builtin_return_address(0));
}
void *kernel_zalloc_check(size_t bytes) {
return kernel_zalloc(bytes);
}
void *kernel_malloc_check(size_t bytes) {
return malloc_and_track(bytes, __builtin_return_address(0));
}
void *kernel_realloc(void *ptr, size_t bytes) {
return realloc_and_track(ptr, bytes, __builtin_return_address(0));
}
void kernel_free(void *ptr) {
free_and_track(ptr);
}
void* kernel_calloc(size_t count, size_t size) {
return calloc_and_track(count, size, __builtin_return_address(0));
}
char* kernel_strdup(const char* s) {
char *r = malloc_and_track(strlen(s) + 1, __builtin_return_address(0));
if (!r) {
return NULL;
}
strcpy(r, s);
return r;
}
char* kernel_strdup_check(const char* s) {
return kernel_strdup(s);
}
char* task_strdup(const char* s) {
return kernel_strdup(s);
}
void smart_free(void *ptr) {
free_and_track(ptr);
}

View file

@ -0,0 +1,36 @@
/*
* 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.
*/
#pragma once
#include "util/time/time.h"
struct tm *pbl_override_localtime(const time_t *timep) {
static struct tm local_tm;
localtime_r(timep, &local_tm);
return &local_tm;
}
struct tm *pbl_override_gmtime(const time_t *timep) {
static struct tm local_tm;
gmtime_r(timep, &local_tm);
return &local_tm;
}
time_t pbl_override_mktime(struct tm *tb) {
return mktime(tb);
}

View file

@ -0,0 +1,39 @@
/*
* 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.
*/
#pragma once
#include "stubs_worker_manager.h"
#include "kernel/pebble_tasks.h"
#include "FreeRTOS.h"
#include "task.h"
static PebbleTask s_current_task = PebbleTask_KernelMain;
PebbleTask pebble_task_get_current(void) {
return s_current_task;
}
void stub_pebble_tasks_set_current(PebbleTask task) {
s_current_task = task;
}
const char* pebble_task_get_name(PebbleTask task) {
return "App <Stub>";
}

View file

@ -0,0 +1,42 @@
/*
* 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 "applib/ui/property_animation_private.h"
#include "applib/ui/layer.h"
static const PropertyAnimationImplementation s_frame_layer_implementation = {
.accessors = {
.setter.grect = (const GRectSetter)layer_set_frame_by_value,
.getter.grect = (const GRectGetter)layer_get_frame_by_value,
},
};
PropertyAnimation *WEAK property_animation_create_layer_frame(
struct Layer *layer, GRect *from_frame, GRect *to_frame) {
PropertyAnimationPrivate *animation = (PropertyAnimationPrivate *)
property_animation_create(&s_frame_layer_implementation, layer, from_frame, to_frame);
if (from_frame) {
animation->values.from.grect = *from_frame;
PropertyAnimationImplementation *impl =
(PropertyAnimationImplementation *)animation->animation.implementation;
impl->accessors.setter.grect(animation->subject, animation->values.from.grect);
}
if (to_frame) {
animation->values.to.grect = *to_frame;
}
return (PropertyAnimation *)animation;
}

View file

@ -0,0 +1,141 @@
/*
* 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 "fake_put_bytes_storage_mem.h"
#include "clar_asserts.h"
#include "kernel/pbl_malloc.h"
#include "services/common/put_bytes/put_bytes_storage_internal.h"
#include "system/passert.h"
#define FAKE_STORAGE_MAX_SIZE (512 * 1024)
typedef struct FakePutBytesStorageData {
PutBytesStorageInfo *info;
bool last_is_success;
uint32_t crc;
uint32_t total_size;
uint8_t buffer[FAKE_STORAGE_MAX_SIZE];
} FakePutBytesStorageData;
FakePutBytesStorageData s_storage_data;
bool pb_storage_raw_get_status(PutBytesObjectType obj_type, PbInstallStatus *status) {
return false;
}
static bool fake_pb_storage_mem_init(PutBytesStorage *storage, PutBytesObjectType object_type,
uint32_t total_size, PutBytesStorageInfo *info,
uint32_t append_offset) {
// This fake only supports one put bytes storage to be init'd at a time.
PBL_ASSERTN(!s_storage_data.total_size);
size_t buffer_size = total_size + sizeof(FirmwareDescription);
memset(s_storage_data.buffer, 0, sizeof(s_storage_data.buffer));
s_storage_data.total_size = buffer_size;
PutBytesStorageInfo *info_copy = NULL;
if (info) {
info_copy = (PutBytesStorageInfo *)kernel_malloc_check(sizeof(PutBytesStorageInfo));
*info_copy = *info;
}
s_storage_data.info = info_copy;
storage->impl_data = &s_storage_data;
// put_bytes_storage_raw.c is weird, it reserves space at the beginning for FirmwareDescription:
storage->current_offset = sizeof(FirmwareDescription);
return true;
}
uint32_t fake_pb_storage_mem_get_max_size(PutBytesObjectType object_type) {
return FAKE_STORAGE_MAX_SIZE;
}
static void(*s_do_before_write)(void) = NULL;
static void fake_pb_storage_mem_write(PutBytesStorage *storage, uint32_t offset,
const uint8_t *buffer, uint32_t length) {
PBL_ASSERTN(s_storage_data.total_size);
PBL_ASSERTN(offset + length <= s_storage_data.total_size);
if (s_do_before_write) {
s_do_before_write();
s_do_before_write = NULL;
}
memcpy(s_storage_data.buffer + offset, buffer, length);
}
static uint32_t fake_pb_storage_mem_calculate_crc(PutBytesStorage *storage, PutBytesCrcType crc_type) {
PBL_ASSERTN(storage->impl_data == &s_storage_data);
return s_storage_data.crc;
}
static void prv_cleanup(void) {
kernel_free(s_storage_data.info);
s_storage_data.info = NULL;
s_storage_data.total_size = 0;
}
static void fake_pb_storage_mem_deinit(PutBytesStorage *storage, bool is_success) {
PBL_ASSERTN(storage->impl_data == &s_storage_data);
prv_cleanup();
s_storage_data.last_is_success = is_success;
}
const PutBytesStorageImplementation s_raw_implementation = {
.init = fake_pb_storage_mem_init,
.get_max_size = fake_pb_storage_mem_get_max_size,
.write = fake_pb_storage_mem_write,
.calculate_crc = fake_pb_storage_mem_calculate_crc,
.deinit = fake_pb_storage_mem_deinit,
};
const PutBytesStorageImplementation s_file_implementation = {
.init = fake_pb_storage_mem_init,
.get_max_size = fake_pb_storage_mem_get_max_size,
.write = fake_pb_storage_mem_write,
.calculate_crc = fake_pb_storage_mem_calculate_crc,
.deinit = fake_pb_storage_mem_deinit,
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// Fake manipulation
void fake_pb_storage_register_cb_before_write(void (*cb_before_write)(void)) {
s_do_before_write = cb_before_write;
}
void fake_pb_storage_mem_reset(void) {
prv_cleanup();
s_storage_data = (FakePutBytesStorageData) {};
}
void fake_pb_storage_mem_set_crc(uint32_t crc) {
s_storage_data.crc = crc;
}
bool fake_pb_storage_mem_get_last_success(void) {
return s_storage_data.last_is_success;
}
void fake_pb_storage_mem_assert_contents_written(const uint8_t contents[], size_t size) {
cl_assert_equal_m(contents, s_storage_data.buffer + sizeof(FirmwareDescription), size);
}
void fake_pb_storage_mem_assert_fw_description_written(const FirmwareDescription *fw_descr) {
cl_assert_equal_m(fw_descr, s_storage_data.buffer, sizeof(*fw_descr));
}
#undef FAKE_STORAGE_MAX_SIZE

View file

@ -0,0 +1,37 @@
/*
* 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.
*/
#pragma once
#include "services/common/put_bytes/put_bytes.h"
#include "system/firmware_storage.h"
#include <stddef.h>
#include <stdint.h>
uint32_t fake_pb_storage_mem_get_max_size(PutBytesObjectType object_type);
void fake_pb_storage_mem_reset(void);
void fake_pb_storage_mem_set_crc(uint32_t crc);
bool fake_pb_storage_mem_get_last_success(void);
void fake_pb_storage_mem_assert_contents_written(const uint8_t contents[], size_t size);
void fake_pb_storage_mem_assert_fw_description_written(const FirmwareDescription *fw_descr);
void fake_pb_storage_register_cb_before_write(void (*cb_before_write)(void));

127
tests/fakes/fake_queue.c Normal file
View file

@ -0,0 +1,127 @@
/*
* 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 "fake_queue.h"
#include "util/circular_buffer.h"
#include "FreeRTOS.h"
#include "queue.h"
#include <stdlib.h>
typedef struct {
uint16_t item_size;
bool is_semph;
//! @return ticks spent executing the callback
TickType_t (*yield_cb)(QueueHandle_t);
CircularBuffer circular_buffer;
uint8_t storage[];
} FakeQueue;
signed portBASE_TYPE xQueueGenericReceive(QueueHandle_t xQueue, void * const pvBuffer,
TickType_t xTicksToWait, portBASE_TYPE xJustPeeking) {
FakeQueue *q = (FakeQueue *) xQueue;
TickType_t ticks_waited = 0;
while (true) {
uint16_t read_space = circular_buffer_get_read_space_remaining(&q->circular_buffer);
if (read_space >= q->item_size) {
circular_buffer_consume(&q->circular_buffer, q->item_size);
return pdTRUE;
} else {
if (!xTicksToWait || !q->yield_cb) {
return pdFALSE;
} else {
ticks_waited += q->yield_cb(xQueue);
if (ticks_waited >= xTicksToWait) {
return pdFALSE;
}
}
}
}
}
signed portBASE_TYPE xQueueGenericSend(QueueHandle_t xQueue, const void * const pvItemToQueue,
TickType_t xTicksToWait, portBASE_TYPE xCopyPosition) {
FakeQueue *q = (FakeQueue *) xQueue;
TickType_t ticks_waited = 0;
while (true) {
uint16_t write_space = circular_buffer_get_write_space_remaining(&q->circular_buffer);
if (write_space >= q->item_size) {
// Just write a zero for a semaphore
const uint8_t semph_data = 0;
const uint8_t *data = q->is_semph ? &semph_data : (const uint8_t *) pvItemToQueue;
circular_buffer_write(&q->circular_buffer, data, q->item_size);
return pdTRUE;
} else {
if (!xTicksToWait || !q->yield_cb) {
return pdFALSE;
} else {
ticks_waited += q->yield_cb(xQueue);
if (ticks_waited >= xTicksToWait) {
return pdFALSE;
}
}
}
}
}
QueueHandle_t xQueueGenericCreate(unsigned portBASE_TYPE uxQueueLength,
unsigned portBASE_TYPE uxItemSize, unsigned char ucQueueType) {
uint16_t item_size;
const bool is_semph = (ucQueueType == queueQUEUE_TYPE_BINARY_SEMAPHORE);
if (is_semph) {
item_size = 1;
} else {
item_size = uxItemSize;
}
const uint16_t storage_size = (item_size * uxQueueLength);
uint8_t *buffer = malloc(sizeof(FakeQueue) + storage_size);
FakeQueue *q = (FakeQueue *) buffer;
*q = (const FakeQueue) {
.item_size = item_size,
.is_semph = is_semph,
};
circular_buffer_init(&q->circular_buffer, q->storage, storage_size);
return q;
}
void vQueueDelete(QueueHandle_t xQueue) {
free(xQueue);
}
QueueHandle_t xQueueCreateMutex(unsigned char ucQueueType) {
return (QueueHandle_t)1;
}
portBASE_TYPE xQueueTakeMutexRecursive(QueueHandle_t pxMutex, TickType_t xBlockTime) {
return pdTRUE;
}
portBASE_TYPE xQueueGiveMutexRecursive(QueueHandle_t xMutex) {
return pdTRUE;
}
BaseType_t xQueueGenericReset(QueueHandle_t xQueue, BaseType_t xNewQueue) {
return pdTRUE;
}
void fake_queue_set_yield_callback(QueueHandle_t queue,
TickType_t (*yield_cb)(QueueHandle_t)) {
FakeQueue *fake_queue = (FakeQueue *) queue;
fake_queue->yield_cb = yield_cb;
}

25
tests/fakes/fake_queue.h Normal file
View file

@ -0,0 +1,25 @@
/*
* 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.
*/
#pragma once
#include "FreeRTOS.h"
#include "queue.h"
#include <stdbool.h>
void fake_queue_set_yield_callback(QueueHandle_t queue,
TickType_t (*yield_cb)(QueueHandle_t));

View file

@ -0,0 +1,73 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
#include "services/common/regular_timer.h"
static ListNode s_seconds_callbacks;
static ListNode s_minutes_callbacks;
static bool prv_callback_registered_filter(ListNode *found_node, void *data) {
return (found_node == (ListNode *)data);
}
void regular_timer_add_multisecond_callback(RegularTimerInfo* cb, uint16_t seconds) {
if (!list_find(&s_seconds_callbacks, prv_callback_registered_filter, &cb->list_node)) {
list_append(&s_seconds_callbacks, &cb->list_node);
}
}
void regular_timer_add_seconds_callback(RegularTimerInfo* cb) {
regular_timer_add_multisecond_callback(cb, 1);
}
void regular_timer_add_multiminute_callback(RegularTimerInfo* cb, uint16_t minutes) {
if (!list_find(&s_minutes_callbacks, prv_callback_registered_filter, &cb->list_node)) {
list_append(&s_minutes_callbacks, &cb->list_node);
}
}
void regular_timer_add_minutes_callback(RegularTimerInfo* cb) {
regular_timer_add_multiminute_callback(cb, 1);
}
bool regular_timer_is_scheduled(RegularTimerInfo *cb) {
return (list_find(&s_seconds_callbacks, prv_callback_registered_filter, &cb->list_node) ||
list_find(&s_minutes_callbacks, prv_callback_registered_filter, &cb->list_node));
}
bool regular_timer_pending_deletion(RegularTimerInfo *cb) {
return cb->pending_delete;
}
bool regular_timer_remove_callback(RegularTimerInfo* cb) {
bool timer_removed = false;
if (regular_timer_is_scheduled(cb)) {
list_remove(&cb->list_node, NULL, NULL);
timer_removed = true;
}
return timer_removed;
}
void fake_regular_timer_trigger(RegularTimerInfo *timer) {
if (regular_timer_is_scheduled(timer)) {
timer->cb(timer->cb_data);
}
}

View file

@ -0,0 +1,26 @@
/*
* 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.
*/
bool s_empty = true;
void fake_reminder_db_set_empty(bool empty) {
s_empty = empty;
}
bool reminder_db_is_empty() {
return s_empty;
}

View file

@ -0,0 +1,41 @@
/*
* 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 "resource/resource_storage_flash.h"
#include "resource/resource_storage_impl.h"
#include "resource/resource.h"
#include <stdio.h>
#include <inttypes.h>
#include "flash_region/flash_region.h"
#include "util/string.h"
void resource_storage_get_file_name(char *name, size_t buf_length, ResAppNum resource_bank) {
concat_str_int("res_bank", resource_bank, name, buf_length);
}
void resource_storage_clear(ResAppNum app_num) {
return;
}
const SystemResourceBank * resource_storage_flash_get_unused_bank(void) {
static const SystemResourceBank unused_bank = {
.begin = FLASH_REGION_SYSTEM_RESOURCES_BANK_1_BEGIN,
.end = FLASH_REGION_SYSTEM_RESOURCES_BANK_1_END,
};
return &unused_bank;
}

View file

@ -0,0 +1,19 @@
/*
* 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.
*/
#pragma once
void resource_storage_get_file_name(char *name, size_t buf_length, ResAppNum resource_bank);

View file

@ -0,0 +1,96 @@
/*
* 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 "fake_resource_syscalls.h"
#define PATH_STRING_LENGTH 512
#define MAX_OPEN_FILES 512
static FILE* resource_files[MAX_OPEN_FILES] = {NULL};
static const uint32_t resource_start_index = 1; // must start at 1 so font resources work
static uint32_t resource_index = resource_start_index;
ResAppNum sys_get_current_resource_num(void) {
return 0;
}
uint32_t sys_resource_load_file_as_resource(const char *filepath, const char *filename) {
uint32_t resource_id = UINT32_MAX;
char full_path[PATH_STRING_LENGTH];
if (filepath) {
snprintf(full_path, sizeof(full_path), "%s/%s", filepath, filename);
} else {
snprintf(full_path, sizeof(full_path), "%s", filename);
}
FILE* resource_file = fopen(full_path, "r");
if (resource_file) {
resource_files[resource_index] = resource_file;
resource_id = resource_index;
resource_index++; // Increment to next slot
}
return resource_id;
}
size_t sys_resource_size(ResAppNum app_num, uint32_t handle) {
if (handle < UINT32_MAX) {
FILE* resource_file = resource_files[handle];
fseek(resource_file, 0, SEEK_END);
size_t resource_size = ftell(resource_file);
fseek(resource_file, 0, SEEK_SET);
return resource_size;
}
return 0;
}
size_t sys_resource_load_range(ResAppNum app_num, uint32_t id, uint32_t start_bytes,
uint8_t *buffer, size_t num_bytes) {
if (buffer && id < UINT32_MAX) {
FILE* resource_file = resource_files[id];
fseek(resource_file, start_bytes, SEEK_SET);
return fread(buffer, 1, num_bytes, resource_file);
}
return 0;
}
bool sys_resource_bytes_are_readonly(void *bytes) {
return false;
}
const uint8_t *sys_resource_read_only_bytes(ResAppNum app_num, uint32_t resource_id,
size_t *num_bytes_out) {
return NULL;
}
uint32_t sys_resource_get_and_cache(ResAppNum app_num, uint32_t resource_id) {
return resource_id;
}
bool sys_resource_is_valid(ResAppNum app_num, uint32_t resource_id) {
return true;
}
void fake_resource_syscalls_cleanup(void) {
for (int i = resource_start_index; i <= resource_index; i++) {
FILE *resource_file = resource_files[i];
if (resource_file) {
fclose(resource_file);
resource_files[i] = NULL;
}
}
resource_index = resource_start_index;
}

View file

@ -0,0 +1,41 @@
/*
* 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.
*/
#pragma once
#include "resource/resource.h"
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
ResAppNum sys_get_current_resource_num(void);
uint32_t sys_resource_load_file_as_resource(const char *filepath, const char *filename);
size_t sys_resource_size(ResAppNum app_num, uint32_t handle);
size_t sys_resource_load_range(ResAppNum app_num, uint32_t id, uint32_t start_bytes, uint8_t *buffer, size_t num_bytes);
const uint8_t * sys_resource_read_only_bytes(ResAppNum app_num, uint32_t resource_id,
size_t *num_bytes_out);
uint32_t sys_resource_get_and_cache(ResAppNum app_num, uint32_t resource_id);
bool sys_resource_is_valid(ResAppNum app_num, uint32_t resource_id);
void fake_resource_syscalls_cleanup(void);

30
tests/fakes/fake_rng.h Normal file
View file

@ -0,0 +1,30 @@
/*
* 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.
*/
#pragma once
#include <stdlib.h>
static bool s_is_seeded = false;
bool rng_rand(uint32_t *rand_out) {
if (!s_is_seeded) {
srand(0); // Seed with constant, to make unit tests less random :)
s_is_seeded = true;
}
*rand_out = rand();
return true;
}

156
tests/fakes/fake_rtc.c Normal file
View file

@ -0,0 +1,156 @@
/*
* 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 "fake_rtc.h"
#include <stdlib.h>
#include <string.h>
#include <time.h>
static RtcTicks s_rtc_tick_count;
static RtcTicks s_rtc_auto_increment = 0;
// Defined identically to the static variables in rtc.c
static time_t s_time_base = 0;
static int16_t s_time_ms_base = 0;
static int64_t s_time_tick_base = 0;
static TimezoneInfo s_tzinfo = { {0} };
/*
// TODO: Unused right now
void rtc_init(void);
void rtc_init_timers(void);
void rtc_set_time_tm(struct tm* time_tm);
void rtc_next_tick_alarm_init(void);
void rtc_set_next_tick_alarm(RtcTicks tick);
void rtc_disable_next_tick_alarm(void);
bool rtc_is_tick_alarm_initialized(void);
bool rtc_is_lse_started(void);
//! @param buffer Buffer used to write the string into. Must be at least TIME_STRING_BUFFER_SIZE
const char* time_t_to_string(char* buffer, time_t t);
*/
// Stubs
////////////////////////////////////
//! @param buffer Buffer used to write the string into. Must be at least TIME_STRING_BUFFER_SIZE
const char* rtc_get_time_string(char* buffer) {return NULL;}
void rtc_get_time_tm(struct tm* time_tm) {
if (time_tm) {
time_t temp = rtc_get_time();
gmtime_r(&temp, time_tm);
}
}
void rtc_set_time(time_t time) {
s_time_base = time;
}
void rtc_set_timezone(TimezoneInfo *tzinfo) {
s_tzinfo = *tzinfo;
}
bool rtc_is_timezone_set(void) {
// The actual driver checks for the first 4 chars as a uint32_t being 0
return memcmp(s_tzinfo.tm_zone, "\0\0\0\0", 4) != 0;
}
void rtc_get_timezone(TimezoneInfo *tzinfo) {
*tzinfo = s_tzinfo;
}
void rtc_timezone_clear(void) {
memset(&s_tzinfo, 0, sizeof(s_tzinfo));
}
bool rtc_sanitize_struct_tm(struct tm *t) {
return false;
}
bool rtc_sanitize_time_t(time_t *t) {
return rtc_sanitize_struct_tm(NULL);
}
uint16_t rtc_get_timezone_id(void) {
if (rtc_is_timezone_set()) {
return s_tzinfo.timezone_id;
}
return -1;
}
//! @return millisecond port of the current second.
void rtc_get_time_ms(time_t* out_seconds, uint16_t* out_ms) {
*out_ms = s_time_ms_base;
*out_seconds = s_time_base;
}
// @return a unix timestamp from the host machine
time_t rtc_get_time(void) {
return s_time_base;
}
time_t sys_get_time(void) {
return rtc_get_time();
}
//! @return Absolute number of ticks since system start.
RtcTicks rtc_get_ticks(void) {
RtcTicks result = s_rtc_tick_count;
s_rtc_tick_count += s_rtc_auto_increment;
return result;
}
RtcTicks sys_get_ticks(void) {
return rtc_get_ticks();
}
//
// Fake Functions!
//
void fake_rtc_init(RtcTicks initial_ticks, time_t initial_time) {
s_rtc_tick_count = initial_ticks;
s_time_tick_base = initial_ticks;
s_time_base = initial_time;
}
void fake_rtc_increment_time(time_t inc) {
s_time_base += inc;
}
void fake_rtc_increment_time_ms(uint32_t inc) {
const uint32_t new_ms = s_time_ms_base + inc;
if (new_ms >= 1000) {
s_time_base += new_ms / 1000;
}
s_time_ms_base = new_ms % 1000;
}
void fake_rtc_set_ticks(RtcTicks new_ticks) {
s_rtc_tick_count = new_ticks;
}
void fake_rtc_increment_ticks(RtcTicks tick_increment) {
s_rtc_tick_count += tick_increment;
}
void fake_rtc_auto_increment_ticks(RtcTicks auto_increment) {
s_rtc_auto_increment = auto_increment;
}

33
tests/fakes/fake_rtc.h Normal file
View file

@ -0,0 +1,33 @@
/*
* 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.
*/
#pragma once
#include "drivers/rtc.h"
#include <stdbool.h>
#include <stdint.h>
#include <time.h>
void fake_rtc_init(RtcTicks initial_ticks, time_t initial_time);
void fake_rtc_increment_time(time_t inc);
void fake_rtc_increment_time_ms(uint32_t inc);
void fake_rtc_set_ticks(RtcTicks new_ticks);
void fake_rtc_increment_ticks(RtcTicks tick_increment);
void fake_rtc_auto_increment_ticks(RtcTicks tick_increment);
// TODO: there is a lot of stuff missing.

502
tests/fakes/fake_session.c Normal file
View file

@ -0,0 +1,502 @@
/*
* 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 "fake_session.h"
#include "comm/bt_lock.h"
#include "kernel/pbl_malloc.h"
#include "system/logging.h"
#include "services/common/comm_session/protocol.h"
#include "services/common/comm_session/session_send_buffer.h"
#include "services/common/system_task.h"
#include "util/circular_buffer.h"
#include "system/hexdump.h"
#include "clar_asserts.h"
#include "util/list.h"
#include <string.h>
extern void fake_system_task_callbacks_invoke_pending(void);
typedef struct CommSession {
ListNode node;
Transport *transport;
const TransportImplementation *transport_imp;
bool is_send_next_call_pending;
TransportDestination destination;
uint8_t *temp_write_buffer;
uint16_t endpoint_id;
uint16_t bytes_written;
uint16_t max_out_payload_length;
CircularBuffer send_buffer;
uint8_t storage[1024];
} CommSession;
static CommSession *s_session_head;
static int s_session_close_call_count;
static int s_session_open_call_count;
bool comm_session_is_valid(const CommSession *session) {
return list_contains((ListNode *) s_session_head, &session->node);
}
static bool prv_find_session_is_system_filter(ListNode *found_node, void *data) {
const CommSessionType requested_type = (const bool) (uintptr_t) data;
const TransportDestination destination = ((const CommSession *) found_node)->destination;
switch (requested_type) {
case CommSessionTypeApp:
return destination == TransportDestinationApp || destination == TransportDestinationHybrid;
case CommSessionTypeSystem:
return destination == TransportDestinationSystem || destination == TransportDestinationHybrid;
default:
return false;
}
}
bool comm_session_has_capability(CommSession *session, CommSessionCapability capability){
return true;
}
CommSession * comm_session_get_by_type(CommSessionType type) {
// TODO: This is not going to fly with multiple app sessions
CommSession *session;
bt_lock();
{
session = (CommSession *) list_find((ListNode *) s_session_head,
prv_find_session_is_system_filter,
(void *) (uintptr_t) type);
}
bt_unlock();
return session;
}
CommSession* comm_session_get_system_session(void) {
// TODO: What if Pebble App is connected via iSPP *and* PPoGATT ?
return comm_session_get_by_type(CommSessionTypeSystem);
}
CommSession* comm_session_get_current_app_session(void) {
// TODO: What if App is connected via iSPP *and* PPoGATT ?
return comm_session_get_by_type(CommSessionTypeApp);
}
void comm_session_close(CommSession *session, CommSessionCloseReason reason) {
cl_assert(list_contains((const ListNode *) s_session_head, &session->node));
if (session->temp_write_buffer) {
kernel_free(session->temp_write_buffer);
}
list_remove(&session->node, (ListNode **) &s_session_head, NULL);
kernel_free(session);
++s_session_close_call_count;
}
void comm_session_receive_router_write(CommSession *session,
const uint8_t *received_data,
size_t num_bytes_to_copy) {
PBL_LOG(LOG_LEVEL_DEBUG, "Received Data:");
PBL_HEXDUMP(LOG_LEVEL_DEBUG, received_data, num_bytes_to_copy);
}
bool comm_session_send_data(CommSession *session, uint16_t endpoint_id,
const uint8_t *data, size_t length, uint32_t timeout_ms) {
SendBuffer *sb = comm_session_send_buffer_begin_write(session, endpoint_id, length, timeout_ms);
if (!sb) {
return false;
}
comm_session_send_buffer_write(sb, data, length);
comm_session_send_buffer_end_write(sb);
return true;
}
CommSession * comm_session_open(Transport *transport, const TransportImplementation *implementation,
TransportDestination destination) {
++s_session_open_call_count;
CommSession *session = kernel_malloc(sizeof(CommSession));
memset(session, 0, sizeof(*session));
*session = (const CommSession) {
.transport = transport,
.transport_imp = implementation,
.destination = destination,
.max_out_payload_length = COMM_MAX_OUTBOUND_PAYLOAD_SIZE,
};
const size_t max_pp_msg_size = session->max_out_payload_length + sizeof(PebbleProtocolHeader);
// If this fails, you need to bump up the size of the storage[] array in the fake CommSession
cl_assert(sizeof(session->storage) >= max_pp_msg_size);
circular_buffer_init(&session->send_buffer, session->storage, max_pp_msg_size);
s_session_head = (CommSession *) list_prepend((ListNode *) s_session_head, &session->node);
return session;
}
size_t comm_session_send_queue_get_length(const CommSession *session) {
cl_assert(list_contains((const ListNode *) s_session_head, &session->node));
return circular_buffer_get_read_space_remaining(&session->send_buffer);
}
size_t comm_session_send_queue_copy(CommSession *session, uint32_t start_off, size_t length,
uint8_t *data_out) {
cl_assert(data_out);
cl_assert(list_contains((const ListNode *) s_session_head, &session->node));
return circular_buffer_copy_offset(&session->send_buffer, start_off, data_out, length);
}
void comm_session_send_queue_consume(CommSession *session, size_t length) {
circular_buffer_consume(&session->send_buffer, length);
}
static void prv_send_next_kernel_bg_cb(void *data) {
CommSession *session = (CommSession *) data;
if (!list_contains((const ListNode *) s_session_head, (const ListNode *) session)) {
// Session closed in the mean time
return;
}
// Flip the flag before the send_next callback, so it can schedule again if needed.
session->is_send_next_call_pending = false;
// Kick the transport to send out the next bytes from the send buffer
const size_t read_space = comm_session_send_queue_get_length(session);
if (read_space) {
session->transport_imp->send_next(session->transport);
}
}
void comm_session_send_next(CommSession *session) {
if (session->is_send_next_call_pending) {
return;
}
system_task_add_callback(prv_send_next_kernel_bg_cb, session);
session->is_send_next_call_pending = true;
}
bool prv_filter_by_transport_callback(ListNode *node, void *data) {
return (((CommSession *) node)->transport == data);
}
CommSession * prv_find_session_by_transport(Transport *transport) {
return (CommSession *) list_find((ListNode *) s_session_head,
prv_filter_by_transport_callback, transport);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Send buffer fakes
size_t comm_session_send_buffer_get_max_payload_length(const CommSession *session) {
size_t max_length = 0;
if (comm_session_is_valid(session)) {
max_length = session->send_buffer.buffer_size - sizeof(PebbleProtocolHeader);
}
return max_length;
}
SendBuffer * comm_session_send_buffer_begin_write(CommSession *session, uint16_t endpoint_id,
size_t required_free_length,
uint32_t timeout_ms) {
if (!session) {
return NULL;
}
if (!comm_session_is_valid(session)) {
return NULL;
}
if (required_free_length + sizeof(PebbleProtocolHeader) >
circular_buffer_get_write_space_remaining(&session->send_buffer)) {
return NULL;
}
if (session->temp_write_buffer) {
// Already writing, fake doesn't support multiple tasks trying to write at the same time
return NULL;
}
session->temp_write_buffer = (uint8_t *) kernel_malloc(session->max_out_payload_length);
session->bytes_written = 0;
session->endpoint_id = endpoint_id;
return (SendBuffer *) session;
}
bool comm_session_send_buffer_write(SendBuffer *sb, const uint8_t *data, size_t length) {
CommSession *session = (CommSession *) sb;
cl_assert(session);
cl_assert(session->temp_write_buffer);
cl_assert(length + session->bytes_written <= session->max_out_payload_length);
memcpy(session->temp_write_buffer + session->bytes_written, data, length);
session->bytes_written += length;
return true;
}
void comm_session_send_buffer_end_write(SendBuffer *sb) {
CommSession *session = (CommSession *) sb;
cl_assert(session);
cl_assert(session->temp_write_buffer);
const PebbleProtocolHeader pp_header = {
.length = session->bytes_written,
.endpoint_id = session->endpoint_id,
};
circular_buffer_write(&session->send_buffer, (const uint8_t *) &pp_header, sizeof(pp_header));
circular_buffer_write(&session->send_buffer, session->temp_write_buffer, session->bytes_written);
kernel_free(session->temp_write_buffer);
session->temp_write_buffer = NULL;
session->endpoint_id = ~0;
session->bytes_written = 0;
}
static uint32_t s_responsiveness_max_period_s;
static bool s_responsiveness_latency_is_reduced;
static ResponsivenessGrantedHandler s_last_responsiveness_granted_handler;
void comm_session_set_responsiveness(
CommSession *session, BtConsumer consumer, ResponseTimeState state, uint16_t max_period_secs) {
comm_session_set_responsiveness_ext(session, consumer, state, max_period_secs, NULL);
}
void comm_session_set_responsiveness_ext(CommSession *session, BtConsumer consumer,
ResponseTimeState state, uint16_t max_period_secs,
ResponsivenessGrantedHandler granted_handler) {
s_responsiveness_max_period_s = max_period_secs;
if (state == ResponseTimeMiddle) {
s_responsiveness_latency_is_reduced = true;
} else if (state == ResponseTimeMax) {
s_responsiveness_latency_is_reduced = false;
}
s_last_responsiveness_granted_handler = granted_handler;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Session related functions
ResponsivenessGrantedHandler fake_comm_session_get_last_responsiveness_granted_handler(void) {
return s_last_responsiveness_granted_handler;
}
int fake_comm_session_open_call_count(void) {
return s_session_open_call_count;
}
int fake_comm_session_close_call_count(void) {
return s_session_close_call_count;
}
void fake_comm_session_process_send_next(void) {
CommSession *session = s_session_head;
while (session) {
CommSession *next = (CommSession *) session->node.next;
comm_session_send_next(session);
session = next;
}
fake_system_task_callbacks_invoke_pending();
}
uint32_t fake_comm_session_get_responsiveness_max_period(void) {
return s_responsiveness_max_period_s;
}
uint32_t fake_comm_session_is_latency_reduced(void) {
return s_responsiveness_latency_is_reduced;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Transport mock
typedef struct {
ListNode node;
uint16_t endpoint_id;
size_t length;
uint8_t data[];
} DataNode;
typedef struct {
ListNode node;
TransportDestination destination;
FakeTransportSentCallback sent_cb;
Uuid app_uuid;
CommSession *session;
//! When no sent_cb is used, data is appended to this list
DataNode *sent_data;
} FakeTransport;
static FakeTransport *s_fake_transport_head;
static void prv_fake_transport_send_next(Transport *transport) {
FakeTransport *fake_transport = (FakeTransport *) transport;
cl_assert_equal_b(list_contains((const ListNode *) s_fake_transport_head,
(const ListNode *) fake_transport), true);
CommSession *session = fake_transport->session;
PebbleProtocolHeader pp_header;
uint8_t *buffer = kernel_malloc(1024);
while (circular_buffer_copy(&session->send_buffer,
(uint8_t *) &pp_header, sizeof(pp_header)) == sizeof(pp_header)) {
circular_buffer_copy_offset(&session->send_buffer, sizeof(pp_header), buffer, pp_header.length);
if (fake_transport->sent_cb) {
fake_transport->sent_cb(pp_header.endpoint_id, buffer, pp_header.length);
} else {
PBL_LOG(LOG_LEVEL_DEBUG, "Sending Data to PP endpoint %u (0x%x):",
pp_header.endpoint_id, pp_header.endpoint_id);
PBL_HEXDUMP(LOG_LEVEL_DEBUG, buffer, pp_header.length);
DataNode *data_node = kernel_malloc(sizeof(DataNode) + pp_header.length);
list_init(&data_node->node);
data_node->endpoint_id = pp_header.endpoint_id;
data_node->length = pp_header.length;
memcpy(data_node->data, buffer, pp_header.length);
fake_transport->sent_data = (DataNode *) list_prepend(&fake_transport->sent_data->node,
&data_node->node);
}
circular_buffer_consume(&session->send_buffer, sizeof(pp_header) + pp_header.length);
}
kernel_free(buffer);
}
static void prv_fake_transport_reset(Transport *transport) {
cl_assert_(false, "Not implemented: prv_fake_transport_reset");
}
static const TransportImplementation s_fake_transport_implementation = {
.send_next = prv_fake_transport_send_next,
.reset = prv_fake_transport_reset,
};
Transport *fake_transport_create(TransportDestination destination,
const Uuid *app_uuid,
FakeTransportSentCallback sent_cb) {
if (app_uuid == NULL) {
cl_assert_(TransportDestinationSystem == destination ||
TransportDestinationHybrid == TransportDestinationSystem,
"When passing NULL app_uuid, the destination can only be System or Hybrid");
} else {
cl_assert_(TransportDestinationSystem == destination ||
TransportDestinationHybrid == TransportDestinationSystem,
"When passing an app_uuid, the destination can only be App or Hybrid");
}
FakeTransport *transport = (FakeTransport *) kernel_malloc(sizeof(FakeTransport));
*transport = (const FakeTransport) {
.destination = destination,
.sent_cb = sent_cb,
};
if (app_uuid) {
transport->app_uuid = *app_uuid;
}
s_fake_transport_head = (FakeTransport *) list_prepend((ListNode *) s_fake_transport_head,
&transport->node);
return (Transport *) transport;
}
CommSession *fake_transport_set_connected(Transport *transport, bool connected) {
FakeTransport *fake_transport = (FakeTransport *) transport;
if (connected) {
cl_assert_equal_p(fake_transport->session, NULL);
fake_transport->session = comm_session_open(transport, &s_fake_transport_implementation,
fake_transport->destination);
return fake_transport->session;
} else {
cl_assert(fake_transport->session);
comm_session_close(fake_transport->session, 0);
fake_transport->session = NULL;
return NULL;
}
}
void fake_transport_set_sent_cb(Transport *transport, FakeTransportSentCallback sent_cb) {
cl_assert(transport);
FakeTransport *fake_transport = (FakeTransport *) transport;
fake_transport->sent_cb = sent_cb;
}
void fake_transport_assert_sent(Transport *transport, uint16_t index, uint16_t endpoint_id,
const uint8_t data[], size_t length) {
cl_assert(transport);
FakeTransport *fake_transport = (FakeTransport *)transport;
DataNode *data_node = fake_transport->sent_data;
for (uint16_t i = 0; i <= index; ++i) {
cl_assert_(data_node, "Sent out too few packets");
if (i == index) {
cl_assert_equal_i(data_node->endpoint_id, endpoint_id);
cl_assert_equal_i(data_node->length, length);
cl_assert_equal_m(data_node->data, data, length);
}
data_node = (DataNode *) data_node->node.next;
}
}
void fake_transport_assert_nothing_sent(Transport *transport) {
cl_assert(transport);
FakeTransport *fake_transport = (FakeTransport *)transport;
DataNode *data_node = fake_transport->sent_data;
cl_assert_equal_p(data_node, NULL);
}
void fake_transport_destroy(Transport *transport) {
FakeTransport *fake_transport = (FakeTransport *) transport;
cl_assert(transport);
cl_assert_equal_b(list_contains((const ListNode *)s_fake_transport_head,
(const ListNode *)fake_transport), true);
if (fake_transport->session) {
// Causes clean up of CommSession:
fake_transport_set_connected((Transport *)fake_transport, false /* connected */);
}
list_remove((ListNode *) fake_transport, (ListNode **) &s_fake_transport_head, NULL);
DataNode *data_node = fake_transport->sent_data;
while (data_node) {
DataNode *next_data_node = (DataNode *)data_node->node.next;
kernel_free(data_node);
data_node = next_data_node;
}
kernel_free(fake_transport);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Transport helper functions
bool fake_comm_session_send_buffer_write_raw_by_transport(Transport *transport,
const uint8_t *data, size_t length) {
CommSession *session = prv_find_session_by_transport(transport);
cl_assert(session);
return circular_buffer_write(&session->send_buffer, data, length);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Fake life cycle
void fake_comm_session_init(void) {
cl_assert_(s_fake_transport_head == NULL,
"Didn't clean up the fake transports? \
Call fake_comm_session_cleanup() if you don't want to clean them up manually.");
s_session_close_call_count = 0;
s_session_open_call_count = 0;
s_last_responsiveness_granted_handler = NULL;
}
void fake_comm_session_cleanup(void) {
FakeTransport *fake_transport = s_fake_transport_head;
while (fake_transport) {
FakeTransport *next = (FakeTransport *) fake_transport->node.next;
fake_transport_destroy((Transport *) fake_transport);
fake_transport = next;
}
}

113
tests/fakes/fake_session.h Normal file
View file

@ -0,0 +1,113 @@
/*
* 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.
*/
#pragma once
#include "services/common/comm_session/session_transport.h"
//
// Usage hints:
// ------------
//
// Typically, you'll want to do something like:
//
// 1. Connect a fake transport for the system CommSession:
//
// Transport *transport = fake_transport_create(TransportDestinationSystem, NULL, NULL);
// fake_transport_set_connected(transport, true /* connected */);
//
// 2. Simulate receiving some data by calling your module's endpoint handler:
//
// put_bytes_protocol_msg_callback(comm_session_get_system_session(), msg, sizeof(msg));
//
// 3. Process the outbound data that has been queued up by your endpoint implementation:
//
// fake_comm_session_process_send_next();
//
// 4. Assert the sent data is what you expect:
//
// const uint8_t expected_payload[] = { 0x01, 0x02, 0x03 };
// fake_transport_assert_sent(transport, 0, endpoint_id,
// expected_payload, sizeof(expected_payload));
////////////////////////////////////////////////////////////////////////////////////////////////////
// Session related functions
ResponsivenessGrantedHandler fake_comm_session_get_last_responsiveness_granted_handler(void);
int fake_comm_session_open_call_count(void);
int fake_comm_session_close_call_count(void);
void fake_comm_session_process_send_next(void);
uint32_t fake_comm_session_get_responsiveness_max_period(void);
uint32_t fake_comm_session_is_latency_reduced(void);
////////////////////////////////////////////////////////////////////////////////////////////////////
// Transport mock
//! Pointer to function handling data that is sent out by the session
typedef void (*FakeTransportSentCallback)(uint16_t endpoint_id,
const uint8_t* data, unsigned int data_length);
//! Creates a mock transport
//! @param destination The destination type this transport is connected to. See comments with the
//! TransportDestination struct for more info.
//! @param app_uuid The UUID of the app that this transport is connected to.
//! Pass NULL if this information is not known or irrelevant.
//! @param sent_cb The callback that needs to be called whenever data is sent out using this mock
//! transport. Note that data will only be sent out when fake_comm_session_process_send_next() is
//! called. It's recommended to leave this NULL and use fake_transport_assert_sent instead.
Transport *fake_transport_create(TransportDestination destination,
const Uuid *app_uuid,
FakeTransportSentCallback sent_cb);
//! Simulating (dis)connecting the transport.
//! @return When connected, returns the opened CommSession. Returns NULL when disconnected.
CommSession *fake_transport_set_connected(Transport *transport, bool connected);
//! Asserts the data of sent packets.
//! @note This function can only be used when fake_transport_set_sent_cb is not used. They are
//! mutually exclusive.
//! @param index Packet index. Zero-based, newest packet first, oldest last.
void fake_transport_assert_sent(Transport *transport, uint16_t index, uint16_t endpoint_id,
const uint8_t data[], size_t length);
//! Asserts no data has been sent out.
void fake_transport_assert_nothing_sent(Transport *transport);
//! Assigns a new callback that needs to be called whenever data is sent out using this mock
//! transport. Note that data will only be sent out when fake_comm_session_process_send_next() is
//! called. It's recommended to use fake_transport_assert_sent, because it results in tests that
//! are much easier to read.
void fake_transport_set_sent_cb(Transport *transport, FakeTransportSentCallback sent_cb);
//! Destroys the mock transport
void fake_transport_destroy(Transport *transport);
////////////////////////////////////////////////////////////////////////////////////////////////////
// Transport helper functions
//! Writes data into the fake send buffer, skipping Pebble Protocol
//! @return false if there's insufficient space.
bool fake_comm_session_send_buffer_write_raw_by_transport(Transport *transport,
const uint8_t *data, size_t length);
////////////////////////////////////////////////////////////////////////////////////////////////////
// Fake life cycle
void fake_comm_session_init(void);
void fake_comm_session_cleanup(void);

View file

@ -0,0 +1,69 @@
/*
* 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 "fake_session_send_buffer.h"
#include <stdint.h>
static SendBuffer *s_stub_send_buffer = (SendBuffer *) ~0;
SendBuffer * comm_session_send_buffer_begin_write(CommSession *session, uint16_t endpoint_id,
size_t required_free_length,
uint32_t timeout_ms) {
if (!session) {
return NULL;
}
return s_stub_send_buffer;
}
bool comm_session_send_buffer_write(SendBuffer *send_buffer, const uint8_t *data, size_t length) {
return true;
}
void comm_session_send_buffer_end_write(SendBuffer *send_buffer) {
}
static int s_send_buffer_create_count;
static bool s_send_buffer_create_simulate_oom;
SendBuffer * comm_session_send_buffer_create(bool is_system) {
++s_send_buffer_create_count;
if (s_send_buffer_create_simulate_oom) {
return NULL;
} else {
return (SendBuffer *) ~0;
}
}
static int s_send_buffer_destroy_count;
void comm_session_send_buffer_destroy(SendBuffer *sb) {
++s_send_buffer_destroy_count;
}
void fake_session_send_buffer_init(void) {
s_send_buffer_create_count = 0;
s_send_buffer_destroy_count = 0;
s_send_buffer_create_simulate_oom = false;
}
void fake_session_send_buffer_set_simulate_oom(bool enabled) {
s_send_buffer_create_simulate_oom = enabled;
}
SendBuffer *fake_session_send_buffer_get_buffer(void) {
return s_stub_send_buffer;
}

View file

@ -0,0 +1,27 @@
/*
* 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.
*/
#pragma once
#include "services/common/comm_session/session_send_buffer.h"
#include <stdbool.h>
void fake_session_send_buffer_init(void);
void fake_session_send_buffer_set_simulate_oom(bool enabled);
SendBuffer *fake_session_send_buffer_get_buffer(void);

View file

@ -0,0 +1,197 @@
/*
* 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/settings/settings_file.h"
#include "system/status_codes.h"
#include "util/crc8.h"
#include <string.h>
#include <stdint.h>
#include "clar_asserts.h"
struct {
bool open;
void *values[UINT8_MAX];
void *keys[UINT8_MAX];
uint32_t val_lens[UINT8_MAX];
uint32_t key_lens[UINT8_MAX];
bool dirty[UINT8_MAX];
} s_settings_file;
void fake_settings_file_reset(void) {
for (unsigned i = 0; i < UINT8_MAX; ++i) {
free(s_settings_file.values[i]);
s_settings_file.values[i] = NULL;
free(s_settings_file.keys[i]);
s_settings_file.keys[i] = NULL;
s_settings_file.val_lens[i] = 0;
s_settings_file.key_lens[i] = 0;
s_settings_file.dirty[i] = false;
}
s_settings_file.open = false;
}
status_t settings_file_get(SettingsFile *file, const void *key, size_t key_len,
void *val_out, size_t val_out_len) {
if (settings_file_exists(file, key, key_len)) {
const uint8_t key_crc8 = crc8_calculate_bytes(key, key_len, false);
memcpy(val_out, s_settings_file.values[key_crc8], val_out_len);
return S_SUCCESS;
}
memset(val_out, 0, val_out_len);
return E_DOES_NOT_EXIST;
}
status_t settings_file_set(SettingsFile *file, const void *key, size_t key_len,
const void *val, size_t val_len) {
void *val_copy = malloc(val_len);
void *key_copy = malloc(key_len);
cl_assert(val_copy && key_copy);
memcpy(val_copy, val, val_len);
memcpy(key_copy, key, key_len);
const uint8_t key_crc8 = crc8_calculate_bytes(key, key_len, false);
if (s_settings_file.values[key_crc8] != NULL) {
cl_assert(memcmp(key, s_settings_file.keys[key_crc8], key_len) == 0);
}
free(s_settings_file.values[key_crc8]);
free(s_settings_file.keys[key_crc8]);
s_settings_file.values[key_crc8] = val_copy;
s_settings_file.keys[key_crc8] = key_copy;
s_settings_file.val_lens[key_crc8] = val_len;
s_settings_file.key_lens[key_crc8] = key_len;
s_settings_file.dirty[key_crc8] = true;
return S_SUCCESS;
}
int settings_file_get_len(SettingsFile *file, const void *key, size_t key_len) {
if (settings_file_exists(file, key, key_len)) {
const uint8_t key_crc8 = crc8_calculate_bytes(key, key_len, false);
return s_settings_file.val_lens[key_crc8];
}
return 0;
}
status_t settings_file_delete(SettingsFile *file,
const void *key, size_t key_len) {
if (settings_file_exists(file, key, key_len)) {
const uint8_t key_crc8 = crc8_calculate_bytes(key, key_len, false);
free(s_settings_file.values[key_crc8]);
free(s_settings_file.keys[key_crc8]);
s_settings_file.values[key_crc8] = NULL;
s_settings_file.keys[key_crc8] = NULL;
s_settings_file.val_lens[key_crc8] = 0;
s_settings_file.key_lens[key_crc8] = 0;
s_settings_file.dirty[key_crc8] = false;
return S_SUCCESS;
} else {
return E_DOES_NOT_EXIST;
}
}
status_t settings_file_open(SettingsFile *file, const char *name,
int max_used_space) {
if (s_settings_file.open) {
return E_BUSY;
} else {
*file = (SettingsFile){};
s_settings_file.open = true;
return S_SUCCESS;
}
}
void settings_file_close(SettingsFile *file) {
cl_assert(s_settings_file.open);
s_settings_file.open = false;
}
bool settings_file_exists(SettingsFile *file, const void *key, size_t key_len) {
const uint8_t key_crc8 = crc8_calculate_bytes(key, key_len, false);
return (s_settings_file.values[key_crc8] != NULL &&
memcmp(s_settings_file.keys[key_crc8], key, key_len) == 0);
}
status_t settings_file_mark_synced(SettingsFile *file, const void *key, size_t key_len) {
if (settings_file_exists(file, key, key_len)) {
const uint8_t key_crc8 = crc8_calculate_bytes(key, key_len, false);
s_settings_file.dirty[key_crc8] = false;
return S_SUCCESS;
}
return E_DOES_NOT_EXIST;
}
status_t settings_file_set_byte(SettingsFile *file, const void *key, size_t key_len, size_t offset,
uint8_t byte) {
if (settings_file_exists(file, key, key_len)) {
const uint8_t key_crc8 = crc8_calculate_bytes(key, key_len, false);
uint8_t *val_bytes = s_settings_file.values[key_crc8];
val_bytes[offset] &= byte;
return S_SUCCESS;
} else {
return E_DOES_NOT_EXIST;
}
}
uint8_t s_cur_itr;
static void prv_get_key(SettingsFile *file, void *key, size_t key_len) {
memcpy(key, s_settings_file.keys[s_cur_itr], key_len);
}
static void prv_get_val(SettingsFile *file, void *val, size_t val_len) {
memcpy(val, s_settings_file.values[s_cur_itr], val_len);
}
status_t settings_file_each(SettingsFile *file, SettingsFileEachCallback cb,
void *context) {
for (unsigned i = 0; i < UINT8_MAX; ++i) {
if (s_settings_file.keys[i] != NULL &&
s_settings_file.values[i] != NULL) {
s_cur_itr = i;
SettingsRecordInfo info = {
.get_key = prv_get_key,
.get_val = prv_get_val,
.key_len = s_settings_file.key_lens[i],
.val_len = s_settings_file.val_lens[i],
.dirty = s_settings_file.dirty[i],
};
if (!cb(file, &info, context)) {
break;
}
}
}
return S_SUCCESS;
}
status_t settings_file_rewrite(SettingsFile *file,
SettingsFileRewriteCallback cb,
void *context) {
// TODO
fake_settings_file_reset();
return S_SUCCESS;
}
status_t settings_file_rewrite_filtered(SettingsFile *file,
SettingsFileRewriteFilterCallback filter_cb,
void *context) {
// TODO
fake_settings_file_reset();
return S_SUCCESS;
}

View file

@ -0,0 +1,19 @@
/*
* 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.
*/
#pragma once
void fake_settings_file_reset(void);

View file

@ -0,0 +1,155 @@
/*
* 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 "fake_shared_prf_storage.h"
#include <bluetooth/bluetooth_types.h>
#include <bluetooth/sm_types.h>
static int s_prf_storage_ble_store_count;
static int s_prf_storage_ble_delete_count;
static int s_prf_storage_bt_classic_store_count;
static int s_prf_storage_bt_classic_platform_bits_count;
static int s_prf_storage_bt_classic_delete_count;
///////////////////////////////////////////////////////////////////////////////////////////////////
//! Test functions
void fake_shared_prf_storage_reset_counts(void) {
s_prf_storage_ble_store_count = 0;
s_prf_storage_ble_delete_count = 0;
s_prf_storage_bt_classic_store_count = 0;
s_prf_storage_bt_classic_platform_bits_count = 0;
s_prf_storage_bt_classic_delete_count = 0;
}
int fake_shared_prf_storage_get_ble_store_count(void) {
return s_prf_storage_ble_store_count;
}
int fake_shared_prf_storage_get_ble_delete_count(void) {
return s_prf_storage_ble_delete_count;
}
int fake_shared_prf_storage_get_bt_classic_store_count(void) {
return s_prf_storage_bt_classic_store_count;
}
int fake_shared_prf_storage_get_bt_classic_platform_bits_count(void) {
return s_prf_storage_bt_classic_platform_bits_count;
}
int fake_shared_prf_storage_get_bt_classic_delete_count(void) {
return s_prf_storage_bt_classic_delete_count;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//! Custom Local Device Name
bool shared_prf_storage_get_local_device_name(char *local_device_name_out, size_t max_size) {
return false;
}
void shared_prf_storage_set_local_device_name(char *local_device_name) {
return;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//! BLE Root Keys
bool shared_prf_storage_get_root_key(SMRootKeyType key_type, SM128BitKey *key_out) {
return false;
}
void shared_prf_storage_set_root_keys(SM128BitKey *keys_in) {
return;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//! BLE Pairing Data
bool shared_prf_storage_get_ble_pairing_data(SMPairingInfo *pairing_info_out,
char *name_out, bool *requires_address_pinning_out,
uint8_t *flags) {
return false;
}
void shared_prf_storage_store_ble_pairing_data(const SMPairingInfo *pairing_info,
char *name, bool requires_address_pinning,
uint8_t flags) {
s_prf_storage_ble_delete_count++;
s_prf_storage_ble_store_count++;
}
void shared_prf_storage_erase_ble_pairing_data(void) {
s_prf_storage_ble_delete_count++;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//! BT Classic Pairing Data
bool shared_prf_storage_get_bt_classic_pairing_data(BTDeviceAddress *addr_out,
char *device_name_out,
SM128BitKey *link_key_out,
uint8_t *platform_bits) {
return false;
}
void shared_prf_storage_store_bt_classic_pairing_data(BTDeviceAddress *addr,
char *device_name,
SM128BitKey *link_key,
uint8_t platform_bits) {
s_prf_storage_bt_classic_delete_count++;
s_prf_storage_bt_classic_store_count++;
}
void shared_prf_storage_store_platform_bits(uint8_t platform_bits) {
s_prf_storage_bt_classic_platform_bits_count++;
}
void shared_prf_storage_erase_bt_classic_pairing_data(void) {
s_prf_storage_bt_classic_delete_count++;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//! Getting Started Is Complete
bool shared_prf_storage_get_getting_started_complete(void) {
return true;
}
void shared_prf_storage_set_getting_started_complete(bool set) {
return;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//! Factory Reset
void shared_prf_storage_wipe_all(void) {
return;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//! Pinned Address
bool shared_prf_storage_get_ble_pinned_address(BTDeviceAddress *address_out) {
return false;
}
//! Stores the new BLE Pinned Address in the shared storage.
void shared_prf_storage_set_ble_pinned_address(const BTDeviceAddress *address) {
}

View file

@ -0,0 +1,24 @@
/*
* 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.
*/
#pragma once
void fake_shared_prf_storage_reset_counts(void);
int fake_shared_prf_storage_get_ble_store_count(void);
int fake_shared_prf_storage_get_ble_delete_count(void);
int fake_shared_prf_storage_get_bt_classic_store_count(void);
int fake_shared_prf_storage_get_bt_classic_platform_bits_count(void);
int fake_shared_prf_storage_get_bt_classic_delete_count(void);

View file

@ -0,0 +1,26 @@
/*
* 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 "fake_smartstrap_connection.h"
void smartstrap_connection_kick_monitor(void) {
}
void sys_smartstrap_subscribe(void) {
}
void sys_smartstrap_unsubscribe(void) {
}

View file

@ -0,0 +1,21 @@
/*
* 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.
*/
#pragma once
void smartstrap_connection_kick_monitor(void);
void sys_smartstrap_subscribe(void);
void sys_smartstrap_unsubscribe(void);

View file

@ -0,0 +1,80 @@
/*
* 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 "fake_smartstrap_profiles.h"
#include "clar_asserts.h"
static bool s_did_read;
static bool s_read_success;
static SmartstrapProfile s_read_profile;
static uint32_t s_read_length;
static bool s_did_notify;
static bool s_notify_success;
static bool s_notify_profile;
static bool s_did_request;
static SmartstrapRequest s_request;
void smartstrap_profiles_handle_read(bool success, SmartstrapProfile profile, uint32_t length) {
s_read_success = success;
s_read_profile = profile;
s_read_length = length;
s_did_read = true;
}
void smartstrap_profiles_handle_read_aborted(SmartstrapProfile profile) {
}
void fake_smartstrap_profiles_check_read_params(bool success, SmartstrapProfile profile,
uint32_t length) {
cl_assert(s_did_read);
cl_assert(success == s_read_success);
cl_assert(profile == s_read_profile);
cl_assert(length == s_read_length);
s_did_read = false;
}
void smartstrap_profiles_handle_notification(bool success, SmartstrapProfile profile) {
s_notify_success = success;
s_notify_profile = profile;
s_did_notify = true;
}
void fake_smartstrap_profiles_check_notify_params(bool success, SmartstrapProfile profile) {
cl_assert(s_did_notify);
cl_assert(success = s_notify_success);
cl_assert(profile = s_notify_profile);
s_did_notify = false;
}
SmartstrapResult smartstrap_profiles_handle_request(const SmartstrapRequest *request) {
s_request = *request;
s_did_request = true;
return SmartstrapResultOk;
}
void fake_smartstrap_profiles_check_request_params(const SmartstrapRequest *request) {
cl_assert(s_did_request);
cl_assert(s_request.service_id == request->service_id);
cl_assert(s_request.attribute_id == request->attribute_id);
cl_assert((s_request.write_mbuf == NULL) == (request->write_mbuf == NULL));
cl_assert((s_request.read_mbuf == NULL) == (request->read_mbuf == NULL));
cl_assert(s_request.timeout_ms == request->timeout_ms);
s_did_request = false;
}

View file

@ -0,0 +1,37 @@
/*
* 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.
*/
#pragma once
#include "services/normal/accessory/smartstrap_comms.h"
#include <stdbool.h>
#include <stdint.h>
void smartstrap_profiles_handle_read(bool success, SmartstrapProfile profile, uint32_t length);
void fake_smartstrap_profiles_check_read_params(bool success, SmartstrapProfile profile,
uint32_t length);
void smartstrap_profiles_handle_notification(bool success, SmartstrapProfile profile);
void fake_smartstrap_profiles_check_notify_params(bool success, SmartstrapProfile profile);
SmartstrapResult smartstrap_profiles_handle_request(const SmartstrapRequest *request);
void smartstrap_profiles_handle_read_aborted(SmartstrapProfile profile);
void fake_smartstrap_profiles_check_request_params(const SmartstrapRequest *request);

View file

@ -0,0 +1,86 @@
/*
* 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_asserts.h"
#include "fake_smartstrap_state.h"
static bool s_locked = false;
static SmartstrapState s_fsm_state = SmartstrapStateUnsubscribed;
static void prv_check_fsm_transition(SmartstrapState prev_state, SmartstrapState new_state) {
if (new_state == SmartstrapStateUnsubscribed) {
} else if ((prev_state == SmartstrapStateUnsubscribed) &&
(new_state == SmartstrapStateReadReady)) {
} else if ((prev_state == SmartstrapStateReadReady) &&
(new_state == SmartstrapStateNotifyInProgress)) {
} else if ((prev_state == SmartstrapStateReadReady) &&
(new_state == SmartstrapStateReadDisabled)) {
} else if ((prev_state == SmartstrapStateNotifyInProgress) &&
(new_state == SmartstrapStateReadComplete)) {
} else if ((prev_state == SmartstrapStateReadDisabled) &&
(new_state == SmartstrapStateReadInProgress)) {
} else if ((prev_state == SmartstrapStateReadDisabled) &&
(new_state == SmartstrapStateReadReady)) {
} else if ((prev_state == SmartstrapStateReadInProgress) &&
(new_state == SmartstrapStateReadComplete)) {
} else if ((prev_state == SmartstrapStateReadComplete) &&
(new_state == SmartstrapStateReadReady)) {
} else {
// all other transitions are invalid
cl_assert(false);
}
}
SmartstrapState smartstrap_fsm_state_get(void) {
return s_fsm_state;
}
void smartstrap_fsm_state_reset(void) {
s_fsm_state = SmartstrapStateReadReady;
}
bool smartstrap_fsm_state_test_and_set(SmartstrapState expected_state, SmartstrapState next_state) {
if (s_fsm_state != expected_state) {
return false;
}
prv_check_fsm_transition(s_fsm_state, next_state);
s_fsm_state = next_state;
return true;
}
void smartstrap_fsm_state_set(SmartstrapState next_state) {
prv_check_fsm_transition(s_fsm_state, next_state);
s_fsm_state = next_state;
}
void smartstrap_state_lock(void) {
cl_assert(!s_locked);
s_locked = true;
}
void smartstrap_state_unlock(void) {
cl_assert(s_locked);
s_locked = false;
}
void smartstrap_state_assert_locked_by_current_task(void) {
cl_assert(s_locked);
}
bool sys_smartstrap_is_service_connected(uint16_t service_id) {
return true;
}

View file

@ -0,0 +1,39 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
typedef enum {
SmartstrapStateUnsubscribed,
SmartstrapStateReadReady,
SmartstrapStateNotifyInProgress,
SmartstrapStateReadDisabled,
SmartstrapStateReadInProgress,
SmartstrapStateReadComplete
} SmartstrapState;
SmartstrapState smartstrap_fsm_state_get(void);
void smartstrap_fsm_state_reset(void);
bool smartstrap_fsm_state_test_and_set(SmartstrapState expected_state, SmartstrapState next_state);
void smartstrap_fsm_state_set(SmartstrapState next_state);
void smartstrap_state_lock(void);
void smartstrap_state_unlock(void);
void smartstrap_state_assert_locked_by_current_task(void);
bool sys_smartstrap_is_service_connected(uint16_t service_id);

View file

@ -0,0 +1,195 @@
/*
* 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 "fake_spi_flash.h"
#include "flash_region/flash_region.h"
#include "system/status_codes.h"
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "clar_asserts.h"
typedef struct FakeFlashState {
uint32_t offset;
uint32_t length;
uint32_t bytes_left_till_write_failure;
jmp_buf *jmp_on_failure;
uint8_t* storage; //! Allocated buffer of length bytes.
uint32_t write_count;
uint32_t erase_count;
} FakeFlashState;
static FakeFlashState s_state = { 0 };
void fake_spi_flash_erase(void) {
memset(s_state.storage, 0xff, s_state.length);
}
void fake_spi_flash_cleanup(void) {
free(s_state.storage);
s_state.storage = NULL;
s_state = (FakeFlashState) { 0 };
}
//! @param offset the offset at which this fake region of flash begins.
//! @param length the length of this fake region of flash.
void fake_spi_flash_init(uint32_t offset, uint32_t length) {
// Clients are not required to cleanup due to prior code, so do so here.
fake_spi_flash_cleanup();
s_state.offset = offset;
s_state.length = length;
s_state.storage = malloc(length);
s_state.write_count = 0;
// Note: this is a harness failure, not a code failure.
cl_assert(s_state.storage != NULL);
memset(s_state.storage, 0xff, length);
}
void fake_flash_assert_region_untouched(uint32_t start_addr, uint32_t length) {
if (length == 0) {
return;
}
for (uint32_t i = 0; i < length; i++) {
cl_assert(s_state.storage[start_addr + i] == 0xff);
}
}
int32_t fake_spi_flash_find_next_write(int32_t offset) {
if(offset < s_state.offset || offset >= s_state.offset + s_state.length) {
return E_RANGE;
}
do {
if(s_state.storage[offset] != 0xff) {
return offset;
}
offset++;
} while(offset < s_state.offset + s_state.length);
return E_DOES_NOT_EXIST;
}
void fake_spi_flash_populate_from_file(char *path, uint32_t offset) {
cl_assert(s_state.storage);
cl_assert(offset >= s_state.offset);
cl_assert((offset - s_state.offset) <= s_state.length);
// find the offset in the storage array
uint32_t fake_offset = offset - s_state.offset;
// check that file exists and fits in buffer
struct stat st;
cl_assert(stat(path, &st) == 0);
cl_assert(st.st_size < (s_state.length - fake_offset));
FILE *file = fopen(path, "r");
cl_assert(file);
// copy file to fake flash storage
cl_assert(fread(&s_state.storage[fake_offset], 1, st.st_size, file) > 0);
}
void fake_spi_flash_force_future_failure(int after_n_bytes, jmp_buf *retire_to) {
s_state.bytes_left_till_write_failure = after_n_bytes;
s_state.jmp_on_failure = retire_to;
}
void flash_read_bytes(uint8_t* buffer, uint32_t start_addr, uint32_t buffer_size) {
cl_assert(start_addr >= s_state.offset);
cl_assert(start_addr + buffer_size <= s_state.offset + s_state.length);
memcpy(buffer, s_state.storage + (start_addr - s_state.offset), buffer_size);
}
void flash_write_bytes(const uint8_t* buffer, uint32_t start_addr, uint32_t buffer_size) {
cl_assert(start_addr >= s_state.offset);
cl_assert(start_addr + buffer_size <= s_state.offset + s_state.length);
++s_state.write_count;
for (int i = 0; i < buffer_size; ++i) {
if (s_state.jmp_on_failure != NULL) {
if (s_state.bytes_left_till_write_failure == 0) {
longjmp(*s_state.jmp_on_failure, 1);
} else {
s_state.bytes_left_till_write_failure--;
}
}
// 0 write 0 = 0
// 1 write 0 = 0
// 1 write 1 = 1
// 0 write 1 = 0
s_state.storage[start_addr - s_state.offset + i] &= buffer[i];
}
}
//! @param block_size must be a power of two
static void erase_block(uint32_t block_addr, uint32_t block_size) {
++s_state.erase_count;
const uint32_t block_mask = ~(block_size - 1);
uint32_t block_start = block_addr & block_mask;
cl_assert(block_start >= s_state.offset);
if (block_start + block_size > s_state.offset + s_state.length) {
printf("-0x%x 0x%x\n", block_start + block_size, s_state.offset + s_state.length);
}
cl_assert(block_start + block_size <= s_state.offset + s_state.length);
memset(&s_state.storage[block_start - s_state.offset], 0xff, block_size);
}
void flash_erase_sector_blocking(uint32_t sector_addr) {
#if PLATFORM_SNOWY
if (sector_addr <= BOTTOM_BOOT_REGION_END) {
erase_block(sector_addr, BOTTOM_BOOT_SECTOR_SIZE);
return;
}
#endif
erase_block(sector_addr, SECTOR_SIZE_BYTES);
}
uint32_t flash_get_subsector_base_address(uint32_t flash_addr) {
return flash_addr & ~(SUBSECTOR_SIZE_BYTES - 1);
}
void flash_erase_subsector_blocking(uint32_t subsector_addr) {
erase_block(subsector_addr, SUBSECTOR_SIZE_BYTES);
}
uint32_t flash_get_sector_base_address(uint32_t flash_addr) {
#if PLATFORM_SNOWY
if (flash_addr <= BOTTOM_BOOT_REGION_END) {
return (flash_addr & ~(BOTTOM_BOOT_SECTOR_SIZE - 1));
}
#endif
return (flash_addr & ~(SECTOR_SIZE_BYTES - 1));
}
uint32_t fake_flash_write_count(void) {
return s_state.write_count;
}
uint32_t fake_flash_erase_count(void) {
return s_state.erase_count;
}

View file

@ -0,0 +1,47 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
#include <setjmp.h>
/*!
Initialize the fake SPI flash region.
@param offset the offset at which this fake region of flash begins.
@param length the length of this fake region of flash.
*/
void fake_spi_flash_init(uint32_t offset, uint32_t length);
//! Add data to the fake spi flash from a file on your local filesystem
void fake_spi_flash_populate_from_file(char *path, uint32_t offset);
//! Cleanup the fake SPI flash region, freeing all resources.
void fake_spi_flash_erase(void);
//! Cleanup the fake SPI flash region, freeing all resources.
void fake_spi_flash_cleanup(void);
//! Force the SPI flash to fail at some point in the future, jumping back to
//! the given jmp_buf when it does so. This is intended for use in verifying
//! the atomicity of algorithms which are purported to be so.
void fake_spi_flash_force_future_failure(int after_n_bytes, jmp_buf *retire_to);
void fake_flash_assert_region_untouched(uint32_t start_addr, uint32_t length);
uint32_t fake_flash_write_count(void);
uint32_t fake_flash_erase_count(void);

Some files were not shown because too many files have changed in this diff Show more