• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Lint as: python2, python3
2from __future__ import absolute_import
3from __future__ import division
4from __future__ import print_function
5
6import json
7import logging
8import uuid
9import xml.etree.ElementTree as ET
10
11import common
12from autotest_lib.client.common_lib import error
13from autotest_lib.server.cros.bluetooth import bluetooth_adapter_tests
14from six.moves import range
15
16
17class bluetooth_SDP_Test(object):
18    """Base class with Properties and methods common across SDP tests"""
19    version = 1
20
21    MIN_ATTR_BYTE_CNT                = 7
22    MAX_ATTR_BYTE_CNT                = 300
23
24    SDP_SERVER_CLASS_ID              = 0x1000
25    GAP_CLASS_ID                     = 0x1800
26    BROWSE_GROUP_LIST_ATTR_ID        = 0x0005
27    PUBLIC_BROWSE_ROOT               = 0x1002
28
29    DOCUMENTATION_URL_ATTR_ID        = 0x000A
30    CLIENT_EXECUTABLE_URL_ATTR_ID    = 0x000B
31    ICON_URL_ATTR_ID                 = 0x000C
32    PROTOCOL_DESCRIPTOR_LIST_ATTR_ID = 0x0004
33    L2CAP_UUID                       = 0x0100
34    ATT_UUID                         = 0x0007
35    ATT_PSM                          = 0x001F
36    PNP_INFORMATION_CLASS_ID         = 0x1200
37    VERSION_NUMBER_LIST_ATTR_ID      = 0x0200
38    SERVICE_DATABASE_STATE_ATTR_ID   = 0x0201
39    AVRCP_TG_CLASS_ID                = 0x110C
40    PROFILE_DESCRIPTOR_LIST_ATTR_ID  = 0x0009
41    ADDITIONAL_PROTOCOLLIST_ATTR_ID  = 0x000D
42
43    FAKE_SERVICE_PATH                = '/autotest/fake_service'
44    BLUEZ_URL                        = 'http://www.bluez.org/'
45
46    FAKE_SERVICE_CLASS_ID            = 0xCDEF
47    FAKE_ATTRIBUTE_VALUE             = 42
48    LANGUAGE_BASE_ATTRIBUTE_ID       = 0x0006
49
50    FAKE_GENERAL_ATTRIBUTE_IDS       = [
51                                        0x0003, # TP/SERVER/SA/BV-04-C
52                                        0x0002, # TP/SERVER/SA/BV-06-C
53                                        0x0007, # TP/SERVER/SA/BV-07-C
54                                        0x0008, # TP/SERVER/SA/BV-10-C
55                                        # TP/SERVER/SA/BV-09-C:
56                                        LANGUAGE_BASE_ATTRIBUTE_ID
57                                       ]
58
59    FAKE_LANGUAGE_ATTRIBUTE_OFFSETS  = [
60                                        0x0000, # TP/SERVER/SA/BV-12-C
61                                        0x0001, # TP/SERVER/SA/BV-13-C
62                                        0x0002  # TP/SERVER/SA/BV-14-C
63                                       ]
64
65    BLUETOOTH_BASE_UUID              = 0x0000000000001000800000805F9B34FB
66    SERVICE_CLASS_ID_ATTR_ID         = 0x0001
67    ERROR_CODE_INVALID_RECORD_HANDLE = 0x0002
68    ERROR_CODE_INVALID_SYNTAX        = 0x0003
69    ERROR_CODE_INVALID_PDU_SIZE      = 0x0004
70    INVALID_RECORD_HANDLE            = 0xFEEE
71    INVALID_SYNTAX_REQUEST           = '123'
72    INVALID_PDU_SIZE                 = 11
73
74
75    def build_service_record(self):
76        """Build SDP record manually for the fake service.
77
78        @return resulting record as string
79
80        """
81        value = ET.Element('uint16', {'value': str(self.FAKE_ATTRIBUTE_VALUE)})
82
83        sdp_record = ET.Element('record')
84
85        service_id_attr = ET.Element(
86            'attribute', {'id': str(self.SERVICE_CLASS_ID_ATTR_ID)})
87        service_id_attr.append(
88            ET.Element('uuid', {'value': '0x%X' % self.FAKE_SERVICE_CLASS_ID}))
89        sdp_record.append(service_id_attr)
90
91        for attr_id in self.FAKE_GENERAL_ATTRIBUTE_IDS:
92            attr = ET.Element('attribute', {'id': str(attr_id)})
93            attr.append(value)
94            sdp_record.append(attr)
95
96        for offset in self.FAKE_LANGUAGE_ATTRIBUTE_OFFSETS:
97            attr_id = self.FAKE_ATTRIBUTE_VALUE + offset
98            attr = ET.Element('attribute', {'id': str(attr_id)})
99            attr.append(value)
100            sdp_record.append(attr)
101
102        sdp_record_str = ('<?xml version="1.0" encoding="UTF-8"?>' +
103                          ET.tostring(sdp_record).decode('utf-8'))
104        return sdp_record_str
105
106
107class bluetooth_SDP_ServiceAttributeRequest(bluetooth_SDP_Test,
108    bluetooth_adapter_tests.BluetoothAdapterTests):
109    """
110    Verify the correct behaviour of the device when searching for attributes of
111    services.
112    """
113    version = 1
114
115    MAX_REC_CNT                      = 3
116
117    SERVICE_RECORD_HANDLE_ATTR_ID    = 0x0000
118
119    NON_EXISTING_ATTRIBUTE_ID        = 0xFEDC
120
121    @staticmethod
122    def assert_equal(actual, expected):
123        """Verify that |actual| is equal to |expected|.
124
125        @param actual: The value we got.
126        @param expected: The value we expected.
127        @raise error.TestFail: If the values are unequal.
128        """
129        if actual != expected:
130            raise error.TestFail(
131                'Expected |%s|, got |%s|' % (expected, actual))
132
133
134    @staticmethod
135    def assert_nonempty_list(value):
136        """Verify that |value| is a list, and that the list is non-empty.
137
138        @param value: The value to check.
139        @raise error.TestFail: If the value is not a list, or is empty.
140        """
141        if not isinstance(value, list):
142            raise error.TestFail('Value is not a list. Got |%s|.' % value)
143
144        if value == []:
145            raise error.TestFail('List is empty')
146
147
148    def get_single_handle(self, class_id):
149        """Send a Service Search Request to get a handle for specific class ID.
150
151        @param class_id: The class that we want a handle for.
152        @return The record handle, as an int.
153        @raise error.TestFail: If we failed to retrieve a handle.
154        """
155        res = json.loads(self.tester.service_search_request([class_id],
156                         self.MAX_REC_CNT, dict()))
157        if not (isinstance(res, list) and len(res) > 0):
158            raise error.TestFail(
159                    'Failed to retrieve handle for 0x%x' % class_id)
160        return res[0]
161
162
163    # TODO(quiche): Place this after get_attribute(), so all the tests are
164    # grouped together.
165    def test_record_handle_attribute(self):
166        """Implementation of test TP/SERVER/SA/BV-01-C from SDP Specification.
167
168        @raise error.TestFail: If the DUT failed the test.
169        """
170        # Send Service Search Request to find out record handle for
171        # SDP Server service.
172        record_handle = self.get_single_handle(self.SDP_SERVER_CLASS_ID)
173
174        # Send Service Attribute Request for Service Record Handle Attribute.
175        res = json.loads(self.tester.service_attribute_request(
176                  record_handle,
177                  self.MAX_ATTR_BYTE_CNT,
178                  [self.SERVICE_RECORD_HANDLE_ATTR_ID], {}))
179
180        # Ensure that returned attribute is correct.
181        self.assert_equal(res,
182                          [self.SERVICE_RECORD_HANDLE_ATTR_ID, record_handle])
183
184
185    def get_attribute(self, class_id, attr_id):
186        """Get a single attribute of a single service
187
188        @param class_id: Class ID of service to check.
189        @param attr_id: ID of attribute to check.
190        @return attribute value if attribute exists, None otherwise
191
192        """
193        record_handle = self.get_single_handle(class_id)
194        res = json.loads(self.tester.service_attribute_request(
195                  record_handle, self.MAX_ATTR_BYTE_CNT, [attr_id], {}))
196        if isinstance(res, list) and len(res) == 2 and res[0] == attr_id:
197            return res[1]
198        return None
199
200
201    # TODO(quiche): Move this up, to be grouped with the other |assert|
202    # methods.
203    def assert_attribute_equals(self, class_id, attr_id, expected_value):
204        """Verify that |attr_id| of service with |class_id| has |expected_value|
205
206        @param class_id: Class ID of service to check.
207        @param attr_id: ID of attribute to check.
208        @param expected_value: The expected value for the attribute.
209        @raise error.TestFail: If the actual value differs from |expected_value|
210        """
211        self.assert_equal(self.get_attribute(class_id, attr_id),
212                          expected_value)
213
214
215    def test_browse_group_attribute(self):
216        """Implementation of test TP/SERVER/SA/BV-08-C from SDP Specification.
217
218        @raise error.TestFail: If the DUT failed the test.
219        """
220        self.assert_attribute_equals(self.GAP_CLASS_ID,
221                                     self.BROWSE_GROUP_LIST_ATTR_ID,
222                                     [self.PUBLIC_BROWSE_ROOT])
223
224
225    def test_icon_url_attribute(self):
226        """Implementation of test TP/SERVER/SA/BV-11-C from SDP Specification.
227
228        @raise error.TestFail: If the DUT failed the test.
229        """
230        self.assert_attribute_equals(self.GAP_CLASS_ID,
231                                     self.ICON_URL_ATTR_ID,
232                                     self.BLUEZ_URL)
233
234
235    def test_documentation_url_attribute(self):
236        """Implementation of test TP/SERVER/SA/BV-18-C from SDP Specification.
237
238        @raise error.TestFail: If the DUT failed the test.
239        """
240        self.assert_attribute_equals(self.GAP_CLASS_ID,
241                                     self.DOCUMENTATION_URL_ATTR_ID,
242                                     self.BLUEZ_URL)
243
244
245    def test_client_executable_url_attribute(self):
246        """Implementation of test TP/SERVER/SA/BV-19-C from SDP Specification.
247
248        @raise error.TestFail: If the DUT failed the test.
249        """
250        self.assert_attribute_equals(self.GAP_CLASS_ID,
251                                     self.CLIENT_EXECUTABLE_URL_ATTR_ID,
252                                     self.BLUEZ_URL)
253
254
255    def test_protocol_descriptor_list_attribute(self):
256        """Implementation of test TP/SERVER/SA/BV-05-C from SDP Specification.
257
258        @raise error.TestFail: If the DUT failed the test.
259        """
260        value = self.get_attribute(self.GAP_CLASS_ID,
261                                   self.PROTOCOL_DESCRIPTOR_LIST_ATTR_ID)
262
263        # The first-layer protocol is L2CAP, using the PSM for ATT protocol.
264        self.assert_equal(value[0], [self.L2CAP_UUID, self.ATT_PSM])
265
266        # The second-layer protocol is ATT. The additional parameters are
267        # ignored, since they may reasonably vary between implementations.
268        self.assert_equal(value[1][0], self.ATT_UUID)
269
270
271    def test_continuation_state(self):
272        """Implementation of test TP/SERVER/SA/BV-03-C from SDP Specification.
273
274        @raise error.TestFail: If the DUT failed the test.
275        """
276        record_handle = self.get_single_handle(self.PNP_INFORMATION_CLASS_ID)
277        self.assert_nonempty_list(
278            json.loads(self.tester.service_attribute_request(
279                record_handle, self.MIN_ATTR_BYTE_CNT, [[0, 0xFFFF]], {})))
280
281
282    def test_version_list_attribute(self):
283        """Implementation of test TP/SERVER/SA/BV-15-C from SDP Specification.
284
285        @raise error.TestFail: If the DUT failed the test.
286        """
287        self.assert_nonempty_list(
288            self.get_attribute(self.SDP_SERVER_CLASS_ID,
289                self.VERSION_NUMBER_LIST_ATTR_ID))
290
291
292    def test_service_database_state_attribute(self):
293        """Implementation of test TP/SERVER/SA/BV-16-C from SDP Specification.
294
295        @raise error.TestFail: If the DUT failed the test.
296        """
297        state = self.get_attribute(self.SDP_SERVER_CLASS_ID,
298                                   self.SERVICE_DATABASE_STATE_ATTR_ID)
299        if not isinstance(state, int):
300            raise error.TestFail('State is not an int: %s' % state)
301
302
303    def test_profile_descriptor_list_attribute(self):
304        """Implementation of test TP/SERVER/SA/BV-17-C from SDP Specification.
305
306        @raise error.TestFail: If list attribute not correct form.
307
308        """
309        profile_list = self.get_attribute(self.PNP_INFORMATION_CLASS_ID,
310                                          self.PROFILE_DESCRIPTOR_LIST_ATTR_ID)
311
312        if not isinstance(profile_list, list):
313            raise error.TestFail('Value is not a list')
314        self.assert_equal(len(profile_list), 1)
315
316        if not isinstance(profile_list[0], list):
317            raise error.TestFail('Item is not a list')
318        self.assert_equal(len(profile_list[0]), 2)
319
320        self.assert_equal(profile_list[0][0], self.PNP_INFORMATION_CLASS_ID)
321
322
323    def test_additional_protocol_descriptor_list_attribute(self):
324        """Implementation of test TP/SERVER/SA/BV-21-C from SDP Specification.
325
326        @raise error.TestFail: If the DUT failed the test.
327
328        """
329
330        """AVRCP is not supported by Chromebook and no need to run this test
331        self.assert_nonempty_list(
332            self.get_attribute(self.AVRCP_TG_CLASS_ID,
333                self.ADDITIONAL_PROTOCOLLIST_ATTR_ID))
334        """
335
336    def test_non_existing_attribute(self):
337        """Implementation of test TP/SERVER/SA/BV-20-C from SDP Specification.
338
339        @raise error.TestFail: If the DUT failed the test.
340        """
341        record_handle = self.get_single_handle(self.FAKE_SERVICE_CLASS_ID)
342        res = json.loads(self.tester.service_attribute_request(
343                  record_handle, self.MAX_ATTR_BYTE_CNT,
344                  [self.NON_EXISTING_ATTRIBUTE_ID], {}))
345        self.assert_equal(res, [])
346
347
348    def test_fake_attributes(self):
349        """Test values of attributes of the fake service record.
350
351        @raise error.TestFail: If the DUT failed the test.
352        """
353        for attr_id in self.FAKE_GENERAL_ATTRIBUTE_IDS:
354            self.assert_attribute_equals(self.FAKE_SERVICE_CLASS_ID,
355                                         attr_id, self.FAKE_ATTRIBUTE_VALUE)
356
357        for offset in self.FAKE_LANGUAGE_ATTRIBUTE_OFFSETS:
358            record_handle = self.get_single_handle(self.FAKE_SERVICE_CLASS_ID)
359
360            lang_base = json.loads(self.tester.service_attribute_request(
361                            record_handle, self.MAX_ATTR_BYTE_CNT,
362                            [self.LANGUAGE_BASE_ATTRIBUTE_ID], {}))
363            attr_id = lang_base[1] + offset
364
365            response = json.loads(self.tester.service_attribute_request(
366                record_handle, self.MAX_ATTR_BYTE_CNT, [attr_id], {}))
367            self.assert_equal(response, [attr_id, self.FAKE_ATTRIBUTE_VALUE])
368
369
370    def test_invalid_record_handle(self):
371        """Implementation of test TP/SERVER/SA/BI-01-C from SDP Specification.
372
373        @raise error.TestFail: If the DUT failed the test.
374        """
375        res = json.loads(self.tester.service_attribute_request(
376                          self.INVALID_RECORD_HANDLE, self.MAX_ATTR_BYTE_CNT,
377                          [self.NON_EXISTING_ATTRIBUTE_ID], {}))
378        self.assert_equal(res, self.ERROR_CODE_INVALID_RECORD_HANDLE)
379
380
381    def test_invalid_request_syntax(self):
382        """Implementation of test TP/SERVER/SA/BI-02-C from SDP Specification.
383
384        @raise error.TestFail: If the DUT failed the test.
385        """
386        record_handle = self.get_single_handle(self.SDP_SERVER_CLASS_ID)
387        res = json.loads(self.tester.service_attribute_request(
388                          record_handle,
389                          self.MAX_ATTR_BYTE_CNT,
390                          [self.SERVICE_RECORD_HANDLE_ATTR_ID],
391                          {'invalid_request':self.INVALID_SYNTAX_REQUEST}))
392        self.assert_equal(res, self.ERROR_CODE_INVALID_SYNTAX)
393
394
395    def test_invalid_pdu_size(self):
396        """Implementation of test TP/SERVER/SA/BI-03-C from SDP Specification.
397
398        @raise error.TestFail: If the DUT failed the test.
399        """
400        opts = dict({'forced_pdu_size':self.INVALID_PDU_SIZE})
401        record_handle = self.get_single_handle(self.SDP_SERVER_CLASS_ID)
402        res = json.loads(self.tester.service_attribute_request(record_handle,
403            self.MAX_ATTR_BYTE_CNT, [self.SERVICE_RECORD_HANDLE_ATTR_ID], opts))
404        self.assert_equal(res, self.ERROR_CODE_INVALID_PDU_SIZE)
405
406
407    def correct_request_att_request_test(self):
408        """Run basic tests for Service Attribute Request."""
409        # Connect to the DUT via L2CAP using SDP socket.
410        self.tester.connect(self.adapter['Address'])
411
412        self.test_record_handle_attribute()
413        self.test_browse_group_attribute()
414        self.test_icon_url_attribute()
415        self.test_documentation_url_attribute()
416        self.test_client_executable_url_attribute()
417        self.test_protocol_descriptor_list_attribute()
418        self.test_continuation_state()
419        self.test_version_list_attribute()
420        self.test_service_database_state_attribute()
421        self.test_profile_descriptor_list_attribute()
422        self.test_additional_protocol_descriptor_list_attribute()
423        self.test_fake_attributes()
424        self.test_non_existing_attribute()
425        self.test_invalid_record_handle()
426        self.test_invalid_pdu_size()
427        self.test_invalid_request_syntax()
428
429
430    def sdp_service_attribute_request_test(self, device):
431        """Runs service attribute request test"""
432
433        if self.host.btpeer.get_platform() != 'RASPI':
434            raise error.TestNAError('Test only runs on Raspi')
435
436        self.tester = device
437        # Reset the adapter to the powered on, discoverable state.
438        if not self.bluetooth_facade.reset_on():
439            raise error.TestFail('DUT adapter could not be powered on')
440        if not self.bluetooth_facade.set_discoverable(True):
441            raise error.TestFail('DUT could not be set as discoverable')
442
443        self.adapter = self.bluetooth_facade.get_adapter_properties()
444
445        # Create a fake service record in order to test attributes,
446        # that are not present in any of existing services.
447        uuid128 = ((self.FAKE_SERVICE_CLASS_ID << 96) +
448                   self.BLUETOOTH_BASE_UUID)
449        uuid_str = str(uuid.UUID(int=uuid128))
450        sdp_record = self.build_service_record()
451        self.bluetooth_facade.register_profile(self.FAKE_SERVICE_PATH,
452                                     uuid_str,
453                                     {"ServiceRecord": sdp_record})
454
455        # Setup the tester as a generic computer.
456        if not self.tester.setup('computer'):
457            raise error.TestNAError('Tester could not be initialized')
458
459        self.correct_request_att_request_test()
460
461
462
463class bluetooth_SDP_ServiceBrowse(bluetooth_SDP_Test,
464    bluetooth_adapter_tests.BluetoothAdapterTests):
465    """
466    Verify that the IUT behave correct during Service Browse procedure.
467    """
468    version = 1
469
470    MAX_BROWSE_REC_CNT             = 100
471    MAX_ATTR_BYTE_CNT       = 300
472    SERVICE_CLASS_ID_LIST   = 0x0001
473    BROWSE_GROUP_DESCRIPTOR = 0x1001
474    GROUP_ID                = 0x0200
475
476
477    def get_attribute_ssr_sar(self, class_id, attr_id, size):
478        """Get service attributes using Service Search Request and Service
479        Attribute Request.
480
481        @param class_id: Class ID of service to check.
482        @param attr_id: ID of attribute to check.
483        @param size: Preferred size of UUID.
484
485        @return attribute value if attribute exists, None otherwise
486
487        """
488        handles = json.loads(self.tester.service_search_request(
489                      [class_id], self.MAX_BROWSE_REC_CNT,
490                      {'preferred_size':size}))
491
492        if not (isinstance(handles, list) and len(handles) > 0):
493            return None
494
495        res = []
496        for record_handle in handles:
497            value = json.loads(self.tester.service_attribute_request(
498                        record_handle, self.MAX_ATTR_BYTE_CNT, [attr_id], {}))
499            if not (isinstance(value, list) and len(value) == 2 and
500                value[0] == attr_id):
501                return None
502            res.append(value[1])
503
504        return res
505
506
507    def get_attribute_ssar(self, class_id, attr_id, size):
508        """Get service attributes using Service Search Attribute Request.
509
510        @param class_id: Class ID of service to check.
511        @param attr_id: ID of attribute to check.
512        @param size: Preferred size of UUID.
513
514        @return attribute value if attribute exists, None otherwise
515
516        """
517        response = json.loads(self.tester.service_search_attribute_request(
518                       [class_id], self.MAX_ATTR_BYTE_CNT, [attr_id],
519                       {'preferred_size':size}))
520
521        if not isinstance(response, list):
522            return None
523
524        res = []
525        for elem in response:
526            if not (isinstance(elem, list) and len(elem) == 2 and
527                    elem[0] == attr_id):
528                return None
529            res.append(elem[1])
530
531        return res
532
533
534    def test_attribute(self, class_id, attr_id, get_attribute):
535        """Test service attributes using 16-bit, 32-bit and 128-bit
536        size of UUID.
537
538        @param class_id: Class ID of service to check.
539        @param attr_id: ID of attribute to check.
540        @param get_attribute: Method to use to get an attribute value.
541
542        @return attribute value if attribute exists and values from three tests
543        are equal, None otherwise
544
545        """
546        result_16 = get_attribute(class_id, attr_id, 16)
547
548        for size in 32, 128:
549            result_cur = get_attribute(class_id, attr_id, size)
550            if result_16 != result_cur:
551                return None
552
553        return result_16
554
555
556    def service_browse(self, get_attribute):
557        """Execute a Service Browse procedure.
558
559        @param get_attribute: Method to use to get an attribute value.
560
561        @return sorted list of unique services on the DUT, or False if browse
562        did not finish correctly
563
564        """
565        # Find services on top of hierarchy.
566        root_services = self.test_attribute(self.PUBLIC_BROWSE_ROOT,
567                                            self.SERVICE_CLASS_ID_LIST,
568                                            get_attribute)
569
570        if not root_services:
571            return False
572
573        # Find additional browse groups.
574        group_ids = self.test_attribute(self.BROWSE_GROUP_DESCRIPTOR,
575                                        self.GROUP_ID,
576                                        get_attribute)
577        if not group_ids:
578            return False
579
580        # Find services from all browse groups.
581        all_services = []
582        for group_id in group_ids:
583            services = self.test_attribute(group_id,
584                                           self.SERVICE_CLASS_ID_LIST,
585                                           get_attribute)
586            if not services:
587                return False
588            all_services.extend(services)
589
590        # Ensure that root services are among all services.
591        for service in root_services:
592            if service not in all_services:
593                return False
594
595        # Sort all services and remove duplicates.
596        all_services.sort()
597        last = 0
598        for service in all_services[1:]:
599            if all_services[last] != service:
600                last += 1
601                all_services[last] = service
602
603        return all_services[:last + 1]
604
605
606    def correct_request_browse_test(self):
607        """Run basic tests for Service Browse procedure.
608
609        @return True if all tests finishes correctly, False otherwise
610
611        """
612
613        # Connect to the DUT via L2CAP using SDP socket.
614        self.tester.connect(self.adapter['Address'])
615
616        browse_ssar = self.service_browse(self.get_attribute_ssar)
617        if not browse_ssar:
618            return False
619
620        browse_ssr_sar = self.service_browse(self.get_attribute_ssr_sar)
621
622
623        # Ensure that two different browse methods return the same results.
624        return browse_ssar == browse_ssr_sar
625
626
627    def sdp_service_browse_test(self, device):
628        """Runs service browse test"""
629
630        if self.host.btpeer.get_platform() != 'RASPI':
631            raise error.TestNAError('Test only runs on Raspi')
632
633        self.tester = device
634        # Reset the adapter to the powered on, discoverable state.
635        if not (self.bluetooth_facade.reset_on() and
636                self.bluetooth_facade.set_discoverable(True)):
637            raise error.TestFail('DUT could not be reset to initial state')
638
639        self.adapter = self.bluetooth_facade.get_adapter_properties()
640
641        # Setup the tester as a generic computer.
642        if not self.tester.setup('computer'):
643            raise error.TestNAError('Tester could not be initialized')
644
645        # Since radio is involved, this test is not 100% reliable; instead we
646        # repeat a few times until it succeeds.
647        for failed_attempts in range(0, 5):
648            if self.correct_request_browse_test():
649                break
650        else:
651            raise error.TestFail('Expected device was not found')
652
653        # Record how many attempts this took, hopefully we'll one day figure out
654        # a way to reduce this to zero and then the loop above can go away.
655        self.write_perf_keyval({'failed_attempts': failed_attempts })
656
657
658class bluetooth_SDP_ServiceSearchAttributeRequest(bluetooth_SDP_Test,
659    bluetooth_adapter_tests.BluetoothAdapterTests):
660    """
661    Verify the correct behaviour of the device when searching for services and
662    attributes.
663    """
664
665
666    NON_EXISTING_SERVICE_CLASS_ID    = 0x9875
667    PUBLIC_BROWSE_GROUP_CLASS_ID     = 0x1002
668
669    NON_EXISTING_ATTRIBUTE_ID        = 0xABCD
670    SERVICE_CLASS_ID_ATTRIBUTE_ID    = 0x0001
671    VERSION_NUMBER_LIST_ATTR_ID      = 0x0200
672
673
674    def fail_test(self, testname, value):
675        """Raise an error for a particular SDP test.
676
677        @param testname: a string representation of the test name.
678        @param value: the value that did not pass muster.
679
680        """
681        raise error.TestFail('SDP test %s failed: got %s.' % (testname, value))
682
683
684    def test_non_existing(self, class_id, attr_id):
685        """Check that a single attribute of a single service does not exist
686
687        @param class_id: Class ID of service to check.
688        @param attr_id: ID of attribute to check.
689
690        @raises error.TestFail if service or attribute does exists.
691
692        """
693        for size in 16, 32, 128:
694            result = json.loads(self.tester.service_search_attribute_request(
695                         [class_id],
696                         self.MAX_ATTR_BYTE_CNT,
697                         [attr_id],
698                         {'preferred_size':size}))
699            if result != []:
700                raise error.TestFail('Attribute %s of class %s exists when it '
701                                     'should not!' % (class_id, attr_id))
702
703
704    def get_attribute_sssar(self, class_id, attr_id, size):
705        """Get a single attribute of a single service using Service Search
706        Attribute Request.
707
708        @param class_id: Class ID of service to check.
709        @param attr_id: ID of attribute to check.
710        @param size: Preferred size of UUID.
711
712        @return attribute value if attribute exists
713
714        @raises error.TestFail if attribute does not exist
715
716        """
717        res = json.loads(self.tester.service_search_attribute_request(
718                  [class_id], self.MAX_ATTR_BYTE_CNT, [attr_id],
719                  {'preferred_size':size}))
720
721        if (isinstance(res, list) and len(res) == 1 and
722            isinstance(res[0], list) and len(res[0]) == 2 and
723            res[0][0] == attr_id):
724            return res[0][1]
725
726        raise error.TestFail('Attribute %s of class %s does not exist! (size '
727                             '%s)' % (class_id, attr_id, size))
728
729
730    def test_attribute_sssar(self, class_id, attr_id):
731        """Test a single attribute of a single service using 16-bit, 32-bit and
732        128-bit size of UUID.
733
734        @param class_id: Class ID of service to check.
735        @param attr_id: ID of attribute to check.
736
737        @return attribute value if attribute exists and values from three tests
738        are equal
739
740        @raises error.TestFail if attribute doesn't exist or values not equal
741
742        """
743        result_16 = self.get_attribute_sssar(class_id, attr_id, 16)
744        for size in 32, 128:
745            result_cur = self.get_attribute_sssar(class_id, attr_id, size)
746            if result_16 != result_cur:
747                raise error.TestFail('Attribute test failed %s: expected %s, '
748                                     'got %s' % (size, result_16, result_cur))
749
750        return result_16
751
752
753    def test_non_existing_service(self):
754        """Implementation of test TP/SERVER/SSA/BV-01-C from SDP Specification.
755
756        @raises error.TestFail if test fails
757
758        """
759        self.test_non_existing(self.NON_EXISTING_SERVICE_CLASS_ID,
760                               self.SERVICE_CLASS_ID_ATTRIBUTE_ID)
761
762
763    def test_non_existing_attribute_sssar(self):
764        """Implementation of test TP/SERVER/SSA/BV-02-C from SDP Specification.
765
766        @raises error.TestFail if test fails
767
768        """
769        self.test_non_existing(self.PUBLIC_BROWSE_GROUP_CLASS_ID,
770                               self.NON_EXISTING_ATTRIBUTE_ID)
771
772
773    def test_non_existing_service_attribute(self):
774        """Implementation of test TP/SERVER/SSA/BV-03-C from SDP Specification.
775
776        @raises error.TestFail if test fails
777
778        """
779        self.test_non_existing(self.NON_EXISTING_SERVICE_CLASS_ID,
780                               self.NON_EXISTING_ATTRIBUTE_ID)
781
782
783    def test_existing_service_attribute(self):
784        """Implementation of test TP/SERVER/SSA/BV-04-C from SDP Specification.
785
786        @raises error.TestFail if test fails
787
788        """
789        value = self.test_attribute_sssar(self.SDP_SERVER_CLASS_ID,
790                                    self.SERVICE_CLASS_ID_ATTRIBUTE_ID)
791        if not value == [self.SDP_SERVER_CLASS_ID]:
792            self.fail_test('TP/SERVER/SSA/BV-04-C', value)
793
794
795    def test_service_database_state_attribute_sssar(self):
796        """Implementation of test TP/SERVER/SSA/BV-08-C from SDP Specification.
797
798        @raises error.TestFail if test fails
799
800        """
801        value = self.test_attribute_sssar(self.SDP_SERVER_CLASS_ID,
802                                    self.SERVICE_DATABASE_STATE_ATTR_ID)
803        if not isinstance(value, int):
804            self.fail_test('TP/SERVER/SSA/BV-08-C', value)
805
806
807    def test_protocol_descriptor_list_attribute_sssar(self):
808        """Implementation of test TP/SERVER/SSA/BV-11-C from SDP Specification.
809
810        @raises error.TestFail if test fails
811
812        """
813        value = self.test_attribute_sssar(self.GAP_CLASS_ID,
814                                    self.PROTOCOL_DESCRIPTOR_LIST_ATTR_ID)
815
816        # The first-layer protocol is L2CAP, using the PSM for ATT protocol.
817        if value[0] != [self.L2CAP_UUID, self.ATT_PSM]:
818            self.fail_test('TP/SERVER/SSA/BV-11-C', value)
819
820        # The second-layer protocol is ATT. The additional parameters are
821        # ignored, since they may reasonably vary between implementations.
822        if value[1][0] != self.ATT_UUID:
823            self.fail_test('TP/SERVER/SSA/BV-11-C', value)
824
825
826
827    def test_browse_group_attribute_sssar(self):
828        """Implementation of test TP/SERVER/SSA/BV-12-C from SDP Specification.
829
830        @raises error.TestFail if test fails
831
832        """
833        value = self.test_attribute_sssar(self.GAP_CLASS_ID,
834                                    self.BROWSE_GROUP_LIST_ATTR_ID)
835        if not value == [self.PUBLIC_BROWSE_ROOT]:
836            self.fail_test('TP/SERVER/SSA/BV-12-C', value)
837
838
839    def test_icon_url_attribute_sssar(self):
840        """Implementation of test TP/SERVER/SSA/BV-15-C from SDP Specification.
841
842        @raises error.TestFail if test fails
843
844        """
845        value = self.test_attribute_sssar(self.GAP_CLASS_ID,
846                                    self.ICON_URL_ATTR_ID)
847        if not value == self.BLUEZ_URL:
848            self.fail_test('TP/SERVER/SSA/BV-15-C', value)
849
850
851    def test_version_list_attribute_sssar(self):
852        """Implementation of test TP/SERVER/SSA/BV-19-C from SDP Specification.
853
854        @raises error.TestFail if test fails
855
856        """
857        value = self.test_attribute_sssar(self.SDP_SERVER_CLASS_ID,
858                                    self.VERSION_NUMBER_LIST_ATTR_ID)
859        if not isinstance(value, list) and value != []:
860            self.fail_test('TP/SERVER/SSA/BV-19-C', value)
861
862
863    def test_profile_descriptor_list_attribute_sssar(self):
864        """Implementation of test TP/SERVER/SSA/BV-20-C from SDP Specification.
865
866        @raises error.TestFail if test fails
867
868        """
869        value = self.test_attribute_sssar(self.PNP_INFORMATION_CLASS_ID,
870                                    self.PROFILE_DESCRIPTOR_LIST_ATTR_ID)
871        if not (isinstance(value, list) and len(value) == 1 and
872                isinstance(value[0], list) and len(value[0]) == 2 and
873                value[0][0] == self.PNP_INFORMATION_CLASS_ID):
874            self.fail_test('TP/SERVER/SSA/BV-20-C', value)
875
876
877    def test_documentation_url_attribute_sssar(self):
878        """Implementation of test TP/SERVER/SSA/BV-21-C from SDP Specification.
879
880        @raises error.TestFail if test fails
881
882        """
883        value = self.test_attribute_sssar(self.GAP_CLASS_ID,
884                                    self.DOCUMENTATION_URL_ATTR_ID)
885        if not value == self.BLUEZ_URL:
886            self.fail_test('TP/SERVER/SSA/BV-21-C', value)
887
888
889    def test_client_executable_url_attribute_sssar(self):
890        """Implementation of test TP/SERVER/SSA/BV-22-C from SDP Specification.
891
892        @raises error.TestFail if test fails
893
894        """
895        value = self.test_attribute_sssar(self.GAP_CLASS_ID,
896                                    self.CLIENT_EXECUTABLE_URL_ATTR_ID)
897        if not value == self.BLUEZ_URL:
898            self.fail_test('TP/SERVER/SSA/BV-22-C', value)
899
900
901    def test_additional_protocol_descriptor_list_attribute_sssar(self):
902        """Implementation of test TP/SERVER/SSA/BV-23-C from SDP Specification.
903
904        @raises error.TestFail if test fails
905
906        """
907
908        """AVRCP is not supported by Chromebook and no need to run this test
909        value = self.test_attribute_sssar(self.AVRCP_TG_CLASS_ID,
910                                    self.ADDITIONAL_PROTOCOLLIST_ATTR_ID)
911        if not isinstance(value, list) and value != []:
912            self.fail_test('TP/SERVER/SSA/BV-23-C', value)
913        """
914
915    def test_fake_attributes_sssar(self):
916        """Test values of attributes of the fake service record.
917
918        @raises error.TestFail if test fails
919
920        """
921        for attr_id in self.FAKE_GENERAL_ATTRIBUTE_IDS:
922            value = self.test_attribute_sssar(self.FAKE_SERVICE_CLASS_ID,
923                                              attr_id)
924            if value != self.FAKE_ATTRIBUTE_VALUE:
925                self.fail_test('fake service attributes', value)
926
927        for offset in self.FAKE_LANGUAGE_ATTRIBUTE_OFFSETS:
928            lang_base = self.test_attribute_sssar(self.FAKE_SERVICE_CLASS_ID,
929                                            self.LANGUAGE_BASE_ATTRIBUTE_ID)
930            attr_id = lang_base + offset
931            value = self.test_attribute_sssar(self.FAKE_SERVICE_CLASS_ID,
932                                              attr_id)
933            if value != self.FAKE_ATTRIBUTE_VALUE:
934                self.fail_test('fake service attributes', value)
935
936
937    def test_continuation_state_sssar(self):
938        """Implementation of test TP/SERVER/SSA/BV-06-C from SDP Specification.
939
940        @raises error.TestFail if test fails
941
942        """
943        for size in 16, 32, 128:
944            # This request should generate a long response, which will be
945            # split into 98 chunks.
946            value = json.loads(self.tester.service_search_attribute_request(
947                        [self.PUBLIC_BROWSE_GROUP_CLASS_ID],
948                        self.MIN_ATTR_BYTE_CNT,
949                        [[0, 0xFFFF]], {'preferred_size':size}))
950            if not isinstance(value, list) or value == []:
951                self.fail_test('TP/SERVER/SSA/BV-06-C', value)
952
953
954    def test_invalid_request_syntax_sssar(self):
955        """Implementation of test TP/SERVER/SSA/BI-01-C from SDP Specification.
956
957        @raises error.TestFail if test fails
958
959        """
960        for size in 16, 32, 128:
961            value = json.loads(self.tester.service_search_attribute_request(
962                        [self.SDP_SERVER_CLASS_ID],
963                        self.MAX_ATTR_BYTE_CNT,
964                        [self.SERVICE_CLASS_ID_ATTRIBUTE_ID],
965                        {'preferred_size':size,
966                        'invalid_request':'9875'}))
967            if value != self.ERROR_CODE_INVALID_SYNTAX:
968                self.fail_test('TP/SERVER/SSA/BI-01-C', value)
969
970
971    def test_invalid_pdu_size_sssar(self):
972        """Implementation of test TP/SERVER/SSA/BI-02-C from SDP Specification.
973
974        @raises error.TestFail if test fails
975
976        """
977        for size in 16, 32, 128:
978            value = json.loads(self.tester.service_search_attribute_request(
979                        [self.SDP_SERVER_CLASS_ID],
980                        self.MAX_ATTR_BYTE_CNT,
981                        [self.SERVICE_CLASS_ID_ATTRIBUTE_ID],
982                        {'preferred_size':size,
983                        'forced_pdu_size':100}))
984            if value != self.ERROR_CODE_INVALID_PDU_SIZE:
985                self.fail_test('TP/SERVER/SSA/BI-02-C', value)
986
987
988    def correct_request_search_att_test(self):
989        """Run tests for Service Search Attribute request.
990
991        @raises error.TestFail if any test fails
992
993        """
994        # connect to the DUT via L2CAP using SDP socket
995        self.tester.connect(self.adapter['Address'])
996
997        self.test_non_existing_service()
998        self.test_non_existing_attribute_sssar()
999        self.test_non_existing_service_attribute()
1000        #self.test_existing_service_attribute()
1001        self.test_service_database_state_attribute_sssar()
1002        self.test_protocol_descriptor_list_attribute_sssar()
1003        self.test_browse_group_attribute_sssar()
1004        self.test_icon_url_attribute_sssar()
1005        self.test_version_list_attribute_sssar()
1006        self.test_profile_descriptor_list_attribute_sssar()
1007        self.test_documentation_url_attribute_sssar()
1008        self.test_client_executable_url_attribute_sssar()
1009        self.test_additional_protocol_descriptor_list_attribute_sssar()
1010        self.test_fake_attributes_sssar()
1011        self.test_continuation_state_sssar()
1012        self.test_invalid_request_syntax_sssar()
1013        self.test_invalid_pdu_size_sssar()
1014        logging.info('correct_request finished successfully!')
1015
1016
1017    def sdp_service_search_attribute_request_test(self, device):
1018        """Runs service search attribute request test"""
1019
1020        if self.host.btpeer.get_platform() != 'RASPI':
1021            raise error.TestNAError('Test only runs on Raspi')
1022
1023        self.tester = device
1024        # Reset the adapter to the powered on, discoverable state.
1025        if not self.bluetooth_facade.reset_on():
1026            raise error.TestFail('DUT adapter could not be powered on')
1027        if not self.bluetooth_facade.set_discoverable(True):
1028            raise error.TestFail('DUT could not be set as discoverable')
1029
1030        self.adapter = self.bluetooth_facade.get_adapter_properties()
1031
1032        # Create a fake service record in order to test attributes,
1033        # that are not present in any of existing services.
1034        uuid128 = ((self.FAKE_SERVICE_CLASS_ID << 96) +
1035                   self.BLUETOOTH_BASE_UUID)
1036        uuid_str = str(uuid.UUID(int=uuid128))
1037        sdp_record = self.build_service_record()
1038        self.bluetooth_facade.register_profile(self.FAKE_SERVICE_PATH,
1039                                     uuid_str,
1040                                     {"ServiceRecord": sdp_record})
1041
1042        # Setup the tester as a generic computer.
1043        if not self.tester.setup('computer'):
1044            raise error.TestNAError('Tester could not be initialized')
1045
1046        # Since radio is involved, this test is not 100% reliable; instead we
1047        # repeat a few times until it succeeds.
1048        passing = False
1049        for failed_attempts in range(0, 4):
1050            try:
1051                self.correct_request_search_att_test()
1052                passing = True
1053            except error.TestFail as e:
1054                logging.warning('Ignoring error: %s', e)
1055            if passing:
1056                break
1057        else:
1058            self.correct_request_search_att_test()
1059
1060        # Record how many attempts this took, hopefully we'll one day figure out
1061        # a way to reduce this to zero and then the loop above can go away.
1062        self.write_perf_keyval({'failed_attempts': failed_attempts})
1063
1064
1065class bluetooth_SDP_ServiceSearchRequestBasic(
1066    bluetooth_adapter_tests.BluetoothAdapterTests):
1067    """
1068    Verify the correct behaviour of the device when searching for services.
1069    """
1070    version = 1
1071
1072    SDP_SERVER_CLASS_ID                = 0x1000
1073    NO_EXISTING_SERVICE_CLASS_ID       = 0x0001
1074    FAKE_SERVICES_CNT                  = 300
1075    FAKE_SERVICES_PATH                 = '/autotest/fake_service_'
1076    FAKE_SERVICES_CLASS_ID             = 0xABCD
1077    BLUETOOTH_BASE_UUID                = 0x0000000000001000800000805F9B34FB
1078    SSRB_INVALID_PDU_SIZE              = 9875
1079    ERROR_CODE_INVALID_REQUEST_SYNTAX  = 0x0003
1080    ERROR_CODE_INVALID_PDU_SIZE        = 0x0004
1081
1082
1083    def correct_request_basic_test(self):
1084        """Search the existing service on the DUT using the Tester.
1085
1086        @return True if found, False if not found
1087
1088        """
1089        # connect to the DUT via L2CAP using SDP socket
1090        self.tester.connect(self.adapter['Address'])
1091
1092        for size in 16, 32, 128:
1093            # test case TP/SERVER/SS/BV-01-C:
1094            # at least the SDP server service exists
1095            resp = json.loads(self.tester.service_search_request(
1096                              [self.SDP_SERVER_CLASS_ID], 3,
1097                              {'preferred_size':size}))
1098            if resp != [0]:
1099                return False
1100            # test case TP/SERVER/SS/BV-04-C:
1101            # Service with Class ID = 0x0001 should never exist, as this UUID is
1102            # reserved as Bluetooth Core Specification UUID
1103            resp = json.loads(self.tester.service_search_request(
1104                              [self.NO_EXISTING_SERVICE_CLASS_ID], 3,
1105                              {'preferred_size':size}))
1106            if resp != []:
1107                return False
1108            # test case TP/SERVER/SS/BV-03-C:
1109            # request the fake services' Class ID to force SDP to use
1110            # continuation state
1111            resp = json.loads(self.tester.service_search_request(
1112                              [self.FAKE_SERVICES_CLASS_ID],
1113                              self.FAKE_SERVICES_CNT * 2,
1114                              {'preferred_size':size}))
1115            if len(resp) != self.FAKE_SERVICES_CNT:
1116                return False
1117            # test case TP/SERVER/SS/BI-01-C:
1118            # send a Service Search Request with intentionally invalid PDU size
1119            resp = json.loads(self.tester.service_search_request(
1120                              [self.SDP_SERVER_CLASS_ID], 3,
1121                              {'preferred_size':size,
1122                              'forced_pdu_size':self.SSRB_INVALID_PDU_SIZE}))
1123            if resp != self.ERROR_CODE_INVALID_PDU_SIZE:
1124                return False
1125            # test case TP/SERVER/SS/BI-02-C:
1126            # send a Service Search Request with invalid syntax
1127            resp = json.loads(self.tester.service_search_request(
1128                              [self.SDP_SERVER_CLASS_ID], 3,
1129                              {'preferred_size':size, 'invalid_request':True}))
1130            if resp != self.ERROR_CODE_INVALID_REQUEST_SYNTAX:
1131                return False
1132
1133        return True
1134
1135
1136    def sdp_service_search_request_basic_test(self, device):
1137        """Runs service search request basic test"""
1138
1139        if self.host.btpeer.get_platform() != 'RASPI':
1140            raise error.TestNAError('Test only runs on Raspi')
1141
1142        self.tester = device
1143        # Reset the adapter to the powered on, discoverable state.
1144        if not (self.bluetooth_facade.reset_on() and
1145                self.bluetooth_facade.set_discoverable(True)):
1146            raise error.TestFail('DUT could not be reset to initial state')
1147
1148        self.adapter = self.bluetooth_facade.get_adapter_properties()
1149
1150        # Setup the tester as a generic computer.
1151        if not self.tester.setup('computer'):
1152            raise error.TestNAError('Tester could not be initialized')
1153
1154        # Create many fake services with the same Class ID
1155        for num in range(0, self.FAKE_SERVICES_CNT):
1156            path_str = self.FAKE_SERVICES_PATH + str(num)
1157            uuid128 = ((self.FAKE_SERVICES_CLASS_ID << 96) +
1158                      self.BLUETOOTH_BASE_UUID)
1159            uuid_str = str(uuid.UUID(int=uuid128))
1160            self.bluetooth_facade.register_profile(path_str, uuid_str, {})
1161
1162        # Since radio is involved, this test is not 100% reliable; instead we
1163        # repeat a few times until it succeeds.
1164        for failed_attempts in range(0, 5):
1165            if self.correct_request_basic_test():
1166                break
1167        else:
1168            raise error.TestFail('Expected device was not found')
1169
1170        # Record how many attempts this took, hopefully we'll one day figure out
1171        # a way to reduce this to zero and then the loop above can go away.
1172        self.write_perf_keyval({'failed_attempts': failed_attempts })
1173
1174
1175class BluetoothSDPTests(bluetooth_SDP_ServiceAttributeRequest,
1176                        bluetooth_SDP_ServiceBrowse,
1177                        bluetooth_SDP_ServiceSearchAttributeRequest,
1178                        bluetooth_SDP_ServiceSearchRequestBasic):
1179    """Master class that simplifies inheritance of sdp tests"""
1180    pass
1181