mina-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From lgoldst...@apache.org
Subject [29/51] [abbrv] mina-sshd git commit: [SSHD-842] Split common utilities code from sshd-core into sshd-common (new artifact)
Date Thu, 06 Sep 2018 16:03:39 GMT
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrarTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrarTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrarTest.java
new file mode 100644
index 0000000..c2ce550
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrarTest.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.common.util.security.eddsa;
+
+import java.security.KeyFactory;
+import java.security.KeyPairGenerator;
+import java.security.Provider;
+import java.security.Signature;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+
+import org.apache.sshd.common.util.security.SecurityProviderRegistrar;
+import org.apache.sshd.common.util.security.SecurityProviderRegistrarTestSupport;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+import net.i2p.crypto.eddsa.EdDSASecurityProvider;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class EdDSASecurityProviderRegistrarTest extends SecurityProviderRegistrarTestSupport {
+    private static SecurityProviderRegistrar registrarInstance;
+
+    public EdDSASecurityProviderRegistrarTest() {
+        super();
+    }
+
+    @BeforeClass
+    public static void checkEDDSASupported() {
+        Assume.assumeTrue(SecurityUtils.isEDDSACurveSupported());
+        registrarInstance = new EdDSASecurityProviderRegistrar();
+    }
+
+    @Test
+    public void testSupportedSecurityEntities() {
+        assertSecurityEntitySupportState(getCurrentTestName(), registrarInstance, true, registrarInstance.getName(),
+                KeyPairGenerator.class, KeyFactory.class);
+        assertSecurityEntitySupportState(getCurrentTestName(), registrarInstance, true,
+                SecurityUtils.CURVE_ED25519_SHA512, Signature.class);
+
+        Collection<Class<?>> supported = new HashSet<>(Arrays.asList(KeyPairGenerator.class, KeyFactory.class, Signature.class));
+        for (Class<?> entity : SecurityProviderRegistrar.SECURITY_ENTITIES) {
+            if (supported.contains(entity)) {
+                continue;
+            }
+            assertFalse("Unexpected support for " + entity.getSimpleName(), registrarInstance.isSecurityEntitySupported(entity, registrarInstance.getName()));
+        }
+    }
+
+    @Test
+    public void testGetSecurityProvider() {
+        Provider expected = registrarInstance.getSecurityProvider();
+        assertNotNull("No provider created", expected);
+        assertEquals("Mismatched provider name", registrarInstance.getName(), expected.getName());
+        assertObjectInstanceOf("Mismatched provider type", EdDSASecurityProvider.class, expected);
+    }
+
+    @Test
+    public void testGetSecurityProviderCaching() {
+        testGetSecurityProviderCaching(getCurrentTestName(), registrarInstance);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProviderTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProviderTest.java b/sshd-common/src/test/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProviderTest.java
new file mode 100644
index 0000000..2978a18
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProviderTest.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.server.keyprovider;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.MethodSorters;
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Category({ NoIoTestCase.class })
+public class AbstractGeneratorHostKeyProviderTest extends JUnitTestSupport {
+    public AbstractGeneratorHostKeyProviderTest() {
+        super();
+    }
+
+    @SuppressWarnings("synthetic-access")
+    @Test
+    public void testOverwriteKey() throws Exception {
+        Path tempDir = assertHierarchyTargetFolderExists(getTempTargetFolder());
+        Path keyPairFile = tempDir.resolve(getCurrentTestName() + ".key");
+        Files.deleteIfExists(keyPairFile);
+
+        TestProvider provider = new TestProvider(keyPairFile);
+        provider.loadKeys();
+        assertEquals("Mismatched generate write count", 1, provider.getWriteCount());
+
+        provider = new TestProvider(keyPairFile);
+        provider.setOverwriteAllowed(false);
+        provider.loadKeys();
+        assertEquals("Mismatched load write count", 0, provider.getWriteCount());
+    }
+
+    private static final class TestProvider extends AbstractGeneratorHostKeyProvider {
+        private final AtomicInteger writes = new AtomicInteger(0);
+
+        private TestProvider(Path file) {
+            setKeySize(512);
+            setPath(file);
+        }
+
+        @Override
+        protected KeyPair doReadKeyPair(String resourceKey, InputStream inputStream) throws IOException, GeneralSecurityException {
+            return null;
+        }
+
+        @Override
+        protected void doWriteKeyPair(String resourceKey, KeyPair kp, OutputStream outputStream) throws IOException, GeneralSecurityException {
+            writes.incrementAndGet();
+        }
+
+        public int getWriteCount() {
+            return writes.get();
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/server/keyprovider/PEMGeneratorHostKeyProviderTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/server/keyprovider/PEMGeneratorHostKeyProviderTest.java b/sshd-common/src/test/java/org/apache/sshd/server/keyprovider/PEMGeneratorHostKeyProviderTest.java
new file mode 100644
index 0000000..3be5b6d
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/server/keyprovider/PEMGeneratorHostKeyProviderTest.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.server.keyprovider;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.ECGenParameterSpec;
+
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.Assume;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Category({ NoIoTestCase.class })
+public class PEMGeneratorHostKeyProviderTest extends JUnitTestSupport {
+    public PEMGeneratorHostKeyProviderTest() {
+        super();
+    }
+
+    @Test
+    public void testDSA() throws IOException {
+        Assume.assumeTrue("BouncyCastle not registered", SecurityUtils.isBouncyCastleRegistered());
+        testPEMGeneratorHostKeyProvider(KeyUtils.DSS_ALGORITHM, KeyPairProvider.SSH_DSS, 512, null);
+    }
+
+    @Test
+    public void testRSA() throws IOException {
+        Assume.assumeTrue("BouncyCastle not registered", SecurityUtils.isBouncyCastleRegistered());
+        testPEMGeneratorHostKeyProvider(KeyUtils.RSA_ALGORITHM, KeyPairProvider.SSH_RSA, 512, null);
+    }
+
+    @Test
+    public void testECnistp256() throws IOException {
+        Assume.assumeTrue("BouncyCastle not registered", SecurityUtils.isBouncyCastleRegistered());
+        Assume.assumeTrue("ECC not supported", SecurityUtils.isECCSupported());
+        Assume.assumeTrue(ECCurves.nistp256 + " N/A", ECCurves.nistp256.isSupported());
+        testPEMGeneratorHostKeyProvider(KeyUtils.EC_ALGORITHM, KeyPairProvider.ECDSA_SHA2_NISTP256, -1, new ECGenParameterSpec("prime256v1"));
+    }
+
+    @Test
+    public void testECnistp384() throws IOException {
+        Assume.assumeTrue("BouncyCastle not registered", SecurityUtils.isBouncyCastleRegistered());
+        Assume.assumeTrue("ECC not supported", SecurityUtils.isECCSupported());
+        Assume.assumeTrue(ECCurves.nistp384 + " N/A", ECCurves.nistp384.isSupported());
+        testPEMGeneratorHostKeyProvider(KeyUtils.EC_ALGORITHM, KeyPairProvider.ECDSA_SHA2_NISTP384, -1, new ECGenParameterSpec("P-384"));
+    }
+
+    @Test
+    public void testECnistp521() throws IOException {
+        Assume.assumeTrue("BouncyCastle not registered", SecurityUtils.isBouncyCastleRegistered());
+        Assume.assumeTrue("ECC not supported", SecurityUtils.isECCSupported());
+        Assume.assumeTrue(ECCurves.nistp521 + " N/A", ECCurves.nistp521.isSupported());
+        testPEMGeneratorHostKeyProvider(KeyUtils.EC_ALGORITHM, KeyPairProvider.ECDSA_SHA2_NISTP521, -1, new ECGenParameterSpec("P-521"));
+    }
+
+    private Path testPEMGeneratorHostKeyProvider(String algorithm, String keyType, int keySize, AlgorithmParameterSpec keySpec) throws IOException {
+        Path path = initKeyFileLocation(algorithm);
+        KeyPair kpWrite = invokePEMGeneratorHostKeyProvider(path, algorithm, keyType, keySize, keySpec);
+        assertTrue("Key file not generated: " + path, Files.exists(path, IoUtils.EMPTY_LINK_OPTIONS));
+
+        KeyPair kpRead = invokePEMGeneratorHostKeyProvider(path, algorithm, keyType, keySize, keySpec);
+        PublicKey pubWrite = kpWrite.getPublic();
+        PublicKey pubRead = kpRead.getPublic();
+        if (pubWrite instanceof ECPublicKey) {
+            // The algorithm is reported as ECDSA instead of EC
+            assertECPublicKeyEquals("Mismatched EC public key", ECPublicKey.class.cast(pubWrite), ECPublicKey.class.cast(pubRead));
+        } else {
+            assertKeyEquals("Mismatched public keys", pubWrite, pubRead);
+        }
+        return path;
+    }
+
+    private static KeyPair invokePEMGeneratorHostKeyProvider(Path path, String algorithm, String keyType, int keySize, AlgorithmParameterSpec keySpec) {
+        AbstractGeneratorHostKeyProvider provider = SecurityUtils.createGeneratorHostKeyProvider(path.toAbsolutePath().normalize());
+        provider.setAlgorithm(algorithm);
+        provider.setOverwriteAllowed(true);
+        if (keySize > 0) {
+            provider.setKeySize(keySize);
+        }
+        if (keySpec != null) {
+            provider.setKeySpec(keySpec);
+        }
+
+        return validateKeyPairProvider(provider, keyType);
+    }
+
+    private static KeyPair validateKeyPairProvider(KeyPairProvider provider, String keyType) {
+        Iterable<String> types = provider.getKeyTypes();
+        KeyPair kp = null;
+        for (String type : types) {
+            if (keyType.equals(type)) {
+                kp = provider.loadKey(keyType);
+                assertNotNull("Failed to load key for " + keyType, kp);
+                break;
+            }
+        }
+
+        assertNotNull("Expected key type not found: " + keyType, kp);
+        return kp;
+    }
+
+    private Path initKeyFileLocation(String algorithm) throws IOException {
+        Path path = assertHierarchyTargetFolderExists(getTempTargetRelativeFile(getClass().getSimpleName()));
+        path = path.resolve(algorithm + "-PEM.key");
+        Files.deleteIfExists(path);
+        return path;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/server/keyprovider/SimpleGeneratorHostKeyProviderTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/server/keyprovider/SimpleGeneratorHostKeyProviderTest.java b/sshd-common/src/test/java/org/apache/sshd/server/keyprovider/SimpleGeneratorHostKeyProviderTest.java
new file mode 100644
index 0000000..dc5e9fd
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/server/keyprovider/SimpleGeneratorHostKeyProviderTest.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.server.keyprovider;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.ECGenParameterSpec;
+
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.Assume;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.MethodSorters;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Category({ NoIoTestCase.class })
+public class SimpleGeneratorHostKeyProviderTest extends JUnitTestSupport {
+    public SimpleGeneratorHostKeyProviderTest() {
+        super();
+    }
+
+    @Test
+    public void testDSA() throws IOException {
+        testSimpleGeneratorHostKeyProvider(KeyUtils.DSS_ALGORITHM, KeyPairProvider.SSH_DSS, 512, null);
+    }
+
+    @Test
+    public void testRSA() throws IOException {
+        testSimpleGeneratorHostKeyProvider(KeyUtils.RSA_ALGORITHM, KeyPairProvider.SSH_RSA, 512, null);
+    }
+
+    @Test
+    public void testECnistp256() throws IOException {
+        Assume.assumeTrue("BouncyCastle not registered", SecurityUtils.isBouncyCastleRegistered());
+        Assume.assumeTrue("ECC not supported", SecurityUtils.isECCSupported());
+        Assume.assumeTrue(ECCurves.nistp256 + " N/A", ECCurves.nistp256.isSupported());
+        testSimpleGeneratorHostKeyProvider(KeyUtils.EC_ALGORITHM, KeyPairProvider.ECDSA_SHA2_NISTP256, -1, new ECGenParameterSpec("prime256v1"));
+    }
+
+    @Test
+    public void testECnistp384() throws IOException {
+        Assume.assumeTrue("BouncyCastle not registered", SecurityUtils.isBouncyCastleRegistered());
+        Assume.assumeTrue("ECC not supported", SecurityUtils.isECCSupported());
+        Assume.assumeTrue(ECCurves.nistp384 + " N/A", ECCurves.nistp384.isSupported());
+        testSimpleGeneratorHostKeyProvider(KeyUtils.EC_ALGORITHM, KeyPairProvider.ECDSA_SHA2_NISTP384, -1, new ECGenParameterSpec("P-384"));
+    }
+
+    @Test
+    public void testECnistp521() throws IOException {
+        Assume.assumeTrue("BouncyCastle not registered", SecurityUtils.isBouncyCastleRegistered());
+        Assume.assumeTrue("ECC not supported", SecurityUtils.isECCSupported());
+        Assume.assumeTrue(ECCurves.nistp521 + " N/A", ECCurves.nistp521.isSupported());
+        testSimpleGeneratorHostKeyProvider(KeyUtils.EC_ALGORITHM, KeyPairProvider.ECDSA_SHA2_NISTP521, -1, new ECGenParameterSpec("P-521"));
+    }
+
+    private Path testSimpleGeneratorHostKeyProvider(String algorithm, String keyType, int keySize, AlgorithmParameterSpec keySpec) throws IOException {
+        Path path = initKeyFileLocation(algorithm);
+        KeyPair kpWrite = invokeSimpleGeneratorHostKeyProvider(path, algorithm, keyType, keySize, keySpec);
+        assertTrue("Key file not generated: " + path, Files.exists(path, IoUtils.EMPTY_LINK_OPTIONS));
+
+        KeyPair kpRead = invokeSimpleGeneratorHostKeyProvider(path, algorithm, keyType, keySize, keySpec);
+        assertKeyPairEquals("Mismatched write/read key pairs", kpWrite, kpRead);
+        return path;
+    }
+
+    private static KeyPair invokeSimpleGeneratorHostKeyProvider(Path path, String algorithm, String keyType, int keySize, AlgorithmParameterSpec keySpec) {
+        SimpleGeneratorHostKeyProvider provider = new SimpleGeneratorHostKeyProvider();
+        provider.setAlgorithm(algorithm);
+        provider.setOverwriteAllowed(true);
+        provider.setPath(path);
+        if (keySize > 0) {
+            provider.setKeySize(keySize);
+        }
+        if (keySpec != null) {
+            provider.setKeySpec(keySpec);
+        }
+
+        return validateKeyPairProvider(provider, keyType);
+    }
+
+    private static KeyPair validateKeyPairProvider(KeyPairProvider provider, String keyType) {
+        Iterable<String> types = provider.getKeyTypes();
+        KeyPair kp = null;
+        for (String type : types) {
+            if (keyType.equals(type)) {
+                kp = provider.loadKey(keyType);
+                assertNotNull("Failed to load key for " + keyType, kp);
+                break;
+            }
+        }
+
+        assertNotNull("Expected key type not found: " + keyType, kp);
+        return kp;
+    }
+
+    private Path initKeyFileLocation(String algorithm) throws IOException {
+        Path path = assertHierarchyTargetFolderExists(getTempTargetRelativeFile(getClass().getSimpleName()));
+        path = path.resolve(algorithm + "-simple.key");
+        Files.deleteIfExists(path);
+        return path;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/util/test/CommonTestSupportUtils.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/util/test/CommonTestSupportUtils.java b/sshd-common/src/test/java/org/apache/sshd/util/test/CommonTestSupportUtils.java
new file mode 100644
index 0000000..7ae3703
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/util/test/CommonTestSupportUtils.java
@@ -0,0 +1,620 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.util.test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.security.CodeSource;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.ProtectionDomain;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.sshd.common.Factory;
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
+import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.keyprovider.KeyPairProviderHolder;
+import org.apache.sshd.common.random.Random;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class CommonTestSupportUtils {
+    /**
+     * URL/URI scheme that refers to a file
+     */
+    public static final String FILE_URL_SCHEME = "file";
+    /**
+     * Prefix used in URL(s) that reference a file resource
+     */
+    public static final String FILE_URL_PREFIX = FILE_URL_SCHEME + ":";
+
+    /**
+     * Separator used in URL(s) that reference a resource inside a JAR
+     * to denote the sub-path inside the JAR
+     */
+    public static final char RESOURCE_SUBPATH_SEPARATOR = '!';
+
+    /**
+     * Suffix of JAR files
+     */
+    public static final String JAR_FILE_SUFFIX = ".jar";
+
+    /**
+     * URL/URI scheme that refers to a JAR
+     */
+    public static final String JAR_URL_SCHEME = "jar";
+
+    /**
+     * Prefix used in URL(s) that reference a resource inside a JAR
+     */
+    public static final String JAR_URL_PREFIX = JAR_URL_SCHEME + ":";
+
+    /**
+     * Suffix of compile Java class files
+     */
+    public static final String CLASS_FILE_SUFFIX = ".class";
+
+    public static final List<String> TARGET_FOLDER_NAMES =    // NOTE: order is important
+        Collections.unmodifiableList(
+            Arrays.asList("target" /* Maven */, "build" /* Gradle */));
+
+    public static final String DEFAULT_TEST_HOST_KEY_PROVIDER_ALGORITHM = KeyUtils.RSA_ALGORITHM;
+    // uses a cached instance to avoid re-creating the keys as it is a time-consuming effort
+    private static final AtomicReference<KeyPairProvider> KEYPAIR_PROVIDER_HOLDER = new AtomicReference<>();
+    // uses a cached instance to avoid re-creating the keys as it is a time-consuming effort
+    private static final Map<String, FileKeyPairProvider> PROVIDERS_MAP = new ConcurrentHashMap<>();
+
+    private CommonTestSupportUtils() {
+        throw new UnsupportedOperationException("No instance allowed");
+    }
+
+    /**
+     * @param clazz A {@link Class} object
+     * @return A {@link URI} to the location of the class bytes container
+     * - e.g., the root folder, the containing JAR, etc.. Returns
+     * {@code null} if location could not be resolved
+     * @throws URISyntaxException if location is not a valid URI
+     * @see #getClassContainerLocationURL(Class)
+     */
+    public static URI getClassContainerLocationURI(Class<?> clazz) throws URISyntaxException {
+        URL url = getClassContainerLocationURL(clazz);
+        return (url == null) ? null : url.toURI();
+    }
+
+    /**
+     * @param clazz A {@link Class} object
+     * @return A {@link URL} to the location of the class bytes container
+     * - e.g., the root folder, the containing JAR, etc.. Returns
+     * {@code null} if location could not be resolved
+     */
+    public static URL getClassContainerLocationURL(Class<?> clazz) {
+        ProtectionDomain pd = clazz.getProtectionDomain();
+        CodeSource cs = (pd == null) ? null : pd.getCodeSource();
+        URL url = (cs == null) ? null : cs.getLocation();
+        if (url == null) {
+            url = getClassBytesURL(clazz);
+            if (url == null) {
+                return null;
+            }
+
+            String srcForm = getURLSource(url);
+            if (GenericUtils.isEmpty(srcForm)) {
+                return null;
+            }
+
+            try {
+                url = new URL(srcForm);
+            } catch (MalformedURLException e) {
+                throw new IllegalArgumentException("getClassContainerLocationURL(" + clazz.getName() + ")"
+                        + " Failed to create URL=" + srcForm + " from " + url.toExternalForm()
+                        + ": " + e.getMessage());
+            }
+        }
+
+        return url;
+    }
+
+    /**
+     * @param uri The {@link URI} value - ignored if {@code null}
+     * @return The URI(s) source path where {@link #JAR_URL_PREFIX} and
+     * any sub-resource are stripped
+     * @see #getURLSource(String)
+     */
+    public static String getURLSource(URI uri) {
+        return getURLSource((uri == null) ? null : uri.toString());
+    }
+
+    /**
+     * @param url The {@link URL} value - ignored if {@code null}
+     * @return The URL(s) source path where {@link #JAR_URL_PREFIX} and
+     * any sub-resource are stripped
+     * @see #getURLSource(String)
+     */
+    public static String getURLSource(URL url) {
+        return getURLSource((url == null) ? null : url.toExternalForm());
+    }
+
+    /**
+     * @param externalForm The {@link URL#toExternalForm()} string - ignored if
+     *                     {@code null}/empty
+     * @return The URL(s) source path where {@link #JAR_URL_PREFIX} and
+     * any sub-resource are stripped
+     */
+    public static String getURLSource(String externalForm) {
+        String url = externalForm;
+        if (GenericUtils.isEmpty(url)) {
+            return url;
+        }
+
+        url = stripJarURLPrefix(externalForm);
+        if (GenericUtils.isEmpty(url)) {
+            return url;
+        }
+
+        int sepPos = url.indexOf(RESOURCE_SUBPATH_SEPARATOR);
+        if (sepPos < 0) {
+            return adjustURLPathValue(url);
+        } else {
+            return adjustURLPathValue(url.substring(0, sepPos));
+        }
+    }
+
+    /**
+     * @param url A {@link URL} - ignored if {@code null}
+     * @return The path after stripping any trailing '/' provided the path
+     * is not '/' itself
+     * @see #adjustURLPathValue(String)
+     */
+    public static String adjustURLPathValue(URL url) {
+        return adjustURLPathValue((url == null) ? null : url.getPath());
+    }
+
+    /**
+     * @param path A URL path value - ignored if {@code null}/empty
+     * @return The path after stripping any trailing '/' provided the path
+     * is not '/' itself
+     */
+    public static String adjustURLPathValue(final String path) {
+        final int pathLen = (path == null) ? 0 : path.length();
+        if ((pathLen <= 1) || (path.charAt(pathLen - 1) != '/')) {
+            return path;
+        }
+
+        return path.substring(0, pathLen - 1);
+    }
+
+    public static String stripJarURLPrefix(String externalForm) {
+        String url = externalForm;
+        if (GenericUtils.isEmpty(url)) {
+            return url;
+        }
+
+        if (url.startsWith(JAR_URL_PREFIX)) {
+            return url.substring(JAR_URL_PREFIX.length());
+        }
+
+        return url;
+    }
+
+    /**
+     * @param clazz The request {@link Class}
+     * @return A {@link URL} to the location of the <code>.class</code> file
+     * - {@code null} if location could not be resolved
+     */
+    public static URL getClassBytesURL(Class<?> clazz) {
+        String className = clazz.getName();
+        int sepPos = className.indexOf('$');
+        // if this is an internal class, then need to use its parent as well
+        if (sepPos > 0) {
+            sepPos = className.lastIndexOf('.');
+            if (sepPos > 0) {
+                className = className.substring(sepPos + 1);
+            }
+        } else {
+            className = clazz.getSimpleName();
+        }
+
+        return clazz.getResource(className + CLASS_FILE_SUFFIX);
+    }
+
+    public static String getClassBytesResourceName(Class<?> clazz) {
+        return getClassBytesResourceName((clazz == null) ? null : clazz.getName());
+    }
+
+    /**
+     * @param name The fully qualified class name - ignored if {@code null}/empty
+     * @return The relative path of the class file byte-code resource
+     */
+    public static String getClassBytesResourceName(String name) {
+        if (GenericUtils.isEmpty(name)) {
+            return name;
+        } else {
+            return name.replace('.', '/') + CLASS_FILE_SUFFIX;
+        }
+    }
+
+    public static Path resolve(Path root, String... children) {
+        if (GenericUtils.isEmpty(children)) {
+            return root;
+        } else {
+            return resolve(root, Arrays.asList(children));
+        }
+    }
+
+    public static Path resolve(Path root, Collection<String> children) {
+        Path path = root;
+        if (!GenericUtils.isEmpty(children)) {
+            for (String child : children) {
+                path = path.resolve(child);
+            }
+        }
+
+        return path;
+    }
+
+    /**
+     * @param anchor An anchor {@link Class} whose container we want to use
+     * as the starting point for the &quot;target&quot; folder lookup up the
+     * hierarchy
+     * @return The &quot;target&quot; <U>folder</U> - {@code null} if not found
+     * @see #detectTargetFolder(File)
+     */
+    public static File detectTargetFolder(Class<?> anchor) {
+        return detectTargetFolder(getClassContainerLocationFile(anchor));
+    }
+
+    /**
+     * @param clazz A {@link Class} object
+     * @return A {@link File} of the location of the class bytes container
+     * - e.g., the root folder, the containing JAR, etc.. Returns
+     * {@code null} if location could not be resolved
+     * @throws IllegalArgumentException If location is not a valid {@link File} location
+     * @see #getClassContainerLocationURI(Class)
+     * @see #toFileSource(URI)
+     */
+    public static File getClassContainerLocationFile(Class<?> clazz)
+            throws IllegalArgumentException {
+        try {
+            URI uri = getClassContainerLocationURI(clazz);
+            return toFileSource(uri);
+        } catch (URISyntaxException | MalformedURLException e) {
+            throw new IllegalArgumentException(e.getClass().getSimpleName() + ": " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Converts a {@link URL} that may refer to an internal resource to
+     * a {@link File} representing is &quot;source&quot; container (e.g.,
+     * if it is a resource in a JAR, then the result is the JAR's path)
+     *
+     * @param url The {@link URL} - ignored if {@code null}
+     * @return The matching {@link File}
+     * @throws MalformedURLException If source URL does not refer to a file location
+     * @see #toFileSource(URI)
+     */
+    public static File toFileSource(URL url) throws MalformedURLException {
+        if (url == null) {
+            return null;
+        }
+
+        try {
+            return toFileSource(url.toURI());
+        } catch (URISyntaxException e) {
+            throw new MalformedURLException("toFileSource(" + url.toExternalForm() + ")"
+                    + " cannot (" + e.getClass().getSimpleName() + ")"
+                    + " convert to URI: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Converts a {@link URI} that may refer to an internal resource to
+     * a {@link File} representing is &quot;source&quot; container (e.g.,
+     * if it is a resource in a JAR, then the result is the JAR's path)
+     *
+     * @param uri The {@link URI} - ignored if {@code null}
+     * @return The matching {@link File}
+     * @throws MalformedURLException If source URI does not refer to a file location
+     * @see #getURLSource(URI)
+     */
+    public static File toFileSource(URI uri) throws MalformedURLException {
+        String src = getURLSource(uri);
+        if (GenericUtils.isEmpty(src)) {
+            return null;
+        }
+
+        if (!src.startsWith(FILE_URL_PREFIX)) {
+            throw new MalformedURLException("toFileSource(" + src + ") not a '" + FILE_URL_SCHEME + "' scheme");
+        }
+
+        try {
+            return new File(new URI(src));
+        } catch (URISyntaxException e) {
+            throw new MalformedURLException("toFileSource(" + src + ")"
+                    + " cannot (" + e.getClass().getSimpleName() + ")"
+                    + " convert to URI: " + e.getMessage());
+        }
+    }
+
+    /**
+     * @param anchorFile An anchor {@link File} we want to use
+     * as the starting point for the &quot;target&quot; or &quot;build&quot; folder
+     * lookup up the hierarchy
+     * @return The &quot;target&quot; <U>folder</U> - {@code null} if not found
+     */
+    public static File detectTargetFolder(File anchorFile) {
+        for (File file = anchorFile; file != null; file = file.getParentFile()) {
+            if (!file.isDirectory()) {
+                continue;
+            }
+
+            String name = file.getName();
+            if (TARGET_FOLDER_NAMES.contains(name)) {
+                return file;
+            }
+        }
+
+        return null;
+    }
+
+    public static KeyPair generateKeyPair(String algorithm, int keySize) throws GeneralSecurityException {
+        KeyPairGenerator gen = SecurityUtils.getKeyPairGenerator(algorithm);
+        if (KeyUtils.EC_ALGORITHM.equalsIgnoreCase(algorithm)) {
+            ECCurves curve = ECCurves.fromCurveSize(keySize);
+            if (curve == null) {
+                throw new InvalidKeySpecException("Unknown curve for key size=" + keySize);
+            }
+            gen.initialize(curve.getParameters());
+        } else {
+            gen.initialize(keySize);
+        }
+
+        return gen.generateKeyPair();
+    }
+
+    public static KeyPairProvider createTestHostKeyProvider(Class<?> anchor) {
+        KeyPairProvider provider = KEYPAIR_PROVIDER_HOLDER.get();
+        if (provider != null) {
+            return provider;
+        }
+
+        File targetFolder = Objects.requireNonNull(CommonTestSupportUtils.detectTargetFolder(anchor), "Failed to detect target folder");
+        File file = new File(targetFolder, "hostkey." + DEFAULT_TEST_HOST_KEY_PROVIDER_ALGORITHM.toLowerCase());
+        provider = createTestHostKeyProvider(file);
+
+        KeyPairProvider prev = KEYPAIR_PROVIDER_HOLDER.getAndSet(provider);
+        if (prev != null) { // check if somebody else beat us to it
+            return prev;
+        } else {
+            return provider;
+        }
+    }
+
+    public static KeyPairProvider createTestHostKeyProvider(File file) {
+        return createTestHostKeyProvider(Objects.requireNonNull(file, "No file").toPath());
+    }
+
+    public static KeyPairProvider createTestHostKeyProvider(Path path) {
+        SimpleGeneratorHostKeyProvider keyProvider = new SimpleGeneratorHostKeyProvider();
+        keyProvider.setPath(Objects.requireNonNull(path, "No path"));
+        keyProvider.setAlgorithm(DEFAULT_TEST_HOST_KEY_PROVIDER_ALGORITHM);
+        return validateKeyPairProvider(keyProvider);
+    }
+
+    public static KeyPair getFirstKeyPair(KeyPairProviderHolder holder) {
+        return getFirstKeyPair(Objects.requireNonNull(holder, "No holder").getKeyPairProvider());
+    }
+
+    public static KeyPair getFirstKeyPair(KeyIdentityProvider provider) {
+        Objects.requireNonNull(provider, "No key pair provider");
+        Iterable<? extends KeyPair> pairs = Objects.requireNonNull(provider.loadKeys(), "No loaded keys");
+        Iterator<? extends KeyPair> iter = Objects.requireNonNull(pairs.iterator(), "No keys iterator");
+        ValidateUtils.checkTrue(iter.hasNext(), "Empty loaded kyes iterator");
+        return Objects.requireNonNull(iter.next(), "No key pair in iterator");
+    }
+
+    private static File getFile(String resource) {
+        URL url = CommonTestSupportUtils.class.getClassLoader().getResource(resource);
+        try {
+            return new File(url.toURI());
+        } catch (URISyntaxException e) {
+            return new File(url.getPath());
+        }
+    }
+
+    public static File resolve(File root, String... children) {
+        if (GenericUtils.isEmpty(children)) {
+            return root;
+        } else {
+            return resolve(root, Arrays.asList(children));
+        }
+    }
+
+    public static File resolve(File root, Collection<String> children) {
+        File path = root;
+        if (!GenericUtils.isEmpty(children)) {
+            for (String child : children) {
+                path = new File(path, child);
+            }
+        }
+
+        return path;
+    }
+
+    /**
+     * Removes the specified file - if it is a directory, then its children
+     * are deleted recursively and then the directory itself. <B>Note:</B>
+     * no attempt is made to make sure that {@link File#delete()} was successful
+     *
+     * @param file The {@link File} to be deleted - ignored if {@code null}
+     *             or does not exist anymore
+     * @return The <tt>file</tt> argument
+     */
+    public static File deleteRecursive(File file) {
+        if ((file == null) || (!file.exists())) {
+            return file;
+        }
+
+        if (file.isDirectory()) {
+            File[] children = file.listFiles();
+            if (!GenericUtils.isEmpty(children)) {
+                for (File child : children) {
+                    deleteRecursive(child);
+                }
+            }
+        }
+
+        // seems that if a file is not writable it cannot be deleted
+        if (!file.canWrite()) {
+            file.setWritable(true, false);
+        }
+
+        if (!file.delete()) {
+            System.err.append("Failed to delete ").println(file.getAbsolutePath());
+        }
+
+        return file;
+    }
+
+    /**
+     * Removes the specified file - if it is a directory, then its children
+     * are deleted recursively and then the directory itself.
+     *
+     * @param path    The file {@link Path} to be deleted - ignored if {@code null}
+     *                or does not exist anymore
+     * @param options The {@link LinkOption}s to use
+     * @return The <tt>path</tt> argument
+     * @throws IOException If failed to access/remove some file(s)
+     */
+    public static Path deleteRecursive(Path path, LinkOption... options) throws IOException {
+        if ((path == null) || (!Files.exists(path))) {
+            return path;
+        }
+
+        if (Files.isDirectory(path)) {
+            try (DirectoryStream<Path> ds = Files.newDirectoryStream(path)) {
+                for (Path child : ds) {
+                    deleteRecursive(child, options);
+                }
+            }
+        }
+
+        try {
+            // seems that if a file is not writable it cannot be deleted
+            if (!Files.isWritable(path)) {
+                path.toFile().setWritable(true, false);
+            }
+            Files.delete(path);
+        } catch (IOException e) {
+            // same logic as deleteRecursive(File) which does not check if deletion succeeded
+            System.err.append("Failed (").append(e.getClass().getSimpleName()).append(")")
+                    .append(" to delete ").append(path.toString())
+                    .append(": ").println(e.getMessage());
+        }
+
+        return path;
+    }
+
+    public static String resolveRelativeRemotePath(Path root, Path file) {
+        Path relPath = root.relativize(file);
+        return relPath.toString().replace(File.separatorChar, '/');
+    }
+
+    public static FileKeyPairProvider createTestKeyPairProvider(String resource) {
+        File file = getFile(resource);
+        String filePath = file.getAbsolutePath();
+        FileKeyPairProvider provider = PROVIDERS_MAP.get(filePath);
+        if (provider != null) {
+            return provider;
+        }
+
+        provider = new FileKeyPairProvider();
+        provider.setFiles(Collections.singletonList(file));
+        provider = validateKeyPairProvider(provider);
+
+        FileKeyPairProvider prev = PROVIDERS_MAP.put(filePath, provider);
+        if (prev != null) { // check if somebody else beat us to it
+            return prev;
+        } else {
+            return provider;
+        }
+    }
+
+    private static <P extends KeyIdentityProvider> P validateKeyPairProvider(P provider) {
+        Objects.requireNonNull(provider, "No provider");
+
+        // get the I/O out of the way
+        Iterable<KeyPair> keys = Objects.requireNonNull(provider.loadKeys(), "No keys loaded");
+        if (keys instanceof Collection<?>) {
+            ValidateUtils.checkNotNullAndNotEmpty((Collection<?>) keys, "Empty keys loaded");
+        }
+
+        return provider;
+    }
+
+    public static Random getRandomizerInstance() {
+        Factory<Random> factory = SecurityUtils.getRandomFactory();
+        return factory.create();
+    }
+
+    /**
+     * @param path The {@link Path} to write the data to
+     * @param data The data to write (as UTF-8)
+     * @return The UTF-8 data bytes
+     * @throws IOException If failed to write
+     */
+    public static byte[] writeFile(Path path, String data) throws IOException {
+        try (OutputStream fos = Files.newOutputStream(path, IoUtils.EMPTY_OPEN_OPTIONS)) {
+            byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
+            fos.write(bytes);
+            return bytes;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/util/test/JUnit4ClassRunnerWithParameters.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnit4ClassRunnerWithParameters.java b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnit4ClassRunnerWithParameters.java
new file mode 100644
index 0000000..acf44a5
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnit4ClassRunnerWithParameters.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.util.test;
+
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParameters;
+import org.junit.runners.parameterized.TestWithParameters;
+
+/**
+ * Uses a cached created instance instead of a new one on every call of {@code #createTest()}
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class JUnit4ClassRunnerWithParameters extends BlockJUnit4ClassRunnerWithParameters {
+    private volatile Object testInstance;
+
+    public JUnit4ClassRunnerWithParameters(TestWithParameters test) throws InitializationError {
+        super(test);
+    }
+
+    @Override
+    public Object createTest() throws Exception {
+        synchronized (this) {
+            if (testInstance == null) {
+                testInstance = super.createTest();
+            }
+        }
+
+        return testInstance;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/util/test/JUnit4ClassRunnerWithParametersFactory.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnit4ClassRunnerWithParametersFactory.java b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnit4ClassRunnerWithParametersFactory.java
new file mode 100644
index 0000000..12ab252
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnit4ClassRunnerWithParametersFactory.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.util.test;
+
+import org.junit.runner.Runner;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.parameterized.ParametersRunnerFactory;
+import org.junit.runners.parameterized.TestWithParameters;
+
+/**
+ * Avoids re-creating a test class instance for each parameterized test method. Usage:
+ *
+ * <PRE><code>
+ * @FixMethodOrder(MethodSorters.NAME_ASCENDING)
+ * @RunWith(Parameterized.class)
+ * @UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class)
+ * public class MyParameterizedTest {
+ *      public MyParameterizedTest(...params...) {
+ *          ....
+ *      }
+ *
+ *      @Parameters(...)
+ *      public static List<Object[]> parameters() {
+ *          ...
+ *      }
+ * }
+ * </code></PRE>
+ *
+ * @see JUnit4ClassRunnerWithParameters
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class JUnit4ClassRunnerWithParametersFactory implements ParametersRunnerFactory {
+    public JUnit4ClassRunnerWithParametersFactory() {
+        super();
+    }
+
+    @Override
+    public Runner createRunnerForTestWithParameters(TestWithParameters test) throws InitializationError {
+        return new JUnit4ClassRunnerWithParameters(test);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/util/test/JUnit4SingleInstanceClassRunner.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnit4SingleInstanceClassRunner.java b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnit4SingleInstanceClassRunner.java
new file mode 100644
index 0000000..a8f11ce
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnit4SingleInstanceClassRunner.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.util.test;
+
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.TestClass;
+
+/**
+ * @see <A HREF="https://issues.apache.org/jira/browse/SSHD-764">SSHD-764</A>
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class JUnit4SingleInstanceClassRunner extends BlockJUnit4ClassRunner {
+    private final AtomicReference<Map.Entry<Class<?>, ?>> testHolder = new AtomicReference<>();
+
+    public JUnit4SingleInstanceClassRunner(Class<?> klass) throws InitializationError {
+        super(klass);
+    }
+
+    @Override
+    protected Object createTest() throws Exception {
+        Map.Entry<Class<?>, ?> lastTest = testHolder.get();
+        Class<?> lastTestClass = (lastTest == null) ? null : lastTest.getKey();
+        TestClass curTest = getTestClass();
+        Class<?> curTestClass = curTest.getJavaClass();
+        if (curTestClass == lastTestClass) {
+            return lastTest.getValue();
+        }
+
+        Object instance = super.createTest();
+        testHolder.set(new SimpleImmutableEntry<>(curTestClass, instance));
+        return instance;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
new file mode 100644
index 0000000..a854699
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
@@ -0,0 +1,572 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.util.test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.ECField;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.EllipticCurve;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@RunWith(JUnit4SingleInstanceClassRunner.class)
+public abstract class JUnitTestSupport extends Assert {
+    public static final String TEMP_SUBFOLDER_NAME = "temp";
+    public static final boolean OUTPUT_DEBUG_MESSAGES =
+        Boolean.parseBoolean(System.getProperty("org.apache.sshd.test.outputDebugMessages", "false"));
+    public static final String MAIN_SUBFOLDER = "main";
+    public static final String TEST_SUBFOLDER = "test";
+    public static final String RESOURCES_SUBFOLDER = "resources";
+
+    // useful test sizes for keys
+    public static final List<Integer> DSS_SIZES =
+            Collections.unmodifiableList(Arrays.asList(512, 768, 1024));
+    public static final List<Integer> RSA_SIZES =
+            Collections.unmodifiableList(Arrays.asList(1024, 2048, 3072, 4096));
+    public static final List<Integer> ED25519_SIZES =
+            Collections.unmodifiableList(Arrays.asList(256));
+
+    @Rule
+    public final TestName testNameHolder = new TestName();
+
+    private Path targetFolder;
+    private Path tempFolder;
+
+    protected JUnitTestSupport() {
+        super();
+    }
+
+    public final String getCurrentTestName() {
+        return testNameHolder.getMethodName();
+    }
+
+    /**
+     * Attempts to build a <U>relative</U> path whose root is the location
+     * of the TEMP sub-folder of the Maven &quot;target&quot; folder associated
+     * with the project
+     *
+     * @param comps The path components - ignored if {@code null}/empty
+     * @return The {@link Path} representing the result - same as target folder if no components
+     * @see #TEMP_SUBFOLDER_NAME
+     * @see #getTargetRelativeFile(Collection)
+     */
+    protected Path getTempTargetRelativeFile(String... comps) {
+        return getTempTargetRelativeFile(GenericUtils.isEmpty(comps) ? Collections.emptyList() : Arrays.asList(comps));
+    }
+
+    /**
+     * Attempts to build a <U>relative</U> path whose root is the location
+     * of the TEMP sub-folder of the Maven &quot;target&quot; folder associated
+     * with the project
+     *
+     * @param comps The path components - ignored if {@code null}/empty
+     * @return The {@link Path} representing the result - same as target folder if no components
+     * @see #TEMP_SUBFOLDER_NAME
+     * @see #getTempTargetFolder()
+     */
+    protected Path getTempTargetRelativeFile(Collection<String> comps) {
+        return CommonTestSupportUtils.resolve(getTempTargetFolder(), comps);
+    }
+
+    /**
+     * @return The TEMP sub-folder {@link Path} of the Maven &quot;target&quot; folder
+     * associated with the project - never {@code null}
+     */
+    protected Path getTempTargetFolder() {
+        synchronized (TEMP_SUBFOLDER_NAME) {
+            if (tempFolder == null) {
+                tempFolder = Objects.requireNonNull(detectTargetFolder(), "No target folder detected").resolve(TEMP_SUBFOLDER_NAME);
+            }
+        }
+
+        return tempFolder;
+    }
+
+    /**
+     * Attempts to build a <U>relative</U> path whose root is the location
+     * of the Maven &quot;target&quot; folder associated with the project
+     *
+     * @param comps The path components - ignored if {@code null}/empty
+     * @return The {@link Path} representing the result - same as target folder if no components
+     */
+    protected Path getTargetRelativeFile(String... comps) {
+        return getTargetRelativeFile(GenericUtils.isEmpty(comps) ? Collections.emptyList() : Arrays.asList(comps));
+    }
+
+    /**
+     * Attempts to build a <U>relative</U> path whose root is the location
+     * of the Maven &quot;target&quot; folder associated with the project
+     *
+     * @param comps The path components - ignored if {@code null}/empty
+     * @return The {@link Path} representing the result - same as target folder if no components
+     * @see #detectTargetFolder()
+     */
+    protected Path getTargetRelativeFile(Collection<String> comps) {
+        return CommonTestSupportUtils.resolve(detectTargetFolder(), comps);
+    }
+
+    /**
+     * Attempts to detect the location of the Maven &quot;target&quot; folder
+     * associated with the project that contains the actual class extending this
+     * base class
+     *
+     * @return The {@link File} representing the location of the &quot;target&quot; folder
+     * @throws IllegalArgumentException If failed to detect the folder
+     */
+    protected Path detectTargetFolder() throws IllegalArgumentException {
+        synchronized (TEMP_SUBFOLDER_NAME) {
+            if (targetFolder == null) {
+                File path = CommonTestSupportUtils.detectTargetFolder(getClass());
+                targetFolder = Objects.requireNonNull(path, "Failed to detect target folder").toPath();
+            }
+        }
+
+        return targetFolder;
+    }
+
+    /**
+     * Creates a folder bearing the class's simple name under the project's target temporary folder
+     *
+     * @return The created folder {@link Path}
+     * @throws IOException If failed to detect or create the folder's location
+     * @see #detectTargetFolder() detectTargetFolder
+     * @see #assertHierarchyTargetFolderExists(Path, LinkOption...) assertHierarchyTargetFolderExists
+     */
+    protected Path createTempClassFolder() throws IOException {
+        Path tmpDir = detectTargetFolder();
+        return assertHierarchyTargetFolderExists(tmpDir.resolve(getClass().getSimpleName()));
+    }
+
+    protected Path detectSourcesFolder() throws IllegalStateException {
+        Path target = detectTargetFolder();
+        Path parent = target.getParent();
+        return parent.resolve("src");
+    }
+
+    protected Path getTestResourcesFolder() {
+        try {
+            URL url = getClass().getResource(getClass().getSimpleName() + ".class");
+            return Paths.get(url.toURI()).getParent();
+        } catch (URISyntaxException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    protected Path getClassResourcesFolder(String resType /* test or main */) {
+        return getClassResourcesFolder(resType, getClass());
+    }
+
+    protected Path getClassResourcesFolder(String resType /* test or main */, Class<?> clazz) {
+        return getPackageResourcesFolder(resType, clazz.getPackage());
+    }
+
+    protected Path getPackageResourcesFolder(String resType /* test or main */, Package pkg) {
+        return getPackageResourcesFolder(resType, pkg.getName());
+    }
+
+    protected Path getPackageResourcesFolder(String resType /* test or main */, String pkgName) {
+        Path src = detectSourcesFolder();
+        Path root = src.resolve(resType);
+        Path resources = root.resolve(RESOURCES_SUBFOLDER);
+        return resources.resolve(pkgName.replace('.', File.separatorChar));
+    }
+
+    protected KeyPairProvider createTestHostKeyProvider() {
+        return CommonTestSupportUtils.createTestHostKeyProvider(getClass());
+    }
+
+    /* ------------------- Useful extra test helpers ---------------------- */
+
+    public static String shuffleCase(CharSequence cs) {
+        if (GenericUtils.isEmpty(cs)) {
+            return "";
+        }
+
+        StringBuilder sb = new StringBuilder(cs.length());
+        for (int index = 0; index < cs.length(); index++) {
+            char ch = cs.charAt(index);
+            double v = Math.random();
+            if (Double.compare(v, 0.3d) < 0) {
+                ch = Character.toUpperCase(ch);
+            } else if ((Double.compare(v, 0.3d) >= 0) && (Double.compare(v, 0.6d) < 0)) {
+                ch = Character.toLowerCase(ch);
+            }
+            sb.append(ch);
+        }
+
+        return sb.toString();
+    }
+
+    public static String repeat(CharSequence csq, int nTimes) {
+        if (GenericUtils.isEmpty(csq) || (nTimes <= 0)) {
+            return "";
+        }
+
+        StringBuilder sb = new StringBuilder(nTimes * csq.length());
+        for (int index = 0; index < nTimes; index++) {
+            sb.append(csq);
+        }
+
+        return sb.toString();
+    }
+
+    public static List<Object[]> parameterize(Collection<?> params) {
+        if (GenericUtils.isEmpty(params)) {
+            return Collections.emptyList();
+        }
+
+        List<Object[]> result = new ArrayList<>(params.size());
+        for (Object p : params) {
+            result.add(new Object[]{p});
+        }
+
+        return result;
+    }
+
+    /* ----------------------- Useful extra assertions --------------------- */
+
+    public static void assertEquals(String message, boolean expected, boolean actual) {
+        assertEquals(message, Boolean.valueOf(expected), Boolean.valueOf(actual));
+    }
+
+    public static <T> void assertEquals(String message, Iterable<? extends T> expected, Iterable<? extends T> actual) {
+        if (expected != actual) {
+            assertEquals(message, expected.iterator(), actual.iterator());
+        }
+    }
+
+    public static <T> void assertEquals(String message, Iterator<? extends T> expected, Iterator<? extends T> actual) {
+        if (expected == actual) {
+            return;
+        }
+
+        for (int index = 0; expected.hasNext(); index++) {
+            assertTrue(message + "[next actual index=" + index + "]", actual.hasNext());
+
+            T expValue = expected.next();
+            T actValue = actual.next();
+            assertEquals(message + "[iterator index=" + index + "]", expValue, actValue);
+        }
+
+        // once expected is exhausted make sure no more actual items left
+        assertFalse(message + "[non-empty-actual]", actual.hasNext());
+    }
+
+    public static Path assertHierarchyTargetFolderExists(Path folder, LinkOption... options) throws IOException {
+        if (Files.exists(folder, options)) {
+            assertTrue("Target is an existing file instead of a folder: " + folder, Files.isDirectory(folder, options));
+        } else {
+            Files.createDirectories(folder);
+        }
+
+        return folder;
+    }
+
+    public static void assertFileContentsEquals(String prefix, Path expected, Path actual) throws IOException {
+        long cmpSize = Files.size(expected);
+        assertEquals(prefix + ": Mismatched file size", cmpSize, Files.size(expected));
+
+        try (InputStream expStream = Files.newInputStream(expected);
+             InputStream actStream = Files.newInputStream(actual)) {
+            byte[] expData = new byte[IoUtils.DEFAULT_COPY_SIZE];
+            byte[] actData = new byte[expData.length];
+
+            for (long offset = 0L; offset < cmpSize;) {
+                Arrays.fill(expData, (byte) 0);
+                int expLen = expStream.read(expData);
+                Arrays.fill(actData, (byte) 0);
+                int actLen = actStream.read(actData);
+                assertEquals(prefix + ": Mismatched read size at offset=" + offset, expLen, actLen);
+                assertArrayEquals(prefix + ": Mismatched data at offset=" + offset, expData, actData);
+
+                offset += expLen;
+            }
+        }
+    }
+
+    public static File assertHierarchyTargetFolderExists(File folder) {
+        if (folder.exists()) {
+            assertTrue("Target is an existing file instead of a folder: " + folder.getAbsolutePath(), folder.isDirectory());
+        } else {
+            assertTrue("Failed to create hierarchy of " + folder.getAbsolutePath(), folder.mkdirs());
+        }
+
+        return folder;
+    }
+
+    public static void assertObjectInstanceOf(String message, Class<?> expected, Object obj) {
+        assertNotNull(message + " - no actual object", obj);
+
+        Class<?> actual = obj.getClass();
+        if (!expected.isAssignableFrom(actual)) {
+            fail(message + " - actual object type (" + actual.getName() + ") incompatible with expected (" + expected.getName() + ")");
+        }
+    }
+
+    public static <E> void assertListEquals(String message, List<? extends E> expected, List<? extends E> actual) {
+        int expSize = GenericUtils.size(expected);
+        int actSize = GenericUtils.size(actual);
+        assertEquals(message + "[size]", expSize, actSize);
+
+        for (int index = 0; index < expSize; index++) {
+            E expValue = expected.get(index);
+            E actValue = actual.get(index);
+            assertEquals(message + "[" + index + "]", expValue, actValue);
+        }
+    }
+
+    public static <K, V> void assertMapEquals(String message, Map<? extends K, ? extends V> expected, Map<? super K, ? extends V> actual) {
+        int numItems = GenericUtils.size(expected);
+        assertEquals(message + "[size]", numItems, GenericUtils.size(actual));
+
+        if (numItems > 0) {
+            expected.forEach((key, expValue) -> {
+                V actValue = actual.get(key);
+                assertEquals(message + "[" + key + "]", expValue, actValue);
+            });
+        }
+    }
+
+    public static void assertKeyPairEquals(String message, KeyPair expected, KeyPair actual) {
+        assertKeyEquals(message + "[public]", expected.getPublic(), actual.getPublic());
+        assertKeyEquals(message + "[private]", expected.getPrivate(), actual.getPrivate());
+    }
+
+    public static <T extends Key> void assertKeyEquals(String message, T expected, T actual) {
+        if (expected == actual) {
+            return;
+        }
+
+        assertEquals(message + "[algorithm]", expected.getAlgorithm(), actual.getAlgorithm());
+
+        if (expected instanceof RSAPublicKey) {
+            assertRSAPublicKeyEquals(message, RSAPublicKey.class.cast(expected), RSAPublicKey.class.cast(actual));
+        } else if (expected instanceof DSAPublicKey) {
+            assertDSAPublicKeyEquals(message, DSAPublicKey.class.cast(expected), DSAPublicKey.class.cast(actual));
+        } else if (expected instanceof ECPublicKey) {
+            assertECPublicKeyEquals(message, ECPublicKey.class.cast(expected), ECPublicKey.class.cast(actual));
+        } else if (expected instanceof RSAPrivateKey) {
+            assertRSAPrivateKeyEquals(message, RSAPrivateKey.class.cast(expected), RSAPrivateKey.class.cast(actual));
+        } else if (expected instanceof ECPrivateKey) {
+            assertECPrivateKeyEquals(message, ECPrivateKey.class.cast(expected), ECPrivateKey.class.cast(actual));
+        }
+        assertArrayEquals(message + "[encdoded-data]", expected.getEncoded(), actual.getEncoded());
+    }
+
+    public static void assertRSAPublicKeyEquals(String message, RSAPublicKey expected, RSAPublicKey actual) {
+        if (expected == actual) {
+            return;
+        }
+
+        assertEquals(message + "[e]", expected.getPublicExponent(), actual.getPublicExponent());
+        assertEquals(message + "[n]", expected.getModulus(), actual.getModulus());
+    }
+
+    public static void assertDSAPublicKeyEquals(String message, DSAPublicKey expected, DSAPublicKey actual) {
+        if (expected == actual) {
+            return;
+        }
+
+        assertEquals(message + "[y]", expected.getY(), actual.getY());
+        assertDSAParamsEquals(message + "[params]", expected.getParams(), actual.getParams());
+    }
+
+    public static void assertECPublicKeyEquals(String message, ECPublicKey expected, ECPublicKey actual) {
+        if (expected == actual) {
+            return;
+        }
+
+        assertECPointEquals(message + "[W]", expected.getW(), actual.getW());
+        assertECParameterSpecEquals(message, expected, actual);
+    }
+
+    public static void assertRSAPrivateKeyEquals(String message, RSAPrivateKey expected, RSAPrivateKey actual) {
+        if (expected == actual) {
+            return;
+        }
+
+        assertEquals(message + "[d]", expected.getPrivateExponent(), actual.getPrivateExponent());
+        assertEquals(message + "[n]", expected.getModulus(), actual.getModulus());
+    }
+
+    public static void assertDSAPrivateKeyEquals(String message, DSAPrivateKey expected, DSAPrivateKey actual) {
+        if (expected == actual) {
+            return;
+        }
+
+        assertEquals(message + "[x]", expected.getX(), actual.getX());
+        assertDSAParamsEquals(message + "[params]", expected.getParams(), actual.getParams());
+    }
+
+    public static void assertDSAParamsEquals(String message, DSAParams expected, DSAParams actual) {
+        if (expected == actual) {
+            return;
+        }
+
+        assertEquals(message + "[g]", expected.getG(), actual.getG());
+        assertEquals(message + "[p]", expected.getP(), actual.getP());
+        assertEquals(message + "[q]", expected.getQ(), actual.getQ());
+    }
+
+    public static void assertECPrivateKeyEquals(String message, ECPrivateKey expected, ECPrivateKey actual) {
+        if (expected == actual) {
+            return;
+        }
+
+        assertEquals(message + "[S]", expected.getS(), actual.getS());
+        assertECParameterSpecEquals(message, expected, actual);
+    }
+
+    public static void assertECParameterSpecEquals(String message, ECKey expected, ECKey actual) {
+        if (expected == actual) {
+            return;
+        }
+        assertECParameterSpecEquals(message, expected.getParams(), actual.getParams());
+    }
+
+    public static void assertECParameterSpecEquals(String message, ECParameterSpec expected, ECParameterSpec actual) {
+        if (expected == actual) {
+            return;
+        }
+
+        assertEquals(message + "[order]", expected.getOrder(), actual.getOrder());
+        assertEquals(message + "[cofactor]", expected.getCofactor(), actual.getCofactor());
+        assertECPointEquals(message + "[generator]", expected.getGenerator(), actual.getGenerator());
+        assertCurveEquals(message + "[curve]", expected.getCurve(), actual.getCurve());
+    }
+
+    public static void assertCurveEquals(String message, EllipticCurve expected, EllipticCurve actual) {
+        if (expected == actual) {
+            return;
+        }
+
+        assertEquals(message + "[A]", expected.getA(), actual.getA());
+        assertEquals(message + "[B]", expected.getB(), actual.getB());
+        assertArrayEquals(message + "[seed]", expected.getSeed(), actual.getSeed());
+        assertECFieldEquals(message + "[field]", expected.getField(), actual.getField());
+    }
+
+    public static void assertECFieldEquals(String message, ECField expected, ECField actual) {
+        if (expected == actual) {
+            return;
+        }
+
+        assertEquals(message + "[size]", expected.getFieldSize(), actual.getFieldSize());
+    }
+
+    public static void assertECPointEquals(String message, ECPoint expected, ECPoint actual) {
+        if (expected == actual) {
+            return;
+        }
+
+        assertEquals(message + "[x]", expected.getAffineX(), actual.getAffineX());
+        assertEquals(message + "[y]", expected.getAffineY(), actual.getAffineY());
+    }
+
+    public static void assertFileLength(File file, long length, long timeout) throws Exception {
+        assertFileLength(file.toPath(), length, timeout);
+    }
+
+    /**
+     * Waits the specified timeout for the file to exist and have the required length
+     *
+     * @param file    The file {@link Path} to check
+     * @param length  Expected length
+     * @param timeout Timeout (msec.) to wait for satisfying the requirements
+     * @throws Exception If failed to access the file
+     */
+    public static void assertFileLength(Path file, long length, long timeout) throws Exception {
+        if (waitForFile(file, length, timeout)) {
+            return;
+        }
+        assertTrue("File not found: " + file, Files.exists(file));
+        assertEquals("Mismatched file size for " + file, length, Files.size(file));
+    }
+
+    public static boolean waitForFile(Path file, long length, long timeout) throws Exception {
+        while (timeout > 0L) {
+            long sleepTime = Math.min(timeout, 100L);
+            if (Files.exists(file) && (Files.size(file) == length)) {
+                return true;
+            }
+
+            long sleepStart = System.nanoTime();
+            Thread.sleep(sleepTime);
+            long sleepEnd = System.nanoTime();
+            long nanoSleep = sleepEnd - sleepStart;
+
+            sleepTime = TimeUnit.NANOSECONDS.toMillis(nanoSleep);
+            timeout -= sleepTime;
+        }
+
+        return false;
+    }
+
+    public static void outputDebugMessage(String format, Object... args) {
+        if (OUTPUT_DEBUG_MESSAGES) {
+            outputDebugMessage(String.format(format, args));
+        }
+    }
+
+    public static void outputDebugMessage(Object message) {
+        if (OUTPUT_DEBUG_MESSAGES) {
+            System.out.append("===[DEBUG]=== ").println(message);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/util/test/NoIoTestCase.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/util/test/NoIoTestCase.java b/sshd-common/src/test/java/org/apache/sshd/util/test/NoIoTestCase.java
new file mode 100644
index 0000000..7d52892
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/util/test/NoIoTestCase.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.util.test;
+
+/**
+ * Marker interface used as <A HREF="https://github.com/junit-team/junit4/wiki/categories">jUnit category</A>
+ * to indicate a test that does not require real client/server interaction.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface NoIoTestCase {
+    // Marker interface
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/util/test/OutputCountTrackingOutputStream.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/util/test/OutputCountTrackingOutputStream.java b/sshd-common/src/test/java/org/apache/sshd/util/test/OutputCountTrackingOutputStream.java
new file mode 100644
index 0000000..b4eb84c
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/util/test/OutputCountTrackingOutputStream.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.util.test;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class OutputCountTrackingOutputStream extends FilterOutputStream {
+    protected long writeCount;
+
+    public OutputCountTrackingOutputStream(OutputStream out) {
+        super(out);
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+        out.write(b);
+        updateWriteCount(1L);
+    }
+
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+        out.write(b, off, len); // don't call super since it calls the single 'write'
+        updateWriteCount(len);
+    }
+
+    public long getWriteCount() {
+        return writeCount;
+    }
+
+    protected long updateWriteCount(long delta) {
+        writeCount += delta;
+        return writeCount;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/util/test/TeeOutputStream.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/util/test/TeeOutputStream.java b/sshd-common/src/test/java/org/apache/sshd/util/test/TeeOutputStream.java
new file mode 100644
index 0000000..4cd6014
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/util/test/TeeOutputStream.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.util.test;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class TeeOutputStream extends OutputStream {
+
+    private OutputStream[] tees;
+
+    public TeeOutputStream(OutputStream... tees) {
+        this.tees = tees;
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+        for (OutputStream s : tees) {
+            s.write(b);
+        }
+    }
+
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+        for (OutputStream s : tees) {
+            s.write(b, off, len);
+        }
+    }
+
+    @Override
+    public void flush() throws IOException {
+        for (OutputStream s : tees) {
+            s.flush();
+        }
+    }
+
+    @Override
+    public void close() throws IOException {
+        for (OutputStream s : tees) {
+            s.close();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/resources/log4j.properties b/sshd-common/src/test/resources/log4j.properties
new file mode 100644
index 0000000..cf1d08a
--- /dev/null
+++ b/sshd-common/src/test/resources/log4j.properties
@@ -0,0 +1,38 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed  under the  License is distributed on an "AS IS" BASIS,
+# WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
+# implied.
+#
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+
+#
+# The logging properties used during tests..
+#
+log4j.rootLogger=INFO, stdout, logfile
+#log4j.logger.org.apache.sshd=TRACE
+#log4j.logger.org.apache.sshd.common.channel.Window=DEBUG
+
+# CONSOLE appender
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d | %-5.5p | %-16.16t | %-32.32c{1} | %-64.64C %4L | %m%n
+
+# File appender
+log4j.appender.logfile=org.apache.log4j.FileAppender
+log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
+log4j.appender.logfile.layout.ConversionPattern=%d | %-5.5p | %-16.16t | %-32.32c{1} | %-64.64C %4L | %m%n
+log4j.appender.logfile.file=target/sshd-common-tests.log
+log4j.appender.logfile.append=true

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/resources/org/apache/sshd/client/config/hosts/testReadGlobalHostsConfigEntries.config.txt
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/resources/org/apache/sshd/client/config/hosts/testReadGlobalHostsConfigEntries.config.txt b/sshd-common/src/test/resources/org/apache/sshd/client/config/hosts/testReadGlobalHostsConfigEntries.config.txt
new file mode 100644
index 0000000..5f772cd
--- /dev/null
+++ b/sshd-common/src/test/resources/org/apache/sshd/client/config/hosts/testReadGlobalHostsConfigEntries.config.txt
@@ -0,0 +1,22 @@
+# Global section first
+
+    User global
+    Port 7365
+    IdentityFile ~/.ssh/github.key
+    HostName 37.77.34.7
+
+# User override
+Host *.apache.org
+    User mina-sshd
+
+# Port override
+Host *.github.com
+    Port 443
+
+# Host name override
+Host 10.*.*.*
+    HostName 7.3.6.5
+
+# Identity override
+Host 192.168.*.*
+    IdentityFile ~/.ssh/internal.key

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/resources/org/apache/sshd/client/config/hosts/testReadMultipleHostPatterns.config.txt
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/resources/org/apache/sshd/client/config/hosts/testReadMultipleHostPatterns.config.txt b/sshd-common/src/test/resources/org/apache/sshd/client/config/hosts/testReadMultipleHostPatterns.config.txt
new file mode 100644
index 0000000..0f5edcb
--- /dev/null
+++ b/sshd-common/src/test/resources/org/apache/sshd/client/config/hosts/testReadMultipleHostPatterns.config.txt
@@ -0,0 +1,5 @@
+# Same configuration duplicated in multiple configuration entries
+Host *.github.com *.apache.org 10.*.*.*
+    User mina-sshd
+    Port 443
+    HostName 7.3.6.5

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/resources/org/apache/sshd/client/config/hosts/testReadSimpleHostsConfigEntries.config.txt
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/resources/org/apache/sshd/client/config/hosts/testReadSimpleHostsConfigEntries.config.txt b/sshd-common/src/test/resources/org/apache/sshd/client/config/hosts/testReadSimpleHostsConfigEntries.config.txt
new file mode 100644
index 0000000..1058777
--- /dev/null
+++ b/sshd-common/src/test/resources/org/apache/sshd/client/config/hosts/testReadSimpleHostsConfigEntries.config.txt
@@ -0,0 +1,8 @@
+# Simple configuration file
+Host *.github.com
+    User mina-sshd
+    Port 443
+    IdentityFile ~/.ssh/github.key
+
+Host *.apache.org
+    User mina-sshd

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/resources/org/apache/sshd/client/config/keys/id_dsa
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/resources/org/apache/sshd/client/config/keys/id_dsa b/sshd-common/src/test/resources/org/apache/sshd/client/config/keys/id_dsa
new file mode 100644
index 0000000..1d3ef24
--- /dev/null
+++ b/sshd-common/src/test/resources/org/apache/sshd/client/config/keys/id_dsa
@@ -0,0 +1,12 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBvAIBAAKBgQDIPyMbBuQcZxeYDOyCqqkdK37cWQvp+RpWzvieB/oiG/ykfDQX
+oZMRtwqwWTBfejNitbBBmC6G/t5OK+9aFmj7pfJ+a7fZKXfiUquIg9soDsoOindf
+2AwR6MZ3os8uiP2xrC8IQAClnETa15mFShs4a4b2VjddgCQ6tphnY97MywIVAPtr
+YyW11RIXsVTf/9KlbhYaNlt5AoGAX9JzbHykC/0xDKOyKU6xDIOVdEZ0ooAl9Psl
+BEUuNhlv2XgmQScO6C9l2W7gbbut7zIw4FaZ2/dgXa3D4IyexBVug5XMnrssErZo
+NcoF5g0dgEAsb9Hl9gkIK3VHM5kWteeUg1VE700JTtSMisdL8CgIdR+xN8iVH5Ew
+CbLWxmECgYEAtv+cdRfNevYFkp55jVqazc8zRLvfb64jzgc5oSJVc64kFs4yx+ab
+YpGX9WxNxDlG6g2WiY8voDBB0YnUJsn0kVRjBKX9OceROxrfT4K4dVbQZsdt+SLa
+XWL4lGJFrFZL3LZqvySvq6xfhJfakQDDivW4hUOhFPXPHrE5/Ia3T7ACFQCE6flG
+nmVCAbzo9YsbdJWBnxMnBA==
+-----END DSA PRIVATE KEY-----
\ No newline at end of file


Mime
View raw message