Java tutorial
/******************************************************************************* * Copyright (c) 2015 FRESCO (http://github.com/aicis/fresco). * * This file is part of the FRESCO project. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * FRESCO uses SCAPI - http://crypto.biu.ac.il/SCAPI, Crypto++, Miracl, NTL, * and Bouncy Castle. Please see these projects for any further licensing issues. *******************************************************************************/ package dk.alexandra.fresco.framework.configuration; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import dk.alexandra.fresco.framework.Party; import dk.alexandra.fresco.framework.ProtocolEvaluator; import dk.alexandra.fresco.framework.Reporter; import dk.alexandra.fresco.framework.sce.configuration.ProtocolSuiteConfiguration; import dk.alexandra.fresco.framework.sce.configuration.SCEConfiguration; import dk.alexandra.fresco.framework.sce.evaluator.EvaluationStrategy; import dk.alexandra.fresco.framework.sce.evaluator.SequentialEvaluator; import dk.alexandra.fresco.framework.sce.resources.storage.InMemoryStorage; import dk.alexandra.fresco.framework.sce.resources.storage.Storage; import dk.alexandra.fresco.framework.sce.resources.storage.StorageStrategy; import dk.alexandra.fresco.framework.sce.resources.storage.StreamedStorage; import dk.alexandra.fresco.suite.ProtocolSuite; import dk.alexandra.fresco.suite.bgw.configuration.BgwConfiguration; import dk.alexandra.fresco.suite.dummy.DummyConfiguration; import dk.alexandra.fresco.suite.spdz.configuration.SpdzConfiguration; import dk.alexandra.fresco.suite.tinytables.online.TinyTablesConfiguration; import dk.alexandra.fresco.suite.tinytables.prepro.TinyTablesPreproConfiguration; /** * Utility for reading all configuration from command line. * <p> * A set of default configurations are used when parameters are not specified at runtime. * </p> * */ public class CmdLineUtil { private final Options options; private Options appOptions; private CommandLine cmd; private SCEConfiguration sceConf; private ProtocolSuiteConfiguration psConf; public CmdLineUtil() { this.appOptions = new Options(); this.options = buildStandardOptions(); } public SCEConfiguration getSCEConfiguration() { return this.sceConf; } public ProtocolSuiteConfiguration getProtocolSuiteConfiguration() { return this.psConf; } /** * Adds standard options. * * TODO: Move standard options to SCE. * * For instance, options for setting player id and protocol suite. * */ private static Options buildStandardOptions() { Options options = new Options(); options.addOption(Option.builder("i").desc("The id of this player. Must be a unique positive integer.") .longOpt("id").required(true).hasArg().build()); options.addOption(Option.builder("s") .desc("The name of the protocol suite to use. Must be one of these: " + ProtocolSuite.getSupportedProtocolSuites() + ". " + "The default value is: bgw") .longOpt("suite").required(true).hasArg().build()); options.addOption(Option.builder("p").desc( "Connection data for a party. Use -p multiple times to specify many players. You must always at least include yourself." + "Must be on the form [id]:[hostname]:[port] or [id]:[hostname]:[port]:[shared key]. " + "id is a unique positive integer for the player, host and port is where to find the player, " + " shared key is an optional string defining a secret key that is shared by you and the other player " + " (the other player must submit the same key for you as you do for him). ") .longOpt("party").required(true).hasArgs().build()); options.addOption(Option.builder("l").desc( "The log level. Can be either OFF, SEVERE, CONFIG, WARNING, INFO, FINE, FINER, FINEST. Default is 'WARNING'") .longOpt("log-level").required(false).hasArg().build()); options.addOption(Option.builder("t") .desc("The number of threads to use for the SCE. Defaults to " + getDefaultNoOfThreads()) .longOpt("no-threads").required(false).hasArg(true).build()); options.addOption(Option.builder("vt") .desc("The number of threads to use in the VM (evaluator). Defaults to " + getDefaultNoOfThreads()) .longOpt("no-vm-threads").required(false).hasArg(true).build()); options.addOption(Option.builder("e") .desc("The strategy for evaluation. Can be one of: " + Arrays.toString(EvaluationStrategy.values()) + ". Defaults to " + EvaluationStrategy.SEQUENTIAL) .longOpt("evaluator").required(false).hasArg(true).build()); options.addOption(Option.builder("b").desc( "The maximum number of native protocols kept in memory at any point in time. Defaults to 4096") .longOpt("max-batch").required(false).hasArg(true).build()); options.addOption(Option.builder("D").argName("property=value") .desc("Used to set properties of protocol suite and other customizable components.").required(false) .hasArg().numberOfArgs(2).valueSeparator().build()); return options; } private static int getDefaultNoOfThreads() { int n = Runtime.getRuntime().availableProcessors(); if (n == 1) { return 1; } // Heuristic that gives best performance: One thread for each worker // and one for the 'system'. return n - 1; } private int parseNonzeroInt(String optionId) throws ParseException { int res; String opStr = this.cmd.getOptionValue(optionId); if (opStr == null) { throw new ParseException("No value for option: " + optionId); } try { res = Integer.parseInt(opStr); if (res < 0) { throw new ParseException(optionId + " must be a positive integer"); } } catch (NumberFormatException e) { throw new ParseException("Cannot parse '" + this.cmd.getOptionValue(optionId) + "' as an integer"); } return res; } private void validateStandardOptions() throws ParseException { int myId; Level logLevel; Object suiteObj = this.cmd.getParsedOptionValue("s"); if (suiteObj == null) { throw new ParseException("Cannot parse '" + this.cmd.getOptionValue("s") + "' as a string"); } final Map<Integer, Party> parties = new HashMap<Integer, Party>(); final String suite = (String) suiteObj; if (!ProtocolSuite.getSupportedProtocolSuites().contains(suite.toLowerCase())) { throw new ParseException("Unknown protocol suite: " + suite); } myId = parseNonzeroInt("i"); if (myId == 0) { throw new ParseException("Player id must be positive, non-zero integer"); } for (String pStr : this.cmd.getOptionValues("p")) { String[] p = pStr.split(":"); if (p.length < 3 || p.length > 4) { throw new ParseException( "Could not parse '" + pStr + "' as [id]:[host]:[port] or [id]:[host]:[port]:[shared key]"); } try { int id = Integer.parseInt(p[0]); InetAddress.getByName(p[1]); // Check that hostname is valid. int port = Integer.parseInt(p[2]); Party party; if (p.length == 3) { party = new Party(id, p[1], port); } else { party = new Party(id, p[1], port, p[3]); } if (parties.containsKey(id)) { throw new ParseException("Party ids must be unique"); } parties.put(id, party); } catch (NumberFormatException | UnknownHostException e) { throw new ParseException("Could not parse '" + pStr + "': " + e.getMessage()); } } if (!parties.containsKey(myId)) { throw new ParseException("This party is given the id " + myId + " but this id is not present in the list of parties " + parties.keySet()); } if (this.cmd.hasOption("l")) { System.out.println(this.cmd.getOptionValue("l")); logLevel = Level.parse(this.cmd.getOptionValue("l")); } else { logLevel = Level.WARNING; } Reporter.init(logLevel); int noOfThreads = this.cmd.hasOption("t") ? parseNonzeroInt("t") : getDefaultNoOfThreads(); if (noOfThreads > Runtime.getRuntime().availableProcessors()) { Reporter.warn("You are using " + noOfThreads + " but system has only " + Runtime.getRuntime().availableProcessors() + " available processors. This is likely to result in less than optimal performance."); } int vmThreads = this.cmd.hasOption("vt") ? parseNonzeroInt("vt") : getDefaultNoOfThreads(); if (vmThreads > Runtime.getRuntime().availableProcessors()) { Reporter.warn("You are using " + vmThreads + " but system has only " + Runtime.getRuntime().availableProcessors() + " available processors. This is likely to result in less than optimal performance."); } final ProtocolEvaluator evaluator; if (this.cmd.hasOption("e")) { try { evaluator = EvaluationStrategy.fromString(this.cmd.getOptionValue("e")); } catch (ConfigurationException e) { throw new ParseException("Invalid evaluation strategy: " + this.cmd.getOptionValue("e")); } } else { evaluator = new SequentialEvaluator(); } final Storage storage; if (this.cmd.hasOption("store")) { try { storage = StorageStrategy.fromString(this.cmd.getOptionValue("store")); } catch (ConfigurationException e) { throw new ParseException("Invalid storage strategy: " + this.cmd.getOptionValue("store")); } } else { storage = new InMemoryStorage(); } final int maxBatchSize; if (this.cmd.hasOption("b")) { try { maxBatchSize = Integer.parseInt(this.cmd.getOptionValue("b")); } catch (Exception e) { throw new ParseException(""); } } else { maxBatchSize = 4096; } // TODO: Rather: Just log sceConf.toString() Reporter.config("Player id : " + myId); Reporter.config("Protocol suite : " + suite); Reporter.config("Players : " + parties); Reporter.config("Log level : " + logLevel); Reporter.config("No of threads : " + noOfThreads); Reporter.config("No of vm threads : " + vmThreads); Reporter.config("Evaluation strategy: " + evaluator); Reporter.config("Storage strategy : " + storage); Reporter.config("Maximum batch size : " + maxBatchSize); this.sceConf = new SCEConfiguration() { @Override public int getMyId() { return myId; } @Override public String getProtocolSuiteName() { return suite; } @Override public Map<Integer, Party> getParties() { return parties; } @Override public Level getLogLevel() { return logLevel; } @Override public int getNoOfThreads() { return noOfThreads; } @Override public int getNoOfVMThreads() { return vmThreads; } @Override public ProtocolEvaluator getEvaluator() { return evaluator; } @Override public Storage getStorage() { return storage; } @Override public int getMaxBatchSize() { return maxBatchSize; } @Override public StreamedStorage getStreamedStorage() { if (storage instanceof StreamedStorage) { return (StreamedStorage) storage; } else { return null; } } }; } /** * For adding application specific options. * */ public void addOption(Option option) { this.appOptions.addOption(option); } public CommandLine parse(String[] args) { try { CommandLineParser parser = new DefaultParser(); Options helpOpt = new Options(); helpOpt.addOption(Option.builder("h").desc("Displays this help message").longOpt("help").required(false) .hasArg(false).build()); cmd = parser.parse(helpOpt, args, true); if (cmd.hasOption("h")) { displayHelp(); System.exit(0); } Options allOpts = new Options(); for (Option o : options.getOptions()) { allOpts.addOption(o); } for (Option o : appOptions.getOptions()) { allOpts.addOption(o); } cmd = parser.parse(allOpts, args); validateStandardOptions(); // TODO: Do this without hardcoding the protocol suite names here. switch (this.sceConf.getProtocolSuiteName()) { case "bgw": this.psConf = BgwConfiguration.fromCmdLine(this.sceConf, cmd); break; case "dummy": this.psConf = DummyConfiguration.fromCmdLine(this.sceConf, cmd); break; case "spdz": this.psConf = SpdzConfiguration.fromCmdLine(this.sceConf, cmd); break; case "tinytablesprepro": this.psConf = TinyTablesPreproConfiguration.fromCmdLine(this.sceConf, cmd); break; case "tinytables": this.psConf = TinyTablesConfiguration.fromCmdLine(this.sceConf, cmd); break; default: throw new ParseException( "Unknown protocol suite: " + this.getSCEConfiguration().getProtocolSuiteName()); } } catch (ParseException e) { System.out.println("Error while parsing arguments: " + e.getLocalizedMessage()); System.out.println(); displayHelp(); System.exit(-1); // TODO: Consider moving to top level. } return this.cmd; } public void displayHelp() { HelpFormatter formatter = new HelpFormatter(); formatter.setSyntaxPrefix(""); formatter.printHelp("General SCE options are:", this.options); formatter.setSyntaxPrefix(""); formatter.printHelp("Application specific options are:", this.appOptions); } }