1#!/usr/bin/env python3 2# 3# Copyright (C) 2016 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); you may not 6# use this file except in compliance with the License. You may obtain a copy of 7# the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14# License for the specific language governing permissions and limitations under 15# the License. 16 17import time 18import os 19 20from acts.keys import Config 21from acts.utils import rand_ascii_str 22from acts_contrib.test_utils.bt.bt_constants import gatt_cb_strings 23from acts_contrib.test_utils.bt.bt_constants import gatt_characteristic 24from acts_contrib.test_utils.bt.bt_constants import gatt_characteristic_value_format 25from acts_contrib.test_utils.bt.bt_constants import gatt_cb_err 26from acts_contrib.test_utils.bt.bt_constants import gatt_transport 27from acts_contrib.test_utils.bt.bt_constants import gatt_event 28from acts_contrib.test_utils.bt.bt_constants import gatt_server_responses 29from acts_contrib.test_utils.bt.bt_constants import gatt_service_types 30from acts_contrib.test_utils.bt.bt_constants import small_timeout 31from acts_contrib.test_utils.bt.gatt_test_database import STRING_512BYTES 32 33from acts.utils import exe_cmd 34from math import ceil 35 36 37class GattServerLib(): 38 39 characteristic_list = [] 40 default_timeout = 10 41 descriptor_list = [] 42 dut = None 43 gatt_server = None 44 gatt_server_callback = None 45 gatt_server_list = [] 46 log = None 47 service_list = [] 48 write_mapping = {} 49 50 def __init__(self, log, dut): 51 self.dut = dut 52 self.log = log 53 54 def list_all_uuids(self): 55 """From the GATT Client, discover services and list all services, 56 chars and descriptors. 57 """ 58 self.log.info("Service List:") 59 for service in self.dut.droid.gattGetServiceUuidList(self.gatt_server): 60 self.dut.log.info("GATT Server service uuid: {}".format(service)) 61 self.log.info("Characteristics List:") 62 for characteristic in self.characteristic_list: 63 instance_id = self.dut.droid.gattServerGetCharacteristicInstanceId( 64 characteristic) 65 uuid = self.dut.droid.gattServerGetCharacteristicUuid( 66 characteristic) 67 self.dut.log.info( 68 "GATT Server characteristic handle uuid: {} {}".format( 69 hex(instance_id), uuid)) 70 # TODO: add getting insance ids and uuids from each descriptor. 71 72 def open(self): 73 """Open an empty GATT Server instance""" 74 self.gatt_server_callback = self.dut.droid.gattServerCreateGattServerCallback( 75 ) 76 self.gatt_server = self.dut.droid.gattServerOpenGattServer( 77 self.gatt_server_callback) 78 self.gatt_server_list.append(self.gatt_server) 79 80 def clear_services(self): 81 """Clear BluetoothGattServices from BluetoothGattServer""" 82 self.dut.droid.gattServerClearServices(self.gatt_server) 83 84 def close_bluetooth_gatt_servers(self): 85 """Close Bluetooth Gatt Servers""" 86 try: 87 for btgs in self.gatt_server_list: 88 self.dut.droid.gattServerClose(btgs) 89 except Exception as err: 90 self.log.error( 91 "Failed to close Bluetooth GATT Servers: {}".format(err)) 92 self.characteristic_list = [] 93 self.descriptor_list = [] 94 self.gatt_server_list = [] 95 self.service_list = [] 96 97 def characteristic_set_value_by_instance_id(self, instance_id, value): 98 """Set Characteristic value by instance id""" 99 self.dut.droid.gattServerCharacteristicSetValueByInstanceId( 100 int(instance_id, 16), value) 101 102 def notify_characteristic_changed(self, instance_id, confirm): 103 """ Notify characteristic changed """ 104 self.dut.droid.gattServerNotifyCharacteristicChangedByInstanceId( 105 self.gatt_server, 0, int(instance_id, 16), confirm) 106 107 def send_response(self, user_input): 108 """Send a single response to the GATT Client""" 109 args = user_input.split() 110 mtu = 23 111 if len(args) == 2: 112 user_input = args[0] 113 mtu = int(args[1]) 114 desc_read = gatt_event['desc_read_req']['evt'].format( 115 self.gatt_server_callback) 116 desc_write = gatt_event['desc_write_req']['evt'].format( 117 self.gatt_server_callback) 118 char_read = gatt_event['char_read_req']['evt'].format( 119 self.gatt_server_callback) 120 char_write_req = gatt_event['char_write_req']['evt'].format( 121 self.gatt_server_callback) 122 char_write = gatt_event['char_write']['evt'].format( 123 self.gatt_server_callback) 124 execute_write = gatt_event['exec_write']['evt'].format( 125 self.gatt_server_callback) 126 regex = "({}|{}|{}|{}|{}|{})".format(desc_read, desc_write, char_read, 127 char_write, execute_write, 128 char_write_req) 129 events = self.dut.ed.pop_events(regex, 5, small_timeout) 130 status = 0 131 if user_input: 132 status = gatt_server_responses.get(user_input) 133 for event in events: 134 self.log.debug("Found event: {}.".format(event)) 135 request_id = event['data']['requestId'] 136 if event['name'] == execute_write: 137 if ('execute' in event['data'] 138 and event['data']['execute'] == True): 139 for key in self.write_mapping: 140 value = self.write_mapping[key] 141 self.log.info("Writing key, value: {}, {}".format( 142 key, value)) 143 self.dut.droid.gattServerSetByteArrayValueByInstanceId( 144 key, value) 145 else: 146 self.log.info("Execute result is false") 147 self.write_mapping = {} 148 self.dut.droid.gattServerSendResponse( 149 self.gatt_server, 0, request_id, status, 0, []) 150 continue 151 offset = event['data']['offset'] 152 instance_id = event['data']['instanceId'] 153 if (event['name'] == desc_write or event['name'] == char_write 154 or event['name'] == char_write_req): 155 if ('preparedWrite' in event['data'] 156 and event['data']['preparedWrite'] == True): 157 value = event['data']['value'] 158 if instance_id in self.write_mapping.keys(): 159 self.write_mapping[ 160 instance_id] = self.write_mapping[instance_id] + value 161 self.log.info( 162 "New Prepared Write Value for {}: {}".format( 163 instance_id, self.write_mapping[instance_id])) 164 else: 165 self.log.info("write mapping key, value {}, {}".format( 166 instance_id, value)) 167 self.write_mapping[instance_id] = value 168 self.log.info("current value {}, {}".format( 169 instance_id, value)) 170 self.dut.droid.gattServerSendResponse( 171 self.gatt_server, 0, request_id, status, 0, value) 172 continue 173 else: 174 self.dut.droid.gattServerSetByteArrayValueByInstanceId( 175 event['data']['instanceId'], event['data']['value']) 176 177 try: 178 data = self.dut.droid.gattServerGetReadValueByInstanceId( 179 int(event['data']['instanceId'])) 180 except Exception as err: 181 self.log.error(err) 182 if not data: 183 data = [1] 184 self.log.info( 185 "GATT Server Send Response [request_id, status, offset, data]" \ 186 " [{}, {}, {}, {}]". 187 format(request_id, status, offset, data)) 188 data = data[offset:offset + mtu - 1] 189 self.dut.droid.gattServerSendResponse( 190 self.gatt_server, 0, request_id, status, offset, data) 191 192 def _setup_service(self, serv): 193 service = self.dut.droid.gattServerCreateService( 194 serv['uuid'], serv['type']) 195 if 'handles' in serv: 196 self.dut.droid.gattServerServiceSetHandlesToReserve( 197 service, serv['handles']) 198 return service 199 200 def _setup_characteristic(self, char): 201 characteristic = \ 202 self.dut.droid.gattServerCreateBluetoothGattCharacteristic( 203 char['uuid'], char['properties'], char['permissions']) 204 if 'instance_id' in char: 205 self.dut.droid.gattServerCharacteristicSetInstanceId( 206 characteristic, char['instance_id']) 207 set_id = self.dut.droid.gattServerCharacteristicGetInstanceId( 208 characteristic) 209 if set_id != char['instance_id']: 210 self.log.error( 211 "Instance ID did not match up. Found {} Expected {}". 212 format(set_id, char['instance_id'])) 213 if 'value_type' in char: 214 value_type = char['value_type'] 215 value = char['value'] 216 if value_type == gatt_characteristic_value_format['string']: 217 self.log.info("Set String value result: {}".format( 218 self.dut.droid.gattServerCharacteristicSetStringValue( 219 characteristic, value))) 220 elif value_type == gatt_characteristic_value_format['byte']: 221 self.log.info("Set Byte Array value result: {}".format( 222 self.dut.droid.gattServerCharacteristicSetByteValue( 223 characteristic, value))) 224 else: 225 self.log.info("Set Int value result: {}".format( 226 self.dut.droid.gattServerCharacteristicSetIntValue( 227 characteristic, value, value_type, char['offset']))) 228 return characteristic 229 230 def _setup_descriptor(self, desc): 231 descriptor = self.dut.droid.gattServerCreateBluetoothGattDescriptor( 232 desc['uuid'], desc['permissions']) 233 if 'value' in desc: 234 self.dut.droid.gattServerDescriptorSetByteValue( 235 descriptor, desc['value']) 236 if 'instance_id' in desc: 237 self.dut.droid.gattServerDescriptorSetInstanceId( 238 descriptor, desc['instance_id']) 239 self.descriptor_list.append(descriptor) 240 return descriptor 241 242 def setup_gatts_db(self, database): 243 """Setup GATT Server database""" 244 self.gatt_server_callback = \ 245 self.dut.droid.gattServerCreateGattServerCallback() 246 self.gatt_server = self.dut.droid.gattServerOpenGattServer( 247 self.gatt_server_callback) 248 self.gatt_server_list.append(self.gatt_server) 249 for serv in database['services']: 250 service = self._setup_service(serv) 251 self.service_list.append(service) 252 if 'characteristics' in serv: 253 for char in serv['characteristics']: 254 characteristic = self._setup_characteristic(char) 255 if 'descriptors' in char: 256 for desc in char['descriptors']: 257 descriptor = self._setup_descriptor(desc) 258 self.dut.droid.gattServerCharacteristicAddDescriptor( 259 characteristic, descriptor) 260 self.characteristic_list.append(characteristic) 261 self.dut.droid.gattServerAddCharacteristicToService( 262 service, characteristic) 263 self.dut.droid.gattServerAddService(self.gatt_server, service) 264 expected_event = gatt_cb_strings['serv_added'].format( 265 self.gatt_server_callback) 266 self.dut.ed.pop_event(expected_event, 10) 267 return self.gatt_server, self.gatt_server_callback 268 269 def send_continuous_response(self, user_input): 270 """Send the same response""" 271 desc_read = gatt_event['desc_read_req']['evt'].format( 272 self.gatt_server_callback) 273 desc_write = gatt_event['desc_write_req']['evt'].format( 274 self.gatt_server_callback) 275 char_read = gatt_event['char_read_req']['evt'].format( 276 self.gatt_server_callback) 277 char_write = gatt_event['char_write']['evt'].format( 278 self.gatt_server_callback) 279 execute_write = gatt_event['exec_write']['evt'].format( 280 self.gatt_server_callback) 281 regex = "({}|{}|{}|{}|{})".format(desc_read, desc_write, char_read, 282 char_write, execute_write) 283 offset = 0 284 status = 0 285 mtu = 23 286 char_value = [] 287 for i in range(512): 288 char_value.append(i % 256) 289 len_min = 470 290 end_time = time.time() + 180 291 i = 0 292 num_packets = ceil((len(char_value) + 1) / (mtu - 1)) 293 while time.time() < end_time: 294 events = self.dut.ed.pop_events(regex, 10, small_timeout) 295 for event in events: 296 start_offset = i * (mtu - 1) 297 i += 1 298 self.log.debug("Found event: {}.".format(event)) 299 request_id = event['data']['requestId'] 300 data = char_value[start_offset:start_offset + mtu - 1] 301 if not data: 302 data = [1] 303 self.log.debug( 304 "GATT Server Send Response [request_id, status, offset, " \ 305 "data] [{}, {}, {}, {}]".format(request_id, status, offset, 306 data)) 307 self.dut.droid.gattServerSendResponse( 308 self.gatt_server, 0, request_id, status, offset, data) 309 310 def send_continuous_response_data(self, user_input): 311 """Send the same response with data""" 312 desc_read = gatt_event['desc_read_req']['evt'].format( 313 self.gatt_server_callback) 314 desc_write = gatt_event['desc_write_req']['evt'].format( 315 self.gatt_server_callback) 316 char_read = gatt_event['char_read_req']['evt'].format( 317 self.gatt_server_callback) 318 char_write = gatt_event['char_write']['evt'].format( 319 self.gatt_server_callback) 320 execute_write = gatt_event['exec_write']['evt'].format( 321 self.gatt_server_callback) 322 regex = "({}|{}|{}|{}|{})".format(desc_read, desc_write, char_read, 323 char_write, execute_write) 324 offset = 0 325 status = 0 326 mtu = 11 327 char_value = [] 328 len_min = 470 329 end_time = time.time() + 180 330 i = 0 331 num_packets = ceil((len(char_value) + 1) / (mtu - 1)) 332 while time.time() < end_time: 333 events = self.dut.ed.pop_events(regex, 10, small_timeout) 334 for event in events: 335 self.log.info(event) 336 request_id = event['data']['requestId'] 337 if event['name'] == execute_write: 338 if ('execute' in event['data'] 339 and event['data']['execute'] == True): 340 for key in self.write_mapping: 341 value = self.write_mapping[key] 342 self.log.debug("Writing key, value: {}, {}".format( 343 key, value)) 344 self.dut.droid.gattServerSetByteArrayValueByInstanceId( 345 key, value) 346 self.write_mapping = {} 347 self.dut.droid.gattServerSendResponse( 348 self.gatt_server, 0, request_id, status, 0, [1]) 349 continue 350 offset = event['data']['offset'] 351 instance_id = event['data']['instanceId'] 352 if (event['name'] == desc_write 353 or event['name'] == char_write): 354 if ('preparedWrite' in event['data'] 355 and event['data']['preparedWrite'] == True): 356 value = event['data']['value'] 357 if instance_id in self.write_mapping: 358 self.write_mapping[ 359 instance_id] = self.write_mapping[instance_id] + value 360 else: 361 self.write_mapping[instance_id] = value 362 else: 363 self.dut.droid.gattServerSetByteArrayValueByInstanceId( 364 event['data']['instanceId'], 365 event['data']['value']) 366 try: 367 data = self.dut.droid.gattServerGetReadValueByInstanceId( 368 int(event['data']['instanceId'])) 369 except Exception as err: 370 self.log.error(err) 371 if not data: 372 self.dut.droid.gattServerSendResponse( 373 self.gatt_server, 0, request_id, status, offset, [1]) 374 else: 375 self.dut.droid.gattServerSendResponse( 376 self.gatt_server, 0, request_id, status, offset, 377 data[offset:offset + 17]) 378