Merge "CTS: Add tests for ScanResult" into rvc-dev
diff --git a/tests/cts/net/src/android/net/wifi/cts/WifiFrameworkInitializerTest.java b/tests/cts/net/src/android/net/wifi/cts/WifiFrameworkInitializerTest.java
new file mode 100644
index 0000000..d714ed6
--- /dev/null
+++ b/tests/cts/net/src/android/net/wifi/cts/WifiFrameworkInitializerTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+package android.net.wifi.cts;
+
+import android.net.wifi.WifiFrameworkInitializer;
+import android.test.AndroidTestCase;
+
+public class WifiFrameworkInitializerTest extends AndroidTestCase {
+    /**
+     * WifiFrameworkInitializer.registerServiceWrappers() should only be called by
+     * SystemServiceRegistry during boot up when Wifi is first initialized. Calling this API at
+     * any other time should throw an exception.
+     */
+    public void testRegisterServiceWrappers_failsWhenCalledOutsideOfSystemServiceRegistry() {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        try {
+            WifiFrameworkInitializer.registerServiceWrappers();
+            fail("Expected exception when calling "
+                    + "WifiFrameworkInitializer.registerServiceWrappers() outside of "
+                    + "SystemServiceRegistry!");
+        } catch (IllegalStateException expected) {}
+    }
+}
diff --git a/tests/cts/net/src/android/net/wifi/cts/WifiInfoTest.java b/tests/cts/net/src/android/net/wifi/cts/WifiInfoTest.java
index 6f94fea..557710d 100644
--- a/tests/cts/net/src/android/net/wifi/cts/WifiInfoTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/WifiInfoTest.java
@@ -33,6 +33,7 @@
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.SystemUtil;
 
+import java.nio.charset.StandardCharsets;
 import java.util.concurrent.Callable;
 
 @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
@@ -49,6 +50,12 @@
     private static final int STATE_WIFI_CHANGING = 1;
     private static final int STATE_WIFI_CHANGED = 2;
 
+    private static final String TEST_SSID = "Test123";
+    private static final String TEST_BSSID = "12:12:12:12:12:12";
+    private static final int TEST_RSSI = -60;
+    private static final int TEST_NETWORK_ID = 5;
+    private static final int TEST_NETWORK_ID2 = 6;
+
     private static final String TAG = "WifiInfoTest";
     private static final int TIMEOUT_MSEC = 6000;
     private static final int WAIT_MSEC = 60;
@@ -207,4 +214,42 @@
         assertThat(wifiInfo.getMaxSupportedTxLinkSpeedMbps()).isAtLeast(-1);
         assertThat(wifiInfo.getMaxSupportedRxLinkSpeedMbps()).isAtLeast(-1);
     }
+
+    /**
+     * Test that the WifiInfo Builder returns the same values that was set, and that
+     * calling build multiple times returns different instances.
+     */
+    public void testWifiInfoBuilder() throws Exception {
+        WifiInfo.Builder builder = new WifiInfo.Builder()
+                .setSsid(TEST_SSID.getBytes(StandardCharsets.UTF_8))
+                .setBssid(TEST_BSSID)
+                .setRssi(TEST_RSSI)
+                .setNetworkId(TEST_NETWORK_ID);
+
+        WifiInfo info1 = builder.build();
+
+        assertThat(info1.getSSID()).isEqualTo("\"" + TEST_SSID + "\"");
+        assertThat(info1.getBSSID()).isEqualTo(TEST_BSSID);
+        assertThat(info1.getRssi()).isEqualTo(TEST_RSSI);
+        assertThat(info1.getNetworkId()).isEqualTo(TEST_NETWORK_ID);
+
+        WifiInfo info2 = builder
+                .setNetworkId(TEST_NETWORK_ID2)
+                .build();
+
+        // different instances
+        assertThat(info1).isNotSameAs(info2);
+
+        // assert that info1 didn't change
+        assertThat(info1.getSSID()).isEqualTo("\"" + TEST_SSID + "\"");
+        assertThat(info1.getBSSID()).isEqualTo(TEST_BSSID);
+        assertThat(info1.getRssi()).isEqualTo(TEST_RSSI);
+        assertThat(info1.getNetworkId()).isEqualTo(TEST_NETWORK_ID);
+
+        // assert that info2 changed
+        assertThat(info2.getSSID()).isEqualTo("\"" + TEST_SSID + "\"");
+        assertThat(info2.getBSSID()).isEqualTo(TEST_BSSID);
+        assertThat(info2.getRssi()).isEqualTo(TEST_RSSI);
+        assertThat(info2.getNetworkId()).isEqualTo(TEST_NETWORK_ID2);
+    }
 }
diff --git a/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java b/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java
index 743454f..7e9e97d 100644
--- a/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java
@@ -16,6 +16,12 @@
 
 package android.net.wifi.cts;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static android.net.NetworkCapabilitiesProto.TRANSPORT_WIFI;
+import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT;
+
+import static org.junit.Assert.assertNotEquals;
 
 import android.app.UiAutomation;
 import android.content.BroadcastReceiver;
@@ -26,8 +32,12 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.net.ConnectivityManager;
+import android.net.LinkProperties;
 import android.net.MacAddress;
+import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
+import android.net.NetworkRequest;
 import android.net.wifi.ScanResult;
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.WifiConfiguration;
@@ -46,12 +56,16 @@
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.SystemUtil;
 
+import java.net.HttpURLConnection;
+import java.net.URL;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
@@ -62,11 +76,12 @@
     }
 
     private WifiManager mWifiManager;
+    private ConnectivityManager mConnectivityManager;
     private WifiLock mWifiLock;
     private static MySync mMySync;
     private List<ScanResult> mScanResults = null;
     private NetworkInfo mNetworkInfo;
-    private Object mLOHSLock = new Object();
+    private Object mLock = new Object();
     private UiDevice mUiDevice;
 
     // Please refer to WifiManager
@@ -88,7 +103,7 @@
     private static final int SCAN_TIMEOUT_MSEC = 9000;
     private static final int TIMEOUT_MSEC = 6000;
     private static final int WAIT_MSEC = 60;
-    private static final int DURATION = 10000;
+    private static final int DURATION = 10_000;
     private static final int DURATION_SCREEN_TOGGLE = 2000;
     private static final int WIFI_SCAN_TEST_INTERVAL_MILLIS = 60 * 1000;
     private static final int WIFI_SCAN_TEST_CACHE_DELAY_MILLIS = 3 * 60 * 1000;
@@ -165,6 +180,7 @@
 
         mContext.registerReceiver(mReceiver, mIntentFilter);
         mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
+        mConnectivityManager = getContext().getSystemService(ConnectivityManager.class);
         assertNotNull(mWifiManager);
         mWifiLock = mWifiManager.createWifiLock(TAG);
         mWifiLock.acquire();
@@ -177,6 +193,10 @@
         synchronized (mMySync) {
             mMySync.expectedState = STATE_NULL;
         }
+
+        List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mWifiManager, (wm) -> wm.getConfiguredNetworks());
+        assertFalse("Need at least one saved network", savedNetworks.isEmpty());
     }
 
     @Override
@@ -242,15 +262,24 @@
         }
     }
 
-    private void connectWifi() throws Exception {
+    private void waitForNetworkInfoState(NetworkInfo.State state) throws Exception {
         synchronized (mMySync) {
-            if (mNetworkInfo.getState() == NetworkInfo.State.CONNECTED) return;
+            if (mNetworkInfo.getState() == state) return;
             long timeout = System.currentTimeMillis() + TIMEOUT_MSEC;
             while (System.currentTimeMillis() < timeout
-                    && mNetworkInfo.getState() != NetworkInfo.State.CONNECTED)
+                    && mNetworkInfo.getState() != state)
                 mMySync.wait(WAIT_MSEC);
-            assertTrue(mNetworkInfo.getState() == NetworkInfo.State.CONNECTED);
+            assertTrue(mNetworkInfo.getState() == state);
         }
+
+    }
+
+    private void waitForConnection() throws Exception {
+        waitForNetworkInfoState(NetworkInfo.State.CONNECTED);
+    }
+
+    private void waitForDisconnection() throws Exception {
+        waitForNetworkInfoState(NetworkInfo.State.DISCONNECTED);
     }
 
     private boolean existSSID(String ssid) {
@@ -423,6 +452,33 @@
         assertTrue(WifiManager.compareSignalLevel(rssiA, rssiB) > 0);
     }
 
+    /**
+     * Test that {@link WifiManager#calculateSignalLevel(int)} returns a value in the range
+     * [0, {@link WifiManager#getMaxSignalLevel()}], and its value is monotonically increasing as
+     * the RSSI increases.
+     */
+    public void testCalculateSignalLevel() {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+
+        int maxSignalLevel = mWifiManager.getMaxSignalLevel();
+
+        int prevSignalLevel = 0;
+        for (int rssi = -150; rssi <= 50; rssi++) {
+            int signalLevel = mWifiManager.calculateSignalLevel(rssi);
+
+            // between [0, maxSignalLevel]
+            assertWithMessage("For RSSI=%s", rssi).that(signalLevel).isAtLeast(0);
+            assertWithMessage("For RSSI=%s", rssi).that(signalLevel).isAtMost(maxSignalLevel);
+
+            // calculateSignalLevel(rssi) <= calculateSignalLevel(rssi + 1)
+            assertWithMessage("For RSSI=%s", rssi).that(signalLevel).isAtLeast(prevSignalLevel);
+            prevSignalLevel = signalLevel;
+        }
+    }
+
     private static class TestLocalOnlyHotspotCallback extends WifiManager.LocalOnlyHotspotCallback {
         Object hotspotLock;
         WifiManager.LocalOnlyHotspotReservation reservation = null;
@@ -468,12 +524,12 @@
             fail("Please enable location for this test");
         }
 
-        TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLOHSLock);
-        synchronized (mLOHSLock) {
+        TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLock);
+        synchronized (mLock) {
             try {
                 mWifiManager.startLocalOnlyHotspot(callback, null);
                 // now wait for callback
-                mLOHSLock.wait(DURATION);
+                mLock.wait(DURATION);
             } catch (InterruptedException e) {
             }
             // check if we got the callback
@@ -543,7 +599,7 @@
             return;
         }
         setWifiEnabled(true);
-        connectWifi(); // ensures that there is at-least 1 saved network on the device.
+        waitForConnection(); // ensures that there is at-least 1 saved network on the device.
 
         WifiConfiguration wifiConfiguration = new WifiConfiguration();
         wifiConfiguration.SSID = SSID1;
@@ -590,7 +646,7 @@
         TestLocalOnlyHotspotCallback callback = startLocalOnlyHotspot();
 
         // now make a second request - this should fail.
-        TestLocalOnlyHotspotCallback callback2 = new TestLocalOnlyHotspotCallback(mLOHSLock);
+        TestLocalOnlyHotspotCallback callback2 = new TestLocalOnlyHotspotCallback(mLock);
         try {
             mWifiManager.startLocalOnlyHotspot(callback2, null);
         } catch (IllegalStateException e) {
@@ -641,7 +697,7 @@
                 .setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
                 .build();
         TestExecutor executor = new TestExecutor();
-        TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLOHSLock);
+        TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLock);
         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         try {
             uiAutomation.adoptShellPermissionIdentity();
@@ -1007,6 +1063,274 @@
                 > ENFORCED_NUM_NETWORK_SUGGESTIONS_PER_APP);
     }
 
+    private static class TestActionListener implements WifiManager.ActionListener {
+        private final Object mLock;
+        public boolean onSuccessCalled = false;
+        public boolean onFailedCalled = false;
+        public int failureReason = -1;
+
+        TestActionListener(Object lock) {
+            mLock = lock;
+        }
+
+        @Override
+        public void onSuccess() {
+            synchronized (mLock) {
+                onSuccessCalled = true;
+                mLock.notify();
+            }
+        }
+
+        @Override
+        public void onFailure(int reason) {
+            synchronized (mLock) {
+                onFailedCalled = true;
+                failureReason = reason;
+                mLock.notify();
+            }
+        }
+    }
+
+    /**
+     * Triggers connection to one of the saved networks using {@link WifiManager#connect(
+     * int, WifiManager.ActionListener)} or {@link WifiManager#connect(WifiConfiguration,
+     * WifiManager.ActionListener)}
+     *
+     * @param withNetworkId Use networkId for triggering connection, false for using
+     *                      WifiConfiguration.
+     * @throws Exception
+     */
+    private void testConnect(boolean withNetworkId) throws Exception {
+        TestActionListener actionListener = new TestActionListener(mLock);
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        List<WifiConfiguration> savedNetworks = null;
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            // These below API's only work with privileged permissions (obtained via shell identity
+            // for test)
+            savedNetworks = mWifiManager.getConfiguredNetworks();
+
+            // Disable all the saved networks to trigger disconnect & disable autojoin.
+            for (WifiConfiguration network : savedNetworks) {
+                assertTrue(mWifiManager.disableNetwork(network.networkId));
+            }
+            waitForDisconnection();
+
+            // Now trigger connection to the first saved network.
+            synchronized (mLock) {
+                try {
+                    if (withNetworkId) {
+                        mWifiManager.connect(savedNetworks.get(0).networkId, actionListener);
+                    } else {
+                        mWifiManager.connect(savedNetworks.get(0), actionListener);
+                    }
+                    // now wait for callback
+                    mLock.wait(DURATION);
+                } catch (InterruptedException e) {
+                }
+            }
+            // check if we got the success callback
+            assertTrue(actionListener.onSuccessCalled);
+            // Wait for connection to complete & ensure we are connected to the saved network.
+            waitForConnection();
+            assertEquals(savedNetworks.get(0).networkId,
+                    mWifiManager.getConnectionInfo().getNetworkId());
+        } finally {
+            // Re-enable all saved networks before exiting.
+            if (savedNetworks != null) {
+                for (WifiConfiguration network : savedNetworks) {
+                    mWifiManager.enableNetwork(network.networkId, false);
+                }
+            }
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    /**
+     * Tests {@link WifiManager#connect(int, WifiManager.ActionListener)} to an existing saved
+     * network.
+     */
+    public void testConnectWithNetworkId() throws Exception {
+        testConnect(true);
+    }
+
+    /**
+     * Tests {@link WifiManager#connect(WifiConfiguration, WifiManager.ActionListener)} to an
+     * existing saved network.
+     */
+    public void testConnectWithWifiConfiguration() throws Exception {
+        testConnect(false);
+
+    }
+
+    private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
+        private final Object mLock;
+        public boolean onAvailableCalled = false;
+        public NetworkCapabilities networkCapabilities;
+
+        TestNetworkCallback(Object lock) {
+            mLock = lock;
+        }
+
+        @Override
+        public void onAvailable(Network network, NetworkCapabilities networkCapabilities,
+                LinkProperties linkProperties, boolean blocked) {
+            synchronized (mLock) {
+                onAvailableCalled = true;
+                this.networkCapabilities = networkCapabilities;
+                mLock.notify();
+            }
+        }
+    }
+
+    private void waitForNetworkCallbackAndCheckForMeteredness(boolean expectMetered) {
+        TestNetworkCallback networkCallbackListener = new TestNetworkCallback(mLock);
+        synchronized (mLock) {
+            try {
+                // File a request for wifi network.
+                mConnectivityManager.registerNetworkCallback(
+                        new NetworkRequest.Builder()
+                                .addTransportType(TRANSPORT_WIFI)
+                                .build(),
+                        networkCallbackListener);
+                // now wait for callback
+                mLock.wait(DURATION);
+            } catch (InterruptedException e) {
+            }
+        }
+        assertTrue(networkCallbackListener.onAvailableCalled);
+        assertNotEquals(expectMetered, networkCallbackListener.networkCapabilities.hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_NOT_METERED));
+    }
+
+    /**
+     * Tests {@link WifiManager#save(WifiConfiguration, WifiManager.ActionListener)} by marking
+     * an existing saved network metered.
+     */
+    public void testSave() throws Exception {
+        TestActionListener actionListener = new TestActionListener(mLock);
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        List<WifiConfiguration> savedNetworks = null;
+        WifiConfiguration savedNetwork = null;
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            // These below API's only work with privileged permissions (obtained via shell identity
+            // for test)
+            savedNetworks = mWifiManager.getConfiguredNetworks();
+
+            // Ensure that the saved network is not metered.
+            savedNetwork = savedNetworks.get(0);
+            assertNotEquals("Ensure that the saved network is configured as unmetered",
+                    savedNetwork.meteredOverride,
+                    WifiConfiguration.METERED_OVERRIDE_METERED);
+
+            // Trigger a scan & wait for connection to one of the saved networks.
+            mWifiManager.startScan();
+            waitForConnection();
+
+            // Check the network capabilities to ensure that the network is marked not metered.
+            waitForNetworkCallbackAndCheckForMeteredness(false);
+
+            // Now mark the network metered and save.
+            synchronized (mLock) {
+                try {
+                    WifiConfiguration modSavedNetwork = new WifiConfiguration(savedNetwork);
+                    modSavedNetwork.meteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED;
+                    mWifiManager.save(modSavedNetwork, actionListener);
+                    // now wait for callback
+                    mLock.wait(DURATION);
+                } catch (InterruptedException e) {
+                }
+            }
+            // check if we got the success callback
+            assertTrue(actionListener.onSuccessCalled);
+            // Check the network capabilities to ensure that the network is marked metered now.
+            waitForNetworkCallbackAndCheckForMeteredness(true);
+
+        } finally {
+            // Restore original network config (restore the meteredness back);
+            if (savedNetwork != null) {
+                mWifiManager.updateNetwork(savedNetwork);
+            }
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    private static class TestTrafficStateCallback implements WifiManager.TrafficStateCallback {
+        private final Object mLock;
+        public boolean onStateChangedCalled = false;
+        public int state = -1;
+
+        TestTrafficStateCallback(Object lock) {
+            mLock = lock;
+        }
+
+        @Override
+        public void onStateChanged(int state) {
+            synchronized (mLock) {
+                onStateChangedCalled = true;
+                this.state = state;
+                mLock.notify();
+            }
+        }
+    }
+
+    private void sendTraffic() {
+        for (int i = 0; i < 10; i ++) {
+            // Do some network operations
+            HttpURLConnection connection = null;
+            try {
+                URL url = new URL("http://www.google.com/");
+                connection = (HttpURLConnection) url.openConnection();
+                connection.setInstanceFollowRedirects(false);
+                connection.setConnectTimeout(TIMEOUT_MSEC);
+                connection.setReadTimeout(TIMEOUT_MSEC);
+                connection.setUseCaches(false);
+                connection.getInputStream();
+            } catch (Exception e) {
+                // ignore
+            } finally {
+                if (connection != null) connection.disconnect();
+            }
+        }
+    }
+
+    /**
+     * Tests {@link WifiManager#registerTrafficStateCallback(Executor,
+     * WifiManager.TrafficStateCallback)} by sending some traffic.
+     */
+    public void testTrafficStateCallback() throws Exception {
+        TestTrafficStateCallback trafficStateCallback = new TestTrafficStateCallback(mLock);
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            // Trigger a scan & wait for connection to one of the saved networks.
+            mWifiManager.startScan();
+            waitForConnection();
+
+            // Turn screen on for wifi traffic polling.
+            turnScreenOn();
+            synchronized (mLock) {
+                try {
+                    mWifiManager.registerTrafficStateCallback(
+                            Executors.newSingleThreadExecutor(), trafficStateCallback);
+                    // Send some traffic to trigger the traffic state change callbacks.
+                    sendTraffic();
+                    // now wait for callback
+                    mLock.wait(DURATION);
+                } catch (InterruptedException e) {
+                }
+            }
+            // check if we got the state changed callback
+            assertTrue(trafficStateCallback.onStateChangedCalled);
+            assertEquals(DATA_ACTIVITY_INOUT, trafficStateCallback.state);
+        } finally {
+            turnScreenOff();
+            mWifiManager.unregisterTrafficStateCallback(trafficStateCallback);
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
     private void assertWifiScanningIsOn() {
         if(!mWifiManager.isScanAlwaysAvailable()) {
             fail("Wi-Fi scanning should be on.");
diff --git a/tests/cts/net/src/android/net/wifi/rtt/cts/WifiRttTest.java b/tests/cts/net/src/android/net/wifi/rtt/cts/WifiRttTest.java
index 44a9cc2..d5361d7 100644
--- a/tests/cts/net/src/android/net/wifi/rtt/cts/WifiRttTest.java
+++ b/tests/cts/net/src/android/net/wifi/rtt/cts/WifiRttTest.java
@@ -163,7 +163,7 @@
 
         // Analyze results
         assertTrue("Wi-Fi RTT failure rate exceeds threshold: FAIL=" + numFailures + ", ITERATIONS="
-                        + NUM_OF_RTT_ITERATIONS,
+                        + NUM_OF_RTT_ITERATIONS + ", AP RSSI=" + testAp.level,
                 numFailures <= NUM_OF_RTT_ITERATIONS * MAX_FAILURE_RATE_PERCENT / 100);
         if (numFailures != NUM_OF_RTT_ITERATIONS) {
             double distanceAvg = distanceSum / (NUM_OF_RTT_ITERATIONS - numFailures);