Merge "Rewrite the power measurement retry loop" am: e6e7030c75
am: a5f9a0d432

Change-Id: I4167b2cb33ff9da36753c32a47c7b9ce7db6e4bd
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 8429cf6..5ad47bf 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,5 +1,6 @@
 [Hook Scripts]
 acts_base_class_test = ./acts/framework/tests/acts_base_class_test.py
+acts_adb_test = ./acts/framework/tests/acts_adb_test.py
 acts_android_device_test = ./acts/framework/tests/acts_android_device_test.py
 acts_asserts_test = ./acts/framework/tests/acts_asserts_test.py
 acts_base_class_test = ./acts/framework/tests/acts_base_class_test.py
@@ -14,7 +15,10 @@
 acts_import_test_utils_test = ./acts/framework/tests/acts_import_test_utils_test.py
 acts_import_unit_test = ./acts/framework/tests/acts_import_unit_test.py
 acts_relay_controller_test = ./acts/framework/tests/acts_relay_controller_test.py
+commit_message_hook = ./tools/commit_message_check.py
 yapf_hook = ./tools/yapf_checker.py
+commit_message_check = ./tools/commit_message_check.py
+lab_test =  ./tools/lab/test_main.py
 
 [Builtin Hooks]
 commit_msg_bug_field = true
diff --git a/acts/README.md b/acts/README.md
index bf0ec48..caa2f0c 100644
--- a/acts/README.md
+++ b/acts/README.md
@@ -20,6 +20,19 @@
 [Python Style Guide](https://google.github.io/styleguide/pyguide.html), and
 it is recommended for all new test cases.
 
+System dependencies:
+  - adb
+  - python3.4+
+  - python3.4-setuptools
+
+Python dependencies (installed automatically by setup.py):
+  - future
+  - pyserial
+
+To run unit tests:
+$ python3 setup.py test
+$ python setup.py test
+
 ## ACTS Execution Flow Overview
 Below is a high level view of the ACTS flow:
 
@@ -57,9 +70,12 @@
 displayed. Check the "Always" box and click "Yes".
 
 ## ACTS Setup
-From the ACTS directory, run setup
 
-- `$ sudo python setup.py develop`
+1. Install the system dependencies.
+     On Ubuntu, sudo apt-get install python3.4 python3-setuptools
+2. Run "python3.4 setup.py install" with elevated permissions
+3. To verify ACTS is ready to go, at the location for README, and run:
+     cd tests/ && act.py -c acts_sanity_test_config.json -tc IntegrationTest
 
 After installation, `act.py` will be in usr/bin and can be called as command
 line utilities. Components in ACTS are importable under the package "acts."
@@ -71,34 +87,15 @@
 >>> device_list = android_device.get_all_instances()
 ```
 
-## Verifying Setup
-To verify the host and device are ready, from the frameworks folder run:
+## Breaking Down a Sample Command
 
-- `$ act.py -c sample_config.json -tb SampleTestBed -tc SampleTest`
-
-If the above command executed successfully, the ouput should look something
-similar to following:
-
-```
-[SampleTestBed] 07-22 15:23:50.323 INFO ==========> SampleTest <==========
-[SampleTestBed] 07-22 15:23:50.327 INFO [Test Case] test_make_toast
-[SampleTestBed] 07-22 15:23:50.334 INFO [Test Case] test_make_toast PASS
-[SampleTestBed] 07-22 15:23:50.338 INFO Summary for test class SampleTest:
-Requested 1, Executed 1, Passed 1, Failed 0, Skipped 0
-[SampleTestBed] 07-22 15:23:50.338 INFO Summary for test run
-SampleTestBed@07-22-2015_1-23-44-096: Requested 1, Executed 1, Passed 1,
-Failed 0, Skipped 0
-```
-
-By default, all logs are saved in `/tmp/logs`
-
-## Breaking Down the Example
-Below are the components of the command run for the SampleTest:
+Above, the command `act.py -c acts_sanity_test_config.json -tc IntegrationTest`
+was run to verify ACTS was properly set up.
+Below are the components of that command:
 - `acts.py`: is the script that runs the test
--  -c sample_config: is the flag and name of the configuration file to be used
-in the test
--  -tb StampleTestBed: is the flag and name of the test bed to be used
--  -tc SampleTest: is the name of the test case
+-  -c acts_sanity_test_config: is the flag and name of the configuration file
+to be used in the test
+-  -tc IntegrationTest: is the name of the test case
 
 ### Configuration Files
 To run tests, required information must be provided via a json-formatted
@@ -133,8 +130,8 @@
 and are created based on the provided configuration file.
 
 Test classes must also contain an iterable member self.tests that lists the
-test case names within the class.  More on this is discussed after the following
-code snippet.
+test case names within the class.  More on this is discussed after the
+following code snippet.
 
 ```
 from acts.base_test import BaseTestClass
diff --git a/acts/framework/README b/acts/framework/README
deleted file mode 100644
index 43c98aa..0000000
--- a/acts/framework/README
+++ /dev/null
@@ -1,21 +0,0 @@
-This package includes the Android Comms Testing Suite (ACTS) alpha release
-
-System dependencies:
-  - adb
-  - python3.4+
-  - python3.4-setuptools
-
-Python dependencies (installed automatically by setup.py):
-  - future
-  - pyserial
-
-To run unit tests:
-$ python3 setup.py test
-$ python setup.py test
-
-Setup:
-  1. Install the system dependencies.
-     On Ubuntu, sudo apt-get install python3.4 python3-setuptools
-  2. Run "python3.4 setup.py install" with elevated permissions
-  3. To verify ACTS is ready to go, at the location for README, and run:
-     cd tests/ && act.py -c acts_sanity_test_config.json -tc IntegrationTest
diff --git a/acts/framework/acts/controllers/adb.py b/acts/framework/acts/controllers/adb.py
index 5524700..0a652a3 100644
--- a/acts/framework/acts/controllers/adb.py
+++ b/acts/framework/acts/controllers/adb.py
@@ -29,6 +29,9 @@
 
 DEFAULT_ADB_TIMEOUT = 60
 DEFAULT_ADB_PULL_TIMEOUT = 180
+# Uses a regex to be backwards compatible with previous versions of ADB
+# (N and above add the serial to the error msg).
+DEVICE_NOT_FOUND_REGEX = re.compile('^error: device (?:\'.*?\' )?not found')
 
 
 def parsing_parcel_output(output):
@@ -107,31 +110,28 @@
     def _exec_cmd(self, cmd, ignore_status=False, timeout=DEFAULT_ADB_TIMEOUT):
         """Executes adb commands in a new shell.
 
-        This is specific to executing adb binary because stderr is not a good
-        indicator of cmd execution status.
+        This is specific to executing adb commands.
 
         Args:
-            cmds: A string that is the adb command to execute.
+            cmd: A string that is the adb command to execute.
 
         Returns:
-            The output of the adb command run if exit code is 0.
+            The stdout of the adb command.
 
         Raises:
-            AdbError is raised if the adb command exit code is not 0.
+            AdbError is raised if adb cannot find the device.
         """
         result = job.run(cmd, ignore_status=True, timeout=timeout)
         ret, out, err = result.exit_status, result.stdout, result.stderr
 
         logging.debug("cmd: %s, stdout: %s, stderr: %s, ret: %s", cmd, out,
                       err, ret)
-
-        if ret == 0 or ignore_status:
-            if "Result: Parcel" in out:
-                return parsing_parcel_output(out)
-            else:
-                return out
-        else:
+        if not ignore_status and ret == 1 and DEVICE_NOT_FOUND_REGEX.match(err):
             raise AdbError(cmd=cmd, stdout=out, stderr=err, ret_code=ret)
+        elif "Result: Parcel" in out:
+            return parsing_parcel_output(out)
+        else:
+            return out
 
     def _exec_adb_cmd(self, name, arg_str, **kwargs):
         return self._exec_cmd(' '.join((self.adb_str, name, arg_str)),
diff --git a/acts/framework/acts/controllers/android_device.py b/acts/framework/acts/controllers/android_device.py
index b5ac863..29e2be0 100755
--- a/acts/framework/acts/controllers/android_device.py
+++ b/acts/framework/acts/controllers/android_device.py
@@ -791,18 +791,15 @@
         Returns:
         True if package is installed. False otherwise.
         """
+
         try:
-            out = self.adb.shell(
-                'pm list packages | grep -w "package:%s"' % package_name,
-                ignore_status=False)
-            if package_name in out:
-                self.log.debug("apk %s is installed", package_name)
-                return True
-            else:
-                self.log.debug("apk %s is not installed, query returns %s",
-                               package_name, out)
-                return False
-        except:
+            return bool(
+                self.adb.shell('pm list packages | grep -w "package:%s"' %
+                               package_name))
+
+        except Exception as err:
+            self.log.error('Could not determine if %s is installed. '
+                           'Received error:\n%s', package_name, err)
             return False
 
     def is_sl4a_installed(self):
diff --git a/acts/framework/acts/controllers/native.py b/acts/framework/acts/controllers/native.py
index 7c9df2b..aa987b1 100644
--- a/acts/framework/acts/controllers/native.py
+++ b/acts/framework/acts/controllers/native.py
@@ -14,7 +14,7 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
-from acts.controllers.android import Android
+from acts.controllers.sl4a_client import Sl4aClient
 import json
 import os
 import socket
@@ -46,13 +46,12 @@
         i += 1
 
 
-class NativeAndroid(Android):
+class NativeAndroid(Sl4aClient):
     COUNTER = IDCounter()
 
     def _rpc(self, method, *args):
-        self.lock.acquire()
-        apiid = next(NativeAndroid.COUNTER)
-        self.lock.release()
+        with self._lock:
+            apiid = next(self._counter)
         data = {'id': apiid, 'method': method, 'params': args}
         request = json.dumps(data)
         self.client.write(request.encode("utf8") + b'\n')
@@ -61,9 +60,8 @@
         if not response:
             raise SL4NProtocolError(SL4NProtocolError.NO_RESPONSE_FROM_SERVER)
         #TODO: (tturney) fix the C side from sending \x00 char over the socket.
-        result = json.loads(str(response,
-                                encoding="utf8").rstrip().replace("\x00", ""))
-
+        result = json.loads(
+            str(response, encoding="utf8").rstrip().replace("\x00", ""))
         if result['error']:
             raise SL4NAPIError(result['error'])
         if result['id'] != apiid:
diff --git a/acts/framework/acts/controllers/native_android_device.py b/acts/framework/acts/controllers/native_android_device.py
index 00662b3..cbe3237 100644
--- a/acts/framework/acts/controllers/native_android_device.py
+++ b/acts/framework/acts/controllers/native_android_device.py
@@ -15,7 +15,7 @@
 #   limitations under the License.
 
 from acts.controllers.android_device import AndroidDevice
-from acts.controllers.utils_lib  import host_utils
+from acts.controllers.utils_lib import host_utils
 import acts.controllers.native as native
 from subprocess import call
 
@@ -29,8 +29,8 @@
 
 
 def create(configs):
-    logger = logging.getLogger()
-    ads = get_instances(configs, logger)
+    logger = logging
+    ads = get_instances(configs)
     for ad in ads:
         try:
             ad.get_droid()
@@ -43,7 +43,7 @@
     pass
 
 
-def get_instances(serials, logger=None):
+def get_instances(serials, ):
     """Create AndroidDevice instances from a list of serials.
 
     Args:
@@ -55,7 +55,7 @@
     """
     results = []
     for s in serials:
-        results.append(NativeAndroidDevice(s, logger=logger))
+        results.append(NativeAndroidDevice(s))
     return results
 
 
@@ -97,12 +97,10 @@
         if not self.h_port or not host_utils.is_port_available(self.h_port):
             self.h_port = host_utils.get_available_host_port()
         self.adb.tcp_forward(self.h_port, self.d_port)
-        pid = self.adb.shell("ps | grep sl4n | awk '{print $2}'").decode(
-            'ascii')
+        pid = self.adb.shell("pidof -s sl4n", ignore_status=True)
         while (pid):
             self.adb.shell("kill {}".format(pid))
-            pid = self.adb.shell("ps | grep sl4n | awk '{print $2}'").decode(
-                'ascii')
+            pid = self.adb.shell("pidof -s sl4n", ignore_status=True)
         call(
             ["adb -s " + self.serial + " shell sh -c \"/system/bin/sl4n\" &"],
             shell=True)
@@ -127,6 +125,7 @@
             existing uid to a new session.
         """
         droid = native.NativeAndroid(port=self.h_port)
+        droid.open()
         if droid.uid in self._droid_sessions:
             raise bt.SL4NException(("SL4N returned an existing uid for a "
                                     "new session. Abort."))
diff --git a/acts/framework/acts/test_runner.py b/acts/framework/acts/test_runner.py
index b47a47b..aa81976 100644
--- a/acts/framework/acts/test_runner.py
+++ b/acts/framework/acts/test_runner.py
@@ -287,8 +287,8 @@
         for ctrl_name in keys.Config.builtin_controller_names.value:
             if ctrl_name in self.testbed_configs:
                 module_name = keys.get_module_name(ctrl_name)
-                module = importlib.import_module("acts.controllers.%s" %
-                                                 module_name)
+                module = importlib.import_module(
+                    "acts.controllers.%s" % module_name)
                 builtin_controllers.append(module)
         return builtin_controllers
 
@@ -315,8 +315,8 @@
                                                            attr))
             if not getattr(module, attr):
                 raise signals.ControllerError(
-                    "Controller interface %s in %s cannot be null." % (
-                     attr, module.__name__))
+                    "Controller interface %s in %s cannot be null." %
+                    (attr, module.__name__))
 
     def register_controller(self, module, required=True):
         """Registers an ACTS controller module for a test run.
@@ -434,8 +434,8 @@
         # tracking controller objs in test_run_info.
         if builtin:
             self.test_run_info[module_ref_name] = objects
-        self.log.debug("Found %d objects for controller %s", len(objects),
-                      module_config_name)
+        self.log.debug("Found %d objects for controller %s",
+                       len(objects), module_config_name)
         destroy_func = module.destroy
         self.controller_destructors[module_ref_name] = destroy_func
         return objects
diff --git a/acts/framework/acts/test_utils/bt/bt_gatt_utils.py b/acts/framework/acts/test_utils/bt/bt_gatt_utils.py
index a6d5b81..3f80d68 100644
--- a/acts/framework/acts/test_utils/bt/bt_gatt_utils.py
+++ b/acts/framework/acts/test_utils/bt/bt_gatt_utils.py
@@ -40,11 +40,12 @@
 def setup_gatt_connection(cen_ad,
                           mac_address,
                           autoconnect,
-                          transport=gatt_transport['auto']):
+                          transport=gatt_transport['auto'],
+                          opportunistic=False):
     gatt_callback = cen_ad.droid.gattCreateGattCallback()
     log.info("Gatt Connect to mac address {}.".format(mac_address))
     bluetooth_gatt = cen_ad.droid.gattClientConnectGatt(
-        gatt_callback, mac_address, autoconnect, transport,
+        gatt_callback, mac_address, autoconnect, transport, opportunistic,
         gatt_phy_mask['1m_mask'])
     expected_event = gatt_cb_strings['gatt_conn_change'].format(gatt_callback)
     try:
@@ -66,6 +67,13 @@
     return bluetooth_gatt, gatt_callback
 
 
+def close_gatt_client(cen_ad, bluetooth_gatt):
+    try:
+        cen_ad.droid.gattClientClose(bluetooth_gatt)
+    except Exception:
+        log.debug("Failed to close gatt client.")
+
+
 def disconnect_gatt_connection(cen_ad, bluetooth_gatt, gatt_callback):
     cen_ad.droid.gattClientDisconnect(bluetooth_gatt)
     wait_for_gatt_disconnect_event(cen_ad, gatt_callback)
@@ -92,7 +100,8 @@
                                 per_ad,
                                 transport=gatt_transport['le'],
                                 mac_address=None,
-                                autoconnect=False):
+                                autoconnect=False,
+                                opportunistic=False):
     adv_callback = None
     if mac_address is None:
         if transport == gatt_transport['le']:
@@ -106,7 +115,7 @@
             mac_address = per_ad.droid.bluetoothGetLocalAddress()
             adv_callback = None
     bluetooth_gatt, gatt_callback = setup_gatt_connection(
-        cen_ad, mac_address, autoconnect, transport)
+        cen_ad, mac_address, autoconnect, transport, opportunistic)
     return bluetooth_gatt, gatt_callback, adv_callback
 
 
diff --git a/acts/framework/acts/test_utils/bt/native_bt_test_utils.py b/acts/framework/acts/test_utils/bt/native_bt_test_utils.py
index d070f13..9be24f0 100644
--- a/acts/framework/acts/test_utils/bt/native_bt_test_utils.py
+++ b/acts/framework/acts/test_utils/bt/native_bt_test_utils.py
@@ -25,15 +25,14 @@
 def setup_native_bluetooth(native_devices):
     for n in native_devices:
         droid = n.droid
-        pid = n.adb.shell("ps | grep bluetoothtbd | awk '{print $2}'").decode(
-            'ascii')
+        pid = n.adb.shell("pidof -s bluetoothtbd")
         if not pid:
             call(
                 ["adb -s " + n.serial + " shell sh -c \"bluetoothtbd\" &"],
                 shell=True)
-        droid.BluetoothBinderInitInterface()
+        droid.BtBinderInitInterface()
         time.sleep(5)  #temporary sleep statement
-        droid.BluetoothBinderEnable()
+        droid.BtBinderEnable()
         time.sleep(5)  #temporary sleep statement
-        droid.BluetoothBinderRegisterBLE()
+        droid.BtBinderRegisterBLE()
         time.sleep(5)  #temporary sleep statement
diff --git a/acts/framework/acts/test_utils/net/connectivity_const.py b/acts/framework/acts/test_utils/net/connectivity_const.py
index de8de47..89bdb2c 100644
--- a/acts/framework/acts/test_utils/net/connectivity_const.py
+++ b/acts/framework/acts/test_utils/net/connectivity_const.py
@@ -39,6 +39,8 @@
 NETWORK_CB_KEY_MAX_MS_TO_LIVE = "maxMsToLive"
 NETWORK_CB_KEY_RSSI = "rssi"
 NETWORK_CB_KEY_INTERFACE_NAME = "interfaceName"
+NETWORK_CB_KEY_CREATE_TS = "creation_timestamp"
+NETWORK_CB_KEY_CURRENT_TS = "current_timestamp"
 
 # Constants for VPN connection status
 VPN_STATE_DISCONNECTED = 0
diff --git a/acts/framework/acts/test_utils/tel/tel_test_utils.py b/acts/framework/acts/test_utils/tel/tel_test_utils.py
index e97a406..d02307a 100644
--- a/acts/framework/acts/test_utils/tel/tel_test_utils.py
+++ b/acts/framework/acts/test_utils/tel/tel_test_utils.py
@@ -3886,10 +3886,12 @@
     Phone not in airplane mode.
     """
     result = True
+
     if not toggle_airplane_mode(log, ad, False, False):
         ad.log.error("Fail to turn off airplane mode")
         result = False
     set_wifi_to_default(log, ad)
+
     try:
         if ad.droid.telecomIsInCall():
             ad.droid.telecomEndCall()
diff --git a/acts/framework/acts/test_utils/wifi/aware/AwareBaseTest.py b/acts/framework/acts/test_utils/wifi/aware/AwareBaseTest.py
new file mode 100644
index 0000000..d95a3eb
--- /dev/null
+++ b/acts/framework/acts/test_utils/wifi/aware/AwareBaseTest.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2017 - Google
+#
+#   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.
+
+from acts import asserts
+from acts.base_test import BaseTestClass
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+
+
+class AwareBaseTest(BaseTestClass):
+  def __init__(self, controllers):
+    BaseTestClass.__init__(self, controllers)
+
+  # message ID counter to make sure all uses are unique
+  msg_id = 0
+
+  # offset (in seconds) to separate the start-up of multiple devices.
+  # De-synchronizes the start-up time so that they don't start and stop scanning
+  # at the same time - which can lead to very long clustering times.
+  device_startup_offset = 2
+
+  def setup_test(self):
+    required_params = ("aware_default_power_mode", )
+    self.unpack_userparams(required_params)
+
+    for ad in self.android_devices:
+      wutils.wifi_toggle_state(ad, True)
+      ad.droid.wifiP2pClose()
+      aware_avail = ad.droid.wifiIsAwareAvailable()
+      if not aware_avail:
+        self.log.info('Aware not available. Waiting ...')
+        autils.wait_for_event(ad, aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+      ad.ed.pop_all(aconsts.BROADCAST_WIFI_AWARE_AVAILABLE) # clear-out extras
+      ad.aware_capabilities = autils.get_aware_capabilities(ad)
+      self.reset_device_parameters(ad)
+      self.reset_device_statistics(ad)
+      self.set_power_mode_parameters(ad)
+
+  def teardown_test(self):
+    for ad in self.android_devices:
+      ad.droid.wifiP2pClose()
+      ad.droid.wifiAwareDestroyAll()
+      self.reset_device_parameters(ad)
+      autils.validate_forbidden_callbacks(ad)
+
+  def reset_device_parameters(self, ad):
+    """Reset device configurations which may have been set by tests. Should be
+    done before tests start (in case previous one was killed without tearing
+    down) and after they end (to leave device in usable state).
+
+    Args:
+      ad: device to be reset
+    """
+    ad.adb.shell("cmd wifiaware reset")
+
+  def reset_device_statistics(self, ad):
+    """Reset device statistics.
+
+    Args:
+        ad: device to be reset
+    """
+    ad.adb.shell("cmd wifiaware native_cb get_cb_count --reset")
+
+  def set_power_mode_parameters(self, ad):
+    """Set the power configuration DW parameters for the device based on any
+    configuration overrides (if provided)"""
+    if self.aware_default_power_mode == "INTERACTIVE":
+      autils.config_dw_high_power(ad)
+    elif self.aware_default_power_mode == "NON_INTERACTIVE":
+      autils.config_dw_low_power(ad)
+    else:
+      asserts.assert_false(
+          "The 'aware_default_power_mode' configuration must be INTERACTIVE or "
+          "NON_INTERACTIVE"
+      )
+
+  def get_next_msg_id(self):
+    """Increment the message ID and returns the new value. Guarantees that
+    each call to the method returns a unique value.
+
+    Returns: a new message id value.
+    """
+    self.msg_id = self.msg_id + 1
+    return self.msg_id
diff --git a/acts/framework/acts/test_utils/wifi/aware/__init__.py b/acts/framework/acts/test_utils/wifi/aware/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts/framework/acts/test_utils/wifi/aware/__init__.py
diff --git a/acts/framework/acts/test_utils/wifi/wifi_aware_const.py b/acts/framework/acts/test_utils/wifi/aware/aware_const.py
similarity index 66%
rename from acts/framework/acts/test_utils/wifi/wifi_aware_const.py
rename to acts/framework/acts/test_utils/wifi/aware/aware_const.py
index 7e495f5..a7574a4 100644
--- a/acts/framework/acts/test_utils/wifi/wifi_aware_const.py
+++ b/acts/framework/acts/test_utils/wifi/aware/aware_const.py
@@ -15,6 +15,16 @@
 #   limitations under the License.
 
 ######################################################
+# Aware DW (Discovery Window) power mode values
+######################################################
+
+DW_24_INTERACTIVE = 1
+DW_5_INTERACTIVE = 1
+
+DW_24_NON_INTERACTIVE = 4
+DW_5_NON_INTERACTIVE = 0
+
+######################################################
 # Broadcast events
 ######################################################
 BROADCAST_WIFI_AWARE_AVAILABLE = "WifiAwareAvailable"
@@ -31,29 +41,22 @@
 CONFIG_KEY_ENABLE_IDEN_CB = "EnableIdentityChangeCallback"
 
 ######################################################
-# PublishConfig keys
+# Publish & Subscribe Config keys
 ######################################################
 
-PUBLISH_KEY_SERVICE_NAME = "ServiceName"
-PUBLISH_KEY_SSI = "ServiceSpecificInfo"
-PUBLISH_KEY_MATCH_FILTER = "MatchFilter"
-PUBLISH_KEY_TYPE = "PublishType"
-PUBLISH_KEY_COUNT = "PublishCount"
-PUBLISH_KEY_TTL = "TtlSec"
-PUBLISH_KEY_TERM_CB_ENABLED = "TerminateNotificationEnabled"
+DISCOVERY_KEY_SERVICE_NAME = "ServiceName"
+DISCOVERY_KEY_SSI = "ServiceSpecificInfo"
+DISCOVERY_KEY_MATCH_FILTER = "MatchFilter"
+DISCOVERY_KEY_MATCH_FILTER_LIST = "MatchFilterList"
+DISCOVERY_KEY_DISCOVERY_TYPE = "DiscoveryType"
+DISCOVERY_KEY_TTL = "TtlSec"
+DISCOVERY_KEY_TERM_CB_ENABLED = "TerminateNotificationEnabled"
 
-######################################################
-# SubscribeConfig keys
-######################################################
+PUBLISH_TYPE_UNSOLICITED = 0
+PUBLISH_TYPE_SOLICITED = 1
 
-SUBSCRIBE_KEY_SERVICE_NAME = "ServiceName"
-SUBSCRIBE_KEY_SSI = "ServiceSpecificInfo"
-SUBSCRIBE_KEY_MATCH_FILTER = "MatchFilter"
-SUBSCRIBE_KEY_TYPE = "SubscribeType"
-SUBSCRIBE_KEY_COUNT = "SubscribeCount"
-SUBSCRIBE_KEY_TTL = "TtlSec"
-SUBSCRIBE_KEY_STYLE = "MatchStyle"
-SUBSCRIBE_KEY_ENABLE_TERM_CB = "EnableTerminateNotification"
+SUBSCRIBE_TYPE_PASSIVE = 0
+SUBSCRIBE_TYPE_ACTIVE = 1
 
 ######################################################
 # WifiAwareAttachCallback events
@@ -92,6 +95,7 @@
 SESSION_CB_KEY_PEER_ID = "peerId"
 SESSION_CB_KEY_SERVICE_SPECIFIC_INFO = "serviceSpecificInfo"
 SESSION_CB_KEY_MATCH_FILTER = "matchFilter"
+SESSION_CB_KEY_MATCH_FILTER_LIST = "matchFilterList"
 SESSION_CB_KEY_MESSAGE = "message"
 SESSION_CB_KEY_MESSAGE_ID = "messageId"
 SESSION_CB_KEY_MESSAGE_AS_STRING = "messageAsString"
@@ -113,6 +117,32 @@
 RTT_LISTENER_CB_KEY_DESCRIPTION = "description"
 
 ######################################################
+# Capabilities keys
+######################################################
+
+CAP_MAX_CONCURRENT_AWARE_CLUSTERS = "maxConcurrentAwareClusters"
+CAP_MAX_PUBLISHES = "maxPublishes"
+CAP_MAX_SUBSCRIBES = "maxSubscribes"
+CAP_MAX_SERVICE_NAME_LEN = "maxServiceNameLen"
+CAP_MAX_MATCH_FILTER_LEN = "maxMatchFilterLen"
+CAP_MAX_TOTAL_MATCH_FILTER_LEN = "maxTotalMatchFilterLen"
+CAP_MAX_SERVICE_SPECIFIC_INFO_LEN = "maxServiceSpecificInfoLen"
+CAP_MAX_EXTENDED_SERVICE_SPECIFIC_INFO_LEN = "maxExtendedServiceSpecificInfoLen"
+CAP_MAX_NDI_INTERFACES = "maxNdiInterfaces"
+CAP_MAX_NDP_SESSIONS = "maxNdpSessions"
+CAP_MAX_APP_INFO_LEN = "maxAppInfoLen"
+CAP_MAX_QUEUED_TRANSMIT_MESSAGES = "maxQueuedTransmitMessages"
+CAP_MAX_SUBSCRIBE_INTERFACE_ADDRESSES = "maxSubscribeInterfaceAddresses"
+CAP_SUPPORTED_CIPHER_SUITES = "supportedCipherSuites"
+
+######################################################
+
+# Aware NDI (NAN data-interface) name prefix
+AWARE_NDI_PREFIX = "aware_data"
+
+# Aware discovery channels
+AWARE_DISCOVERY_CHANNEL_24_BAND = 6
+AWARE_DISCOVERY_CHANNEL_5_BAND = 149
 
 # Aware Data-Path Constants
 DATA_PATH_INITIATOR = 0
@@ -120,3 +150,16 @@
 
 # Maximum send retry
 MAX_TX_RETRIES = 5
+
+# Callback keys (for 'adb shell cmd wifiaware native_cb get_cb_count')
+CB_EV_CLUSTER = "0"
+CB_EV_DISABLED = "1"
+CB_EV_PUBLISH_TERMINATED = "2"
+CB_EV_SUBSCRIBE_TERMINATED = "3"
+CB_EV_MATCH = "4"
+CB_EV_MATCH_EXPIRED = "5"
+CB_EV_FOLLOWUP_RECEIVED = "6"
+CB_EV_TRANSMIT_FOLLOWUP = "7"
+CB_EV_DATA_PATH_REQUEST = "8"
+CB_EV_DATA_PATH_CONFIRM = "9"
+CB_EV_DATA_PATH_TERMINATED = "10"
diff --git a/acts/framework/acts/test_utils/wifi/aware/aware_test_utils.py b/acts/framework/acts/test_utils/wifi/aware/aware_test_utils.py
new file mode 100644
index 0000000..9ed704f
--- /dev/null
+++ b/acts/framework/acts/test_utils/wifi/aware/aware_test_utils.py
@@ -0,0 +1,672 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2017 - Google
+#
+#   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 base64
+import json
+import queue
+import re
+import statistics
+import time
+from acts import asserts
+
+from acts.test_utils.net import connectivity_const as cconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+
+# arbitrary timeout for events
+EVENT_TIMEOUT = 10
+
+# semi-arbitrary timeout for network formation events. Based on framework
+# timeout for NDP (NAN data-path) negotiation to be completed.
+EVENT_NDP_TIMEOUT = 20
+
+# number of second to 'reasonably' wait to make sure that devices synchronize
+# with each other - useful for OOB test cases, where the OOB discovery would
+# take some time
+WAIT_FOR_CLUSTER = 5
+
+
+def decorate_event(event_name, id):
+  return '%s_%d' % (event_name, id)
+
+
+def wait_for_event(ad, event_name, timeout=EVENT_TIMEOUT):
+  """Wait for the specified event or timeout.
+
+  Args:
+    ad: The android device
+    event_name: The event to wait on
+    timeout: Number of seconds to wait
+  Returns:
+    The event (if available)
+  """
+  prefix = ''
+  if hasattr(ad, 'pretty_name'):
+    prefix = '[%s] ' % ad.pretty_name
+  try:
+    event = ad.ed.pop_event(event_name, timeout)
+    ad.log.info('%s%s: %s', prefix, event_name, event['data'])
+    return event
+  except queue.Empty:
+    ad.log.info('%sTimed out while waiting for %s', prefix, event_name)
+    asserts.fail(event_name)
+
+def wait_for_event_with_keys(ad, event_name, timeout=EVENT_TIMEOUT, *keyvalues):
+  """Wait for the specified event contain the key/value pairs or timeout
+
+  Args:
+    ad: The android device
+    event_name: The event to wait on
+    timeout: Number of seconds to wait
+    keyvalues: (kay, value) pairs
+  Returns:
+    The event (if available)
+  """
+  def filter_callbacks(event, keyvalues):
+    for keyvalue in keyvalues:
+      key, value = keyvalue
+      if event['data'][key] != value:
+        return False
+    return True
+
+  prefix = ''
+  if hasattr(ad, 'pretty_name'):
+    prefix = '[%s] ' % ad.pretty_name
+  try:
+    event = ad.ed.wait_for_event(event_name, filter_callbacks, timeout,
+                                 keyvalues)
+    ad.log.info('%s%s: %s', prefix, event_name, event['data'])
+    return event
+  except queue.Empty:
+    ad.log.info('%sTimed out while waiting for %s (%s)', prefix, event_name,
+                keyvalues)
+    asserts.fail(event_name)
+
+def fail_on_event(ad, event_name, timeout=EVENT_TIMEOUT):
+  """Wait for a timeout period and looks for the specified event - fails if it
+  is observed.
+
+  Args:
+    ad: The android device
+    event_name: The event to wait for (and fail on its appearance)
+  """
+  prefix = ''
+  if hasattr(ad, 'pretty_name'):
+    prefix = '[%s] ' % ad.pretty_name
+  try:
+    event = ad.ed.pop_event(event_name, timeout)
+    ad.log.info('%sReceived unwanted %s: %s', prefix, event_name, event['data'])
+    asserts.fail(event_name, extras=event)
+  except queue.Empty:
+    ad.log.info('%s%s not seen (as expected)', prefix, event_name)
+    return
+
+def fail_on_event_with_keys(ad, event_name, timeout=EVENT_TIMEOUT, *keyvalues):
+  """Wait for a timeout period and looks for the specified event which contains
+  the key/value pairs - fails if it is observed.
+
+  Args:
+    ad: The android device
+    event_name: The event to wait on
+    timeout: Number of seconds to wait
+    keyvalues: (kay, value) pairs
+  """
+  def filter_callbacks(event, keyvalues):
+    for keyvalue in keyvalues:
+      key, value = keyvalue
+      if event['data'][key] != value:
+        return False
+    return True
+
+  prefix = ''
+  if hasattr(ad, 'pretty_name'):
+    prefix = '[%s] ' % ad.pretty_name
+  try:
+    event = ad.ed.wait_for_event(event_name, filter_callbacks, timeout,
+                                 keyvalues)
+    ad.log.info('%sReceived unwanted %s: %s', prefix, event_name, event['data'])
+    asserts.fail(event_name, extras=event)
+  except queue.Empty:
+    ad.log.info('%s%s (%s) not seen (as expected)', prefix, event_name,
+                keyvalues)
+    return
+
+def verify_no_more_events(ad, timeout=EVENT_TIMEOUT):
+  """Verify that there are no more events in the queue.
+  """
+  prefix = ''
+  if hasattr(ad, 'pretty_name'):
+    prefix = '[%s] ' % ad.pretty_name
+  should_fail = False
+  try:
+    while True:
+      event = ad.ed.pop_events('.*', timeout, freq=0)
+      ad.log.info('%sQueue contains %s', prefix, event)
+      should_fail = True
+  except queue.Empty:
+    if should_fail:
+      asserts.fail('%sEvent queue not empty' % prefix)
+    ad.log.info('%sNo events in the queue (as expected)', prefix)
+    return
+
+
+def encode_list(list_of_objects):
+  """Converts the list of strings or bytearrays to a list of b64 encoded
+  bytearrays.
+
+  A None object is treated as a zero-length bytearray.
+
+  Args:
+    list_of_objects: A list of strings or bytearray objects
+  Returns: A list of the same objects, converted to bytes and b64 encoded.
+  """
+  encoded_list = []
+  for obj in list_of_objects:
+    if obj is None:
+      obj = bytes()
+    if isinstance(obj, str):
+      encoded_list.append(base64.b64encode(bytes(obj, 'utf-8')).decode('utf-8'))
+    else:
+      encoded_list.append(base64.b64encode(obj).decode('utf-8'))
+  return encoded_list
+
+
+def decode_list(list_of_b64_strings):
+  """Converts the list of b64 encoded strings to a list of bytearray.
+
+  Args:
+    list_of_b64_strings: list of strings, each of which is b64 encoded array
+  Returns: a list of bytearrays.
+  """
+  decoded_list = []
+  for str in list_of_b64_strings:
+    decoded_list.append(base64.b64decode(str))
+  return decoded_list
+
+
+def construct_max_match_filter(max_size):
+  """Constructs a maximum size match filter that fits into the 'max_size' bytes.
+
+  Match filters are a set of LVs (Length, Value pairs) where L is 1 byte. The
+  maximum size match filter will contain max_size/2 LVs with all Vs (except
+  possibly the last one) of 1 byte, the last V may be 2 bytes for odd max_size.
+
+  Args:
+    max_size: Maximum size of the match filter.
+  Returns: an array of bytearrays.
+  """
+  mf_list = []
+  num_lvs = max_size // 2
+  for i in range(num_lvs - 1):
+    mf_list.append(bytes([i]))
+  if (max_size % 2 == 0):
+    mf_list.append(bytes([255]))
+  else:
+    mf_list.append(bytes([254, 255]))
+  return mf_list
+
+
+def assert_equal_strings(first, second, msg=None, extras=None):
+  """Assert equality of the string operands - where None is treated as equal to
+  an empty string (''), otherwise fail the test.
+
+  Error message is "first != second" by default. Additional explanation can
+  be supplied in the message.
+
+  Args:
+      first, seconds: The strings that are evaluated for equality.
+      msg: A string that adds additional info about the failure.
+      extras: An optional field for extra information to be included in
+              test result.
+  """
+  if first == None:
+    first = ''
+  if second == None:
+    second = ''
+  asserts.assert_equal(first, second, msg, extras)
+
+
+def get_aware_capabilities(ad):
+  """Get the Wi-Fi Aware capabilities from the specified device. The
+  capabilities are a dictionary keyed by aware_const.CAP_* keys.
+
+  Args:
+    ad: the Android device
+  Returns: the capability dictionary.
+  """
+  return json.loads(ad.adb.shell('cmd wifiaware state_mgr get_capabilities'))
+
+def get_wifi_mac_address(ad):
+  """Get the Wi-Fi interface MAC address as a upper-case string of hex digits
+  without any separators (e.g. ':').
+
+  Args:
+    ad: Device on which to run.
+  """
+  return ad.droid.wifiGetConnectionInfo()['mac_address'].upper().replace(
+      ':', '')
+
+def validate_forbidden_callbacks(ad, limited_cb=None):
+  """Validate that the specified callbacks have not been called more then permitted.
+
+  In addition to the input configuration also validates that forbidden callbacks
+  have never been called.
+
+  Args:
+    ad: Device on which to run.
+    limited_cb: Dictionary of CB_EV_* ids and maximum permitted calls (0
+                meaning never).
+  """
+  cb_data = json.loads(ad.adb.shell('cmd wifiaware native_cb get_cb_count'))
+
+  if limited_cb is None:
+    limited_cb = {}
+  # add callbacks which should never be called
+  limited_cb[aconsts.CB_EV_MATCH_EXPIRED] = 0
+
+  fail = False
+  for cb_event in limited_cb.keys():
+    if cb_event in cb_data:
+      if cb_data[cb_event] > limited_cb[cb_event]:
+        fail = True
+        ad.log.info(
+            'Callback %s observed %d times: more then permitted %d times',
+            cb_event, cb_data[cb_event], limited_cb[cb_event])
+
+  asserts.assert_false(fail, 'Forbidden callbacks observed', extras=cb_data)
+
+def extract_stats(ad, data, results, key_prefix, log_prefix):
+  """Extract statistics from the data, store in the results dictionary, and
+  output to the info log.
+
+  Args:
+    ad: Android device (for logging)
+    data: A list containing the data to be analyzed.
+    results: A dictionary into which to place the statistics.
+    key_prefix: A string prefix to use for the dict keys storing the
+                extracted stats.
+    log_prefix: A string prefix to use for the info log.
+    include_data: If True includes the raw data in the dictionary,
+                  otherwise just the stats.
+  """
+  num_samples = len(data)
+  results['%snum_samples' % key_prefix] = num_samples
+
+  if not data:
+    return
+
+  data_min = min(data)
+  data_max = max(data)
+  data_mean = statistics.mean(data)
+
+  results['%smin' % key_prefix] = data_min
+  results['%smax' % key_prefix] = data_max
+  results['%smean' % key_prefix] = data_mean
+  results['%sraw_data' % key_prefix] = data
+
+  if num_samples > 1:
+    data_stdev = statistics.stdev(data)
+    results['%sstdev' % key_prefix] = data_stdev
+    ad.log.info('%s: num_samples=%d, min=%.2f, max=%.2f, mean=%.2f, stdev=%.2f',
+                log_prefix, num_samples, data_min, data_max, data_mean,
+                data_stdev)
+  else:
+    ad.log.info('%s: num_samples=%d, min=%.2f, max=%.2f, mean=%.2f', log_prefix,
+                num_samples, data_min, data_max, data_mean)
+
+def get_mac_addr(device, interface):
+  """Get the MAC address of the specified interface. Uses ifconfig and parses
+  its output. Normalizes string to remove ':' and upper case.
+
+  Args:
+    device: Device on which to query the interface MAC address.
+    interface: Name of the interface for which to obtain the MAC address.
+  """
+  out = device.adb.shell("ifconfig %s" % interface)
+  res = re.match(".* HWaddr (\S+).*", out , re.S)
+  asserts.assert_true(
+      res,
+      'Unable to obtain MAC address for interface %s' % interface,
+      extras=out)
+  return res.group(1).upper().replace(':', '')
+
+#########################################################
+# Aware primitives
+#########################################################
+
+def request_network(dut, ns):
+  """Request a Wi-Fi Aware network.
+
+  Args:
+    dut: Device
+    ns: Network specifier
+  Returns: the request key
+  """
+  network_req = {"TransportType": 5, "NetworkSpecifier": ns}
+  return dut.droid.connectivityRequestWifiAwareNetwork(network_req)
+
+def configure_dw(device, is_default, is_24_band, value):
+  """Use the command-line API to configure the DW (discovery window) setting
+
+  Args:
+    device: Device on which to perform configuration
+    is_default: True for the default setting, False for the non-interactive
+                setting
+    is_24_band: True for 2.4GHz band, False for 5GHz band
+    value: An integer 0 to 5
+  """
+  variable = 'dw_%s_%sghz' % ('default' if is_default else 'on_inactive', '24'
+                              if is_24_band else '5')
+  device.adb.shell("cmd wifiaware native_api set %s %d" % (variable, value))
+
+def config_dw_high_power(device):
+  """Configure device's discovery window (DW) values to high power mode -
+  whether device is in interactive or non-interactive modes"""
+  configure_dw(
+      device, is_default=True, is_24_band=True, value=aconsts.DW_24_INTERACTIVE)
+  configure_dw(
+      device, is_default=True, is_24_band=False, value=aconsts.DW_5_INTERACTIVE)
+  configure_dw(
+      device,
+      is_default=False,
+      is_24_band=True,
+      value=aconsts.DW_24_INTERACTIVE)
+  configure_dw(
+      device,
+      is_default=False,
+      is_24_band=False,
+      value=aconsts.DW_5_INTERACTIVE)
+
+def config_dw_low_power(device):
+  """Configure device's discovery window (DW) values to low power mode - whether
+  device is in interactive or non-interactive modes"""
+  configure_dw(
+      device,
+      is_default=True,
+      is_24_band=True,
+      value=aconsts.DW_24_NON_INTERACTIVE)
+  configure_dw(
+      device,
+      is_default=True,
+      is_24_band=False,
+      value=aconsts.DW_5_NON_INTERACTIVE)
+  configure_dw(
+      device,
+      is_default=False,
+      is_24_band=True,
+      value=aconsts.DW_24_NON_INTERACTIVE)
+  configure_dw(
+      device,
+      is_default=False,
+      is_24_band=False,
+      value=aconsts.DW_5_NON_INTERACTIVE)
+
+def config_dw_all_modes(device, dw_24ghz, dw_5ghz):
+  """Configure device's discovery window (DW) values to the specified values -
+  whether the device is in interactive or non-interactive mode.
+
+  Args:
+    dw_24ghz: DW interval in the 2.4GHz band.
+    dw_5ghz: DW interval in the 5GHz band.
+  """
+  configure_dw(device, is_default=True, is_24_band=True, value=dw_24ghz)
+  configure_dw(device, is_default=True, is_24_band=False, value=dw_5ghz)
+  configure_dw(device, is_default=False, is_24_band=True, value=dw_24ghz)
+  configure_dw(device, is_default=False, is_24_band=False, value=dw_5ghz)
+
+def create_discovery_config(service_name,
+                          d_type,
+                          ssi=None,
+                          match_filter=None,
+                          match_filter_list=None,
+                          ttl=0,
+                          term_cb_enable=True):
+  """Create a publish discovery configuration based on input parameters.
+
+  Args:
+    service_name: Service name - required
+    d_type: Discovery type (publish or subscribe constants)
+    ssi: Supplemental information - defaults to None
+    match_filter, match_filter_list: The match_filter, only one mechanism can
+                                     be used to specify. Defaults to None.
+    ttl: Time-to-live - defaults to 0 (i.e. non-self terminating)
+    term_cb_enable: True (default) to enable callback on termination, False
+                    means that no callback is called when session terminates.
+  Returns:
+    publish discovery configuration object.
+  """
+  config = {}
+  config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = service_name
+  config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = d_type
+  if ssi is not None:
+    config[aconsts.DISCOVERY_KEY_SSI] = ssi
+  if match_filter is not None:
+    config[aconsts.DISCOVERY_KEY_MATCH_FILTER] = match_filter
+  if match_filter_list is not None:
+    config[aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST] = match_filter_list
+  config[aconsts.DISCOVERY_KEY_TTL] = ttl
+  config[aconsts.DISCOVERY_KEY_TERM_CB_ENABLED] = term_cb_enable
+  return config
+
+def create_discovery_pair(p_dut,
+                          s_dut,
+                          p_config,
+                          s_config,
+                          device_startup_offset,
+                          msg_id=None):
+  """Creates a discovery session (publish and subscribe), and waits for
+  service discovery - at that point the sessions are connected and ready for
+  further messaging of data-path setup.
+
+  Args:
+    p_dut: Device to use as publisher.
+    s_dut: Device to use as subscriber.
+    p_config: Publish configuration.
+    s_config: Subscribe configuration.
+    device_startup_offset: Number of seconds to offset the enabling of NAN on
+                           the two devices.
+    msg_id: Controls whether a message is sent from Subscriber to Publisher
+            (so that publisher has the sub's peer ID). If None then not sent,
+            otherwise should be an int for the message id.
+  Returns: variable size list of:
+    p_id: Publisher attach session id
+    s_id: Subscriber attach session id
+    p_disc_id: Publisher discovery session id
+    s_disc_id: Subscriber discovery session id
+    peer_id_on_sub: Peer ID of the Publisher as seen on the Subscriber
+    peer_id_on_pub: Peer ID of the Subscriber as seen on the Publisher. Only
+                    included if |msg_id| is not None.
+  """
+  p_dut.pretty_name = 'Publisher'
+  s_dut.pretty_name = 'Subscriber'
+
+  # Publisher+Subscriber: attach and wait for confirmation
+  p_id = p_dut.droid.wifiAwareAttach()
+  wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
+  time.sleep(device_startup_offset)
+  s_id = s_dut.droid.wifiAwareAttach()
+  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)
+  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)
+  wait_for_event(s_dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
+
+  # Subscriber: wait for service discovery
+  discovery_event = wait_for_event(s_dut,
+                                   aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+  peer_id_on_sub = discovery_event['data'][aconsts.SESSION_CB_KEY_PEER_ID]
+
+  # Optionally send a message from Subscriber to Publisher
+  if msg_id is not None:
+    ping_msg = 'PING'
+
+    # Subscriber: send message to peer (Publisher)
+    s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub, msg_id,
+                                     ping_msg, aconsts.MAX_TX_RETRIES)
+    sub_tx_msg_event = wait_for_event(s_dut, aconsts.SESSION_CB_ON_MESSAGE_SENT)
+    asserts.assert_equal(
+        msg_id, sub_tx_msg_event['data'][aconsts.SESSION_CB_KEY_MESSAGE_ID],
+        'Subscriber -> Publisher message ID corrupted')
+
+    # Publisher: wait for received message
+    pub_rx_msg_event = 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]
+    asserts.assert_equal(
+        ping_msg,
+        pub_rx_msg_event['data'][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING],
+        'Subscriber -> Publisher message corrupted')
+    return p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub, peer_id_on_pub
+
+  return p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub
+
+def create_ib_ndp(p_dut, s_dut, p_config, s_config, device_startup_offset):
+  """Create an NDP (using in-band discovery)
+
+  Args:
+    p_dut: Device to use as publisher.
+    s_dut: Device to use as subscriber.
+    p_config: Publish configuration.
+    s_config: Subscribe configuration.
+    device_startup_offset: Number of seconds to offset the enabling of NAN on
+                           the two devices.
+  """
+  (p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub,
+   peer_id_on_pub) = create_discovery_pair(
+       p_dut, s_dut, p_config, s_config, device_startup_offset, msg_id=9999)
+
+  # Publisher: request network
+  p_req_key = request_network(p_dut,
+                              p_dut.droid.wifiAwareCreateNetworkSpecifier(
+                                  p_disc_id, peer_id_on_pub, None))
+
+  # Subscriber: request network
+  s_req_key = request_network(s_dut,
+                              s_dut.droid.wifiAwareCreateNetworkSpecifier(
+                                  s_disc_id, peer_id_on_sub, None))
+
+  # Publisher & Subscriber: wait for network formation
+  p_net_event = wait_for_event_with_keys(
+      p_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_TIMEOUT,
+      (cconsts.NETWORK_CB_KEY_EVENT,
+       cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED), (cconsts.NETWORK_CB_KEY_ID,
+                                                     p_req_key))
+  s_net_event = wait_for_event_with_keys(
+      s_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_TIMEOUT,
+      (cconsts.NETWORK_CB_KEY_EVENT,
+       cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED), (cconsts.NETWORK_CB_KEY_ID,
+                                                     s_req_key))
+
+  p_aware_if = p_net_event["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+  s_aware_if = s_net_event["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+
+  p_ipv6 = p_dut.droid.connectivityGetLinkLocalIpv6Address(p_aware_if).split(
+      "%")[0]
+  s_ipv6 = s_dut.droid.connectivityGetLinkLocalIpv6Address(s_aware_if).split(
+      "%")[0]
+
+  return p_req_key, s_req_key, p_aware_if, s_aware_if, p_ipv6, s_ipv6
+
+def create_oob_ndp_on_sessions(init_dut, resp_dut, init_id, init_mac, resp_id,
+                               resp_mac):
+  """Create an NDP on top of existing Aware sessions (using OOB discovery)
+
+  Args:
+    init_dut: Initiator device
+    resp_dut: Responder device
+    init_id: Initiator attach session id
+    init_mac: Initiator discovery MAC address
+    resp_id: Responder attach session id
+    resp_mac: Responder discovery MAC address
+  Returns:
+    init_req_key: Initiator network request
+    resp_req_key: Responder network request
+    init_aware_if: Initiator Aware data interface
+    resp_aware_if: Responder Aware data interface
+    init_ipv6: Initiator IPv6 address
+    resp_ipv6: Responder IPv6 address
+  """
+  # Responder: request network
+  resp_req_key = request_network(
+      resp_dut,
+      resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+          resp_id, aconsts.DATA_PATH_RESPONDER, init_mac, None))
+
+  # Initiator: request network
+  init_req_key = request_network(
+      init_dut,
+      init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+          init_id, aconsts.DATA_PATH_INITIATOR, resp_mac, None))
+
+  # Initiator & Responder: wait for network formation
+  init_net_event = wait_for_event_with_keys(
+      init_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_TIMEOUT,
+      (cconsts.NETWORK_CB_KEY_EVENT,
+       cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED), (cconsts.NETWORK_CB_KEY_ID,
+                                                     init_req_key))
+  resp_net_event = wait_for_event_with_keys(
+      resp_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_TIMEOUT,
+      (cconsts.NETWORK_CB_KEY_EVENT,
+       cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED), (cconsts.NETWORK_CB_KEY_ID,
+                                                     resp_req_key))
+
+  init_aware_if = init_net_event['data'][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+  resp_aware_if = resp_net_event['data'][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+
+  init_ipv6 = init_dut.droid.connectivityGetLinkLocalIpv6Address(
+      init_aware_if).split('%')[0]
+  resp_ipv6 = resp_dut.droid.connectivityGetLinkLocalIpv6Address(
+      resp_aware_if).split('%')[0]
+
+  return (init_req_key, resp_req_key, init_aware_if, resp_aware_if, init_ipv6,
+          resp_ipv6)
+
+def create_oob_ndp(init_dut, resp_dut):
+  """Create an NDP (using OOB discovery)
+
+  Args:
+    init_dut: Initiator device
+    resp_dut: Responder device
+  """
+  init_dut.pretty_name = 'Initiator'
+  resp_dut.pretty_name = 'Responder'
+
+  # Initiator+Responder: attach and wait for confirmation & identity
+  init_id = init_dut.droid.wifiAwareAttach(True)
+  wait_for_event(init_dut, aconsts.EVENT_CB_ON_ATTACHED)
+  init_ident_event = wait_for_event(init_dut,
+                                    aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+  init_mac = init_ident_event['data']['mac']
+  resp_id = resp_dut.droid.wifiAwareAttach(True)
+  wait_for_event(resp_dut, aconsts.EVENT_CB_ON_ATTACHED)
+  resp_ident_event = wait_for_event(resp_dut,
+                                    aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+  resp_mac = resp_ident_event['data']['mac']
+
+  # wait for for devices to synchronize with each other - there are no other
+  # mechanisms to make sure this happens for OOB discovery (except retrying
+  # to execute the data-path request)
+  time.sleep(WAIT_FOR_CLUSTER)
+
+  (init_req_key, resp_req_key, init_aware_if,
+   resp_aware_if, init_ipv6, resp_ipv6) = create_oob_ndp_on_sessions(
+       init_dut, resp_dut, init_id, init_mac, resp_id, resp_mac)
+
+  return (init_req_key, resp_req_key, init_aware_if, resp_aware_if, init_ipv6,
+          resp_ipv6)
diff --git a/acts/framework/acts/utils.py b/acts/framework/acts/utils.py
index b23fdb0..802e0c1 100755
--- a/acts/framework/acts/utils.py
+++ b/acts/framework/acts/utils.py
@@ -30,6 +30,8 @@
 import traceback
 import zipfile
 
+from acts.controllers import adb
+
 # File name length is limited to 255 chars on some OS, so we need to make sure
 # the file names we output fits within the limit.
 MAX_FILENAME_LEN = 255
@@ -723,28 +725,62 @@
         1 if new_state else 0))
 
 
-def bypass_setup_wizard(ad):
+def bypass_setup_wizard(ad, bypass_wait_time=3):
     """Bypass the setup wizard on an input Android device
 
     Args:
         ad: android device object.
+        bypass_wait_time: Do not set this variable. Only modified for framework
+        tests.
 
     Returns:
         True if Andorid device successfully bypassed the setup wizard.
         False if failed.
     """
-    ad.adb.shell(
-        "am start -n \"com.google.android.setupwizard/.SetupWizardExitActivity\""
-    )
+    try:
+        ad.adb.shell("am start -n \"com.google.android.setupwizard/"
+                     ".SetupWizardExitActivity\"")
+        logging.debug("No error during default bypass call.")
+    except adb.AdbError as adb_error:
+        if adb_error.stdout == "ADB_CMD_OUTPUT:0":
+            if adb_error.stderr and \
+                    not adb_error.stderr.startswith("Error type 3\n"):
+                logging.error(
+                    "ADB_CMD_OUTPUT:0, but error is %s " % adb_error.stderr)
+                raise adb_error
+            logging.debug("Bypass wizard call received harmless error 3: "
+                          "No setup to bypass.")
+        elif adb_error.stdout == "ADB_CMD_OUTPUT:255":
+            # Run it again as root.
+            ad.adb.root_adb()
+            logging.debug("Need root access to bypass setup wizard.")
+            try:
+                ad.adb.shell("am start -n \"com.google.android.setupwizard/"
+                             ".SetupWizardExitActivity\"")
+                logging.debug("No error during rooted bypass call.")
+            except adb.AdbError as adb_error:
+                if adb_error.stdout == "ADB_CMD_OUTPUT:0":
+                    if adb_error.stderr and \
+                            not adb_error.stderr.startswith("Error type 3\n"):
+                        logging.error("Rooted ADB_CMD_OUTPUT:0, but error is "
+                                      "%s " % adb_error.stderr)
+                        raise adb_error
+                    logging.debug(
+                        "Rooted bypass wizard call received harmless "
+                        "error 3: No setup to bypass.")
+
     # magical sleep to wait for the gservices override broadcast to complete
-    time.sleep(3)
+    time.sleep(bypass_wait_time)
+
     provisioned_state = int(
         ad.adb.shell("settings get global device_provisioned"))
-    if (provisioned_state != 1):
+    if provisioned_state != 1:
         logging.error("Failed to bypass setup wizard.")
         return False
+    logging.debug("Setup wizard successfully bypassed.")
     return True
 
+
 def parse_ping_ouput(ad, count, out, loss_tolerance=20):
     """Ping Parsing util.
 
@@ -780,7 +816,10 @@
     return True
 
 
-def adb_shell_ping(ad, count=120, dest_ip="www.google.com", timeout=200,
+def adb_shell_ping(ad,
+                   count=120,
+                   dest_ip="www.google.com",
+                   timeout=200,
                    loss_tolerance=20):
     """Ping utility using adb shell.
 
diff --git a/acts/framework/setup.py b/acts/framework/setup.py
index ed550e7..48c71bb 100755
--- a/acts/framework/setup.py
+++ b/acts/framework/setup.py
@@ -24,7 +24,8 @@
 import sys
 
 install_requires = [
-    'future',
+    # Future needs to have a newer version that contains urllib.
+    'future>=0.16.0',
     # mock-1.0.1 is the last version compatible with setuptools <17.1,
     # which is what comes with Ubuntu 14.04 LTS.
     'mock<=1.0.1',
@@ -78,6 +79,8 @@
         pass
 
     def run(self):
+        import pip
+        pip.main(['install', '--upgrade', 'pip'])
         required_packages = self.distribution.install_requires
 
         for package in required_packages:
diff --git a/acts/framework/tests/acts_adb_test.py b/acts/framework/tests/acts_adb_test.py
new file mode 100755
index 0000000..b56ef8b
--- /dev/null
+++ b/acts/framework/tests/acts_adb_test.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env 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 unittest
+import mock
+from acts.controllers import adb
+
+
+class MockJob(object):
+    def __init__(self, exit_status=0, stderr='', stdout=''):
+        self.exit_status = exit_status
+        self.stderr = stderr
+        self.stdout = stdout
+
+
+class MockAdbProxy(adb.AdbProxy):
+    def __init__(self):
+        pass
+
+
+class ADBTest(unittest.TestCase):
+    """A class for testing acts/controllers/adb.py"""
+
+    def test__exec_cmd_failure_old_adb(self):
+        mock_job = MockJob(exit_status=1, stderr='error: device not found')
+        cmd = ['adb', '-s', '"SOME_SERIAL"', 'shell', '"SOME_SHELL_CMD"']
+        with mock.patch('acts.libs.proc.job.run', return_value=mock_job):
+            with self.assertRaises(adb.AdbError):
+                MockAdbProxy()._exec_cmd(cmd)
+
+    def test__exec_cmd_failure_new_adb(self):
+        mock_job = MockJob(
+            exit_status=1, stderr='error: device \'DEADBEEF\' not found')
+        cmd = ['adb', '-s', '"SOME_SERIAL"', 'shell', '"SOME_SHELL_CMD"']
+        with mock.patch('acts.libs.proc.job.run', return_value=mock_job):
+            with self.assertRaises(adb.AdbError):
+                MockAdbProxy()._exec_cmd(cmd)
+
+    def test__exec_cmd_pass_ret_1(self):
+        mock_job = MockJob(exit_status=1, stderr='error not related to adb')
+        cmd = ['adb', '-s', '"SOME_SERIAL"', 'shell', '"SOME_SHELL_CMD"']
+        with mock.patch('acts.libs.proc.job.run', return_value=mock_job):
+            MockAdbProxy()._exec_cmd(cmd)
+
+    def test__exec_cmd_pass_basic(self):
+        mock_job = MockJob(exit_status=0, stderr='', stdout='FEEDACAB')
+        cmd = ['adb', '-s', '"SOME_SERIAL"', 'shell', '"SOME_SHELL_CMD"']
+        with mock.patch('acts.libs.proc.job.run', return_value=mock_job):
+            MockAdbProxy()._exec_cmd(cmd)
+
+    def test__exec_cmd_pass_no_stdout(self):
+        mock_job = MockJob(exit_status=0, stderr='', stdout='')
+        cmd = ['adb', '-s', '"SOME_SERIAL"', 'shell', '"SOME_SHELL_CMD"']
+        with mock.patch('acts.libs.proc.job.run', return_value=mock_job):
+            MockAdbProxy()._exec_cmd(cmd)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/acts/framework/tests/acts_base_class_test.py b/acts/framework/tests/acts_base_class_test.py
index 53849ed..75bd2e2 100755
--- a/acts/framework/tests/acts_base_class_test.py
+++ b/acts/framework/tests/acts_base_class_test.py
@@ -956,9 +956,10 @@
                 return "test_%s_%s" % (setting, arg)
 
             def logic(self, setting, arg, special_arg=None):
-                asserts.assert_true(setting in itrs, (
-                    "%s is not in acceptable settings range %s") %
-                                    (setting, itrs))
+                asserts.assert_true(
+                    setting in itrs,
+                    ("%s is not in acceptable settings range %s") % (setting,
+                                                                     itrs))
                 asserts.assert_true(arg == static_arg,
                                     "Expected %s, got %s" % (static_arg, arg))
                 asserts.assert_true(arg == static_arg, "Expected %s, got %s" %
diff --git a/acts/framework/tests/acts_relay_controller_test.py b/acts/framework/tests/acts_relay_controller_test.py
index bb06eb6..2676bff 100755
--- a/acts/framework/tests/acts_relay_controller_test.py
+++ b/acts/framework/tests/acts_relay_controller_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3.4
 #
 #   Copyright 2016 - The Android Open Source Project
 #
@@ -20,19 +20,27 @@
 import shutil
 import unittest
 
-from acts.controllers.relay_lib.generic_relay_device import GenericRelayDevice
-from acts.controllers.relay_lib.relay import Relay
-from acts.controllers.relay_lib.relay import RelayDict
-from acts.controllers.relay_lib.relay import RelayState
-from acts.controllers.relay_lib.relay import SynchronizeRelays
-from acts.controllers.relay_lib.relay_board import RelayBoard
-from acts.controllers.relay_lib.relay_device import RelayDevice
-from acts.controllers.relay_lib.relay_rig import RelayRig
-from acts.controllers.relay_lib.sain_smart_board import SainSmartBoard
+from acts.controllers.relay_lib import generic_relay_device
+from acts.controllers.relay_lib import relay
+from acts.controllers.relay_lib import relay_board
+from acts.controllers.relay_lib import relay_device
+from acts.controllers.relay_lib import relay_rig
+from acts.controllers.relay_lib import sain_smart_board
 
 import acts.controllers.relay_lib.errors as errors
 import acts.controllers.relay_lib.fugu_remote as fugu_remote
 
+# Shorthand versions of the long class names.
+GenericRelayDevice = generic_relay_device.GenericRelayDevice
+Relay = relay.Relay
+RelayDict = relay.RelayDict
+RelayState = relay.RelayState
+SynchronizeRelays = relay.SynchronizeRelays
+RelayBoard = relay_board.RelayBoard
+RelayDevice = relay_device.RelayDevice
+RelayRig = relay_rig.RelayRig
+SainSmartBoard = sain_smart_board.SainSmartBoard
+
 
 class MockBoard(RelayBoard):
     def __init__(self, config):
@@ -366,26 +374,25 @@
     def test_init_relay_rig_device_gets_relays(self):
         modded_config = copy.deepcopy(self.config)
         del modded_config['devices'][0]['relays']['Relay00']
-        relay_rig = RelayRigMock(modded_config)
-        self.assertEqual(len(relay_rig.relays), 4)
-        self.assertEqual(len(relay_rig.devices['device'].relays), 1)
+        rig = RelayRigMock(modded_config)
+        self.assertEqual(len(rig.relays), 4)
+        self.assertEqual(len(rig.devices['device'].relays), 1)
 
-        relay_rig = RelayRigMock(self.config)
-        self.assertEqual(len(relay_rig.devices['device'].relays), 2)
+        rig = RelayRigMock(self.config)
+        self.assertEqual(len(rig.devices['device'].relays), 2)
 
     def test_init_relay_rig_correct_device_type(self):
-        relay_rig = RelayRigMock(self.config)
-        self.assertEqual(len(relay_rig.devices), 1)
-        self.assertIsInstance(relay_rig.devices['device'], GenericRelayDevice)
+        rig = RelayRigMock(self.config)
+        self.assertEqual(len(rig.devices), 1)
+        self.assertIsInstance(rig.devices['device'], GenericRelayDevice)
 
     def test_init_relay_rig_missing_devices_creates_generic_device(self):
         modded_config = copy.deepcopy(self.config)
         del modded_config['devices']
-        relay_rig = RelayRigMock(modded_config)
-        self.assertEqual(len(relay_rig.devices), 1)
-        self.assertIsInstance(relay_rig.devices['device'], GenericRelayDevice)
-        self.assertDictEqual(relay_rig.devices['device'].relays,
-                             relay_rig.relays)
+        rig = RelayRigMock(modded_config)
+        self.assertEqual(len(rig.devices), 1)
+        self.assertIsInstance(rig.devices['device'], GenericRelayDevice)
+        self.assertDictEqual(rig.devices['device'].relays, rig.relays)
 
 
 class RelayRigMock(RelayRig):
@@ -445,8 +452,8 @@
         modified_config = copy.deepcopy(self.device_config)
         del modified_config['relays']['r1']
 
-        generic_relay_device = GenericRelayDevice(modified_config, self.rig)
-        generic_relay_device.setup()
+        grd = GenericRelayDevice(modified_config, self.rig)
+        grd.setup()
 
         self.assertEqual(self.r0.get_status(), RelayState.NO)
         self.assertEqual(self.r1.get_status(), RelayState.NC)
@@ -455,8 +462,8 @@
         self.board.set(self.r0.position, RelayState.NC)
         self.board.set(self.r1.position, RelayState.NC)
 
-        generic_relay_device = GenericRelayDevice(self.device_config, self.rig)
-        generic_relay_device.setup()
+        grd = GenericRelayDevice(self.device_config, self.rig)
+        grd.setup()
 
         self.assertEqual(self.r0.get_status(), RelayState.NO)
         self.assertEqual(self.r1.get_status(), RelayState.NO)
@@ -469,8 +476,8 @@
 
     def change_state(self, begin_state, call, end_state, previous_state=None):
         self.board.set(self.r0.position, begin_state)
-        generic_relay_device = GenericRelayDevice(self.device_config, self.rig)
-        call(generic_relay_device)
+        grd = GenericRelayDevice(self.device_config, self.rig)
+        call(grd)
         self.assertEqual(self.r0.get_status(), end_state)
         if previous_state:
             self.assertEqual(
@@ -632,8 +639,8 @@
                 'r1': 'MockBoard/1'
             }
         }
-        relay_device = rig.create_relay_device(config)
-        self.assertIsInstance(relay_device, GenericRelayDevice)
+        device = rig.create_relay_device(config)
+        self.assertIsInstance(device, GenericRelayDevice)
 
     def test_create_relay_device_config_with_type(self):
         rig = RelayRigMock()
@@ -647,8 +654,8 @@
                 'r1': 'MockBoard/1'
             }
         }
-        relay_device = rig.create_relay_device(config)
-        self.assertIsInstance(relay_device, GenericRelayDevice)
+        device = rig.create_relay_device(config)
+        self.assertIsInstance(device, GenericRelayDevice)
 
     def test_create_relay_device_raise_on_type_not_found(self):
         rig = RelayRigMock()
diff --git a/acts/framework/tests/acts_utils_test.py b/acts/framework/tests/acts_utils_test.py
index 7ee2c9d..fd7b083 100755
--- a/acts/framework/tests/acts_utils_test.py
+++ b/acts/framework/tests/acts_utils_test.py
@@ -16,8 +16,10 @@
 
 import time
 import unittest
+import enum
 
 from acts import utils
+from acts.controllers.adb import AdbError
 
 
 class ActsUtilsTest(unittest.TestCase):
@@ -37,6 +39,97 @@
                                      "Process .* has terminated"):
             utils.stop_standing_subprocess(p)
 
+    def test_bypass_setup_wizard_no_complications(self):
+        ad = MockAd()
+        ad.adb.return_state = BypassSetupWizardReturn.NO_COMPLICATIONS
+        self.assertTrue(utils.bypass_setup_wizard(ad, 0))
+        self.assertFalse(ad.adb.root_adb_called)
+
+    def test_bypass_setup_wizard_unrecognized_error(self):
+        ad = MockAd()
+        ad.adb.return_state = BypassSetupWizardReturn.UNRECOGNIZED_ERR
+        with self.assertRaises(AdbError):
+            utils.bypass_setup_wizard(ad, 0)
+        self.assertFalse(ad.adb.root_adb_called)
+
+    def test_bypass_setup_wizard_need_root_access(self):
+        ad = MockAd()
+        ad.adb.return_state = BypassSetupWizardReturn.ROOT_ADB_NO_COMP
+        self.assertTrue(utils.bypass_setup_wizard(ad, 0))
+        self.assertTrue(ad.adb.root_adb_called)
+
+    def test_bypass_setup_wizard_need_root_already_skipped(self):
+        ad = MockAd()
+        ad.adb.return_state = BypassSetupWizardReturn.ROOT_ADB_SKIPPED
+        self.assertTrue(utils.bypass_setup_wizard(ad, 0))
+        self.assertTrue(ad.adb.root_adb_called)
+
+    def test_bypass_setup_wizard_root_access_still_fails(self):
+        ad = MockAd()
+        ad.adb.return_state = BypassSetupWizardReturn.ROOT_ADB_FAILS
+        with self.assertRaises(AdbError):
+            utils.bypass_setup_wizard(ad, 0)
+        self.assertTrue(ad.adb.root_adb_called)
+
+
+class BypassSetupWizardReturn:
+    # No complications. Bypass works the first time without issues.
+    NO_COMPLICATIONS = AdbError("", "", "", 1)
+    # Fail with doesn't need to be skipped/was skipped already.
+    ALREADY_BYPASSED = AdbError("", "ADB_CMD_OUTPUT:0", "Error type 3\n"
+                                "Error: Activity class", 1)
+    # Fail with different error.
+    UNRECOGNIZED_ERR = AdbError("", "ADB_CMD_OUTPUT:0", "Error type 4\n"
+                                "Error: Activity class", 0)
+    # Fail, get root access, then no complications arise.
+    ROOT_ADB_NO_COMP = AdbError("", "ADB_CMD_OUTPUT:255",
+                                "Security exception: Permission Denial: "
+                                "starting Intent { flg=0x10000000 "
+                                "cmp=com.google.android.setupwizard/"
+                                ".SetupWizardExitActivity } from null "
+                                "(pid=5045, uid=2000) not exported from uid "
+                                "10000", 0)
+    # Even with root access, the bypass setup wizard doesn't need to be skipped.
+    ROOT_ADB_SKIPPED = AdbError("", "ADB_CMD_OUTPUT:255",
+                                "Security exception: Permission Denial: "
+                                "starting Intent { flg=0x10000000 "
+                                "cmp=com.google.android.setupwizard/"
+                                ".SetupWizardExitActivity } from null "
+                                "(pid=5045, uid=2000) not exported from "
+                                "uid 10000", 0)
+    # Even with root access, the bypass setup wizard fails
+    ROOT_ADB_FAILS = AdbError(
+        "", "ADB_CMD_OUTPUT:255", "Security exception: Permission Denial: "
+        "starting Intent { flg=0x10000000 "
+        "cmp=com.google.android.setupwizard/"
+        ".SetupWizardExitActivity } from null (pid=5045, "
+        "uid=2000) not exported from uid 10000", 0)
+
+
+class MockAd:
+    def __init__(self):
+        self.adb = MockAdb()
+
+
+class MockAdb:
+    def __init__(self):
+        self.return_state = BypassSetupWizardReturn.NO_COMPLICATIONS
+        self.root_adb_called = False
+
+    def shell(self, string):
+        if string == "settings get global device_provisioned":
+            return self.return_state.ret_code
+        raise self.return_state
+
+    def root_adb(self):
+        self.root_adb_called = True
+        if self.return_state is BypassSetupWizardReturn.ROOT_ADB_FAILS:
+            self.return_state = BypassSetupWizardReturn.UNRECOGNIZED_ERR
+        elif self.return_state is not BypassSetupWizardReturn.ROOT_ADB_SKIPPED:
+            self.return_state = BypassSetupWizardReturn.ALREADY_BYPASSED
+        else:
+            self.return_state = BypassSetupWizardReturn.NO_COMPLICATIONS
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/acts/tests/google/ble/gatt/GattConnectTest.py b/acts/tests/google/ble/gatt/GattConnectTest.py
index 374becd..667611d 100644
--- a/acts/tests/google/ble/gatt/GattConnectTest.py
+++ b/acts/tests/google/ble/gatt/GattConnectTest.py
@@ -34,13 +34,14 @@
 from acts.test_utils.bt.bt_constants import gatt_transport
 from acts.test_utils.bt.bt_gatt_utils import GattTestUtilsError
 from acts.test_utils.bt.bt_gatt_utils import disconnect_gatt_connection
+from acts.test_utils.bt.bt_gatt_utils import wait_for_gatt_disconnect_event
+from acts.test_utils.bt.bt_gatt_utils import close_gatt_client
 from acts.test_utils.bt.bt_gatt_utils import log_gatt_server_uuids
 from acts.test_utils.bt.bt_gatt_utils import orchestrate_gatt_connection
-from acts.test_utils.bt.bt_gatt_utils import setup_gatt_characteristics
 from acts.test_utils.bt.bt_gatt_utils import setup_gatt_connection
-from acts.test_utils.bt.bt_gatt_utils import setup_gatt_descriptors
 from acts.test_utils.bt.bt_gatt_utils import setup_multiple_services
 from acts.test_utils.bt.bt_test_utils import get_mac_address_of_generic_advertisement
+from acts.test_utils.bt.bt_test_utils import clear_bonded_devices
 
 
 class GattConnectTest(BluetoothBaseTest):
@@ -75,6 +76,7 @@
         try:
             disconnect_gatt_connection(self.cen_ad, bluetooth_gatt,
                                        gatt_callback)
+            close_gatt_client(self.cen_ad, bluetooth_gatt)
             if bluetooth_gatt in self.bluetooth_gatt_list:
                 self.bluetooth_gatt_list.remove(bluetooth_gatt)
         except GattTestUtilsError as err:
@@ -224,7 +226,7 @@
         try:
             self._orchestrate_gatt_disconnection(bluetooth_gatt, gatt_callback)
         except Exception as err:
-            self.log.info("Failed to orchestrate disconnect: {}".format(e))
+            self.log.info("Failed to orchestrate disconnect: {}".format(err))
             return False
         return True
 
@@ -233,7 +235,7 @@
     def test_gatt_connect_autoconnect(self):
         """Test GATT connection over LE.
 
-        Test re-establishing a gat connection using autoconnect
+        Test re-establishing a gatt connection using autoconnect
         set to True in order to test connection whitelist.
 
         Steps:
@@ -274,6 +276,7 @@
         try:
             disconnect_gatt_connection(self.cen_ad, bluetooth_gatt,
                                        gatt_callback)
+            close_gatt_client(self.cen_ad, bluetooth_gatt)
             if bluetooth_gatt in self.bluetooth_gatt_list:
                 self.bluetooth_gatt_list.remove(bluetooth_gatt)
         except GattTestUtilsError as err:
@@ -281,9 +284,8 @@
             return False
         autoconnect = True
         bluetooth_gatt = self.cen_ad.droid.gattClientConnectGatt(
-            gatt_callback, mac_address, autoconnect,
-            gatt_transport['auto'],
-            gatt_phy_mask['1m_mask'])
+            gatt_callback, mac_address, autoconnect, gatt_transport['auto'],
+            False, gatt_phy_mask['1m_mask'])
         self.bluetooth_gatt_list.append(bluetooth_gatt)
         expected_event = gatt_cb_strings['gatt_conn_change'].format(
             gatt_callback)
@@ -298,6 +300,82 @@
                                                     gatt_callback)
 
     @BluetoothBaseTest.bt_test_wrap
+    @test_tracker_info(uuid='e506fa50-7cd9-4bd8-938a-6b85dcfe6bc6')
+    def test_gatt_connect_opportunistic(self):
+        """Test opportunistic GATT connection over LE.
+
+        Test establishing a gatt connection between a GATT server and GATT
+        client in opportunistic mode.
+
+        Steps:
+        1. Start a generic advertisement.
+        2. Start a generic scanner.
+        3. Find the advertisement and extract the mac address.
+        4. Stop the first scanner.
+        5. Create GATT connection 1 between the scanner and advertiser normally
+        6. Create GATT connection 2 between the scanner and advertiser using
+           opportunistic mode
+        7. Disconnect GATT connection 1
+
+        Expected Result:
+        Verify GATT connection 2 automatically disconnects when GATT connection
+        1 disconnect
+
+        Returns:
+          Pass if True
+          Fail if False
+
+        TAGS: LE, Advertising, Filtering, Scanning, GATT
+        Priority: 0
+        """
+        gatt_server_cb = self.per_ad.droid.gattServerCreateGattServerCallback()
+        gatt_server = self.per_ad.droid.gattServerOpenGattServer(
+            gatt_server_cb)
+        self.gatt_server_list.append(gatt_server)
+        mac_address, adv_callback = (
+            get_mac_address_of_generic_advertisement(self.cen_ad, self.per_ad))
+        # Make GATT connection 1
+        try:
+            bluetooth_gatt_1, gatt_callback_1 = setup_gatt_connection(
+                self.cen_ad,
+                mac_address,
+                False,
+                transport=gatt_transport['auto'],
+                opportunistic=False)
+            self.bluetooth_gatt_list.append(bluetooth_gatt_1)
+        except GattTestUtilsError as err:
+            self.log.error(err)
+            return False
+        # Make GATT connection 2
+        try:
+            bluetooth_gatt_2, gatt_callback_2 = setup_gatt_connection(
+                self.cen_ad,
+                mac_address,
+                False,
+                transport=gatt_transport['auto'],
+                opportunistic=True)
+            self.bluetooth_gatt_list.append(bluetooth_gatt_2)
+        except GattTestUtilsError as err:
+            self.log.error(err)
+            return False
+        # Disconnect GATT connection 1
+        try:
+            disconnect_gatt_connection(self.cen_ad, bluetooth_gatt_1,
+                                       gatt_callback_1)
+            close_gatt_client(self.cen_ad, bluetooth_gatt_1)
+            if bluetooth_gatt_1 in self.bluetooth_gatt_list:
+                self.bluetooth_gatt_list.remove(bluetooth_gatt_1)
+        except GattTestUtilsError as err:
+            self.log.error(err)
+            return False
+        # Confirm that GATT connection 2 also disconnects
+        wait_for_gatt_disconnect_event(self.cen_ad, gatt_callback_2)
+        close_gatt_client(self.cen_ad, bluetooth_gatt_2)
+        if bluetooth_gatt_2 in self.bluetooth_gatt_list:
+            self.bluetooth_gatt_list.remove(bluetooth_gatt_2)
+        return True
+
+    @BluetoothBaseTest.bt_test_wrap
     @test_tracker_info(uuid='1e01838e-c4de-4720-9adf-9e0419378226')
     def test_gatt_request_min_mtu(self):
         """Test GATT connection over LE and exercise MTU sizes.
@@ -717,7 +795,6 @@
                 return False
             test_result = self._orchestrate_gatt_disconnection(bluetooth_gatt,
                                                                gatt_callback)
-            self.cen_ad.droid.gattClientClose(bluetooth_gatt)
             if not test_result:
                 self.log.info("Failed to disconnect from peripheral device.")
                 return False
@@ -821,8 +898,18 @@
                     start_time = time.time() + self.default_timeout
                     target_name = self.per_ad.droid.bluetoothGetLocalName()
                     while time.time() < start_time and bonded == False:
-                        bonded_devices = self.cen_ad.droid.bluetoothGetBondedDevices(
-                        )
+                        bonded_devices = \
+                            self.cen_ad.droid.bluetoothGetBondedDevices()
+                        for device in bonded_devices:
+                            if ('name' in device.keys() and
+                                    device['name'] == target_name):
+                                bonded = True
+                                break
+                    bonded = False
+                    target_name = self.cen_ad.droid.bluetoothGetLocalName()
+                    while time.time() < start_time and bonded == False:
+                        bonded_devices = \
+                            self.per_ad.droid.bluetoothGetBondedDevices()
                         for device in bonded_devices:
                             if ('name' in device.keys() and
                                     device['name'] == target_name):
diff --git a/acts/tests/google/bt/pts/BtCmdLineTest.py b/acts/tests/google/bt/pts/BtCmdLineTest.py
index 2e81163..5091db3 100644
--- a/acts/tests/google/bt/pts/BtCmdLineTest.py
+++ b/acts/tests/google/bt/pts/BtCmdLineTest.py
@@ -40,8 +40,7 @@
             self.log.error(
                 "Missing mandatory user config \"target_mac_address\"!")
             return False
-        self.target_mac_address = self.user_params["target_mac_address"].upper(
-        )
+        self.target_mac_address = self.user_params["target_mac_address"].upper()
 
         self.android_devices[0].droid.bluetoothSetLocalName("CMD LINE Test")
         if len(self.android_devices) > 1:
@@ -78,8 +77,8 @@
             for filename in filenames:
                 file = os.path.join(dirname, filename)
                 #TODO: Handle file paths with spaces
-                self.android_devices[0].adb.push("{} {}".format(
-                    file, android_music_path))
+                self.android_devices[0].adb.push(
+                    "{} {}".format(file, android_music_path))
 
     def setup_class(self):
         return True
diff --git a/acts/tests/google/bt/pts/cmd_input.py b/acts/tests/google/bt/pts/cmd_input.py
index e1579b2..e97b1a4 100644
--- a/acts/tests/google/bt/pts/cmd_input.py
+++ b/acts/tests/google/bt/pts/cmd_input.py
@@ -16,7 +16,6 @@
 """
 Python script for wrappers to various libraries.
 """
-
 from acts.test_utils.bt.BtEnum import BluetoothScanModeType
 from acts.test_utils.bt.GattEnum import GattServerResponses
 from ble_lib import BleLib
@@ -26,6 +25,7 @@
 from gatts_lib import GattServerLib
 from rfcomm_lib import RfcommLib
 
+import time
 import cmd
 import gatt_test_database
 """Various Global Strings"""
@@ -37,6 +37,30 @@
     """Simple command processor for Bluetooth PTS Testing"""
     gattc_lib = None
 
+    def connect_hsp_helper(self, ad):
+        """A helper function for making HSP connections"""
+        end_time = time.time() + 20
+        connected_hsp_devices = len(ad.droid.bluetoothHspGetConnectedDevices())
+        while connected_hsp_devices != 1 and time.time() < end_time:
+            try:
+                ad.droid.bluetoothHspConnect(self.mac_addr)
+                time.sleep(3)
+                if len(ad.droid.bluetoothHspGetConnectedDevices() == 1):
+                    break
+            except Exception:
+                self.log.debug("Failed to connect hsp trying again...")
+            try:
+                ad.droid.bluetoothConnectBonded(self.mac_addr)
+            except Exception:
+                self.log.info("Failed to connect to bonded device...")
+            connected_hsp_devices = len(
+                ad.droid.bluetoothHspGetConnectedDevices())
+        if connected_hsp_devices != 1:
+            self.log.error("Failed to reconnect to HSP service...")
+            return False
+        self.log.info("Connected to HSP service...")
+        return True
+
     def setup_vars(self, android_devices, mac_addr, log):
         self.pri_dut = android_devices[0]
         if len(android_devices) > 1:
diff --git a/acts/tests/google/native/bt/BtNativeTest.py b/acts/tests/google/native/bt/BtNativeTest.py
index 9660a8e..5315e34 100644
--- a/acts/tests/google/native/bt/BtNativeTest.py
+++ b/acts/tests/google/native/bt/BtNativeTest.py
@@ -1,20 +1,4 @@
-#/usr/bin/env python3.4
-#
-# Copyright (C) 2016 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 time
+mport time
 from acts.base_test import BaseTestClass
 from acts.controllers import native_android_device
 from acts.test_utils.bt.native_bt_test_utils import setup_native_bluetooth
@@ -26,14 +10,15 @@
 
     def __init__(self, controllers):
         BaseTestClass.__init__(self, controllers)
-        setup_native_bluetooth(self.native_devices)
-        self.droid = self.native_devices[0].droid
-        self.tests = ("test_binder_get_name",
-                      "test_binder_get_name_invalid_parameter",
-                      "test_binder_set_name_get_name",
-                      "test_binder_get_address", )
-        if len(self.native_devices) > 1:
-            self.droid1 = self.native_devices[1].droid
+        setup_native_bluetooth(self.native_android_devices)
+        self.droid = self.native_android_devices[0].droid
+        self.tests = (
+            "test_binder_get_name",
+            "test_binder_get_name_invalid_parameter",
+            "test_binder_set_name_get_name",
+            "test_binder_get_address", )
+        if len(self.native_android_devices) > 1:
+            self.droid1 = self.native_android_devices[1].droid
             self.tests = self.tests + ("test_two_devices_set_get_name", )
 
     def test_binder_get_name(self):
@@ -67,10 +52,11 @@
 
     def test_two_devices_set_get_name(self):
         test_name = generate_id_by_size(4)
-        for n in self.native_devices:
+        for n in self.native_android_devices:
             d = n.droid
             d.BtBinderSetName(test_name)
             name = d.BtBinderGetName()
             if name != test_name:
                 return False
         return True
+
diff --git a/acts/tests/google/tel/live/TelLivePreflightTest.py b/acts/tests/google/tel/live/TelLivePreflightTest.py
index 2e0a78c..adb1379 100644
--- a/acts/tests/google/tel/live/TelLivePreflightTest.py
+++ b/acts/tests/google/tel/live/TelLivePreflightTest.py
@@ -19,6 +19,8 @@
 
 import time
 from queue import Empty
+
+from acts.test_decorators import test_tracker_info
 from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts.test_utils.tel.tel_defines import AOSP_PREFIX
 from acts.test_utils.tel.tel_defines import CAPABILITY_PHONE
@@ -58,11 +60,9 @@
         TelephonyBaseTest.__init__(self, controllers)
 
         self.wifi_network_ssid = self.user_params.get(
-            "wifi_network_ssid") or self.user_params.get(
-                "wifi_network_ssid_2g")
+            "wifi_network_ssid") or self.user_params.get("wifi_network_ssid_2g")
         self.wifi_network_pass = self.user_params.get(
-            "wifi_network_pass") or self.user_params.get(
-                "wifi_network_pass_2g")
+            "wifi_network_pass") or self.user_params.get("wifi_network_pass_2g")
 
     def setup_class(self):
         pass
@@ -72,6 +72,7 @@
 
     """ Tests Begin """
 
+    @test_tracker_info(uuid="8390a2eb-a744-4cda-bade-f94a2cc83f02")
     @TelephonyBaseTest.tel_test_wrap
     def test_check_environment(self):
         ad = self.android_devices[0]
@@ -91,6 +92,7 @@
         # TODO: add more environment check here.
         return True
 
+    @test_tracker_info(uuid="7bb23ac7-6b7b-4d5e-b8d6-9dd10032b9ad")
     @TelephonyBaseTest.tel_test_wrap
     def test_pre_flight_check(self):
         for ad in self.android_devices:
@@ -99,6 +101,7 @@
                 abort_all_tests(ad.log, "Unable to find a valid subscription!")
         return True
 
+    @test_tracker_info(uuid="1070b160-902b-43bf-92a0-92cc2d05bb13")
     @TelephonyBaseTest.tel_test_wrap
     def test_check_crash(self):
         for ad in self.android_devices:
diff --git a/acts/tests/google/wifi/SetupWifiNetworkTest.py b/acts/tests/google/wifi/SetupWifiNetworkTest.py
new file mode 100644
index 0000000..26026ff
--- /dev/null
+++ b/acts/tests/google/wifi/SetupWifiNetworkTest.py
@@ -0,0 +1,107 @@
+#!/usr/bin/env 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 logging
+import socket
+import sys
+
+from acts import base_test
+from acts.controllers.ap_lib import hostapd_ap_preset
+from acts.controllers.ap_lib import hostapd_bss_settings
+from acts.controllers.ap_lib import hostapd_constants
+from acts.controllers.ap_lib import hostapd_config
+from acts.controllers.ap_lib import hostapd_security
+
+
+class SetupWifiNetworkTest(base_test.BaseTestClass):
+    def wait_for_test_completion(self):
+        port = int(self.user_params["socket_port"])
+        timeout = float(self.user_params["socket_timeout_secs"])
+
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+
+        server_address = ('localhost', port)
+        logging.info("Starting server socket on localhost port %s", port)
+        sock.bind(('localhost', port))
+        sock.settimeout(timeout)
+        sock.listen(1)
+        logging.info("Waiting for client socket connection")
+        try:
+            connection, client_address = sock.accept()
+        except socket.timeout:
+            logging.error("Did not receive signal. Shutting down AP")
+        except socket.error:
+            logging.error("Socket connection errored out. Shutting down AP")
+        finally:
+            connection.close()
+            sock.shutdown(socket.SHUT_RDWR)
+            sock.close()
+
+    def setup_ap(self):
+        bss_settings = []
+        self.access_point = self.access_points[0]
+        network_type = self.user_params["network_type"]
+        if (network_type == "2G"):
+            self.channel = hostapd_constants.AP_DEFAULT_CHANNEL_2G
+        else:
+            self.channel = hostapd_constants.AP_DEFAULT_CHANNEL_5G
+
+        self.ssid = self.user_params["ssid"]
+        self.security = self.user_params["security"]
+        if self.security == "none":
+            self.config = hostapd_ap_preset.create_ap_preset(
+                channel=self.channel,
+                ssid=self.ssid,
+                bss_settings=bss_settings,
+                profile_name='whirlwind')
+        else:
+            self.passphrase = self.user_params["passphrase"]
+            self.hostapd_security = hostapd_security.Security(
+                security_mode=self.security, password=self.passphrase)
+            self.config = hostapd_ap_preset.create_ap_preset(
+                channel=self.channel,
+                ssid=self.ssid,
+                security=self.hostapd_security,
+                bss_settings=bss_settings,
+                profile_name='whirlwind')
+        self.access_point.start_ap(self.config)
+
+    def test_set_up_single_ap(self):
+        req_params = [
+            "AccessPoint", "network_type", "ssid", "passphrase", "security",
+            "socket_port", "socket_timeout_secs"
+        ]
+        opt_params = []
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_params)
+        # Setup the AP environment
+        self.setup_ap()
+        # AP enviroment created. Wait for client to teardown the environment
+        self.wait_for_test_completion()
+
+    def test_set_up_open_ap(self):
+        req_params = [
+            "AccessPoint", "network_type", "ssid", "security", "socket_port",
+            "socket_timeout_secs"
+        ]
+        opt_params = []
+        self.unpack_userparams(
+            req_param_names=req_params, opt_param_names=opt_params)
+        # Setup the AP environment
+        self.setup_ap()
+        # AP enviroment created. Wait for client to teardown the environment
+        self.wait_for_test_completion()
diff --git a/acts/tests/google/wifi/WifiAwareManagerTest.py b/acts/tests/google/wifi/WifiAwareManagerTest.py
deleted file mode 100644
index a3d4874..0000000
--- a/acts/tests/google/wifi/WifiAwareManagerTest.py
+++ /dev/null
@@ -1,1180 +0,0 @@
-#!/usr/bin/python3.4
-#
-#   Copyright 2016 - 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 json
-import pprint
-import re
-import queue
-import statistics
-import time
-
-from acts import asserts
-from acts import base_test
-from acts.signals import generated_test
-from acts.test_utils.net import connectivity_const as con_const
-from acts.test_utils.net import nsd_const as nsd_const
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi import wifi_aware_const as aware_const
-
-# arbitrary timeout for events
-EVENT_TIMEOUT = 30
-
-
-class WifiAwareManagerTest(base_test.BaseTestClass):
-    # configuration parameters used by tests
-    publish_config = {"ServiceName": "GoogleTestServiceX",
-                      "ServiceSpecificInfo": "Data XYZ",
-                      "MatchFilter": {"int0": 14,
-                                      "data0": "MESSAGE_ALL"},
-                      "PublishType": 0,
-                      "TtlSec": 0}
-    subscribe_config = {"ServiceName": "GoogleTestServiceX",
-                        "ServiceSpecificInfo": "Data ABC",
-                        "MatchFilter": {"int0": 14,
-                                        "data0": "MESSAGE_ALL"},
-                        "SubscribeType": 0,
-                        "TtlSec": 0}
-    rtt_24_20 = {"deviceType": 5,
-                 "requestType": 2,
-                 "frequency": 2437,
-                 "channelWidth": 0,
-                 "centerFreq0": 2437,
-                 "centerFreq1": 0,
-                 "numberBurst": 0,
-                 "numSamplesPerBurst": 5,
-                 "numRetriesPerMeasurementFrame": 3,
-                 "numRetriesPerFTMR": 3,
-                 "LCIRequest": False,
-                 "LCRRequest": False,
-                 "burstTimeout": 15,
-                 "preamble": 2,
-                 "bandwidth": 4}
-    rtt_50_40 = {"deviceType": 5,
-                 "requestType": 2,
-                 "frequency": 5200,
-                 "channelWidth": 1,
-                 "centerFreq0": 5190,
-                 "centerFreq1": 0,
-                 "numberBurst": 0,
-                 "numSamplesPerBurst": 5,
-                 "numRetriesPerMeasurementFrame": 3,
-                 "numRetriesPerFTMR": 3,
-                 "LCIRequest": False,
-                 "LCRRequest": False,
-                 "burstTimeout": 15,
-                 "preamble": 2,
-                 "bandwidth": 8}
-    rtt_50_80 = {"deviceType": 5,
-                 "requestType": 2,
-                 "frequency": 5200,
-                 "channelWidth": 2,
-                 "centerFreq0": 5210,
-                 "centerFreq1": 0,
-                 "numberBurst": 0,
-                 "numSamplesPerBurst": 5,
-                 "numRetriesPerMeasurementFrame": 3,
-                 "numRetriesPerFTMR": 3,
-                 "LCIRequest": False,
-                 "LCRRequest": False,
-                 "burstTimeout": 15,
-                 "preamble": 4,
-                 "bandwidth": 16}
-    network_req = {"TransportType": 5}
-    nsd_service_info = {"serviceInfoServiceName": "sl4aTestAwareIperf",
-                        "serviceInfoServiceType": "_simple-tx-rx._tcp.",
-                        "serviceInfoPort": 2257}
-
-    def setup_test(self):
-        self.msg_id = 10
-        for ad in self.android_devices:
-            wutils.wifi_toggle_state(ad, True)
-            aware_usage_enabled = ad.droid.wifiIsAwareAvailable()
-            if not aware_usage_enabled:
-                self.log.info('Aware not enabled. Waiting for %s',
-                              aware_const.BROADCAST_WIFI_AWARE_AVAILABLE)
-                try:
-                    ad.ed.pop_event(aware_const.BROADCAST_WIFI_AWARE_AVAILABLE,
-                                    EVENT_TIMEOUT)
-                    self.log.info(aware_const.BROADCAST_WIFI_AWARE_AVAILABLE)
-                except queue.Empty:
-                    asserts.fail('Timed out while waiting for %s' %
-                                 aware_const.BROADCAST_WIFI_AWARE_AVAILABLE)
-
-    def teardown_test(self):
-        for ad in self.android_devices:
-            ad.droid.wifiAwareDestroyAll()
-    #         asserts.assert_true(
-    #             wutils.wifi_toggle_state(ad, False),
-    #             "Failed disabling Wi-Fi interface")
-
-    def extract_stats(self, data, results, key_prefix, log_prefix):
-        """Extract statistics from the data, store in the dict dictionary, and
-        output to the info log.
-
-        Args:
-            data: A list containing the data to be analyzed.
-            results: A dictionary into which to place the statistics.
-            key_prefix: A string prefix to use for the dict keys storing the
-                        extracted stats.
-            log_prefix: A string prefix to use for the info log.
-            include_data: If True includes the raw data in the dictionary,
-                          otherwise just the stats.
-        """
-        num_samples = len(data)
-        results['%s_num_samples' % key_prefix] = num_samples
-
-        if not data:
-            return
-
-        data_min = min(data)
-        data_max = max(data)
-        data_mean = statistics.mean(data)
-
-        results['%s_min' % key_prefix] = data_min
-        results['%s_max' % key_prefix] = data_max
-        results['%s_mean' % key_prefix] = data_mean
-        results['%s_raw_data' % key_prefix] = data
-
-        if num_samples > 1:
-            data_stdev = statistics.stdev(data)
-            results['%s_stdev' % key_prefix] = data_stdev
-            self.log.info(
-                '%s: num_samples=%d, min=%.2f, max=%.2f, mean=%.2f, stdev=%.2f',
-                log_prefix, num_samples, data_min, data_max, data_mean,
-                data_stdev)
-        else:
-            self.log.info('%s: num_samples=%d, min=%.2f, max=%.2f, mean=%.2f',
-                          log_prefix, num_samples, data_min, data_max,
-                          data_mean)
-
-    def get_interface_mac(self, device, interface):
-        """Get the HW MAC address of the specified interface.
-
-        Returns the HW MAC address or raises an exception on failure.
-
-        Args:
-            device: The 'AndroidDevice' on which to query the interface.
-            interface: The name of the interface to query.
-
-        Returns:
-            mac: MAC address of the interface.
-        """
-        out = device.adb.shell("ifconfig %s" % interface)
-        completed = out.decode('utf-8').strip()
-        res = re.match(".* HWaddr (\S+).*", completed, re.S)
-        asserts.assert_true(
-            res, 'Unable to obtain MAC address for interface %s' % interface)
-        return res.group(1)
-
-    def get_interface_ipv6_link_local(self, device, interface):
-        """Get the link-local IPv6 address of the specified interface.
-
-        Returns the link-local IPv6 address of the interface or raises an
-        exception on failure.
-
-        Args:
-            device: The 'AndroidDevice' on which to query the interface.
-            interface: The name of the interface to query.
-
-        Returns:
-            addr: link-local IPv6 address of the interface.
-        """
-        out = device.adb.shell("ifconfig %s" % interface)
-        completed = out.decode('utf-8').strip()
-        res = re.match(".*inet6 addr: (\S+)/64 Scope: Link.*", completed, re.S)
-        asserts.assert_true(
-            res,
-            'Unable to obtain IPv6 link-local for interface %s' % interface)
-        return res.group(1)
-
-    def exec_connect(self, device, name, config=None):
-        """Executes the Aware connection creation operation.
-
-        Creates an Aware connection (client) and waits for a confirmation event
-        of success. Raise test failure signal if no such event received.
-
-        Args:
-            device: The 'AndroidDevice' on which to set up the connection.
-            name: An arbitary name used for logging.
-            config: An optional ConfigRequest (JSON-configured) structure.
-        """
-        session_id = device.droid.wifiAwareAttach(config)
-        try:
-            event = device.ed.pop_event(aware_const.EVENT_CB_ON_ATTACHED,
-                                        EVENT_TIMEOUT)
-            self.log.info('%s: %s', aware_const.EVENT_CB_ON_ATTACHED,
-                          event['data'])
-        except queue.Empty:
-            asserts.fail('Timed out while waiting for %s on %s' %
-                         (aware_const.EVENT_CB_ON_ATTACHED, name))
-        self.log.debug(event)
-        return session_id
-
-    def reliable_tx(self, device, session_id, peer, msg):
-        """Sends an Aware message.
-
-        Sends a message to the peer and waits for success confirmation. Raises
-        an exception on failure or timeout.
-
-        The message is sent using the MAX retransmission count.
-
-        Args:
-            device: The 'AndroidDevice' on which to send the message.
-            session_id: The session ID context from which to send the message.
-                This is the value returned by wifiAwarePublish() or
-                wifiAwareSubscribe().
-            peer: The peer ID to send the message to. Obtained through a match
-                or a received message.
-            msg: The message to be transmitted to the peer.
-        """
-        events_regex = '%s|%s' % (aware_const.SESSION_CB_ON_MESSAGE_SEND_FAILED,
-                                  aware_const.SESSION_CB_ON_MESSAGE_SENT)
-        self.msg_id = self.msg_id + 1
-
-        while True:
-            try:
-                device.droid.wifiAwareSendMessage(session_id, peer, self.msg_id,
-                                                  msg,
-                                                  aware_const.MAX_TX_RETRIES)
-                events = device.ed.pop_events(events_regex, EVENT_TIMEOUT)
-            except queue.Empty:
-                asserts.fail('Timed out while waiting for %s', events_regex)
-
-            for event in events:
-                self.log.info('%s: %s', event['name'], event['data'])
-                if event['data']['messageId'] == self.msg_id:
-                    asserts.assert_equal(event['name'],
-                                         aware_const.SESSION_CB_ON_MESSAGE_SENT,
-                                         'Failed (re)transmission')
-                    return
-
-    def exec_rtt(self, device, session_id, peer_id, rtt_param, label,
-                 repeat_count):
-        """Executes an RTT operation.
-
-        Args:
-            device: The 'AndroidDevice' on which to send the message.
-            session_id: The session ID context from which to send the message.
-                This is the value returned by wifiAwarePublish() or
-                wifiAwareSubscribe().
-            peer_id: The peer ID to send the message to. Obtained through a
-                match or a received message.
-            rtt_param: RTT session parameters.
-            msg: Message/tag describing RTT experiment.
-            repeat_count: Number of RTT measurements to execute.
-        """
-        rtt_param['bssid'] = peer_id
-        rtt_stats = {
-            'failure_codes': {},
-            'distance': {
-                'sum': 0,
-                'num_samples': 0
-            }
-        }
-        for i in range(repeat_count):
-            device.droid.wifiAwareStartRanging(0, session_id, [rtt_param])
-
-            events_regex = '%s|%s|%s' % (aware_const.RTT_LISTENER_CB_ON_SUCCESS,
-                                         aware_const.RTT_LISTENER_CB_ON_FAILURE,
-                                         aware_const.RTT_LISTENER_CB_ON_ABORT)
-            try:
-                events_pub_range = device.ed.pop_events(events_regex,
-                                                        EVENT_TIMEOUT)
-                for event in events_pub_range:
-                    self.log.debug('%s: %s: %s', label, event['name'],
-                                   event['data'])
-                    results = event['data']['Results']
-                    for rtt_result in results:
-                        rtt_status = rtt_result['status']
-                        if rtt_status == 0:
-                            distance = rtt_result['distance']
-                            self.log.info('%s: distance=%d', label, distance)
-                            rtt_stats['distance']['sum'] = (
-                                rtt_stats['distance']['sum'] + distance)
-                            rtt_stats['distance']['num_samples'] = (
-                                rtt_stats['distance']['num_samples'] + 1)
-                        else:
-                            self.log.info('%s: status=%d', label, rtt_status)
-                            if rtt_status not in rtt_stats['failure_codes']:
-                                rtt_stats['failure_codes'][rtt_status] = 0
-                            rtt_stats['failure_codes'][rtt_status] = (
-                                rtt_stats['failure_codes'][rtt_status] + 1)
-            except queue.Empty:
-                self.log.info('%s: Timed out while waiting for RTT events %s',
-                              label, events_regex)
-        self.log.info('%s:\n\tParam: %s\n\tRTT statistics: %s', label,
-                      rtt_param, rtt_stats)
-
-    def test_cluster_latency(self):
-        """Measure the time it takes to make Aware available to an app from
-        the time a request is made.
-
-        Configuration: 1 device
-
-        Logical steps:
-          * DUT initiate Aware clustering
-          * DUT wait for attach event (measure time)
-          * DUT wait for identity change event (measure time)
-          * DUT destroy Aware session
-        """
-        results = {}
-        results['num_iterations'] = 100
-
-        self.dut = self.android_devices[0]
-
-        attach_latency = []
-        identity_change_latency = []
-        results['num_failed_attaches'] = 0
-        for i in range(results['num_iterations']):
-            session_id = self.dut.droid.wifiAwareAttach()
-            try:
-                event_attached = self.dut.ed.pop_event(
-                    aware_const.EVENT_CB_ON_ATTACHED, EVENT_TIMEOUT)
-                attach_latency.append(event_attached['data'][
-                    aware_const.EVENT_CB_KEY_LATENCY_MS])
-                self.log.debug('%s: %s', aware_const.EVENT_CB_ON_ATTACHED,
-                               event_attached['data'])
-
-                event_identity_change = self.dut.ed.pop_event(
-                    aware_const.EVENT_CB_ON_IDENTITY_CHANGED, EVENT_TIMEOUT)
-                identity_change_latency.append(event_identity_change['data'][
-                    aware_const.EVENT_CB_KEY_TIMESTAMP_MS] - event_attached[
-                        'data'][
-                            aware_const.SESSION_CB_KEY_TIMESTAMP_MS])
-            except queue.Empty:
-                results['num_failed_attaches'] += 1
-                self.log.debug('Timed out while waiting for %s|%s on DUT',
-                               aware_const.EVENT_CB_ON_ATTACHED,
-                               aware_const.EVENT_CB_ON_IDENTITY_CHANGED)
-            self.dut.droid.wifiAwareDestroy(session_id)
-
-        self.extract_stats(attach_latency, results, 'attach_latency', 'Attach')
-        self.extract_stats(identity_change_latency, results,
-                           'identity_change_latency', 'Identity Change')
-        asserts.explicit_pass('test_cluster_latency finished', extras=results)
-
-    def run_aware_discovery_session(self, discovery_config):
-        """Perform Aware configuration, discovery, and message exchange.
-
-        Configuration: 2 devices, one acting as Publisher (P) and the
-        other as Subscriber (S)
-
-        Logical steps:
-          * P & S initiate Aware clustering (if not already up)
-          * P & S wait for Aware connection confirmation
-          * P starts publishing
-          * S starts subscribing
-          * S waits for a match (discovery) notification
-          * S sends a message to P, confirming that sent successfully
-          * P waits for a message and confirms that received (uncorrupted)
-          * P sends a message to S, confirming that sent successfully
-          * S waits for a message and confirms that received (uncorrupted)
-
-        Args:
-            discovery_configs: {'Title': description,
-                'PublishConfig': publish_config,
-                'SubscribeConfig': subscribe_config}
-
-        Returns:
-            True if discovery succeeds, else false.
-        """
-        # Configure Test
-        self.publisher = self.android_devices[0]
-        self.subscriber = self.android_devices[1]
-
-        sub2pub_msg = "How are you doing? 你好嗎?"
-        pub2sub_msg = "Doing ok - thanks! 做的不錯 - 謝謝!"
-
-        # Start Test
-        pub_connect_id = self.exec_connect(self.publisher, "publisher")
-        sub_connect_id = self.exec_connect(self.subscriber, "subscriber")
-
-        # Configuration
-        publish_config = discovery_config['PublishConfig']
-        subscribe_config = discovery_config['SubscribeConfig']
-        self.log.debug('Publish config=%s, Subscribe config=%s', publish_config,
-                       subscribe_config)
-
-        pub_id = self.publisher.droid.wifiAwarePublish(pub_connect_id,
-                                                       publish_config)
-        sub_id = self.subscriber.droid.wifiAwareSubscribe(sub_connect_id,
-                                                          subscribe_config)
-
-        try:
-            event_sub_match = self.subscriber.ed.pop_event(
-                aware_const.SESSION_CB_ON_SERVICE_DISCOVERED, EVENT_TIMEOUT)
-            self.log.info('%s: %s',
-                          aware_const.SESSION_CB_ON_SERVICE_DISCOVERED,
-                          event_sub_match['data'])
-        except queue.Empty:
-            asserts.fail('Timed out while waiting for %s on Subscriber' %
-                         aware_const.SESSION_CB_ON_SERVICE_DISCOVERED)
-        self.log.debug(event_sub_match)
-
-        self.reliable_tx(self.subscriber, sub_id,
-                         event_sub_match['data']['peerId'], sub2pub_msg)
-
-        try:
-            event_pub_rx = self.publisher.ed.pop_event(
-                aware_const.SESSION_CB_ON_MESSAGE_RECEIVED, EVENT_TIMEOUT)
-            self.log.info('%s: %s', aware_const.SESSION_CB_ON_MESSAGE_RECEIVED,
-                          event_pub_rx['data'])
-            asserts.assert_equal(event_pub_rx['data']['messageAsString'],
-                                 sub2pub_msg,
-                                 "Subscriber -> publisher message corrupted")
-        except queue.Empty:
-            asserts.fail('Timed out while waiting for %s on publisher' %
-                         aware_const.SESSION_CB_ON_MESSAGE_RECEIVED)
-
-        self.reliable_tx(self.publisher, pub_id, event_pub_rx['data']['peerId'],
-                         pub2sub_msg)
-
-        try:
-            event_sub_rx = self.subscriber.ed.pop_event(
-                aware_const.SESSION_CB_ON_MESSAGE_RECEIVED, EVENT_TIMEOUT)
-            self.log.info('%s: %s', aware_const.SESSION_CB_ON_MESSAGE_RECEIVED,
-                          event_sub_rx['data'])
-            asserts.assert_equal(event_sub_rx['data']['messageAsString'],
-                                 pub2sub_msg,
-                                 "Publisher -> subscriber message corrupted")
-        except queue.Empty:
-            asserts.fail('Timed out while waiting for %s on subscriber' %
-                         aware_const.SESSION_CB_ON_MESSAGE_RECEIVED)
-
-        if publish_config['TtlSec'] != 0:
-            try:
-                event_pub_term = self.publisher.ed.pop_event(
-                    aware_const.SESSION_CB_ON_SESSION_TERMINATED,
-                    publish_config['TtlSec'] + 5)
-                self.log.info('%s: %s',
-                              aware_const.SESSION_CB_ON_SESSION_TERMINATED,
-                              event_pub_term['data'])
-            except queue.Empty:
-                asserts.fail('Timed out while waiting for %s on publisher' %
-                             aware_const.SESSION_CB_ON_SESSION_TERMINATED)
-        if subscribe_config['TtlSec'] != 0:
-            try:
-                event_sub_term = self.subscriber.ed.pop_event(
-                    aware_const.SESSION_CB_ON_SESSION_TERMINATED,
-                    subscribe_config['TtlSec'] + 5)
-                self.log.info('%s: %s',
-                              aware_const.SESSION_CB_ON_SESSION_TERMINATED,
-                              event_sub_term['data'])
-            except queue.Empty:
-                asserts.fail('Timed out while waiting for %s on subscriber' %
-                             aware_const.SESSION_CB_ON_SESSION_TERMINATED)
-
-    @generated_test
-    def test_aware_discovery_session(self):
-        """Perform Aware configuration, discovery, and message exchange.
-
-        Test multiple discovery types:
-        - Unsolicited publish + passive subscribe
-        - Solicited publish + active subscribe
-        """
-
-        discovery_configs = (
-            {'Title': 'ActivePub',
-             'PublishConfig': self.publish_config,
-             'SubscribeConfig': self.subscribe_config},
-            {'Title': 'ActiveSub',
-             'PublishConfig': dict(self.publish_config, **{'PublishType': 1}),
-             'SubscribeConfig': dict(self.subscribe_config,
-                                     **{'SubscribeType': 1})},
-            {'Title': 'ActivePub-LimitedTtl',
-             'PublishConfig': dict(self.publish_config, **{"TtlSec": 20}),
-             'SubscribeConfig': dict(self.subscribe_config, **{"TtlSec": 20})})
-        name_func = lambda discovery_config: ("test_aware_discovery_session__%s") % discovery_config['Title']
-        self.run_generated_testcases(self.run_aware_discovery_session,
-                                     discovery_configs,
-                                     name_func=name_func)
-
-    def run_aware_discovery_latency(self, dw_interval):
-        """Measure the latency of Aware discovery on the subscriber.
-
-        Args:
-            dw_interval: Discovery Window Interval configuration
-
-        Configuration: 2 devices, one acting as Publisher (P) and the
-        other as Subscriber (S)
-
-        Logical steps:
-          * P & S if not new_session initiate Aware clustering (if not already up)
-          * P & S if not new session wait for Aware connection confirmation
-          * P starts publishing
-          * Wait for a few seconds to make sure everyone is ready (measuring
-          * discovery - not clustering).
-          * Loop:
-          *   S start subscribing
-          *   Measure latency to S registering a discovery
-          *   S terminates subscribe
-        """
-        self.publisher = self.android_devices[0]
-        self.subscriber = self.android_devices[1]
-        results = {}
-        results['num_iterations'] = 100
-
-        # Start Test
-        pub_connect_id = self.exec_connect(self.publisher, "publisher",
-                                           {"DiscoveryWindowInterval": [dw_interval, dw_interval]})
-        sub_connect_id = self.exec_connect(self.subscriber, "subscriber",
-                                           {"DiscoveryWindowInterval": [dw_interval, dw_interval]})
-
-        pub_id = self.publisher.droid.wifiAwarePublish(pub_connect_id,
-                                                       self.publish_config)
-        try:
-            self.publisher.ed.pop_event(
-                aware_const.SESSION_CB_ON_PUBLISH_STARTED, EVENT_TIMEOUT)
-        except:
-            asserts.fail('Timed out while waiting for %s on Publisher' %
-                         aware_const.SESSION_CB_ON_PUBLISH_STARTED)
-
-        # another arbitrary long sleep time to make sure that publisher is
-        # on-the-air
-        time.sleep(10)
-
-        sub_session_setup_latency = []
-        sub_session_discovery_latency = []
-        results['num_failed_discovery'] = 0
-        for i in range(results['num_iterations']):
-            sub_id = self.subscriber.droid.wifiAwareSubscribe(
-                sub_connect_id, self.subscribe_config)
-            try:
-                event_sub = self.subscriber.ed.pop_event(
-                    aware_const.SESSION_CB_ON_SUBSCRIBE_STARTED, EVENT_TIMEOUT)
-                sub_session_setup_latency.append(event_sub['data'][
-                    aware_const.SESSION_CB_KEY_LATENCY_MS])
-                self.log.debug('%s: %s',
-                               aware_const.SESSION_CB_ON_SUBSCRIBE_STARTED,
-                               event_sub['data'])
-
-                event_discovery = self.subscriber.ed.pop_event(
-                    aware_const.SESSION_CB_ON_SERVICE_DISCOVERED, EVENT_TIMEOUT)
-                sub_session_discovery_latency.append(event_discovery['data'][
-                    aware_const.SESSION_CB_KEY_TIMESTAMP_MS] - event_sub[
-                        'data'][aware_const.SESSION_CB_KEY_TIMESTAMP_MS])
-                self.log.debug('%s: %s',
-                               aware_const.SESSION_CB_ON_SERVICE_DISCOVERED,
-                               event_discovery['data'])
-            except queue.Empty:
-                results['num_failed_discovery'] += 1
-                self.log.debug(
-                    'Timed out while waiting for %s|%s on Subscriber',
-                    aware_const.SESSION_CB_ON_SUBSCRIBE_STARTED,
-                    aware_const.SESSION_CB_ON_SERVICE_DISCOVERED)
-            self.subscriber.droid.wifiAwareDestroyDiscoverySession(sub_id)
-
-        self.extract_stats(sub_session_setup_latency, results,
-                           'sub_session_setup_latency',
-                           'Subscribe Session Setup')
-        self.extract_stats(sub_session_discovery_latency, results,
-                           'sub_session_discovery_latency',
-                           'Subscribe Session Discovery')
-        asserts.explicit_pass('test_aware_discovery_latency finished',
-                              extras=results)
-
-    @generated_test
-    def test_aware_discovery_latency(self):
-        """Measure the latency of Aware discovery on the subscriber.
-
-        Test different discovery window intervals: 1, 2, 3, 4, 5
-        """
-
-        name_func = lambda dw_interval: "test_aware_discovery_latency__dw_%d" % dw_interval
-        self.run_generated_testcases(self.run_aware_discovery_latency,
-                                     [1, 2, 3, 4, 5],
-                                     name_func=name_func)
-
-    def run_aware_messaging(self, retry_count):
-        """Perform Aware configuration, discovery, and large message exchange.
-
-        Args:
-            retry_count: retransmission count - from 0 to aware_const.MAX_TX_RETRIES
-
-        Configuration: 2 devices, one acting as Publisher (P) and the
-        other as Subscriber (S)
-
-        Logical steps:
-          * P & S initiate Aware clustering (if not already up)
-          * P & S wait for Aware connection confirmation
-          * P starts publishing
-          * S starts subscribing
-          * S waits for a match (discovery) notification
-          * S sends XX messages to P
-          * S confirms that all XXX messages were transmitted
-          * P confirms that all XXX messages are received
-        """
-        self.publisher = self.android_devices[0]
-        self.subscriber = self.android_devices[1]
-        results = {}
-        results['num_non_empty_messages'] = 100
-        results[
-            'num_null_and_empty_messages'] = 10  # one of each in sequence until reach count
-
-        # Start Test
-        pub_connect_id = self.exec_connect(self.publisher, "publisher")
-        sub_connect_id = self.exec_connect(self.subscriber, "subscriber")
-
-        pub_id = self.publisher.droid.wifiAwarePublish(pub_connect_id,
-                                                       self.publish_config)
-        sub_id = self.subscriber.droid.wifiAwareSubscribe(sub_connect_id,
-                                                          self.subscribe_config)
-
-        try:
-            event_sub_match = self.subscriber.ed.pop_event(
-                aware_const.SESSION_CB_ON_SERVICE_DISCOVERED, EVENT_TIMEOUT)
-            self.log.info('%s: %s',
-                          aware_const.SESSION_CB_ON_SERVICE_DISCOVERED,
-                          event_sub_match['data'])
-        except queue.Empty:
-            asserts.fail('Timed out while waiting for %s on Subscriber' %
-                         aware_const.SESSION_CB_ON_SERVICE_DISCOVERED)
-        self.log.debug(event_sub_match)
-
-        # send all messages at once
-        for i in range(results['num_non_empty_messages']):
-            self.msg_id = self.msg_id + 1
-            self.subscriber.droid.wifiAwareSendMessage(
-                sub_id, event_sub_match['data']['peerId'], self.msg_id,
-                "msg %s" % i, retry_count)
-
-        # send all empty & null messages
-        for i in range(results['num_null_and_empty_messages']):
-            self.msg_id = self.msg_id + 1
-            msg_to_send = None if (i % 2) else ""  # flip between null and ""
-            self.subscriber.droid.wifiAwareSendMessage(
-                sub_id, event_sub_match['data']['peerId'], self.msg_id,
-                msg_to_send, retry_count)
-
-        # wait for all messages to be transmitted correctly
-        results['num_tx_ok'] = 0
-        results['num_tx_fail'] = 0
-        tx_ok_stats = []
-        tx_fail_stats = []
-        events_regex = '%s|%s' % (aware_const.SESSION_CB_ON_MESSAGE_SEND_FAILED,
-                                  aware_const.SESSION_CB_ON_MESSAGE_SENT)
-        while (results['num_tx_ok'] + results['num_tx_fail']) < (
-                results['num_non_empty_messages'] +
-                results['num_null_and_empty_messages']):
-            try:
-                events = self.subscriber.ed.pop_events(events_regex,
-                                                       EVENT_TIMEOUT)
-
-                for event in events:
-                    if event['name'] == aware_const.SESSION_CB_ON_MESSAGE_SENT:
-                        results['num_tx_ok'] = results['num_tx_ok'] + 1
-                        if aware_const.SESSION_CB_KEY_LATENCY_MS in event[
-                                'data']:
-                            tx_ok_stats.append(event['data'][
-                                aware_const.SESSION_CB_KEY_LATENCY_MS])
-                    if event[
-                            'name'] == aware_const.SESSION_CB_ON_MESSAGE_SEND_FAILED:
-                        results['num_tx_fail'] = results['num_tx_fail'] + 1
-                        if aware_const.SESSION_CB_KEY_LATENCY_MS in event[
-                                'data']:
-                            tx_fail_stats.append(event['data'][
-                                aware_const.SESSION_CB_KEY_LATENCY_MS])
-            except queue.Empty:
-                self.log.warning('Timed out while waiting for %s on Subscriber'
-                                 ' - %d events received', events_regex,
-                                 results['num_tx_ok'] + results['num_tx_fail'])
-                break
-        self.log.info('Transmission stats: %d success, %d fail',
-                      results['num_tx_ok'], results['num_tx_fail'])
-        self.extract_stats(tx_ok_stats, results, 'tx_ok_latency',
-                           'Successful tx')
-        self.extract_stats(tx_fail_stats, results, 'tx_fail_latency', 'Fail tx')
-
-        # validate that all messages are received (not just the correct
-        # number of messages - since on occasion there may be duplicates
-        # received).
-        results['num_non_empty_received'] = 0
-        results['num_unique_received'] = 0
-        results['num_empty_received'] = 0
-        messages = {}
-        while (results['num_unique_received'] + results['num_empty_received'] <
-               results['num_tx_ok']):
-            try:
-                event = self.publisher.ed.pop_event(
-                    aware_const.SESSION_CB_ON_MESSAGE_RECEIVED, EVENT_TIMEOUT)
-                msg = event['data']['messageAsString']
-                if msg:
-                    results['num_non_empty_received'] = results[
-                        'num_non_empty_received'] + 1
-                    if msg not in messages:
-                        results['num_unique_received'] = results[
-                            'num_unique_received'] + 1
-                        messages[msg] = 0
-                    messages[msg] = messages[msg] + 1
-                else:
-                    results['num_empty_received'] = results[
-                        'num_empty_received'] + 1
-                self.log.debug('%s: %s',
-                               aware_const.SESSION_CB_ON_MESSAGE_RECEIVED, msg)
-            except queue.Empty:
-                asserts.fail(
-                    'Timed out while waiting for %s on Publisher: %d non-empty '
-                    'messages received, %d unique messages, %d empty messages' %
-                    (aware_const.SESSION_CB_ON_MESSAGE_RECEIVED,
-                     results['num_non_empty_received'],
-                     results['num_unique_received'],
-                     results['num_empty_received']),
-                    extras=results)
-        self.log.info(
-            'Reception stats: %d non-empty received (%d unique), %d empty',
-            results['num_non_empty_received'], results['num_unique_received'],
-            results['num_empty_received'])
-        if results['num_non_empty_received'] != results['num_unique_received']:
-            self.log.info('%d duplicate receptions of %d messages: %s',
-                          results['num_non_empty_received'] -
-                          results['num_unique_received'],
-                          results['num_non_empty_received'], messages)
-        if results['num_empty_received'] != results[
-                'num_null_and_empty_messages']:
-            self.log.info('%d extra empty/null message reception',
-                          results['num_empty_received'] -
-                          results['num_null_and_empty_messages'])
-        asserts.explicit_pass("run_aware_messaging pass finished successfully",
-                              extras=results)
-
-    @generated_test
-    def test_aware_messaging(self):
-        """Perform Aware configuration, discovery, and large message exchange.
-
-        Test multiple message send retry counts.
-        """
-        name_func = lambda retry_count: "test_aware_messaging__retries_%d" % retry_count
-        self.run_generated_testcases(self.run_aware_messaging,
-                                     [0, aware_const.MAX_TX_RETRIES],
-                                     name_func=name_func)
-
-    def test_aware_messaging_latency(self):
-        """Measure the latency of Aware message transmission which are not queued. Unqueued
-        message transmission data is a function of raw protocol and firmware behavior.
-
-        Configuration: 2 devices, one acting as Publisher (P) and the
-        other as Subscriber (S)
-
-        Logical steps:
-          * P & S initiate Aware clustering (if not already up)
-          * P & S wait for Aware connection confirmation
-          * P starts publishing
-          * S starts subscribing
-          * S waits for a match (discovery) notification
-          * Loop:
-          *    S sends 1 message to P
-          *    S confirms that message transmitted and measures latency
-        """
-        self.publisher = self.android_devices[0]
-        self.subscriber = self.android_devices[1]
-        results = {}
-        results['num_messages'] = 100
-
-        # Start Test
-        pub_connect_id = self.exec_connect(self.publisher, "publisher")
-        sub_connect_id = self.exec_connect(self.subscriber, "subscriber")
-
-        pub_id = self.publisher.droid.wifiAwarePublish(pub_connect_id,
-                                                       self.publish_config)
-        sub_id = self.subscriber.droid.wifiAwareSubscribe(sub_connect_id,
-                                                          self.subscribe_config)
-
-        try:
-            event_sub_match = self.subscriber.ed.pop_event(
-                aware_const.SESSION_CB_ON_SERVICE_DISCOVERED, EVENT_TIMEOUT)
-            self.log.info('%s: %s',
-                          aware_const.SESSION_CB_ON_SERVICE_DISCOVERED,
-                          event_sub_match['data'])
-        except queue.Empty:
-            asserts.fail('Timed out while waiting for %s on Subscriber' %
-                         aware_const.SESSION_CB_ON_SERVICE_DISCOVERED)
-        self.log.debug(event_sub_match)
-
-        results['num_tx_ok'] = 0
-        results['num_tx_fail'] = 0
-        tx_ok_stats = []
-        tx_fail_stats = []
-        events_regex = '%s|%s' % (aware_const.SESSION_CB_ON_MESSAGE_SEND_FAILED,
-                                  aware_const.SESSION_CB_ON_MESSAGE_SENT)
-        for i in range(results['num_messages']):
-            self.msg_id = self.msg_id + 1
-            self.subscriber.droid.wifiAwareSendMessage(
-                sub_id, event_sub_match['data']['peerId'], self.msg_id,
-                "msg %s" % i, 0)
-            try:
-                events = self.subscriber.ed.pop_events(events_regex,
-                                                       EVENT_TIMEOUT)
-                for event in events:
-                    if event['name'] == aware_const.SESSION_CB_ON_MESSAGE_SENT:
-                        results['num_tx_ok'] = results['num_tx_ok'] + 1
-                        if aware_const.SESSION_CB_KEY_LATENCY_MS in event[
-                                'data']:
-                            tx_ok_stats.append(event['data'][
-                                aware_const.SESSION_CB_KEY_LATENCY_MS])
-                    if event[
-                            'name'] == aware_const.SESSION_CB_ON_MESSAGE_SEND_FAILED:
-                        results['num_tx_fail'] = results['num_tx_fail'] + 1
-                        if aware_const.SESSION_CB_KEY_LATENCY_MS in event[
-                                'data']:
-                            tx_fail_stats.append(event['data'][
-                                aware_const.SESSION_CB_KEY_LATENCY_MS])
-            except queue.Empty:
-                asserts.fail('Timed out while waiting for %s on Subscriber',
-                             events_regex,
-                             extras=results)
-        self.extract_stats(tx_ok_stats, results, 'tx_ok_latency',
-                           'Successful tx')
-        self.extract_stats(tx_fail_stats, results, 'tx_fail_latency', 'Fail tx')
-        asserts.explicit_pass(
-            'test_aware_messaging_no_queue finished successfully',
-            extras=results)
-
-    def test_aware_rtt(self):
-        """Perform Aware configuration, discovery, and RTT.
-
-        Configuration: 2 devices, one acting as Publisher (P) and the
-        other as Subscriber (S)
-
-        Logical steps:
-          * P & S initiate Aware clustering (if not already up)
-          * P & S wait for Aware connection confirmation
-          * P starts publishing
-          * S starts subscribing
-          * S waits for a match (discovery) notification
-          * S performs 3 RTT measurements with P
-        """
-        # Configure Test
-        self.publisher = self.android_devices[0]
-        self.subscriber = self.android_devices[1]
-        rtt_iterations = 10
-
-        # Start Test
-        pub_connect_id = self.exec_connect(self.publisher, "publisher")
-        sub_connect_id = self.exec_connect(self.subscriber, "subscriber")
-
-        pub_id = self.publisher.droid.wifiAwarePublish(pub_connect_id,
-                                                       self.publish_config)
-        sub_id = self.subscriber.droid.wifiAwareSubscribe(sub_connect_id,
-                                                          self.subscribe_config)
-
-        try:
-            event_sub_match = self.subscriber.ed.pop_event(
-                aware_const.SESSION_CB_ON_SERVICE_DISCOVERED, EVENT_TIMEOUT)
-            self.log.info('%s: %s',
-                          aware_const.SESSION_CB_ON_SERVICE_DISCOVERED,
-                          event_sub_match['data'])
-        except queue.Empty:
-            asserts.fail('Timed out while waiting for %s on Subscriber' %
-                         aware_const.SESSION_CB_ON_SERVICE_DISCOVERED)
-        self.log.debug(event_sub_match)
-
-        self.exec_rtt(device=self.subscriber,
-                      session_id=sub_id,
-                      peer_id=event_sub_match['data']['peerId'],
-                      rtt_param=self.rtt_24_20, label="2.4GHz / 20MHz BW",
-                      repeat_count=rtt_iterations)
-        self.exec_rtt(device=self.subscriber,
-                      session_id=sub_id,
-                      peer_id=event_sub_match['data']['peerId'],
-                      rtt_param=self.rtt_50_40, label="5Hz / 40MHz BW",
-                      repeat_count=rtt_iterations)
-        self.exec_rtt(device=self.subscriber,
-                      session_id=sub_id,
-                      peer_id=event_sub_match['data']['peerId'],
-                      rtt_param=self.rtt_50_80, label="5GHz / 80MHz BW",
-                      repeat_count=rtt_iterations)
-
-    def test_disable_wifi_during_connection(self):
-        """Validate behavior when Wi-Fi is disabled during an active Aware
-        connection. Expected behavior: receive an onAwareDown(1002) event.
-
-        Configuration: 1 device - the DUT.
-
-        Logical steps:
-          * DUT initiate Aware clustering (if not already up)
-          * DUT waits for Aware connection confirmation
-          * DUT starts publishing
-          * Disable Wi-Fi
-          * DUT waits for an onAwareDown(1002) event and confirms that received
-        """
-        # Configure Test
-        self.dut = self.android_devices[0]
-
-        # Start Test
-        connect_id = self.dut.droid.wifiAwareAttach()
-
-        try:
-            event = self.dut.ed.pop_event(aware_const.EVENT_CB_ON_ATTACHED,
-                                          EVENT_TIMEOUT)
-            self.log.info('%s: %s', aware_const.EVENT_CB_ON_ATTACHED,
-                          event['data'])
-        except queue.Empty:
-            asserts.fail('Timed out while waiting for %s on dut' %
-                         aware_const.EVENT_CB_ON_ATTACHED)
-        self.log.debug(event)
-
-        pub_id = self.dut.droid.wifiAwarePublish(connect_id,
-                                                 self.publish_config)
-
-        asserts.assert_true(
-            wutils.wifi_toggle_state(self.dut, False),
-            "Failed disabling Wi-Fi interface on dut")
-
-        try:
-            event = self.dut.ed.pop_event(
-                aware_const.BROADCAST_WIFI_AWARE_NOT_AVAILABLE, EVENT_TIMEOUT)
-            self.log.info(aware_const.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
-        except queue.Empty:
-            asserts.fail('Timed out while waiting for %s on dut' %
-                         aware_const.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
-        self.log.debug(event)
-
-    def test_aware_data_path(self):
-        """Perform Aware configuration, discovery, data-path setup, and data
-        transfer.
-
-        Configuration: 2 devices, one acting as Publisher (P) and the
-        other as Subscriber (S)
-
-        Logical steps:
-          * P & S initiate Aware clustering (if not already up)
-          * P & S wait for Aware connection confirmation
-          * P starts publishing
-          * S starts subscribing
-          * S waits for a match (discovery) notification
-          * S sends a message to P
-          * P waits for message
-          * P creates an Aware network to S as RESPONDER
-          * P sends a message to S
-          * S waits for message
-          * S creates an Aware network to P as INITIATOR (order important!)
-          * Both P & S wait for events confirming network set up
-          * NSD option:
-          *     P registers NSD service
-          *     S discovers NSD service and obtains P's IPv6 address
-          * Direct config option:
-          *     No communication: script uses address read from P
-          * run iperf3 between P (server) and S (client)
-          * unregister network callback on S
-        """
-        # Configure Test
-        self.publisher = self.android_devices[0]
-        self.subscriber = self.android_devices[1]
-        results = {}
-
-        sub2pub_msg = "Get ready!"
-        pub2sub_msg = "Ready!"
-        publisher_passphrase = None
-        subscriber_passphrase = None
-        use_nsd = False
-
-        # Start Test
-        pub_connect_id = self.exec_connect(self.publisher, "publisher")
-        sub_connect_id = self.exec_connect(self.subscriber, "subscriber")
-
-        # Discovery: publish + subscribe + wait for match
-        pub_id = self.publisher.droid.wifiAwarePublish(pub_connect_id,
-                                                       self.publish_config)
-        sub_id = self.subscriber.droid.wifiAwareSubscribe(sub_connect_id,
-                                                          self.subscribe_config)
-
-        def filter_callbacks(event, key, name):
-            return event['data'][key] == name
-
-        try:
-            event_sub_match = self.subscriber.ed.pop_event(
-                aware_const.SESSION_CB_ON_SERVICE_DISCOVERED, EVENT_TIMEOUT)
-            self.log.info('%s: %s',
-                          aware_const.SESSION_CB_ON_SERVICE_DISCOVERED,
-                          event_sub_match['data'])
-        except queue.Empty:
-            asserts.fail('Timed out while waiting for %s on Subscriber' %
-                         aware_const.SESSION_CB_ON_SERVICE_DISCOVERED)
-        self.log.debug(event_sub_match)
-
-        # S sends message to P
-        self.reliable_tx(self.subscriber, sub_id,
-                         event_sub_match['data']['peerId'], sub2pub_msg)
-
-        try:
-            event_pub_rx = self.publisher.ed.pop_event(
-                aware_const.SESSION_CB_ON_MESSAGE_RECEIVED, EVENT_TIMEOUT)
-        except queue.Empty:
-            asserts.fail('Timed out while waiting for %s on publisher' %
-                         aware_const.SESSION_CB_ON_MESSAGE_RECEIVED)
-        self.log.info('%s: %s', aware_const.SESSION_CB_ON_MESSAGE_RECEIVED,
-                      event_pub_rx['data'])
-        asserts.assert_equal(event_pub_rx['data']['messageAsString'],
-                             sub2pub_msg,
-                             "Subscriber -> publisher message corrupted")
-
-        # P requests an Aware network as RESPONDER
-        pub_ns = self.publisher.droid.wifiAwareCreateNetworkSpecifier(pub_id,
-            event_pub_rx['data']['peerId'], publisher_passphrase)
-        self.log.info("Publisher network specifier - '%s'", pub_ns)
-        self.network_req['NetworkSpecifier'] = pub_ns
-        pub_req_key = self.publisher.droid.connectivityRequestWifiAwareNetwork(
-            self.network_req)
-
-        # P sends message to S
-        self.reliable_tx(self.publisher, pub_id, event_pub_rx['data']['peerId'],
-                         pub2sub_msg)
-
-        try:
-            event_sub_rx = self.subscriber.ed.pop_event(
-                aware_const.SESSION_CB_ON_MESSAGE_RECEIVED, EVENT_TIMEOUT)
-        except queue.Empty:
-            asserts.fail('Timed out while waiting for %s on subscriber' %
-                         aware_const.SESSION_CB_ON_MESSAGE_RECEIVED)
-        self.log.info('%s: %s', aware_const.SESSION_CB_ON_MESSAGE_RECEIVED,
-                      event_sub_rx['data'])
-        asserts.assert_equal(event_sub_rx['data']['messageAsString'],
-                             pub2sub_msg,
-                             "Publisher -> subscriber message corrupted")
-
-        # S requests an Aware network as INITIATOR
-        sub_ns = self.subscriber.droid.wifiAwareCreateNetworkSpecifier(sub_id,
-            event_sub_rx['data']['peerId'], subscriber_passphrase)
-        self.log.info("Subscriber network specifier - '%s'", sub_ns)
-        self.network_req['NetworkSpecifier'] = sub_ns
-        sub_req_key = self.subscriber.droid.connectivityRequestWifiAwareNetwork(
-            self.network_req)
-
-        # Wait until both S and P get confirmation that network formed
-        try:
-            event_network = self.subscriber.ed.wait_for_event(
-                con_const.EVENT_NETWORK_CALLBACK,
-                filter_callbacks,
-                EVENT_TIMEOUT,
-                key=con_const.NETWORK_CB_KEY_EVENT,
-                name=con_const.NETWORK_CB_LINK_PROPERTIES_CHANGED)
-            self.log.info('Subscriber %s: %s', con_const.EVENT_NETWORK_CALLBACK,
-                          event_network['data'])
-        except queue.Empty:
-            asserts.fail('Timed out while waiting for %s/%s on Subscriber' %
-                         (con_const.EVENT_NETWORK_CALLBACK,
-                          con_const.NETWORK_CB_LINK_PROPERTIES_CHANGED))
-        self.log.debug(event_network)
-        sub_aware_if = event_network['data']['interfaceName']
-
-        try:
-            event_network = self.publisher.ed.wait_for_event(
-                con_const.EVENT_NETWORK_CALLBACK,
-                filter_callbacks,
-                EVENT_TIMEOUT,
-                key=con_const.NETWORK_CB_KEY_EVENT,
-                name=con_const.NETWORK_CB_LINK_PROPERTIES_CHANGED)
-            self.log.info('Publisher %s: %s', con_const.EVENT_NETWORK_CALLBACK,
-                          event_network['data'])
-        except queue.Empty:
-            asserts.fail('Timed out while waiting for %s/%s on Publisher' %
-                         (con_const.EVENT_NETWORK_CALLBACK,
-                          con_const.NETWORK_CB_LINK_PROPERTIES_CHANGED))
-        self.log.debug(event_network)
-        pub_aware_if = event_network['data']['interfaceName']
-
-        pub_ipv6 = "";
-        if use_nsd:
-            try:
-                # P registers NSD service (i.e. starts publishing)
-                nsd_reg = self.publisher.droid.nsdRegisterService(
-                    self.nsd_service_info)
-                try:
-                    event_nsd = self.publisher.ed.wait_for_event(
-                        nsd_const.REG_LISTENER_EVENT,
-                        filter_callbacks,
-                        EVENT_TIMEOUT,
-                        key=nsd_const.REG_LISTENER_CALLBACK,
-                        name=nsd_const.REG_LISTENER_EVENT_ON_SERVICE_REGISTERED)
-                    self.log.info(
-                        'Publisher %s: %s',
-                        nsd_const.REG_LISTENER_EVENT_ON_SERVICE_REGISTERED,
-                        event_nsd['data'])
-                except queue.Empty:
-                    asserts.fail('Timed out while waiting for %s on Publisher' %
-                             nsd_const.REG_LISTENER_EVENT_ON_SERVICE_REGISTERED)
-
-                # S starts NSD discovery
-                nsd_discovery = self.subscriber.droid.nsdDiscoverServices(
-                    self.nsd_service_info[
-                        nsd_const.NSD_SERVICE_INFO_SERVICE_TYPE])
-                try:
-                    event_nsd = self.subscriber.ed.wait_for_event(
-                        nsd_const.DISCOVERY_LISTENER_EVENT,
-                        filter_callbacks,
-                        EVENT_TIMEOUT,
-                        key=nsd_const.DISCOVERY_LISTENER_DATA_CALLBACK,
-                       name=nsd_const.DISCOVERY_LISTENER_EVENT_ON_SERVICE_FOUND)
-                    self.log.info(
-                        'Subscriber %s: %s',
-                        nsd_const.DISCOVERY_LISTENER_EVENT_ON_SERVICE_FOUND,
-                        event_nsd['data'])
-                except queue.Empty:
-                    asserts.fail(
-                        'Timed out while waiting for %s on Subscriber' %
-                        nsd_const.DISCOVERY_LISTENER_EVENT_ON_SERVICE_FOUND)
-
-                # S resolves IP address of P from NSD service discovery
-                self.subscriber.droid.nsdResolveService(event_nsd['data'])
-                try:
-                    event_nsd = self.subscriber.ed.wait_for_event(
-                        nsd_const.RESOLVE_LISTENER_EVENT,
-                        filter_callbacks,
-                        EVENT_TIMEOUT,
-                        key=nsd_const.RESOLVE_LISTENER_DATA_CALLBACK,
-                      name=nsd_const.RESOLVE_LISTENER_EVENT_ON_SERVICE_RESOLVED)
-                    self.log.info(
-                        'Subscriber %s: %s',
-                        nsd_const.RESOLVE_LISTENER_EVENT_ON_SERVICE_RESOLVED,
-                        event_nsd['data'])
-                except queue.Empty:
-                    asserts.fail(
-                        'Timed out while waiting for %s on Subscriber' %
-                        nsd_const.RESOLVE_LISTENER_EVENT_ON_SERVICE_RESOLVED)
-
-                # mDNS returns first character as '/' - strip
-                # out to get clean IPv6
-                pub_ipv6 = event_nsd['data'][
-                               nsd_const.NSD_SERVICE_INFO_HOST][1:]
-            finally:
-                # Stop NSD
-                if nsd_reg is not None:
-                    self.publisher.droid.nsdUnregisterService(nsd_reg)
-                if nsd_discovery is not None:
-                    self.subscriber.droid.nsdStopServiceDiscovery(nsd_discovery)
-        else:
-            pub_ipv6 = self.publisher.droid.connectivityGetLinkLocalIpv6Address(
-                pub_aware_if)
-            pub_ipv6 = pub_ipv6.split('%')[0] # get rid of <name> - xx:xx%<name>
-            self.log.info('Publisher IPv6: %s', pub_ipv6)
-
-        # P starts iPerf server
-        result, data = self.publisher.run_iperf_server("-D")
-        asserts.assert_true(result, "Can't start iperf3 server")
-
-        # S starts iPerf client
-        result, data = self.subscriber.run_iperf_client(
-            "%s%%%s" % (pub_ipv6, sub_aware_if), "-6 -J")
-        self.log.debug(data)
-        asserts.assert_true(result,
-                            "Failure starting/running iperf3 in client mode")
-        self.log.debug(pprint.pformat(data))
-        data_json = json.loads(''.join(data))
-        results['tx_rate'] = data_json['end']['sum_sent']['bits_per_second']
-        results['rx_rate'] = data_json['end']['sum_received']['bits_per_second']
-        self.log.info('iPerf3: Sent = %d bps Received = %d bps',
-                      results['tx_rate'], results['rx_rate'])
-        asserts.explicit_pass('Aware data-path test passes', extras=results)
diff --git a/acts/tests/google/wifi/aware/README.md b/acts/tests/google/wifi/aware/README.md
new file mode 100644
index 0000000..11a8296
--- /dev/null
+++ b/acts/tests/google/wifi/aware/README.md
@@ -0,0 +1,50 @@
+# Wi-Fi Aware Integrated (ACTS/sl4a) Test Suite
+
+This directory contains ACTS/sl4a test scripts to verify and characterize
+the Wi-Fi Aware implementation in Android.
+
+There are 4 groups of tests (in 4 sub-directories):
+
+* functional: Functional tests that each implementation must pass. These
+are pass/fail tests.
+* performance: Tests which measure performance of an implementation - e.g.
+latency or throughput. Some of the tests may not have pass/fail results -
+they just record the measured performance. Even when tests do have a pass/
+fail criteria - that criteria may not apply to all implementations.
+* stress: Tests which run through a large number of iterations to stress
+test the implementation. Considering that some failures are expected,
+especially in an over-the-air situation, pass/fail criteria are either
+not provided or may not apply to all implementations or test environments.
+* ota (over-the-air): A small number of tests which configure the device
+in a particular mode and expect the tester to capture an over-the-air
+sniffer trace and analyze it for validity. These tests are **not** automated.
+
+The tests can be executed in several ways:
+
+1. Individual test(s): `act.py -c <config> -tc {<test_class>|<test_class>:<test_name>}`
+
+Where a test file is any of the `.py` files in any of the test sub-directories.
+If a test class is specified, then all tests within that test class are executed.
+
+2. All tests in a test group: `act.py -c <config> -tf <test_file>`
+
+Where `<test_file>` is a file containing a list of tests. Each of the test
+group sub-directories contains a file with the same name as that of the
+directory which lists all tests in the directory. E.g. to execute all functional
+tests execute:
+
+`act.py -c <config> -tf ./tools/test/connectivity/acts/tests/google/wifi/aware/functional/functional`
+
+## Test Configurations
+The test configurations, the `<config>` in the commands above, are stored in
+the *config* sub-directory. The configurations simply use all connected
+devices without listing specific serial numbers. Note that some tests use a
+single device while others use 2 devices. In addition, the configurations
+define the following key to configure the test:
+
+* **aware_default_power_mode**: The power mode in which to run all tests. Options
+are `INTERACTIVE` and `NON_INTERACTIVE`.
+
+The following configurations are provided:
+* wifi_aware.json: Normal (high power/interactive) test mode.
+* wifi_aware_non_interactive.json: Low power (non-interactive) test mode.
diff --git a/acts/tests/google/wifi/aware/config/wifi_aware.json b/acts/tests/google/wifi/aware/config/wifi_aware.json
new file mode 100644
index 0000000..97323fc
--- /dev/null
+++ b/acts/tests/google/wifi/aware/config/wifi_aware.json
@@ -0,0 +1,15 @@
+{
+    "_description": "This is a test configuration file for Wi-Fi Aware tests in INTERACTIVE (high-power) mode.",
+    "testbed":
+    [
+        {
+            "_description": "Wi-Fi Aware testbed: auto-detect all attached devices",
+            "name": "WifiAwareAllAttached",
+            "AndroidDevice": "*"
+        }
+    ],
+    "logpath": "~/logs",
+    "testpaths": ["./tools/test/connectivity/acts/tests/google/wifi"],
+    "adb_logcat_param": "-b all",
+    "aware_default_power_mode": "INTERACTIVE"
+}
diff --git a/acts/tests/google/wifi/aware/config/wifi_aware_non_interactive.json b/acts/tests/google/wifi/aware/config/wifi_aware_non_interactive.json
new file mode 100644
index 0000000..f2e6e79
--- /dev/null
+++ b/acts/tests/google/wifi/aware/config/wifi_aware_non_interactive.json
@@ -0,0 +1,15 @@
+{
+    "_description": "This is a test configuration file for Wi-Fi Aware tests in NON-INTERACTIVE (low-power) mode.",
+    "testbed":
+    [
+        {
+            "_description": "Wi-Fi Aware testbed: auto-detect all attached devices",
+            "name": "WifiAwareAllAttached",
+            "AndroidDevice": "*"
+        }
+    ],
+    "logpath": "~/logs",
+    "testpaths": ["./tools/test/connectivity/acts/tests/google/wifi"],
+    "adb_logcat_param": "-b all",
+    "aware_default_power_mode": "NON_INTERACTIVE"
+}
diff --git a/acts/tests/google/wifi/aware/functional/AttachTest.py b/acts/tests/google/wifi/aware/functional/AttachTest.py
new file mode 100644
index 0000000..191a26f
--- /dev/null
+++ b/acts/tests/google/wifi/aware/functional/AttachTest.py
@@ -0,0 +1,127 @@
+#!/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 time
+
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts.utils import force_airplane_mode
+
+
+class AttachTest(AwareBaseTest):
+  def __init__(self, controllers):
+    AwareBaseTest.__init__(self, controllers)
+
+  def test_attach(self):
+    """Functional test case / Attach test cases / attach
+
+    Validates that attaching to the Wi-Fi Aware service works (receive
+    the expected callback).
+    """
+    dut = self.android_devices[0]
+    dut.droid.wifiAwareAttach(False)
+    autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+    autils.fail_on_event(dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+
+  def test_attach_with_identity(self):
+    """Functional test case / Attach test cases / attach with identity callback
+
+    Validates that attaching to the Wi-Fi Aware service works (receive
+    the expected callbacks).
+    """
+    dut = self.android_devices[0]
+    dut.droid.wifiAwareAttach(True)
+    autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+    autils.wait_for_event(dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+
+  def test_attach_multiple_sessions(self):
+    """Functional test case / Attach test cases / multiple attach sessions
+
+    Validates that when creating multiple attach sessions each can be
+    configured independently as to whether or not to receive an identity
+    callback.
+    """
+    dut = self.android_devices[0]
+
+    # Create 3 attach sessions: 2 without identity callback, 1 with
+    id1 = dut.droid.wifiAwareAttach(False, None, True)
+    time.sleep(10) # to make sure all calls and callbacks are done
+    id2 = dut.droid.wifiAwareAttach(True, None, True)
+    time.sleep(10) # to make sure all calls and callbacks are done
+    id3 = dut.droid.wifiAwareAttach(False, None, True)
+    dut.log.info('id1=%d, id2=%d, id3=%d', id1, id2, id3)
+
+    # Attach session 1: wait for attach, should not get identity
+    autils.wait_for_event(dut,
+                          autils.decorate_event(aconsts.EVENT_CB_ON_ATTACHED,
+                                                id1))
+    autils.fail_on_event(dut,
+                         autils.decorate_event(
+                             aconsts.EVENT_CB_ON_IDENTITY_CHANGED, id1))
+
+    # Attach session 2: wait for attach and for identity callback
+    autils.wait_for_event(dut,
+                          autils.decorate_event(aconsts.EVENT_CB_ON_ATTACHED,
+                                                id2))
+    autils.wait_for_event(dut,
+                          autils.decorate_event(
+                              aconsts.EVENT_CB_ON_IDENTITY_CHANGED, id2))
+
+    # Attach session 3: wait for attach, should not get identity
+    autils.wait_for_event(dut,
+                          autils.decorate_event(aconsts.EVENT_CB_ON_ATTACHED,
+                                                id3))
+    autils.fail_on_event(dut,
+                         autils.decorate_event(
+                             aconsts.EVENT_CB_ON_IDENTITY_CHANGED, id3))
+
+  def test_attach_with_no_wifi(self):
+    """Function test case / Attach test cases / attempt to attach with wifi off
+
+    Validates that if trying to attach with Wi-Fi disabled will receive the
+    expected failure callback. As a side-effect also validates that the broadcast
+    for Aware unavailable is received.
+    """
+    dut = self.android_devices[0]
+    wutils.wifi_toggle_state(dut, False)
+    autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
+    dut.droid.wifiAwareAttach()
+    autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACH_FAILED)
+
+  def test_attach_apm_toggle_attach_again(self):
+    """Validates that enabling Airplane mode while Aware is on resets it
+    correctly, and allows it to be re-enabled when Airplane mode is then
+    disabled."""
+    dut = self.android_devices[0]
+
+    # enable Aware (attach)
+    dut.droid.wifiAwareAttach()
+    autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+    # enable airplane mode
+    force_airplane_mode(dut, True)
+    autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
+
+    # wait a few seconds and disable airplane mode
+    time.sleep(10)
+    force_airplane_mode(dut, False)
+    autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+
+    # try enabling Aware again (attach)
+    dut.droid.wifiAwareAttach()
+    autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
diff --git a/acts/tests/google/wifi/aware/functional/CapabilitiesTest.py b/acts/tests/google/wifi/aware/functional/CapabilitiesTest.py
new file mode 100644
index 0000000..55e63e9
--- /dev/null
+++ b/acts/tests/google/wifi/aware/functional/CapabilitiesTest.py
@@ -0,0 +1,252 @@
+#!/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.
+
+from acts import asserts
+from acts import signals
+
+from acts.test_utils.net import connectivity_const as cconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class CapabilitiesTest(AwareBaseTest):
+  """Set of tests for Wi-Fi Aware Capabilities - verifying that the provided
+  capabilities are real (i.e. available)."""
+
+  SERVICE_NAME = "GoogleTestXYZ"
+
+  def __init__(self, controllers):
+    AwareBaseTest.__init__(self, controllers)
+
+  def create_config(self, dtype, service_name):
+    """Create a discovery configuration based on input parameters.
+
+    Args:
+      dtype: Publish or Subscribe discovery type
+      service_name: Service name.
+
+    Returns:
+      Discovery configuration object.
+    """
+    config = {}
+    config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = dtype
+    config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = service_name
+    return config
+
+  def start_discovery_session(self, dut, session_id, is_publish, dtype,
+                              service_name, expect_success):
+    """Start a discovery session
+
+    Args:
+      dut: Device under test
+      session_id: ID of the Aware session in which to start discovery
+      is_publish: True for a publish session, False for subscribe session
+      dtype: Type of the discovery session
+      service_name: Service name to use for the discovery session
+      expect_success: True if expect session to be created, False otherwise
+
+    Returns:
+      Discovery session ID.
+    """
+    config = {}
+    config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = dtype
+    config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = service_name
+
+    if is_publish:
+      disc_id = dut.droid.wifiAwarePublish(session_id, config)
+      event_name = aconsts.SESSION_CB_ON_PUBLISH_STARTED
+    else:
+      disc_id = dut.droid.wifiAwareSubscribe(session_id, config)
+      event_name = aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED
+
+    if expect_success:
+      autils.wait_for_event(dut, event_name)
+    else:
+      autils.wait_for_event(dut, aconsts.SESSION_CB_ON_SESSION_CONFIG_FAILED)
+
+    return disc_id
+
+  ###############################
+
+  def test_max_discovery_sessions(self):
+    """Validate that the device can create as many discovery sessions as are
+    indicated in the device capabilities
+    """
+    dut = self.android_devices[0]
+
+    # attach
+    session_id = dut.droid.wifiAwareAttach(True)
+    autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+    service_name_template = 'GoogleTestService-%s-%d'
+
+    # start the max number of publish sessions
+    for i in range(dut.aware_capabilities[aconsts.CAP_MAX_PUBLISHES]):
+      # create publish discovery session of both types
+      pub_disc_id = self.start_discovery_session(
+          dut, session_id, True, aconsts.PUBLISH_TYPE_UNSOLICITED
+          if i % 2 == 0 else aconsts.PUBLISH_TYPE_SOLICITED,
+          service_name_template % ('pub', i), True)
+
+    # start the max number of subscribe sessions
+    for i in range(dut.aware_capabilities[aconsts.CAP_MAX_SUBSCRIBES]):
+      # create publish discovery session of both types
+      sub_disc_id = self.start_discovery_session(
+          dut, session_id, False, aconsts.SUBSCRIBE_TYPE_PASSIVE
+          if i % 2 == 0 else aconsts.SUBSCRIBE_TYPE_ACTIVE,
+          service_name_template % ('sub', i), True)
+
+    # start another publish & subscribe and expect failure
+    self.start_discovery_session(dut, session_id, True,
+                                 aconsts.PUBLISH_TYPE_UNSOLICITED,
+                                 service_name_template % ('pub', 900), False)
+    self.start_discovery_session(dut, session_id, False,
+                                 aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                                 service_name_template % ('pub', 901), False)
+
+    # delete one of the publishes and try again (see if can create subscribe
+    # instead - should not)
+    dut.droid.wifiAwareDestroyDiscoverySession(pub_disc_id)
+    self.start_discovery_session(dut, session_id, False,
+                                 aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                                 service_name_template % ('pub', 902), False)
+    self.start_discovery_session(dut, session_id, True,
+                                 aconsts.PUBLISH_TYPE_UNSOLICITED,
+                                 service_name_template % ('pub', 903), True)
+
+    # delete one of the subscribes and try again (see if can create publish
+    # instead - should not)
+    dut.droid.wifiAwareDestroyDiscoverySession(sub_disc_id)
+    self.start_discovery_session(dut, session_id, True,
+                                 aconsts.PUBLISH_TYPE_UNSOLICITED,
+                                 service_name_template % ('pub', 904), False)
+    self.start_discovery_session(dut, session_id, False,
+                                 aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                                 service_name_template % ('pub', 905), True)
+
+  def test_max_ndp(self):
+    """Validate that the device can create as many NDPs as are specified
+    by its capabilities.
+
+    Mechanics:
+    - Publisher on DUT (first device)
+    - Subscribers on all other devices
+    - On discovery set up NDP
+
+    Note: the test requires MAX_NDP + 2 devices to be validated. If these are
+    not available it will be skipped (not failed).
+    """
+    dut = self.android_devices[0]
+
+    # get max NDP: using first available device (assumes all devices are the
+    # same)
+    max_ndp = dut.aware_capabilities[aconsts.CAP_MAX_NDP_SESSIONS]
+
+    if len(self.android_devices) < max_ndp + 2:
+      raise signals.TestSkip('Setup does not contain a sufficient number of '
+                             'devices: need %d, have %d' % (max_ndp + 2,
+                             len(self.android_devices)))
+
+    # attach
+    session_id = dut.droid.wifiAwareAttach()
+    autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+    # start publisher
+    p_disc_id = self.start_discovery_session(
+        dut,
+        session_id,
+        is_publish=True,
+        dtype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+        service_name=self.SERVICE_NAME,
+        expect_success=True)
+
+    # loop over other DUTs
+    for i in range(max_ndp + 1):
+      other_dut = self.android_devices[i + 1]
+
+      # attach
+      other_session_id = other_dut.droid.wifiAwareAttach()
+      autils.wait_for_event(other_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+      # start subscriber
+      s_disc_id = self.start_discovery_session(
+          other_dut,
+          other_session_id,
+          is_publish=False,
+          dtype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+          service_name=self.SERVICE_NAME,
+          expect_success=True)
+
+      discovery_event = autils.wait_for_event(
+          other_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+      peer_id_on_sub = discovery_event['data'][aconsts.SESSION_CB_KEY_PEER_ID]
+
+      # Subscriber: send message to peer (Publisher - so it knows our address)
+      other_dut.droid.wifiAwareSendMessage(
+          s_disc_id, peer_id_on_sub,
+          self.get_next_msg_id(), "ping", aconsts.MAX_TX_RETRIES)
+      autils.wait_for_event(other_dut, aconsts.SESSION_CB_ON_MESSAGE_SENT)
+
+      # Publisher: wait for received message
+      pub_rx_msg_event = autils.wait_for_event(
+          dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
+      peer_id_on_pub = pub_rx_msg_event['data'][aconsts.SESSION_CB_KEY_PEER_ID]
+
+      # publisher (responder): request network
+      p_req_key = autils.request_network(
+          dut,
+          dut.droid.wifiAwareCreateNetworkSpecifier(p_disc_id, peer_id_on_pub))
+
+      # subscriber (initiator): request network
+      s_req_key = autils.request_network(
+          other_dut,
+          other_dut.droid.wifiAwareCreateNetworkSpecifier(
+              s_disc_id, peer_id_on_sub))
+
+      # wait for network (or not - on the last iteration)
+      if i != max_ndp:
+        p_net_event = autils.wait_for_event_with_keys(
+            dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+            (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+        s_net_event = autils.wait_for_event_with_keys(
+            other_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+            (cconsts.NETWORK_CB_KEY_ID, s_req_key))
+
+        p_aware_if = p_net_event['data'][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+        s_aware_if = s_net_event['data'][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+        self.log.info('Interface names: p=%s, s=%s', p_aware_if, s_aware_if)
+
+        p_ipv6 = dut.droid.connectivityGetLinkLocalIpv6Address(
+            p_aware_if).split('%')[0]
+        s_ipv6 = other_dut.droid.connectivityGetLinkLocalIpv6Address(
+            s_aware_if).split('%')[0]
+        self.log.info('Interface addresses (IPv6): p=%s, s=%s', p_ipv6, s_ipv6)
+      else:
+        autils.fail_on_event_with_keys(
+            dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+            (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+        autils.fail_on_event_with_keys(
+            other_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+            (cconsts.NETWORK_CB_KEY_EVENT,
+             cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+            (cconsts.NETWORK_CB_KEY_ID, s_req_key))
diff --git a/acts/tests/google/wifi/aware/functional/DataPathTest.py b/acts/tests/google/wifi/aware/functional/DataPathTest.py
new file mode 100644
index 0000000..8414c1d
--- /dev/null
+++ b/acts/tests/google/wifi/aware/functional/DataPathTest.py
@@ -0,0 +1,729 @@
+#!/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 time
+
+from acts.test_utils.net import connectivity_const as cconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class DataPathTest(AwareBaseTest):
+  """Set of tests for Wi-Fi Aware data-path."""
+
+  # configuration parameters used by tests
+  ENCR_TYPE_OPEN = 0
+  ENCR_TYPE_PASSPHRASE = 1
+  ENCR_TYPE_PMK = 2
+
+  PASSPHRASE = "This is some random passphrase - very very secure!!"
+  PASSPHRASE_MIN = "01234567"
+  PASSPHRASE_MAX = "012345678901234567890123456789012345678901234567890123456789012"
+  PMK = "ODU0YjE3YzdmNDJiNWI4NTQ2NDJjNDI3M2VkZTQyZGU="
+  PASSPHRASE2 = "This is some random passphrase - very very secure - but diff!!"
+  PMK2 = "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="
+
+  PING_MSG = "ping"
+
+  # 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
+
+  # number of second to 'reasonably' wait to make sure that devices synchronize
+  # with each other - useful for OOB test cases, where the OOB discovery would
+  # take some time
+  WAIT_FOR_CLUSTER = 5
+
+  def __init__(self, controllers):
+    AwareBaseTest.__init__(self, controllers)
+
+  def create_config(self, dtype):
+    """Create a base configuration based on input parameters.
+
+    Args:
+      dtype: Publish or Subscribe discovery type
+
+    Returns:
+      Discovery configuration object.
+    """
+    config = {}
+    config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = dtype
+    config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = "GoogleTestServiceDataPath"
+    return config
+
+  def request_network(self, dut, ns):
+    """Request a Wi-Fi Aware network.
+
+    Args:
+      dut: Device
+      ns: Network specifier
+    Returns: the request key
+    """
+    network_req = {"TransportType": 5, "NetworkSpecifier": ns}
+    return dut.droid.connectivityRequestWifiAwareNetwork(network_req)
+
+  def set_up_discovery(self, ptype, stype, get_peer_id):
+    """Set up discovery sessions and wait for service discovery.
+
+    Args:
+      ptype: Publish discovery type
+      stype: Subscribe discovery type
+      get_peer_id: Send a message across to get the peer's id
+    """
+    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()
+    autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
+    time.sleep(self.device_startup_offset)
+    s_id = s_dut.droid.wifiAwareAttach()
+    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, self.create_config(ptype))
+    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, self.create_config(stype))
+    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]
+
+    peer_id_on_pub = None
+    if get_peer_id: # only need message to receive peer ID
+      # Subscriber: send message to peer (Publisher - so it knows our address)
+      s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub,
+                                       self.get_next_msg_id(), self.PING_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]
+
+    return (p_dut, s_dut, p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub,
+            peer_id_on_pub)
+
+  def run_ib_data_path_test(self,
+      ptype,
+      stype,
+      encr_type,
+      use_peer_id,
+      passphrase_to_use=None):
+    """Runs the in-band data-path tests.
+
+    Args:
+      ptype: Publish discovery type
+      stype: Subscribe discovery type
+      encr_type: Encryption type, one of ENCR_TYPE_*
+      use_peer_id: On Responder (publisher): True to use peer ID, False to
+                   accept any request
+      passphrase_to_use: The passphrase to use if encr_type=ENCR_TYPE_PASSPHRASE
+                         If None then use self.PASSPHRASE
+    """
+    (p_dut, s_dut, p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub,
+     peer_id_on_pub) = self.set_up_discovery(ptype, stype, use_peer_id)
+
+    passphrase = None
+    pmk = None
+    if encr_type == self.ENCR_TYPE_PASSPHRASE:
+      passphrase = self.PASSPHRASE if passphrase_to_use == None else passphrase_to_use
+    elif encr_type == self.ENCR_TYPE_PMK:
+      pmk = self.PMK
+
+    # Publisher: request network
+    p_req_key = self.request_network(
+        p_dut,
+        p_dut.droid.wifiAwareCreateNetworkSpecifier(p_disc_id, peer_id_on_pub if
+        use_peer_id else None, passphrase, pmk))
+
+    # Subscriber: request network
+    s_req_key = self.request_network(
+        s_dut,
+        s_dut.droid.wifiAwareCreateNetworkSpecifier(s_disc_id, peer_id_on_sub,
+                                                    passphrase, pmk))
+
+    # Publisher & Subscriber: wait for network formation
+    p_net_event = autils.wait_for_event_with_keys(
+        p_dut, cconsts.EVENT_NETWORK_CALLBACK,
+        autils.EVENT_NDP_TIMEOUT,
+        (cconsts.NETWORK_CB_KEY_EVENT,
+         cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+        (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+    s_net_event = autils.wait_for_event_with_keys(
+        s_dut, cconsts.EVENT_NETWORK_CALLBACK,
+        autils.EVENT_NDP_TIMEOUT,
+        (cconsts.NETWORK_CB_KEY_EVENT,
+         cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+        (cconsts.NETWORK_CB_KEY_ID, s_req_key))
+
+    p_aware_if = p_net_event["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+    s_aware_if = s_net_event["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+    self.log.info("Interface names: p=%s, s=%s", p_aware_if, s_aware_if)
+
+    p_ipv6 = p_dut.droid.connectivityGetLinkLocalIpv6Address(p_aware_if).split(
+        "%")[0]
+    s_ipv6 = s_dut.droid.connectivityGetLinkLocalIpv6Address(s_aware_if).split(
+        "%")[0]
+    self.log.info("Interface addresses (IPv6): p=%s, s=%s", p_ipv6, s_ipv6)
+
+    # TODO: possibly send messages back and forth, prefer to use netcat/nc
+
+    # terminate sessions and wait for ON_LOST callbacks
+    p_dut.droid.wifiAwareDestroy(p_id)
+    s_dut.droid.wifiAwareDestroy(s_id)
+
+    autils.wait_for_event_with_keys(
+        p_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+        (cconsts.NETWORK_CB_KEY_EVENT,
+         cconsts.NETWORK_CB_LOST), (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+    autils.wait_for_event_with_keys(
+        s_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+        (cconsts.NETWORK_CB_KEY_EVENT,
+         cconsts.NETWORK_CB_LOST), (cconsts.NETWORK_CB_KEY_ID, s_req_key))
+
+    # clean-up
+    p_dut.droid.connectivityUnregisterNetworkCallback(p_req_key)
+    s_dut.droid.connectivityUnregisterNetworkCallback(s_req_key)
+
+  def run_oob_data_path_test(self, encr_type, use_peer_id):
+    """Runs the out-of-band data-path tests.
+
+    Args:
+      encr_type: Encryption type, one of ENCR_TYPE_*
+      use_peer_id: On Responder: True to use peer ID, False to accept any
+                   request
+    """
+    init_dut = self.android_devices[0]
+    init_dut.pretty_name = "Initiator"
+    resp_dut = self.android_devices[1]
+    resp_dut.pretty_name = "Responder"
+
+    # Initiator+Responder: attach and wait for confirmation & identity
+    init_id = init_dut.droid.wifiAwareAttach(True)
+    autils.wait_for_event(init_dut, aconsts.EVENT_CB_ON_ATTACHED)
+    init_ident_event = autils.wait_for_event(
+        init_dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+    init_mac = init_ident_event["data"]["mac"]
+    time.sleep(self.device_startup_offset)
+    resp_id = resp_dut.droid.wifiAwareAttach(True)
+    autils.wait_for_event(resp_dut, aconsts.EVENT_CB_ON_ATTACHED)
+    resp_ident_event = autils.wait_for_event(
+        resp_dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+    resp_mac = resp_ident_event["data"]["mac"]
+
+    # wait for for devices to synchronize with each other - there are no other
+    # mechanisms to make sure this happens for OOB discovery (except retrying
+    # to execute the data-path request)
+    time.sleep(self.WAIT_FOR_CLUSTER)
+
+    passphrase = None
+    pmk = None
+    if encr_type == self.ENCR_TYPE_PASSPHRASE:
+      passphrase = self.PASSPHRASE
+    elif encr_type == self.ENCR_TYPE_PMK:
+      pmk = self.PMK
+
+    # Responder: request network
+    resp_req_key = self.request_network(
+        resp_dut,
+        resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+            resp_id, aconsts.DATA_PATH_RESPONDER, init_mac
+            if use_peer_id else None, passphrase, pmk))
+
+    # Initiator: request network
+    init_req_key = self.request_network(
+        init_dut,
+        init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+            init_id, aconsts.DATA_PATH_INITIATOR, resp_mac, passphrase, pmk))
+
+    # Initiator & Responder: wait for network formation
+    init_net_event = autils.wait_for_event_with_keys(
+        init_dut, cconsts.EVENT_NETWORK_CALLBACK,
+        autils.EVENT_NDP_TIMEOUT,
+        (cconsts.NETWORK_CB_KEY_EVENT,
+         cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+        (cconsts.NETWORK_CB_KEY_ID, init_req_key))
+    resp_net_event = autils.wait_for_event_with_keys(
+        resp_dut, cconsts.EVENT_NETWORK_CALLBACK,
+        autils.EVENT_NDP_TIMEOUT,
+        (cconsts.NETWORK_CB_KEY_EVENT,
+         cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+        (cconsts.NETWORK_CB_KEY_ID, resp_req_key))
+
+    init_aware_if = init_net_event["data"][
+      cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+    resp_aware_if = resp_net_event["data"][
+      cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+    self.log.info("Interface names: I=%s, R=%s", init_aware_if, resp_aware_if)
+
+    init_ipv6 = init_dut.droid.connectivityGetLinkLocalIpv6Address(
+        init_aware_if).split("%")[0]
+    resp_ipv6 = resp_dut.droid.connectivityGetLinkLocalIpv6Address(
+        resp_aware_if).split("%")[0]
+    self.log.info("Interface addresses (IPv6): I=%s, R=%s", init_ipv6,
+                  resp_ipv6)
+
+    # TODO: possibly send messages back and forth, prefer to use netcat/nc
+
+    # terminate sessions and wait for ON_LOST callbacks
+    init_dut.droid.wifiAwareDestroy(init_id)
+    resp_dut.droid.wifiAwareDestroy(resp_id)
+
+    autils.wait_for_event_with_keys(
+        init_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+        (cconsts.NETWORK_CB_KEY_EVENT,
+         cconsts.NETWORK_CB_LOST), (cconsts.NETWORK_CB_KEY_ID, init_req_key))
+    autils.wait_for_event_with_keys(
+        resp_dut, cconsts.EVENT_NETWORK_CALLBACK, autils.EVENT_NDP_TIMEOUT,
+        (cconsts.NETWORK_CB_KEY_EVENT,
+         cconsts.NETWORK_CB_LOST), (cconsts.NETWORK_CB_KEY_ID, resp_req_key))
+
+    # clean-up
+    resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+    init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
+
+  def run_mismatched_ib_data_path_test(self, pub_mismatch, sub_mismatch):
+    """Runs the negative in-band data-path tests: mismatched peer ID.
+
+    Args:
+      pub_mismatch: Mismatch the publisher's ID
+      sub_mismatch: Mismatch the subscriber's ID
+    """
+    (p_dut, s_dut, p_id, s_id, p_disc_id, s_disc_id,
+     peer_id_on_sub, peer_id_on_pub) = self.set_up_discovery(
+         aconsts.PUBLISH_TYPE_UNSOLICITED, aconsts.SUBSCRIBE_TYPE_PASSIVE, True)
+
+    if pub_mismatch:
+      peer_id_on_pub = peer_id_on_pub -1
+    if sub_mismatch:
+      peer_id_on_sub = peer_id_on_sub - 1
+
+    # Publisher: request network
+    p_req_key = self.request_network(
+        p_dut,
+        p_dut.droid.wifiAwareCreateNetworkSpecifier(p_disc_id, peer_id_on_pub,
+                                                    None))
+
+    # Subscriber: request network
+    s_req_key = self.request_network(
+        s_dut,
+        s_dut.droid.wifiAwareCreateNetworkSpecifier(s_disc_id, peer_id_on_sub,
+                                                    None))
+
+    # Publisher & Subscriber: fail on network formation
+    time.sleep(autils.EVENT_NDP_TIMEOUT)
+    autils.fail_on_event(p_dut, cconsts.EVENT_NETWORK_CALLBACK, timeout=0)
+    autils.fail_on_event(s_dut, cconsts.EVENT_NETWORK_CALLBACK, timeout=0)
+
+    # clean-up
+    p_dut.droid.connectivityUnregisterNetworkCallback(p_req_key)
+    s_dut.droid.connectivityUnregisterNetworkCallback(s_req_key)
+
+  def run_mismatched_oob_data_path_test(self,
+      init_mismatch_mac=False,
+      resp_mismatch_mac=False,
+      init_encr_type=ENCR_TYPE_OPEN,
+      resp_encr_type=ENCR_TYPE_OPEN):
+    """Runs the negative out-of-band data-path tests: mismatched information
+    between Responder and Initiator.
+
+    Args:
+      init_mismatch_mac: True to mismatch the Initiator MAC address
+      resp_mismatch_mac: True to mismatch the Responder MAC address
+      init_encr_type: Encryption type of Initiator - ENCR_TYPE_*
+      resp_encr_type: Encryption type of Responder - ENCR_TYPE_*
+    """
+    init_dut = self.android_devices[0]
+    init_dut.pretty_name = "Initiator"
+    resp_dut = self.android_devices[1]
+    resp_dut.pretty_name = "Responder"
+
+    # Initiator+Responder: attach and wait for confirmation & identity
+    init_id = init_dut.droid.wifiAwareAttach(True)
+    autils.wait_for_event(init_dut, aconsts.EVENT_CB_ON_ATTACHED)
+    init_ident_event = autils.wait_for_event(
+        init_dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+    init_mac = init_ident_event["data"]["mac"]
+    time.sleep(self.device_startup_offset)
+    resp_id = resp_dut.droid.wifiAwareAttach(True)
+    autils.wait_for_event(resp_dut, aconsts.EVENT_CB_ON_ATTACHED)
+    resp_ident_event = autils.wait_for_event(
+        resp_dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+    resp_mac = resp_ident_event["data"]["mac"]
+
+    if init_mismatch_mac: # assumes legit ones don't start with "00"
+      init_mac = "00" + init_mac[2:]
+    if resp_mismatch_mac:
+      resp_mac = "00" + resp_mac[2:]
+
+    # wait for for devices to synchronize with each other - there are no other
+    # mechanisms to make sure this happens for OOB discovery (except retrying
+    # to execute the data-path request)
+    time.sleep(self.WAIT_FOR_CLUSTER)
+
+    # set up separate keys: even if types are the same we want a mismatch
+    init_passphrase = None
+    init_pmk = None
+    if init_encr_type == self.ENCR_TYPE_PASSPHRASE:
+      init_passphrase = self.PASSPHRASE
+    elif init_encr_type == self.ENCR_TYPE_PMK:
+      init_pmk = self.PMK
+
+    resp_passphrase = None
+    resp_pmk = None
+    if resp_encr_type == self.ENCR_TYPE_PASSPHRASE:
+      resp_passphrase = self.PASSPHRASE2
+    elif resp_encr_type == self.ENCR_TYPE_PMK:
+      resp_pmk = self.PMK2
+
+    # Responder: request network
+    resp_req_key = self.request_network(
+        resp_dut,
+        resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+            resp_id, aconsts.DATA_PATH_RESPONDER, init_mac, resp_passphrase,
+            resp_pmk))
+
+    # Initiator: request network
+    init_req_key = self.request_network(
+        init_dut,
+        init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+            init_id, aconsts.DATA_PATH_INITIATOR, resp_mac, init_passphrase,
+            init_pmk))
+
+    # Initiator & Responder: fail on network formation
+    time.sleep(autils.EVENT_NDP_TIMEOUT)
+    autils.fail_on_event(init_dut, cconsts.EVENT_NETWORK_CALLBACK, timeout=0)
+    autils.fail_on_event(resp_dut, cconsts.EVENT_NETWORK_CALLBACK, timeout=0)
+
+    # clean-up
+    resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+    init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
+
+
+  #######################################
+  # Positive In-Band (IB) tests key:
+  #
+  # names is: test_ib_<pub_type>_<sub_type>_<encr_type>_<peer_spec>
+  # where:
+  #
+  # pub_type: Type of publish discovery session: unsolicited or solicited.
+  # sub_type: Type of subscribe discovery session: passive or active.
+  # encr_type: Encription type: open, passphrase
+  # peer_spec: Peer specification method: any or specific
+  #
+  # Note: In-Band means using Wi-Fi Aware for discovery and referring to the
+  # peer using the Aware-provided peer handle (as opposed to a MAC address).
+  #######################################
+
+  def test_ib_unsolicited_passive_open_specific(self):
+    """Data-path: in-band, unsolicited/passive, open encryption, specific peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+    self.run_ib_data_path_test(
+        ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+        stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+        encr_type=self.ENCR_TYPE_OPEN,
+        use_peer_id=True)
+
+  def test_ib_unsolicited_passive_open_any(self):
+    """Data-path: in-band, unsolicited/passive, open encryption, any peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+    self.run_ib_data_path_test(
+        ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+        stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+        encr_type=self.ENCR_TYPE_OPEN,
+        use_peer_id=False)
+
+  def test_ib_unsolicited_passive_passphrase_specific(self):
+    """Data-path: in-band, unsolicited/passive, passphrase, specific peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+    self.run_ib_data_path_test(
+        ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+        stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+        encr_type=self.ENCR_TYPE_PASSPHRASE,
+        use_peer_id=True)
+
+  def test_ib_unsolicited_passive_passphrase_any(self):
+    """Data-path: in-band, unsolicited/passive, passphrase, any peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+    self.run_ib_data_path_test(
+        ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+        stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+        encr_type=self.ENCR_TYPE_PASSPHRASE,
+        use_peer_id=False)
+
+  def test_ib_unsolicited_passive_pmk_specific(self):
+    """Data-path: in-band, unsolicited/passive, PMK, specific peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+    self.run_ib_data_path_test(
+        ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+        stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+        encr_type=self.ENCR_TYPE_PMK,
+        use_peer_id=True)
+
+  def test_ib_unsolicited_passive_pmk_any(self):
+    """Data-path: in-band, unsolicited/passive, PMK, any peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+    self.run_ib_data_path_test(
+        ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+        stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+        encr_type=self.ENCR_TYPE_PMK,
+        use_peer_id=False)
+
+  def test_ib_solicited_active_open_specific(self):
+    """Data-path: in-band, solicited/active, open encryption, specific peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+    self.run_ib_data_path_test(
+        ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+        stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+        encr_type=self.ENCR_TYPE_OPEN,
+        use_peer_id=True)
+
+  def test_ib_solicited_active_open_any(self):
+    """Data-path: in-band, solicited/active, open encryption, any peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+    self.run_ib_data_path_test(
+        ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+        stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+        encr_type=self.ENCR_TYPE_OPEN,
+        use_peer_id=False)
+
+  def test_ib_solicited_active_passphrase_specific(self):
+    """Data-path: in-band, solicited/active, passphrase, specific peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+    self.run_ib_data_path_test(
+        ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+        stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+        encr_type=self.ENCR_TYPE_PASSPHRASE,
+        use_peer_id=True)
+
+  def test_ib_solicited_active_passphrase_any(self):
+    """Data-path: in-band, solicited/active, passphrase, any peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+    self.run_ib_data_path_test(
+        ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+        stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+        encr_type=self.ENCR_TYPE_PASSPHRASE,
+        use_peer_id=False)
+
+  def test_ib_solicited_active_pmk_specific(self):
+    """Data-path: in-band, solicited/active, PMK, specific peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+    self.run_ib_data_path_test(
+        ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+        stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+        encr_type=self.ENCR_TYPE_PMK,
+        use_peer_id=True)
+
+  def test_ib_solicited_active_pmk_any(self):
+    """Data-path: in-band, solicited/active, PMK, any peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+    self.run_ib_data_path_test(
+        ptype=aconsts.PUBLISH_TYPE_SOLICITED,
+        stype=aconsts.SUBSCRIBE_TYPE_ACTIVE,
+        encr_type=self.ENCR_TYPE_PMK,
+        use_peer_id=False)
+
+  #######################################
+  # Positive Out-of-Band (OOB) tests key:
+  #
+  # names is: test_oob_<encr_type>_<peer_spec>
+  # where:
+  #
+  # encr_type: Encription type: open, passphrase
+  # peer_spec: Peer specification method: any or specific
+  #
+  # Note: Out-of-Band means using a non-Wi-Fi Aware mechanism for discovery and
+  # exchange of MAC addresses and then Wi-Fi Aware for data-path.
+  #######################################
+
+  def test_oob_open_specific(self):
+    """Data-path: out-of-band, open encryption, specific peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+    self.run_oob_data_path_test(
+        encr_type=self.ENCR_TYPE_OPEN,
+        use_peer_id=True)
+
+  def test_oob_open_any(self):
+    """Data-path: out-of-band, open encryption, any peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+    self.run_oob_data_path_test(
+        encr_type=self.ENCR_TYPE_OPEN,
+        use_peer_id=False)
+
+  def test_oob_passphrase_specific(self):
+    """Data-path: out-of-band, passphrase, specific peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+    self.run_oob_data_path_test(
+        encr_type=self.ENCR_TYPE_PASSPHRASE,
+        use_peer_id=True)
+
+  def test_oob_passphrase_any(self):
+    """Data-path: out-of-band, passphrase, any peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+    self.run_oob_data_path_test(
+        encr_type=self.ENCR_TYPE_PASSPHRASE,
+        use_peer_id=False)
+
+  def test_oob_pmk_specific(self):
+    """Data-path: out-of-band, PMK, specific peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+    self.run_oob_data_path_test(
+        encr_type=self.ENCR_TYPE_PMK,
+        use_peer_id=True)
+
+  def test_oob_pmk_any(self):
+    """Data-path: out-of-band, PMK, any peer
+
+    Verifies end-to-end discovery + data-path creation.
+    """
+    self.run_oob_data_path_test(
+        encr_type=self.ENCR_TYPE_PMK,
+        use_peer_id=False)
+
+  ##############################################################
+
+  def test_passphrase_min(self):
+    """Data-path: minimum passphrase length
+
+    Use in-band, unsolicited/passive, any peer combination
+    """
+    self.run_ib_data_path_test(ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+                               stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+                               encr_type=self.ENCR_TYPE_PASSPHRASE,
+                               use_peer_id=False,
+                               passphrase_to_use=self.PASSPHRASE_MIN)
+
+  def test_passphrase_max(self):
+    """Data-path: maximum passphrase length
+
+    Use in-band, unsolicited/passive, any peer combination
+    """
+    self.run_ib_data_path_test(ptype=aconsts.PUBLISH_TYPE_UNSOLICITED,
+                               stype=aconsts.SUBSCRIBE_TYPE_PASSIVE,
+                               encr_type=self.ENCR_TYPE_PASSPHRASE,
+                               use_peer_id=False,
+                               passphrase_to_use=self.PASSPHRASE_MAX)
+
+  def test_negative_mismatch_publisher_peer_id(self):
+    """Data-path: failure when publisher peer ID is mismatched"""
+    self.run_mismatched_ib_data_path_test(pub_mismatch=True, sub_mismatch=False)
+
+  def test_negative_mismatch_subscriber_peer_id(self):
+    """Data-path: failure when subscriber peer ID is mismatched"""
+    self.run_mismatched_ib_data_path_test(pub_mismatch=False, sub_mismatch=True)
+
+  def test_negative_mismatch_init_mac(self):
+    """Data-path: failure when Initiator MAC address mismatch"""
+    self.run_mismatched_oob_data_path_test(
+        init_mismatch_mac=True,
+        resp_mismatch_mac=False)
+
+  def test_negative_mismatch_resp_mac(self):
+    """Data-path: failure when Responder MAC address mismatch"""
+    self.run_mismatched_oob_data_path_test(
+        init_mismatch_mac=False,
+        resp_mismatch_mac=True)
+
+  def test_negative_mismatch_passphrase(self):
+    """Data-path: failure when passphrases mismatch"""
+    self.run_mismatched_oob_data_path_test(
+        init_encr_type=self.ENCR_TYPE_PASSPHRASE,
+        resp_encr_type=self.ENCR_TYPE_PASSPHRASE)
+
+  def test_negative_mismatch_pmk(self):
+    """Data-path: failure when PMK mismatch"""
+    self.run_mismatched_oob_data_path_test(
+        init_encr_type=self.ENCR_TYPE_PMK,
+        resp_encr_type=self.ENCR_TYPE_PMK)
+
+  def test_negative_mismatch_open_passphrase(self):
+    """Data-path: failure when initiator is open, and responder passphrase"""
+    self.run_mismatched_oob_data_path_test(
+        init_encr_type=self.ENCR_TYPE_OPEN,
+        resp_encr_type=self.ENCR_TYPE_PASSPHRASE)
+
+  def test_negative_mismatch_open_pmk(self):
+    """Data-path: failure when initiator is open, and responder PMK"""
+    self.run_mismatched_oob_data_path_test(
+        init_encr_type=self.ENCR_TYPE_OPEN,
+        resp_encr_type=self.ENCR_TYPE_PMK)
+
+  def test_negative_mismatch_pmk_passphrase(self):
+    """Data-path: failure when initiator is pmk, and responder passphrase"""
+    self.run_mismatched_oob_data_path_test(
+        init_encr_type=self.ENCR_TYPE_PMK,
+        resp_encr_type=self.ENCR_TYPE_PASSPHRASE)
+
+  def test_negative_mismatch_passphrase_open(self):
+    """Data-path: failure when initiator is passphrase, and responder open"""
+    self.run_mismatched_oob_data_path_test(
+        init_encr_type=self.ENCR_TYPE_PASSPHRASE,
+        resp_encr_type=self.ENCR_TYPE_OPEN)
+
+  def test_negative_mismatch_pmk_open(self):
+    """Data-path: failure when initiator is PMK, and responder open"""
+    self.run_mismatched_oob_data_path_test(
+        init_encr_type=self.ENCR_TYPE_PMK,
+        resp_encr_type=self.ENCR_TYPE_OPEN)
+
+  def test_negative_mismatch_passphrase_pmk(self):
+    """Data-path: failure when initiator is passphrase, and responder pmk"""
+    self.run_mismatched_oob_data_path_test(
+        init_encr_type=self.ENCR_TYPE_PASSPHRASE,
+        resp_encr_type=self.ENCR_TYPE_OPEN)
diff --git a/acts/tests/google/wifi/aware/functional/DiscoveryTest.py b/acts/tests/google/wifi/aware/functional/DiscoveryTest.py
new file mode 100644
index 0000000..a1ace3e
--- /dev/null
+++ b/acts/tests/google/wifi/aware/functional/DiscoveryTest.py
@@ -0,0 +1,811 @@
+#!/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 string
+import time
+
+from acts import asserts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+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
+
+  # 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 __init__(self, controllers):
+    AwareBaseTest.__init__(self, controllers)
+
+  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")
+
+    # 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)
+
+    # sleep for timeout period and then verify all 'fail_on_event' together
+    time.sleep(autils.EVENT_TIMEOUT)
+
+    # verify that there were no other events
+    autils.verify_no_more_events(p_dut, timeout=0)
+    autils.verify_no_more_events(s_dut, timeout=0)
+
+    # 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)
+
+    # verify that there were no other events (including terminations)
+    time.sleep(autils.EVENT_TIMEOUT)
+    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.
+  #######################################
+
+  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)
+
+  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)
+
+  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)
+
+
+  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)
+
+  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)
+
+  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
+  #######################################
+
+  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)
+
+  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)
+
+  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)
+
+  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)
+
+  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)
+
+  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)
+
+  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)
+
+  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.
+  #######################################
+
+  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")
+
+  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.
+  #######################################
+
+  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)
+
+  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.
+  #######################################
+
+  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")
+
+  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")
diff --git a/acts/tests/google/wifi/aware/functional/MacRandomTest.py b/acts/tests/google/wifi/aware/functional/MacRandomTest.py
new file mode 100644
index 0000000..8dabb04
--- /dev/null
+++ b/acts/tests/google/wifi/aware/functional/MacRandomTest.py
@@ -0,0 +1,133 @@
+#!/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 time
+
+from acts import asserts
+from acts.test_utils.net import connectivity_const as cconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class MacRandomTest(AwareBaseTest):
+  """Set of tests for Wi-Fi Aware MAC address randomization of NMI (NAN
+  management interface) and NDI (NAN data interface)."""
+
+  NUM_ITERATIONS = 10
+
+  # number of second to 'reasonably' wait to make sure that devices synchronize
+  # with each other - useful for OOB test cases, where the OOB discovery would
+  # take some time
+  WAIT_FOR_CLUSTER = 5
+
+  def __init__(self, controllers):
+    AwareBaseTest.__init__(self, controllers)
+
+  def request_network(self, dut, ns):
+    """Request a Wi-Fi Aware network.
+
+    Args:
+      dut: Device
+      ns: Network specifier
+    Returns: the request key
+    """
+    network_req = {"TransportType": 5, "NetworkSpecifier": ns}
+    return dut.droid.connectivityRequestWifiAwareNetwork(network_req)
+
+  ##########################################################################
+
+  def test_nmi_ndi_randomization_on_enable(self):
+    """Validate randomization of the NMI (NAN management interface) and all NDIs
+    (NAN data-interface) on each enable/disable cycle"""
+    dut = self.android_devices[0]
+
+    # DUT: attach and wait for confirmation & identity 10 times
+    mac_addresses = {}
+    for i in range(self.NUM_ITERATIONS):
+      id = dut.droid.wifiAwareAttach(True)
+      autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+      ident_event = autils.wait_for_event(dut,
+                                          aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+
+      # process NMI
+      mac = ident_event["data"]["mac"]
+      dut.log.info("NMI=%s", mac)
+      if mac in mac_addresses:
+        mac_addresses[mac] = mac_addresses[mac] + 1
+      else:
+        mac_addresses[mac] = 1
+
+      # process NDIs
+      time.sleep(5) # wait for NDI creation to complete
+      for j in range(dut.aware_capabilities[aconsts.CAP_MAX_NDI_INTERFACES]):
+        ndi_interface = "%s%d" % (aconsts.AWARE_NDI_PREFIX, j)
+        ndi_mac = autils.get_mac_addr(dut, ndi_interface)
+        dut.log.info("NDI %s=%s", ndi_interface, ndi_mac)
+        if ndi_mac in mac_addresses:
+          mac_addresses[ndi_mac] = mac_addresses[ndi_mac] + 1
+        else:
+          mac_addresses[ndi_mac] = 1
+
+      dut.droid.wifiAwareDestroy(id)
+
+    # Test for uniqueness
+    for mac in mac_addresses.keys():
+      if mac_addresses[mac] != 1:
+        asserts.fail("MAC address %s repeated %d times (all=%s)" % (mac,
+                     mac_addresses[mac], mac_addresses))
+
+    # Verify that infra interface (e.g. wlan0) MAC address is not used for NMI
+    infra_mac = autils.get_wifi_mac_address(dut)
+    asserts.assert_false(
+        infra_mac in mac_addresses,
+        "Infrastructure MAC address (%s) is used for Aware NMI (all=%s)" %
+        (infra_mac, mac_addresses))
+
+  def test_nmi_randomization_on_interval(self):
+    """Validate randomization of the NMI (NAN management interface) on a set
+    interval. Default value is 30 minutes - change to a small value to allow
+    testing in real-time"""
+    RANDOM_INTERVAL = 120 # minimal value in current implementation
+
+    dut = self.android_devices[0]
+
+    # set randomization interval to 5 seconds
+    dut.adb.shell("cmd wifiaware native_api set mac_random_interval_sec %d" %
+                  RANDOM_INTERVAL)
+
+    # attach and wait for first identity
+    id = dut.droid.wifiAwareAttach(True)
+    autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+    ident_event = autils.wait_for_event(dut,
+                                        aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+    mac1 = ident_event["data"]["mac"]
+
+    # wait for second identity callback
+    # Note: exact randomization interval is not critical, just approximate,
+    # hence giving a few more seconds.
+    ident_event = autils.wait_for_event(dut,
+                                        aconsts.EVENT_CB_ON_IDENTITY_CHANGED,
+                                        timeout=RANDOM_INTERVAL + 5)
+    mac2 = ident_event["data"]["mac"]
+
+    # validate MAC address is randomized
+    asserts.assert_false(
+        mac1 == mac2,
+        "Randomized MAC addresses (%s, %s) should be different" % (mac1, mac2))
+
+    # clean-up
+    dut.droid.wifiAwareDestroy(id)
diff --git a/acts/tests/google/wifi/aware/functional/MatchFilterTest.py b/acts/tests/google/wifi/aware/functional/MatchFilterTest.py
new file mode 100644
index 0000000..e01bfff
--- /dev/null
+++ b/acts/tests/google/wifi/aware/functional/MatchFilterTest.py
@@ -0,0 +1,191 @@
+#!/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 base64
+import time
+import queue
+
+from acts import asserts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class MatchFilterTest(AwareBaseTest):
+  """Set of tests for Wi-Fi Aware Discovery Match Filter behavior. These all
+  use examples from Appendix H of the Wi-Fi Aware standard."""
+
+  SERVICE_NAME = "GoogleTestServiceMFMFMF"
+
+  MF_NNNNN = bytes([0x0, 0x0, 0x0, 0x0, 0x0])
+  MF_12345 = bytes([0x1, 0x1, 0x1, 0x2, 0x1, 0x3, 0x1, 0x4, 0x1, 0x5])
+  MF_12145 = bytes([0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x4, 0x1, 0x5])
+  MF_1N3N5 = bytes([0x1, 0x1, 0x0, 0x1, 0x3, 0x0, 0x1, 0x5])
+  MF_N23N5 = bytes([0x0, 0x1, 0x2, 0x1, 0x3, 0x0, 0x1, 0x5])
+  MF_N2N4 = bytes([0x0, 0x1, 0x2, 0x0, 0x1, 0x4])
+  MF_1N3N = bytes([0x1, 0x1, 0x0, 0x1, 0x3, 0x0])
+
+  # Set of sample match filters from the spec. There is a set of matched
+  # filters:
+  # - Filter 1
+  # - Filter 2
+  # - Expected to match if the Subscriber uses Filter 1 as Tx and the Publisher
+  #   uses Filter 2 as Rx (implies Solicited/Active)
+  # - (the reverse) Expected to match if the Publisher uses Filter 1 as Tx and
+  #   the Subscriber uses Filter 2 as Rx (implies Unsolicited/Passive)
+  match_filters = [
+    [None, None, True, True],
+    [None, MF_NNNNN, True, True],
+    [MF_NNNNN, None, True, True],
+    [None, MF_12345, True, False],
+    [MF_12345, None, False, True],
+    [MF_NNNNN, MF_12345, True, True],
+    [MF_12345, MF_NNNNN, True, True],
+    [MF_12345, MF_12345, True, True],
+    [MF_12345, MF_12145, False, False],
+    [MF_1N3N5, MF_12345, True,True],
+    [MF_12345, MF_N23N5, True, True],
+    [MF_N2N4, MF_12345, True, False],
+    [MF_12345, MF_1N3N, False, True]
+  ]
+
+  def __init__(self, controllers):
+    AwareBaseTest.__init__(self, controllers)
+
+  def run_discovery(self, p_dut, s_dut, p_mf, s_mf, do_unsolicited_passive,
+      expect_discovery):
+    """Creates a discovery session (publish and subscribe) with the specified
+    configuration.
+
+    Args:
+      p_dut: Device to use as publisher.
+      s_dut: Device to use as subscriber.
+      p_mf: Publish's match filter.
+      s_mf: Subscriber's match filter.
+      do_unsolicited_passive: True to use an Unsolicited/Passive discovery,
+                              False for a Solicited/Active discovery session.
+      expect_discovery: True if service should be discovered, False otherwise.
+    Returns: True on success, False on failure (based on expect_discovery arg)
+    """
+    # Encode the match filters
+    p_mf = base64.b64encode(p_mf).decode("utf-8") if p_mf is not None else None
+    s_mf = base64.b64encode(s_mf).decode("utf-8") if s_mf is not None else None
+
+    # Publisher+Subscriber: attach and wait for confirmation
+    p_id = p_dut.droid.wifiAwareAttach()
+    autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
+    time.sleep(self.device_startup_offset)
+    s_id = s_dut.droid.wifiAwareAttach()
+    autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+    # Publisher: start publish and wait for confirmation
+    p_dut.droid.wifiAwarePublish(p_id,
+                                 autils.create_discovery_config(
+                                     self.SERVICE_NAME,
+                                     d_type=aconsts.PUBLISH_TYPE_UNSOLICITED
+                                     if do_unsolicited_passive else
+                                     aconsts.PUBLISH_TYPE_SOLICITED,
+                                     match_filter=p_mf))
+    autils.wait_for_event(p_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+
+    # Subscriber: start subscribe and wait for confirmation
+    s_dut.droid.wifiAwareSubscribe(s_id,
+                                   autils.create_discovery_config(
+                                       self.SERVICE_NAME,
+                                       d_type=aconsts.SUBSCRIBE_TYPE_PASSIVE
+                                       if do_unsolicited_passive else
+                                       aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                                       match_filter=s_mf))
+    autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
+
+    # Subscriber: wait or fail on service discovery
+    event = None
+    try:
+      event = s_dut.ed.pop_event(aconsts.SESSION_CB_ON_SERVICE_DISCOVERED,
+                                 autils.EVENT_TIMEOUT)
+      s_dut.log.info("[Subscriber] SESSION_CB_ON_SERVICE_DISCOVERED: %s", event)
+    except queue.Empty:
+      s_dut.log.info("[Subscriber] No SESSION_CB_ON_SERVICE_DISCOVERED")
+
+    # clean-up
+    p_dut.droid.wifiAwareDestroy(p_id)
+    s_dut.droid.wifiAwareDestroy(s_id)
+
+    if expect_discovery:
+      return event is not None
+    else:
+      return event is None
+
+  def run_match_filters_per_spec(self, do_unsolicited_passive):
+    """Validate all the match filter combinations in the Wi-Fi Aware spec,
+    Appendix H.
+
+    Args:
+      do_unsolicited_passive: True to run the Unsolicited/Passive tests, False
+                              to run the Solicited/Active tests.
+    """
+    p_dut = self.android_devices[0]
+    p_dut.pretty_name = "Publisher"
+    s_dut = self.android_devices[1]
+    s_dut.pretty_name = "Subscriber"
+
+    fails = []
+    for i in range(len(self.match_filters)):
+      test_info = self.match_filters[i]
+      if do_unsolicited_passive:
+        pub_type = "Unsolicited"
+        sub_type = "Passive"
+        pub_mf = test_info[0]
+        sub_mf = test_info[1]
+        expect_discovery = test_info[3]
+      else:
+        pub_type = "Solicited"
+        sub_type = "Active"
+        pub_mf = test_info[1]
+        sub_mf = test_info[0]
+        expect_discovery = test_info[2]
+
+      self.log.info("Test #%d: %s Pub MF=%s, %s Sub MF=%s: Discovery %s", i,
+                    pub_type, pub_mf, sub_type, sub_mf, "EXPECTED"
+                    if test_info[2] else "UNEXPECTED")
+      result = self.run_discovery(
+          p_dut,
+          s_dut,
+          p_mf=pub_mf,
+          s_mf=sub_mf,
+          do_unsolicited_passive=do_unsolicited_passive,
+          expect_discovery=expect_discovery)
+      self.log.info("Test #%d %s Pub/%s Sub %s", i, pub_type, sub_type, "PASS"
+                    if result else "FAIL")
+      if not result:
+        fails.append(i)
+
+    asserts.assert_true(
+        len(fails) == 0, "Some match filter tests are failing", extras=fails)
+
+  ###############################################################
+
+  def test_match_filters_per_spec_unsolicited_passive(self):
+    """Validate all the match filter combinations in the Wi-Fi Aware spec,
+    Appendix H for Unsolicited Publish (tx filter) Passive Subscribe (rx
+    filter)"""
+    self.run_match_filters_per_spec(do_unsolicited_passive=True)
+
+  def test_match_filters_per_spec_solicited_active(self):
+    """Validate all the match filter combinations in the Wi-Fi Aware spec,
+    Appendix H for Solicited Publish (rx filter) Active Subscribe (tx
+    filter)"""
+    self.run_match_filters_per_spec(do_unsolicited_passive=False)
diff --git a/acts/tests/google/wifi/aware/functional/MessageTest.py b/acts/tests/google/wifi/aware/functional/MessageTest.py
new file mode 100644
index 0000000..1abca68
--- /dev/null
+++ b/acts/tests/google/wifi/aware/functional/MessageTest.py
@@ -0,0 +1,437 @@
+#!/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 string
+import time
+
+from acts import asserts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class MessageTest(AwareBaseTest):
+  """Set of tests for Wi-Fi Aware L2 (layer 2) message exchanges."""
+
+  # configuration parameters used by tests
+  PAYLOAD_SIZE_MIN = 0
+  PAYLOAD_SIZE_TYPICAL = 1
+  PAYLOAD_SIZE_MAX = 2
+
+  NUM_MSGS_NO_QUEUE = 10
+  NUM_MSGS_QUEUE_DEPTH_MULT = 2  # number of messages = mult * queue depth
+
+  def __init__(self, controllers):
+    AwareBaseTest.__init__(self, controllers)
+
+  def create_msg(self, caps, payload_size, id):
+    """Creates a message string of the specified size containing the input id.
+
+    Args:
+      caps: Device capabilities.
+      payload_size: The size of the message to create - min (null or empty
+                    message), typical, max (based on device capabilities). Use
+                    the PAYLOAD_SIZE_xx constants.
+      id: Information to include in the generated message (or None).
+
+    Returns: A string of the requested size, optionally containing the id.
+    """
+    if payload_size == self.PAYLOAD_SIZE_MIN:
+      # arbitrarily return a None or an empty string (equivalent messages)
+      return None if id % 2 == 0 else ""
+    elif payload_size == self.PAYLOAD_SIZE_TYPICAL:
+      return "*** ID=%d ***" % id + string.ascii_uppercase
+    else:  # PAYLOAD_SIZE_MAX
+      return "*** ID=%4d ***" % id + "M" * (
+          caps[aconsts.CAP_MAX_SERVICE_SPECIFIC_INFO_LEN] - 15)
+
+  def create_config(self, is_publish, extra_diff=None):
+    """Create a base configuration based on input parameters.
+
+    Args:
+      is_publish: True for publish, False for subscribe sessions.
+      extra_diff: String to add to service name: allows differentiating
+                  discovery sessions.
+
+    Returns:
+      publish discovery configuration object.
+    """
+    config = {}
+    if is_publish:
+      config[aconsts.
+             DISCOVERY_KEY_DISCOVERY_TYPE] = aconsts.PUBLISH_TYPE_UNSOLICITED
+    else:
+      config[
+          aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = aconsts.SUBSCRIBE_TYPE_PASSIVE
+    config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = "GoogleTestServiceX" + (
+        extra_diff if extra_diff is not None else "")
+    return config
+
+  def prep_message_exchange(self, extra_diff=None):
+    """Creates a discovery session (publish and subscribe), and waits for
+    service discovery - at that point the sessions are ready for message
+    exchange.
+
+    Args:
+      extra_diff: String to add to service name: allows differentiating
+                  discovery sessions.
+    """
+    p_dut = self.android_devices[0]
+    p_dut.pretty_name = "Publisher"
+    s_dut = self.android_devices[1]
+    s_dut.pretty_name = "Subscriber"
+
+    # if differentiating (multiple) sessions then should decorate events with id
+    use_id = extra_diff is not None
+
+    # Publisher+Subscriber: attach and wait for confirmation
+    p_id = p_dut.droid.wifiAwareAttach(False, None, use_id)
+    autils.wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED
+                          if not use_id else 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, aconsts.EVENT_CB_ON_ATTACHED
+                          if not use_id else autils.decorate_event(
+                              aconsts.EVENT_CB_ON_ATTACHED, s_id))
+
+    # Publisher: start publish and wait for confirmation
+    p_disc_id = p_dut.droid.wifiAwarePublish(p_id,
+                                             self.create_config(
+                                                 True, extra_diff=extra_diff),
+                                             use_id)
+    autils.wait_for_event(p_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED
+                          if not use_id else autils.decorate_event(
+                              aconsts.SESSION_CB_ON_PUBLISH_STARTED, p_disc_id))
+
+    # Subscriber: start subscribe and wait for confirmation
+    s_disc_id = s_dut.droid.wifiAwareSubscribe(
+        s_id, self.create_config(False, extra_diff=extra_diff), use_id)
+    autils.wait_for_event(s_dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED
+                          if not use_id else autils.decorate_event(
+                              aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED,
+                              s_disc_id))
+
+    # Subscriber: wait for service discovery
+    discovery_event = autils.wait_for_event(
+        s_dut, aconsts.SESSION_CB_ON_SERVICE_DISCOVERED
+        if not use_id else autils.decorate_event(
+            aconsts.SESSION_CB_ON_SERVICE_DISCOVERED, s_disc_id))
+    peer_id_on_sub = discovery_event["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+
+    return {
+        "p_dut": p_dut,
+        "s_dut": s_dut,
+        "p_id": p_id,
+        "s_id": s_id,
+        "p_disc_id": p_disc_id,
+        "s_disc_id": s_disc_id,
+        "peer_id_on_sub": peer_id_on_sub
+    }
+
+  def run_message_no_queue(self, payload_size):
+    """Validate L2 message exchange between publisher & subscriber with no
+    queueing - i.e. wait for an ACK on each message before sending the next
+    message.
+
+    Args:
+      payload_size: min, typical, or max (PAYLOAD_SIZE_xx).
+    """
+    discovery_info = self.prep_message_exchange()
+    p_dut = discovery_info["p_dut"]
+    s_dut = discovery_info["s_dut"]
+    p_disc_id = discovery_info["p_disc_id"]
+    s_disc_id = discovery_info["s_disc_id"]
+    peer_id_on_sub = discovery_info["peer_id_on_sub"]
+
+    for i in range(self.NUM_MSGS_NO_QUEUE):
+      msg = self.create_msg(s_dut.aware_capabilities, payload_size, i)
+      msg_id = self.get_next_msg_id()
+      s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub, msg_id, msg,
+                                       0)
+      tx_event = autils.wait_for_event(s_dut,
+                                       aconsts.SESSION_CB_ON_MESSAGE_SENT)
+      rx_event = autils.wait_for_event(p_dut,
+                                       aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
+      asserts.assert_equal(msg_id,
+                           tx_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_ID],
+                           "Subscriber -> Publisher message ID corrupted")
+      autils.assert_equal_strings(
+          msg, rx_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING],
+          "Subscriber -> Publisher message %d corrupted" % i)
+
+    peer_id_on_pub = rx_event["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+    for i in range(self.NUM_MSGS_NO_QUEUE):
+      msg = self.create_msg(s_dut.aware_capabilities, payload_size, 1000 + i)
+      msg_id = self.get_next_msg_id()
+      p_dut.droid.wifiAwareSendMessage(p_disc_id, peer_id_on_pub, msg_id, msg,
+                                       0)
+      tx_event = autils.wait_for_event(p_dut,
+                                       aconsts.SESSION_CB_ON_MESSAGE_SENT)
+      rx_event = autils.wait_for_event(s_dut,
+                                       aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
+      asserts.assert_equal(msg_id,
+                           tx_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_ID],
+                           "Publisher -> Subscriber message ID corrupted")
+      autils.assert_equal_strings(
+          msg, rx_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING],
+          "Publisher -> Subscriber message %d corrupted" % i)
+
+    # verify there are no more events
+    time.sleep(autils.EVENT_TIMEOUT)
+    autils.verify_no_more_events(p_dut, timeout=0)
+    autils.verify_no_more_events(s_dut, timeout=0)
+
+  def wait_for_messages(self, tx_msgs, tx_msg_ids, tx_disc_id, rx_disc_id,
+                        tx_dut, rx_dut, are_msgs_empty=False):
+    """Validate that all expected messages are transmitted correctly and
+    received as expected. Method is called after the messages are sent into
+    the transmission queue.
+
+    Note: that message can be transmitted and received out-of-order (which is
+    acceptable and the method handles that correctly).
+
+    Args:
+      tx_msgs: dictionary of transmitted messages
+      tx_msg_ids: dictionary of transmitted message ids
+      tx_disc_id: transmitter discovery session id (None for no decoration)
+      rx_disc_id: receiver discovery session id (None for no decoration)
+      tx_dut: transmitter device
+      rx_dut: receiver device
+      are_msgs_empty: True if the messages are None or empty (changes dup detection)
+
+    Returns: the peer ID from any of the received messages
+    """
+    # peer id on receiver
+    peer_id_on_rx = None
+
+    # wait for all messages to be transmitted
+    still_to_be_tx = len(tx_msg_ids)
+    while still_to_be_tx != 0:
+      tx_event = autils.wait_for_event(
+          tx_dut, aconsts.SESSION_CB_ON_MESSAGE_SENT
+          if tx_disc_id is None else autils.decorate_event(
+              aconsts.SESSION_CB_ON_MESSAGE_SENT, tx_disc_id))
+      tx_msg_id = tx_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_ID]
+      tx_msg_ids[tx_msg_id] = tx_msg_ids[tx_msg_id] + 1
+      if tx_msg_ids[tx_msg_id] == 1:
+        still_to_be_tx = still_to_be_tx - 1
+
+    # check for any duplicate transmit notifications
+    asserts.assert_equal(
+        len(tx_msg_ids),
+        sum(tx_msg_ids.values()),
+        "Duplicate transmit message IDs: %s" % tx_msg_ids)
+
+    # wait for all messages to be received
+    still_to_be_rx = len(tx_msg_ids)
+    while still_to_be_rx != 0:
+      rx_event = autils.wait_for_event(
+          rx_dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED
+          if rx_disc_id is None else autils.decorate_event(
+              aconsts.SESSION_CB_ON_MESSAGE_RECEIVED, rx_disc_id))
+      peer_id_on_rx = rx_event["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+      if are_msgs_empty:
+        still_to_be_rx = still_to_be_rx - 1
+      else:
+        rx_msg = rx_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING]
+        asserts.assert_true(
+            rx_msg in tx_msgs,
+            "Received a message we did not send!? -- '%s'" % rx_msg)
+        tx_msgs[rx_msg] = tx_msgs[rx_msg] + 1
+        if tx_msgs[rx_msg] == 1:
+          still_to_be_rx = still_to_be_rx - 1
+
+    # check for any duplicate received messages
+    if not are_msgs_empty:
+      asserts.assert_equal(
+          len(tx_msgs),
+          sum(tx_msgs.values()), "Duplicate transmit messages: %s" % tx_msgs)
+
+    return peer_id_on_rx
+
+  def run_message_with_queue(self, payload_size):
+    """Validate L2 message exchange between publisher & subscriber with
+    queueing - i.e. transmit all messages and then wait for ACKs.
+
+    Args:
+      payload_size: min, typical, or max (PAYLOAD_SIZE_xx).
+    """
+    discovery_info = self.prep_message_exchange()
+    p_dut = discovery_info["p_dut"]
+    s_dut = discovery_info["s_dut"]
+    p_disc_id = discovery_info["p_disc_id"]
+    s_disc_id = discovery_info["s_disc_id"]
+    peer_id_on_sub = discovery_info["peer_id_on_sub"]
+
+    msgs = {}
+    msg_ids = {}
+    for i in range(
+        self.NUM_MSGS_QUEUE_DEPTH_MULT *
+        s_dut.aware_capabilities[aconsts.CAP_MAX_QUEUED_TRANSMIT_MESSAGES]):
+      msg = self.create_msg(s_dut.aware_capabilities, payload_size, i)
+      msg_id = self.get_next_msg_id()
+      msgs[msg] = 0
+      msg_ids[msg_id] = 0
+      s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub, msg_id, msg,
+                                       0)
+    peer_id_on_pub = self.wait_for_messages(
+        msgs, msg_ids, None, None, s_dut, p_dut,
+        payload_size == self.PAYLOAD_SIZE_MIN)
+
+    msgs = {}
+    msg_ids = {}
+    for i in range(
+            self.NUM_MSGS_QUEUE_DEPTH_MULT *
+            p_dut.aware_capabilities[aconsts.CAP_MAX_QUEUED_TRANSMIT_MESSAGES]):
+      msg = self.create_msg(p_dut.aware_capabilities, payload_size, 1000 + i)
+      msg_id = self.get_next_msg_id()
+      msgs[msg] = 0
+      msg_ids[msg_id] = 0
+      p_dut.droid.wifiAwareSendMessage(p_disc_id, peer_id_on_pub, msg_id, msg,
+                                       0)
+    self.wait_for_messages(msgs, msg_ids, None, None, p_dut, s_dut,
+                           payload_size == self.PAYLOAD_SIZE_MIN)
+
+    # verify there are no more events
+    time.sleep(autils.EVENT_TIMEOUT)
+    autils.verify_no_more_events(p_dut, timeout=0)
+    autils.verify_no_more_events(s_dut, timeout=0)
+
+  def run_message_multi_session_with_queue(self, payload_size):
+    """Validate L2 message exchange between publishers & subscribers with
+    queueing - i.e. transmit all messages and then wait for ACKs. Uses 2
+    discovery sessions running concurrently and validates that messages
+    arrive at the correct destination.
+
+    Args:
+      payload_size: min, typical, or max (PAYLOAD_SIZE_xx)
+    """
+    discovery_info1 = self.prep_message_exchange(extra_diff="-111")
+    p_dut = discovery_info1["p_dut"] # same for both sessions
+    s_dut = discovery_info1["s_dut"] # same for both sessions
+    p_disc_id1 = discovery_info1["p_disc_id"]
+    s_disc_id1 = discovery_info1["s_disc_id"]
+    peer_id_on_sub1 = discovery_info1["peer_id_on_sub"]
+
+    discovery_info2 = self.prep_message_exchange(extra_diff="-222")
+    p_disc_id2 = discovery_info2["p_disc_id"]
+    s_disc_id2 = discovery_info2["s_disc_id"]
+    peer_id_on_sub2 = discovery_info2["peer_id_on_sub"]
+
+    msgs1 = {}
+    msg_ids1 = {}
+    msgs2 = {}
+    msg_ids2 = {}
+    for i in range(
+            self.NUM_MSGS_QUEUE_DEPTH_MULT *
+            s_dut.aware_capabilities[aconsts.CAP_MAX_QUEUED_TRANSMIT_MESSAGES]):
+      msg1 = self.create_msg(s_dut.aware_capabilities, payload_size, i)
+      msg_id1 = self.get_next_msg_id()
+      msgs1[msg1] = 0
+      msg_ids1[msg_id1] = 0
+      s_dut.droid.wifiAwareSendMessage(s_disc_id1, peer_id_on_sub1, msg_id1,
+                                       msg1, 0)
+      msg2 = self.create_msg(s_dut.aware_capabilities, payload_size, 100 + i)
+      msg_id2 = self.get_next_msg_id()
+      msgs2[msg2] = 0
+      msg_ids2[msg_id2] = 0
+      s_dut.droid.wifiAwareSendMessage(s_disc_id2, peer_id_on_sub2, msg_id2,
+                                       msg2, 0)
+
+    peer_id_on_pub1 = self.wait_for_messages(
+        msgs1, msg_ids1, s_disc_id1, p_disc_id1, s_dut, p_dut,
+        payload_size == self.PAYLOAD_SIZE_MIN)
+    peer_id_on_pub2 = self.wait_for_messages(
+        msgs2, msg_ids2, s_disc_id2, p_disc_id2, s_dut, p_dut,
+        payload_size == self.PAYLOAD_SIZE_MIN)
+
+    msgs1 = {}
+    msg_ids1 = {}
+    msgs2 = {}
+    msg_ids2 = {}
+    for i in range(
+            self.NUM_MSGS_QUEUE_DEPTH_MULT *
+            p_dut.aware_capabilities[aconsts.CAP_MAX_QUEUED_TRANSMIT_MESSAGES]):
+      msg1 = self.create_msg(p_dut.aware_capabilities, payload_size, 1000 + i)
+      msg_id1 = self.get_next_msg_id()
+      msgs1[msg1] = 0
+      msg_ids1[msg_id1] = 0
+      p_dut.droid.wifiAwareSendMessage(p_disc_id1, peer_id_on_pub1, msg_id1,
+                                       msg1, 0)
+      msg2 = self.create_msg(p_dut.aware_capabilities, payload_size, 1100 + i)
+      msg_id2 = self.get_next_msg_id()
+      msgs2[msg2] = 0
+      msg_ids2[msg_id2] = 0
+      p_dut.droid.wifiAwareSendMessage(p_disc_id2, peer_id_on_pub2, msg_id2,
+                                       msg2, 0)
+
+    self.wait_for_messages(msgs1, msg_ids1, p_disc_id1, s_disc_id1, p_dut,
+                           s_dut, payload_size == self.PAYLOAD_SIZE_MIN)
+    self.wait_for_messages(msgs2, msg_ids2, p_disc_id2, s_disc_id2, p_dut,
+                           s_dut, payload_size == self.PAYLOAD_SIZE_MIN)
+
+    # verify there are no more events
+    time.sleep(autils.EVENT_TIMEOUT)
+    autils.verify_no_more_events(p_dut, timeout=0)
+    autils.verify_no_more_events(s_dut, timeout=0)
+
+  ############################################################################
+
+  def test_message_no_queue_min(self):
+    """Functional / Message / No queue
+    - Minimal payload size (None or "")
+    """
+    self.run_message_no_queue(self.PAYLOAD_SIZE_MIN)
+
+  def test_message_no_queue_typical(self):
+    """Functional / Message / No queue
+    - Typical payload size
+    """
+    self.run_message_no_queue(self.PAYLOAD_SIZE_TYPICAL)
+
+  def test_message_no_queue_max(self):
+    """Functional / Message / No queue
+    - Max payload size (based on device capabilities)
+    """
+    self.run_message_no_queue(self.PAYLOAD_SIZE_MAX)
+
+  def test_message_with_queue_min(self):
+    """Functional / Message / With queue
+    - Minimal payload size (none or "")
+    """
+    self.run_message_with_queue(self.PAYLOAD_SIZE_MIN)
+
+  def test_message_with_queue_typical(self):
+    """Functional / Message / With queue
+    - Typical payload size
+    """
+    self.run_message_with_queue(self.PAYLOAD_SIZE_TYPICAL)
+
+  def test_message_with_queue_max(self):
+    """Functional / Message / With queue
+    - Max payload size (based on device capabilities)
+    """
+    self.run_message_with_queue(self.PAYLOAD_SIZE_MAX)
+
+  def test_message_with_multiple_discovery_sessions_typical(self):
+    """Functional / Message / Multiple sessions
+
+     Sets up 2 discovery sessions on 2 devices. Sends a message in each
+     direction on each discovery session and verifies that reaches expected
+     destination.
+    """
+    self.run_message_multi_session_with_queue(self.PAYLOAD_SIZE_TYPICAL)
diff --git a/acts/tests/google/wifi/aware/functional/NonConcurrencyTest.py b/acts/tests/google/wifi/aware/functional/NonConcurrencyTest.py
new file mode 100644
index 0000000..d0b83cf
--- /dev/null
+++ b/acts/tests/google/wifi/aware/functional/NonConcurrencyTest.py
@@ -0,0 +1,115 @@
+#!/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.
+
+from acts import asserts
+from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class NonConcurrencyTest(AwareBaseTest):
+  """Tests lack of concurrency scenarios Wi-Fi Aware with WFD (p2p) and
+  SoftAP
+
+  Note: these tests should be modified if the concurrency behavior changes!"""
+
+  SERVICE_NAME = "GoogleTestXYZ"
+  TETHER_SSID = "GoogleTestSoftApXYZ"
+
+  def __init__(self, controllers):
+    AwareBaseTest.__init__(self, controllers)
+
+  def teardown_test(self):
+    AwareBaseTest.teardown_test(self)
+    for ad in self.android_devices:
+      ad.droid.wifiP2pClose()
+
+  def run_aware_then_incompat_service(self, is_p2p):
+    """Run test to validate that a running Aware session terminates when an
+    Aware-incompatible service is started.
+
+    Args:
+      is_p2p: True for p2p, False for SoftAP
+    """
+    dut = self.android_devices[0]
+
+    # start Aware
+    id = dut.droid.wifiAwareAttach()
+    autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+    # start other service
+    if is_p2p:
+      dut.droid.wifiP2pInitialize()
+    else:
+      wutils.start_wifi_tethering(dut, self.TETHER_SSID, password=None)
+
+    # expect an announcement about Aware non-availability
+    autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
+
+    # local clean-up
+    if not is_p2p:
+      wutils.stop_wifi_tethering(dut)
+
+  def run_incompat_service_then_aware(self, is_p2p):
+    """Validate that if an Aware-incompatible service is already up then any
+    Aware operation fails"""
+    dut = self.android_devices[0]
+
+    # start other service
+    if is_p2p:
+      dut.droid.wifiP2pInitialize()
+    else:
+      wutils.start_wifi_tethering(dut, self.TETHER_SSID, password=None)
+
+    # expect an announcement about Aware non-availability
+    autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
+
+    # try starting anyway (expect failure)
+    dut.droid.wifiAwareAttach()
+    autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACH_FAILED)
+
+    # stop other service
+    if is_p2p:
+      dut.droid.wifiP2pClose()
+    else:
+      wutils.stop_wifi_tethering(dut)
+
+    # expect an announcement about Aware availability
+    autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+
+    # try starting Aware
+    dut.droid.wifiAwareAttach()
+    autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+  ##########################################################################
+
+  def test_run_p2p_then_aware(self):
+    """Validate that if p2p is already up then any Aware operation fails"""
+    self.run_incompat_service_then_aware(is_p2p=True)
+
+  def test_run_aware_then_p2p(self):
+    """Validate that a running Aware session terminates when p2p is started"""
+    self.run_aware_then_incompat_service(is_p2p=True)
+
+  def test_run_softap_then_aware(self):
+    """Validate that if SoftAp is already up then any Aware operation fails"""
+    self.run_incompat_service_then_aware(is_p2p=False)
+
+  def test_run_aware_then_softap(self):
+    """Validate that a running Aware session terminates when softAp is
+    started"""
+    self.run_aware_then_incompat_service(is_p2p=False)
diff --git a/acts/tests/google/wifi/aware/functional/ProtocolsTest.py b/acts/tests/google/wifi/aware/functional/ProtocolsTest.py
new file mode 100644
index 0000000..bf4a561
--- /dev/null
+++ b/acts/tests/google/wifi/aware/functional/ProtocolsTest.py
@@ -0,0 +1,193 @@
+#!/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.
+
+from acts import asserts
+from acts.test_utils.net import nsd_const as nconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class ProtocolsTest(AwareBaseTest):
+  """Set of tests for Wi-Fi Aware data-paths: validating protocols running on
+  top of a data-path"""
+
+  SERVICE_NAME = "GoogleTestServiceXY"
+
+  def __init__(self, controllers):
+    AwareBaseTest.__init__(self, controllers)
+
+  def run_ping6(self, dut, peer_ipv6, dut_if):
+    """Run a ping6 over the specified device/link
+
+    Args:
+      dut: Device on which to execute ping6
+      peer_ipv6: IPv6 address of the peer to ping
+      dut_if: interface name on the dut
+    """
+    cmd = "ping6 -c 3 -W 5 %s%%%s" % (peer_ipv6, dut_if)
+    results = dut.adb.shell(cmd)
+    self.log.info("cmd='%s' -> '%s'", cmd, results)
+    if results == "":
+      asserts.fail("ping6 empty results - seems like a failure")
+
+  ########################################################################
+
+  def test_ping6_oob(self):
+    """Validate that ping6 works correctly on an NDP created using OOB (out-of
+    band) discovery"""
+    init_dut = self.android_devices[0]
+    resp_dut = self.android_devices[1]
+
+    # create NDP
+    (init_req_key, resp_req_key, init_aware_if, resp_aware_if, init_ipv6,
+     resp_ipv6) = autils.create_oob_ndp(init_dut, resp_dut)
+    self.log.info("Interface names: I=%s, R=%s", init_aware_if, resp_aware_if)
+    self.log.info("Interface addresses (IPv6): I=%s, R=%s", init_ipv6,
+                  resp_ipv6)
+
+    # run ping6
+    self.run_ping6(init_dut, resp_ipv6, init_aware_if)
+    self.run_ping6(resp_dut, init_ipv6, resp_aware_if)
+
+    # clean-up
+    resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+    init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
+
+  def test_ping6_ib_unsolicited_passive(self):
+    """Validate that ping6 works correctly on an NDP created using Aware
+    discovery with UNSOLICITED/PASSIVE sessions."""
+    p_dut = self.android_devices[0]
+    s_dut = self.android_devices[1]
+
+    # create NDP
+    (p_req_key, s_req_key, p_aware_if, s_aware_if, p_ipv6,
+     s_ipv6) = autils.create_ib_ndp(
+         p_dut,
+         s_dut,
+         p_config=autils.create_discovery_config(
+             self.SERVICE_NAME, aconsts.PUBLISH_TYPE_UNSOLICITED),
+         s_config=autils.create_discovery_config(
+             self.SERVICE_NAME, aconsts.SUBSCRIBE_TYPE_PASSIVE),
+         device_startup_offset=self.device_startup_offset)
+    self.log.info("Interface names: P=%s, S=%s", p_aware_if, s_aware_if)
+    self.log.info("Interface addresses (IPv6): P=%s, S=%s", p_ipv6, s_ipv6)
+
+    # run ping6
+    self.run_ping6(p_dut, s_ipv6, p_aware_if)
+    self.run_ping6(s_dut, p_ipv6, s_aware_if)
+
+    # clean-up
+    p_dut.droid.connectivityUnregisterNetworkCallback(p_req_key)
+    s_dut.droid.connectivityUnregisterNetworkCallback(s_req_key)
+
+  def test_ping6_ib_solicited_active(self):
+    """Validate that ping6 works correctly on an NDP created using Aware
+    discovery with SOLICITED/ACTIVE sessions."""
+    p_dut = self.android_devices[0]
+    s_dut = self.android_devices[1]
+
+    # create NDP
+    (p_req_key, s_req_key, p_aware_if, s_aware_if, p_ipv6,
+     s_ipv6) = autils.create_ib_ndp(
+         p_dut,
+         s_dut,
+         p_config=autils.create_discovery_config(
+             self.SERVICE_NAME, aconsts.PUBLISH_TYPE_SOLICITED),
+         s_config=autils.create_discovery_config(self.SERVICE_NAME,
+                                                 aconsts.SUBSCRIBE_TYPE_ACTIVE),
+         device_startup_offset=self.device_startup_offset)
+    self.log.info("Interface names: P=%s, S=%s", p_aware_if, s_aware_if)
+    self.log.info("Interface addresses (IPv6): P=%s, S=%s", p_ipv6, s_ipv6)
+
+    # run ping6
+    self.run_ping6(p_dut, s_ipv6, p_aware_if)
+    self.run_ping6(s_dut, p_ipv6, s_aware_if)
+
+    # clean-up
+    p_dut.droid.connectivityUnregisterNetworkCallback(p_req_key)
+    s_dut.droid.connectivityUnregisterNetworkCallback(s_req_key)
+
+  def test_nsd_oob(self):
+    """Validate that NSD (mDNS) works correctly on an NDP created using OOB
+    (out-of band) discovery"""
+    init_dut = self.android_devices[0]
+    resp_dut = self.android_devices[1]
+
+    # create NDP
+    (init_req_key, resp_req_key, init_aware_if, resp_aware_if, init_ipv6,
+     resp_ipv6) = autils.create_oob_ndp(init_dut, resp_dut)
+    self.log.info("Interface names: I=%s, R=%s", init_aware_if, resp_aware_if)
+    self.log.info("Interface addresses (IPv6): I=%s, R=%s", init_ipv6,
+                  resp_ipv6)
+
+    # run NSD
+    nsd_service_info = {
+        "serviceInfoServiceName": "sl4aTestAwareNsd",
+        "serviceInfoServiceType": "_simple-tx-rx._tcp.",
+        "serviceInfoPort": 2257
+    }
+    nsd_reg = None
+    nsd_discovery = None
+    try:
+      # Initiator registers an NSD service
+      nsd_reg = init_dut.droid.nsdRegisterService(nsd_service_info)
+      event_nsd = autils.wait_for_event_with_keys(
+          init_dut, nconsts.REG_LISTENER_EVENT, autils.EVENT_TIMEOUT,
+          (nconsts.REG_LISTENER_CALLBACK,
+           nconsts.REG_LISTENER_EVENT_ON_SERVICE_REGISTERED))
+      self.log.info("Initiator %s: %s",
+                    nconsts.REG_LISTENER_EVENT_ON_SERVICE_REGISTERED,
+                    event_nsd["data"])
+
+      # Responder starts an NSD discovery
+      nsd_discovery = resp_dut.droid.nsdDiscoverServices(
+          nsd_service_info[nconsts.NSD_SERVICE_INFO_SERVICE_TYPE])
+      event_nsd = autils.wait_for_event_with_keys(
+          resp_dut, nconsts.DISCOVERY_LISTENER_EVENT, autils.EVENT_TIMEOUT,
+          (nconsts.DISCOVERY_LISTENER_DATA_CALLBACK,
+           nconsts.DISCOVERY_LISTENER_EVENT_ON_SERVICE_FOUND))
+      self.log.info("Responder %s: %s",
+                    nconsts.DISCOVERY_LISTENER_EVENT_ON_SERVICE_FOUND,
+                    event_nsd["data"])
+
+      # Responder resolves IP address of Initiator from NSD service discovery
+      resp_dut.droid.nsdResolveService(event_nsd["data"])
+      event_nsd = autils.wait_for_event_with_keys(
+          resp_dut, nconsts.RESOLVE_LISTENER_EVENT, autils.EVENT_TIMEOUT,
+          (nconsts.RESOLVE_LISTENER_DATA_CALLBACK,
+           nconsts.RESOLVE_LISTENER_EVENT_ON_SERVICE_RESOLVED))
+      self.log.info("Responder %s: %s",
+                    nconsts.RESOLVE_LISTENER_EVENT_ON_SERVICE_RESOLVED,
+                    event_nsd["data"])
+
+      # mDNS returns first character as '/' - strip
+      # out to get clean IPv6
+      init_ipv6_nsd = event_nsd["data"][nconsts.NSD_SERVICE_INFO_HOST][1:]
+
+      asserts.assert_equal(
+          init_ipv6, init_ipv6_nsd,
+          "Initiator's IPv6 address obtained through NSD doesn't match!?")
+    finally:
+      # Stop NSD
+      if nsd_reg is not None:
+        init_dut.droid.nsdUnregisterService(nsd_reg)
+      if nsd_discovery is not None:
+        resp_dut.droid.nsdStopServiceDiscovery(nsd_discovery)
+
+    # clean-up
+    resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+    init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
diff --git a/acts/tests/google/wifi/aware/functional/functional b/acts/tests/google/wifi/aware/functional/functional
new file mode 100644
index 0000000..e13a470
--- /dev/null
+++ b/acts/tests/google/wifi/aware/functional/functional
@@ -0,0 +1,9 @@
+AttachTest
+DiscoveryTest
+MessageTest
+DataPathTest
+MacRandomTest
+CapabilitiesTest
+ProtocolsTest
+NonConcurrencyTest
+MatchFilterTest
\ No newline at end of file
diff --git a/acts/tests/google/wifi/aware/ota/ServiceIdsTest.py b/acts/tests/google/wifi/aware/ota/ServiceIdsTest.py
new file mode 100644
index 0000000..1137303
--- /dev/null
+++ b/acts/tests/google/wifi/aware/ota/ServiceIdsTest.py
@@ -0,0 +1,100 @@
+#!/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 time
+
+from acts import asserts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class ServiceIdsTest(AwareBaseTest):
+  """Set of tests for Wi-Fi Aware to verify that beacons include service IDs
+  for discovery.
+
+  Note: this test is an OTA (over-the-air) and requires a Sniffer.
+  """
+
+  def __init__(self, controllers):
+    AwareBaseTest.__init__(self, controllers)
+
+  def start_discovery_session(self, dut, session_id, is_publish, dtype,
+                              service_name):
+    """Start a discovery session
+
+    Args:
+      dut: Device under test
+      session_id: ID of the Aware session in which to start discovery
+      is_publish: True for a publish session, False for subscribe session
+      dtype: Type of the discovery session
+      service_name: Service name to use for the discovery session
+
+    Returns:
+      Discovery session ID.
+    """
+    config = {}
+    config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = dtype
+    config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = service_name
+
+    if is_publish:
+      disc_id = dut.droid.wifiAwarePublish(session_id, config)
+      event_name = aconsts.SESSION_CB_ON_PUBLISH_STARTED
+    else:
+      disc_id = dut.droid.wifiAwareSubscribe(session_id, config)
+      event_name = aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED
+
+    autils.wait_for_event(dut, event_name)
+    return disc_id
+
+  ####################################################################
+
+  def test_service_ids_in_beacon(self):
+    """Verify that beacons include service IDs for both publish and subscribe
+    sessions of all types: solicited/unsolicited/active/passive."""
+    dut = self.android_devices[0]
+
+    self.log.info("Reminder: start a sniffer before running test")
+
+    # attach
+    session_id = dut.droid.wifiAwareAttach(True)
+    autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+    ident_event = autils.wait_for_event(dut,
+                                        aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+    mac = ident_event["data"]["mac"]
+    self.log.info("Source MAC Address of 'interesting' packets = %s", mac)
+    self.log.info("Wireshark filter = 'wlan.ta == %s:%s:%s:%s:%s:%s'", mac[0:2],
+                  mac[2:4], mac[4:6], mac[6:8], mac[8:10], mac[10:12])
+
+    time.sleep(5)  # get some samples pre-discovery
+
+    # start 4 discovery session (one of each type)
+    self.start_discovery_session(dut, session_id, True,
+                                 aconsts.PUBLISH_TYPE_UNSOLICITED,
+                                 "GoogleTestService-Pub-Unsolicited")
+    self.start_discovery_session(dut, session_id, True,
+                                 aconsts.PUBLISH_TYPE_SOLICITED,
+                                 "GoogleTestService-Pub-Solicited")
+    self.start_discovery_session(dut, session_id, False,
+                                 aconsts.SUBSCRIBE_TYPE_ACTIVE,
+                                 "GoogleTestService-Sub-Active")
+    self.start_discovery_session(dut, session_id, False,
+                                 aconsts.SUBSCRIBE_TYPE_PASSIVE,
+                                 "GoogleTestService-Sub-Passive")
+
+    time.sleep(15)  # get some samples while discovery is alive
+
+    self.log.info("Reminder: stop sniffer")
diff --git a/acts/tests/google/wifi/aware/ota/ota b/acts/tests/google/wifi/aware/ota/ota
new file mode 100644
index 0000000..27f6724
--- /dev/null
+++ b/acts/tests/google/wifi/aware/ota/ota
@@ -0,0 +1 @@
+ServiceIdsTest
\ No newline at end of file
diff --git a/acts/tests/google/wifi/aware/performance/LatencyTest.py b/acts/tests/google/wifi/aware/performance/LatencyTest.py
new file mode 100644
index 0000000..20176c1
--- /dev/null
+++ b/acts/tests/google/wifi/aware/performance/LatencyTest.py
@@ -0,0 +1,555 @@
+#!/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 time
+
+from acts import asserts
+from acts.test_utils.net import connectivity_const as cconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class LatencyTest(AwareBaseTest):
+  """Set of tests for Wi-Fi Aware to measure latency of Aware operations."""
+  SERVICE_NAME = "GoogleTestServiceXY"
+
+  # number of second to 'reasonably' wait to make sure that devices synchronize
+  # with each other - useful for OOB test cases, where the OOB discovery would
+  # take some time
+  WAIT_FOR_CLUSTER = 5
+
+  def __init__(self, controllers):
+    AwareBaseTest.__init__(self, controllers)
+
+  def start_discovery_session(self, dut, session_id, is_publish, dtype):
+    """Start a discovery session
+
+    Args:
+      dut: Device under test
+      session_id: ID of the Aware session in which to start discovery
+      is_publish: True for a publish session, False for subscribe session
+      dtype: Type of the discovery session
+
+    Returns:
+      Discovery session started event.
+    """
+    config = {}
+    config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = dtype
+    config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = "GoogleTestServiceXY"
+
+    if is_publish:
+      disc_id = dut.droid.wifiAwarePublish(session_id, config)
+      event_name = aconsts.SESSION_CB_ON_PUBLISH_STARTED
+    else:
+      disc_id = dut.droid.wifiAwareSubscribe(session_id, config)
+      event_name = aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED
+
+    event = autils.wait_for_event(dut, event_name)
+    return disc_id, event
+
+  def run_synchronization_latency(self, results, do_unsolicited_passive,
+                                  dw_24ghz, dw_5ghz, num_iterations,
+                                  startup_offset, timeout_period):
+    """Run the synchronization latency test with the specified DW intervals.
+    There is no direct measure of synchronization. Instead starts a discovery
+    session as soon as possible and measures both probability of discovery
+    within a timeout period and the actual discovery time (not necessarily
+    accurate).
+
+    Args:
+      results: Result array to be populated - will add results (not erase it)
+      do_unsolicited_passive: True for unsolicited/passive, False for
+                              solicited/active.
+      dw_24ghz: DW interval in the 2.4GHz band.
+      dw_5ghz: DW interval in the 5GHz band.
+      startup_offset: The start-up gap (in seconds) between the two devices
+      timeout_period: Time period over which to measure synchronization
+    """
+    key = "%s_dw24_%d_dw5_%d_offset_%d" % (
+        "unsolicited_passive" if do_unsolicited_passive else "solicited_active",
+        dw_24ghz, dw_5ghz, startup_offset)
+    results[key] = {}
+    results[key]["num_iterations"] = num_iterations
+
+    p_dut = self.android_devices[0]
+    p_dut.pretty_name = "Publisher"
+    s_dut = self.android_devices[1]
+    s_dut.pretty_name = "Subscriber"
+
+    # override the default DW configuration
+    autils.config_dw_all_modes(p_dut, dw_24ghz, dw_5ghz)
+    autils.config_dw_all_modes(s_dut, dw_24ghz, dw_5ghz)
+
+    latencies = []
+    failed_discoveries = 0
+    for i in range(num_iterations):
+      # 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(startup_offset)
+      s_id = s_dut.droid.wifiAwareAttach(False)
+      autils.wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+      # start publish
+      p_disc_id, p_disc_event = self.start_discovery_session(
+          p_dut, p_id, True, aconsts.PUBLISH_TYPE_UNSOLICITED
+          if do_unsolicited_passive else aconsts.PUBLISH_TYPE_SOLICITED)
+
+      # start subscribe
+      s_disc_id, s_session_event = self.start_discovery_session(
+          s_dut, s_id, False, aconsts.SUBSCRIBE_TYPE_PASSIVE
+          if do_unsolicited_passive else aconsts.SUBSCRIBE_TYPE_ACTIVE)
+
+      # wait for discovery (allow for failures here since running lots of
+      # samples and would like to get the partial data even in the presence of
+      # errors)
+      try:
+        discovery_event = s_dut.ed.pop_event(
+            aconsts.SESSION_CB_ON_SERVICE_DISCOVERED, timeout_period)
+        s_dut.log.info("[Subscriber] SESSION_CB_ON_SERVICE_DISCOVERED: %s",
+                       discovery_event["data"])
+      except queue.Empty:
+        s_dut.log.info("[Subscriber] Timed out while waiting for "
+                       "SESSION_CB_ON_SERVICE_DISCOVERED")
+        failed_discoveries = failed_discoveries + 1
+        continue
+      finally:
+        # destroy sessions
+        p_dut.droid.wifiAwareDestroyDiscoverySession(p_disc_id)
+        s_dut.droid.wifiAwareDestroyDiscoverySession(s_disc_id)
+        p_dut.droid.wifiAwareDestroy(p_id)
+        s_dut.droid.wifiAwareDestroy(s_id)
+
+      # collect latency information
+      latencies.append(
+          discovery_event["data"][aconsts.SESSION_CB_KEY_TIMESTAMP_MS] -
+          s_session_event["data"][aconsts.SESSION_CB_KEY_TIMESTAMP_MS])
+      self.log.info("Latency #%d = %d" % (i, latencies[-1]))
+
+    autils.extract_stats(
+        s_dut,
+        data=latencies,
+        results=results[key],
+        key_prefix="",
+        log_prefix="Subscribe Session Sync/Discovery (%s, dw24=%d, dw5=%d)" %
+        ("Unsolicited/Passive"
+         if do_unsolicited_passive else "Solicited/Active", dw_24ghz, dw_5ghz))
+    results[key]["num_failed_discovery"] = failed_discoveries
+
+  def run_discovery_latency(self, results, do_unsolicited_passive, dw_24ghz,
+                            dw_5ghz, num_iterations):
+    """Run the service discovery latency test with the specified DW intervals.
+
+    Args:
+      results: Result array to be populated - will add results (not erase it)
+      do_unsolicited_passive: True for unsolicited/passive, False for
+                              solicited/active.
+      dw_24ghz: DW interval in the 2.4GHz band.
+      dw_5ghz: DW interval in the 5GHz band.
+    """
+    key = "%s_dw24_%d_dw5_%d" % (
+        "unsolicited_passive"
+        if do_unsolicited_passive else "solicited_active", dw_24ghz, dw_5ghz)
+    results[key] = {}
+    results[key]["num_iterations"] = num_iterations
+
+    p_dut = self.android_devices[0]
+    p_dut.pretty_name = "Publisher"
+    s_dut = self.android_devices[1]
+    s_dut.pretty_name = "Subscriber"
+
+    # override the default DW configuration
+    autils.config_dw_all_modes(p_dut, dw_24ghz, dw_5ghz)
+    autils.config_dw_all_modes(s_dut, dw_24ghz, dw_5ghz)
+
+    # 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)
+
+    # start publish
+    p_disc_event = self.start_discovery_session(
+        p_dut, p_id, True, aconsts.PUBLISH_TYPE_UNSOLICITED
+        if do_unsolicited_passive else aconsts.PUBLISH_TYPE_SOLICITED)
+
+    # wait for for devices to synchronize with each other - used so that first
+    # discovery isn't biased by synchronization.
+    time.sleep(self.WAIT_FOR_CLUSTER)
+
+    # loop, perform discovery, and collect latency information
+    latencies = []
+    failed_discoveries = 0
+    for i in range(num_iterations):
+      # start subscribe
+      s_disc_id, s_session_event = self.start_discovery_session(
+          s_dut, s_id, False, aconsts.SUBSCRIBE_TYPE_PASSIVE
+          if do_unsolicited_passive else aconsts.SUBSCRIBE_TYPE_ACTIVE)
+
+      # wait for discovery (allow for failures here since running lots of
+      # samples and would like to get the partial data even in the presence of
+      # errors)
+      try:
+        discovery_event = s_dut.ed.pop_event(
+            aconsts.SESSION_CB_ON_SERVICE_DISCOVERED, autils.EVENT_TIMEOUT)
+      except queue.Empty:
+        s_dut.log.info("[Subscriber] Timed out while waiting for "
+                       "SESSION_CB_ON_SERVICE_DISCOVERED")
+        failed_discoveries = failed_discoveries + 1
+        continue
+      finally:
+        # destroy subscribe
+        s_dut.droid.wifiAwareDestroyDiscoverySession(s_disc_id)
+
+      # collect latency information
+      latencies.append(
+          discovery_event["data"][aconsts.SESSION_CB_KEY_TIMESTAMP_MS] -
+          s_session_event["data"][aconsts.SESSION_CB_KEY_TIMESTAMP_MS])
+      self.log.info("Latency #%d = %d" % (i, latencies[-1]))
+
+    autils.extract_stats(
+        s_dut,
+        data=latencies,
+        results=results[key],
+        key_prefix="",
+        log_prefix="Subscribe Session Discovery (%s, dw24=%d, dw5=%d)" %
+        ("Unsolicited/Passive"
+         if do_unsolicited_passive else "Solicited/Active", dw_24ghz, dw_5ghz))
+    results[key]["num_failed_discovery"] = failed_discoveries
+
+    # clean up
+    p_dut.droid.wifiAwareDestroyAll()
+    s_dut.droid.wifiAwareDestroyAll()
+
+  def run_message_latency(self, results, dw_24ghz, dw_5ghz, num_iterations):
+    """Run the message tx latency test with the specified DW intervals.
+
+    Args:
+      results: Result array to be populated - will add results (not erase it)
+      dw_24ghz: DW interval in the 2.4GHz band.
+      dw_5ghz: DW interval in the 5GHz band.
+    """
+    key = "dw24_%d_dw5_%d" % (dw_24ghz, dw_5ghz)
+    results[key] = {}
+    results[key]["num_iterations"] = num_iterations
+
+    p_dut = self.android_devices[0]
+    s_dut = self.android_devices[1]
+
+    # override the default DW configuration
+    autils.config_dw_all_modes(p_dut, dw_24ghz, dw_5ghz)
+    autils.config_dw_all_modes(s_dut, dw_24ghz, dw_5ghz)
+
+    # Start up a discovery session
+    (p_id, s_id, p_disc_id, s_disc_id,
+     peer_id_on_sub) = autils.create_discovery_pair(
+         p_dut,
+         s_dut,
+         p_config=autils.create_discovery_config(
+             self.SERVICE_NAME, aconsts.PUBLISH_TYPE_UNSOLICITED),
+         s_config=autils.create_discovery_config(
+             self.SERVICE_NAME, aconsts.SUBSCRIBE_TYPE_PASSIVE),
+         device_startup_offset=self.device_startup_offset)
+
+    latencies = []
+    failed_tx = 0
+    messages_rx = 0
+    missing_rx = 0
+    corrupted_rx = 0
+    for i in range(num_iterations):
+      # send message
+      msg_s2p = "Message Subscriber -> Publisher #%d" % i
+      next_msg_id = self.get_next_msg_id()
+      s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub, next_msg_id,
+                                       msg_s2p, 0)
+
+      # wait for Tx confirmation
+      try:
+        sub_tx_msg_event = s_dut.ed.pop_event(
+            aconsts.SESSION_CB_ON_MESSAGE_SENT, 2 * autils.EVENT_TIMEOUT)
+        latencies.append(
+            sub_tx_msg_event["data"][aconsts.SESSION_CB_KEY_LATENCY_MS])
+      except queue.Empty:
+        s_dut.log.info("[Subscriber] Timed out while waiting for "
+                       "SESSION_CB_ON_MESSAGE_SENT")
+        failed_tx = failed_tx + 1
+        continue
+
+      # wait for Rx confirmation (and validate contents)
+      try:
+        pub_rx_msg_event = p_dut.ed.pop_event(
+            aconsts.SESSION_CB_ON_MESSAGE_RECEIVED, 2 * autils.EVENT_TIMEOUT)
+        messages_rx = messages_rx + 1
+        if (pub_rx_msg_event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING]
+            != msg_s2p):
+          corrupted_rx = corrupted_rx + 1
+      except queue.Empty:
+        s_dut.log.info("[Publisher] Timed out while waiting for "
+                       "SESSION_CB_ON_MESSAGE_RECEIVED")
+        missing_rx = missing_rx + 1
+        continue
+
+    autils.extract_stats(
+        s_dut,
+        data=latencies,
+        results=results[key],
+        key_prefix="",
+        log_prefix="Subscribe Session Discovery (dw24=%d, dw5=%d)" %
+                   (dw_24ghz, dw_5ghz))
+    results[key]["failed_tx"] = failed_tx
+    results[key]["messages_rx"] = messages_rx
+    results[key]["missing_rx"] = missing_rx
+    results[key]["corrupted_rx"] = corrupted_rx
+
+    # clean up
+    p_dut.droid.wifiAwareDestroyAll()
+    s_dut.droid.wifiAwareDestroyAll()
+
+  def run_ndp_oob_latency(self, results, dw_24ghz, dw_5ghz, num_iterations):
+    """Runs the NDP setup with OOB (out-of-band) discovery latency test.
+
+    Args:
+      results: Result array to be populated - will add results (not erase it)
+      dw_24ghz: DW interval in the 2.4GHz band.
+      dw_5ghz: DW interval in the 5GHz band.
+    """
+    key_avail = "on_avail_dw24_%d_dw5_%d" % (dw_24ghz, dw_5ghz)
+    key_link_props = "link_props_dw24_%d_dw5_%d" % (dw_24ghz, dw_5ghz)
+    results[key_avail] = {}
+    results[key_link_props] = {}
+    results[key_avail]["num_iterations"] = num_iterations
+
+    init_dut = self.android_devices[0]
+    init_dut.pretty_name = 'Initiator'
+    resp_dut = self.android_devices[1]
+    resp_dut.pretty_name = 'Responder'
+
+    # override the default DW configuration
+    autils.config_dw_all_modes(init_dut, dw_24ghz, dw_5ghz)
+    autils.config_dw_all_modes(resp_dut, dw_24ghz, dw_5ghz)
+
+    # Initiator+Responder: attach and wait for confirmation & identity
+    init_id = init_dut.droid.wifiAwareAttach(True)
+    autils.wait_for_event(init_dut, aconsts.EVENT_CB_ON_ATTACHED)
+    init_ident_event = autils.wait_for_event(init_dut,
+                                      aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+    init_mac = init_ident_event['data']['mac']
+    time.sleep(self.device_startup_offset)
+    resp_id = resp_dut.droid.wifiAwareAttach(True)
+    autils.wait_for_event(resp_dut, aconsts.EVENT_CB_ON_ATTACHED)
+    resp_ident_event = autils.wait_for_event(resp_dut,
+                                      aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+    resp_mac = resp_ident_event['data']['mac']
+
+    # wait for for devices to synchronize with each other - there are no other
+    # mechanisms to make sure this happens for OOB discovery (except retrying
+    # to execute the data-path request)
+    time.sleep(autils.WAIT_FOR_CLUSTER)
+
+    on_available_latencies = []
+    link_props_latencies = []
+    ndp_setup_failures = 0
+    for i in range(num_iterations):
+      # Responder: request network
+      resp_req_key = autils.request_network(
+          resp_dut,
+          resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+              resp_id, aconsts.DATA_PATH_RESPONDER, init_mac, None))
+
+      # Initiator: request network
+      init_req_key = autils.request_network(
+          init_dut,
+          init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+              init_id, aconsts.DATA_PATH_INITIATOR, resp_mac, None))
+
+      # Initiator & Responder: wait for network formation
+      got_on_available = False
+      got_on_link_props = False
+      while not got_on_available or not got_on_link_props:
+        try:
+          nc_event = init_dut.ed.pop_event(cconsts.EVENT_NETWORK_CALLBACK,
+                                           autils.EVENT_NDP_TIMEOUT)
+          if nc_event["data"][
+              cconsts.NETWORK_CB_KEY_EVENT] == cconsts.NETWORK_CB_AVAILABLE:
+            got_on_available = True
+            on_available_latencies.append(
+                nc_event["data"][cconsts.NETWORK_CB_KEY_CURRENT_TS] -
+                nc_event["data"][cconsts.NETWORK_CB_KEY_CREATE_TS])
+          elif (nc_event["data"][cconsts.NETWORK_CB_KEY_EVENT] ==
+                cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED):
+            got_on_link_props = True
+            link_props_latencies.append(
+                nc_event["data"][cconsts.NETWORK_CB_KEY_CURRENT_TS] -
+                nc_event["data"][cconsts.NETWORK_CB_KEY_CREATE_TS])
+        except queue.Empty:
+          ndp_setup_failures = ndp_setup_failures + 1
+          init_dut.log.info("[Initiator] Timed out while waiting for "
+                         "EVENT_NETWORK_CALLBACK")
+          break
+
+      # clean-up
+      init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
+      resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+
+      # wait before trying another iteration (need to let CM clean-up)
+      time.sleep(10)
+
+    autils.extract_stats(
+        init_dut,
+        data=on_available_latencies,
+        results=results[key_avail],
+        key_prefix="",
+        log_prefix="NDP setup OnAvailable(dw24=%d, dw5=%d)" % (dw_24ghz,
+                                                               dw_5ghz))
+    autils.extract_stats(
+        init_dut,
+        data=link_props_latencies,
+        results=results[key_link_props],
+        key_prefix="",
+        log_prefix="NDP setup OnLinkProperties (dw24=%d, dw5=%d)" % (dw_24ghz,
+                                                                     dw_5ghz))
+    results[key_avail]["ndp_setup_failures"] = ndp_setup_failures
+
+
+  ########################################################################
+
+  def test_synchronization_default_dws(self):
+    """Measure the device synchronization for default dws. Loop over values
+    from 0 to 4 seconds."""
+    results = {}
+    for startup_offset in range(5):
+      self.run_synchronization_latency(
+          results=results,
+          do_unsolicited_passive=True,
+          dw_24ghz=aconsts.DW_24_INTERACTIVE,
+          dw_5ghz=aconsts.DW_5_INTERACTIVE,
+          num_iterations=10,
+          startup_offset=startup_offset,
+          timeout_period=20)
+    asserts.explicit_pass(
+        "test_synchronization_default_dws finished", extras=results)
+
+  def test_synchronization_non_interactive_dws(self):
+    """Measure the device synchronization for non-interactive dws. Loop over
+    values from 0 to 4 seconds."""
+    results = {}
+    for startup_offset in range(5):
+      self.run_synchronization_latency(
+          results=results,
+          do_unsolicited_passive=True,
+          dw_24ghz=aconsts.DW_24_NON_INTERACTIVE,
+          dw_5ghz=aconsts.DW_5_NON_INTERACTIVE,
+          num_iterations=10,
+          startup_offset=startup_offset,
+          timeout_period=20)
+    asserts.explicit_pass(
+        "test_synchronization_non_interactive_dws finished", extras=results)
+
+  def test_discovery_latency_default_dws(self):
+    """Measure the service discovery latency with the default DW configuration.
+    """
+    results = {}
+    self.run_discovery_latency(
+        results=results,
+        do_unsolicited_passive=True,
+        dw_24ghz=aconsts.DW_24_INTERACTIVE,
+        dw_5ghz=aconsts.DW_5_INTERACTIVE,
+        num_iterations=100)
+    asserts.explicit_pass(
+        "test_discovery_latency_default_parameters finished", extras=results)
+
+  def test_discovery_latency_non_interactive_dws(self):
+    """Measure the service discovery latency with the DW configuration for non
+    -interactive mode (lower power)."""
+    results = {}
+    self.run_discovery_latency(
+        results=results,
+        do_unsolicited_passive=True,
+        dw_24ghz=aconsts.DW_24_NON_INTERACTIVE,
+        dw_5ghz=aconsts.DW_5_NON_INTERACTIVE,
+        num_iterations=100)
+    asserts.explicit_pass(
+        "test_discovery_latency_non_interactive_dws finished", extras=results)
+
+  def test_discovery_latency_all_dws(self):
+    """Measure the service discovery latency with all DW combinations (low
+    iteration count)"""
+    results = {}
+    for dw24 in range(1, 6):  # permitted values: 1-5
+      for dw5 in range(0, 6): # permitted values: 0, 1-5
+        self.run_discovery_latency(
+            results=results,
+            do_unsolicited_passive=True,
+            dw_24ghz=dw24,
+            dw_5ghz=dw5,
+            num_iterations=10)
+    asserts.explicit_pass(
+        "test_discovery_latency_all_dws finished", extras=results)
+
+  def test_message_latency_default_dws(self):
+    """Measure the send message latency with the default DW configuration. Test
+    performed on non-queued message transmission - i.e. waiting for confirmation
+    of reception (ACK) before sending the next message."""
+    results = {}
+    self.run_message_latency(
+        results=results,
+        dw_24ghz=aconsts.DW_24_INTERACTIVE,
+        dw_5ghz=aconsts.DW_5_INTERACTIVE,
+        num_iterations=100)
+    asserts.explicit_pass(
+        "test_message_latency_default_dws finished", extras=results)
+
+  def test_message_latency_non_interactive_dws(self):
+    """Measure the send message latency with the DW configuration for
+    non-interactive mode. Test performed on non-queued message transmission -
+    i.e. waiting for confirmation of reception (ACK) before sending the next
+    message."""
+    results = {}
+    self.run_message_latency(
+        results=results,
+        dw_24ghz=aconsts.DW_24_NON_INTERACTIVE,
+        dw_5ghz=aconsts.DW_5_NON_INTERACTIVE,
+        num_iterations=100)
+    asserts.explicit_pass(
+        "test_message_latency_non_interactive_dws finished", extras=results)
+
+  def test_oob_ndp_setup_latency_default_dws(self):
+    """Measure the NDP setup latency with the default DW configuration. The
+    NDP is setup with OOB (out-of-band) configuration."""
+    results = {}
+    self.run_ndp_oob_latency(
+        results=results,
+        dw_24ghz=aconsts.DW_24_INTERACTIVE,
+        dw_5ghz=aconsts.DW_5_INTERACTIVE,
+        num_iterations=10)
+    asserts.explicit_pass(
+        "test_ndp_setup_latency_default_dws finished", extras=results)
+
+  def test_oob_ndp_setup_latency_non_interactive_dws(self):
+    """Measure the NDP setup latency with the DW configuration for
+    non-interactive mode. The NDP is setup with OOB (out-of-band)
+    configuration"""
+    results = {}
+    self.run_ndp_oob_latency(
+        results=results,
+        dw_24ghz=aconsts.DW_24_NON_INTERACTIVE,
+        dw_5ghz=aconsts.DW_5_NON_INTERACTIVE,
+        num_iterations=10)
+    asserts.explicit_pass(
+        "test_ndp_setup_latency_non_interactive_dws finished", extras=results)
diff --git a/acts/tests/google/wifi/aware/performance/ThroughputTest.py b/acts/tests/google/wifi/aware/performance/ThroughputTest.py
new file mode 100644
index 0000000..fe2fd0c
--- /dev/null
+++ b/acts/tests/google/wifi/aware/performance/ThroughputTest.py
@@ -0,0 +1,116 @@
+#!/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 json
+import pprint
+import time
+
+from acts import asserts
+from acts.test_utils.net import connectivity_const as cconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class ThroughputTest(AwareBaseTest):
+  """Set of tests for Wi-Fi Aware to measure latency of Aware operations."""
+
+  SERVICE_NAME = "GoogleTestServiceXYZ"
+
+  def __init__(self, controllers):
+    AwareBaseTest.__init__(self, controllers)
+
+  def request_network(self, dut, ns):
+    """Request a Wi-Fi Aware network.
+
+    Args:
+      dut: Device
+      ns: Network specifier
+    Returns: the request key
+    """
+    network_req = {"TransportType": 5, "NetworkSpecifier": ns}
+    return dut.droid.connectivityRequestWifiAwareNetwork(network_req)
+
+  def run_iperf_single_ndp_aware_only(self, use_ib, results):
+    """Measure iperf performance on a single NDP, with Aware enabled and no
+    infrastructure connection - i.e. device is not associated to an AP.
+
+    Args:
+      use_ib: True to use in-band discovery, False to use out-of-band discovery.
+      results: Dictionary into which to place test results.
+    """
+    init_dut = self.android_devices[0]
+    resp_dut = self.android_devices[1]
+
+    if use_ib:
+      # note: Publisher = Responder, Subscribe = Initiator
+      (resp_req_key, init_req_key, resp_aware_if,
+       init_aware_if, resp_ipv6, init_ipv6) = autils.create_ib_ndp(
+           resp_dut, init_dut,
+           autils.create_discovery_config(self.SERVICE_NAME,
+                                          aconsts.PUBLISH_TYPE_UNSOLICITED),
+           autils.create_discovery_config(self.SERVICE_NAME,
+                                          aconsts.SUBSCRIBE_TYPE_PASSIVE),
+           self.device_startup_offset)
+    else:
+      (init_req_key, resp_req_key, init_aware_if, resp_aware_if, init_ipv6,
+      resp_ipv6) = autils.create_oob_ndp(init_dut, resp_dut)
+    self.log.info("Interface names: I=%s, R=%s", init_aware_if, resp_aware_if)
+    self.log.info("Interface addresses (IPv6): I=%s, R=%s", init_ipv6,
+                  resp_ipv6)
+
+    # Run iperf3
+    result, data = init_dut.run_iperf_server("-D")
+    asserts.assert_true(result, "Can't start iperf3 server")
+
+    result, data = resp_dut.run_iperf_client(
+        "%s%%%s" % (init_ipv6, resp_aware_if), "-6 -J")
+    self.log.debug(data)
+    asserts.assert_true(result,
+                        "Failure starting/running iperf3 in client mode")
+    self.log.debug(pprint.pformat(data))
+
+    # clean-up
+    resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+    init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
+
+    # Collect results
+    data_json = json.loads("".join(data))
+    if "error" in data_json:
+      asserts.fail(
+          "iperf run failed: %s" % data_json["error"], extras=data_json)
+    results["tx_rate"] = data_json["end"]["sum_sent"]["bits_per_second"]
+    results["rx_rate"] = data_json["end"]["sum_received"]["bits_per_second"]
+    self.log.info("iPerf3: Sent = %d bps Received = %d bps", results["tx_rate"],
+                  results["rx_rate"])
+
+  ########################################################################
+
+  def test_iperf_single_ndp_aware_only_ib(self):
+    """Measure throughput using iperf on a single NDP, with Aware enabled and
+    no infrastructure connection. Use in-band discovery."""
+    results = {}
+    self.run_iperf_single_ndp_aware_only(use_ib=True, results=results)
+    asserts.explicit_pass(
+        "test_iperf_single_ndp_aware_only_ib passes", extras=results)
+
+  def test_iperf_single_ndp_aware_only_oob(self):
+    """Measure throughput using iperf on a single NDP, with Aware enabled and
+    no infrastructure connection. Use out-of-band discovery."""
+    results = {}
+    self.run_iperf_single_ndp_aware_only(use_ib=False, results=results)
+    asserts.explicit_pass(
+        "test_iperf_single_ndp_aware_only_ib passes", extras=results)
diff --git a/acts/tests/google/wifi/aware/performance/performance b/acts/tests/google/wifi/aware/performance/performance
new file mode 100644
index 0000000..17e3963
--- /dev/null
+++ b/acts/tests/google/wifi/aware/performance/performance
@@ -0,0 +1,2 @@
+LatencyTest
+ThroughputTest
\ No newline at end of file
diff --git a/acts/tests/google/wifi/aware/stress/DataPathStressTest.py b/acts/tests/google/wifi/aware/stress/DataPathStressTest.py
new file mode 100644
index 0000000..ab204b6
--- /dev/null
+++ b/acts/tests/google/wifi/aware/stress/DataPathStressTest.py
@@ -0,0 +1,149 @@
+#!/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 time
+
+from acts import asserts
+from acts.test_utils.net import connectivity_const as cconsts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class DataPathStressTest(AwareBaseTest):
+
+  # Number of iterations on create/destroy Attach sessions.
+  ATTACH_ITERATIONS = 2
+
+  # Number of iterations on create/destroy NDP in each discovery session.
+  NDP_ITERATIONS = 5
+
+  def __init__(self, controllers):
+    AwareBaseTest.__init__(self, controllers)
+
+  ################################################################
+
+  def test_oob_ndp_stress(self):
+    """Run NDP (NAN data-path) stress test creating and destroying Aware
+    attach sessions, discovery sessions, and NDPs."""
+    init_dut = self.android_devices[0]
+    init_dut.pretty_name = 'Initiator'
+    resp_dut = self.android_devices[1]
+    resp_dut.pretty_name = 'Responder'
+
+    ndp_init_setup_success = 0
+    ndp_init_setup_failures = 0
+    ndp_resp_setup_success = 0
+    ndp_resp_setup_failures = 0
+
+    for attach_iter in range(self.ATTACH_ITERATIONS):
+      init_id = init_dut.droid.wifiAwareAttach(True)
+      autils.wait_for_event(init_dut, aconsts.EVENT_CB_ON_ATTACHED)
+      init_ident_event = autils.wait_for_event(
+          init_dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+      init_mac = init_ident_event['data']['mac']
+      time.sleep(self.device_startup_offset)
+      resp_id = resp_dut.droid.wifiAwareAttach(True)
+      autils.wait_for_event(resp_dut, aconsts.EVENT_CB_ON_ATTACHED)
+      resp_ident_event = autils.wait_for_event(
+          resp_dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+      resp_mac = resp_ident_event['data']['mac']
+
+      # wait for for devices to synchronize with each other - there are no other
+      # mechanisms to make sure this happens for OOB discovery (except retrying
+      # to execute the data-path request)
+      time.sleep(autils.WAIT_FOR_CLUSTER)
+
+      for ndp_iteration in range(self.NDP_ITERATIONS):
+        # Responder: request network
+        resp_req_key = autils.request_network(
+            resp_dut,
+            resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+                resp_id, aconsts.DATA_PATH_RESPONDER, init_mac, None))
+
+        # Initiator: request network
+        init_req_key = autils.request_network(
+            init_dut,
+            init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+                init_id, aconsts.DATA_PATH_INITIATOR, resp_mac, None))
+
+        # Initiator: wait for network formation
+        got_on_available = False
+        got_on_link_props = False
+        while not got_on_available or not got_on_link_props:
+          try:
+            nc_event = init_dut.ed.pop_event(cconsts.EVENT_NETWORK_CALLBACK,
+                                             autils.EVENT_NDP_TIMEOUT)
+            if nc_event['data'][
+                cconsts.NETWORK_CB_KEY_EVENT] == cconsts.NETWORK_CB_AVAILABLE:
+              got_on_available = True
+            elif (nc_event['data'][cconsts.NETWORK_CB_KEY_EVENT] ==
+                  cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED):
+              got_on_link_props = True
+          except queue.Empty:
+            ndp_init_setup_failures = ndp_init_setup_failures + 1
+            init_dut.log.info('[Initiator] Timed out while waiting for '
+                              'EVENT_NETWORK_CALLBACK')
+            break
+
+        if got_on_available and got_on_link_props:
+          ndp_init_setup_success = ndp_init_setup_success + 1
+
+        # Responder: wait for network formation
+        got_on_available = False
+        got_on_link_props = False
+        while not got_on_available or not got_on_link_props:
+          try:
+            nc_event = resp_dut.ed.pop_event(cconsts.EVENT_NETWORK_CALLBACK,
+                                             autils.EVENT_NDP_TIMEOUT)
+            if nc_event['data'][
+                cconsts.NETWORK_CB_KEY_EVENT] == cconsts.NETWORK_CB_AVAILABLE:
+              got_on_available = True
+            elif (nc_event['data'][cconsts.NETWORK_CB_KEY_EVENT] ==
+                  cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED):
+              got_on_link_props = True
+          except queue.Empty:
+            ndp_resp_setup_failures = ndp_resp_setup_failures + 1
+            init_dut.log.info('[Responder] Timed out while waiting for '
+                              'EVENT_NETWORK_CALLBACK')
+            break
+
+        if got_on_available and got_on_link_props:
+          ndp_resp_setup_success = ndp_resp_setup_success + 1
+
+        # clean-up
+        init_dut.droid.connectivityUnregisterNetworkCallback(init_req_key)
+        resp_dut.droid.connectivityUnregisterNetworkCallback(resp_req_key)
+
+        # wait before trying another iteration (need to let CM clean-up)
+        time.sleep(10)
+
+      # clean-up at end of iteration
+      init_dut.droid.wifiAwareDestroy(init_id)
+      resp_dut.droid.wifiAwareDestroy(resp_id)
+
+    results = {}
+    results['ndp_init_setup_success'] = ndp_init_setup_success
+    results['ndp_init_setup_failures'] = ndp_init_setup_failures
+    results['ndp_resp_setup_success'] = ndp_resp_setup_success
+    results['ndp_resp_setup_failures'] = ndp_resp_setup_failures
+    asserts.assert_equal(
+        ndp_init_setup_failures + ndp_resp_setup_failures,
+        0,
+        'test_oob_ndp_stress finished',
+        extras=results)
+    asserts.explicit_pass("test_oob_ndp_stress done", extras=results)
diff --git a/acts/tests/google/wifi/aware/stress/DiscoveryStressTest.py b/acts/tests/google/wifi/aware/stress/DiscoveryStressTest.py
new file mode 100644
index 0000000..8b3d925
--- /dev/null
+++ b/acts/tests/google/wifi/aware/stress/DiscoveryStressTest.py
@@ -0,0 +1,109 @@
+#!/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 time
+
+from acts import asserts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+
+class DiscoveryStressTest(AwareBaseTest):
+  """Stress tests for Discovery sessions"""
+
+  # Number of iterations on create/destroy Attach sessions.
+  ATTACH_ITERATIONS = 2
+
+  # Number of iterations on create/destroy Discovery sessions
+  DISCOVERY_ITERATIONS = 40
+
+  def __init__(self, controllers):
+    AwareBaseTest.__init__(self, controllers)
+
+  ####################################################################
+
+  def test_discovery_stress(self):
+    """Create and destroy a random array of discovery sessions, up to the
+    limit of capabilities."""
+    dut = self.android_devices[0]
+
+    discovery_setup_success = 0
+    discovery_setup_fail = 0
+
+    for attach_iter in range(self.ATTACH_ITERATIONS):
+      # attach
+      session_id = dut.droid.wifiAwareAttach(True)
+      autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+      p_discovery_ids = []
+      s_discovery_ids = []
+      for discovery_iter in range(self.DISCOVERY_ITERATIONS):
+        service_name = 'GoogleTestService-%d-%d' % (attach_iter, discovery_iter)
+
+        p_config = None
+        s_config = None
+
+        if discovery_iter % 4 == 0:  # publish/unsolicited
+          p_config = autils.create_discovery_config(
+              service_name, aconsts.PUBLISH_TYPE_UNSOLICITED)
+        elif discovery_iter % 4 == 1:  # publish/solicited
+          p_config = autils.create_discovery_config(
+              service_name, aconsts.PUBLISH_TYPE_SOLICITED)
+        elif discovery_iter % 4 == 2:  # subscribe/passive
+          s_config = autils.create_discovery_config(
+              service_name, aconsts.SUBSCRIBE_TYPE_PASSIVE)
+        elif discovery_iter % 4 == 3:  # subscribe/active
+          s_config = autils.create_discovery_config(
+              service_name, aconsts.SUBSCRIBE_TYPE_ACTIVE)
+
+        if p_config is not None:
+          if len(p_discovery_ids) == dut.aware_capabilities[
+              aconsts.CAP_MAX_PUBLISHES]:
+            dut.droid.wifiAwareDestroyDiscoverySession(
+                p_discovery_ids.pop(dut.aware_capabilities[
+                    aconsts.CAP_MAX_PUBLISHES] // 2))
+          disc_id = dut.droid.wifiAwarePublish(session_id, p_config)
+          event_name = aconsts.SESSION_CB_ON_PUBLISH_STARTED
+          p_discovery_ids.append(disc_id)
+        else:
+          if len(s_discovery_ids) == dut.aware_capabilities[
+              aconsts.CAP_MAX_SUBSCRIBES]:
+            dut.droid.wifiAwareDestroyDiscoverySession(
+                s_discovery_ids.pop(dut.aware_capabilities[
+                    aconsts.CAP_MAX_SUBSCRIBES] // 2))
+          disc_id = dut.droid.wifiAwareSubscribe(session_id, s_config)
+          event_name = aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED
+          s_discovery_ids.append(disc_id)
+
+        try:
+          dut.ed.pop_event(event_name, autils.EVENT_TIMEOUT)
+          discovery_setup_success = discovery_setup_success + 1
+        except queue.Empty:
+          discovery_setup_fail = discovery_setup_fail + 1
+
+      dut.droid.wifiAwareDestroy(session_id)
+
+    results = {}
+    results['discovery_setup_success'] = discovery_setup_success
+    results['discovery_setup_fail'] = discovery_setup_fail
+    asserts.assert_equal(
+        discovery_setup_fail,
+        0,
+        'test_discovery_stress finished',
+        extras=results)
+    asserts.explicit_pass('test_discovery_stress done', extras=results)
diff --git a/acts/tests/google/wifi/aware/stress/MessagesStressTest.py b/acts/tests/google/wifi/aware/stress/MessagesStressTest.py
new file mode 100644
index 0000000..5871d61
--- /dev/null
+++ b/acts/tests/google/wifi/aware/stress/MessagesStressTest.py
@@ -0,0 +1,269 @@
+#!/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
+
+from acts import asserts
+from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+
+KEY_ID = "id"
+KEY_TX_OK_COUNT = "tx_ok_count"
+KEY_TX_FAIL_COUNT = "tx_fail_count"
+KEY_RX_COUNT = "rx_count"
+
+
+class MessagesStressTest(AwareBaseTest):
+  """Set of stress tests for Wi-Fi Aware L2 (layer 2) message exchanges."""
+  NUM_ITERATIONS = 100
+  SERVICE_NAME = "GoogleTestServiceXY"
+
+  def __init__(self, controllers):
+    AwareBaseTest.__init__(self, controllers)
+
+  def init_info(self, msg, id, messages_by_msg, messages_by_id):
+    """Initialize the message data structures.
+
+    Args:
+      msg: message text
+      id: message id
+      messages_by_msg: {text -> {id, tx_ok_count, tx_fail_count, rx_count}}
+      messages_by_id: {id -> text}
+    """
+    messages_by_msg[msg] = {}
+    messages_by_msg[msg][KEY_ID] = id
+    messages_by_msg[msg][KEY_TX_OK_COUNT] = 0
+    messages_by_msg[msg][KEY_TX_FAIL_COUNT] = 0
+    messages_by_msg[msg][KEY_RX_COUNT] = 0
+    messages_by_id[id] = msg
+
+  def wait_for_tx_events(self, dut, num_msgs, messages_by_msg, messages_by_id):
+    """Wait for messages to be transmitted and update data structures.
+
+    Args:
+      dut: device under test
+      num_msgs: number of expected message tx
+      messages_by_msg: {text -> {id, tx_ok_count, tx_fail_count, rx_count}}
+      messages_by_id: {id -> text}
+    """
+    num_ok_tx_confirmations = 0
+    num_fail_tx_confirmations = 0
+    num_unexpected_ids = 0
+    tx_events_regex = "%s|%s" % (aconsts.SESSION_CB_ON_MESSAGE_SEND_FAILED,
+                                 aconsts.SESSION_CB_ON_MESSAGE_SENT)
+    while num_ok_tx_confirmations + num_fail_tx_confirmations < num_msgs:
+      try:
+        events = dut.ed.pop_events(tx_events_regex, autils.EVENT_TIMEOUT)
+        for event in events:
+          if (event["name"] != aconsts.SESSION_CB_ON_MESSAGE_SENT and
+              event["name"] != aconsts.SESSION_CB_ON_MESSAGE_SEND_FAILED):
+            asserts.fail("Unexpected event: %s" % event)
+          is_tx_ok = event["name"] == aconsts.SESSION_CB_ON_MESSAGE_SENT
+
+          id = event["data"][aconsts.SESSION_CB_KEY_MESSAGE_ID]
+          if id in messages_by_id:
+            msg = messages_by_id[id]
+            if is_tx_ok:
+              messages_by_msg[msg][
+                  KEY_TX_OK_COUNT] = messages_by_msg[msg][KEY_TX_OK_COUNT] + 1
+              if messages_by_msg[msg][KEY_TX_OK_COUNT] == 1:
+                num_ok_tx_confirmations = num_ok_tx_confirmations + 1
+            else:
+              messages_by_msg[msg][KEY_TX_FAIL_COUNT] = (
+                  messages_by_msg[msg][KEY_TX_FAIL_COUNT] + 1)
+              if messages_by_msg[msg][KEY_TX_FAIL_COUNT] == 1:
+                num_fail_tx_confirmations = num_fail_tx_confirmations + 1
+          else:
+            self.log.warning(
+                "Tx confirmation of unknown message ID received: %s", event)
+            num_unexpected_ids = num_unexpected_ids + 1
+      except queue.Empty:
+        self.log.warning("[%s] Timed out waiting for any MESSAGE_SEND* event - "
+                         "assuming the rest are not coming", dut.pretty_name)
+        break
+
+    return (num_ok_tx_confirmations, num_fail_tx_confirmations,
+            num_unexpected_ids)
+
+  def wait_for_rx_events(self, dut, num_msgs, messages_by_msg):
+    """Wait for messages to be received and update data structures
+
+    Args:
+      dut: device under test
+      num_msgs: number of expected messages to receive
+      messages_by_msg: {text -> {id, tx_ok_count, tx_fail_count, rx_count}}
+    """
+    num_rx_msgs = 0
+    while num_rx_msgs < num_msgs:
+      try:
+        event = dut.ed.pop_event(aconsts.SESSION_CB_ON_MESSAGE_RECEIVED,
+                                 autils.EVENT_TIMEOUT)
+        msg = event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING]
+        if msg not in messages_by_msg:
+          messages_by_msg[msg] = {}
+          messages_by_msg[msg][KEY_ID] = -1
+          messages_by_msg[msg][KEY_TX_OK_COUNT] = 0
+          messages_by_msg[msg][KEY_TX_FAIL_COUNT] = 0
+          messages_by_msg[msg][KEY_RX_COUNT] = 1
+
+        messages_by_msg[msg][
+            KEY_RX_COUNT] = messages_by_msg[msg][KEY_RX_COUNT] + 1
+        if messages_by_msg[msg][KEY_RX_COUNT] == 1:
+          num_rx_msgs = num_rx_msgs + 1
+      except queue.Empty:
+        self.log.warning(
+            "[%s] Timed out waiting for ON_MESSAGE_RECEIVED event - "
+            "assuming the rest are not coming", dut.pretty_name)
+        break
+
+  def analyze_results(self, results, messages_by_msg):
+    """Analyze the results of the stress message test and add to the results
+    dictionary
+
+    Args:
+      results: result dictionary into which to add data
+      messages_by_msg: {text -> {id, tx_ok_count, tx_fail_count, rx_count}}
+    """
+    results["raw_data"] = messages_by_msg
+    results["tx_count_success"] = 0
+    results["tx_count_duplicate_success"] = 0
+    results["tx_count_fail"] = 0
+    results["tx_count_duplicate_fail"] = 0
+    results["tx_count_neither"] = 0
+    results["tx_count_tx_ok_but_no_rx"] = 0
+    results["rx_count"] = 0
+    results["rx_count_duplicate"] = 0
+    results["rx_count_no_ok_tx_indication"] = 0
+    results["rx_count_fail_tx_indication"] = 0
+    results["rx_count_no_tx_message"] = 0
+
+    for msg, data in messages_by_msg.items():
+      if data[KEY_TX_OK_COUNT] > 0:
+        results["tx_count_success"] = results["tx_count_success"] + 1
+      if data[KEY_TX_OK_COUNT] > 1:
+        results["tx_count_duplicate_success"] = (
+            results["tx_count_duplicate_success"] + 1)
+      if data[KEY_TX_FAIL_COUNT] > 0:
+        results["tx_count_fail"] = results["tx_count_fail"] + 1
+      if data[KEY_TX_FAIL_COUNT] > 1:
+        results[
+            "tx_count_duplicate_fail"] = results["tx_count_duplicate_fail"] + 1
+      if (data[KEY_TX_OK_COUNT] == 0 and data[KEY_TX_FAIL_COUNT] == 0 and
+          data[KEY_ID] != -1):
+        results["tx_count_neither"] = results["tx_count_neither"] + 1
+      if data[KEY_TX_OK_COUNT] > 0 and data[KEY_RX_COUNT] == 0:
+        results["tx_count_tx_ok_but_no_rx"] = (
+            results["tx_count_tx_ok_but_no_rx"] + 1)
+      if data[KEY_RX_COUNT] > 0:
+        results["rx_count"] = results["rx_count"] + 1
+      if data[KEY_RX_COUNT] > 1:
+        results["rx_count_duplicate"] = results["rx_count_duplicate"] + 1
+      if data[KEY_RX_COUNT] > 0 and data[KEY_TX_OK_COUNT] == 0:
+        results["rx_count_no_ok_tx_indication"] = (
+            results["rx_count_no_ok_tx_indication"] + 1)
+      if data[KEY_RX_COUNT] > 0 and data[KEY_TX_FAIL_COUNT] > 0:
+        results["rx_count_fail_tx_indication"] = (
+            results["rx_count_fail_tx_indication"] + 1)
+      if data[KEY_RX_COUNT] > 0 and data[KEY_ID] == -1:
+        results[
+            "rx_count_no_tx_message"] = results["rx_count_no_tx_message"] + 1
+
+  #######################################################################
+
+  def test_stress_message(self):
+    """Stress test for bi-directional message transmission and reception."""
+    p_dut = self.android_devices[0]
+    s_dut = self.android_devices[1]
+
+    # Start up a discovery session
+    discovery_data = autils.create_discovery_pair(
+        p_dut,
+        s_dut,
+        p_config=autils.create_discovery_config(
+            self.SERVICE_NAME, aconsts.PUBLISH_TYPE_UNSOLICITED),
+        s_config=autils.create_discovery_config(self.SERVICE_NAME,
+                                                aconsts.SUBSCRIBE_TYPE_PASSIVE),
+        device_startup_offset=self.device_startup_offset,
+        msg_id=self.get_next_msg_id())
+    p_id = discovery_data[0]
+    s_id = discovery_data[1]
+    p_disc_id = discovery_data[2]
+    s_disc_id = discovery_data[3]
+    peer_id_on_sub = discovery_data[4]
+    peer_id_on_pub = discovery_data[5]
+
+    # Store information on Tx & Rx messages
+    messages_by_msg = {}  # keyed by message text
+    # {text -> {id, tx_ok_count, tx_fail_count, rx_count}}
+    messages_by_id = {}  # keyed by message ID {id -> text}
+
+    # send all messages at once (one in each direction)
+    for i in range(self.NUM_ITERATIONS):
+      msg_p2s = "Message Publisher -> Subscriber #%d" % i
+      next_msg_id = self.get_next_msg_id()
+      self.init_info(msg_p2s, next_msg_id, messages_by_msg, messages_by_id)
+      p_dut.droid.wifiAwareSendMessage(p_disc_id, peer_id_on_pub, next_msg_id,
+                                       msg_p2s, 0)
+
+      msg_s2p = "Message Subscriber -> Publisher #%d" % i
+      next_msg_id = self.get_next_msg_id()
+      self.init_info(msg_s2p, next_msg_id, messages_by_msg, messages_by_id)
+      s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub, next_msg_id,
+                                       msg_s2p, 0)
+
+    # wait for message tx confirmation
+    (p_tx_ok_count, p_tx_fail_count, p_tx_unknown_id) = self.wait_for_tx_events(
+        p_dut, self.NUM_ITERATIONS, messages_by_msg, messages_by_id)
+    (s_tx_ok_count, s_tx_fail_count, s_tx_unknown_id) = self.wait_for_tx_events(
+        s_dut, self.NUM_ITERATIONS, messages_by_msg, messages_by_id)
+    self.log.info("Transmission done: pub=%d, sub=%d transmitted successfully",
+                  p_tx_ok_count, s_tx_ok_count)
+
+    # wait for message rx confirmation (giving it the total number of messages
+    # transmitted rather than just those transmitted correctly since sometimes
+    # the Tx doesn't get that information correctly. I.e. a message the Tx
+    # thought was not transmitted correctly is actually received - missing ACK?
+    # bug?)
+    self.wait_for_rx_events(p_dut, self.NUM_ITERATIONS, messages_by_msg)
+    self.wait_for_rx_events(s_dut, self.NUM_ITERATIONS, messages_by_msg)
+
+    # analyze results
+    results = {}
+    results["tx_count"] = 2 * self.NUM_ITERATIONS
+    results["tx_unknown_ids"] = p_tx_unknown_id + s_tx_unknown_id
+    self.analyze_results(results, messages_by_msg)
+
+    # clear errors
+    asserts.assert_equal(results["tx_unknown_ids"], 0, "Message ID corruption",
+                         results)
+    asserts.assert_equal(results["tx_count_duplicate_fail"], 0,
+                         "Duplicate Tx fail messages", results)
+    asserts.assert_equal(results["tx_count_duplicate_success"], 0,
+                         "Duplicate Tx success messages", results)
+    asserts.assert_equal(results["rx_count_no_tx_message"], 0,
+                         "Rx message which wasn't sent - message corruption?",
+                         results)
+    asserts.assert_equal(results["tx_count_tx_ok_but_no_rx"], 0,
+                         "Tx got ACK but Rx didn't get message", results)
+
+    # possibly ok - but flag since most frequently a bug
+    asserts.assert_equal(results["rx_count_no_ok_tx_indication"], 0,
+                         "Message received but Tx didn't get ACK", results)
+    asserts.assert_equal(results["rx_count_fail_tx_indication"], 0,
+                         "Message received but Tx didn't get ACK", results)
+
+    asserts.explicit_pass("test_stress_message done", extras=results)
diff --git a/acts/tests/google/wifi/aware/stress/stress b/acts/tests/google/wifi/aware/stress/stress
new file mode 100644
index 0000000..0860507
--- /dev/null
+++ b/acts/tests/google/wifi/aware/stress/stress
@@ -0,0 +1,3 @@
+MessagesStressTest
+DataPathStressTest
+DiscoveryStressTest
\ No newline at end of file
diff --git a/acts/tests/sample/RelayDeviceSampleTest.py b/acts/tests/sample/RelayDeviceSampleTest.py
old mode 100755
new mode 100644
index e4622ed..040ef62
--- a/acts/tests/sample/RelayDeviceSampleTest.py
+++ b/acts/tests/sample/RelayDeviceSampleTest.py
@@ -17,6 +17,7 @@
 from acts import test_runner
 from acts.controllers.relay_lib.relay import SynchronizeRelays
 
+
 class RelayDeviceSampleTest(base_test.BaseTestClass):
     """ Demonstrates example usage of a configurable access point."""
 
@@ -27,7 +28,7 @@
         # You can use this workaround to get devices by name:
 
         relay_rig = self.relay_devices[0].rig
-        #self.other_relay_device = relay_rig.devices['UniqueDeviceName']
+        self.other_relay_device = relay_rig.devices['UniqueDeviceName']
         # Note: If the "devices" key from the config is missing
         # a GenericRelayDevice that contains every switch in the config
         # will be stored in relay_devices[0]. Its name will be
@@ -43,13 +44,61 @@
         # Unless overridden, the default state is all switches set to off.
         self.relay_device.clean_up()
 
+    # Typical use of a GenericRelayDevice looks like this:
+    def test_relay_device(self):
+
+        # This function call will sleep until .25 seconds are up.
+        # Blocking_nc_for will emulate a button press, which turns on the relay
+        # (or stays on if it already was on) for the given time, and then turns
+        # off.
+        self.relay_device.relays['BT_Power_Button'].set_nc_for(.25)
+
+        # do_something_after_turning_on_bt_power()
+
+        # Note that the relays are mechanical switches, and do take real time
+        # to go from one state to the next.
+
+        self.relay_device.relays['BT_Pair'].set_nc()
+
+        # do_something_while_holding_down_the_pair_button()
+
+        self.relay_device.relays['BT_Pair'].set_no()
+
+        # do_something_after_releasing_bt_pair()
+
+        # Note that although cleanup sets the relays to the 'NO' state after
+        # each test, they do not press things like the power button to turn
+        # off whatever hardware is attached. When using a GenericRelayDevice,
+        # you'll have to do this manually.
+        # Other RelayDevices may handle this for you in their clean_up() call.
+        self.relay_device.relays['BT_Power_Button'].set_nc_for(.25)
 
     def test_toggling(self):
         # This test just spams the toggle on each relay.
-        ## print(self.relay_device.relays.mac_address)
-        print("--->" + self.relay_device.mac_address + "<---")
         for _ in range(0, 2):
-            self.relay_device.relays['Play'].toggle()
+            self.relay_device.relays['BT_Power_Button'].toggle()
+            self.relay_device.relays['BT_Pair'].toggle()
+            self.relay_device.relays['BT_Reset'].toggle()
+            self.relay_device.relays['BT_SomethingElse'].toggle()
+
+    def test_synchronize_relays(self):
+        """Toggles relays using SynchronizeRelays().
+
+        This makes each relay do it's action at the same time, without waiting
+        after each relay to swap. Instead, all relays swap at the same time, and
+        the wait is done after exiting the with statement.
+        """
+        for _ in range(0, 10):
+            with SynchronizeRelays():
+                self.relay_device.relays['BT_Power_Button'].toggle()
+                self.relay_device.relays['BT_Pair'].toggle()
+                self.relay_device.relays['BT_Reset'].toggle()
+                self.relay_device.relays['BT_SomethingElse'].toggle()
+
+        # For more fine control over the wait time of relays, you can set
+        # Relay.transition_wait_time. This is not recommended unless you are
+        # using solid state relays, or async calls.
+
 
 if __name__ == "__main__":
     test_runner.main()
diff --git a/tools/.gitignore b/tools/.gitignore
new file mode 100644
index 0000000..c436601
--- /dev/null
+++ b/tools/.gitignore
@@ -0,0 +1,67 @@
+.DS_Store
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+.hypothesis/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+#Ipython Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
diff --git a/tools/commit_message_check.py b/tools/commit_message_check.py
new file mode 100755
index 0000000..092c18a
--- /dev/null
+++ b/tools/commit_message_check.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+#
+#   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.
+from __future__ import print_function
+
+import logging
+import os
+import re
+import sys
+
+from acts.libs.proc import job
+
+GLOBAL_KEYWORDS_FILEPATH = 'vendor/google_testing/comms/framework/etc/' \
+                           'commit_keywords'
+LOCAL_KEYWORDS_FILEPATH = '~/.repo_acts_commit_keywords'
+
+FIND_COMMIT_KEYWORDS = 'git log @{u}..| grep -i %s'
+GET_EMAIL_ADDRESS = 'git log --format=%ce -1'
+
+
+def main(argv):
+    file_path = os.path.join(
+        os.path.dirname(__file__), "../../../../%s" % GLOBAL_KEYWORDS_FILEPATH)
+
+    if not os.path.isfile(file_path):
+        file_path = os.path.expanduser(LOCAL_KEYWORDS_FILEPATH)
+        if not os.path.exists(file_path) or not os.path.isfile(file_path):
+            result = job.run(GET_EMAIL_ADDRESS)
+            if result.stdout.endswith('@google.com'):
+                logging.error(
+                    'You do not have the necessary file %s. Please run '
+                    'tools/ignore_commit_keywords.sh, or link it with the '
+                    'following command:\n ln -sf <internal_master_root>/%s %s'
+                    % (LOCAL_KEYWORDS_FILEPATH, GLOBAL_KEYWORDS_FILEPATH,
+                       LOCAL_KEYWORDS_FILEPATH))
+                exit(1)
+            return
+
+    with open(file_path) as file:
+        # Places every line within quotes with -e before them.
+        # i.e. '-e "line1" -e "line2" -e "line3"'
+        grep_args = re.sub('^(.+?)$\n?', ' -e "\\1"',
+                           file.read(), 0, re.MULTILINE)
+
+    result = job.run(FIND_COMMIT_KEYWORDS % grep_args, ignore_status=True)
+
+    if result.stdout:
+        logging.error('Your commit message contains at least one keyword.')
+        logging.error('Keyword(s) found in the following line(s):')
+        logging.error(result.stdout)
+        logging.error('Please fix/remove these before committing.')
+        exit(1)
+
+
+if __name__ == '__main__':
+    main(sys.argv[1:])
diff --git a/tools/ignore_commit_keywords.sh b/tools/ignore_commit_keywords.sh
new file mode 100755
index 0000000..3a564b8
--- /dev/null
+++ b/tools/ignore_commit_keywords.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+#   Copyright 2016 - 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.
+
+touch ~/.repo_acts_commit_keywords
+echo "Ignoring repo commit keywords list."
diff --git a/tools/lab/README.md b/tools/lab/README.md
new file mode 100644
index 0000000..0b2ebbb
--- /dev/null
+++ b/tools/lab/README.md
@@ -0,0 +1 @@
+This folder contains tools to be used in the testing lab.
diff --git a/tools/lab/main.py b/tools/lab/main.py
new file mode 100755
index 0000000..873f682
--- /dev/null
+++ b/tools/lab/main.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python
+#
+#   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.
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import argparse
+import sys
+
+from runner import InstantRunner
+from metrics.usb_metric import UsbMetric
+from reporter import LoggerReporter
+
+
+class RunnerFactory(object):
+    _reporter_constructor = {
+        'logger': lambda: LoggerReporter(),
+    }
+
+    _metric_constructor = {
+        'usb_io': lambda param: UsbMetric(),
+        'disk': lambda param: DiskMetric(),
+        'uptime': lambda param: UptimeMetric(),
+        'verify_devices': lambda param: VerifyMetric(),
+        'ram': lambda param: RamMetric(),
+        'cpu': lambda param: CpuMetric(),
+    }
+
+    @classmethod
+    def create(cls, arguments):
+        """ Creates the Runner Class that will take care of gather metrics
+        and determining how to report those metrics.
+
+        Args:
+            arguments: The arguments passed in through command line, a dict.
+
+        Returns:
+            Returns a Runner that was created by passing in a list of
+            metrics and list of reporters.
+        """
+        arg_dict = arguments
+        metrics = []
+
+        # If no reporter specified, default to logger.
+        reporters = arg_dict.pop('reporter')
+        if reporters is None:
+            reporters = ['logger']
+
+        # Check keys and values to see what metrics to include.
+        for key in arg_dict:
+            val = arg_dict[key]
+            if val is not None:
+                metrics.append(cls._metric_constructor[key](val))
+
+        return InstantRunner(metrics, reporters)
+
+
+def _argparse():
+    parser = argparse.ArgumentParser(
+        description='Tool for getting lab health of android testing lab',
+        prog='Lab Health')
+
+    parser.add_argument(
+        '-v',
+        '--version',
+        action='version',
+        version='%(prog)s v0.1.0',
+        help='specify version of program')
+    parser.add_argument(
+        '-i',
+        '--usb-io',
+        action='store_true',
+        default=None,
+        help='display recent USB I/O')
+    parser.add_argument(
+        '-u',
+        '--uptime',
+        action='store_true',
+        default=None,
+        help='display uptime of current lab station')
+    parser.add_argument(
+        '-d',
+        '--disk',
+        choices=['size', 'used', 'avail', 'percent'],
+        nargs='*',
+        help='display the disk space statistics')
+    parser.add_argument(
+        '-ra',
+        '--ram',
+        action='store_true',
+        default=None,
+        help='display the current RAM usage')
+    parser.add_argument(
+        '-c',
+        '--cpu',
+        action='count',
+        default=None,
+        help='display the current CPU usage as percent')
+    parser.add_argument(
+        '-vd',
+        '--verify-devices',
+        action='store_true',
+        default=None,
+        help='verify all devices connected are in \'device\' mode')
+    parser.add_argument(
+        '-r',
+        '--reporter',
+        choices=['logger', 'proto', 'json'],
+        nargs='+',
+        help='choose the reporting method needed')
+    parser.add_argument(
+        '-p',
+        '--program',
+        choices=['python', 'adb', 'fastboot', 'os', 'kernel'],
+        nargs='*',
+        help='display the versions of chosen programs (default = all)')
+
+    return parser
+
+
+def main():
+    parser = _argparse()
+
+    if len(sys.argv) == 1:
+        parser.print_help()
+        sys.exit(1)
+
+    RunnerFactory().create(vars(parser.parse_args()))
+
+
+if __name__ == '__main__':
+    main()
diff --git a/tools/lab/main_test.py b/tools/lab/main_test.py
new file mode 100644
index 0000000..366767f
--- /dev/null
+++ b/tools/lab/main_test.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+#
+#   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 unittest
+
+from main import RunnerFactory
+from metrics.usb_metric import UsbMetric
+
+
+class RunnerFactoryTestCase(unittest.TestCase):
+    def test_create_with_reporter(self):
+        self.assertEqual(
+            RunnerFactory.create({
+                'reporter': ['proto']
+            }).reporter_list, ['proto'])
+
+    def test_create_without_reporter(self):
+        self.assertEqual(
+            RunnerFactory.create({
+                'reporter': None
+            }).reporter_list, ['logger'])
+
+    def test_metric_none(self):
+        self.assertEqual(
+            RunnerFactory.create({
+                'disk': None,
+                'reporter': None
+            }).metric_list, [])
+
+    def test_metric_true(self):
+        self.assertIsInstance(
+            RunnerFactory.create({
+                'usb_io': True,
+                'reporter': None
+            }).metric_list[0], UsbMetric)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tools/lab/metrics/__init__.py b/tools/lab/metrics/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/lab/metrics/__init__.py
diff --git a/tools/lab/metrics/disk_metric.py b/tools/lab/metrics/disk_metric.py
new file mode 100644
index 0000000..8357b42
--- /dev/null
+++ b/tools/lab/metrics/disk_metric.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+#
+#   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 metric
+
+
+class DiskMetric(metric.Metric):
+
+    COMMAND = "df /var/tmp"
+    # Fields for response dictionary
+    TOTAL = 'total'
+    USED = 'used'
+    AVAIL = 'avail'
+    PERCENT_USED = 'percent_used'
+
+    def gather_metric(self):
+        """Finds disk space metrics for /var/tmp
+
+        Returns:
+            A dict with the following fields:
+                total: int representing total space in 1k blocks
+                used: int representing total used in 1k blocks
+                avail: int representing total available in 1k blocks
+                percent_used: int representing percentage space used (0-100)
+        """
+        # Run shell command
+        result = self._shell.run(self.COMMAND)
+        """Example stdout:
+        Filesystem     1K-blocks     Used Available Use% Mounted on
+        /dev/dm-1       57542652 18358676  36237928  34% /
+        """
+        # Get only second line
+        output = result.stdout.splitlines()[1]
+        # Split by space
+        fields = output.split()
+        # Create response dictionary
+        response = {
+            self.TOTAL: int(fields[1]),
+            self.USED: int(fields[2]),
+            self.AVAIL: int(fields[3]),
+            # Strip the percentage symbol
+            self.PERCENT_USED: int(fields[4][:-1])
+        }
+        return (response)
diff --git a/tools/lab/metrics/metric.py b/tools/lab/metrics/metric.py
new file mode 100644
index 0000000..9f54237
--- /dev/null
+++ b/tools/lab/metrics/metric.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+#
+#   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.
+
+from utils import job
+from utils import shell
+
+
+class Metric(object):
+    """Interface class for metric gathering.
+
+    Attributes:
+        _shell: a shell.ShellCommand object
+    """
+
+    def __init__(self, shell=shell.ShellCommand(job)):
+        self._shell = shell
+
+    def gather_metric(self):
+        """Gathers all values that this metric watches.
+
+        Mandatory for every class that extends Metric. Should always return.
+
+        Returns:
+          A dict mapping keys (class level constant strings representing
+          fields of a metric) to their statistics.
+
+        Raises:
+          NotImplementedError: A metric did not implement this function.
+        """
+        raise NotImplementedError()
diff --git a/tools/lab/metrics/usb_metric.py b/tools/lab/metrics/usb_metric.py
new file mode 100644
index 0000000..2bead7a
--- /dev/null
+++ b/tools/lab/metrics/usb_metric.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+#
+#   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.
+
+from metrics.metric import Metric
+import job
+
+
+class UsbMetric(Metric):
+    def check_usbmon(self):
+        try:
+            job.run('grep usbmon /proc/modules')
+        except job.Error:
+            print('Kernel module not loaded, attempting to load usbmon')
+            result = job.run('modprobe usbmon', ignore_status=True)
+            if result.exit_status != 0:
+                print result.stderr
+
+    def gather_metric(self):
+        self.check_usbmon()
diff --git a/tools/lab/reporter.py b/tools/lab/reporter.py
new file mode 100644
index 0000000..ce11412
--- /dev/null
+++ b/tools/lab/reporter.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+#
+#   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.
+
+
+class Reporter(object):
+    """ Base class for the multiple ways to report the data gathered.
+
+    The method report takes in a dictionary where the key is the class that
+    generated the value, and the value is the actual data gathered from
+    collecting that metric. For example, an UptimeMetric, would have
+    UptimeMetric() as key, and '1-02:22:42' as the value.
+    """
+
+    def report(self, responses):
+        raise NotImplementedError('Must implement this method')
+
+
+class LoggerReporter(Reporter):
+    def report(self, response_dict):
+        for key in response_dict:
+            print(response_dict[key])
+
+
+class FileReporter(Reporter):
+    pass
diff --git a/tools/lab/runner.py b/tools/lab/runner.py
new file mode 100644
index 0000000..7506938
--- /dev/null
+++ b/tools/lab/runner.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+#
+#   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.
+
+
+class Runner:
+    """Calls metrics and passes response to reporters.
+
+    Attributes:
+        metric_list: a list of metric objects
+        reporter_list: a list of reporter objects
+        object and value is dictionary returned by that response
+    """
+
+    def __init__(self, metric_list, reporter_list):
+        self.metric_list = metric_list
+        self.reporter_list = reporter_list
+
+    def run(self):
+        """Calls metrics and passes response to reporters."""
+        raise NotImplementedError()
+
+
+class InstantRunner(Runner):
+    def run(self):
+        """Calls all metrics, passes responses to reporters."""
+        responses = {}
+        for metric in self.metric_list:
+            responses[metric] = metric.gatherMetric()
+        for reporter in self.reporter_list:
+            reporter.report(responses)
diff --git a/tools/lab/test_main.py b/tools/lab/test_main.py
new file mode 100755
index 0000000..48dc714
--- /dev/null
+++ b/tools/lab/test_main.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+#
+#   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 unittest
+
+if __name__ == "__main__":
+    suite = unittest.TestLoader().discover('.', pattern="*_test.py")
+    unittest.TextTestRunner().run(suite)
diff --git a/tools/lab/tests/__init__.py b/tools/lab/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/lab/tests/__init__.py
diff --git a/tools/lab/tests/disk_metric_test.py b/tools/lab/tests/disk_metric_test.py
new file mode 100755
index 0000000..339ed49
--- /dev/null
+++ b/tools/lab/tests/disk_metric_test.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+#
+#   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 unittest
+
+from metrics import disk_metric
+from tests import fake
+
+
+class DiskMetricTest(unittest.TestCase):
+    """Class for testing DiskMetric."""
+
+    def setUp(self):
+        pass
+
+    def test_return_total_used_avail_percent(self):
+        # Create sample stdout string ShellCommand.run() would return
+        stdout_string = ('Filesystem     1K-blocks     Used Available Use% '
+                         'mounted on\n/dev/dm-1       57542652 18358676 '
+                         '36237928  34% /')
+        self.FAKE_RESULT = fake.FakeResult(stdout=stdout_string)
+        fake_shell = fake.MockShellCommand(fake_result=self.FAKE_RESULT)
+        self.metric_obj = disk_metric.DiskMetric(shell=fake_shell)
+
+        expected_result = {
+            disk_metric.DiskMetric.TOTAL: 57542652,
+            disk_metric.DiskMetric.USED: 18358676,
+            disk_metric.DiskMetric.AVAIL: 36237928,
+            disk_metric.DiskMetric.PERCENT_USED: 34
+        }
+        self.assertEqual(expected_result, self.metric_obj.gather_metric())
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tools/lab/tests/fake.py b/tools/lab/tests/fake.py
new file mode 100644
index 0000000..628a725
--- /dev/null
+++ b/tools/lab/tests/fake.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+#
+#   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.
+
+
+class FakeResult(object):
+    """A fake version of the object returned from ShellCommand.run. """
+
+    def __init__(self, exit_status=1, stdout='', stderr=''):
+        self.exit_status = exit_status
+        self.stdout = stdout
+        self.stderr = stderr
+
+
+class MockShellCommand(object):
+    """A fake ShellCommand object.
+
+    Attributes:
+        fake_result: a FakeResult object
+    """
+
+    def __init__(self, fake_result):
+        self._fake_result = fake_result
+
+    """Returns a FakeResult object.
+
+    Args:
+        Same as ShellCommand.run, but none are used in function
+
+    Returns:
+        The FakeResult object it was initalized with
+    """
+
+    def run(self, command, timeout=3600):
+        return self._fake_result
diff --git a/tools/lab/utils/__init__.py b/tools/lab/utils/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/lab/utils/__init__.py
diff --git a/tools/lab/utils/job.py b/tools/lab/utils/job.py
new file mode 100644
index 0000000..da565c7
--- /dev/null
+++ b/tools/lab/utils/job.py
@@ -0,0 +1,201 @@
+# 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 logging
+import os
+import shlex
+import sys
+import time
+
+if os.name == 'posix' and sys.version_info[0] < 3:
+    import subprocess32 as subprocess
+    DEVNULL = open(os.devnull, 'wb')
+else:
+    import subprocess
+    # Only exists in python3.3
+    from subprocess import DEVNULL
+
+
+class Error(Exception):
+    """Indicates that a command failed, is fatal to the test unless caught."""
+
+    def __init__(self, result):
+        super(Error, self).__init__(result)
+        self.result = result
+
+
+class TimeoutError(Error):
+    """Thrown when a BackgroundJob times out on wait."""
+
+
+class Result(object):
+    """Command execution result.
+
+    Contains information on subprocess execution after it has exited.
+
+    Attributes:
+        command: An array containing the command and all arguments that
+                 was executed.
+        exit_status: Integer exit code of the process.
+        stdout_raw: The raw bytes output from standard out.
+        stderr_raw: The raw bytes output from standard error
+        duration: How long the process ran for.
+        did_timeout: True if the program timed out and was killed.
+    """
+
+    @property
+    def stdout(self):
+        """String representation of standard output."""
+        if not self._stdout_str:
+            self._stdout_str = self._raw_stdout.decode(encoding=self._encoding)
+            self._stdout_str = self._stdout_str.strip()
+        return self._stdout_str
+
+    @property
+    def stderr(self):
+        """String representation of standard error."""
+        if not self._stderr_str:
+            self._stderr_str = self._raw_stderr.decode(encoding=self._encoding)
+            self._stderr_str = self._stderr_str.strip()
+        return self._stderr_str
+
+    def __init__(self,
+                 command=[],
+                 stdout=bytes(),
+                 stderr=bytes(),
+                 exit_status=None,
+                 duration=0,
+                 did_timeout=False,
+                 encoding='utf-8'):
+        """
+        Args:
+            command: The command that was run. This will be a list containing
+                     the executed command and all args.
+            stdout: The raw bytes that standard output gave.
+            stderr: The raw bytes that standard error gave.
+            exit_status: The exit status of the command.
+            duration: How long the command ran.
+            did_timeout: True if the command timed out.
+            encoding: The encoding standard that the program uses.
+        """
+        self.command = command
+        self.exit_status = exit_status
+        self._raw_stdout = stdout
+        self._raw_stderr = stderr
+        self._stdout_str = None
+        self._stderr_str = None
+        self._encoding = encoding
+        self.duration = duration
+        self.did_timeout = did_timeout
+
+    def __repr__(self):
+        return ('job.Result(command=%r, stdout=%r, stderr=%r, exit_status=%r, '
+                'duration=%r, did_timeout=%r, encoding=%r)') % (
+                    self.command, self._raw_stdout, self._raw_stderr,
+                    self.exit_status, self.duration, self.did_timeout,
+                    self._encoding)
+
+
+def run(command,
+        timeout=60,
+        ignore_status=False,
+        env=None,
+        io_encoding='utf-8'):
+    """Execute a command in a subproccess and return its output.
+
+    Commands can be either shell commands (given as strings) or the
+    path and arguments to an executable (given as a list).  This function
+    will block until the subprocess finishes or times out.
+
+    Args:
+        command: The command to execute. Can be either a string or a list.
+        timeout: number seconds to wait for command to finish.
+        ignore_status: bool True to ignore the exit code of the remote
+                       subprocess.  Note that if you do ignore status codes,
+                       you should handle non-zero exit codes explicitly.
+        env: dict enviroment variables to setup on the remote host.
+        io_encoding: str unicode encoding of command output.
+
+    Returns:
+        A job.Result containing the results of the ssh command.
+
+    Raises:
+        job.TimeoutError: When the remote command took to long to execute.
+        Error: When the ssh connection failed to be created.
+        CommandError: Ssh worked, but the command had an error executing.
+    """
+    start_time = time.time()
+    proc = subprocess.Popen(
+        command,
+        env=env,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+        shell=not isinstance(command, list))
+    # Wait on the process terminating
+    timed_out = False
+    out = bytes()
+    err = bytes()
+    try:
+        (out, err) = proc.communicate(timeout=timeout)
+    except subprocess.TimeoutExpired:
+        timed_out = True
+        proc.kill()
+        proc.wait()
+
+    result = Result(
+        command=command,
+        stdout=out,
+        stderr=err,
+        exit_status=proc.returncode,
+        duration=time.time() - start_time,
+        encoding=io_encoding,
+        did_timeout=timed_out)
+    logging.debug(result)
+
+    if timed_out:
+        logging.error("Command %s with %s timeout setting timed out", command,
+                      timeout)
+        raise TimeoutError(result)
+
+    if not ignore_status and proc.returncode != 0:
+        raise Error(result)
+
+    return result
+
+
+def run_async(command, env=None):
+    """Execute a command in a subproccess asynchronously.
+
+    It is the callers responsibility to kill/wait on the resulting
+    subprocess.Popen object.
+
+    Commands can be either shell commands (given as strings) or the
+    path and arguments to an executable (given as a list).  This function
+    will not block.
+
+    Args:
+        command: The command to execute. Can be either a string or a list.
+        env: dict enviroment variables to setup on the remote host.
+
+    Returns:
+        A subprocess.Popen object representing the created subprocess.
+
+    """
+    return subprocess.Popen(
+        command,
+        env=env,
+        close_fds=True,
+        shell=not isinstance(command, list),
+        stdout=DEVNULL,
+        stderr=subprocess.STDOUT)
diff --git a/tools/lab/utils/shell.py b/tools/lab/utils/shell.py
new file mode 100644
index 0000000..5655dd4
--- /dev/null
+++ b/tools/lab/utils/shell.py
@@ -0,0 +1,238 @@
+# 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 shellescape
+import signal
+import time
+
+from utils import job
+
+
+class ShellCommand(object):
+    """Wraps basic commands that tend to be tied very closely to a shell.
+
+    This class is a wrapper for running basic shell commands through
+    any object that has a run command. Basic shell functionality for managing
+    the system, programs, and files in wrapped within this class.
+
+    Note: At the moment this only works with the ssh runner.
+    """
+
+    def __init__(self, runner, working_dir=None):
+        """Creates a new shell command invoker.
+
+        Args:
+            runner: The object that will run the shell commands.
+            working_dir: The directory that all commands should work in,
+                         if none then the runners enviroment default is used.
+        """
+        self._runner = runner
+        self._working_dir = working_dir
+
+    def run(self, command, timeout=3600):
+        """Runs a generic command through the runner.
+
+        Takes the command and prepares it to be run in the target shell using
+        this objects settings.
+
+        Args:
+            command: The command to run.
+            timeout: How long to wait for the command (in seconds).
+
+        Returns:
+            A CmdResult object containing the results of the shell command.
+
+        Raises:
+            job.Error: When the command executed but had an error.
+        """
+        if self._working_dir:
+            command_str = 'cd %s; %s' % (self._working_dir, command)
+        else:
+            command_str = command
+
+        return self._runner.run(command_str, timeout=timeout)
+
+    def is_alive(self, identifier):
+        """Checks to see if a program is alive.
+
+        Checks to see if a program is alive on the shells enviroment. This can
+        be used to check on generic programs, or a specific program using
+        a pid.
+
+        Args:
+            identifier: string or int, Used to identify the program to check.
+                        if given an int then it is assumed to be a pid. If
+                        given a string then it will be used as a search key
+                        to compare on the running processes.
+        Returns:
+            True if a process was found running, false otherwise.
+        """
+        try:
+            if isinstance(identifier, str):
+                self.run('ps aux | grep -v grep | grep %s' % identifier)
+            elif isinstance(identifier, int):
+                self.signal(identifier, 0)
+            else:
+                raise ValueError('Bad type was given for identifier')
+
+            return True
+        except job.Error:
+            return False
+
+    def get_pids(self, identifier):
+        """Gets the pids of a program.
+
+        Searches for a program with a specific name and grabs the pids for all
+        programs that match.
+
+        Args:
+            identifier: A search term that identifies the program.
+
+        Returns: An array of all pids that matched the identifier, or None
+                  if no pids were found.
+        """
+        try:
+            result = self.run('ps aux | grep -v grep | grep %s' % identifier)
+        except job.Error:
+            raise StopIteration
+
+        lines = result.stdout.splitlines()
+
+        # The expected output of the above command is like so:
+        # bob    14349  0.0  0.0  34788  5552 pts/2    Ss   Oct10   0:03 bash
+        # bob    52967  0.0  0.0  34972  5152 pts/4    Ss   Oct10   0:00 bash
+        # Where the format is:
+        # USER    PID  ...
+        for line in lines:
+            pieces = line.split()
+            yield int(pieces[1])
+
+    def search_file(self, search_string, file_name):
+        """Searches through a file for a string.
+
+        Args:
+            search_string: The string or pattern to look for.
+            file_name: The name of the file to search.
+
+        Returns:
+            True if the string or pattern was found, False otherwise.
+        """
+        try:
+            self.run('grep %s %s' % (shellescape.quote(search_string),
+                                     file_name))
+            return True
+        except job.Error:
+            return False
+
+    def read_file(self, file_name):
+        """Reads a file through the shell.
+
+        Args:
+            file_name: The name of the file to read.
+
+        Returns:
+            A string of the files contents.
+        """
+        return self.run('cat %s' % file_name).stdout
+
+    def write_file(self, file_name, data):
+        """Writes a block of data to a file through the shell.
+
+        Args:
+            file_name: The name of the file to write to.
+            data: The string of data to write.
+        """
+        return self.run('echo %s > %s' % (shellescape.quote(data), file_name))
+
+    def append_file(self, file_name, data):
+        """Appends a block of data to a file through the shell.
+
+        Args:
+            file_name: The name of the file to write to.
+            data: The string of data to write.
+        """
+        return self.run('echo %s >> %s' % (shellescape.quote(data), file_name))
+
+    def touch_file(self, file_name):
+        """Creates a file through the shell.
+
+        Args:
+            file_name: The name of the file to create.
+        """
+        self.write_file(file_name, '')
+
+    def delete_file(self, file_name):
+        """Deletes a file through the shell.
+
+        Args:
+            file_name: The name of the file to delete.
+        """
+        try:
+            self.run('rm %s' % file_name)
+        except job.Error as e:
+            if 'No such file or directory' in e.result.stderr:
+                return
+
+            raise
+
+    def kill(self, identifier, timeout=10):
+        """Kills a program or group of programs through the shell.
+
+        Kills all programs that match an identifier through the shell. This
+        will send an increasing queue of kill signals to all programs
+        that match the identifier until either all are dead or the timeout
+        finishes.
+
+        Programs are guranteed to be killed after running this command.
+
+        Args:
+            identifier: A string used to identify the program.
+            timeout: The time to wait for all programs to die. Each signal will
+                     take an equal portion of this time.
+        """
+        if isinstance(identifier, int):
+            pids = [identifier]
+        else:
+            pids = list(self.get_pids(identifier))
+
+        signal_queue = [signal.SIGINT, signal.SIGTERM, signal.SIGKILL]
+
+        signal_duration = timeout / len(signal_queue)
+        for sig in signal_queue:
+            for pid in pids:
+                try:
+                    self.signal(pid, sig)
+                except job.Error:
+                    pass
+
+            start_time = time.time()
+            while pids and time.time() - start_time < signal_duration:
+                time.sleep(0.1)
+                pids = [pid for pid in pids if self.is_alive(pid)]
+
+            if not pids:
+                break
+
+    def signal(self, pid, sig):
+        """Sends a specific signal to a program.
+
+        Args:
+            pid: The process id of the program to kill.
+            sig: The singal to send.
+
+        Raises:
+            job.Error: Raised when the signal fail to reach
+                       the specified program.
+        """
+        self.run('kill -%d %d' % (sig, pid))