com.mobiperf.measurements.RRCTask.java Source code

Java tutorial

Introduction

Here is the source code for com.mobiperf.measurements.RRCTask.java

Source

/*
 * Copyright 2013 RobustNet Lab, University of Michigan. All Rights Reserved.
 * 
 * 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 com.mobiperf.measurements;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InvalidClassException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.content.SharedPreferences;
import android.net.TrafficStats;
import android.util.StringBuilderPrinter;

import com.mobiperf.Checkin;
import com.mobiperf.Config;
import com.mobiperf.Logger;
import com.mobiperf.MeasurementDesc;
import com.mobiperf.MeasurementError;
import com.mobiperf.MeasurementResult;
import com.mobiperf.MeasurementTask;
import com.mobiperf.RRCTrafficControl;
import com.mobiperf.util.MeasurementJsonConvertor;
import com.mobiperf.util.PhoneUtils;

/**
 * This class measures the round trip times of packets as you vary the packet timings between them,
 * with the purpose of inferring RRC state transitions.
 * 
 * See "Characterizing Radio Resource Allocation for 3G Networks" by Feng et. al, IMC 2010 for a
 * full explanation of the methodology and goals.
 * 
 * TODO (sanae): Pause the RRC tasks when a checkin is performed, rather than pausing the checkin
 * while the RRC task is performed. 
 * 
 * @author sanae@umich.edu (Sanae Rosen)
 * 
 */
public class RRCTask extends MeasurementTask {
    // Type name for internal use
    public static final String TYPE = "rrc";
    // Human readable name for the task
    public static final String DESCRIPTOR = "rrc";
    public static String TAG = "MobiPerf_RRC_INFERENCE";
    private boolean stop = false;
    private Context context;

    // Track data consumption for this task to avoid exceeding user's limit
    public static long data_consumed = 0;

    /**
     * Stores parameters for the RRC inference task
     * @author sanae@umich.edu (Sanae Rosen)
     *
     */
    public static class RRCDesc extends MeasurementDesc {
        private static String HOST = "www.google.com";

        // Default echo server name and port to measure the RTT to infer RRC state
        private static int PORT = 50000;
        private static String ECHO_HOST = "ep2.eecs.umich.edu";
        // Perform RTT measurements every GRANULARITY ms
        public int GRANULARITY = 500;
        // MIN / MAX is the echo packet size
        int MIN = 0;
        int MAX = 1024;
        // Default total number of measurements
        int size = 31;
        // Echo server / port, and target to perform the upper-layer tasks
        public String echoHost = ECHO_HOST;
        public String target = HOST;
        int port = PORT;

        long testId; // unique value for this set of tests

        // Default threshold to repeat each RTT measurement because of background traffic
        int GIVEUP_THRESHHOLD = 15;

        // server controled variable
        boolean DNS = true;
        boolean TCP = true;
        boolean HTTP = true;
        boolean RRC = true;
        boolean SIZES = true;

        // Whether RRC result is visible to users
        public boolean RESULT_VISIBILITY = false;

        /*
         * For the upper-layer tests, a series of tests are made for different inter-packet intervals,
         * in order. "Times" indicates the inter-packet intervals, the other fields store the results.
         * All must be the same size.
         */
        Integer[] times; // The times where the above tests were made, in units of GRANULARITY.
        int sizeGranularity = 200; // the spacing between sizes to test
        int[] httpTest; // The results of the HTTP test performed at each time
        int[] dnsTest; // likewise, for the DNS test
        int[] tcpTest; // likewise, for the TCP test

        // Whether or not to run the upper layer tests, i.e. the HTTP, TCP and DNS tests.
        // Disabling this flag will disable all upper layer tests.
        private boolean runUpperLayerTests = false;

        /*
         * Default times between packets for which the upper layer tests are performed. Later, these
         * times will be replaced by times from the model. These times are GRANULARITY milliseconds: if
         * GRANULARITY is 500, then a default time of 6 means measurements are taken 500 ms apart.
         */
        Integer[] defaultTimesULTasks = new Integer[] { 0, 2, 4, 8, 12, 16, 22 };

        public RRCDesc(String key, Date startTime, Date endTime, double intervalSec, long count, long priority,
                Map<String, String> params) {
            super(RRCTask.TYPE, key, startTime, endTime, intervalSec, count, priority, params);
            initializeParams(params);
        }

        public MeasurementResult getResults(MeasurementResult result) {
            if (HTTP)
                result.addResult("http", httpTest);
            if (TCP)
                result.addResult("tcp", tcpTest);
            if (DNS)
                result.addResult("dns", dnsTest);
            result.addResult("times", times);
            return result;
        }

        public void displayResults(StringBuilderPrinter printer) {
            String DEL = "\t", toprint = DEL + DEL;
            for (int i = 1; i <= times.length; i++) {
                toprint += DEL + " | state" + i;
            }
            toprint += " |";
            int oneLineLen = toprint.length();
            toprint += "\n";
            // seperator
            for (int i = 0; i < oneLineLen; i++) {
                toprint += "-";
            }
            toprint += "\n";
            if (HTTP) {
                toprint += "HTTP (ms)" + DEL;
                for (int i = 0; i < httpTest.length; i++) {
                    toprint += DEL + " | " + Integer.toString(httpTest[i]);
                }
                toprint += " |\n";
                for (int i = 0; i < oneLineLen; i++) {
                    toprint += "-";
                }
                toprint += "\n";
            }

            if (DNS) {
                toprint += "DNS (ms)" + DEL;
                for (int i = 0; i < dnsTest.length; i++) {
                    toprint += DEL + " | " + Integer.toString(dnsTest[i]);
                }
                toprint += " |\n";
                for (int i = 0; i < oneLineLen; i++) {
                    toprint += "-";
                }
                toprint += "\n";
            }

            if (TCP) {
                toprint += "TCP (ms)" + DEL;
                for (int i = 0; i < tcpTest.length; i++) {
                    toprint += DEL + " | " + Integer.toString(tcpTest[i]);
                }
                toprint += " |\n";
                for (int i = 0; i < oneLineLen; i++) {
                    toprint += "-";
                }
                toprint += "\n";
            }

            toprint += "Timers (s)";
            for (int i = 0; i < times.length; i++) {
                double curTime = (double) times[i] * (double) GRANULARITY / 1000.0;
                toprint += DEL + " | " + String.format("%.2f", curTime);
            }
            toprint += " |\n";
            printer.println(toprint);
        }

        @Override
        public String getType() {
            return RRCTask.TYPE;
        }

        /**
         * Given the parameters fetched from the server, sets up the parameters as needed for the upper
         * layer tests, i.e. the application layer tests.
         */
        @Override
        protected void initializeParams(Map<String, String> params) {

            // In this case, we fall back to the default values defined above.
            if (params == null) {
                return;
            }

            // The parameters for the echo server
            this.echoHost = params.get("echo_host");
            this.target = params.get("target");
            if (this.echoHost == null) {
                this.echoHost = ECHO_HOST;
            }
            if (this.target == null) {
                this.target = HOST;
            }
            Logger.d("param: echo_host " + this.echoHost);
            Logger.d("param: target " + this.target);

            try {
                String val = null;
                // Size of the small packet
                if ((val = params.get("min")) != null && val.length() > 0 && Integer.parseInt(val) > 0) {
                    this.MIN = Integer.parseInt(val);
                }
                Logger.d("param: Min " + this.MIN);
                // Size of the large packet
                if ((val = params.get("max")) != null && val.length() > 0 && Integer.parseInt(val) > 0) {
                    this.MAX = Integer.parseInt(val);
                }
                Logger.d("param: MAX " + this.MAX);
                // Echo server port
                if ((val = params.get("port")) != null && val.length() > 0 && Integer.parseInt(val) > 0) {
                    this.port = Integer.parseInt(val);
                }
                Logger.d("param: port " + this.port);
                // Number of tests to run from the RRC test
                if ((val = params.get("size")) != null && val.length() > 0 && Integer.parseInt(val) > 0) {
                    this.size = Integer.parseInt(val);
                }
                Logger.d("param: size " + this.size);
                // When testing size dependence, increase the
                if ((val = params.get("size_granularity")) != null && val.length() > 0
                        && Integer.parseInt(val) > 0) {
                    this.sizeGranularity = Integer.parseInt(val);
                }
                Logger.d("param: size_granularity " + this.sizeGranularity);
                // Whether or not to run the DNS test
                if ((val = params.get("dns")) != null && val.length() > 0) {
                    this.DNS = Boolean.parseBoolean(val);
                }
                Logger.d("param: DNS " + this.DNS);
                // Whether or not to run the HTTP test
                if ((val = params.get("http")) != null && val.length() > 0) {
                    this.HTTP = Boolean.parseBoolean(val);
                }
                Logger.d("param: HTTP " + this.HTTP);
                // Whether or not to run the TCP test
                if ((val = params.get("tcp")) != null && val.length() > 0) {
                    this.TCP = Boolean.parseBoolean(val);
                }
                Logger.d(params.get("rrc"));
                Logger.d("param: TCP " + this.TCP);
                // Whether or not to run the RRC inference task
                if ((val = params.get("rrc")) != null && val.length() > 0) {
                    this.RRC = Boolean.parseBoolean(val);
                }
                Logger.d("param: RRC " + this.RRC);
                if ((val = params.get("measure_sizes")) != null && val.length() > 0) {
                    this.SIZES = Boolean.parseBoolean(val);
                }
                Logger.d("param: SIZES " + this.SIZES);
                // Whether the RRC result is visible to users
                if ((val = params.get("result_visibility")) != null && val.length() > 0) {
                    this.RESULT_VISIBILITY = Boolean.parseBoolean(val);
                }
                Logger.d("param: visibility " + this.RESULT_VISIBILITY);
                // How many times to retry a test when interrupted by background traffic
                if ((val = params.get("giveup_threshhold")) != null && val.length() > 0
                        && Integer.parseInt(val) > 0) {
                    this.GIVEUP_THRESHHOLD = Integer.parseInt(val);
                }
                Logger.d("param: GIVEUP_THRESHHOLD " + this.GIVEUP_THRESHHOLD);

                // Default assumed timers for the upper layer tests (HTTP, DNS, TCP),
                // in units of GRANULARITY. These are set via a comma-separated list
                // of numbers.
                if ((val = params.get("default_extra_test_timers")) != null && val.length() > 0) {
                    String[] timesString = val.split("\\s*,\\s*");
                    List<String> stringList = new ArrayList<String>(Arrays.asList(timesString));
                    List<Integer> intList = new ArrayList<Integer>();
                    Iterator<String> iterator = stringList.iterator();
                    while (iterator.hasNext()) {
                        intList.add(Integer.parseInt(iterator.next()));
                    }
                    times = (Integer[]) intList.toArray(new Integer[intList.size()]);

                }
                if (times == null) {
                    times = defaultTimesULTasks;
                }
            } catch (NumberFormatException e) {
                throw new InvalidParameterException(" RRCTask cannot be created due to invalid params");
            }

            if (size == 0) {
                // 31 tests, by default. From 0s to 15s inclusive, in half-second intervals.
                size = 31;
            }
        }

        /**
         * For the arrays holding the results for the upper layer tests, we need to initialize them to
         * be the same size as the number of tests we run. -1 means uninitialized.
         * 
         * @param size Number of results to store
         */
        public void initializeExtraTaskResults(int size) {
            httpTest = new int[size];
            dnsTest = new int[size];
            tcpTest = new int[size];
            for (int i = 0; i < size; i++) {
                httpTest[i] = -1;
                dnsTest[i] = -1;
                tcpTest[i] = -1;
            }
            runUpperLayerTests = true;
        }

        /**
         * Given an interpacket interval and a round trip time from an HTTP test, store that value.
         * 
         * @param index Index into measurement result array, corresponding to an interpacket interval
         * @param rtt Time for HTTP test to complete, in milliseconds
         * @throws MeasurementError
         */
        public void setHttp(int index, int rtt) throws MeasurementError {
            if (!runUpperLayerTests) {
                throw new MeasurementError("Data class not initialized");
            }
            httpTest[index] = rtt;
        }

        /**
         * Given an interpacket interval and a round trip time from a TCP test, store that value.
         * 
         * @param index Index into measurement result array, corresponding to an interpacket interval
         * @param rtt Time for the TCP test to complete, in milliseconds
         * @throws MeasurementError
         */
        public void setTcp(int index, int rtt) throws MeasurementError {
            if (!runUpperLayerTests) {
                throw new MeasurementError("Data class not initialized");
            }
            tcpTest[index] = rtt;
        }

        /**
         * Given an interpacket interval and a round trip time from a DNS test, store that value.
         * 
         * @param index Index into measurement result array, corresponding to an interpacket interval
         * @param rtt Time for the DNS test to complete, in milliseconds
         * @throws MeasurementError
         */
        public void setDns(int index, int rtt) throws MeasurementError {
            if (!runUpperLayerTests) {
                throw new MeasurementError("Data class not initialized");
            }
            dnsTest[index] = rtt;
        }

    }

    /**
     * Stores data from the tests on the effect of packet size on RRC state related delays
     * 
     * @author sanae@umich.edu (Sanae Rosen)   
     */
    public static class RrcSizeTestData {
        int time; // Interval between packets
        int size; // Size of packets sent
        long result; // Round trip time, in milliseconds
        long testId; // A unique id associated with a set of measurements

        /**
         * 
         * @param time The inter-packet interval
         * @param size The packet size, in bytes
         * @param result The round trip time for that packet size and inter-packet interval
         * @param testId The unique id for this set of tests
         */
        public RrcSizeTestData(int time, int size, long result, long testId) {
            this.time = time;
            this.size = size;
            this.result = result;
            this.testId = testId;
        }

        public JSONObject toJSON(String networktype, String phoneId) throws JSONException {
            JSONObject entry = new JSONObject();
            entry.put("network_type", networktype);
            entry.put("phone_id", phoneId);
            entry.put("test_id", testId);
            entry.put("time_delay", time);
            entry.put("size", size);
            entry.put("result", result);

            return entry;
        }

    }

    /**
     * Store the results of our RRC test.
     * 
     * Data is stored as a list of results indexed by the test number. Tests are performed in order
     * with increasing inter-packet intervals and results and data about the tests are stored here.
     * 
     * @author sanae@umich.edu (Sanae Rosen)
     * 
     */
    public static class RRCTestData {

        // Round-trip times, in ms
        int[] rttsSmall;
        int[] rttsLarge;
        // Packets lost for each test
        int[] packetsLostSmall;
        int[] packetsLostLarge;
        // Signal strengths at time of each test
        int[] signalStrengthSmall;
        int[] signalStrengthLarge;
        // Error Counts from each test
        int[] errorCountSmall;
        int[] errorCountLarge;

        ArrayList<RrcSizeTestData> packetSizes;

        // Unique incrementing value that identifies this set of tests.
        long testId;

        /**
         * @param size Number of tests to run (each corresponding to an interpacket interval, 
         * increasing in increments of 500 ms)
         * @param testId Unique ID for this set ot tests
         */
        public RRCTestData(int size, long testId) {
            size = size + 1;

            rttsSmall = new int[size];
            rttsLarge = new int[size];
            packetsLostSmall = new int[size];
            packetsLostLarge = new int[size];
            signalStrengthSmall = new int[size];
            signalStrengthLarge = new int[size];
            errorCountLarge = new int[size];
            errorCountSmall = new int[size];

            this.testId = testId;

            packetSizes = new ArrayList<RrcSizeTestData>();

            // Set default values
            for (int i = 0; i < rttsSmall.length; i++) {
                // 7000 is the cutoff for timeouts.
                // This makes the model-building script treat no data and timeouts the same.
                rttsSmall[i] = 7000;
                rttsLarge[i] = 7000;
                packetsLostSmall[i] = -1;
                packetsLostLarge[i] = -1;
                signalStrengthSmall[i] = -1;
                signalStrengthLarge[i] = -1;
                errorCountSmall[i] = -1;
                errorCountLarge[i] = -1;
            }
        }

        public long testId() {
            return testId;
        }

        public String[] toJSON(String networktype, String phoneId) {
            String[] returnval = new String[rttsSmall.length];
            try {
                for (int i = 0; i < rttsSmall.length; i++) {
                    JSONObject subtest = new JSONObject();
                    subtest.put("rtt_low", rttsSmall[i]);
                    subtest.put("rtt_high", rttsLarge[i]);
                    subtest.put("lost_low", packetsLostSmall[i]);
                    subtest.put("lost_high", packetsLostLarge[i]);
                    subtest.put("signal_low", signalStrengthSmall[i]);
                    subtest.put("signal_high", signalStrengthLarge[i]);
                    subtest.put("error_low", errorCountSmall[i]);
                    subtest.put("error_high", errorCountLarge[i]);
                    subtest.put("network_type", networktype);
                    subtest.put("time_delay", i);
                    subtest.put("test_id", testId);
                    subtest.put("phone_id", phoneId);
                    returnval[i] = subtest.toString();
                    Logger.w("Test ID for rrc inference test was " + this.testId);
                }
            } catch (JSONException e) {
                Logger.e("Error converting RRC data to JSON");
            }
            return returnval;
        }

        /**
         * Erase all data corresponding to a set of results with a particular interpacket interval
         * @param index Index into the array of results.
         */
        public void deleteItem(int index) {
            rttsSmall[index] = -1;
            rttsLarge[index] = -1;
            packetsLostSmall[index] = -1;
            packetsLostLarge[index] = -1;
            signalStrengthSmall[index] = -1;
            signalStrengthLarge[index] = -1;
            errorCountSmall[index] = -1;
            errorCountLarge[index] = -1;
        }

        /**
         * Save the data resulting from a given test.
         * 
         * @param index Index into the array of results.
         * @param rttMax Round trip time for large packets (generally 1KB)
         * @param rttMin Round time time for small packets (generally empty)
         * @param numPacketsLostMax Number of packets lost for the set of large packets, out of 10
         * @param numPacketsLostMin Likewise, for the small packets
         * @param errorHigh Error count for the set of large packets
         * @param errorLow Likewise, for the small packets
         * @param signalHigh The signal strength when sending the large packets
         * @param signalLow Likewise, for the small packets
         */
        public void updateAll(int index, int rttMax, int rttMin, int numPacketsLostMax, int numPacketsLostMin,
                int errorHigh, int errorLow, int signalHigh, int signalLow) {
            this.rttsLarge[index] = (int) rttMax;
            this.rttsSmall[index] = (int) rttMin;
            this.packetsLostLarge[index] = numPacketsLostMax;
            this.packetsLostSmall[index] = numPacketsLostMin;
            this.errorCountLarge[index] = errorHigh;
            this.errorCountSmall[index] = errorLow;
            this.signalStrengthLarge[index] = signalHigh;
            this.signalStrengthSmall[index] = signalLow;
        }

        public void setRrcSizeTestData(int index, int size, long result, long testId) throws MeasurementError {
            packetSizes.add(new RrcSizeTestData(index, size, result, testId));
        }

        public String[] sizeDataToJSON(String networkType, String phoneId) {
            String[] returnval = new String[packetSizes.size()];
            try {
                for (int i = 0; i < packetSizes.size(); i++) {
                    returnval[i] = packetSizes.get(i).toJSON(networkType, phoneId).toString();
                }
            } catch (JSONException e) {
                Logger.e("Error converting RRC data to JSON");
            }
            return returnval;
        }
    }

    /**
     * Class for tracking if there has been interfering traffic.
     * 
     * We need to know how many packets are expected. We can then use the global packet counters to
     * see if more packets are sent than expected.
     * 
     * @author sanae@umich.edu (Sanae Rosen)
     * 
     */
    public static class PacketMonitor {
        private long[] packetsFirst;
        private long[] packetsLast;
        boolean bySize = false;

        /**
         * Initialize immediately before use. Values are time-sensitive.
         */
        PacketMonitor() {
            readCurrentPacketValues();
        }

        void setBySize() {
            bySize = true;
        }

        void readCurrentPacketValues() {
            packetsFirst = getPacketsSent();
        }

        /**
         * Call this to determine if packets have been sent since initializing.
         * 
         * @param expectedRcv Number of packets expected to be received by the device since 
         * initialization
         * @param expectedSent Number of packets expected to be sent by the device since 
         * initialization
         * @return Whether or not there is interfering traffic
         */
        boolean isTrafficInterfering(int expectedRcv, int expectedSent) {
            packetsLast = getPacketsSent();

            long rcvPackets = (packetsLast[0] - packetsFirst[0]);
            long sentPackets = (packetsLast[1] - packetsFirst[1]);
            if (rcvPackets <= expectedRcv && sentPackets <= expectedSent) {
                Logger.d("No competing traffic, continue");
                return false;
            }
            Logger.d("Competing traffic, retry");

            return true;
        }

        /**
         * Determine how many packets, so far, have been sent (the contents of /proc/net/dev/). This is
         * a global value. We use this to determine if any other app anywhere on the phone may have sent
         * interfering traffic that might have changed the RRC state without our knowledge.
         * 
         * @return Two values: number of bytes or packets received at index 0, followed by the number 
         * sent at index 1.  
         */
        public long[] getPacketsSent() {
            long[] retval = { -1, -1 };
            if (bySize) {
                retval[0] = TrafficStats.getMobileRxBytes();
                retval[1] = TrafficStats.getMobileTxBytes();

            } else {
                retval[0] = TrafficStats.getMobileRxPackets();
                retval[1] = TrafficStats.getMobileTxPackets();
            }

            return retval;
        }

        /**
         * The total traffic sent and received since getPacketsSent was run.
         * 
         * @return The total packets (or data) sent and received, as a sum
         */
        public long getPacketsSentDiff() {
            packetsLast = getPacketsSent();
            long rcvPackets = (packetsLast[0] - packetsFirst[0]);
            long sentPackets = (packetsLast[1] - packetsFirst[1]);
            return rcvPackets + sentPackets;
        }
    }

    @SuppressWarnings("rawtypes")
    public static Class getDescClass() throws InvalidClassException {
        return RRCDesc.class;
    }

    public RRCTask(MeasurementDesc desc, Context parent) {
        super(new RRCDesc(desc.key, desc.startTime, desc.endTime, desc.intervalSec, desc.count, desc.priority,
                desc.parameters), parent);
        context = parent;
    }

    @Override
    public MeasurementTask clone() {
        MeasurementDesc desc = this.measurementDesc;
        RRCDesc newDesc = new RRCDesc(desc.key, desc.startTime, desc.endTime, desc.intervalSec, desc.count,
                desc.priority, desc.parameters);
        return new RRCTask(newDesc, parent);
    }

    @Override
    public MeasurementResult call() throws MeasurementError {
        RRCDesc desc = runInferenceTests();
        return constructResultStandard(desc);
    }

    @Override
    public String getDescriptor() {
        return DESCRIPTOR;
    }

    @Override
    public String getType() {
        return RRCTask.TYPE;
    }

    @Override
    public void stop() {
        stop = true;
    }

    /**
     * Helper function to construct MeasurementResults to submit to the server
     * 
     * @param desc The data collected for the RRC test to be converted into something understandable 
     * by the server. 
     * @return A data structure that can be sent to the server.  Contains only the results of the 
     * TCP, DNS and HTTP tests: the others are sent via a separate mechanism as they are stored 
     * separately. 
     */
    private MeasurementResult constructResultStandard(RRCDesc desc) {
        PhoneUtils phoneUtils = PhoneUtils.getPhoneUtils();
        boolean success = true;
        MeasurementResult result = new MeasurementResult(phoneUtils.getDeviceInfo().deviceId,
                phoneUtils.getDeviceProperty(), RRCTask.TYPE, System.currentTimeMillis() * 1000, success,
                this.measurementDesc);

        if (desc.runUpperLayerTests) {
            result = desc.getResults(result);
        }

        Logger.i(MeasurementJsonConvertor.toJsonString(result));
        return result;
    }

    /**
     * The core RRC inference functionality is in this function. The steps involved can be summarized
     * as follows:
     * <ol> 
     * <li> Fetch the last model generated by the server, if it exists.</li> 
     * <li> Check we are on a cellular network, otherwise abort. </li>
     * <li> Inform all other tasks that they should delay network traffic until later. </li>
     * <li> For every upper layer test, if upper layer tests and that test specifically are enabled,
     * run that test.</li> 
     * </ol> 
     * @return A data structure containing all the results and metadata surrounding the RRC inference tests 
     * performed.
     * @throws MeasurementError
     */
    private RRCDesc runInferenceTests() throws MeasurementError {
        data_consumed = 0;

        Checkin checkin = new Checkin(context);

        // Fetch the existing model from the server, if it exists
        RRCDesc desc = (RRCDesc) measurementDesc;
        desc.testId = getTestId(context);
        Logger.w("Test ID set to " + desc.testId);
        PhoneUtils utils = PhoneUtils.getPhoneUtils();
        desc.initializeExtraTaskResults(desc.times.length);

        // Check to make sure we are on a valid (i.e. cellular) network
        if (utils.getNetwork() == "UNKNOWN" || utils.getNetwork() == "WIRELESS") {
            Logger.d("Returning: network is" + utils.getNetwork() + " rssi " + utils.getCurrentRssi());
            return desc;
        }

        try {
            /*
             * Suspend all other tasks performed by the app as they can interfere. Although we have a
             * built-in check where we abort if traffic in the background interferes, in the past people
             * have scheduled other tests to be every 5 minutes, which can cause the RRC task to never
             * successfully complete without having to abort.
             */
            RRCTrafficControl.PauseTraffic();

            RRCTestData data = new RRCTestData(desc.size, desc.testId);

            // If the RRC task is enabled
            if (desc.RRC) {
                // Set up the connection to the echo server
                Logger.d("Active inference: about to begin");
                Logger.d(desc.echoHost + ":" + desc.port);
                InetAddress serverAddr = InetAddress.getByName(desc.echoHost);

                // Perform the RRC timer and latency inference task
                Logger.d("Demotion inference: about to begin");
                desc = inferDemotion(serverAddr, desc, data, utils);

                Logger.d("About to save data");
                this.progress = Math.min(Config.MAX_PROGRESS_BAR_VALUE, 40);
                try {
                    Logger.w("RRC: update the model on the GAE datastore");
                    checkin.uploadRrcInferenceData(data);
                    Logger.d("Saving data complete");
                } catch (IOException e) {
                    e.printStackTrace();
                    Logger.e("Data not saved: " + e.getMessage());
                }
            }

            // Check if the upper layer tasks are enabled
            if (desc.runUpperLayerTests) {
                if (desc.DNS) {
                    Logger.w("Start DNS task");
                    // Test the dependence of DNS latency on the RRC state, using
                    // the previously constructed model if available.
                    runDnsTest(desc.times, desc);
                }
                this.progress = Math.min(Config.MAX_PROGRESS_BAR_VALUE, 60);
                if (desc.TCP) {
                    Logger.w("Start TCP task");
                    // Test the dependence of TCP latency on the RRC state.
                    runTCPHandshakeTest(desc.times, desc);
                }
                this.progress = Math.min(Config.MAX_PROGRESS_BAR_VALUE, 80);
                if (desc.HTTP) {
                    Logger.w("Start HTTP task");
                    // Test the dependence of HTTP latency on the RRC state.
                    runHTTPTest(desc.times, desc);
                }

                if (desc.SIZES) {
                    Logger.w("Start size dependence task");
                    runSizeThresholdTest(desc.times, desc, data, desc.testId);
                    checkin.uploadRrcInferenceSizeData(data);
                }
            }

            this.progress = Math.min(Config.MAX_PROGRESS_BAR_VALUE, 100);
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            RRCTrafficControl.UnPauseTraffic();
        }

        return desc;
    }

    /**
     * Determines the packet size dependence of the RRC task.
     * 
     * Given a specified list of inter-packet intervals, perform the RRC inference measurement for
     * packets of sizes incremented by the size granularity specified.
     * 
     * @param times  Inter-packet intervals to test
     * @param desc Parameters for the RRC inference task
     * @param data Stores results of the RRC inference task
     * @param testId A unique ID identifying this set of tests
     */
    private void runSizeThresholdTest(final Integer[] times, RRCDesc desc, RRCTestData data, long testId) {

        InetAddress serverAddr;
        try {
            serverAddr = InetAddress.getByName(desc.echoHost);
        } catch (UnknownHostException e) {
            Logger.e("Invalid or unreachable echo host. Test aborted.");
            e.printStackTrace();
            return;
        }
        for (int i = 0; i < times.length; i++) {
            for (int j = desc.sizeGranularity; j <= 1024; j += desc.sizeGranularity) {
                try {
                    long result = inferDemotionPacketSize(serverAddr, times[i], desc, j);
                    data.setRrcSizeTestData(times[i], j, result, testId);
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (MeasurementError e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * Due to problems with how we detect interfering traffic, this test does not always work and
     * results should be treated with caution. I am keeping this test around, though, as we can still 
     * get data on how much HTTP handshakes vary in how long they take.
     * 
     * The "times" are the inter-packet intervals at which to run the test. Ideally these should be
     * based on the model constructed by the server, a default assumed value is used in their absence.
     * 
     * Based on the time it takes to load a response from the page.
     * 
     * This test is not currently as accurate as the other tests, for reasons described below, and has
     * thus been disabled.
     * 
     * @param times List of inter-packet intervals, in half-second increments, at which to run the 
     * tests
     * @param desc Stores parameters for the RRC inference tests in general
     */
    private void runHTTPTest(final Integer[] times, RRCDesc desc) {
        /*
         * Length of time it takes to request and read in a page.
         */

        Logger.d("Active inference HTTP test: about to begin");
        if (times.length != desc.httpTest.length) {
            desc.httpTest = new int[times.length];
        }
        long startTime = 0;
        long endTime = 0;

        try {
            for (int i = 0; i < times.length; i++) {
                // We try until we reach a threshhold or until there is no
                // competing traffic.
                for (int j = 0; j < desc.GIVEUP_THRESHHOLD; j++) {
                    // Sometimes the network can change in the middle of a test
                    checkIfWifi();
                    if (stop) {
                        return;
                    }

                    /*
                     * We keep track of the packets sent at the beginning and end of the test so we can detect
                     * if there is competing traffic anywhere on the phone.
                     */
                    PacketMonitor packetMonitor;

                    /*
                     * We also keep track of the data consumed in order to remain within
                     * the data limit
                     */
                    PacketMonitor datamonitor = new PacketMonitor();
                    datamonitor.setBySize();
                    datamonitor.readCurrentPacketValues();

                    // Initiate the desired RRC state by sending a large enough packet
                    // to go to DCH and waiting for the specified amount of time
                    try {
                        // Wait for 1 second. Give time for any extraneous data sending to complete
                        waitTime(1, false);
                        InetAddress serverAddr;
                        serverAddr = InetAddress.getByName(desc.echoHost);
                        sendPacket(serverAddr, desc.MAX, desc);
                        packetMonitor = new PacketMonitor();

                        waitTime(times[i] * desc.GRANULARITY, true);

                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                        continue;
                    } catch (UnknownHostException e) {
                        e.printStackTrace();
                        continue;
                    } catch (IOException e) {
                        e.printStackTrace();
                        continue;
                    }
                    startTime = System.currentTimeMillis();

                    // Somewhat approximte: we can pick up packets sent by our request.
                    // Our request seems to never send more than 24 packets when there is no interference.
                    boolean success = !packetMonitor.isTrafficInterfering(3, 70);
                    HttpClient client = new DefaultHttpClient();
                    HttpGet request = new HttpGet();

                    request.setURI(new URI("http://" + desc.target + "?dummy=" + i + "" + j));

                    HttpResponse response = client.execute(request);
                    endTime = System.currentTimeMillis();

                    BufferedReader in = null;
                    in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
                    StringBuffer sb = new StringBuffer("");
                    String line = "";

                    while ((line = in.readLine()) != null) {
                        sb.append(line + "\n");
                    }
                    in.close();

                    // This may overestimate the data consumed, but there's no good way
                    // to tell what was us and what was another app
                    data_consumed += datamonitor.getPacketsSentDiff();

                    if (success) {
                        break;
                    }
                    startTime = 0;
                    endTime = 0;

                }

                long rtt = endTime - startTime;
                try {
                    desc.setHttp(i, (int) rtt);
                } catch (MeasurementError e) {
                    e.printStackTrace();
                }
                Logger.d("Time for Http" + rtt);
            }
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
    }

    /**
     * The "times" are the inter-packet intervals at which to run the test. Ideally these should be
     * based on the model constructed by the server, a default assumed value is used in their absence.
     * 
     * <ol>
     * <li> Send a packet to initiate the RRC state desired. </li>
     * <li>Create a randomly generated host name (to ensure that the host name is not cached). I 
     * found on some devices that even when you clear the cache manually, the data remains in 
     * the cache.</li>
     * <li> Time how long it took to look it up.</li> 
     * <li> Count the total packets sent, globally on the phone. If more packets were sent than </li>
     * expected,  abort and try again. </li> 
     * <li> Otherwise, save the data for that test and move to the next inter-packet interval.</li>
     * </ol>
     * 
     * Test is similar to the approach taken in DnsLookUpTask.java.
     * 
     * @param times List of inter-packet intervals, in half-second increments, at which to run the 
     * test
     * @param desc Stores parameters for the RRC inference tests in general
     * @throws MeasurementError
     */

    public void runDnsTest(final Integer[] times, RRCDesc desc) throws MeasurementError {
        Logger.d("Active inference DNS test: about to begin");
        if (times.length != desc.dnsTest.length) {
            desc.dnsTest = new int[times.length];
        }
        long dataConsumedThisTask = 0;

        long startTime = 0;
        long endTime = 0;

        // For each inter-packet interval...
        for (int i = 0; i < times.length; i++) {
            // On a failure, try again until a threshold is reached.
            for (int j = 0; j < desc.GIVEUP_THRESHHOLD; j++) {

                // Sometimes the network can change in the middle of a test
                checkIfWifi();
                if (stop) {
                    return;
                }

                /*
                 * We keep track of the packets sent at the beginning and end of the test so we can detect
                 * if there is competing traffic anywhere on the phone.
                 */

                PacketMonitor packetMonitor = new PacketMonitor();

                // Initiate the desired RRC state by sending a large enough packet
                // to go to DCH and waiting for the specified amount of time
                try {
                    InetAddress serverAddr;
                    serverAddr = InetAddress.getByName(desc.echoHost);
                    sendPacket(serverAddr, desc.MAX, desc);
                    waitTime(times[i] * desc.GRANULARITY, true);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                    continue;
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                    continue;
                } catch (IOException e) {
                    e.printStackTrace();
                    continue;
                }

                // Create a random URL, to avoid the caching problem
                UUID uuid = UUID.randomUUID();
                String host = uuid.toString() + ".com";
                // Start measuring the time to complete the task
                startTime = System.currentTimeMillis();
                try {
                    @SuppressWarnings("unused")
                    InetAddress serverAddr = InetAddress.getByName(host);
                } catch (UnknownHostException e) {
                    // we do this on purpose! Since it's a fake URL the lookup will fail
                }
                dataConsumedThisTask += DnsLookupTask.AVG_DATA_USAGE_BYTE;
                // When we fail to find the URL, we stop timing
                endTime = System.currentTimeMillis();

                // Check how many packets were sent again. If the expected number
                // of packets were sent, we can finish and go to the next task.
                // Otherwise, we have to try again.
                if (!packetMonitor.isTrafficInterfering(5, 5)) {
                    break;
                }

                startTime = 0;
                endTime = 0;
            }

            // If we broke out of the try-again loop, the last set of results are
            // valid and we can save them.
            long rtt = endTime - startTime;
            try {
                desc.setDns(i, (int) rtt);
            } catch (MeasurementError e) {
                e.printStackTrace();
            }
            Logger.d("Time for DNS" + rtt);
        }
        incrementData(dataConsumedThisTask);
    }

    /**
     * Time how long it takes to do a TCP 3-way handshake, starting from the induced RRC state.
     * 
     * <ol>
     * <li> Send a packet to initiate the RRC state desired. </li>
     * <li> Open a TCP connection to the echo host server. </li>
     * <li> Time how long it took to look it up. </li>
     * <li> Count the total packets sent, globally on  the phone. If more packets were sent than 
     * expected, abort and try again. </li>
     * <li> Otherwise, save the data for that test and move to the next inter-packet interval.
     * </ol>
     * 
     * @param times List of inter-packet intervals, in half-second increments, at which to run the 
     * test
     * @param desc Stores parameters for the RRC inference tests in general
     */
    public void runTCPHandshakeTest(final Integer[] times, RRCDesc desc) {
        Logger.d("Active inference TCP test: about to begin");
        if (times.length != desc.tcpTest.length) {
            desc.tcpTest = new int[times.length];
        }
        long startTime = 0;
        long endTime = 0;
        long dataConsumedThisTask = 0;

        try {
            // For each inter-packet interval...
            for (int i = 0; i < times.length; i++) {
                // On a failure, try again until a threshhold is reached.
                for (int j = 0; j < desc.GIVEUP_THRESHHOLD; j++) {
                    checkIfWifi();
                    if (stop) {
                        return;
                    }

                    PacketMonitor packetMonitor = new PacketMonitor();

                    // Induce DCH then wait for specified time
                    InetAddress serverAddr;
                    serverAddr = InetAddress.getByName(desc.echoHost);
                    sendPacket(serverAddr, desc.MAX, desc);
                    waitTime(times[i] * 500, true);

                    // begin test. We test the time to do a 3-way handshake only.
                    startTime = System.currentTimeMillis();

                    serverAddr = InetAddress.getByName(desc.target);
                    // three-way handshake done when socket created
                    Socket socket = new Socket(serverAddr, 80);
                    endTime = System.currentTimeMillis();

                    // Not exact, but also a smallish task...
                    dataConsumedThisTask += DnsLookupTask.AVG_DATA_USAGE_BYTE;

                    // Check how many packets were sent again. If the expected number
                    // of packets were sent, we can finish and go to the next task.
                    // Otherwise, we have to try again.
                    if (!packetMonitor.isTrafficInterfering(5, 4)) {
                        socket.close();
                        break;
                    }
                    startTime = 0;
                    endTime = 0;
                    socket.close();
                }
                long rtt = endTime - startTime;
                try {
                    desc.setTcp(i, (int) rtt);
                } catch (MeasurementError e) {
                    e.printStackTrace();
                }
                Logger.d("Time for TCP" + rtt);
            }
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        incrementData(dataConsumedThisTask);
    }

    /**
     * 
     * For all time intervals specified, go through and perform the RRC inference test.
     *  
     * The way this test works, at a high level, is: 
     * <ol>
     * <li> Send a large packet to induce DCH (or the equivalent) </li>
     * <li> Wait for an amount of time, t </li>
     * <li> Send a large packet and measure the round-trip time </li>
     * <li> Wait for another amount of time, t </li>
     * <li> Send a small packet and measure the round-trip time 6. Repeat for all specified values 
     * of t. These start at 0 and increase by GRANULARITY, "size" times. </li>
     * </ol>
     * 
     * The size of "small" and "large" packets are defined in the parameters. We observe the total
     * packets sent to make sure there is no interfering traffic.
     * 
     * Packets are UDP packets.
     * 
     * From this, we can infer the timers associated with RRC states. By sending a large packet, we
     * induce the highest power state. Waiting a number of seconds afterwards allows us to demote to
     * the next state. Sending a packet and observing the RTT allows us to infer if a state promotion
     * had to take place.
     * 
     * FACH is characterized by different state promotion times for large and small packets.
     * 
     * @param serverAddr Address of the echo server
     * @param desc Stores the parameters for the RRC tests
     * @param data Stores the results of the RRC tests
     * @param utils For fetching the signal strength when the test is performed
     * @return The parameters for the RRC tests
     * @throws InterruptedException
     * @throws IOException
     */
    private RRCDesc inferDemotion(InetAddress serverAddr, RRCDesc desc, RRCTestData data, PhoneUtils utils)
            throws InterruptedException, IOException {
        Logger.d("Demotion basic test");

        for (int i = 0; i <= desc.size; i++) {
            this.progress = Math.min(Config.MAX_PROGRESS_BAR_VALUE, (int) (100 * i / (desc.size)));

            checkIfWifi();
            if (stop) {
                return desc;
            }
            inferDemotionHelper(serverAddr, i, data, desc, utils);
            Logger.d("Finished demotion test with length" + i);

            // Note that we scale from 0-90 to save some stuff for upper layer tests.
            // If we wanted to really do this properly we could scale
            // according to how long each task should take.
            this.progress = Math.min(Config.MAX_PROGRESS_BAR_VALUE, (int) (90 * i / desc.size));
        }
        return desc;
    }

    @Override
    public String toString() {
        RRCDesc desc = (RRCDesc) measurementDesc;
        return "[RRC]\n  Echo Server: " + desc.echoHost + "\n  Target: " + desc.target + "\n  Interval (sec): "
                + desc.intervalSec + "\n  Next run: " + desc.startTime;
    }

    /*********************************************************************
     * UTILITIES *
     *********************************************************************/

    /**
     * 
     * Sleep for the amount of time indicated.
     * 
     * @param timeToSleep Time for which we pause the current thread
     * @param useMs Toggles between units of milliseconds for the first parameter (true) and
     *        seconds(false).
     * @throws InterruptedException
     */
    public static void waitTime(int timeToSleep, boolean useMs) throws InterruptedException {
        Logger.d("Wait for n ms: " + timeToSleep);

        if (!useMs) {
            timeToSleep = timeToSleep * 1000;
        }
        Thread.sleep(timeToSleep);
    }

    /**
     * Sends a bunch of UDP packets of the size indicated and wait for the response.
     * 
     * Counts how long it takes for all the packets to return. PAckets are currently not labelled: the
     * total time is the time for the first packet to leave until the last packet arrives. AFter 7000
     * ms it is assumed packets are lost and the socket times out. In that case, the number of packets
     * lost is recorded.
     * 
     * @param serverAddr server to which to send the packets
     * @param size size of the packets
     * @param num number of packets to send
     * @param packetSize size of the packets sent
     * @param port port to send the packets to
     * @return first value: the amount of time to send all packets and get a response. second value:
     *         number of packets lost, on a timeout.
     * @throws IOException
     */
    public static long[] sendMultiPackets(InetAddress serverAddr, int size, int num, int packetSize, int port)
            throws IOException {

        long startTime = 0;
        long endTime = 0;
        byte[] buf = new byte[size];
        byte[] rcvBuf = new byte[packetSize];
        long[] retval = { -1, -1 };
        long numLost = 0;
        int i = 0;
        long dataConsumedThisTask = 0;

        DatagramSocket socket = new DatagramSocket();
        DatagramPacket packetRcv = new DatagramPacket(rcvBuf, rcvBuf.length);
        DatagramPacket packet = new DatagramPacket(buf, buf.length, serverAddr, port);

        // number * (packet sent + packet received)
        dataConsumedThisTask += num * (size + packetSize);

        try {
            socket.setSoTimeout(7000);
            startTime = System.currentTimeMillis();
            Logger.d("Sending packet, waiting for response ");
            for (i = 0; i < num; i++) {
                socket.send(packet);
            }
            for (i = 0; i < num; i++) {
                socket.receive(packetRcv);
                if (i == 0) {

                    endTime = System.currentTimeMillis();
                }
            }
        } catch (SocketTimeoutException e) {
            Logger.d("Timed out");
            numLost += (num - i);
            socket.close();
        }
        Logger.d("Sending complete: " + endTime);

        retval[0] = endTime - startTime;
        retval[1] = numLost;

        incrementData(dataConsumedThisTask);
        return retval;
    }

    /**
     * Helper function that sends a single packet and receives an empty packet back. 
     * @param serverAddr Echo server to calculate round trip
     * @param size size of packet to send in bytes
     * @param desc Holds parameters for the RRC inference task
     * @return The round trip time for the packet
     * @throws IOException
     */
    private static long sendPacket(InetAddress serverAddr, int size, RRCDesc desc) throws IOException {
        return sendPacket(serverAddr, size, desc.MIN, desc.port);
    }

    /** 
     * Send a single packet of the size indicated and wait for a response.
     * 
     * After 7000 ms, time out and return a value of -1 (meaning no response). Otherwise, return the
     * time from when the packet was sent to when a response was returned by the echo server.
     * 
     * @param serverAddr Echo server to calculate round trip
     * @param size size of packet to send in bytes
     * @param rcvSize size of packets sent from the echo server
     * @param port where the echo server is listening
     * @return The round trip time for the packet
     * @throws IOException
     */
    public static long sendPacket(InetAddress serverAddr, int size, int rcvSize, int port) throws IOException {
        long startTime = 0;
        byte[] buf = new byte[size];
        byte[] rcvBuf = new byte[rcvSize];
        long dataConsumedThisTask = 0;

        DatagramSocket socket = new DatagramSocket();
        DatagramPacket packetRcv = new DatagramPacket(rcvBuf, rcvBuf.length);

        dataConsumedThisTask += (size + rcvSize);

        DatagramPacket packet = new DatagramPacket(buf, buf.length, serverAddr, port);

        try {
            socket.setSoTimeout(7000);
            startTime = System.currentTimeMillis();
            Logger.d("Sending packet, waiting for response ");

            socket.send(packet);
            socket.receive(packetRcv);
        } catch (SocketTimeoutException e) {
            Logger.d("Timed out, trying again");
            socket.close();
            return -1;
        }
        long endTime = System.currentTimeMillis();
        Logger.d("Sending complete: " + endTime);
        incrementData(dataConsumedThisTask);
        return endTime - startTime;
    }

    /**
     * Performs a single RRC inference test to account for packet sizes.
     * 
     * Sends a packet, waits for the specified length of time, then sends a cluster of packets of 
     * the specified size.
     * 
     * @param serverAddr  Echo server to calculate round trip
     * @param wait Time to wait between packets, in milliseconds.
     * @param desc  Holds parameters for the RRC inference task
     * @param size Size, in bytes, of the packet to send.
     * @return The amount of time to send all packets and get a response.
     * @throws IOException
     * @throws InterruptedException
     */
    public static long inferDemotionPacketSize(InetAddress serverAddr, int wait, RRCDesc desc, int size)
            throws IOException, InterruptedException {
        long retval = -1;
        for (int j = 0; j < desc.GIVEUP_THRESHHOLD; j++) {
            Logger.d("Active inference: determine packet size, size " + size + " interval " + wait);

            // Induce the highest power state
            sendPacket(serverAddr, desc.MAX, desc.MIN, desc.port);

            // WAit for the specified amount of time
            waitTime(wait * desc.GRANULARITY, true);

            // Send the specified packet size
            long[] rtts = sendMultiPackets(serverAddr, size, 1, desc.MIN, desc.port);
            long rttPacket = rtts[0];

            PacketMonitor packetMonitor = new PacketMonitor();
            if (!packetMonitor.isTrafficInterfering(3, 3)) {
                retval = rttPacket;
                break;
            }
        }
        return retval;
    }

    private long[] inferDemotionHelper(InetAddress serverAddr, int wait, RRCTestData data, RRCDesc desc,
            PhoneUtils utils) throws IOException, InterruptedException {
        return inferDemotionHelper(serverAddr, wait, data, desc, wait, utils);
    }

    /**
     * One component of the RRC inference task. 
     * 
     * <ol>
     * <li> Induce the highest-power RRC state by sending a large packet. </li>
     * <li> Wait the indicated number of seconds. </li>
     * <li> Send a series of 10 large packets at once. Measure: a) Time for all packets to be 
     * echoed back b) number of packets lost, if any c) associated signal strength d) error rate 
     * is currently not implemented. </li>
     * <li> Check if the expected number of packets were sent while performing a test. If too many
     *  packets were sent, abort. </li>
     * </ol>
     * 
     * @param serverAddr Echo server to calculate round trip
     * @param wait Time in milliseconds to pause between packets sent
     * @param data Stores the results of the RRC inference tests 
     * @param desc Stores parameters for the RRC inference tests
     * @param index Index of the current test, corresponds to the inter-packet time in intervals of 
     * half milliseconds
     * @param utils Used to retrieve the phone's RSSI at the time of collecting the data
     * @return first value: the amount of time to send all small packets and get a response. 
     * Second value: time to send all large packets and get a response.
     * @throws IOException
     * @throws InterruptedException
     */
    public static long[] inferDemotionHelper(InetAddress serverAddr, int wait, RRCTestData data, RRCDesc desc,
            int index, PhoneUtils utils) throws IOException, InterruptedException {
        /**
         * Once we generalize the RRC state inference problem, this is what we will use (since in
         * general, RRC state can differ between large and small packets). Gives the RTT for a large
         * packet and a small packet for a given time after inducing DCH state. Granularity currently
         * half-seconds but can easily be increased.
         * 
         * Measures packets sent before and after to make sure no extra packets were sent, and retries
         * on a failure. Also checks that there was no timeout and retries on a failure.
         */
        long rttLargePacket = -1;
        long rttSmallPacket = -1;
        int packetsLostSmall = 0;
        int packetsLostLarge = 0;

        int errorCountLarge = 0;
        int errorCountSmall = 0;
        int signalStrengthLarge = 0;
        int signalStrengthSmall = 0;

        for (int j = 0; j < desc.GIVEUP_THRESHHOLD; j++) {
            Logger.d("Active inference: about to begin helper");

            PacketMonitor packetMonitor = new PacketMonitor();

            // Induce the highest power state
            sendPacket(serverAddr, desc.MAX, desc.MIN, desc.port);

            // WAit for the specified amount of time
            waitTime(wait * desc.GRANULARITY, true);

            // Send a bunch of large packets, all at once, and take measurements on the result
            signalStrengthLarge = utils.getCurrentRssi();
            long[] retval = sendMultiPackets(serverAddr, desc.MAX, 10, desc.MIN, desc.port);
            packetsLostSmall = (int) retval[1];
            rttLargePacket = retval[0];

            // wait for the specified amount of time
            waitTime(wait * desc.GRANULARITY, true);

            // Send a bunch of small packets, all at once, and take measurements on the result
            signalStrengthSmall = utils.getCurrentRssi();
            retval = sendMultiPackets(serverAddr, desc.MIN, 10, desc.MIN, desc.port);
            packetsLostLarge = (int) retval[1];
            rttSmallPacket = retval[0];

            if (!packetMonitor.isTrafficInterfering(21, 21)) {
                break;
            }
            Logger.d("Try again.");
            rttLargePacket = -1;
            rttSmallPacket = -1;
            packetsLostSmall = 0;
            packetsLostLarge = 0;

            errorCountLarge = 0;
            errorCountSmall = 0;
            signalStrengthLarge = 0;
            signalStrengthSmall = 0;
        }

        Logger.d("3G demotion, lower bound: rtts are:" + rttLargePacket + " " + rttSmallPacket + " "
                + packetsLostSmall + " " + packetsLostLarge);

        long[] retval = { rttLargePacket, rttSmallPacket };
        data.updateAll(index, (int) rttLargePacket, (int) rttSmallPacket, packetsLostSmall, packetsLostLarge,
                errorCountLarge, errorCountSmall, signalStrengthLarge, signalStrengthSmall);

        return retval;
    }

    /**
     * Keep a global counter that labels each test with a unique, increasing integer.
     * 
     * @param context Any context instance, needed to fetch the last test id from permanent storage
     * @return The unique (for this device) test ID generated.
     */
    public static synchronized int getTestId(Context context) {
        SharedPreferences prefs = context.getSharedPreferences("test_ids", Context.MODE_PRIVATE);
        int testid = prefs.getInt("test_id", 0) + 1;
        SharedPreferences.Editor editor = prefs.edit();
        editor.putInt("test_id", testid);
        editor.commit();
        return testid;
    }

    /**
     * If on wifi, suspend the task until we go back to the cellular network. Use exponential back-off
     * to calculate the wait time, with a limit of 500 s. Once the limit is reached, unpause other
     * tasks. Repause traffic if we are good to resume again.
     * 
     */
    public void checkIfWifi() {
        PhoneUtils phoneUtils = PhoneUtils.getPhoneUtils();

        int timeToWait = 10;
        while (true) {
            if (phoneUtils.getNetwork() != PhoneUtils.NETWORK_WIFI) {
                RRCTrafficControl.PauseTraffic();
                return;
            }
            if (stop) {
                return;
            }

            if (timeToWait < 60) {
                RRCTrafficControl.UnPauseTraffic();
            }
            Logger.d("RRCTask: on Wifi, try again later: " + phoneUtils.getNetwork());
            if (timeToWait < 500) { // 500s, or a bit over 8 minutes.
                timeToWait = timeToWait * 2;
            } else {
                // if it's taking a while, stop pausing traffic
            }
            try {
                waitTime(timeToWait, false);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private synchronized static void incrementData(long data_increment) {
        data_consumed += data_increment;
    }

    /**  
     * For RRC inference, we calculate this precisely based on the number and size
     * of packets sent.  For the TCP handshake and DNS tasks, we use small, fixed 
     * values based on the average data consumption measured for those tasks.
     * 
     * For the HTTP task, we count <i>all</i> data sent during the task time towards
     * the budget.  This will tend to overestimate the data used, but due to 
     * retransmissions, etc, it is impossible to get a remotely accurate estimate 
     * otherwise, I found.
     */
    @Override
    public long getDataConsumed() {
        return data_consumed;
    }
}