You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2018/11/07 18:01:07 UTC

[4/5] mina-sshd git commit: [SSHD-860] UserAuthPublicKeyIterator uses lazy loading of public key identities both from agent and client session

[SSHD-860] UserAuthPublicKeyIterator uses lazy loading of public key identities both from agent and client session


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

Branch: refs/heads/master
Commit: 2c2982d67f39eeafa938ba510f2bc4f126e3b72c
Parents: c507cb0
Author: Lyor Goldstein <lg...@apache.org>
Authored: Wed Nov 7 12:16:37 2018 +0200
Committer: Lyor Goldstein <lg...@apache.org>
Committed: Wed Nov 7 20:06:39 2018 +0200

----------------------------------------------------------------------
 CHANGES.md                                      |   6 +
 .../auth/AuthenticationIdentitiesProvider.java  |   8 +-
 .../apache/sshd/common/util/GenericUtils.java   |  85 -----------
 .../util/helper/LazyIterablesConcatenator.java  | 114 ++++++++++++++
 .../util/helper/LazyMatchingTypeIterable.java   |  82 ++++++++++
 .../util/helper/LazyMatchingTypeIterator.java   | 105 +++++++++++++
 .../sshd/common/util/GenericUtilsTest.java      |  57 -------
 .../helper/LazyIterablesConcatenatorTest.java   |  71 +++++++++
 .../helper/LazyMatchingTypeIteratorTest.java    |  98 ++++++++++++
 .../client/auth/pubkey/KeyPairIdentity.java     |   4 +-
 .../auth/pubkey/UserAuthPublicKeyIterator.java  | 152 +++++++++++++++----
 11 files changed, 606 insertions(+), 176 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2c2982d6/CHANGES.md
----------------------------------------------------------------------
diff --git a/CHANGES.md b/CHANGES.md
index 0f44a1b..c43339c 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -26,3 +26,9 @@ accept also an `AttributeRepository` connection context argument (propagated fro
 user to try and repeat an encrypted private key decoding using a different password.
 
 * `SshAgent#getIdentities` returns an `Iterable` rather than a `List`
+
+
+## Behavioral changes
+
+* [SSHD-860](https://issues.apache.org/jira/browse/SSHD-860) `UserAuthPublicKeyIterator` uses lazy loading
+of public key identities both from agent and client session

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2c2982d6/sshd-common/src/main/java/org/apache/sshd/client/auth/AuthenticationIdentitiesProvider.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/client/auth/AuthenticationIdentitiesProvider.java b/sshd-common/src/main/java/org/apache/sshd/client/auth/AuthenticationIdentitiesProvider.java
index 3fa61ba..ee7c856 100644
--- a/sshd-common/src/main/java/org/apache/sshd/client/auth/AuthenticationIdentitiesProvider.java
+++ b/sshd-common/src/main/java/org/apache/sshd/client/auth/AuthenticationIdentitiesProvider.java
@@ -26,7 +26,7 @@ import java.util.List;
 import org.apache.sshd.client.auth.password.PasswordIdentityProvider;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.helper.LazyMatchingTypeIterable;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -83,17 +83,17 @@ public interface AuthenticationIdentitiesProvider extends KeyIdentityProvider, P
         return new AuthenticationIdentitiesProvider() {
             @Override
             public Iterable<KeyPair> loadKeys() {
-                return GenericUtils.lazySelectMatchingTypes(identities, KeyPair.class);
+                return LazyMatchingTypeIterable.lazySelectMatchingTypes(identities, KeyPair.class);
             }
 
             @Override
             public Iterable<String> loadPasswords() {
-                return GenericUtils.lazySelectMatchingTypes(identities, String.class);
+                return LazyMatchingTypeIterable.lazySelectMatchingTypes(identities, String.class);
             }
 
             @Override
             public Iterable<?> loadIdentities() {
-                return GenericUtils.lazySelectMatchingTypes(identities, Object.class);
+                return LazyMatchingTypeIterable.lazySelectMatchingTypes(identities, Object.class);
             }
         };
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2c2982d6/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 52cf171..b84dbd4 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
@@ -36,7 +36,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.NavigableMap;
 import java.util.NavigableSet;
-import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Set;
 import java.util.TreeMap;
@@ -868,90 +867,6 @@ public final class GenericUtils {
     }
 
     /**
-     * @param <T> Generic selected identities type
-     * @param The source values - ignored if {@code null}
-     * @param type The (never @code null) type of values to select - any value
-     * whose type is assignable to this type will be selected by the iterator.
-     * @return {@link Iterable} whose {@link Iterator} selects only values
-     * matching the specific type. <b>Note:</b> the matching values are not
-     * pre-calculated (hence the &quot;lazy&quot; denomination) - i.e.,
-     * the match is performed only when {@link Iterator#hasNext()} is called.
-     */
-    public static <T> Iterable<T> lazySelectMatchingTypes(Iterable<?> values, Class<T> type) {
-        Objects.requireNonNull(type, "No type selector specified");
-        if (values == null) {
-            return Collections.emptyList();
-        }
-
-        return new Iterable<T>() {
-            @Override
-            public Iterator<T> iterator() {
-                return lazySelectMatchingTypes(values.iterator(), type);
-            }
-
-            @Override
-            public String toString() {
-                return Iterable.class.getSimpleName() + "[lazy-select](" + type.getSimpleName() + ")";
-            }
-        };
-    }
-
-    /**
-     * @param <T> Generic selected identities type
-     * @param The source values - ignored if {@code null}
-     * @param type The (never @code null) type of values to select - any value
-     * whose type is assignable to this type will be selected by the iterator.
-     * @return An {@link Iterator} whose {@code next()} call selects only values
-     * matching the specific type. <b>Note:</b> the matching values are not
-     * pre-calculated (hence the &quot;lazy&quot; denomination) - i.e.,
-     * the match is performed only when {@link Iterator#hasNext()} is called.
-     */
-    public static <T> Iterator<T> lazySelectMatchingTypes(Iterator<?> values, Class<T> type) {
-        Objects.requireNonNull(type, "No type selector specified");
-        if (values == null) {
-            return Collections.emptyIterator();
-        }
-
-        return new Iterator<T>() {
-            private boolean finished;
-            private T nextValue;
-
-            @Override
-            public boolean hasNext() {
-                if (finished) {
-                    return false;
-                }
-
-                nextValue = selectNextMatchingValue(values, type);
-                if (nextValue == null) {
-                    finished = true;
-                }
-
-                return !finished;
-            }
-
-            @Override
-            public T next() {
-                if (finished) {
-                    throw new NoSuchElementException("All values have been exhausted");
-                }
-                if (nextValue == null) {
-                    throw new IllegalStateException("'next()' called without asking 'hasNext()'");
-                }
-
-                T v = nextValue;
-                nextValue = null;   // so it will be re-fetched when 'hasNext' is called
-                return v;
-            }
-
-            @Override
-            public String toString() {
-                return Iterator.class.getSimpleName() + "[lazy-select](" + type.getSimpleName() + ")";
-            }
-        };
-    }
-
-    /**
      * @param <T> Generic return type
      * @param The source values - ignored if {@code null}
      * @param type The (never @code null) type of values to select - any value

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2c2982d6/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyIterablesConcatenator.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyIterablesConcatenator.java b/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyIterablesConcatenator.java
new file mode 100644
index 0000000..2e1b042
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyIterablesConcatenator.java
@@ -0,0 +1,114 @@
+/*
+ * 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.helper;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Creates a &quot;smooth&quot; wrapping {@link Iterable} using several
+ * underlying ones to provide the values. The &quot;lazy&quot; denomination
+ * is due to the fact that no iterable is consulted until the one(s) before
+ * it have been fully exhausted.
+ *
+ * @param <T> Type of element being iterared
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class LazyIterablesConcatenator<T> implements Iterable<T> {
+    private final Iterable<? extends Iterable<? extends T>> iterables;
+
+    public LazyIterablesConcatenator(Iterable<? extends Iterable<? extends T>> iterables) {
+        this.iterables = iterables;
+    }
+
+    public Iterable<? extends Iterable<? extends T>> getIterables() {
+        return iterables;
+    }
+
+    @Override
+    public Iterator<T> iterator() {
+        return new Iterator<T>() {
+            @SuppressWarnings("synthetic-access")
+            private final Iterator<? extends Iterable<? extends T>> itit =
+                (iterables == null) ? Collections.emptyIterator() : iterables.iterator();
+            private Iterator<? extends T> currentIterator;
+            private boolean finished;
+
+            @Override
+            public boolean hasNext() {
+                if (finished) {
+                    return false;
+                }
+
+                // Do we have a current iterator, and if so does it still have values in it
+                if ((currentIterator != null) && currentIterator.hasNext()) {
+                    return true;
+                }
+
+                while (itit.hasNext()) {
+                    Iterable<? extends T> currentIterable = itit.next();
+                    currentIterator = currentIterable.iterator();
+                    if (currentIterator.hasNext()) {
+                        return true;
+                    }
+                }
+
+                // exhausted all
+                finished = true;
+                return false;
+            }
+
+            @Override
+            public T next() {
+                if (finished) {
+                    throw new NoSuchElementException("All elements have been exhausted");
+                }
+
+                if (currentIterator == null) {
+                    throw new IllegalStateException("'next()' called without a preceding 'hasNext()' query");
+                }
+
+                return currentIterator.next();
+            }
+
+            @Override
+            public String toString() {
+                return Iterator.class.getSimpleName() + "[lazy-concat]";
+            }
+        };
+    }
+
+    @Override
+    public String toString() {
+        return Iterable.class.getSimpleName() + "[lazy-concat]";
+    }
+
+    /**
+     * @param <T> Type if iterated element
+     * @param iterables The iterables to concatenate - ignored if {@code null}
+     * @return An {@link Iterable} that goes over all the elements in the wrapped
+     * iterables one after the other. The denomination &quot;lazy&quot; indicates
+     * that no iterable is consulted until the previous one has been fully exhausted.
+     */
+    public static <T> Iterable<T> lazyConcatenateIterables(Iterable<? extends Iterable<? extends T>> iterables) {
+        return (iterables == null) ? Collections.emptyList() : new LazyIterablesConcatenator<>(iterables);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2c2982d6/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIterable.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIterable.java b/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIterable.java
new file mode 100644
index 0000000..267c661
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIterable.java
@@ -0,0 +1,82 @@
+/*
+ * 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.helper;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Objects;
+
+/**
+ * Provides a selective {@link Iterable} over values that match a
+ * specific type out of all available. The &quot;lazy&quot; denomination
+ * is due to the fact that the next matching value is calculated on-the-fly
+ * every time {@link Iterator#hasNext()} is called
+ *
+ * @param <T> Type of element being selected
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class LazyMatchingTypeIterable<T> implements Iterable<T> {
+    private final Iterable<?> values;
+    private final Class<T> type;
+
+    public LazyMatchingTypeIterable(Iterable<?> values, Class<T> type) {
+        this.values = values;
+        this.type = Objects.requireNonNull(type, "No type selector specified");
+    }
+
+    public Iterable<?> getValues() {
+        return values;
+    }
+
+    public Class<T> getType() {
+        return type;
+    }
+
+    @Override
+    public Iterator<T> iterator() {
+        Iterable<?> vals = getValues();
+        if (vals == null) {
+            return Collections.emptyIterator();
+        }
+
+        return LazyMatchingTypeIterator.lazySelectMatchingTypes(vals.iterator(), getType());
+    }
+
+    @Override
+    public String toString() {
+        Class<?> t = getType();
+        return Iterable.class.getSimpleName() + "[lazy-select](" + t.getSimpleName() + ")";
+    }
+
+    /**
+     * @param <T> Type if iterated element
+     * @param The source values - ignored if {@code null}
+     * @param type The (never @code null) type of values to select - any value
+     * whose type is assignable to this type will be selected by the iterator.
+     * @return {@link Iterable} whose {@link Iterator} selects only values
+     * matching the specific type. <b>Note:</b> the matching values are not
+     * pre-calculated (hence the &quot;lazy&quot; denomination) - i.e.,
+     * the match is performed only when {@link Iterator#hasNext()} is called.
+     */
+    public static <T> Iterable<T> lazySelectMatchingTypes(Iterable<?> values, Class<T> type) {
+        Objects.requireNonNull(type, "No type selector specified");
+        return (values == null) ? Collections.emptyList() : new LazyMatchingTypeIterable<>(values, type);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2c2982d6/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIterator.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIterator.java b/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIterator.java
new file mode 100644
index 0000000..d0fd18c
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIterator.java
@@ -0,0 +1,105 @@
+/*
+ * 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.helper;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * An {@link Iterator} that selects only objects of a certain type from
+ * the underlying available ones. The &quot;lazy&quot; denomination is due
+ * to the fact that selection occurs only when {@link #hasNext()} is called
+ *
+ * @param <T> Type of iterated element
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class LazyMatchingTypeIterator<T> implements Iterator<T> {
+    protected boolean finished;
+    protected T nextValue;
+
+    private final Iterator<?> values;
+    private final Class<T> type;
+
+    public LazyMatchingTypeIterator(Iterator<?> values, Class<T> type) {
+        this.values = values;
+        this.type = Objects.requireNonNull(type, "No type selector specified");
+    }
+
+    public Iterator<?> getValues() {
+        return values;
+    }
+
+    public Class<T> getType() {
+        return type;
+    }
+
+    @Override
+    public boolean hasNext() {
+        if (finished) {
+            return false;
+        }
+
+        nextValue = GenericUtils.selectNextMatchingValue(getValues(), getType());
+        if (nextValue == null) {
+            finished = true;
+        }
+
+        return !finished;
+    }
+
+    @Override
+    public T next() {
+        if (finished) {
+            throw new NoSuchElementException("All values have been exhausted");
+        }
+        if (nextValue == null) {
+            throw new IllegalStateException("'next()' called without asking 'hasNext()'");
+        }
+
+        T v = nextValue;
+        nextValue = null;   // so it will be re-fetched when 'hasNext' is called
+        return v;
+    }
+
+    @Override
+    public String toString() {
+        Class<?> t = getType();
+        return Iterator.class.getSimpleName() + "[lazy-select](" + t.getSimpleName() + ")";
+    }
+
+    /**
+     * @param <T> Type if iterated element
+     * @param The source values - ignored if {@code null}
+     * @param type The (never @code null) type of values to select - any value
+     * whose type is assignable to this type will be selected by the iterator.
+     * @return An {@link Iterator} whose {@code next()} call selects only values
+     * matching the specific type. <b>Note:</b> the matching values are not
+     * pre-calculated (hence the &quot;lazy&quot; denomination) - i.e.,
+     * the match is performed only when {@link Iterator#hasNext()} is called.
+     */
+    public static <T> Iterator<T> lazySelectMatchingTypes(Iterator<?> values, Class<T> type) {
+        Objects.requireNonNull(type, "No type selector specified");
+        return (values == null) ? Collections.emptyIterator() : new LazyMatchingTypeIterator<>(values, type);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2c2982d6/sshd-common/src/test/java/org/apache/sshd/common/util/GenericUtilsTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/GenericUtilsTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/GenericUtilsTest.java
index 8470385..2b25f4a 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/util/GenericUtilsTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/util/GenericUtilsTest.java
@@ -19,21 +19,10 @@
 
 package org.apache.sshd.common.util;
 
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.LocalTime;
-import java.time.temporal.Temporal;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.NoSuchElementException;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 import org.apache.sshd.util.test.JUnitTestSupport;
 import org.apache.sshd.util.test.NoIoTestCase;
@@ -184,50 +173,4 @@ public class GenericUtilsTest extends JUnitTestSupport {
         assertEquals("s1 vs. s2", Integer.signum(s1.compareTo(s2)), Integer.signum(GenericUtils.compare(c1, c2)));
         assertEquals("s2 vs. s1", Integer.signum(s2.compareTo(s1)), Integer.signum(GenericUtils.compare(c2, c1)));
     }
-
-    @Test
-    public void testLazySelectMatchingTypes() {
-        Collection<String> strings = Arrays.asList(
-            getCurrentTestName(),
-            getClass().getSimpleName(),
-            getClass().getPackage().getName());
-        Collection<Temporal> times = Arrays.asList(
-            LocalDateTime.now(),
-            LocalTime.now(),
-            LocalDate.now());
-        List<Object> values = Stream.concat(strings.stream(), times.stream()).collect(Collectors.toList());
-        AtomicInteger matchCount = new AtomicInteger(0);
-        for (int index = 1, count = values.size(); index <= count; index++) {
-            Collections.shuffle(values);
-            Class<?> type = ((index & 0x01) == 0) ? String.class : Temporal.class;
-            Iterator<?> lazy = GenericUtils.lazySelectMatchingTypes(
-                new Iterator<Object>() {
-                    private final Iterator<?> iter = values.iterator();
-
-                    {
-                        matchCount.set(0);
-                    }
-
-                    @Override
-                    public boolean hasNext() {
-                        return iter.hasNext();
-                    }
-
-                    @Override
-                    public Object next() {
-                        Object v = iter.next();
-                        if (type.isInstance(v)) {
-                            matchCount.incrementAndGet();
-                        }
-                        return v;
-                    }
-                }, type);
-            Set<?> expected = (type == String.class) ? new HashSet<>(strings) : new HashSet<>(times);
-            for (int c = 1; lazy.hasNext(); c++) {
-                Object o = lazy.next();
-                assertEquals("Mismatched match count for " + o, c, matchCount.get());
-                assertTrue("Unexpected value: " + o, expected.remove(o));
-            }
-        }
-    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2c2982d6/sshd-common/src/test/java/org/apache/sshd/common/util/helper/LazyIterablesConcatenatorTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/helper/LazyIterablesConcatenatorTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/helper/LazyIterablesConcatenatorTest.java
new file mode 100644
index 0000000..06d2e7d
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/util/helper/LazyIterablesConcatenatorTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.helper;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LazyIterablesConcatenatorTest extends JUnitTestSupport {
+    public LazyIterablesConcatenatorTest() {
+        super();
+    }
+
+    @Test
+    public void testLazyConcatenateIterables() {
+        Collection<String> l1 = Arrays.asList(
+            getCurrentTestName(),
+            getClass().getSimpleName(),
+            getClass().getPackage().getName());
+        Collection<String> l2 = Arrays.asList(
+            LocalDateTime.now().toString(),
+            LocalTime.now().toString(),
+            LocalDate.now().toString());
+        List<String> expected = Stream.concat(l1.stream(), l2.stream()).collect(Collectors.toList());
+        Iterable<String> iter = LazyIterablesConcatenator.lazyConcatenateIterables(Arrays.asList(l1, l2));
+        List<String> actual = new ArrayList<>(expected.size());
+        for (int index = 1, count = expected.size(); index <= count; index++) {
+            actual.clear();
+
+            for (String s : iter) {
+                actual.add(s);
+            }
+
+            assertListEquals("Attempt #" + index, expected, actual);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2c2982d6/sshd-common/src/test/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIteratorTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIteratorTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIteratorTest.java
new file mode 100644
index 0000000..43a0d70
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIteratorTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.helper;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.temporal.Temporal;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LazyMatchingTypeIteratorTest extends JUnitTestSupport {
+    public LazyMatchingTypeIteratorTest() {
+        super();
+    }
+
+    @Test
+    public void testLazySelectMatchingTypes() {
+        Collection<String> strings = Arrays.asList(
+            getCurrentTestName(),
+            getClass().getSimpleName(),
+            getClass().getPackage().getName());
+        Collection<Temporal> times = Arrays.asList(
+            LocalDateTime.now(),
+            LocalTime.now(),
+            LocalDate.now());
+        List<Object> values = Stream.concat(strings.stream(), times.stream()).collect(Collectors.toList());
+        AtomicInteger matchCount = new AtomicInteger(0);
+        for (int index = 1, count = values.size(); index <= count; index++) {
+            Collections.shuffle(values);
+            Class<?> type = ((index & 0x01) == 0) ? String.class : Temporal.class;
+            Iterator<?> lazy = LazyMatchingTypeIterator.lazySelectMatchingTypes(
+                new Iterator<Object>() {
+                    private final Iterator<?> iter = values.iterator();
+
+                    {
+                        matchCount.set(0);
+                    }
+
+                    @Override
+                    public boolean hasNext() {
+                        return iter.hasNext();
+                    }
+
+                    @Override
+                    public Object next() {
+                        Object v = iter.next();
+                        if (type.isInstance(v)) {
+                            matchCount.incrementAndGet();
+                        }
+                        return v;
+                    }
+                }, type);
+            Set<?> expected = (type == String.class) ? new HashSet<>(strings) : new HashSet<>(times);
+            for (int c = 1; lazy.hasNext(); c++) {
+                Object o = lazy.next();
+                assertEquals("Mismatched match count for " + o, c, matchCount.get());
+                assertTrue("Unexpected value: " + o, expected.remove(o));
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2c2982d6/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyPairIdentity.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyPairIdentity.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyPairIdentity.java
index b53c111..deb158c 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyPairIdentity.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyPairIdentity.java
@@ -41,8 +41,8 @@ public class KeyPairIdentity implements PublicKeyIdentity {
 
     public KeyPairIdentity(SignatureFactoriesManager primary, SignatureFactoriesManager secondary, KeyPair pair) {
         this.signatureFactories = ValidateUtils.checkNotNullAndNotEmpty(
-                SignatureFactoriesManager.resolveSignatureFactories(primary, secondary),
-                "No available signature factories");
+            SignatureFactoriesManager.resolveSignatureFactories(primary, secondary),
+            "No available signature factories");
         this.pair = Objects.requireNonNull(pair, "No key pair");
     }
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2c2982d6/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKeyIterator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKeyIterator.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKeyIterator.java
index 84414a0..73e34ee 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKeyIterator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKeyIterator.java
@@ -21,15 +21,17 @@ package org.apache.sshd.client.auth.pubkey;
 
 import java.io.IOException;
 import java.nio.channels.Channel;
+import java.security.KeyPair;
 import java.security.PublicKey;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.stream.Stream;
+import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.sshd.agent.SshAgent;
 import org.apache.sshd.agent.SshAgentFactory;
@@ -37,13 +39,13 @@ import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.FactoryManager;
 import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
 import org.apache.sshd.common.signature.SignatureFactoriesManager;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.helper.LazyIterablesConcatenator;
+import org.apache.sshd.common.util.helper.LazyMatchingTypeIterator;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class UserAuthPublicKeyIterator extends AbstractKeyPairIterator<PublicKeyIdentity> implements Channel {
-
     private final AtomicBoolean open = new AtomicBoolean(true);
     private Iterator<? extends PublicKeyIdentity> current;
     private SshAgent agent;
@@ -51,38 +53,132 @@ public class UserAuthPublicKeyIterator extends AbstractKeyPairIterator<PublicKey
     public UserAuthPublicKeyIterator(ClientSession session, SignatureFactoriesManager signatureFactories) throws Exception {
         super(session);
 
-        Collection<Stream<? extends PublicKeyIdentity>> identities = new LinkedList<>();
+        try {
+            Collection<Iterable<? extends PublicKeyIdentity>> identities = new ArrayList<>(2);
+            Iterable<? extends PublicKeyIdentity> agentIds = initializeAgentIdentities(session);
+            if (agentIds != null) {
+                identities.add(agentIds);
+            }
 
-        FactoryManager manager = Objects.requireNonNull(session.getFactoryManager(), "No session factory manager");
-        SshAgentFactory factory = manager.getAgentFactory();
-        if (factory != null) {
+            Iterable<? extends PublicKeyIdentity> sessionIds =
+                initializeSessionIdentities(session, signatureFactories);
+            if (sessionIds != null) {
+                identities.add(sessionIds);
+            }
+
+            if (identities.isEmpty()) {
+                current = Collections.emptyIterator();
+            } else {
+                Iterable<? extends PublicKeyIdentity> keys =
+                    LazyIterablesConcatenator.lazyConcatenateIterables(identities);
+                current = LazyMatchingTypeIterator.lazySelectMatchingTypes(keys.iterator(), PublicKeyIdentity.class);
+            }
+        } catch (Exception e) {
             try {
-                agent = Objects.requireNonNull(factory.createClient(manager), "No agent created");
-                Iterable<? extends Map.Entry<PublicKey, String>> agentIds = agent.getIdentities();
-                Collection<KeyAgentIdentity> ids = new LinkedList<>();
-                for (Map.Entry<PublicKey, String> kp : agentIds) {
-                    ids.add(new KeyAgentIdentity(agent, kp.getKey(), kp.getValue()));
-                }
-                if (!ids.isEmpty()) {
-                    identities.add(ids.stream());
-                }
-            } catch (Exception e) {
-                try {
-                    closeAgent();
-                } catch (Exception err) {
-                    e.addSuppressed(err);
+                closeAgent();
+            } catch (Exception err) {
+                e.addSuppressed(err);
+            }
+
+            throw e;
+        }
+    }
+
+    protected Iterable<KeyPairIdentity> initializeSessionIdentities(
+            ClientSession session, SignatureFactoriesManager signatureFactories) {
+        return new Iterable<KeyPairIdentity>() {
+            private final String sessionId = session.toString();
+            private final AtomicReference<Iterable<KeyPair>> keysHolder = new AtomicReference<>();
+
+            @Override
+            public Iterator<KeyPairIdentity> iterator() {
+                // Lazy load the keys the 1st time the iterator is called
+                if (keysHolder.get() == null) {
+                    KeyIdentityProvider sessionKeysProvider = ClientSession.providerOf(session);
+                    keysHolder.set(sessionKeysProvider.loadKeys());
                 }
 
-                throw e;
+                return new Iterator<KeyPairIdentity>() {
+                    private final Iterator<KeyPair> keys;
+
+                    {
+                        @SuppressWarnings("synthetic-access")
+                        Iterable<KeyPair> sessionKeys =
+                            Objects.requireNonNull(keysHolder.get(), "No session keys available");
+                        keys = sessionKeys.iterator();
+                    }
+
+                    @Override
+                    public boolean hasNext() {
+                        return keys.hasNext();
+                    }
+
+                    @Override
+                    public KeyPairIdentity next() {
+                        KeyPair kp = keys.next();
+                        return new KeyPairIdentity(signatureFactories, session, kp);
+                    }
+
+                    @Override
+                    @SuppressWarnings("synthetic-access")
+                    public String toString() {
+                        return KeyPairIdentity.class.getSimpleName() + "[iterator][" + sessionId + "]";
+                    }
+                };
             }
+
+            @Override
+            public String toString() {
+                return KeyPairIdentity.class.getSimpleName() + "[iterable][" + sessionId + "]";
+            }
+        };
+    }
+
+    protected Iterable<KeyAgentIdentity> initializeAgentIdentities(ClientSession session) throws IOException {
+        FactoryManager manager = Objects.requireNonNull(session.getFactoryManager(), "No session factory manager");
+        SshAgentFactory factory = manager.getAgentFactory();
+        if (factory == null) {
+            return null;
         }
 
-        identities.add(Stream.of(ClientSession.providerOf(session))
-            .map(KeyIdentityProvider::loadKeys)
-            .flatMap(GenericUtils::stream)
-            .map(kp -> new KeyPairIdentity(signatureFactories, session, kp)));
+        agent = Objects.requireNonNull(factory.createClient(manager), "No agent created");
+        return new Iterable<KeyAgentIdentity>() {
+            @SuppressWarnings("synthetic-access")
+            private final Iterable<? extends Map.Entry<PublicKey, String>> agentIds = agent.getIdentities();
+            @SuppressWarnings("synthetic-access")
+            private final String agentId = agent.toString();
+
+            @Override
+            public Iterator<KeyAgentIdentity> iterator() {
+                return new Iterator<KeyAgentIdentity>() {
+                    @SuppressWarnings("synthetic-access")
+                    private final Iterator<? extends Map.Entry<PublicKey, String>> iter = agentIds.iterator();
+
+                    @Override
+                    public boolean hasNext() {
+                        return iter.hasNext();
+                    }
+
+                    @Override
+                    @SuppressWarnings("synthetic-access")
+                    public KeyAgentIdentity next() {
+                        Map.Entry<PublicKey, String> kp = iter.next();
+                        return new KeyAgentIdentity(agent, kp.getKey(), kp.getValue());
+                    }
+
+                    @Override
+                    @SuppressWarnings("synthetic-access")
+                    public String toString() {
+                        return KeyAgentIdentity.class.getSimpleName() + "[iterator][" + agentId + "]";
+                    }
+                };
+            }
 
-        current = identities.stream().flatMap(r -> r).iterator();
+            @Override
+            public String toString() {
+                return KeyAgentIdentity.class.getSimpleName() + "[iterable][" + agentId + "]";
+            }
+        };
     }
 
     @Override