Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/fw/comm/ble/gatt_client_subscriptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,8 @@ static BTErrno prv_subscribe(BLECharacteristic characteristic_ref,
.att_handle = att_handle,
};
// Prepend to the list of subscriptions of the connection:
ListNode *head = &connection->gatt_subscriptions->node;
ListNode *head = connection->gatt_subscriptions ?
&connection->gatt_subscriptions->node : NULL;
connection->gatt_subscriptions =
(GATTClientSubscriptionNode *) list_prepend(head, &subscription->node);

Expand Down
23 changes: 21 additions & 2 deletions src/fw/comm/ble/kernel_le_client/ppogatt/ppogatt.c
Original file line number Diff line number Diff line change
Expand Up @@ -758,8 +758,23 @@ static void prv_handle_meta_read(PPoGATTClient *client, const uint8_t *value,
size_t value_length, BLEGATTError error) {
PBL_ASSERTN(client->state == StateDisconnectedReadingMeta);
if (error != BLEGATTErrorSuccess) {
// GATT read failed - this is retriable since the mobile app may not be ready yet
goto handle_retriable_error;
// Check if this is a permanent error that should not be retried
// Permanent errors include: InvalidHandle, ReadNotPermitted, InvalidPDU,
// InsufficientAuthentication/Authorization/Encryption, AttributeNotFound, etc.
// These errors indicate that the operation cannot succeed through retrying.
// Timeout and resource errors are retriable since the remote might recover.
switch (error) {
case BLEGATTErrorSuccess:
break;
// Retriable errors - remote might recover:
case BLEGATTErrorPrepareQueueFull:
case BLEGATTErrorInsufficientResources:
case BLEGATTErrorRequestTimeOut:
goto handle_retriable_error;
// Permanent errors - delete client immediately:
default:
goto handle_error;
}
}
if (value_length < sizeof(PPoGATTMetaV0)) {
goto handle_error;
Expand Down Expand Up @@ -844,6 +859,10 @@ static void prv_handle_meta_read(PPoGATTClient *client, const uint8_t *value,
return;
}

// Subscribe failed - this is a permanent error, delete the client
PBL_LOG(LOG_LEVEL_ERROR, "Failed to subscribe to PPoGATT data characteristic: err=%x", e);
goto handle_error;

handle_retriable_error:
// GATT read failed - schedule a retry if we haven't exceeded the max retry count
if (++client->meta_read_retries < PPOGATT_META_READ_RETRY_COUNT_MAX) {
Expand Down
30 changes: 30 additions & 0 deletions tests/fakes/fake_GAPAPI.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

#include "fake_GAPAPI.h"

#ifdef GAPAPI_AVAILABLE

#include "bluetopia_interface.h"

#include <string.h>
Expand Down Expand Up @@ -348,3 +350,31 @@ void fake_GAPAPI_init(void) {
memset(&s_scan_resp_data, 0, sizeof(s_scan_resp_data));
s_scan_resp_data_length = 0;
}
#else
// When GAPAPI is not available, provide dummy implementations
static bool s_is_le_advertising_enabled = false;

void gap_le_set_advertising_disabled(void) {
s_is_le_advertising_enabled = false;
}

bool gap_le_is_advertising_enabled(void) {
return s_is_le_advertising_enabled;
}

void gap_le_assert_advertising_interval(uint16_t expected_min_slots, uint16_t expected_max_slots) {
// Dummy implementation - does nothing
}

unsigned int gap_le_get_advertising_data(Advertising_Data_t *ad_data_out) {
return 0;
}

unsigned int gap_le_get_scan_response_data(Scan_Response_Data_t *scan_resp_data_out) {
return 0;
}

void fake_GAPAPI_init(void) {
s_is_le_advertising_enabled = false;
}
#endif // GAPAPI_AVAILABLE
79 changes: 64 additions & 15 deletions tests/fakes/fake_GAPAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,92 @@

#pragma once

#include "GAPAPI.h"
#ifdef __has_include
#if __has_include("GAPAPI.h")
#include "GAPAPI.h"
#define GAPAPI_AVAILABLE
#endif
#else
#ifdef COMPONENT_BTSTACK
#include "GAPAPI.h"
#define GAPAPI_AVAILABLE
#endif
#endif

#include <bluetooth/bluetooth_types.h>

#include <stdbool.h>
#include <stdint.h>

//! Provided to simulate stopping advertising because of an inbound connection.
// If GAPAPI.h is not available, provide dummy type definitions
#ifndef GAPAPI_AVAILABLE
typedef struct GAPLEConnection GAPLEConnection; // Forward declaration (already defined elsewhere)
typedef struct GAP_LE_Event_Data_t GAP_LE_Event_Data_t;

// Advertising and scan response data are 31-byte arrays per Bluetooth spec
#define GAP_ADVERTISING_DATA_SIZE 31
typedef uint8_t Advertising_Data_t[GAP_ADVERTISING_DATA_SIZE];
typedef uint8_t Scan_Response_Data_t[GAP_ADVERTISING_DATA_SIZE];

// HCI error codes
#define HCI_ERROR_CODE_SUCCESS 0x00
#define HCI_ERROR_CODE_CONNECTION_TERMINATED_BY_LOCAL_HOST 0x16

// Boolean constants
#define TRUE true
#define FALSE false

// Bluetooth address types
typedef uint8_t BD_ADDR_t[6];
typedef uint8_t Encryption_Key_t[16];

// GAP API types
typedef struct {
uint8_t IO_Capability;
uint8_t OOB_Data_Flag;
uint8_t Authentication_Requirements;
uint8_t Max_Encryption_Key_Size;
uint8_t Link_Key_Request_Notification_Flag;
} GAP_LE_Pairing_Capabilities_t;

// GAP API function declarations
int GAP_LE_Advertising_Enable(unsigned int BluetoothStackID, unsigned int Enable,
Advertising_Data_t *Advertising_Data,
Scan_Response_Data_t *Scan_Response_Data,
void (*ConnectionCallback)(unsigned int, void *, unsigned long),
unsigned int CallbackParameter);

const GAP_LE_Pairing_Capabilities_t* gap_le_pairing_capabilities(void);

#endif // GAPAPI_AVAILABLE

// These functions are always available (either from GAPAPI or from the fake)
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);
int GAP_LE_Set_Advertising_Data(unsigned int BluetoothStackID, unsigned int Length,
Advertising_Data_t *Advertising_Data);
int GAP_LE_Set_Scan_Response_Data(unsigned int BluetoothStackID, unsigned int Length,
Scan_Response_Data_t *Scan_Response_Data);
void fake_GAPAPI_init(void);

// Fake GAP API functions (available even when real GAPAPI is not)
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);
#ifdef GAPAPI_AVAILABLE
// Additional functions when GAPAPI is available
// (none needed currently)
#endif
127 changes: 127 additions & 0 deletions tests/fakes/fake_GATTAPI.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

#include "fake_GATTAPI.h"

#ifdef GATTAPI_AVAILABLE

#include "clar_asserts.h"

#include <string.h>
Expand Down Expand Up @@ -176,3 +178,128 @@ void fake_gatt_put_write_response_for_last_write(void) {
s_write_cb(s_write_stack_id, &event, s_write_cb_param);
s_write_cb = NULL;
}
#else
// Stub implementations when GATTAPI_AVAILABLE is not defined (Linux/Docker)
// These are minimal stubs that allow the tests to link

static bool s_service_discovery_running = false;
static int s_start_count = 0;
static int s_stop_count = 0;
static int s_service_changed_indication_count = 0;
static uint16_t s_write_handle = 0;
static unsigned int s_service_discovery_stack_id;
static GATT_Service_Discovery_Event_Callback_t s_service_discovery_callback;
static unsigned long s_service_discovery_callback_param;

int GATT_Initialize(unsigned int BluetoothStackID,
unsigned long Flags,
GATT_Connection_Event_Callback_t ConnectionEventCallback,
unsigned long 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_running = true;
s_service_discovery_stack_id = stack_id;
s_service_discovery_callback = ServiceDiscoveryCallback;
s_service_discovery_callback_param = CallbackParameter;
++s_start_count;
return 0;
}

int GATT_Stop_Service_Discovery(unsigned int BluetoothStackID, unsigned int ConnectionID) {
s_service_discovery_running = false;
++s_stop_count;
return 0;
}

bool fake_gatt_is_service_discovery_running(void) {
return s_service_discovery_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) {
// Stub - does nothing
}

void fake_gatt_set_stop_return_value(int ret_value) {
// Stub - does nothing
}

void fake_gatt_put_service_discovery_event(GATT_Service_Discovery_Event_Data_t *event) {
if (event->Event_Data_Type == 0 /* etGATT_Service_Discovery_Complete */) {
s_service_discovery_running = false;
}
// Call the registered callback if it exists
if (s_service_discovery_callback) {
s_service_discovery_callback(s_service_discovery_stack_id, event, s_service_discovery_callback_param);
}
}

void fake_gatt_init(void) {
s_service_discovery_running = false;
s_start_count = 0;
s_stop_count = 0;
s_service_changed_indication_count = 0;
s_write_handle = 0;
s_service_discovery_stack_id = 0;
s_service_discovery_callback = NULL;
s_service_discovery_callback_param = 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;
}

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;
}

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;
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) {
// Stub - does nothing
}
#endif // GATTAPI_AVAILABLE
Loading
Loading