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