2 * Copyright © 2019 FRINX s.r.o. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.restconf.websocket.client;
10 import com.google.common.base.Preconditions;
12 import java.io.PrintWriter;
13 import java.io.StringWriter;
14 import java.util.ArrayList;
15 import java.util.List;
16 import java.util.Optional;
17 import net.sourceforge.argparse4j.ArgumentParsers;
18 import net.sourceforge.argparse4j.annotation.Arg;
19 import net.sourceforge.argparse4j.helper.HelpScreenException;
20 import net.sourceforge.argparse4j.inf.ArgumentParser;
21 import net.sourceforge.argparse4j.inf.ArgumentParserException;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
26 * Holder of the parsed user-input application arguments.
28 final class ApplicationSettings {
31 * Credentials used for basic authentication - grouping of username and password.
33 static final class Credentials {
34 final String userName;
35 final String password;
37 private Credentials(final String userName, final String password) {
38 this.userName = userName;
39 this.password = password;
42 private static Credentials extractCredentials(final String basicAuthentication) {
43 final String[] credentials = basicAuthentication.split(":");
44 Preconditions.checkArgument(credentials.length == 2, "Both username and password must be specified in the "
45 + "format [username]:[password] for basic authentication.");
46 final String userName = credentials[0].trim();
47 final String password = credentials[1].trim();
48 return new Credentials(userName, password);
52 private static final Logger LOG = LoggerFactory.getLogger(ApplicationSettings.class);
53 private static final ArgumentParser PARSER = ArgumentParsers.newFor("web-socket client test-tool").build();
56 PARSER.addArgument("-l")
62 .help("Logging level threshold used throughout the whole web-socket client.");
63 PARSER.addArgument("-s")
67 .help("Web-socket stream paths with ws or wss schemas.")
70 PARSER.addArgument("-pi")
72 .help("Interval in milliseconds between sending of ping web-socket frames to server. "
73 + "Value of 0 disables ping process.")
77 PARSER.addArgument("-pm")
79 .help("Explicitly set ping message.")
83 PARSER.addArgument("-t")
85 .help("Explicitly set size of thread-pool used for holding of web-socket handlers and ping processes.")
89 PARSER.addArgument("-r")
91 .help("Allowed TLS/SSL session regeneration.")
95 PARSER.addArgument("-kpath")
97 .help("Path to the certificates key-store file.")
100 PARSER.addArgument("-kpass")
101 .dest("keystorePassword")
102 .help("Password used for unlocking of the certificates keystore.")
105 PARSER.addArgument("-tpath")
106 .dest("truststorePath")
107 .help("Path to the certificates trust-store file.")
110 PARSER.addArgument("-tpass")
111 .dest("truststorePassword")
112 .help("Password used for unlocking of the certificates truststore.")
115 PARSER.addArgument("-ta")
117 .help("All incoming certificates are trusted when both truststore and keystore are not specified.")
120 .type(Boolean.class);
121 PARSER.addArgument("-ip")
122 .dest("includedProtocols")
124 .help("Explicitly specified list of permitted versions of web-security protocols.")
126 .setDefault("TLSv1.2", "TLSv1.3")
128 PARSER.addArgument("-ep")
129 .dest("excludedProtocols")
131 .help("Explicitly specified list of denied versions of web-security protocols (denied protocols have "
132 + "the highest priority).")
134 .setDefault("TLSv1", "TLSv1.1", "SSL", "SSLv2", "SSLv2Hello", "SSLv3")
136 PARSER.addArgument("-ic")
137 .dest("includedCipherSuites")
139 .help("Explicitly specified list of permitted cipher suites.")
141 .setDefault("TLS_ECDHE.*", "TLS_DHE_RSA.*")
143 PARSER.addArgument("-ec")
144 .dest("excludedCipherSuites")
146 .help("Explicitly specified list of denied cipher suites (denied ciphers have the highest priority).")
148 .setDefault(".*MD5.*", ".*RC4.*", ".*DSS.*", ".*NULL.*", ".*DES.*")
150 PARSER.addArgument("-b")
151 .dest("basicAuthentication")
152 .help("[username:password] used with basic authentication that can be required on upgrade-request.")
153 .metavar("[USERNAME]:[PASSWORD]")
157 @Arg(dest = "loggingLevel")
158 private String loggingLevel;
159 @Arg(dest = "streams")
160 private List<String> streams;
161 @Arg(dest = "pingInterval")
162 private int pingInterval;
163 @Arg(dest = "pingMessage")
164 private String pingMessage;
165 @Arg(dest = "threads")
166 private int threadPoolSize;
167 @Arg(dest = "regeneration")
168 private boolean regenerationAllowed;
169 @Arg(dest = "keystorePath")
170 private File keystorePath;
171 @Arg(dest = "keystorePassword")
172 private String keystorePassword;
173 @Arg(dest = "truststorePath")
174 private File truststorePath;
175 @Arg(dest = "truststorePassword")
176 private String truststorePassword;
177 @Arg(dest = "trustAll")
178 private boolean trustAll;
179 @Arg(dest = "includedProtocols")
180 private List<String> includedProtocols;
181 @Arg(dest = "excludedProtocols")
182 private List<String> excludedProtocols;
183 @Arg(dest = "includedCipherSuites")
184 private List<String> includedCipherSuites;
185 @Arg(dest = "excludedCipherSuites")
186 private List<String> excludedCipherSuites;
187 @Arg(dest = "basicAuthentication")
188 private String basicAuthentication;
190 private Credentials credentials;
192 private ApplicationSettings() {
196 * Creation of application settings object using input command-line arguments (factory method).
198 * @param arguments Raw program arguments.
199 * @return Parsed arguments wrapped in {@link Optional} or {@link Optional#empty()} if only help is going
202 static Optional<ApplicationSettings> parseApplicationSettings(final String[] arguments) {
203 final ApplicationSettings applicationSettings = new ApplicationSettings();
205 PARSER.parseArgs(arguments, applicationSettings);
206 applicationSettings.verifyParsedArguments();
207 if (applicationSettings.basicAuthentication == null) {
208 applicationSettings.credentials = null;
210 applicationSettings.credentials = Credentials.extractCredentials(
211 applicationSettings.basicAuthentication);
213 } catch (final ArgumentParserException | IllegalArgumentException e) {
214 if (e instanceof HelpScreenException) {
215 return Optional.empty();
217 final StringWriter helpWriter = new StringWriter();
218 final PrintWriter helpPrintWriter = new PrintWriter(helpWriter);
219 PARSER.printHelp(helpPrintWriter);
220 LOG.error("Cannot parse input arguments {}.", arguments, e);
221 LOG.info("Help: {}", helpWriter.toString());
222 throw new IllegalArgumentException("Cannot parse input arguments", e);
225 LOG.info("Application settings {} have been parsed successfully.", (Object) arguments);
226 return Optional.of(applicationSettings);
229 private void verifyParsedArguments() {
230 Preconditions.checkArgument(pingInterval >= 0, "Ping interval must be set to value higher than 0 (enabled) or "
231 + "to 0 (disabled).");
232 Preconditions.checkArgument(threadPoolSize > 0, "Thread pool must have capacity of at least 1 thread.");
233 Preconditions.checkArgument((keystorePath == null && keystorePassword == null) || (keystorePath != null
234 && keystorePassword != null), "Both keystore path and keystore password must be configured at once.");
235 Preconditions.checkArgument((truststorePath == null && truststorePassword == null) || (truststorePath != null
236 && truststorePassword != null), "Both truststore path and truststore password must be configured");
239 String getLoggingLevel() {
243 List<String> getStreams() {
244 return new ArrayList<>(streams);
247 int getPingInterval() {
251 String getPingMessage() {
255 File getKeystorePath() {
259 String getKeystorePassword() {
260 return keystorePassword;
263 File getTruststorePath() {
264 return truststorePath;
267 String getTruststorePassword() {
268 return truststorePassword;
271 List<String> getIncludedProtocols() {
272 return new ArrayList<>(includedProtocols);
275 List<String> getExcludedProtocols() {
276 return new ArrayList<>(excludedProtocols);
279 List<String> getIncludedCipherSuites() {
280 return new ArrayList<>(includedCipherSuites);
283 List<String> getExcludedCipherSuites() {
284 return new ArrayList<>(excludedCipherSuites);
287 boolean isTrustAll() {
291 boolean isRegenerationAllowed() {
292 return regenerationAllowed;
295 int getThreadPoolSize() {
296 return threadPoolSize;
299 Credentials getCredentials() {