summaryrefslogtreecommitdiff
path: root/src/cz/crcs/ectester/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/cz/crcs/ectester/common')
-rw-r--r--src/cz/crcs/ectester/common/cli/Argument.java29
-rw-r--r--src/cz/crcs/ectester/common/cli/CLITools.java140
-rw-r--r--src/cz/crcs/ectester/common/cli/ParserOptions.java38
-rw-r--r--src/cz/crcs/ectester/common/cli/TreeCommandLine.java178
-rw-r--r--src/cz/crcs/ectester/common/cli/TreeParser.java128
-rw-r--r--src/cz/crcs/ectester/common/ec/EC_Category.java142
-rw-r--r--src/cz/crcs/ectester/common/ec/EC_Curve.java132
-rw-r--r--src/cz/crcs/ectester/common/ec/EC_Data.java217
-rw-r--r--src/cz/crcs/ectester/common/ec/EC_KAResult.java65
-rw-r--r--src/cz/crcs/ectester/common/ec/EC_Key.java83
-rw-r--r--src/cz/crcs/ectester/common/ec/EC_Keypair.java41
-rw-r--r--src/cz/crcs/ectester/common/ec/EC_Params.java194
-rw-r--r--src/cz/crcs/ectester/common/output/BaseTextTestWriter.java82
-rw-r--r--src/cz/crcs/ectester/common/output/BaseXMLTestWriter.java103
-rw-r--r--src/cz/crcs/ectester/common/output/BaseYAMLTestWriter.java91
-rw-r--r--src/cz/crcs/ectester/common/output/OutputLogger.java60
-rw-r--r--src/cz/crcs/ectester/common/output/TeeOutputStream.java36
-rw-r--r--src/cz/crcs/ectester/common/output/TestWriter.java15
-rw-r--r--src/cz/crcs/ectester/common/test/BaseTestable.java32
-rw-r--r--src/cz/crcs/ectester/common/test/CompoundTest.java111
-rw-r--r--src/cz/crcs/ectester/common/test/Result.java96
-rw-r--r--src/cz/crcs/ectester/common/test/SimpleTest.java19
-rw-r--r--src/cz/crcs/ectester/common/test/Test.java66
-rw-r--r--src/cz/crcs/ectester/common/test/TestCallback.java12
-rw-r--r--src/cz/crcs/ectester/common/test/TestException.java13
-rw-r--r--src/cz/crcs/ectester/common/test/TestSuite.java56
-rw-r--r--src/cz/crcs/ectester/common/test/Testable.java33
-rw-r--r--src/cz/crcs/ectester/common/util/ByteUtil.java128
-rw-r--r--src/cz/crcs/ectester/common/util/CardUtil.java272
-rw-r--r--src/cz/crcs/ectester/common/util/ECUtil.java172
30 files changed, 2784 insertions, 0 deletions
diff --git a/src/cz/crcs/ectester/common/cli/Argument.java b/src/cz/crcs/ectester/common/cli/Argument.java
new file mode 100644
index 0000000..e9b6688
--- /dev/null
+++ b/src/cz/crcs/ectester/common/cli/Argument.java
@@ -0,0 +1,29 @@
+package cz.crcs.ectester.common.cli;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class Argument {
+ private String name;
+ private String desc;
+ private boolean required;
+
+ public Argument(String name, String desc, boolean isRequired) {
+ this.name = name;
+ this.desc = desc;
+ this.required = isRequired;
+ }
+
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ public boolean isRequired() {
+ return required;
+ }
+}
diff --git a/src/cz/crcs/ectester/common/cli/CLITools.java b/src/cz/crcs/ectester/common/cli/CLITools.java
new file mode 100644
index 0000000..91f121f
--- /dev/null
+++ b/src/cz/crcs/ectester/common/cli/CLITools.java
@@ -0,0 +1,140 @@
+package cz.crcs.ectester.common.cli;
+
+import cz.crcs.ectester.common.ec.EC_Category;
+import cz.crcs.ectester.common.ec.EC_Data;
+import cz.crcs.ectester.data.EC_Store;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Options;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Map;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class CLITools {
+
+ /**
+ * Print help.
+ */
+ public static void help(String prog, String header, Options options, String footer, boolean usage) {
+ HelpFormatter help = new HelpFormatter();
+ help.setOptionComparator(null);
+ help.printHelp(prog, header, options, footer, usage);
+ }
+
+ private static void help(HelpFormatter help, PrintWriter pw, CommandLineParser cli, Options opts, int depth) {
+ if (opts.getOptions().size() > 0) {
+ help.printOptions(pw, HelpFormatter.DEFAULT_WIDTH, opts, HelpFormatter.DEFAULT_LEFT_PAD + depth, HelpFormatter.DEFAULT_DESC_PAD);
+ }
+ if (cli instanceof TreeParser) {
+ TreeParser tp = (TreeParser) cli;
+ for (Argument arg : tp.getArgs()) {
+ String argname = arg.isRequired() ? "<" + arg.getName() + ">" : "[" + arg.getName() + "]";
+ help.printWrapped(pw, HelpFormatter.DEFAULT_WIDTH, String.format("%" + (depth + 1) + "s" + argname + " " + arg.getDesc(), " "));
+ }
+ tp.getParsers().forEach((key, value) -> {
+ pw.println();
+ help.printWrapped(pw, HelpFormatter.DEFAULT_WIDTH, String.format("%" + depth + "s" + key + ":", " "));
+ CLITools.help(help, pw, value.getParser(), value.getOptions(), depth + 1);
+ });
+ }
+ }
+
+ private static void usage(HelpFormatter help, PrintWriter pw, CommandLineParser cli, Options opts) {
+ StringWriter sw = new StringWriter();
+ PrintWriter upw = new PrintWriter(sw);
+ help.printUsage(upw, HelpFormatter.DEFAULT_WIDTH, "", opts);
+ if (cli instanceof TreeParser) {
+ upw.print(" ");
+ TreeParser tp = (TreeParser) cli;
+ String[] keys = tp.getParsers().keySet().toArray(new String[tp.getParsers().size()]);
+ if (keys.length > 0 && !tp.isRequired()) {
+ upw.print("[ ");
+ }
+
+ for (int i = 0; i < keys.length; ++i) {
+ String key = keys[i];
+ ParserOptions value = tp.getParsers().get(key);
+ upw.print("(" + key);
+ usage(help, upw, value.getParser(), value.getOptions());
+ upw.print(")");
+ if (i != keys.length - 1) {
+ upw.print(" | ");
+ }
+ }
+
+ if (keys.length > 0 && !tp.isRequired()) {
+ upw.print(" ]");
+ }
+
+ Argument[] args = tp.getArgs().toArray(new Argument[tp.getArgs().size()]);
+ if (args.length > 0) {
+ String[] argss = new String[tp.getArgs().size()];
+ for (int i = 0; i < args.length; ++i) {
+ Argument arg = args[i];
+ argss[i] = arg.isRequired() ? "<" + arg.getName() + ">" : "[" + arg.getName() + "]";
+ }
+ upw.print(" " + String.join(" ", argss));
+ }
+ }
+ pw.println(sw.toString().replaceAll("usage:( )?", "").replace("\n", ""));
+ }
+
+ /**
+ * Print tree help.
+ */
+ public static void help(String prog, String header, Options baseOpts, TreeParser baseParser, String footer, boolean printUsage) {
+ HelpFormatter help = new HelpFormatter();
+ help.setOptionComparator(null);
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ help.printWrapped(pw, HelpFormatter.DEFAULT_WIDTH, header);
+ if (printUsage) {
+ StringWriter uw = new StringWriter();
+ PrintWriter upw = new PrintWriter(uw);
+ usage(help, upw, baseParser, baseOpts);
+ pw.print("usage: " + prog);
+ help.printWrapped(pw, HelpFormatter.DEFAULT_WIDTH, uw.toString());
+ upw.close();
+ pw.println();
+ }
+ help(help, pw, baseParser, baseOpts, 1);
+ help.printWrapped(pw, HelpFormatter.DEFAULT_WIDTH, footer);
+ System.out.println(sw.toString());
+ }
+
+ /**
+ * Print version info.
+ */
+ public static void version(String description, String license) {
+ System.out.println(description);
+ System.out.println(license);
+ }
+
+ /**
+ * List categories and named curves.
+ */
+ public static void listNamed(EC_Store dataStore, String named) {
+ Map<String, EC_Category> categories = dataStore.getCategories();
+ if (named == null) {
+ // print all categories, briefly
+ for (EC_Category cat : categories.values()) {
+ System.out.println(cat);
+ }
+ } else if (categories.containsKey(named)) {
+ // print given category
+ System.out.println(categories.get(named));
+ } else {
+ // print given object
+ EC_Data object = dataStore.getObject(EC_Data.class, named);
+ if (object != null) {
+ System.out.println(object);
+ } else {
+ System.err.println("Named object " + named + " not found!");
+ }
+ }
+ }
+}
diff --git a/src/cz/crcs/ectester/common/cli/ParserOptions.java b/src/cz/crcs/ectester/common/cli/ParserOptions.java
new file mode 100644
index 0000000..ee2097e
--- /dev/null
+++ b/src/cz/crcs/ectester/common/cli/ParserOptions.java
@@ -0,0 +1,38 @@
+package cz.crcs.ectester.common.cli;
+
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.Options;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class ParserOptions {
+ private CommandLineParser parser;
+ private Options options;
+ private List<Argument> arguments;
+
+ public ParserOptions(CommandLineParser parser, Options options) {
+ this.parser = parser;
+ this.options = options;
+ }
+
+ public ParserOptions(CommandLineParser parser, Options options, List<Argument> arguments) {
+ this(parser, options);
+ this.arguments = arguments;
+ }
+
+ public CommandLineParser getParser() {
+ return parser;
+ }
+
+ public Options getOptions() {
+ return options;
+ }
+
+ public List<Argument> getArguments() {
+ return Collections.unmodifiableList(arguments);
+ }
+}
diff --git a/src/cz/crcs/ectester/common/cli/TreeCommandLine.java b/src/cz/crcs/ectester/common/cli/TreeCommandLine.java
new file mode 100644
index 0000000..6a044d2
--- /dev/null
+++ b/src/cz/crcs/ectester/common/cli/TreeCommandLine.java
@@ -0,0 +1,178 @@
+package cz.crcs.ectester.common.cli;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.ParseException;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.function.BiFunction;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class TreeCommandLine extends CommandLine {
+ private String name = "";
+ private TreeCommandLine next;
+ private CommandLine cli;
+
+ public TreeCommandLine(CommandLine cli, TreeCommandLine next) {
+ this.cli = cli;
+ this.next = next;
+ }
+
+ public TreeCommandLine(String name, CommandLine cli, TreeCommandLine next) {
+ this(cli, next);
+ this.name = name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getNextName() {
+ if (next != null) {
+ return next.getName();
+ }
+ return null;
+ }
+
+ public TreeCommandLine getNext() {
+ return next;
+ }
+
+ public boolean isNext(String next) {
+ return Objects.equals(getNextName(), next);
+ }
+
+ public CommandLine getThis() {
+ return cli;
+ }
+
+ public int getDepth() {
+ if (next == null) {
+ return 0;
+ }
+ return next.getDepth() + 1;
+ }
+
+ private <T> T getOption(String opt, BiFunction<CommandLine, String, T> getter, T defaultValue) {
+ if (opt.contains(".")) {
+ String[] parts = opt.split("\\.", 2);
+ if (next != null && parts[0].equals(next.getName())) {
+ T result = getter.apply(next, parts[1]);
+ if (result != null)
+ return result;
+ return defaultValue;
+ }
+ return defaultValue;
+ }
+ return getter.apply(cli, opt);
+ }
+
+ @Override
+ public boolean hasOption(String opt) {
+ return getOption(opt, CommandLine::hasOption, false);
+ }
+
+ @Override
+ public boolean hasOption(char opt) {
+ return cli.hasOption(opt);
+ }
+
+ @Override
+ public Object getParsedOptionValue(String opt) throws ParseException {
+ if (opt.contains(".")) {
+ String[] parts = opt.split("\\.", 2);
+ if (next != null && parts[0].equals(next.getName())) {
+ return next.getParsedOptionValue(parts[1]);
+ }
+ return null;
+ }
+ return cli.getParsedOptionValue(opt);
+ }
+
+ @Override
+ public Object getOptionObject(char opt) {
+ return cli.getOptionObject(opt);
+ }
+
+ @Override
+ public String getOptionValue(String opt) {
+ return getOption(opt, CommandLine::getOptionValue, null);
+ }
+
+ @Override
+ public String getOptionValue(char opt) {
+ return cli.getOptionValue(opt);
+ }
+
+ @Override
+ public String[] getOptionValues(String opt) {
+ return getOption(opt, CommandLine::getOptionValues, null);
+ }
+
+ @Override
+ public String[] getOptionValues(char opt) {
+ return cli.getOptionValues(opt);
+ }
+
+ @Override
+ public String getOptionValue(String opt, String defaultValue) {
+ return getOption(opt, CommandLine::getOptionValue, defaultValue);
+ }
+
+ @Override
+ public String getOptionValue(char opt, String defaultValue) {
+ return cli.getOptionValue(opt, defaultValue);
+ }
+
+ @Override
+ public Properties getOptionProperties(String opt) {
+ return getOption(opt, CommandLine::getOptionProperties, new Properties());
+ }
+
+ @Override
+ public Iterator<Option> iterator() {
+ return cli.iterator();
+ }
+
+ @Override
+ public Option[] getOptions() {
+ return cli.getOptions();
+ }
+
+ public boolean hasArg(int index) {
+ return getArg(index) != null;
+ }
+
+ public String getArg(int index) {
+ if (next != null) {
+ return next.getArg(index);
+ }
+ String[] args = cli.getArgs();
+ if (index >= args.length) {
+ return null;
+ }
+ if (index < 0 && -index > args.length) {
+ return null;
+ }
+ return index < 0 ? args[args.length + index] : args[index];
+ }
+
+ @Override
+ public String[] getArgs() {
+ return cli.getArgs();
+ }
+
+ @Override
+ public List<String> getArgList() {
+ return cli.getArgList();
+ }
+}
diff --git a/src/cz/crcs/ectester/common/cli/TreeParser.java b/src/cz/crcs/ectester/common/cli/TreeParser.java
new file mode 100644
index 0000000..f1a1980
--- /dev/null
+++ b/src/cz/crcs/ectester/common/cli/TreeParser.java
@@ -0,0 +1,128 @@
+package cz.crcs.ectester.common.cli;
+
+import org.apache.commons.cli.*;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class TreeParser implements CommandLineParser {
+ private Map<String, ParserOptions> parsers;
+ private boolean required;
+ private List<Argument> args = Collections.emptyList();
+
+ public TreeParser(Map<String, ParserOptions> parsers, boolean required) {
+ this.parsers = parsers;
+ this.required = required;
+ }
+
+ public TreeParser(Map<String, ParserOptions> parsers, boolean required, List<Argument> args) {
+ this(parsers, required);
+ this.args = args;
+ }
+
+ public Map<String, ParserOptions> getParsers() {
+ return Collections.unmodifiableMap(parsers);
+ }
+
+ public boolean isRequired() {
+ return required;
+ }
+
+ public List<Argument> getArgs() {
+ return Collections.unmodifiableList(args);
+ }
+
+ @Override
+ public TreeCommandLine parse(Options options, String[] arguments) throws ParseException {
+ return this.parse(options, arguments, null);
+ }
+
+ public TreeCommandLine parse(Options options, String[] arguments, Properties properties) throws ParseException {
+ return this.parse(options, arguments, properties, false);
+ }
+
+ @Override
+ public TreeCommandLine parse(Options options, String[] arguments, boolean stopAtNonOption) throws ParseException {
+ return this.parse(options, arguments, null, stopAtNonOption);
+ }
+
+ public TreeCommandLine parse(Options options, String[] arguments, Properties properties, boolean stopAtNonOption) throws ParseException {
+ DefaultParser thisParser = new DefaultParser();
+ CommandLine cli = thisParser.parse(options, arguments, properties, true);
+
+ CommandLine subCli = null;
+ String[] cliArgs = cli.getArgs();
+ String sub = null;
+ if (cliArgs.length != 0) {
+ sub = cliArgs[0];
+
+ List<String> matches = new LinkedList<>();
+ String finalSub = sub;
+ for (Map.Entry<String, ParserOptions> entry : parsers.entrySet()) {
+ if (entry.getKey().equalsIgnoreCase(finalSub)) {
+ matches.clear();
+ matches.add(finalSub);
+ break;
+ } else if (entry.getKey().startsWith(finalSub)) {
+ matches.add(entry.getKey());
+ }
+ }
+
+ if (matches.size() == 1) {
+ sub = matches.get(0);
+ ParserOptions subparser = parsers.get(sub);
+ String[] remainingArgs = new String[cliArgs.length - 1];
+ System.arraycopy(cliArgs, 1, remainingArgs, 0, cliArgs.length - 1);
+ subCli = subparser.getParser().parse(subparser.getOptions(), remainingArgs, true);
+ } else if (matches.size() > 1) {
+ throw new AmbiguousOptionException(sub, matches);
+ }
+ } else {
+ if (required) {
+ throw new MissingOptionException(new ArrayList(parsers.keySet()));
+ }
+ }
+
+ int maxArgs = args.size();
+ long requiredArgs = args.stream().filter(Argument::isRequired).count();
+ String reqArgs = String.join(" ", args.stream().filter(Argument::isRequired).map(Argument::getName).collect(Collectors.toList()));
+
+ if (subCli instanceof TreeCommandLine) {
+ TreeCommandLine subTreeCli = (TreeCommandLine) subCli;
+
+ TreeCommandLine lastCli = subTreeCli;
+ while (lastCli.getNext() != null) {
+ lastCli = lastCli.getNext();
+ }
+
+ if (lastCli.getArgs().length < requiredArgs) {
+ throw new MissingArgumentException("Not enough arguments: " + reqArgs);
+ } else if (lastCli.getArgs().length > maxArgs) {
+ throw new MissingArgumentException("Too many arguments.");
+ }
+
+ subTreeCli.setName(sub);
+ return new TreeCommandLine(cli, subTreeCli);
+ } else if (subCli != null) {
+ if (subCli.getArgs().length < requiredArgs) {
+ throw new MissingArgumentException("Not enough arguments: " + reqArgs);
+ } else if (subCli.getArgs().length > maxArgs) {
+ throw new MissingArgumentException("Too many arguments.");
+ }
+
+ TreeCommandLine subTreeCli = new TreeCommandLine(sub, subCli, null);
+ return new TreeCommandLine(cli, subTreeCli);
+ } else {
+ if (cliArgs.length < requiredArgs) {
+ throw new MissingArgumentException("Not enough arguments: " + reqArgs);
+ } else if (cliArgs.length > maxArgs) {
+ throw new MissingArgumentException("Too many arguments.");
+ }
+
+ return new TreeCommandLine(cli, null);
+ }
+ }
+}
diff --git a/src/cz/crcs/ectester/common/ec/EC_Category.java b/src/cz/crcs/ectester/common/ec/EC_Category.java
new file mode 100644
index 0000000..32a788d
--- /dev/null
+++ b/src/cz/crcs/ectester/common/ec/EC_Category.java
@@ -0,0 +1,142 @@
+package cz.crcs.ectester.common.ec;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+
+/**
+ * A category of EC_Data objects, has a name, description and represents a directory in
+ * the cz.crcs.ectester.data package.
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class EC_Category {
+
+ private String name;
+ private String directory;
+ private String desc;
+
+ private Map<String, EC_Data> objects;
+
+
+ public EC_Category(String name, String directory) {
+ this.name = name;
+ this.directory = directory;
+ }
+
+ public EC_Category(String name, String directory, String desc) {
+ this(name, directory);
+ this.desc = desc;
+ }
+
+ public EC_Category(String name, String directory, String desc, Map<String, EC_Data> objects) {
+ this(name, directory, desc);
+ this.objects = objects;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDirectory() {
+ return directory;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ public Map<String, EC_Data> getObjects() {
+ return Collections.unmodifiableMap(objects);
+ }
+
+ public <T extends EC_Data> Map<String, T> getObjects(Class<T> cls) {
+ Map<String, T> objs = new TreeMap<>();
+ for (Map.Entry<String, EC_Data> entry : objects.entrySet()) {
+ if (cls.isInstance(entry.getValue())) {
+ objs.put(entry.getKey(), cls.cast(entry.getValue()));
+ }
+ }
+ return Collections.unmodifiableMap(objs);
+ }
+
+ public <T extends EC_Data> T getObject(Class<T> cls, String id) {
+ EC_Data obj = objects.get(id);
+ if (cls.isInstance(obj)) {
+ return cls.cast(obj);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder out = new StringBuilder();
+ out.append("\t- ").append(name).append((desc == null || desc.equals("")) ? "" : ": " + desc);
+ out.append(System.lineSeparator());
+
+ Map<String, EC_Curve> curves = getObjects(EC_Curve.class);
+ int size = curves.size();
+ if (size > 0) {
+ out.append("\t\tCurves: ");
+ for (Map.Entry<String, EC_Curve> curve : curves.entrySet()) {
+ out.append(curve.getKey());
+ size--;
+ if (size > 0)
+ out.append(", ");
+ }
+ out.append(System.lineSeparator());
+ }
+
+ Map<String, EC_Key> keys = getObjects(EC_Key.class);
+ size = keys.size();
+ if (size > 0) {
+ out.append("\t\tKeys: ");
+ for (Map.Entry<String, EC_Key> key : keys.entrySet()) {
+ out.append(key.getKey());
+ size--;
+ if (size > 0)
+ out.append(", ");
+ }
+ out.append(System.lineSeparator());
+ }
+
+ Map<String, EC_Keypair> keypairs = getObjects(EC_Keypair.class);
+ size = keypairs.size();
+ if (size > 0) {
+ out.append("\t\tKeypairs: ");
+ for (Map.Entry<String, EC_Keypair> key : keypairs.entrySet()) {
+ out.append(key.getKey());
+ size--;
+ if (size > 0)
+ out.append(", ");
+ }
+ out.append(System.lineSeparator());
+ }
+
+ Map<String, EC_KAResult> results = getObjects(EC_KAResult.class);
+ size = results.size();
+ if (size > 0) {
+ out.append("\t\tResults: ");
+ for (Map.Entry<String, EC_KAResult> result : results.entrySet()) {
+ out.append(result.getKey());
+ size--;
+ if (size > 0)
+ out.append(", ");
+ }
+ out.append(System.lineSeparator());
+ }
+ return out.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof EC_Category && Objects.equals(this.name, ((EC_Category) obj).name);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.name.hashCode() ^ this.directory.hashCode();
+ }
+
+}
diff --git a/src/cz/crcs/ectester/common/ec/EC_Curve.java b/src/cz/crcs/ectester/common/ec/EC_Curve.java
new file mode 100644
index 0000000..173fd29
--- /dev/null
+++ b/src/cz/crcs/ectester/common/ec/EC_Curve.java
@@ -0,0 +1,132 @@
+package cz.crcs.ectester.common.ec;
+
+import cz.crcs.ectester.applet.EC_Consts;
+import cz.crcs.ectester.common.util.ByteUtil;
+import javacard.security.KeyPair;
+
+import java.math.BigInteger;
+import java.security.spec.*;
+
+/**
+ * An Elliptic curve, contains parameters Fp/F2M, A, B, G, R, (K)?.
+ *
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class EC_Curve extends EC_Params {
+ private short bits;
+ private byte field;
+ private String desc;
+
+ /**
+ * @param bits
+ * @param field KeyPair.ALG_EC_FP or KeyPair.ALG_EC_F2M
+ */
+ public EC_Curve(short bits, byte field) {
+ super(field == KeyPair.ALG_EC_FP ? EC_Consts.PARAMETERS_DOMAIN_FP : EC_Consts.PARAMETERS_DOMAIN_F2M);
+ this.bits = bits;
+ this.field = field;
+ }
+
+ public EC_Curve(String id, short bits, byte field) {
+ this(bits, field);
+ this.id = id;
+ }
+
+ public EC_Curve(String id, short bits, byte field, String desc) {
+ this(id, bits, field);
+ this.desc = desc;
+ }
+
+ public short getBits() {
+ return bits;
+ }
+
+ public byte getField() {
+ return field;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ @Override
+ public String toString() {
+ return "<" + getId() + "> " + (field == KeyPair.ALG_EC_FP ? "Prime" : "Binary") + " field Elliptic curve (" + String.valueOf(bits) + "b)" + (desc == null ? "" : ": " + desc);
+ }
+
+ public ECParameterSpec toSpec() {
+ ECField field;
+ if (this.field == KeyPair.ALG_EC_FP) {
+ field = new ECFieldFp(new BigInteger(1, getData(0)));
+ } else {
+ byte[][] fieldData = getParam(EC_Consts.PARAMETER_F2M);
+ int m = ByteUtil.getShort(fieldData[0], 0);
+ int e1 = ByteUtil.getShort(fieldData[1], 0);
+ int e2 = ByteUtil.getShort(fieldData[2], 0);
+ int e3 = ByteUtil.getShort(fieldData[3], 0);
+ int[] powers = new int[]{e1, e2, e3};
+ field = new ECFieldF2m(m, powers);
+ }
+
+ BigInteger a = new BigInteger(1, getParam(EC_Consts.PARAMETER_A)[0]);
+ BigInteger b = new BigInteger(1, getParam(EC_Consts.PARAMETER_B)[0]);
+
+ EllipticCurve curve = new EllipticCurve(field, a, b);
+
+ byte[][] G = getParam(EC_Consts.PARAMETER_G);
+ BigInteger gx = new BigInteger(1, G[0]);
+ BigInteger gy = new BigInteger(1, G[1]);
+ ECPoint generator = new ECPoint(gx, gy);
+
+ BigInteger n = new BigInteger(1, getParam(EC_Consts.PARAMETER_R)[0]);
+
+ int h = ByteUtil.getShort(getParam(EC_Consts.PARAMETER_K)[0], 0);
+
+ return new ECParameterSpec(curve, generator, n, h);
+ }
+
+ public static EC_Curve fromSpec(ECParameterSpec spec) {
+ EllipticCurve curve = spec.getCurve();
+ ECField field = curve.getField();
+
+ short bits = (short) field.getFieldSize();
+ byte[][] params;
+ int paramIndex = 0;
+ byte fieldType;
+ if (field instanceof ECFieldFp) {
+ ECFieldFp primeField = (ECFieldFp) field;
+ params = new byte[7][];
+ params[paramIndex++] = primeField.getP().toByteArray();
+ fieldType = KeyPair.ALG_EC_FP;
+ } else if (field instanceof ECFieldF2m) {
+ ECFieldF2m binaryField = (ECFieldF2m) field;
+ params = new byte[10][];
+ params[paramIndex] = new byte[2];
+ ByteUtil.setShort(params[paramIndex++], 0, (short) binaryField.getM());
+ int[] powers = binaryField.getMidTermsOfReductionPolynomial();
+ for (int i = 0; i < 3; ++i) {
+ params[paramIndex] = new byte[2];
+ ByteUtil.setShort(params[paramIndex++], 0, (short) powers[i]);
+ }
+ fieldType = KeyPair.ALG_EC_F2M;
+ } else {
+ throw new IllegalArgumentException("ECParameterSpec with an unknown field.");
+ }
+
+ ECPoint generator = spec.getGenerator();
+
+ params[paramIndex++] = curve.getA().toByteArray();
+ params[paramIndex++] = curve.getB().toByteArray();
+
+ params[paramIndex++] = generator.getAffineX().toByteArray();
+ params[paramIndex++] = generator.getAffineY().toByteArray();
+
+ params[paramIndex++] = spec.getOrder().toByteArray();
+ params[paramIndex] = new byte[2];
+ ByteUtil.setShort(params[paramIndex], 0, (short) spec.getCofactor());
+
+ EC_Curve result = new EC_Curve(bits, fieldType);
+ result.readByteArray(params);
+ return result;
+ }
+}
diff --git a/src/cz/crcs/ectester/common/ec/EC_Data.java b/src/cz/crcs/ectester/common/ec/EC_Data.java
new file mode 100644
index 0000000..c048ef7
--- /dev/null
+++ b/src/cz/crcs/ectester/common/ec/EC_Data.java
@@ -0,0 +1,217 @@
+package cz.crcs.ectester.common.ec;
+
+import cz.crcs.ectester.common.util.ByteUtil;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.Pattern;
+
+/**
+ * A list of byte arrays for holding EC data.
+ * <p>
+ * The data can be read from a byte array via <code>readBytes()</code>, from a CSV via <code>readCSV()</code>.
+ * The data can be exported to a byte array via <code>flatten()</code> or to a string array via <code>expand()</code>.
+ *
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public abstract class EC_Data {
+ String id;
+ int count;
+ byte[][] data;
+
+ private static final Pattern HEX = Pattern.compile("(0x|0X)?[a-fA-F\\d]+");
+
+ EC_Data() {
+ }
+
+ EC_Data(int count) {
+ this.count = count;
+ this.data = new byte[count][];
+ }
+
+ EC_Data(byte[][] data) {
+ this.count = data.length;
+ this.data = data;
+ }
+
+ EC_Data(String id, int count) {
+ this(count);
+ this.id = id;
+ }
+
+ EC_Data(String id, byte[][] data) {
+ this(data);
+ this.id = id;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public byte[][] getData() {
+ return data;
+ }
+
+ public byte[] getData(int index) {
+ return data[index];
+ }
+
+ public boolean hasData() {
+ return data != null;
+ }
+
+ public byte[] flatten() {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ for (byte[] param : data) {
+ byte[] length = new byte[2];
+ ByteUtil.setShort(length, 0, (short) param.length);
+
+ out.write(length, 0, 2);
+ out.write(param, 0, param.length);
+ }
+
+ return out.toByteArray();
+ }
+
+ public String[] expand() {
+ List<String> out = new ArrayList<>(count);
+ for (byte[] param : data) {
+ out.add(ByteUtil.bytesToHex(param, false));
+ }
+
+ return out.toArray(new String[out.size()]);
+ }
+
+ private static byte[] pad(byte[] data) {
+ if (data.length == 1) {
+ return new byte[]{(byte) 0, data[0]};
+ } else if (data.length == 0 || data.length > 2) {
+ return data;
+ }
+ return null;
+ }
+
+ private static byte[] parse(String param) {
+ byte[] data;
+ if (param.startsWith("0x") || param.startsWith("0X")) {
+ data = ByteUtil.hexToBytes(param.substring(2));
+ } else {
+ data = ByteUtil.hexToBytes(param);
+ }
+ if (data == null)
+ return new byte[0];
+ if (data.length < 2)
+ return pad(data);
+ return data;
+ }
+
+ private boolean readHex(String[] hex) {
+ if (hex.length != count) {
+ return false;
+ }
+
+ for (int i = 0; i < count; ++i) {
+ this.data[i] = parse(hex[i]);
+ }
+ return true;
+ }
+
+ public boolean readCSV(InputStream in) {
+ Scanner s = new Scanner(in);
+
+ s.useDelimiter(",|;");
+ List<String> data = new LinkedList<>();
+ while (s.hasNext()) {
+ String field = s.next();
+ data.add(field.replaceAll("\\s+", ""));
+ }
+
+ if (data.isEmpty()) {
+ return false;
+ }
+ for (String param : data) {
+ if (!HEX.matcher(param).matches()) {
+ return false;
+ }
+ }
+ return readHex(data.toArray(new String[data.size()]));
+ }
+
+ public boolean readBytes(byte[] bytes) {
+ if (bytes == null) {
+ return false;
+ }
+
+ int offset = 0;
+ for (int i = 0; i < count; i++) {
+ if (bytes.length - offset < 2) {
+ return false;
+ }
+ short paramLength = ByteUtil.getShort(bytes, offset);
+ offset += 2;
+ if (bytes.length < offset + paramLength) {
+ return false;
+ }
+ data[i] = new byte[paramLength];
+ System.arraycopy(bytes, offset, data[i], 0, paramLength);
+ offset += paramLength;
+ }
+ return true;
+ }
+
+ public boolean readByteArray(byte[][] bytes) {
+ if (bytes == null || count != bytes.length) {
+ return false;
+ }
+
+ for (int i = 0; i < count; ++i) {
+ data[i] = new byte[bytes[i].length];
+ System.arraycopy(bytes[i], 0, data[i], 0, bytes[i].length);
+ }
+ return true;
+ }
+
+ public void writeCSV(OutputStream out) throws IOException {
+ Writer w = new OutputStreamWriter(out);
+ w.write(String.join(",", expand()));
+ w.flush();
+ }
+
+ @Override
+ public String toString() {
+ return String.join(",", expand());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof EC_Data) {
+ EC_Data other = (EC_Data) obj;
+ if (this.id != null || other.id != null) {
+ return Objects.equals(this.id, other.id);
+ }
+
+ if (this.count != other.count)
+ return false;
+ for (int i = 0; i < this.count; ++i) {
+ if (!Arrays.equals(this.data[i], other.data[i])) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ if (this.id != null) {
+ return this.id.hashCode();
+ }
+ return Arrays.deepHashCode(this.data);
+ }
+}
diff --git a/src/cz/crcs/ectester/common/ec/EC_KAResult.java b/src/cz/crcs/ectester/common/ec/EC_KAResult.java
new file mode 100644
index 0000000..8a5fcb4
--- /dev/null
+++ b/src/cz/crcs/ectester/common/ec/EC_KAResult.java
@@ -0,0 +1,65 @@
+package cz.crcs.ectester.common.ec;
+
+import cz.crcs.ectester.common.util.CardUtil;
+
+/**
+ * A result of EC based Key agreement operation.
+ *
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class EC_KAResult extends EC_Data {
+ private String ka;
+ private String curve;
+ private String oneKey;
+ private String otherKey;
+
+ private String desc;
+
+ public EC_KAResult(String ka, String curve, String oneKey, String otherKey) {
+ super(1);
+ this.ka = ka;
+ this.curve = curve;
+ this.oneKey = oneKey;
+ this.otherKey = otherKey;
+ }
+
+ public EC_KAResult(String id, String ka, String curve, String oneKey, String otherKey) {
+ this(ka, curve, oneKey, otherKey);
+ this.id = id;
+ }
+
+ public EC_KAResult(String id, String ka, String curve, String oneKey, String otherKey, String desc) {
+ this(id, ka, curve, oneKey, otherKey);
+ this.desc = desc;
+ }
+
+ public String getKA() {
+ return ka;
+ }
+
+ public byte getJavaCardKA() {
+ return CardUtil.getKA(ka);
+ }
+
+ public String getCurve() {
+ return curve;
+ }
+
+ public String getOneKey() {
+ return oneKey;
+ }
+
+ public String getOtherKey() {
+ return otherKey;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ @Override
+ public String toString() {
+ return "<" + getId() + "> " + ka + " result over " + curve + ", " + oneKey + " + " + otherKey + (desc == null ? "" : ": " + desc);
+ }
+
+}
diff --git a/src/cz/crcs/ectester/common/ec/EC_Key.java b/src/cz/crcs/ectester/common/ec/EC_Key.java
new file mode 100644
index 0000000..a34b0e7
--- /dev/null
+++ b/src/cz/crcs/ectester/common/ec/EC_Key.java
@@ -0,0 +1,83 @@
+package cz.crcs.ectester.common.ec;
+
+import cz.crcs.ectester.applet.EC_Consts;
+
+/**
+ * An abstract-like EC key. Concrete implementations create a public and private keys.
+ *
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class EC_Key extends EC_Params {
+
+ private String curve;
+ private String desc;
+
+ private EC_Key(short mask, String curve) {
+ super(mask);
+ this.curve = curve;
+ }
+
+ private EC_Key(short mask, String curve, String desc) {
+ this(mask, curve);
+ this.desc = desc;
+ }
+
+ private EC_Key(String id, short mask, String curve, String desc) {
+ this(mask, curve, desc);
+ this.id = id;
+ }
+
+ public String getCurve() {
+ return curve;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ /**
+ * An EC public key, contains the W parameter.
+ */
+ public static class Public extends EC_Key {
+
+ public Public(String curve) {
+ super(EC_Consts.PARAMETER_W, curve);
+ }
+
+ public Public(String curve, String desc) {
+ super(EC_Consts.PARAMETER_W, curve, desc);
+ }
+
+ public Public(String id, String curve, String desc) {
+ super(id, EC_Consts.PARAMETER_W, curve, desc);
+ }
+
+ @Override
+ public String toString() {
+ return "<" + getId() + "> EC Public key, over " + getCurve() + (getDesc() == null ? "" : ": " + getDesc());
+ }
+ }
+
+ /**
+ * An EC private key, contains the S parameter.
+ */
+ public static class Private extends EC_Key {
+
+ public Private(String curve) {
+ super(EC_Consts.PARAMETER_S, curve);
+ }
+
+ public Private(String curve, String desc) {
+ super(EC_Consts.PARAMETER_S, curve, desc);
+ }
+
+ public Private(String id, String curve, String desc) {
+ super(id, EC_Consts.PARAMETER_S, curve, desc);
+ }
+
+ @Override
+ public String toString() {
+ return "<" + getId() + "> EC Private key, over " + getCurve() + (getDesc() == null ? "" : ": " + getDesc());
+ }
+ }
+}
diff --git a/src/cz/crcs/ectester/common/ec/EC_Keypair.java b/src/cz/crcs/ectester/common/ec/EC_Keypair.java
new file mode 100644
index 0000000..53632cd
--- /dev/null
+++ b/src/cz/crcs/ectester/common/ec/EC_Keypair.java
@@ -0,0 +1,41 @@
+package cz.crcs.ectester.common.ec;
+
+import cz.crcs.ectester.applet.EC_Consts;
+
+/**
+ * An EC keypair, contains both the W and S parameters.
+ *
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class EC_Keypair extends EC_Params {
+ private String curve;
+ private String desc;
+
+ public EC_Keypair(String curve) {
+ super(EC_Consts.PARAMETERS_KEYPAIR);
+ this.curve = curve;
+ }
+
+ public EC_Keypair(String curve, String desc) {
+ this(curve);
+ this.desc = desc;
+ }
+
+ public EC_Keypair(String id, String curve, String desc) {
+ this(curve, desc);
+ this.id = id;
+ }
+
+ public String getCurve() {
+ return curve;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ @Override
+ public String toString() {
+ return "<" + getId() + "> EC Keypair, over " + curve + (desc == null ? "" : ": " + desc);
+ }
+}
diff --git a/src/cz/crcs/ectester/common/ec/EC_Params.java b/src/cz/crcs/ectester/common/ec/EC_Params.java
new file mode 100644
index 0000000..1c066e7
--- /dev/null
+++ b/src/cz/crcs/ectester/common/ec/EC_Params.java
@@ -0,0 +1,194 @@
+package cz.crcs.ectester.common.ec;
+
+import cz.crcs.ectester.applet.EC_Consts;
+import cz.crcs.ectester.common.util.ByteUtil;
+
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A list of EC parameters, can contain a subset of the Fp/F2M, A, B, G, R, K, W, S parameters.
+ *
+ * The set of parameters is uniquely identified by a short bit string.
+ * The parameters can be exported to a byte array via <code>flatten()</code> or to a comma delimited
+ * string via <code>expand()</code>.
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class EC_Params extends EC_Data {
+ private short params;
+
+ public EC_Params(short params) {
+ this.params = params;
+ this.count = numParams();
+ this.data = new byte[this.count][];
+ }
+
+ public EC_Params(short params, byte[][] data) {
+ this.params = params;
+ this.count = data.length;
+ this.data = data;
+ }
+
+ public EC_Params(String id, short params) {
+ this(params);
+ this.id = id;
+ }
+
+ public EC_Params(String id, short params, byte[][] data) {
+ this(params, data);
+ this.id = id;
+ }
+
+ public short getParams() {
+ return params;
+ }
+
+ public byte[][] getParam(short param) {
+ if (!hasParam(param)) {
+ return null;
+ }
+ if (Integer.bitCount(param) != 1) {
+ return null;
+ }
+ short paramMask = EC_Consts.PARAMETER_FP;
+ byte[][] result = null;
+ int i = 0;
+ while (paramMask <= EC_Consts.PARAMETER_S) {
+ short masked = (short) (this.params & param & paramMask);
+ short shallow = (short) (this.params & paramMask);
+ if (masked != 0) {
+ if (masked == EC_Consts.PARAMETER_F2M) {
+ result = new byte[4][];
+ result[0] = data[i].clone();
+ result[1] = data[i+1].clone();
+ result[2] = data[i+2].clone();
+ result[3] = data[i+3].clone();
+ break;
+ }
+ if (masked == EC_Consts.PARAMETER_G || masked == EC_Consts.PARAMETER_W) {
+ result = new byte[2][];
+ result[0] = data[i].clone();
+ result[1] = data[i+1].clone();
+ break;
+ }
+ result = new byte[1][];
+ result[0] = data[i].clone();
+ }
+ if (shallow == EC_Consts.PARAMETER_F2M) {
+ i += 4;
+ } else if (shallow == EC_Consts.PARAMETER_G || shallow == EC_Consts.PARAMETER_W) {
+ i += 2;
+ } else if (shallow != 0) {
+ i++;
+ }
+ paramMask = (short) (paramMask << 1);
+ }
+ return result;
+ }
+
+ public boolean hasParam(short param) {
+ return (params & param) != 0;
+ }
+
+ public int numParams() {
+ short paramMask = EC_Consts.PARAMETER_FP;
+ int num = 0;
+ while (paramMask <= EC_Consts.PARAMETER_S) {
+ if ((paramMask & params) != 0) {
+ if (paramMask == EC_Consts.PARAMETER_F2M) {
+ num += 3;
+ }
+ if (paramMask == EC_Consts.PARAMETER_W || paramMask == EC_Consts.PARAMETER_G) {
+ num += 1;
+ }
+ ++num;
+ }
+ paramMask = (short) (paramMask << 1);
+ }
+ return num;
+ }
+
+ @Override
+ public byte[] flatten() {
+ return flatten(params);
+ }
+
+ public byte[] flatten(short params) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ short paramMask = EC_Consts.PARAMETER_FP;
+ int i = 0;
+ while (paramMask <= EC_Consts.PARAMETER_S) {
+ short masked = (short) (this.params & params & paramMask);
+ short shallow = (short) (this.params & paramMask);
+ if (masked != 0) {
+ byte[] param = data[i];
+ if (masked == EC_Consts.PARAMETER_F2M) {
+ //add m, e_1, e_2, e_3
+ param = ByteUtil.concatenate(param, data[i + 1]);
+ if (!ByteUtil.allValue(data[i + 2], (byte) 0)) {
+ param = ByteUtil.concatenate(param, data[i + 2]);
+ }
+ if (!ByteUtil.allValue(data[i + 3], (byte) 0)) {
+ param = ByteUtil.concatenate(param, data[i + 3]);
+ }
+ if (!(param.length == 4 || param.length == 8))
+ throw new RuntimeException("PARAMETER_F2M length is not 8.(should be)");
+ }
+ if (masked == EC_Consts.PARAMETER_G || masked == EC_Consts.PARAMETER_W) {
+ //read another param (the y coord) and put into X962 format.
+ byte[] y = data[i + 1];
+ param = ByteUtil.concatenate(new byte[]{4}, param, y); //<- ugly but works!
+ }
+ if (param.length == 0)
+ throw new RuntimeException("Empty parameter read?");
+
+ //write length
+ byte[] length = new byte[2];
+ ByteUtil.setShort(length, 0, (short) param.length);
+ out.write(length, 0, 2);
+ //write data
+ out.write(param, 0, param.length);
+ }
+ if (shallow == EC_Consts.PARAMETER_F2M) {
+ i += 4;
+ } else if (shallow == EC_Consts.PARAMETER_G || shallow == EC_Consts.PARAMETER_W) {
+ i += 2;
+ } else if (shallow != 0) {
+ i++;
+ }
+ paramMask = (short) (paramMask << 1);
+ }
+
+ return (out.size() == 0) ? null : out.toByteArray();
+ }
+
+ @Override
+ public String[] expand() {
+ List<String> out = new ArrayList<>();
+
+ short paramMask = EC_Consts.PARAMETER_FP;
+ int index = 0;
+ while (paramMask <= EC_Consts.PARAMETER_S) {
+ short masked = (short) (params & paramMask);
+ if (masked != 0) {
+ byte[] param = data[index];
+ if (masked == EC_Consts.PARAMETER_F2M) {
+ for (int i = 0; i < 4; ++i) {
+ out.add(ByteUtil.bytesToHex(data[index + i], false));
+ }
+ index += 4;
+ } else if (masked == EC_Consts.PARAMETER_G || masked == EC_Consts.PARAMETER_W) {
+ out.add(ByteUtil.bytesToHex(param, false));
+ out.add(ByteUtil.bytesToHex(data[index + 1], false));
+ index += 2;
+ } else {
+ out.add(ByteUtil.bytesToHex(param, false));
+ index++;
+ }
+ }
+ paramMask = (short) (paramMask << 1);
+ }
+ return out.toArray(new String[out.size()]);
+ }
+}
diff --git a/src/cz/crcs/ectester/common/output/BaseTextTestWriter.java b/src/cz/crcs/ectester/common/output/BaseTextTestWriter.java
new file mode 100644
index 0000000..29eb671
--- /dev/null
+++ b/src/cz/crcs/ectester/common/output/BaseTextTestWriter.java
@@ -0,0 +1,82 @@
+package cz.crcs.ectester.common.output;
+
+import cz.crcs.ectester.common.test.*;
+
+import java.io.PrintStream;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public abstract class BaseTextTestWriter implements TestWriter {
+ private PrintStream output;
+
+ public static int BASE_WIDTH = 90;
+
+ public BaseTextTestWriter(PrintStream output) {
+ this.output = output;
+ }
+
+ @Override
+ public void begin(TestSuite suite) {
+ output.println("═══ Running test suite: " + suite.getName() + " ═══");
+ output.println("═══ " + suite.getDescription());
+ output.print(deviceString(suite));
+ }
+
+ protected abstract String testableString(Testable t);
+
+ protected abstract String deviceString(TestSuite suite);
+
+ private String testString(Test t, String prefix) {
+ if (!t.hasRun()) {
+ return null;
+ }
+ boolean compound = t instanceof CompoundTest;
+
+ StringBuilder out = new StringBuilder();
+ out.append(t.ok() ? " OK " : "NOK ");
+ out.append(compound ? "┳ " : "━ ");
+ int width = BASE_WIDTH - (prefix.length() + out.length());
+ String widthSpec = "%-" + String.valueOf(width) + "s";
+ out.append(String.format(widthSpec, t.getDescription()));
+ out.append(" ┃ ");
+ out.append(String.format("%-9s", t.getResultValue().name()));
+ out.append(" ┃ ");
+
+ if (compound) {
+ CompoundTest test = (CompoundTest) t;
+ out.append(test.getResultCause());
+ out.append(System.lineSeparator());
+ Test[] tests = test.getTests();
+ for (int i = 0; i < tests.length; ++i) {
+ if (i == tests.length - 1) {
+ out.append(prefix).append(" ┗ ");
+ out.append(testString(tests[i], prefix + " "));
+ } else {
+ out.append(prefix).append(" ┣ ");
+ out.append(testString(tests[i], prefix + " ┃ "));
+ }
+
+ if (i != tests.length - 1) {
+ out.append(System.lineSeparator());
+ }
+ }
+ } else {
+ SimpleTest test = (SimpleTest) t;
+ out.append(testableString(test.getTestable()));
+ }
+ return out.toString();
+ }
+
+ @Override
+ public void outputTest(Test t) {
+ if (!t.hasRun())
+ return;
+ output.println(testString(t, ""));
+ output.flush();
+ }
+
+ @Override
+ public void end() {
+ }
+}
diff --git a/src/cz/crcs/ectester/common/output/BaseXMLTestWriter.java b/src/cz/crcs/ectester/common/output/BaseXMLTestWriter.java
new file mode 100644
index 0000000..f3e9411
--- /dev/null
+++ b/src/cz/crcs/ectester/common/output/BaseXMLTestWriter.java
@@ -0,0 +1,103 @@
+package cz.crcs.ectester.common.output;
+
+import cz.crcs.ectester.common.test.*;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.io.OutputStream;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public abstract class BaseXMLTestWriter implements TestWriter {
+ private OutputStream output;
+ private DocumentBuilder db;
+ protected Document doc;
+ private Node root;
+
+ public BaseXMLTestWriter(OutputStream output) throws ParserConfigurationException {
+ this.output = output;
+ this.db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+ }
+
+ @Override
+ public void begin(TestSuite suite) {
+ doc = db.newDocument();
+ Element rootElem = doc.createElement("testSuite");
+ rootElem.setAttribute("name", suite.getName());
+ rootElem.setAttribute("desc", suite.getDescription());
+
+ root = rootElem;
+ doc.appendChild(root);
+ root.appendChild(deviceElement(suite));
+ }
+
+ protected abstract Element testableElement(Testable t);
+
+ protected abstract Element deviceElement(TestSuite suite);
+
+ private Element testElement(Test t) {
+ Element testElem;
+ if (t instanceof CompoundTest) {
+ CompoundTest test = (CompoundTest) t;
+ testElem = doc.createElement("test");
+ testElem.setAttribute("type", "compound");
+ for (Test innerTest : test.getTests()) {
+ testElem.appendChild(testElement(innerTest));
+ }
+ } else {
+ SimpleTest test = (SimpleTest) t;
+ testElem = testableElement(test.getTestable());
+ }
+
+ Element description = doc.createElement("desc");
+ description.setTextContent(t.getDescription());
+ testElem.appendChild(description);
+
+ Element result = doc.createElement("result");
+ Element ok = doc.createElement("ok");
+ ok.setTextContent(String.valueOf(t.ok()));
+ Element value = doc.createElement("value");
+ value.setTextContent(t.getResultValue().name());
+ Element cause = doc.createElement("cause");
+ cause.setTextContent(t.getResultCause());
+ result.appendChild(ok);
+ result.appendChild(value);
+ result.appendChild(cause);
+ testElem.appendChild(result);
+
+ return testElem;
+ }
+
+ @Override
+ public void outputTest(Test t) {
+ if (!t.hasRun())
+ return;
+ root.appendChild(testElement(t));
+ }
+
+ @Override
+ public void end() {
+ try {
+ DOMSource domSource = new DOMSource(doc);
+ StreamResult result = new StreamResult(output);
+ TransformerFactory tf = TransformerFactory.newInstance();
+ Transformer transformer = tf.newTransformer();
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+ transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
+ transformer.transform(domSource, result);
+ } catch (TransformerException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/cz/crcs/ectester/common/output/BaseYAMLTestWriter.java b/src/cz/crcs/ectester/common/output/BaseYAMLTestWriter.java
new file mode 100644
index 0000000..0769e83
--- /dev/null
+++ b/src/cz/crcs/ectester/common/output/BaseYAMLTestWriter.java
@@ -0,0 +1,91 @@
+package cz.crcs.ectester.common.output;
+
+import cz.crcs.ectester.common.test.*;
+import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.Yaml;
+
+import java.io.PrintStream;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public abstract class BaseYAMLTestWriter implements TestWriter {
+ private PrintStream output;
+ private Map<String, Object> testRun;
+ private Map<String, String> testSuite;
+ protected List<Object> tests;
+
+ public BaseYAMLTestWriter(PrintStream output) {
+ this.output = output;
+ }
+
+ @Override
+ public void begin(TestSuite suite) {
+ output.println("---");
+ testRun = new HashMap<>();
+ testSuite = new HashMap<>();
+ tests = new LinkedList<>();
+ testSuite.put("name", suite.getName());
+ testSuite.put("desc", suite.getDescription());
+
+ testRun.put("suite", testSuite);
+ testRun.put("device", deviceObject(suite));
+ testRun.put("tests", tests);
+ }
+
+ abstract protected Map<String, Object> testableObject(Testable t);
+
+ abstract protected Map<String, Object> deviceObject(TestSuite suite);
+
+ private Map<String, Object> testObject(Test t) {
+ Map<String, Object> testObj;
+ if (t instanceof CompoundTest) {
+ CompoundTest test = (CompoundTest) t;
+ testObj = new HashMap<>();
+ testObj.put("type", "compound");
+ List<Map<String, Object>> innerTests = new LinkedList<>();
+ for (Test innerTest : test.getTests()) {
+ innerTests.add(testObject(innerTest));
+ }
+ testObj.put("tests", innerTests);
+ } else {
+ SimpleTest test = (SimpleTest) t;
+ testObj = testableObject(test.getTestable());
+ }
+
+ testObj.put("desc", t.getDescription());
+ Map<String, Object> result = new HashMap<>();
+ result.put("ok", t.ok());
+ result.put("value", t.getResultValue().name());
+ result.put("cause", t.getResultCause());
+ testObj.put("result", result);
+
+ return testObj;
+ }
+
+ @Override
+ public void outputTest(Test t) {
+ if (!t.hasRun())
+ return;
+ tests.add(testObject(t));
+ }
+
+ @Override
+ public void end() {
+ DumperOptions options = new DumperOptions();
+ options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
+ options.setPrettyFlow(true);
+ Yaml yaml = new Yaml(options);
+
+ Map<String, Object> result = new HashMap<>();
+ result.put("testRun", testRun);
+ String out = yaml.dump(result);
+
+ output.println(out);
+ output.println("---");
+ }
+}
diff --git a/src/cz/crcs/ectester/common/output/OutputLogger.java b/src/cz/crcs/ectester/common/output/OutputLogger.java
new file mode 100644
index 0000000..09b8f73
--- /dev/null
+++ b/src/cz/crcs/ectester/common/output/OutputLogger.java
@@ -0,0 +1,60 @@
+package cz.crcs.ectester.common.output;
+
+import java.io.*;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author Petr Svenda petr@svenda.com
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class OutputLogger {
+ private OutputStream out;
+ private PrintStream print;
+
+ public OutputLogger(boolean systemOut, String... filePaths) throws IOException {
+ List<OutputStream> streams = new LinkedList<>();
+ for (String filePath : filePaths) {
+ if (filePath != null) {
+ streams.add(new FileOutputStream(filePath));
+ }
+ }
+ if (systemOut) {
+ streams.add(System.out);
+ }
+ this.out = new TeeOutputStream(streams.toArray(new OutputStream[0]));
+ this.print = new PrintStream(this.out);
+ }
+
+ public OutputLogger(String filePath) throws IOException {
+ this(true, filePath);
+ }
+
+ public OutputStream getOutputStream() {
+ return this.out;
+ }
+
+ public PrintStream getPrintStream() {
+ return this.print;
+ }
+
+ public void println() {
+ print.println();
+ }
+
+ public void println(String logLine) {
+ print.println(logLine);
+ }
+
+ public void print(String logLine) {
+ print.print(logLine);
+ }
+
+ public void flush() {
+ print.flush();
+ }
+
+ public void close() {
+ print.close();
+ }
+}
diff --git a/src/cz/crcs/ectester/common/output/TeeOutputStream.java b/src/cz/crcs/ectester/common/output/TeeOutputStream.java
new file mode 100644
index 0000000..2401fce
--- /dev/null
+++ b/src/cz/crcs/ectester/common/output/TeeOutputStream.java
@@ -0,0 +1,36 @@
+package cz.crcs.ectester.common.output;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class TeeOutputStream extends OutputStream {
+ private OutputStream[] outputs;
+
+ public TeeOutputStream(OutputStream... outputs) {
+ this.outputs = outputs;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ for (OutputStream out : outputs) {
+ out.write(b);
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ for (OutputStream out : outputs) {
+ out.flush();
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ for (OutputStream out : outputs) {
+ out.close();
+ }
+ }
+}
diff --git a/src/cz/crcs/ectester/common/output/TestWriter.java b/src/cz/crcs/ectester/common/output/TestWriter.java
new file mode 100644
index 0000000..0ecfd5a
--- /dev/null
+++ b/src/cz/crcs/ectester/common/output/TestWriter.java
@@ -0,0 +1,15 @@
+package cz.crcs.ectester.common.output;
+
+import cz.crcs.ectester.common.test.Test;
+import cz.crcs.ectester.common.test.TestSuite;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public interface TestWriter {
+ void begin(TestSuite suite);
+
+ void outputTest(Test t);
+
+ void end();
+}
diff --git a/src/cz/crcs/ectester/common/test/BaseTestable.java b/src/cz/crcs/ectester/common/test/BaseTestable.java
new file mode 100644
index 0000000..a4b9a00
--- /dev/null
+++ b/src/cz/crcs/ectester/common/test/BaseTestable.java
@@ -0,0 +1,32 @@
+package cz.crcs.ectester.common.test;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public abstract class BaseTestable implements Testable {
+ protected boolean hasRun;
+ protected boolean ok;
+ protected boolean error;
+
+ @Override
+ public boolean hasRun() {
+ return hasRun;
+ }
+
+ @Override
+ public boolean ok() {
+ return ok;
+ }
+
+ @Override
+ public boolean error() {
+ return error;
+ }
+
+ @Override
+ public void reset() {
+ hasRun = false;
+ ok = false;
+ error = false;
+ }
+}
diff --git a/src/cz/crcs/ectester/common/test/CompoundTest.java b/src/cz/crcs/ectester/common/test/CompoundTest.java
new file mode 100644
index 0000000..10ecf9c
--- /dev/null
+++ b/src/cz/crcs/ectester/common/test/CompoundTest.java
@@ -0,0 +1,111 @@
+package cz.crcs.ectester.common.test;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * A compound test that runs many Tests and has a Result dependent on all/some of their Results.
+ *
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class CompoundTest extends Test {
+ private Function<Test[], Result> callback;
+ private Test[] tests;
+ private String description;
+
+ private CompoundTest(Function<Test[], Result> callback, Test... tests) {
+ this.callback = callback;
+ this.tests = Arrays.stream(tests).filter(Objects::nonNull).toArray(Test[]::new);
+ }
+
+ private CompoundTest(Function<Test[], Result> callback, String descripiton, Test... tests) {
+ this(callback, tests);
+ this.description = descripiton;
+ }
+
+ public static CompoundTest function(Function<Test[], Result> callback, Test... tests) {
+ return new CompoundTest(callback, tests);
+ }
+
+ public static CompoundTest function(Function<Test[], Result> callback, String description, Test... tests) {
+ return new CompoundTest(callback, description, tests);
+ }
+
+ public static CompoundTest all(Result.ExpectedValue what, Test... all) {
+ return new CompoundTest((tests) -> {
+ for (Test test : tests) {
+ if (!Result.Value.fromExpected(what, test.ok()).ok()) {
+ return new Result(Result.Value.FAILURE, "Some sub-tests did not have the expected result.");
+ }
+ }
+ return new Result(Result.Value.SUCCESS, "All sub-tests had the expected result.");
+ }, all);
+ }
+
+ public static CompoundTest all(Result.ExpectedValue what, String description, Test... all) {
+ CompoundTest result = CompoundTest.all(what, all);
+ result.setDescription(description);
+ return result;
+ }
+
+ public static CompoundTest any(Result.ExpectedValue what, Test... any) {
+ return new CompoundTest((tests) -> {
+ for (Test test : tests) {
+ if (Result.Value.fromExpected(what, test.ok()).ok()) {
+ return new Result(Result.Value.SUCCESS, "Some sub-tests did have the expected result.");
+ }
+ }
+ return new Result(Result.Value.FAILURE, "None of the sub-tests had the expected result.");
+ }, any);
+ }
+
+ public static CompoundTest any(Result.ExpectedValue what, String description, Test... any) {
+ CompoundTest result = CompoundTest.any(what, any);
+ result.setDescription(description);
+ return result;
+ }
+
+ public static CompoundTest mask(Result.ExpectedValue[] results, Test... masked) {
+ return new CompoundTest((tests) -> {
+ for (int i = 0; i < results.length; ++i) {
+ if (!Result.Value.fromExpected(results[i], tests[i].ok()).ok()) {
+ return new Result(Result.Value.FAILURE, "Some sub-tests did not match the result mask.");
+ }
+ }
+ return new Result(Result.Value.SUCCESS, "All sub-tests matched the expected mask.");
+ }, masked);
+ }
+
+ public static CompoundTest mask(Result.ExpectedValue[] results, String description, Test... masked) {
+ CompoundTest result = CompoundTest.mask(results, masked);
+ result.setDescription(description);
+ return result;
+ }
+
+ public Test[] getTests() {
+ return tests;
+ }
+
+ @Override
+ public void run() throws TestException {
+ if (hasRun)
+ return;
+
+ for (Test test : tests) {
+ test.run();
+ }
+
+ result = callback.apply(tests);
+ this.hasRun = true;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public String getDescription() {
+ return description;
+ }
+}
diff --git a/src/cz/crcs/ectester/common/test/Result.java b/src/cz/crcs/ectester/common/test/Result.java
new file mode 100644
index 0000000..11fcb4d
--- /dev/null
+++ b/src/cz/crcs/ectester/common/test/Result.java
@@ -0,0 +1,96 @@
+package cz.crcs.ectester.common.test;
+
+/**
+ * A Result of a Test. Has a Value and an optional String cause.
+ *
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class Result {
+
+ private Value value;
+ private String cause;
+
+ public Result(Value value) {
+ this.value = value;
+ }
+
+ public Result(Value value, String cause) {
+ this(value);
+ this.cause = cause;
+ }
+
+ public Value getValue() {
+ return value;
+ }
+
+ public String getCause() {
+ return cause;
+ }
+
+ public boolean ok() {
+ return value.ok();
+ }
+
+ public boolean compareTo(Result other) {
+ if (other == null) {
+ return false;
+ }
+ return value == other.value;
+ }
+
+ public boolean compareTo(Value other) {
+ if (other == null) {
+ return false;
+ }
+ return value == other;
+ }
+
+ /**
+ * A result value of a Test.
+ */
+ public enum Value {
+ SUCCESS(true),
+ FAILURE(false),
+ UXSUCCESS(false),
+ XFAILURE(true),
+ ERROR(false);
+
+ private boolean ok;
+
+ Value(boolean ok) {
+ this.ok = ok;
+ }
+
+ public static Value fromExpected(ExpectedValue expected, boolean successful) {
+ switch (expected) {
+ case SUCCESS:
+ return successful ? SUCCESS : FAILURE;
+ case FAILURE:
+ return successful ? UXSUCCESS : XFAILURE;
+ case ANY:
+ return SUCCESS;
+ }
+ return SUCCESS;
+ }
+
+ public static Value fromExpected(ExpectedValue expected, boolean successful, boolean error) {
+ if (error) {
+ return ERROR;
+ }
+ return fromExpected(expected, successful);
+ }
+
+ public boolean ok() {
+ return ok;
+ }
+ }
+
+ /**
+ * A possible expected value result of a Test.
+ */
+ public enum ExpectedValue {
+ SUCCESS,
+ FAILURE,
+ ANY
+ }
+}
diff --git a/src/cz/crcs/ectester/common/test/SimpleTest.java b/src/cz/crcs/ectester/common/test/SimpleTest.java
new file mode 100644
index 0000000..f68320a
--- /dev/null
+++ b/src/cz/crcs/ectester/common/test/SimpleTest.java
@@ -0,0 +1,19 @@
+package cz.crcs.ectester.common.test;
+
+/**
+ * @param <T>
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public abstract class SimpleTest<T extends BaseTestable> extends Test {
+ protected T testable;
+ protected TestCallback<T> callback;
+
+ public SimpleTest(T testable, TestCallback<T> callback) {
+ this.testable = testable;
+ this.callback = callback;
+ }
+
+ public T getTestable() {
+ return testable;
+ }
+}
diff --git a/src/cz/crcs/ectester/common/test/Test.java b/src/cz/crcs/ectester/common/test/Test.java
new file mode 100644
index 0000000..3d0baf6
--- /dev/null
+++ b/src/cz/crcs/ectester/common/test/Test.java
@@ -0,0 +1,66 @@
+package cz.crcs.ectester.common.test;
+
+import static cz.crcs.ectester.common.test.Result.Value;
+
+/**
+ * An abstract test that can be run and has a Result.
+ *
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public abstract class Test implements Testable {
+ protected boolean hasRun;
+ protected Result result;
+
+ public Result getResult() {
+ if (!hasRun) {
+ return null;
+ }
+ return result;
+ }
+
+ public Value getResultValue() {
+ if (!hasRun) {
+ return null;
+ }
+ return result.getValue();
+ }
+
+ public String getResultCause() {
+ if (!hasRun) {
+ return null;
+ }
+ return result.getCause();
+ }
+
+ public boolean ok() {
+ if (!hasRun) {
+ return true;
+ }
+ return result.ok();
+ }
+
+ @Override
+ public boolean error() {
+ if (!hasRun) {
+ return false;
+ }
+ return result.compareTo(Value.ERROR);
+ }
+
+ @Override
+ public boolean hasRun() {
+ return hasRun;
+ }
+
+ @Override
+ public void reset() {
+ hasRun = false;
+ result = null;
+ }
+
+ public abstract String getDescription();
+
+ @Override
+ public abstract void run() throws TestException;
+
+}
diff --git a/src/cz/crcs/ectester/common/test/TestCallback.java b/src/cz/crcs/ectester/common/test/TestCallback.java
new file mode 100644
index 0000000..ce6000b
--- /dev/null
+++ b/src/cz/crcs/ectester/common/test/TestCallback.java
@@ -0,0 +1,12 @@
+package cz.crcs.ectester.common.test;
+
+import java.util.function.Function;
+
+/**
+ *
+ * @author Jan Jancar johny@neuromancer.sk
+ * @param <T>
+ */
+public abstract class TestCallback<T extends Testable> implements Function<T, Result> {
+
+}
diff --git a/src/cz/crcs/ectester/common/test/TestException.java b/src/cz/crcs/ectester/common/test/TestException.java
new file mode 100644
index 0000000..008e9f6
--- /dev/null
+++ b/src/cz/crcs/ectester/common/test/TestException.java
@@ -0,0 +1,13 @@
+package cz.crcs.ectester.common.test;
+
+/**
+ * A TestException is an Exception that can be thrown during the running of a Testable,
+ * or a TestSuite. It means that the Testable/TestSuite encountered an unexpected error
+ * during it's run which points to an error in ECTester or it's runtime environment.cd
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class TestException extends Exception {
+ public TestException(Throwable e) {
+ super(e);
+ }
+}
diff --git a/src/cz/crcs/ectester/common/test/TestSuite.java b/src/cz/crcs/ectester/common/test/TestSuite.java
new file mode 100644
index 0000000..f4f30ee
--- /dev/null
+++ b/src/cz/crcs/ectester/common/test/TestSuite.java
@@ -0,0 +1,56 @@
+package cz.crcs.ectester.common.test;
+
+import cz.crcs.ectester.common.output.TestWriter;
+import cz.crcs.ectester.data.EC_Store;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public abstract class TestSuite {
+ protected String name;
+ protected String description;
+ protected TestWriter writer;
+
+ public TestSuite(TestWriter writer, String name, String description) {
+ this.writer = writer;
+ this.name = name;
+ this.description = description;
+ }
+
+ public void run() throws TestException {
+ writer.begin(this);
+ try {
+ runTests();
+ } catch (Exception e) {
+ throw new TestException(e);
+ }
+ writer.end();
+ }
+
+ protected Test runTest(Test t) throws TestException {
+ t.run();
+ return t;
+ }
+
+ protected Test doTest(Test t) throws TestException {
+ t.run();
+ writer.outputTest(t);
+ return t;
+ }
+
+ protected abstract void runTests() throws Exception;
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+}
diff --git a/src/cz/crcs/ectester/common/test/Testable.java b/src/cz/crcs/ectester/common/test/Testable.java
new file mode 100644
index 0000000..33c9485
--- /dev/null
+++ b/src/cz/crcs/ectester/common/test/Testable.java
@@ -0,0 +1,33 @@
+package cz.crcs.ectester.common.test;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public interface Testable {
+ /**
+ * @return Whether this Testable was OK.
+ */
+ boolean ok();
+
+ /**
+ * @return Whether an error happened.
+ */
+ boolean error();
+
+ /**
+ * @return Whether this runnable was run.
+ */
+ boolean hasRun();
+
+ /**
+ *
+ */
+ void reset();
+
+ /**
+ * Run this Runnable.
+ *
+ * @throws TestException If an unexpected exception/error is encountered.
+ */
+ void run() throws TestException;
+}
diff --git a/src/cz/crcs/ectester/common/util/ByteUtil.java b/src/cz/crcs/ectester/common/util/ByteUtil.java
new file mode 100644
index 0000000..90c6eaa
--- /dev/null
+++ b/src/cz/crcs/ectester/common/util/ByteUtil.java
@@ -0,0 +1,128 @@
+package cz.crcs.ectester.common.util;
+
+/**
+ * Utility class, some byte/hex manipulation, convenient byte[] methods.
+ *
+ * @author Petr Svenda petr@svenda.com
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class ByteUtil {
+ public static short getShort(byte[] array, int offset) {
+ return (short) (((array[offset] & 0xFF) << 8) | (array[offset + 1] & 0xFF));
+ }
+
+ public static void setShort(byte[] array, int offset, short value) {
+ array[offset + 1] = (byte) (value & 0xFF);
+ array[offset] = (byte) ((value >> 8) & 0xFF);
+ }
+
+ public static int diffBytes(byte[] one, int oneOffset, byte[] other, int otherOffset, int length) {
+ for (int i = 0; i < length; ++i) {
+ byte a = one[i + oneOffset];
+ byte b = other[i + otherOffset];
+ if (a != b) {
+ return i;
+ }
+ }
+ return length;
+ }
+
+ public static boolean compareBytes(byte[] one, int oneOffset, byte[] other, int otherOffset, int length) {
+ return diffBytes(one, oneOffset, other, otherOffset, length) == length;
+ }
+
+ public static boolean allValue(byte[] array, byte value) {
+ for (byte a : array) {
+ if (a != value)
+ return false;
+ }
+ return true;
+ }
+
+ public static byte[] hexToBytes(String hex) {
+ return hexToBytes(hex, true);
+ }
+
+ public static byte[] hexToBytes(String hex, boolean bigEndian) {
+ hex = hex.replace(" ", "");
+ int len = hex.length();
+ StringBuilder sb = new StringBuilder();
+
+ if (len % 2 == 1) {
+ sb.append("0");
+ ++len;
+ }
+
+ if (bigEndian) {
+ sb.append(hex);
+ } else {
+ for (int i = 0; i < len / 2; ++i) {
+ if (sb.length() >= 2) {
+ sb.insert(sb.length() - 2, hex.substring(2 * i, 2 * i + 2));
+ } else {
+ sb.append(hex.substring(2 * i, 2 * i + 2));
+ }
+
+ }
+ }
+
+ String data = sb.toString();
+ byte[] result = new byte[len / 2];
+ for (int i = 0; i < len; i += 2) {
+ result[i / 2] = (byte) ((Character.digit(data.charAt(i), 16) << 4)
+ + (Character.digit(data.charAt(i + 1), 16)));
+ }
+ return result;
+ }
+
+ public static String byteToHex(byte data) {
+ return String.format("%02x", data);
+ }
+
+ public static String bytesToHex(byte[] data) {
+ return bytesToHex(data, true);
+ }
+
+ public static String bytesToHex(byte[] data, boolean addSpace) {
+ if (data == null) {
+ return "";
+ }
+ return bytesToHex(data, 0, data.length, addSpace);
+ }
+
+ public static String bytesToHex(byte[] data, int offset, int len) {
+ return bytesToHex(data, offset, len, true);
+ }
+
+ public static String bytesToHex(byte[] data, int offset, int len, boolean addSpace) {
+ if (data == null) {
+ return "";
+ }
+ StringBuilder buf = new StringBuilder();
+ for (int i = offset; i < (offset + len); i++) {
+ buf.append(byteToHex(data[i]));
+ if (addSpace && i != (offset + len - 1)) {
+ buf.append(" ");
+ }
+ }
+ return (buf.toString());
+ }
+
+ public static byte[] concatenate(byte[]... arrays) {
+ int len = 0;
+ for (byte[] array : arrays) {
+ if (array == null)
+ continue;
+ len += array.length;
+ }
+ byte[] out = new byte[len];
+ int offset = 0;
+ for (byte[] array : arrays) {
+ if (array == null || array.length == 0)
+ continue;
+ System.arraycopy(array, 0, out, offset, array.length);
+ offset += array.length;
+ }
+ return out;
+ }
+}
diff --git a/src/cz/crcs/ectester/common/util/CardUtil.java b/src/cz/crcs/ectester/common/util/CardUtil.java
new file mode 100644
index 0000000..8285d8b
--- /dev/null
+++ b/src/cz/crcs/ectester/common/util/CardUtil.java
@@ -0,0 +1,272 @@
+package cz.crcs.ectester.common.util;
+
+import cz.crcs.ectester.applet.ECTesterApplet;
+import cz.crcs.ectester.applet.EC_Consts;
+import javacard.framework.ISO7816;
+import javacard.security.CryptoException;
+
+import static cz.crcs.ectester.applet.ECTesterApplet.*;
+
+/**
+ * @author Petr Svenda petr@svenda.com
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class CardUtil {
+ public static byte getKA(String name) {
+ switch (name) {
+ case "DH":
+ case "ECDH":
+ return ECTesterApplet.KeyAgreement_ALG_EC_SVDP_DH;
+ case "DHC":
+ case "ECDHC":
+ return ECTesterApplet.KeyAgreement_ALG_EC_SVDP_DHC;
+ default:
+ return ECTesterApplet.KeyAgreement_ALG_EC_SVDP_DH;
+ }
+ }
+
+ public static String getSWSource(short sw) {
+ switch (sw) {
+ case ISO7816.SW_NO_ERROR:
+ case ISO7816.SW_APPLET_SELECT_FAILED:
+ case ISO7816.SW_BYTES_REMAINING_00:
+ case ISO7816.SW_CLA_NOT_SUPPORTED:
+ case ISO7816.SW_COMMAND_NOT_ALLOWED:
+ case ISO7816.SW_CONDITIONS_NOT_SATISFIED:
+ case ISO7816.SW_CORRECT_LENGTH_00:
+ case ISO7816.SW_DATA_INVALID:
+ case ISO7816.SW_FILE_FULL:
+ case ISO7816.SW_FILE_INVALID:
+ case ISO7816.SW_FILE_NOT_FOUND:
+ case ISO7816.SW_FUNC_NOT_SUPPORTED:
+ case ISO7816.SW_INCORRECT_P1P2:
+ case ISO7816.SW_INS_NOT_SUPPORTED:
+ case ISO7816.SW_LOGICAL_CHANNEL_NOT_SUPPORTED:
+ case ISO7816.SW_RECORD_NOT_FOUND:
+ case ISO7816.SW_SECURE_MESSAGING_NOT_SUPPORTED:
+ case ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED:
+ case ISO7816.SW_UNKNOWN:
+ case ISO7816.SW_WARNING_STATE_UNCHANGED:
+ case ISO7816.SW_WRONG_DATA:
+ case ISO7816.SW_WRONG_LENGTH:
+ case ISO7816.SW_WRONG_P1P2:
+ return "ISO";
+ case CryptoException.ILLEGAL_VALUE:
+ case CryptoException.UNINITIALIZED_KEY:
+ case CryptoException.NO_SUCH_ALGORITHM:
+ case CryptoException.INVALID_INIT:
+ case CryptoException.ILLEGAL_USE:
+ return "CryptoException";
+ case ECTesterApplet.SW_SIG_VERIFY_FAIL:
+ case ECTesterApplet.SW_DH_DHC_MISMATCH:
+ case ECTesterApplet.SW_KEYPAIR_NULL:
+ case ECTesterApplet.SW_KA_NULL:
+ case ECTesterApplet.SW_SIGNATURE_NULL:
+ case ECTesterApplet.SW_OBJECT_NULL:
+ return "ECTesterApplet";
+ default:
+ return "?";
+ }
+ }
+
+ public static String getSW(short sw) {
+ switch (sw) {
+ case ISO7816.SW_APPLET_SELECT_FAILED:
+ return "APPLET_SELECT_FAILED";
+ case ISO7816.SW_BYTES_REMAINING_00:
+ return "BYTES_REMAINING";
+ case ISO7816.SW_CLA_NOT_SUPPORTED:
+ return "CLA_NOT_SUPPORTED";
+ case ISO7816.SW_COMMAND_NOT_ALLOWED:
+ return "COMMAND_NOT_ALLOWED";
+ case ISO7816.SW_CONDITIONS_NOT_SATISFIED:
+ return "CONDITIONS_NOT_SATISFIED";
+ case ISO7816.SW_CORRECT_LENGTH_00:
+ return "CORRECT_LENGTH";
+ case ISO7816.SW_DATA_INVALID:
+ return "DATA_INVALID";
+ case ISO7816.SW_FILE_FULL:
+ return "FILE_FULL";
+ case ISO7816.SW_FILE_INVALID:
+ return "FILE_INVALID";
+ case ISO7816.SW_FILE_NOT_FOUND:
+ return "FILE_NOT_FOUND";
+ case ISO7816.SW_FUNC_NOT_SUPPORTED:
+ return "FUNC_NOT_SUPPORTED";
+ case ISO7816.SW_INCORRECT_P1P2:
+ return "INCORRECT_P1P2";
+ case ISO7816.SW_INS_NOT_SUPPORTED:
+ return "INS_NOT_SUPPORTED";
+ case ISO7816.SW_LOGICAL_CHANNEL_NOT_SUPPORTED:
+ return "LOGICAL_CHANNEL_NOT_SUPPORTED";
+ case ISO7816.SW_RECORD_NOT_FOUND:
+ return "RECORD_NOT_FOUND";
+ case ISO7816.SW_SECURE_MESSAGING_NOT_SUPPORTED:
+ return "SECURE_MESSAGING_NOT_SUPPORTED";
+ case ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED:
+ return "SECURITY_STATUS_NOT_SATISFIED";
+ case ISO7816.SW_UNKNOWN:
+ return "UNKNOWN";
+ case ISO7816.SW_WARNING_STATE_UNCHANGED:
+ return "WARNING_STATE_UNCHANGED";
+ case ISO7816.SW_WRONG_DATA:
+ return "WRONG_DATA";
+ case ISO7816.SW_WRONG_LENGTH:
+ return "WRONG_LENGTH";
+ case ISO7816.SW_WRONG_P1P2:
+ return "WRONG_P1P2";
+ case CryptoException.ILLEGAL_VALUE:
+ return "ILLEGAL_VALUE";
+ case CryptoException.UNINITIALIZED_KEY:
+ return "UNINITIALIZED_KEY";
+ case CryptoException.NO_SUCH_ALGORITHM:
+ return "NO_SUCH_ALG";
+ case CryptoException.INVALID_INIT:
+ return "INVALID_INIT";
+ case CryptoException.ILLEGAL_USE:
+ return "ILLEGAL_USE";
+ case ECTesterApplet.SW_SIG_VERIFY_FAIL:
+ return "SIG_VERIFY_FAIL";
+ case ECTesterApplet.SW_DH_DHC_MISMATCH:
+ return "DH_DHC_MISMATCH";
+ case ECTesterApplet.SW_KEYPAIR_NULL:
+ return "KEYPAIR_NULL";
+ case ECTesterApplet.SW_KA_NULL:
+ return "KA_NULL";
+ case ECTesterApplet.SW_SIGNATURE_NULL:
+ return "SIGNATURE_NULL";
+ case ECTesterApplet.SW_OBJECT_NULL:
+ return "OBJECT_NULL";
+ default:
+ return "unknown";
+ }
+ }
+
+ public static String getSWString(short sw) {
+ if (sw == ISO7816.SW_NO_ERROR) {
+ return "OK (0x9000)";
+ } else {
+ String str = getSW(sw);
+ return String.format("fail (%s, 0x%04x)", str, sw);
+ }
+ }
+
+ public static String getCorruption(short corruptionType) {
+ switch (corruptionType) {
+ case EC_Consts.CORRUPTION_NONE:
+ return "NONE";
+ case EC_Consts.CORRUPTION_FIXED:
+ return "FIXED";
+ case EC_Consts.CORRUPTION_ONE:
+ return "ONE";
+ case EC_Consts.CORRUPTION_ZERO:
+ return "ZERO";
+ case EC_Consts.CORRUPTION_ONEBYTERANDOM:
+ return "ONE_BYTE_RANDOM";
+ case EC_Consts.CORRUPTION_FULLRANDOM:
+ return "FULL_RANDOM";
+ case EC_Consts.CORRUPTION_INCREMENT:
+ return "INCREMENT";
+ case EC_Consts.CORRUPTION_INFINITY:
+ return "INFINITY";
+ case EC_Consts.CORRUPTION_COMPRESS:
+ return "COMPRESSED";
+ case EC_Consts.CORRUPTION_MAX:
+ return "MAX";
+ default:
+ return "unknown";
+ }
+ }
+
+ public static String getKATypeString(byte kaType) {
+ switch (kaType) {
+ case KeyAgreement_ALG_EC_SVDP_DH:
+ return "ALG_EC_SVDP_DH";
+ case KeyAgreement_ALG_EC_SVDP_DH_PLAIN:
+ return "ALG_EC_SVDP_DH_PLAIN";
+ case KeyAgreement_ALG_EC_PACE_GM:
+ return "ALG_EC_PACE_GM";
+ case KeyAgreement_ALG_EC_SVDP_DH_PLAIN_XY:
+ return "ALG_EC_SVDP_DH_PLAIN_XY";
+ case KeyAgreement_ALG_EC_SVDP_DHC:
+ return "ALG_EC_SVDP_DHC";
+ case KeyAgreement_ALG_EC_SVDP_DHC_PLAIN:
+ return "ALG_EC_SVDP_DHC_PLAIN";
+ default:
+ return "unknown";
+ }
+ }
+
+ public static byte getKAType(String kaTypeString) {
+ switch (kaTypeString) {
+ case "ALG_EC_SVDP_DH":
+ return KeyAgreement_ALG_EC_SVDP_DH;
+ case "ALG_EC_SVDP_DH_PLAIN":
+ return KeyAgreement_ALG_EC_SVDP_DH_PLAIN;
+ case "ALG_EC_PACE_GM":
+ return KeyAgreement_ALG_EC_PACE_GM;
+ case "ALG_EC_SVDP_DH_PLAIN_XY":
+ return KeyAgreement_ALG_EC_SVDP_DH_PLAIN_XY;
+ case "ALG_EC_SVDP_DHC":
+ return KeyAgreement_ALG_EC_SVDP_DHC;
+ case "ALG_EC_SVDP_DHC_PLAIN":
+ return KeyAgreement_ALG_EC_SVDP_DHC_PLAIN;
+ default:
+ return 0;
+ }
+ }
+
+ public static byte parseKAType(String kaTypeString) {
+ byte kaType;
+ try {
+ kaType = Byte.parseByte(kaTypeString);
+ } catch (NumberFormatException nfex) {
+ kaType = getKAType(kaTypeString);
+ }
+ return kaType;
+ }
+
+ public static String getSigTypeString(byte sigType) {
+ switch (sigType) {
+ case Signature_ALG_ECDSA_SHA:
+ return "ALG_ECDSA_SHA";
+ case Signature_ALG_ECDSA_SHA_224:
+ return "ALG_ECDSA_SHA_224";
+ case Signature_ALG_ECDSA_SHA_256:
+ return "ALG_ECDSA_SHA_256";
+ case Signature_ALG_ECDSA_SHA_384:
+ return "ALG_ECDSA_SHA_384";
+ case Signature_ALG_ECDSA_SHA_512:
+ return "ALG_ECDSA_SHA_512";
+ default:
+ return "unknown";
+ }
+ }
+
+ public static byte getSigType(String sigTypeString) {
+ switch (sigTypeString) {
+ case "ALG_ECDSA_SHA":
+ return Signature_ALG_ECDSA_SHA;
+ case "ALG_ECDSA_SHA_224":
+ return Signature_ALG_ECDSA_SHA_224;
+ case "ALG_ECDSA_SHA_256":
+ return Signature_ALG_ECDSA_SHA_256;
+ case "ALG_ECDSA_SHA_384":
+ return Signature_ALG_ECDSA_SHA_384;
+ case "ALG_ECDSA_SHA_512":
+ return Signature_ALG_ECDSA_SHA_512;
+ default:
+ return 0;
+ }
+ }
+
+ public static byte parseSigType(String sigTypeString) {
+ byte sigType;
+ try {
+ sigType = Byte.parseByte(sigTypeString);
+ } catch (NumberFormatException nfex) {
+ sigType = getSigType(sigTypeString);
+ }
+ return sigType;
+ }
+}
diff --git a/src/cz/crcs/ectester/common/util/ECUtil.java b/src/cz/crcs/ectester/common/util/ECUtil.java
new file mode 100644
index 0000000..973b813
--- /dev/null
+++ b/src/cz/crcs/ectester/common/util/ECUtil.java
@@ -0,0 +1,172 @@
+package cz.crcs.ectester.common.util;
+
+import java.math.BigInteger;
+import java.security.spec.*;
+
+/**
+ * @author Jan Jancar johny@neuromancer.sk
+ */
+public class ECUtil {
+
+ public static byte[] toByteArray(BigInteger what, int bits) {
+ byte[] raw = what.toByteArray();
+ int bytes = (bits + 7) / 8;
+ if (raw.length < bytes) {
+ byte[] result = new byte[bytes];
+ System.arraycopy(raw, 0, result, bytes - raw.length, raw.length);
+ return result;
+ }
+ if (bytes < raw.length) {
+ byte[] result = new byte[bytes];
+ System.arraycopy(raw, raw.length - bytes, result, 0, bytes);
+ return result;
+ }
+ return raw;
+ }
+
+ public static byte[] toX962Compressed(ECPoint point, int bits) {
+ if (point.equals(ECPoint.POINT_INFINITY)) {
+ return new byte[]{0};
+ }
+ byte[] x = toByteArray(point.getAffineX(), bits);
+ byte marker = (byte) (0x02 | point.getAffineY().mod(BigInteger.valueOf(2)).byteValue());
+ return ByteUtil.concatenate(new byte[]{marker}, x);
+ }
+
+ public static byte[] toX962Compressed(ECPoint point, EllipticCurve curve) {
+ return toX962Compressed(point, curve.getField().getFieldSize());
+ }
+
+ public static byte[] toX962Compressed(ECPoint point, ECParameterSpec spec) {
+ return toX962Compressed(point, spec.getCurve());
+ }
+
+ public static byte[] toX962Uncompressed(ECPoint point, int bits) {
+ if (point.equals(ECPoint.POINT_INFINITY)) {
+ return new byte[]{0};
+ }
+ byte[] x = toByteArray(point.getAffineX(), bits);
+ byte[] y = toByteArray(point.getAffineY(), bits);
+ return ByteUtil.concatenate(new byte[]{0x04}, x, y);
+ }
+
+ public static byte[] toX962Uncompressed(ECPoint point, EllipticCurve curve) {
+ return toX962Uncompressed(point, curve.getField().getFieldSize());
+ }
+
+ public static byte[] toX962Uncompressed(ECPoint point, ECParameterSpec spec) {
+ return toX962Uncompressed(point, spec.getCurve());
+ }
+
+ public static byte[] toX962Hybrid(ECPoint point, int bits) {
+ if (point.equals(ECPoint.POINT_INFINITY)) {
+ return new byte[]{0};
+ }
+ byte[] x = toByteArray(point.getAffineX(), bits);
+ byte[] y = toByteArray(point.getAffineY(), bits);
+ byte marker = (byte) (0x06 | point.getAffineY().mod(BigInteger.valueOf(2)).byteValue());
+ return ByteUtil.concatenate(new byte[]{marker}, x, y);
+ }
+
+ public static byte[] toX962Hybrid(ECPoint point, EllipticCurve curve) {
+ return toX962Hybrid(point, curve.getField().getFieldSize());
+ }
+
+ public static byte[] toX962Hybrid(ECPoint point, ECParameterSpec spec) {
+ return toX962Hybrid(point, spec.getCurve());
+ }
+
+ private static boolean isResidue(BigInteger a, BigInteger p) {
+ BigInteger exponent = p.subtract(BigInteger.ONE).divide(BigInteger.valueOf(2));
+ BigInteger result = a.modPow(exponent, p);
+ return result.intValueExact() == 1;
+ }
+
+ private static BigInteger modSqrt(BigInteger a, BigInteger p) {
+ BigInteger q = p.subtract(BigInteger.ONE);
+ int s = 0;
+ while (q.mod(BigInteger.valueOf(2)).equals(BigInteger.ZERO)) {
+ q = q.divide(BigInteger.valueOf(2));
+ s++;
+ }
+
+ BigInteger z = BigInteger.ONE;
+ do {
+ z = z.add(BigInteger.ONE);
+ } while (isResidue(z, p));
+
+ BigInteger m = BigInteger.valueOf(s);
+ BigInteger c = z.modPow(q, p);
+ BigInteger t = a.modPow(q, p);
+ BigInteger rExponent = q.add(BigInteger.ONE).divide(BigInteger.valueOf(2));
+ BigInteger r = a.modPow(rExponent, p);
+
+ while (!t.equals(BigInteger.ONE)) {
+ int i = 0;
+ BigInteger exponent;
+ do {
+ exponent = BigInteger.valueOf(2).pow(++i);
+ } while (!t.modPow(exponent, p).equals(BigInteger.ONE));
+
+ BigInteger twoExponent = m.subtract(BigInteger.valueOf(i + 1));
+ BigInteger b = c.modPow(BigInteger.valueOf(2).modPow(twoExponent, p), p);
+ m = BigInteger.valueOf(i);
+ c = b.modPow(BigInteger.valueOf(2), p);
+ t = t.multiply(c).mod(p);
+ r = r.multiply(b).mod(p);
+ }
+ return r;
+ }
+
+ public static ECPoint fromX962(byte[] data, EllipticCurve curve) {
+ if (data == null) {
+ return null;
+ }
+ if (data[0] == 0x04 || data[0] == 0x06 || data[0] == 0x07) {
+ int len = (data.length - 1) / 2;
+ byte[] xbytes = new byte[len];
+ System.arraycopy(data, 1, xbytes, 0, len);
+ byte[] ybytes = new byte[len];
+ System.arraycopy(data, 1 + len, ybytes, 0, len);
+ return new ECPoint(new BigInteger(1, xbytes), new BigInteger(1, ybytes));
+ } else if (data[0] == 0x02 || data[0] == 0x03) {
+ if (curve == null) {
+ throw new IllegalArgumentException();
+ }
+ byte[] xbytes = new byte[data.length - 1];
+ System.arraycopy(data, 1, xbytes, 0, data.length - 1);
+ BigInteger x = new BigInteger(1, xbytes);
+ BigInteger a = curve.getA();
+ BigInteger b = curve.getB();
+
+ ECField field = curve.getField();
+ if (field instanceof ECFieldFp) {
+ BigInteger p = ((ECFieldFp) field).getP();
+ BigInteger alpha = x.modPow(BigInteger.valueOf(3), p);
+ alpha = alpha.add(x.multiply(a));
+ alpha = alpha.add(b);
+
+ BigInteger beta = modSqrt(alpha, p);
+ if (beta.getLowestSetBit() == 0) {
+ // rightmost bit is one
+ if (data[0] == 0x02) {
+ beta = beta.negate();
+ }
+ } else {
+ // rightmost bit is zero
+ if (data[0] == 0x03) {
+ beta = beta.negate();
+ }
+ }
+
+ return new ECPoint(x, beta);
+ } else if (field instanceof ECFieldF2m) {
+ //TODO
+ throw new UnsupportedOperationException();
+ }
+ return null;
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+}