1# Copyright 2021-2022 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15# ----------------------------------------------------------------------------- 16# GATT - Generic Attribute Profile 17# Server 18# 19# See Bluetooth spec @ Vol 3, Part G 20# 21# ----------------------------------------------------------------------------- 22 23# ----------------------------------------------------------------------------- 24# Imports 25# ----------------------------------------------------------------------------- 26import asyncio 27import logging 28from collections import defaultdict 29import struct 30from typing import List, Tuple, Optional 31from pyee import EventEmitter 32 33from .colors import color 34from .core import UUID 35from .att import ( 36 ATT_ATTRIBUTE_NOT_FOUND_ERROR, 37 ATT_ATTRIBUTE_NOT_LONG_ERROR, 38 ATT_CID, 39 ATT_DEFAULT_MTU, 40 ATT_INVALID_ATTRIBUTE_LENGTH_ERROR, 41 ATT_INVALID_HANDLE_ERROR, 42 ATT_INVALID_OFFSET_ERROR, 43 ATT_REQUEST_NOT_SUPPORTED_ERROR, 44 ATT_REQUESTS, 45 ATT_UNLIKELY_ERROR_ERROR, 46 ATT_UNSUPPORTED_GROUP_TYPE_ERROR, 47 ATT_Error, 48 ATT_Error_Response, 49 ATT_Exchange_MTU_Response, 50 ATT_Find_By_Type_Value_Response, 51 ATT_Find_Information_Response, 52 ATT_Handle_Value_Indication, 53 ATT_Handle_Value_Notification, 54 ATT_Read_Blob_Response, 55 ATT_Read_By_Group_Type_Response, 56 ATT_Read_By_Type_Response, 57 ATT_Read_Response, 58 ATT_Write_Response, 59 Attribute, 60) 61from .gatt import ( 62 GATT_CHARACTERISTIC_ATTRIBUTE_TYPE, 63 GATT_CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR, 64 GATT_MAX_ATTRIBUTE_VALUE_SIZE, 65 GATT_PRIMARY_SERVICE_ATTRIBUTE_TYPE, 66 GATT_REQUEST_TIMEOUT, 67 GATT_SECONDARY_SERVICE_ATTRIBUTE_TYPE, 68 Characteristic, 69 CharacteristicDeclaration, 70 CharacteristicValue, 71 Descriptor, 72 Service, 73) 74 75 76# ----------------------------------------------------------------------------- 77# Logging 78# ----------------------------------------------------------------------------- 79logger = logging.getLogger(__name__) 80 81 82# ----------------------------------------------------------------------------- 83# Constants 84# ----------------------------------------------------------------------------- 85GATT_SERVER_DEFAULT_MAX_MTU = 517 86 87 88# ----------------------------------------------------------------------------- 89# GATT Server 90# ----------------------------------------------------------------------------- 91class Server(EventEmitter): 92 attributes: List[Attribute] 93 94 def __init__(self, device): 95 super().__init__() 96 self.device = device 97 self.attributes = [] # Attributes, ordered by increasing handle values 98 self.attributes_by_handle = {} # Map for fast attribute access by handle 99 self.max_mtu = ( 100 GATT_SERVER_DEFAULT_MAX_MTU # The max MTU we're willing to negotiate 101 ) 102 self.subscribers = ( 103 {} 104 ) # Map of subscriber states by connection handle and attribute handle 105 self.indication_semaphores = defaultdict(lambda: asyncio.Semaphore(1)) 106 self.pending_confirmations = defaultdict(lambda: None) 107 108 def __str__(self): 109 return "\n".join(map(str, self.attributes)) 110 111 def send_gatt_pdu(self, connection_handle, pdu): 112 self.device.send_l2cap_pdu(connection_handle, ATT_CID, pdu) 113 114 def next_handle(self): 115 return 1 + len(self.attributes) 116 117 def get_advertising_service_data(self): 118 return { 119 attribute: data 120 for attribute in self.attributes 121 if isinstance(attribute, Service) 122 and (data := attribute.get_advertising_data()) 123 } 124 125 def get_attribute(self, handle): 126 attribute = self.attributes_by_handle.get(handle) 127 if attribute: 128 return attribute 129 130 # Not in the cached map, perform a linear lookup 131 for attribute in self.attributes: 132 if attribute.handle == handle: 133 # Store in cached map 134 self.attributes_by_handle[handle] = attribute 135 return attribute 136 return None 137 138 def get_service_attribute(self, service_uuid: UUID) -> Optional[Service]: 139 return next( 140 ( 141 attribute 142 for attribute in self.attributes 143 if attribute.type == GATT_PRIMARY_SERVICE_ATTRIBUTE_TYPE 144 and isinstance(attribute, Service) 145 and attribute.uuid == service_uuid 146 ), 147 None, 148 ) 149 150 def get_characteristic_attributes( 151 self, service_uuid: UUID, characteristic_uuid: UUID 152 ) -> Optional[Tuple[CharacteristicDeclaration, Characteristic]]: 153 service_handle = self.get_service_attribute(service_uuid) 154 if not service_handle: 155 return None 156 157 return next( 158 ( 159 (attribute, self.get_attribute(attribute.characteristic.handle)) 160 for attribute in map( 161 self.get_attribute, 162 range(service_handle.handle, service_handle.end_group_handle + 1), 163 ) 164 if attribute.type == GATT_CHARACTERISTIC_ATTRIBUTE_TYPE 165 and attribute.characteristic.uuid == characteristic_uuid 166 ), 167 None, 168 ) 169 170 def get_descriptor_attribute( 171 self, service_uuid: UUID, characteristic_uuid: UUID, descriptor_uuid: UUID 172 ) -> Optional[Descriptor]: 173 characteristics = self.get_characteristic_attributes( 174 service_uuid, characteristic_uuid 175 ) 176 if not characteristics: 177 return None 178 179 (_, characteristic_value) = characteristics 180 181 return next( 182 ( 183 attribute 184 for attribute in map( 185 self.get_attribute, 186 range( 187 characteristic_value.handle + 1, 188 characteristic_value.end_group_handle + 1, 189 ), 190 ) 191 if attribute.type == descriptor_uuid 192 ), 193 None, 194 ) 195 196 def add_attribute(self, attribute): 197 # Assign a handle to this attribute 198 attribute.handle = self.next_handle() 199 attribute.end_group_handle = ( 200 attribute.handle 201 ) # TODO: keep track of descriptors in the group 202 203 # Add this attribute to the list 204 self.attributes.append(attribute) 205 206 def add_service(self, service: Service): 207 # Add the service attribute to the DB 208 self.add_attribute(service) 209 210 # TODO: add included services 211 212 # Add all characteristics 213 for characteristic in service.characteristics: 214 # Add a Characteristic Declaration 215 characteristic_declaration = CharacteristicDeclaration( 216 characteristic, self.next_handle() + 1 217 ) 218 self.add_attribute(characteristic_declaration) 219 220 # Add the characteristic value 221 self.add_attribute(characteristic) 222 223 # Add the descriptors 224 for descriptor in characteristic.descriptors: 225 self.add_attribute(descriptor) 226 227 # If the characteristic supports subscriptions, add a CCCD descriptor 228 # unless there is one already 229 if ( 230 characteristic.properties 231 & (Characteristic.NOTIFY | Characteristic.INDICATE) 232 and characteristic.get_descriptor( 233 GATT_CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR 234 ) 235 is None 236 ): 237 self.add_attribute( 238 # pylint: disable=line-too-long 239 Descriptor( 240 GATT_CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR, 241 Attribute.READABLE | Attribute.WRITEABLE, 242 CharacteristicValue( 243 read=lambda connection, characteristic=characteristic: self.read_cccd( 244 connection, characteristic 245 ), 246 write=lambda connection, value, characteristic=characteristic: self.write_cccd( 247 connection, characteristic, value 248 ), 249 ), 250 ) 251 ) 252 253 # Update the service and characteristic group ends 254 characteristic_declaration.end_group_handle = self.attributes[-1].handle 255 characteristic.end_group_handle = self.attributes[-1].handle 256 257 # Update the service group end 258 service.end_group_handle = self.attributes[-1].handle 259 260 def add_services(self, services): 261 for service in services: 262 self.add_service(service) 263 264 def read_cccd(self, connection, characteristic): 265 if connection is None: 266 return bytes([0, 0]) 267 268 subscribers = self.subscribers.get(connection.handle) 269 cccd = None 270 if subscribers: 271 cccd = subscribers.get(characteristic.handle) 272 273 return cccd or bytes([0, 0]) 274 275 def write_cccd(self, connection, characteristic, value): 276 logger.debug( 277 f'Subscription update for connection=0x{connection.handle:04X}, ' 278 f'handle=0x{characteristic.handle:04X}: {value.hex()}' 279 ) 280 281 # Sanity check 282 if len(value) != 2: 283 logger.warning('CCCD value not 2 bytes long') 284 return 285 286 cccds = self.subscribers.setdefault(connection.handle, {}) 287 cccds[characteristic.handle] = value 288 logger.debug(f'CCCDs: {cccds}') 289 notify_enabled = value[0] & 0x01 != 0 290 indicate_enabled = value[0] & 0x02 != 0 291 characteristic.emit( 292 'subscription', connection, notify_enabled, indicate_enabled 293 ) 294 self.emit( 295 'characteristic_subscription', 296 connection, 297 characteristic, 298 notify_enabled, 299 indicate_enabled, 300 ) 301 302 def send_response(self, connection, response): 303 logger.debug( 304 f'GATT Response from server: [0x{connection.handle:04X}] {response}' 305 ) 306 self.send_gatt_pdu(connection.handle, response.to_bytes()) 307 308 async def notify_subscriber(self, connection, attribute, value=None, force=False): 309 # Check if there's a subscriber 310 if not force: 311 subscribers = self.subscribers.get(connection.handle) 312 if not subscribers: 313 logger.debug('not notifying, no subscribers') 314 return 315 cccd = subscribers.get(attribute.handle) 316 if not cccd: 317 logger.debug( 318 f'not notifying, no subscribers for handle {attribute.handle:04X}' 319 ) 320 return 321 if len(cccd) != 2 or (cccd[0] & 0x01 == 0): 322 logger.debug(f'not notifying, cccd={cccd.hex()}') 323 return 324 325 # Get or encode the value 326 value = ( 327 attribute.read_value(connection) 328 if value is None 329 else attribute.encode_value(value) 330 ) 331 332 # Truncate if needed 333 if len(value) > connection.att_mtu - 3: 334 value = value[: connection.att_mtu - 3] 335 336 # Notify 337 notification = ATT_Handle_Value_Notification( 338 attribute_handle=attribute.handle, attribute_value=value 339 ) 340 logger.debug( 341 f'GATT Notify from server: [0x{connection.handle:04X}] {notification}' 342 ) 343 self.send_gatt_pdu(connection.handle, bytes(notification)) 344 345 async def indicate_subscriber(self, connection, attribute, value=None, force=False): 346 # Check if there's a subscriber 347 if not force: 348 subscribers = self.subscribers.get(connection.handle) 349 if not subscribers: 350 logger.debug('not indicating, no subscribers') 351 return 352 cccd = subscribers.get(attribute.handle) 353 if not cccd: 354 logger.debug( 355 f'not indicating, no subscribers for handle {attribute.handle:04X}' 356 ) 357 return 358 if len(cccd) != 2 or (cccd[0] & 0x02 == 0): 359 logger.debug(f'not indicating, cccd={cccd.hex()}') 360 return 361 362 # Get or encode the value 363 value = ( 364 attribute.read_value(connection) 365 if value is None 366 else attribute.encode_value(value) 367 ) 368 369 # Truncate if needed 370 if len(value) > connection.att_mtu - 3: 371 value = value[: connection.att_mtu - 3] 372 373 # Indicate 374 indication = ATT_Handle_Value_Indication( 375 attribute_handle=attribute.handle, attribute_value=value 376 ) 377 logger.debug( 378 f'GATT Indicate from server: [0x{connection.handle:04X}] {indication}' 379 ) 380 381 # Wait until we can send (only one pending indication at a time per connection) 382 async with self.indication_semaphores[connection.handle]: 383 assert self.pending_confirmations[connection.handle] is None 384 385 # Create a future value to hold the eventual response 386 self.pending_confirmations[ 387 connection.handle 388 ] = asyncio.get_running_loop().create_future() 389 390 try: 391 self.send_gatt_pdu(connection.handle, indication.to_bytes()) 392 await asyncio.wait_for( 393 self.pending_confirmations[connection.handle], GATT_REQUEST_TIMEOUT 394 ) 395 except asyncio.TimeoutError as error: 396 logger.warning(color('!!! GATT Indicate timeout', 'red')) 397 raise TimeoutError(f'GATT timeout for {indication.name}') from error 398 finally: 399 self.pending_confirmations[connection.handle] = None 400 401 async def notify_or_indicate_subscribers( 402 self, indicate, attribute, value=None, force=False 403 ): 404 # Get all the connections for which there's at least one subscription 405 connections = [ 406 connection 407 for connection in [ 408 self.device.lookup_connection(connection_handle) 409 for (connection_handle, subscribers) in self.subscribers.items() 410 if force or subscribers.get(attribute.handle) 411 ] 412 if connection is not None 413 ] 414 415 # Indicate or notify for each connection 416 if connections: 417 coroutine = self.indicate_subscriber if indicate else self.notify_subscriber 418 await asyncio.wait( 419 [ 420 asyncio.create_task(coroutine(connection, attribute, value, force)) 421 for connection in connections 422 ] 423 ) 424 425 async def notify_subscribers(self, attribute, value=None, force=False): 426 return await self.notify_or_indicate_subscribers(False, attribute, value, force) 427 428 async def indicate_subscribers(self, attribute, value=None, force=False): 429 return await self.notify_or_indicate_subscribers(True, attribute, value, force) 430 431 def on_disconnection(self, connection): 432 if connection.handle in self.subscribers: 433 del self.subscribers[connection.handle] 434 if connection.handle in self.indication_semaphores: 435 del self.indication_semaphores[connection.handle] 436 if connection.handle in self.pending_confirmations: 437 del self.pending_confirmations[connection.handle] 438 439 def on_gatt_pdu(self, connection, att_pdu): 440 logger.debug(f'GATT Request to server: [0x{connection.handle:04X}] {att_pdu}') 441 handler_name = f'on_{att_pdu.name.lower()}' 442 handler = getattr(self, handler_name, None) 443 if handler is not None: 444 try: 445 handler(connection, att_pdu) 446 except ATT_Error as error: 447 logger.debug(f'normal exception returned by handler: {error}') 448 response = ATT_Error_Response( 449 request_opcode_in_error=att_pdu.op_code, 450 attribute_handle_in_error=error.att_handle, 451 error_code=error.error_code, 452 ) 453 self.send_response(connection, response) 454 except Exception as error: 455 logger.warning(f'{color("!!! Exception in handler:", "red")} {error}') 456 response = ATT_Error_Response( 457 request_opcode_in_error=att_pdu.op_code, 458 attribute_handle_in_error=0x0000, 459 error_code=ATT_UNLIKELY_ERROR_ERROR, 460 ) 461 self.send_response(connection, response) 462 raise error 463 else: 464 # No specific handler registered 465 if att_pdu.op_code in ATT_REQUESTS: 466 # Invoke the generic handler 467 self.on_att_request(connection, att_pdu) 468 else: 469 # Just ignore 470 logger.warning( 471 color( 472 f'--- Ignoring GATT Request from [0x{connection.handle:04X}]: ', 473 'red', 474 ) 475 + str(att_pdu) 476 ) 477 478 ####################################################### 479 # ATT handlers 480 ####################################################### 481 def on_att_request(self, connection, pdu): 482 ''' 483 Handler for requests without a more specific handler 484 ''' 485 logger.warning( 486 color( 487 f'--- Unsupported ATT Request from [0x{connection.handle:04X}]: ', 'red' 488 ) 489 + str(pdu) 490 ) 491 response = ATT_Error_Response( 492 request_opcode_in_error=pdu.op_code, 493 attribute_handle_in_error=0x0000, 494 error_code=ATT_REQUEST_NOT_SUPPORTED_ERROR, 495 ) 496 self.send_response(connection, response) 497 498 def on_att_exchange_mtu_request(self, connection, request): 499 ''' 500 See Bluetooth spec Vol 3, Part F - 3.4.2.1 Exchange MTU Request 501 ''' 502 self.send_response( 503 connection, ATT_Exchange_MTU_Response(server_rx_mtu=self.max_mtu) 504 ) 505 506 # Compute the final MTU 507 if request.client_rx_mtu >= ATT_DEFAULT_MTU: 508 mtu = min(self.max_mtu, request.client_rx_mtu) 509 510 # Notify the device 511 self.device.on_connection_att_mtu_update(connection.handle, mtu) 512 else: 513 logger.warning('invalid client_rx_mtu received, MTU not changed') 514 515 def on_att_find_information_request(self, connection, request): 516 ''' 517 See Bluetooth spec Vol 3, Part F - 3.4.3.1 Find Information Request 518 ''' 519 520 # Check the request parameters 521 if ( 522 request.starting_handle == 0 523 or request.starting_handle > request.ending_handle 524 ): 525 self.send_response( 526 connection, 527 ATT_Error_Response( 528 request_opcode_in_error=request.op_code, 529 attribute_handle_in_error=request.starting_handle, 530 error_code=ATT_INVALID_HANDLE_ERROR, 531 ), 532 ) 533 return 534 535 # Build list of returned attributes 536 pdu_space_available = connection.att_mtu - 2 537 attributes = [] 538 uuid_size = 0 539 for attribute in ( 540 attribute 541 for attribute in self.attributes 542 if attribute.handle >= request.starting_handle 543 and attribute.handle <= request.ending_handle 544 ): 545 this_uuid_size = len(attribute.type.to_pdu_bytes()) 546 547 if attributes: 548 # Check if this attribute has the same type size as the previous one 549 if this_uuid_size != uuid_size: 550 break 551 552 # Check if there's enough space for one more entry 553 uuid_size = this_uuid_size 554 if pdu_space_available < 2 + uuid_size: 555 break 556 557 # Add the attribute to the list 558 attributes.append(attribute) 559 pdu_space_available -= 2 + uuid_size 560 561 # Return the list of attributes 562 if attributes: 563 information_data_list = [ 564 struct.pack('<H', attribute.handle) + attribute.type.to_pdu_bytes() 565 for attribute in attributes 566 ] 567 response = ATT_Find_Information_Response( 568 format=1 if len(attributes[0].type.to_pdu_bytes()) == 2 else 2, 569 information_data=b''.join(information_data_list), 570 ) 571 else: 572 response = ATT_Error_Response( 573 request_opcode_in_error=request.op_code, 574 attribute_handle_in_error=request.starting_handle, 575 error_code=ATT_ATTRIBUTE_NOT_FOUND_ERROR, 576 ) 577 578 self.send_response(connection, response) 579 580 def on_att_find_by_type_value_request(self, connection, request): 581 ''' 582 See Bluetooth spec Vol 3, Part F - 3.4.3.3 Find By Type Value Request 583 ''' 584 585 # Build list of returned attributes 586 pdu_space_available = connection.att_mtu - 2 587 attributes = [] 588 for attribute in ( 589 attribute 590 for attribute in self.attributes 591 if attribute.handle >= request.starting_handle 592 and attribute.handle <= request.ending_handle 593 and attribute.type == request.attribute_type 594 and attribute.read_value(connection) == request.attribute_value 595 and pdu_space_available >= 4 596 ): 597 # TODO: check permissions 598 599 # Add the attribute to the list 600 attributes.append(attribute) 601 pdu_space_available -= 4 602 603 # Return the list of attributes 604 if attributes: 605 handles_information_list = [] 606 for attribute in attributes: 607 if attribute.type in ( 608 GATT_PRIMARY_SERVICE_ATTRIBUTE_TYPE, 609 GATT_SECONDARY_SERVICE_ATTRIBUTE_TYPE, 610 GATT_CHARACTERISTIC_ATTRIBUTE_TYPE, 611 ): 612 # Part of a group 613 group_end_handle = attribute.end_group_handle 614 else: 615 # Not part of a group 616 group_end_handle = attribute.handle 617 handles_information_list.append( 618 struct.pack('<HH', attribute.handle, group_end_handle) 619 ) 620 response = ATT_Find_By_Type_Value_Response( 621 handles_information_list=b''.join(handles_information_list) 622 ) 623 else: 624 response = ATT_Error_Response( 625 request_opcode_in_error=request.op_code, 626 attribute_handle_in_error=request.starting_handle, 627 error_code=ATT_ATTRIBUTE_NOT_FOUND_ERROR, 628 ) 629 630 self.send_response(connection, response) 631 632 def on_att_read_by_type_request(self, connection, request): 633 ''' 634 See Bluetooth spec Vol 3, Part F - 3.4.4.1 Read By Type Request 635 ''' 636 637 pdu_space_available = connection.att_mtu - 2 638 639 response = ATT_Error_Response( 640 request_opcode_in_error=request.op_code, 641 attribute_handle_in_error=request.starting_handle, 642 error_code=ATT_ATTRIBUTE_NOT_FOUND_ERROR, 643 ) 644 645 attributes = [] 646 for attribute in ( 647 attribute 648 for attribute in self.attributes 649 if attribute.type == request.attribute_type 650 and attribute.handle >= request.starting_handle 651 and attribute.handle <= request.ending_handle 652 and pdu_space_available 653 ): 654 655 try: 656 attribute_value = attribute.read_value(connection) 657 except ATT_Error as error: 658 # If the first attribute is unreadable, return an error 659 # Otherwise return attributes up to this point 660 if not attributes: 661 response = ATT_Error_Response( 662 request_opcode_in_error=request.op_code, 663 attribute_handle_in_error=attribute.handle, 664 error_code=error.error_code, 665 ) 666 break 667 668 # Check the attribute value size 669 max_attribute_size = min(connection.att_mtu - 4, 253) 670 if len(attribute_value) > max_attribute_size: 671 # We need to truncate 672 attribute_value = attribute_value[:max_attribute_size] 673 if attributes and len(attributes[0][1]) != len(attribute_value): 674 # Not the same size as previous attribute, stop here 675 break 676 677 # Check if there is enough space 678 entry_size = 2 + len(attribute_value) 679 if pdu_space_available < entry_size: 680 break 681 682 # Add the attribute to the list 683 attributes.append((attribute.handle, attribute_value)) 684 pdu_space_available -= entry_size 685 686 if attributes: 687 attribute_data_list = [ 688 struct.pack('<H', handle) + value for handle, value in attributes 689 ] 690 response = ATT_Read_By_Type_Response( 691 length=entry_size, attribute_data_list=b''.join(attribute_data_list) 692 ) 693 else: 694 logging.debug(f"not found {request}") 695 696 self.send_response(connection, response) 697 698 def on_att_read_request(self, connection, request): 699 ''' 700 See Bluetooth spec Vol 3, Part F - 3.4.4.3 Read Request 701 ''' 702 703 if attribute := self.get_attribute(request.attribute_handle): 704 try: 705 value = attribute.read_value(connection) 706 except ATT_Error as error: 707 response = ATT_Error_Response( 708 request_opcode_in_error=request.op_code, 709 attribute_handle_in_error=request.attribute_handle, 710 error_code=error.error_code, 711 ) 712 else: 713 value_size = min(connection.att_mtu - 1, len(value)) 714 response = ATT_Read_Response(attribute_value=value[:value_size]) 715 else: 716 response = ATT_Error_Response( 717 request_opcode_in_error=request.op_code, 718 attribute_handle_in_error=request.attribute_handle, 719 error_code=ATT_INVALID_HANDLE_ERROR, 720 ) 721 self.send_response(connection, response) 722 723 def on_att_read_blob_request(self, connection, request): 724 ''' 725 See Bluetooth spec Vol 3, Part F - 3.4.4.5 Read Blob Request 726 ''' 727 728 if attribute := self.get_attribute(request.attribute_handle): 729 try: 730 value = attribute.read_value(connection) 731 except ATT_Error as error: 732 response = ATT_Error_Response( 733 request_opcode_in_error=request.op_code, 734 attribute_handle_in_error=request.attribute_handle, 735 error_code=error.error_code, 736 ) 737 else: 738 if request.value_offset > len(value): 739 response = ATT_Error_Response( 740 request_opcode_in_error=request.op_code, 741 attribute_handle_in_error=request.attribute_handle, 742 error_code=ATT_INVALID_OFFSET_ERROR, 743 ) 744 elif len(value) <= connection.att_mtu - 1: 745 response = ATT_Error_Response( 746 request_opcode_in_error=request.op_code, 747 attribute_handle_in_error=request.attribute_handle, 748 error_code=ATT_ATTRIBUTE_NOT_LONG_ERROR, 749 ) 750 else: 751 part_size = min( 752 connection.att_mtu - 1, len(value) - request.value_offset 753 ) 754 response = ATT_Read_Blob_Response( 755 part_attribute_value=value[ 756 request.value_offset : request.value_offset + part_size 757 ] 758 ) 759 else: 760 response = ATT_Error_Response( 761 request_opcode_in_error=request.op_code, 762 attribute_handle_in_error=request.attribute_handle, 763 error_code=ATT_INVALID_HANDLE_ERROR, 764 ) 765 self.send_response(connection, response) 766 767 def on_att_read_by_group_type_request(self, connection, request): 768 ''' 769 See Bluetooth spec Vol 3, Part F - 3.4.4.9 Read by Group Type Request 770 ''' 771 if request.attribute_group_type not in ( 772 GATT_PRIMARY_SERVICE_ATTRIBUTE_TYPE, 773 GATT_SECONDARY_SERVICE_ATTRIBUTE_TYPE, 774 ): 775 response = ATT_Error_Response( 776 request_opcode_in_error=request.op_code, 777 attribute_handle_in_error=request.starting_handle, 778 error_code=ATT_UNSUPPORTED_GROUP_TYPE_ERROR, 779 ) 780 self.send_response(connection, response) 781 return 782 783 pdu_space_available = connection.att_mtu - 2 784 attributes = [] 785 for attribute in ( 786 attribute 787 for attribute in self.attributes 788 if attribute.type == request.attribute_group_type 789 and attribute.handle >= request.starting_handle 790 and attribute.handle <= request.ending_handle 791 and pdu_space_available 792 ): 793 # No need to catch permission errors here, since these attributes 794 # must all be world-readable 795 attribute_value = attribute.read_value(connection) 796 # Check the attribute value size 797 max_attribute_size = min(connection.att_mtu - 6, 251) 798 if len(attribute_value) > max_attribute_size: 799 # We need to truncate 800 attribute_value = attribute_value[:max_attribute_size] 801 if attributes and len(attributes[0][2]) != len(attribute_value): 802 # Not the same size as previous attributes, stop here 803 break 804 805 # Check if there is enough space 806 entry_size = 4 + len(attribute_value) 807 if pdu_space_available < entry_size: 808 break 809 810 # Add the attribute to the list 811 attributes.append( 812 (attribute.handle, attribute.end_group_handle, attribute_value) 813 ) 814 pdu_space_available -= entry_size 815 816 if attributes: 817 attribute_data_list = [ 818 struct.pack('<HH', handle, end_group_handle) + value 819 for handle, end_group_handle, value in attributes 820 ] 821 response = ATT_Read_By_Group_Type_Response( 822 length=len(attribute_data_list[0]), 823 attribute_data_list=b''.join(attribute_data_list), 824 ) 825 else: 826 response = ATT_Error_Response( 827 request_opcode_in_error=request.op_code, 828 attribute_handle_in_error=request.starting_handle, 829 error_code=ATT_ATTRIBUTE_NOT_FOUND_ERROR, 830 ) 831 832 self.send_response(connection, response) 833 834 def on_att_write_request(self, connection, request): 835 ''' 836 See Bluetooth spec Vol 3, Part F - 3.4.5.1 Write Request 837 ''' 838 839 # Check that the attribute exists 840 attribute = self.get_attribute(request.attribute_handle) 841 if attribute is None: 842 self.send_response( 843 connection, 844 ATT_Error_Response( 845 request_opcode_in_error=request.op_code, 846 attribute_handle_in_error=request.attribute_handle, 847 error_code=ATT_INVALID_HANDLE_ERROR, 848 ), 849 ) 850 return 851 852 # TODO: check permissions 853 854 # Check the request parameters 855 if len(request.attribute_value) > GATT_MAX_ATTRIBUTE_VALUE_SIZE: 856 self.send_response( 857 connection, 858 ATT_Error_Response( 859 request_opcode_in_error=request.op_code, 860 attribute_handle_in_error=request.attribute_handle, 861 error_code=ATT_INVALID_ATTRIBUTE_LENGTH_ERROR, 862 ), 863 ) 864 return 865 866 # Accept the value 867 attribute.write_value(connection, request.attribute_value) 868 869 # Done 870 self.send_response(connection, ATT_Write_Response()) 871 872 def on_att_write_command(self, connection, request): 873 ''' 874 See Bluetooth spec Vol 3, Part F - 3.4.5.3 Write Command 875 ''' 876 877 # Check that the attribute exists 878 attribute = self.get_attribute(request.attribute_handle) 879 if attribute is None: 880 return 881 882 # TODO: check permissions 883 884 # Check the request parameters 885 if len(request.attribute_value) > GATT_MAX_ATTRIBUTE_VALUE_SIZE: 886 return 887 888 # Accept the value 889 try: 890 attribute.write_value(connection, request.attribute_value) 891 except Exception as error: 892 logger.warning(f'!!! ignoring exception: {error}') 893 894 def on_att_handle_value_confirmation(self, connection, _confirmation): 895 ''' 896 See Bluetooth spec Vol 3, Part F - 3.4.7.3 Handle Value Confirmation 897 ''' 898 if self.pending_confirmations[connection.handle] is None: 899 # Not expected! 900 logger.warning( 901 '!!! unexpected confirmation, there is no pending indication' 902 ) 903 return 904 905 self.pending_confirmations[connection.handle].set_result(None) 906