Central setup
Usually, a Central must start scanning to find Peripherals. When the Central has scanned a Peripheral it wants to connect to, it stops scanning and initiates a connection to that Peripheral. After the connection has been established, it may start pairing, if the Peripheral requires it, or directly encrypt the link, if the two devices have already bonded in the past.
Scanning
The most basic setup for a Central device begins with scanning, which is performed by the following function from gap_interface.h:
bleResult_t **Gap\_StartScanning**
(
const gapScanningParameters_t* pScanningParameters,
gapScanningCallback_t scanningCallback,
gapFilterDuplicates_t enableFilterDuplicates,
uint16_t duration,
uint16_t period
);
If the pScanningParameters pointer is NULL, the currently set parameters are used. If no parameters have been set after a device power-up, the standard default values are used:
**\#define** gGapDefaultScanningParameters_d \
{ \
/* type */ gGapScanTypePassive_c, \
/* interval */ gGapScanIntervalDefault_d, \
/* window */ gGapScanWindowDefault_d, \
/* ownAddressType */ gBleAddrTypePublic_c, \
/* filterPolicy */ gScanAll_c \
/* scanning PHY */ gLePhylMFlag_c\
}
The easiest way to define non-default scanning parameters is to initialize a gapScanningParameters_t structure with the above default and change only the required fields.
For example, to perform active scanning and only scan for devices in the Filter Accept List, the following code can be used:
gapScanningParameters_t scanningParameters = gGapDefaultScanningParameters_d;
scanningParameters.type = gGapScanTypeActive_c;
scanningParameters.filterPolicy = gScanWithFilterAcceptList_c;
Gap_StartScanning(&scanningParamters, scanningCallback, enableFilterDuplicates, duration, period);
When using the common application structure, the application can use the following API defined in app_conn.h:
bleResult_t **BluetoothLEHost\_StartScanning**
(
appScanningParams_t *pAppScanParams,
gapScanningCallback_t pfCallback
);
The API uses the appScanningParams_t structures, which is defined as follows:
typedef struct appScanningParams_tag
{
gapScanningParameters_t *pHostScanParams; /*!< Pointer to host scan structure */
gapFilterDuplicates_t enableDuplicateFiltering; /*!< Duplicate filtering mode */
uint16_t duration; /*!< scan duration */
uint16_t period; /*!< scan period */
} appScanningParams_t;
The scanningCallback is triggered by the GAP layer to signal events related to scanning.
The most important event is the gDeviceScanned_c event, which is triggered each time an advertising device is scanned. This event data contains information about the advertiser:
typedef struct
{
bleAddressType_t addressType ;
bleDeviceAddress_t aAddress ;
int8_t rssi ;
uint8_t dataLength ;
uint8_t* data ;
bleAdvertisingReportEventType_t advEventType ;
bool_t directRpaUsed;
bleDeviceAddress_t directRpa;
bool_t advertisingAddressResolved;
} gapScannedDevice_t;
If this information signals a known Peripheral that the Central wants to connect to, the latter must stop scanning and connect to the Peripheral.
To stop scanning, call this function:
bleResult_t **Gap\_StopScanning** (**void**);
By default, the GAP layer is configured to report all scanned devices to the application using the gDeviceScanned_c event type. However, some use cases might require to perform specific GAP Discovery Procedures. In such use cases the advertising reports might require the filtering of Flags AD value from the advertising data. Other use cases require the Bluetooth LE Host Stack to automatically initiate a connection when a specific device has been scanned.
To enable filtering based on the Flags AD value or to set device addresses for automatic connections, the following function must be called before the scanning is started:
bleResult_t **Gap\_SetScanMode**
(
gapScanMode_t scanMode,
gapAutoConnectParams_t* pAutoConnectParams,
gapConnectionCallback_t connCallback
);
The default value for the scan mode is gDefaultScan_c, which reports all packets regardless of their content and does not perform any automatic connection.
To enable Limited Discovery, the gLimitedDiscovery_c value must be used, while the gGeneralDiscovery_c value activates General Discovery.
To enable automatic connection when specific devices are scanned, the gAutoConnect_c value must be set, in which case the pAutoConnectParams parameter must point to the structure that holds the target device addresses and the connection parameters to be used by the Host for these devices.
If scanMode
is set to gAutoConnect_c, connCallback must be set and is triggered by GAP to send the events related to the connection.
Parent topic:Central setup
Initiating and closing a connection
To connect to a scanned Peripheral, extract its address and address type from the gDeviceScanned_c event data, stop scanning, and call the following function:
bleResult_t **Gap\_Connect**
(
const gapConnectionRequestParameters_t * pParameters,
gapConnectionCallback_t connCallback
);
When using the common application structure, the application can also use the following API defined in app_conn.h:
bleResult_t **BluetoothLEHost\_Connect**
(
gapConnectionRequestParameters_t* pParameters,
gapConnectionCallback_t connCallback
);
An easy way to create the connection parameter structure is to initialize it with the defaults, then change only the necessary fields. The default structure is defined as shown here:
**\#define** gGapDefaultConnectionRequestParameters_d \
{ \
/* scanInterval */ gGapScanIntervalDefault_d, \
/* scanWindow */ gGapScanWindowDefault_d, \
/* filterPolicy */ gUseDeviceAddress_c, \
/* ownAddressType */ gBleAddrTypePublic_c, \
/* peerAddressType */ gBleAddrTypePublic_c, \
/* peerAddress */ { 0, 0, 0, 0, 0, 0 }, \
/* connIntervalMin */ gGapDefaultMinConnectionInterval_d, \
/* connIntervalMax */ gGapDefaultMaxConnectionInterval_d, \
/* connLatency */ gGapDefaultConnectionLatency_d, \
/* supervisionTimeout */ gGapDefaultSupervisionTimeout_d, \
/* connEventLengthMin */ gGapConnEventLengthMin_d, \
/* connEventLengthMax */ gGapConnEventLengthMax_d \
/* initiatingPHYs */ gLePhylMFlag_c \
}
In the following example, Central scans for a specific Heart Rate Sensor with a known address. When it finds it, it immediately connects to it.
static void **BleApp\_ScanningCallback**
(
gapScanningEvent_t *pScanningEvent
)
{
switch (pScanningEvent->eventType)
{
case gDeviceScanned_c:
{
if (BleApp_CheckScanEvent(&pScanningEvent->eventData.scannedDevice))
{
gConnReqParams.peerAddressType = pScanningEvent->eventData.scannedDevice.addressType;
FLib_MemCpy(gConnReqParams.peerAddress,
pScanningEvent->eventData.scannedDevice.aAddress,
sizeof(bleDeviceAddress_t));
(void)Gap_StopScanning();
#if gAppUsePrivacy_d
gConnReqParams.usePeerIdentityAddress = pScanningEvent->eventData.scannedDevice.advertisingAddressResolved;
#endif
(void)BluetoothLEHost_Connect(&gConnReqParams, BleApp_ConnectionCallback);
}
}
break;
}
The connCallback is triggered by GAP to send all events related to the active connection. It has the following prototype:
**typedef** **void** (* gapConnectionCallback_t )
(
deviceId_t deviceId,
gapConnectionEvent_t * pConnectionEvent
);
The very first event that should be listened inside this callback is the gConnEvtConnected_c event. If the application decides to drop the connection establishment before this event is generated, it should call the following macro:
**\#define** Gap_CancelInitiatingConnection()\
Gap_Disconnect(gCancelOngoingInitiatingConnection_d)
This is useful, for instance, when the application chooses to use an expiration timer for the connection request.
Upon receiving the gConnEvtConnected_c event, the application may proceed to extract the necessary parameters from the event data (pConnectionEvent->event.connectedEvent). The most important parameter to be saved is the deviceId.
The deviceId is a unique 8-bit, unsigned integer, used to identify an active connection for subsequent GAP and GATT API calls. All functions related to a certain connection require a deviceId parameter. For example, to disconnect, call this function:
bleResult_t **Gap\_Disconnect**
(
deviceId_t deviceId
);
Parent topic:Central setup
Pairing and bonding (Central)
After the user has connected to a Peripheral, use the following function to check whether this device has bonded in the past:
If it has, link encryption can be requested with:
If the link encryption is successful, the gConnEvtEncryptionChanged_c connection event is triggered. Otherwise, a gConnEvtAuthenticationRejected_cevent is received with the rejectReason event data parameter set to gLinkEncryptionFailed_c.
On the other hand, if this is a new device (not bonded), pairing may be started as shown here:
The pairing parameters are shown here:
The names of the parameters are self-explanatory. The withBonding flag should be set to TRUE if the Central must/wants to bond.
When Advanced Secure Mode is enabled, (gAppSecureMode_d id defined as 1 in app_preinclude.h), the security mode and level for pairing is automatically enforced as Mode 1 Level 4, and LE Secure Connection Supported is automatically enforced TRUE. Legacy pairing is not supported in this mode.
For the Security Mode and Level, the GAP layer defines them as follows:
Security Mode 1 Level 1 stands for no security requirements.
Except for Level 1 (which is only used with Mode 1), Security Mode 1 requires encryption, while Security Mode 2 requires data signing.
Mode 1 Level 2 and Mode 2 Level 1 do not require authentication (in other words, they allow Just Works pairing, which has no MITM protection). Mode 1 Level 3 and Mode 2 Level 2 require authentication (must pair with PIN or OOB data, which provide MITM protection).
Starting with Bluetooth specification 4.2, OOB pairing offers MITM protection only in certain conditions. The application must inform the stack if the OOB data exchange capabilities offer MITM protection via a dedicated API.
Security Mode 1 Level 4 is reserved for authenticated pairing (with MITM protection) using a LE Secure Connections pairing method.
If a pairing method is used but it does not offer MITM protection, then the pairing parameters must use Security Mode 1 level 2. If the requested pairing parameters are incompatible (for example, Security Mode 1 Level 4 without LE Secure Connections enabled), a
gBleInvalidParameter_c
status is returned by the security API functions:Gap_SetDefaultPairingParameters
,Gap_SendPeripheralSecurityRequest
,Gap_Pair
andGap_AcceptPairingRequest
.
— |
No security |
No MITM protection |
Legacy MITM protection |
LE secure connections with MITM protection |
---|---|---|---|---|
Mode 1 (encryption) distributed LTK (EDIV+RAND) or generated LTK |
Level 1 no security |
Level 2 unauthenticated encryption |
Level 3 authenticated encryption |
Level 4 LE SC authenticated encryption |
Mode 2 (data signing) distributed CSRK |
— |
Level 1 unauthenticated data signing |
Level 2 authenticated data signing |
— |
The centralKeys should have the flags set for all the keys that are available in the application. The IRK is mandatory if the Central is using a Private Resolvable Address, while the CSRK is necessary if the Central wants to use data signing. The LTK is provided by the Peripheral and should only be included if the Central intends on becoming a Peripheral in future reconnections (GAP role change).
The peripheralKeys should follow the same guidelines. The LTK is mandatory if encryption is to be performed, while the peer’s IRK should be requested if the Peripheral is using Private Resolvable Addresses.
See Table 2 for detailed guidelines regarding key distribution.
The first three rows are both guidelines for Pairing Parameters (centralKeys and peripheralKeys) and for distribution of keys with Gap_SendSmpKeys.
If LE Secure Connections Pairing is performed (Bluetooth Low Energy 4.2 and above), then the LTK is generated internally, so the corresponding bits in the key distribution fields from the pairing parameters are ignored by the devices.
The Identity Address is distributed if the IRK is also distributed (its flag has been set in the Pairing Parameters). Therefore, it can be “asked” only by asking for IRK (it does not have a separate flag in a gapSmpKeyFlags_t structure). Therefore, it is N/A.
The negotiation of the distributed keys is as follows:
In the SMP Pairing Request (started by Gap_Pair), the Central sets the flags for the keys it wants to distribute (centralKeys) and receive (peripheralKeys).
CENTRAL |
PERIPHERAL |
|
---|---|---|
Central keys |
Peripheral keys |
|
Long Term Key (LTK) +EDIV +RAND |
If it wants to be a peripheral in a future reconnection |
If it wants encryption |
Identity Resolving Key (IRK) |
If it uses or intends to use private resolvable addresses |
If a peripheral is using a private resolvable address |
Connection Signature Resolving Key (CSRK) |
If it wants to sign data as GATT Client |
If it wants the peripheral to sign data as GATT Client |
Identity address |
If it distributes the IRK |
N/A |
The Peripheral examines the two distributions and must send an SMP Pairing Response (started by the Gap_AcceptPairingRequest) after performing any changes it deems necessary. The Peripheral is only allowed to set to 0 some flags that are set to 1 by the Central, but not the other way around. For example, it cannot request/distribute keys that were not offered/requested by the Central. If the Peripheral is adverse to the Central’s distributions, it can reject the pairing by using the Gap_RejectPairing function.
The Central examines the updated distributions from the Pairing Response. If it is adverse to the changes made by the Peripheral, it can reject the pairing (Gap_RejectPairing). Otherwise, the pairing continues and, during the key distribution phase (the gConnEvtKeyExchangeRequest_c event) only the final negotiated keys are included in the key structure sent with Gap_SendSmpKeys.
For LE Secure Connections (both devices set the SC bit in the AuthReq field of the Pairing Request and Pairing Response packets), the LTK is not distributed. It is generated and the corresponding bit in the Initiator Key Distribution and Responder Key Distribution fields of the Pairing Response packet are set to 0.
If LE Secure Connections Pairing (Bluetooth LE 4.2 and above) is used, and OOB data needs to be exchanged, the application must obtain the local LE SC OOB Data from the Bluetooth LE Host Stack by calling the Gap_LeScGetLocalOobData function. The data is contained by the generic gLeScLocalOobData_c event.
The local LE SC OOB Data is refreshed in the following situations:
The Gap_LeScRegeneratePublicKey function is called (the gLeScPublicKeyRegenerated_c generic event is also generated as a result of this API).
The device is reset (which also causes the Public Key to be regenerated).
If the pairing continues, the following connection events may occur:
Request events
gConnEvtPasskeyRequest_c: a PIN is required for pairing; the application must respond with the Gap_EnterPasskey(deviceId, passkey).
gConnEvtOobRequest_c: if the pairing started with the oobAvailable set to TRUE by both sides; the application must respond with the Gap_ProvideOob(deviceId, oob).
gConnEvtKeyExchangeRequest_c: the pairing has reached the key exchange phase; the application must respond with the Gap_SendSmpKeys(deviceId, smpKeys).
gConnEvtLeScOobDataRequest_c: the stack requests the LE SC OOB Data received from the peer (r, Cr and Addr); the application must respond with Gap_LeScSetPeerOobData(deviceId, leScOobData).
gConnEvtLeScDisplayNumericValue_c: the stack requests the display and confirmation of the LE SC Numeric Comparison Value; the application must respond with Gap_LeScValidateNumericValue(deviceId, ncvValidated).
Informational events
gConnEvtKeysReceived_c: the key exchange phase is complete; keys are automatically saved in the internal device database and are also provided to the application for immediate inspection; application does not have to save the keys in NVM storage because this is done internally if withBondingwas set to TRUE by both sides.
gConnEvtAuthenticationRejected_c: the peer device rejected the pairing; the rejectReason parameter of the event data indicates the reason that the Peripheral does not agree with the pairing parameters (it cannot be gLinkEncryptionFailed_c because that reason is reserved for the link encryption failure).
gConnEvtPairingComplete_c: the pairing process is complete, either successfully, or an error may have occurred during the SMP packet exchanges; note that this is different from the gConnEvtKeyExchangeRequest_c event; the latter signals that the pairing was rejected by the peer, while the former is used for failures due to the SMP packet exchanges.
gConnEvtLeScKeypressNotification_c: the stack informs the application that a remote SMP Keypress Notification has been received during Passkey Entry Pairing Method.
After the link encryption or pairing is completed successfully, the Central may immediately start exchanging data using the GATT APIs.
|
|
Parent topic:Central setup
Parent topic:Generic Access Profile (GAP) Layer