#!/usr/bin/python3.4
#
#   Copyright 2017 - The Android Open Source Project
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

import queue
import string
import time

from acts import asserts
from acts.test_decorators import test_tracker_info
from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest


class DiscoveryTest(AwareBaseTest):
    """Set of tests for Wi-Fi Aware discovery."""

    # configuration parameters used by tests
    PAYLOAD_SIZE_MIN = 0
    PAYLOAD_SIZE_TYPICAL = 1
    PAYLOAD_SIZE_MAX = 2
    EVENT_TIMEOUT = 3

    # message strings
    query_msg = "How are you doing? 你好嗎？"
    response_msg = "Doing ok - thanks! 做的不錯 - 謝謝！"

    # message re-transmit counter (increases reliability in open-environment)
    # Note: reliability of message transmission is tested elsewhere
    msg_retx_count = 5  # hard-coded max value, internal API

    def create_base_config(self, caps, is_publish, ptype, stype, payload_size,
                           ttl, term_ind_on, null_match):
        """Create a base configuration based on input parameters.

    Args:
      caps: device capability dictionary
      is_publish: True if a publish config, else False
      ptype: unsolicited or solicited (used if is_publish is True)
      stype: passive or active (used if is_publish is False)
      payload_size: min, typical, max (PAYLOAD_SIZE_xx)
      ttl: time-to-live configuration (0 - forever)
      term_ind_on: is termination indication enabled
      null_match: null-out the middle match filter
    Returns:
      publish discovery configuration object.
    """
        config = {}
        if is_publish:
            config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = ptype
        else:
            config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = stype
        config[aconsts.DISCOVERY_KEY_TTL] = ttl
        config[aconsts.DISCOVERY_KEY_TERM_CB_ENABLED] = term_ind_on
        if payload_size == self.PAYLOAD_SIZE_MIN:
            config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = "a"
            config[aconsts.DISCOVERY_KEY_SSI] = None
            config[aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST] = []
        elif payload_size == self.PAYLOAD_SIZE_TYPICAL:
            config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = "GoogleTestServiceX"
            if is_publish:
                config[aconsts.DISCOVERY_KEY_SSI] = string.ascii_letters
            else:
                config[aconsts.
                       DISCOVERY_KEY_SSI] = string.ascii_letters[::
                                                                 -1]  # reverse
            config[
                aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST] = autils.encode_list(
                    [(10).to_bytes(1, byteorder="big"), "hello there string"
                     if not null_match else None,
                     bytes(range(40))])
        else:  # PAYLOAD_SIZE_MAX
            config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = "VeryLong" + "X" * (
                caps[aconsts.CAP_MAX_SERVICE_NAME_LEN] - 8)
            config[aconsts.DISCOVERY_KEY_SSI] = (
                "P" if is_publish else
                "S") * caps[aconsts.CAP_MAX_SERVICE_SPECIFIC_INFO_LEN]
            mf = autils.construct_max_match_filter(
                caps[aconsts.CAP_MAX_MATCH_FILTER_LEN])
            if null_match:
                mf[2] = None
            config[aconsts.
                   DISCOVERY_KEY_MATCH_FILTER_LIST] = autils.encode_list(mf)

        return config

    def create_publish_config(self, caps, ptype, payload_size, ttl,
                              term_ind_on, null_match):
        """Create a publish configuration based on input parameters.

    Args:
      caps: device capability dictionary
      ptype: unsolicited or solicited
      payload_size: min, typical, max (PAYLOAD_SIZE_xx)
      ttl: time-to-live configuration (0 - forever)
      term_ind_on: is termination indication enabled
      null_match: null-out the middle match filter
    Returns:
      publish discovery configuration object.
    """
        return self.create_base_config(caps, True, ptype, None, payload_size,
                                       ttl, term_ind_on, null_match)

    def create_subscribe_config(self, caps, stype, payload_size, ttl,
                                term_ind_on, null_match):
        """Create a subscribe configuration based on input parameters.

    Args:
      caps: device capability dictionary
      stype: passive or active
      payload_size: min, typical, max (PAYLOAD_SIZE_xx)
      ttl: time-to-live configuration (0 - forever)
      term_ind_on: is termination indication enabled
      null_match: null-out the middle match filter
    Returns:
      subscribe discovery configuration object.
    """
        return self.create_base_config(caps, False, None, stype, payload_size,
                                       ttl, term_ind_on, null_match)

    def positive_discovery_test_utility(self, ptype, stype, payload_size):
        """Utility which runs a positive discovery test:
    - Discovery (publish/subscribe) with TTL=0 (non-self-terminating)
    - Exchange messages
    - Update publish/subscribe
    - Terminate

    Args:
      ptype: Publish discovery type
      stype: Subscribe discovery type
      payload_size: One of PAYLOAD_SIZE_* constants - MIN, TYPICAL, MAX
    """
        p_dut = self.android_devices[0]
        p_dut.pretty_name = "Publisher"
        s_dut = self.android_devices[1]
        s_dut.pretty_name = "Subscriber"

        # Publisher+Subscriber: attach and wait for confirmation
        p_id = p_dut.droid.wifiAwareAttach(False)
        autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
        time.sleep(self.device_startup_offset)
        s_id = s_dut.droid.wifiAwareAttach(False)
        autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)

        # Publisher: start publish and wait for confirmation
        p_config = self.create_publish_config(
            p_dut.aware_capabilities,
            ptype,
            payload_size,
            ttl=0,
            term_ind_on=False,
            null_match=False)
        p_disc_id = p_dut.droid.wifiAwarePublish(p_id, p_config)
        autils.wait_for_event(p_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)

        # Subscriber: start subscribe and wait for confirmation
        s_config = self.create_subscribe_config(
            s_dut.aware_capabilities,
            stype,
            payload_size,
            ttl=0,
            term_ind_on=False,
            null_match=True)
        s_disc_id = s_dut.droid.wifiAwareSubscribe(s_id, s_config)
        autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)

        # Subscriber: wait for service discovery
        discovery_event = autils.wait_for_event(
            s_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
        peer_id_on_sub = discovery_event["data"][
            aconsts.SESSION_CB_KEY_PEER_ID]

        # Subscriber: validate contents of discovery:
        # - SSI: publisher's
        # - Match filter: UNSOLICITED - publisher, SOLICITED - subscriber
        autils.assert_equal_strings(
            bytes(discovery_event["data"][
                aconsts.SESSION_CB_KEY_SERVICE_SPECIFIC_INFO]).decode("utf-8"),
            p_config[aconsts.DISCOVERY_KEY_SSI],
            "Discovery mismatch: service specific info (SSI)")
        asserts.assert_equal(
            autils.decode_list(discovery_event["data"][
                aconsts.SESSION_CB_KEY_MATCH_FILTER_LIST]),
            autils.decode_list(
                p_config[aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST]
                if ptype == aconsts.PUBLISH_TYPE_UNSOLICITED else s_config[
                    aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST]),
            "Discovery mismatch: match filter")

        # Subscriber: send message to peer (Publisher)
        s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub,
                                         self.get_next_msg_id(),
                                         self.query_msg, self.msg_retx_count)
        autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_MESSAGE_SENT)

        # Publisher: wait for received message
        pub_rx_msg_event = autils.wait_for_event(
            p_dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
        peer_id_on_pub = pub_rx_msg_event["data"][
            aconsts.SESSION_CB_KEY_PEER_ID]

        # Publisher: validate contents of message
        asserts.assert_equal(
            pub_rx_msg_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING],
            self.query_msg, "Subscriber -> Publisher message corrupted")

        # Publisher: send message to peer (Subscriber)
        p_dut.droid.wifiAwareSendMessage(p_disc_id, peer_id_on_pub,
                                         self.get_next_msg_id(),
                                         self.response_msg,
                                         self.msg_retx_count)
        autils.wait_for_event(p_dut, aconsts.SESSION_CB_ON_MESSAGE_SENT)

        # Subscriber: wait for received message
        sub_rx_msg_event = autils.wait_for_event(
            s_dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)

        # Subscriber: validate contents of message
        asserts.assert_equal(
            sub_rx_msg_event["data"][aconsts.SESSION_CB_KEY_PEER_ID],
            peer_id_on_sub,
            "Subscriber received message from different peer ID then discovery!?"
        )
        autils.assert_equal_strings(
            sub_rx_msg_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING],
            self.response_msg, "Publisher -> Subscriber message corrupted")

        # Subscriber: validate that we're not getting another Service Discovery
        autils.fail_on_event(s_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)

        # Publisher: update publish and wait for confirmation
        p_config[aconsts.DISCOVERY_KEY_SSI] = "something else"
        p_dut.droid.wifiAwareUpdatePublish(p_disc_id, p_config)
        autils.wait_for_event(p_dut,
                              aconsts.SESSION_CB_ON_SESSION_CONFIG_UPDATED)

        # Subscriber: expect a new service discovery
        discovery_event = autils.wait_for_event(
            s_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)

        # Subscriber: validate contents of discovery
        autils.assert_equal_strings(
            bytes(discovery_event["data"][
                aconsts.SESSION_CB_KEY_SERVICE_SPECIFIC_INFO]).decode("utf-8"),
            p_config[aconsts.DISCOVERY_KEY_SSI],
            "Discovery mismatch (after pub update): service specific info (SSI)"
        )
        asserts.assert_equal(
            autils.decode_list(discovery_event["data"][
                aconsts.SESSION_CB_KEY_MATCH_FILTER_LIST]),
            autils.decode_list(
                p_config[aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST]
                if ptype == aconsts.PUBLISH_TYPE_UNSOLICITED else s_config[
                    aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST]),
            "Discovery mismatch: match filter")
        asserts.assert_equal(
            peer_id_on_sub,
            discovery_event["data"][aconsts.SESSION_CB_KEY_PEER_ID],
            "Peer ID changed when publish was updated!?")

        # Subscribe: update subscribe and wait for confirmation
        s_config = self.create_subscribe_config(
            s_dut.aware_capabilities,
            stype,
            payload_size,
            ttl=0,
            term_ind_on=False,
            null_match=False)
        s_dut.droid.wifiAwareUpdateSubscribe(s_disc_id, s_config)
        autils.wait_for_event(s_dut,
                              aconsts.SESSION_CB_ON_SESSION_CONFIG_UPDATED)

        # Publisher+Subscriber: Terminate sessions
        p_dut.droid.wifiAwareDestroyDiscoverySession(p_disc_id)
        s_dut.droid.wifiAwareDestroyDiscoverySession(s_disc_id)

        autils.wait_for_event(p_dut,
                              aconsts.SESSION_CB_ON_SESSION_TERMINATED)
        autils.wait_for_event(s_dut,
                              aconsts.SESSION_CB_ON_SESSION_TERMINATED)

        # verify that forbidden callbacks aren't called
        autils.validate_forbidden_callbacks(p_dut, {aconsts.CB_EV_MATCH: 0})

    def verify_discovery_session_term(self, dut, disc_id, config, is_publish,
                                      term_ind_on):
        """Utility to verify that the specified discovery session has terminated (by
    waiting for the TTL and then attempting to reconfigure).

    Args:
      dut: device under test
      disc_id: discovery id for the existing session
      config: configuration of the existing session
      is_publish: True if the configuration was publish, False if subscribe
      term_ind_on: True if a termination indication is expected, False otherwise
    """
        # Wait for session termination
        if term_ind_on:
            autils.wait_for_event(
                dut,
                autils.decorate_event(aconsts.SESSION_CB_ON_SESSION_TERMINATED,
                                      disc_id))
        else:
            # can't defer wait to end since in any case have to wait for session to
            # expire
            autils.fail_on_event(
                dut,
                autils.decorate_event(aconsts.SESSION_CB_ON_SESSION_TERMINATED,
                                      disc_id))

        # Validate that session expired by trying to configure it (expect failure)
        config[aconsts.DISCOVERY_KEY_SSI] = "something else"
        if is_publish:
            dut.droid.wifiAwareUpdatePublish(disc_id, config)
        else:
            dut.droid.wifiAwareUpdateSubscribe(disc_id, config)

        # The response to update discovery session is:
        # term_ind_on=True: session was cleaned-up so won't get an explicit failure, but won't get a
        #                   success either. Can check for no SESSION_CB_ON_SESSION_CONFIG_UPDATED but
        #                   will defer to the end of the test (no events on queue).
        # term_ind_on=False: session was not cleaned-up (yet). So expect
        #                    SESSION_CB_ON_SESSION_CONFIG_FAILED.
        if not term_ind_on:
            autils.wait_for_event(
                dut,
                autils.decorate_event(
                    aconsts.SESSION_CB_ON_SESSION_CONFIG_FAILED, disc_id))

    def positive_ttl_test_utility(self, is_publish, ptype, stype, term_ind_on):
        """Utility which runs a positive discovery session TTL configuration test

    Iteration 1: Verify session started with TTL
    Iteration 2: Verify session started without TTL and reconfigured with TTL
    Iteration 3: Verify session started with (long) TTL and reconfigured with
                 (short) TTL

    Args:
      is_publish: True if testing publish, False if testing subscribe
      ptype: Publish discovery type (used if is_publish is True)
      stype: Subscribe discovery type (used if is_publish is False)
      term_ind_on: Configuration of termination indication
    """
        SHORT_TTL = 5  # 5 seconds
        LONG_TTL = 100  # 100 seconds
        dut = self.android_devices[0]

        # Attach and wait for confirmation
        id = dut.droid.wifiAwareAttach(False)
        autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)

        # Iteration 1: Start discovery session with TTL
        config = self.create_base_config(
            dut.aware_capabilities, is_publish, ptype, stype,
            self.PAYLOAD_SIZE_TYPICAL, SHORT_TTL, term_ind_on, False)
        if is_publish:
            disc_id = dut.droid.wifiAwarePublish(id, config, True)
            autils.wait_for_event(
                dut,
                autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
                                      disc_id))
        else:
            disc_id = dut.droid.wifiAwareSubscribe(id, config, True)
            autils.wait_for_event(
                dut,
                autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
                                      disc_id))

        # Wait for session termination & verify
        self.verify_discovery_session_term(dut, disc_id, config, is_publish,
                                           term_ind_on)

        # Iteration 2: Start a discovery session without TTL
        config = self.create_base_config(
            dut.aware_capabilities, is_publish, ptype, stype,
            self.PAYLOAD_SIZE_TYPICAL, 0, term_ind_on, False)
        if is_publish:
            disc_id = dut.droid.wifiAwarePublish(id, config, True)
            autils.wait_for_event(
                dut,
                autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
                                      disc_id))
        else:
            disc_id = dut.droid.wifiAwareSubscribe(id, config, True)
            autils.wait_for_event(
                dut,
                autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
                                      disc_id))

        # Update with a TTL
        config = self.create_base_config(
            dut.aware_capabilities, is_publish, ptype, stype,
            self.PAYLOAD_SIZE_TYPICAL, SHORT_TTL, term_ind_on, False)
        if is_publish:
            dut.droid.wifiAwareUpdatePublish(disc_id, config)
        else:
            dut.droid.wifiAwareUpdateSubscribe(disc_id, config)
        autils.wait_for_event(
            dut,
            autils.decorate_event(aconsts.SESSION_CB_ON_SESSION_CONFIG_UPDATED,
                                  disc_id))

        # Wait for session termination & verify
        self.verify_discovery_session_term(dut, disc_id, config, is_publish,
                                           term_ind_on)

        # Iteration 3: Start a discovery session with (long) TTL
        config = self.create_base_config(
            dut.aware_capabilities, is_publish, ptype, stype,
            self.PAYLOAD_SIZE_TYPICAL, LONG_TTL, term_ind_on, False)
        if is_publish:
            disc_id = dut.droid.wifiAwarePublish(id, config, True)
            autils.wait_for_event(
                dut,
                autils.decorate_event(aconsts.SESSION_CB_ON_PUBLISH_STARTED,
                                      disc_id))
        else:
            disc_id = dut.droid.wifiAwareSubscribe(id, config, True)
            autils.wait_for_event(
                dut,
                autils.decorate_event(aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
                                      disc_id))

        # Update with a TTL
        config = self.create_base_config(
            dut.aware_capabilities, is_publish, ptype, stype,
            self.PAYLOAD_SIZE_TYPICAL, SHORT_TTL, term_ind_on, False)
        if is_publish:
            dut.droid.wifiAwareUpdatePublish(disc_id, config)
        else:
            dut.droid.wifiAwareUpdateSubscribe(disc_id, config)
        autils.wait_for_event(
            dut,
            autils.decorate_event(aconsts.SESSION_CB_ON_SESSION_CONFIG_UPDATED,
                                  disc_id))

        # Wait for session termination & verify
        self.verify_discovery_session_term(dut, disc_id, config, is_publish,
                                           term_ind_on)

        # verify that there were no other events
        autils.verify_no_more_events(dut)

        # verify that forbidden callbacks aren't called
        if not term_ind_on:
            autils.validate_forbidden_callbacks(
                dut, {
                    aconsts.CB_EV_PUBLISH_TERMINATED: 0,
                    aconsts.CB_EV_SUBSCRIBE_TERMINATED: 0
                })

    def discovery_mismatch_test_utility(self,
                                        is_expected_to_pass,
                                        p_type,
                                        s_type,
                                        p_service_name=None,
                                        s_service_name=None,
                                        p_mf_1=None,
                                        s_mf_1=None):
        """Utility which runs the negative discovery test for mismatched service
    configs.

    Args:
      is_expected_to_pass: True if positive test, False if negative
      p_type: Publish discovery type
      s_type: Subscribe discovery type
      p_service_name: Publish service name (or None to leave unchanged)
      s_service_name: Subscribe service name (or None to leave unchanged)
      p_mf_1: Publish match filter element [1] (or None to leave unchanged)
      s_mf_1: Subscribe match filter element [1] (or None to leave unchanged)
    """
        p_dut = self.android_devices[0]
        p_dut.pretty_name = "Publisher"
        s_dut = self.android_devices[1]
        s_dut.pretty_name = "Subscriber"

        # create configurations
        p_config = self.create_publish_config(
            p_dut.aware_capabilities,
            p_type,
            self.PAYLOAD_SIZE_TYPICAL,
            ttl=0,
            term_ind_on=False,
            null_match=False)
        if p_service_name is not None:
            p_config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = p_service_name
        if p_mf_1 is not None:
            p_config[
                aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST] = autils.encode_list(
                    [(10).to_bytes(1, byteorder="big"), p_mf_1,
                     bytes(range(40))])
        s_config = self.create_publish_config(
            s_dut.aware_capabilities,
            s_type,
            self.PAYLOAD_SIZE_TYPICAL,
            ttl=0,
            term_ind_on=False,
            null_match=False)
        if s_service_name is not None:
            s_config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = s_service_name
        if s_mf_1 is not None:
            s_config[
                aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST] = autils.encode_list(
                    [(10).to_bytes(1, byteorder="big"), s_mf_1,
                     bytes(range(40))])

        p_id = p_dut.droid.wifiAwareAttach(False)
        autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
        time.sleep(self.device_startup_offset)
        s_id = s_dut.droid.wifiAwareAttach(False)
        autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)

        # Publisher: start publish and wait for confirmation
        p_disc_id = p_dut.droid.wifiAwarePublish(p_id, p_config)
        autils.wait_for_event(p_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)

        # Subscriber: start subscribe and wait for confirmation
        s_disc_id = s_dut.droid.wifiAwareSubscribe(s_id, s_config)
        autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)

        # Subscriber: fail on service discovery
        if is_expected_to_pass:
            autils.wait_for_event(s_dut,
                                  aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
        else:
            autils.fail_on_event(s_dut,
                                 aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)

        # Publisher+Subscriber: Terminate sessions
        p_dut.droid.wifiAwareDestroyDiscoverySession(p_disc_id)
        s_dut.droid.wifiAwareDestroyDiscoverySession(s_disc_id)
        autils.wait_for_event(p_dut,
                              aconsts.SESSION_CB_ON_SESSION_TERMINATED)
        autils.wait_for_event(s_dut,
                          aconsts.SESSION_CB_ON_SESSION_TERMINATED)

        # verify that there were no other events (including terminations)
        autils.verify_no_more_events(p_dut, timeout=0)
        autils.verify_no_more_events(s_dut, timeout=0)

    #######################################
    # Positive tests key:
    #
    # names is: test_<pub_type>_<sub_type>_<size>
    # where:
    #
    # pub_type: Type of publish discovery session: unsolicited or solicited.
    # sub_type: Type of subscribe discovery session: passive or active.
    # size: Size of payload fields (service name, service specific info, and match
    # filter: typical, max, or min.
    #######################################

    @test_tracker_info(uuid="954ebbde-ed2b-4f04-9e68-88239187d69d")
    @WifiBaseTest.wifi_test_wrap
    def test_positive_unsolicited_passive_typical(self):
        """Functional test case / Discovery test cases / positive test case:
    - Solicited publish + passive subscribe
    - Typical payload fields size

    Verifies that discovery and message exchange succeeds.
    """
        self.positive_discovery_test_utility(
            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
            payload_size=self.PAYLOAD_SIZE_TYPICAL)

    @test_tracker_info(uuid="67fb22bb-6985-4345-95a4-90b76681a58b")
    def test_positive_unsolicited_passive_min(self):
        """Functional test case / Discovery test cases / positive test case:
    - Solicited publish + passive subscribe
    - Minimal payload fields size

    Verifies that discovery and message exchange succeeds.
    """
        self.positive_discovery_test_utility(
            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
            payload_size=self.PAYLOAD_SIZE_MIN)

    @test_tracker_info(uuid="a02a47b9-41bb-47bb-883b-921024a2c30d")
    def test_positive_unsolicited_passive_max(self):
        """Functional test case / Discovery test cases / positive test case:
    - Solicited publish + passive subscribe
    - Maximal payload fields size

    Verifies that discovery and message exchange succeeds.
    """
        self.positive_discovery_test_utility(
            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
            payload_size=self.PAYLOAD_SIZE_MAX)

    @test_tracker_info(uuid="586c657f-2388-4e7a-baee-9bce2f3d1a16")
    def test_positive_solicited_active_typical(self):
        """Functional test case / Discovery test cases / positive test case:
    - Unsolicited publish + active subscribe
    - Typical payload fields size

    Verifies that discovery and message exchange succeeds.
    """
        self.positive_discovery_test_utility(
            ptype=aconsts.PUBLISH_TYPE_SOLICITED,
            stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
            payload_size=self.PAYLOAD_SIZE_TYPICAL)

    @test_tracker_info(uuid="5369e4ff-f406-48c5-b41a-df38ec340146")
    def test_positive_solicited_active_min(self):
        """Functional test case / Discovery test cases / positive test case:
    - Unsolicited publish + active subscribe
    - Minimal payload fields size

    Verifies that discovery and message exchange succeeds.
    """
        self.positive_discovery_test_utility(
            ptype=aconsts.PUBLISH_TYPE_SOLICITED,
            stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
            payload_size=self.PAYLOAD_SIZE_MIN)

    @test_tracker_info(uuid="634c6eb8-2c4f-42bd-9bbb-d874d0ec22f3")
    def test_positive_solicited_active_max(self):
        """Functional test case / Discovery test cases / positive test case:
    - Unsolicited publish + active subscribe
    - Maximal payload fields size

    Verifies that discovery and message exchange succeeds.
    """
        self.positive_discovery_test_utility(
            ptype=aconsts.PUBLISH_TYPE_SOLICITED,
            stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
            payload_size=self.PAYLOAD_SIZE_MAX)

    #######################################
    # TTL tests key:
    #
    # names is: test_ttl_<pub_type|sub_type>_<term_ind>
    # where:
    #
    # pub_type: Type of publish discovery session: unsolicited or solicited.
    # sub_type: Type of subscribe discovery session: passive or active.
    # term_ind: ind_on or ind_off
    #######################################

    @test_tracker_info(uuid="9d7e758e-e0e2-4550-bcee-bfb6a2bff63e")
    def test_ttl_unsolicited_ind_on(self):
        """Functional test case / Discovery test cases / TTL test case:
    - Unsolicited publish
    - Termination indication enabled
    """
        self.positive_ttl_test_utility(
            is_publish=True,
            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
            stype=None,
            term_ind_on=True)

    @test_tracker_info(uuid="48fd69bc-cc2a-4f65-a0a1-63d7c1720702")
    def test_ttl_unsolicited_ind_off(self):
        """Functional test case / Discovery test cases / TTL test case:
    - Unsolicited publish
    - Termination indication disabled
    """
        self.positive_ttl_test_utility(
            is_publish=True,
            ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
            stype=None,
            term_ind_on=False)

    @test_tracker_info(uuid="afb75fc1-9ba7-446a-b5ed-7cd37ab51b1c")
    def test_ttl_solicited_ind_on(self):
        """Functional test case / Discovery test cases / TTL test case:
    - Solicited publish
    - Termination indication enabled
    """
        self.positive_ttl_test_utility(
            is_publish=True,
            ptype=aconsts.PUBLISH_TYPE_SOLICITED,
            stype=None,
            term_ind_on=True)

    @test_tracker_info(uuid="703311a6-e444-4055-94ee-ea9b9b71799e")
    def test_ttl_solicited_ind_off(self):
        """Functional test case / Discovery test cases / TTL test case:
    - Solicited publish
    - Termination indication disabled
    """
        self.positive_ttl_test_utility(
            is_publish=True,
            ptype=aconsts.PUBLISH_TYPE_SOLICITED,
            stype=None,
            term_ind_on=False)

    @test_tracker_info(uuid="38a541c4-ff55-4387-87b7-4d940489da9d")
    def test_ttl_passive_ind_on(self):
        """Functional test case / Discovery test cases / TTL test case:
    - Passive subscribe
    - Termination indication enabled
    """
        self.positive_ttl_test_utility(
            is_publish=False,
            ptype=None,
            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
            term_ind_on=True)

    @test_tracker_info(uuid="ba971e12-b0ca-417c-a1b5-9451598de47d")
    def test_ttl_passive_ind_off(self):
        """Functional test case / Discovery test cases / TTL test case:
    - Passive subscribe
    - Termination indication disabled
    """
        self.positive_ttl_test_utility(
            is_publish=False,
            ptype=None,
            stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
            term_ind_on=False)

    @test_tracker_info(uuid="7b5d96f2-2415-4b98-9a51-32957f0679a0")
    def test_ttl_active_ind_on(self):
        """Functional test case / Discovery test cases / TTL test case:
    - Active subscribe
    - Termination indication enabled
    """
        self.positive_ttl_test_utility(
            is_publish=False,
            ptype=None,
            stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
            term_ind_on=True)

    @test_tracker_info(uuid="c9268eca-0a30-42dd-8e6c-b8b0b84697fb")
    def test_ttl_active_ind_off(self):
        """Functional test case / Discovery test cases / TTL test case:
    - Active subscribe
    - Termination indication disabled
    """
        self.positive_ttl_test_utility(
            is_publish=False,
            ptype=None,
            stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
            term_ind_on=False)

    #######################################
    # Mismatched service name tests key:
    #
    # names is: test_mismatch_service_name_<pub_type>_<sub_type>
    # where:
    #
    # pub_type: Type of publish discovery session: unsolicited or solicited.
    # sub_type: Type of subscribe discovery session: passive or active.
    #######################################

    @test_tracker_info(uuid="175415e9-7d07-40d0-95f0-3a5f91ea4711")
    def test_mismatch_service_name_unsolicited_passive(self):
        """Functional test case / Discovery test cases / Mismatch service name
    - Unsolicited publish
    - Passive subscribe
    """
        self.discovery_mismatch_test_utility(
            is_expected_to_pass=False,
            p_type=aconsts.PUBLISH_TYPE_UNSOLICITED,
            s_type=aconsts.SUBSCRIBE_TYPE_PASSIVE,
            p_service_name="GoogleTestServiceXXX",
            s_service_name="GoogleTestServiceYYY")

    @test_tracker_info(uuid="c22a54ce-9e46-47a5-ac44-831faf93d317")
    def test_mismatch_service_name_solicited_active(self):
        """Functional test case / Discovery test cases / Mismatch service name
    - Solicited publish
    - Active subscribe
    """
        self.discovery_mismatch_test_utility(
            is_expected_to_pass=False,
            p_type=aconsts.PUBLISH_TYPE_SOLICITED,
            s_type=aconsts.SUBSCRIBE_TYPE_ACTIVE,
            p_service_name="GoogleTestServiceXXX",
            s_service_name="GoogleTestServiceYYY")

    #######################################
    # Mismatched discovery session type tests key:
    #
    # names is: test_mismatch_service_type_<pub_type>_<sub_type>
    # where:
    #
    # pub_type: Type of publish discovery session: unsolicited or solicited.
    # sub_type: Type of subscribe discovery session: passive or active.
    #######################################

    @test_tracker_info(uuid="4806f631-d9eb-45fd-9e75-24674962770f")
    def test_mismatch_service_type_unsolicited_active(self):
        """Functional test case / Discovery test cases / Mismatch service name
    - Unsolicited publish
    - Active subscribe
    """
        self.discovery_mismatch_test_utility(
            is_expected_to_pass=True,
            p_type=aconsts.PUBLISH_TYPE_UNSOLICITED,
            s_type=aconsts.SUBSCRIBE_TYPE_ACTIVE)

    @test_tracker_info(uuid="12d648fd-b8fa-4c0f-9467-95e2366047de")
    def test_mismatch_service_type_solicited_passive(self):
        """Functional test case / Discovery test cases / Mismatch service name
    - Unsolicited publish
    - Active subscribe
    """
        self.discovery_mismatch_test_utility(
            is_expected_to_pass=False,
            p_type=aconsts.PUBLISH_TYPE_SOLICITED,
            s_type=aconsts.SUBSCRIBE_TYPE_PASSIVE)

    #######################################
    # Mismatched discovery match filter tests key:
    #
    # names is: test_mismatch_match_filter_<pub_type>_<sub_type>
    # where:
    #
    # pub_type: Type of publish discovery session: unsolicited or solicited.
    # sub_type: Type of subscribe discovery session: passive or active.
    #######################################

    @test_tracker_info(uuid="d98454cb-64af-4266-8fed-f0b545a2d7c4")
    def test_mismatch_match_filter_unsolicited_passive(self):
        """Functional test case / Discovery test cases / Mismatch match filter
    - Unsolicited publish
    - Passive subscribe
    """
        self.discovery_mismatch_test_utility(
            is_expected_to_pass=False,
            p_type=aconsts.PUBLISH_TYPE_UNSOLICITED,
            s_type=aconsts.SUBSCRIBE_TYPE_PASSIVE,
            p_mf_1="hello there string",
            s_mf_1="goodbye there string")

    @test_tracker_info(uuid="663c1008-ae11-4e1a-87c7-c311d83f481c")
    def test_mismatch_match_filter_solicited_active(self):
        """Functional test case / Discovery test cases / Mismatch match filter
    - Solicited publish
    - Active subscribe
    """
        self.discovery_mismatch_test_utility(
            is_expected_to_pass=False,
            p_type=aconsts.PUBLISH_TYPE_SOLICITED,
            s_type=aconsts.SUBSCRIBE_TYPE_ACTIVE,
            p_mf_1="hello there string",
            s_mf_1="goodbye there string")

    #######################################
    # Multiple concurrent services
    #######################################

    def run_multiple_concurrent_services(self, type_x, type_y):
        """Validate multiple identical discovery services running on both devices:
    - DUT1 & DUT2 running Publish for X
    - DUT1 & DUT2 running Publish for Y
    - DUT1 Subscribes for X
    - DUT2 Subscribes for Y
    Message exchanges.

    Note: test requires that devices support 2 publish sessions concurrently.
    The test will be skipped if the devices are not capable.

    Args:
      type_x, type_y: A list of [ptype, stype] of the publish and subscribe
                      types for services X and Y respectively.
    """
        dut1 = self.android_devices[0]
        dut2 = self.android_devices[1]

        X_SERVICE_NAME = "ServiceXXX"
        Y_SERVICE_NAME = "ServiceYYY"

        asserts.skip_if(
            dut1.aware_capabilities[aconsts.CAP_MAX_PUBLISHES] < 2
            or dut2.aware_capabilities[aconsts.CAP_MAX_PUBLISHES] < 2,
            "Devices do not support 2 publish sessions")

        # attach and wait for confirmation
        id1 = dut1.droid.wifiAwareAttach(False)
        autils.wait_for_event(dut1, aconsts.EVENT_CB_ON_ATTACHED)
        time.sleep(self.device_startup_offset)
        id2 = dut2.droid.wifiAwareAttach(False)
        autils.wait_for_event(dut2, aconsts.EVENT_CB_ON_ATTACHED)

        # DUT1 & DUT2: start publishing both X & Y services and wait for
        # confirmations
        dut1_x_pid = dut1.droid.wifiAwarePublish(
            id1, autils.create_discovery_config(X_SERVICE_NAME, type_x[0]))
        event = autils.wait_for_event(dut1,
                                      aconsts.SESSION_CB_ON_PUBLISH_STARTED)
        asserts.assert_equal(event["data"][aconsts.SESSION_CB_KEY_SESSION_ID],
                             dut1_x_pid,
                             "Unexpected DUT1 X publish session discovery ID")

        dut1_y_pid = dut1.droid.wifiAwarePublish(
            id1, autils.create_discovery_config(Y_SERVICE_NAME, type_y[0]))
        event = autils.wait_for_event(dut1,
                                      aconsts.SESSION_CB_ON_PUBLISH_STARTED)
        asserts.assert_equal(event["data"][aconsts.SESSION_CB_KEY_SESSION_ID],
                             dut1_y_pid,
                             "Unexpected DUT1 Y publish session discovery ID")

        dut2_x_pid = dut2.droid.wifiAwarePublish(
            id2, autils.create_discovery_config(X_SERVICE_NAME, type_x[0]))
        event = autils.wait_for_event(dut2,
                                      aconsts.SESSION_CB_ON_PUBLISH_STARTED)
        asserts.assert_equal(event["data"][aconsts.SESSION_CB_KEY_SESSION_ID],
                             dut2_x_pid,
                             "Unexpected DUT2 X publish session discovery ID")

        dut2_y_pid = dut2.droid.wifiAwarePublish(
            id2, autils.create_discovery_config(Y_SERVICE_NAME, type_y[0]))
        event = autils.wait_for_event(dut2,
                                      aconsts.SESSION_CB_ON_PUBLISH_STARTED)
        asserts.assert_equal(event["data"][aconsts.SESSION_CB_KEY_SESSION_ID],
                             dut2_y_pid,
                             "Unexpected DUT2 Y publish session discovery ID")

        # DUT1: start subscribing for X
        dut1_x_sid = dut1.droid.wifiAwareSubscribe(
            id1, autils.create_discovery_config(X_SERVICE_NAME, type_x[1]))
        autils.wait_for_event(dut1, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)

        # DUT2: start subscribing for Y
        dut2_y_sid = dut2.droid.wifiAwareSubscribe(
            id2, autils.create_discovery_config(Y_SERVICE_NAME, type_y[1]))
        autils.wait_for_event(dut2, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)

        # DUT1 & DUT2: wait for service discovery
        event = autils.wait_for_event(dut1,
                                      aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
        asserts.assert_equal(
            event["data"][aconsts.SESSION_CB_KEY_SESSION_ID], dut1_x_sid,
            "Unexpected DUT1 X subscribe session discovery ID")
        dut1_peer_id_for_dut2_x = event["data"][aconsts.SESSION_CB_KEY_PEER_ID]

        event = autils.wait_for_event(dut2,
                                      aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
        asserts.assert_equal(
            event["data"][aconsts.SESSION_CB_KEY_SESSION_ID], dut2_y_sid,
            "Unexpected DUT2 Y subscribe session discovery ID")
        dut2_peer_id_for_dut1_y = event["data"][aconsts.SESSION_CB_KEY_PEER_ID]

        # DUT1.X send message to DUT2
        x_msg = "Hello X on DUT2!"
        dut1.droid.wifiAwareSendMessage(dut1_x_sid, dut1_peer_id_for_dut2_x,
                                        self.get_next_msg_id(), x_msg,
                                        self.msg_retx_count)
        autils.wait_for_event(dut1, aconsts.SESSION_CB_ON_MESSAGE_SENT)
        event = autils.wait_for_event(dut2,
                                      aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
        asserts.assert_equal(
            event["data"][aconsts.SESSION_CB_KEY_SESSION_ID], dut2_x_pid,
            "Unexpected publish session ID on DUT2 for meesage "
            "received on service X")
        asserts.assert_equal(
            event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING], x_msg,
            "Message on service X from DUT1 to DUT2 not received correctly")

        # DUT2.Y send message to DUT1
        y_msg = "Hello Y on DUT1!"
        dut2.droid.wifiAwareSendMessage(dut2_y_sid, dut2_peer_id_for_dut1_y,
                                        self.get_next_msg_id(), y_msg,
                                        self.msg_retx_count)
        autils.wait_for_event(dut2, aconsts.SESSION_CB_ON_MESSAGE_SENT)
        event = autils.wait_for_event(dut1,
                                      aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
        asserts.assert_equal(
            event["data"][aconsts.SESSION_CB_KEY_SESSION_ID], dut1_y_pid,
            "Unexpected publish session ID on DUT1 for meesage "
            "received on service Y")
        asserts.assert_equal(
            event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING], y_msg,
            "Message on service Y from DUT2 to DUT1 not received correctly")

    @test_tracker_info(uuid="eef80cf3-1fd2-4526-969b-6af2dce785d7")
    def test_multiple_concurrent_services_both_unsolicited_passive(self):
        """Validate multiple concurrent discovery sessions running on both devices.
    - DUT1 & DUT2 running Publish for X
    - DUT1 & DUT2 running Publish for Y
    - DUT1 Subscribes for X
    - DUT2 Subscribes for Y
    Message exchanges.

    Both sessions are Unsolicited/Passive.

    Note: test requires that devices support 2 publish sessions concurrently.
    The test will be skipped if the devices are not capable.
    """
        self.run_multiple_concurrent_services(
            type_x=[
                aconsts.PUBLISH_TYPE_UNSOLICITED,
                aconsts.SUBSCRIBE_TYPE_PASSIVE
            ],
            type_y=[
                aconsts.PUBLISH_TYPE_UNSOLICITED,
                aconsts.SUBSCRIBE_TYPE_PASSIVE
            ])

    @test_tracker_info(uuid="46739f04-ab2b-4556-b1a4-9aa2774869b5")
    def test_multiple_concurrent_services_both_solicited_active(self):
        """Validate multiple concurrent discovery sessions running on both devices.
    - DUT1 & DUT2 running Publish for X
    - DUT1 & DUT2 running Publish for Y
    - DUT1 Subscribes for X
    - DUT2 Subscribes for Y
    Message exchanges.

    Both sessions are Solicited/Active.

    Note: test requires that devices support 2 publish sessions concurrently.
    The test will be skipped if the devices are not capable.
    """
        self.run_multiple_concurrent_services(
            type_x=[
                aconsts.PUBLISH_TYPE_SOLICITED, aconsts.SUBSCRIBE_TYPE_ACTIVE
            ],
            type_y=[
                aconsts.PUBLISH_TYPE_SOLICITED, aconsts.SUBSCRIBE_TYPE_ACTIVE
            ])

    @test_tracker_info(uuid="5f8f7fd2-4a0e-4cca-8cbb-6d54353f2baa")
    def test_multiple_concurrent_services_mix_unsolicited_solicited(self):
        """Validate multiple concurrent discovery sessions running on both devices.
    - DUT1 & DUT2 running Publish for X
    - DUT1 & DUT2 running Publish for Y
    - DUT1 Subscribes for X
    - DUT2 Subscribes for Y
    Message exchanges.

    Session A is Unsolicited/Passive.
    Session B is Solicited/Active.

    Note: test requires that devices support 2 publish sessions concurrently.
    The test will be skipped if the devices are not capable.
    """
        self.run_multiple_concurrent_services(
            type_x=[
                aconsts.PUBLISH_TYPE_UNSOLICITED,
                aconsts.SUBSCRIBE_TYPE_PASSIVE
            ],
            type_y=[
                aconsts.PUBLISH_TYPE_SOLICITED, aconsts.SUBSCRIBE_TYPE_ACTIVE
            ])

    #########################################################

    @test_tracker_info(uuid="908ec896-fc7a-4ee4-b633-a2f042b74448")
    def test_upper_lower_service_name_equivalence(self):
        """Validate that Service Name is case-insensitive. Publish a service name
    with mixed case, subscribe to the same service name with alternative case
    and verify that discovery happens."""
        p_dut = self.android_devices[0]
        s_dut = self.android_devices[1]

        pub_service_name = "GoogleAbCdEf"
        sub_service_name = "GoogleaBcDeF"

        autils.create_discovery_pair(
            p_dut,
            s_dut,
            p_config=autils.create_discovery_config(
                pub_service_name, aconsts.PUBLISH_TYPE_UNSOLICITED),
            s_config=autils.create_discovery_config(
                sub_service_name, aconsts.SUBSCRIBE_TYPE_PASSIVE),
            device_startup_offset=self.device_startup_offset)

    ##########################################################

    def exchange_messages(self, p_dut, p_disc_id, s_dut, s_disc_id, peer_id_on_sub, session_name):
        """
        Exchange message between Publisher and Subscriber on target discovery session

    Args:
      p_dut: Publisher device
      p_disc_id: Publish discovery session id
      s_dut: Subscriber device
      s_disc_id: Subscribe discovery session id
      peer_id_on_sub: Peer ID of the Publisher as seen on the Subscriber
      session_name: dictionary of discovery session name base on role("pub" or "sub")
                    {role: {disc_id: name}}
    """
        msg_template = "Hello {} from {} !"

        # Message send from Subscriber to Publisher
        s_to_p_msg = msg_template.format(session_name["pub"][p_disc_id],
                                         session_name["sub"][s_disc_id])
        s_dut.droid.wifiAwareSendMessage(s_disc_id,
                                         peer_id_on_sub,
                                         self.get_next_msg_id(),
                                         s_to_p_msg,
                                         self.msg_retx_count)
        autils.wait_for_event(s_dut,
                              autils.decorate_event(aconsts.SESSION_CB_ON_MESSAGE_SENT, s_disc_id))
        event = autils.wait_for_event(p_dut,
                                      autils.decorate_event(aconsts.SESSION_CB_ON_MESSAGE_RECEIVED,
                                                            p_disc_id))
        asserts.assert_equal(
            event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING], s_to_p_msg,
            "Message on service %s from Subscriber to Publisher "
            "not received correctly" % session_name["pub"][p_disc_id])
        try:
            event = p_dut.ed.pop_event(autils.decorate_event(aconsts.SESSION_CB_ON_MESSAGE_RECEIVED,
                                             p_disc_id), self.EVENT_TIMEOUT)
            p_dut.log.info("re-transmit message received: "
                           + event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING])
            asserts.assert_equal(
                event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING], s_to_p_msg,
                "Message on service %s from Subscriber to Publisher "
                "not received correctly" % session_name["pub"][p_disc_id])
        except queue.Empty:
            p_dut.log.info("no re-transmit message")

        peer_id_on_pub = event["data"][aconsts.SESSION_CB_KEY_PEER_ID]

        # Message send from Publisher to Subscriber
        p_to_s_msg = msg_template.format(session_name["sub"][s_disc_id],
                                         session_name["pub"][p_disc_id])
        p_dut.droid.wifiAwareSendMessage(p_disc_id,
                                         peer_id_on_pub,
                                         self.get_next_msg_id(), p_to_s_msg,
                                         self.msg_retx_count)
        autils.wait_for_event(
            p_dut, autils.decorate_event(aconsts.SESSION_CB_ON_MESSAGE_SENT, p_disc_id))
        event = autils.wait_for_event(s_dut,
                                      autils.decorate_event(aconsts.SESSION_CB_ON_MESSAGE_RECEIVED,
                                                            s_disc_id))
        asserts.assert_equal(
            event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING], p_to_s_msg,
            "Message on service %s from Publisher to Subscriber"
            "not received correctly" % session_name["sub"][s_disc_id])
        try:
            event = s_dut.ed.pop_event(autils.decorate_event(aconsts.SESSION_CB_ON_MESSAGE_RECEIVED,
                                                             s_disc_id), self.EVENT_TIMEOUT)
            s_dut.log.info("re-transmit message received: "
                           + event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING])
            asserts.assert_equal(
                event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING], p_to_s_msg,
                "Message on service %s from Publisher to Subscriber"
                "not received correctly" % session_name["sub"][s_disc_id])
        except queue.Empty:
            s_dut.log.info("no re-transmit message")

    def run_multiple_concurrent_services_same_name_diff_ssi(self, type_x, type_y):
        """Validate same service name with multiple service specific info on publisher
        and subscriber can see all service

    - p_dut running Publish X and Y
    - s_dut running subscribe A and B
    - subscribe A find X and Y
    - subscribe B find X and Y

    Message exchanges:
    - A to X and X to A
    - B to X and X to B
    - A to Y and Y to A
    - B to Y and Y to B

    Note: test requires that publisher device support 2 publish sessions concurrently,
    and subscriber device support 2 subscribe sessions concurrently.
    The test will be skipped if the devices are not capable.

    Args:
      type_x, type_y: A list of [ptype, stype] of the publish and subscribe
                      types for services X and Y respectively.
    """
        p_dut = self.android_devices[0]
        s_dut = self.android_devices[1]

        asserts.skip_if(
            p_dut.aware_capabilities[aconsts.CAP_MAX_PUBLISHES] < 2
            or s_dut.aware_capabilities[aconsts.CAP_MAX_SUBSCRIBES] < 2,
            "Devices do not support 2 publish sessions or 2 subscribe sessions")

        SERVICE_NAME = "ServiceName"
        X_SERVICE_SSI = "ServiceSpecificInfoXXX"
        Y_SERVICE_SSI = "ServiceSpecificInfoYYY"
        use_id = True

        # attach and wait for confirmation
        p_id = p_dut.droid.wifiAwareAttach(False, None, use_id)
        autils.wait_for_event(p_dut, autils.decorate_event(aconsts.EVENT_CB_ON_ATTACHED, p_id))
        time.sleep(self.device_startup_offset)
        s_id = s_dut.droid.wifiAwareAttach(False, None, use_id)
        autils.wait_for_event(s_dut, autils.decorate_event(aconsts.EVENT_CB_ON_ATTACHED, s_id))

        # Publisher: start publishing both X & Y services and wait for confirmations
        p_disc_id_x = p_dut.droid.wifiAwarePublish(
            p_id, autils.create_discovery_config(SERVICE_NAME, type_x[0], X_SERVICE_SSI), use_id)
        event = autils.wait_for_event(p_dut,
                                      autils.decorate_event(
                                          aconsts.SESSION_CB_ON_PUBLISH_STARTED, p_disc_id_x))

        p_disc_id_y = p_dut.droid.wifiAwarePublish(
            p_id, autils.create_discovery_config(SERVICE_NAME, type_x[0], Y_SERVICE_SSI), use_id)
        event = autils.wait_for_event(p_dut,
                                      autils.decorate_event(
                                          aconsts.SESSION_CB_ON_PUBLISH_STARTED, p_disc_id_y))

        # Subscriber: start subscribe session A
        s_disc_id_a = s_dut.droid.wifiAwareSubscribe(
            s_id, autils.create_discovery_config(SERVICE_NAME, type_x[1]), use_id)
        autils.wait_for_event(s_dut, autils.decorate_event(
            aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED, s_disc_id_a))

        # Subscriber: start subscribe session B
        s_disc_id_b = s_dut.droid.wifiAwareSubscribe(
            p_id, autils.create_discovery_config(SERVICE_NAME, type_y[1]), use_id)
        autils.wait_for_event(s_dut, autils.decorate_event(
            aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED, s_disc_id_b))

        session_name = {"pub": {p_disc_id_x: "X", p_disc_id_y: "Y"},
                        "sub": {s_disc_id_a: "A", s_disc_id_b: "B"}}

        # Subscriber: subscribe session A & B wait for service discovery
        # Number of results on each session should be exactly 2
        results_a = {}
        for i in range(2):
            event = autils.wait_for_event(s_dut, autils.decorate_event(
                aconsts.SESSION_CB_ON_SERVICE_DISCOVERED, s_disc_id_a))
            results_a[
                bytes(event["data"][
                          aconsts.SESSION_CB_KEY_SERVICE_SPECIFIC_INFO]).decode('utf-8')] = event
        autils.fail_on_event(s_dut, autils.decorate_event(
            aconsts.SESSION_CB_ON_SERVICE_DISCOVERED, s_disc_id_a))

        results_b = {}
        for i in range(2):
            event = autils.wait_for_event(s_dut, autils.decorate_event(
                aconsts.SESSION_CB_ON_SERVICE_DISCOVERED, s_disc_id_b))
            results_b[
                bytes(event["data"][
                          aconsts.SESSION_CB_KEY_SERVICE_SPECIFIC_INFO]).decode('utf-8')] = event
        autils.fail_on_event(s_dut, autils.decorate_event(
            aconsts.SESSION_CB_ON_SERVICE_DISCOVERED, s_disc_id_b))

        s_a_peer_id_for_p_x = results_a[X_SERVICE_SSI]["data"][aconsts.SESSION_CB_KEY_PEER_ID]
        s_a_peer_id_for_p_y = results_a[Y_SERVICE_SSI]["data"][aconsts.SESSION_CB_KEY_PEER_ID]
        s_b_peer_id_for_p_x = results_b[X_SERVICE_SSI]["data"][aconsts.SESSION_CB_KEY_PEER_ID]
        s_b_peer_id_for_p_y = results_b[Y_SERVICE_SSI]["data"][aconsts.SESSION_CB_KEY_PEER_ID]

        # Message exchange between Publisher and Subscribe
        self.exchange_messages(p_dut, p_disc_id_x,
                               s_dut, s_disc_id_a, s_a_peer_id_for_p_x, session_name)

        self.exchange_messages(p_dut, p_disc_id_x,
                               s_dut, s_disc_id_b, s_b_peer_id_for_p_x, session_name)

        self.exchange_messages(p_dut, p_disc_id_y,
                               s_dut, s_disc_id_a, s_a_peer_id_for_p_y, session_name)

        self.exchange_messages(p_dut, p_disc_id_y,
                               s_dut, s_disc_id_b, s_b_peer_id_for_p_y, session_name)

        # Check no more messages
        time.sleep(autils.EVENT_TIMEOUT)
        autils.verify_no_more_events(p_dut, timeout=0)
        autils.verify_no_more_events(s_dut, timeout=0)

        ##########################################################

    @test_tracker_info(uuid="78d89d63-1cbc-47f6-a8fc-74057fea655e")
    def test_multiple_concurrent_services_diff_ssi_unsolicited_passive(self):
        """Multi service test on same service name but different Service Specific Info
    - Unsolicited publish
    - Passive subscribe
    """
        self.run_multiple_concurrent_services_same_name_diff_ssi(
            type_x=[aconsts.PUBLISH_TYPE_UNSOLICITED, aconsts.SUBSCRIBE_TYPE_PASSIVE],
            type_y=[aconsts.PUBLISH_TYPE_UNSOLICITED, aconsts.SUBSCRIBE_TYPE_PASSIVE])

    @test_tracker_info(uuid="5d349491-48e4-4ca1-a8af-7afb44e7bcbc")
    def test_multiple_concurrent_services_diff_ssi_solicited_active(self):
        """Multi service test on same service name but different Service Specific Info
    - Solicited publish
    - Active subscribe
    """
        self.run_multiple_concurrent_services_same_name_diff_ssi(
            type_x=[aconsts.PUBLISH_TYPE_SOLICITED, aconsts.SUBSCRIBE_TYPE_ACTIVE],
            type_y=[aconsts.PUBLISH_TYPE_SOLICITED, aconsts.SUBSCRIBE_TYPE_ACTIVE])

    def run_service_discovery_on_service_lost(self, p_type, s_type):
        """
        Validate service lost callback will be receive on subscriber, when publisher stopped publish
    - p_dut running Publish
    - s_dut running subscribe
    - s_dut discover p_dut
    - p_dut stop publish
    - s_dut receive service lost callback

    Args:
      p_type: Publish discovery type
      s_type: Subscribe discovery type
    """
        p_dut = self.android_devices[0]
        p_dut.pretty_name = "Publisher"
        s_dut = self.android_devices[1]
        s_dut.pretty_name = "Subscriber"

        asserts.skip_if(not s_dut.droid.isSdkAtLeastS(),
                        "R build and below do not have onServiceLost API.")

        # Publisher+Subscriber: attach and wait for confirmation
        p_id = p_dut.droid.wifiAwareAttach(False)
        autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
        time.sleep(self.device_startup_offset)
        s_id = s_dut.droid.wifiAwareAttach(False)
        autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)

        # Publisher: start publish and wait for confirmation
        p_config = self.create_publish_config(
            p_dut.aware_capabilities,
            p_type,
            self.PAYLOAD_SIZE_TYPICAL,
            ttl=0,
            term_ind_on=False,
            null_match=False)
        p_disc_id = p_dut.droid.wifiAwarePublish(p_id, p_config)
        autils.wait_for_event(p_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)

        # Subscriber: start subscribe and wait for confirmation
        s_config = self.create_subscribe_config(
            s_dut.aware_capabilities,
            s_type,
            self.PAYLOAD_SIZE_TYPICAL,
            ttl=0,
            term_ind_on=False,
            null_match=True)
        s_disc_id = s_dut.droid.wifiAwareSubscribe(s_id, s_config)
        autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)

        # Subscriber: wait for service discovery
        discovery_event = autils.wait_for_event(
            s_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
        peer_id_on_sub = discovery_event["data"][
            aconsts.SESSION_CB_KEY_PEER_ID]

        # Publisher+Subscriber: Terminate sessions
        p_dut.droid.wifiAwareDestroyDiscoverySession(p_disc_id)
        time.sleep(10)
        service_lost_event = autils.wait_for_event(
            s_dut, aconsts.SESSION_CB_ON_SERVICE_LOST)
        asserts.assert_equal(peer_id_on_sub,
                             service_lost_event["data"][aconsts.SESSION_CB_KEY_PEER_ID])
        asserts.assert_equal(aconsts.REASON_PEER_NOT_VISIBLE,
                             service_lost_event["data"][aconsts.SESSION_CB_KEY_LOST_REASON])

        s_dut.droid.wifiAwareDestroyDiscoverySession(s_disc_id)

    @test_tracker_info(uuid="b1894ce3-8692-478b-a96f-db2797e22caa")
    def test_service_discovery_on_service_lost_unsolicited_passive(self):
        """
        Test service discovery lost with unsolicited publish and passive subscribe
        """
        self.run_service_discovery_on_service_lost(aconsts.PUBLISH_TYPE_UNSOLICITED,
                                                   aconsts.SUBSCRIBE_TYPE_PASSIVE)

    @test_tracker_info(uuid="4470d897-223a-4f9f-b21f-4061943137dd")
    def test_service_discovery_on_service_lost_solicited_active(self):
        """
        Test service discovery lost with solicited publish and active subscribe
        """
        self.run_service_discovery_on_service_lost(aconsts.PUBLISH_TYPE_SOLICITED,
                                                   aconsts.SUBSCRIBE_TYPE_ACTIVE)
