Java tutorial
/** * Copyright 2013 Crypto Workshop Pty Ltd * * 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 org.cryptoworkshop.ximix.console.applet; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; import java.security.Security; import java.security.cert.X509Certificate; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.table.AbstractTableModel; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1Object; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cms.CMSSignedDataParser; import org.bouncycastle.crypto.ec.CustomNamedCurves; import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.crypto.util.PublicKeyFactory; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.openssl.MiscPEMGenerator; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.PEMWriter; import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import org.cryptoworkshop.ximix.client.BoardCreationOptions; import org.cryptoworkshop.ximix.client.BoardDetail; import org.cryptoworkshop.ximix.client.CommandService; import org.cryptoworkshop.ximix.client.DownloadOperationListener; import org.cryptoworkshop.ximix.client.DownloadOptions; import org.cryptoworkshop.ximix.client.DownloadShuffleResultOptions; import org.cryptoworkshop.ximix.client.KeyService; import org.cryptoworkshop.ximix.client.MonitorService; import org.cryptoworkshop.ximix.client.NetworkBoardListener; import org.cryptoworkshop.ximix.client.RegistrarServiceException; import org.cryptoworkshop.ximix.client.ShuffleOperationListener; import org.cryptoworkshop.ximix.client.ShuffleOptions; import org.cryptoworkshop.ximix.client.ShuffleStatus; import org.cryptoworkshop.ximix.client.ShuffleTranscriptOptions; import org.cryptoworkshop.ximix.client.ShuffleTranscriptsDownloadOperationListener; import org.cryptoworkshop.ximix.client.UploadService; import org.cryptoworkshop.ximix.client.connection.XimixRegistrar; import org.cryptoworkshop.ximix.client.connection.XimixRegistrarFactory; import org.cryptoworkshop.ximix.client.verify.ECShuffledTranscriptVerifier; import org.cryptoworkshop.ximix.client.verify.LinkIndexVerifier; import org.cryptoworkshop.ximix.client.verify.SignedDataVerifier; import org.cryptoworkshop.ximix.common.asn1.board.PointSequence; import org.cryptoworkshop.ximix.common.asn1.message.SeedAndWitnessMessage; import org.cryptoworkshop.ximix.common.util.EventNotifier; import org.cryptoworkshop.ximix.common.util.Operation; import org.cryptoworkshop.ximix.common.util.TranscriptType; import org.cryptoworkshop.ximix.console.util.vote.BallotUnpacker; public class CommandApplet extends JApplet { private static final int BATCH_SIZE = 10; private ExecutorService threadPool = Executors.newCachedThreadPool(); // TODO: maybe configure? private static final int ncores = 3; private X509Certificate trustAnchor; private Semaphore processSemaphore = new Semaphore(ncores); private Semaphore maxProcessSemaphore = new Semaphore(ncores * 5); public void init() { if (Security.getProvider("BC") == null) { Security.addProvider(new BouncyCastleProvider()); } final URL mixnetConf = getConfURL(); final URL trustCa = getCaURL(); JPanel topPanel = new JPanel(); topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.X_AXIS)); JPanel uploadPanel = new JPanel(); uploadPanel.setBorder(BorderFactory.createTitledBorder("Upload Source Directory")); JButton uploadBrowseButton = new JButton("..."); final JTextField uploadDirField = new JTextField(20); final XimixRegistrar adminRegistrar; try { PEMParser pemParser = new PEMParser(new InputStreamReader(trustCa.openStream())); trustAnchor = new JcaX509CertificateConverter().setProvider("BC") .getCertificate((X509CertificateHolder) pemParser.readObject()); adminRegistrar = XimixRegistrarFactory.createAdminServiceRegistrar(mixnetConf.openStream(), new EventNotifier() { @Override public void notify(Level level, Throwable throwable) { System.err.print(level + " " + throwable.getMessage()); throwable.printStackTrace(System.err); } @Override public void notify(Level level, Object detail) { System.err.println(level + " " + detail.toString()); } @Override public void notify(Level level, Object detail, Throwable throwable) { System.err.println(level + " " + detail.toString()); throwable.printStackTrace(System.err); } }); } catch (Exception e) { throw new IllegalStateException("Can't parse trust anchor.", e); } uploadBrowseButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent actionEvent) { JFileChooser chooser = new JFileChooser(); chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); int result = chooser.showDialog(CommandApplet.this, "Select"); if (result == JFileChooser.APPROVE_OPTION) { uploadDirField.setText(chooser.getSelectedFile().getAbsolutePath()); } } }); uploadPanel.add(uploadDirField); uploadPanel.add(uploadBrowseButton); JPanel downloadPanel = new JPanel(); downloadPanel.setBorder(BorderFactory.createTitledBorder("Download Directory")); JButton downloadBrowseButton = new JButton("..."); final JTextField downloadDirField = new JTextField(20); downloadBrowseButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent actionEvent) { JFileChooser chooser = new JFileChooser(); chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); int result = chooser.showDialog(CommandApplet.this, "Select"); if (result == JFileChooser.APPROVE_OPTION) { downloadDirField.setText(chooser.getSelectedFile().getAbsolutePath()); } } }); downloadPanel.add(downloadDirField); downloadPanel.add(downloadBrowseButton); JPanel tablePanel = new JPanel(); tablePanel.setLayout(new BoxLayout(tablePanel, BoxLayout.Y_AXIS)); JPanel topTablePanel = new JPanel(); topTablePanel.setLayout(new BoxLayout(topTablePanel, BoxLayout.X_AXIS)); final JTextField shufflePlan = new JTextField(30); final EventNotifier eventNotifier = new EventNotifier() { @Override public void notify(Level level, Throwable throwable) { System.err.print(level + " " + throwable.getMessage()); throwable.printStackTrace(System.err); } @Override public void notify(Level level, Object detail) { System.err.println(level + " " + detail.toString()); } @Override public void notify(Level level, Object detail, Throwable throwable) { System.err.println(level + " " + detail.toString()); throwable.printStackTrace(System.err); } }; final JTable boardTable = new JTable(new BoardTableModel()); JButton candidateMapBrowseButton = new JButton("..."); final JTextField configField = new JTextField(20); candidateMapBrowseButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent actionEvent) { JFileChooser chooser = new JFileChooser(); chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); int result = chooser.showDialog(CommandApplet.this, "Select"); if (result == JFileChooser.APPROVE_OPTION) { configField.setText(chooser.getSelectedFile().getAbsolutePath()); } } }); JButton uploadButton = new JButton("Do Upload"); final URL finalMixnetConf = mixnetConf; uploadButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent actionEvent) { String dirName = uploadDirField.getText().trim(); if (dirName.length() > 0) { Thread taskThread = new Thread(new FullUploadTask((BoardTableModel) boardTable.getModel(), dirName, finalMixnetConf, eventNotifier)); taskThread.setPriority(Thread.NORM_PRIORITY); taskThread.start(); } else { JOptionPane.showMessageDialog(SwingUtilities.windowForComponent(CommandApplet.this), "Please enter an upload source directory.", "Missing Field Error", JOptionPane.ERROR_MESSAGE); return; } } }); topTablePanel.add(uploadButton); JPanel shufflePanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); shufflePanel.add(new JLabel("Shuffle Plan:")); shufflePanel.add(shufflePlan); topTablePanel.add(shufflePanel); final JTextField keyID = new JTextField(15); JTextField threshold = new JTextField(3); keyID.setText("ECENCKEY"); threshold.setText("4"); JButton shuffleButton = new JButton("Shuffle and Download Selected"); shuffleButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent actionEvent) { String planStr = shufflePlan.getText().trim(); String dirName = downloadDirField.getText().trim(); String configName = configField.getText().trim(); if (dirName.length() == 0) { JOptionPane.showMessageDialog(SwingUtilities.windowForComponent(CommandApplet.this), "Please enter a download directory.", "Missing Field Error", JOptionPane.ERROR_MESSAGE); return; } if (configName.length() == 0) { JOptionPane.showMessageDialog(SwingUtilities.windowForComponent(CommandApplet.this), "Please enter a candidate configuration file name.", "Missing Field Error", JOptionPane.ERROR_MESSAGE); return; } if (planStr.length() > 0) { String[] plan = planStr.split(","); for (int i = 0; i != plan.length; i++) { plan[i] = plan[i].trim(); if (plan[i].length() == 0) { JOptionPane.showMessageDialog(SwingUtilities.windowForComponent(CommandApplet.this), "Empty node name found.", "Syntax Error", JOptionPane.ERROR_MESSAGE); return; } } Thread taskThread = new Thread(new FullShuffleTask(new File(dirName), keyID.getText().trim(), (BoardTableModel) boardTable.getModel(), plan, finalMixnetConf, configField.getText().trim(), eventNotifier)); taskThread.setPriority(Thread.NORM_PRIORITY); taskThread.start(); } else { JOptionPane.showMessageDialog(SwingUtilities.windowForComponent(CommandApplet.this), "Please enter a shuffle plan.", "Missing Field Error", JOptionPane.ERROR_MESSAGE); } } }); JPanel downloadControlPanel = new JPanel(); downloadControlPanel.setLayout(new BoxLayout(downloadControlPanel, BoxLayout.Y_AXIS)); JPanel downloadKeyPanel = new JPanel(); downloadKeyPanel.setLayout(new BoxLayout(downloadKeyPanel, BoxLayout.X_AXIS)); JButton exportButton = new JButton("Export Key"); exportButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { JFileChooser chooser = new JFileChooser(); chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); int result = chooser.showDialog(CommandApplet.this, "Save"); if (result == JFileChooser.APPROVE_OPTION) { try { KeyService keyService = adminRegistrar.connect(KeyService.class); byte[] encPubKey = keyService.fetchPublicKey(keyID.getText().trim()); PEMWriter pWrt = new PEMWriter(new FileWriter(chooser.getSelectedFile().getAbsolutePath())); pWrt.writeObject(new MiscPEMGenerator(SubjectPublicKeyInfo.getInstance(encPubKey))); pWrt.close(); keyService.shutdown(); } catch (Exception e) { // TODO: e.printStackTrace(); } } } }); JPanel keyIDPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); keyIDPanel.add(new JLabel("Key ID: ")); keyIDPanel.add(keyID); JPanel thresholdPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); thresholdPanel.add(new JLabel("Threshold")); thresholdPanel.add(threshold); downloadKeyPanel.add(keyIDPanel); downloadKeyPanel.add(thresholdPanel); downloadKeyPanel.add(exportButton); JPanel candidateMapPanel = new JPanel(); candidateMapPanel.add(new JLabel("Candidate Config: ")); candidateMapPanel.add(configField); candidateMapPanel.add(candidateMapBrowseButton); JPanel downloadButtonPanel = new JPanel(); downloadButtonPanel.setLayout(new BoxLayout(downloadButtonPanel, BoxLayout.X_AXIS)); final JButton selectAllButton = new JButton("Select All"); selectAllButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent actionEvent) { BoardTableModel tableModel = (BoardTableModel) boardTable.getModel(); if (selectAllButton.getText().startsWith("Sele")) { selectAllButton.setText("Deselect All"); for (BoardEntry entry : tableModel.getEntries()) { entry.setSelected(true); } } else { selectAllButton.setText("Select All"); for (BoardEntry entry : tableModel.getEntries()) { entry.setSelected(false); } } } }); downloadButtonPanel.add(selectAllButton); downloadButtonPanel.add(shuffleButton); downloadControlPanel.add(downloadKeyPanel); downloadControlPanel.add(candidateMapPanel); downloadControlPanel.add(downloadButtonPanel); topTablePanel.add(downloadControlPanel); topTablePanel.add(Box.createHorizontalGlue()); boardTable.getTableHeader().setPreferredSize( new Dimension(boardTable.getColumnModel().getTotalColumnWidth(), boardTable.getRowHeight(0) * 2)); tablePanel.add(topTablePanel); tablePanel.add(new JScrollPane(boardTable)); JPanel basePanel = new JPanel(); basePanel.setLayout(new BoxLayout(basePanel, BoxLayout.Y_AXIS)); topPanel.add(uploadPanel); topPanel.add(Box.createHorizontalGlue()); topPanel.add(downloadPanel); basePanel.add(topPanel); basePanel.add(tablePanel); try { MonitorService monitor = adminRegistrar.connect(MonitorService.class); monitor.addBulletinBoardListener(new NetworkBoardListener() { @Override public void boardChanged(String boardName, BoardDetail boardDetail) { BoardTableModel tableModel = (BoardTableModel) boardTable.getModel(); BoardEntry entry = tableModel.getEntry(boardName, boardDetail.getHost(), boardDetail.getBackupHost()); entry.setMessageCount(boardDetail.getMessageCount()); } }); } catch (RegistrarServiceException e) { // TODO: e.printStackTrace(); } this.getContentPane().add(basePanel); } private URL getConfURL() { URL mixnetConf; try { mixnetConf = new URL(getParameter("mixnetConf")); } catch (MalformedURLException e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. mixnetConf = null; // TODO: } return mixnetConf; } private URL getCaURL() { URL mixnetConf; try { mixnetConf = new URL(getParameter("trustAnchor")); } catch (MalformedURLException e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. mixnetConf = null; // TODO: } return mixnetConf; } public void start() { } public void stop() { } private static class BoardTableModel extends AbstractTableModel { static String[] title = new String[] { "Select", "Board", "<html><body><center>Primary<br/>Node</center></body></html>", "<html><body><center>Backup<br/>Node</center></body></html>", "Size", "Status" }; private final Map<String, BoardEntry> entries = Collections .<String, BoardEntry>synchronizedMap(new LinkedHashMap()); BoardTableModel() { } @Override public int getRowCount() { return entries.size(); } @Override public String getColumnName(int x) { return title[x]; } @Override public int getColumnCount() { return title.length; } @Override public boolean isCellEditable(int row, int column) { return column == 0; } @Override public Object getValueAt(int row, int column) { if (row >= entries.size()) { return null; } return new ArrayList<>(entries.values()).get(row).getValue(column); } public Class getColumnClass(int column) { if (column == 0) { return Boolean.class; } return String.class; } @Override public void setValueAt(Object o, int row, int column) { if (column == 0) { new ArrayList<>(entries.values()).get(row).isSelected = ((Boolean) o).booleanValue(); } } public synchronized List<BoardEntry> getEntries() { return new ArrayList<>(entries.values()); } public synchronized BoardEntry getEntry(String fileName, String primary, String secondary) { BoardEntry boardEntry = entries.get(fileName); if (boardEntry == null) { boardEntry = new BoardEntry(this, fileName, primary, secondary); entries.put(fileName, boardEntry); fireTableRowsInserted(entries.size() - 1, entries.size() - 1); } return boardEntry; } public synchronized List<BoardEntry> getSelectedEntries() { List<BoardEntry> selected = new ArrayList<>(); for (BoardEntry entry : entries.values()) { if (entry.isSelected) { selected.add(entry); } } return selected; } } private static class BoardEntry { enum State { LOADING, SHUFFLING } private final DecimalFormat fmt = new DecimalFormat("##0.00"); private final BoardTableModel parent; private final String name; private final String primary; private final String secondary; private volatile boolean isSelected = false; private volatile State state = State.LOADING; private volatile int totalMesages = 0; private volatile double pCentProgress = 0.0; private volatile String progressMessage; public BoardEntry(BoardTableModel parent, String name, String primary, String secondary) { this.parent = parent; this.name = name; this.primary = primary; this.secondary = secondary; this.progressMessage = "Loaded"; } public synchronized Object getValue(int column) { switch (column) { case 0: return isSelected; case 1: return name; case 2: return primary; case 3: return secondary; case 4: return totalMesages; case 5: if (progressMessage != null) { return progressMessage; } if (pCentProgress != 1.0) { if (state == State.LOADING) { return "Loading (" + fmt.format(pCentProgress * 100) + " %)"; } else { return "Processing (" + fmt.format(pCentProgress * 100) + " %)"; } } else { return "Complete"; } default: return "Not Set"; } } public synchronized void markProgress(State state, int numMessages, double pCentDone) { this.progressMessage = null; this.state = state; this.totalMesages += numMessages; this.pCentProgress = pCentDone; this.parent.fireTableDataChanged(); } public synchronized void setShuffleProgress(String progress) { this.progressMessage = progress; this.parent.fireTableDataChanged(); } public synchronized void setMessageCount(int messageCount) { this.totalMesages = messageCount; this.parent.fireTableDataChanged(); } public synchronized void setSelected(boolean selected) { this.isSelected = selected; this.parent.fireTableDataChanged(); } public String getName() { return name; } } private class ProgressDialog extends JDialog { private final JProgressBar progressBar; private final int mult; private final JButton dismiss; ProgressDialog(String title, int size) { super(SwingUtilities.windowForComponent(CommandApplet.this), ModalityType.APPLICATION_MODAL); this.setTitle(title); JPanel basePanel = new JPanel(); basePanel.setLayout(new BoxLayout(basePanel, BoxLayout.Y_AXIS)); basePanel.add(new JLabel(title)); if (size > 200) { mult = 1; } else { mult = 2; } progressBar = new JProgressBar(0, size * mult); basePanel.add(progressBar); JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); dismiss = new JButton("Dismiss"); dismiss.setEnabled(false); dismiss.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { ProgressDialog.this.setVisible(false); ProgressDialog.this.dispose(); } }); buttonPanel.add(dismiss); basePanel.add(buttonPanel); this.getContentPane().add(basePanel); this.pack(); this.setLocationRelativeTo(CommandApplet.this); } void adjustProgress(final int incr) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { progressBar.setValue(progressBar.getValue() + incr * mult); if (progressBar.getValue() == progressBar.getMaximum()) { dismiss.setEnabled(true); } } }); } } private class FullUploadTask implements Runnable { private final BoardTableModel boardModel; private final String dirName; private final EventNotifier eventNotifier; private final URL mixnetConf; public FullUploadTask(BoardTableModel boardModel, String dirName, URL mixnetConf, EventNotifier eventNotifier) { this.boardModel = boardModel; this.dirName = dirName; this.mixnetConf = mixnetConf; this.eventNotifier = eventNotifier; } @Override public void run() { File f = new File(dirName); final File[] files = f.listFiles(new FilenameFilter() { @Override public boolean accept(File file, String s) { return true; } }); try { XimixRegistrar adminRegistrar = XimixRegistrarFactory .createAdminServiceRegistrar(mixnetConf.openStream(), eventNotifier); CommandService commandService = adminRegistrar.connect(CommandService.class); UploadService uploadService = adminRegistrar.connect(UploadService.class); String[] nodes = commandService.getNodeNames().toArray(new String[0]); CountDownLatch uploadLatch = new CountDownLatch(files.length); processSemaphore = new Semaphore(nodes.length); final ProgressDialog dialog = getProgressDialog("Upload Progress", files.length); for (int i = 0; i != files.length; i++) { String primary = nodes[i % nodes.length]; String secondary = nodes[(i + 1) % nodes.length]; threadPool.submit(new UploadTask(commandService, uploadService, uploadLatch, primary, secondary, files[i], dialog, eventNotifier)); } uploadLatch.await(); processSemaphore = new Semaphore(ncores); // TODO: use field in UI. commandService.shutdown(); } catch (Exception e) { eventNotifier.notify(EventNotifier.Level.ERROR, "Cannot perform upload: " + e.getMessage(), e); } } private class UploadTask implements Runnable { private final CommandService commandService; private final UploadService uploadService; private final CountDownLatch uploadLatch; private final String primary; private final String secondary; private final File file; private final ProgressDialog dialog; private final EventNotifier eventNotifier; public UploadTask(CommandService commandService, UploadService uploadService, CountDownLatch uploadLatch, String primary, String secondary, File file, ProgressDialog dialog, EventNotifier eventNotifier) { this.commandService = commandService; this.uploadService = uploadService; this.uploadLatch = uploadLatch; this.primary = primary; this.secondary = secondary; this.file = file; this.dialog = dialog; this.eventNotifier = eventNotifier; } @Override public void run() { try { processSemaphore.acquire(); String boardName = file.getName().substring(0, file.getName().indexOf('.')); BoardEntry entry = boardModel.getEntry(boardName, primary, secondary); if (!commandService.isBoardExisting(boardName)) { commandService.createBoard(boardName, new BoardCreationOptions.Builder(primary).withBackUpHost(secondary).build()); } ASN1InputStream aIn = new ASN1InputStream(new BufferedInputStream(new FileInputStream(file))); long totalSize = file.length(); long accumlatedSize = 0; ASN1Object obj; List<byte[]> messages = new ArrayList<>(); while ((obj = aIn.readObject()) != null) { byte[] message = obj.getEncoded(); accumlatedSize += message.length; messages.add(message); if (messages.size() == BATCH_SIZE) { uploadService.uploadMessages(boardName, messages.toArray(new byte[BATCH_SIZE][])); entry.markProgress(BoardEntry.State.LOADING, messages.size(), (double) accumlatedSize / totalSize); messages.clear(); } } if (!messages.isEmpty()) { uploadService.uploadMessages(boardName, messages.toArray(new byte[messages.size()][])); } processSemaphore.release(); entry.markProgress(BoardEntry.State.LOADING, messages.size(), 1.0); aIn.close(); } catch (Exception e) { eventNotifier.notify(EventNotifier.Level.ERROR, "Unable to load file " + file.getName(), e); } finally { dialog.adjustProgress(1); uploadLatch.countDown(); } } } } private ProgressDialog getProgressDialog(final String title, final int numTasks) throws InterruptedException, ExecutionException { FutureTask<ProgressDialog> dialogTask = new FutureTask<>(new Callable<ProgressDialog>() { @Override public ProgressDialog call() throws Exception { return new ProgressDialog(title, numTasks); } }); SwingUtilities.invokeLater(dialogTask); final ProgressDialog dialog = dialogTask.get(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { dialog.setVisible(true); } }); return dialog; } private class FullShuffleTask implements Runnable { private final File destDir; private final String keyID; private final BoardTableModel boardModel; private final String[] shuffflePlan; private final EventNotifier eventNotifier; private final URL mixnetConf; private final String csvConfig; public FullShuffleTask(File destDir, String keyID, BoardTableModel boardModel, String[] shufflePlan, URL mixnetConf, String csvConfig, EventNotifier eventNotifier) { this.destDir = destDir; this.keyID = keyID; this.boardModel = boardModel; this.shuffflePlan = shufflePlan; this.mixnetConf = mixnetConf; this.csvConfig = csvConfig; this.eventNotifier = eventNotifier; } public void run() { try { XimixRegistrar adminRegistrar = XimixRegistrarFactory .createAdminServiceRegistrar(mixnetConf.openStream(), eventNotifier); KeyService keyService = adminRegistrar.connect(KeyService.class); ECPublicKeyParameters pubKey = (ECPublicKeyParameters) PublicKeyFactory .createKey(keyService.fetchPublicKey(keyID)); List<BoardEntry> selected = boardModel.getSelectedEntries(); if (selected.isEmpty()) { JOptionPane.showMessageDialog(SwingUtilities.windowForComponent(CommandApplet.this), "Please select some boards to shuffle.", "No Selection Error", JOptionPane.ERROR_MESSAGE); return; } CountDownLatch shuffleLatch = new CountDownLatch(selected.size()); final ProgressDialog dialog = getProgressDialog("Shuffle and Download Progress", selected.size()); List<CommandService> servicePool = new ArrayList<>(); for (int i = 0; i != 3; i++) { servicePool.add(adminRegistrar.connect(CommandService.class)); } for (int i = 0; i != selected.size(); i++) { BoardEntry entry = selected.get(i); entry.setShuffleProgress("Pending"); threadPool.submit( new ShuffleAndDownloadTask(destDir, entry, servicePool.get(i % servicePool.size()), keyID, pubKey, shuffleLatch, shuffflePlan, csvConfig, dialog, eventNotifier)); } shuffleLatch.await(); for (CommandService service : servicePool) { service.shutdown(); } keyService.shutdown(); } catch (Exception e) { eventNotifier.notify(EventNotifier.Level.ERROR, "Cannot perform upload: " + e.getMessage(), e); } } } private class ShuffleAndDownloadTask implements Runnable { private final File destDir; private final BoardEntry boardEntry; private final CommandService commandService; private final String keyID; private final ECPublicKeyParameters pubKey; private final CountDownLatch shuffleLatch; private final String[] shufflePlan; private final ProgressDialog dialog; private final EventNotifier eventNotifier; private final String conversionFile; public ShuffleAndDownloadTask(File destDir, BoardEntry boardEntry, CommandService commandService, String keyID, ECPublicKeyParameters pubKey, CountDownLatch shuffleLatch, String[] shufflePlan, String conversionFile, ProgressDialog dialog, EventNotifier eventNotifier) { this.destDir = destDir; this.boardEntry = boardEntry; this.commandService = commandService; this.keyID = keyID; this.pubKey = pubKey; this.shuffleLatch = shuffleLatch; this.shufflePlan = shufflePlan; this.conversionFile = conversionFile; this.dialog = dialog; this.eventNotifier = eventNotifier; } public void run() { try { processSemaphore.acquire(); maxProcessSemaphore.acquire(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } try { final CountDownLatch shuffleOperationLatch = new CountDownLatch(1); final CountDownLatch seedLatch = new CountDownLatch(1); final AtomicBoolean hasFailed = new AtomicBoolean(false); final Map<String, byte[]> seedCommitmentMap = new HashMap<>(); ShuffleOperationListener shuffleListener = new ShuffleOperationListener() { private boolean firstStepDone = false; private String startNode = ""; @Override public void commit(Map<String, byte[]> seedCommitments) { seedCommitmentMap.putAll(seedCommitments); seedLatch.countDown(); } @Override public void completed() { shuffleOperationLatch.countDown(); System.err.println("done"); } @Override public void status(ShuffleStatus statusObject) { String statusMessage = statusObject.getMessage(); boardEntry.setShuffleProgress(statusMessage); if (statusMessage.startsWith("Starting")) { startNode = statusObject.getNodeName(); } else if (!firstStepDone && !startNode.equals(statusObject.getNodeName())) { firstStepDone = true; processSemaphore.release(); } System.err.println("status: " + statusMessage); } @Override public void failed(ShuffleStatus errorObject) { hasFailed.set(true); shuffleOperationLatch.countDown(); System.err.println("failed: " + errorObject.getMessage()); if (errorObject.getCause() != null) { errorObject.getCause().printStackTrace(System.err); } } }; boardEntry.markProgress(BoardEntry.State.SHUFFLING, 0, 0.0); Operation<ShuffleOperationListener> shuffleOp = commandService.doShuffleAndMove( boardEntry.getName(), new ShuffleOptions.Builder("MultiColumnRowTransform").withKeyID(keyID).build(), shuffleListener, shufflePlan); shuffleOperationLatch.await(); if (hasFailed.get()) { throw new IllegalStateException("Shuffle process failed!!!"); } seedLatch.await(); boardEntry.markProgress(BoardEntry.State.SHUFFLING, 0, 0.25); final CountDownLatch transcriptCompleted = new CountDownLatch(1); final Map<Integer, File> generalTranscripts = new HashMap<>(); ShuffleTranscriptsDownloadOperationListener transcriptListener = new ShuffleTranscriptsDownloadOperationListener() { @Override public void shuffleTranscriptArrived(long operationNumber, final int stepNumber, final InputStream transcript) { try { File transcriptFile = new File(destDir, boardEntry.getName() + "." + stepNumber + ".gtr"); OutputStream generalTranscript = new BufferedOutputStream( new FileOutputStream(transcriptFile)); BufferedInputStream bIn = new BufferedInputStream(transcript); int ch; while ((ch = bIn.read()) >= 0) { generalTranscript.write(ch); } generalTranscript.close(); generalTranscripts.put(stepNumber, transcriptFile); } catch (IOException e) { e.printStackTrace(); } } @Override public void completed() { transcriptCompleted.countDown(); } @Override public void status(String statusObject) { //To change body of implemented methods use File | Settings | File Templates. } @Override public void failed(String errorObject) { System.err.println(errorObject); transcriptCompleted.countDown(); } }; commandService.downloadShuffleTranscripts(boardEntry.getName(), shuffleOp.getOperationNumber(), new ShuffleTranscriptOptions.Builder(TranscriptType.GENERAL).build(), transcriptListener, shufflePlan); transcriptCompleted.await(); SignedDataVerifier signatureVerifier = new SignedDataVerifier(trustAnchor); Map<String, byte[][]> seedAndWitnessesMap = commandService.downloadShuffleSeedsAndWitnesses( boardEntry.getName(), shuffleOp.getOperationNumber(), shufflePlan); int boardSize = LinkIndexVerifier.getAndCheckBoardSize( generalTranscripts.values().toArray(new File[generalTranscripts.size()])); LinkIndexVerifier.Builder builder = new LinkIndexVerifier.Builder(boardSize); builder.setNetworkSeeds(seedCommitmentMap, seedAndWitnessesMap); for (Integer step : generalTranscripts.keySet()) { File transcriptFile = generalTranscripts.get(step); if (signatureVerifier.signatureVerified(new CMSSignedDataParser( new JcaDigestCalculatorProviderBuilder().setProvider("BC").build(), new BufferedInputStream(new FileInputStream(transcriptFile))))) { builder.addTranscript(transcriptFile); } else { System.err.println("General commitment check signature failed"); } } LinkIndexVerifier linkVerifier = builder.build(); byte[] challengeSeed = linkVerifier.getChallengeSeed(); boardEntry.markProgress(BoardEntry.State.SHUFFLING, 0, 0.50); final CountDownLatch witnessTranscriptCompleted = new CountDownLatch(1); final Map<Integer, File> witnessTranscripts = new HashMap<>(); transcriptListener = new ShuffleTranscriptsDownloadOperationListener() { @Override public void shuffleTranscriptArrived(long operationNumber, final int stepNumber, final InputStream transcript) { try { File transcriptFile = new File(destDir, boardEntry.getName() + "." + stepNumber + ".wtr"); OutputStream witnessTranscript = new BufferedOutputStream( new FileOutputStream(transcriptFile)); BufferedInputStream bIn = new BufferedInputStream(transcript); int ch; while ((ch = bIn.read()) >= 0) { witnessTranscript.write(ch); } witnessTranscript.close(); witnessTranscripts.put(stepNumber, transcriptFile); } catch (IOException e) { e.printStackTrace(); } } @Override public void completed() { witnessTranscriptCompleted.countDown(); } @Override public void status(String statusObject) { //To change body of implemented methods use File | Settings | File Templates. } @Override public void failed(String errorObject) { System.err.println(errorObject); witnessTranscriptCompleted.countDown(); } }; commandService.downloadShuffleTranscripts(boardEntry.getName(), shuffleOp.getOperationNumber(), new ShuffleTranscriptOptions.Builder(TranscriptType.WITNESSES) .withChallengeSeed(challengeSeed).withPairingEnabled(true).build(), transcriptListener, shufflePlan); witnessTranscriptCompleted.await(); boardEntry.markProgress(BoardEntry.State.SHUFFLING, 0, 0.70); for (Integer key : witnessTranscripts.keySet()) { File transcriptFile = witnessTranscripts.get(key); File initialTranscript = generalTranscripts.get(key); File nextTranscript = generalTranscripts.get(key + 1); InputStream witnessTranscriptStream = new BufferedInputStream( new FileInputStream(transcriptFile)); ECShuffledTranscriptVerifier verifier = new ECShuffledTranscriptVerifier(pubKey, witnessTranscriptStream, initialTranscript, nextTranscript); verifier.verify(); witnessTranscriptStream.close(); } boardEntry.markProgress(BoardEntry.State.SHUFFLING, 0, 0.75); final OutputStream downloadStream = new FileOutputStream( new File(destDir, boardEntry.getName() + ".out")); final OutputStream proofLogStream = new FileOutputStream( new File(destDir, boardEntry.getName() + ".plg")); Map<String, InputStream> streamSeedCommitments = new HashMap<>(); for (String key : seedCommitmentMap.keySet()) { streamSeedCommitments.put(key, new ByteArrayInputStream(seedCommitmentMap.get(key))); } Map<String, InputStream> streamSeedsAndWitnesses = new HashMap<>(); for (String key : seedAndWitnessesMap.keySet()) { byte[][] sAndW = seedAndWitnessesMap.get(key); streamSeedsAndWitnesses.put(key, new ByteArrayInputStream(new SeedAndWitnessMessage(sAndW[0], sAndW[1]).getEncoded())); } Map<Integer, InputStream> streamWitnessTranscripts = new HashMap<>(); for (Integer key : witnessTranscripts.keySet()) { streamWitnessTranscripts.put(key, new FileInputStream(witnessTranscripts.get(key))); } Map<Integer, InputStream> streamGeneralTranscripts = new HashMap<>(); for (Integer key : generalTranscripts.keySet()) { streamGeneralTranscripts.put(key, new FileInputStream(generalTranscripts.get(key))); } final CountDownLatch shuffleOutputDownloadCompleted = new CountDownLatch(1); commandService.downloadShuffleResult(boardEntry.getName(), new DownloadShuffleResultOptions.Builder().withKeyID("ECENCKEY").withThreshold(4) .withPairingEnabled(true).withNodes("A", "B", "C", "D", "E").build(), streamSeedCommitments, streamSeedsAndWitnesses, streamGeneralTranscripts, streamWitnessTranscripts, new DownloadOperationListener() { int counter = 0; @Override public void messageDownloaded(int index, byte[] message, List<byte[]> proofs) { try { downloadStream.write(message); for (byte[] proof : proofs) { proofLogStream.write(proof); } } catch (IOException e) { e.printStackTrace(); // TODO: } counter++; } @Override public void completed() { shuffleOutputDownloadCompleted.countDown(); } @Override public void status(String statusObject) { System.err.println("status: " + statusObject); } @Override public void failed(String errorObject) { shuffleOutputDownloadCompleted.countDown(); System.err.println("failed shuffle download: " + errorObject); } }); // make sure you do this before closing the streams! shuffleOutputDownloadCompleted.await(); for (Integer key : witnessTranscripts.keySet()) { streamWitnessTranscripts.get(key).close(); } for (Integer key : generalTranscripts.keySet()) { streamGeneralTranscripts.get(key).close(); } downloadStream.close(); proofLogStream.close(); // log seed commitments and seed and witnesses for (String node : seedCommitmentMap.keySet()) { FileOutputStream fOut = new FileOutputStream( new File(destDir, boardEntry.getName() + "." + node + ".sc")); fOut.write(seedCommitmentMap.get(node)); fOut.close(); } for (String node : seedAndWitnessesMap.keySet()) { FileOutputStream fOut = new FileOutputStream( new File(destDir, boardEntry.getName() + "." + node + ".svw")); fOut.write(new SeedAndWitnessMessage(seedAndWitnessesMap.get(node)[0], seedAndWitnessesMap.get(node)[1]).getEncoded()); fOut.close(); } boardEntry.markProgress(BoardEntry.State.SHUFFLING, 0, 1.0); // // Convert the votes into a CSV // BallotUnpacker unpacker = new BallotUnpacker(new File(conversionFile)); ASN1InputStream aIn = new ASN1InputStream( new FileInputStream(new File(destDir, boardEntry.getName() + ".out"))); String[] details = boardEntry.getName().split("_"); // The second part of the name tells us which type the race is BufferedWriter bfOut = new BufferedWriter(new FileWriter(new File(destDir, boardEntry.getName() + "." + unpacker.getSuffix(details[0], details[1], details[2]) + ".csv"))); int ballotLength = unpacker.getBallotLength(details[0], details[1], details[2]); Object o; while ((o = aIn.readObject()) != null) { PointSequence seq = PointSequence .getInstance(CustomNamedCurves.getByName("secp256r1").getCurve(), o); ECPoint[] points = seq.getECPoints(); List<Integer> candidates = new ArrayList<>(); int maxCandidateID = 0; for (int i = 0; i != points.length; i++) { int[] votes = unpacker.lookup(details[0], details[1], details[2], points[i]); for (int j = 0; j != votes.length; j++) { candidates.add(votes[j]); if (votes[j] > maxCandidateID) { maxCandidateID = votes[j]; } } } int[] preferences = new int[ballotLength]; int preference = 1; for (int i = 0; i != candidates.size(); i++) { preferences[candidates.get(i) - 1] = preference++; } for (int i = 0; i != preferences.length; i++) { if (i != 0) { bfOut.write(","); } if (preferences[i] != 0) { bfOut.write(Integer.toString(preferences[i])); } } bfOut.newLine(); } bfOut.close(); // if we get to here download the board contents and empty it // TODO: add explicit board clear command. final CountDownLatch downloadLatch = new CountDownLatch(1); Operation<DownloadOperationListener> op = commandService.downloadBoardContents(boardEntry.getName(), new DownloadOptions.Builder().build(), new DownloadOperationListener() { int counter = 0; @Override public void messageDownloaded(int index, byte[] message, List<byte[]> proofs) { // ignore } @Override public void completed() { downloadLatch.countDown(); } @Override public void status(String statusObject) { System.err.println("status: " + statusObject); } @Override public void failed(String errorObject) { downloadLatch.countDown(); System.err.println("failed: " + errorObject); } }); downloadLatch.await(); } catch (Exception e) { eventNotifier.notify(EventNotifier.Level.ERROR, "Cannot perform download: " + e.getMessage(), e); } finally { shuffleLatch.countDown(); maxProcessSemaphore.release(); dialog.adjustProgress(1); } } } }