From 0c37e50c867432a4710b2d8580b52162b171fa09 Mon Sep 17 00:00:00 2001 From: Frederic Bregier Date: Thu, 18 Jul 2019 11:25:58 +0200 Subject: [PATCH] Add Junit to v3.1.0 --- .gitignore | 62 ++ pom.xml | 12 +- .../server/LocalExecServerHandler.java | 9 +- src/test/java/.gitkeep | 0 .../client/test/LocalExecClientTest.java | 489 ++++++++-------- .../commandexec/client/test/package-info.java | 22 +- .../client/test/LocalExecSslClientTest.java | 538 ++++++++++-------- .../ssl/client/test/package-info.java | 22 +- src/test/resources/certs/testcert.jks | Bin 0 -> 3907 bytes src/test/resources/certs/testsslnocert.jks | Bin 0 -> 2293 bytes 10 files changed, 672 insertions(+), 482 deletions(-) create mode 100644 src/test/java/.gitkeep create mode 100755 src/test/resources/certs/testcert.jks create mode 100755 src/test/resources/certs/testsslnocert.jks diff --git a/.gitignore b/.gitignore index cfdc549..bc42b32 100644 --- a/.gitignore +++ b/.gitignore @@ -32,9 +32,31 @@ local.properties ################# ## Java ################# +# Compiled class file *.class *.jardesc +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + ################# ## Visual Studio ################# @@ -132,3 +154,43 @@ Thumbs.db Desktop.ini runtime.properties + +############### +## Intellij +############### +# User-specific stuff +.idea/ + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# CMake +cmake-build-*/ + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +*.log + diff --git a/pom.xml b/pom.xml index 04202f8..a495ca8 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 WaarpExec jar - 3.0.5 + 3.1.0 WaarpExec The Waarp Exec project is about a Daemon for local execution of system commands (as System.exec) but through client/server model and not within the same JVM. @@ -46,7 +46,7 @@ Waarp WaarpCommon - 3.1.0 + 3.1.1 xom @@ -83,6 +83,12 @@ 1.2.3 true + + junit + junit + 4.12 + test + UTF-8 @@ -110,7 +116,7 @@ 1.6 1.6 true - true + true diff --git a/src/main/java/org/waarp/commandexec/server/LocalExecServerHandler.java b/src/main/java/org/waarp/commandexec/server/LocalExecServerHandler.java index fa6f372..206a7d0 100644 --- a/src/main/java/org/waarp/commandexec/server/LocalExecServerHandler.java +++ b/src/main/java/org/waarp/commandexec/server/LocalExecServerHandler.java @@ -45,6 +45,7 @@ import org.waarp.common.crypto.ssl.WaarpSslUtility; import org.waarp.common.logging.WaarpLogger; import org.waarp.common.logging.WaarpLoggerFactory; +import org.waarp.common.utility.DetectionUtils; import org.waarp.common.utility.WaarpStringUtils; /** @@ -87,6 +88,10 @@ public static boolean isShutdown(Channel channel) { return false; } + public static void junitSetNotShutdown() { + isShutdown = false; + } + /** * Print stack trace * @@ -122,7 +127,7 @@ public void run() { GGLETimerTask ggleTimerTask = new GGLETimerTask(); timer.schedule(ggleTimerTask, delay); factory.releaseResources(); - System.exit(0); + DetectionUtils.SystemExit(0); } } @@ -148,7 +153,7 @@ public void run() { for (Thread thread : map.keySet()) { printStackTrace(thread, map.get(thread)); } - System.exit(0); + DetectionUtils.SystemExit(0); } } diff --git a/src/test/java/.gitkeep b/src/test/java/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/test/java/org/waarp/commandexec/client/test/LocalExecClientTest.java b/src/test/java/org/waarp/commandexec/client/test/LocalExecClientTest.java index c1b5ada..997c912 100644 --- a/src/test/java/org/waarp/commandexec/client/test/LocalExecClientTest.java +++ b/src/test/java/org/waarp/commandexec/client/test/LocalExecClientTest.java @@ -1,269 +1,308 @@ -/** - This file is part of Waarp Project. - - Copyright 2009, Frederic Bregier, and individual contributors by the @author - tags. See the COPYRIGHT.txt in the distribution for a full listing of - individual contributors. - - All Waarp Project is free software: you can redistribute it and/or - modify it under the terms of the GNU General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Waarp is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Waarp . If not, see . - */ +/******************************************************************************* + * This file is part of Waarp Project (named also Waarp or GG). + * + * Copyright (c) 2019, Waarp SAS, and individual contributors by the @author + * tags. See the COPYRIGHT.txt in the distribution for a full listing of + * individual contributors. + * + * All Waarp Project is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Waarp . If not, see . + ******************************************************************************/ package org.waarp.commandexec.client.test; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.UnknownHostException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; - +import io.netty.util.concurrent.DefaultEventExecutorGroup; +import io.netty.util.concurrent.EventExecutorGroup; +import org.junit.Test; import org.waarp.commandexec.client.LocalExecClientHandler; import org.waarp.commandexec.client.LocalExecClientInitializer; +import org.waarp.commandexec.server.LocalExecServerHandler; +import org.waarp.commandexec.server.LocalExecServerInitializer; +import org.waarp.commandexec.utils.LocalExecDefaultResult; import org.waarp.commandexec.utils.LocalExecResult; import org.waarp.common.crypto.ssl.WaarpSslUtility; import org.waarp.common.logging.WaarpLogLevel; import org.waarp.common.logging.WaarpLoggerFactory; import org.waarp.common.logging.WaarpSlf4JLoggerFactory; +import org.waarp.common.utility.DetectionUtils; import org.waarp.common.utility.WaarpNettyUtil; +import org.waarp.common.utility.WaarpThreadFactory; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.*; /** * LocalExec client. - * + *

* This class is an example of client. - * - * On a bi-core Centrino2 vPro: 18/s in 50 sequential, 30/s in 10 threads with 50 sequential
- * On a quad-core i7: 29/s in 50 sequential, 187/s in 10 threads with 50 sequential + *

+ * On a bi-core Centrino2 vPro: 18/s in 50 sequential, 30/s in 10 threads with + * 50 sequential
On a quad-core i7: 29/s + * in 50 sequential, 187/s in 10 threads with 50 sequential */ public class LocalExecClientTest extends Thread { + static int nit = 20; + static int nth = 4; + static String command = "echo"; + static int port = 9999; + static InetSocketAddress address; + static LocalExecResult result; + static int ok = 0; + static int ko = 0; + static AtomicInteger atomicInteger = new AtomicInteger(); + static EventLoopGroup workerGroup = new NioEventLoopGroup(); + static EventExecutorGroup executor = + new DefaultEventExecutorGroup(DetectionUtils.numberThreads(), + new WaarpThreadFactory( + "LocalExecServer")); + // Configure the client. + static Bootstrap bootstrap; + // Configure the pipeline factory. + static LocalExecClientInitializer localExecClientInitializer; + private Channel channel; - static int nit = 50; - static int nth = 10; - static String command = "/opt/R66/testexec.sh"; - static int port = 9999; - static InetSocketAddress address; + { + DetectionUtils.setJunit(true); + } - static LocalExecResult result; - static int ok = 0; - static int ko = 0; - static AtomicInteger atomicInteger = new AtomicInteger(); + /** + * Simple constructor + */ + public LocalExecClientTest() { + } - static EventLoopGroup workerGroup = new NioEventLoopGroup(); - // Configure the client. - static Bootstrap bootstrap; - // Configure the pipeline factory. - static LocalExecClientInitializer localExecClientInitializer; + @Test + public void testClient() throws Exception { + WaarpLoggerFactory.setDefaultFactory(new WaarpSlf4JLoggerFactory( + WaarpLogLevel.WARN)); + DetectionUtils.setJunit(true); + InetAddress addr; + byte[] loop = { 127, 0, 0, 1 }; + try { + addr = InetAddress.getByAddress(loop); + } catch (UnknownHostException e) { + return; + } + address = new InetSocketAddress(addr, port); - /** - * Test & example main - * - * @param args - * ignored - * @throws Exception - */ - public static void main(String[] aregs) throws Exception { - WaarpLoggerFactory.setDefaultFactory(new WaarpSlf4JLoggerFactory( - WaarpLogLevel.WARN)); - InetAddress addr; - byte[] loop = { 127, 0, 0, 1 }; - try { - addr = InetAddress.getByAddress(loop); - } catch (UnknownHostException e) { - return; - } - address = new InetSocketAddress(addr, port); + // configure the server + ServerBootstrap bootstrapServer = new ServerBootstrap(); + WaarpNettyUtil.setServerBootstrap(bootstrapServer, workerGroup, 1000); + + // Configure the pipeline factory. + LocalExecServerInitializer localExecServerInitializer = + new LocalExecServerInitializer( + LocalExecDefaultResult.MAXWAITPROCESS, executor); + bootstrapServer.childHandler(localExecServerInitializer); - // Configure the client. - bootstrap = new Bootstrap(); - WaarpNettyUtil.setBootstrap(bootstrap, workerGroup, 30000); - // Configure the pipeline factory. - localExecClientInitializer = new LocalExecClientInitializer(); - bootstrap.handler(localExecClientInitializer); + // Bind and start to accept incoming connections only on local address. + ChannelFuture future = + bootstrapServer.bind(new InetSocketAddress(addr, port)); - try { - // Parse options. - LocalExecClientTest client = new LocalExecClientTest(); - // run once - long first = System.currentTimeMillis(); - if (client.connect()) { - client.runOnce(); - client.disconnect(); - } - long second = System.currentTimeMillis(); - // print time for one exec - System.err.println("1=Total time in ms: " + (second - first) + " or " + (1 * 1000 / (second - first)) - + " exec/s"); - System.err.println("Result: " + ok + ":" + ko); - ok = 0; - ko = 0; - // Now run multiple within one thread - first = System.currentTimeMillis(); - for (int i = 0; i < nit; i++) { - if (client.connect()) { - client.runOnce(); - client.disconnect(); - } - } - second = System.currentTimeMillis(); - // print time for one exec - System.err.println(nit + "=Total time in ms: " + (second - first) + " or " - + (nit * 1000 / (second - first)) + " exec/s"); - System.err.println("Result: " + ok + ":" + ko); - ok = 0; - ko = 0; - // Now run multiple within multiple threads - // Create multiple threads - ExecutorService executorService = Executors.newFixedThreadPool(nth); - first = System.currentTimeMillis(); - // Starts all thread with a default number of execution - for (int i = 0; i < nth; i++) { - executorService.submit(new LocalExecClientTest()); - } - Thread.sleep(500); - executorService.shutdown(); - while (!executorService.awaitTermination(200, TimeUnit.MILLISECONDS)) { - Thread.sleep(50); - } - second = System.currentTimeMillis(); + // Configure the client. + bootstrap = new Bootstrap(); + WaarpNettyUtil.setBootstrap(bootstrap, workerGroup, 1000); + // Configure the pipeline factory. + localExecClientInitializer = new LocalExecClientInitializer(); + bootstrap.handler(localExecClientInitializer); - // print time for one exec - System.err.println((nit * nth) + "=Total time in ms: " + (second - first) + " or " - + (nit * nth * 1000 / (second - first)) + " exec/s"); - System.err.println("Result: " + ok + ":" + ko); - ok = 0; - ko = 0; + // Wait for the server + future.sync(); - // run once - first = System.currentTimeMillis(); - if (client.connect()) { - client.runFinal(); - client.disconnect(); - } - second = System.currentTimeMillis(); - // print time for one exec - System.err.println("1=Total time in ms: " + (second - first) + " or " + (1 * 1000 / (second - first)) - + " exec/s"); - System.err.println("Result: " + ok + ":" + ko); - ok = 0; - ko = 0; - } finally { - // Shut down all thread pools to exit. - workerGroup.shutdownGracefully(); - localExecClientInitializer.releaseResources(); + try { + // Parse options. + LocalExecClientTest client = new LocalExecClientTest(); + // run once + long first = System.currentTimeMillis(); + if (client.connect()) { + client.runOnce(); + client.disconnect(); + } + long second = System.currentTimeMillis(); + // print time for one exec + System.err.println("1=Total time in ms: " + (second - first) + " or " + + (1 * 1000 / (second - first)) + + " exec/s"); + System.err.println("Result: " + ok + ":" + ko); + assertEquals(0, ko); + ok = 0; + ko = 0; + // Now run multiple within one thread + first = System.currentTimeMillis(); + for (int i = 0; i < nit; i++) { + if (client.connect()) { + client.runOnce(); + client.disconnect(); } - } + } + second = System.currentTimeMillis(); + // print time for one exec + System.err.println(nit + "=Total time in ms: " + (second - first) + " or " + + (nit * 1000 / (second - first)) + " exec/s"); + System.err.println("Result: " + ok + ":" + ko); + assertEquals(0, ko); + ok = 0; + ko = 0; + // Now run multiple within multiple threads + // Create multiple threads + ExecutorService executorService = Executors.newFixedThreadPool(nth); + first = System.currentTimeMillis(); + // Starts all thread with a default number of execution + for (int i = 0; i < nth; i++) { + executorService.submit(new LocalExecClientTest()); + } + Thread.sleep(500); + executorService.shutdown(); + while (!executorService.awaitTermination(200, TimeUnit.MILLISECONDS)) { + Thread.sleep(50); + } + second = System.currentTimeMillis(); - /** - * Simple constructor - */ - public LocalExecClientTest() { + // print time for one exec + System.err.println( + (nit * nth) + "=Total time in ms: " + (second - first) + " or " + + (nit * nth * 1000 / (second - first)) + " exec/s"); + System.err.println("Result: " + ok + ":" + ko); + assertEquals(0, ko); + ok = 0; + ko = 0; + + // run once + first = System.currentTimeMillis(); + if (client.connect()) { + client.runFinal(); + client.disconnect(); + } + second = System.currentTimeMillis(); + // print time for one exec + System.err.println("1=Total time in ms: " + (second - first) + " or " + + (1 * 1000 / (second - first)) + + " exec/s"); + System.err.println("Result: " + ok + ":" + ko); + assertEquals(0, ko); + ok = 0; + ko = 0; + } finally { + future.channel().close(); + // Shut down all thread pools to exit. + localExecClientInitializer.releaseResources(); + localExecServerInitializer.releaseResources(); + // Shut down all thread pools to exit. + workerGroup.shutdownGracefully(); + localExecClientInitializer.releaseResources(); + LocalExecServerHandler.junitSetNotShutdown(); } + } - private Channel channel; + /** + * Connect to the Server + */ + private boolean connect() { + // Start the connection attempt. + ChannelFuture future = bootstrap.connect(address); - /** - * Run method for thread - */ - public void run() { - if (connect()) { - for (int i = 0; i < nit; i++) { - this.runOnce(); - } - disconnect(); - } + // Wait until the connection attempt succeeds or fails. + try { + channel = future.await().sync().channel(); + } catch (InterruptedException e) { } + if (!future.isSuccess()) { + System.err.println("Client Not Connected"); + future.cause().printStackTrace(); + fail("Cannot connect"); + return false; + } + return true; + } - /** - * Connect to the Server - */ - private boolean connect() { - // Start the connection attempt. - ChannelFuture future = bootstrap.connect(address); - - // Wait until the connection attempt succeeds or fails. - try { - channel = future.await().sync().channel(); - } catch (InterruptedException e) { - } - if (!future.isSuccess()) { - System.err.println("Client Not Connected"); - future.cause().printStackTrace(); - return false; - } - return true; + /** + * Run method both for not threaded execution and threaded execution + */ + public void runOnce() { + // Initialize the command context + LocalExecClientHandler clientHandler = + (LocalExecClientHandler) channel.pipeline().last(); + // Command to execute + String line = command + " " + atomicInteger.incrementAndGet(); + clientHandler.initExecClient(0, line); + // Wait for the end of the exec command + LocalExecResult localExecResult = clientHandler.waitFor(10000); + int status = localExecResult.getStatus(); + if (status < 0) { + System.err.println(line + " Status: " + status + "\tResult: " + + localExecResult.getResult()); + ko++; + } else { + ok++; + result = localExecResult; } + } - /** - * Disconnect from the server - */ - private void disconnect() { - // Close the connection. Make sure the close operation ends because - // all I/O operations are asynchronous in Netty. - try { - ChannelFuture closeFuture = WaarpSslUtility.closingSslChannel(channel); - closeFuture.await(30000); - } catch (InterruptedException e) { - } + /** + * Disconnect from the server + */ + private void disconnect() { + // Close the connection. Make sure the close operation ends because + // all I/O operations are asynchronous in Netty. + try { + ChannelFuture closeFuture = WaarpSslUtility.closingSslChannel(channel); + closeFuture.await(30000); + } catch (InterruptedException e) { } + } - /** - * Run method both for not threaded execution and threaded execution - */ - public void runOnce() { - // Initialize the command context - LocalExecClientHandler clientHandler = - (LocalExecClientHandler) channel.pipeline().last(); - // Command to execute - String line = command + " " + atomicInteger.incrementAndGet(); - clientHandler.initExecClient(0, line); - // Wait for the end of the exec command - LocalExecResult localExecResult = clientHandler.waitFor(10000); - int status = localExecResult.getStatus(); - if (status < 0) { - System.err.println(line + " Status: " + status + "\tResult: " + - localExecResult.getResult()); - ko++; - } else { - ok++; - result = localExecResult; - } + /** + * Run method for closing Server + */ + private void runFinal() { + // Initialize the command context + LocalExecClientHandler clientHandler = + (LocalExecClientHandler) channel.pipeline().last(); + // Command to execute + clientHandler.initExecClient(-1000, "stop"); + // Wait for the end of the exec command + LocalExecResult localExecResult = clientHandler.waitFor(10000); + int status = localExecResult.getStatus(); + if (status < 0) { + System.err.println("Shutdown Status: " + status + "\nResult: " + + localExecResult.getResult()); + ok++; + } else { + ok++; + result = localExecResult; } + } - /** - * Run method for closing Server - */ - private void runFinal() { - // Initialize the command context - LocalExecClientHandler clientHandler = - (LocalExecClientHandler) channel.pipeline().last(); - // Command to execute - clientHandler.initExecClient(-1000, "stop"); - // Wait for the end of the exec command - LocalExecResult localExecResult = clientHandler.waitFor(10000); - int status = localExecResult.getStatus(); - if (status < 0) { - System.err.println("Shutdown Status: " + status + "\nResult: " + - localExecResult.getResult()); - ko++; - } else { - ok++; - result = localExecResult; - } + /** + * Run method for thread + */ + public void run() { + if (connect()) { + for (int i = 0; i < nit; i++) { + this.runOnce(); + } + disconnect(); } + } } diff --git a/src/test/java/org/waarp/commandexec/client/test/package-info.java b/src/test/java/org/waarp/commandexec/client/test/package-info.java index f18b6f7..f3ba47a 100644 --- a/src/test/java/org/waarp/commandexec/client/test/package-info.java +++ b/src/test/java/org/waarp/commandexec/client/test/package-info.java @@ -1,6 +1,24 @@ -/** - * Classes implementing LocalExec Client example without SSL link +/******************************************************************************* + * This file is part of Waarp Project (named also Waarp or GG). + * + * Copyright (c) 2019, Waarp SAS, and individual contributors by the @author + * tags. See the COPYRIGHT.txt in the distribution for a full listing of + * individual contributors. * + * All Waarp Project is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. * + * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Waarp . If not, see . + ******************************************************************************/ + +/** + * Classes implementing LocalExec Client example without SSL link */ package org.waarp.commandexec.client.test; \ No newline at end of file diff --git a/src/test/java/org/waarp/commandexec/ssl/client/test/LocalExecSslClientTest.java b/src/test/java/org/waarp/commandexec/ssl/client/test/LocalExecSslClientTest.java index 5ed4187..6b85cb2 100644 --- a/src/test/java/org/waarp/commandexec/ssl/client/test/LocalExecSslClientTest.java +++ b/src/test/java/org/waarp/commandexec/ssl/client/test/LocalExecSslClientTest.java @@ -1,43 +1,38 @@ -/** - This file is part of Waarp Project. - - Copyright 2009, Frederic Bregier, and individual contributors by the @author - tags. See the COPYRIGHT.txt in the distribution for a full listing of - individual contributors. - - All Waarp Project is free software: you can redistribute it and/or - modify it under the terms of the GNU General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Waarp is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Waarp . If not, see . - */ +/******************************************************************************* + * This file is part of Waarp Project (named also Waarp or GG). + * + * Copyright (c) 2019, Waarp SAS, and individual contributors by the @author + * tags. See the COPYRIGHT.txt in the distribution for a full listing of + * individual contributors. + * + * All Waarp Project is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Waarp . If not, see . + ******************************************************************************/ package org.waarp.commandexec.ssl.client.test; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.UnknownHostException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.util.concurrent.DefaultEventExecutorGroup; import io.netty.util.concurrent.EventExecutorGroup; - +import org.junit.Test; +import org.waarp.commandexec.server.LocalExecServerHandler; import org.waarp.commandexec.ssl.client.LocalExecSslClientHandler; import org.waarp.commandexec.ssl.client.LocalExecSslClientInitializer; +import org.waarp.commandexec.ssl.server.LocalExecSslServerInitializer; +import org.waarp.commandexec.utils.LocalExecDefaultResult; import org.waarp.commandexec.utils.LocalExecResult; import org.waarp.common.crypto.ssl.WaarpSecureKeyStore; import org.waarp.common.crypto.ssl.WaarpSslContextFactory; @@ -49,254 +44,301 @@ import org.waarp.common.utility.WaarpNettyUtil; import org.waarp.common.utility.WaarpThreadFactory; +import java.io.File; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.*; + /** * LocalExecSsl client. - * + *

* This class is an example of client. - * - * No client authentication On a bi-core Centrino2 vPro: 5/s in 50 sequential, 29/s in 10 threads with 50 sequential
- * With client authentication On a bi-core Centrino2 vPro: 3/s in 50 sequential, 27/s in 10 threads with 50 sequential
- * No client authentication On a quad-core i7: 20/s in 50 sequential, 178/s in 10 threads with 50 sequential
- * With client authentication On a quad-core i7: 17/s in 50 sequential, 176/s in 10 threads with 50 sequential
- * + *

+ * No client authentication On a bi-core Centrino2 vPro: 5/s in 50 sequential, + * 29/s in 10 threads with 50 sequential
+ * With client authentication On a bi-core Centrino2 vPro: 3/s in 50 sequential, + * 27/s in 10 threads with 50 + * sequential
No client authentication On a quad-core i7: 20/s in 50 + * sequential, 178/s in 10 threads with 50 + * sequential
With client authentication On a quad-core i7: 17/s in 50 + * sequential, 176/s in 10 threads with 50 + * sequential
*/ public class LocalExecSslClientTest extends Thread { + static int nit = 20; + static int nth = 4; + static String command = "echo"; + static int port = 9999; + static InetSocketAddress address; + static LocalExecResult result; + static int ok = 0; + static int ko = 0; + static AtomicInteger atomicInteger = new AtomicInteger(); + static EventLoopGroup workerGroup = new NioEventLoopGroup(); + static EventExecutorGroup executor = + new DefaultEventExecutorGroup(DetectionUtils.numberThreads(), + new WaarpThreadFactory("LocalExecServer")); + // Configure the client. + static Bootstrap bootstrap; + // Configure the pipeline factory. + static LocalExecSslClientInitializer localExecClientInitializer; + private Channel channel; - static int nit = 50; - static int nth = 10; - static String command = "/opt/R66/testexec.sh"; - static int port = 9999; - static InetSocketAddress address; - // with client authentication - static String keyStoreFilename = "/opt/R66/AllJarsWaarpR66-2.4.28-2/config/certs/testclient2.jks"; - // without client authentication - // static String keyStoreFilename = null; - static String keyStorePasswd = "testclient2"; - static String keyPasswd = "client2"; - static String keyTrustStoreFilename = "/opt/R66/AllJarsWaarpR66-2.4.28-2/config/certs/testclient.jks"; - static String keyTrustStorePasswd = "testclient"; - static LocalExecResult result; + { + DetectionUtils.setJunit(true); + } - static int ok = 0; - static int ko = 0; - static AtomicInteger atomicInteger = new AtomicInteger(); + /** + * Simple constructor + */ + public LocalExecSslClientTest() { + } - static EventLoopGroup workerGroup = new NioEventLoopGroup(); - static EventExecutorGroup executor = new DefaultEventExecutorGroup(DetectionUtils.numberThreads(), - new WaarpThreadFactory("LocalExecServer")); + @Test + public void testSslClient() throws Exception { + WaarpLoggerFactory.setDefaultFactory(new WaarpSlf4JLoggerFactory( + WaarpLogLevel.WARN)); + DetectionUtils.setJunit(true); + InetAddress addr; + byte[] loop = { 127, 0, 0, 1 }; + try { + addr = InetAddress.getByAddress(loop); + } catch (UnknownHostException e) { + return; + } + address = new InetSocketAddress(addr, port); // Configure the client. - static Bootstrap bootstrap; + bootstrap = new Bootstrap(); + WaarpNettyUtil.setBootstrap(bootstrap, workerGroup, 1000); // Configure the pipeline factory. - static LocalExecSslClientInitializer localExecClientInitializer; + // First create the SSL part + // Load the KeyStore (No certificates) + ClassLoader classLoader = LocalExecSslClientTest.class.getClassLoader(); + String keyStoreFilename = "certs/testsslnocert.jks"; + URL url = classLoader.getResource(keyStoreFilename); + assertNotNull(url); + File file = new File(url.getFile()); + assertTrue("File Should exists", file.exists()); + String keyStorePasswd = "testsslnocert"; + String keyPassword = "testalias"; + WaarpSecureKeyStore waarpSecureKeyStore = + new WaarpSecureKeyStore(file.getAbsolutePath(), keyStorePasswd, + keyPassword); + WaarpSecureKeyStore waarpSecureKeyStoreClient = + new WaarpSecureKeyStore(file.getAbsolutePath(), keyStorePasswd, + keyPassword); + // Include certificates + String trustStoreFilename = "certs/testcert.jks"; + File file2 = + new File(classLoader.getResource(trustStoreFilename).getFile()); + assertTrue("File2 Should exists", file2.exists()); + String trustStorePasswd = "testcert"; + waarpSecureKeyStore + .initTrustStore(file2.getAbsolutePath(), trustStorePasswd, true); - /** - * Test & example main - * - * @param args - * ignored - * @throws Exception - */ - public static void main(String[] args) throws Exception { - WaarpLoggerFactory.setDefaultFactory(new WaarpSlf4JLoggerFactory( - WaarpLogLevel.WARN)); - InetAddress addr; - byte[] loop = { 127, 0, 0, 1 }; - try { - addr = InetAddress.getByAddress(loop); - } catch (UnknownHostException e) { - return; - } - address = new InetSocketAddress(addr, port); - // Configure the client. - bootstrap = new Bootstrap(); - WaarpNettyUtil.setBootstrap(bootstrap, workerGroup, 30000); - // Configure the pipeline factory. - // First create the SSL part - WaarpSecureKeyStore WaarpSecureKeyStore; - // For empty KeyStore - if (keyStoreFilename == null) { - WaarpSecureKeyStore = - new WaarpSecureKeyStore(keyStorePasswd, keyPasswd); - } else { - WaarpSecureKeyStore = - new WaarpSecureKeyStore(keyStoreFilename, keyStorePasswd, keyPasswd); - } + // configure the server + ServerBootstrap bootstrapServer = new ServerBootstrap(); + WaarpNettyUtil.setServerBootstrap(bootstrapServer, workerGroup, 1000); - if (keyTrustStoreFilename != null) { - // Load the client TrustStore - WaarpSecureKeyStore.initTrustStore(keyTrustStoreFilename, keyTrustStorePasswd, false); - } else { - WaarpSecureKeyStore.initEmptyTrustStore(); - } - WaarpSslContextFactory waarpSslContextFactory = new WaarpSslContextFactory(WaarpSecureKeyStore, false); - localExecClientInitializer = - new LocalExecSslClientInitializer(waarpSslContextFactory); - bootstrap.handler(localExecClientInitializer); + // Configure the pipeline factory. + WaarpSslContextFactory waarpSslContextFactoryServer = + new WaarpSslContextFactory(waarpSecureKeyStore, true); + LocalExecSslServerInitializer localExecServerInitializer = + new LocalExecSslServerInitializer( + waarpSslContextFactoryServer, + LocalExecDefaultResult.MAXWAITPROCESS, executor); + bootstrapServer.childHandler(localExecServerInitializer); - try { - // Parse options. - LocalExecSslClientTest client = new LocalExecSslClientTest(); - // run once - long first = System.currentTimeMillis(); - if (client.connect()) { - client.runOnce(); - client.disconnect(); - } - long second = System.currentTimeMillis(); - // print time for one exec - System.err.println("1=Total time in ms: " + (second - first) + " or " + (1 * 1000 / (second - first)) - + " exec/s"); - System.err.println("Result: " + ok + ":" + ko); - ok = 0; - ko = 0; + // Bind and start to accept incoming connections only on local address. + ChannelFuture future = + bootstrapServer.bind(new InetSocketAddress(addr, port)); - // Now run multiple within one thread - first = System.currentTimeMillis(); - for (int i = 0; i < nit; i++) { - if (client.connect()) { - client.runOnce(); - client.disconnect(); - } - } - second = System.currentTimeMillis(); - // print time for one exec - System.err.println(nit + "=Total time in ms: " + (second - first) + " or " - + (nit * 1000 / (second - first)) + " exec/s"); - System.err.println("Result: " + ok + ":" + ko); - ok = 0; - ko = 0; + // Finalize client configuration + waarpSecureKeyStoreClient + .initTrustStore(file2.getAbsolutePath(), trustStorePasswd, false); + WaarpSslContextFactory waarpSslContextFactoryClient = + new WaarpSslContextFactory(waarpSecureKeyStoreClient); - // Now run multiple within multiple threads - // Create multiple threads - ExecutorService executorService = Executors.newFixedThreadPool(nth); - first = System.currentTimeMillis(); - // Starts all thread with a default number of execution - for (int i = 0; i < nth; i++) { - executorService.submit(new LocalExecSslClientTest()); - } - Thread.sleep(500); - executorService.shutdown(); - while (!executorService.awaitTermination(200, TimeUnit.MILLISECONDS)) { - Thread.sleep(50); - } - second = System.currentTimeMillis(); + localExecClientInitializer = + new LocalExecSslClientInitializer(waarpSslContextFactoryClient); + bootstrap.handler(localExecClientInitializer); - // print time for one exec - System.err.println((nit * nth) + "=Total time in ms: " + (second - first) + " or " - + (nit * nth * 1000 / (second - first)) + " exec/s"); - System.err.println("Result: " + ok + ":" + ko); - ok = 0; - ko = 0; + // Wait for the server + future.sync(); - // run once - first = System.currentTimeMillis(); - if (client.connect()) { - client.runFinal(); - client.disconnect(); - } - second = System.currentTimeMillis(); - // print time for one exec - System.err.println("1=Total time in ms: " + (second - first) + " or " + (1 * 1000 / (second - first)) - + " exec/s"); - System.err.println("Result: " + ok + ":" + ko); - ok = 0; - ko = 0; - } finally { - // Shut down all thread pools to exit. - workerGroup.shutdownGracefully(); - localExecClientInitializer.releaseResources(); + try { + // Parse options. + LocalExecSslClientTest client = new LocalExecSslClientTest(); + // run once + long first = System.currentTimeMillis(); + if (client.connect()) { + client.runOnce(); + client.disconnect(); + } + long second = System.currentTimeMillis(); + // print time for one exec + System.err.println("1=Total time in ms: " + (second - first) + " or " + + (1 * 1000 / (second - first)) + + " exec/s"); + System.err.println("Result: " + ok + ":" + ko); + ok = 0; + ko = 0; + + // Now run multiple within one thread + first = System.currentTimeMillis(); + for (int i = 0; i < nit; i++) { + if (client.connect()) { + client.runOnce(); + client.disconnect(); } - } + } + second = System.currentTimeMillis(); + // print time for one exec + System.err.println(nit + "=Total time in ms: " + (second - first) + " or " + + (nit * 1000 / (second - first)) + " exec/s"); + System.err.println("Result: " + ok + ":" + ko); + ok = 0; + ko = 0; - /** - * Simple constructor - */ - public LocalExecSslClientTest() { - } + // Now run multiple within multiple threads + // Create multiple threads + ExecutorService executorService = Executors.newFixedThreadPool(nth); + first = System.currentTimeMillis(); + // Starts all thread with a default number of execution + for (int i = 0; i < nth; i++) { + executorService.submit(new LocalExecSslClientTest()); + } + Thread.sleep(500); + executorService.shutdown(); + while (!executorService.awaitTermination(200, TimeUnit.MILLISECONDS)) { + Thread.sleep(50); + } + second = System.currentTimeMillis(); - private Channel channel; + // print time for one exec + System.err.println( + (nit * nth) + "=Total time in ms: " + (second - first) + " or " + + (nit * nth * 1000 / (second - first)) + " exec/s"); + System.err.println("Result: " + ok + ":" + ko); + ok = 0; + ko = 0; - /** - * Run method for thread - */ - public void run() { - if (connect()) { - for (int i = 0; i < nit; i++) { - this.runOnce(); - } - disconnect(); - } + // run once + first = System.currentTimeMillis(); + if (client.connect()) { + client.runFinal(); + client.disconnect(); + } + second = System.currentTimeMillis(); + // print time for one exec + System.err.println("1=Total time in ms: " + (second - first) + " or " + + (1 * 1000 / (second - first)) + + " exec/s"); + System.err.println("Result: " + ok + ":" + ko); + assertEquals(0, ko); + ok = 0; + ko = 0; + } finally { + future.channel().close(); + // Shut down all thread pools to exit. + localExecClientInitializer.releaseResources(); + localExecServerInitializer.releaseResources(); + // Shut down all thread pools to exit. + workerGroup.shutdownGracefully(); + localExecClientInitializer.releaseResources(); + LocalExecServerHandler.junitSetNotShutdown(); } + } - /** - * Connect to the Server - */ - private boolean connect() { - // Start the connection attempt. - ChannelFuture future = bootstrap.connect(address); + /** + * Connect to the Server + */ + private boolean connect() { + // Start the connection attempt. + ChannelFuture future = bootstrap.connect(address); - // Wait until the connection attempt succeeds or fails. - channel = WaarpSslUtility.waitforChannelReady(future); - if (channel == null) { - System.err.println("Client Not Connected"); - if (future.cause() != null) { - future.cause().printStackTrace(); - } - return false; - } - return true; + // Wait until the connection attempt succeeds or fails. + channel = WaarpSslUtility.waitforChannelReady(future); + if (channel == null) { + System.err.println("Client Not Connected"); + if (future.cause() != null) { + future.cause().printStackTrace(); + } + fail("Cannot connect"); + return false; } + return true; + } - /** - * Disconnect from the server - */ - private void disconnect() { - WaarpSslUtility.closingSslChannel(channel); - WaarpSslUtility.waitForClosingSslChannel(channel, 10000); + /** + * Run method both for not threaded execution and threaded execution + */ + private void runOnce() { + // Initialize the command context + LocalExecSslClientHandler clientHandler = + (LocalExecSslClientHandler) channel.pipeline().last(); + // Command to execute + String line = command + " " + atomicInteger.incrementAndGet(); + clientHandler.initExecClient(0, line); + // Wait for the end of the exec command + LocalExecResult localExecResult = clientHandler.waitFor(10000); + int status = localExecResult.getStatus(); + if (status < 0) { + System.err.println("Status: " + status + "\nResult: " + + localExecResult.getResult()); + ko++; + } else { + ok++; + result = localExecResult; } + } - /** - * Run method both for not threaded execution and threaded execution - */ - private void runOnce() { - // Initialize the command context - LocalExecSslClientHandler clientHandler = - (LocalExecSslClientHandler) channel.pipeline().last(); - // Command to execute - String line = command + " " + atomicInteger.incrementAndGet(); - clientHandler.initExecClient(0, line); - // Wait for the end of the exec command - LocalExecResult localExecResult = clientHandler.waitFor(10000); - int status = localExecResult.getStatus(); - if (status < 0) { - System.err.println("Status: " + status + "\nResult: " + - localExecResult.getResult()); - ko++; - } else { - ok++; - result = localExecResult; - } + /** + * Disconnect from the server + */ + private void disconnect() { + WaarpSslUtility.closingSslChannel(channel); + WaarpSslUtility.waitForClosingSslChannel(channel, 10000); + } + + /** + * Run method for closing Server + */ + private void runFinal() { + // Initialize the command context + LocalExecSslClientHandler clientHandler = + (LocalExecSslClientHandler) channel.pipeline().last(); + // Command to execute + clientHandler.initExecClient(-1000, "stop"); + // Wait for the end of the exec command + LocalExecResult localExecResult = clientHandler.waitFor(10000); + int status = localExecResult.getStatus(); + if (status < 0) { + System.err.println("Status: " + status + "\nResult: " + + localExecResult.getResult()); + ok++; + } else { + ok++; + result = localExecResult; } + } - /** - * Run method for closing Server - */ - private void runFinal() { - // Initialize the command context - LocalExecSslClientHandler clientHandler = - (LocalExecSslClientHandler) channel.pipeline().last(); - // Command to execute - clientHandler.initExecClient(-1000, "stop"); - // Wait for the end of the exec command - LocalExecResult localExecResult = clientHandler.waitFor(10000); - int status = localExecResult.getStatus(); - if (status < 0) { - System.err.println("Status: " + status + "\nResult: " + - localExecResult.getResult()); - ko++; - } else { - ok++; - result = localExecResult; - } + /** + * Run method for thread + */ + public void run() { + if (connect()) { + for (int i = 0; i < nit; i++) { + this.runOnce(); + } + disconnect(); } + } } diff --git a/src/test/java/org/waarp/commandexec/ssl/client/test/package-info.java b/src/test/java/org/waarp/commandexec/ssl/client/test/package-info.java index b21102c..7c04f89 100644 --- a/src/test/java/org/waarp/commandexec/ssl/client/test/package-info.java +++ b/src/test/java/org/waarp/commandexec/ssl/client/test/package-info.java @@ -1,7 +1,25 @@ -/** - * Classes implementing LocalExec Client test with SSL link +/******************************************************************************* + * This file is part of Waarp Project (named also Waarp or GG). + * + * Copyright (c) 2019, Waarp SAS, and individual contributors by the @author + * tags. See the COPYRIGHT.txt in the distribution for a full listing of + * individual contributors. + * + * All Waarp Project is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. * + * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. * + * You should have received a copy of the GNU General Public License along with + * Waarp . If not, see . + ******************************************************************************/ + +/** + * Classes implementing LocalExec Client test with SSL link */ package org.waarp.commandexec.ssl.client.test; diff --git a/src/test/resources/certs/testcert.jks b/src/test/resources/certs/testcert.jks new file mode 100755 index 0000000000000000000000000000000000000000..02f454e58913a80a38fc5b8684b0c53b271ecf85 GIT binary patch literal 3907 zcmd7Uc{J4P9|!RH&M@{pTSSq|SZBt}FtRk3A;8FI|KRV=G&h zGCvBHFk#YlZBZ0ix(u@1y7$&`e&=`Y@4rree9!mue!k~>&T~G`^E%J7ySTdu0)fDv zAEv+FEN(;}58q%U2m~k)E*TsGG21Dl;aCs|QVu6W%HU*hauEa!fWb_rg^qYQ7nDst z&M1z-1pv&12$vA%LcrlzBnE*(!QmJOVJ@T^TpfY50V^ z`jAMS9Rv5;>Ze=d(Yq0iDe(<_`fmq*lB;eSWqq$bZC^rf1h;M9>mzSc9}uo@vQkz* zoOY1nrq%WM)D4$Tq?h!pQ+uVtv0I7HdQsUe=68xh&bUx$%9fEK3(oDGh{sDz3*5Yb z_dI4}s^Z!sFa!VrCFXE5__5E6en^H11xG?z6`-Ft4hsbMR)N1&%-<^Lw~7VM{pDvK z0N7(uizU8=ao^x}|cS(}tMz1VZv&E)%c~LU*}`K!}riY*#GWo(=MzBI-$E zb?UV^dU)dEC}`x1v=Ij}_)OCH^0oVNIgAe_VJ3z;ueXLz38>k;oZ8bxw?cK6epZJP zZ2}Pi;&gqnkm~59NJ+146VK-*ED3sHW5-Yq#lh8A?o5lOez?cQ=)}6+=3qPbiYD#% z?C^aPnyC}iKiQ{vV<@IvN+w3#IE$L`ZhPpb_d+OTQpow~ODr9e(Lhfp}Idp>989#NUuvWB(QM;s0vmcc!xsvMLs-z7O(W@jK7v|3Rix)9HCJ zDZ+dNgRwHkuD0!Ma6j;=GO#Ph#bz)FNEGxl46sT+LWSdoLo8R-8{#4~CS?6*s1Q5y zatA*_iF!`d%xsQ52xdRH49WJNcCn|@uOfR$O}Blz;_~`S7@|3&hzMY}ijIA}tFzCE(Tc(>;`J&4o=a$%G~tPp2~o3g>MNet-X?R|RQ7g+OQE5axSnD^G%=G*5>RZCj%h!C`B?{vN&CrH z&e2JeigyAsX?pn!;<5}%1b0BXSf5c=^$175a%1vX^xKz)0Eu>^K@8nV1lOCoGuGu- z0iS*qmS;{4C`*yEt?Cl>!!{O+6Z^wS@@EoNYV_xsyjjB?)8YeTtnIjs#}f+ZefLP4_7vF#D|zSD z7h210)>-=Y2a*sDAXs=q-QyVX);bxxG(0?71k6D9V=ahJ*^}^`4vl3IWkKTrS1jLweuZ z>jv#u>Gy+oF=j;ZCVc4d@l2il2eRtl2Xb8Lzd$Z@%=|GRf4NyBkm#?Fzuc_9#2*ZD zdY6*YUFe5lMxb3{zlOu9)x&9XZ{$yEYXPvXWahOR-e}lyNM^R+oy+-MO6*=N53bqu zHQ!wrq&6NH_ykL|R?vH43}G588cG)B&r4I*9T5=8F*QD}0J zQIAYrJ2tJHSeA5I^t!I-R!c>^-y0|OsY6ep%B2kaU&G{2e=;7KUeO#OTWG zA&DK=&4{Ew7(sk;Bw2whL9(f;OvZ#dN3ch(FT6f(YE-giVBFcvSuEq@N0|hDA)oID zIhstk+sVf3JQ26&lAo&{`67BfUZP_55vpgKr|{6}+sk!#BN;12o1y)D-W1e&v+EHj z-}!MTDrU+UVs&PuY4q1H$ZAx@Z&@drCvUy)-G#s&xjiP>8GCoXdAl>i%qexXu3Bf> zZOm*%UGhV#nyg14E*5tzPlZX0XFMeJ1qKgdWg)jdd*1;hbEj@DZrHa6z4Vg5`@Vy3 z96BHz;Uk|JP8zQ(fMVNEJ{F~qf|pv3W(c+@!X$L*i4<*?0%(j(T1RQS@h`c9+XjM% zS3;lH)EfG`COx{atF@Q!tt!=KdqqY2EmDC8i$rTuwCH87t|m%8x}7(ICQn>+#Jda~ WDhIFSHsBUIBMRy#dN73N4SxdZTuX@n literal 0 HcmV?d00001 diff --git a/src/test/resources/certs/testsslnocert.jks b/src/test/resources/certs/testsslnocert.jks new file mode 100755 index 0000000000000000000000000000000000000000..c1224a2eb1cae2c95d583547f8e8bf05af2a516d GIT binary patch literal 2293 zcmcgtc{J4f8=miM&6u%oAv<9#^Bo$JvP5IaIus$h!B|SRDBIXZLY6d^LPXbEjHQdB zl&g{`+(C7TAF^iWT7K?5_jdZ@_xJCQ_nhZ>KIc8>JwZUq8CKtURO59C0yDIx(lfP-=a00IK1!M{-~8Ui83(Y~@C(cR{c z3lsCi`$RPpKm3joG(SHI-W;dv$2V5D%)854uQKKYwyZ+bz9{L~!Yu8=+|WnsQJRU> zR~%33wJJ@TM%dy_I&)-j-FRpOO?z)N4Dk!H2EG=HL*E5!87JTpwUeP$ty9-J{T z(aP0ar*3q($;su;2P!9tOG~xn;?A|)eu|K2x(|KoigBOqTLbICg{h(2C^?1Jk%H-c zjv&_5u~G$1R7|kRpv7Tv=ItRXs>6of`~Z+Lhmz8_nY7AJdb_GtcK)8Dsp`uaJ4CVo z10ODdro1uG2`qwe!8u=rM5B+EhlxT~nfVXGOIeNF_t(rn;R1V}%jATq;q#fBGgl)hHt@xOmlo(JDhfx-l32!84lHWcS)^@OwMBdnBMe($ls+Eu$-2u zaEpu9Pb7T2g-ZvCMWBJ9l`x`wY5L&3B3X9}`F`ja8WolmV*o}JgRcmTNPHemHqbK-rdj&iwN;Q}+ia~-*= zXN<#O@6ME~Se4}Ewmf>;h0A1n`Z1RHVTJA1a!q0%zMQ?~Oyae(Oz}0T5y&{7_f_iE z{)G_jsl&6dXZtF_1fw#pju?}d?u{*J^a6&IwQHQzPFqbXZ{X1~P}zaXjOX=m8N0X_ znaRPscs#`Tb)4J2NQ_(k)6~sh%Q6EMFpZMQIdOlSEJZpGqG%{?vDJJSo>-NMbl$Cb?`p4~G%{&UF>E^)*f5v$zaWZ-v`C8DxZ z1@paZB#xWg2Hz(bVPf@LOq+D^BI%xe<7AA+3oDGz=X`loP$8YPnfm#0~a+SU1aKO$(5`K)YeJry$3^5^_SXzd~p8cLg&l#5% zCB9n2l^osacD>6=j^2*z%cHa|>&;`XC+D%mM%QbL@@J>sYr8kJOb?m11#z|-iIIE+ z*ZlDkEq^?OIrE*CbvY@2ZACb(`un)8e)G&E(abVYT34#fJ-@eLJVHdWVAeR~I+~g` z?!R-J>z#ip!!7DDddQX6qk{smk+G-D+;gkx+idqio?s+$Cpbe>-<-7Nr2l;!5q2s> zP!j@y{Q}Zp4?r3;wFm|UpisE!J)Uro9f`&z8YOP9A6!QS1oR_dKr|9|4vyr7lE?%O z5c?NFVG)*kWMk`p`JsO~FUbAZI~$hG@RFa0XMmw=uqWXk5%a%9*v}#;Cdf_zK}}U6 zfk@CGsM!m$t7?G93E)5U|2z#1kowt%?i`lCu^zF0MD>f3)%IM}XH~wxGDtP(p*=o2ULzrAS z6OmkO8I={CpCI2JLE&D!p;kbuq&|{Xnz_T@wNY_C-$4caNZ=t=VX{{xE+E5@fLEE` zBv%5A?XM%A!skpJfU8?Kc5krb6gKCJQ+mUraV{zNT7wz5H!9rWR??L?8#@k@&o>H& zdg`OME>y|QS>v3SKb%@>waGEKz;}~i|4WL6Vah(j>o2ZH`Uh#v^~106a@bfK+W-7^Oj~XX^w@@AR3oA9>x9K1j*DZ#ni|FF|GLUUQ}W_OFs?Ll&oR zs&X``%gf=uCh0gtH>A5YJ}lvb$IHqt-5+S@$=9=-nDlgVH(TCG=l65tQ?xV$hP7kV zmGo_CM@R;&Q2$5ozmHO@mL@t#S#)Q}74e!An_ic*h9_+Ty5@!N)Nc1oc>6-oa^D4s zs8S|n>#|~C%w5;mxO!FZvi&Z|uC+lgbWh#P??@9VbU^pEj%qG^Y+;~GdC~PycU@U{ KlU<&0*na`jHnw^I literal 0 HcmV?d00001