Discussion:
mina-sshd git commit: [SSHD-860] Using lazy-loaded KeyPair(s) when wrapping several KeyIdentityProvider(s) into a single logical one
l***@apache.org
2018-11-12 17:46:09 UTC
Permalink
Repository: mina-sshd
Updated Branches:
refs/heads/master 44576a59a -> 3bea9ff05


[SSHD-860] Using lazy-loaded KeyPair(s) when wrapping several KeyIdentityProvider(s) into a single logical one


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/3bea9ff0
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/3bea9ff0
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/3bea9ff0

Branch: refs/heads/master
Commit: 3bea9ff05503bb11665c2b0db6607eef4f7ccd28
Parents: 44576a5
Author: Lyor Goldstein <***@apache.org>
Authored: Mon Nov 12 07:59:54 2018 +0200
Committer: Lyor Goldstein <***@apache.org>
Committed: Mon Nov 12 19:51:49 2018 +0200

----------------------------------------------------------------------
.../common/keyprovider/KeyIdentityProvider.java | 33 +++++-
.../keyprovider/MultiKeyIdentityIterator.java | 88 +++++++++++++++
.../keyprovider/MultiKeyIdentityProvider.java | 53 +++++++++
.../apache/sshd/common/util/GenericUtils.java | 28 +++++
.../MultiKeyIdentityProviderTest.java | 108 +++++++++++++++++++
.../sshd/client/session/ClientSession.java | 2 +-
6 files changed, 306 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3bea9ff0/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java
index 72b063f..f03b68b 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java
@@ -24,7 +24,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.function.Function;
-import java.util.function.Supplier;

import org.apache.sshd.common.util.GenericUtils;

@@ -128,9 +127,17 @@ public interface KeyIdentityProvider {
* @param providers The providers - ignored if {@code null}/empty (i.e., returns
* {@link #EMPTY_KEYS_PROVIDER})
* @return The wrapping provider
+ * @see MultiKeyIdentityProvider
*/
static KeyIdentityProvider multiProvider(Collection<? extends KeyIdentityProvider> providers) {
- return GenericUtils.isEmpty(providers) ? EMPTY_KEYS_PROVIDER : wrapKeyPairs(iterableOf(providers));
+ int numProviders = GenericUtils.size(providers);
+ if (numProviders <= 0) {
+ return EMPTY_KEYS_PROVIDER;
+ } else if (numProviders == 1) {
+ return GenericUtils.head(providers);
+ } else {
+ return new MultiKeyIdentityProvider(providers);
+ }
}

/**
@@ -141,9 +148,25 @@ public interface KeyIdentityProvider {
* @return The wrapping iterable
*/
static Iterable<KeyPair> iterableOf(Collection<? extends KeyIdentityProvider> providers) {
- Iterable<Supplier<Iterable<KeyPair>>> keysSuppliers =
- GenericUtils.<KeyIdentityProvider, Supplier<Iterable<KeyPair>>>wrapIterable(providers, p -> p::loadKeys);
- return GenericUtils.multiIterableSuppliers(keysSuppliers);
+ int numProviders = GenericUtils.size(providers);
+ if (numProviders <= 0) {
+ return Collections.emptyList();
+ } else if (numProviders == 1) {
+ KeyIdentityProvider p = GenericUtils.head(providers);
+ return p.loadKeys();
+ } else {
+ return new Iterable<KeyPair>() {
+ @Override
+ public Iterator<KeyPair> iterator() {
+ return new MultiKeyIdentityIterator(providers);
+ }
+
+ @Override
+ public String toString() {
+ return Iterable.class.getSimpleName() + "[of(providers)]";
+ }
+ };
+ }
}

/**

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3bea9ff0/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityIterator.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityIterator.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityIterator.java
new file mode 100644
index 0000000..5ba5386
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityIterator.java
@@ -0,0 +1,88 @@
+/*
+ * 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.keyprovider;
+
+import java.security.KeyPair;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Iterates over several {@link KeyIdentityProvider}-s exhausting their
+ * keys one by one (lazily).
+ *
+ * @author <a href="mailto:***@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class MultiKeyIdentityIterator implements Iterator<KeyPair> {
+ protected Iterator<KeyPair> currentProvider;
+ protected boolean finished;
+ private final Iterator<? extends KeyIdentityProvider> providers;
+
+ public MultiKeyIdentityIterator(Iterable<? extends KeyIdentityProvider> providers) {
+ this.providers = (providers == null) ? null : providers.iterator();
+ }
+
+ public Iterator<? extends KeyIdentityProvider> getProviders() {
+ return providers;
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (finished) {
+ return false;
+ }
+
+ Iterator<? extends KeyIdentityProvider> provs = getProviders();
+ if (provs == null) {
+ finished = true;
+ return false;
+ }
+
+ if ((currentProvider != null) && currentProvider.hasNext()) {
+ return true;
+ }
+
+ while (provs.hasNext()) {
+ KeyIdentityProvider p = provs.next();
+ Iterable<KeyPair> keys = (p == null) ? null : p.loadKeys();
+ currentProvider = (keys == null) ? null : keys.iterator();
+
+ if ((currentProvider != null) && currentProvider.hasNext()) {
+ return true;
+ }
+ }
+
+ // exhausted all providers
+ finished = false;
+ return false;
+ }
+
+ @Override
+ public KeyPair next() {
+ if (finished) {
+ throw new NoSuchElementException("All identities have been exhausted");
+ }
+
+ if (currentProvider == null) {
+ throw new IllegalStateException("'next()' called without asking 'hasNext()'");
+ }
+
+ return currentProvider.next();
+ }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3bea9ff0/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityProvider.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityProvider.java
new file mode 100644
index 0000000..18304ff
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityProvider.java
@@ -0,0 +1,53 @@
+/*
+ * 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.keyprovider;
+
+import java.security.KeyPair;
+import java.util.Collections;
+import java.util.Iterator;
+
+/**
+ * Aggregates several {@link KeyIdentityProvider}-s into a single logical
+ * one that (lazily) exposes the keys from each aggregated provider
+ *
+ * @author <a href="mailto:***@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class MultiKeyIdentityProvider implements KeyIdentityProvider {
+ protected final Iterable<? extends KeyIdentityProvider> providers;
+
+ public MultiKeyIdentityProvider(Iterable<? extends KeyIdentityProvider> providers) {
+ this.providers = providers;
+ }
+
+ @Override
+ public Iterable<KeyPair> loadKeys() {
+ return new Iterable<KeyPair>() {
+ @Override
+ public Iterator<KeyPair> iterator() {
+ return (providers == null) ? Collections.emptyIterator() : new MultiKeyIdentityIterator(providers);
+ }
+
+ @Override
+ public String toString() {
+ return Iterable.class.getSimpleName() + "[" + MultiKeyIdentityProvider.class.getSimpleName() + "]";
+ }
+ };
+ }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3bea9ff0/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
index b84dbd4..4dba3f2 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
@@ -27,6 +27,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.Deque;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
@@ -38,6 +39,7 @@ import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.Objects;
import java.util.Set;
+import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ExecutionException;
@@ -830,6 +832,32 @@ public final class GenericUtils {
}

/**
+ * Returns the first element in iterable - it has some optimization for {@link List}-s
+ * {@link Deque}-s and {@link SortedSet}s.
+ *
+ * @param <T> Type of element
+ * @param it The {@link Iterable} instance - ignored if {@code null}/empty
+ * @return first element by iteration or {@code null} if none available
+ */
+ public static <T> T head(Iterable<? extends T> it) {
+ if (it == null) {
+ return null;
+ } else if (it instanceof Deque<?>) { // check before (!) instanceof List since LinkedList implements List
+ Deque<? extends T> l = (Deque<? extends T>) it;
+ return (l.size() > 0) ? l.getFirst() : null;
+ } else if (it instanceof List<?>) {
+ List<? extends T> l = (List<? extends T>) it;
+ return (l.size() > 0) ? l.get(0) : null;
+ } else if (it instanceof SortedSet<?>) {
+ SortedSet<? extends T> s = (SortedSet<? extends T>) it;
+ return (s.size() > 0) ? s.first() : null;
+ } else {
+ Iterator<? extends T> iter = it.iterator();
+ return ((iter == null) || (!iter.hasNext())) ? null : iter.next();
+ }
+ }
+
+ /**
* Resolves to an always non-{@code null} iterator
*
* @param <T> Type of value being iterated

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3bea9ff0/sshd-common/src/test/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityProviderTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityProviderTest.java b/sshd-common/src/test/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityProviderTest.java
new file mode 100644
index 0000000..5a24da2
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityProviderTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.keyprovider;
+
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.mockito.Mockito;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:***@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class MultiKeyIdentityProviderTest extends JUnitTestSupport {
+ public MultiKeyIdentityProviderTest() {
+ super();
+ }
+
+ @Test // see SSHD-860
+ public void testLazyKeyIdentityMultiProvider() {
+ List<KeyPair> expected = new ArrayList<>();
+ for (int index = 1; index <= Short.SIZE; index++) {
+ PublicKey pub = Mockito.mock(PublicKey.class);
+ PrivateKey prv = Mockito.mock(PrivateKey.class);
+ expected.add(new KeyPair(pub, prv));
+ }
+
+ Collection<KeyIdentityProvider> providers = new ArrayList<>();
+ AtomicInteger position = new AtomicInteger(0);
+ for (int startIndex = 0, count = expected.size(), slice = count / 3; startIndex < count;) {
+ int nextIndex = Math.min(count, startIndex + slice);
+ Collection<KeyPair> keys = expected.subList(startIndex, nextIndex);
+ providers.add(wrapKeyPairs(position, keys));
+ startIndex = nextIndex;
+ }
+
+ KeyIdentityProvider multiProvider = KeyIdentityProvider.multiProvider(providers);
+ assertObjectInstanceOf(MultiKeyIdentityProvider.class.getSimpleName(), MultiKeyIdentityProvider.class, multiProvider);
+
+ Iterable<KeyPair> keys = multiProvider.loadKeys();
+ Iterator<KeyPair> iter = keys.iterator();
+ for (int index = 0, count = expected.size(); index < count; index++) {
+ KeyPair kpExpected = expected.get(index);
+ assertTrue("Premature keys exhaustion after " + index + " iterations", iter.hasNext());
+ KeyPair kpActual = iter.next();
+ assertSame("Mismatched key at index=" + index, kpExpected, kpActual);
+ assertEquals("Mismatched requested lazy key position", index + 1, position.get());
+ }
+
+ assertFalse("Not all keys exhausted", iter.hasNext());
+ }
+
+ private static KeyIdentityProvider wrapKeyPairs(AtomicInteger position, Iterable<KeyPair> keys) {
+ return new KeyIdentityProvider() {
+ @Override
+ public Iterable<KeyPair> loadKeys() {
+ return new Iterable<KeyPair>() {
+ @Override
+ public Iterator<KeyPair> iterator() {
+ return new Iterator<KeyPair>() {
+ private final Iterator<KeyPair> iter = keys.iterator();
+
+ @Override
+ public boolean hasNext() {
+ return iter.hasNext();
+ }
+
+ @Override
+ public KeyPair next() {
+ position.incrementAndGet();
+ return iter.next();
+ }
+ };
+ }
+ };
+ }
+ };
+ }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3bea9ff0/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
index 808586f..63343cb 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
@@ -378,7 +378,7 @@ public interface ClientSession
* @see ClientSession#getKeyPairProvider()
*/
static KeyIdentityProvider providerOf(ClientSession session) {
- return session == null
+ return (session == null)
? KeyIdentityProvider.EMPTY_KEYS_PROVIDER
: KeyIdentityProvider.resolveKeyIdentityProvider(
session.getRegisteredIdentities(), session.getKeyPairProvider());
Loading...