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:[email protected]">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:[email protected]">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:[email protected]">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:[email protected]">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 "target" folder lookup up the + * hierarchy + * @return The "target" <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 "source" 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 "source" 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 "target" or "build" folder + * lookup up the hierarchy + * @return The "target" <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:[email protected]">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:[email protected]">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:[email protected]">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:[email protected]">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 "target" 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 "target" 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 "target" 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 "target" 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 "target" 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 "target" folder + * associated with the project that contains the actual class extending this + * base class + * + * @return The {@link File} representing the location of the "target" 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:[email protected]">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:[email protected]">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:[email protected]">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
