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