1#!/usr/bin/python3.4
2#
3#   Copyright 2017 - The Android Open Source Project
4#
5#   Licensed under the Apache License, Version 2.0 (the "License");
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of 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,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16
17import string
18import time
19
20from acts import asserts
21from acts.test_decorators import test_tracker_info
22from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
23from acts.test_utils.wifi.aware import aware_const as aconsts
24from acts.test_utils.wifi.aware import aware_test_utils as autils
25from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
26
27
28class MessageTest(AwareBaseTest):
29    """Set of tests for Wi-Fi Aware L2 (layer 2) message exchanges."""
30
31    # configuration parameters used by tests
32    PAYLOAD_SIZE_MIN = 0
33    PAYLOAD_SIZE_TYPICAL = 1
34    PAYLOAD_SIZE_MAX = 2
35
36    NUM_MSGS_NO_QUEUE = 10
37    NUM_MSGS_QUEUE_DEPTH_MULT = 2  # number of messages = mult * queue depth
38
39    def create_msg(self, caps, payload_size, id):
40        """Creates a message string of the specified size containing the input id.
41
42    Args:
43      caps: Device capabilities.
44      payload_size: The size of the message to create - min (null or empty
45                    message), typical, max (based on device capabilities). Use
46                    the PAYLOAD_SIZE_xx constants.
47      id: Information to include in the generated message (or None).
48
49    Returns: A string of the requested size, optionally containing the id.
50    """
51        if payload_size == self.PAYLOAD_SIZE_MIN:
52            # arbitrarily return a None or an empty string (equivalent messages)
53            return None if id % 2 == 0 else ""
54        elif payload_size == self.PAYLOAD_SIZE_TYPICAL:
55            return "*** ID=%d ***" % id + string.ascii_uppercase
56        else:  # PAYLOAD_SIZE_MAX
57            return "*** ID=%4d ***" % id + "M" * (
58                caps[aconsts.CAP_MAX_SERVICE_SPECIFIC_INFO_LEN] - 15)
59
60    def create_config(self, is_publish, extra_diff=None):
61        """Create a base configuration based on input parameters.
62
63    Args:
64      is_publish: True for publish, False for subscribe sessions.
65      extra_diff: String to add to service name: allows differentiating
66                  discovery sessions.
67
68    Returns:
69      publish discovery configuration object.
70    """
71        config = {}
72        if is_publish:
73            config[
74                aconsts.
75                DISCOVERY_KEY_DISCOVERY_TYPE] = aconsts.PUBLISH_TYPE_UNSOLICITED
76        else:
77            config[
78                aconsts.
79                DISCOVERY_KEY_DISCOVERY_TYPE] = aconsts.SUBSCRIBE_TYPE_PASSIVE
80        config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = "GoogleTestServiceX" + (
81            extra_diff if extra_diff is not None else "")
82        return config
83
84    def prep_message_exchange(self, extra_diff=None):
85        """Creates a discovery session (publish and subscribe), and waits for
86    service discovery - at that point the sessions are ready for message
87    exchange.
88
89    Args:
90      extra_diff: String to add to service name: allows differentiating
91                  discovery sessions.
92    """
93        p_dut = self.android_devices[0]
94        p_dut.pretty_name = "Publisher"
95        s_dut = self.android_devices[1]
96        s_dut.pretty_name = "Subscriber"
97
98        # if differentiating (multiple) sessions then should decorate events with id
99        use_id = extra_diff is not None
100
101        # Publisher+Subscriber: attach and wait for confirmation
102        p_id = p_dut.droid.wifiAwareAttach(False, None, use_id)
103        autils.wait_for_event(
104            p_dut, aconsts.EVENT_CB_ON_ATTACHED
105            if not use_id else autils.decorate_event(
106                aconsts.EVENT_CB_ON_ATTACHED, p_id))
107        time.sleep(self.device_startup_offset)
108        s_id = s_dut.droid.wifiAwareAttach(False, None, use_id)
109        autils.wait_for_event(
110            s_dut, aconsts.EVENT_CB_ON_ATTACHED
111            if not use_id else autils.decorate_event(
112                aconsts.EVENT_CB_ON_ATTACHED, s_id))
113
114        # Publisher: start publish and wait for confirmation
115        p_disc_id = p_dut.droid.wifiAwarePublish(
116            p_id, self.create_config(True, extra_diff=extra_diff), use_id)
117        autils.wait_for_event(
118            p_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED
119            if not use_id else autils.decorate_event(
120                aconsts.SESSION_CB_ON_PUBLISH_STARTED, p_disc_id))
121
122        # Subscriber: start subscribe and wait for confirmation
123        s_disc_id = s_dut.droid.wifiAwareSubscribe(
124            s_id, self.create_config(False, extra_diff=extra_diff), use_id)
125        autils.wait_for_event(
126            s_dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED
127            if not use_id else autils.decorate_event(
128                aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED, s_disc_id))
129
130        # Subscriber: wait for service discovery
131        discovery_event = autils.wait_for_event(
132            s_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED
133            if not use_id else autils.decorate_event(
134                aconsts.SESSION_CB_ON_SERVICE_DISCOVERED, s_disc_id))
135        peer_id_on_sub = discovery_event["data"][
136            aconsts.SESSION_CB_KEY_PEER_ID]
137
138        return {
139            "p_dut": p_dut,
140            "s_dut": s_dut,
141            "p_id": p_id,
142            "s_id": s_id,
143            "p_disc_id": p_disc_id,
144            "s_disc_id": s_disc_id,
145            "peer_id_on_sub": peer_id_on_sub
146        }
147
148    def run_message_no_queue(self, payload_size):
149        """Validate L2 message exchange between publisher & subscriber with no
150    queueing - i.e. wait for an ACK on each message before sending the next
151    message.
152
153    Args:
154      payload_size: min, typical, or max (PAYLOAD_SIZE_xx).
155    """
156        discovery_info = self.prep_message_exchange()
157        p_dut = discovery_info["p_dut"]
158        s_dut = discovery_info["s_dut"]
159        p_disc_id = discovery_info["p_disc_id"]
160        s_disc_id = discovery_info["s_disc_id"]
161        peer_id_on_sub = discovery_info["peer_id_on_sub"]
162
163        for i in range(self.NUM_MSGS_NO_QUEUE):
164            msg = self.create_msg(s_dut.aware_capabilities, payload_size, i)
165            msg_id = self.get_next_msg_id()
166            s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub, msg_id,
167                                             msg, 0)
168            tx_event = autils.wait_for_event(
169                s_dut, aconsts.SESSION_CB_ON_MESSAGE_SENT)
170            rx_event = autils.wait_for_event(
171                p_dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
172            asserts.assert_equal(
173                msg_id, tx_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_ID],
174                "Subscriber -> Publisher message ID corrupted")
175            autils.assert_equal_strings(
176                msg,
177                rx_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING],
178                "Subscriber -> Publisher message %d corrupted" % i)
179
180        peer_id_on_pub = rx_event["data"][aconsts.SESSION_CB_KEY_PEER_ID]
181        for i in range(self.NUM_MSGS_NO_QUEUE):
182            msg = self.create_msg(s_dut.aware_capabilities, payload_size,
183                                  1000 + i)
184            msg_id = self.get_next_msg_id()
185            p_dut.droid.wifiAwareSendMessage(p_disc_id, peer_id_on_pub, msg_id,
186                                             msg, 0)
187            tx_event = autils.wait_for_event(
188                p_dut, aconsts.SESSION_CB_ON_MESSAGE_SENT)
189            rx_event = autils.wait_for_event(
190                s_dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
191            asserts.assert_equal(
192                msg_id, tx_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_ID],
193                "Publisher -> Subscriber message ID corrupted")
194            autils.assert_equal_strings(
195                msg,
196                rx_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING],
197                "Publisher -> Subscriber message %d corrupted" % i)
198
199        # verify there are no more events
200        time.sleep(autils.EVENT_TIMEOUT)
201        autils.verify_no_more_events(p_dut, timeout=0)
202        autils.verify_no_more_events(s_dut, timeout=0)
203
204    def wait_for_messages(self,
205                          tx_msgs,
206                          tx_msg_ids,
207                          tx_disc_id,
208                          rx_disc_id,
209                          tx_dut,
210                          rx_dut,
211                          are_msgs_empty=False):
212        """Validate that all expected messages are transmitted correctly and
213    received as expected. Method is called after the messages are sent into
214    the transmission queue.
215
216    Note: that message can be transmitted and received out-of-order (which is
217    acceptable and the method handles that correctly).
218
219    Args:
220      tx_msgs: dictionary of transmitted messages
221      tx_msg_ids: dictionary of transmitted message ids
222      tx_disc_id: transmitter discovery session id (None for no decoration)
223      rx_disc_id: receiver discovery session id (None for no decoration)
224      tx_dut: transmitter device
225      rx_dut: receiver device
226      are_msgs_empty: True if the messages are None or empty (changes dup detection)
227
228    Returns: the peer ID from any of the received messages
229    """
230        # peer id on receiver
231        peer_id_on_rx = None
232
233        # wait for all messages to be transmitted
234        still_to_be_tx = len(tx_msg_ids)
235        while still_to_be_tx != 0:
236            tx_event = autils.wait_for_event(
237                tx_dut, aconsts.SESSION_CB_ON_MESSAGE_SENT
238                if tx_disc_id is None else autils.decorate_event(
239                    aconsts.SESSION_CB_ON_MESSAGE_SENT, tx_disc_id))
240            tx_msg_id = tx_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_ID]
241            tx_msg_ids[tx_msg_id] = tx_msg_ids[tx_msg_id] + 1
242            if tx_msg_ids[tx_msg_id] == 1:
243                still_to_be_tx = still_to_be_tx - 1
244
245        # check for any duplicate transmit notifications
246        asserts.assert_equal(
247            len(tx_msg_ids), sum(tx_msg_ids.values()),
248            "Duplicate transmit message IDs: %s" % tx_msg_ids)
249
250        # wait for all messages to be received
251        still_to_be_rx = len(tx_msg_ids)
252        while still_to_be_rx != 0:
253            rx_event = autils.wait_for_event(
254                rx_dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED
255                if rx_disc_id is None else autils.decorate_event(
256                    aconsts.SESSION_CB_ON_MESSAGE_RECEIVED, rx_disc_id))
257            peer_id_on_rx = rx_event["data"][aconsts.SESSION_CB_KEY_PEER_ID]
258            if are_msgs_empty:
259                still_to_be_rx = still_to_be_rx - 1
260            else:
261                rx_msg = rx_event["data"][
262                    aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING]
263                asserts.assert_true(
264                    rx_msg in tx_msgs,
265                    "Received a message we did not send!? -- '%s'" % rx_msg)
266                tx_msgs[rx_msg] = tx_msgs[rx_msg] + 1
267                if tx_msgs[rx_msg] == 1:
268                    still_to_be_rx = still_to_be_rx - 1
269
270        # check for any duplicate received messages
271        if not are_msgs_empty:
272            asserts.assert_equal(
273                len(tx_msgs), sum(tx_msgs.values()),
274                "Duplicate transmit messages: %s" % tx_msgs)
275
276        return peer_id_on_rx
277
278    def run_message_with_queue(self, payload_size):
279        """Validate L2 message exchange between publisher & subscriber with
280    queueing - i.e. transmit all messages and then wait for ACKs.
281
282    Args:
283      payload_size: min, typical, or max (PAYLOAD_SIZE_xx).
284    """
285        discovery_info = self.prep_message_exchange()
286        p_dut = discovery_info["p_dut"]
287        s_dut = discovery_info["s_dut"]
288        p_disc_id = discovery_info["p_disc_id"]
289        s_disc_id = discovery_info["s_disc_id"]
290        peer_id_on_sub = discovery_info["peer_id_on_sub"]
291
292        msgs = {}
293        msg_ids = {}
294        for i in range(
295                self.NUM_MSGS_QUEUE_DEPTH_MULT * s_dut.
296                aware_capabilities[aconsts.CAP_MAX_QUEUED_TRANSMIT_MESSAGES]):
297            msg = self.create_msg(s_dut.aware_capabilities, payload_size, i)
298            msg_id = self.get_next_msg_id()
299            msgs[msg] = 0
300            msg_ids[msg_id] = 0
301            s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub, msg_id,
302                                             msg, 0)
303        peer_id_on_pub = self.wait_for_messages(
304            msgs, msg_ids, None, None, s_dut, p_dut,
305            payload_size == self.PAYLOAD_SIZE_MIN)
306
307        msgs = {}
308        msg_ids = {}
309        for i in range(
310                self.NUM_MSGS_QUEUE_DEPTH_MULT * p_dut.
311                aware_capabilities[aconsts.CAP_MAX_QUEUED_TRANSMIT_MESSAGES]):
312            msg = self.create_msg(p_dut.aware_capabilities, payload_size,
313                                  1000 + i)
314            msg_id = self.get_next_msg_id()
315            msgs[msg] = 0
316            msg_ids[msg_id] = 0
317            p_dut.droid.wifiAwareSendMessage(p_disc_id, peer_id_on_pub, msg_id,
318                                             msg, 0)
319        self.wait_for_messages(msgs, msg_ids, None, None, p_dut, s_dut,
320                               payload_size == self.PAYLOAD_SIZE_MIN)
321
322        # verify there are no more events
323        time.sleep(autils.EVENT_TIMEOUT)
324        autils.verify_no_more_events(p_dut, timeout=0)
325        autils.verify_no_more_events(s_dut, timeout=0)
326
327    def run_message_multi_session_with_queue(self, payload_size):
328        """Validate L2 message exchange between publishers & subscribers with
329    queueing - i.e. transmit all messages and then wait for ACKs. Uses 2
330    discovery sessions running concurrently and validates that messages
331    arrive at the correct destination.
332
333    Args:
334      payload_size: min, typical, or max (PAYLOAD_SIZE_xx)
335    """
336        discovery_info1 = self.prep_message_exchange(extra_diff="-111")
337        p_dut = discovery_info1["p_dut"]  # same for both sessions
338        s_dut = discovery_info1["s_dut"]  # same for both sessions
339        p_disc_id1 = discovery_info1["p_disc_id"]
340        s_disc_id1 = discovery_info1["s_disc_id"]
341        peer_id_on_sub1 = discovery_info1["peer_id_on_sub"]
342
343        discovery_info2 = self.prep_message_exchange(extra_diff="-222")
344        p_disc_id2 = discovery_info2["p_disc_id"]
345        s_disc_id2 = discovery_info2["s_disc_id"]
346        peer_id_on_sub2 = discovery_info2["peer_id_on_sub"]
347
348        msgs1 = {}
349        msg_ids1 = {}
350        msgs2 = {}
351        msg_ids2 = {}
352        for i in range(
353                self.NUM_MSGS_QUEUE_DEPTH_MULT * s_dut.
354                aware_capabilities[aconsts.CAP_MAX_QUEUED_TRANSMIT_MESSAGES]):
355            msg1 = self.create_msg(s_dut.aware_capabilities, payload_size, i)
356            msg_id1 = self.get_next_msg_id()
357            msgs1[msg1] = 0
358            msg_ids1[msg_id1] = 0
359            s_dut.droid.wifiAwareSendMessage(s_disc_id1, peer_id_on_sub1,
360                                             msg_id1, msg1, 0)
361            msg2 = self.create_msg(s_dut.aware_capabilities, payload_size,
362                                   100 + i)
363            msg_id2 = self.get_next_msg_id()
364            msgs2[msg2] = 0
365            msg_ids2[msg_id2] = 0
366            s_dut.droid.wifiAwareSendMessage(s_disc_id2, peer_id_on_sub2,
367                                             msg_id2, msg2, 0)
368
369        peer_id_on_pub1 = self.wait_for_messages(
370            msgs1, msg_ids1, s_disc_id1, p_disc_id1, s_dut, p_dut,
371            payload_size == self.PAYLOAD_SIZE_MIN)
372        peer_id_on_pub2 = self.wait_for_messages(
373            msgs2, msg_ids2, s_disc_id2, p_disc_id2, s_dut, p_dut,
374            payload_size == self.PAYLOAD_SIZE_MIN)
375
376        msgs1 = {}
377        msg_ids1 = {}
378        msgs2 = {}
379        msg_ids2 = {}
380        for i in range(
381                self.NUM_MSGS_QUEUE_DEPTH_MULT * p_dut.
382                aware_capabilities[aconsts.CAP_MAX_QUEUED_TRANSMIT_MESSAGES]):
383            msg1 = self.create_msg(p_dut.aware_capabilities, payload_size,
384                                   1000 + i)
385            msg_id1 = self.get_next_msg_id()
386            msgs1[msg1] = 0
387            msg_ids1[msg_id1] = 0
388            p_dut.droid.wifiAwareSendMessage(p_disc_id1, peer_id_on_pub1,
389                                             msg_id1, msg1, 0)
390            msg2 = self.create_msg(p_dut.aware_capabilities, payload_size,
391                                   1100 + i)
392            msg_id2 = self.get_next_msg_id()
393            msgs2[msg2] = 0
394            msg_ids2[msg_id2] = 0
395            p_dut.droid.wifiAwareSendMessage(p_disc_id2, peer_id_on_pub2,
396                                             msg_id2, msg2, 0)
397
398        self.wait_for_messages(msgs1, msg_ids1, p_disc_id1, s_disc_id1, p_dut,
399                               s_dut, payload_size == self.PAYLOAD_SIZE_MIN)
400        self.wait_for_messages(msgs2, msg_ids2, p_disc_id2, s_disc_id2, p_dut,
401                               s_dut, payload_size == self.PAYLOAD_SIZE_MIN)
402
403        # verify there are no more events
404        time.sleep(autils.EVENT_TIMEOUT)
405        autils.verify_no_more_events(p_dut, timeout=0)
406        autils.verify_no_more_events(s_dut, timeout=0)
407
408    ############################################################################
409
410    @test_tracker_info(uuid="a8cd0512-b279-425f-93cf-949ddba22c7a")
411    @WifiBaseTest.wifi_test_wrap
412    def test_message_no_queue_min(self):
413        """Functional / Message / No queue
414    - Minimal payload size (None or "")
415    """
416        self.run_message_no_queue(self.PAYLOAD_SIZE_MIN)
417
418    @test_tracker_info(uuid="2c26170a-5d0a-4cf4-b0b9-56ef03f5dcf4")
419    def test_message_no_queue_typical(self):
420        """Functional / Message / No queue
421    - Typical payload size
422    """
423        self.run_message_no_queue(self.PAYLOAD_SIZE_TYPICAL)
424
425    @test_tracker_info(uuid="c984860c-b62d-4d9b-8bce-4d894ea3bfbe")
426    @WifiBaseTest.wifi_test_wrap
427    def test_message_no_queue_max(self):
428        """Functional / Message / No queue
429    - Max payload size (based on device capabilities)
430    """
431        self.run_message_no_queue(self.PAYLOAD_SIZE_MAX)
432
433    @test_tracker_info(uuid="3f06de73-31ab-4e0c-bc6f-59abdaf87f4f")
434    def test_message_with_queue_min(self):
435        """Functional / Message / With queue
436    - Minimal payload size (none or "")
437    """
438        self.run_message_with_queue(self.PAYLOAD_SIZE_MIN)
439
440    @test_tracker_info(uuid="9b7f5bd8-b0b1-479e-8e4b-9db0bb56767b")
441    def test_message_with_queue_typical(self):
442        """Functional / Message / With queue
443    - Typical payload size
444    """
445        self.run_message_with_queue(self.PAYLOAD_SIZE_TYPICAL)
446
447    @test_tracker_info(uuid="4f9a6dce-3050-4e6a-a143-53592c6c7c28")
448    def test_message_with_queue_max(self):
449        """Functional / Message / With queue
450    - Max payload size (based on device capabilities)
451    """
452        self.run_message_with_queue(self.PAYLOAD_SIZE_MAX)
453
454    @test_tracker_info(uuid="4cece232-0983-4d6b-90a9-1bb9314b64f0")
455    def test_message_with_multiple_discovery_sessions_typical(self):
456        """Functional / Message / Multiple sessions
457
458     Sets up 2 discovery sessions on 2 devices. Sends a message in each
459     direction on each discovery session and verifies that reaches expected
460     destination.
461    """
462        self.run_message_multi_session_with_queue(self.PAYLOAD_SIZE_TYPICAL)
463