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"""
17This test script exercises different GATT write procedures.
18"""
19
20from acts.test_decorators import test_tracker_info
21from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
22from acts.test_utils.bt.GattConnectedBaseTest import GattConnectedBaseTest
23from acts.test_utils.bt.bt_constants import gatt_characteristic
24from acts.test_utils.bt.bt_constants import gatt_descriptor
25from acts.test_utils.bt.bt_constants import gatt_event
26from acts.test_utils.bt.bt_constants import gatt_cb_strings
27from acts.test_utils.bt.bt_constants import gatt_connection_priority
28from acts.test_utils.bt.bt_constants import gatt_characteristic_attr_length
29from acts.test_utils.bt.bt_constants import gatt_mtu_size
30from acts.test_utils.bt.bt_gatt_utils import setup_gatt_mtu
31
32
33class GattWriteTest(GattConnectedBaseTest):
34    @BluetoothBaseTest.bt_test_wrap
35    @test_tracker_info(uuid='513f4cef-489e-4bb6-96cc-c298c589225c')
36    def test_write_char(self):
37        """Test write characteristic value
38
39        Test write characteristic value using Write Request
40
41        1. Central: write WRITABLE_CHAR_UUID characteristic with char_value
42           using write request.
43        2. Peripheral: receive the written data.
44        3. Peripheral: send response with status 0 (success).
45        4. Central: make sure write callback is called.
46
47        Expected Result:
48        Verify that write request/response is properly delivered.
49
50        Returns:
51          Pass if True
52          Fail if False
53
54        TAGS: LE, GATT, Characteristic
55        Priority: 0
56        """
57        char_value = [1, 2, 3, 4, 5, 6, 7]
58        self.cen_ad.droid.gattClientCharacteristicSetValue(
59            self.bluetooth_gatt, self.discovered_services_index,
60            self.test_service_index, self.WRITABLE_CHAR_UUID, char_value)
61
62        self.cen_ad.droid.gattClientCharacteristicSetWriteType(
63            self.bluetooth_gatt, self.discovered_services_index,
64            self.test_service_index, self.WRITABLE_CHAR_UUID,
65            gatt_characteristic['write_type_default'])
66
67        self.cen_ad.droid.gattClientWriteCharacteristic(
68            self.bluetooth_gatt, self.discovered_services_index,
69            self.test_service_index, self.WRITABLE_CHAR_UUID)
70
71        event = self._server_wait(gatt_event['char_write_req'])
72
73        request_id = event['data']['requestId']
74        self.assertEqual(True, event['data']['responseNeeded'],
75                         "Should need response")
76        self.assertEqual(char_value, event['data']['value'])
77        self.assertEqual(0, event['data']['offset'])
78
79        bt_device_id = 0
80        status = 0
81        #both offset and return value don't matter, just the status
82        offset = 0
83        self.per_ad.droid.gattServerGetConnectedDevices(self.gatt_server)
84        self.per_ad.droid.gattServerSendResponse(
85            self.gatt_server, bt_device_id, request_id, status, offset, [])
86
87        event = self._client_wait(gatt_event['char_write'])
88        self.assertEqual(status, event["data"]["Status"],
89                         "Write status should be 0")
90        # Write response doesn't carry any data expcept status
91        return True
92
93    @BluetoothBaseTest.bt_test_wrap
94    @test_tracker_info(uuid='329dbef8-1b54-47e2-a388-b33ef9384464')
95    def test_write_descr(self):
96        """Test write descriptor value
97
98        Test write descriptor value
99
100        1. Central: write WRITABLE_DESC_UUID descriptor with desc_value.
101        2. Peripheral: receive the written data.
102        3. Peripheral: send response with status 0 (success).
103        4. Central: make sure write callback is called.
104
105        Expected Result:
106        Verify that write request/response is properly delivered.
107
108        Returns:
109          Pass if True
110          Fail if False
111
112        TAGS: LE, GATT, Descriptor
113        Priority: 0
114        """
115        desc_value = [1, 2, 3, 4, 5, 6, 7]
116        self.cen_ad.droid.gattClientDescriptorSetValue(
117            self.bluetooth_gatt, self.discovered_services_index,
118            self.test_service_index, self.WRITABLE_CHAR_UUID,
119            self.WRITABLE_DESC_UUID, desc_value)
120
121        self.cen_ad.droid.gattClientWriteDescriptor(
122            self.bluetooth_gatt, self.discovered_services_index,
123            self.test_service_index, self.WRITABLE_CHAR_UUID,
124            self.WRITABLE_DESC_UUID)
125
126        event = self._server_wait(gatt_event['desc_write_req'])
127
128        request_id = event['data']['requestId']
129        self.assertEqual(True, event['data']['responseNeeded'],
130                         "Should need response")
131        self.assertEqual(desc_value, event['data']['value'])
132        self.assertEqual(0, event['data']['offset'])
133
134        bt_device_id = 0
135        status = 0
136        #both offset and return value don't matter, just the status
137        offset = 0
138        self.per_ad.droid.gattServerGetConnectedDevices(self.gatt_server)
139        self.per_ad.droid.gattServerSendResponse(
140            self.gatt_server, bt_device_id, request_id, status, offset, [])
141
142        event = self._client_wait(gatt_event['desc_write'])
143        self.assertEqual(status, event["data"]["Status"],
144                         "Write status should be 0")
145        # Write response doesn't carry any data except status
146        return True
147
148    @BluetoothBaseTest.bt_test_wrap
149    @test_tracker_info(uuid='85757307-5bb1-43e5-9331-f1d7bdcbd6a0')
150    def test_write_char_no_resp(self):
151        """Test write characteristic value
152
153        Test write characteristic value using Write Command
154
155        1. Central: write WRITABLE_CHAR_UUID characteristic with char_value
156           using write command.
157        2. Central: make sure write callback is called.
158        3. Peripheral: receive the written data.
159
160        Expected Result:
161        Verify that write command is properly delivered.
162
163        Returns:
164          Pass if True
165          Fail if False
166
167        TAGS: LE, GATT, Characteristic
168        Priority: 0
169        """
170        char_value = [1, 2, 3, 4, 5, 6, 7]
171        self.cen_ad.droid.gattClientCharacteristicSetValue(
172            self.bluetooth_gatt, self.discovered_services_index,
173            self.test_service_index, self.WRITABLE_CHAR_UUID, char_value)
174
175        self.cen_ad.droid.gattClientCharacteristicSetWriteType(
176            self.bluetooth_gatt, self.discovered_services_index,
177            self.test_service_index, self.WRITABLE_CHAR_UUID,
178            gatt_characteristic['write_type_no_response'])
179
180        self.cen_ad.droid.gattClientWriteCharacteristic(
181            self.bluetooth_gatt, self.discovered_services_index,
182            self.test_service_index, self.WRITABLE_CHAR_UUID)
183
184        event = self._client_wait(gatt_event['char_write'])
185        if event["data"]["Status"] != 0:
186            self.log.error("Write status should be 0")
187            return False
188
189        event = self._server_wait(gatt_event['char_write_req'])
190
191        request_id = event['data']['requestId']
192        self.assertEqual(False, event['data']['responseNeeded'],
193                         "Should not need response")
194        self.assertEqual(0, event['data']['offset'])
195        self.assertEqual(char_value, event['data']['value'])
196
197        return True
198
199    @BluetoothBaseTest.bt_test_wrap
200    @test_tracker_info(uuid='0bf0182a-c315-4160-81be-9ce09f93608b')
201    def test_write_characteristic_long_no_resp(self):
202        """Test write characteristic value
203
204        Test write characteristic value using Write Command
205
206        1. Central: write WRITABLE_CHAR_UUID characteristic with char_value
207           using write command.
208        2. Central: make sure write callback is called.
209        3. Peripheral: receive the written data. Check it was properly trimmed.
210
211        Expected Result:
212        Verify that write command is properly trimmed and delivered.
213
214        Returns:
215          Pass if True
216          Fail if False
217
218        TAGS: LE, GATT, Characteristic
219        Priority: 0
220        """
221        char_value = []
222        for i in range(512):
223            char_value.append(i % 256)
224
225        self.cen_ad.droid.gattClientCharacteristicSetValue(
226            self.bluetooth_gatt, self.discovered_services_index,
227            self.test_service_index, self.WRITABLE_CHAR_UUID, char_value)
228
229        self.cen_ad.droid.gattClientCharacteristicSetWriteType(
230            self.bluetooth_gatt, self.discovered_services_index,
231            self.test_service_index, self.WRITABLE_CHAR_UUID,
232            gatt_characteristic['write_type_no_response'])
233
234        self.cen_ad.droid.gattClientWriteCharacteristic(
235            self.bluetooth_gatt, self.discovered_services_index,
236            self.test_service_index, self.WRITABLE_CHAR_UUID)
237
238        event = self._server_wait(gatt_event['char_write_req'])
239
240        request_id = event['data']['requestId']
241        self.assertEqual(False, event['data']['responseNeeded'])
242
243        # value shall be trimmed to MTU-3
244        trimmed_value = char_value[0:self.mtu - 3]
245        self.assertEqual(
246            trimmed_value, event['data']['value'],
247            "Received value should be sent value trimmed to MTU-3")
248
249        event = self._client_wait(gatt_event['char_write'])
250        if event["data"]["Status"] != 0:
251            self.log.error("Write status should be 0")
252            return False
253        return True
254
255    @BluetoothBaseTest.bt_test_wrap
256    @test_tracker_info(uuid='b80f1b5a-a223-441e-a6ed-d3c284c83cc7')
257    def test_write_characteristic_value_longer_than_mtu_request(self):
258        """Test writing characteristic value longer than what mtu limts
259
260        Test establishing a gatt connection between a GATT Peripheral and GATT
261        Client. Request mtu size equal to the max MTU.
262        The max number of bytes can be sent within a characteristic is
263        (MTU - gatt_characteristic_attr_length['attr_2']) since
264        the gatt_characteristic_attr_length['attr_2'] (3 bytes) are
265        used for its attribute of the command code and its handle.
266        Then reduce mtu by 1 and re-send the same characteristic.
267        Make sure the characteristic value received by the remote side is
268        also reduced properly.
269
270        Steps:
271        1. Create a GATT connection between the scanner(Client) and
272           advertiser(Peripheral).
273        2. Client: request new mtu size change to max MTU.
274        3. Client: write a characteristic with char_value of max MTU bytes.
275        4. Peripheral: receive the written data.  Check it was properly
276           truncated to (max MTU - gatt_characteristic_attr_length['attr_2']).
277        5. Client: request mtu size change to (max MTU - 1).
278        6. Client: write the same characteristic again.
279        7. Peripheral: receive the written data.  Check it was properly
280           truncated to (max MTU - 1 - gatt_characteristic_attr_length['attr_2'])
281        8. Client: return mtu size.
282
283        Expected Result:
284        Verify that data received by the Peripheral side is properly truncated
285        when mtu is set.
286
287        Returns:
288          Pass if True
289          Fail if False
290
291        TAGS: LE, GATT, Characteristic, MTU
292        Priority: 2
293        """
294        self.mtu = gatt_mtu_size['max']
295        self.log.info("Set mtu to max MTU: {}".format(self.mtu))
296        # set new MTU to the middle point of min and max of MTU
297        if not setup_gatt_mtu(self.cen_ad, self.bluetooth_gatt,
298                              self.gatt_callback, self.mtu):
299            return False
300
301        # create a characteristic with max MTU (217) bytes
302        char_value = []
303        for i in range(gatt_mtu_size['max']):
304            char_value.append(i)
305
306        self.cen_ad.droid.gattClientCharacteristicSetValue(
307            self.bluetooth_gatt, self.discovered_services_index,
308            self.test_service_index, self.WRITABLE_CHAR_UUID, char_value)
309
310        self.cen_ad.droid.gattClientCharacteristicSetWriteType(
311            self.bluetooth_gatt, self.discovered_services_index,
312            self.test_service_index, self.WRITABLE_CHAR_UUID,
313            gatt_characteristic['write_type_no_response'])
314
315        # write data to the characteristic of the Peripheral
316        self.cen_ad.droid.gattClientWriteCharacteristic(
317            self.bluetooth_gatt, self.discovered_services_index,
318            self.test_service_index, self.WRITABLE_CHAR_UUID)
319
320        event = self._server_wait(gatt_event['char_write_req'])
321        self.log.info("Received value with mtu = max MTU: {}".format(event[
322            'data']['value']))
323
324        # check the data received by Peripheral shall be truncated to
325        # (mtu - gatt_characteristic_attr_length['attr_2']) bytes
326        data_length = self.mtu - gatt_characteristic_attr_length['attr_2']
327        expected_value = char_value[:data_length]
328        self.assertEqual(
329            expected_value, event['data']['value'],
330            "Received value should have {} bytes".format(data_length))
331
332        # set the mtu to max MTU-1
333        self.mtu = gatt_mtu_size['max'] - 1
334        self.log.info("Set mtu to max MTU - 1 : {}".format(self.mtu))
335        data_length = self.mtu - gatt_characteristic_attr_length['attr_2']
336        if not setup_gatt_mtu(self.cen_ad, self.bluetooth_gatt,
337                              self.gatt_callback, self.mtu):
338            return False
339
340        # write the same characteric to Peripheral again
341        self.cen_ad.droid.gattClientWriteCharacteristic(
342            self.bluetooth_gatt, self.discovered_services_index,
343            self.test_service_index, self.WRITABLE_CHAR_UUID)
344
345        event = self._server_wait(gatt_event['char_write_req'])
346        self.log.info("Data received when mtu = max MTU - 1: {}".format(event[
347            'data']['value']))
348
349        # check the data received by Peripheral shall be truncated to
350        # (mtu - gatt_characteristic_attr_length['attr_2']) bytes
351        # when mtu is reduced
352        expected_value = char_value[:data_length]
353        self.assertEqual(
354            expected_value, event['data']['value'],
355            "Received value should have {} bytes".format(data_length))
356
357        # return the mtu to default
358        self.mtu = gatt_mtu_size['min']
359        self.log.info("Set mtu to min : {}".format(self.mtu))
360        if not setup_gatt_mtu(self.cen_ad, self.bluetooth_gatt,
361                              self.gatt_callback, self.mtu):
362            return False
363        return True
364
365    @BluetoothBaseTest.bt_test_wrap
366    @test_tracker_info(uuid='319eee6d-22d9-4498-bb15-21d0018e45e6')
367    def test_write_characteristic_stress(self):
368        """Stress test write characteristic value
369
370        Test write characteristic value using Write Request
371
372        1. Central: write WRITABLE_CHAR_UUID characteristic with char_value
373           using write request.
374        2. Peripheral: receive the written data.
375        3. Peripheral: send response with status 0 (success).
376        4. Central: make sure write callback is called.
377        5. Repeat steps 1-4 100 times.
378
379        Expected Result:
380        Verify that write request/response is properly delivered.
381
382        Returns:
383          Pass if True
384          Fail if False
385
386        TAGS: LE, GATT, Characteristic
387        Priority: 0
388        """
389        self.cen_ad.droid.gattClientRequestConnectionPriority(
390            self.bluetooth_gatt, gatt_connection_priority['high'])
391
392        bt_device_id = 0
393
394        self.cen_ad.droid.gattClientCharacteristicSetWriteType(
395            self.bluetooth_gatt, self.discovered_services_index,
396            self.test_service_index, self.WRITABLE_CHAR_UUID,
397            gatt_characteristic['write_type_default'])
398
399        for i in range(100):
400
401            char_value = []
402            for j in range(i, i + self.mtu - 3):
403                char_value.append(j % 256)
404
405            self.cen_ad.droid.gattClientCharacteristicSetValue(
406                self.bluetooth_gatt, self.discovered_services_index,
407                self.test_service_index, self.WRITABLE_CHAR_UUID, char_value)
408
409            self.cen_ad.droid.gattClientWriteCharacteristic(
410                self.bluetooth_gatt, self.discovered_services_index,
411                self.test_service_index, self.WRITABLE_CHAR_UUID)
412
413            event = self._server_wait(gatt_event['char_write_req'])
414
415            self.log.info("{} event found: {}".format(gatt_cb_strings[
416                'char_write_req'].format(self.gatt_server_callback), event[
417                    'data']['value']))
418            request_id = event['data']['requestId']
419            found_value = event['data']['value']
420            if found_value != char_value:
421                self.log.info("Values didn't match. Found: {}, "
422                              "Expected: {}".format(found_value, char_value))
423                return False
424
425            # only status is sent
426            status = 0
427            offset = 0
428            char_value_return = []
429            self.per_ad.droid.gattServerSendResponse(
430                self.gatt_server, bt_device_id, request_id, status, offset,
431                char_value_return)
432
433            event = self._client_wait(gatt_event['char_write'])
434            if event["data"]["Status"] != status:
435                self.log.error("Write status should be 0")
436                return False
437
438        return True
439
440    @BluetoothBaseTest.bt_test_wrap
441    @test_tracker_info(uuid='b19d42dc-58ba-4b20-b6c1-6628e7d21de4')
442    def test_write_descriptor_stress(self):
443        """Stress test write descriptor value
444
445        Stress test write descriptor value
446
447        1. Central: write WRITABLE_DESC_UUID descriptor with desc_value.
448        2. Peripheral: receive the written data.
449        3. Peripheral: send response with status 0 (success).
450        4. Central: make sure write callback is called.
451        5. Repeat 1-4 100 times
452
453        Expected Result:
454        Verify that write request/response is properly delivered.
455
456        Returns:
457          Pass if True
458          Fail if False
459
460        TAGS: LE, GATT, Descriptor
461        Priority: 0
462        """
463        self.cen_ad.droid.gattClientRequestConnectionPriority(
464            self.bluetooth_gatt, gatt_connection_priority['high'])
465
466        for i in range(100):
467
468            desc_value = []
469            for j in range(i, i + self.mtu - 3):
470                desc_value.append(j % 256)
471
472            self.cen_ad.droid.gattClientDescriptorSetValue(
473                self.bluetooth_gatt, self.discovered_services_index,
474                self.test_service_index, self.WRITABLE_CHAR_UUID,
475                self.WRITABLE_DESC_UUID, desc_value)
476
477            self.cen_ad.droid.gattClientWriteDescriptor(
478                self.bluetooth_gatt, self.discovered_services_index,
479                self.test_service_index, self.WRITABLE_CHAR_UUID,
480                self.WRITABLE_DESC_UUID)
481
482            event = self._server_wait(gatt_event['desc_write_req'])
483
484            self.log.info("{} event found: {}".format(gatt_cb_strings[
485                'char_write_req'].format(self.gatt_server_callback), event[
486                    'data']['value']))
487
488            request_id = event['data']['requestId']
489            self.assertEqual(True, event['data']['responseNeeded'],
490                             "Should need response")
491            self.assertEqual(desc_value, event['data']['value'])
492            self.assertEqual(0, event['data']['offset'])
493
494            bt_device_id = 0
495            status = 0
496            #both offset and return value don't matter, just the status
497            offset = 0
498            self.per_ad.droid.gattServerGetConnectedDevices(self.gatt_server)
499            self.per_ad.droid.gattServerSendResponse(
500                self.gatt_server, bt_device_id, request_id, status, offset, [])
501
502            event = self._client_wait(gatt_event['desc_write'])
503            self.assertEqual(status, event["data"]["Status"],
504                             "Write status should be 0")
505            # Write response doesn't carry any data except status
506        return True
507
508    @BluetoothBaseTest.bt_test_wrap
509    @test_tracker_info(uuid='74c147eb-2702-4cd8-be1f-efff3e9eaa6c')
510    def test_write_characteristic_no_resp_stress(self):
511        """Stress test write characteristic value
512
513        Stress test write characteristic value using Write Command
514
515        1. Central: write WRITABLE_CHAR_UUID characteristic with char_value
516           using write command.
517        2. Central: make sure write callback is called.
518        3. Peripheral: receive the written data.
519        4. Repeat steps 1-3 100 times.
520
521        Expected Result:
522        Verify that write command is properly delivered.
523
524        Returns:
525          Pass if True
526          Fail if False
527
528        TAGS: LE, GATT, Characteristic
529        Priority: 0
530        """
531        self.cen_ad.droid.gattClientRequestConnectionPriority(
532            self.bluetooth_gatt, gatt_connection_priority['high'])
533
534        bt_device_id = 0
535
536        self.cen_ad.droid.gattClientCharacteristicSetWriteType(
537            self.bluetooth_gatt, self.discovered_services_index,
538            self.test_service_index, self.WRITABLE_CHAR_UUID,
539            gatt_characteristic['write_type_no_response'])
540
541        for i in range(100):
542            char_value = []
543            for j in range(i, i + self.mtu - 3):
544                char_value.append(j % 256)
545
546            self.cen_ad.droid.gattClientCharacteristicSetValue(
547                self.bluetooth_gatt, self.discovered_services_index,
548                self.test_service_index, self.WRITABLE_CHAR_UUID, char_value)
549
550            self.cen_ad.droid.gattClientWriteCharacteristic(
551                self.bluetooth_gatt, self.discovered_services_index,
552                self.test_service_index, self.WRITABLE_CHAR_UUID)
553
554            # client shall not wait for server, get complete event right away
555            event = self._client_wait(gatt_event['char_write'])
556            if event["data"]["Status"] != 0:
557                self.log.error("Write status should be 0")
558                return False
559
560            event = self._server_wait(gatt_event['char_write_req'])
561
562            self.log.info("{} event found: {}".format(gatt_cb_strings[
563                'char_write_req'].format(self.gatt_server_callback), event[
564                    'data']['value']))
565            request_id = event['data']['requestId']
566            found_value = event['data']['value']
567            if found_value != char_value:
568                self.log.info("Values didn't match. Found: {}, "
569                              "Expected: {}".format(found_value, char_value))
570                return False
571
572        return True
573