This is an automated email from the ASF dual-hosted git repository.

lgoldstein pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git


The following commit(s) were added to refs/heads/master by this push:
     new 28b0aaa  [SSHD-1089] Added wrappers for one-time single session usage 
of SFTP/SCP clients
28b0aaa is described below

commit 28b0aaa14844c4f2e9803548901430e93dc93286
Author: Lyor Goldstein <lgoldst...@apache.org>
AuthorDate: Sat Sep 26 10:43:43 2020 +0300

    [SSHD-1089] Added wrappers for one-time single session usage of SFTP/SCP 
clients
---
 CHANGES.md                                         |   1 +
 docs/scp.md                                        |  26 +-
 docs/sftp.md                                       |  29 +-
 .../apache/sshd/common/session/SessionContext.java |   4 +-
 .../sshd/common/util/EventListenerUtils.java       |  10 +-
 .../org/apache/sshd/common/util/ProxyUtils.java    |  51 +++
 .../AutoCloseableDelegateInvocationHandler.java    | 129 +++++++
 .../NioChannelDelegateInvocationHandler.java       |  95 +++++
 .../apache/sshd/util/test/JUnitTestSupport.java    |   6 +-
 .../java/org/apache/sshd/KeyReExchangeTest.java    |   5 +-
 .../sshd/common/forward/PortForwardingTest.java    |   5 +-
 .../apache/sshd/scp/client/CloseableScpClient.java |  12 +-
 .../sshd/scp/client/SimpleScpClientImpl.java       |  25 +-
 .../sshd/scp/client/AbstractScpTestSupport.java    |  18 +
 .../java/org/apache/sshd/scp/client/ScpTest.java   |  80 ++---
 .../sshd/sftp/client/FullAccessSftpClient.java     |  20 +-
 .../org/apache/sshd/sftp/client/SftpClient.java    |  10 +
 .../sshd/sftp/client/impl/AbstractSftpClient.java  |   5 +-
 .../sftp/client/impl/SimpleSftpClientImpl.java     |  60 +---
 .../sftp/client/AbstractSftpClientTestSupport.java |  22 ++
 .../java/org/apache/sshd/sftp/client/SftpTest.java | 396 ++++++++++-----------
 .../extensions/UnsupportedExtensionTest.java       |   8 +-
 .../helpers/AbstractCheckFileExtensionTest.java    |   4 +-
 .../helpers/AbstractMD5HashExtensionTest.java      |   4 +-
 .../helpers/CopyDataExtensionImplTest.java         |   4 +-
 .../helpers/CopyFileExtensionImplTest.java         |   4 +-
 .../helpers/SpaceAvailableExtensionImplTest.java   |   4 +-
 .../openssh/helpers/OpenSSHExtensionsTest.java     |   7 +-
 .../sftp/client/fs/SftpDirectoryScannersTest.java  |   4 +-
 .../client/impl/SftpRemotePathChannelTest.java     |  46 ++-
 30 files changed, 682 insertions(+), 412 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 1c35b7c..c9d2864 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -32,6 +32,7 @@ or `-key-file` command line option.
 * [SSHD-1077](https://issues.apache.org/jira/browse/SSHD-1077) Added command 
line option to request specific SFTP version in `SftpCommandMain`
 * [SSHD-1079](https://issues.apache.org/jira/browse/SSHD-1079) Experimental 
async mode on the local port forwarder
 * [SSHD-1086](https://issues.apache.org/jira/browse/SSHD-1086) Added SFTP 
aware directory scanning helper classes
+* [SSHD-1089](https://issues.apache.org/jira/browse/SSHD-1089) Added wrappers 
for one-time single session usage of SFTP/SCP clients
 
 ## Behavioral changes and enhancements
 
diff --git a/docs/scp.md b/docs/scp.md
index 499241c..b3f131c 100644
--- a/docs/scp.md
+++ b/docs/scp.md
@@ -44,14 +44,32 @@ In order to obtain an `ScpClient` one needs to use an 
`ScpClientCreator`:
 
 ```java
 
-ClientSession session = ... obtain an instance ...
-ScpClientCreator creator = ... obtain an instance ...
-ScpClient client = creator.createScpClient(session);
-
+try (ClientSession session = ... obtain an instance ...) {
+    ScpClientCreator creator = ... obtain an instance ...
+    ScpClient client = creator.createScpClient(session);
+    ... use client ...
+}
 ```
 
 A default `ScpClientCreator` instance is provided as part of the module - see 
`ScpClientCreator.instance()`
 
+If the intended use of the client instance is "one-shot" - i.e., the client 
session should be closed when the
+SCP client instance is closed, then it is possible to obtain a special wrapper 
that implements this functionality:
+
+```java
+// The underlying session will also be closed when the client is
+try (CloseableScpClient client = createScpClient(...)) {
+    ... use client ...
+}
+
+CloseableScpClient createScpClient(...) {
+    ClientSession session = ... obtain an instance ...;
+    ScpClientCreator creator = ... obtain an instance ...
+    ScpClient client = creator.createScpClient(session);
+    return CloseableScpClient.singleSessionInstance(client);
+}
+```
+
 The `ScpClientCreator` can also be used to attach a default 
`ScpTransferEventListener` that will be automatically
 add to **all** created SCP client instances through that creator - unless 
specifically overridden:
 
diff --git a/docs/sftp.md b/docs/sftp.md
index 7a1b15a..3fd7cc9 100644
--- a/docs/sftp.md
+++ b/docs/sftp.md
@@ -143,14 +143,37 @@ In order to obtain an `SftpClient` instance one needs to 
use an `SftpClientFacto
 
 ```java
 
-    ClientSession session = ...obtain session...
-    SftpClientFactory factory = ...obtain factory...
-    SftpClient client = factory.createSftpClient(session);
+    try (ClientSession session = ...obtain session...) {
+        SftpClientFactory factory = ...obtain factory...
+        try (SftpClient client = factory.createSftpClient(session)) {
+            ... use the SFTP client...
+        }
+        
+        // NOTE: session is still alive here...
+    }
 
 ```
 
 A default client factory implementations is provided in the module - see 
`SftpClientFactory.instance()`
 
+If the intended use of the client instance is "one-shot" - i.e., the client 
session should be closed when the
+SFTP client instance is closed, then it is possible to obtain a special 
wrapper that implements this functionality:
+
+```java
+
+// The underlying session will also be closed when the client is
+try (SftpClient client = createSftpClient(....)) {
+    ... use the SFTP client...
+}
+
+SftpClient createSftpClient(...) {
+    ClientSession session = ...obtain session...
+    SftpClientFactory factory = ...obtain factory...
+    SftpClient client = factory.createSftpClient(session);
+    return client.singleSessionInstance();
+}
+```
+
 ### Using a custom `SftpClientFactory`
 
 The code creates `SftpClient`-s and `SftpFileSystem`-s using a default 
built-in `SftpClientFactory` instance (see
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/session/SessionContext.java 
b/sshd-common/src/main/java/org/apache/sshd/common/session/SessionContext.java
index 4bc87f6..622c5db 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/session/SessionContext.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/session/SessionContext.java
@@ -46,14 +46,14 @@ public interface SessionContext
         Closeable {
     /**
      * Default prefix expected for the client / server identification string
-     * 
+     *
      * @see <A HREF="https://tools.ietf.org/html/rfc4253#section-4.2";>RFC 4253 
- section 4.2</A>
      */
     String DEFAULT_SSH_VERSION_PREFIX = "SSH-2.0-";
 
     /**
      * Backward compatible special prefix
-     * 
+     *
      * @see <A HREF="https://tools.ietf.org/html/rfc4253#section-5";>RFC 4253 - 
section 5</A>
      */
     String FALLBACK_SSH_VERSION_PREFIX = "SSH-1.99-";
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/EventListenerUtils.java 
b/sshd-common/src/main/java/org/apache/sshd/common/util/EventListenerUtils.java
index f66f3c6..ffc719f 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/EventListenerUtils.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/EventListenerUtils.java
@@ -189,13 +189,10 @@ public final class EventListenerUtils {
      * @see                             #proxyWrapper(Class, ClassLoader, 
Iterable)
      */
     public static <T extends SshdEventListener> T proxyWrapper(
-            Class<T> listenerType, ClassLoader loader, final Iterable<? 
extends T> listeners) {
-        Objects.requireNonNull(listenerType, "No listener type specified");
-        ValidateUtils.checkTrue(listenerType.isInterface(), "Target proxy is 
not an interface: %s",
-                listenerType.getSimpleName());
+            Class<T> listenerType, ClassLoader loader, Iterable<? extends T> 
listeners) {
         Objects.requireNonNull(listeners, "No listeners container provided");
 
-        Object wrapper = Proxy.newProxyInstance(loader, new Class<?>[] { 
listenerType }, (proxy, method, args) -> {
+        return ProxyUtils.newProxyInstance(loader, listenerType, (proxy, 
method, args) -> {
             Throwable err = null;
             for (T l : listeners) {
                 try {
@@ -207,11 +204,10 @@ public final class EventListenerUtils {
             }
 
             if (err != null) {
-                throw err;
+                throw ProxyUtils.unwrapInvocationThrowable(err);
             }
 
             return null; // we assume always void return value...
         });
-        return listenerType.cast(wrapper);
     }
 }
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/ProxyUtils.java 
b/sshd-common/src/main/java/org/apache/sshd/common/util/ProxyUtils.java
new file mode 100644
index 0000000..864c5b6
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/ProxyUtils.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Proxy;
+
+/**
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ */
+public final class ProxyUtils {
+    private ProxyUtils() {
+        throw new UnsupportedOperationException("No instance");
+    }
+
+    public static <T> T newProxyInstance(Class<T> type, InvocationHandler 
handler) {
+        return newProxyInstance(type.getClassLoader(), type, handler);
+    }
+
+    public static <T> T newProxyInstance(ClassLoader cl, Class<T> type, 
InvocationHandler handler) {
+        Class<?>[] interfaces = { type };
+        Object wrapper = Proxy.newProxyInstance(cl, interfaces, handler);
+        return type.cast(wrapper);
+    }
+
+    public static Throwable unwrapInvocationThrowable(Throwable t) {
+        if (t instanceof InvocationTargetException) {
+            return unwrapInvocationThrowable(((InvocationTargetException) 
t).getTargetException());
+        } else {
+            return t;
+        }
+    }
+}
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/AutoCloseableDelegateInvocationHandler.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/AutoCloseableDelegateInvocationHandler.java
new file mode 100644
index 0000000..e6914d5
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/AutoCloseableDelegateInvocationHandler.java
@@ -0,0 +1,129 @@
+/*
+ * 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.closeable;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Objects;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ProxyUtils;
+import org.apache.sshd.common.util.logging.LoggingUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Wraps a target instance and an {@link AutoCloseable} delegate into a proxy 
instance that closes both when wrapper
+ * {@link AutoCloseable#close() close} method called.
+ *
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ */
+public class AutoCloseableDelegateInvocationHandler implements 
InvocationHandler {
+    private final Object proxyTarget;
+    private final AutoCloseable delegate;
+    // Order is important - we want to close the proxy before the delegate
+    private final Object[] closers;
+
+    public AutoCloseableDelegateInvocationHandler(Object proxyTarget, 
AutoCloseable delegate) {
+        this.proxyTarget = Objects.requireNonNull(proxyTarget, "No proxy 
target to wrap");
+        this.delegate = Objects.requireNonNull(delegate, "No delegate to 
auto-close");
+        this.closers = new Object[] { proxyTarget, delegate };
+    }
+
+    public Object getProxyTarget() {
+        return proxyTarget;
+    }
+
+    public AutoCloseable getAutoCloseableDelegate() {
+        return delegate;
+    }
+
+    @Override
+    public Object invoke(Object proxy, Method method, Object[] args) throws 
Throwable {
+        // If not invoking "close" then propagate to target as-is
+        if (!isCloseMethodInvocation(method, args)) {
+            Object target = getProxyTarget();
+            try {
+                return method.invoke(target, args);
+            } catch (Throwable t) {
+                Class<?> targetType = target.getClass();
+                Logger log = LoggerFactory.getLogger(targetType);
+                LoggingUtils.debug(log, "invoke({}#{}) failed ({}) to execute: 
{}",
+                        targetType.getSimpleName(), method.getName(), 
t.getClass().getSimpleName(), t.getMessage(), t);
+                throw ProxyUtils.unwrapInvocationThrowable(t);
+            }
+        }
+
+        Throwable err = null;
+        for (Object c : closers) {
+            if (!(c instanceof AutoCloseable)) {
+                continue;   // OK if proxy target is not AutoCloseable
+            }
+
+            try {
+                method.invoke(c, args);
+            } catch (Throwable t) {
+                Class<? extends Object> closerType = c.getClass();
+                Logger log = LoggerFactory.getLogger(closerType);
+                LoggingUtils.debug(log, "invoke({}#{}) failed ({}) to execute: 
{}",
+                        closerType.getSimpleName(), method.getName(), 
t.getClass().getSimpleName(), t.getMessage(), t);
+                err = GenericUtils.accumulateException(err, t);
+            }
+        }
+
+        if (err != null) {
+            throw ProxyUtils.unwrapInvocationThrowable(err);
+        }
+
+        return null;
+    }
+
+    /**
+     * Wraps a target instance and an {@link AutoCloseable} delegate into a 
proxy instance that closes both when wrapper
+     * {@link AutoCloseable#close() close} method called.
+     *
+     * @param  <T>         The generic {@link AutoCloseable} wrapping interface
+     * @param  proxyTarget The (never {@code null}) target instance - if not 
{@link AutoCloseable} then it's
+     *                     {@code close()} method will not be invoked (i.e., 
only the delegate)
+     * @param  type        The target wrapping interface
+     * @param  delegate    The (never {@code null}) delegate to close. 
<B>Note:</B> the delegate is closed <U>after</U>
+     *                     the target instance.
+     * @return             The wrapping proxy
+     */
+    public static <T extends AutoCloseable> T wrapDelegateCloseable(
+            Object proxyTarget, Class<T> type, AutoCloseable delegate) {
+        return ProxyUtils.newProxyInstance(type, new 
AutoCloseableDelegateInvocationHandler(proxyTarget, delegate));
+    }
+
+    public static boolean isCloseMethodInvocation(Method m, Object[] args) {
+        return isCloseMethod(m) && GenericUtils.isEmpty(args);
+    }
+
+    public static boolean isCloseMethod(Method m) {
+        int mods = (m == null) ? 0 : m.getModifiers();
+        return (m != null)
+                && "close".equals(m.getName())
+                && Modifier.isPublic(mods)
+                && (!Modifier.isStatic(mods))
+                && (void.class == m.getReturnType())
+                && GenericUtils.isEmpty(m.getParameterTypes());
+    }
+}
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/NioChannelDelegateInvocationHandler.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/NioChannelDelegateInvocationHandler.java
new file mode 100644
index 0000000..886eeb2
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/NioChannelDelegateInvocationHandler.java
@@ -0,0 +1,95 @@
+/*
+ * 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.closeable;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.nio.channels.Channel;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ProxyUtils;
+import org.apache.sshd.common.util.logging.LoggingUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Wraps a target instance and a {@link Channel} delegate into a proxy 
instance that closes both when wrapper
+ * {@link AutoCloseable#close() close} method called. The {@link 
Channel#isOpen()} call is invoked only on the delegate
+ *
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ */
+public class NioChannelDelegateInvocationHandler extends 
AutoCloseableDelegateInvocationHandler {
+    public NioChannelDelegateInvocationHandler(Object proxyTarget, Channel 
delegate) {
+        super(proxyTarget, delegate);
+    }
+
+    public Channel getChannelDelegate() {
+        return Channel.class.cast(super.getAutoCloseableDelegate());
+    }
+
+    @Override
+    public Object invoke(Object proxy, Method method, Object[] args) throws 
Throwable {
+        if (!isQueryOpenMethodInvocation(method, args)) {
+            return super.invoke(proxy, method, args);
+        }
+
+        Channel channelDelegate = getChannelDelegate();
+        try {
+            return method.invoke(channelDelegate, args);
+        } catch (Throwable t) {
+            Class<?> targetType = channelDelegate.getClass();
+            Logger log = LoggerFactory.getLogger(targetType);
+            LoggingUtils.debug(log, "invoke({}#{}) failed ({}) to execute: {}",
+                    targetType.getSimpleName(), method.getName(), 
t.getClass().getSimpleName(), t.getMessage(), t);
+            throw ProxyUtils.unwrapInvocationThrowable(t);
+        }
+    }
+
+    /**
+     * Wraps a target instance and a {@link Channel} delegate into a proxy 
instance that closes both when wrapper
+     * {@link Channel#close() close} method called. The {@link 
Channel#isOpen()} call is invoked only on the delegate
+     *
+     * @param  <T>         The generic {@link Channel} wrapping interface
+     * @param  proxyTarget The (never {@code null}) target instance - if not 
{@link AutoCloseable} then it's
+     *                     {@code close()} method will not be invoked (i.e., 
only the delegate)
+     * @param  type        The target wrapping interface
+     * @param  delegate    The (never {@code null}) delegate to use. 
<B>Note:</B> the delegate is closed <U>after</U>
+     *                     the target instance.
+     * @return             The wrapping proxy
+     */
+    public static <T extends Channel> T wrapDelegateChannel(
+            Object proxyTarget, Class<T> type, Channel delegate) {
+        return ProxyUtils.newProxyInstance(type, new 
NioChannelDelegateInvocationHandler(proxyTarget, delegate));
+    }
+
+    public static boolean isQueryOpenMethodInvocation(Method m, Object[] args) 
{
+        return isQueryOpenMethodInvocation(m) && GenericUtils.isEmpty(args);
+    }
+
+    public static boolean isQueryOpenMethodInvocation(Method m) {
+        int mods = (m == null) ? 0 : m.getModifiers();
+        return (m != null)
+                && "isOpen".equals(m.getName())
+                && Modifier.isPublic(mods)
+                && (!Modifier.isStatic(mods))
+                && (boolean.class == m.getReturnType())
+                && GenericUtils.isEmpty(m.getParameterTypes());
+    }
+}
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
index cd71bc2..a635fc9 100644
--- 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
@@ -608,7 +608,11 @@ public abstract class JUnitTestSupport extends Assert {
             long nanoSleep = sleepEnd - sleepStart;
 
             sleepTime = TimeUnit.NANOSECONDS.toMillis(nanoSleep);
-            timeout -= sleepTime;
+            if (sleepTime <= 0L) {
+                timeout -= 10L;
+            } else {
+                timeout -= sleepTime;
+            }
         }
 
         return false;
diff --git a/sshd-core/src/test/java/org/apache/sshd/KeyReExchangeTest.java 
b/sshd-core/src/test/java/org/apache/sshd/KeyReExchangeTest.java
index 2724a8e..a536252 100644
--- a/sshd-core/src/test/java/org/apache/sshd/KeyReExchangeTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/KeyReExchangeTest.java
@@ -24,7 +24,6 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.PipedInputStream;
 import java.io.PipedOutputStream;
-import java.lang.reflect.Proxy;
 import java.nio.charset.StandardCharsets;
 import java.time.Duration;
 import java.util.ArrayList;
@@ -52,6 +51,7 @@ import org.apache.sshd.common.kex.KeyExchangeFactory;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.session.SessionListener;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ProxyUtils;
 import org.apache.sshd.common.util.io.NullOutputStream;
 import org.apache.sshd.common.util.security.SecurityUtils;
 import org.apache.sshd.core.CoreModuleProperties;
@@ -156,7 +156,6 @@ public class KeyReExchangeTest extends BaseTestSupport {
             AtomicBoolean successfulInit = new AtomicBoolean(true);
             AtomicBoolean successfulNext = new AtomicBoolean(true);
             ClassLoader loader = getClass().getClassLoader();
-            Class<?>[] interfaces = { KeyExchange.class };
             for (KeyExchangeFactory factory : 
client.getKeyExchangeFactories()) {
                 kexFactories.add(new KeyExchangeFactory() {
                     @Override
@@ -167,7 +166,7 @@ public class KeyReExchangeTest extends BaseTestSupport {
                     @Override
                     public KeyExchange createKeyExchange(Session s) throws 
Exception {
                         KeyExchange proxiedInstance = 
factory.createKeyExchange(s);
-                        return (KeyExchange) Proxy.newProxyInstance(loader, 
interfaces, (proxy, method, args) -> {
+                        return ProxyUtils.newProxyInstance(loader, 
KeyExchange.class, (proxy, method, args) -> {
                             String name = method.getName();
                             if ("init".equals(name) && 
(!successfulInit.get())) {
                                 throw new 
UnsupportedOperationException("Intentionally failing 'init'");
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/common/forward/PortForwardingTest.java
 
b/sshd-core/src/test/java/org/apache/sshd/common/forward/PortForwardingTest.java
index 1210e59..51bcb80 100644
--- 
a/sshd-core/src/test/java/org/apache/sshd/common/forward/PortForwardingTest.java
+++ 
b/sshd-core/src/test/java/org/apache/sshd/common/forward/PortForwardingTest.java
@@ -24,7 +24,6 @@ import java.io.OutputStream;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
 import java.net.InetSocketAddress;
 import java.net.Socket;
 import java.nio.charset.StandardCharsets;
@@ -55,6 +54,7 @@ import 
org.apache.sshd.client.session.forward.ExplicitPortForwardingTracker;
 import org.apache.sshd.common.session.ConnectionService;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.MapEntryUtils.NavigableMapBuilder;
+import org.apache.sshd.common.util.ProxyUtils;
 import org.apache.sshd.common.util.net.SshdSocketAddress;
 import org.apache.sshd.core.CoreModuleProperties;
 import org.apache.sshd.server.SshServer;
@@ -180,7 +180,6 @@ public class PortForwardingTest extends BaseTestSupport {
 
         ForwarderFactory factory = 
Objects.requireNonNull(sshd.getForwarderFactory(), "No ForwarderFactory");
         sshd.setForwarderFactory(new ForwarderFactory() {
-            private final Class<?>[] interfaces = { Forwarder.class };
             private final Map<String, String> method2req
                     = NavigableMapBuilder.<String, String> 
builder(String.CASE_INSENSITIVE_ORDER)
                             .put("localPortForwardingRequested", 
TcpipForwardHandler.REQUEST)
@@ -193,7 +192,7 @@ public class PortForwardingTest extends BaseTestSupport {
                 ClassLoader cl = thread.getContextClassLoader();
 
                 Forwarder forwarder = factory.create(service);
-                return (Forwarder) Proxy.newProxyInstance(cl, interfaces, new 
InvocationHandler() {
+                return ProxyUtils.newProxyInstance(cl, Forwarder.class, new 
InvocationHandler() {
                     private final org.slf4j.Logger log = 
LoggerFactory.getLogger(Forwarder.class);
 
                     @SuppressWarnings("synthetic-access")
diff --git 
a/sshd-scp/src/main/java/org/apache/sshd/scp/client/CloseableScpClient.java 
b/sshd-scp/src/main/java/org/apache/sshd/scp/client/CloseableScpClient.java
index 0171a88..68ddb1c 100644
--- a/sshd-scp/src/main/java/org/apache/sshd/scp/client/CloseableScpClient.java
+++ b/sshd-scp/src/main/java/org/apache/sshd/scp/client/CloseableScpClient.java
@@ -21,11 +21,21 @@ package org.apache.sshd.scp.client;
 
 import java.nio.channels.Channel;
 
+import 
org.apache.sshd.common.util.closeable.NioChannelDelegateInvocationHandler;
+
 /**
  * An {@link ScpClient} wrapper that also closes the underlying session when 
closed
  *
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
  */
 public interface CloseableScpClient extends ScpClient, Channel {
-    // Marker interface
+    /**
+     * @param  client The (never {@code null}) {@link ScpClient} instance
+     * @return        A {@link CloseableScpClient} wrapper that also closes 
the underlying {@link #getClientSession()}
+     *                when closed
+     */
+    static CloseableScpClient singleSessionInstance(ScpClient client) {
+        return NioChannelDelegateInvocationHandler.wrapDelegateChannel(
+                client, CloseableScpClient.class, client.getClientSession());
+    }
 }
diff --git 
a/sshd-scp/src/main/java/org/apache/sshd/scp/client/SimpleScpClientImpl.java 
b/sshd-scp/src/main/java/org/apache/sshd/scp/client/SimpleScpClientImpl.java
index afb1990..7b8e474 100644
--- a/sshd-scp/src/main/java/org/apache/sshd/scp/client/SimpleScpClientImpl.java
+++ b/sshd-scp/src/main/java/org/apache/sshd/scp/client/SimpleScpClientImpl.java
@@ -20,7 +20,6 @@
 package org.apache.sshd.scp.client;
 
 import java.io.IOException;
-import java.lang.reflect.Proxy;
 import java.net.SocketAddress;
 import java.security.KeyPair;
 import java.util.Objects;
@@ -98,7 +97,7 @@ public class SimpleScpClientImpl extends AbstractLoggingBean 
implements SimpleSc
         try {
             ScpClientCreator creator = getScpClientCreator();
             ScpClient client = 
creator.createScpClient(Objects.requireNonNull(session, "No client session"));
-            return createScpClient(session, client);
+            return CloseableScpClient.singleSessionInstance(client);
         } catch (Exception e) {
             log.warn("createScpClient({}) failed ({}) to create proxy: {}",
                     session, e.getClass().getSimpleName(), e.getMessage());
@@ -114,28 +113,6 @@ public class SimpleScpClientImpl extends 
AbstractLoggingBean implements SimpleSc
         }
     }
 
-    protected CloseableScpClient createScpClient(ClientSession session, 
ScpClient client) throws IOException {
-        ClassLoader loader = CloseableScpClient.class.getClassLoader();
-        Class<?>[] interfaces = { CloseableScpClient.class };
-        return (CloseableScpClient) Proxy.newProxyInstance(loader, interfaces, 
(proxy, method, args) -> {
-            String name = method.getName();
-            try {
-                // The Channel implementation is provided by the session
-                if (("close".equals(name) || "isOpen".equals(name)) && 
GenericUtils.isEmpty(args)) {
-                    return method.invoke(session, args);
-                } else {
-                    return method.invoke(client, args);
-                }
-            } catch (Throwable t) {
-                if (log.isTraceEnabled()) {
-                    log.trace("invoke(CloseableScpClient#{}) failed ({}) to 
execute: {}",
-                            name, t.getClass().getSimpleName(), 
t.getMessage());
-                }
-                throw t;
-            }
-        });
-    }
-
     @Override
     public boolean isOpen() {
         return true;
diff --git 
a/sshd-scp/src/test/java/org/apache/sshd/scp/client/AbstractScpTestSupport.java 
b/sshd-scp/src/test/java/org/apache/sshd/scp/client/AbstractScpTestSupport.java
index abb0021..b5f6c87 100644
--- 
a/sshd-scp/src/test/java/org/apache/sshd/scp/client/AbstractScpTestSupport.java
+++ 
b/sshd-scp/src/test/java/org/apache/sshd/scp/client/AbstractScpTestSupport.java
@@ -174,6 +174,24 @@ public abstract class AbstractScpTestSupport extends 
BaseTestSupport {
         return OUTPUT_DEBUG_MESSAGES ? DEBUG_LISTENER : 
ScpTransferEventListener.EMPTY;
     }
 
+    protected CloseableScpClient createCloseableScpClient() throws IOException 
{
+        return createCloseableScpClient(getCurrentTestName());
+    }
+
+    protected CloseableScpClient createCloseableScpClient(String username) 
throws IOException {
+        ClientSession session = createAuthenticatedClientSession(username);
+        try {
+            ScpClient client = createScpClient(session);
+            CloseableScpClient closer = 
CloseableScpClient.singleSessionInstance(client);
+            session = null; // avoid auto-close at finally clause
+            return closer;
+        } finally {
+            if (session != null) {
+                session.close();
+            }
+        }
+    }
+
     protected static ScpClient createScpClient(ClientSession session) {
         ScpClientCreator creator = ScpClientCreator.instance();
         ScpTransferEventListener listener = 
getScpTransferEventListener(session);
diff --git a/sshd-scp/src/test/java/org/apache/sshd/scp/client/ScpTest.java 
b/sshd-scp/src/test/java/org/apache/sshd/scp/client/ScpTest.java
index a07c097..214fe0a 100644
--- a/sshd-scp/src/test/java/org/apache/sshd/scp/client/ScpTest.java
+++ b/sshd-scp/src/test/java/org/apache/sshd/scp/client/ScpTest.java
@@ -120,8 +120,7 @@ public class ScpTest extends AbstractScpTestSupport {
         String[] remoteComps = GenericUtils.split(remotePath, '/');
         Factory<? extends Random> factory = client.getRandomFactory();
         Random rnd = factory.create();
-        try (ClientSession session = createAuthenticatedClientSession()) {
-            ScpClient scp = createScpClient(session);
+        try (CloseableScpClient scp = createCloseableScpClient()) {
             StringBuilder sb = new StringBuilder(remotePath.length() + 
Long.SIZE);
             for (int i = 0; i < Math.max(Long.SIZE, remoteComps.length); i++) {
                 if (sb.length() > 0) {
@@ -175,8 +174,7 @@ public class ScpTest extends AbstractScpTestSupport {
         Path remoteFile = 
remoteDir.resolve(localFile.getFileName().toString());
         String localPath = localFile.toString();
         String remotePath = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, remoteFile);
-        try (ClientSession session = createAuthenticatedClientSession()) {
-            ScpClient scp = createScpClient(session);
+        try (CloseableScpClient scp = createCloseableScpClient()) {
             scp.upload(localPath, remotePath);
             assertFileLength(remoteFile, data.length, DEFAULT_TIMEOUT);
 
@@ -194,28 +192,28 @@ public class ScpTest extends AbstractScpTestSupport {
 
     @Test
     public void testScpUploadOverwrite() throws Exception {
-        try (ClientSession session = createAuthenticatedClientSession()) {
-            ScpClient scp = createScpClient(session);
-            String data = getClass().getName() + "#" + getCurrentTestName() + 
IoUtils.EOL;
+        String data = getClass().getName() + "#" + getCurrentTestName() + 
IoUtils.EOL;
 
-            Path targetPath = detectTargetFolder();
-            Path parentPath = targetPath.getParent();
-            Path scpRoot = CommonTestSupportUtils.resolve(targetPath,
-                    ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), 
getCurrentTestName());
-            CommonTestSupportUtils.deleteRecursive(scpRoot);
+        Path targetPath = detectTargetFolder();
+        Path parentPath = targetPath.getParent();
+        Path scpRoot = CommonTestSupportUtils.resolve(targetPath,
+                ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), 
getCurrentTestName());
+        CommonTestSupportUtils.deleteRecursive(scpRoot);
 
-            Path localDir = 
assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
-            Path localFile = localDir.resolve("file.txt");
-            CommonTestSupportUtils.writeFile(localFile, data);
+        Path localDir = 
assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+        Path localFile = localDir.resolve("file.txt");
+        CommonTestSupportUtils.writeFile(localFile, data);
 
-            Path remoteDir = 
assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
-            Path remoteFile = remoteDir.resolve(localFile.getFileName());
-            CommonTestSupportUtils.writeFile(remoteFile, data + data);
+        Path remoteDir = 
assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+        Path remoteFile = remoteDir.resolve(localFile.getFileName());
+        CommonTestSupportUtils.writeFile(remoteFile, data + data);
 
-            String remotePath = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, remoteFile);
+        String remotePath = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, remoteFile);
+        try (CloseableScpClient scp = createCloseableScpClient()) {
             scp.upload(localFile.toString(), remotePath);
-            assertFileLength(remoteFile, data.length(), DEFAULT_TIMEOUT);
         }
+
+        assertFileLength(remoteFile, data.length(), DEFAULT_TIMEOUT);
     }
 
     @Test
@@ -240,12 +238,11 @@ public class ScpTest extends AbstractScpTestSupport {
             Files.delete(zeroRemote);
         }
 
-        try (ClientSession session = createAuthenticatedClientSession()) {
-            ScpClient scp = createScpClient(session);
-            String remotePath = 
CommonTestSupportUtils.resolveRelativeRemotePath(targetPath.getParent(), 
zeroRemote);
+        String remotePath = 
CommonTestSupportUtils.resolveRelativeRemotePath(targetPath.getParent(), 
zeroRemote);
+        try (CloseableScpClient scp = createCloseableScpClient()) {
             scp.upload(zeroLocal.toString(), remotePath);
-            assertFileLength(zeroRemote, 0L, DEFAULT_TIMEOUT);
         }
+        assertFileLength(zeroRemote, 0L, DEFAULT_TIMEOUT);
     }
 
     @Test
@@ -269,12 +266,11 @@ public class ScpTest extends AbstractScpTestSupport {
         }
         assertEquals("Non-zero size for remote file=" + zeroRemote, 0L, 
Files.size(zeroRemote));
 
-        try (ClientSession session = createAuthenticatedClientSession()) {
-            ScpClient scp = createScpClient(session);
-            String remotePath = 
CommonTestSupportUtils.resolveRelativeRemotePath(targetPath.getParent(), 
zeroRemote);
+        String remotePath = 
CommonTestSupportUtils.resolveRelativeRemotePath(targetPath.getParent(), 
zeroRemote);
+        try (CloseableScpClient scp = createCloseableScpClient()) {
             scp.download(remotePath, zeroLocal.toString());
-            assertFileLength(zeroLocal, 0L, DEFAULT_TIMEOUT);
         }
+        assertFileLength(zeroLocal, 0L, DEFAULT_TIMEOUT);
     }
 
     @Test
@@ -295,8 +291,7 @@ public class ScpTest extends AbstractScpTestSupport {
         Path remoteDir = scpRoot.resolve("remote");
         Path remoteOutFile = remoteDir.resolve(localOutFile.getFileName());
 
-        try (ClientSession session = createAuthenticatedClientSession()) {
-            ScpClient scp = createScpClient(session);
+        try (CloseableScpClient scp = createCloseableScpClient()) {
             CommonTestSupportUtils.writeFile(localOutFile, data);
 
             assertFalse("Remote folder already exists: " + remoteDir, 
Files.exists(remoteDir));
@@ -335,8 +330,7 @@ public class ScpTest extends AbstractScpTestSupport {
         // see SSHD-822
         
assumeNotIoServiceProvider(EnumSet.of(BuiltinIoServiceFactoryFactories.MINA, 
BuiltinIoServiceFactoryFactories.NETTY));
 
-        try (ClientSession session = createAuthenticatedClientSession()) {
-            ScpClient scp = createScpClient(session);
+        try (CloseableScpClient scp = createCloseableScpClient()) {
             Path targetPath = detectTargetFolder();
             Path parentPath = targetPath.getParent();
             Path scpRoot = CommonTestSupportUtils.resolve(targetPath,
@@ -409,8 +403,7 @@ public class ScpTest extends AbstractScpTestSupport {
 
     @Test
     public void testScpNativeOnRecursiveDirs() throws Exception {
-        try (ClientSession session = createAuthenticatedClientSession()) {
-            ScpClient scp = createScpClient(session);
+        try (CloseableScpClient scp = createCloseableScpClient()) {
             Path targetPath = detectTargetFolder();
             Path parentPath = targetPath.getParent();
             Path scpRoot = CommonTestSupportUtils.resolve(targetPath,
@@ -444,8 +437,7 @@ public class ScpTest extends AbstractScpTestSupport {
 
     @Test // see SSHD-893
     public void testScpNativeOnDirWithPattern() throws Exception {
-        try (ClientSession session = createAuthenticatedClientSession()) {
-            ScpClient scp = createScpClient(session);
+        try (CloseableScpClient scp = createCloseableScpClient()) {
             Path targetPath = detectTargetFolder();
             Path parentPath = targetPath.getParent();
             Path scpRoot = CommonTestSupportUtils.resolve(targetPath,
@@ -481,8 +473,7 @@ public class ScpTest extends AbstractScpTestSupport {
         Files.createDirectories(remoteDir);
         sshd.setFileSystemFactory(new VirtualFileSystemFactory(remoteDir));
 
-        try (ClientSession session = createAuthenticatedClientSession()) {
-            ScpClient scp = createScpClient(session);
+        try (CloseableScpClient scp = createCloseableScpClient()) {
             Path targetPath = detectTargetFolder();
             Path scpRoot = CommonTestSupportUtils.resolve(targetPath,
                     ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), 
getCurrentTestName());
@@ -519,8 +510,7 @@ public class ScpTest extends AbstractScpTestSupport {
 
     @Test
     public void testScpNativeOnMixedDirAndFiles() throws Exception {
-        try (ClientSession session = createAuthenticatedClientSession()) {
-            ScpClient scp = createScpClient(session);
+        try (CloseableScpClient scp = createCloseableScpClient()) {
             Path targetPath = detectTargetFolder();
             Path parentPath = targetPath.getParent();
             Path scpRoot = CommonTestSupportUtils.resolve(targetPath,
@@ -559,8 +549,7 @@ public class ScpTest extends AbstractScpTestSupport {
 
     @Test
     public void testScpNativePreserveAttributes() throws Exception {
-        try (ClientSession session = createAuthenticatedClientSession()) {
-            ScpClient scp = createScpClient(session);
+        try (CloseableScpClient scp = createCloseableScpClient()) {
             Path targetPath = detectTargetFolder();
             Path parentPath = targetPath.getParent();
             Path scpRoot = CommonTestSupportUtils.resolve(targetPath,
@@ -617,8 +606,7 @@ public class ScpTest extends AbstractScpTestSupport {
 
     @Test
     public void testStreamsUploadAndDownload() throws Exception {
-        try (ClientSession session = createAuthenticatedClientSession()) {
-            ScpClient scp = createScpClient(session);
+        try (CloseableScpClient scp = createCloseableScpClient()) {
             Path targetPath = detectTargetFolder();
             Path parentPath = targetPath.getParent();
             Path scpRoot = CommonTestSupportUtils.resolve(targetPath,
@@ -770,9 +758,7 @@ public class ScpTest extends AbstractScpTestSupport {
             }
         });
 
-        try (ClientSession session = createAuthenticatedClientSession()) {
-            ScpClientCreator creator = ScpClientCreator.instance();
-            ScpClient scp = creator.createScpClient(session);
+        try (CloseableScpClient scp = createCloseableScpClient()) {
             Path targetPath = detectTargetFolder();
             Path parentPath = targetPath.getParent();
             Path scpRoot = CommonTestSupportUtils.resolve(targetPath,
diff --git 
a/sshd-scp/src/main/java/org/apache/sshd/scp/client/CloseableScpClient.java 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/FullAccessSftpClient.java
similarity index 55%
copy from 
sshd-scp/src/main/java/org/apache/sshd/scp/client/CloseableScpClient.java
copy to 
sshd-sftp/src/main/java/org/apache/sshd/sftp/client/FullAccessSftpClient.java
index 0171a88..eec17c5 100644
--- a/sshd-scp/src/main/java/org/apache/sshd/scp/client/CloseableScpClient.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/FullAccessSftpClient.java
@@ -17,15 +17,23 @@
  * under the License.
  */
 
-package org.apache.sshd.scp.client;
+package org.apache.sshd.sftp.client;
 
-import java.nio.channels.Channel;
+import 
org.apache.sshd.common.util.closeable.AutoCloseableDelegateInvocationHandler;
 
 /**
- * An {@link ScpClient} wrapper that also closes the underlying session when 
closed
- *
+ * Provides both structured and raw SFTP access
+ * 
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
  */
-public interface CloseableScpClient extends ScpClient, Channel {
-    // Marker interface
+public interface FullAccessSftpClient extends SftpClient, RawSftpClient {
+    static SftpClient singleSessionInstance(SftpClient client) {
+        if (client instanceof FullAccessSftpClient) {
+            return 
AutoCloseableDelegateInvocationHandler.wrapDelegateCloseable(
+                    client, FullAccessSftpClient.class, 
client.getClientSession());
+        } else {
+            return 
AutoCloseableDelegateInvocationHandler.wrapDelegateCloseable(
+                    client, SftpClient.class, client.getClientSession());
+        }
+    }
 }
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpClient.java 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpClient.java
index d20c03f..28ebef2 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpClient.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpClient.java
@@ -978,4 +978,14 @@ public interface SftpClient extends SubsystemClient {
      * @see                  #getServerExtensions()
      */
     SftpClientExtension getExtension(String extensionName);
+
+    /**
+     * Creates an {@link SftpClient} instance that also closes the underlying 
{@link #getClientSession() client session}
+     * when the client instance is closed.
+     *
+     * @return The wrapper instance
+     */
+    default SftpClient singleSessionInstance() {
+        return FullAccessSftpClient.singleSessionInstance(this);
+    }
 }
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/AbstractSftpClient.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/AbstractSftpClient.java
index 7e48fb3..c36049e 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/AbstractSftpClient.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/AbstractSftpClient.java
@@ -44,8 +44,7 @@ import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 import org.apache.sshd.sftp.SftpModuleProperties;
-import org.apache.sshd.sftp.client.RawSftpClient;
-import org.apache.sshd.sftp.client.SftpClient;
+import org.apache.sshd.sftp.client.FullAccessSftpClient;
 import org.apache.sshd.sftp.client.extensions.BuiltinSftpClientExtensions;
 import org.apache.sshd.sftp.client.extensions.SftpClientExtension;
 import org.apache.sshd.sftp.client.extensions.SftpClientExtensionFactory;
@@ -57,7 +56,7 @@ import org.apache.sshd.sftp.common.extensions.ParserUtils;
 /**
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
  */
-public abstract class AbstractSftpClient extends AbstractSubsystemClient 
implements SftpClient, RawSftpClient {
+public abstract class AbstractSftpClient extends AbstractSubsystemClient 
implements FullAccessSftpClient {
     public static final int INIT_COMMAND_SIZE = Byte.BYTES /* command */ + 
Integer.BYTES /* version */;
 
     private final Attributes fileOpenAttributes = new Attributes();
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SimpleSftpClientImpl.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SimpleSftpClientImpl.java
index 71198b2..8cf82b0 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SimpleSftpClientImpl.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SimpleSftpClientImpl.java
@@ -20,7 +20,6 @@
 package org.apache.sshd.sftp.client.impl;
 
 import java.io.IOException;
-import java.lang.reflect.Proxy;
 import java.net.SocketAddress;
 import java.security.KeyPair;
 
@@ -33,8 +32,10 @@ import org.apache.sshd.sftp.client.SftpClient;
 import org.apache.sshd.sftp.client.SftpClientFactory;
 import org.apache.sshd.sftp.client.SimpleSftpClient;
 
+/**
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ */
 public class SimpleSftpClientImpl extends AbstractLoggingBean implements 
SimpleSftpClient {
-
     private SimpleClient clientInstance;
     private SftpClientFactory sftpClientFactory;
 
@@ -97,15 +98,20 @@ public class SimpleSftpClientImpl extends 
AbstractLoggingBean implements SimpleS
         try {
             SftpClient client = sftpClientFactory.createSftpClient(session);
             try {
-                return createSftpClient(session, client);
+                SftpClient closer = client.singleSessionInstance();
+                client = null; // disable auto-close at finally block
+                return closer;
             } catch (Exception e) {
                 err = GenericUtils.accumulateException(err, e);
-                try {
-                    client.close();
-                } catch (Exception t) {
-                    debug("createSftpClient({}) failed ({}) to close client: 
{}",
-                            session, t.getClass().getSimpleName(), 
t.getMessage(), t);
-                    err = GenericUtils.accumulateException(err, t);
+            } finally {
+                if (client != null) {
+                    try {
+                        client.close();
+                    } catch (Exception t) {
+                        debug("createSftpClient({}) failed ({}) to close 
client: {}",
+                                session, t.getClass().getSimpleName(), 
t.getMessage(), t);
+                        err = GenericUtils.accumulateException(err, t);
+                    }
                 }
             }
         } catch (Exception e) {
@@ -113,7 +119,7 @@ public class SimpleSftpClientImpl extends 
AbstractLoggingBean implements SimpleS
         }
 
         // This point is reached if error occurred
-        log.warn("createSftpClient({}) failed ({}) to create session: {}",
+        log.warn("createSftpClient({}) failed ({}) to create client: {}",
                 session, err.getClass().getSimpleName(), err.getMessage());
 
         try {
@@ -131,40 +137,6 @@ public class SimpleSftpClientImpl extends 
AbstractLoggingBean implements SimpleS
         }
     }
 
-    protected SftpClient createSftpClient(ClientSession session, SftpClient 
client) throws IOException {
-        ClassLoader loader = SftpClient.class.getClassLoader();
-        Class<?>[] interfaces = { SftpClient.class };
-        return (SftpClient) Proxy.newProxyInstance(loader, interfaces, (proxy, 
method, args) -> {
-            Throwable err = null;
-            Object result = null;
-            String name = method.getName();
-            try {
-                result = method.invoke(client, args);
-            } catch (Throwable t) {
-                debug("invoke(SftpClient#{}) failed ({}) to execute: {}",
-                        name, t.getClass().getSimpleName(), t.getMessage(), t);
-                err = GenericUtils.accumulateException(err, t);
-            }
-
-            // propagate the "close" call to the session as well
-            if ("close".equals(name) && GenericUtils.isEmpty(args)) {
-                try {
-                    session.close();
-                } catch (Throwable t) {
-                    debug("invoke(ClientSession#{}) failed ({}) to execute: 
{}",
-                            name, t.getClass().getSimpleName(), 
t.getMessage(), t);
-                    err = GenericUtils.accumulateException(err, t);
-                }
-            }
-
-            if (err != null) {
-                throw err;
-            }
-
-            return result;
-        });
-    }
-
     @Override
     public boolean isOpen() {
         return true;
diff --git 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/AbstractSftpClientTestSupport.java
 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/AbstractSftpClientTestSupport.java
index 9ffd69d..4d7b14e 100644
--- 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/AbstractSftpClientTestSupport.java
+++ 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/AbstractSftpClientTestSupport.java
@@ -109,6 +109,28 @@ public abstract class AbstractSftpClientTestSupport 
extends BaseTestSupport {
         }
     }
 
+    protected SftpClient createSingleSessionClient() throws IOException {
+        ClientSession session = createAuthenticatedClientSession();
+        try {
+            SftpClient client = createSftpClient(session);
+            try {
+                SftpClient closer = client.singleSessionInstance();
+                // avoid auto-close at finally clause
+                client = null;
+                session = null;
+                return closer;
+            } finally {
+                if (client != null) {
+                    client.close();
+                }
+            }
+        } finally {
+            if (session != null) {
+                session.close();
+            }
+        }
+    }
+
     protected SftpClient createSftpClient(ClientSession session) throws 
IOException {
         return SftpClientFactory.instance().createSftpClient(session);
     }
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java
index 415b3c5..c920cf5 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java
@@ -169,24 +169,21 @@ public class SftpTest extends 
AbstractSftpClientTestSupport {
         rnd.fill(expectedRandom);
 
         byte[] expectedText = (getClass().getName() + "#" + 
getCurrentTestName()).getBytes(StandardCharsets.UTF_8);
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session)) {
-            String file = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, testFile);
-
-            try (CloseableHandle handle = sftp.open(
-                    file, OpenMode.Create, OpenMode.Write, OpenMode.Read, 
OpenMode.Append)) {
-                sftp.write(handle, 7365L, expectedRandom);
-                byte[] actualRandom = new byte[expectedRandom.length];
-                int readLen = sftp.read(handle, 0L, actualRandom);
-                assertEquals("Incomplete random data read", 
expectedRandom.length, readLen);
-                assertArrayEquals("Mismatched read random data", 
expectedRandom, actualRandom);
-
-                sftp.write(handle, 3777347L, expectedText);
-                byte[] actualText = new byte[expectedText.length];
-                readLen = sftp.read(handle, actualRandom.length, actualText);
-                assertEquals("Incomplete text data read", actualText.length, 
readLen);
-                assertArrayEquals("Mismatched read text data", expectedText, 
actualText);
-            }
+        String file = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, testFile);
+        try (SftpClient sftp = createSingleSessionClient();
+             CloseableHandle handle = sftp.open(
+                     file, OpenMode.Create, OpenMode.Write, OpenMode.Read, 
OpenMode.Append)) {
+            sftp.write(handle, 7365L, expectedRandom);
+            byte[] actualRandom = new byte[expectedRandom.length];
+            int readLen = sftp.read(handle, 0L, actualRandom);
+            assertEquals("Incomplete random data read", expectedRandom.length, 
readLen);
+            assertArrayEquals("Mismatched read random data", expectedRandom, 
actualRandom);
+
+            sftp.write(handle, 3777347L, expectedText);
+            byte[] actualText = new byte[expectedText.length];
+            readLen = sftp.read(handle, actualRandom.length, actualText);
+            assertEquals("Incomplete text data read", actualText.length, 
readLen);
+            assertArrayEquals("Mismatched read text data", expectedText, 
actualText);
         }
 
         byte[] actualBytes = Files.readAllBytes(testFile);
@@ -214,29 +211,27 @@ public class SftpTest extends 
AbstractSftpClientTestSupport {
         rnd.fill(expected);
         Files.write(testFile, expected);
 
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session)) {
-            String file = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, testFile);
-            byte[] actual = new byte[expected.length];
-            int maxAllowed = actual.length / 4;
-            // allow less than actual
-            SftpModuleProperties.MAX_READDATA_PACKET_LENGTH.set(sshd, 
maxAllowed);
-            try (CloseableHandle handle = sftp.open(file, OpenMode.Read)) {
-                int readLen = sftp.read(handle, 0L, actual);
-                assertEquals("Mismatched read len", maxAllowed, readLen);
-
-                for (int index = 0; index < readLen; index++) {
-                    byte expByte = expected[index];
-                    byte actByte = actual[index];
-                    if (expByte != actByte) {
-                        fail("Mismatched values at index=" + index
-                             + ": expected=0x" + Integer.toHexString(expByte & 
0xFF)
-                             + ", actual=0x" + Integer.toHexString(actByte & 
0xFF));
-                    }
+        String file = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, testFile);
+        byte[] actual = new byte[expected.length];
+        int maxAllowed = actual.length / 4;
+        // allow less than actual
+        SftpModuleProperties.MAX_READDATA_PACKET_LENGTH.set(sshd, maxAllowed);
+        try (SftpClient sftp = createSingleSessionClient();
+             CloseableHandle handle = sftp.open(file, OpenMode.Read)) {
+            int readLen = sftp.read(handle, 0L, actual);
+            assertEquals("Mismatched read len", maxAllowed, readLen);
+
+            for (int index = 0; index < readLen; index++) {
+                byte expByte = expected[index];
+                byte actByte = actual[index];
+                if (expByte != actByte) {
+                    fail("Mismatched values at index=" + index
+                         + ": expected=0x" + Integer.toHexString(expByte & 
0xFF)
+                         + ", actual=0x" + Integer.toHexString(actByte & 
0xFF));
                 }
-            } finally {
-                SftpModuleProperties.MAX_READDATA_PACKET_LENGTH.remove(sshd);
             }
+        } finally {
+            SftpModuleProperties.MAX_READDATA_PACKET_LENGTH.remove(sshd);
         }
     }
 
@@ -256,8 +251,7 @@ public class SftpTest extends AbstractSftpClientTestSupport 
{
             }
         });
 
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session)) {
+        try (SftpClient sftp = createSingleSessionClient()) {
             String rootDir = sftp.canonicalPath("/");
             String upDir = sftp.canonicalPath(rootDir + "/..");
             assertEquals("Mismatched root dir parent", rootDir, upDir);
@@ -281,39 +275,36 @@ public class SftpTest extends 
AbstractSftpClientTestSupport {
         lclSftp = assertHierarchyTargetFolderExists(lclSftp);
         sshd.setFileSystemFactory(new VirtualFileSystemFactory(lclSftp));
 
-        try (ClientSession session = createAuthenticatedClientSession()) {
-            String escapePath;
-            if (useAbsolutePath) {
-                escapePath = targetPath.toString();
-                if (OsUtils.isWin32()) {
-                    escapePath = "/" + escapePath.replace(File.separatorChar, 
'/');
-                }
-            } else {
-                Path parent = lclSftp.getParent();
-                Path forbidden = 
Files.createDirectories(parent.resolve("forbidden"));
-                escapePath = "../" + forbidden.getFileName();
+        String escapePath;
+        if (useAbsolutePath) {
+            escapePath = targetPath.toString();
+            if (OsUtils.isWin32()) {
+                escapePath = "/" + escapePath.replace(File.separatorChar, '/');
             }
+        } else {
+            Path parent = lclSftp.getParent();
+            Path forbidden = 
Files.createDirectories(parent.resolve("forbidden"));
+            escapePath = "../" + forbidden.getFileName();
+        }
 
-            try (SftpClient sftp = createSftpClient(session)) {
-                SftpClient.Attributes attrs = sftp.stat(escapePath);
-                fail("Unexpected escape success for path=" + escapePath + ": " 
+ attrs);
-            } catch (SftpException e) {
-                int expected = OsUtils.isWin32() || (!useAbsolutePath)
-                        ? SftpConstants.SSH_FX_INVALID_FILENAME
-                        : SftpConstants.SSH_FX_NO_SUCH_FILE;
-                assertEquals("Mismatched status for " + escapePath,
-                        SftpConstants.getStatusName(expected),
-                        SftpConstants.getStatusName(e.getStatus()));
-            }
+        try (SftpClient sftp = createSingleSessionClient()) {
+            SftpClient.Attributes attrs = sftp.stat(escapePath);
+            fail("Unexpected escape success for path=" + escapePath + ": " + 
attrs);
+        } catch (SftpException e) {
+            int expected = OsUtils.isWin32() || (!useAbsolutePath)
+                    ? SftpConstants.SSH_FX_INVALID_FILENAME
+                    : SftpConstants.SSH_FX_NO_SUCH_FILE;
+            assertEquals("Mismatched status for " + escapePath,
+                    SftpConstants.getStatusName(expected),
+                    SftpConstants.getStatusName(e.getStatus()));
         }
     }
 
     @Test
     public void testNormalizeRemoteRootValues() throws Exception {
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session)) {
-            StringBuilder sb = new StringBuilder(Long.SIZE + 1);
+        try (SftpClient sftp = createSingleSessionClient()) {
             String expected = sftp.canonicalPath("/");
+            StringBuilder sb = new StringBuilder(Long.SIZE + 1);
             for (int i = 0; i < Long.SIZE; i++) {
                 if (sb.length() > 0) {
                     sb.setLength(0);
@@ -342,8 +333,7 @@ public class SftpTest extends AbstractSftpClientTestSupport 
{
 
         Factory<? extends Random> factory = client.getRandomFactory();
         Random rnd = factory.create();
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session)) {
+        try (SftpClient sftp = createSingleSessionClient()) {
             StringBuilder sb = new StringBuilder(file.length() + comps.length);
             String expected = sftp.canonicalPath(file);
             for (int i = 0; i < file.length(); i++) {
@@ -392,86 +382,83 @@ public class SftpTest extends 
AbstractSftpClientTestSupport {
         File javaFile = testFile.toFile();
         assertHierarchyTargetFolderExists(javaFile.getParentFile());
 
-        try (ClientSession session = createAuthenticatedClientSession()) {
-            javaFile.createNewFile();
-            javaFile.setWritable(false, false);
-            javaFile.setReadable(false, false);
+        javaFile.createNewFile();
+        javaFile.setWritable(false, false);
+        javaFile.setReadable(false, false);
+        try (SftpClient sftp = createSingleSessionClient()) {
+            boolean isWindows = OsUtils.isWin32();
 
-            try (SftpClient sftp = createSftpClient(session)) {
-                boolean isWindows = OsUtils.isWin32();
-
-                try (SftpClient.CloseableHandle h = sftp.open(file /* no mode 
== read */)) {
-                    // NOTE: on Windows files are always readable
-                    // see 
https://svn.apache.org/repos/asf/harmony/enhanced/java/branches/java6/classlib/modules/
-                    // 
luni/src/test/api/windows/org/apache/harmony/luni/tests/java/io/WinFileTest.java
-                    assertTrue("Empty read should have failed on " + file, 
isWindows);
-                } catch (IOException e) {
-                    if (isWindows) {
-                        throw e;
-                    }
-                }
-
-                try (SftpClient.CloseableHandle h = sftp.open(file, 
EnumSet.of(SftpClient.OpenMode.Write))) {
-                    fail("Empty write should have failed on " + file);
-                } catch (IOException e) {
-                    // ok
+            try (SftpClient.CloseableHandle h = sftp.open(file /* no mode == 
read */)) {
+                // NOTE: on Windows files are always readable
+                // see 
https://svn.apache.org/repos/asf/harmony/enhanced/java/branches/java6/classlib/modules/
+                // 
luni/src/test/api/windows/org/apache/harmony/luni/tests/java/io/WinFileTest.java
+                assertTrue("Empty read should have failed on " + file, 
isWindows);
+            } catch (IOException e) {
+                if (isWindows) {
+                    throw e;
                 }
+            }
 
-                try (SftpClient.CloseableHandle h = sftp.open(file, 
EnumSet.of(SftpClient.OpenMode.Truncate))) {
-                    // NOTE: on Windows files are always readable
-                    assertTrue("Empty truncate should have failed on " + file, 
isWindows);
-                } catch (IOException e) {
-                    // ok
-                }
+            try (SftpClient.CloseableHandle h = sftp.open(file, 
EnumSet.of(SftpClient.OpenMode.Write))) {
+                fail("Empty write should have failed on " + file);
+            } catch (IOException e) {
+                // ok
+            }
 
+            try (SftpClient.CloseableHandle h = sftp.open(file, 
EnumSet.of(SftpClient.OpenMode.Truncate))) {
                 // NOTE: on Windows files are always readable
-                int perms = sftp.stat(file).getPermissions();
-                int readMask = isWindows ? 0 : SftpConstants.S_IRUSR;
-                int permsMask = SftpConstants.S_IWUSR | readMask;
-                assertEquals("Mismatched permissions for " + file + ": 0x" + 
Integer.toHexString(perms), 0, perms & permsMask);
+                assertTrue("Empty truncate should have failed on " + file, 
isWindows);
+            } catch (IOException e) {
+                // ok
+            }
 
-                javaFile.setWritable(true, false);
+            // NOTE: on Windows files are always readable
+            int perms = sftp.stat(file).getPermissions();
+            int readMask = isWindows ? 0 : SftpConstants.S_IRUSR;
+            int permsMask = SftpConstants.S_IWUSR | readMask;
+            assertEquals("Mismatched permissions for " + file + ": 0x" + 
Integer.toHexString(perms), 0, perms & permsMask);
 
-                try (SftpClient.CloseableHandle h = sftp.open(
-                        file, EnumSet.of(SftpClient.OpenMode.Truncate, 
SftpClient.OpenMode.Write))) {
-                    // OK should succeed
-                    assertTrue("Handle not marked as open for file=" + file, 
h.isOpen());
-                }
+            javaFile.setWritable(true, false);
 
-                byte[] d = "0123456789\n".getBytes(StandardCharsets.UTF_8);
-                try (SftpClient.CloseableHandle h = sftp.open(file, 
EnumSet.of(SftpClient.OpenMode.Write))) {
-                    sftp.write(h, 0, d, 0, d.length);
-                    sftp.write(h, d.length, d, 0, d.length);
-                }
+            try (SftpClient.CloseableHandle h = sftp.open(
+                    file, EnumSet.of(SftpClient.OpenMode.Truncate, 
SftpClient.OpenMode.Write))) {
+                // OK should succeed
+                assertTrue("Handle not marked as open for file=" + file, 
h.isOpen());
+            }
 
-                try (SftpClient.CloseableHandle h = sftp.open(file, 
EnumSet.of(SftpClient.OpenMode.Write))) {
-                    sftp.write(h, d.length * 2, d, 0, d.length);
-                }
+            byte[] d = "0123456789\n".getBytes(StandardCharsets.UTF_8);
+            try (SftpClient.CloseableHandle h = sftp.open(file, 
EnumSet.of(SftpClient.OpenMode.Write))) {
+                sftp.write(h, 0, d, 0, d.length);
+                sftp.write(h, d.length, d, 0, d.length);
+            }
 
-                try (SftpClient.CloseableHandle h = sftp.open(file, 
EnumSet.of(SftpClient.OpenMode.Write))) {
-                    byte[] overwrite = "-".getBytes(StandardCharsets.UTF_8);
-                    sftp.write(h, 3L, overwrite, 0, 1);
-                    d[3] = overwrite[0];
-                }
+            try (SftpClient.CloseableHandle h = sftp.open(file, 
EnumSet.of(SftpClient.OpenMode.Write))) {
+                sftp.write(h, d.length * 2, d, 0, d.length);
+            }
 
-                try (SftpClient.CloseableHandle h = sftp.open(file /* no mode 
== read */)) {
-                    // NOTE: on Windows files are always readable
-                    assertTrue("Data read should have failed on " + file, 
isWindows);
-                } catch (IOException e) {
-                    if (isWindows) {
-                        throw e;
-                    }
+            try (SftpClient.CloseableHandle h = sftp.open(file, 
EnumSet.of(SftpClient.OpenMode.Write))) {
+                byte[] overwrite = "-".getBytes(StandardCharsets.UTF_8);
+                sftp.write(h, 3L, overwrite, 0, 1);
+                d[3] = overwrite[0];
+            }
+
+            try (SftpClient.CloseableHandle h = sftp.open(file /* no mode == 
read */)) {
+                // NOTE: on Windows files are always readable
+                assertTrue("Data read should have failed on " + file, 
isWindows);
+            } catch (IOException e) {
+                if (isWindows) {
+                    throw e;
                 }
+            }
 
-                javaFile.setReadable(true, false);
+            javaFile.setReadable(true, false);
 
-                byte[] buf = new byte[3];
-                try (SftpClient.CloseableHandle h = sftp.open(file /* no mode 
== read */)) {
-                    int l = sftp.read(h, 2L, buf, 0, buf.length);
-                    String expected = new String(d, 2, l, 
StandardCharsets.UTF_8);
-                    String actual = new String(buf, 0, l, 
StandardCharsets.UTF_8);
-                    assertEquals("Mismatched read data", expected, actual);
-                }
+            byte[] buf = new byte[3];
+            try (SftpClient.CloseableHandle h = sftp.open(file /* no mode == 
read */)) {
+                int l = sftp.read(h, 2L, buf, 0, buf.length);
+                String expected = new String(d, 2, l, StandardCharsets.UTF_8);
+                String actual = new String(buf, 0, l, StandardCharsets.UTF_8);
+                assertEquals("Mismatched read data", expected, actual);
             }
         }
     }
@@ -487,28 +474,26 @@ public class SftpTest extends 
AbstractSftpClientTestSupport {
         String file = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, testFile);
 
         assertHierarchyTargetFolderExists(testFile.getParent());
-        try (ClientSession session = createAuthenticatedClientSession()) {
-            Files.deleteIfExists(testFile); // make sure starting fresh
-            Files.createFile(testFile, IoUtils.EMPTY_FILE_ATTRIBUTES);
+        Files.deleteIfExists(testFile); // make sure starting fresh
+        Files.createFile(testFile, IoUtils.EMPTY_FILE_ATTRIBUTES);
 
-            try (SftpClient sftp = createSftpClient(session)) {
-                Collection<PosixFilePermission> initialPermissions = 
IoUtils.getPermissions(testFile);
-                assertTrue("File does not have enough initial permissions: " + 
initialPermissions,
-                        initialPermissions.containsAll(
-                                EnumSet.of(PosixFilePermission.OWNER_READ, 
PosixFilePermission.OWNER_WRITE)));
+        try (SftpClient sftp = createSingleSessionClient()) {
+            Collection<PosixFilePermission> initialPermissions = 
IoUtils.getPermissions(testFile);
+            assertTrue("File does not have enough initial permissions: " + 
initialPermissions,
+                    initialPermissions.containsAll(
+                            EnumSet.of(PosixFilePermission.OWNER_READ, 
PosixFilePermission.OWNER_WRITE)));
 
-                try (CloseableHandle handle = sendRawAttributeImpactOpen(file, 
sftp)) {
-                    outputDebugMessage("%s - handle=%s", getCurrentTestName(), 
handle);
-                }
-
-                Collection<PosixFilePermission> updatedPermissions = 
IoUtils.getPermissions(testFile);
-                assertEquals("Mismatched updated permissions count", 
initialPermissions.size(), updatedPermissions.size());
-                assertTrue("File does not preserve initial permissions: 
expected=" + initialPermissions + ", actual="
-                           + updatedPermissions,
-                        updatedPermissions.containsAll(initialPermissions));
-            } finally {
-                Files.delete(testFile);
+            try (CloseableHandle handle = sendRawAttributeImpactOpen(file, 
sftp)) {
+                outputDebugMessage("%s - handle=%s", getCurrentTestName(), 
handle);
             }
+
+            Collection<PosixFilePermission> updatedPermissions = 
IoUtils.getPermissions(testFile);
+            assertEquals("Mismatched updated permissions count", 
initialPermissions.size(), updatedPermissions.size());
+            assertTrue("File does not preserve initial permissions: expected=" 
+ initialPermissions + ", actual="
+                       + updatedPermissions,
+                    updatedPermissions.containsAll(initialPermissions));
+        } finally {
+            Files.delete(testFile);
         }
     }
 
@@ -552,8 +537,7 @@ public class SftpTest extends AbstractSftpClientTestSupport 
{
         byte[] data
                 = (getClass().getName() + "#" + getCurrentTestName() + "[" + 
localFile + "]").getBytes(StandardCharsets.UTF_8);
         Files.write(localFile, data, StandardOpenOption.CREATE);
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session);
+        try (SftpClient sftp = createSingleSessionClient();
              InputStream stream = sftp.read(
                      
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, localFile), 
OpenMode.Read)) {
             byte[] expected = new byte[data.length / 4];
@@ -626,8 +610,7 @@ public class SftpTest extends AbstractSftpClientTestSupport 
{
             byte[] expected = (getClass().getName() + "#" + 
getCurrentTestName() + "[" + localFile + "]")
                     .getBytes(StandardCharsets.UTF_8);
             Files.write(localFile, expected, StandardOpenOption.CREATE);
-            try (ClientSession session = createAuthenticatedClientSession();
-                 SftpClient sftp = createSftpClient(session)) {
+            try (SftpClient sftp = createSingleSessionClient()) {
                 byte[] actual = new byte[expected.length];
                 try (InputStream stream = sftp.read(
                         
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, localFile), 
OpenMode.Read)) {
@@ -865,8 +848,7 @@ public class SftpTest extends AbstractSftpClientTestSupport 
{
         };
         factory.addSftpEventListener(listener);
 
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session)) {
+        try (SftpClient sftp = createSingleSessionClient()) {
             assertEquals("Mismatched negotiated version", sftp.getVersion(), 
versionHolder.get());
             testClient(client, sftp);
 
@@ -895,51 +877,49 @@ public class SftpTest extends 
AbstractSftpClientTestSupport {
      */
     @Test
     public void testWriteChunking() throws Exception {
-        try (ClientSession session = createAuthenticatedClientSession()) {
-            Path targetPath = detectTargetFolder();
-            Path lclSftp = CommonTestSupportUtils.resolve(
-                    targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, 
getClass().getSimpleName(), getCurrentTestName());
-            CommonTestSupportUtils.deleteRecursive(lclSftp);
+        Path targetPath = detectTargetFolder();
+        Path lclSftp = CommonTestSupportUtils.resolve(
+                targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, 
getClass().getSimpleName(), getCurrentTestName());
+        CommonTestSupportUtils.deleteRecursive(lclSftp);
 
-            Path parentPath = targetPath.getParent();
-            Path clientFolder = 
assertHierarchyTargetFolderExists(lclSftp).resolve("client");
-            String dir = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder);
+        Path parentPath = targetPath.getParent();
+        Path clientFolder = 
assertHierarchyTargetFolderExists(lclSftp).resolve("client");
+        String dir = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder);
 
-            try (SftpClient sftp = createSftpClient(session)) {
-                sftp.mkdir(dir);
-
-                uploadAndVerifyFile(sftp, clientFolder, dir, 0, 
"emptyFile.txt");
-                uploadAndVerifyFile(sftp, clientFolder, dir, 1000, 
"smallFile.txt");
-
-                // Make sure sizes should invoke our internal chunking 
mechanism
-                ClientChannel clientChannel = sftp.getClientChannel();
-                SftpModuleProperties.WRITE_CHUNK_SIZE.set(clientChannel,
-                        Math.min(SftpClient.IO_BUFFER_SIZE, 
SftpModuleProperties.WRITE_CHUNK_SIZE.getRequiredDefault())
-                                                                         - 
Byte.MAX_VALUE);
-
-                uploadAndVerifyFile(sftp, clientFolder, dir,
-                        SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT 
- 1, "bufferMaxLenMinusOneFile.txt");
-                uploadAndVerifyFile(sftp, clientFolder, dir,
-                        SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT, 
"bufferMaxLenFile.txt");
-                uploadAndVerifyFile(sftp, clientFolder, dir,
-                        SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT 
+ 1, "bufferMaxLenPlusOneFile.txt");
-                uploadAndVerifyFile(sftp, clientFolder, dir,
-                        (int) (1.5 * 
SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT), 
"1point5BufferMaxLenFile.txt");
-                uploadAndVerifyFile(sftp, clientFolder, dir,
-                        (2 * 
SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT) - 1, 
"2TimesBufferMaxLenMinusOneFile.txt");
-                uploadAndVerifyFile(sftp, clientFolder, dir,
-                        2 * 
SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT, 
"2TimesBufferMaxLenFile.txt");
-                uploadAndVerifyFile(sftp, clientFolder, dir,
-                        (2 * 
SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT) + 1, 
"2TimesBufferMaxLenPlusOneFile.txt");
-                uploadAndVerifyFile(sftp, clientFolder, dir, 200000, 
"largerFile.txt");
-
-                // test erroneous calls that check for negative values
-                Path invalidPath = clientFolder.resolve(getCurrentTestName() + 
"-invalid");
-                testInvalidParams(sftp, invalidPath, 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, invalidPath));
-
-                // cleanup
-                sftp.rmdir(dir);
-            }
+        try (SftpClient sftp = createSingleSessionClient()) {
+            sftp.mkdir(dir);
+
+            uploadAndVerifyFile(sftp, clientFolder, dir, 0, "emptyFile.txt");
+            uploadAndVerifyFile(sftp, clientFolder, dir, 1000, 
"smallFile.txt");
+
+            // Make sure sizes should invoke our internal chunking mechanism
+            ClientChannel clientChannel = sftp.getClientChannel();
+            SftpModuleProperties.WRITE_CHUNK_SIZE.set(clientChannel,
+                    Math.min(SftpClient.IO_BUFFER_SIZE, 
SftpModuleProperties.WRITE_CHUNK_SIZE.getRequiredDefault())
+                                                                     - 
Byte.MAX_VALUE);
+
+            uploadAndVerifyFile(sftp, clientFolder, dir,
+                    SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT - 1, 
"bufferMaxLenMinusOneFile.txt");
+            uploadAndVerifyFile(sftp, clientFolder, dir,
+                    SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT, 
"bufferMaxLenFile.txt");
+            uploadAndVerifyFile(sftp, clientFolder, dir,
+                    SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT + 1, 
"bufferMaxLenPlusOneFile.txt");
+            uploadAndVerifyFile(sftp, clientFolder, dir,
+                    (int) (1.5 * 
SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT), 
"1point5BufferMaxLenFile.txt");
+            uploadAndVerifyFile(sftp, clientFolder, dir,
+                    (2 * 
SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT) - 1, 
"2TimesBufferMaxLenMinusOneFile.txt");
+            uploadAndVerifyFile(sftp, clientFolder, dir,
+                    2 * SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT, 
"2TimesBufferMaxLenFile.txt");
+            uploadAndVerifyFile(sftp, clientFolder, dir,
+                    (2 * 
SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT) + 1, 
"2TimesBufferMaxLenPlusOneFile.txt");
+            uploadAndVerifyFile(sftp, clientFolder, dir, 200000, 
"largerFile.txt");
+
+            // test erroneous calls that check for negative values
+            Path invalidPath = clientFolder.resolve(getCurrentTestName() + 
"-invalid");
+            testInvalidParams(sftp, invalidPath, 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, invalidPath));
+
+            // cleanup
+            sftp.rmdir(dir);
         }
     }
 
@@ -1125,8 +1105,7 @@ public class SftpTest extends 
AbstractSftpClientTestSupport {
 
         Path parentPath = targetPath.getParent();
         Path clientFolder = 
assertHierarchyTargetFolderExists(lclSftp.resolve("client"));
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session)) {
+        try (SftpClient sftp = createSingleSessionClient()) {
             Path file1 = clientFolder.resolve("file-1.txt");
             String file1Path = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, file1);
             try (OutputStream os = sftp.write(file1Path, 
SftpClient.MIN_WRITE_BUFFER_SIZE)) {
@@ -1163,8 +1142,7 @@ public class SftpTest extends 
AbstractSftpClientTestSupport {
 
     @Test
     public void testServerExtensionsDeclarations() throws Exception {
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session)) {
+        try (SftpClient sftp = createSingleSessionClient()) {
             Map<String, byte[]> extensions = sftp.getServerExtensions();
             for (String name : new String[] {
                     SftpConstants.EXT_NEWLINE, SftpConstants.EXT_VERSIONS,
@@ -1497,8 +1475,7 @@ public class SftpTest extends 
AbstractSftpClientTestSupport {
     @Test // see SSHD-903
     public void testForcedVersionNegotiation() throws Exception {
         SftpModuleProperties.SFTP_VERSION.set(sshd, SftpConstants.SFTP_V3);
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session)) {
+        try (SftpClient sftp = createSingleSessionClient()) {
             assertEquals("Mismatched negotiated version", 
SftpConstants.SFTP_V3, sftp.getVersion());
         }
     }
@@ -1547,8 +1524,7 @@ public class SftpTest extends 
AbstractSftpClientTestSupport {
 
         Path parentPath = targetPath.getParent();
         Path clientFolder = 
assertHierarchyTargetFolderExists(lclSftp.resolve("client"));
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session)) {
+        try (SftpClient sftp = createSingleSessionClient()) {
             Path file = clientFolder.resolve("file.txt");
             String filePath = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, file);
             try (OutputStream os = sftp.write(filePath, 
SftpClient.MIN_WRITE_BUFFER_SIZE)) {
diff --git 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/UnsupportedExtensionTest.java
 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/UnsupportedExtensionTest.java
index 3aaae5b..cea742a 100644
--- 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/UnsupportedExtensionTest.java
+++ 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/UnsupportedExtensionTest.java
@@ -21,7 +21,6 @@ package org.apache.sshd.sftp.client.extensions;
 
 import java.io.IOException;
 
-import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
@@ -41,14 +40,13 @@ public class UnsupportedExtensionTest extends 
AbstractSftpClientTestSupport {
 
     @Test // see SSHD-890
     public void testUnsupportedExtension() throws IOException {
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftpClient = createSftpClient(session)) {
+        try (SftpClient sftpClient = createSingleSessionClient()) {
+            RawSftpClient sftp = assertObjectInstanceOf("Not a raw SFTP 
client", RawSftpClient.class, sftpClient);
+
             String opcode = getCurrentTestName();
             Buffer buffer = new ByteArrayBuffer(Integer.BYTES + 
GenericUtils.length(opcode) + Byte.SIZE, false);
             buffer.putString(opcode);
 
-            assertObjectInstanceOf("Not a raw SFTP client", 
RawSftpClient.class, sftpClient);
-            RawSftpClient sftp = (RawSftpClient) sftpClient;
             int cmd = sftp.send(SftpConstants.SSH_FXP_EXTENDED, buffer);
             Buffer responseBuffer = sftp.receive(cmd);
 
diff --git 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractCheckFileExtensionTest.java
 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractCheckFileExtensionTest.java
index af7b3ec..b192122 100644
--- 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractCheckFileExtensionTest.java
+++ 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractCheckFileExtensionTest.java
@@ -31,7 +31,6 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
-import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.digest.BuiltinDigests;
@@ -175,8 +174,7 @@ public class AbstractCheckFileExtensionTest extends 
AbstractSftpClientTestSuppor
         Path parentPath = targetPath.getParent();
         String srcPath = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, srcFile);
         String srcFolder = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, 
srcFile.getParent());
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session)) {
+        try (SftpClient sftp = createSingleSessionClient()) {
             CheckFileNameExtension file = assertExtensionCreated(sftp, 
CheckFileNameExtension.class);
             try {
                 Map.Entry<String, ?> result = file.checkFileName(srcFolder, 
algorithms, 0L, 0L, hashBlockSize);
diff --git 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractMD5HashExtensionTest.java
 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractMD5HashExtensionTest.java
index aa70070..460fc20 100644
--- 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractMD5HashExtensionTest.java
+++ 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractMD5HashExtensionTest.java
@@ -29,7 +29,6 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
-import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.digest.BuiltinDigests;
 import org.apache.sshd.common.digest.Digest;
 import org.apache.sshd.common.util.GenericUtils;
@@ -133,8 +132,7 @@ public class AbstractMD5HashExtensionTest extends 
AbstractSftpClientTestSupport
         Path parentPath = targetPath.getParent();
         String srcPath = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, srcFile);
         String srcFolder = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, 
srcFile.getParent());
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session)) {
+        try (SftpClient sftp = createSingleSessionClient()) {
             MD5FileExtension file = assertExtensionCreated(sftp, 
MD5FileExtension.class);
             try {
                 byte[] actual = file.getHash(srcFolder, 0L, 0L, quickHash);
diff --git 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyDataExtensionImplTest.java
 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyDataExtensionImplTest.java
index 8301d3c..e24458d 100644
--- 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyDataExtensionImplTest.java
+++ 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyDataExtensionImplTest.java
@@ -33,7 +33,6 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
-import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.Factory;
 import org.apache.sshd.common.random.Random;
 import org.apache.sshd.common.util.io.IoUtils;
@@ -159,8 +158,7 @@ public class CopyDataExtensionImplTest extends 
AbstractSftpClientTestSupport {
             }
         }
 
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session)) {
+        try (SftpClient sftp = createSingleSessionClient()) {
             CopyDataExtension ext = assertExtensionCreated(sftp, 
CopyDataExtension.class);
             try (CloseableHandle readHandle = sftp.open(srcPath, 
SftpClient.OpenMode.Read);
                  CloseableHandle writeHandle = sftp.open(dstPath, 
SftpClient.OpenMode.Write, SftpClient.OpenMode.Create)) {
diff --git 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyFileExtensionImplTest.java
 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyFileExtensionImplTest.java
index 08dbe67..f2e5dd3 100644
--- 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyFileExtensionImplTest.java
+++ 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyFileExtensionImplTest.java
@@ -25,7 +25,6 @@ import java.nio.file.Files;
 import java.nio.file.LinkOption;
 import java.nio.file.Path;
 
-import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.sftp.client.AbstractSftpClientTestSupport;
 import org.apache.sshd.sftp.client.SftpClient;
@@ -71,8 +70,7 @@ public class CopyFileExtensionImplTest extends 
AbstractSftpClientTestSupport {
         LinkOption[] options = IoUtils.getLinkOptions(true);
         assertFalse("Destination file unexpectedly exists", 
Files.exists(dstFile, options));
 
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session)) {
+        try (SftpClient sftp = createSingleSessionClient()) {
             CopyFileExtension ext = assertExtensionCreated(sftp, 
CopyFileExtension.class);
             ext.copyFile(srcPath, dstPath, false);
             assertTrue("Source file not preserved", Files.exists(srcFile, 
options));
diff --git 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/SpaceAvailableExtensionImplTest.java
 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/SpaceAvailableExtensionImplTest.java
index 1158c09..a18f17e 100644
--- 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/SpaceAvailableExtensionImplTest.java
+++ 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/SpaceAvailableExtensionImplTest.java
@@ -27,7 +27,6 @@ import java.nio.file.Path;
 import java.util.Collections;
 import java.util.List;
 
-import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.server.channel.ChannelSession;
 import org.apache.sshd.server.command.Command;
 import org.apache.sshd.server.subsystem.SubsystemFactory;
@@ -88,8 +87,7 @@ public class SpaceAvailableExtensionImplTest extends 
AbstractSftpClientTestSuppo
             }
         }));
 
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session)) {
+        try (SftpClient sftp = createSingleSessionClient()) {
             SpaceAvailableExtension ext = assertExtensionCreated(sftp, 
SpaceAvailableExtension.class);
             SpaceAvailableExtensionInfo actual = ext.available(queryPath);
             assertEquals("Mismatched information", expected, actual);
diff --git 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHExtensionsTest.java
 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHExtensionsTest.java
index 639ff89..d0ba605 100644
--- 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHExtensionsTest.java
+++ 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHExtensionsTest.java
@@ -31,7 +31,6 @@ import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
-import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.io.IoUtils;
@@ -81,8 +80,7 @@ public class OpenSSHExtensionsTest extends 
AbstractSftpClientTestSupport {
 
         Path parentPath = targetPath.getParent();
         String srcPath = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, srcFile);
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session)) {
+        try (SftpClient sftp = createSingleSessionClient()) {
             OpenSSHFsyncExtension fsync = assertExtensionCreated(sftp, 
OpenSSHFsyncExtension.class);
             try (CloseableHandle fileHandle = sftp.open(srcPath, 
SftpClient.OpenMode.Write, SftpClient.OpenMode.Create)) {
                 sftp.write(fileHandle, 0L, expected);
@@ -164,8 +162,7 @@ public class OpenSSHExtensionsTest extends 
AbstractSftpClientTestSupport {
             }
         }));
 
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session)) {
+        try (SftpClient sftp = createSingleSessionClient()) {
             OpenSSHStatPathExtension pathStat = assertExtensionCreated(sftp, 
OpenSSHStatPathExtension.class);
             OpenSSHStatExtensionInfo actual = pathStat.stat(srcPath);
             String invokedExtension = extensionHolder.getAndSet(null);
diff --git 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpDirectoryScannersTest.java
 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpDirectoryScannersTest.java
index d238ddc..12ee16b 100644
--- 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpDirectoryScannersTest.java
+++ 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpDirectoryScannersTest.java
@@ -31,7 +31,6 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
-import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.util.io.DirectoryScanner;
 import org.apache.sshd.common.util.io.PathUtils;
 import org.apache.sshd.sftp.client.SftpClient;
@@ -96,8 +95,7 @@ public class SftpDirectoryScannersTest extends 
AbstractSftpFilesSystemSupport {
         List<Path> expected = setup.getExpected();
         String remRoot = setup.getRemoteFilePath();
         List<ScanDirEntry> actual;
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session)) {
+        try (SftpClient sftp = createSingleSessionClient()) {
             SftpClientDirectoryScanner ds = new 
SftpClientDirectoryScanner(remRoot, pattern);
             actual = ds.scan(sftp, () -> new ArrayList<>(expected.size()));
         }
diff --git 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/impl/SftpRemotePathChannelTest.java
 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/impl/SftpRemotePathChannelTest.java
index a2d52ec..f1aa19e 100644
--- 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/impl/SftpRemotePathChannelTest.java
+++ 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/impl/SftpRemotePathChannelTest.java
@@ -30,7 +30,6 @@ import java.nio.file.StandardOpenOption;
 import java.util.Date;
 import java.util.EnumSet;
 
-import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.sftp.SftpModuleProperties;
 import org.apache.sshd.sftp.client.AbstractSftpClientTestSupport;
 import org.apache.sshd.sftp.client.SftpClient;
@@ -66,25 +65,23 @@ public class SftpRemotePathChannelTest extends 
AbstractSftpClientTestSupport {
         byte[] expected
                 = (getClass().getName() + "#" + getCurrentTestName() + "(" + 
new Date() + ")").getBytes(StandardCharsets.UTF_8);
 
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session)) {
-            Path parentPath = targetPath.getParent();
-            String remFilePath = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, lclFile);
-
-            try (FileChannel fc = sftp.openRemotePathChannel(
-                    remFilePath, EnumSet.of(
-                            StandardOpenOption.CREATE, 
StandardOpenOption.READ, StandardOpenOption.WRITE))) {
-                int writeLen = fc.write(ByteBuffer.wrap(expected));
-                assertEquals("Mismatched written length", expected.length, 
writeLen);
-
-                FileChannel fcPos = fc.position(0L);
-                assertSame("Mismatched positioned file channel", fc, fcPos);
-
-                byte[] actual = new byte[expected.length];
-                int readLen = fc.read(ByteBuffer.wrap(actual));
-                assertEquals("Mismatched read len", writeLen, readLen);
-                assertArrayEquals("Mismatched read data", expected, actual);
-            }
+        Path parentPath = targetPath.getParent();
+        String remFilePath = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, lclFile);
+
+        try (SftpClient sftp = createSingleSessionClient();
+             FileChannel fc = sftp.openRemotePathChannel(
+                     remFilePath, EnumSet.of(
+                             StandardOpenOption.CREATE, 
StandardOpenOption.READ, StandardOpenOption.WRITE))) {
+            int writeLen = fc.write(ByteBuffer.wrap(expected));
+            assertEquals("Mismatched written length", expected.length, 
writeLen);
+
+            FileChannel fcPos = fc.position(0L);
+            assertSame("Mismatched positioned file channel", fc, fcPos);
+
+            byte[] actual = new byte[expected.length];
+            int readLen = fc.read(ByteBuffer.wrap(actual));
+            assertEquals("Mismatched read len", writeLen, readLen);
+            assertArrayEquals("Mismatched read data", expected, actual);
         }
 
         byte[] actual = Files.readAllBytes(lclFile);
@@ -114,8 +111,7 @@ public class SftpRemotePathChannelTest extends 
AbstractSftpClientTestSupport {
         Files.deleteIfExists(dstFile);
 
         String remFilePath = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, srcFile);
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session);
+        try (SftpClient sftp = createSingleSessionClient();
              FileChannel srcChannel = sftp.openRemotePathChannel(
                      remFilePath, EnumSet.of(StandardOpenOption.READ));
              FileChannel dstChannel = FileChannel.open(dstFile,
@@ -148,8 +144,7 @@ public class SftpRemotePathChannelTest extends 
AbstractSftpClientTestSupport {
         Files.deleteIfExists(dstFile);
 
         String remFilePath = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, srcFile);
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session);
+        try (SftpClient sftp = createSingleSessionClient();
              FileChannel srcChannel = sftp.openRemotePathChannel(
                      remFilePath, EnumSet.of(StandardOpenOption.READ));
              FileChannel dstChannel = FileChannel.open(dstFile,
@@ -188,8 +183,7 @@ public class SftpRemotePathChannelTest extends 
AbstractSftpClientTestSupport {
         Files.deleteIfExists(dstFile);
 
         String remFilePath = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, dstFile);
-        try (ClientSession session = createAuthenticatedClientSession();
-             SftpClient sftp = createSftpClient(session);
+        try (SftpClient sftp = createSingleSessionClient();
              FileChannel dstChannel = sftp.openRemotePathChannel(
                      remFilePath, EnumSet.of(StandardOpenOption.CREATE, 
StandardOpenOption.WRITE));
              FileChannel srcChannel = FileChannel.open(srcFile, 
StandardOpenOption.READ)) {

Reply via email to