edu.umass.cs.gigapaxos.paxospackets.RequestPacket.java Source code

Java tutorial

Introduction

Here is the source code for edu.umass.cs.gigapaxos.paxospackets.RequestPacket.java

Source

/* Copyright (c) 2015 University of Massachusetts
 * 
 * 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.
 * 
 * Initial developer(s): V. Arun */
package edu.umass.cs.gigapaxos.paxospackets;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Test;

import edu.umass.cs.gigapaxos.PaxosConfig;
import edu.umass.cs.gigapaxos.PaxosManager;
import edu.umass.cs.gigapaxos.PaxosConfig.PC;
import edu.umass.cs.gigapaxos.RequestBatcher;
import edu.umass.cs.gigapaxos.interfaces.ClientRequest;
import edu.umass.cs.gigapaxos.interfaces.Request;
import edu.umass.cs.gigapaxos.paxosutil.Ballot;
import edu.umass.cs.gigapaxos.paxosutil.IntegerMap;
import edu.umass.cs.gigapaxos.paxosutil.PendingDigests;
import edu.umass.cs.gigapaxos.testing.TESTPaxosConfig.TC;
import edu.umass.cs.nio.JSONNIOTransport;
import edu.umass.cs.nio.interfaces.Byteable;
import edu.umass.cs.nio.interfaces.IntegerPacketType;
import edu.umass.cs.utils.Config;
import edu.umass.cs.utils.DefaultTest;
import edu.umass.cs.utils.DelayProfiler;
import edu.umass.cs.utils.Util;

import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;

/**
 * @author arun
 *
 */
@SuppressWarnings("javadoc")
public class RequestPacket extends PaxosPacket implements Request, ClientRequest, Byteable {

    private static final boolean DEBUG = Config.getGlobalBoolean(PC.DEBUG);
    public static final String NO_OP = Request.NO_OP;

    /**
     * These JSON keys are rather specific to RequestPacket or for debugging, so
     * they are here as opposed to PaxosPacket. Application developers don't
     * have to worry about these.
     */
    public static enum Keys {
        /**
         * True if stop request.
         */
        STOP,

        /**
         * Create time.
         */
        CT,

        /**
         * Entry time at the first server.
         */
        ET,

        /**
         * Number of forwards.
         */
        NFWDS,

        /**
         * Most recent forwarder.
         */
        FWDR,

        /**
         * DEBUG mode.
         */
        DBG,

        /**
         * Request ID.
         */
        QID,

        /**
         * Request value.
         */
        QV,

        /**
         * Client socket address.
         */
        CA,

        /**
         * Listening socket address on which request received. Used to decide
         * which messenger to use to send back response.
         */
        LA,

        /**
         * Boolean query flag to specify whether this request came into the
         * system as a string or RequestPacket.
         */
        QF,

        /**
         * Batched requests.
         */
        BATCH,

        /**
         * Response value.
         */
        RV,

        /**
         * Stringified self
         */
        STRINGIFIED,

        /**
         * Digest of request value.
         */
        DIG,

        /**
         * Whether to broadcast.
         */
        BC,
    }

    public static enum ResponseCodes {
        /**
         * 
         */
        ACK,

        /**
         * 
         */
        NACK,
    }

    private static final int MAX_FORWARD_COUNT = 3;
    private static final Random random = new Random();

    /**
     * A unique requestID for each request. Paxos doesn't actually check or care
     * whether two requests with the same ID are identical. This field is useful
     * for asynchronous clients to associate responses with requests.
     * 
     * FIXME: change to long.
     */
    public final long requestID;
    /**
     * Whether this request is a stop request.
     */
    public final boolean stop;

    // non-final fields below

    // the client address in string form
    private InetSocketAddress clientAddress = NULL_SOCKADDR;//null;

    // the client address in string form
    private InetSocketAddress listenAddress = null;

    // the replica that first received this request
    private int entryReplica = IntegerMap.NULL_INT_NODE;

    // used to optimized batching
    private long entryTime = System.currentTimeMillis();

    /* Whether to return requestValue or this.toString() back to client. We need
     * this in order to distinguish between clients that send RequestPacket
     * requests from clients that directly propose a string request value
     * through PaxosManager. */
    private boolean shouldReturnRequestValue = false;

    // needed to stop ping-ponging under coordinator confusion
    private int forwardCount = 0;

    // these two fields below used only with digests (disabled by default)
    private boolean broadcasted = false;
    protected byte[] digest = null;

    /**
     * The actual request body. The client will get back this string if that is
     * what it sent to paxos. If it issued a RequestPacket, then it will get
     * back the whole RequestPacket back.
     */
    public final String requestValue;

    // response to be returned to client
    private String responseValue = null;

    // batch of requests attached to this request
    private RequestPacket[] batched = null;

    // these fields are not passed over the network
    private String stringifiedSelf = null;
    private byte[] byteifiedSelf = null;

    /* These fields are for testing and debugging. They are preserved across
     * forwarding by nodes, so they are not final. They are included only in
     * DEBUG mode */
    private String debugInfo = null;
    // included only in DEBUG mode
    private int forwarderID = IntegerMap.NULL_INT_NODE;

    // ///////// end of all fields /////////////////

    static enum Fields implements GetType {
        // required final fields
        requestID(long.class), stop(boolean.class),

        // address fields
        clientAddress(InetSocketAddress.class), listenAddress(InetSocketAddress.class),

        // non-final
        entryReplica(int.class), entryTime(long.class), shouldReturnRequestValue(boolean.class), forwardCount(
                int.class),

        // digest related fields
        broadcasted(boolean.class), digest((new byte[0]).getClass()),

        // highly variable length fields
        requestValue(String.class), responseValue(String.class), batched((new RequestPacket[0]).getClass());

        final Class<?> type;

        Fields(Class<?> type) {
            this.type = type;
        }

        @Override
        public Class<?> getType() {
            return this.type;
        }
    }

    // used only for initial checking
    static int sizeof(Class<?> clazz) {
        switch (clazz.getSimpleName()) {
        case "int":
            return Integer.BYTES;
        case "long":
            return Long.BYTES;
        case "boolean":
            return 1;
        case "short":
            return Short.BYTES;
        case "InetSocketAddress":
            return Integer.BYTES + Short.BYTES;
        }
        assert (false) : clazz;
        return -1;
    }

    // let a random request ID be picked
    public RequestPacket(String value, boolean stop) {
        this(random.nextInt(), value, stop);
    }

    // the common-case constructor
    public RequestPacket(long reqID, String value, boolean stop) {
        this(reqID, value, stop, (RequestPacket) null);
    }

    public RequestPacket(long reqID, String value, boolean stop, InetSocketAddress csa) {
        this(reqID, value, stop, (RequestPacket) null);
        this.clientAddress = csa != null ? csa : NULL_SOCKADDR;
    }

    public RequestPacket(String value, boolean stop, InetSocketAddress csa) {
        this(value, stop);
        this.clientAddress = csa != null ? csa : NULL_SOCKADDR;
    }

    // called by inheritors
    public RequestPacket(RequestPacket req) {
        this(req.requestID, req.requestValue, req.stop, req);
    }

    // used by makeNoop to convert req to a noop
    public RequestPacket(long reqID, String value, boolean stop, RequestPacket req) {
        super(req); // will take paxosID and version from req

        // final fields
        this.packetType = PaxosPacketType.REQUEST;
        // this.clientID = clientID;
        this.requestID = reqID;
        this.requestValue = value;
        this.stop = stop;

        if (req == null)
            return;

        // non-final fields
        this.entryReplica = req.entryReplica;
        this.clientAddress = req.clientAddress != null ? req.clientAddress : NULL_SOCKADDR;
        this.listenAddress = req.listenAddress;
        this.shouldReturnRequestValue = req.shouldReturnRequestValue;
        this.responseValue = req.responseValue;
        this.digest = req.digest;

        // debug/testing fields
        this.entryTime = req.entryTime;
        this.forwardCount = req.forwardCount;
        this.forwarderID = req.forwarderID;
        this.debugInfo = req.debugInfo;
        this.batched = req.batched;
        this.stringifiedSelf = req.stringifiedSelf;
    }

    public RequestPacket makeNoop() {
        RequestPacket noop = new RequestPacket(requestID, NO_OP, stop, this);
        // make batched requests noop as well
        for (int i = 0; this.batched != null && i < this.batched.length; i++)
            this.batched[i] = this.batched[i].makeNoop();
        // and put them inside the newly minted noop
        noop.batched = this.batched;
        return noop;
    }

    public RequestPacket setReturnRequestValue() {
        this.shouldReturnRequestValue = true;
        return this;
    }

    public int getClientID() {
        if (this.clientAddress != null)
            return this.clientAddress.getPort();
        return -1;
    }

    public boolean isNoop() {
        return this.requestValue.equals(NO_OP);
    }

    private void incrForwardCount() {
        this.forwardCount++;
    }

    public int getForwardCount() {
        return this.forwardCount;
    }

    // we also set entry time when we initialize entryReplica
    public RequestPacket setEntryReplica(int id) {
        if (this.entryReplica == IntegerMap.NULL_INT_NODE) {// one-time
            this.entryReplica = id;
            this.entryTime = System.currentTimeMillis();
        }
        if (this.isBatched())
            for (RequestPacket req : this.batched)
                // recursive but doesn't have to be
                req.setEntryReplica(id);
        return this;
    }

    public void setEntryTime() {
        this.entryTime = System.currentTimeMillis();
        if (this.batchSize() > 0)
            for (RequestPacket req : this.batched)
                req.setEntryTime();
    }

    public int setEntryReplicaAndReturnCount(int id) {
        int count = 0;
        if (this.entryReplica == IntegerMap.NULL_INT_NODE) {// one-time
            this.entryReplica = id;
            this.entryTime = System.currentTimeMillis();
            count++;
        }
        if (this.isBatched())
            for (RequestPacket req : this.batched)
                // recursive but doesn't have to be
                count += req.setEntryReplicaAndReturnCount(id);
        return count;
    }

    // minimum char length of requestValue for it to be digested
    private static final int DIGEST_THRESHOLD_SIZE = Config.getGlobalInt(PC.DIGEST_THRESHOLD_SIZE);

    public boolean shouldDigest() {
        return this.requestValue != null && this.requestValue.length() > DIGEST_THRESHOLD_SIZE;
    }

    public int getEntryReplica() {
        return this.entryReplica;
    }

    public RequestPacket setForwarderID(int id) {
        this.forwarderID = id;
        this.incrForwardCount();
        return this;
    }

    public int getForwarderID() {
        return this.forwarderID;
    }

    private static String makeDebugInfo(String str, long cTime) {
        return str + ":" + (System.currentTimeMillis() - cTime) + " ";
    }

    public RequestPacket addDebugInfo(String str) {
        if (DEBUG)
            this.debugInfo = (this.debugInfo == null ? "" : this.debugInfo)
                    + makeDebugInfo(str, this.getEntryTime());
        return this;
    }

    public RequestPacket addDebugInfoDeep(String str) {
        if (DEBUG)
            if (this.addDebugInfo(str).batched != null)
                for (RequestPacket req : this.batched)
                    req.addDebugInfo(str);
        return this;
    }

    public RequestPacket addDebugInfo(String str, int nodeID) {
        if (DEBUG)
            this.addDebugInfo(str + nodeID);
        return this;
    }

    public RequestPacket addDebugInfoDeep(String str, int nodeID) {
        if (DEBUG)
            this.addDebugInfoDeep(str + nodeID);
        return this;
    }

    public static boolean addDebugInfo(JSONObject msg, String str, int nodeID) throws JSONException {
        boolean added = false;
        if (DEBUG && msg.has(Keys.DBG.toString()) && msg.has(Keys.CT.toString())) {
            str = str + nodeID;
            String debug = msg.getString(Keys.DBG.toString()) + makeDebugInfo(str, msg.getLong(Keys.CT.toString()));
            added = true;
            msg.put(Keys.DBG.toString(), debug);
        }
        return added;
    }

    public String getDebugInfo(boolean get) {
        return get ? " [" + this.debugInfo + "] " : null;
    }

    public static boolean isPingPonging(JSONObject msg) {
        try {
            if (msg.has(Keys.NFWDS.toString()) && msg.getInt(Keys.NFWDS.toString()) > MAX_FORWARD_COUNT) {
                return true;
            }
        } catch (JSONException je) {
            je.printStackTrace();
        }
        return false;
    }

    public boolean isPingPonging() {
        return this.forwardCount > MAX_FORWARD_COUNT;
    }

    public RequestPacket(JSONObject json) throws JSONException {
        super(json);
        this.packetType = PaxosPacketType.REQUEST;
        this.stop = json.optBoolean(Keys.STOP.toString());
        this.requestID = json.getLong(Keys.QID.toString());
        this.requestValue = json.getString(Keys.QV.toString());

        this.responseValue = json.has(Keys.RV.toString()) ? json.getString(Keys.RV.toString()) : null;
        this.entryTime = json.getLong(Keys.ET.toString());
        this.forwardCount = (json.has(Keys.NFWDS.toString()) ? json.getInt(Keys.NFWDS.toString()) : 0);
        this.forwarderID = (json.has(RequestPacket.Keys.FWDR.toString())
                ? json.getInt(RequestPacket.Keys.FWDR.toString())
                : IntegerMap.NULL_INT_NODE);
        this.debugInfo = (json.has(Keys.DBG.toString()) ? json.getString(Keys.DBG.toString()) : "");

        this.clientAddress = (json.has(Keys.CA.toString())
                ? Util.getInetSocketAddressFromString(json.getString(Keys.CA.toString()))
                : JSONNIOTransport.getSenderAddress(json));
        this.listenAddress = (json.has(Keys.LA.toString())
                ? Util.getInetSocketAddressFromString(json.getString(Keys.LA.toString()))
                : JSONNIOTransport.getReceiverAddress(json));

        this.entryReplica = json.getInt(PaxosPacket.NodeIDKeys.E.toString());
        this.shouldReturnRequestValue = json.optBoolean(Keys.QF.toString());

        // unwrap latched along batch
        JSONArray batchedJSON = json.has(Keys.BATCH.toString()) ? json.getJSONArray(Keys.BATCH.toString()) : null;
        if (batchedJSON != null && batchedJSON.length() > 0) {
            this.batched = new RequestPacket[batchedJSON.length()];
            for (int i = 0; i < batchedJSON.length(); i++) {
                this.batched[i] = new RequestPacket((JSONObject) batchedJSON.get(i)
                // new JSONObject(batchedJSON.getString(i))
                );
            }
        }

        // we remembered the original string for recalling here
        this.stringifiedSelf = json.has(Keys.STRINGIFIED.toString())
                ? (String) json.get(Keys.STRINGIFIED.toString())
                : null;

        if (json.has(Keys.BC.toString()))
            this.broadcasted = json.getBoolean(Keys.BC.toString());
        if (json.has(Keys.DIG.toString()))
            try {
                this.digest = json.getString(Keys.DIG.toString()).getBytes(CHARSET);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
    }

    // private static final boolean USE_JSON_SMART =
    // !Config.getGlobalString(PC.JSON_LIBRARY).equals("org.json");

    public String getStringifiedSelf() {
        return this.stringifiedSelf;
    }

    public RequestPacket setStringifiedSelf(String s) {
        this.stringifiedSelf = s;
        // if(Util.oneIn(10)) DelayProfiler.updateMovAvg("stringified",
        // s.length());
        return this;
    }

    // for comparing against a different json implementation
    public RequestPacket(net.minidev.json.JSONObject json) throws JSONException {
        super(json);
        assert (PaxosPacket.getPaxosPacketType(json) != PaxosPacketType.ACCEPT
                || json.containsKey(Keys.STRINGIFIED.toString()));
        this.packetType = PaxosPacketType.REQUEST;
        this.stop = json.containsKey(Keys.STOP.toString()) ? (Boolean) json.get(Keys.STOP.toString()) : false;
        this.requestID = Util.toLong(json.get(Keys.QID.toString()));
        this.requestValue = (String) json.get(Keys.QV.toString());

        this.responseValue = json.containsKey(Keys.RV.toString()) ? (String) json.get(Keys.RV.toString()) : null;
        this.entryTime = Util.toLong(json.get(Keys.ET.toString()));
        this.forwardCount = (json.containsKey(Keys.NFWDS.toString()) ? (Integer) json.get(Keys.NFWDS.toString())
                : 0);
        this.forwarderID = (json.containsKey(RequestPacket.Keys.FWDR.toString())
                ? (Integer) json.get(RequestPacket.Keys.FWDR.toString())
                : IntegerMap.NULL_INT_NODE);
        this.debugInfo = (json.containsKey(Keys.DBG.toString()) ? (String) json.get(Keys.DBG.toString()) : "");

        this.clientAddress = (json.containsKey(Keys.CA.toString())
                ? Util.getInetSocketAddressFromString((String) (json.get(Keys.CA.toString())))
                : JSONNIOTransport.getSenderAddressJSONSmart(json));
        this.listenAddress = (json.containsKey(Keys.LA.toString())
                ? Util.getInetSocketAddressFromString((String) (json.get(Keys.LA.toString())))
                : JSONNIOTransport.getReceiverAddressJSONSmart(json));

        this.entryReplica = (Integer) json.get(PaxosPacket.NodeIDKeys.E.toString());
        this.shouldReturnRequestValue = json.containsKey(Keys.QF.toString())
                ? (Boolean) json.get(Keys.QF.toString())
                : false;

        // unwrap latched along batch
        Collection<?> batchedJSON = json.containsKey(Keys.BATCH.toString())
                ? (Collection<?>) json.get(Keys.BATCH.toString())
                : null;
        if (batchedJSON != null && batchedJSON.size() > 0) {
            this.batched = new RequestPacket[batchedJSON.size()];
            int i = 0;
            for (Object element : batchedJSON) {
                this.batched[i++] = new RequestPacket((net.minidev.json.JSONObject) element
                // (net.minidev.json.JSONObject)net.minidev.json.JSONValue.parse((String)element)
                );
            }
        }

        // we remembered the original string for recalling here
        this.stringifiedSelf = json.containsKey(Keys.STRINGIFIED.toString())
                ? (String) json.get(Keys.STRINGIFIED.toString())
                : null;
        assert (PaxosPacket.getPaxosPacketType(json) != PaxosPacketType.ACCEPT
                || this.stringifiedSelf != null) : PaxosPacket.getPaxosPacketType(json) + ":" + json;

        if (json.containsKey(Keys.BC.toString()))
            this.broadcasted = (Boolean) json.get(Keys.BC.toString());
        if (json.containsKey(Keys.DIG.toString()))
            try {
                this.digest = ((String) json.get(Keys.DIG.toString())).getBytes(CHARSET);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
    }

    @Override
    public JSONObject toJSONObjectImpl() throws JSONException {
        JSONObject json = new JSONObject();
        json.put(Keys.QID.toString(), this.requestID);
        json.put(Keys.QV.toString(), this.requestValue);

        json.putOpt(Keys.RV.toString(), this.responseValue);
        json.put(Keys.ET.toString(), this.entryTime);
        if (forwardCount > 0)
            json.put(Keys.NFWDS.toString(), this.forwardCount);
        if (this.stop)
            json.put(Keys.STOP.toString(), this.stop);
        if (DEBUG) {
            json.put(RequestPacket.Keys.FWDR.toString(), this.forwarderID);
            json.putOpt(Keys.DBG.toString(), this.debugInfo);
        }
        json.put(PaxosPacket.NodeIDKeys.E.toString(), this.entryReplica);
        if (this.clientAddress != null)
            json.put(Keys.CA.toString(), this.clientAddress);
        if (this.listenAddress != null)
            json.put(Keys.LA.toString(), this.listenAddress);

        if (this.shouldReturnRequestValue)
            json.put(Keys.QF.toString(), this.shouldReturnRequestValue);
        // convert latched along batch to json array
        if (this.batched != null && this.batched.length > 0) {
            JSONArray batchedJSON = new JSONArray();
            for (int i = 0; i < this.batched.length; i++) {
                batchedJSON.put(this.batched[i].toJSONObject());
                // batchedJSON.put(this.batched[i].toString());
            }
            json.put(Keys.BATCH.toString(), batchedJSON);
        }
        // digest related parameters
        if (this.broadcasted)
            json.put(Keys.BC.toString(), this.broadcasted);
        if (this.digest != null)
            try {
                json.put(Keys.DIG.toString(), new String(this.digest, CHARSET));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }

        return json;
    }

    public net.minidev.json.JSONObject toJSONSmartImpl() throws JSONException {
        net.minidev.json.JSONObject json = new net.minidev.json.JSONObject();
        json.put(Keys.QID.toString(), this.requestID);
        json.put(Keys.QV.toString(), this.requestValue);

        if (this.responseValue != null)
            json.put(Keys.RV.toString(), this.responseValue);
        json.put(Keys.ET.toString(), this.entryTime);
        if (forwardCount > 0)
            json.put(Keys.NFWDS.toString(), this.forwardCount);
        if (this.stop)
            json.put(Keys.STOP.toString(), this.stop);
        if (DEBUG) {
            json.put(RequestPacket.Keys.FWDR.toString(), this.forwarderID);
            if (this.debugInfo != null)
                json.put(Keys.DBG.toString(), this.debugInfo);
        }
        json.put(PaxosPacket.NodeIDKeys.E.toString(), this.entryReplica);
        if (this.clientAddress != null)
            json.put(Keys.CA.toString(), this.clientAddress.toString());
        if (this.listenAddress != null)
            json.put(Keys.LA.toString(), this.listenAddress.toString());
        if (this.shouldReturnRequestValue)
            json.put(Keys.QF.toString(), this.shouldReturnRequestValue);
        // convert latched along batch to json array
        if (this.batched != null && this.batched.length > 0) {
            net.minidev.json.JSONArray batchedJSON = new net.minidev.json.JSONArray();
            for (int i = 0; i < this.batched.length; i++) {
                batchedJSON.add(this.batched[i].toJSONSmart());
                // batchedJSON.add(this.batched[i].toString());
            }
            json.put(Keys.BATCH.toString(), batchedJSON);
        }

        // digest related parameters
        if (this.broadcasted)
            json.put(Keys.BC.toString(), this.broadcasted);
        if (this.digest != null)
            try {
                json.put(Keys.DIG.toString(), new String(this.digest, CHARSET));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }

        return json;
    }

    public byte[] toBytesInstrument() {
        return this.toBytes(true);
    }

    @Override
    public byte[] toBytes() {
        return this.toBytes(false);
    }

    /**
     * Double-check statically that everything up to {@link #broadcasted} has
     * the length {@link #SIZEOF_REQUEST_FIXED} that we expect.
     */
    private static void checkMyFields() {
        Field[] fields = RequestPacket.class.getDeclaredFields();
        int totalSize = 0;
        if (edu.umass.cs.gigapaxos.PaxosManager.getLogger().isLoggable(Level.FINE))
            System.out.println(RequestPacket.class + " has " + fields.length + " fields");
        for (int i = 0; i < fields.length; i++) {
            if (isStatic(fields[i]))
                continue;
            assert (!fields[i].getType().equals(String.class)) : fields[i];
            totalSize += sizeof(fields[i].getType());
            if (fields[i].getName().equals("broadcasted"))
                break;
        }
        /* The 16 corresponds to 4 integers for the 4 variable length fields:
         * digest, requestValue, responseValue, and batched. */
        assert (totalSize == SIZEOF_REQUEST_FIXED - 4 * Integer.BYTES) : totalSize + " != "
                + (SIZEOF_REQUEST_FIXED - 4 * Integer.BYTES);
    }

    // we use one byte for a boolean
    protected static final int SIZEOF_REQUEST_FIXED = 8 // long requestID
            + 1 // boolean stop

            + Integer.BYTES // client IP
            + Short.BYTES // client port
            + Integer.BYTES // received IP
            + Short.BYTES // received port

            + Integer.BYTES // int entryReplica
            + Long.BYTES // long entryTime
            + 1 // boolean shouldReturnRequestValue
            + Integer.BYTES // int forwardCount

            + 1 // boolean broadcasted
            + Integer.BYTES // int digest length

            + Integer.BYTES // int requestValue length
            + Integer.BYTES // int responseValue length
            + Integer.BYTES // int batchSize
    ;

    /**
     * The weird constant above is to try to avoid mistakes in the painful (but
     * totally worth it) byte'ification method below. Using bytes as opposed to
     * json strings makes a non-trivial difference (~2x over json-smart and >4x
     * over org.json. So we just chuck json libraries and use our own byte[]
     * serializer for select packets.
     * 
     * The serialization overhead really matters most for RequestPacket and
     * AcceptPacket. Every request, even with batching, must be deserialized by
     * the coordinator and must be serialized back while sending out the
     * AcceptPacket. The critical path is the following at a coordinator and is
     * incurred at least in part even with batching for every request: (1)
     * receive request, (2) send accept, (3) receive accept_replies, (4) send
     * commit Accordingly, we use byteification for {@link RequestPacket},
     * {@link AcceptPacket}, {@link BatchedAcceptReply} and
     * {@link BatchedCommit}.
     * 
     * */

    protected byte[] toBytes(boolean instrument) {
        // return cached value if already present
        if ((this.getType() == PaxosPacketType.REQUEST || this.getType() == PaxosPacketType.ACCEPT)
                && this.byteifiedSelf != null && !instrument)
            return this.byteifiedSelf;
        // check if we can use byteification at all; if not, use toString()
        if (!((BYTEIFICATION && IntegerMap.allInt()) || instrument)) {
            try {
                if (this.getType() == PaxosPacketType.REQUEST || this.getType() == PaxosPacketType.ACCEPT)
                    return this.byteifiedSelf = this.toString().getBytes(CHARSET); // cache
                return this.toString().getBytes(CHARSET);
            } catch (UnsupportedEncodingException e1) {
                e1.printStackTrace();
                return null;
            }
        }

        // else byteify
        try {
            int exactLength = 0;
            byte[] array = new byte[this.lengthEstimate()];
            ByteBuffer bbuf = ByteBuffer.wrap(array);
            assert (bbuf.position() == 0);

            // paxospacket stuff
            super.toBytes(bbuf);
            int ppPos = bbuf.position(); // for assertion
            assert (bbuf.position() == ByteBuffer.wrap(array, SIZEOF_PAXOSPACKET_FIXED - 1, 1).get()
                    + SIZEOF_PAXOSPACKET_FIXED) : bbuf.position() + " != "
                            + ByteBuffer.wrap(array, SIZEOF_PAXOSPACKET_FIXED - 1, 1).get()
                            + SIZEOF_PAXOSPACKET_FIXED;
            exactLength += (bbuf.position());

            bbuf.putLong(this.requestID);
            bbuf.put(this.stop ? (byte) 1 : (byte) 0);
            exactLength += (Long.BYTES + 1);

            // addresses
            /* Note: 0 is ambiguous with wildcard address, but that's okay
             * because an incoming packet will never come with a wildcard
             * address. */
            bbuf.put(this.clientAddress != null ? this.clientAddress.getAddress().getAddress() : new byte[4]);
            // 0 (not -1) means invalid port
            bbuf.putShort(this.clientAddress != null ? (short) this.clientAddress.getPort() : 0);
            /* Note: 0 is an ambiguous wildcard address that could also be a
             * legitimate value of the listening socket address. If the request
             * happens to have no listening address, we will end up assuming it
             * was received on the wildcard address. At worst, the matching for
             * the corresponding response back to the client can fail. */
            bbuf.put(this.listenAddress != null ? this.listenAddress.getAddress().getAddress() : new byte[4]);
            // 0 (not -1) means invalid port
            bbuf.putShort(this.listenAddress != null ? (short) this.listenAddress.getPort() : 0);
            exactLength += 2 * (Integer.BYTES + Short.BYTES);

            // other non-final fields
            bbuf.putInt(this.entryReplica);
            bbuf.putLong(this.entryTime);
            bbuf.put(this.shouldReturnRequestValue ? (byte) 1 : (byte) 0);
            bbuf.putInt(this.forwardCount);
            exactLength += (Integer.BYTES + Long.BYTES + 1 + Integer.BYTES);

            // digest related fields: broadcasted, digest
            // whether this request was already broadcasted
            bbuf.put(this.broadcasted ? (byte) 1 : (byte) 0);
            exactLength += 1;
            assert (exactLength ==
            // where parent left us off
            ppPos + SIZEOF_REQUEST_FIXED
            // for the three int fields not yet filled
                    - 4 * Integer.BYTES) : exactLength + " != [" + ppPos + " + " + SIZEOF_REQUEST_FIXED + " - "
                            + 4 * Integer.BYTES + "]";
            // digest length and digest iteself
            bbuf.putInt(this.digest != null ? this.digest.length : 0);
            exactLength += Integer.BYTES;
            if (this.digest != null)
                bbuf.put(this.digest);
            exactLength += (this.digest != null ? this.digest.length : 0);
            // /////////// end of digest related fields //////////

            // highly variable length fields
            // requestValue
            byte[] reqValBytes = this.requestValue != null ? this.requestValue.getBytes(CHARSET) : new byte[0];
            bbuf.putInt(reqValBytes != null ? reqValBytes.length : 0);
            bbuf.put(reqValBytes);
            exactLength += (4 + reqValBytes.length);

            // responseValue
            byte[] respValBytes = this.responseValue != null ? this.responseValue.getBytes(CHARSET) : new byte[0];
            bbuf.putInt(respValBytes != null ? respValBytes.length : 0);
            bbuf.put(respValBytes);
            exactLength += (4 + respValBytes.length);

            // batched requests batchSize|(length:batchedReqBytes)+
            bbuf.putInt(this.batchSize());
            exactLength += (4);
            if (this.batchSize() > 0)
                for (RequestPacket req : this.batched) {
                    byte[] element = req.toBytes();
                    bbuf.putInt(element.length);
                    bbuf.put(element);
                    exactLength += (4 + element.length);
                }

            // bbuf.array() was a generous allocation
            byte[] exactBytes = new byte[exactLength];
            bbuf.flip();
            assert (bbuf.remaining() == exactLength) : bbuf.remaining() + " != " + exactLength;
            bbuf.get(exactBytes);

            if (this.getType() == PaxosPacketType.REQUEST)
                this.byteifiedSelf = exactBytes;

            return exactBytes;

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    public RequestPacket(byte[] bytes) throws UnsupportedEncodingException, UnknownHostException {
        this(ByteBuffer.wrap(bytes));
    }

    public RequestPacket(ByteBuffer bbuf) throws UnsupportedEncodingException, UnknownHostException {
        super(bbuf);
        int exactLength = bbuf.position();

        this.requestID = bbuf.getLong();
        this.stop = bbuf.get() == (byte) 1;
        exactLength += (8 + 1);

        // addresses
        byte[] ca = new byte[4];
        bbuf.get(ca);
        int cport = (int) bbuf.getShort();
        cport = cport >= 0 ? cport : cport + 2 * (Short.MAX_VALUE + 1);
        this.clientAddress = cport != 0 ? new InetSocketAddress(InetAddress.getByAddress(ca), cport) : null;
        byte[] la = new byte[4];
        bbuf.get(la);
        int lport = (int) bbuf.getShort();
        lport = lport >= 0 ? lport : lport + 2 * (Short.MAX_VALUE + 1);
        this.listenAddress = lport != 0 ? new InetSocketAddress(InetAddress.getByAddress(la), lport) : null;
        exactLength += (4 + 2 + 4 + 2);

        // other non-final fields
        this.entryReplica = bbuf.getInt();
        this.entryTime = bbuf.getLong();
        this.shouldReturnRequestValue = bbuf.get() == (byte) 1;
        this.forwardCount = bbuf.getInt();
        exactLength += (4 + 8 + 1 + 4);

        // digest related fields
        this.broadcasted = bbuf.get() == (byte) 1;
        int digestLength = bbuf.getInt();
        if (digestLength > 0)
            bbuf.get(this.digest = new byte[digestLength]);

        // highly variable length fields

        // requestValue
        int reqValLen = bbuf.getInt();
        byte[] reqValBytes = new byte[reqValLen];
        bbuf.get(reqValBytes);
        this.requestValue = reqValBytes.length > 0 ? new String(reqValBytes, CHARSET) : null;
        exactLength += (4 + reqValBytes.length);

        // responseValue
        int respValLen = bbuf.getInt();
        byte[] respValBytes = new byte[respValLen];
        bbuf.get(respValBytes);
        this.responseValue = respValBytes.length > 0 ? new String(respValBytes, CHARSET) : null;
        exactLength += (4 + respValBytes.length);

        int numBatched = bbuf.getInt();
        if (numBatched == 0)
            return;
        // else
        // batched requests
        this.batched = new RequestPacket[numBatched];
        for (int i = 0; i < numBatched; i++) {
            int len = bbuf.getInt();
            byte[] element = new byte[len];
            bbuf.get(element);
            this.batched[i] = new RequestPacket(element);
        }
        assert (exactLength > 0);
    }

    /**
     * Learned the hard way that using org.json to stringify is an order of
     * magnitude slower with large request values compared to manually inserting
     * the string like below. json-smart is fast enough for our purposes but
     * still doesn't beat direct string construction like below.
     * 
     * Note: we need to account for batching carefully here. So, we use a json
     * array of strings as opposed to json objects for batched requests above.
     * Otherwise, this method will only use string construction for the first
     * request in a batch.
     */
    public String toString() {
        try {
            if (this.packetType == PaxosPacketType.ACCEPT)
                if (this.stringifiedSelf != null)
                    return this.stringifiedSelf;
                else {
                    // should never come here because of PaxosMessenger
                    // pre-stringification to fix int to string.
                    return (this.stringifiedSelf = this.toJSONSmart().toString());
                }
            return this.toJSONSmart().toString();
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return null;
    }

    // only for size estimation
    private RequestPacket setClientAddress(InetSocketAddress sockAddr) {
        this.clientAddress = sockAddr;
        return this;
    }

    public InetSocketAddress getClientAddress() {
        return this.clientAddress;
    }

    public InetSocketAddress getListenAddress() {
        return this.listenAddress;
    }

    public boolean isStopRequest() {
        return stop || this.isAnyBatchedRequestStop();
    }

    private boolean isAnyBatchedRequestStop() {
        if (this.batchSize() == 0)
            return false;
        for (RequestPacket req : this.batched)
            if (req.isStopRequest())
                return true;
        return false;
    }

    public long getEntryTime() {
        return this.entryTime;
    }

    private boolean isBatched() {
        return this.batchSize() > 0;
    }

    public RequestPacket latchToBatch(RequestPacket[] reqs) {
        // first flatten out the argument
        RequestPacket[] allThreaded = toArray(reqs);
        if (this.batched == null)
            this.batched = allThreaded;
        else
            this.batched = concatenate(this.batched, allThreaded);
        for (int i = 0; i < this.batched.length; i++)
            assert (!this.batched[i].isBatched());
        return this;
    }

    private static RequestPacket[] concatenate(RequestPacket[] a, RequestPacket[] b) {
        RequestPacket[] c = new RequestPacket[a.length + b.length];
        for (int i = 0; i < a.length; i++)
            c[i] = a[i];
        for (int i = 0; i < b.length; i++)
            c[a.length + i] = b[i];
        return c;
    }

    /* Returns this request unraveled as an array wherein each element is an
     * unbatched request.
     * 
     * Note: This operation is not idempotent because batched gets reset to
     * null. */
    private RequestPacket[] toArray() {
        RequestPacket[] array = new RequestPacket[1 + this.batchSize()];
        array[0] = this;
        for (int i = 0; i < this.batchSize(); i++) {
            array[i + 1] = this.batched[i];
            assert (!this.batched[i].isBatched());
        }
        // toArray always returns an array of unbatched packets
        this.batched = null;
        return array;
    }

    /* Converts an array of possibly batched requests to a single unraveled
     * array wherein each request is unbatched. */
    private static RequestPacket[] toArray(RequestPacket[] reqs) {
        ArrayList<RequestPacket[]> reqArrayList = new ArrayList<RequestPacket[]>();
        int totalSize = 0;
        for (RequestPacket req : reqs) {
            RequestPacket[] reqArray = req.toArray();
            totalSize += reqArray.length;
            reqArrayList.add(reqArray);
        }
        assert (totalSize == size(reqArrayList));
        RequestPacket[] allThreaded = new RequestPacket[totalSize];
        int count = 0;
        for (RequestPacket[] reqArray : reqArrayList) {
            for (int j = 0; j < reqArray.length; j++) {
                assert (!reqArray[j].isBatched());
                allThreaded[count++] = reqArray[j];
            }
        }
        assert (count == totalSize) : count + " != " + totalSize + " while unraveling " + print(reqArrayList);
        return allThreaded;
    }

    public boolean isMetaValue() {
        return this.requestValue == null;
    }

    private static int size(ArrayList<RequestPacket[]> reqArrayList) {
        int size = 0;
        for (RequestPacket[] reqArray : reqArrayList)
            size += reqArray.length;
        return size;
    }

    public RequestPacket[] getBatched() {
        return this.batched;
    }

    // gets only the first request without the batch
    protected RequestPacket getFirstOnly() {
        if (this.batchSize() == 0)
            return this;
        // else
        RequestPacket req = (
        // retain slot in first coz it is useful for testing
        this instanceof ProposalPacket ? new ProposalPacket(((ProposalPacket) this).slot, this)
                // create new before modifying this
                : new RequestPacket(this));
        req.batched = null;
        return req;
    }

    public RequestPacket getACK() {
        // RequestPacket req = this.getFirstOnly();
        RequestPacket reply = new RequestPacket(this.requestID, ResponseCodes.ACK.toString(), this.stop, this);
        reply.batched = null;
        reply.responseValue = this.responseValue;
        return reply;
    }

    public RequestPacket getNACK() {
        RequestPacket req = this.getFirstOnly();
        return new RequestPacket(req.requestID, ResponseCodes.NACK.toString(), req.stop, req);
    }

    private static String print(ArrayList<RequestPacket[]> reqArrayList) {
        String s = "[\n";
        int count = 0;
        for (RequestPacket[] reqArray : reqArrayList) {
            s += "req" + count++ + " = \n[\n";
            for (RequestPacket req : reqArray) {
                s += "    " + req + "\n";
            }
            s += "]\n";

        }
        return s;
    }

    public String getRequestValue() {
        return this.shouldReturnRequestValue ? this.requestValue : this.toString();
    }

    public String[] getRequestValues() {
        String[] reqValues = null;
        if (this.shouldReturnRequestValue) {
            reqValues = new String[this.batchSize() + 1];
            reqValues[0] = this.requestValue;
            if (this.batched != null)
                for (int i = 0; i < this.batched.length; i++) {
                    reqValues[i + 1] = this.batched[i].shouldReturnRequestValue ? this.batched[i].requestValue
                            : this.batched[i].toString();
                    assert (this.batched[i].batched == null);
                }
        } else {
            reqValues = new String[1];
            reqValues[0] = toString();
        }
        return reqValues;
    }

    public RequestPacket[] getRequestPackets() {
        RequestPacket[] reqs = new RequestPacket[this.batchSize() + 1];
        reqs[0] = this.getFirstOnly();
        for (int i = 0; i < this.batchSize(); i++)
            reqs[i + 1] = this.batched[i];
        return reqs;
    }

    public int batchSize() {
        return this.batched != null ? this.batched.length : 0;
    }

    /* This ugly method is used only for testing and is needed in order to
     * separate requests that first entered the requests (or requests with
     * entryReplica==-1) from the rest in a batched request. */
    public RequestPacket[] getEntryReplicaRequestsAsBatch(int id) {
        RequestPacket[] reqArray = this.toArray(); // all unbatched

        List<RequestPacket> noEntryReplicaRequests = new LinkedList<RequestPacket>();
        List<RequestPacket> entryReplicaRequests = new LinkedList<RequestPacket>();

        for (int i = 0; i < reqArray.length; i++)
            if (reqArray[i].getEntryReplica() == IntegerMap.NULL_INT_NODE || reqArray[i].getEntryReplica() == id)
                noEntryReplicaRequests.add(reqArray[i]);
            else
                entryReplicaRequests.add(reqArray[i]);

        // [0] is old, [1] is new
        RequestPacket[] retRequests = new RequestPacket[2];
        if (entryReplicaRequests.size() > 0) {
            // else requests with no entry replica exist
            RequestPacket entryReplicaRequest = entryReplicaRequests.remove(0);
            if (!entryReplicaRequests.isEmpty())
                entryReplicaRequest.latchToBatch(noEntryReplicaRequests.toArray(new RequestPacket[0]));
            retRequests[0] = entryReplicaRequest;
        }
        if (noEntryReplicaRequests.size() > 0) {
            // else requests with no entry replica exist
            RequestPacket noEntryReplicaRequest = noEntryReplicaRequests.remove(0);
            if (!noEntryReplicaRequests.isEmpty())
                noEntryReplicaRequest.latchToBatch(noEntryReplicaRequests.toArray(new RequestPacket[0]));
            retRequests[1] = noEntryReplicaRequest;
        }
        return retRequests;
    }

    @Override
    public IntegerPacketType getRequestType() {
        return this.getType();
    }

    @Override
    public String getServiceName() {
        return this.paxosID;
    }

    @Override
    protected String getSummaryString() {
        return requestID + (this.isBroadcasted() ? "*" : "") + ":" + "["
                + (this.requestValue != null && NO_OP.equals(this.requestValue) ? NO_OP
                        : this.requestValue == null ? "null" : "...")
                + "]" + (stop ? ":STOP" : "")
                + (isBatched()
                        ? "+(" + batchSize() + " batched "
                                + (this.batchSize() <= 4 ? getBatchedIDs() : this.batchSize()) + ")"
                        : "");
    }

    private String getBatchedIDs() {
        String s = "[";
        if (this.batched != null)
            for (RequestPacket req : this.batched) {
                s += req.requestID + (req.isBroadcasted() ? "*" : "") + " ";
            }
        return s + "]";
    }

    // testing
    private static PValuePacket getRandomPValue(String paxosID, int version, int slot, Ballot ballot, boolean stop,
            InetSocketAddress isa) {
        PValuePacket pvalue = new PValuePacket(ballot,
                new ProposalPacket(slot, new RequestPacket("some_request", stop).setClientAddress(isa)));
        pvalue.putPaxosID(paxosID, version);
        return pvalue;
    }

    public static PValuePacket getRandomPValue(String paxosID, int version, int slot, Ballot ballot) {
        return getRandomPValue(paxosID, version, slot, ballot, false, null);
    }

    static PValuePacket samplePValue = null;

    public static PValuePacket getSamplePValue() {
        return samplePValue;
    }

    /**
     * We need this estimate to use it in {@link RequestBatcher#dequeueImpl()}.
     * The value needs to be an upper bound on the sum total of all of the gunk
     * in PValuePacket other than the requestValue itself, i.e., the size of a
     * no-op decision.
     */
    public static final int SIZE_ESTIMATE = estimateSize();

    private static int estimateSize() {
        int length = 0;
        try {
            length = (samplePValue == null
                    ? (samplePValue = (PValuePacket) (getRandomPValue(TC.TEST_GUID.toString(), 0, 3142,
                            new Ballot(23, 2178), true, new InetSocketAddress("128.119.245.40", 2000))))
                    : samplePValue).toJSONObject().toString().length();
            // 25% extra for other miscellaneous additions
            return (int) (length * 1.25);

        } catch (JSONException e) {
            e.printStackTrace();
        }
        throw new RuntimeException("Failed to get size");
    }

    public boolean hasRequestValue() {
        return this.requestValue != null;
    }

    /* Need an upper bound here for limiting batch size. Currently all the
     * fields in RequestPacket other than requestValue add up to ~200B. */
    public int lengthEstimate() {
        int len = this.requestValue.length() + SIZE_ESTIMATE;
        if (this.isBatched())
            for (RequestPacket req : this.batched)
                len += req.lengthEstimate();
        return len;
    }

    public String printBatched() {
        String s = "";
        s += this.getSummary();
        if (this.batchSize() > 0)
            for (int i = 0; i < this.batchSize(); i++)
                s += "\n  " + this.batched[i].getSummary();
        return s;
    }

    @Override
    public ClientRequest getResponse() {
        return this.getACK();
    }

    public void setResponse(String response) {
        if (this.responseValue == null)
            this.responseValue = response;
        else
            assert (isRecovery(this));
    }

    public boolean shouldReturnRequestValue() {
        return this.shouldReturnRequestValue;
    }

    @Override
    public long getRequestID() {
        return this.requestID;
    }

    // in-depth
    public byte[] getDigest(MessageDigest md) {
        if (this.digest != null)
            return digest;

        try {
            // long t = System.nanoTime();
            assert (this.requestValue != null);
            synchronized (md) {
                this.digest = md.digest(this.requestValue.getBytes(CHARSET));
            }
            // DelayProfiler.updateDelayNano("digest", t);
            return this.digest;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    public boolean digestEquals(RequestPacket req, MessageDigest md) {
        byte[] d1 = this.getDigest(md);
        byte[] d2 = req.getDigest(md);
        return (MessageDigest.isEqual(d1, d2));
    }

    // only top level
    public RequestPacket setDigest(byte[] d) {
        assert (d != null);
        // assert (this.digest == null && this.requestValue == null);
        this.digest = d;
        return this;
    }

    public RequestPacket setBroadcasted() {
        this.broadcasted = true;
        /* Marking batch members as broadcasted is unnecessary as a broadcasted
         * request packet can not be batched any further. */
        return this;
    }

    public boolean shouldBroadcast() {
        return this.broadcasted == false;
    }

    public boolean isBroadcasted() {
        return this.broadcasted == true;
    }

    @Override
    public Object getSummary() {
        return super.getSummary();
    }

    protected byte[] getByteifiedSelf() {
        return this.byteifiedSelf;
    }

    protected void setByteifiedSelf(byte[] bytes) {
        this.byteifiedSelf = bytes;
    }

    /** We use this address instead of a null client socket address or listen address
     * so that a null address doesn't get overwritten by a bad legitimate address
     * when a request is forwarded across servers. Port 9 is a privileged port 
     * and is used for the Discard protocol.
     */
    public static final InetSocketAddress NULL_SOCKADDR = new InetSocketAddress(InetAddress.getLoopbackAddress(),
            9);

    /* We don't need the request values to match. If the request IDs, paxos IDs,
     * and client addresses are the same, the two requests are considered equal. */
    private static boolean enforceRequestValueMatch = false;

    @Override
    public boolean equals(Object obj) {
        RequestPacket req = null;
        return
        // RequestPacket instance
        obj instanceof RequestPacket && ((req = (RequestPacket) obj) != null)

        // request IDs match
                && this.requestID == req.requestID

                // paxosIDs match
                && this.getPaxosID().equals(req.getPaxosID())

                // client addresses match
                && this.clientAddress.equals(req.clientAddress)

                // request values or digests match (disabled by default)
                && (!enforceRequestValueMatch
                        || this.requestValue != null && this.requestValue.equals(req.requestValue)
                        // or digests match
                        || (this.digestEquals(req, PendingDigests.getMessageDigest()) || logAnomaly(this, req))

                );
    }

    private static boolean logAnomaly(RequestPacket req1, RequestPacket req2) {
        PaxosManager.getLogger().log(Level.SEVERE, "Received two requests with identical ");
        return true;
    }

    public static void doubleCheckFields() {
        checkFields(RequestPacket.class, Fields.values());
        checkMyFields();
    }

    public static class RequestPacketTest extends DefaultTest {
        public RequestPacketTest() {
        }

        @Test
        public void testCheckFields() {
            doubleCheckFields();
        }
    }

    static {
        if (Config.getGlobalBoolean(PC.ENABLE_STATIC_CHECKS))
            doubleCheckFields();
    }

    public static void main(String[] args) throws UnsupportedEncodingException, UnknownHostException {
        Util.assertAssertionsEnabled();
        int numReqs = 25;
        RequestPacket[] reqs = new RequestPacket[numReqs];
        RequestPacket req = new RequestPacket("asd" + 999, true);
        for (int i = 0; i < numReqs; i++) {
            reqs[i] = new RequestPacket("asd" + i, true);
        }

        System.out.println("Decision size estimate = " + SIZE_ESTIMATE);

        req.latchToBatch(reqs);
        String reqStr = req.toString();
        try {
            RequestPacket reqovered = new RequestPacket(req.toJSONObject());
            String reqoveredStr = reqovered.toString();
            assert (reqStr.equals(reqoveredStr));
            System.out.println("batchSize = " + reqovered.batched.length);
            // System.out.println(reqovered.batched[3]);
            System.out.println(samplePValue);

            System.out.println(reqs[1]);
            System.out.println(new RequestPacket(reqs[1].toBytes()));

            System.out.println(req);
            System.out.println(reqovered = new RequestPacket(req.toBytes()));
            assert (req.toString().equals(reqovered.toString()));

        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

}