diff --git a/features/FEATURE_BLE/ble/generic/GenericGattClient.h b/features/FEATURE_BLE/ble/generic/GenericGattClient.h new file mode 100644 index 0000000000..8b525e8d06 --- /dev/null +++ b/features/FEATURE_BLE/ble/generic/GenericGattClient.h @@ -0,0 +1,154 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017-2017 ARM Limited + * + * 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. + */ + +#ifndef MBED_BLE_GENERIC_GATT_CLIENT +#define MBED_BLE_GENERIC_GATT_CLIENT + +#include +#include "ble/GattClient.h" +#include "ble/pal/PalGattClient.h" + +// IMPORTANT: private header. Not part of the public interface. + +namespace ble { +namespace generic { + +// forward declarations +struct procedure_control_block_t; +struct discovery_control_block_t; +struct read_control_block_t; +struct write_control_block_t; +struct descriptor_discovery_control_block_t; + +/** + * Generic implementation of the GattClient. + * It requires a pal::GattClient injected at construction site. + * @important: Not part of the public interface of BLE API. + */ +class GenericGattClient : public GattClient { + + // give access to control block classes + friend struct procedure_control_block_t; + friend struct discovery_control_block_t; + friend struct read_control_block_t; + friend struct write_control_block_t; + friend struct descriptor_discovery_control_block_t; + +public: + /** + * Create a GenericGattClient from a pal::GattClient + */ + GenericGattClient(pal::GattClient* pal_client); + + /** + * @see GattClient::launchServiceDiscovery + */ + virtual ble_error_t launchServiceDiscovery( + Gap::Handle_t connection_handle, + ServiceDiscovery::ServiceCallback_t service_callback, + ServiceDiscovery::CharacteristicCallback_t characteristic_callback, + const UUID& matching_service_uuid, + const UUID& matching_characteristic_uuid + ); + + /** + * @see GattClient::isServiceDiscoveryActive + */ + virtual bool isServiceDiscoveryActive() const; + + /** + * @see GattClient::terminateServiceDiscovery + */ + virtual void terminateServiceDiscovery(); + + /** + * @see GattClient::read + */ + virtual ble_error_t read( + Gap::Handle_t connection_handle, + GattAttribute::Handle_t attribute_handle, + uint16_t offset + ) const; + + /** + * @see GattClient::write + */ + virtual ble_error_t write( + GattClient::WriteOp_t cmd, + Gap::Handle_t connection_handle, + GattAttribute::Handle_t attribute_handle, + size_t length, + const uint8_t* value + ) const; + + /** + * @see GattClient::onServiceDiscoveryTermination + */ + virtual void onServiceDiscoveryTermination( + ServiceDiscovery::TerminationCallback_t callback + ); + + /** + * @see GattClient::discoverCharacteristicDescriptors + */ + virtual ble_error_t discoverCharacteristicDescriptors( + const DiscoveredCharacteristic& characteristic, + const CharacteristicDescriptorDiscovery::DiscoveryCallback_t& discoveryCallback, + const CharacteristicDescriptorDiscovery::TerminationCallback_t& terminationCallback + ); + + /** + * @see GattClient::isCharacteristicDescriptorDiscoveryActive + */ + virtual bool isCharacteristicDescriptorDiscoveryActive( + const DiscoveredCharacteristic& characteristic + ) const; + + /** + * @see GattClient::terminateCharacteristicDescriptorDiscovery + */ + virtual void terminateCharacteristicDescriptorDiscovery( + const DiscoveredCharacteristic& characteristic + ); + + /** + * @see GattClient::reset + */ + virtual ble_error_t reset(void); + +private: + procedure_control_block_t* get_control_block(Gap::Handle_t connection); + const procedure_control_block_t* get_control_block(Gap::Handle_t connection) const; + void insert_control_block(procedure_control_block_t* cb) const; + void remove_control_block(procedure_control_block_t* cb) const; + + void on_termination(Gap::Handle_t connection_handle); + void on_server_message_received(connection_handle_t, const pal::AttServerMessage&); + void on_server_response(connection_handle_t, const pal::AttServerMessage&); + void on_server_event(connection_handle_t, const pal::AttServerMessage&); + void on_transaction_timeout(connection_handle_t); + + uint16_t get_mtu(Gap::Handle_t connection) const; + + pal::GattClient* const _pal_client; + ServiceDiscovery::TerminationCallback_t _termination_callback; + mutable procedure_control_block_t* control_blocks; +}; + +} +} + +#endif /* MBED_BLE_GENERIC_GATT_CLIENT */ diff --git a/features/FEATURE_BLE/ble/pal/AttClient.h b/features/FEATURE_BLE/ble/pal/AttClient.h new file mode 100644 index 0000000000..56bd1a2f06 --- /dev/null +++ b/features/FEATURE_BLE/ble/pal/AttClient.h @@ -0,0 +1,687 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017-2017 ARM Limited + * + * 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. + */ + +#ifndef BLE_PAL_ATTCLIENT_H_ +#define BLE_PAL_ATTCLIENT_H_ + +#include "ble/UUID.h" +#include "ble/BLETypes.h" +#include "ble/ArrayView.h" +#include "ble/blecommon.h" +#include "platform/Callback.h" +#include "AttServerMessage.h" + +namespace ble { +namespace pal { + +/** + * Send attribute protocol requests to an ATT server. It also handle reception + * of ATT response and server indication/notification. + * + * Every request send and response or response event received is for a specified + * connection. + * + * @warning This class should not be used outside mbed BLE, availability is not + * guaranteed for all ports. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F + */ +struct AttClient { + /** + * Initialization of the instance. An implementation can use this function + * to initialise the subsystems needed to realize the ATT operations of this + * interface. + * + * This function has to be called before any other operations. + * + * @return BLE_ERROR_NONE if the request has been successfully sent or the + * appropriate error otherwise. + */ + virtual ble_error_t initialize() = 0; + + /** + * Termination of the instance. An implementation can use this function + * to release the subsystems initialised to realise the ATT operations of + * this interface. + * + * After a call to this function, initialise should be called again to + * allow usage of the interface. + * + * @return BLE_ERROR_NONE if the request has been successfully sent or the + * appropriate error otherwise. + */ + virtual ble_error_t terminate() = 0; + + /** + * Send an exchange MTU request which negotiate the size of the MTU used by + * the connection. + * + * First the client send to the server the maximum rx mtu that it can receive + * then the client reply with the maximum rx mtu it can receive. + * + * The mtu choosen for the connection is the minimum of the client Rx mtu + * and server Rx mtu values. + * + * If an error occured then the mtu used remains the default value. + * + * @param connection The handle of the connection to send this request to. + * + * @return BLE_ERROR_NONE if the request has been succesfully sent or the + * appropriate error otherwise. + * + * @see ble::pal::AttExchangeMTUResponse The type of response received from + * the server + * @see ble::pal::AttErrorResponse::REQUEST_NOT_SUPPORTED The error code + * returned by the server in case of error. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.2.1 + */ + virtual ble_error_t exchange_mtu_request(connection_handle_t connection) = 0; + + /** + * Acquire the size of the mtu for a given connection. + * + * @param connection The handle of the connection for which the the MTU size + * should be acquired. + * + * @param mtu_size Output parameter which will contain the MTU size. + * + * @return BLE_ERROR_NONE if the MTU size has been acquired or the + * appropriate error otherwise. + */ + virtual ble_error_t get_mtu_size( + connection_handle_t connection_handle, + uint16_t& mtu_size + ) = 0; + + /** + * Send a find information request to a server in order to obtain the + * mapping of attribute handles with their associated types. + * + * The server will reply with a ble::pal::AttFindInformationResponse + * containing at least one [attribute handle, attribute type] pair. If the + * last handle in the response is not equal to the end handle of the finding + * range then this request can be issued again with an updated range (begin + * equal to last handle received + 1) to discover the remaining attributes. + * + * To discover the whole ATT server, the first find information request + * should have a discovery range of [0x0001 - 0xFFFF]. + * + * The server can send a ble::pal::AttErrorResponse with the code + * ble::pal::AttErrorResponse::ATTRIBUTE_NOT_FOUND if no attributes have + * been found in the range specified. The attribute handle in the response + * is then equal to the first handle of the discovery range. + * + * If the range is malformed the server will reply a + * ble::pal::AttErrorResponse with the error code ble::pal::INVALID_HANDLE. + * + * @param connection_handle The handle of the connection to send this + * request to. + * @param discovery_range The attribute range where handle-type informations + * should be discovered. + * + * @return BLE_ERROR_NONE if the request has been successfully sent or the + * appropriate error otherwise. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.3.1 + */ + virtual ble_error_t find_information_request( + connection_handle_t connection_handle, + attribute_handle_range_t discovery_range + ) = 0; + + /** + * Send a Find By Type Value Request which retrieve the handles of attributes + * that have known 16-bit UUID attribute type and known attribute value. + * + * The server should reply with a ble::pal::AttFindByTypeValueResponse + * containing the handle (or handle range in case of grouping attributes) of + * the attribute found. + * + * If not all attributes can be contained in the response it is necessary to + * send again this request with an updated range to continue the discovery. + * + * The server can send a ble::pal::AttErrorResponse with the code + * ble::pal::AttErrorResponse::ATTRIBUTE_NOT_FOUND if no attributes have + * been found in the range specified. The attribute handle in the response + * is then equal to the first handle of the discovery range. + * + * If the range is malformed the server will reply a + * ble::pal::AttErrorResponse with the error code ble::pal::INVALID_HANDLE. + * + * @param connection_handle The handle of the connection to send this + * request to. + * @param discovery_range The handle range where attributes with type and + * value are searched. + * @param type The type of attribute to find (it is a 16 bit UUID). + * @param value The value of the attributes to found. + * + * @return BLE_ERROR_NONE if the request has been successfully sent or the + * appropriate error otherwise. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.3.3 + */ + virtual ble_error_t find_by_type_value_request( + connection_handle_t connection_handle, + attribute_handle_range_t discovery_range, + uint16_t type, + const ArrayView& value + ) = 0; + + /** + * Send a Read By Type Request used to obtain the values of attributes where + * the attribute type is known but the handle is not known. + * + * If attributes with the type requested are present in the range, the server + * should reply with a ble::pal::AttReadByTypeResponse. If the response does + * not cover the full range, the request should be sent again with an updated + * range. + * + * In case of error, the server will send a ble::pal::AttErrorResponse. The + * error code depends on the situation: + * - ble::pal::AttErrorResponse::ATTRIBUTE_NOT_FOUND: If there is no + * attributes matching type in the range. + * - ble::pal::AttErrorResponse::INVALID_HANDLE: If the range is + * invalid. + * - ble::pal::AttErrorResponse::INSUFFICIENT_AUTHENTICATION: If the client + * security is not sufficient. + * - ble::pal::AttErrorResponse::INSUFFICIENT_AUTHORIZATION: If the client + * authorization is not sufficient. + * - ble::pal::AttErrorResponse::INSUFFICIENT_ENCRYPTION_KEY_SIZE: If the + * client has an insufficient encryption key size. + * - ble::pal::AttErrorResponse::INSUFFICIENT_ENCRYPTION: If the client + * has not enabled encryption. + * - ble::pal::AttErrorResponse::READ_NOT_PERMITTED: If the attribute + * value cannot be read. + * + * @param connection_handle The handle of the connection to send this + * request to. + * @param read_range The handle range where attributes with the given type + * should be read. + * @param type The type of attributes to read. + * + * @return BLE_ERROR_NONE if the request has been successfully sent or the + * appropriate error otherwise. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.4.1 + */ + virtual ble_error_t read_by_type_request( + connection_handle_t connection_handle, + attribute_handle_range_t read_range, + const UUID& type + ) = 0; + + /** + * Send a Read Request to read the value of an attribute in the server. + * + * In case of success, the server will reply with a ble::pal::AttReadResponse. + * containing the value of the attribute. If the length of the value in the + * response is equal to (mtu - 1) then the remaining part of the value can + * be obtained by a read_blob_request. + * + * In case of error, the server will send a ble::pal::AttErrorResponse. The + * error code depends on the situation: + * - ble::pal::AttErrorResponse::INVALID_HANDLE: If the attribute handle + * is invalid. + * - ble::pal::AttErrorResponse::INSUFFICIENT_AUTHENTICATION: If the client + * security is not sufficient. + * - ble::pal::AttErrorResponse::INSUFFICIENT_AUTHORIZATION: If the client + * authorization is not sufficient. + * - ble::pal::AttErrorResponse::INSUFFICIENT_ENCRYPTION_KEY_SIZE: If the + * client has an insufficient encryption key size. + * - ble::pal::AttErrorResponse::INSUFFICIENT_ENCRYPTION: If the client + * has not enabled encryption. + * - ble::pal::AttErrorResponse::READ_NOT_PERMITTED: If the attribute + * value cannot be read. + * Higher layer can also set an application error code (0x80 - 0x9F). + * + * @param connection_handle The handle of the connection to send this + * request to. + * @param attribute_handle The handle of the attribute to read. + * + * @return BLE_ERROR_NONE if the request has been successfully sent or the + * appropriate error otherwise. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.4.3 + */ + virtual ble_error_t read_request( + connection_handle_t connection_handle, + attribute_handle_t attribute_handle + ) = 0; + + /** + * Send a read blob request to a server to read a part of the value of an + * attribute at a given offset. + * + * In case of success, the server will reply with a ble::pal::AttReadBlobResponse + * containing the value read. If the value of the attribute starting at the + * offset requested is longer than (mtu - 1) octets then only the first + * (mtu - 1) octets will be present in the response. + * The remaining octets can be acquired by another Read Blob Request with an + * updated index. + * + * In case of error, the server will send a ble::pal::AttErrorResponse. The + * error code depends on the situation: + * - ble::pal::AttErrorResponse::INVALID_HANDLE: If the attribute handle + * is invalid. + * - ble::pal::AttErrorResponse::INSUFFICIENT_AUTHENTICATION: If the client + * security is not sufficient. + * - ble::pal::AttErrorResponse::INSUFFICIENT_AUTHORIZATION: If the client + * authorization is not sufficient. + * - ble::pal::AttErrorResponse::INSUFFICIENT_ENCRYPTION_KEY_SIZE: If the + * client has an insufficient encryption key size. + * - ble::pal::AttErrorResponse::INSUFFICIENT_ENCRYPTION: If the client + * has not enabled encryption. + * - ble::pal::AttErrorResponse::READ_NOT_PERMITTED: If the attribute + * value cannot be read. + * - ble::pal::AttErrorResponse::INVALID_OFFSET: If the offset is greater + * than the attribute length. + * - ble::pal::AttErrorResponse::ATTRIBUTE_NOT_LONG: If the attribute + * value has a length that is less than or equal to (mtu - 1). + * Higher layer can also set an application error code (0x80 - 0x9F). + * + * @param connection_handle The handle of the connection to send this + * request to. + * @param attribute_handle The handle of the attribute to read. + * @param offset The offset of the first octet to read. + * + * @return BLE_ERROR_NONE if the request has been successfully sent or an + * appropriate error otherwise. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.4.5 + */ + virtual ble_error_t read_blob_request( + connection_handle_t connection_handle, + attribute_handle_t attribute_handle, + uint16_t offset + ) = 0; + + /** + * Send a read multiple request to the server. It is used to read two or more + * attributes values at once. + * + * In case of success, the server will reply with a + * ble::pal::AttReadMultipleResponse containing the concatenation of the + * values read. Given that values are concatained, all attributes values + * should be of fixed size except for the last one. The concatained value + * is also truncated to (mtu - 1) if it doesn't fit in the response. + * + * In case of error, the server will send a ble::pal::AttErrorResponse. The + * error code depends on the situation: + * - ble::pal::AttErrorResponse::INVALID_HANDLE: If any of the attribute + * handle is invalid. + * - ble::pal::AttErrorResponse::INSUFFICIENT_AUTHENTICATION: If the client + * security is not sufficient to read any of the attribute. + * - ble::pal::AttErrorResponse::INSUFFICIENT_AUTHORIZATION: If the client + * authorization is not sufficient to read any of the attribute. + * - ble::pal::AttErrorResponse::INSUFFICIENT_ENCRYPTION_KEY_SIZE: If the + * client has an insufficient encryption key size to read any of the + * attributes. + * - ble::pal::AttErrorResponse::INSUFFICIENT_ENCRYPTION: If the client + * has not enabled encryption required to read any of the attributes. + * - ble::pal::AttErrorResponse::READ_NOT_PERMITTED: If any of the + * attributes value cannot be read. + * The first attribute causing the error is reporter in the handle_in_error + * field in the error response. + * Higher layer can also set an application error code (0x80 - 0x9F). + * + * @param connection_handle The handle of the connection to send this + * request to. + * @param attribute_handles Set of attribute handles to read. + * + * @return BLE_ERROR_NONE if the request has been successfully sent or an + * appropriate error otherwise. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.4.7 + */ + virtual ble_error_t read_multiple_request( + connection_handle_t connection_handle, + const ArrayView& attribute_handles + ) = 0; + + /** + * Send a read by group type request to the server. It is used to get + * informations about grouping attribute of a given type on a server. + * + * The server will reply with a ble::pal::ReadByGroupTypeResponse containing + * informations about the grouping attribute found. Informations are: + * - handle of the grouping attribute. + * - last handle of the group . + * - attribute value. + * + * If the last handle received is not the last handle of the discovery range + * then it is necessary to send another request with a discovery range + * updated to: [last handle + 1 : end]. + * + * In case of error, the server will send a ble::pal::AttErrorResponse. The + * error code depends on the situation: + * - ble::pal::AttErrorResponse::INVALID_HANDLE: If the range of handle + * provided is invalid. + * - ble::pal::AttErrorResponse::UNSUPPORTED_GROUP_TYPE: if the group type + * is not a supported grouping attribute. + * - ble::pal::AttErrorResponse::ATTRIBUTE_NOT_FOUND: If no attribute with + * the given type exists within the range provided. + * - ble::pal::AttErrorResponse::INSUFFICIENT_AUTHENTICATION: If the client + * security is not sufficient to read the requested attribute. + * - ble::pal::AttErrorResponse::INSUFFICIENT_AUTHORIZATION: If the client + * authorization is not sufficient to read the requested attribute. + * - ble::pal::AttErrorResponse::INSUFFICIENT_ENCRYPTION_KEY_SIZE: If the + * client has an insufficient encryption key size to read the requested + * attributes. + * - ble::pal::AttErrorResponse::INSUFFICIENT_ENCRYPTION: If the client + * has not enabled encryption required to read the requested attributes. + * - ble::pal::AttErrorResponse::READ_NOT_PERMITTED: If any of the + * attributes value cannot be read. + * Higher layer can also set an application error code (0x80 - 0x9F). + * + * @param connection_handle The handle of the connection to send this + * request to. + * @param read_range Range where this request apply. + * @param group_type Type of the grouping attribute to find and read. + * + * @return BLE_ERROR_NONE if the request has been successfully sent or an + * appropriate error otherwise. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.4.9 + */ + virtual ble_error_t read_by_group_type_request( + connection_handle_t connection_handle, + attribute_handle_range_t read_range, + const UUID& group_type + ) = 0; + + /** + * Send a write request to the server to write the value of an attribute. + * + * In case of success, the server will reply with a + * ble::pal::AttWriteResponse to acknowledge that the write operation went + * well. + * + * If the attribute value has a variable length, then the attribute value + * shall be truncated or lengthened to match the length of the value in the + * request. + * + * If the attribute value has a fixed length and the Attribute Value parameter length + * is less than or equal to the length of the attribute value, the octets of the + * attribute value parameter length shall be written; all other octets in this attribute + * value shall be unchanged. + * + * In case of error, the server will send a ble::pal::AttErrorResponse. The + * error code depends on the situation: + * - ble::pal::AttErrorResponse::INVALID_HANDLE: If the handle to write is + * invalid. + * - ble::pal::AttErrorResponse::INSUFFICIENT_AUTHENTICATION: If the client + * security is not sufficient to write the requested attribute. + * - ble::pal::AttErrorResponse::INSUFFICIENT_AUTHORIZATION: If the client + * authorization is not sufficient to write the requested attribute. + * - ble::pal::AttErrorResponse::INSUFFICIENT_ENCRYPTION_KEY_SIZE: If the + * client has an insufficient encryption key size to write the requested + * attributes. + * - ble::pal::AttErrorResponse::INSUFFICIENT_ENCRYPTION: If the client + * has not enabled encryption required to write the requested attributes. + * - ble::pal::AttErrorResponse::WRITE_NOT_PERMITTED: If the attribute + * value cannot be written due to permission. + * - ble::pal::AttErrorResponse::INVALID_ATTRIBUTE_VALUE_LENGTH: If the + * value to write exceeds the maximum valid length or of the attribute + * value; whether the attribute has a variable length value or a fixed + * length value. + * Higher layer can also set an application error code (0x80 - 0x9F). + * + * @param connection_handle The handle of the connection to send this + * request to. + * @param attribute_handle Handle of the attribute to write. + * @param value Value to write. It can't be longer than (mtu - 3). + * + * @return BLE_ERROR_NONE if the request has been successfully sent or an + * appropriate error otherwise. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.5.1 + */ + virtual ble_error_t write_request( + connection_handle_t connection_handle, + attribute_handle_t attribute_handle, + const ArrayView& value + ) = 0; + + /** + * Send a write command to the server. A write command is similar to a write + * request except that it won't receive any response from the server + * + * @param connection_handle The handle of the connection to send this + * request to. + * @param attribute_handle Handle of the attribute to write. + * @param value Value to write. It can't be longer than (mtu - 3). + * + * @return BLE_ERROR_NONE if the request has been successfully sent or an + * appropriate error otherwise. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.5.3 + */ + virtual ble_error_t write_command( + connection_handle_t connection_handle, + attribute_handle_t attribute_handle, + const ArrayView& value + ) = 0; + + /** + * Send a signed write command to the server. Behaviour is similar to a write + * command except that 12 bytes of the mtu are reserved for the authentication + * signature. + * + * @param connection_handle The handle of the connection to send this + * request to. + * @param attribute_handle Handle of the attribute to write. + * @param value Value to write. It can't be longer than (mtu - 15). + * + * @note the authentication signature to send with this request is + * computed by the implementation following the rules defined in BLUETOOTH + * SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.3.1. + * + * @return BLE_ERROR_NONE if the request has been successfully sent or an + * appropriate error otherwise. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.5.4 + */ + virtual ble_error_t signed_write_command( + connection_handle_t connection_handle, + attribute_handle_t attribute_handle, + const ArrayView& value + ) = 0; + + /** + * The Prepare Write Request is used to request the server to prepare to + * write the value of an attribute. The client can send multiple prepare + * write request which will be put in a queue until the client send an + * Execute Write Request which will execute sequentially the write request + * in the queue. + * + * In case of success the server will respond with a + * ble::pal::AttPrepareWriteResponse containing the values (attribute handle, + * offset and value) present in the write request. + * + * If a prepare write request is rejected by the server, the state queue of + * the prepare write request queue remains unaltered. + * + * In case of error, the server will send a ble::pal::AttErrorResponse. The + * error code depends on the situation: + * - ble::pal::AttErrorResponse::INVALID_HANDLE: If the handle to write is + * invalid. + * - ble::pal::AttErrorResponse::INSUFFICIENT_AUTHENTICATION: If the client + * security is not sufficient to write the requested attribute. + * - ble::pal::AttErrorResponse::INSUFFICIENT_AUTHORIZATION: If the client + * authorization is not sufficient to write the requested attribute. + * - ble::pal::AttErrorResponse::INSUFFICIENT_ENCRYPTION_KEY_SIZE: If the + * client has an insufficient encryption key size to write the requested + * attributes. + * - ble::pal::AttErrorResponse::INSUFFICIENT_ENCRYPTION: If the client + * has not enabled encryption required to write the requested attributes. + * - ble::pal::AttErrorResponse::WRITE_NOT_PERMITTED: If the attribute + * value cannot be written due to permission. + * - ble::pal::PREPARE_QUEUE_FULL: If the queue of prepare write request + * is full. + * Higher layer can also set an application error code (0x80 - 0x9F). + * + * @param connection_handle The handle of the connection to send this + * request to. + * @param attribute_handle The handle of the attribute to be written. + * @param offset The offset of the first octet to be written. + * @param value The value of the attribute to be written. It can't be longer + * than (mtu - 5). + * + * @return BLE_ERROR_NONE if the request has been successfully sent or an + * appropriate error otherwise. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.6.1 + * + */ + virtual ble_error_t prepare_write_request( + connection_handle_t connection_handle, + attribute_handle_t attribute_handle, + uint16_t offset, + const ArrayView& value + ) = 0; + + /** + * Send an Execute Write Request to the server. This request will instruct + * the server to execute or cancel the prepare write requests currently held + * in the prepare queue from this client. + * + * If the execute parameter is set to true, the server should execute the + * request held in the queue. If the parameter is equal to false then the + * server should cancel the requests in the queue. + * + * In case of success, the server will respond with a + * ble::pal::AttExecuteWriteResponse indicating that the request was correctly + * handled. + * + * In case of error, the server will send a ble::pal::AttErrorResponse. The + * error code depends on the situation: + * - ble::pal::AttErrorResponse::INVALID_OFFSET: If the value offset is + * greater than the current length of the attribute to write. + * - ble::pal::AttErrorResponse::INVALID_ATTRIBUTE_VALUE_LENGTH: If the + * length of the value write exceeds the length of the attribute value + * about to be written. + * Higher layer can also set an application error code (0x80 - 0x9F). + * + * The error response will contains the attribute handle which as caused the + * error and the remaining of the prepare queue is discarded. The state of + * the attributes that were to be written from the prepare queue is not + * defined in this case. + * + * @param connection_handle The handle of the connection to send this + * request to. + * @param execute Boolean indicating if the prepare queue should be executed + * or cleared. + * + * @return BLE_ERROR_NONE if the request has been successfully sent or an + * appropriate error otherwise. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.6.3 + */ + virtual ble_error_t execute_write_request( + connection_handle_t connection_handle, + bool execute + ) = 0; + + /** + * Register a callback which will handle messages from the server. + * + * @param cb The callback object which will handle messages from the server. + * It accept two parameters in input: The handle of the connection where the + * message was received and the message received. Real type of the message + * can be obtained from its opcode. + */ + void when_server_message_received( + mbed::Callback cb + ) { + _server_message_cb = cb; + } + + /** + * Register a callback handling transaction timeout. + * + * @param cb The callback handling timeout of a transaction. It accepts as + * a parameter the connection handle involved in the timeout. + * + * @note No more attribute protocol requests, commands, indication or + * notification shall be sent over a connection implied in a transaction + * timeout. To send a new ATT message, the conenction should be + * reestablished. + */ + void when_transaction_timeout( + mbed::Callback cb + ) { + _transaction_timeout_cb = cb; + } + +protected: + AttClient() { } + + virtual ~AttClient() { } + + /** + * Upon server message reception an implementation shall call this function. + * + * @param connection_handle The handle of the connection which has received + * the server message. + * @param server_message The message received from the server. + */ + void on_server_event( + connection_handle_t connection_handle, + const AttServerMessage& server_message + ) { + if (_server_message_cb) { + _server_message_cb(connection_handle, server_message); + } + } + + /** + * Upon transaction timeout an implementation shall call this function. + * + * @param connection_handle The handle of the connection of the transaction + * which has times out. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.3.3 + */ + void on_transaction_timeout( + connection_handle_t connection_handle + ) { + if (_transaction_timeout_cb) { + _transaction_timeout_cb(connection_handle); + } + } + +private: + /** + * Callback called when the client receive a message from the server. + */ + mbed::Callback _server_message_cb; + + /** + * Callback called when a transaction times out. + */ + mbed::Callback _transaction_timeout_cb; + + // Disallow copy construction and copy assignment. + AttClient(const AttClient&); + AttClient& operator=(const AttClient&); +}; + + +} // namespace pal +} // namespace ble + +#endif /* BLE_PAL_ATTCLIENT_H_ */ diff --git a/features/FEATURE_BLE/ble/pal/AttClientToGattClientAdapter.h b/features/FEATURE_BLE/ble/pal/AttClientToGattClientAdapter.h new file mode 100644 index 0000000000..d99a55e497 --- /dev/null +++ b/features/FEATURE_BLE/ble/pal/AttClientToGattClientAdapter.h @@ -0,0 +1,297 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017-2017 ARM Limited + * + * 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. + */ + +#ifndef BLE_PAL_ATTCLIENTTOGATTCLIENTADAPTER_H_ +#define BLE_PAL_ATTCLIENTTOGATTCLIENTADAPTER_H_ + +#include "AttClient.h" +#include "PalGattClient.h" + +namespace ble { +namespace pal { + +/** + * Adapt a pal::AttClient into a pal::GattClient. + * + * This class let vendors define their abstraction layer in term of an AttClient + * and adapt any AttClient into a GattClient. + */ +class AttClientToGattClientAdapter : public GattClient { + +public: + static const uint16_t END_ATTRIBUTE_HANDLE = 0xFFFF; + static const uint16_t SERVICE_TYPE_UUID = 0x2800; + static const uint16_t INCLUDE_TYPE_UUID = 0x2802; + static const uint16_t CHARACTERISTIC_TYPE_UUID = 0x2803; + + /** + * Construct an instance of GattClient from an instance of AttClient. + * @param client The client to adapt. + */ + AttClientToGattClientAdapter(AttClient& client) : + GattClient(), _client(client) { + _client.when_server_message_received( + mbed::callback(this, &AttClientToGattClientAdapter::on_server_event) + ); + _client.when_transaction_timeout( + mbed::callback( + this, &AttClientToGattClientAdapter::on_transaction_timeout + ) + ); + } + + /** + * @see ble::pal::GattClient::exchange_mtu + */ + virtual ble_error_t exchange_mtu(connection_handle_t connection) { + return _client.exchange_mtu_request(connection); + } + + /** + * @see ble::pal::GattClient::get_mtu_size + */ + virtual ble_error_t get_mtu_size( + connection_handle_t connection_handle, + uint16_t& mtu_size + ) { + return _client.get_mtu_size(connection_handle, mtu_size); + } + + /** + * @see ble::pal::GattClient::discover_primary_service + */ + virtual ble_error_t discover_primary_service( + connection_handle_t connection, + attribute_handle_t discovery_range_begining + ) { + return _client.read_by_group_type_request( + connection, + attribute_handle_range(discovery_range_begining, END_ATTRIBUTE_HANDLE), + SERVICE_TYPE_UUID + ); + } + + /** + * @see ble::pal::GattClient::discover_primary_service_by_service_uuid + */ + virtual ble_error_t discover_primary_service_by_service_uuid( + connection_handle_t connection_handle, + attribute_handle_t discovery_range_begining, + const UUID& uuid + ) { + return _client.find_by_type_value_request( + connection_handle, + attribute_handle_range(discovery_range_begining, END_ATTRIBUTE_HANDLE), + SERVICE_TYPE_UUID, + ArrayView( + uuid.getBaseUUID(), + (uuid.shortOrLong() == UUID::UUID_TYPE_SHORT) ? 2 : UUID::LENGTH_OF_LONG_UUID + ) + ); + } + + /** + * @see ble::pal::GattClient::find_included_service + */ + virtual ble_error_t find_included_service( + connection_handle_t connection_handle, + attribute_handle_range_t service_range + ) { + return _client.read_by_type_request( + connection_handle, + service_range, + INCLUDE_TYPE_UUID + ); + } + + /** + * @see ble::pal::GattClient::discover_characteristics_of_a_service + */ + virtual ble_error_t discover_characteristics_of_a_service( + connection_handle_t connection_handle, + attribute_handle_range_t discovery_range + ) { + return _client.read_by_type_request( + connection_handle, + discovery_range, + CHARACTERISTIC_TYPE_UUID + ); + } + + /** + * @see ble::pal::GattClient::discover_characteristics_descriptors + */ + virtual ble_error_t discover_characteristics_descriptors( + connection_handle_t connection_handle, + attribute_handle_range_t descriptors_discovery_range + ) { + return _client.find_information_request( + connection_handle, + descriptors_discovery_range + ); + } + + /** + * @see ble::pal::GattClient::read_attribute_value + */ + virtual ble_error_t read_attribute_value( + connection_handle_t connection_handle, + attribute_handle_t attribute_handle + ) { + return _client.read_request( + connection_handle, + attribute_handle + ); + } + + /** + * @see ble::pal::GattClient::read_using_characteristic_uuid + */ + virtual ble_error_t read_using_characteristic_uuid( + connection_handle_t connection_handle, + attribute_handle_range_t read_range, + const UUID& uuid + ) { + return _client.read_by_type_request( + connection_handle, + read_range, + uuid + ); + } + + /** + * @see ble::pal::GattClient::read_attribute_blob + */ + virtual ble_error_t read_attribute_blob( + connection_handle_t connection_handle, + attribute_handle_t attribute_handle, + uint16_t offset + ) { + return _client.read_blob_request( + connection_handle, + attribute_handle, + offset + ); + } + + /** + * @see ble::pal::GattClient::read_multiple_characteristic_values + */ + virtual ble_error_t read_multiple_characteristic_values( + connection_handle_t connection_handle, + const ArrayView& characteristic_value_handles + ) { + return _client.read_multiple_request( + connection_handle, + characteristic_value_handles + ); + } + + /** + * @see ble::pal::GattClient::write_without_response + */ + virtual ble_error_t write_without_response( + connection_handle_t connection_handle, + attribute_handle_t characteristic_value_handle, + const ArrayView& value + ) { + return _client.write_command( + connection_handle, + characteristic_value_handle, + value + ); + } + + /** + * @see ble::pal::GattClient::signed_write_without_response + */ + virtual ble_error_t signed_write_without_response( + connection_handle_t connection_handle, + attribute_handle_t characteristic_value_handle, + const ArrayView& value + ) { + return _client.signed_write_command( + connection_handle, + characteristic_value_handle, + value + ); + } + + /** + * @see ble::pal::GattClient::write_attribute + */ + virtual ble_error_t write_attribute( + connection_handle_t connection_handle, + attribute_handle_t attribute_handle, + const ArrayView& value + ) { + return _client.write_request( + connection_handle, + attribute_handle, + value + ); + } + + /** + * @see ble::pal::GattClient::queue_prepare_write + */ + virtual ble_error_t queue_prepare_write( + connection_handle_t connection_handle, + attribute_handle_t characteristic_value_handle, + const ArrayView& value, + uint16_t offset + ) { + return _client.prepare_write_request( + connection_handle, + characteristic_value_handle, + offset, + value + ); + } + + /** + * @see ble::pal::GattClient::execute_write_queue + */ + virtual ble_error_t execute_write_queue( + connection_handle_t connection_handle, + bool execute + ) { + return _client.execute_write_request(connection_handle, execute); + } + + /** + * @see ble::pal::GattClient::initialize + */ + virtual ble_error_t initialize() { + return _client.initialize(); + } + + /** + * @see ble::pal::GattClient::terminate + */ + virtual ble_error_t terminate() { + return _client.initialize(); + } + +private: + AttClient& _client; +}; + +} // namespace pal +} // namespace ble + + +#endif /* BLE_PAL_ATTCLIENTTOGATTCLIENTADAPTER_H_ */ diff --git a/features/FEATURE_BLE/ble/pal/AttServerMessage.h b/features/FEATURE_BLE/ble/pal/AttServerMessage.h new file mode 100644 index 0000000000..5a61850bbf --- /dev/null +++ b/features/FEATURE_BLE/ble/pal/AttServerMessage.h @@ -0,0 +1,772 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017-2017 ARM Limited + * + * 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. + */ + +#ifndef BLE_PAL_ATT_SERVER_MESSAGE_H_ +#define BLE_PAL_ATT_SERVER_MESSAGE_H_ + +#include "ble/BLETypes.h" +#include "ble/ArrayView.h" + +namespace ble { +namespace pal { + +/** + * Operation code defined for attribute operations + * @note see: BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.8 + */ +struct AttributeOpcode { + enum Code { + ERROR_RESPONSE = 0x01, /// Opcode of an AttErrorResponse + EXCHANGE_MTU_REQUEST = 0x02, + EXCHANGE_MTU_RESPONSE = 0x03, /// OpCode of an AttExchangeMTUResponse + FIND_INFORMATION_REQUEST = 0x04, + FIND_INFORMATION_RESPONSE = 0x05, /// OpCode of an AttFindInformationResponse + FIND_BY_TYPE_VALUE_REQUEST = 0x06, + FIND_BY_VALUE_TYPE_RESPONSE = 0x07, /// OpCode of an AttFindByTypeValueResponse + READ_BY_TYPE_REQUEST = 0x08, + READ_BY_TYPE_RESPONSE = 0x09, /// Opcode of an AttReadByTypeResponse + READ_REQUEST = 0x0A, + READ_RESPONSE = 0x0B, /// Opcode of an AttReadResponse + READ_BLOB_REQUEST = 0x0C, + READ_BLOB_RESPONSE = 0x0D, /// Opcode of an AttReadBlobResponse + READ_MULTIPLE_REQUEST = 0x0E, + READ_MULTIPLE_RESPONSE = 0x0F, /// Opcode of an AttReadMultipleResponse + READ_BY_GROUP_TYPE_REQUEST = 0x10, + READ_BY_GROUP_TYPE_RESPONSE = 0x11, /// Opcode of an AttReadByGroupTypeResponse + WRITE_REQUEST = 0x12, + WRITE_RESPONSE = 0x13, /// Opcode of an AttWriteResponse + WRITE_COMMAND = 0x52, + SIGNED_WRITE_COMMAND = 0xD2, + PREPARE_WRITE_REQUEST = 0x16, + PREPARE_WRITE_RESPONSE = 0x17, /// Opcode of an AttPrepareWriteResponse + EXECUTE_WRITE_REQUEST = 0x18, + EXECUTE_WRITE_RESPONSE = 0x19, /// Opcode of an AttExecuteWriteResponse + HANDLE_VALUE_NOTIFICATION = 0x1B, + HANDLE_VALUE_INDICATION = 0x1D + }; + + /** + * Construct an AttributeOpcode from a Code. + */ + AttributeOpcode(Code value) : _value(value) { } + + /** + * Equality comparison operator between two AttributeOpcode + */ + friend bool operator==(AttributeOpcode lhs, AttributeOpcode rhs) { + return lhs._value == rhs._value; + } + + /** + * Non equality comparison operator between two AttributeOpcode + */ + friend bool operator!=(AttributeOpcode lhs, AttributeOpcode rhs) { + return lhs._value != rhs._value; + } + + /** + * implicit cast to uint8_t. + * Allows AttributeOpcode to be used in switch statements. + */ + operator uint8_t() const { + return _value; + } + +private: + uint8_t _value; +}; + + + +/** + * Base class for Attribute Server Message. + * The correct type of the instance can be determined with the attribute opcode. + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.3.1 + */ +struct AttServerMessage { + /** + * Op code used to identify the type of the attribute response. + */ + const AttributeOpcode opcode; + +protected: + /** + * Construction of an AttResponse is reserved for descendent of the class + */ + AttServerMessage(AttributeOpcode opcode_) : opcode(opcode_) { } +}; + + +/** + * Response to a request which can't be performed + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.1.1 + * for details about error response. + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.9 + * which details possible error response by requests. + */ +struct AttErrorResponse : public AttServerMessage { + /** + * Construct an attribute error response. + * + * @param request_opcode_ The Attribute opcode of the request that generated + * the error. + * @param handle_in_error_ The attribute handle that generated this error + * response. + * @param error_code The reason why the request has generated an error. + */ + AttErrorResponse( + AttributeOpcode request_opcode_, + attribute_handle_t handle_in_error_, + uint8_t error_code_ + ) : AttServerMessage(AttributeOpcode::ERROR_RESPONSE), + request_opcode(request_opcode_), + handle_in_error(handle_in_error_), error_code(error_code_) { + } + + /** + * Construct an attribute error response in the case where there was no + * attribute handle in the original response or if the request is not + * supported. + * + * @param request_opcode_ The Attribute opcode of the request that generated + * the error. + * @param error_code The reason why the request has generated an error. + */ + AttErrorResponse( + AttributeOpcode request_opcode_, + uint8_t error_code_ + ) : AttServerMessage(AttributeOpcode::ERROR_RESPONSE), + request_opcode(request_opcode_), + handle_in_error(0x0000), error_code(error_code_) { + } + + /** + * The opcode of the request that generated this error response. + */ + const AttributeOpcode request_opcode; + + /** + * The attribute handle that generated this error response. + * If there was no attribute handle in the original request or if the + * request is not supported, then this field is equal to 0x0000. + */ + const attribute_handle_t handle_in_error; + + /** + * The reason why the request has generated an error response + */ + const uint8_t error_code; + + /** + * List of Error codes for the ATT protocol + */ + enum AttributeErrorCode { + /** The attribute handle given was not valid on this server. */ + INVALID_HANDLE = 0x01, + + /** The attribute cannot be read. */ + READ_NOT_PERMITTED = 0x02, + + /** The attribute cannot be written. */ + WRITE_NOT_PERMITTED = 0x03, + + /** The attribute PDU was invalid. */ + INVALID_PDU = 0x04, + + /** The attribute requires authentication before it can be read or + * written. + */ + INSUFFICIENT_AUTHENTICATION = 0x05, + + /** Attribute server does not support the request received from the + * client. + */ + REQUEST_NOT_SUPPORTED = 0x06, + + /** Offset specified was past the end of the attribute. */ + INVALID_OFFSET = 0x07, + + /** The attribute requires authorization before it can be read or written. */ + INSUFFICIENT_AUTHORIZATION = 0x08, + + /** Too many prepare writes have been queued. */ + PREPARE_QUEUE_FULL = 0x09, + + /** No attribute found within the given attribute handle range. */ + ATTRIBUTE_NOT_FOUND = 0x0A, + + /** The attribute cannot be read using the Read Blob Request. */ + ATTRIBUTE_NOT_LONG = 0x0B, + + /** The Encryption Key Size used for encrypting this link is + * insufficient. + */ + INSUFFICIENT_ENCRYPTION_KEY_SIZE = 0x0C, + + /** The attribute value length is invalid for the operation. */ + INVALID_ATTRIBUTE_VALUE_LENGTH = 0x0D, + + /** The attribute request that was requested has encountered an error + * that was unlikely, and therefore could not be completed as requested. + */ + UNLIKELY_ERROR = 0x0E, + + /** The attribute requires encryption before it can be read or written. */ + INSUFFICIENT_ENCRYPTION = 0x0F, + + /** The attribute type is not a supported grouping attribute as defined + * by a higher layer specification. + */ + UNSUPPORTED_GROUP_TYPE = 0x10, + + /** Insufficient Resources to complete the request. */ + INSUFFICIENT_RESOURCES = 0x11, + + /* 0x12 - 0x7F => reserved for future use */ + + /* 0x80 - 0x9F => Application Error */ + + /* 0xA0 0xDF => Reserved for future use */ + + /* 0xE0 - 0xFF Common Profile and service Error Codes */ + + /** The Write Request Rejected error code is used when a requested write + * operation cannot be fulfilled for reasons other than permissions. + */ + WRITE_REQUEST_REJECTED = 0xFC, + + /** The Client Characteristic Configuration Descriptor Improperly + * Configured error code is used when a Client Characteristic + * Configuration descriptor is not configured according to the + * requirements of the profile or service. + */ + CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_IMPROPERLY_CONFIGURED = 0xFD, + + /** The Procedure Already in Progress error code is used when a profile + * or service request cannot be serviced because an operation that has + * been previously triggered is still in progress + */ + PROCEDURE_ALREADY_IN_PROGRESS = 0xFE, + + /** The Out of Range error code is used when an attribute value is out + * of range as defined by a profile or service specification. + */ + OUT_OF_RANGE = 0xFF + }; +}; + + +/** + * The Exchange MTU Request is used by the client to inform the server of the + * client’s maximum receive MTU size and request the server to respond with its + * maximum rx MTU size. + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.2.2 + */ +struct AttExchangeMTUResponse : public AttServerMessage { + /** + * Construct an exchange mtu response containing the max rx mtu of the + * server. + * + * @param server_rx_mtu_ The max rx mtu the server can handle. + */ + AttExchangeMTUResponse(uint16_t server_rx_mtu_) : + AttServerMessage(AttributeOpcode::EXCHANGE_MTU_RESPONSE), + server_rx_mtu(server_rx_mtu_) { + } + + /** + * The max rx mtu the server can handle. + */ + const uint16_t server_rx_mtu; +}; + + +/** + * The Find Information Response is sent in reply to a received Find Information + * Request and contains information about this server. + * + * The Find Information Response contains a sequence of handle-uuid pairs in + * ascending order if attribute handles. + * + * This class has to be subclassed by an implementation specific class defining + * the member function size and the subscript operator. + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.3.2 + */ +struct AttFindInformationResponse : public AttServerMessage { + + /** handle-uuid pair */ + struct information_data_t { + attribute_handle_t handle; + UUID uuid; + }; + + /** + * Base constructor, setup the OpCode of the response. + */ + AttFindInformationResponse() : + AttServerMessage(AttributeOpcode::FIND_INFORMATION_RESPONSE) { + } + + /** + * virtual destructor to overide if the sub class needs it. + */ + virtual ~AttFindInformationResponse() { } + + /** + * Returns the number of information_data_t present in the response. + */ + virtual size_t size() const = 0; + + /** + * Access to information_data_t elements present in the response. + * @note Out of range access is undefined. + */ + virtual information_data_t operator[](size_t index) const = 0; +}; + + +/** + * Find by type value responses are sent in response to find by type value + * request. + * + * The response contains a sequence of Found Attribute Handle, Group End Handle + * pair where: + * - Found Attribute Handle is the handle of an attribute matching the type + * and the value requested. + * - Group End Handle is the end of the attribute group if the attribute found + * is a grouping attribute or the same value as Found Attribute Handle if + * the attribute is not a grouping attribute. + * + * This class should be subclassed by an implementation specific class defining + * the member function size and the subscript operator. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.3.4 + */ +struct AttFindByTypeValueResponse : public AttServerMessage { + /** + * Base constructor, setup the OpCode of the response. + */ + AttFindByTypeValueResponse() : + AttServerMessage(AttributeOpcode::FIND_BY_VALUE_TYPE_RESPONSE) { + } + + /** + * virtual destructor to overide if the sub class needs it. + */ + virtual ~AttFindByTypeValueResponse() { } + + /** + * Returns the number of attribute_handle_range_t present in the response. + */ + virtual std::size_t size() const = 0; + + /** + * Access to the attribute range present in the response. + * @note Out of range access is undefined. + */ + virtual attribute_handle_range_t operator[](size_t index) const = 0; +}; + + +/** + * Response to a Read By Type request. + * + * It contains a list of handle-value pairs where: + * - handle is the handle of the attribute matching the rype requested. + * - value is the value of the attribute found. If the value is longer than + * (mtu - 4) then it can be truncated and read blob request should be used + * to read the remaining octet of the attribute. + * + * This class has to be subclassed by an implementation specific class defining + * the member function size and the subscript operator. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.4.2 + */ +struct AttReadByTypeResponse : public AttServerMessage { + /** + * handle-value pair + */ + struct attribute_data_t { + attribute_handle_t handle; + ArrayView value; + }; + + /** + * Base constructor, setup the OpCode of the response. + */ + AttReadByTypeResponse() : + AttServerMessage(AttributeOpcode::READ_BY_TYPE_RESPONSE) { + } + + /** + * virtual destructor to overide if the sub class needs it. + */ + virtual ~AttReadByTypeResponse() { } + + /** + * Return the number of attribute_data_t presents in the response. + */ + virtual size_t size() const = 0; + + /** + * Return the attribute data at index. + * @note Out of range access is undefined. + */ + virtual attribute_data_t operator[](size_t index) const = 0; +}; + + +/** + * The read response is sent in reply to a received Read Request and contains + * the value of the attribute that has been read. + * + * The attribute value shall be set to the value of the attribute identified by + * the attribute handle in the request. If the attribute value is longer than + * (ATT_MTU-1) then the first (ATT_MTU-1) octets shall be included in this + * response. + * + * @note The Read Blob Request would be used to read the remaining octets of a + * long attribute value. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.4.4 + */ +struct AttReadResponse : public AttServerMessage { + /** + * Construct a Read Response from an array of bytes. + */ + AttReadResponse(ArrayView data_) : + AttServerMessage(AttributeOpcode::READ_RESPONSE), _data(data_) { + } + + /** + * Return the number of octets presents in the response. + */ + size_t size() const { + return _data.size(); + } + + /** + * Return the octet at the specified index. + * @note Out of range access is undefined. + */ + uint8_t operator[](size_t index) const { + return _data[index]; + } + + /** + * Return the pointer to the actual data + */ + const uint8_t* data() const { + return _data.data(); + } + +private: + const ArrayView _data; +}; + + +/** + * The Read Blob Response is sent in reply to a received Read Blob Request and + * contains part of the value of the attribute that has been read. + * + * If the offset requested is equal to the length of the attribute then the + * response contains no data and the size of the data returned is equal to 0. + * + * If the value of the attribute starting at the offset requested is longer than + * (mtu - 1) octets then the first (mtu - 1) will be present in the response. + * The remaining octets will be acquired by another Read Blob Request with an + * updated index. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.4.6 + */ +struct AttReadBlobResponse : public AttServerMessage { + /** + * Construct a read blob response from the value responded. + */ + AttReadBlobResponse(ArrayView data_) : + AttServerMessage(AttributeOpcode::READ_BLOB_RESPONSE), _data(data_) { + } + + /** + * Return the number of octets presents in the response value. + */ + size_t size() const { + return _data.size(); + } + + /** + * Return the octet of the value read at the specified index. + * @note Out of range access is undefined. + */ + uint8_t operator[](size_t index) const { + return _data[index]; + } + + /** + * Return the pointer to the actual data + */ + const uint8_t* data() const { + return _data.data(); + } + +private: + const ArrayView _data; +}; + + +/** + * Response to a Read Multiple Request. It contains the values of the attributes + * that have been read. + * + * If the set of values that has been read is longer than (mtu - 1) then only + * the first (mtu - 1) octets are included in the response. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.4.8 + */ +struct AttReadMultipleResponse : public AttServerMessage { + /** + * Construct a Resd Multiple Response from the set of value received. + */ + AttReadMultipleResponse(ArrayView data_) : + AttServerMessage(AttributeOpcode::READ_MULTIPLE_RESPONSE), _data(data_) { + } + + /** + * Return the number of octets presents in the response set of value. + */ + size_t size() const { + return _data.size(); + } + + /** + * Return the octet of the set of value read at the specified index. + * @note Out of range access is undefined. + */ + uint8_t operator[](size_t index) const { + return _data[index]; + } + +private: + const ArrayView _data; +}; + + +/** + * The Read By Group Type Response is sent in reply to a received Read By + * Group Type Request and contains the handles and values of the attributes that + * have been read. + * + * The response is a list of group range-value pair where: + * - group range: The range of the group found where begin is the grouping + * attribute handle and end is the handle of the end of the group. + * - value: The value of the grouping attribute. + * + * This class has to be subclassed by an implementation specific class defining + * the member function size and the subscript operator. + * + * @note The value responded can be trucated if it doesn't fit in the response, + * in that case a Read Blob Request could be used to read the remaining octets. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.4.10 + */ +struct AttReadByGroupTypeResponse : public AttServerMessage { + /** + * Data read from the grouping attribute. + * It includes the range of the group and the value of the attribute. + */ + struct attribute_data_t { + attribute_handle_range_t group_range; + ArrayView value; + }; + + /** + * Base constructor, setup the OpCode of the response. + */ + AttReadByGroupTypeResponse() : + AttServerMessage(AttributeOpcode::READ_BY_GROUP_TYPE_RESPONSE) { + } + + /** + * virtual destructor to overide if the sub class needs it. + */ + virtual ~AttReadByGroupTypeResponse() { } + + /** + * Return the number of attribute_data_t present in the response. + */ + virtual size_t size() const = 0; + + /** + * Return the attribute data read at the index specified. + * @note Out of range access is undefined. + */ + virtual attribute_data_t operator[](size_t index) const = 0; +}; + + +/** + * The Write Response is sent in reply to a valid Write Request and + * acknowledges that the attribute has been successfully written. + * It is just a placeholder which indicates the client that the write request + * was successful. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.5.2 + */ +struct AttWriteResponse : public AttServerMessage { + /** + * Construct a write response. + */ + AttWriteResponse() : AttServerMessage(AttributeOpcode::WRITE_RESPONSE) { } +}; + + +/** + * Response to a Prepare Write Request. It acknowledges the client that the + * value has been successfully received and placed in the write queue. + * + * The response contains the same values as the one present in the request. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.6.2 + */ +struct AttPrepareWriteResponse : public AttServerMessage { + /** + * Construct a prepare write response. + * @param handle_ The handle of the attribute to be written. + * @param offset_: The offset of the first octet to be writen. + * @param value_: The value of the attribute to be written at the offset + * indicated. + */ + AttPrepareWriteResponse( + attribute_handle_t handle_, + uint16_t offset_, + ArrayView value_ + ) : AttServerMessage(AttributeOpcode::PREPARE_WRITE_RESPONSE), + attribute_handle(handle_), + offset(offset_), + partial_value(value_) { + } + + /** + * The handle of the attribute to be written. + */ + const attribute_handle_t attribute_handle; + + /** + * The offset of the first octet to be writen. + */ + const uint16_t offset; + + /** + * The value of the attribute to be written at the offset indicated. + */ + const ArrayView partial_value; +}; + + +/** + * The Execute Write Response is sent in response to a received Execute Write + * Request. + * + * It is just a placeholder which indicates the client that the execution of the + * write request has been successfull. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.6.4 + */ +struct AttExecuteWriteResponse : public AttServerMessage { + /** + * Construct an execute write response object. + */ + AttExecuteWriteResponse() : + AttServerMessage(AttributeOpcode::EXECUTE_WRITE_RESPONSE) { + } +}; + + +/** + * Notification of an attribute's value sent by the server. + * + * It contains the handle of the attribute and its value. + * + * If the attribute value is longer than (mtu - 3) then the value is truncated + * to (mtu - 3) octets to fit in the response and the client will have to use + * a read blob request to read the remaining octets of the attribute. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.7.1 + */ +struct AttHandleValueNotification : public AttServerMessage { + /** + * Construct an Handle Value Notification from the attribute handle and its + * value notified. + */ + AttHandleValueNotification( + attribute_handle_t attribute_handle, + ArrayView attribute_value + ) : AttServerMessage(AttributeOpcode::HANDLE_VALUE_NOTIFICATION), + attribute_handle(attribute_handle), + attribute_value(attribute_value) { + } + + /** + * Handle of the attribute + */ + const attribute_handle_t attribute_handle; + + /** + * The current value of the attribute. + */ + const ArrayView attribute_value; +}; + + +/** + * Indication of an attribute's value sent by the server. + * + * It contains the handle of the attribute and its value. The client should + * respond with and handle value confirmation. + * + * If the attribute value is longer than (mtu - 3) then the value is truncated + * to (mtu - 3) octets to fit in the response and the client will have to use + * a read blob request to read the remaining octets of the attribute. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.4.7.2 + */ +struct AttHandleValueIndication : public AttServerMessage { + /** + * Construct an Handle Value Indication from the attribute handle and its + * value indicated. + */ + AttHandleValueIndication( + attribute_handle_t handle, ArrayView value + ) : AttServerMessage(AttributeOpcode::HANDLE_VALUE_INDICATION), + attribute_handle(handle), attribute_value(value) { + } + + /** + * Handle of the attribute + */ + const attribute_handle_t attribute_handle; + + /** + * The current value of the attribute. + */ + const ArrayView attribute_value; +}; + + +} // namespace pal +} // namespace ble + +#endif /* BLE_PAL_ATT_SERVER_MESSAGE_H_ */ diff --git a/features/FEATURE_BLE/ble/pal/PalGattClient.h b/features/FEATURE_BLE/ble/pal/PalGattClient.h new file mode 100644 index 0000000000..2152c428f4 --- /dev/null +++ b/features/FEATURE_BLE/ble/pal/PalGattClient.h @@ -0,0 +1,642 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017-2017 ARM Limited + * + * 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. + */ + +#ifndef BLE_PAL_GATT_CLIENT_H_ +#define BLE_PAL_GATT_CLIENT_H_ + +#include "ble/UUID.h" +#include "ble/BLETypes.h" +#include "ble/ArrayView.h" +#include "ble/blecommon.h" + +#include "platform/Callback.h" + +#include "AttServerMessage.h" + +namespace ble { +namespace pal { + +/** + * Adaptation layer for a GATT client. + * + * Define the primitive necessary to implement a proper GATT client. These + * primitives are sometime GATT procedure, ATT procedure or a bit of both. + * + * In general, discovery procedures follow strictly GATT formulation while + * attribute manipulation procedures (read/write) follow the ATT formulation + * to avoid multiple identical implementation for characteristic values and + * characteristic descriptors value, it also fill a hole leave by the + * specification which doesn't define any GATT procedure to get the UUID of an + * included service with a 128 bit UUID. + * + * Complementary informations around ATT procedures can be found in the Section + * 3.4 of the Vol3, Part F of the Bluetooth specification while more informations + * around the GATT procedures can be found in the Section 4 of the Vol 3, Part + * G of the Bluetooth specification. + * + * Complete and compliant Gatt Client used by developer is realized at an higher + * level using the primitives defined in this adaptation layer. + * + * If a stack expose the complete ATT layer then it is possible to provide an + * implementation for GattClient by subclassing the AttClient class and use + * the class AttClientToGattClientAdapter + */ +class GattClient { + +public: + /** + * Initialisation of the instance. An implementation can use this function + * to initialise the subsystems needed to realize the operations of this + * interface. + * + * This function has to be called before any other operations. + * + * @return BLE_ERROR_NONE if the request has been successfully sent or the + * appropriate error otherwise. + */ + virtual ble_error_t initialize() = 0; + + /** + * Termination of the instance. An implementation can use this function + * to release the subsystems initialised to realise the operations of + * this interface. + * + * After a call to this function, initialise should be called again to + * allow usage of the interface. + * + * @return BLE_ERROR_NONE if the request has been successfully sent or the + * appropriate error otherwise. + */ + virtual ble_error_t terminate() = 0; + + /** + * Negotiate the mtu to use by this connection. + * First the client send to the server the maximum rx mtu that it can receive + * then the client reply with the maximum rx mtu it can receive. + * The mtu chosen for the connection is the minimum of the client Rx mtu + * and server Rx mtu values. + * + * If an error occurred then the mtu used remains the default value. + * + * The server will reply to this request with an AttExchangeMTUResponse in + * case of success or AttErrorResponse in case of failure. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part G Section 4.3.1 + * + * @param connection The handle of the connection to send this request to. + * @return BLE_ERROR_NONE or an appropriate error. + */ + virtual ble_error_t exchange_mtu(connection_handle_t connection) = 0; + + /** + * Acquire the size of the mtu for a given connection. + * + * @param connection The handle of the connection for which the the MTU size + * should be acquired. + * + * @param mtu_size Output parameter which will contain the MTU size. + * + * @return BLE_ERROR_NONE if the MTU size has been acquired or the + * appropriate error otherwise. + */ + virtual ble_error_t get_mtu_size( + connection_handle_t connection_handle, + uint16_t& mtu_size + ) = 0; + + /** + * Discover primary services in the range [begin - 0xFFFF]. + * + * If services exists in the range provided, the server will reply with a + * ReadByGoupType response where for each attribute_data exposed: + * - attribute_handle is the service attribute handle + * - end_group_handle is the last handle of the service + * - attribute_value is the UUID of the service. + * + * If the end of the range is not reached, this procedure can be relaunched + * with the last handle of the last service discovered plus one as the + * beginning of the discovery range. + * + * If there is not services left to discover in the range, the server can + * either: + * * Reply with an ErrorResponse with the Error code set to ATTRIBUTE_NOT_FOUND + * * Set the end handle of the last service to 0xFFFF. + * + * @note This function realize a partial implementation of the Discover All + * Primary Services procedure. The complete implementation of the procedure + * is realized at an higher level. + * @note This function issue a Read By Group Type ATT request where the + * type parameter is equal to Primary Service and the Ending Handle parameter + * is equal to 0xFFFF. + * @note BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part G Section 4.4.1 + * + * @param connection The handle of the connection to send this request to. + * @param begin The beginning of the discovery range. + * + * @return BLE_ERROR_NONE or an appropriate error. + */ + virtual ble_error_t discover_primary_service( + connection_handle_t connection, + attribute_handle_t discovery_range_begining + ) = 0; + + /** + * Discover primary services by UUID in the range [discovery_range_begining - 0xFFFF]. + * + * If services exists in the range provided, the server will reply with a + * FindByTypeValueResponse containing the attribute range of each service + * discovered. + * + * If the end of the range is not reached, this procedure can be relaunched + * with the last handle of the last service discovered plus one as the + * beginning of the discovery range. + * + * If there is not services left to discover in the range, the server can + * either: + * * Reply with an ErrorResponse with the Error code set to ATTRIBUTE_NOT_FOUND + * * Set the end handle of the last service to 0xFFFF. + * + * @note This function realize a partial implementation of the Discover + * Primary Service by Service UUID procedure. The complete implementation of + * the procedure is realized at an higher level. + * @note This function issue a Find By Type Value ATT request where the + * type parameter is equal to Primary Service and the Ending Handle + * parameter is equal to 0xFFFF. + * @note BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part G Section 4.4.2 + * + * @param connection The handle of the connection to send this request to. + * @param begin The beginning of the discovery range. + * @param uuid The UUID of the service to discover. + * + * @return BLE_ERROR_NONE or an appropriate error. + */ + virtual ble_error_t discover_primary_service_by_service_uuid( + connection_handle_t connection_handle, + attribute_handle_t discovery_range_beginning, + const UUID& uuid + ) = 0; + + /** + * Find included services within a service. + * + * If services are included in the service range then the server will reply + * with a ReadByTypeResponse where for each attribute record: + * - attribute_handle The handle where the service is included within the + * service definition. + * - attribute_data Contains two handles defining the range of the included. + * If the service found have a 16 bit uuid then its UUID is also included + * in the attribute data. + * + * If the end of the service range is not reached, this procedure can be + * relaunched with the handle of the last included service discovered plus + * one as the beginning of the new discovery range. + * + * The procedure is complete when either: + * - The server reply with an ErrorResponse with the Error code set to + * ATTRIBUTE_NOT_FOUND + * - An included service handle returned is equal to the end of the range. + * + * If the service UUID is a 128 bits one then it is necessary to issue a read + * attribute request on the first handle of the service discovered to get it. + * + * @note This function realize a partial implementation of the Find Included + * Services procedure. The complete implementation of the procedure is + * realized at an higher level. + * @note This function issue a Read By Type ATT request where the + * type parameter is equal to Include. + * @note BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part G Section 4.5.1 + * + * @param connection The handle of the connection to send this request to. + * @param service_range The range of the service where service inclusion has + * to be discovered. + * + * @return BLE_ERROR_NONE or an appropriate error. + */ + virtual ble_error_t find_included_service( + connection_handle_t connection_handle, + attribute_handle_range_t service_range + ) = 0; + + /** + * Find characteristic declarations within a service definition. + * + * If characteristic declarations are found within the range then the server + * should reply with a ReadByTypeResponse where for each attribute record: + * - attribute_handle is the handle of the characteristic definition + * - attribute_data contains the the following values attached to the + * characteristic : + * + properties: the properties of the characteristic. + * + value handle: the handle of the value of the characteristic. + * + uuid: the UUID of the characteristic. + * + * The procedure is considered complete when the server send an ErrorResponse + * with the ErrorCode set to ATTRIBUTE_NOT_FOUND or a ReadByType response + * has an attribute_handle set to the end of the discovery range. + * + * If the procedure is not complete after a server response, it should be + * relaunched with an updated range starting at the last attribute_handle + * discovered plus one. + * + * @note This function realize a partial implementation of the Discover All + * Characteristics of a Service procedure. The complete implementation of + * the procedure is realized at an higher level. + * @note This function issue a Read By Type ATT request where the type + * parameter is equal to Characteristic. + * @note BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part G Section 4.6.1 + * + * @note The GATT procedure Discover Characteristics by UUID is implemented + * at a higher level using this function as a base. + * + * @param connection The handle of the connection to send this request to. + * @param discovery_range The range of attributes where the characteristics + * are discovered. It should be contained within a service. + * + * @return BLE_ERROR_NONE or an appropriate error. + */ + virtual ble_error_t discover_characteristics_of_a_service( + connection_handle_t connection_handle, + attribute_handle_range_t discovery_range + ) = 0; + + /** + * Discover characteristic descriptors of a characteristic. + * + * If descriptors are found within the range provided then the server should + * reply with a FindInformationResponse containing a list of + * attribute_handle_t, UUID pairs where the attribute handle is the + * descriptor handle and UUID is the UUID of the descriptor. + * + * The procedure is complete when the server send an ErrorResponse with the + * error code ATTRIBUTE_NOT_FOUND or the FindInformationResponse has an + * attribute handle that is equal to the end handle of the discovery range. + * + * @note This function realize a partial implementation of the Discover All + * Characteristics Descriptors procedure. The complete implementation of + * the procedure is realized at an higher level. + * @note This function issue a Find Information ATT request.. + * @note BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part G Section 4.7.1 + * + * @note It should be possible to use this function to issue a regular + * ATT Find Information Request. + * + * @param connection The handle of the connection to send this request to. + * @param descriptors_discovery_range The range of attributes where the + * descriptors are discovered. The first handle shall be no less than the + * characteristic value handle plus one and the last handle shall be no more + * than the last handle of the characteristic. + * + * @return BLE_ERROR_NONE or an appropriate error. + */ + virtual ble_error_t discover_characteristics_descriptors( + connection_handle_t connection_handle, + attribute_handle_range_t descriptors_discovery_range + ) = 0; + + /** + * Read the value of an attribute. + * + * The server will reply with an AttReadResponse. In case of error, the + * server will reply with an AttErrorResponse. + * + * @note This function issue an ATT Read Request. + * + * @note: This function is the base function for the read Characteristic + * Value and Read Characteristic Descriptor GATT procedures. It can also + * be used to read the 128 bit UUID of a service discovered with + * find_included_service. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part G Section 4.8.1 + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part G Section 4.12.1 + * + * @param connection The handle of the connection to send this request to. + * @param attribute_handle Handle of the attribute to read. + * + * @return BLE_ERROR_NONE or an appropriate error. + */ + virtual ble_error_t read_attribute_value( + connection_handle_t connection_handle, + attribute_handle_t attribute_handle + ) = 0; + + /** + * Read a characteristic value using its UUID (type). + * + * The server will respond a ReadByTypeResponse containing a sequence of + * attribute handle and attribute value pairs. + * To read remaining attributes, the client should launch a new request + * with an updated range. + * + * The procedure is considered complete when the server respond with an + * ErrorResponse containing the ErrorCode ATTRIBUTE_NOT_FOUND or when an + * handle in the ReadByTypeResponse is equal to the end of the discovered + * range. + * + * @note This function realize a partial implementation of the Read Using + * Characteristics Characteristic procedure. The complete implementation of + * the procedure is realized at an higher level. + * @note This function issue a Read By Type ATT request. + * @note BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part G Section 4.8.2 + * + * @note It should be possible to use this function to issue a regular + * ATT Read By Type Request. + * + * @param connection The handle of the connection to send this request to. + * @param attribute_range Range of the handle where an attribute with + * uuid as type is present. + * @param uuid UUID of the characteristic(s) to read. + * + * @return BLE_ERROR_NONE or an appropriate error. + */ + virtual ble_error_t read_using_characteristic_uuid( + connection_handle_t connection_handle, + attribute_handle_range_t read_range, + const UUID& uuid + ) = 0; + + /** + * Read a partial value of an attribute. + * + * The server will respond with a ReadBlobResponse containing the data read + * or an ErrorResponse in case of error. + * + * The procedure is not complete as long as the value in response have the + * same size as the mtu minus one. If the procedure is not complete, it can + * be launch again with an updated offset to read the remaining part of the + * attribute value. + * + * @note This function issue an ATT Read Blob Request. + * + * @note: This function is the base function for the Read Long + * Characteristic Value and Read Long Characteristic Descriptor GATT + * procedures. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part G Section 4.8.3 + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part G Section 4.12.2 + * + * @param connection_handle The handle of the connection to send this request to. + * @param attribute_handle Handle of the attribute to read. + * @param offset Beginning offset for the read operation. + * + * @return BLE_ERROR_NONE or an appropriate error. + */ + virtual ble_error_t read_attribute_blob( + connection_handle_t connection_handle, + attribute_handle_t attribute_handle, + uint16_t offset + ) = 0; + + /** + * Read atomically multiple characteristics values. + * + * The server will respond with a ReadMultiple response containing the + * concatenation of the values of the characteristics. + * + * @note values might be truncated + * + * In case of error, the server should respond a with an ErrorResponse. + * + * @note This function issue an ATT Read Multiple Request. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part G Section 4.8.4 + * + * @param connection_handle The handle of the connection to send this request to. + * @param characteristic_value_handles Handle of the characteristic values + * to read. + * + * @return BLE_ERROR_NONE or an appropriate error. + */ + virtual ble_error_t read_multiple_characteristic_values( + connection_handle_t connection_handle, + const ArrayView& characteristic_value_handles + ) = 0; + + /** + * Send a write command to the server. + * + * @note This function issue an ATT Write Command. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part G Section 4.9.1 + * + * @param connection_handle The handle of the connection to send this request to. + * @param attribute_handle Handle of the attribute to write. + * @param value The value to write. + * + * @return BLE_ERROR_NONE or an appropriate error. + */ + virtual ble_error_t write_without_response( + connection_handle_t connection_handle, + attribute_handle_t characteristic_value_handle, + const ArrayView& value + ) = 0; + + /** + * Send a Signed Write without Response command to the server. + * + * @note This function issue an ATT Write Command with the signed flag and + * the signature. + * + * @note signature is calculated by the stack. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part G Section 4.9.2 + * + * @param connection_handle The handle of the connection to send this request to. + * @param attribute_handle Handle of the attribute to write. + * @param value The value to write. + * + * @return BLE_ERROR_NONE or an appropriate error. + */ + virtual ble_error_t signed_write_without_response( + connection_handle_t connection_handle, + attribute_handle_t characteristic_value_handle, + const ArrayView& value + ) = 0; + + /** + * Send a write request to the server. + * + * The server should respond with a WriteResponse in case of success or an + * ErrorResponse in case of error. + * + * @note This function issue an ATT Write Request. + * + * @note: This function is the base function for the Write Characteristic + * Value and Write Characteristic Descriptors GATT procedures. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part G Section 4.9.3 + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part G Section 4.12.3 + * + * @param connection_handle The handle of the connection to send this request to. + * @param attribute_handle Handle of the attribute to write. + * @param value The value to write. + * + * @return BLE_ERROR_NONE or an appropriate error. + */ + virtual ble_error_t write_attribute( + connection_handle_t connection_handle, + attribute_handle_t attribute_handle, + const ArrayView& value + ) = 0; + + /** + * Send a prepare write request to the server. + * + * The write request will go into a queue until the client execute or cancel + * the request with an execute write request which will write all the values + * in the queue atomically. + * + * The server should respond with a PrepareWriteResponse containing the + * content of the request in case of success and an ErrorResponse in case of + * error. + * + * If an ErrorResponse is received it doesn't invalidate what is already in + * the queue. + * + * @note This function issue an ATT Prepare Write Request. + * + * @note: This function is one of the base function for the Write Long + * Characteristic Value and Reliable Writes GATT procedures. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part G Section 4.9.4 + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part G Section 4.9.5 + * + * @param connection_handle The handle of the connection to send this request to. + * @param attribute_handle Handle of the attribute to write. + * @param value The value to write. + * @param offset offset where the value should be written. + * + * @return BLE_ERROR_NONE or an appropriate error. + */ + virtual ble_error_t queue_prepare_write( + connection_handle_t connection_handle, + attribute_handle_t characteristic_value_handle, + const ArrayView& value, + uint16_t offset + ) = 0; + + /** + * Send a request to the server to execute the queue of prepared write + * requests. + * + * The server should respond with an ExecuteWriteResponse in case of success + * and an Error response in case of failure. + * + * @note This function issue an ATT Execute Write Request. + * + * @note: This function is one of the base function for the Write Long + * Characteristic Value and Reliable Writes GATT procedures. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part G Section 4.9.4 + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part G Section 4.9.5 + * + * @param connection_handle The handle of the connection to send this request to. + * @param execute If true, execute the write request queue otherwise cancel it. + * + * @return BLE_ERROR_NONE or an appropriate error. + */ + virtual ble_error_t execute_write_queue( + connection_handle_t connection_handle, + bool execute + ) = 0; + + /** + * Register a callback which will handle messages from the server. + * + * @param cb The callback object which will handle messages from the server. + * It accept two parameters in input: The handle of the connection where the + * message was received and the message received. Real type of the message + * can be obtained from its opcode. + */ + void when_server_message_received( + mbed::Callback cb + ) { + _server_message_cb = cb; + } + + /** + * Register a callback handling transaction timeout. + * + * @param cb The callback handling timeout of a transaction. It accepts as + * a parameter the connection handle involved in the timeout. + * + * @note No more attribute protocol requests, commands, indication or + * notification shall be sent over a connection implied in a transaction + * timeout. To send a new ATT message, the conenction should be + * reestablished. + */ + void when_transaction_timeout( + mbed::Callback cb + ) { + _transaction_timeout_cb = cb; + } + +protected: + GattClient() { } + + virtual ~GattClient() { } + + /** + * Upon server message reception an implementation shall call this function. + * + * @param connection_handle The handle of the connection which has received + * the server message. + * @param server_message The message received from the server. + */ + void on_server_event( + connection_handle_t connection_handle, + const AttServerMessage& server_message + ) { + if (_server_message_cb) { + _server_message_cb(connection_handle, server_message); + } + } + + /** + * Upon transaction timeout an implementation shall call this function. + * + * @param connection_handle The handle of the connection of the transaction + * which has times out. + * + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part F Section 3.3.3 + * @note see BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part G Section 4.4.14 + */ + void on_transaction_timeout( + connection_handle_t connection_handle + ) { + if (_transaction_timeout_cb) { + _transaction_timeout_cb(connection_handle); + } + } + +private: + /** + * Callback called when the client receive a message from the server. + */ + mbed::Callback _server_message_cb; + + /** + * Callback called when a transaction times out. + */ + mbed::Callback _transaction_timeout_cb; + + // Disallow copy construction and copy assignment. + GattClient(const GattClient&); + GattClient& operator=(const GattClient&); +}; + +} // namespace pal +} // namespace ble + +#endif /* BLE_PAL_GATT_CLIENT_H_ */ diff --git a/features/FEATURE_BLE/ble/pal/SimpleAttServerMessage.h b/features/FEATURE_BLE/ble/pal/SimpleAttServerMessage.h new file mode 100644 index 0000000000..a0f3346dea --- /dev/null +++ b/features/FEATURE_BLE/ble/pal/SimpleAttServerMessage.h @@ -0,0 +1,232 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017-2017 ARM Limited + * + * 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. + */ + +#ifndef BLE_PAL_SIMPLEATTSERVERMESSAGE_H_ +#define BLE_PAL_SIMPLEATTSERVERMESSAGE_H_ + +#include "AttServerMessage.h" + +namespace ble { +namespace pal { + +/** + * Simple implementation of ble::pal::AttFindInformationResponse. + * It should fit for any vendor stack exposing a proper ATT interface. + */ +struct SimpleAttFindInformationResponse : public AttFindInformationResponse { + /** + * Format of the UUID in the response. + */ + enum Format { + FORMAT_16_BIT_UUID = 0x01,//!< FORMAT_16_BIT_UUID + FORMAT_128_BIT_UUID = 0x02//!< FORMAT_128_BIT_UUID + }; + + /** + * Construct an AttFindInformationResponse from format and an array of bytes + * containing the information data. + * @param format The format of the information data. + * @param information_data The information data whose format is determined + * by the Format field + */ + SimpleAttFindInformationResponse( + Format format, ArrayView information_data + ) : AttFindInformationResponse(), + _format(format), _information_data(information_data), + _item_size(information_data_item_size()) { + } + + /** + * @see ble::pal::AttFindInformationResponse::size + */ + virtual size_t size() const { + return _information_data.size() / _item_size; + } + + /** + * @see ble::pal::AttFindInformationResponse::operator[] + */ + virtual information_data_t operator[](size_t index) const { + const uint8_t* item = &_information_data[index * _item_size]; + + information_data_t result; + memcpy(&result.handle, item, sizeof(result.handle)); + + if (_format == FORMAT_16_BIT_UUID) { + uint16_t short_uuid = 0; + memcpy(&short_uuid, item + 2, sizeof(short_uuid)); + result.uuid = UUID(short_uuid); + } else { + result.uuid = UUID(item + 2, UUID::LSB); + } + + return result; + } + +private: + size_t information_data_item_size() const { + return sizeof(attribute_handle_t) + ((_format == FORMAT_16_BIT_UUID) ? 2 : 16); + } + + const Format _format; + const ArrayView _information_data; + const size_t _item_size; +}; + + +/** + * Simple implementation of ble::pal::AttFindByTypeValueResponse. + * It should fit for any vendor stack exposing a proper ATT interface. + */ +struct SimpleAttFindByTypeValueResponse : public AttFindByTypeValueResponse { + /** + * Construct a AttFindByTypeValueResponse from a raw array containing the + * Handle Informations. + * @param handles raw array containing one or more Handle Informations. + */ + SimpleAttFindByTypeValueResponse(ArrayView handles) : + AttFindByTypeValueResponse(), _handles(handles) { + } + + /** + * @see ble::pal::AttFindByTypeValueResponse::size + */ + virtual std::size_t size() const { + return _handles.size() / item_size; + } + + /** + * @see ble::pal::AttFindByTypeValueResponse::operator[] + */ + virtual attribute_handle_range_t operator[](size_t index) const { + attribute_handle_range_t result; + const uint8_t* item = &_handles[index * item_size]; + memcpy(&result.begin, item, sizeof(result.begin)); + memcpy(&result.end, item + sizeof(result.begin), sizeof(result.end)); + return result; + } + +private: + static const size_t item_size = 4; + const ArrayView _handles; +}; + + +/** + * Simple implementation of ble::pal::AttReadByTypeResponse. + * It should fit for any vendor stack exposing a proper ATT interface. + */ +struct SimpleAttReadByTypeResponse : public AttReadByTypeResponse { + /** + * Construct an AttReadByTypeResponse from the size of each attribute + * handle-value pair and a list of attribute data. + * @param element_size The size of each attribute-handle pair. + * @param attribute_data Raw bytes array containing the list of attribute + * data. + */ + SimpleAttReadByTypeResponse( + uint8_t element_size, ArrayView attribute_data + ) : AttReadByTypeResponse(), + _attribute_data(attribute_data), _element_size(element_size) { + } + + /** + * @see ble::pal::AttReadByTypeResponse::size + */ + virtual size_t size() const { + return _attribute_data.size() / _element_size; + } + + /** + * @see ble::pal::AttReadByTypeResponse::operator[] + */ + virtual attribute_data_t operator[](size_t index) const { + const uint8_t* item = &_attribute_data[index * _element_size]; + uint16_t handle; + memcpy(&handle, item, sizeof(handle)); + + attribute_data_t result = { + handle, + ArrayView( + item + sizeof(handle), + _element_size - sizeof(handle) + ) + }; + + return result; + } + +private: + ArrayView _attribute_data; + uint8_t _element_size; +}; + + +/** + * Simple implementation of ble::pal::AttReadByGroupTypeResponse. + * It should fit for any vendor stack exposing a proper ATT interface. + */ +struct SimpleAttReadByGroupTypeResponse : public AttReadByGroupTypeResponse { + /** + * Construct an instance of AttReadByGroupTypeResponse from the size of each + * attribute data and a byte array containing the list of attribute data. + * @param element_size The size of each Attribute Data + * @param attribute_data Byte array containing the list of Attribute Data. + */ + SimpleAttReadByGroupTypeResponse( + uint8_t element_size, ArrayView attribute_data + ) : AttReadByGroupTypeResponse(), + _attribute_data(attribute_data), _element_size(element_size) { + } + + /** + * @see ble::pal::AttReadByGroupTypeResponse::size + */ + virtual size_t size() const { + return _attribute_data.size() / _element_size; + } + + /** + * @see ble::pal::AttReadByGroupTypeResponse::operator[] + */ + virtual attribute_data_t operator[](size_t index) const { + const uint8_t* item = &_attribute_data[index * _element_size]; + uint16_t begin; + uint16_t end; + + memcpy(&begin, item, sizeof(begin)); + memcpy(&end, item + sizeof(begin), sizeof(end)); + + attribute_data_t result = { + { begin, end }, + ArrayView( + item + sizeof(begin) + sizeof(end), + _element_size - (sizeof(begin) + sizeof(end)) + ) + }; + + return result; + } + +private: + ArrayView _attribute_data; + uint8_t _element_size; +}; + +} // namespace pal +} // namespace ble + +#endif /* BLE_PAL_SIMPLEATTSERVERMESSAGE_H_ */ diff --git a/features/FEATURE_BLE/source/generic/GenericGattClient.cpp b/features/FEATURE_BLE/source/generic/GenericGattClient.cpp new file mode 100644 index 0000000000..595babcccc --- /dev/null +++ b/features/FEATURE_BLE/source/generic/GenericGattClient.cpp @@ -0,0 +1,1342 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017-2017 ARM Limited + * + * 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 +#include + +#include +#include +#include "ble/generic/GenericGattClient.h" +#include "ble/blecommon.h" +#include + +using ble::pal::AttServerMessage; +using ble::pal::AttReadResponse; +using ble::pal::AttReadBlobResponse; +using ble::pal::AttReadByTypeResponse; +using ble::pal::AttReadByGroupTypeResponse; +using ble::pal::AttFindByTypeValueResponse; +using ble::pal::AttErrorResponse; +using ble::pal::AttributeOpcode; +using ble::pal::AttWriteResponse; +using ble::pal::AttPrepareWriteResponse; +using ble::pal::AttExecuteWriteResponse; +using ble::pal::AttHandleValueIndication; +using ble::pal::AttHandleValueNotification; +using ble::pal::AttFindInformationResponse; + +namespace ble { +namespace generic { + +/* + * Type of procedures which can be launched by the client. + */ +enum procedure_type_t { + COMPLETE_DISCOVERY_PROCEDURE, + READ_PROCEDURE, + WRITE_PROCEDURE, + DESCRIPTOR_DISCOVERY_PROCEDURE +}; + + +/* + * Base class for a procedure control block + */ +struct procedure_control_block_t { + /* + * Base constructor for procedure control block. + */ + procedure_control_block_t(procedure_type_t type, Gap::Handle_t handle) : + type(type), connection_handle(handle), next(NULL) { } + + virtual ~procedure_control_block_t() { } + + /* + * Entry point of the control block stack machine. + */ + virtual void handle(GenericGattClient* client, const AttServerMessage& message) = 0; + + /* + * Function call in case of timeout + */ + virtual void handle_timeout_error(GenericGattClient* client) = 0; + + procedure_type_t type; + Gap::Handle_t connection_handle; + procedure_control_block_t* next; +}; + + +/* + * Procedure control block for the discovery process. + */ +struct discovery_control_block_t : public procedure_control_block_t { + discovery_control_block_t( + Gap::Handle_t handle, + ServiceDiscovery::ServiceCallback_t service_callback, + ServiceDiscovery::CharacteristicCallback_t characteristic_callback, + UUID matching_service_uuid, + UUID matching_characteristic_uuid + ) : procedure_control_block_t(COMPLETE_DISCOVERY_PROCEDURE, handle), + service_callback(service_callback), + characteristic_callback(characteristic_callback), + matching_service_uuid(matching_service_uuid), + matching_characteristic_uuid(matching_characteristic_uuid), + services_discovered(NULL), + done(false) { + } + + virtual ~discovery_control_block_t() { + while(services_discovered) { + service_t* tmp = services_discovered->next; + delete services_discovered; + services_discovered = tmp; + } + } + + virtual void handle_timeout_error(GenericGattClient* client) { + terminate(client); + } + + virtual void handle(GenericGattClient* client, const AttServerMessage& message) { + // if end of discovery has been requested, ends it immediately + if (done) { + terminate(client); + return; + } + + switch(message.opcode) { + case AttributeOpcode::READ_BY_GROUP_TYPE_RESPONSE: + handle_service_discovered( + client, static_cast(message) + ); + break; + case AttributeOpcode::FIND_BY_VALUE_TYPE_RESPONSE: + handle_service_discovered( + client, static_cast(message) + ); + break; + case AttributeOpcode::READ_BY_TYPE_RESPONSE: + handle_characteristic_discovered( + client, static_cast(message) + ); + break; + case AttributeOpcode::ERROR_RESPONSE: { + const AttErrorResponse& error = static_cast(message); + if (error.error_code != AttErrorResponse::ATTRIBUTE_NOT_FOUND) { + terminate(client); + } + + switch (error.request_opcode) { + case AttributeOpcode::READ_BY_GROUP_TYPE_REQUEST: + case AttributeOpcode::FIND_BY_TYPE_VALUE_REQUEST: + start_characteristic_discovery(client); + break; + case AttributeOpcode::READ_BY_TYPE_REQUEST: + handle_all_characteristics_discovered(client); + break; + default: + // error + break; + } + } break; + default: + // error + break; + } + } + + template + void handle_service_discovered(GenericGattClient* client, const Response& response) { + if (!response.size()) { + terminate(client); + return; + } + + uint16_t end_handle = 0x0000; + for (size_t i = 0; i < response.size(); ++i) { + uint16_t start_handle = get_start_handle(response[i]); + end_handle = get_end_handle(response[i]); + UUID uuid = get_uuid(response[i]); + + if (!characteristic_callback) { + DiscoveredService discovered_service; + discovered_service.setup(uuid, start_handle, end_handle); + service_callback(&discovered_service); + } else { + service_t* discovered_service = new (std::nothrow) service_t( + start_handle, end_handle, uuid + ); + + if (discovered_service == NULL) { + terminate(client); + return; + } + + insert_service(discovered_service); + } + } + + if (end_handle == 0xFFFF) { + start_characteristic_discovery(client); + } else { + ble_error_t err = client->_pal_client->discover_primary_service( + connection_handle, end_handle + 1 + ); + + if (err) { + terminate(client); + } + } + } + + void start_characteristic_discovery(GenericGattClient* client) { + if (!services_discovered) { + terminate(client); + return; + } + + if (!characteristic_callback) { + terminate(client); + return; + } + + if (service_callback) { + DiscoveredService discovered_service; + discovered_service.setup( + services_discovered->uuid, + services_discovered->begin, + services_discovered->end + ); + service_callback(&discovered_service); + } + + last_characteristic = characteristic_t(); + client->_pal_client->discover_characteristics_of_a_service( + connection_handle, + attribute_handle_range( + services_discovered->begin, + services_discovered->end + ) + ); + } + + void handle_characteristic_discovered(GenericGattClient* client, const AttReadByTypeResponse& response) { + for (size_t i = 0; i < response.size(); ++i) { + if (last_characteristic.is_valid() == false) { + last_characteristic.set_last_handle(response[i].handle - 1); + if (matching_characteristic_uuid == UUID() + || last_characteristic.getUUID() == matching_characteristic_uuid) { + characteristic_callback(&last_characteristic); + } + } + + last_characteristic = characteristic_t( + client, connection_handle, response[i].handle, response[i].value + ); + } + + // check if all the characteristics of the service has been discovered + if (last_characteristic.getValueHandle() == services_discovered->end) { + handle_all_characteristics_discovered(client); + } else { + ble_error_t err = client->_pal_client->discover_characteristics_of_a_service( + connection_handle, + attribute_handle_range( + last_characteristic.getValueHandle() + 1, + services_discovered->end + ) + ); + + if (err) { + terminate(client); + } + } + } + + void handle_all_characteristics_discovered(GenericGattClient* client) { + if (last_characteristic.is_valid() == false) { + if (matching_characteristic_uuid == UUID() + || matching_characteristic_uuid == last_characteristic.getUUID()) { + last_characteristic.set_last_handle(services_discovered->end); + characteristic_callback(&last_characteristic); + } + } + + service_t* old = services_discovered; + services_discovered = services_discovered->next; + delete old; + + if (!services_discovered) { + terminate(client); + } else { + start_characteristic_discovery(client); + } + } + + void terminate(GenericGattClient* client) { + // unknown error, terminate the procedure immediately + client->remove_control_block(this); + Gap::Handle_t handle = connection_handle; + delete this; + client->on_termination(handle); + } + + uint16_t get_start_handle(const AttReadByGroupTypeResponse::attribute_data_t& data) { + return data.group_range.begin; + } + + uint16_t get_start_handle(const attribute_handle_range_t& range) { + return range.begin; + } + + uint16_t get_end_handle(const AttReadByGroupTypeResponse::attribute_data_t& data) { + return data.group_range.end; + } + + uint16_t get_end_handle(const attribute_handle_range_t& range) { + return range.end; + } + + UUID get_uuid(const AttReadByGroupTypeResponse::attribute_data_t& data) { + if (data.value.size() == 2) { + return UUID(data.value[0] | data.value[1] << 8); + } else { + return UUID(data.value.data(), UUID::LSB); + } + } + + UUID get_uuid(const attribute_handle_range_t& range) { + return matching_service_uuid; + } + + struct service_t { + service_t(uint16_t begin, uint16_t end, const UUID& uuid) : + begin(begin), end(end), uuid(uuid), next(NULL) { } + uint16_t begin; + uint16_t end; + UUID uuid; + service_t* next; + }; + + struct characteristic_t : DiscoveredCharacteristic { + characteristic_t() : DiscoveredCharacteristic() { + lastHandle = 0x0001; + } + + characteristic_t( + GattClient* client, + Gap::Handle_t connection_handle, + uint16_t decl_handle, + const ArrayView value + ) : DiscoveredCharacteristic() { + gattc = client; + uuid = get_uuid(value); + props = get_properties(value); + declHandle = decl_handle; + valueHandle = get_value_handle(value); + lastHandle = 0x0000; + connHandle = connection_handle; + } + + static UUID get_uuid(const ArrayView& value) { + if (value.size() == 5) { + return UUID(value[3] | (value[4] << 8)); + } else { + return UUID(value.data() + 3, UUID::LSB); + } + } + + static DiscoveredCharacteristic::Properties_t get_properties(const ArrayView& value) { + uint8_t raw_properties = value[0]; + DiscoveredCharacteristic::Properties_t result; + result._broadcast = (raw_properties & (1 << 0)) ? true : false; + result._read = (raw_properties & (1 << 1)) ? true : false; + result._writeWoResp = (raw_properties & (1 << 2)) ? true : false; + result._write = (raw_properties & (1 << 3)) ? true : false; + result._notify = (raw_properties & (1 << 4)) ? true : false; + result._indicate = (raw_properties & (1 << 5)) ? true : false; + result._authSignedWrite = (raw_properties & (1 << 6)) ? true : false; + return result; + } + + static uint16_t get_value_handle(const ArrayView& value) { + return value[1] | (value[2] << 8); + } + + void set_last_handle(uint16_t last_handle) { + lastHandle = last_handle; + } + + bool is_valid() const { + return lastHandle != 0x0000; + } + }; + + void insert_service(service_t* service) { + if (services_discovered == NULL) { + services_discovered = service; + return; + } + + service_t* current = services_discovered; + while (current->next) { + current = current->next; + } + current->next = service; + } + + ServiceDiscovery::ServiceCallback_t service_callback; + ServiceDiscovery::CharacteristicCallback_t characteristic_callback; + UUID matching_service_uuid; + UUID matching_characteristic_uuid; + service_t* services_discovered; + characteristic_t last_characteristic; + bool done; +}; + + +struct read_control_block_t : public procedure_control_block_t { + read_control_block_t( + Gap::Handle_t connection_handle, uint16_t attribute_handle, uint16_t offset + ) : procedure_control_block_t(READ_PROCEDURE, connection_handle), + attribute_handle(attribute_handle), + offset(offset), current_offset(offset), data(NULL) { + } + + virtual ~read_control_block_t() { + if (data != NULL) { + free(data); + } + } + + virtual void handle_timeout_error(GenericGattClient* client) { + GattReadCallbackParams response = { + connection_handle, + attribute_handle, + offset, + 0, // size of 0 + NULL, // no data + BLE_ERROR_UNSPECIFIED, + + }; + terminate(client, response); + } + + void terminate(GenericGattClient* client, const GattReadCallbackParams& response) { + client->remove_control_block(this); + client->processReadResponse(&response); + delete this; + } + + virtual void handle(GenericGattClient* client, const AttServerMessage& message) { + switch(message.opcode) { + case AttributeOpcode::ERROR_RESPONSE: + handle_error(client, static_cast(message)); + break; + + case AttributeOpcode::READ_RESPONSE: + handle_read_response(client, static_cast(message)); + break; + + case AttributeOpcode::READ_BLOB_RESPONSE: + handle_read_response(client, static_cast(message)); + break; + + default: { + // should not happen, terminate the procedure and notify client with an error + // in such case + GattReadCallbackParams response = { + connection_handle, + attribute_handle, + AttErrorResponse::UNLIKELY_ERROR, + 0, // size of 0 + NULL, // no data + BLE_ERROR_UNSPECIFIED, + + }; + terminate(client, response); + } break; + } + } + + template + void handle_read_response(GenericGattClient* client, const ResponseType& read_response) { + uint16_t mtu_size = client->get_mtu(connection_handle); + + // end of responses ? + if ((uint16_t) read_response.size() < (mtu_size - 1)) { + GattReadCallbackParams response = { + connection_handle, + attribute_handle, + offset, + 0, // size of 0 + NULL, // no data + BLE_ERROR_NONE, + }; + + // is it the first response, or is there any other response already + // in the object ? + if (data == NULL) { + response.len = (uint16_t) read_response.size(); + response.data = read_response.data(); + } else { + // copy the data in the existing buffer + memcpy(data + (current_offset - offset), read_response.data(), read_response.size()); + response.len = (current_offset + read_response.size()) - offset; + response.data = data; + } + terminate(client, response); + } else { + // allocation which will contain the response data plus the next one. + data = (uint8_t*) realloc(data, (current_offset - offset) + ((mtu_size - 1) * 2)); + if (data == NULL) { + GattReadCallbackParams response = { + connection_handle, + attribute_handle, + offset, + AttErrorResponse::INSUFFICIENT_RESOURCES, + NULL, + BLE_ERROR_NO_MEM, + }; + terminate(client, response); + return; + } + + memcpy(data + (current_offset - offset), read_response.data(), read_response.size()); + current_offset = current_offset + read_response.size(); + ble_error_t err = client->_pal_client->read_attribute_blob( + connection_handle, + attribute_handle, + current_offset + ); + + if (err) { + GattReadCallbackParams response = { + connection_handle, + attribute_handle, + AttErrorResponse::UNLIKELY_ERROR, + 0, // size of 0 + NULL, // no data + BLE_ERROR_UNSPECIFIED, + + }; + terminate(client, response); + } + } + } + + void handle_error(GenericGattClient* client, const AttErrorResponse& error) { + ble_error_t status = BLE_ERROR_UNSPECIFIED; + + switch (error.error_code) { + case AttErrorResponse::INVALID_HANDLE: + status = BLE_ERROR_INVALID_PARAM; + break; + case AttErrorResponse::INSUFFICIENT_AUTHORIZATION: + status = BLE_ERROR_INVALID_STATE; + break; + case AttErrorResponse::INSUFFICIENT_AUTHENTICATION: + status = BLE_ERROR_INVALID_STATE; + break; + case AttErrorResponse::INSUFFICIENT_ENCRYPTION_KEY_SIZE: + status = BLE_ERROR_INVALID_STATE; + break; + case AttErrorResponse::INSUFFICIENT_ENCRYPTION: + status = BLE_ERROR_INVALID_STATE; + break; + case AttErrorResponse::READ_NOT_PERMITTED: + status = BLE_ERROR_OPERATION_NOT_PERMITTED; + break; + case AttErrorResponse::INVALID_OFFSET: + status = BLE_ERROR_PARAM_OUT_OF_RANGE; + break; + case AttErrorResponse::ATTRIBUTE_NOT_LONG: + status = BLE_ERROR_PARAM_OUT_OF_RANGE; + break; + default: + status = BLE_ERROR_UNSPECIFIED; + break; + } + + GattReadCallbackParams response = { + connection_handle, + attribute_handle, + offset, + error.error_code, + /* data */ NULL, + status + }; + + terminate(client, response); + } + + uint16_t attribute_handle; + uint16_t offset; + uint16_t current_offset; + uint8_t* data; +}; + +/* + * Control block for the write process + */ +struct write_control_block_t : public procedure_control_block_t { + write_control_block_t( + Gap::Handle_t connection_handle, uint16_t attribute_handle, + uint8_t* data, uint16_t len + ) : procedure_control_block_t(WRITE_PROCEDURE, connection_handle), + attribute_handle(attribute_handle), len(len), offset(0), data(data), + prepare_success(false), status(BLE_ERROR_UNSPECIFIED), error_code(0xFF) { + } + + virtual ~write_control_block_t() { + free(data); + } + + virtual void handle_timeout_error(GenericGattClient* client) { + GattWriteCallbackParams response = { + connection_handle, + attribute_handle, + GattWriteCallbackParams::OP_WRITE_REQ, + BLE_ERROR_UNSPECIFIED, + 0x00 + }; + terminate(client, response); + } + + void terminate(GenericGattClient* client, const GattWriteCallbackParams& response) { + client->remove_control_block(this); + client->processWriteResponse(&response); + delete this; + } + + virtual void handle(GenericGattClient* client, const AttServerMessage& message) { + switch(message.opcode) { + case AttributeOpcode::ERROR_RESPONSE: + handle_error(client, static_cast(message)); + break; + + case AttributeOpcode::WRITE_RESPONSE: + handle_write_response(client, static_cast(message)); + break; + + case AttributeOpcode::PREPARE_WRITE_RESPONSE: + handle_prepare_write_response(client, static_cast(message)); + break; + + case AttributeOpcode::EXECUTE_WRITE_RESPONSE: + handle_execute_write_response(client, static_cast(message)); + break; + + default: { + GattWriteCallbackParams response = { + connection_handle, + attribute_handle, + GattWriteCallbackParams::OP_WRITE_REQ, + BLE_ERROR_UNSPECIFIED, + AttErrorResponse::UNLIKELY_ERROR + }; + + terminate(client, response); + } break; + } + } + + void handle_write_response(GenericGattClient* client, const AttWriteResponse& write_response) { + GattWriteCallbackParams response = { + connection_handle, attribute_handle, + GattWriteCallbackParams::OP_WRITE_REQ, + BLE_ERROR_NONE, 0x00 + }; + + terminate(client, response); + } + + void handle_prepare_write_response(GenericGattClient* client, const AttPrepareWriteResponse& write_response) { + ble_error_t err = BLE_ERROR_UNSPECIFIED; + + uint16_t mtu_size = client->get_mtu(connection_handle); + offset = write_response.offset + write_response.partial_value.size(); + if (offset < len) { + err = client->_pal_client->queue_prepare_write( + connection_handle, attribute_handle, + make_const_ArrayView( + data + offset, + std::min((len - offset), (mtu_size - 5)) + ), + offset + ); + } else { + err = client->_pal_client->execute_write_queue( + connection_handle, true + ); + } + + if (err) { + clear_prepare_queue(client, err, AttErrorResponse::UNLIKELY_ERROR); + } + } + + void handle_execute_write_response(GenericGattClient* client, const AttExecuteWriteResponse& execute_response) { + if (prepare_success) { + status = BLE_ERROR_NONE; + error_code = 0x00; + } + + GattWriteCallbackParams response = { + connection_handle, + attribute_handle, + GattWriteCallbackParams::OP_WRITE_REQ, + status, + error_code + }; + + terminate(client, response); + } + + void clear_prepare_queue(GenericGattClient* client, ble_error_t s, uint8_t e) { + prepare_success = false; + status = s; + error_code = e; + ble_error_t err = client->_pal_client->execute_write_queue( + connection_handle, false + ); + + if (err) { + GattWriteCallbackParams response = { + connection_handle, + attribute_handle, + GattWriteCallbackParams::OP_WRITE_REQ, + err, + AttErrorResponse::UNLIKELY_ERROR + }; + + terminate(client, response); + } + } + + void handle_error(GenericGattClient* client, const AttErrorResponse& error) { + ble_error_t status = BLE_ERROR_UNSPECIFIED; + + switch (error.error_code) { + case AttErrorResponse::INVALID_HANDLE: + status = BLE_ERROR_INVALID_PARAM; + break; + case AttErrorResponse::INVALID_ATTRIBUTE_VALUE_LENGTH: + status = BLE_ERROR_INVALID_PARAM; + break; + case AttErrorResponse::INSUFFICIENT_AUTHORIZATION: + status = BLE_ERROR_INVALID_STATE; + break; + case AttErrorResponse::INSUFFICIENT_AUTHENTICATION: + status = BLE_ERROR_INVALID_STATE; + break; + case AttErrorResponse::INSUFFICIENT_ENCRYPTION_KEY_SIZE: + status = BLE_ERROR_INVALID_STATE; + break; + case AttErrorResponse::INSUFFICIENT_ENCRYPTION: + status = BLE_ERROR_INVALID_STATE; + break; + case AttErrorResponse::WRITE_NOT_PERMITTED: + status = BLE_ERROR_OPERATION_NOT_PERMITTED; + break; + default: + status = BLE_ERROR_UNSPECIFIED; + break; + } + + if (error.request_opcode == AttributeOpcode(AttributeOpcode::PREPARE_WRITE_REQUEST)) { + clear_prepare_queue(client, status, error.error_code); + } else { + GattWriteCallbackParams response = { + connection_handle, + attribute_handle, + GattWriteCallbackParams::OP_WRITE_REQ, + status, + error.error_code + }; + + terminate(client, response); + } + } + + uint16_t attribute_handle; + uint16_t len; + uint16_t offset; + uint8_t* data; + bool prepare_success; + ble_error_t status; + uint8_t error_code; +}; + +/* + * Control block for the descriptor discovery process + */ +struct descriptor_discovery_control_block_t : public procedure_control_block_t { + descriptor_discovery_control_block_t( + const DiscoveredCharacteristic& characteristic, + const CharacteristicDescriptorDiscovery::DiscoveryCallback_t& discoveryCallback, + const CharacteristicDescriptorDiscovery::TerminationCallback_t& terminationCallback + ) : procedure_control_block_t(DESCRIPTOR_DISCOVERY_PROCEDURE, characteristic.getConnectionHandle()), + characteristic(characteristic), + discovery_cb(discoveryCallback), + termination_cb(terminationCallback), + next_handle(characteristic.getValueHandle() + 1), + done(false) { + } + + virtual ~descriptor_discovery_control_block_t() { } + + ble_error_t start(GenericGattClient* client) { + return client->_pal_client->discover_characteristics_descriptors( + connection_handle, + attribute_handle_range( + next_handle, + characteristic.getLastHandle() + ) + ); + } + + virtual void handle_timeout_error(GenericGattClient* client) { + terminate(client, BLE_ERROR_UNSPECIFIED); + } + + virtual void handle(GenericGattClient* client, const AttServerMessage& message) { + if (done) { + terminate(client, BLE_ERROR_NONE); + return; + } + + switch(message.opcode) { + case AttributeOpcode::ERROR_RESPONSE: + handle_error(client, static_cast(message)); + return; + + case AttributeOpcode::FIND_INFORMATION_RESPONSE: + handle_response(client, static_cast(message)); + return; + + default: + break; + } + } + + void handle_error(GenericGattClient* client, const AttErrorResponse& error) { + if (error.error_code == AttErrorResponse::ATTRIBUTE_NOT_FOUND) { + terminate(client, BLE_ERROR_NONE); + } else { + terminate(client, BLE_ERROR_UNSPECIFIED, error.error_code); + } + } + + void handle_response(GenericGattClient* client, const AttFindInformationResponse& response) { + for (size_t i = 0; i < response.size(); ++i) { + DiscoveredCharacteristicDescriptor descriptor( + client, connection_handle, response[i].handle, response[i].uuid + ); + CharacteristicDescriptorDiscovery::DiscoveryCallbackParams_t params = { + characteristic, + descriptor + }; + discovery_cb(¶ms); + if (done) { + terminate(client, BLE_ERROR_NONE); + return; + } + + if (response[i].handle == characteristic.getLastHandle()) { + terminate(client, BLE_ERROR_NONE); + return; + } + next_handle = response[i].handle + 1; + } + + ble_error_t err = start(client); + if (err) { + terminate(client, err, AttErrorResponse::UNLIKELY_ERROR); + } + } + + void terminate(GenericGattClient* client, ble_error_t status, uint8_t error_code = 0x00) { + client->remove_control_block(this); + CharacteristicDescriptorDiscovery::TerminationCallbackParams_t params = { + characteristic, + status, + error_code + }; + termination_cb(¶ms); + delete this; + } + + DiscoveredCharacteristic characteristic; + CharacteristicDescriptorDiscovery::DiscoveryCallback_t discovery_cb; + CharacteristicDescriptorDiscovery::TerminationCallback_t termination_cb; + uint16_t next_handle; + bool done; +}; + + +GenericGattClient::GenericGattClient(pal::GattClient* pal_client) : + _pal_client(pal_client), + _termination_callback(), + control_blocks(NULL) { + _pal_client->when_server_message_received( + mbed::callback(this, &GenericGattClient::on_server_message_received) + ); + _pal_client->when_transaction_timeout( + mbed::callback(this, &GenericGattClient::on_transaction_timeout) + ); +} + +ble_error_t GenericGattClient::launchServiceDiscovery( + Gap::Handle_t connection_handle, + ServiceDiscovery::ServiceCallback_t service_callback, + ServiceDiscovery::CharacteristicCallback_t characteristic_callback, + const UUID& matching_service_uuid, + const UUID& matching_characteristic_uuid +) { + // verify that there is no other procedures going on this connection + if (get_control_block(connection_handle)) { + return BLE_ERROR_INVALID_STATE; + } + + // terminate and return if there is no callback to call + if (!service_callback && !characteristic_callback) { + on_termination(connection_handle); + return BLE_ERROR_NONE; + } + + discovery_control_block_t* discovery_pcb = new(std::nothrow) discovery_control_block_t( + connection_handle, + service_callback, + characteristic_callback, + matching_service_uuid, + matching_characteristic_uuid + ); + + if (discovery_pcb == NULL) { + return BLE_ERROR_NO_MEM; + } + + // note: control block inserted prior the request because they are part of + // of the transaction and the callback can be call synchronously + insert_control_block(discovery_pcb); + + // launch the request + ble_error_t err = BLE_ERROR_UNSPECIFIED; + if (matching_service_uuid == UUID()) { + err = _pal_client->discover_primary_service( + connection_handle, + 0x0001 + ); + } else { + err = _pal_client->discover_primary_service_by_service_uuid( + connection_handle, + 0x0001, + matching_service_uuid + ); + } + + if (err) { + remove_control_block(discovery_pcb); + delete discovery_pcb; + } + + return err; +} + +bool GenericGattClient::isServiceDiscoveryActive() const { + procedure_control_block_t* pcb = control_blocks; + + while (pcb) { + if (pcb->type == COMPLETE_DISCOVERY_PROCEDURE) { + return true; + } + pcb = pcb->next; + } + + return false; +} + +void GenericGattClient::terminateServiceDiscovery() +{ + procedure_control_block_t* pcb = control_blocks; + while (pcb) { + if (pcb->type == COMPLETE_DISCOVERY_PROCEDURE) { + static_cast(pcb)->done = true; + } + pcb = pcb->next; + } +} + +ble_error_t GenericGattClient::read( + Gap::Handle_t connection_handle, + GattAttribute::Handle_t attribute_handle, + uint16_t offset) const +{ + // verify that there is no other procedures going on this connection + if (get_control_block(connection_handle)) { + return BLE_ERROR_INVALID_STATE; + } + + read_control_block_t* read_pcb = new(std::nothrow) read_control_block_t( + connection_handle, + attribute_handle, + offset + ); + + if (read_pcb == NULL) { + return BLE_ERROR_NO_MEM; + } + + insert_control_block(read_pcb); + + ble_error_t err = BLE_ERROR_NONE; + + if (offset == 0) { + err = _pal_client->read_attribute_value( + connection_handle, attribute_handle + ); + } else { + err = _pal_client->read_attribute_blob( + connection_handle, attribute_handle, offset + ); + } + + if (err) { + remove_control_block(read_pcb); + delete read_pcb; + } + + return err; +} + +ble_error_t GenericGattClient::write( + GattClient::WriteOp_t cmd, + Gap::Handle_t connection_handle, + GattAttribute::Handle_t attribute_handle, + size_t length, + const uint8_t* value +) const { + // verify that there is no other procedures going on this connection + if (get_control_block(connection_handle)) { + return BLE_ERROR_INVALID_STATE; + } + + uint16_t mtu = get_mtu(connection_handle); + + if (cmd == GattClient::GATT_OP_WRITE_CMD) { + if (length > (uint16_t)(mtu - 3)) { + return BLE_ERROR_PARAM_OUT_OF_RANGE; + } + return _pal_client->write_without_response( + connection_handle, + attribute_handle, + make_const_ArrayView(value, length) + ); + } else { + uint8_t* data = NULL; + + if (length > (uint16_t)(mtu - 3)) { + data = (uint8_t*) malloc(length); + if (data == NULL) { + return BLE_ERROR_NO_MEM; + } + memcpy(data, value, length); + } + + write_control_block_t* write_pcb = new(std::nothrow) write_control_block_t( + connection_handle, + attribute_handle, + data, + length + ); + + if (write_pcb == NULL) { + free(data); + return BLE_ERROR_NO_MEM; + } + + insert_control_block(write_pcb); + + ble_error_t err = BLE_ERROR_UNSPECIFIED; + if (data) { + err = _pal_client->queue_prepare_write( + connection_handle, + attribute_handle, + make_const_ArrayView(value, mtu - 5), + /* offset */ 0 + ); + } else { + err = _pal_client->write_attribute( + connection_handle, + attribute_handle, + make_const_ArrayView(value, length) + ); + } + + if (err) { + remove_control_block(write_pcb); + delete write_pcb; + } + + return err; + } + + return BLE_ERROR_NOT_IMPLEMENTED; +} + +void GenericGattClient::onServiceDiscoveryTermination( + ServiceDiscovery::TerminationCallback_t callback +) { + _termination_callback = callback; +} + +ble_error_t GenericGattClient::discoverCharacteristicDescriptors( + const DiscoveredCharacteristic& characteristic, + const CharacteristicDescriptorDiscovery::DiscoveryCallback_t& discoveryCallback, + const CharacteristicDescriptorDiscovery::TerminationCallback_t& terminationCallback +) { + // verify that there is no other procedures going on this connection + if (get_control_block(characteristic.getConnectionHandle())) { + return BLE_ERROR_INVALID_STATE; + } + + if (characteristic.getValueHandle() == characteristic.getLastHandle()) { + CharacteristicDescriptorDiscovery::TerminationCallbackParams_t params = { + characteristic, + BLE_ERROR_NONE, + /* error code */ 0x00 + }; + + terminationCallback(¶ms); + return BLE_ERROR_NONE; + } + + descriptor_discovery_control_block_t* discovery_pcb = + new(std::nothrow) descriptor_discovery_control_block_t( + characteristic, + discoveryCallback, + terminationCallback + ); + + if (discovery_pcb == NULL) { + return BLE_ERROR_NO_MEM; + } + + insert_control_block(discovery_pcb); + + ble_error_t err = discovery_pcb->start(this); + + if (err) { + remove_control_block(discovery_pcb); + delete discovery_pcb; + } + + return err; +} + +bool GenericGattClient::isCharacteristicDescriptorDiscoveryActive( + const DiscoveredCharacteristic& characteristic +) const { + procedure_control_block_t* pcb = control_blocks; + + while (pcb) { + if (pcb->type == DESCRIPTOR_DISCOVERY_PROCEDURE && + static_cast(pcb)->characteristic == characteristic) { + return true; + } + pcb = pcb->next; + } + + return false; +} + +void GenericGattClient::terminateCharacteristicDescriptorDiscovery( + const DiscoveredCharacteristic& characteristic +) { + procedure_control_block_t* pcb = control_blocks; + + while (pcb) { + if (pcb->type == DESCRIPTOR_DISCOVERY_PROCEDURE) { + descriptor_discovery_control_block_t* dpcb = + static_cast(pcb); + if (dpcb->characteristic == characteristic) { + dpcb->done = true; + return; + } + } + + pcb = pcb->next; + } + +} + +ble_error_t GenericGattClient::reset(void) { + return BLE_ERROR_NOT_IMPLEMENTED; +} + +void GenericGattClient::on_termination(Gap::Handle_t connection_handle) { + if (_termination_callback) { + _termination_callback(connection_handle); + } +} + +void GenericGattClient::on_server_message_received( + connection_handle_t connection_handle, + const AttServerMessage& message +) { + switch(message.opcode) { + case AttributeOpcode::ERROR_RESPONSE: + case AttributeOpcode::EXCHANGE_MTU_RESPONSE: + case AttributeOpcode::FIND_INFORMATION_RESPONSE: + case AttributeOpcode::FIND_BY_VALUE_TYPE_RESPONSE: + case AttributeOpcode::READ_BY_TYPE_RESPONSE: + case AttributeOpcode::READ_RESPONSE: + case AttributeOpcode::READ_BLOB_RESPONSE: + case AttributeOpcode::READ_MULTIPLE_RESPONSE: + case AttributeOpcode::READ_BY_GROUP_TYPE_RESPONSE: + case AttributeOpcode::WRITE_RESPONSE: + case AttributeOpcode::PREPARE_WRITE_RESPONSE: + case AttributeOpcode::EXECUTE_WRITE_RESPONSE: { + on_server_response(connection_handle, message); + } break; + + case AttributeOpcode::HANDLE_VALUE_INDICATION: + case AttributeOpcode::HANDLE_VALUE_NOTIFICATION: { + on_server_event(connection_handle, message); + } break; + + default: + // invalid message receive + return; + } +} + +void GenericGattClient::on_server_response( + connection_handle_t connection, + const AttServerMessage& message +) { + procedure_control_block_t* pcb = get_control_block(connection); + if (pcb == NULL) { + return; + } + + pcb->handle(this, message); +} + +void GenericGattClient::on_server_event(connection_handle_t connection, const AttServerMessage& message) { + GattHVXCallbackParams callbacks_params = { + (Gap::Handle_t) connection, 0 + }; + + switch (message.opcode) { + case AttributeOpcode::HANDLE_VALUE_NOTIFICATION: { + const AttHandleValueNotification& notification = + static_cast(message); + callbacks_params.handle = notification.attribute_handle; + callbacks_params.type = BLE_HVX_NOTIFICATION; + callbacks_params.len = notification.attribute_value.size(); + callbacks_params.data = notification.attribute_value.data(); + } break; + + case AttributeOpcode::HANDLE_VALUE_INDICATION: { + const AttHandleValueIndication& indication = + static_cast(message); + callbacks_params.handle = indication.attribute_handle; + callbacks_params.type = BLE_HVX_INDICATION; + callbacks_params.len = indication.attribute_value.size(); + callbacks_params.data = indication.attribute_value.data(); + } break; + + default: + return; + } + + processHVXEvent(&callbacks_params); +} + +void GenericGattClient::on_transaction_timeout(connection_handle_t connection) { + procedure_control_block_t* pcb = get_control_block(connection); + if (pcb == NULL) { + return; + } + + pcb->handle_timeout_error(this); +} + +procedure_control_block_t* GenericGattClient::get_control_block(Gap::Handle_t connection) { + procedure_control_block_t* it = control_blocks; + while (it && it->connection_handle != connection) { + it = it->next; + } + return it; +} + +const procedure_control_block_t* GenericGattClient::get_control_block(Gap::Handle_t connection) const { + procedure_control_block_t* it = control_blocks; + while (it && it->connection_handle != connection) { + it = it->next; + } + return it; +} + +void GenericGattClient::insert_control_block(procedure_control_block_t* cb) const { + if (control_blocks == NULL) { + control_blocks = cb; + return; + } + + procedure_control_block_t* current = control_blocks; + while (current->next) { + current = current->next; + } + current->next = cb; +} + +void GenericGattClient::remove_control_block(procedure_control_block_t* cb) const { + if (control_blocks == NULL) { + return; + } + + if (cb == control_blocks) { + control_blocks = control_blocks->next; + return; + } + + procedure_control_block_t* current = control_blocks; + while (current->next && current->next != cb) { + current = current->next; + } + + if (current->next == NULL) { + return; + } + + current->next = cb->next; + cb->next = NULL; +} + +uint16_t GenericGattClient::get_mtu(Gap::Handle_t connection) const { + uint16_t result = 23; + if(_pal_client->get_mtu_size((connection_handle_t) connection, result) != BLE_ERROR_NONE) { + result = 23; + } + return result; +} + +} // namespace pal +} // namespace ble