tap.Tap.java Source code

Java tutorial

Introduction

Here is the source code for tap.Tap.java

Source

/**
  * This file is part of VoteBox.
  * 
  * VoteBox is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 3 as published by
  * the Free Software Foundation.
  * 
  * You should have received a copy of the GNU General Public License
  * along with VoteBox, found in the root of any distribution or
  * repository containing all or part of VoteBox.
  * 
  * THIS SOFTWARE IS PROVIDED BY WILLIAM MARSH RICE UNIVERSITY, HOUSTON,
  * TX AND IS PROVIDED 'AS IS' AND WITHOUT ANY EXPRESS, IMPLIED OR
  * STATUTORY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF
  * ACCURACY, COMPLETENESS, AND NONINFRINGEMENT.  THE SOFTWARE USER SHALL
  * INDEMNIFY, DEFEND AND HOLD HARMLESS RICE UNIVERSITY AND ITS FACULTY,
  * STAFF AND STUDENTS FROM ANY AND ALL CLAIMS, ACTIONS, DAMAGES, LOSSES,
  * LIABILITIES, COSTS AND EXPENSES, INCLUDING ATTORNEYS' FEES AND COURT
  * COSTS, DIRECTLY OR INDIRECTLY ARISING OUR OF OR IN CONNECTION WITH
  * ACCESS OR USE OF THE SOFTWARE.
 */

package tap;

import auditorium.IAuditoriumParams;
import auditorium.NetworkException;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import sexpression.stream.ASEWriter;
import votebox.AuditoriumParams;
import votebox.events.*;

import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.file.Files;
import java.util.*;

/**
 * Class used to "Tap" outgoing commit and cast messages.<BR>
 * Forwards - ideally across a one-way connection, or diode - these messages to a machine<BR>
 * outside of the Auditorium network, which a voter would then interrogate.<BR>
 * <BR>
 * Must not, under any circumstances, broadcast meaningful messages back onto the Auditorium network.<BR>
 * Keep-alives - or heartbeats - are permissible.
 * @author Montrose
 *
 */
public class Tap {

    private int _mySerial = -1;
    private final String launchCode;
    private ASEWriter _output = null;
    private OutputStream _wrappedOut = null;
    private static VoteBoxAuditoriumConnector _auditorium = null;
    private static String ballotDumpHTTPKey = "3FF968A3B47CT34C";

    private static boolean uploading = false;
    private static long threshold = 0L;

    /** A list of all supervisors who have indicated they are uploading ballots*/
    private ArrayList<Integer> uploadPending;

    /** A list of all supervisors who have finished uploading their ballots */
    private ArrayList<Integer> uploadComplete;

    /** This precinct's supervisor record, which will be uploaded to the bulletin board */
    private Map<String, Serializable> supervisorRecord;

    /**
     * Initializes a new Trapper.<BR>
     *
     * @see Tap#start()
     *
     * @param serial        serial number of this machine.
     * @param out           OutputStream to send selected messages to.
     */
    public Tap(int serial, OutputStream out, String launchCode, IAuditoriumParams params) {

        _mySerial = serial;
        _wrappedOut = out;
        _output = new ASEWriter(_wrappedOut);
        this.launchCode = launchCode;

        uploadPending = new ArrayList<>();
        uploadComplete = new ArrayList<>();
        supervisorRecord = new HashMap<>();

    }

    /**
     * TODO is this still used?
     * Forwards the given event.
     *
     * @param event         IAnnounceEvent to forward.
     */
    protected void forward(IAnnounceEvent event) {

        /* Write the event serial and convert and write as s-expression */
        try {
            _wrappedOut.write(event.getSerial());
            _output.writeASE(event.toSExp());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Dumps the ballots to the server TODO more refined explanation
     */
    public void uploadToServer() {

        System.out.println("Uploading Ballots to the server!");

        HttpClient client = new DefaultHttpClient();

        HttpPost post = new HttpPost("http://localhost:9000/3FF968A3B47CT34C");

        String encoded;

        try {

            System.out.println("Encoding the Supervisors' records... ");
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);

            /* Write the record to the stream */
            objectOutputStream.writeObject(supervisorRecord);
            objectOutputStream.close();

            /* Encode the record as a string */
            encoded = new String(Base64.encodeBase64(byteArrayOutputStream.toByteArray()));

            try {
                PrintWriter out = new PrintWriter("testdata.txt");
                out.print(encoded);
                out.close();
            } catch (Exception e) {
                e.printStackTrace();
            }

            List<BasicNameValuePair> bnvp = new ArrayList();

            bnvp.add(new BasicNameValuePair("record", encoded));
            bnvp.add(new BasicNameValuePair("precinctID", Integer.toString((new Random()).nextInt())));

            /* Set entities for each of the url encoded forms of the NVP */
            post.setEntity(new UrlEncodedFormEntity(bnvp));

            System.out.println("Executing post..." + post.getEntity());

            /* Execute the post */
            client.execute(post);

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

        System.out.println("Upload complete!");

        /* Shutdown the connection when done */
        client.getConnectionManager().shutdown();
    }

    /**
     * Connects the Trapper instance to the Auditorium network and listens for<BR>
     * Commit and EncryptedCastBallot messages to forward.
     */
    public void start() {

        try {

            _auditorium = new VoteBoxAuditoriumConnector(_mySerial, AuditoriumParams.Singleton, launchCode,
                    CommitBallotEvent.getMatcher(), BallotReceivedEvent.getMatcher(),
                    BallotScanAcceptedEvent.getMatcher(), AuthorizedToCastWithNIZKsEvent.getMatcher(),
                    PollsClosedEvent.getMatcher(), StartUploadEvent.getMatcher(), BallotUploadEvent.getMatcher());

        } catch (NetworkException e) { /* TODO runtime exception */
            throw new RuntimeException("Unable to connect to Auditorium: " + e.getMessage(), e);
        }

        _auditorium.addListener(new VoteBoxEventListener() {

            public void ballotAccepted(BallotScanAcceptedEvent e) {
                /* TODO? BallotStore.castCommittedBallot(e.getBID().toString()); */
            }

            public void commitBallot(CommitBallotEvent e) {
                /* TODO? BallotStore.addPrecinct(e.getBID().toString(), e.getVotes()); */
            }

            public void ballotReceived(BallotReceivedEvent e) {
                /* TODO? BallotStore.mapPrecinct(e.getBID(), e.getPrecinct()); */
            }

            /* Ignored events */
            public void left(LeaveEvent e) {
            }

            public void joined(JoinEvent e) {
            }

            public void votebox(VoteBoxEvent e) {
            }

            public void pollsOpen(PollsOpenEvent e) {
            }

            public void activated(ActivatedEvent e) {
            }

            public void pollsOpenQ(PollsOpenQEvent e) {
            }

            public void supervisor(SupervisorEvent e) {
            }

            public void assignLabel(AssignLabelEvent e) {
            }

            public void pinEntered(PINEnteredEvent event) {
            }

            public void invalidPin(InvalidPinEvent event) {
            }

            public void lastPollsOpen(LastPollsOpenEvent e) {
            }

            public void overrideCast(OverrideCommitEvent e) {
            }

            public void ballotScanner(BallotScannerEvent e) {
            }

            public void ballotScanned(BallotScannedEvent e) {
            }

            public void overrideCancel(OverrideCancelEvent e) {
            }

            public void ballotRejected(BallotScanRejectedEvent e) {
            }

            public void overrideCastDeny(OverrideCommitDenyEvent e) {
            }

            public void authorizedToCast(AuthorizedToCastEvent e) {
            }

            public void tapMachine(TapMachineEvent tapMachineEvent) {
            }

            public void pollStatus(PollStatusEvent pollStatusEvent) {
            }

            public void overrideCancelDeny(OverrideCancelDenyEvent e) {
            }

            public void spoilBallot(SpoilBallotEvent spoilBallotEvent) {
            }

            public void castCommittedBallot(CastCommittedBallotEvent e) {
            }

            public void overrideCastConfirm(OverrideCommitConfirmEvent e) {
            }

            public void scannerStart(StartScannerEvent startScannerEvent) {
            }

            public void overrideCancelConfirm(OverrideCancelConfirmEvent e) {
            }

            public void ballotPrinting(BallotPrintingEvent ballotPrintingEvent) {
            }

            public void ballotPrintFail(BallotPrintFailEvent ballotPrintFailEvent) {
            }

            public void ballotPrintSuccess(BallotPrintSuccessEvent ballotPrintSuccessEvent) {
            }

            public void provisionalCommitBallot(ProvisionalCommitEvent provisionalCommitEvent) {
            }

            public void announceProvisionalBallot(ProvisionalBallotEvent provisionalBallotEvent) {
            }

            public void provisionalAuthorizedToCast(ProvisionalAuthorizeEvent provisionalAuthorizeEvent) {
            }

            public void completedUpload(CompletedUploadEvent completedUploadEvent) {
            }

            public void startUpload(StartUploadEvent startUploadEvent) {
                System.out.println("Supervisor started upload...");
                uploadPending.add(startUploadEvent.getSerial());
            }

            @Override
            public void uploadBallots(BallotUploadEvent ballotUploadEvent) {

                System.out.println("A Supervisor is trying to upload ballots! ");

                /* TODO check this edge case waiting for slow connectors */

                /* If this method gets called while we're uploading, chill out for a sec or 5 */
                /* TODO perhaps fix this so that it doesn't always increment by 5 seconds if there are multiple slow connections */
                if (uploading) {
                    System.out.println("Delaying upload for 5 seconds while we load ballots... ");
                    threshold += 5000L;
                }

                int serial = ballotUploadEvent.getSerial();

                /* Check if the event trying to upload ballots to Tap didn't notify first */
                if (!uploadPending.contains(ballotUploadEvent.getSerial())) {
                    System.err.println(
                            "Supervisor " + serial + " tried to upload without first indicating it would!");
                    return;
                }

                /* Remove the serial from the pending uploads*/
                uploadPending.remove(new Integer(serial));

                /* Put the serial and map into the record map */
                supervisorRecord.put(Integer.toString(serial), ballotUploadEvent.getMap());

                /* Start uploading if we're done making the map (i.e. if no more pending and not currently uploading) */
                if (uploadPending.size() == 0 && !uploading) {
                    /* TODO collapse identical maps */
                    System.out.println("Upload to server pending... ");
                    startUploadToServer();
                }

            }

            public void pollMachines(PollMachinesEvent pollMachinesEvent) {
                _auditorium.announce(new TapMachineEvent(_mySerial));
            }

            public void pollsClosed(PollsClosedEvent e) {
            }

        });

        try {
            System.out.println("Connecting to auditorium...");
            _auditorium.connect();
            _auditorium.announce(new TapMachineEvent(_mySerial));
        } catch (NetworkException e) {
            throw new RuntimeException("Unable to connect to Auditorium network: " + e.getMessage(), e);
        }
    }

    private void startUploadToServer() {

        System.out.println("Starting uploading to server...");
        /* TODO test this */

        /* Change uploading status */
        uploading = true;

        long cur = System.currentTimeMillis();

        /* Wait for 5 seconds, and if another call to uploadBallots() is made, it will delay 5 more seconds */
        for (threshold = cur + 5000L; cur < threshold; cur = System.currentTimeMillis())
            ;

        /* Execute upload*/
        uploadToServer();
    }

    private static void testMethod() {
        try {

            String testdata = new Scanner(new File("testdata.txt")).useDelimiter("\\A").next();

            System.out.println("Uploading Ballots to the server!");

            HttpClient client = new DefaultHttpClient();

            HttpPost post = new HttpPost("http://localhost:9000/3FF968A3B47CT34C");

            List<BasicNameValuePair> bnvp = new ArrayList();

            bnvp.add(new BasicNameValuePair("record", testdata));
            bnvp.add(new BasicNameValuePair("precinctID", Integer.toString((new Random()).nextInt())));

            /* Set entities for each of the url encoded forms of the NVP */
            post.setEntity(new UrlEncodedFormEntity(bnvp));

            System.out.println("Executing post..." + post.getEntity());

            /* Execute the post */
            client.execute(post);

            System.out.println("Upload complete!");

            /* Shutdown the connection when done */
            client.getConnectionManager().shutdown();

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

    /**
     * Usage:<BR> FIXME?
     *       java votebox.Tap [serial] [report address] [port]
     *
     * @param args      arguments to be used
     *
     * @throws RuntimeException if there is an issue parsing values, the port used is bad,
     *                          or there is an interruption in the process
     */
    public static void main(String[] args) {

        IAuditoriumParams params = new AuditoriumParams("tap.conf");

        System.out.println(params.getReportAddress());

        String reportAddr;

        int serial;
        int port;
        String launchCode;

        /* See if there isn't a full argument set */
        if (args.length != 4) {

            int p = 0;

            /* Assign the default serial */
            serial = params.getDefaultSerialNumber();

            /* If the serial is still bad... */
            if (serial == -1) {

                /* Try the first of the given arguments */
                try {
                    serial = Integer.parseInt(args[p]);
                    p++;
                } catch (Exception e) {
                    throw new RuntimeException(
                            "usage: Tap [serial] [report address] [port]\nExpected valid serial.");
                }
            }

            /* Assign the report address */
            reportAddr = params.getReportAddress();

            /* If no valid address... */
            if (reportAddr.length() == 0) {

                /* Try one of the given arguments (first or second, depending) */
                try {
                    reportAddr = args[p];
                    p++;
                } catch (Exception e) {
                    throw new RuntimeException("usage: Tap [serial] [report address] [port]");
                }
            }

            /* Assign the port */
            //port = params.getPort();
            port = Integer.parseInt(args[p]);

            /* If the port is still bad... */
            if (port == -1) {

                /* Try one of the given arguments (first, second, or third, depending) */
                try {
                    port = Integer.parseInt(args[p]);
                } catch (Exception e) {
                    throw new RuntimeException("usage: Tap [serial] [report address] [port]\nExpected valid port.");
                }
            }

            launchCode = "0000000000";
        }

        /* If there is a full argument set... */
        else {

            /* Try to load up the args */
            try {
                serial = Integer.parseInt(args[0]);
                reportAddr = args[1];
                port = Integer.parseInt(args[2]);
                launchCode = args[3];
            } catch (Exception e) {
                throw new RuntimeException("usage: Tap [serial] [report address] [port]");
            }
        }

        try {

            /* Create a new socket address */
            System.out.println(reportAddr + port);
            InetSocketAddress addr = new InetSocketAddress(reportAddr, port);

            /* Loop until an exception or tap is started */
            while (true) {

                try {

                    /* Try to establish a socket connection */
                    Socket localCon = new Socket();
                    localCon.connect(addr);

                    /* Start the tap */
                    (new Tap(serial, localCon.getOutputStream(), launchCode, params)).start();
                    System.out.println("Connection successful to " + addr);
                    break;
                } catch (IOException e) { /* If no good, retry */
                    System.out.println("Connection failed: " + e.getMessage());
                    System.out.println("Retry in 5 seconds...");
                    Thread.sleep(5000);
                }
            }
        } catch (NumberFormatException e) {
            throw new RuntimeException(
                    "usage: Tap [serial] [report address] [port]; where port is between 1 and 65335 & [serial] is a positive integer",
                    e);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        /* TEST CODE */

        testMethod();

        /* END TEST CODE */

    }
}