You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2021/03/05 09:26:10 UTC

[mina-sshd] branch master updated (e88c1a7 -> b627015)

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

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


    from e88c1a7  [SSHD-1125] Added option to require immediate close of channel in command ExitCallback invocation
     new 80c5e16  [SSHD-1133] Re-factored locations and names of ServerSession and server-side ChannelSession related classes
     new 0dc159a  [SSHD-1133] Added capability to specify a custom charset for parsing incoming commands to the ScpShell
     new 88aa312  [SSHD-1133] Added capability to specify a custom charset for returning environment variables related data from the ScpShell
     new da23f55  [SSHD-1133] Added capability to specify a custom charset for handling the SCP protocol textual commands and responses
     new 73883a5  Moved some helper methods and classes to more natural locations
     new 11c0049  Fixed SftpCommandMain handling of 'put' command in case of single argument provided
     new b627015  Added SftpCommandMain 'lls' (local-LS) command support

The 7 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 CHANGES.md                                         |   8 +
 docs/scp.md                                        |  28 +++
 .../apache/sshd/cli/client/SftpCommandMain.java    |  64 +++++-
 .../sshd/cli/client/SshClientCliSupport.java       |  11 +-
 .../org/apache/sshd/cli/client/SshKeyScanMain.java |  14 +-
 .../org/apache/sshd/cli/server/SshFsMounter.java   |   4 +-
 .../sshd/client/config/hosts/HostConfigEntry.java  |  15 +-
 .../apache/sshd/common/AttributeRepository.java    |   6 +-
 .../org/apache/sshd/common/PropertyResolver.java   |  14 ++
 .../apache/sshd/common/PropertyResolverUtils.java  |   7 +-
 .../org/apache/sshd/common/channel/PtyMode.java    |  11 +-
 .../apache/sshd/common/config/TimeValueConfig.java |   3 +-
 .../common/config/keys/AuthorizedKeyEntry.java     |   5 +-
 .../sshd/common/config/keys/IdentityUtils.java     |   7 +-
 .../apache/sshd/common/config/keys/KeyUtils.java   |   4 +-
 .../keys/impl/AbstractPublicKeyEntryDecoder.java   |   4 +-
 .../keys/impl/SkECDSAPublicKeyEntryDecoder.java    |   2 +-
 .../keys/impl/SkED25519PublicKeyEntryDecoder.java  |   2 +-
 .../keys/loader/PrivateKeyEncryptionContext.java   |   3 +-
 .../keys/loader/openssh/kdf/BCryptKdfOptions.java  |   6 +-
 .../pem/AbstractPEMResourceKeyPairParser.java      |   8 +-
 .../keys}/u2f/SecurityKeyPublicKey.java            |   2 +-
 .../{ => config/keys}/u2f/SkED25519PublicKey.java  |   2 +-
 .../{ => config/keys}/u2f/SkEcdsaPublicKey.java    |   2 +-
 .../sshd/common/future/AbstractSshFuture.java      |   6 +-
 .../sshd/common/kex/extension/KexExtensions.java   |   3 +-
 .../common/keyprovider/KeyIdentityProvider.java    |   5 +-
 .../signature/AbstractSecurityKeySignature.java    |   2 +-
 .../sshd/common/signature/SignatureFactory.java    |   3 +-
 .../sshd/common/util/EventListenerUtils.java       |   4 +-
 .../apache/sshd/common/util/ExceptionUtils.java    | 162 +++++++++++++
 .../org/apache/sshd/common/util/GenericUtils.java  | 251 +--------------------
 .../org/apache/sshd/common/util/MapEntryUtils.java | 113 ++++++++++
 .../org/apache/sshd/common/util/ValidateUtils.java |   2 +-
 .../buffer/keys/SkECBufferPublicKeyParser.java     |   2 +-
 .../keys/SkED25519BufferPublicKeyParser.java       |   2 +-
 .../AutoCloseableDelegateInvocationHandler.java    |   3 +-
 .../util/{ => functors}/Int2IntFunction.java       |   4 +-
 .../sshd/common/util/functors/UnaryEquator.java    |  12 +-
 .../org/apache/sshd/common/util/io/IoUtils.java    |   3 +-
 .../org/apache/sshd/common/util/io/PathUtils.java  |   2 +-
 .../common/util/{ => io/functors}/Invoker.java     |   7 +-
 .../sshd/common/util/net/SshdSocketAddress.java    |   3 +-
 .../util/security/SecurityEntityFactory.java       |   8 +-
 .../sshd/common/util/security/SecurityUtils.java   |   5 +-
 .../BouncyCastleSecurityProviderRegistrar.java     |   3 +-
 .../eddsa/EdDSASecurityProviderRegistrar.java      |   4 +-
 .../main/java/org/apache/sshd/server/Signal.java   |   9 +-
 .../client/config/hosts/HostConfigEntryTest.java   |   5 +-
 .../client/config/keys/ClientIdentityTest.java     |   3 +-
 .../apache/sshd/common/VersionPropertiesTest.java  |   3 +-
 .../apache/sshd/common/channel/PtyModeTest.java    |   4 +-
 .../common/config/keys/BuiltinIdentitiesTest.java  |   3 +-
 .../common/keyprovider/KeyPairProviderTest.java    |   3 +-
 .../apache/sshd/common/util/GenericUtilsTest.java  |   8 +-
 .../sshd/common/util/Int2IntFunctionTest.java      |   1 +
 .../apache/sshd/util/test/JUnitTestSupport.java    |   5 +-
 .../InteractivePasswordIdentityProvider.java       |   5 +-
 .../common/compression/DeflatingInputStream.java   |  10 +-
 .../sshd/agent/local/ChannelAgentForwarding.java   |   4 +-
 .../sshd/agent/unix/ChannelAgentForwarding.java    |   3 +-
 .../java/org/apache/sshd/client/SshClient.java     |  12 +-
 .../sshd/client/channel/AbstractClientChannel.java |   4 +-
 .../client/channel/PtyCapableChannelSession.java   |   9 +-
 .../client/config/SshClientConfigFileReader.java   |   3 +-
 .../sshd/client/session/AbstractClientSession.java |   3 +-
 .../simple/AbstractSimpleClientSessionCreator.java |   6 +-
 .../sshd/common/channel/AbstractChannel.java       |  15 +-
 .../common/channel/BufferedIoOutputStream.java     |   4 +-
 .../sshd/common/forward/DefaultForwarder.java      |  23 +-
 .../apache/sshd/common/io/nio2/Nio2Connector.java  |   6 +-
 .../apache/sshd/common/io/nio2/Nio2Session.java    |   4 +-
 .../DefaultClientKexExtensionHandler.java          |  10 +-
 .../session/helpers/AbstractConnectionService.java |   2 +-
 .../common/session/helpers/AbstractSession.java    |   5 +-
 .../sshd/common/session/helpers/SessionHelper.java |  34 +--
 ...AuthorizedKeyEntriesPublickeyAuthenticator.java |   3 +-
 .../sshd/server/channel/AbstractServerChannel.java |   4 +-
 .../sshd/server/channel/ChannelDataReceiver.java   |   4 +-
 .../apache/sshd/server/channel/ChannelSession.java |   7 +-
 .../server/{ => channel}/ChannelSessionAware.java  |   3 +-
 .../sshd/server/channel/PuttyRequestHandler.java   |   3 +-
 .../server/channel/ServerChannelSessionHolder.java |   6 +-
 .../server/command/AbstractCommandSupport.java     |   4 +-
 .../command/AbstractDelegatingCommandFactory.java  |  15 +-
 .../server/config/SshServerConfigFileReader.java   |   5 +-
 .../sshd/server/config/keys/ServerIdentity.java    |   3 +-
 .../sshd/server/forward/TcpipServerChannel.java    |   8 +-
 .../server/global/CancelTcpipForwardHandler.java   |   2 +-
 .../sshd/server/global/TcpipForwardHandler.java    |   2 +-
 .../sshd/server/session/AbstractServerSession.java |   3 +-
 .../ServerSessionAware.java}                       |   6 +-
 .../apache/sshd/server/shell/InvertedShell.java    |  14 +-
 .../sshd/server/shell/InvertedShellWrapper.java    |  11 +-
 .../org/apache/sshd/server/shell/ProcessShell.java |  11 +-
 .../KnownHostsServerKeyVerifierTest.java           |   3 +-
 .../sshd/common/PropertyResolverUtilsTest.java     |   4 +-
 .../KeyboardInteractiveAuthenticationTest.java     |   3 +-
 .../java/org/apache/sshd/server/ServerTest.java    |   3 +-
 .../server/config/keys/ServerIdentityTest.java     |   3 +-
 .../server/shell/InvertedShellWrapperTest.java     |   8 +-
 .../sshd/util/test/AsyncEchoShellFactory.java      |   2 +-
 .../apache/sshd/util/test/BogusInvertedShell.java  |   2 +-
 .../apache/sshd/git/AbstractGitCommandFactory.java |   7 +-
 .../sshd/git/transport/GitSshdSessionFactory.java  |   6 +-
 .../sshd/ldap/LdapPasswordAuthenticatorTest.java   |   4 +-
 .../sshd/ldap/LdapPublickeyAuthenticatorTest.java  |   4 +-
 .../java/org/apache/sshd/mina/MinaService.java     |   4 +-
 .../java/org/apache/sshd/mina/MinaSession.java     |   4 +-
 .../java/org/apache/sshd/netty/NettyIoSession.java |   3 +-
 .../sshd/openpgp/PGPAuthorizedEntriesTracker.java  |   3 +-
 .../openpgp/PGPAuthorizedKeyEntriesLoader.java     |   3 +-
 .../apache/sshd/openpgp/PGPPublicRingWatcher.java  |  10 +-
 .../sshd/openpgp/PGPPublicRingWatcherTest.java     |   8 +-
 .../org/apache/sshd/putty/PuttyKeyUtilsTest.java   |   3 +-
 .../org/apache/sshd/scp/ScpModuleProperties.java   |  34 ++-
 .../scp/client/ScpRemote2RemoteTransferHelper.java |  25 +-
 .../sshd/scp/client/SimpleScpClientImpl.java       |   4 +-
 .../java/org/apache/sshd/scp/common/ScpHelper.java |  38 +++-
 .../apache/sshd/scp/common/helpers/ScpAckInfo.java |  28 ++-
 .../apache/sshd/scp/common/helpers/ScpIoUtils.java |  27 ++-
 .../org/apache/sshd/scp/server/ScpCommand.java     |  19 +-
 .../apache/sshd/scp/server/ScpCommandFactory.java  |   6 +-
 .../java/org/apache/sshd/scp/server/ScpShell.java  |  52 +++--
 .../java/org/apache/sshd/scp/client/ScpTest.java   |  38 ++--
 .../org/apache/sshd/sftp/client/SftpClient.java    |   5 +-
 .../helpers/AbstractSftpClientExtension.java       |   3 +-
 .../sftp/client/fs/SftpFileSystemProvider.java     |  11 +-
 .../sshd/sftp/client/impl/DefaultSftpClient.java   |   3 +-
 .../sftp/client/impl/SimpleSftpClientImpl.java     |  10 +-
 .../org/apache/sshd/sftp/common/SftpHelper.java    |   9 +-
 .../sftp/common/extensions/AclSupportedParser.java |   5 +-
 .../sshd/sftp/common/extensions/ParserUtils.java   |   7 +-
 .../server/AbstractSftpEventListenerAdapter.java   |   4 +-
 .../sftp/server/AbstractSftpSubsystemHelper.java   |  27 ++-
 .../org/apache/sshd/sftp/server/FileHandle.java    |   3 +-
 .../org/apache/sshd/sftp/server/SftpSubsystem.java | 110 +++++----
 .../sshd/sftp/server/SftpSubsystemEnvironment.java |   3 +-
 .../sshd/sftp/server/SftpSubsystemFactory.java     |   2 +-
 .../java/org/apache/sshd/sftp/client/SftpTest.java |   3 +-
 .../apache/sshd/sftp/client/SftpVersionsTest.java  |  17 +-
 .../helpers/SpaceAvailableExtensionImplTest.java   |   2 +-
 .../openssh/helpers/OpenSSHExtensionsTest.java     |   2 +-
 .../sshd/sftp/client/fs/SftpFileSystemTest.java    |   3 +-
 .../integration/ApacheSshdSftpSessionFactory.java  |   6 +-
 .../sftp/spring/integration/SpringSftpSession.java |   7 +-
 146 files changed, 1032 insertions(+), 729 deletions(-)
 rename sshd-common/src/main/java/org/apache/sshd/common/{ => config/keys}/u2f/SecurityKeyPublicKey.java (95%)
 rename sshd-common/src/main/java/org/apache/sshd/common/{ => config/keys}/u2f/SkED25519PublicKey.java (97%)
 rename sshd-common/src/main/java/org/apache/sshd/common/{ => config/keys}/u2f/SkEcdsaPublicKey.java (97%)
 create mode 100644 sshd-common/src/main/java/org/apache/sshd/common/util/ExceptionUtils.java
 rename sshd-common/src/main/java/org/apache/sshd/common/util/{ => functors}/Int2IntFunction.java (95%)
 rename sshd-common/src/main/java/org/apache/sshd/common/util/{ => io/functors}/Invoker.java (95%)
 rename sshd-core/src/main/java/org/apache/sshd/server/{ => channel}/ChannelSessionAware.java (93%)
 copy sshd-common/src/main/java/org/apache/sshd/common/auth/PasswordHolder.java => sshd-core/src/main/java/org/apache/sshd/server/channel/ServerChannelSessionHolder.java (87%)
 rename sshd-core/src/main/java/org/apache/sshd/server/{SessionAware.java => session/ServerSessionAware.java} (90%)


[mina-sshd] 03/07: [SSHD-1133] Added capability to specify a custom charset for returning environment variables related data from the ScpShell

Posted by lg...@apache.org.
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

commit 88aa3129b2336e0bfb9156cc4b71bd2bfc1196b4
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri Feb 26 10:53:05 2021 +0200

    [SSHD-1133] Added capability to specify a custom charset for returning environment variables related data from the ScpShell
---
 CHANGES.md                                                  |  3 ++-
 docs/scp.md                                                 |  2 ++
 .../main/java/org/apache/sshd/scp/ScpModuleProperties.java  |  7 +++++++
 .../src/main/java/org/apache/sshd/scp/server/ScpShell.java  | 13 +++++++++----
 4 files changed, 20 insertions(+), 5 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 6de775d..df0eb3e 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -45,4 +45,5 @@
 * [SSHD-1114](https://issues.apache.org/jira/browse/SSHD-1114) Added capability for interactive key based authentication participation via UserInteraction
 * [SSHD-1125](https://issues.apache.org/jira/browse/SSHD-1125) Added mechanism to throttle pending write requests in BufferedIoOutputStream
 * [SSHD-1127](https://issues.apache.org/jira/browse/SSHD-1127) Added capability to register a custom receiver for SFTP STDERR channel raw or stream data
-* [SSHD-1133](https://issues.apache.org/jira/browse/SSHD-1133) Added capability to specify a custom charset for parsing incoming commands to the `ScpShell`
\ No newline at end of file
+* [SSHD-1133](https://issues.apache.org/jira/browse/SSHD-1133) Added capability to specify a custom charset for parsing incoming commands to the `ScpShell`
+* [SSHD-1133](https://issues.apache.org/jira/browse/SSHD-1133) Added capability to specify a custom charset for returning environment variables related data from the `ScpShell`
\ No newline at end of file
diff --git a/docs/scp.md b/docs/scp.md
index 06364c1..96274b8 100644
--- a/docs/scp.md
+++ b/docs/scp.md
@@ -204,6 +204,8 @@ ScpModuleProperties.SHELL_NAME_DECODING_CHARSET.set(sshd, Charset.forName("US-AS
 
 **Caveat emptor:** that the code does not enforce "symmetry" of the chosen character sets - in other words, user can either by design or error cause different encoding to be used for the incoming commands vs. the outgoing responses. It is important to bear in mind that if the text to be encoded/decoded contains characters that cannot be  safely handled by the chosen encoder/decoder than the result might not be correctly parsed/understood by the peer.
 
+A similar behavior is controlled via `SHELL_ENVVARS_ENCODING_CHARSET` which controls responses from the `ScpShell` regarding environment variables - the main difference being that the default is US-ASCII rather than UTF-8.
+
 ## Remote-to-remote transfer
 
 The code provides an `ScpTransferHelper` class that enables copying files between 2 remote accounts without going through
diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/ScpModuleProperties.java b/sshd-scp/src/main/java/org/apache/sshd/scp/ScpModuleProperties.java
index 4bdae9a..9f19e02 100644
--- a/sshd-scp/src/main/java/org/apache/sshd/scp/ScpModuleProperties.java
+++ b/sshd-scp/src/main/java/org/apache/sshd/scp/ScpModuleProperties.java
@@ -60,6 +60,13 @@ public final class ScpModuleProperties {
             = Property.charset("scp-shell-name-encoding-charset", StandardCharsets.UTF_8);
 
     /**
+     * Used to indicate the {@link Charset} (or its name) for handling environment values in {@code ScpShell} -
+     * extracted from the channel session when shell initialized.
+     */
+    public static final Property<Charset> SHELL_ENVVARS_ENCODING_CHARSET
+            = Property.charset("scp-shell-envvars-encoding-charset", StandardCharsets.US_ASCII);
+
+    /**
      * Used to indicate the {@link Charset} (or its name) for decoding incoming commands to be processed by the
      * {@code ScpShell} - extracted from the channel session when shell initialized.
      */
diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpShell.java b/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpShell.java
index af31fbc..df4d433 100644
--- a/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpShell.java
+++ b/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpShell.java
@@ -90,6 +90,7 @@ public class ScpShell extends AbstractFileSystemCommand implements ServerChannel
 
     protected final Map<String, Object> variables = new HashMap<>();
     protected final Charset nameEncodingCharset;
+    protected final Charset envVarsEnodingCharset;
 
     protected final ScpFileOpener opener;
     protected final ScpTransferEventListener listener;
@@ -107,6 +108,7 @@ public class ScpShell extends AbstractFileSystemCommand implements ServerChannel
         this.channelSession = Objects.requireNonNull(channelSession, "No channel session provided");
 
         nameEncodingCharset = ScpModuleProperties.SHELL_NAME_ENCODING_CHARSET.getRequired(channelSession);
+        envVarsEnodingCharset = ScpModuleProperties.SHELL_ENVVARS_ENCODING_CHARSET.getRequired(channelSession);
 
         if (sendSize < ScpHelper.MIN_SEND_BUFFER_SIZE) {
             throw new IllegalArgumentException(
@@ -155,7 +157,7 @@ public class ScpShell extends AbstractFileSystemCommand implements ServerChannel
     }
 
     protected void signalError(String cmd, String errorMsg) {
-        signalError(cmd, errorMsg, StandardCharsets.US_ASCII);
+        signalError(cmd, errorMsg, envVarsEnodingCharset);
     }
 
     protected void signalError(String cmd, String errorMsg, Charset cs) {
@@ -217,6 +219,7 @@ public class ScpShell extends AbstractFileSystemCommand implements ServerChannel
             log.warn("run({}) {}", channel, message);
             try {
                 OutputStream stderr = getErrorStream();
+                // Don't encode it with any user defined charset
                 stderr.write(message.getBytes(StandardCharsets.US_ASCII));
             } catch (IOException ioe) {
                 log.warn("run({}) Failed ({}) to write error message={}: {}",
@@ -311,7 +314,9 @@ public class ScpShell extends AbstractFileSystemCommand implements ServerChannel
     protected void handleUnsupportedCommand(String command, String[] argv) throws Exception {
         log.warn("handleUnsupportedCommand({}) unsupported: {}", getServerChannelSession(), command);
         variables.put(STATUS, 127);
-        getErrorStream().write(("command not found: " + argv[0] + "\n").getBytes(StandardCharsets.US_ASCII));
+        OutputStream errorStream = getErrorStream();
+        // Don't encode it with any user defined charset
+        errorStream.write(("command not found: " + argv[0] + "\n").getBytes(StandardCharsets.US_ASCII));
     }
 
     protected List<String[]> parse(String command) {
@@ -366,7 +371,7 @@ public class ScpShell extends AbstractFileSystemCommand implements ServerChannel
         if (argv.length == 1) {
             envValues.entrySet()
                     .stream()
-                    .forEach(e -> println(argv[0], e.getKey() + "=" + e.getValue(), stdout, StandardCharsets.US_ASCII));
+                    .forEach(e -> println(argv[0], e.getKey() + "=" + e.getValue(), stdout, envVarsEnodingCharset));
             variables.put(STATUS, 0);
             return;
         }
@@ -387,7 +392,7 @@ public class ScpShell extends AbstractFileSystemCommand implements ServerChannel
             log.debug("printenv({}) {}={}", getServerChannelSession(), varName, varValue);
         }
 
-        println(argv[0], varValue, stdout, StandardCharsets.US_ASCII);
+        println(argv[0], varValue, stdout, envVarsEnodingCharset);
         variables.put(STATUS, 0);
     }
 


[mina-sshd] 06/07: Fixed SftpCommandMain handling of 'put' command in case of single argument provided

Posted by lg...@apache.org.
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

commit 11c0049c5376ea3bdf6dc480b4132470139f01fe
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri Mar 5 10:59:03 2021 +0200

    Fixed SftpCommandMain handling of 'put' command in case of single argument provided
---
 sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
index 79cbc57..aa23166 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
@@ -1123,7 +1123,7 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
             String remotePath;
             if (upload) {
                 localPath = src;
-                remotePath = ValidateUtils.checkNotNullAndNotEmpty(tgt, "No remote target specified: %s", args);
+                remotePath = GenericUtils.isEmpty(tgt) ? src : tgt;
             } else {
                 localPath = GenericUtils.isEmpty(tgt) ? getCurrentLocalDirectory() : tgt;
                 remotePath = src;


[mina-sshd] 04/07: [SSHD-1133] Added capability to specify a custom charset for handling the SCP protocol textual commands and responses

Posted by lg...@apache.org.
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

commit da23f559add671399c218c1525852c683d07ea5e
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri Feb 26 11:19:00 2021 +0200

    [SSHD-1133] Added capability to specify a custom charset for handling the SCP protocol textual commands and responses
---
 CHANGES.md                                         |  3 +-
 docs/scp.md                                        | 15 ++++++++-
 .../org/apache/sshd/scp/ScpModuleProperties.java   | 14 ++++++++
 .../scp/client/ScpRemote2RemoteTransferHelper.java | 25 +++++++++-----
 .../java/org/apache/sshd/scp/common/ScpHelper.java | 38 +++++++++++++++-------
 .../apache/sshd/scp/common/helpers/ScpAckInfo.java | 28 +++++++++-------
 .../apache/sshd/scp/common/helpers/ScpIoUtils.java | 27 ++++++++-------
 .../org/apache/sshd/scp/server/ScpCommand.java     |  3 +-
 .../java/org/apache/sshd/scp/server/ScpShell.java  |  2 +-
 .../java/org/apache/sshd/scp/client/ScpTest.java   | 30 ++++++++---------
 10 files changed, 121 insertions(+), 64 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index df0eb3e..131c6d0 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -46,4 +46,5 @@
 * [SSHD-1125](https://issues.apache.org/jira/browse/SSHD-1125) Added mechanism to throttle pending write requests in BufferedIoOutputStream
 * [SSHD-1127](https://issues.apache.org/jira/browse/SSHD-1127) Added capability to register a custom receiver for SFTP STDERR channel raw or stream data
 * [SSHD-1133](https://issues.apache.org/jira/browse/SSHD-1133) Added capability to specify a custom charset for parsing incoming commands to the `ScpShell`
-* [SSHD-1133](https://issues.apache.org/jira/browse/SSHD-1133) Added capability to specify a custom charset for returning environment variables related data from the `ScpShell`
\ No newline at end of file
+* [SSHD-1133](https://issues.apache.org/jira/browse/SSHD-1133) Added capability to specify a custom charset for returning environment variables related data from the `ScpShell`
+* [SSHD-1133](https://issues.apache.org/jira/browse/SSHD-1133) Added capability to specify a custom charset for handling the SCP protocol textual commands and responses
\ No newline at end of file
diff --git a/docs/scp.md b/docs/scp.md
index 96274b8..d04ecd7 100644
--- a/docs/scp.md
+++ b/docs/scp.md
@@ -38,6 +38,19 @@ try (ClientSession session = client.connect(user, host, port)
 
 ```
 
+### General text encoding/decoding
+
+The basic SCP protocol is text-based and therefore subject to character encoding/decoding of the data being exchanged. By default, the exchange is supposed to use the UTF-8 encoding which is the default/standard one for SSH. However, there are clients/servers "in the wild" that do not conform to this convention. For this purpose, it is possible to define a different  character encoding via the `SCP_INCOMING/OUTGOING_ENCODING` properties - e.g.:
+
+```java
+SshServer sshd = ...setup server...
+// Can also use the character name string rather than the object instance itself
+ScpModuleProperties.SCP_INCOMING_ENCODING.set(sshd, Charset.forName("US-ASCII"));
+ScpModuleProperties.SCP_OUTGOING_ENCODING.set(sshd, Charset.forName("US-ASCII"));
+```
+
+**Caveat emptor:** the code does not enforce "symmetry" of the chosen character sets - in other words, users can either by design or error cause different encoding to be used for the incoming commands vs. the outgoing responses. It is important to bear in mind that if the text to be encoded/decoded contains characters that cannot be safely handled by the chosen encoder/decoder than the result might not be correctly parsed/understood by the peer.
+
 ## Client-side SCP
 
 In order to obtain an `ScpClient` one needs to use an `ScpClientCreator`:
@@ -202,7 +215,7 @@ ScpModuleProperties.SHELL_NAME_ENCODING_CHARSET.set(sshd, Charset.forName("US-AS
 ScpModuleProperties.SHELL_NAME_DECODING_CHARSET.set(sshd, Charset.forName("US-ASCII"));
 ```
 
-**Caveat emptor:** that the code does not enforce "symmetry" of the chosen character sets - in other words, user can either by design or error cause different encoding to be used for the incoming commands vs. the outgoing responses. It is important to bear in mind that if the text to be encoded/decoded contains characters that cannot be  safely handled by the chosen encoder/decoder than the result might not be correctly parsed/understood by the peer.
+**Caveat emptor:** the code does not enforce "symmetry" of the chosen character sets - in other words, users can either by design or error cause different encoding to be used for the incoming commands vs. the outgoing responses. It is important to bear in mind that if the text to be encoded/decoded contains characters that cannot be safely handled by the chosen encoder/decoder than the result might not be correctly parsed/understood by the peer.
 
 A similar behavior is controlled via `SHELL_ENVVARS_ENCODING_CHARSET` which controls responses from the `ScpShell` regarding environment variables - the main difference being that the default is US-ASCII rather than UTF-8.
 
diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/ScpModuleProperties.java b/sshd-scp/src/main/java/org/apache/sshd/scp/ScpModuleProperties.java
index 9f19e02..8c3a2e9 100644
--- a/sshd-scp/src/main/java/org/apache/sshd/scp/ScpModuleProperties.java
+++ b/sshd-scp/src/main/java/org/apache/sshd/scp/ScpModuleProperties.java
@@ -47,6 +47,20 @@ public final class ScpModuleProperties {
             = Property.duration("scp-exec-channel-exit-status-timeout", Duration.ofSeconds(5));
 
     /**
+     * Used to indicate the {@link Charset} (or its name) for decoding incoming commands/responses sent by the peer
+     * (either client or server).
+     */
+    public static final Property<Charset> SCP_INCOMING_ENCODING
+            = Property.charset("scp-incoming-encoding-charset", StandardCharsets.UTF_8);
+
+    /**
+     * Used to indicate the {@link Charset} (or its name) for encoding outgoing commands/responses sent to the peer
+     * (either client or server).
+     */
+    public static final Property<Charset> SCP_OUTGOING_ENCODING
+            = Property.charset("scp-outgoing-encoding-charset", StandardCharsets.UTF_8);
+
+    /**
      * Whether to synchronize written file data with underlying file-system
      */
     public static final Property<Boolean> PROP_AUTO_SYNC_FILE_ON_WRITE
diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/client/ScpRemote2RemoteTransferHelper.java b/sshd-scp/src/main/java/org/apache/sshd/scp/client/ScpRemote2RemoteTransferHelper.java
index 387ebbd..fc2a036 100644
--- a/sshd-scp/src/main/java/org/apache/sshd/scp/client/ScpRemote2RemoteTransferHelper.java
+++ b/sshd-scp/src/main/java/org/apache/sshd/scp/client/ScpRemote2RemoteTransferHelper.java
@@ -24,6 +24,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.StreamCorruptedException;
+import java.nio.charset.Charset;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
@@ -36,6 +37,7 @@ import org.apache.sshd.common.util.SelectorUtils;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.common.util.io.LimitInputStream;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+import org.apache.sshd.scp.ScpModuleProperties;
 import org.apache.sshd.scp.client.ScpClient.Option;
 import org.apache.sshd.scp.common.helpers.AbstractScpCommandDetails;
 import org.apache.sshd.scp.common.helpers.ScpAckInfo;
@@ -54,6 +56,8 @@ import org.apache.sshd.scp.common.helpers.ScpTimestampCommandDetails;
  */
 public class ScpRemote2RemoteTransferHelper extends AbstractLoggingBean {
     protected final ScpRemote2RemoteTransferListener listener;
+    protected final Charset csIn;
+    protected final Charset csOut;
 
     private final ClientSession sourceSession;
     private final ClientSession destSession;
@@ -70,7 +74,10 @@ public class ScpRemote2RemoteTransferHelper extends AbstractLoggingBean {
     public ScpRemote2RemoteTransferHelper(ClientSession sourceSession, ClientSession destSession,
                                           ScpRemote2RemoteTransferListener listener) {
         this.sourceSession = Objects.requireNonNull(sourceSession, "No source session provided");
+        this.csIn = ScpModuleProperties.SCP_INCOMING_ENCODING.getRequired(sourceSession);
         this.destSession = Objects.requireNonNull(destSession, "No destination session provided");
+        this.csOut = ScpModuleProperties.SCP_OUTGOING_ENCODING.getRequired(destSession);
+
         this.listener = listener;
     }
 
@@ -199,7 +206,7 @@ public class ScpRemote2RemoteTransferHelper extends AbstractLoggingBean {
             throw new IllegalArgumentException("Invalid file transfer request: " + header);
         }
 
-        ScpIoUtils.writeLine(dstOut, header);
+        ScpIoUtils.writeLine(dstOut, csOut, header);
         ScpAckInfo ackInfo = transferStatusCode(header, dstIn, srcOut);
         ackInfo.validateCommandStatusCode("[DST] " + header, "handleFileTransferRequest");
 
@@ -272,7 +279,7 @@ public class ScpRemote2RemoteTransferHelper extends AbstractLoggingBean {
             throw new IllegalArgumentException("Invalid file transfer request: " + header);
         }
 
-        ScpIoUtils.writeLine(dstOut, header);
+        ScpIoUtils.writeLine(dstOut, csOut, header);
         ScpAckInfo ackInfo = transferStatusCode(header, dstIn, srcOut);
         ackInfo.validateCommandStatusCode("[DST@" + depth + "] " + header, "handleDirectoryTransferRequest");
 
@@ -339,7 +346,7 @@ public class ScpRemote2RemoteTransferHelper extends AbstractLoggingBean {
                     }
 
                     case ScpDirEndCommandDetails.COMMAND_NAME: {
-                        ScpIoUtils.writeLine(dstOut, header);
+                        ScpIoUtils.writeLine(dstOut, csOut, header);
                         ackInfo = transferStatusCode(header, dstIn, srcOut);
                         ackInfo.validateCommandStatusCode("[DST@" + depth + "] " + header, "handleDirectoryTransferRequest");
 
@@ -377,7 +384,7 @@ public class ScpRemote2RemoteTransferHelper extends AbstractLoggingBean {
 
         long xferCount;
         try (InputStream inputStream = new LimitInputStream(srcIn, length)) {
-            ScpAckInfo.sendOk(srcOut); // ready to receive the data from source
+            ScpAckInfo.sendOk(srcOut, csOut); // ready to receive the data from source
             xferCount = IoUtils.copy(inputStream, dstOut);
             dstOut.flush(); // make sure all data sent to destination
         }
@@ -392,7 +399,7 @@ public class ScpRemote2RemoteTransferHelper extends AbstractLoggingBean {
         ackInfo.validateCommandStatusCode("[SRC-EOF] " + header, "transferSimpleFile");
 
         // wait for destination to signal data received
-        ackInfo = ScpAckInfo.readAck(dstIn, false);
+        ackInfo = ScpAckInfo.readAck(dstIn, csIn, false);
         ackInfo.validateCommandStatusCode("[DST-EOF] " + header, "transferSimpleFile");
         return xferCount;
     }
@@ -402,7 +409,7 @@ public class ScpRemote2RemoteTransferHelper extends AbstractLoggingBean {
             String destination, InputStream dstIn, OutputStream dstOut,
             String header)
             throws IOException {
-        ScpIoUtils.writeLine(dstOut, header);
+        ScpIoUtils.writeLine(dstOut, csOut, header);
         ScpAckInfo ackInfo = transferStatusCode(header, dstIn, srcOut);
         ackInfo.validateCommandStatusCode("[DST] " + header, "transferTimestampCommand");
 
@@ -414,11 +421,11 @@ public class ScpRemote2RemoteTransferHelper extends AbstractLoggingBean {
     }
 
     protected ScpAckInfo transferStatusCode(Object logHint, InputStream in, OutputStream out) throws IOException {
-        ScpAckInfo ackInfo = ScpAckInfo.readAck(in, false);
+        ScpAckInfo ackInfo = ScpAckInfo.readAck(in, csIn, false);
         if (log.isDebugEnabled()) {
             log.debug("transferStatusCode({})[{}] {}", this, logHint, ackInfo);
         }
-        ackInfo.send(out);
+        ackInfo.send(out, csOut);
         return ackInfo;
     }
 
@@ -436,7 +443,7 @@ public class ScpRemote2RemoteTransferHelper extends AbstractLoggingBean {
             return new ScpAckInfo(c);
         }
 
-        String line = ScpIoUtils.readLine(in, false);
+        String line = ScpIoUtils.readLine(in, csIn, false);
         if ((c == ScpAckInfo.WARNING) || (c == ScpAckInfo.ERROR)) {
             if (log.isDebugEnabled()) {
                 log.debug("receiveNextCmd({})[{}] - ACK={}", this, logHint, new ScpAckInfo(c, line));
diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/common/ScpHelper.java b/sshd-scp/src/main/java/org/apache/sshd/scp/common/ScpHelper.java
index a22bdcb..6114066 100644
--- a/sshd-scp/src/main/java/org/apache/sshd/scp/common/ScpHelper.java
+++ b/sshd-scp/src/main/java/org/apache/sshd/scp/common/ScpHelper.java
@@ -24,6 +24,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.StreamCorruptedException;
+import java.nio.charset.Charset;
 import java.nio.file.DirectoryStream;
 import java.nio.file.FileSystem;
 import java.nio.file.InvalidPathException;
@@ -45,6 +46,7 @@ import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.common.util.io.LimitInputStream;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+import org.apache.sshd.scp.ScpModuleProperties;
 import org.apache.sshd.scp.common.ScpTransferEventListener.FileOperation;
 import org.apache.sshd.scp.common.helpers.DefaultScpFileOpener;
 import org.apache.sshd.scp.common.helpers.ScpAckInfo;
@@ -79,18 +81,30 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
     public static final int MIN_SEND_BUFFER_SIZE = MIN_COPY_BUFFER_SIZE;
 
     protected final InputStream in;
+    protected final Charset csIn;
     protected final OutputStream out;
+    protected final Charset csOut;
     protected final FileSystem fileSystem;
     protected final ScpFileOpener opener;
     protected final ScpTransferEventListener listener;
 
     private final Session sessionInstance;
 
-    public ScpHelper(Session session, InputStream in, OutputStream out, FileSystem fileSystem, ScpFileOpener opener,
-                     ScpTransferEventListener eventListener) {
+    public ScpHelper(Session session, InputStream in, OutputStream out,
+                     FileSystem fileSystem, ScpFileOpener opener, ScpTransferEventListener eventListener) {
+        this(session, in, ScpModuleProperties.SCP_INCOMING_ENCODING.getRequired(session),
+             out, ScpModuleProperties.SCP_OUTGOING_ENCODING.getRequired(session),
+             fileSystem, opener, eventListener);
+    }
+
+    public ScpHelper(Session session,
+                     InputStream in, Charset csIn, OutputStream out, Charset csOut,
+                     FileSystem fileSystem, ScpFileOpener opener, ScpTransferEventListener eventListener) {
         this.sessionInstance = Objects.requireNonNull(session, "No session");
         this.in = Objects.requireNonNull(in, "No input stream");
+        this.csIn = Objects.requireNonNull(csIn, "No input charset");
         this.out = Objects.requireNonNull(out, "No output stream");
+        this.csOut = Objects.requireNonNull(csOut, "No output charset");
         this.fileSystem = fileSystem;
         this.opener = (opener == null) ? DefaultScpFileOpener.INSTANCE : opener;
         this.listener = (eventListener == null) ? ScpTransferEventListener.EMPTY : eventListener;
@@ -182,7 +196,7 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
                 case -1:
                     return;
                 case ScpReceiveDirCommandDetails.COMMAND_NAME:
-                    line = ScpIoUtils.readLine(in);
+                    line = ScpIoUtils.readLine(in, csIn);
                     line = Character.toString((char) c) + line;
                     isDir = true;
                     if (debugEnabled) {
@@ -190,14 +204,14 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
                     }
                     break;
                 case ScpReceiveFileCommandDetails.COMMAND_NAME:
-                    line = ScpIoUtils.readLine(in);
+                    line = ScpIoUtils.readLine(in, csIn);
                     line = Character.toString((char) c) + line;
                     if (debugEnabled) {
                         log.debug("receive({}) - Received 'C' header: {}", this, line);
                     }
                     break;
                 case ScpTimestampCommandDetails.COMMAND_NAME:
-                    line = ScpIoUtils.readLine(in);
+                    line = ScpIoUtils.readLine(in, csIn);
                     line = Character.toString((char) c) + line;
                     if (debugEnabled) {
                         log.debug("receive({}) - Received 'T' header: {}", this, line);
@@ -206,7 +220,7 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
                     sendOk();
                     continue;
                 case ScpDirEndCommandDetails.COMMAND_NAME:
-                    line = ScpIoUtils.readLine(in);
+                    line = ScpIoUtils.readLine(in, csIn);
                     line = Character.toString((char) c) + line;
                     if (debugEnabled) {
                         log.debug("receive({}) - Received 'E' header: {}", this, line);
@@ -238,7 +252,7 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
         }
 
         if ((c == ScpAckInfo.WARNING) || (c == ScpAckInfo.ERROR)) {
-            String line = ScpIoUtils.readLine(in, true);
+            String line = ScpIoUtils.readLine(in, csIn, true);
             if (log.isDebugEnabled()) {
                 log.debug("receiveNextCmd - ACK={}", new ScpAckInfo(c, line));
             }
@@ -388,7 +402,7 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
     }
 
     public String readLine(boolean canEof) throws IOException {
-        return ScpIoUtils.readLine(in, canEof);
+        return ScpIoUtils.readLine(in, csIn, canEof);
     }
 
     public void send(Collection<String> paths, boolean recursive, boolean preserve, int bufferSize) throws IOException {
@@ -538,7 +552,7 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
 
         ScpTimestampCommandDetails time = resolver.getTimestamp();
         if (preserve && (time != null)) {
-            ScpAckInfo ackInfo = ScpIoUtils.sendAcknowledgedCommand(time, in, out);
+            ScpAckInfo ackInfo = ScpIoUtils.sendAcknowledgedCommand(time, in, csIn, out, csOut);
             String cmd = time.toHeader();
             if (debugEnabled) {
                 log.debug("sendStream({})[{}] command='{}' ACK={}", this, resolver, cmd, ackInfo);
@@ -711,7 +725,7 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
     }
 
     protected ScpAckInfo sendAcknowledgedCommand(String cmd) throws IOException {
-        return ScpIoUtils.sendAcknowledgedCommand(cmd, in, out);
+        return ScpIoUtils.sendAcknowledgedCommand(cmd, in, csIn, out, csOut);
     }
 
     public void sendOk() throws IOException {
@@ -727,11 +741,11 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
     }
 
     protected void sendResponseMessage(int level, String message) throws IOException {
-        ScpAckInfo.sendAck(out, level, message);
+        ScpAckInfo.sendAck(out, csOut, level, message);
     }
 
     public ScpAckInfo readAck(boolean canEof) throws IOException {
-        return ScpAckInfo.readAck(in, canEof);
+        return ScpAckInfo.readAck(in, csIn, canEof);
     }
 
     @Override
diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/common/helpers/ScpAckInfo.java b/sshd-scp/src/main/java/org/apache/sshd/scp/common/helpers/ScpAckInfo.java
index b89e9db..9b0b986 100644
--- a/sshd-scp/src/main/java/org/apache/sshd/scp/common/helpers/ScpAckInfo.java
+++ b/sshd-scp/src/main/java/org/apache/sshd/scp/common/helpers/ScpAckInfo.java
@@ -23,6 +23,7 @@ import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.charset.Charset;
 
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
@@ -59,8 +60,8 @@ public class ScpAckInfo {
         return line;
     }
 
-    public <O extends OutputStream> O send(O out) throws IOException {
-        return sendAck(out, getStatusCode(), getLine());
+    public <O extends OutputStream> O send(O out, Charset cs) throws IOException {
+        return sendAck(out, cs, getStatusCode(), getLine());
     }
 
     public void validateCommandStatusCode(String command, Object location) throws IOException {
@@ -83,7 +84,7 @@ public class ScpAckInfo {
         }
     }
 
-    public static ScpAckInfo readAck(InputStream in, boolean canEof) throws IOException {
+    public static ScpAckInfo readAck(InputStream in, Charset cs, boolean canEof) throws IOException {
         int statusCode = in.read();
         if (statusCode == -1) {
             if (canEof) {
@@ -96,7 +97,7 @@ public class ScpAckInfo {
             return new ScpAckInfo(statusCode);  // OK status has no extra data
         }
 
-        String line = ScpIoUtils.readLine(in);
+        String line = ScpIoUtils.readLine(in, cs);
         return new ScpAckInfo(statusCode, line);
     }
 
@@ -104,24 +105,27 @@ public class ScpAckInfo {
      * Sends {@link #OK} ACK code
      *
      * @param  out         The target {@link OutputStream}
+     * @param  cs          The {@link Charset} to use to write the textual data
      * @throws IOException If failed to send the ACK code
      */
-    public static void sendOk(OutputStream out) throws IOException {
-        sendAck(out, OK, null /* ignored */);
+    public static void sendOk(OutputStream out, Charset cs) throws IOException {
+        sendAck(out, cs, OK, null /* ignored */);
     }
 
-    public static <O extends OutputStream> O sendWarning(O out, String message) throws IOException {
-        return sendAck(out, ScpAckInfo.WARNING, (message == null) ? "" : message);
+    public static <O extends OutputStream> O sendWarning(O out, Charset cs, String message) throws IOException {
+        return sendAck(out, cs, ScpAckInfo.WARNING, (message == null) ? "" : message);
     }
 
-    public static <O extends OutputStream> O sendError(O out, String message) throws IOException {
-        return sendAck(out, ScpAckInfo.ERROR, (message == null) ? "" : message);
+    public static <O extends OutputStream> O sendError(O out, Charset cs, String message) throws IOException {
+        return sendAck(out, cs, ScpAckInfo.ERROR, (message == null) ? "" : message);
     }
 
-    public static <O extends OutputStream> O sendAck(O out, int level, String message) throws IOException {
+    public static <O extends OutputStream> O sendAck(
+            O out, Charset cs, int level, String message)
+            throws IOException {
         out.write(level);
         if (level != OK) {
-            ScpIoUtils.writeLine(out, message); // this also flushes
+            ScpIoUtils.writeLine(out, cs, message); // this also flushes
         } else {
             out.flush();
         }
diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/common/helpers/ScpIoUtils.java b/sshd-scp/src/main/java/org/apache/sshd/scp/common/helpers/ScpIoUtils.java
index 4ad78f2..6407287 100644
--- a/sshd-scp/src/main/java/org/apache/sshd/scp/common/helpers/ScpIoUtils.java
+++ b/sshd-scp/src/main/java/org/apache/sshd/scp/common/helpers/ScpIoUtils.java
@@ -24,7 +24,7 @@ import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
+import java.nio.charset.Charset;
 import java.time.Duration;
 import java.util.Collection;
 import java.util.Collections;
@@ -52,16 +52,16 @@ public final class ScpIoUtils {
         throw new UnsupportedOperationException("No instance");
     }
 
-    public static String readLine(InputStream in) throws IOException {
-        return readLine(in, false);
+    public static String readLine(InputStream in, Charset cs) throws IOException {
+        return readLine(in, cs, false);
     }
 
-    public static String readLine(InputStream in, boolean canEof) throws IOException {
+    public static String readLine(InputStream in, Charset cs, boolean canEof) throws IOException {
         try (ByteArrayOutputStream baos = new ByteArrayOutputStream(Byte.MAX_VALUE)) {
             for (;;) {
                 int c = in.read();
                 if (c == '\n') {
-                    return baos.toString(StandardCharsets.UTF_8.name());
+                    return baos.toString(cs.name());
                 } else if (c == -1) {
                     if (!canEof) {
                         throw new EOFException("EOF while await end of line");
@@ -74,22 +74,25 @@ public final class ScpIoUtils {
         }
     }
 
-    public static void writeLine(OutputStream out, String cmd) throws IOException {
+    public static void writeLine(OutputStream out, Charset cs, String cmd) throws IOException {
         if (cmd != null) {
-            out.write(cmd.getBytes(StandardCharsets.UTF_8));
+            out.write(cmd.getBytes(cs));
         }
         out.write('\n');
         out.flush();
     }
 
-    public static ScpAckInfo sendAcknowledgedCommand(AbstractScpCommandDetails cmd, InputStream in, OutputStream out)
+    public static ScpAckInfo sendAcknowledgedCommand(
+            AbstractScpCommandDetails cmd, InputStream in, Charset csIn, OutputStream out, Charset csOut)
             throws IOException {
-        return sendAcknowledgedCommand(cmd.toHeader(), in, out);
+        return sendAcknowledgedCommand(cmd.toHeader(), in, csIn, out, csOut);
     }
 
-    public static ScpAckInfo sendAcknowledgedCommand(String cmd, InputStream in, OutputStream out) throws IOException {
-        writeLine(out, cmd);
-        return ScpAckInfo.readAck(in, false);
+    public static ScpAckInfo sendAcknowledgedCommand(
+            String cmd, InputStream in, Charset csIn, OutputStream out, Charset csOut)
+            throws IOException {
+        writeLine(out, csOut, cmd);
+        return ScpAckInfo.readAck(in, csIn, false);
     }
 
     public static String getExitStatusName(Integer exitStatus) {
diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpCommand.java b/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpCommand.java
index a3a588a..7b25b68 100644
--- a/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpCommand.java
+++ b/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpCommand.java
@@ -19,6 +19,7 @@
 package org.apache.sshd.scp.server;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.Objects;
 
@@ -220,7 +221,7 @@ public class ScpCommand extends AbstractFileSystemCommand implements ServerChann
             log.debug("writeCommandResponseMessage({}) command='{}', exit-status={}: {}",
                     getServerSession(), command, exitValue, exitMessage);
         }
-        ScpAckInfo.sendAck(getOutputStream(), exitValue, exitMessage);
+        ScpAckInfo.sendAck(getOutputStream(), StandardCharsets.UTF_8, exitValue, exitMessage);
     }
 
     @Override
diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpShell.java b/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpShell.java
index df4d433..232a4b0 100644
--- a/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpShell.java
+++ b/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpShell.java
@@ -499,7 +499,7 @@ public class ScpShell extends AbstractFileSystemCommand implements ServerChannel
                 exitValue = ScpAckInfo.ERROR;
             }
             String exitMessage = GenericUtils.trimToEmpty(e.getMessage());
-            ScpAckInfo.sendAck(getOutputStream(), exitValue, exitMessage);
+            ScpAckInfo.sendAck(getOutputStream(), StandardCharsets.UTF_8, exitValue, exitMessage);
             variables.put(STATUS, exitValue);
         }
     }
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 6f6c095..7f4f2e4 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
@@ -947,7 +947,7 @@ public class ScpTest extends AbstractScpTestSupport {
             os.write(0);
             os.flush();
 
-            String header = ScpIoUtils.readLine(is, false);
+            String header = ScpIoUtils.readLine(is, StandardCharsets.UTF_8, false);
             String expHeader
                     = ScpReceiveFileCommandDetails.COMMAND_NAME + ScpReceiveFileCommandDetails.DEFAULT_FILE_OCTAL_PERMISSIONS
                       + " " + Files.size(target) + " " + fileName;
@@ -979,33 +979,33 @@ public class ScpTest extends AbstractScpTestSupport {
 
         try (OutputStream os = c.getOutputStream();
              InputStream is = c.getInputStream()) {
-            ScpAckInfo.sendOk(os);
+            ScpAckInfo.sendOk(os, StandardCharsets.UTF_8);
 
-            String header = ScpIoUtils.readLine(is, false);
+            String header = ScpIoUtils.readLine(is, StandardCharsets.UTF_8, false);
             String expPrefix = ScpReceiveDirCommandDetails.COMMAND_NAME
                                + ScpReceiveDirCommandDetails.DEFAULT_DIR_OCTAL_PERMISSIONS + " 0 ";
             assertTrue("Bad header prefix for " + path + ": " + header, header.startsWith(expPrefix));
-            ScpAckInfo.sendOk(os);
+            ScpAckInfo.sendOk(os, StandardCharsets.UTF_8);
 
-            header = ScpIoUtils.readLine(is, false);
+            header = ScpIoUtils.readLine(is, StandardCharsets.UTF_8, false);
             String fileName = Objects.toString(target.getFileName(), null);
             String expHeader
                     = ScpReceiveFileCommandDetails.COMMAND_NAME + ScpReceiveFileCommandDetails.DEFAULT_FILE_OCTAL_PERMISSIONS
                       + " " + Files.size(target) + " " + fileName;
             assertEquals("Mismatched dir header for " + path, expHeader, header);
             int length = Integer.parseInt(header.substring(6, header.indexOf(' ', 6)));
-            ScpAckInfo.sendOk(os);
+            ScpAckInfo.sendOk(os, StandardCharsets.UTF_8);
 
             byte[] buffer = new byte[length];
             length = is.read(buffer, 0, buffer.length);
             assertEquals("Mismatched read buffer size for " + path, length, buffer.length);
             assertAckReceived(is, "Read date of " + path);
 
-            ScpAckInfo.sendOk(os);
+            ScpAckInfo.sendOk(os, StandardCharsets.UTF_8);
 
-            header = ScpIoUtils.readLine(is, false);
+            header = ScpIoUtils.readLine(is, StandardCharsets.UTF_8, false);
             assertEquals("Mismatched end value for " + path, "E", header);
-            ScpAckInfo.sendOk(os);
+            ScpAckInfo.sendOk(os, StandardCharsets.UTF_8);
 
             return new String(buffer, StandardCharsets.UTF_8);
         } finally {
@@ -1022,7 +1022,7 @@ public class ScpTest extends AbstractScpTestSupport {
         try (OutputStream os = c.getOutputStream();
              InputStream is = c.getInputStream()) {
 
-            ScpAckInfo.sendOk(os);
+            ScpAckInfo.sendOk(os, StandardCharsets.UTF_8);
             assertEquals("Mismatched response for command: " + command, ScpAckInfo.ERROR, is.read());
         } finally {
             c.disconnect();
@@ -1051,7 +1051,7 @@ public class ScpTest extends AbstractScpTestSupport {
             os.flush();
             assertAckReceived(is, "Sent data (length=" + data.length() + ") for " + path + "[" + name + "]");
 
-            ScpAckInfo.sendOk(os);
+            ScpAckInfo.sendOk(os, StandardCharsets.UTF_8);
 
             Thread.sleep(100);
         } finally {
@@ -1060,7 +1060,7 @@ public class ScpTest extends AbstractScpTestSupport {
     }
 
     protected void assertAckReceived(OutputStream os, InputStream is, String command) throws IOException {
-        ScpIoUtils.writeLine(os, command);
+        ScpIoUtils.writeLine(os, StandardCharsets.UTF_8, command);
         assertAckReceived(is, command);
     }
 
@@ -1080,7 +1080,7 @@ public class ScpTest extends AbstractScpTestSupport {
             assertAckReceived(is, command);
 
             command = "C7777 " + data.length() + " " + name;
-            ScpIoUtils.writeLine(os, command);
+            ScpIoUtils.writeLine(os, StandardCharsets.UTF_8, command);
             assertEquals("Mismatched response for command=" + command, ScpAckInfo.ERROR, is.read());
         } finally {
             c.disconnect();
@@ -1105,8 +1105,8 @@ public class ScpTest extends AbstractScpTestSupport {
             os.flush();
             assertAckReceived(is, "Send data of " + path);
 
-            ScpAckInfo.sendOk(os);
-            ScpIoUtils.writeLine(os, ScpDirEndCommandDetails.HEADER);
+            ScpAckInfo.sendOk(os, StandardCharsets.UTF_8);
+            ScpIoUtils.writeLine(os, StandardCharsets.UTF_8, ScpDirEndCommandDetails.HEADER);
             assertAckReceived(is, "Signal end of " + path);
         } finally {
             c.disconnect();


[mina-sshd] 07/07: Added SftpCommandMain 'lls' (local-LS) command support

Posted by lg...@apache.org.
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

commit b62701572f621df5690fd65074e11df96fdf8965
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri Mar 5 11:12:11 2021 +0200

    Added SftpCommandMain 'lls' (local-LS) command support
---
 .../apache/sshd/cli/client/SftpCommandMain.java    | 56 ++++++++++++++++++++++
 1 file changed, 56 insertions(+)

diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
index aa23166..066181f 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
@@ -117,6 +117,7 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
                 new LcdCommandExecutor(),
                 new MkdirCommandExecutor(),
                 new LsCommandExecutor(),
+                new LlsCommandExecutor(),
                 new LStatCommandExecutor(),
                 new ReadLinkCommandExecutor(),
                 new RmCommandExecutor(),
@@ -709,6 +710,61 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
 
     /* -------------------------------------------------------------------- */
 
+    private class LlsCommandExecutor implements SftpCommandExecutor {
+        LlsCommandExecutor() {
+            super();
+        }
+
+        @Override
+        public String getName() {
+            return "lls";
+        }
+
+        @Override
+        public boolean executeCommand(
+                String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
+                throws Exception {
+            String[] comps = GenericUtils.split(args, ' ');
+            int numComps = GenericUtils.length(comps);
+            String pathArg = (numComps <= 0) ? null : GenericUtils.trimToEmpty(comps[numComps - 1]);
+            String flags = (numComps >= 2) ? GenericUtils.trimToEmpty(comps[0]) : null;
+            // ignore all flags
+            if ((GenericUtils.length(pathArg) > 0) && (pathArg.charAt(0) == '-')) {
+                flags = pathArg;
+                pathArg = null;
+            }
+
+            Path local = Paths.get(resolveLocalPath(pathArg)).normalize().toAbsolutePath();
+            if (Files.notExists(local)) {
+                stderr.println("File/Folder not found");
+            } else if (Files.isDirectory(local)) {
+                try (DirectoryStream<Path> ds = Files.newDirectoryStream(local)) {
+                    for (Path path : ds) {
+                        displayLocalPathInfo(path, stdout);
+                    }
+                }
+            } else if (Files.isRegularFile(local)) {
+                displayLocalPathInfo(local, stdout);
+            } else {
+                stderr.println("Unsupported special file");
+            }
+
+            return false;
+        }
+
+        protected void displayLocalPathInfo(Path path, PrintStream stdout) throws IOException {
+            stdout.append('\t').append(Objects.toString(path.getFileName()));
+            if (Files.isRegularFile(path)) {
+                stdout.append(' ').append(Long.toString(Files.size(path)));
+            } else {
+                stdout.append('/');
+            }
+            stdout.println();
+        }
+    }
+
+    /* -------------------------------------------------------------------- */
+
     private class RmCommandExecutor implements SftpCommandExecutor {
         RmCommandExecutor() {
             super();


[mina-sshd] 01/07: [SSHD-1133] Re-factored locations and names of ServerSession and server-side ChannelSession related classes

Posted by lg...@apache.org.
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

commit 80c5e16cd23dd4727c3542d64e552ad83e7c05e1
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri Feb 26 10:26:50 2021 +0200

    [SSHD-1133] Re-factored locations and names of ServerSession and server-side ChannelSession related classes
---
 CHANGES.md                                         |   2 +
 .../org/apache/sshd/cli/server/SshFsMounter.java   |   4 +-
 .../sshd/server/channel/ChannelDataReceiver.java   |   4 +-
 .../apache/sshd/server/channel/ChannelSession.java |   7 +-
 .../server/{ => channel}/ChannelSessionAware.java  |   3 +-
 .../ServerChannelSessionHolder.java}               |  14 +--
 .../server/command/AbstractCommandSupport.java     |   4 +-
 .../command/AbstractDelegatingCommandFactory.java  |  15 +--
 .../ServerSessionAware.java}                       |   6 +-
 .../apache/sshd/server/shell/InvertedShell.java    |  14 +--
 .../sshd/server/shell/InvertedShellWrapper.java    |   6 +-
 .../org/apache/sshd/server/shell/ProcessShell.java |   8 +-
 .../server/shell/InvertedShellWrapperTest.java     |   8 +-
 .../sshd/util/test/AsyncEchoShellFactory.java      |   2 +-
 .../apache/sshd/util/test/BogusInvertedShell.java  |   2 +-
 .../apache/sshd/git/AbstractGitCommandFactory.java |   7 +-
 .../org/apache/sshd/scp/server/ScpCommand.java     |  16 ++-
 .../apache/sshd/scp/server/ScpCommandFactory.java  |   6 +-
 .../java/org/apache/sshd/scp/server/ScpShell.java  |  36 ++++---
 .../java/org/apache/sshd/scp/client/ScpTest.java   |   8 +-
 .../sftp/server/AbstractSftpSubsystemHelper.java   |  10 +-
 .../org/apache/sshd/sftp/server/SftpSubsystem.java | 110 ++++++++++-----------
 .../sshd/sftp/server/SftpSubsystemEnvironment.java |   3 +-
 .../sshd/sftp/server/SftpSubsystemFactory.java     |   2 +-
 .../apache/sshd/sftp/client/SftpVersionsTest.java  |   4 +-
 .../helpers/SpaceAvailableExtensionImplTest.java   |   2 +-
 .../openssh/helpers/OpenSSHExtensionsTest.java     |   2 +-
 27 files changed, 162 insertions(+), 143 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 942ed01..6e44df8 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -14,6 +14,8 @@
 
 ## Major code re-factoring
 
+* [SSHD-1133](https://issues.apache.org/jira/browse/SSHD-1133) Re-factored locations and names of `ServerSession` and server-side `ChannelSession` related classes
+
 ## Minor code helpers
 
 * [SSHD-525](https://issues.apache.org/jira/browse/SSHD-525) Added support for SFTP **client-side** ["posix-rename@openssh.com"
diff --git a/sshd-cli/src/test/java/org/apache/sshd/cli/server/SshFsMounter.java b/sshd-cli/src/test/java/org/apache/sshd/cli/server/SshFsMounter.java
index d7e29b7..4a55cdf 100644
--- a/sshd-cli/src/test/java/org/apache/sshd/cli/server/SshFsMounter.java
+++ b/sshd-cli/src/test/java/org/apache/sshd/cli/server/SshFsMounter.java
@@ -49,7 +49,6 @@ import org.apache.sshd.common.util.threads.ThreadUtils;
 import org.apache.sshd.scp.server.ScpCommandFactory;
 import org.apache.sshd.server.Environment;
 import org.apache.sshd.server.ExitCallback;
-import org.apache.sshd.server.SessionAware;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.server.auth.password.AcceptAllPasswordAuthenticator;
 import org.apache.sshd.server.channel.ChannelSession;
@@ -58,6 +57,7 @@ import org.apache.sshd.server.command.CommandFactory;
 import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
 import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
 import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.server.session.ServerSessionAware;
 import org.apache.sshd.server.shell.ShellFactory;
 import org.apache.sshd.sftp.server.SftpSubsystemFactory;
 import org.apache.sshd.util.test.CommonTestSupportUtils;
@@ -69,7 +69,7 @@ import org.apache.sshd.util.test.CoreTestSupportUtils;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public final class SshFsMounter extends SshServerCliSupport {
-    public static class MounterCommand extends AbstractLoggingBean implements Command, SessionAware, Runnable {
+    public static class MounterCommand extends AbstractLoggingBean implements Command, ServerSessionAware, Runnable {
         private final String command;
         private final String cmdName;
         private final List<String> args;
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelDataReceiver.java b/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelDataReceiver.java
index 1049cc3..da0d1ab 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelDataReceiver.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelDataReceiver.java
@@ -33,11 +33,11 @@ import java.io.IOException;
  * command wants to do a callback-driven I/O for the data it receives from the client, it can call
  * {@link ChannelSession#setDataReceiver(ChannelDataReceiver)} to do so. (And to grab a reference to
  * {@link ChannelSession}, a {@link org.apache.sshd.server.command.Command} should implement
- * {@link org.apache.sshd.server.ChannelSessionAware}.)
+ * {@link org.apache.sshd.server.channel.ChannelSessionAware}.)
  * </p>
  *
  * @see ChannelSession#setDataReceiver(ChannelDataReceiver)
- * @see org.apache.sshd.server.ChannelSessionAware
+ * @see org.apache.sshd.server.channel.ChannelSessionAware
  */
 public interface ChannelDataReceiver extends Closeable {
     /**
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java b/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
index 86b4bbf..adae173 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
@@ -62,10 +62,8 @@ import org.apache.sshd.common.util.closeable.IoBaseCloseable;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.common.util.io.LoggingFilterOutputStream;
 import org.apache.sshd.core.CoreModuleProperties;
-import org.apache.sshd.server.ChannelSessionAware;
 import org.apache.sshd.server.Environment;
 import org.apache.sshd.server.ServerFactoryManager;
-import org.apache.sshd.server.SessionAware;
 import org.apache.sshd.server.Signal;
 import org.apache.sshd.server.StandardEnvironment;
 import org.apache.sshd.server.command.AsyncCommandInputStreamAware;
@@ -75,6 +73,7 @@ import org.apache.sshd.server.command.CommandFactory;
 import org.apache.sshd.server.forward.AgentForwardingFilter;
 import org.apache.sshd.server.forward.X11ForwardingFilter;
 import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.server.session.ServerSessionAware;
 import org.apache.sshd.server.shell.ShellFactory;
 import org.apache.sshd.server.subsystem.SubsystemFactory;
 import org.apache.sshd.server.x11.X11ForwardSupport;
@@ -707,8 +706,8 @@ public class ChannelSession extends AbstractServerChannel {
         Session session = getSession();
         addEnvVariable(Environment.ENV_USER, session.getUsername());
         // If the shell wants to be aware of the session, let's do that
-        if (command instanceof SessionAware) {
-            ((SessionAware) command).setSession((ServerSession) session);
+        if (command instanceof ServerSessionAware) {
+            ((ServerSessionAware) command).setSession((ServerSession) session);
         }
         if (command instanceof ChannelSessionAware) {
             ((ChannelSessionAware) command).setChannelSession(this);
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/ChannelSessionAware.java b/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSessionAware.java
similarity index 93%
rename from sshd-core/src/main/java/org/apache/sshd/server/ChannelSessionAware.java
rename to sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSessionAware.java
index 5936ed2..949c934 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/ChannelSessionAware.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSessionAware.java
@@ -16,9 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sshd.server;
+package org.apache.sshd.server.channel;
 
-import org.apache.sshd.server.channel.ChannelSession;
 import org.apache.sshd.server.command.Command;
 
 /**
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/SessionAware.java b/sshd-core/src/main/java/org/apache/sshd/server/channel/ServerChannelSessionHolder.java
similarity index 67%
copy from sshd-core/src/main/java/org/apache/sshd/server/SessionAware.java
copy to sshd-core/src/main/java/org/apache/sshd/server/channel/ServerChannelSessionHolder.java
index 4070096..ebb87c0 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/SessionAware.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/channel/ServerChannelSessionHolder.java
@@ -16,19 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sshd.server;
 
-import org.apache.sshd.server.session.ServerSession;
+package org.apache.sshd.server.channel;
 
 /**
- * Interface that can be implemented by a command to be able to access the server session in which this command will be
- * used.
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 @FunctionalInterface
-public interface SessionAware {
-
-    /**
-     * @param session The {@link ServerSession} in which this shell will be executed.
-     */
-    void setSession(ServerSession session);
+public interface ServerChannelSessionHolder {
+    ChannelSession getServerChannelSession();
 }
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/command/AbstractCommandSupport.java b/sshd-core/src/main/java/org/apache/sshd/server/command/AbstractCommandSupport.java
index 4511e29..b09bf4d 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/command/AbstractCommandSupport.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/command/AbstractCommandSupport.java
@@ -34,9 +34,9 @@ import org.apache.sshd.common.util.threads.ExecutorServiceCarrier;
 import org.apache.sshd.common.util.threads.ThreadUtils;
 import org.apache.sshd.server.Environment;
 import org.apache.sshd.server.ExitCallback;
-import org.apache.sshd.server.SessionAware;
 import org.apache.sshd.server.channel.ChannelSession;
 import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.server.session.ServerSessionAware;
 import org.apache.sshd.server.session.ServerSessionHolder;
 
 /**
@@ -46,7 +46,7 @@ import org.apache.sshd.server.session.ServerSessionHolder;
  */
 public abstract class AbstractCommandSupport
         extends AbstractLoggingBean
-        implements Command, Runnable, ExecutorServiceCarrier, SessionAware,
+        implements Command, Runnable, ExecutorServiceCarrier, ServerSessionAware,
         SessionHolder<ServerSession>, ServerSessionHolder {
     protected volatile Thread cmdRunner;
     protected CloseableExecutorService executorService;
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/command/AbstractDelegatingCommandFactory.java b/sshd-core/src/main/java/org/apache/sshd/server/command/AbstractDelegatingCommandFactory.java
index c94e910..3833e0e 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/command/AbstractDelegatingCommandFactory.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/command/AbstractDelegatingCommandFactory.java
@@ -26,7 +26,7 @@ import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 import org.apache.sshd.server.channel.ChannelSession;
 
 /**
- * TODO Add javadoc
+ * A {@link CommandFactory} wrapper that delegates calls to a proxy
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
@@ -58,8 +58,8 @@ public abstract class AbstractDelegatingCommandFactory extends AbstractLoggingBe
 
     @Override
     public Command createCommand(ChannelSession channel, String command) throws IOException {
-        if (isSupportedCommand(command)) {
-            return executeSupportedCommand(command);
+        if (isSupportedCommand(channel, command)) {
+            return executeSupportedCommand(channel, command);
         }
 
         CommandFactory factory = getDelegateCommandFactory();
@@ -67,19 +67,20 @@ public abstract class AbstractDelegatingCommandFactory extends AbstractLoggingBe
             return factory.createCommand(channel, command);
         }
 
-        return createUnsupportedCommand(command);
+        return createUnsupportedCommand(channel, command);
     }
 
     /**
+     * @param  channel The {@link ChannelSession} through which the command was received
      * @param  command The command about to be executed
      * @return         {@code true} if this command is supported by the command factory, {@code false} if it will be
      *                 passed on to the {@link #getDelegateCommandFactory() delegate} factory
      */
-    public abstract boolean isSupportedCommand(String command);
+    public abstract boolean isSupportedCommand(ChannelSession channel, String command);
 
-    protected abstract Command executeSupportedCommand(String command);
+    protected abstract Command executeSupportedCommand(ChannelSession channel, String command);
 
-    protected Command createUnsupportedCommand(String command) {
+    protected Command createUnsupportedCommand(ChannelSession channel, String command) {
         throw new IllegalArgumentException("Unknown command to execute: " + command);
     }
 }
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/SessionAware.java b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSessionAware.java
similarity index 90%
rename from sshd-core/src/main/java/org/apache/sshd/server/SessionAware.java
rename to sshd-core/src/main/java/org/apache/sshd/server/session/ServerSessionAware.java
index 4070096..5fc4d94 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/SessionAware.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSessionAware.java
@@ -16,16 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sshd.server;
-
-import org.apache.sshd.server.session.ServerSession;
+package org.apache.sshd.server.session;
 
 /**
  * Interface that can be implemented by a command to be able to access the server session in which this command will be
  * used.
  */
 @FunctionalInterface
-public interface SessionAware {
+public interface ServerSessionAware {
 
     /**
      * @param session The {@link ServerSession} in which this shell will be executed.
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShell.java b/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShell.java
index 2d5cfe0..6c84e1f 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShell.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShell.java
@@ -22,10 +22,10 @@ import java.io.InputStream;
 import java.io.OutputStream;
 
 import org.apache.sshd.common.session.SessionHolder;
-import org.apache.sshd.server.SessionAware;
-import org.apache.sshd.server.channel.ChannelSession;
+import org.apache.sshd.server.channel.ServerChannelSessionHolder;
 import org.apache.sshd.server.command.CommandLifecycle;
 import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.server.session.ServerSessionAware;
 import org.apache.sshd.server.session.ServerSessionHolder;
 
 /**
@@ -37,9 +37,9 @@ import org.apache.sshd.server.session.ServerSessionHolder;
  */
 public interface InvertedShell
         extends SessionHolder<ServerSession>,
-        ServerSessionHolder,
+        ServerSessionHolder, ServerChannelSessionHolder,
         CommandLifecycle,
-        SessionAware {
+        ServerSessionAware {
 
     @Override
     default ServerSession getSession() {
@@ -47,12 +47,6 @@ public interface InvertedShell
     }
 
     /**
-     * @return The {@link ChannelSession} instance through which the shell was created - may be {@code null} if shell
-     *         not started yet
-     */
-    ChannelSession getChannelSession();
-
-    /**
      * Returns the output stream used to feed the shell. This method is called after the shell has been started.
      *
      * @return The {@link OutputStream} used to feed the shell
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShellWrapper.java b/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShellWrapper.java
index 201f8c0..29b6573 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShellWrapper.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShellWrapper.java
@@ -34,10 +34,10 @@ import org.apache.sshd.common.util.threads.ThreadUtils;
 import org.apache.sshd.core.CoreModuleProperties;
 import org.apache.sshd.server.Environment;
 import org.apache.sshd.server.ExitCallback;
-import org.apache.sshd.server.SessionAware;
 import org.apache.sshd.server.channel.ChannelSession;
 import org.apache.sshd.server.command.Command;
 import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.server.session.ServerSessionAware;
 
 /**
  * A shell implementation that wraps an instance of {@link InvertedShell} as a {@link Command}. This is useful when
@@ -46,7 +46,7 @@ import org.apache.sshd.server.session.ServerSession;
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class InvertedShellWrapper extends AbstractLoggingBean implements Command, SessionAware {
+public class InvertedShellWrapper extends AbstractLoggingBean implements Command, ServerSessionAware {
 
     private final InvertedShell shell;
     private final Executor executor;
@@ -203,7 +203,7 @@ public class InvertedShellWrapper extends AbstractLoggingBean implements Command
         } catch (Throwable e) {
             boolean debugEnabled = log.isDebugEnabled();
             try {
-                shell.destroy(shell.getChannelSession());
+                shell.destroy(shell.getServerChannelSession());
             } catch (Throwable err) {
                 warn("pumpStreams({}) failed ({}) to destroy shell: {}",
                         this, e.getClass().getSimpleName(), e.getMessage(), e);
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShell.java b/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShell.java
index 3965c84..0bb881c 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShell.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShell.java
@@ -48,7 +48,7 @@ public class ProcessShell extends AbstractLoggingBean implements InvertedShell {
     private final List<String> command;
     private String cmdValue;
     private ServerSession session;
-    private ChannelSession channel;
+    private ChannelSession channelSession;
     private Process process;
     private TtyFilterOutputStream in;
     private TtyFilterInputStream out;
@@ -81,13 +81,13 @@ public class ProcessShell extends AbstractLoggingBean implements InvertedShell {
     }
 
     @Override
-    public ChannelSession getChannelSession() {
-        return channel;
+    public ChannelSession getServerChannelSession() {
+        return channelSession;
     }
 
     @Override
     public void start(ChannelSession channel, Environment env) throws IOException {
-        this.channel = channel;
+        this.channelSession = channel;
 
         Map<String, String> varsMap = resolveShellEnvironment(env.getEnv());
         for (int i = 0; i < command.size(); i++) {
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/shell/InvertedShellWrapperTest.java b/sshd-core/src/test/java/org/apache/sshd/server/shell/InvertedShellWrapperTest.java
index 6eb52e6..1a600d7 100644
--- a/sshd-core/src/test/java/org/apache/sshd/server/shell/InvertedShellWrapperTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/server/shell/InvertedShellWrapperTest.java
@@ -93,8 +93,8 @@ public class InvertedShellWrapperTest extends BaseTestSupport {
             }
 
             @Override
-            public ChannelSession getChannelSession() {
-                return bogusShell.getChannelSession();
+            public ChannelSession getServerChannelSession() {
+                return bogusShell.getServerChannelSession();
             }
 
             @Override
@@ -210,12 +210,12 @@ public class InvertedShellWrapperTest extends BaseTestSupport {
                         return session;
                     }
 
-                    ChannelSession channel = getChannelSession();
+                    ChannelSession channel = getServerChannelSession();
                     return (channel == null) ? null : channel.getServerSession();
                 }
 
                 @Override
-                public ChannelSession getChannelSession() {
+                public ChannelSession getServerChannelSession() {
                     return channel;
                 }
 
diff --git a/sshd-core/src/test/java/org/apache/sshd/util/test/AsyncEchoShellFactory.java b/sshd-core/src/test/java/org/apache/sshd/util/test/AsyncEchoShellFactory.java
index 7218ffd..2373539 100644
--- a/sshd-core/src/test/java/org/apache/sshd/util/test/AsyncEchoShellFactory.java
+++ b/sshd-core/src/test/java/org/apache/sshd/util/test/AsyncEchoShellFactory.java
@@ -29,11 +29,11 @@ import org.apache.sshd.common.io.IoInputStream;
 import org.apache.sshd.common.io.IoOutputStream;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
-import org.apache.sshd.server.ChannelSessionAware;
 import org.apache.sshd.server.Environment;
 import org.apache.sshd.server.ExitCallback;
 import org.apache.sshd.server.channel.ChannelDataReceiver;
 import org.apache.sshd.server.channel.ChannelSession;
+import org.apache.sshd.server.channel.ChannelSessionAware;
 import org.apache.sshd.server.command.AsyncCommand;
 import org.apache.sshd.server.command.Command;
 import org.apache.sshd.server.shell.ShellFactory;
diff --git a/sshd-core/src/test/java/org/apache/sshd/util/test/BogusInvertedShell.java b/sshd-core/src/test/java/org/apache/sshd/util/test/BogusInvertedShell.java
index ce5ccdc..2b64789 100644
--- a/sshd-core/src/test/java/org/apache/sshd/util/test/BogusInvertedShell.java
+++ b/sshd-core/src/test/java/org/apache/sshd/util/test/BogusInvertedShell.java
@@ -59,7 +59,7 @@ public class BogusInvertedShell implements InvertedShell {
     }
 
     @Override
-    public ChannelSession getChannelSession() {
+    public ChannelSession getServerChannelSession() {
         return channel;
     }
 
diff --git a/sshd-git/src/main/java/org/apache/sshd/git/AbstractGitCommandFactory.java b/sshd-git/src/main/java/org/apache/sshd/git/AbstractGitCommandFactory.java
index ee66947..2c02708 100644
--- a/sshd-git/src/main/java/org/apache/sshd/git/AbstractGitCommandFactory.java
+++ b/sshd-git/src/main/java/org/apache/sshd/git/AbstractGitCommandFactory.java
@@ -25,6 +25,7 @@ import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.threads.CloseableExecutorService;
 import org.apache.sshd.common.util.threads.ExecutorServiceProvider;
+import org.apache.sshd.server.channel.ChannelSession;
 import org.apache.sshd.server.command.AbstractDelegatingCommandFactory;
 import org.apache.sshd.server.command.Command;
 import org.apache.sshd.server.command.CommandFactory;
@@ -90,7 +91,7 @@ public abstract class AbstractGitCommandFactory
     }
 
     @Override
-    public boolean isSupportedCommand(String command) {
+    public boolean isSupportedCommand(ChannelSession channel, String command) {
         if (GenericUtils.isEmpty(command)) {
             return false;
         }
@@ -100,12 +101,12 @@ public abstract class AbstractGitCommandFactory
     }
 
     @Override
-    protected Command executeSupportedCommand(String command) {
+    protected Command executeSupportedCommand(ChannelSession channel, String command) {
         return createGitCommand(command);
     }
 
     @Override
-    protected Command createUnsupportedCommand(String command) {
+    protected Command createUnsupportedCommand(ChannelSession channel, String command) {
         return new UnknownCommand(command);
     }
 
diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpCommand.java b/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpCommand.java
index 329940c..a3a588a 100644
--- a/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpCommand.java
+++ b/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpCommand.java
@@ -20,6 +20,7 @@ package org.apache.sshd.scp.server;
 
 import java.io.IOException;
 import java.util.Collections;
+import java.util.Objects;
 
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.threads.CloseableExecutorService;
@@ -33,6 +34,7 @@ import org.apache.sshd.scp.common.helpers.ScpAckInfo;
 import org.apache.sshd.server.Environment;
 import org.apache.sshd.server.ExitCallback;
 import org.apache.sshd.server.channel.ChannelSession;
+import org.apache.sshd.server.channel.ServerChannelSessionHolder;
 import org.apache.sshd.server.command.AbstractFileSystemCommand;
 import org.apache.sshd.server.session.ServerSession;
 
@@ -42,7 +44,7 @@ import org.apache.sshd.server.session.ServerSession;
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class ScpCommand extends AbstractFileSystemCommand {
+public class ScpCommand extends AbstractFileSystemCommand implements ServerChannelSessionHolder {
     protected final int sendBufferSize;
     protected final int receiveBufferSize;
     protected final ScpFileOpener opener;
@@ -55,7 +57,10 @@ public class ScpCommand extends AbstractFileSystemCommand {
     protected IOException error;
     protected ScpTransferEventListener listener;
 
+    private final ChannelSession channelSession;
+
     /**
+     * @param channelSession  The {@link ChannelSession} through which the command was received
      * @param command         The command to be executed
      * @param executorService An {@link CloseableExecutorService} to be used when
      *                        {@code start(ChannelSession, Environment)}-ing execution. If {@code null} an ad-hoc
@@ -68,12 +73,14 @@ public class ScpCommand extends AbstractFileSystemCommand {
      * @see                   ScpHelper#MIN_SEND_BUFFER_SIZE
      * @see                   ScpHelper#MIN_RECEIVE_BUFFER_SIZE
      */
-    public ScpCommand(String command,
+    public ScpCommand(ChannelSession channelSession, String command,
                       CloseableExecutorService executorService,
                       int sendSize, int receiveSize,
                       ScpFileOpener fileOpener, ScpTransferEventListener eventListener) {
         super(command, executorService);
 
+        this.channelSession = Objects.requireNonNull(channelSession, "No channel session provided");
+
         if (sendSize < ScpHelper.MIN_SEND_BUFFER_SIZE) {
             throw new IllegalArgumentException(
                     "<ScpCommmand>(" + command + ") send buffer size "
@@ -148,6 +155,11 @@ public class ScpCommand extends AbstractFileSystemCommand {
     }
 
     @Override
+    public ChannelSession getServerChannelSession() {
+        return channelSession;
+    }
+
+    @Override
     public void start(ChannelSession channel, Environment env) throws IOException {
         if (error != null) {
             throw error;
diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpCommandFactory.java b/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpCommandFactory.java
index 6e9b348..569bf8a 100644
--- a/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpCommandFactory.java
+++ b/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpCommandFactory.java
@@ -211,7 +211,7 @@ public class ScpCommandFactory
     }
 
     @Override
-    public boolean isSupportedCommand(String command) {
+    public boolean isSupportedCommand(ChannelSession channel, String command) {
         if (GenericUtils.isEmpty(command)) {
             return false;
         }
@@ -220,9 +220,9 @@ public class ScpCommandFactory
     }
 
     @Override
-    protected Command executeSupportedCommand(String command) {
+    protected Command executeSupportedCommand(ChannelSession channel, String command) {
         return new ScpCommand(
-                command,
+                channel, command,
                 resolveExecutorService(command),
                 getSendBufferSize(), getReceiveBufferSize(),
                 getScpFileOpener(), listenerProxy);
diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpShell.java b/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpShell.java
index dedde72..ca413be 100644
--- a/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpShell.java
+++ b/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpShell.java
@@ -20,6 +20,7 @@ package org.apache.sshd.scp.server;
 
 import java.io.IOError;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.InterruptedIOException;
 import java.io.OutputStream;
 import java.io.Reader;
@@ -64,6 +65,7 @@ import org.apache.sshd.scp.common.helpers.DefaultScpFileOpener;
 import org.apache.sshd.scp.common.helpers.ScpAckInfo;
 import org.apache.sshd.server.Environment;
 import org.apache.sshd.server.channel.ChannelSession;
+import org.apache.sshd.server.channel.ServerChannelSessionHolder;
 import org.apache.sshd.server.command.AbstractFileSystemCommand;
 
 /**
@@ -71,7 +73,7 @@ import org.apache.sshd.server.command.AbstractFileSystemCommand;
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class ScpShell extends AbstractFileSystemCommand {
+public class ScpShell extends AbstractFileSystemCommand implements ServerChannelSessionHolder {
 
     public static final String STATUS = "status";
 
@@ -86,7 +88,6 @@ public class ScpShell extends AbstractFileSystemCommand {
      */
     public static final String ENV_LANG = "LANG";
 
-    protected final ChannelSession channel;
     protected final Map<String, Object> variables = new HashMap<>();
     protected final Charset nameEncodingCharset;
 
@@ -97,13 +98,15 @@ public class ScpShell extends AbstractFileSystemCommand {
     protected Path currentDir;
     protected Path homeDir;
 
-    public ScpShell(ChannelSession channel, CloseableExecutorService executorService,
+    private final ChannelSession channelSession;
+
+    public ScpShell(ChannelSession channelSession, CloseableExecutorService executorService,
                     int sendSize, int receiveSize,
                     ScpFileOpener fileOpener, ScpTransferEventListener eventListener) {
         super(null, executorService);
-        this.channel = channel;
+        this.channelSession = Objects.requireNonNull(channelSession, "No channel session provided");
 
-        nameEncodingCharset = ScpModuleProperties.NAME_ENCODING_CHARSET.getRequired(channel);
+        nameEncodingCharset = ScpModuleProperties.NAME_ENCODING_CHARSET.getRequired(channelSession);
 
         if (sendSize < ScpHelper.MIN_SEND_BUFFER_SIZE) {
             throw new IllegalArgumentException(
@@ -126,6 +129,11 @@ public class ScpShell extends AbstractFileSystemCommand {
     }
 
     @Override
+    public ChannelSession getServerChannelSession() {
+        return channelSession;
+    }
+
+    @Override
     public void setFileSystemFactory(FileSystemFactory factory, SessionContext session) throws IOException {
         homeDir = factory.getUserHomeDir(session);
         super.setFileSystemFactory(factory, session);
@@ -136,7 +144,7 @@ public class ScpShell extends AbstractFileSystemCommand {
             String s = x.toString();
             if (log.isDebugEnabled()) {
                 log.debug("println({})[{}]: {}",
-                        channel, cmd, s.replace('\n', ' ').replace('\t', ' '));
+                        getServerChannelSession(), cmd, s.replace('\n', ' ').replace('\t', ' '));
             }
             out.write(s.getBytes(cs));
             // always write LF even if running on Windows
@@ -151,7 +159,7 @@ public class ScpShell extends AbstractFileSystemCommand {
     }
 
     protected void signalError(String cmd, String errorMsg, Charset cs) {
-        log.warn("{}[{}]: {}", channel, cmd, errorMsg);
+        log.warn("{}[{}]: {}", getServerChannelSession(), cmd, errorMsg);
         println(cmd, errorMsg, getErrorStream(), cs);
         variables.put(STATUS, 1);
     }
@@ -162,6 +170,7 @@ public class ScpShell extends AbstractFileSystemCommand {
         variables.put(STATUS, 0);
 
         boolean debugEnabled = log.isDebugEnabled();
+        ChannelSession channel = getServerChannelSession();
         try {
             // TODO find some better alternative
             if (homeDir == null) {
@@ -177,7 +186,8 @@ public class ScpShell extends AbstractFileSystemCommand {
             prepareEnvironment(getEnvironment());
 
             // Use a special stream reader so that the stream can be used with the scp command
-            try (Reader r = new InputStreamReader(getInputStream(), StandardCharsets.UTF_8)) {
+            try (InputStream inputStream = getInputStream();
+                 Reader r = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
                 for (int executedCommands = 0;; executedCommands++) {
                     command = readLine(r);
                     if (GenericUtils.isEmpty(command)) {
@@ -239,7 +249,7 @@ public class ScpShell extends AbstractFileSystemCommand {
 
     protected boolean handleCommandLine(String command) throws Exception {
         if (log.isDebugEnabled()) {
-            log.debug("handleCommandLine({}) {}", channel, command);
+            log.debug("handleCommandLine({}) {}", getServerChannelSession(), command);
         }
 
         List<String[]> cmds = parse(command);
@@ -298,7 +308,7 @@ public class ScpShell extends AbstractFileSystemCommand {
     }
 
     protected void handleUnsupportedCommand(String command, String[] argv) throws Exception {
-        log.warn("handleUnsupportedCommand({}) unsupported: {}", channel, command);
+        log.warn("handleUnsupportedCommand({}) unsupported: {}", getServerChannelSession(), command);
         variables.put(STATUS, 127);
         getErrorStream().write(("command not found: " + argv[0] + "\n").getBytes(StandardCharsets.US_ASCII));
     }
@@ -373,7 +383,7 @@ public class ScpShell extends AbstractFileSystemCommand {
         }
 
         if (log.isDebugEnabled()) {
-            log.debug("printenv({}) {}={}", channel, varName, varValue);
+            log.debug("printenv({}) {}={}", getServerChannelSession(), varName, varValue);
         }
 
         println(argv[0], varValue, stdout, StandardCharsets.US_ASCII);
@@ -395,7 +405,7 @@ public class ScpShell extends AbstractFileSystemCommand {
         String varName = argv[1];
         String varValue = envValues.remove(varName);
         if (log.isDebugEnabled()) {
-            log.debug("unset({}) {}={}", channel, varName, varValue);
+            log.debug("unset({}) {}={}", getServerChannelSession(), varName, varValue);
         }
         variables.put(STATUS, (varValue == null) ? 1 : 0);
     }
@@ -464,6 +474,7 @@ public class ScpShell extends AbstractFileSystemCommand {
             String path, boolean optR, boolean optT, boolean optF, boolean optD, boolean optP)
             throws Exception {
         try {
+            ChannelSession channel = getServerChannelSession();
             ScpHelper helper = new ScpHelper(
                     channel.getSession(), getInputStream(), getOutputStream(),
                     fileSystem, opener, listener);
@@ -637,6 +648,7 @@ public class ScpShell extends AbstractFileSystemCommand {
             }
         }
 
+        // TODO see what optDirAsPlain means
         doLs(argv[0], path, optListAll, optLong, optFullTime);
     }
 
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 214fe0a..6f6c095 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
@@ -722,12 +722,12 @@ public class ScpTest extends AbstractScpTestSupport {
 
     @Test // see SSHD-628
     public void testScpExitStatusPropagation() throws Exception {
-        final int testExitValue = 7365;
+        int testExitValue = 7365;
         class InternalScpCommand extends ScpCommand {
 
-            InternalScpCommand(String command, CloseableExecutorService executorService,
+            InternalScpCommand(ChannelSession channel, String command, CloseableExecutorService executorService,
                                int sendSize, int receiveSize, ScpFileOpener opener, ScpTransferEventListener eventListener) {
-                super(command, executorService, sendSize, receiveSize, opener, eventListener);
+                super(channel, command, executorService, sendSize, receiveSize, opener, eventListener);
             }
 
             @Override
@@ -750,7 +750,7 @@ public class ScpTest extends AbstractScpTestSupport {
                 ValidateUtils.checkTrue(
                         command.startsWith(ScpHelper.SCP_COMMAND_PREFIX), "Bad SCP command: %s", command);
                 return new InternalScpCommand(
-                        command,
+                        channel, command,
                         resolveExecutorService(command),
                         getSendBufferSize(), getReceiveBufferSize(),
                         DefaultScpFileOpener.INSTANCE,
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java
index 4a5288b..0d539fb 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java
@@ -86,6 +86,7 @@ import org.apache.sshd.common.util.buffer.BufferUtils;
 import org.apache.sshd.common.util.io.FileInfoExtractor;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+import org.apache.sshd.server.channel.ChannelSession;
 import org.apache.sshd.server.session.ServerSession;
 import org.apache.sshd.sftp.SftpModuleProperties;
 import org.apache.sshd.sftp.common.SftpConstants;
@@ -141,13 +142,15 @@ public abstract class AbstractSftpSubsystemHelper
                             SftpConstants.SSH_ACL_CAP_AUDIT,
                             SftpConstants.SSH_ACL_CAP_ALARM)));
 
+    private final ChannelSession channelSession;
     private final UnsupportedAttributePolicy unsupportedAttributePolicy;
     private final Collection<SftpEventListener> sftpEventListeners = new CopyOnWriteArraySet<>();
     private final SftpEventListener sftpEventListenerProxy;
     private final SftpFileSystemAccessor fileSystemAccessor;
     private final SftpErrorStatusDataHandler errorStatusDataHandler;
 
-    protected AbstractSftpSubsystemHelper(SftpSubsystemConfigurator configurator) {
+    protected AbstractSftpSubsystemHelper(ChannelSession channelSession, SftpSubsystemConfigurator configurator) {
+        this.channelSession = Objects.requireNonNull(channelSession, "No channel session provided");
         unsupportedAttributePolicy = Objects.requireNonNull(configurator.getUnsupportedAttributePolicy(),
                 "No unsupported attribute policy provided");
         fileSystemAccessor = Objects.requireNonNull(configurator.getFileSystemAccessor(), "No file system accessor");
@@ -157,6 +160,11 @@ public abstract class AbstractSftpSubsystemHelper
     }
 
     @Override
+    public ChannelSession getServerChannelSession() {
+        return channelSession;
+    }
+
+    @Override
     public UnsupportedAttributePolicy getUnsupportedAttributePolicy() {
         return unsupportedAttributePolicy;
     }
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystem.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystem.java
index 03c8936..923ff94 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystem.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystem.java
@@ -64,12 +64,11 @@ import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.common.util.threads.CloseableExecutorService;
 import org.apache.sshd.common.util.threads.ExecutorServiceCarrier;
 import org.apache.sshd.common.util.threads.ThreadUtils;
-import org.apache.sshd.server.ChannelSessionAware;
 import org.apache.sshd.server.Environment;
 import org.apache.sshd.server.ExitCallback;
-import org.apache.sshd.server.SessionAware;
 import org.apache.sshd.server.channel.ChannelDataReceiver;
 import org.apache.sshd.server.channel.ChannelSession;
+import org.apache.sshd.server.channel.ChannelSessionAware;
 import org.apache.sshd.server.command.AsyncCommand;
 import org.apache.sshd.server.command.AsyncCommandErrorStreamAware;
 import org.apache.sshd.server.command.Command;
@@ -87,8 +86,8 @@ import org.apache.sshd.sftp.common.SftpHelper;
  */
 public class SftpSubsystem
         extends AbstractSftpSubsystemHelper
-        implements Command, Runnable, SessionAware, FileSystemAware, ExecutorServiceCarrier,
-        AsyncCommand, ChannelSessionAware, ChannelDataReceiver {
+        implements Command, Runnable, FileSystemAware, ExecutorServiceCarrier,
+        AsyncCommand, ChannelDataReceiver {
 
     protected static final Buffer CLOSE = new ByteArrayBuffer(null, 0, 0);
 
@@ -98,7 +97,6 @@ public class SftpSubsystem
     protected final Map<String, Handle> handles = new ConcurrentHashMap<>();
     protected final Buffer buffer = new ByteArrayBuffer(1024);
     protected final BlockingQueue<Buffer> requests = new LinkedBlockingQueue<>();
-    protected final ChannelDataReceiver errorDataChannelReceiver;
 
     protected ExitCallback callback;
     protected IoOutputStream out;
@@ -113,39 +111,14 @@ public class SftpSubsystem
     protected int version;
 
     protected ServerSession serverSession;
-    protected ChannelSession channelSession;
     protected CloseableExecutorService executorService;
 
     /**
+     * @param channel      The {@link ChannelSession} through which the command was received
      * @param configurator The {@link SftpSubsystemConfigurator} to use
      */
-    public SftpSubsystem(SftpSubsystemConfigurator configurator) {
-        super(configurator);
-
-        ChannelDataReceiver receiver = configurator.getErrorChannelDataReceiver();
-        if (receiver == null) {
-            errorDataChannelReceiver = new ChannelDataReceiver() {
-                @Override
-                @SuppressWarnings("synthetic-access")
-                public void close() throws IOException {
-                    if (log.isDebugEnabled()) {
-                        log.debug("stderrData({}) closing", getSession());
-                    }
-
-                }
-
-                @Override
-                @SuppressWarnings("synthetic-access")
-                public int data(ChannelSession channel, byte[] buf, int start, int len) throws IOException {
-                    if (log.isDebugEnabled()) {
-                        log.debug("stderrData({}) received {} data bytes", channel, len);
-                    }
-                    return len;
-                }
-            };
-        } else {
-            errorDataChannelReceiver = receiver;
-        }
+    public SftpSubsystem(ChannelSession channel, SftpSubsystemConfigurator configurator) {
+        super(channel, configurator);
 
         CloseableExecutorService executorService = configurator.getExecutorService();
         if (executorService == null) {
@@ -153,6 +126,40 @@ public class SftpSubsystem
         } else {
             this.executorService = executorService;
         }
+
+        initializeSessionRelatedMember(channel);
+
+        ChannelDataReceiver errorDataChannelReceiver
+                = resolveErrorDataChannelReceiver(channel, configurator.getErrorChannelDataReceiver());
+        channel.setDataReceiver(this);
+        channel.setExtendedDataWriter(errorDataChannelReceiver);
+
+        SftpErrorStatusDataHandler errHandler = getErrorStatusDataHandler();
+        if (errHandler instanceof ChannelSessionAware) {
+            ((ChannelSessionAware) errHandler).setChannelSession(channel);
+        }
+    }
+
+    protected ChannelDataReceiver resolveErrorDataChannelReceiver(ChannelSession channelSession, ChannelDataReceiver receiver) {
+        return (receiver != null) ? receiver : new ChannelDataReceiver() {
+            @Override
+            @SuppressWarnings("synthetic-access")
+            public void close() throws IOException {
+                if (log.isDebugEnabled()) {
+                    log.debug("stderrData({}) closing", getSession());
+                }
+
+            }
+
+            @Override
+            @SuppressWarnings("synthetic-access")
+            public int data(ChannelSession channel, byte[] buf, int start, int len) throws IOException {
+                if (log.isDebugEnabled()) {
+                    log.debug("stderrData({}) received {} data bytes", channel, len);
+                }
+                return len;
+            }
+        };
     }
 
     @Override
@@ -170,16 +177,15 @@ public class SftpSubsystem
         return executorService;
     }
 
-    @Override
-    public void setSession(ServerSession session) {
-        this.serverSession = Objects.requireNonNull(session, "No session");
+    protected void initializeSessionRelatedMember(ChannelSession channel) {
+        serverSession = Objects.requireNonNull(channel.getServerSession(), "No session associated with the channel");
 
-        FactoryManager manager = session.getFactoryManager();
+        FactoryManager manager = serverSession.getFactoryManager();
         Factory<? extends Random> factory = manager.getRandomFactory();
         this.randomizer = factory.create();
 
-        this.fileHandleSize = SftpModuleProperties.FILE_HANDLE_SIZE.getRequired(session);
-        this.maxFileHandleRounds = SftpModuleProperties.MAX_FILE_HANDLE_RAND_ROUNDS.getRequired(session);
+        this.fileHandleSize = SftpModuleProperties.FILE_HANDLE_SIZE.getRequired(channel);
+        this.maxFileHandleRounds = SftpModuleProperties.MAX_FILE_HANDLE_RAND_ROUNDS.getRequired(channel);
 
         if (workBuf.length < this.fileHandleSize) {
             workBuf = new byte[this.fileHandleSize];
@@ -192,18 +198,6 @@ public class SftpSubsystem
     }
 
     @Override
-    public void setChannelSession(ChannelSession session) {
-        this.channelSession = session;
-        session.setDataReceiver(this);
-        session.setExtendedDataWriter(errorDataChannelReceiver);
-
-        SftpErrorStatusDataHandler errHandler = getErrorStatusDataHandler();
-        if (errHandler instanceof ChannelSessionAware) {
-            ((ChannelSessionAware) errHandler).setChannelSession(session);
-        }
-    }
-
-    @Override
     public void setFileSystem(FileSystem fileSystem) {
         // reference check on purpose
         if (fileSystem != this.fileSystem) {
@@ -242,8 +236,9 @@ public class SftpSubsystem
 
     @Override
     public void setIoOutputStream(IoOutputStream out) {
-        int channelId = channelSession.getId();
-        this.out = new BufferedIoOutputStream("sftp-out@" + channelId, channelId, out, channelSession);
+        ChannelSession channel = getServerChannelSession();
+        int channelId = channel.getId();
+        this.out = new BufferedIoOutputStream("sftp-out@" + channelId, channelId, out, channel);
     }
 
     @Override
@@ -290,22 +285,25 @@ public class SftpSubsystem
     @Override
     public void run() {
         int exitCode = 0;
+        long buffersCount = 0L;
         try {
+            ChannelSession channel = getServerChannelSession();
+            Window localWindow = channel.getLocalWindow();
             while (true) {
                 Buffer buffer = requests.take();
                 if (buffer == CLOSE) {
                     break;
                 }
                 int len = buffer.available();
+                buffersCount++;
                 process(buffer);
-                Window localWindow = channelSession.getLocalWindow();
                 localWindow.consumeAndCheck(len);
             }
         } catch (Throwable t) {
             if (!closed.get()) { // Ignore
                 Session session = getServerSession();
-                error("run({}) {} caught in SFTP subsystem: {}",
-                        session, t.getClass().getSimpleName(), t.getMessage(), t);
+                error("run({}) {} caught in SFTP subsystem after {} buffers: {}",
+                        session, t.getClass().getSimpleName(), buffersCount, t.getMessage(), t);
                 exitCode = -1;
             }
         } finally {
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystemEnvironment.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystemEnvironment.java
index 037f1e0..5a15821 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystemEnvironment.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystemEnvironment.java
@@ -27,6 +27,7 @@ import java.util.stream.IntStream;
 
 import org.apache.sshd.common.session.SessionHolder;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.server.channel.ServerChannelSessionHolder;
 import org.apache.sshd.server.session.ServerSession;
 import org.apache.sshd.server.session.ServerSessionHolder;
 import org.apache.sshd.sftp.common.SftpConstants;
@@ -35,7 +36,7 @@ import org.apache.sshd.sftp.common.SftpConstants;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public interface SftpSubsystemEnvironment
-        extends SessionHolder<ServerSession>, ServerSessionHolder,
+        extends SessionHolder<ServerSession>, ServerSessionHolder, ServerChannelSessionHolder,
         SftpFileSystemAccessorProvider, SftpUnsupportedAttributePolicyProvider {
 
     int LOWER_SFTP_IMPL = SftpConstants.SFTP_V3; // Working implementation from v3
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystemFactory.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystemFactory.java
index 444d7ca..a5ab56e 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystemFactory.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystemFactory.java
@@ -168,7 +168,7 @@ public class SftpSubsystemFactory
 
     @Override
     public Command createSubsystem(ChannelSession channel) throws IOException {
-        SftpSubsystem subsystem = new SftpSubsystem(this);
+        SftpSubsystem subsystem = new SftpSubsystem(channel, this);
         GenericUtils.forEach(getRegisteredListeners(), subsystem::addSftpEventListener);
         return subsystem;
     }
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpVersionsTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpVersionsTest.java
index de54873..e71510e 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpVersionsTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpVersionsTest.java
@@ -227,7 +227,7 @@ public class SftpVersionsTest extends AbstractSftpClientTestSupport {
         SftpSubsystemFactory factory = new SftpSubsystemFactory() {
             @Override
             public Command createSubsystem(ChannelSession channel) throws IOException {
-                SftpSubsystem subsystem = new SftpSubsystem(this) {
+                SftpSubsystem subsystem = new SftpSubsystem(channel, this) {
                     @Override
                     protected NavigableMap<String, Object> resolveFileAttributes(Path file, int flags, LinkOption... options)
                             throws IOException {
@@ -346,7 +346,7 @@ public class SftpVersionsTest extends AbstractSftpClientTestSupport {
         SftpSubsystemFactory factory = new SftpSubsystemFactory() {
             @Override
             public Command createSubsystem(ChannelSession channel) throws IOException {
-                SftpSubsystem subsystem = new SftpSubsystem(this) {
+                SftpSubsystem subsystem = new SftpSubsystem(channel, this) {
                     @Override
                     protected NavigableMap<String, Object> resolveFileAttributes(Path file, int flags, LinkOption... options)
                             throws IOException {
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 bca6be7..7eaa8f4 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
@@ -71,7 +71,7 @@ public class SpaceAvailableExtensionImplTest extends AbstractSftpClientTestSuppo
         sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory() {
             @Override
             public Command createSubsystem(ChannelSession channel) throws IOException {
-                return new SftpSubsystem(this) {
+                return new SftpSubsystem(channel, this) {
                     @Override
                     protected SpaceAvailableExtensionInfo doSpaceAvailable(int id, String path) throws IOException {
                         if (!queryPath.equals(path)) {
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 4525534..4a81808 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
@@ -120,7 +120,7 @@ public class OpenSSHExtensionsTest extends AbstractSftpClientTestSupport {
         sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory() {
             @Override
             public Command createSubsystem(ChannelSession channel) throws IOException {
-                return new SftpSubsystem(this) {
+                return new SftpSubsystem(channel, this) {
                     @Override
                     protected List<OpenSSHExtension> resolveOpenSSHExtensions(ServerSession session) {
                         List<OpenSSHExtension> original = super.resolveOpenSSHExtensions(session);


[mina-sshd] 05/07: Moved some helper methods and classes to more natural locations

Posted by lg...@apache.org.
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

commit 73883a5c266ee6029ca49e19d18c08516b61aa38
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri Feb 26 13:04:49 2021 +0200

    Moved some helper methods and classes to more natural locations
---
 CHANGES.md                                         |   1 +
 .../apache/sshd/cli/client/SftpCommandMain.java    |   6 +-
 .../sshd/cli/client/SshClientCliSupport.java       |  11 +-
 .../org/apache/sshd/cli/client/SshKeyScanMain.java |  14 +-
 .../sshd/client/config/hosts/HostConfigEntry.java  |  15 +-
 .../apache/sshd/common/AttributeRepository.java    |   6 +-
 .../org/apache/sshd/common/PropertyResolver.java   |  14 ++
 .../apache/sshd/common/PropertyResolverUtils.java  |   7 +-
 .../org/apache/sshd/common/channel/PtyMode.java    |  11 +-
 .../apache/sshd/common/config/TimeValueConfig.java |   3 +-
 .../common/config/keys/AuthorizedKeyEntry.java     |   5 +-
 .../sshd/common/config/keys/IdentityUtils.java     |   7 +-
 .../apache/sshd/common/config/keys/KeyUtils.java   |   4 +-
 .../keys/impl/AbstractPublicKeyEntryDecoder.java   |   4 +-
 .../keys/impl/SkECDSAPublicKeyEntryDecoder.java    |   2 +-
 .../keys/impl/SkED25519PublicKeyEntryDecoder.java  |   2 +-
 .../keys/loader/PrivateKeyEncryptionContext.java   |   3 +-
 .../keys/loader/openssh/kdf/BCryptKdfOptions.java  |   6 +-
 .../pem/AbstractPEMResourceKeyPairParser.java      |   8 +-
 .../keys}/u2f/SecurityKeyPublicKey.java            |   2 +-
 .../{ => config/keys}/u2f/SkED25519PublicKey.java  |   2 +-
 .../{ => config/keys}/u2f/SkEcdsaPublicKey.java    |   2 +-
 .../sshd/common/future/AbstractSshFuture.java      |   6 +-
 .../sshd/common/kex/extension/KexExtensions.java   |   3 +-
 .../common/keyprovider/KeyIdentityProvider.java    |   5 +-
 .../signature/AbstractSecurityKeySignature.java    |   2 +-
 .../sshd/common/signature/SignatureFactory.java    |   3 +-
 .../sshd/common/util/EventListenerUtils.java       |   4 +-
 .../apache/sshd/common/util/ExceptionUtils.java    | 162 +++++++++++++
 .../org/apache/sshd/common/util/GenericUtils.java  | 251 +--------------------
 .../org/apache/sshd/common/util/MapEntryUtils.java | 113 ++++++++++
 .../org/apache/sshd/common/util/ValidateUtils.java |   2 +-
 .../buffer/keys/SkECBufferPublicKeyParser.java     |   2 +-
 .../keys/SkED25519BufferPublicKeyParser.java       |   2 +-
 .../AutoCloseableDelegateInvocationHandler.java    |   3 +-
 .../util/{ => functors}/Int2IntFunction.java       |   4 +-
 .../sshd/common/util/functors/UnaryEquator.java    |  12 +-
 .../org/apache/sshd/common/util/io/IoUtils.java    |   3 +-
 .../org/apache/sshd/common/util/io/PathUtils.java  |   2 +-
 .../common/util/{ => io/functors}/Invoker.java     |   7 +-
 .../sshd/common/util/net/SshdSocketAddress.java    |   3 +-
 .../util/security/SecurityEntityFactory.java       |   8 +-
 .../sshd/common/util/security/SecurityUtils.java   |   5 +-
 .../BouncyCastleSecurityProviderRegistrar.java     |   3 +-
 .../eddsa/EdDSASecurityProviderRegistrar.java      |   4 +-
 .../main/java/org/apache/sshd/server/Signal.java   |   9 +-
 .../client/config/hosts/HostConfigEntryTest.java   |   5 +-
 .../client/config/keys/ClientIdentityTest.java     |   3 +-
 .../apache/sshd/common/VersionPropertiesTest.java  |   3 +-
 .../apache/sshd/common/channel/PtyModeTest.java    |   4 +-
 .../common/config/keys/BuiltinIdentitiesTest.java  |   3 +-
 .../common/keyprovider/KeyPairProviderTest.java    |   3 +-
 .../apache/sshd/common/util/GenericUtilsTest.java  |   8 +-
 .../sshd/common/util/Int2IntFunctionTest.java      |   1 +
 .../apache/sshd/util/test/JUnitTestSupport.java    |   5 +-
 .../InteractivePasswordIdentityProvider.java       |   5 +-
 .../common/compression/DeflatingInputStream.java   |  10 +-
 .../sshd/agent/local/ChannelAgentForwarding.java   |   4 +-
 .../sshd/agent/unix/ChannelAgentForwarding.java    |   3 +-
 .../java/org/apache/sshd/client/SshClient.java     |  12 +-
 .../sshd/client/channel/AbstractClientChannel.java |   4 +-
 .../client/channel/PtyCapableChannelSession.java   |   9 +-
 .../client/config/SshClientConfigFileReader.java   |   3 +-
 .../sshd/client/session/AbstractClientSession.java |   3 +-
 .../simple/AbstractSimpleClientSessionCreator.java |   6 +-
 .../sshd/common/channel/AbstractChannel.java       |  15 +-
 .../common/channel/BufferedIoOutputStream.java     |   4 +-
 .../sshd/common/forward/DefaultForwarder.java      |  23 +-
 .../apache/sshd/common/io/nio2/Nio2Connector.java  |   6 +-
 .../apache/sshd/common/io/nio2/Nio2Session.java    |   4 +-
 .../DefaultClientKexExtensionHandler.java          |  10 +-
 .../session/helpers/AbstractConnectionService.java |   2 +-
 .../common/session/helpers/AbstractSession.java    |   5 +-
 .../sshd/common/session/helpers/SessionHelper.java |  34 +--
 ...AuthorizedKeyEntriesPublickeyAuthenticator.java |   3 +-
 .../sshd/server/channel/AbstractServerChannel.java |   4 +-
 .../sshd/server/channel/PuttyRequestHandler.java   |   3 +-
 .../server/config/SshServerConfigFileReader.java   |   5 +-
 .../sshd/server/config/keys/ServerIdentity.java    |   3 +-
 .../sshd/server/forward/TcpipServerChannel.java    |   8 +-
 .../server/global/CancelTcpipForwardHandler.java   |   2 +-
 .../sshd/server/global/TcpipForwardHandler.java    |   2 +-
 .../sshd/server/session/AbstractServerSession.java |   3 +-
 .../sshd/server/shell/InvertedShellWrapper.java    |   5 +-
 .../org/apache/sshd/server/shell/ProcessShell.java |   3 +-
 .../KnownHostsServerKeyVerifierTest.java           |   3 +-
 .../sshd/common/PropertyResolverUtilsTest.java     |   4 +-
 .../KeyboardInteractiveAuthenticationTest.java     |   3 +-
 .../java/org/apache/sshd/server/ServerTest.java    |   3 +-
 .../server/config/keys/ServerIdentityTest.java     |   3 +-
 .../sshd/git/transport/GitSshdSessionFactory.java  |   6 +-
 .../sshd/ldap/LdapPasswordAuthenticatorTest.java   |   4 +-
 .../sshd/ldap/LdapPublickeyAuthenticatorTest.java  |   4 +-
 .../java/org/apache/sshd/mina/MinaService.java     |   4 +-
 .../java/org/apache/sshd/mina/MinaSession.java     |   4 +-
 .../java/org/apache/sshd/netty/NettyIoSession.java |   3 +-
 .../sshd/openpgp/PGPAuthorizedEntriesTracker.java  |   3 +-
 .../openpgp/PGPAuthorizedKeyEntriesLoader.java     |   3 +-
 .../apache/sshd/openpgp/PGPPublicRingWatcher.java  |  10 +-
 .../sshd/openpgp/PGPPublicRingWatcherTest.java     |   8 +-
 .../org/apache/sshd/putty/PuttyKeyUtilsTest.java   |   3 +-
 .../sshd/scp/client/SimpleScpClientImpl.java       |   4 +-
 .../org/apache/sshd/sftp/client/SftpClient.java    |   5 +-
 .../helpers/AbstractSftpClientExtension.java       |   3 +-
 .../sftp/client/fs/SftpFileSystemProvider.java     |  11 +-
 .../sshd/sftp/client/impl/DefaultSftpClient.java   |   3 +-
 .../sftp/client/impl/SimpleSftpClientImpl.java     |  10 +-
 .../org/apache/sshd/sftp/common/SftpHelper.java    |   9 +-
 .../sftp/common/extensions/AclSupportedParser.java |   5 +-
 .../sshd/sftp/common/extensions/ParserUtils.java   |   7 +-
 .../server/AbstractSftpEventListenerAdapter.java   |   4 +-
 .../sftp/server/AbstractSftpSubsystemHelper.java   |  17 +-
 .../org/apache/sshd/sftp/server/FileHandle.java    |   3 +-
 .../java/org/apache/sshd/sftp/client/SftpTest.java |   3 +-
 .../apache/sshd/sftp/client/SftpVersionsTest.java  |  13 +-
 .../sshd/sftp/client/fs/SftpFileSystemTest.java    |   3 +-
 .../integration/ApacheSshdSftpSessionFactory.java  |   6 +-
 .../sftp/spring/integration/SpringSftpSession.java |   7 +-
 118 files changed, 649 insertions(+), 523 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 131c6d0..a909dfd 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -15,6 +15,7 @@
 ## Major code re-factoring
 
 * [SSHD-1133](https://issues.apache.org/jira/browse/SSHD-1133) Re-factored locations and names of `ServerSession` and server-side `ChannelSession` related classes
+* Moved some helper methods and classes to more natural locations
 
 ## Potential compatibility issues
 
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
index c827fde..79cbc57 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
@@ -58,7 +58,9 @@ import org.apache.sshd.common.kex.KeyExchange;
 import org.apache.sshd.common.mac.MacFactory;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.signature.SignatureFactory;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.common.util.ReflectionUtils;
 import org.apache.sshd.common.util.ValidateUtils;
@@ -287,7 +289,7 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
                         .append(" to instantiate ").append(factoryName)
                         .append(": ").println(t.getMessage());
                 System.err.flush();
-                throw GenericUtils.toRuntimeException(t, true);
+                throw ExceptionUtils.toRuntimeException(t, true);
             }
         }
 
@@ -540,7 +542,7 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
 
             Map<String, byte[]> extensions = sftp.getServerExtensions();
             Map<String, ?> parsed = ParserUtils.parse(extensions);
-            if (GenericUtils.size(extensions) > 0) {
+            if (MapEntryUtils.size(extensions) > 0) {
                 stdout.println();
             }
 
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java
index 87bc6d5..4af2024 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java
@@ -78,6 +78,7 @@ import org.apache.sshd.common.kex.extension.KexExtensionHandler;
 import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
 import org.apache.sshd.common.mac.Mac;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.common.util.ReflectionUtils;
 import org.apache.sshd.common.util.ValidateUtils;
@@ -311,7 +312,7 @@ public abstract class SshClientCliSupport extends CliSupport {
     }
 
     public static Map<String, ?> resolveClientEnvironment(Map<String, ?> options) {
-        if (GenericUtils.isEmpty(options)) {
+        if (MapEntryUtils.isEmpty(options)) {
             return Collections.emptyMap();
         }
 
@@ -350,7 +351,7 @@ public abstract class SshClientCliSupport extends CliSupport {
 
     public static PtyChannelConfiguration resolveClientPtyOptions(Map<String, ?> options)
             throws IOException, InterruptedException {
-        Object v = GenericUtils.isEmpty(options)
+        Object v = MapEntryUtils.isEmpty(options)
                 ? null
                 : options.get(SshClientConfigFileReader.REQUEST_TTY_OPTION);
         String s = Objects.toString(v, "auto");
@@ -367,7 +368,7 @@ public abstract class SshClientCliSupport extends CliSupport {
 
         // TODO add support for height/width, rows/columns and TERM(inal) type
         Map<PtyMode, Integer> ptyModes = resolveClientPtyModes(options);
-        if (GenericUtils.isNotEmpty(ptyModes)) {
+        if (MapEntryUtils.isNotEmpty(ptyModes)) {
             config.setPtyModes(ptyModes);
         }
 
@@ -376,7 +377,7 @@ public abstract class SshClientCliSupport extends CliSupport {
 
     public static Map<PtyMode, Integer> resolveClientPtyModes(Map<String, ?> options)
             throws IOException, InterruptedException {
-        Object v = GenericUtils.isEmpty(options) ? null : options.get(PtyMode.class.getSimpleName());
+        Object v = MapEntryUtils.isEmpty(options) ? null : options.get(PtyMode.class.getSimpleName());
         String s = Objects.toString(v, null);
         if (GenericUtils.isEmpty(s)) {
             return Collections.emptyMap();
@@ -473,7 +474,7 @@ public abstract class SshClientCliSupport extends CliSupport {
             setupSessionExtensions(client, resolver, stdin, stdout, stderr);
 
             Map<String, ?> options = resolver.getProperties();
-            if (GenericUtils.isNotEmpty(options)) {
+            if (MapEntryUtils.isNotEmpty(options)) {
                 Map<String, Object> props = client.getProperties();
                 props.putAll(options);
             }
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshKeyScanMain.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshKeyScanMain.java
index d9f7f45..0686938 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshKeyScanMain.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshKeyScanMain.java
@@ -77,7 +77,9 @@ import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.session.SessionListener;
 import org.apache.sshd.common.signature.BuiltinSignatures;
 import org.apache.sshd.common.signature.Signature;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.common.util.io.NoCloseInputStream;
@@ -199,8 +201,8 @@ public class SshKeyScanMain implements Channel, Callable<Void>, ServerKeyVerifie
             }
         }
 
-        ValidateUtils.checkTrue(!GenericUtils.isEmpty(pairsMap), "No client key pairs");
-        ValidateUtils.checkTrue(!GenericUtils.isEmpty(sigFactories), "No signature factories");
+        ValidateUtils.checkTrue(!MapEntryUtils.isEmpty(pairsMap), "No client key pairs");
+        ValidateUtils.checkTrue(!MapEntryUtils.isEmpty(sigFactories), "No signature factories");
 
         Exception err = null;
         try {
@@ -272,7 +274,7 @@ public class SshKeyScanMain implements Channel, Callable<Void>, ServerKeyVerifie
                             if (isEnabledLevel(Level.FINE)) {
                                 log(Level.FINE, "Failed to retrieve keys from " + h, e);
                             }
-                            err = GenericUtils.accumulateException(err, e);
+                            err = ExceptionUtils.accumulateException(err, e);
                         } finally {
                             currentHostFingerprints.clear();
                         }
@@ -283,7 +285,7 @@ public class SshKeyScanMain implements Channel, Callable<Void>, ServerKeyVerifie
             try {
                 close();
             } catch (IOException e) {
-                err = GenericUtils.accumulateException(err, e);
+                err = ExceptionUtils.accumulateException(err, e);
             }
         }
 
@@ -600,7 +602,7 @@ public class SshKeyScanMain implements Channel, Callable<Void>, ServerKeyVerifie
             try {
                 input.close();
             } catch (IOException e) {
-                err = GenericUtils.accumulateException(err, e);
+                err = ExceptionUtils.accumulateException(err, e);
             } finally {
                 input = null;
             }
@@ -610,7 +612,7 @@ public class SshKeyScanMain implements Channel, Callable<Void>, ServerKeyVerifie
             try {
                 client.close();
             } catch (IOException e) {
-                err = GenericUtils.accumulateException(err, e);
+                err = ExceptionUtils.accumulateException(err, e);
             } finally {
                 try {
                     client.stop();
diff --git a/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java b/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
index 3ae8828..60927de 100644
--- a/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
+++ b/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
@@ -51,6 +51,7 @@ import org.apache.sshd.common.config.ConfigFileReaderSupport;
 import org.apache.sshd.common.config.keys.IdentityUtils;
 import org.apache.sshd.common.config.keys.PublicKeyEntry;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.MapEntryUtils.NavigableMapBuilder;
 import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.common.util.ValidateUtils;
@@ -308,7 +309,7 @@ public class HostConfigEntry extends HostPatternsHolder implements MutableUserHo
     public String getProperty(String name, String defaultValue) {
         String key = ValidateUtils.checkNotNullAndNotEmpty(name, "No property name");
         Map<String, String> props = getProperties();
-        if (GenericUtils.isEmpty(props)) {
+        if (MapEntryUtils.isEmpty(props)) {
             return defaultValue;
         }
 
@@ -342,7 +343,7 @@ public class HostConfigEntry extends HostPatternsHolder implements MutableUserHo
         modified = updateGlobalIdentityOnly(globalEntry.isIdentitiesOnly()) || modified;
 
         Map<String, String> updated = updateGlobalProperties(globalEntry.getProperties());
-        modified = (GenericUtils.size(updated) > 0) || modified;
+        modified = (MapEntryUtils.size(updated) > 0) || modified;
 
         return modified;
     }
@@ -354,7 +355,7 @@ public class HostConfigEntry extends HostPatternsHolder implements MutableUserHo
      * @return       A {@link Map} of the <U>updated</U> properties
      */
     public Map<String, String> updateGlobalProperties(Map<String, String> props) {
-        if (GenericUtils.isEmpty(props)) {
+        if (MapEntryUtils.isEmpty(props)) {
             return Collections.emptyMap();
         }
 
@@ -545,7 +546,7 @@ public class HostConfigEntry extends HostPatternsHolder implements MutableUserHo
         }
 
         String key = ValidateUtils.checkNotNullAndNotEmpty(name, "No property name");
-        if (GenericUtils.isEmpty(properties)) {
+        if (MapEntryUtils.isEmpty(properties)) {
             properties = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
         }
 
@@ -559,7 +560,7 @@ public class HostConfigEntry extends HostPatternsHolder implements MutableUserHo
     public String removeProperty(String name) {
         String key = ValidateUtils.checkNotNullAndNotEmpty(name, "No property name");
         Map<String, String> props = getProperties();
-        if (GenericUtils.isEmpty(props)) {
+        if (MapEntryUtils.isEmpty(props)) {
             return null;
         } else {
             return props.remove(key);
@@ -618,7 +619,7 @@ public class HostConfigEntry extends HostPatternsHolder implements MutableUserHo
      * @see                #appendNonEmptyProperty(Appendable, String, Object)
      */
     public static <A extends Appendable> A appendNonEmptyProperties(A sb, Map<String, ?> props) throws IOException {
-        if (GenericUtils.isEmpty(props)) {
+        if (MapEntryUtils.isEmpty(props)) {
             return sb;
         }
 
@@ -741,7 +742,7 @@ public class HostConfigEntry extends HostPatternsHolder implements MutableUserHo
         normal.setProxyJump(entry.resolveProxyJump(proxyJump));
 
         Map<String, String> props = entry.getProperties();
-        if (GenericUtils.size(props) > 0) {
+        if (MapEntryUtils.size(props) > 0) {
             normal.setProperties(
                     NavigableMapBuilder.<String, String> builder(String.CASE_INSENSITIVE_ORDER)
                             .putAll(props)
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/AttributeRepository.java b/sshd-common/src/main/java/org/apache/sshd/common/AttributeRepository.java
index bd9e561..45613fa 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/AttributeRepository.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/AttributeRepository.java
@@ -25,7 +25,7 @@ import java.util.HashSet;
 import java.util.Map;
 import java.util.Objects;
 
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -111,12 +111,12 @@ public interface AttributeRepository {
             @SuppressWarnings("unchecked")
             public <T> T getAttribute(AttributeKey<T> key) {
                 Objects.requireNonNull(key, "No key provided");
-                return GenericUtils.isEmpty(attributes) ? null : (T) attributes.get(key);
+                return MapEntryUtils.isEmpty(attributes) ? null : (T) attributes.get(key);
             }
 
             @Override
             public Collection<AttributeKey<?>> attributeKeys() {
-                return GenericUtils.isEmpty(attributes)
+                return MapEntryUtils.isEmpty(attributes)
                         ? Collections.emptySet()
                         : new HashSet<>(attributes.keySet());
             }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/PropertyResolver.java b/sshd-common/src/main/java/org/apache/sshd/common/PropertyResolver.java
index 64ddffc..bf8982d 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/PropertyResolver.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/PropertyResolver.java
@@ -23,6 +23,8 @@ import java.nio.charset.Charset;
 import java.util.Collections;
 import java.util.Map;
 
+import org.apache.sshd.common.util.MapEntryUtils;
+
 /**
  * Indicates an entity that can be configured using properties. The properties are simple name-value pairs where the
  * actual value type depends on the property. Some automatic conversions may be available - e.g., from a string to a
@@ -49,6 +51,11 @@ public interface PropertyResolver {
         }
 
         @Override
+        public boolean isEmpty() {
+            return true;
+        }
+
+        @Override
         public String toString() {
             return "EMPTY";
         }
@@ -81,6 +88,10 @@ public interface PropertyResolver {
      */
     Map<String, Object> getProperties();
 
+    default boolean isEmpty() {
+        return MapEntryUtils.isEmpty(getProperties());
+    }
+
     default long getLongProperty(String name, long def) {
         return PropertyResolverUtils.getLongProperty(this, name, def);
     }
@@ -122,4 +133,7 @@ public interface PropertyResolver {
         return (value == null) ? defaultValue : PropertyResolverUtils.toCharset(value);
     }
 
+    static boolean isEmpty(PropertyResolver resolver) {
+        return (resolver == null) || resolver.isEmpty();
+    }
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/PropertyResolverUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/PropertyResolverUtils.java
index a42cd1c..0c19d8e 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/PropertyResolverUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/PropertyResolverUtils.java
@@ -30,6 +30,7 @@ import java.util.Properties;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 
 /**
@@ -128,7 +129,7 @@ public final class PropertyResolverUtils {
      * <LI>If the value is {@code null} then returns {@code null}.</LI>
      *
      * <LI>If the value is already a {@link Long} then it is returned as such.</LI>
-     * 
+     *
      * <LI>If value is a {@link Number} then its {@link Number#longValue()} is wrapped as a {@link Long}</LI>
      *
      * <LI>Otherwise, the value's {@code toString()} is parsed as a {@link Long}</LI>
@@ -317,7 +318,7 @@ public final class PropertyResolverUtils {
      * <LI>Otherwise, throws an {@link UnsupportedOperationException}</LI>
      * </P>
      * </UL>
-     * 
+     *
      * @param  value                         The value to be converted
      * @return                               The result - {@code null} if {@code null} or an empty string
      * @throws UnsupportedOperationException If value cannot be converted to a boolean - e.g., a number.
@@ -504,7 +505,7 @@ public final class PropertyResolverUtils {
     }
 
     public static PropertyResolver toPropertyResolver(Properties props) {
-        if (GenericUtils.isEmpty(props)) {
+        if (MapEntryUtils.isEmpty(props)) {
             return PropertyResolver.EMPTY;
         }
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/channel/PtyMode.java b/sshd-common/src/main/java/org/apache/sshd/common/channel/PtyMode.java
index b79c11d..0f0a1a2 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/channel/PtyMode.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/channel/PtyMode.java
@@ -30,6 +30,7 @@ import java.util.function.Function;
 import java.util.function.ToIntFunction;
 
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 
 /**
  * A enum describing the tty modes according to <a href="https://tools.ietf.org/html/rfc4254#section-8">RFC 4254 -
@@ -294,7 +295,7 @@ public enum PtyMode {
     public static final Set<PtyMode> MODES = Collections.unmodifiableSet(EnumSet.allOf(PtyMode.class));
 
     public static final NavigableMap<Integer, PtyMode> COMMANDS = Collections.unmodifiableNavigableMap(
-            GenericUtils.toSortedMap(MODES, PtyMode::toInt, Function.identity(), Comparator.naturalOrder()));
+            MapEntryUtils.toSortedMap(MODES, PtyMode::toInt, Function.identity(), Comparator.naturalOrder()));
 
     /**
      * A {@code null}-safe {@link ToIntFunction} that returns the {@link PtyMode#toInt()} value and (-1) for
@@ -304,7 +305,7 @@ public enum PtyMode {
 
     /**
      * A {@code null}-safe {@link Comparator} of {@link PtyMode} values according to their {@link PtyMode#toInt()} value
-     * 
+     *
      * @see #OPCODE_EXTRACTOR
      */
     public static final Comparator<PtyMode> BY_OPCODE = new Comparator<PtyMode>() {
@@ -385,7 +386,7 @@ public enum PtyMode {
      * @see            #getBooleanSettingValue(Map, PtyMode)
      */
     public static Set<PtyMode> resolveEnabledOptions(Map<PtyMode, ?> modes, Collection<PtyMode> options) {
-        if (GenericUtils.isEmpty(modes) || GenericUtils.isEmpty(options)) {
+        if (MapEntryUtils.isEmpty(modes) || GenericUtils.isEmpty(options)) {
             return Collections.emptySet();
         }
 
@@ -413,7 +414,7 @@ public enum PtyMode {
      * @see          #getBooleanSettingValue(Object)
      */
     public static boolean getBooleanSettingValue(Map<PtyMode, ?> modes, PtyMode m) {
-        if ((m == null) || GenericUtils.isEmpty(modes)) {
+        if ((m == null) || MapEntryUtils.isEmpty(modes)) {
             return false;
         } else {
             return getBooleanSettingValue(modes.get(m));
@@ -441,7 +442,7 @@ public enum PtyMode {
      */
     public static boolean getBooleanSettingValue(
             Map<PtyMode, ?> modes, Collection<PtyMode> enablers, boolean defaultValue) {
-        if (GenericUtils.isEmpty(modes) || GenericUtils.isEmpty(enablers)) {
+        if (MapEntryUtils.isEmpty(modes) || GenericUtils.isEmpty(enablers)) {
             return defaultValue;
         }
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/TimeValueConfig.java b/sshd-common/src/main/java/org/apache/sshd/common/config/TimeValueConfig.java
index 9652a90..0e84f86 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/TimeValueConfig.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/TimeValueConfig.java
@@ -27,6 +27,7 @@ import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -159,7 +160,7 @@ public enum TimeValueConfig {
      * @throws IllegalArgumentException If negative count for a time unit
      */
     public static long durationOf(Map<TimeValueConfig, ? extends Number> spec) throws IllegalArgumentException {
-        if (GenericUtils.isEmpty(spec)) {
+        if (MapEntryUtils.isEmpty(spec)) {
             return -1L;
         }
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java
index b584b40..2628136 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java
@@ -43,6 +43,7 @@ import java.util.TreeMap;
 
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.io.NoCloseInputStream;
 import org.apache.sshd.common.util.io.NoCloseReader;
@@ -113,7 +114,7 @@ public class AuthorizedKeyEntry extends PublicKeyEntry {
             SessionContext session, Appendable sb, PublicKeyEntryResolver fallbackResolver)
             throws IOException, GeneralSecurityException {
         Map<String, String> options = getLoginOptions();
-        if (!GenericUtils.isEmpty(options)) {
+        if (MapEntryUtils.isNotEmpty(options)) {
             int index = 0;
             // Cannot use forEach because the index value is not effectively final
             for (Map.Entry<String, String> oe : options.entrySet()) {
@@ -160,7 +161,7 @@ public class AuthorizedKeyEntry extends PublicKeyEntry {
         String entry = super.toString();
         String kc = getComment();
         Map<?, ?> ko = getLoginOptions();
-        return (GenericUtils.isEmpty(ko) ? "" : ko.toString() + " ")
+        return (MapEntryUtils.isEmpty(ko) ? "" : ko.toString() + " ")
                + entry
                + (GenericUtils.isEmpty(kc) ? "" : " " + kc);
     }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/IdentityUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/IdentityUtils.java
index 370b5f9..b353dbc 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/IdentityUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/IdentityUtils.java
@@ -35,6 +35,7 @@ import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.keyprovider.MappedKeyPairProvider;
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.io.resource.PathResource;
 import org.apache.sshd.common.util.security.SecurityUtils;
@@ -91,7 +92,7 @@ public final class IdentityUtils {
      * @see                  BuiltinIdentities
      */
     public static KeyPairProvider createKeyPairProvider(Map<String, KeyPair> ids, boolean supportedOnly) {
-        if (GenericUtils.isEmpty(ids)) {
+        if (MapEntryUtils.isEmpty(ids)) {
             return null;
         }
 
@@ -117,7 +118,7 @@ public final class IdentityUtils {
             }
         });
 
-        if (GenericUtils.isEmpty(pairsMap)) {
+        if (MapEntryUtils.isEmpty(pairsMap)) {
             return null;
         } else {
             return new MappedKeyPairProvider(pairsMap);
@@ -142,7 +143,7 @@ public final class IdentityUtils {
     public static NavigableMap<String, KeyPair> loadIdentities(
             SessionContext session, Map<String, ? extends Path> paths, FilePasswordProvider provider, OpenOption... options)
             throws IOException, GeneralSecurityException {
-        if (GenericUtils.isEmpty(paths)) {
+        if (MapEntryUtils.isEmpty(paths)) {
             return Collections.emptyNavigableMap();
         }
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
index 19d6a0e..8283d4b 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
@@ -72,13 +72,13 @@ import org.apache.sshd.common.config.keys.impl.OpenSSHCertificateDecoder;
 import org.apache.sshd.common.config.keys.impl.RSAPublicKeyDecoder;
 import org.apache.sshd.common.config.keys.impl.SkECDSAPublicKeyEntryDecoder;
 import org.apache.sshd.common.config.keys.impl.SkED25519PublicKeyEntryDecoder;
+import org.apache.sshd.common.config.keys.u2f.SkED25519PublicKey;
+import org.apache.sshd.common.config.keys.u2f.SkEcdsaPublicKey;
 import org.apache.sshd.common.digest.BuiltinDigests;
 import org.apache.sshd.common.digest.Digest;
 import org.apache.sshd.common.digest.DigestFactory;
 import org.apache.sshd.common.digest.DigestUtils;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
-import org.apache.sshd.common.u2f.SkED25519PublicKey;
-import org.apache.sshd.common.u2f.SkEcdsaPublicKey;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.MapEntryUtils.NavigableMapBuilder;
 import org.apache.sshd.common.util.OsUtils;
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPublicKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPublicKeyEntryDecoder.java
index 371500f..f0ebec6 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPublicKeyEntryDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPublicKeyEntryDecoder.java
@@ -26,7 +26,7 @@ import java.util.Map;
 
 import org.apache.sshd.common.PropertyResolverUtils;
 import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 
 /**
  * Useful base class implementation for a decoder of an {@code OpenSSH} encoded key data
@@ -43,7 +43,7 @@ public abstract class AbstractPublicKeyEntryDecoder<PUB extends PublicKey, PRV e
     }
 
     protected final boolean parseBooleanHeader(Map<String, String> headers, String propertyKey, boolean defaultVal) {
-        if (GenericUtils.isEmpty(headers) || !headers.containsKey(propertyKey)) {
+        if (MapEntryUtils.isEmpty(headers) || !headers.containsKey(propertyKey)) {
             return defaultVal;
         }
         String stringVal = headers.get(propertyKey);
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkECDSAPublicKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkECDSAPublicKeyEntryDecoder.java
index 7c1f865..5974b8f 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkECDSAPublicKeyEntryDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkECDSAPublicKeyEntryDecoder.java
@@ -35,8 +35,8 @@ import java.util.Objects;
 
 import org.apache.sshd.common.cipher.ECCurves;
 import org.apache.sshd.common.config.keys.KeyEntryResolver;
+import org.apache.sshd.common.config.keys.u2f.SkEcdsaPublicKey;
 import org.apache.sshd.common.session.SessionContext;
-import org.apache.sshd.common.u2f.SkEcdsaPublicKey;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java
index eca03c7..550e1b9 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java
@@ -34,9 +34,9 @@ import java.util.Objects;
 
 import net.i2p.crypto.eddsa.EdDSAPublicKey;
 import org.apache.sshd.common.config.keys.KeyEntryResolver;
+import org.apache.sshd.common.config.keys.u2f.SkED25519PublicKey;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.session.SessionContext;
-import org.apache.sshd.common.u2f.SkED25519PublicKey;
 import org.apache.sshd.common.util.security.eddsa.Ed25519PublicKeyDecoder;
 
 /**
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java
index 1399ff8..78efb2e 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java
@@ -33,6 +33,7 @@ import java.util.stream.Stream;
 
 import org.apache.sshd.common.auth.MutablePassword;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 
 /**
@@ -45,7 +46,7 @@ public class PrivateKeyEncryptionContext implements MutablePassword, Cloneable {
             = Stream.of(AESPrivateKeyObfuscator.INSTANCE, DESPrivateKeyObfuscator.INSTANCE)
                     .collect(Collectors.toMap(
                             AbstractPrivateKeyObfuscator::getCipherName, Function.identity(),
-                            GenericUtils.throwingMerger(), () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)));
+                            MapEntryUtils.throwingMerger(), () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)));
 
     private String cipherName;
     private String cipherType;
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/kdf/BCryptKdfOptions.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/kdf/BCryptKdfOptions.java
index 9fbc35e..5937b0e 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/kdf/BCryptKdfOptions.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/kdf/BCryptKdfOptions.java
@@ -40,7 +40,7 @@ import org.apache.sshd.common.cipher.CipherFactory;
 import org.apache.sshd.common.config.keys.KeyEntryResolver;
 import org.apache.sshd.common.config.keys.loader.openssh.OpenSSHKdfOptions;
 import org.apache.sshd.common.session.SessionContext;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.NumberUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.BufferUtils;
@@ -144,12 +144,12 @@ public class BCryptKdfOptions implements OpenSSHKdfOptions {
                 Arrays.fill(iv, (byte) 0);
             }
         } catch (RuntimeException e) {
-            Throwable t = GenericUtils.peelException(e);
+            Throwable t = ExceptionUtils.peelException(e);
             Throwable err = null;
             if ((t instanceof IOException) || (t instanceof GeneralSecurityException)) {
                 err = t;
             } else {
-                t = GenericUtils.resolveExceptionCause(e);
+                t = ExceptionUtils.resolveExceptionCause(e);
                 if ((t instanceof IOException) || (t instanceof GeneralSecurityException)) {
                     err = t;
                 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java
index d659b7b..79109c2 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java
@@ -45,6 +45,8 @@ import org.apache.sshd.common.config.keys.loader.PrivateKeyEncryptionContext;
 import org.apache.sshd.common.config.keys.loader.PrivateKeyObfuscator;
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
+import org.apache.sshd.common.util.NumberUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.BufferUtils;
 
@@ -91,7 +93,7 @@ public abstract class AbstractPEMResourceKeyPairParser
         byte[] initVector = null;
         String algInfo = null;
         int dataStartIndex = -1;
-        boolean hdrsAvailable = GenericUtils.isNotEmpty(headers);
+        boolean hdrsAvailable = MapEntryUtils.isNotEmpty(headers);
         for (int index = 0; index < lines.size(); index++) {
             String line = GenericUtils.trimToEmpty(lines.get(index));
             if (GenericUtils.isEmpty(line)) {
@@ -108,7 +110,7 @@ public abstract class AbstractPEMResourceKeyPairParser
             String hdrName = line.substring(0, headerPos).trim();
             String hdrValue = line.substring(headerPos + 1).trim();
             if (!hdrsAvailable) {
-                Map<String, String> accHeaders = GenericUtils.isEmpty(headers)
+                Map<String, String> accHeaders = MapEntryUtils.isEmpty(headers)
                         ? new TreeMap<>(String.CASE_INSENSITIVE_ORDER)
                         : headers;
                 accHeaders.put(hdrName, hdrValue);
@@ -216,7 +218,7 @@ public abstract class AbstractPEMResourceKeyPairParser
 
         if (encryptIt) {
             byte[] initVector = encContext.getInitVector();
-            if (GenericUtils.isEmpty(initVector)) {
+            if (NumberUtils.isEmpty(initVector)) {
                 initVector = o.generateInitializationVector(encContext);
                 encContext.setInitVector(initVector);
             }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/u2f/SecurityKeyPublicKey.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/u2f/SecurityKeyPublicKey.java
similarity index 95%
rename from sshd-common/src/main/java/org/apache/sshd/common/u2f/SecurityKeyPublicKey.java
rename to sshd-common/src/main/java/org/apache/sshd/common/config/keys/u2f/SecurityKeyPublicKey.java
index 9496aec..314d848 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/u2f/SecurityKeyPublicKey.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/u2f/SecurityKeyPublicKey.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sshd.common.u2f;
+package org.apache.sshd.common.config.keys.u2f;
 
 import java.security.PublicKey;
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/u2f/SkED25519PublicKey.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/u2f/SkED25519PublicKey.java
similarity index 97%
rename from sshd-common/src/main/java/org/apache/sshd/common/u2f/SkED25519PublicKey.java
rename to sshd-common/src/main/java/org/apache/sshd/common/config/keys/u2f/SkED25519PublicKey.java
index 1573529..5184242 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/u2f/SkED25519PublicKey.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/u2f/SkED25519PublicKey.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sshd.common.u2f;
+package org.apache.sshd.common.config.keys.u2f;
 
 import net.i2p.crypto.eddsa.EdDSAPublicKey;
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/u2f/SkEcdsaPublicKey.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/u2f/SkEcdsaPublicKey.java
similarity index 97%
rename from sshd-common/src/main/java/org/apache/sshd/common/u2f/SkEcdsaPublicKey.java
rename to sshd-common/src/main/java/org/apache/sshd/common/config/keys/u2f/SkEcdsaPublicKey.java
index 41d3166..355cac3 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/u2f/SkEcdsaPublicKey.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/u2f/SkEcdsaPublicKey.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sshd.common.u2f;
+package org.apache.sshd.common.config.keys.u2f;
 
 import java.security.interfaces.ECPublicKey;
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/future/AbstractSshFuture.java b/sshd-common/src/main/java/org/apache/sshd/common/future/AbstractSshFuture.java
index 246c0a6..650d9e1 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/future/AbstractSshFuture.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/future/AbstractSshFuture.java
@@ -25,7 +25,7 @@ import java.io.StreamCorruptedException;
 import java.util.function.Function;
 
 import org.apache.sshd.common.SshException;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 
 /**
@@ -120,13 +120,13 @@ public abstract class AbstractSshFuture<T extends SshFuture> extends AbstractLog
         }
 
         if (Throwable.class.isAssignableFrom(actualType)) {
-            Throwable t = GenericUtils.peelException((Throwable) value);
+            Throwable t = ExceptionUtils.peelException((Throwable) value);
 
             if (t instanceof SshException) {
                 throw new SshException(((SshException) t).getDisconnectCode(), t.getMessage(), t);
             }
 
-            Throwable cause = GenericUtils.resolveExceptionCause(t);
+            Throwable cause = ExceptionUtils.resolveExceptionCause(t);
             throw formatExceptionMessage(
                     msg -> new SshException(msg, cause),
                     "Failed (%s) to execute: %s",
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/KexExtensions.java b/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/KexExtensions.java
index 7741551..bc21f2e 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/KexExtensions.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/KexExtensions.java
@@ -42,6 +42,7 @@ import org.apache.sshd.common.kex.extension.parser.Elevation;
 import org.apache.sshd.common.kex.extension.parser.NoFlowControl;
 import org.apache.sshd.common.kex.extension.parser.ServerSignatureAlgorithms;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.Readable;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
@@ -72,7 +73,7 @@ public final class KexExtensions {
             DelayCompression.INSTANCE)
             .collect(Collectors.toMap(
                     NamedResource::getName, Function.identity(),
-                    GenericUtils.throwingMerger(), () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)));
+                    MapEntryUtils.throwingMerger(), () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)));
 
     private KexExtensions() {
         throw new UnsupportedOperationException("No instance allowed");
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java
index af67047..ea08b6d 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java
@@ -28,6 +28,7 @@ import java.util.Iterator;
 
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.functors.UnaryEquator;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -65,7 +66,7 @@ public interface KeyIdentityProvider {
      * @return          {@code true} if instance is {@code null} or the {@link #EMPTY_KEYS_PROVIDER}
      */
     static boolean isEmpty(KeyIdentityProvider provider) {
-        return (provider == null) || GenericUtils.isSameReference(provider, EMPTY_KEYS_PROVIDER);
+        return (provider == null) || UnaryEquator.isSameReference(provider, EMPTY_KEYS_PROVIDER);
     }
 
     /**
@@ -87,7 +88,7 @@ public interface KeyIdentityProvider {
      */
     static KeyIdentityProvider resolveKeyIdentityProvider(
             KeyIdentityProvider identities, KeyIdentityProvider keys) {
-        if (isEmpty(keys) || GenericUtils.isSameReference(identities, keys)) {
+        if (isEmpty(keys) || UnaryEquator.isSameReference(identities, keys)) {
             // Prefer EMPTY over null
             return (identities == null) ? keys : identities;
         } else if (isEmpty(identities)) {
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/AbstractSecurityKeySignature.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/AbstractSecurityKeySignature.java
index 8745656..66bebb2 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/signature/AbstractSecurityKeySignature.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/AbstractSecurityKeySignature.java
@@ -24,8 +24,8 @@ import java.security.MessageDigest;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 
+import org.apache.sshd.common.config.keys.u2f.SecurityKeyPublicKey;
 import org.apache.sshd.common.session.SessionContext;
-import org.apache.sshd.common.u2f.SecurityKeyPublicKey;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java
index 5a7e86c..b88178e 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java
@@ -40,6 +40,7 @@ import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
@@ -163,7 +164,7 @@ public interface SignatureFactory extends BuiltinFactory<Signature> {
 
     static int resolvePreferredSignaturePosition(
             List<String> preferredOrder, int prefValue, Map<String, Integer> posMap) {
-        if (GenericUtils.isEmpty(preferredOrder) || (prefValue < 0) || GenericUtils.isEmpty(posMap)) {
+        if (GenericUtils.isEmpty(preferredOrder) || (prefValue < 0) || MapEntryUtils.isEmpty(posMap)) {
             return -1;
         }
 
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 ffc719f..f28c4f3 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
@@ -198,8 +198,8 @@ public final class EventListenerUtils {
                 try {
                     method.invoke(l, args);
                 } catch (Throwable t) {
-                    Throwable e = GenericUtils.peelException(t);
-                    err = GenericUtils.accumulateException(err, e);
+                    Throwable e = ExceptionUtils.peelException(t);
+                    err = ExceptionUtils.accumulateException(err, e);
                 }
             }
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/ExceptionUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/ExceptionUtils.java
new file mode 100644
index 0000000..64aae68
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/ExceptionUtils.java
@@ -0,0 +1,162 @@
+/*
+ * 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.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.util.concurrent.ExecutionException;
+
+import javax.management.MBeanException;
+import javax.management.ReflectionException;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class ExceptionUtils {
+    private ExceptionUtils() {
+        throw new UnsupportedOperationException("No instance");
+    }
+
+    public static void rethrowAsIoException(Throwable e) throws IOException {
+        if (e instanceof IOException) {
+            throw (IOException) e;
+        } else if (e instanceof RuntimeException) {
+            throw (RuntimeException) e;
+        } else if (e instanceof Error) {
+            throw (Error) e;
+        } else {
+            throw new IOException(e);
+        }
+    }
+
+    /**
+     * Used to &quot;accumulate&quot; exceptions of the <U>same type</U>. If the current exception is {@code null} then
+     * the new one becomes the current, otherwise the new one is added as a <U>suppressed</U> exception to the current
+     * one
+     *
+     * @param  <T>     The exception type
+     * @param  current The current exception
+     * @param  extra   The extra/new exception
+     * @return         The resolved exception
+     * @see            Throwable#addSuppressed(Throwable)
+     */
+    public static <T extends Throwable> T accumulateException(T current, T extra) {
+        if (current == null) {
+            return extra;
+        }
+
+        if ((extra == null) || (extra == current)) {
+            return current;
+        }
+
+        current.addSuppressed(extra);
+        return current;
+    }
+
+    /**
+     * @param  t The original {@link Throwable} - ignored if {@code null}
+     * @return   If {@link Throwable#getCause()} is non-{@code null} then the cause, otherwise the original exception -
+     *           {@code null} if the original exception was {@code null}
+     */
+    public static Throwable resolveExceptionCause(Throwable t) {
+        if (t == null) {
+            return t;
+        }
+
+        Throwable c = t.getCause();
+        if (c == null) {
+            return t;
+        } else {
+            return c;
+        }
+    }
+
+    /**
+     * Attempts to get to the &quot;effective&quot; exception being thrown, by taking care of some known exceptions that
+     * wrap the original thrown one.
+     *
+     * @param  t The original {@link Throwable} - ignored if {@code null}
+     * @return   The effective exception - same as input if not a wrapper
+     */
+    public static Throwable peelException(Throwable t) {
+        // NOTE: check order is important - e.g., InvocationTargetException extends ReflectiveOperationException
+        if (t == null) {
+            return t;
+        } else if (t instanceof UndeclaredThrowableException) {
+            Throwable wrapped = ((UndeclaredThrowableException) t).getUndeclaredThrowable();
+            // according to the Javadoc it may be null, in which case 'getCause'
+            // might contain the information we need
+            if (wrapped != null) {
+                return peelException(wrapped);
+            }
+
+            wrapped = t.getCause();
+            if (wrapped != t) { // make sure it is a real cause
+                return peelException(wrapped);
+            }
+        } else if (t instanceof InvocationTargetException) {
+            Throwable target = ((InvocationTargetException) t).getTargetException();
+            if (target != null) {
+                return peelException(target);
+            }
+        } else if (t instanceof ReflectionException) {
+            Throwable target = ((ReflectionException) t).getTargetException();
+            if (target != null) {
+                return peelException(target);
+            }
+        } else if (t instanceof ExecutionException) {
+            Throwable wrapped = resolveExceptionCause(t);
+            if (wrapped != null) {
+                return peelException(wrapped);
+            }
+        } else if (t instanceof MBeanException) {
+            Throwable target = ((MBeanException) t).getTargetException();
+            if (target != null) {
+                return peelException(target);
+            }
+        }
+
+        return t; // no special handling required or available
+    }
+
+    /**
+     * Converts a thrown generic exception to a {@link RuntimeException}
+     *
+     * @param  t             The original thrown exception
+     * @param  peelThrowable Whether to determine the root cause by &quot;peeling&quot; any enclosing exceptions
+     * @return               The thrown cause if already a runtime exception, otherwise a runtime exception of the
+     *                       resolved exception as its cause
+     * @see                  #peelException(Throwable)
+     */
+    public static RuntimeException toRuntimeException(Throwable t, boolean peelThrowable) {
+        Throwable e = peelThrowable ? peelException(t) : t;
+        if (e instanceof RuntimeException) {
+            return (RuntimeException) e;
+        }
+
+        return new RuntimeException(e);
+    }
+
+    public static RuntimeException toRuntimeException(Throwable t) {
+        return toRuntimeException(t, true);
+    }
+
+}
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
index 181a902..11da272 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
@@ -19,9 +19,6 @@
 
 package org.apache.sshd.common.util;
 
-import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.UndeclaredThrowableException;
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -34,17 +31,12 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
-import java.util.NavigableMap;
 import java.util.NavigableSet;
 import java.util.Objects;
 import java.util.Set;
 import java.util.SortedSet;
-import java.util.TreeMap;
 import java.util.TreeSet;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.BinaryOperator;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -54,9 +46,6 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 
-import javax.management.MBeanException;
-import javax.management.ReflectionException;
-
 import org.apache.sshd.common.util.functors.UnaryEquator;
 
 /**
@@ -88,9 +77,6 @@ public final class GenericUtils {
 
     public static final String QUOTES = "\"'";
 
-    @SuppressWarnings("rawtypes")
-    private static final Supplier CASE_INSENSITIVE_MAP_FACTORY = () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-
     private GenericUtils() {
         throw new UnsupportedOperationException("No instance");
     }
@@ -185,7 +171,7 @@ public final class GenericUtils {
     }
 
     public static int safeCompare(String s1, String s2, boolean caseSensitive) {
-        if (isSameReference(s1, s2)) {
+        if (UnaryEquator.isSameReference(s1, s2)) {
             return 0;
         } else if (s1 == null) {
             return +1; // push null(s) to end
@@ -198,10 +184,6 @@ public final class GenericUtils {
         }
     }
 
-    public static <T> boolean isSameReference(T o1, T o2) {
-        return o1 == o2;
-    }
-
     public static int length(CharSequence cs) {
         return cs == null ? 0 : cs.length();
     }
@@ -362,18 +344,6 @@ public final class GenericUtils {
                 && c2.containsAll(c1);
     }
 
-    public static int size(Map<?, ?> m) {
-        return (m == null) ? 0 : m.size();
-    }
-
-    public static boolean isEmpty(Map<?, ?> m) {
-        return size(m) <= 0;
-    }
-
-    public static boolean isNotEmpty(Map<?, ?> m) {
-        return !isEmpty(m);
-    }
-
     @SafeVarargs
     public static <T> int length(T... a) {
         return (a == null) ? 0 : a.length;
@@ -401,8 +371,7 @@ public final class GenericUtils {
         return !isEmpty(iter);
     }
 
-    @SafeVarargs
-    public static <T> boolean isEmpty(T... a) {
+    public static <T> boolean isEmpty(T[] a) {
         return length(a) <= 0;
     }
 
@@ -566,25 +535,6 @@ public final class GenericUtils {
         return stream(values).map(mapper).collect(toSortedSet(comparator));
     }
 
-    public static <T, K, U> NavigableMap<K, U> toSortedMap(
-            Iterable<? extends T> values, Function<? super T, ? extends K> keyMapper,
-            Function<? super T, ? extends U> valueMapper, Comparator<? super K> comparator) {
-        return stream(values).collect(toSortedMap(keyMapper, valueMapper, comparator));
-    }
-
-    public static <T, K, U> Collector<T, ?, NavigableMap<K, U>> toSortedMap(
-            Function<? super T, ? extends K> keyMapper,
-            Function<? super T, ? extends U> valueMapper,
-            Comparator<? super K> comparator) {
-        return Collectors.toMap(keyMapper, valueMapper, throwingMerger(), () -> new TreeMap<>(comparator));
-    }
-
-    public static <T> BinaryOperator<T> throwingMerger() {
-        return (u, v) -> {
-            throw new IllegalStateException(String.format("Duplicate key %s", u));
-        };
-    }
-
     public static <T> Collector<T, ?, NavigableSet<T>> toSortedSet(Comparator<? super T> comparator) {
         return Collectors.toCollection(() -> new TreeSet<>(comparator));
     }
@@ -661,79 +611,6 @@ public final class GenericUtils {
         return set;
     }
 
-    /**
-     * @param  <V> Type of mapped value
-     * @return     A {@link Supplier} that returns a <U>new</U> {@link NavigableMap} whenever its {@code get()} method
-     *             is invoked
-     */
-    @SuppressWarnings("unchecked")
-    public static <V> Supplier<NavigableMap<String, V>> caseInsensitiveMap() {
-        return CASE_INSENSITIVE_MAP_FACTORY;
-    }
-
-    /**
-     * Flips between keys and values of an input map
-     *
-     * @param  <K>                      Original map key type
-     * @param  <V>                      Original map value type
-     * @param  <M>                      Flipped map type
-     * @param  map                      The original map to flip
-     * @param  mapCreator               The creator of the target map
-     * @param  allowDuplicates          Whether to ignore duplicates on flip
-     * @return                          The flipped map result
-     * @throws IllegalArgumentException if <tt>allowDuplicates</tt> is {@code false} and a duplicate value found in the
-     *                                  original map.
-     */
-    public static <K, V, M extends Map<V, K>> M flipMap(
-            Map<? extends K, ? extends V> map, Supplier<? extends M> mapCreator, boolean allowDuplicates) {
-        M result = Objects.requireNonNull(mapCreator.get(), "No map created");
-        map.forEach((key, value) -> {
-            K prev = result.put(value, key);
-            if ((prev != null) && (!allowDuplicates)) {
-                ValidateUtils.throwIllegalArgumentException("Multiple values for key=%s: current=%s, previous=%s", value, key,
-                        prev);
-            }
-        });
-
-        return result;
-    }
-
-    @SafeVarargs
-    public static <K, V, M extends Map<K, V>> M mapValues(
-            Function<? super V, ? extends K> keyMapper, Supplier<? extends M> mapCreator, V... values) {
-        return mapValues(keyMapper, mapCreator, isEmpty(values) ? Collections.emptyList() : Arrays.asList(values));
-    }
-
-    /**
-     * Creates a map out of a group of values
-     *
-     * @param  <K>        The key type
-     * @param  <V>        The value type
-     * @param  <M>        The result {@link Map} type
-     * @param  keyMapper  The {@link Function} that generates a key for a given value. If the returned key is
-     *                    {@code null} then the value is not mapped
-     * @param  mapCreator The {@link Supplier} used to create/retrieve the result map - provided non-empty group of
-     *                    values
-     * @param  values     The values to be mapped
-     * @return            The resulting {@link Map} - <B>Note:</B> no validation is made to ensure that 2 (or more)
-     *                    values are not mapped to the same key
-     */
-    public static <K, V, M extends Map<K, V>> M mapValues(
-            Function<? super V, ? extends K> keyMapper,
-            Supplier<? extends M> mapCreator,
-            Collection<? extends V> values) {
-        M map = mapCreator.get();
-        for (V v : values) {
-            K k = keyMapper.apply(v);
-            if (k == null) {
-                continue; // debug breakpoint
-            }
-            map.put(k, v);
-        }
-
-        return map;
-    }
-
     @SafeVarargs
     public static <T> T findFirstMatchingMember(Predicate<? super T> acceptor, T... values) {
         return findFirstMatchingMember(acceptor,
@@ -816,130 +693,6 @@ public final class GenericUtils {
         }
     }
 
-    public static RuntimeException toRuntimeException(Throwable t) {
-        return toRuntimeException(t, true);
-    }
-
-    /**
-     * Converts a thrown generic exception to a {@link RuntimeException}
-     *
-     * @param  t             The original thrown exception
-     * @param  peelThrowable Whether to determine the root cause by &quot;peeling&quot; any enclosing exceptions
-     * @return               The thrown cause if already a runtime exception, otherwise a runtime exception of the
-     *                       resolved exception as its cause
-     * @see                  #peelException(Throwable)
-     */
-    public static RuntimeException toRuntimeException(Throwable t, boolean peelThrowable) {
-        Throwable e = peelThrowable ? peelException(t) : t;
-        if (e instanceof RuntimeException) {
-            return (RuntimeException) e;
-        }
-
-        return new RuntimeException(e);
-    }
-
-    /**
-     * Attempts to get to the &quot;effective&quot; exception being thrown, by taking care of some known exceptions that
-     * wrap the original thrown one.
-     *
-     * @param  t The original {@link Throwable} - ignored if {@code null}
-     * @return   The effective exception - same as input if not a wrapper
-     */
-    public static Throwable peelException(Throwable t) {
-        // NOTE: check order is important - e.g., InvocationTargetException extends ReflectiveOperationException
-        if (t == null) {
-            return t;
-        } else if (t instanceof UndeclaredThrowableException) {
-            Throwable wrapped = ((UndeclaredThrowableException) t).getUndeclaredThrowable();
-            // according to the Javadoc it may be null, in which case 'getCause'
-            // might contain the information we need
-            if (wrapped != null) {
-                return peelException(wrapped);
-            }
-
-            wrapped = t.getCause();
-            if (wrapped != t) { // make sure it is a real cause
-                return peelException(wrapped);
-            }
-        } else if (t instanceof InvocationTargetException) {
-            Throwable target = ((InvocationTargetException) t).getTargetException();
-            if (target != null) {
-                return peelException(target);
-            }
-        } else if (t instanceof ReflectionException) {
-            Throwable target = ((ReflectionException) t).getTargetException();
-            if (target != null) {
-                return peelException(target);
-            }
-        } else if (t instanceof ExecutionException) {
-            Throwable wrapped = resolveExceptionCause(t);
-            if (wrapped != null) {
-                return peelException(wrapped);
-            }
-        } else if (t instanceof MBeanException) {
-            Throwable target = ((MBeanException) t).getTargetException();
-            if (target != null) {
-                return peelException(target);
-            }
-        }
-
-        return t; // no special handling required or available
-    }
-
-    /**
-     * @param  t The original {@link Throwable} - ignored if {@code null}
-     * @return   If {@link Throwable#getCause()} is non-{@code null} then the cause, otherwise the original exception -
-     *           {@code null} if the original exception was {@code null}
-     */
-    public static Throwable resolveExceptionCause(Throwable t) {
-        if (t == null) {
-            return t;
-        }
-
-        Throwable c = t.getCause();
-        if (c == null) {
-            return t;
-        } else {
-            return c;
-        }
-    }
-
-    /**
-     * Used to &quot;accumulate&quot; exceptions of the <U>same type</U>. If the current exception is {@code null} then
-     * the new one becomes the current, otherwise the new one is added as a <U>suppressed</U> exception to the current
-     * one
-     *
-     * @param  <T>     The exception type
-     * @param  current The current exception
-     * @param  extra   The extra/new exception
-     * @return         The resolved exception
-     * @see            Throwable#addSuppressed(Throwable)
-     */
-    public static <T extends Throwable> T accumulateException(T current, T extra) {
-        if (current == null) {
-            return extra;
-        }
-
-        if ((extra == null) || (extra == current)) {
-            return current;
-        }
-
-        current.addSuppressed(extra);
-        return current;
-    }
-
-    public static void rethrowAsIoException(Throwable e) throws IOException {
-        if (e instanceof IOException) {
-            throw (IOException) e;
-        } else if (e instanceof RuntimeException) {
-            throw (RuntimeException) e;
-        } else if (e instanceof Error) {
-            throw (Error) e;
-        } else {
-            throw new IOException(e);
-        }
-    }
-
     /**
      * Wraps a value into a {@link Supplier}
      *
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/MapEntryUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/MapEntryUtils.java
index d17b4a4..0258332 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/MapEntryUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/MapEntryUtils.java
@@ -18,6 +18,8 @@
  */
 package org.apache.sshd.common.util;
 
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.EnumMap;
@@ -26,7 +28,11 @@ import java.util.Map;
 import java.util.NavigableMap;
 import java.util.Objects;
 import java.util.TreeMap;
+import java.util.function.BinaryOperator;
+import java.util.function.Function;
 import java.util.function.Supplier;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
 
 /**
  * Represents an un-modifiable pair of values
@@ -41,6 +47,9 @@ public final class MapEntryUtils {
         return k1.compareTo(k2);
     };
 
+    @SuppressWarnings("rawtypes")
+    private static final Supplier CASE_INSENSITIVE_MAP_FACTORY = () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+
     private MapEntryUtils() {
         throw new UnsupportedOperationException("No instance");
     }
@@ -218,4 +227,108 @@ public final class MapEntryUtils {
             return new EnumMapBuilder<>(keyType);
         }
     }
+
+    public static int size(Map<?, ?> m) {
+        return (m == null) ? 0 : m.size();
+    }
+
+    public static boolean isEmpty(Map<?, ?> m) {
+        return size(m) <= 0;
+    }
+
+    public static boolean isNotEmpty(Map<?, ?> m) {
+        return !isEmpty(m);
+    }
+
+    /**
+     * @param  <V> Type of mapped value
+     * @return     A {@link Supplier} that returns a <U>new</U> {@link NavigableMap} whenever its {@code get()} method
+     *             is invoked
+     */
+    @SuppressWarnings("unchecked")
+    public static <V> Supplier<NavigableMap<String, V>> caseInsensitiveMap() {
+        return CASE_INSENSITIVE_MAP_FACTORY;
+    }
+
+    /**
+     * Flips between keys and values of an input map
+     *
+     * @param  <K>                      Original map key type
+     * @param  <V>                      Original map value type
+     * @param  <M>                      Flipped map type
+     * @param  map                      The original map to flip
+     * @param  mapCreator               The creator of the target map
+     * @param  allowDuplicates          Whether to ignore duplicates on flip
+     * @return                          The flipped map result
+     * @throws IllegalArgumentException if <tt>allowDuplicates</tt> is {@code false} and a duplicate value found in the
+     *                                  original map.
+     */
+    public static <K, V, M extends Map<V, K>> M flipMap(
+            Map<? extends K, ? extends V> map, Supplier<? extends M> mapCreator, boolean allowDuplicates) {
+        M result = Objects.requireNonNull(mapCreator.get(), "No map created");
+        map.forEach((key, value) -> {
+            K prev = result.put(value, key);
+            if ((prev != null) && (!allowDuplicates)) {
+                ValidateUtils.throwIllegalArgumentException("Multiple values for key=%s: current=%s, previous=%s", value, key,
+                        prev);
+            }
+        });
+
+        return result;
+    }
+
+    @SafeVarargs
+    public static <K, V, M extends Map<K, V>> M mapValues(
+            Function<? super V, ? extends K> keyMapper, Supplier<? extends M> mapCreator, V... values) {
+        return mapValues(keyMapper, mapCreator, GenericUtils.isEmpty(values) ? Collections.emptyList() : Arrays.asList(values));
+    }
+
+    /**
+     * Creates a map out of a group of values
+     *
+     * @param  <K>        The key type
+     * @param  <V>        The value type
+     * @param  <M>        The result {@link Map} type
+     * @param  keyMapper  The {@link Function} that generates a key for a given value. If the returned key is
+     *                    {@code null} then the value is not mapped
+     * @param  mapCreator The {@link Supplier} used to create/retrieve the result map - provided non-empty group of
+     *                    values
+     * @param  values     The values to be mapped
+     * @return            The resulting {@link Map} - <B>Note:</B> no validation is made to ensure that 2 (or more)
+     *                    values are not mapped to the same key
+     */
+    public static <K, V, M extends Map<K, V>> M mapValues(
+            Function<? super V, ? extends K> keyMapper,
+            Supplier<? extends M> mapCreator,
+            Collection<? extends V> values) {
+        M map = mapCreator.get();
+        for (V v : values) {
+            K k = keyMapper.apply(v);
+            if (k == null) {
+                continue; // debug breakpoint
+            }
+            map.put(k, v);
+        }
+
+        return map;
+    }
+
+    public static <T, K, U> NavigableMap<K, U> toSortedMap(
+            Iterable<? extends T> values, Function<? super T, ? extends K> keyMapper,
+            Function<? super T, ? extends U> valueMapper, Comparator<? super K> comparator) {
+        return GenericUtils.stream(values).collect(toSortedMap(keyMapper, valueMapper, comparator));
+    }
+
+    public static <T, K, U> Collector<T, ?, NavigableMap<K, U>> toSortedMap(
+            Function<? super T, ? extends K> keyMapper,
+            Function<? super T, ? extends U> valueMapper,
+            Comparator<? super K> comparator) {
+        return Collectors.toMap(keyMapper, valueMapper, throwingMerger(), () -> new TreeMap<>(comparator));
+    }
+
+    public static <T> BinaryOperator<T> throwingMerger() {
+        return (u, v) -> {
+            throw new IllegalStateException(String.format("Duplicate key %s", u));
+        };
+    }
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/ValidateUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/ValidateUtils.java
index 35f46d9..5b3cb38 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/ValidateUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/ValidateUtils.java
@@ -71,7 +71,7 @@ public final class ValidateUtils {
 
     public static <K, V, M extends Map<K, V>> M checkNotNullAndNotEmpty(M t, String message, Object... args) {
         t = checkNotNull(t, message, args);
-        checkTrue(GenericUtils.size(t) > 0, message, args);
+        checkTrue(MapEntryUtils.size(t) > 0, message, args);
         return t;
     }
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/SkECBufferPublicKeyParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/SkECBufferPublicKeyParser.java
index 9d1dbee..682b1ef 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/SkECBufferPublicKeyParser.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/SkECBufferPublicKeyParser.java
@@ -24,7 +24,7 @@ import java.security.interfaces.ECPublicKey;
 
 import org.apache.sshd.common.cipher.ECCurves;
 import org.apache.sshd.common.config.keys.impl.SkECDSAPublicKeyEntryDecoder;
-import org.apache.sshd.common.u2f.SkEcdsaPublicKey;
+import org.apache.sshd.common.config.keys.u2f.SkEcdsaPublicKey;
 import org.apache.sshd.common.util.buffer.Buffer;
 
 /**
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/SkED25519BufferPublicKeyParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/SkED25519BufferPublicKeyParser.java
index 5a58f3d..c905e77 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/SkED25519BufferPublicKeyParser.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/SkED25519BufferPublicKeyParser.java
@@ -24,8 +24,8 @@ import java.security.PublicKey;
 
 import net.i2p.crypto.eddsa.EdDSAPublicKey;
 import org.apache.sshd.common.config.keys.impl.SkED25519PublicKeyEntryDecoder;
+import org.apache.sshd.common.config.keys.u2f.SkED25519PublicKey;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
-import org.apache.sshd.common.u2f.SkED25519PublicKey;
 import org.apache.sshd.common.util.buffer.Buffer;
 
 /**
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
index e6914d5..1d3303d 100644
--- 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
@@ -24,6 +24,7 @@ import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.Objects;
 
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ProxyUtils;
 import org.apache.sshd.common.util.logging.LoggingUtils;
@@ -85,7 +86,7 @@ public class AutoCloseableDelegateInvocationHandler implements InvocationHandler
                 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);
+                err = ExceptionUtils.accumulateException(err, t);
             }
         }
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/Int2IntFunction.java b/sshd-common/src/main/java/org/apache/sshd/common/util/functors/Int2IntFunction.java
similarity index 95%
rename from sshd-common/src/main/java/org/apache/sshd/common/util/Int2IntFunction.java
rename to sshd-common/src/main/java/org/apache/sshd/common/util/functors/Int2IntFunction.java
index 490abb0..64b2022 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/Int2IntFunction.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/functors/Int2IntFunction.java
@@ -17,10 +17,12 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util;
+package org.apache.sshd.common.util.functors;
 
 import java.util.function.IntUnaryOperator;
 
+import org.apache.sshd.common.util.ValidateUtils;
+
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/functors/UnaryEquator.java b/sshd-common/src/main/java/org/apache/sshd/common/util/functors/UnaryEquator.java
index 4cfb6f2..a073d2c 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/functors/UnaryEquator.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/functors/UnaryEquator.java
@@ -23,11 +23,9 @@ import java.util.Comparator;
 import java.util.Objects;
 import java.util.function.BiPredicate;
 
-import org.apache.sshd.common.util.GenericUtils;
-
 /**
  * Checks equality between 2 entities of same type
- * 
+ *
  * @param  <T> Type of compared entity
  * @author     <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
@@ -67,6 +65,10 @@ public interface UnaryEquator<T> extends BiPredicate<T, T> {
         return (t1, t2) -> !this.test(t1, t2);
     }
 
+    static <T> boolean isSameReference(T o1, T o2) {
+        return o1 == o2;
+    }
+
     /**
      * @param  <T> Type of entity
      * @return     The default equality checker
@@ -79,10 +81,10 @@ public interface UnaryEquator<T> extends BiPredicate<T, T> {
     /**
      * @param  <T> Type of entity
      * @return     An equator that checks reference equality
-     * @see        GenericUtils#isSameReference(Object, Object)
+     * @see        UnaryEquator#isSameReference(Object, Object)
      */
     static <T> UnaryEquator<T> referenceEquality() {
-        return GenericUtils::isSameReference;
+        return UnaryEquator::isSameReference;
     }
 
     /**
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/IoUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
index f969a62..f1e394d 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
@@ -49,6 +49,7 @@ import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.OsUtils;
 
@@ -190,7 +191,7 @@ public final class IoUtils {
                     c.close();
                 }
             } catch (IOException e) {
-                err = GenericUtils.accumulateException(err, e);
+                err = ExceptionUtils.accumulateException(err, e);
             }
         }
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/PathUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/PathUtils.java
index 30bfd88..52fec23 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/io/PathUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/PathUtils.java
@@ -61,7 +61,7 @@ public final class PathUtils {
      *                       non-{@code null}-s
      */
     public static int safeCompareFilename(Path p1, Path p2, boolean caseSensitive) {
-        if (GenericUtils.isSameReference(p1, p2)) {
+        if (UnaryEquator.isSameReference(p1, p2)) {
             return 0;
         } else if (p1 == null) {
             return 1;
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/Invoker.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/functors/Invoker.java
similarity index 95%
rename from sshd-common/src/main/java/org/apache/sshd/common/util/Invoker.java
rename to sshd-common/src/main/java/org/apache/sshd/common/util/io/functors/Invoker.java
index 270c3c8..cf9f0ba 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/Invoker.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/functors/Invoker.java
@@ -16,12 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sshd.common.util;
+package org.apache.sshd.common.util.io.functors;
 
 import java.util.AbstractMap.SimpleImmutableEntry;
 import java.util.Collection;
 import java.util.Map;
 
+import org.apache.sshd.common.util.ExceptionUtils;
+import org.apache.sshd.common.util.GenericUtils;
+
 /**
  * The complement to the {@code Callable} interface - accepts one argument and possibly throws something
  *
@@ -76,7 +79,7 @@ public interface Invoker<ARG, RET> {
             try {
                 i.invoke(arg);
             } catch (Throwable t) {
-                err = GenericUtils.accumulateException(err, t);
+                err = ExceptionUtils.accumulateException(err, t);
             }
         }
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/net/SshdSocketAddress.java b/sshd-common/src/main/java/org/apache/sshd/common/util/net/SshdSocketAddress.java
index 701e761..9078570 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/net/SshdSocketAddress.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/net/SshdSocketAddress.java
@@ -36,6 +36,7 @@ import java.util.Objects;
 import java.util.Set;
 
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.NumberUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 
@@ -704,7 +705,7 @@ public class SshdSocketAddress extends SocketAddress {
 
     public static <V> Map.Entry<SshdSocketAddress, ? extends V> findMatchingOptionalWildcardEntry(
             Map<SshdSocketAddress, ? extends V> map, SshdSocketAddress address) {
-        if (GenericUtils.isEmpty(map) || (address == null)) {
+        if (MapEntryUtils.isEmpty(map) || (address == null)) {
             return null;
         }
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityEntityFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityEntityFactory.java
index e425783..7ee9f9b 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityEntityFactory.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityEntityFactory.java
@@ -24,7 +24,7 @@ import java.security.GeneralSecurityException;
 import java.security.Provider;
 import java.util.Objects;
 
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 
 /**
@@ -90,7 +90,7 @@ public interface SecurityEntityFactory<T> {
                     Object value = m.invoke(null, algorithm);
                     return entityType.cast(value);
                 } catch (ReflectiveOperationException t) {
-                    Throwable e = GenericUtils.peelException(t);
+                    Throwable e = ExceptionUtils.peelException(t);
                     if (e instanceof GeneralSecurityException) {
                         throw (GeneralSecurityException) e;
                     } else if (e instanceof RuntimeException) {
@@ -130,7 +130,7 @@ public interface SecurityEntityFactory<T> {
                     Object value = m.invoke(null, algorithm, name);
                     return entityType.cast(value);
                 } catch (ReflectiveOperationException t) {
-                    Throwable e = GenericUtils.peelException(t);
+                    Throwable e = ExceptionUtils.peelException(t);
                     if (e instanceof GeneralSecurityException) {
                         throw (GeneralSecurityException) e;
                     } else if (e instanceof RuntimeException) {
@@ -171,7 +171,7 @@ public interface SecurityEntityFactory<T> {
                     Object value = m.invoke(null, algorithm, provider);
                     return entityType.cast(value);
                 } catch (ReflectiveOperationException t) {
-                    Throwable e = GenericUtils.peelException(t);
+                    Throwable e = ExceptionUtils.peelException(t);
                     if (e instanceof GeneralSecurityException) {
                         throw (GeneralSecurityException) e;
                     } else if (e instanceof RuntimeException) {
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java
index d26a1d1..7acb34f 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java
@@ -69,6 +69,7 @@ import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.random.JceRandomFactory;
 import org.apache.sshd.common.random.RandomFactory;
 import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
@@ -429,7 +430,7 @@ public final class SecurityUtils {
                         r = ThreadUtils.createDefaultInstance(SecurityUtils.class, SecurityProviderRegistrar.class,
                                 registrarClass);
                     } catch (ReflectiveOperationException t) {
-                        Throwable e = GenericUtils.peelException(t);
+                        Throwable e = ExceptionUtils.peelException(t);
                         logger.error("Failed ({}) to create default {} registrar instance: {}",
                                 e.getClass().getSimpleName(), registrarClass, e.getMessage());
                         if (e instanceof RuntimeException) {
@@ -724,7 +725,7 @@ public final class SecurityUtils {
         try {
             return SecurityEntityFactory.toFactory(entityType, registrar, getDefaultProviderChoice());
         } catch (ReflectiveOperationException t) {
-            Throwable e = GenericUtils.peelException(t);
+            Throwable e = ExceptionUtils.peelException(t);
             if (e instanceof RuntimeException) {
                 throw (RuntimeException) e;
             } else if (e instanceof Error) {
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java
index ede8ebf..7d36f22 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java
@@ -25,6 +25,7 @@ import java.security.Signature;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ReflectionUtils;
 import org.apache.sshd.common.util.security.AbstractSecurityProviderRegistrar;
@@ -60,7 +61,7 @@ public class BouncyCastleSecurityProviderRegistrar extends AbstractSecurityProvi
         try {
             return getOrCreateProvider(PROVIDER_CLASS);
         } catch (ReflectiveOperationException t) {
-            Throwable e = GenericUtils.peelException(t);
+            Throwable e = ExceptionUtils.peelException(t);
             log.error("getSecurityProvider({}) failed ({}) to instantiate {}: {}",
                     getName(), e.getClass().getSimpleName(), PROVIDER_CLASS, e.getMessage());
             if (e instanceof RuntimeException) {
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java
index 431a04c..8154b72 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java
@@ -25,7 +25,7 @@ import java.security.Signature;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicReference;
 
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.ReflectionUtils;
 import org.apache.sshd.common.util.security.AbstractSecurityProviderRegistrar;
 import org.apache.sshd.common.util.security.SecurityUtils;
@@ -58,7 +58,7 @@ public class EdDSASecurityProviderRegistrar extends AbstractSecurityProviderRegi
         try {
             return getOrCreateProvider(PROVIDER_CLASS);
         } catch (ReflectiveOperationException t) {
-            Throwable e = GenericUtils.peelException(t);
+            Throwable e = ExceptionUtils.peelException(t);
             log.error("getSecurityProvider({}) failed ({}) to instantiate {}: {}",
                     getName(), e.getClass().getSimpleName(), PROVIDER_CLASS, e.getMessage());
             if (e instanceof RuntimeException) {
diff --git a/sshd-common/src/main/java/org/apache/sshd/server/Signal.java b/sshd-common/src/main/java/org/apache/sshd/server/Signal.java
index c248576..d8418ca 100644
--- a/sshd-common/src/main/java/org/apache/sshd/server/Signal.java
+++ b/sshd-common/src/main/java/org/apache/sshd/server/Signal.java
@@ -26,6 +26,7 @@ import java.util.Set;
 import java.util.function.Function;
 
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 
 /**
  * System signals definition that the shell can receive.
@@ -69,20 +70,20 @@ public enum Signal {
 
     /**
      * An un-modifiable <U>case-insensitive</U> {@link NavigableMap} of the names of all available {@link Signal}s
-     * 
+     *
      * @see #SIGNALS
      */
     public static final NavigableMap<String, Signal> NAME_LOOKUP_TABLE = Collections.unmodifiableNavigableMap(
-            GenericUtils.toSortedMap(SIGNALS, Signal::name, Function.identity(), String.CASE_INSENSITIVE_ORDER));
+            MapEntryUtils.toSortedMap(SIGNALS, Signal::name, Function.identity(), String.CASE_INSENSITIVE_ORDER));
 
     /**
      * An un-modifiable {@link NavigableMap} of the numeric values of all available {@link Signal}s
-     * 
+     *
      * @see #SIGNALS
      * @see #getNumeric()
      */
     public static final NavigableMap<Integer, Signal> NUMERIC_LOOKUP_TABLE = Collections.unmodifiableNavigableMap(
-            GenericUtils.toSortedMap(SIGNALS, Signal::getNumeric, Function.identity(), Comparator.naturalOrder()));
+            MapEntryUtils.toSortedMap(SIGNALS, Signal::getNumeric, Function.identity(), Comparator.naturalOrder()));
 
     private final int numeric;
 
diff --git a/sshd-common/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryTest.java b/sshd-common/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryTest.java
index df9804c..1dc9fce 100644
--- a/sshd-common/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryTest.java
@@ -28,6 +28,7 @@ import java.util.List;
 import java.util.regex.Pattern;
 
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.util.test.JUnitTestSupport;
 import org.apache.sshd.util.test.NoIoTestCase;
 import org.junit.FixMethodOrder;
@@ -261,7 +262,7 @@ public class HostConfigEntryTest extends JUnitTestSupport {
             assertTrue("No target port for " + entry, entry.getPort() > 0);
             assertFalse("No username for " + entry, GenericUtils.isEmpty(entry.getUsername()));
             assertFalse("No identities for " + entry, GenericUtils.isEmpty(entry.getIdentities()));
-            assertFalse("No properties for " + entry, GenericUtils.isEmpty(entry.getProperties()));
+            assertFalse("No properties for " + entry, MapEntryUtils.isEmpty(entry.getProperties()));
         }
     }
 
@@ -327,7 +328,7 @@ public class HostConfigEntryTest extends JUnitTestSupport {
 
         for (HostConfigEntry entry : entries) {
             assertFalse("No pattern for " + entry, GenericUtils.isEmpty(entry.getHost()));
-            assertFalse("No extra properties for " + entry, GenericUtils.isEmpty(entry.getProperties()));
+            assertFalse("No extra properties for " + entry, MapEntryUtils.isEmpty(entry.getProperties()));
         }
 
         return entries;
diff --git a/sshd-common/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityTest.java b/sshd-common/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityTest.java
index 9d4e0d6..260afb7 100644
--- a/sshd-common/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityTest.java
@@ -32,6 +32,7 @@ import org.apache.sshd.common.config.keys.BuiltinIdentities;
 import org.apache.sshd.common.config.keys.IdentityUtils;
 import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.util.test.JUnitTestSupport;
 import org.apache.sshd.util.test.NoIoTestCase;
@@ -76,7 +77,7 @@ public class ClientIdentityTest extends JUnitTestSupport {
                 false, // don't be strict
                 null, // none of the files is password protected
                 options);
-        assertEquals("Mismatched loaded ids count", GenericUtils.size(expected), GenericUtils.size(ids));
+        assertEquals("Mismatched loaded ids count", GenericUtils.size(expected), MapEntryUtils.size(ids));
 
         Collection<KeyPair> pairs = new ArrayList<>(ids.size());
         for (BuiltinIdentities type : BuiltinIdentities.VALUES) {
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/VersionPropertiesTest.java b/sshd-common/src/test/java/org/apache/sshd/common/VersionPropertiesTest.java
index 2d17a1d..e49b362 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/VersionPropertiesTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/VersionPropertiesTest.java
@@ -23,6 +23,7 @@ import java.util.Map;
 
 import org.apache.sshd.common.config.VersionProperties;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.util.test.JUnitTestSupport;
 import org.apache.sshd.util.test.NoIoTestCase;
 import org.junit.FixMethodOrder;
@@ -43,7 +44,7 @@ public class VersionPropertiesTest extends JUnitTestSupport {
     @Test
     public void testNonEmptyProperties() {
         Map<?, ?> props = VersionProperties.getVersionProperties();
-        assertTrue(GenericUtils.isNotEmpty(props));
+        assertTrue(MapEntryUtils.isNotEmpty(props));
     }
 
     @Test
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/channel/PtyModeTest.java b/sshd-common/src/test/java/org/apache/sshd/common/channel/PtyModeTest.java
index 69ae5e8..3eb7fd2 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/channel/PtyModeTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/channel/PtyModeTest.java
@@ -23,7 +23,7 @@ import java.util.EnumSet;
 import java.util.Map;
 import java.util.Set;
 
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.util.test.JUnitTestSupport;
 import org.apache.sshd.util.test.NoIoTestCase;
 import org.junit.FixMethodOrder;
@@ -50,7 +50,7 @@ public class PtyModeTest extends JUnitTestSupport {
     public void testEnabledOptions() {
         Set<PtyMode> expected = EnumSet.of(PtyMode.ECHO, PtyMode.CS8, PtyMode.ICANON);
         Map<PtyMode, Integer> modes = PtyMode.createEnabledOptions(expected);
-        assertEquals("Mismatched modes size", expected.size(), GenericUtils.size(modes));
+        assertEquals("Mismatched modes size", expected.size(), MapEntryUtils.size(modes));
 
         for (PtyMode m : expected) {
             assertSame("Mismatched setting for " + m, PtyMode.TRUE_SETTING, modes.get(m));
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/BuiltinIdentitiesTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/BuiltinIdentitiesTest.java
index b59299a..b5aeb95 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/BuiltinIdentitiesTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/BuiltinIdentitiesTest.java
@@ -29,6 +29,7 @@ import java.util.Collections;
 import java.util.List;
 
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.functors.UnaryEquator;
 import org.apache.sshd.common.util.security.SecurityUtils;
 import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
 import org.apache.sshd.util.test.JUnitTestSupport;
@@ -126,7 +127,7 @@ public class BuiltinIdentitiesTest extends JUnitTestSupport {
     public void testNoOverlappingKeyTypeNamesWithOtherIdentities() {
         Collection<String> current = expected.getSupportedKeyTypes();
         for (BuiltinIdentities identity : BuiltinIdentities.VALUES) {
-            if (GenericUtils.isSameReference(expected, identity)) {
+            if (UnaryEquator.isSameReference(expected, identity)) {
                 continue;
             }
 
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/keyprovider/KeyPairProviderTest.java b/sshd-common/src/test/java/org/apache/sshd/common/keyprovider/KeyPairProviderTest.java
index 8e62fa0..18d534b 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/keyprovider/KeyPairProviderTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/keyprovider/KeyPairProviderTest.java
@@ -29,6 +29,7 @@ import java.util.Map;
 import java.util.function.Function;
 
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.util.test.JUnitTestSupport;
 import org.apache.sshd.util.test.NoIoTestCase;
 import org.junit.FixMethodOrder;
@@ -63,7 +64,7 @@ public class KeyPairProviderTest extends JUnitTestSupport {
         PublicKey pubKey = Mockito.mock(PublicKey.class);
         PrivateKey prvKey = Mockito.mock(PrivateKey.class);
         String[] testKeys = { getCurrentTestName(), getClass().getSimpleName() };
-        Map<String, KeyPair> pairsMap = GenericUtils.toSortedMap(
+        Map<String, KeyPair> pairsMap = MapEntryUtils.toSortedMap(
                 Arrays.asList(testKeys),
                 Function.identity(),
                 k -> new KeyPair(pubKey, prvKey),
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/GenericUtilsTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/GenericUtilsTest.java
index f8bb6ae..ce64af1 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/util/GenericUtilsTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/util/GenericUtilsTest.java
@@ -131,11 +131,11 @@ public class GenericUtilsTest extends JUnitTestSupport {
 
     @Test
     public void testAccumulateExceptionOnNullValues() {
-        assertNull("Unexpected null/null result", GenericUtils.accumulateException(null, null));
+        assertNull("Unexpected null/null result", ExceptionUtils.accumulateException(null, null));
 
         Throwable expected = new NoSuchMethodException(getClass().getName() + "#" + getCurrentTestName());
-        assertSame("Mismatched null/extra result", expected, GenericUtils.accumulateException(null, expected));
-        assertSame("Mismatched current/null result", expected, GenericUtils.accumulateException(expected, null));
+        assertSame("Mismatched null/extra result", expected, ExceptionUtils.accumulateException(null, expected));
+        assertSame("Mismatched current/null result", expected, ExceptionUtils.accumulateException(expected, null));
     }
 
     @Test
@@ -147,7 +147,7 @@ public class GenericUtilsTest extends JUnitTestSupport {
         };
         RuntimeException current = new UnsupportedOperationException("top");
         for (RuntimeException extra : expected) {
-            RuntimeException actual = GenericUtils.accumulateException(current, extra);
+            RuntimeException actual = ExceptionUtils.accumulateException(current, extra);
             assertSame("Mismatched returned actual exception", current, actual);
         }
 
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/Int2IntFunctionTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/Int2IntFunctionTest.java
index b0c48ca..b6238e0 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/util/Int2IntFunctionTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/util/Int2IntFunctionTest.java
@@ -22,6 +22,7 @@ package org.apache.sshd.common.util;
 import java.util.Random;
 import java.util.function.IntUnaryOperator;
 
+import org.apache.sshd.common.util.functors.Int2IntFunction;
 import org.apache.sshd.util.test.JUnitTestSupport;
 import org.apache.sshd.util.test.NoIoTestCase;
 import org.junit.FixMethodOrder;
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 ff3a329..3a0bf0e 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
@@ -57,6 +57,7 @@ import org.apache.sshd.common.config.keys.BuiltinIdentities;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.common.util.logging.LoggingUtils;
 import org.junit.Assert;
@@ -428,8 +429,8 @@ public abstract class JUnitTestSupport extends Assert {
     public static <K, V> void assertMapEquals(
             String message, Map<? extends K, ? extends V> expected, Map<? super K, ? extends V> actual,
             BiPredicate<? super V, ? super V> equator) {
-        int numItems = GenericUtils.size(expected);
-        assertEquals(message + "[size]", numItems, GenericUtils.size(actual));
+        int numItems = MapEntryUtils.size(expected);
+        assertEquals(message + "[size]", numItems, MapEntryUtils.size(actual));
 
         if (numItems > 0) {
             expected.forEach((key, expValue) -> {
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/contrib/client/auth/password/InteractivePasswordIdentityProvider.java b/sshd-contrib/src/main/java/org/apache/sshd/contrib/client/auth/password/InteractivePasswordIdentityProvider.java
index 4f0846f..769d656 100644
--- a/sshd-contrib/src/main/java/org/apache/sshd/contrib/client/auth/password/InteractivePasswordIdentityProvider.java
+++ b/sshd-contrib/src/main/java/org/apache/sshd/contrib/client/auth/password/InteractivePasswordIdentityProvider.java
@@ -29,6 +29,7 @@ import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.client.session.ClientSessionHolder;
 import org.apache.sshd.common.session.SessionHolder;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.functors.UnaryEquator;
 
 /**
  * <P>
@@ -110,7 +111,7 @@ public class InteractivePasswordIdentityProvider
             nextPassword.set(password);
         }
 
-        return !GenericUtils.isSameReference(password, EOF);
+        return !UnaryEquator.isSameReference(password, EOF);
     }
 
     @Override
@@ -120,7 +121,7 @@ public class InteractivePasswordIdentityProvider
             throw new IllegalStateException("hasNext() not called before next()");
         }
 
-        if (GenericUtils.isSameReference(password, EOF)) {
+        if (UnaryEquator.isSameReference(password, EOF)) {
             throw new NoSuchElementException("All passwords exhausted");
         }
 
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/compression/DeflatingInputStream.java b/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/compression/DeflatingInputStream.java
index 9f80d23..ff4cd5e 100644
--- a/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/compression/DeflatingInputStream.java
+++ b/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/compression/DeflatingInputStream.java
@@ -25,7 +25,7 @@ import java.io.OutputStream;
 import java.io.StreamCorruptedException;
 import java.util.Objects;
 
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.contrib.common.util.io.ExposedBufferByteArrayOutputStream;
 
@@ -159,25 +159,25 @@ public class DeflatingInputStream extends InputStream {
         try {
             compressor.close();
         } catch (IOException e) {
-            err = GenericUtils.accumulateException(err, e);
+            err = ExceptionUtils.accumulateException(err, e);
         }
 
         try {
             baos.close();
         } catch (IOException e) {
-            err = GenericUtils.accumulateException(err, e);
+            err = ExceptionUtils.accumulateException(err, e);
         }
 
         try {
             inputStream.close();
         } catch (IOException e) {
-            err = GenericUtils.accumulateException(err, e);
+            err = ExceptionUtils.accumulateException(err, e);
         }
 
         try {
             super.close();
         } catch (IOException e) {
-            err = GenericUtils.accumulateException(err, e);
+            err = ExceptionUtils.accumulateException(err, e);
         }
 
         if (err != null) {
diff --git a/sshd-core/src/main/java/org/apache/sshd/agent/local/ChannelAgentForwarding.java b/sshd-core/src/main/java/org/apache/sshd/agent/local/ChannelAgentForwarding.java
index 60bf0bd..a91b1a2 100644
--- a/sshd-core/src/main/java/org/apache/sshd/agent/local/ChannelAgentForwarding.java
+++ b/sshd-core/src/main/java/org/apache/sshd/agent/local/ChannelAgentForwarding.java
@@ -33,7 +33,7 @@ import org.apache.sshd.common.FactoryManager;
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.channel.ChannelOutputStream;
 import org.apache.sshd.common.session.Session;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
@@ -69,7 +69,7 @@ public class ChannelAgentForwarding extends AbstractServerChannel {
             signalChannelOpenSuccess();
             f.setOpened();
         } catch (Throwable t) {
-            Throwable e = GenericUtils.peelException(t);
+            Throwable e = ExceptionUtils.peelException(t);
             changeEvent = e.getClass().getSimpleName();
             signalChannelOpenFailure(e);
             f.setException(e);
diff --git a/sshd-core/src/main/java/org/apache/sshd/agent/unix/ChannelAgentForwarding.java b/sshd-core/src/main/java/org/apache/sshd/agent/unix/ChannelAgentForwarding.java
index bbafa7b..181ae86 100644
--- a/sshd-core/src/main/java/org/apache/sshd/agent/unix/ChannelAgentForwarding.java
+++ b/sshd-core/src/main/java/org/apache/sshd/agent/unix/ChannelAgentForwarding.java
@@ -30,6 +30,7 @@ import org.apache.sshd.client.future.OpenFuture;
 import org.apache.sshd.common.Closeable;
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.channel.ChannelOutputStream;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
@@ -100,7 +101,7 @@ public class ChannelAgentForwarding extends AbstractServerChannel {
             signalChannelOpenSuccess();
             f.setOpened();
         } catch (Throwable t) {
-            Throwable e = GenericUtils.peelException(t);
+            Throwable e = ExceptionUtils.peelException(t);
             signalChannelOpenFailure(e);
             f.setException(e);
         }
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
index e4cde28..d5fa906 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
@@ -87,8 +87,10 @@ import org.apache.sshd.common.io.IoSession;
 import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.session.helpers.AbstractSession;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.functors.UnaryEquator;
 import org.apache.sshd.common.util.io.resource.PathResource;
 import org.apache.sshd.common.util.net.SshdSocketAddress;
 import org.apache.sshd.core.CoreModuleProperties;
@@ -755,7 +757,7 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
         // check if session listener intervened
         KeyIdentityProvider kpSession = session.getKeyIdentityProvider();
         KeyIdentityProvider kpClient = getKeyIdentityProvider();
-        if (GenericUtils.isSameReference(kpSession, kpClient)) {
+        if (UnaryEquator.isSameReference(kpSession, kpClient)) {
             if (debugEnabled) {
                 log.debug("setupDefaultSessionIdentities({}) key identity provider override in session listener", session);
             }
@@ -763,7 +765,7 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
 
         // Prefer the extra identities to come first since they were probably indicate by the host-config entry
         KeyIdentityProvider kpEffective = KeyIdentityProvider.resolveKeyIdentityProvider(extraIdentities, kpSession);
-        if (!GenericUtils.isSameReference(kpSession, kpEffective)) {
+        if (!UnaryEquator.isSameReference(kpSession, kpEffective)) {
             if (debugEnabled) {
                 log.debug("setupDefaultSessionIdentities({}) key identity provider enhanced", session);
             }
@@ -772,7 +774,7 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
 
         PasswordIdentityProvider passSession = session.getPasswordIdentityProvider();
         PasswordIdentityProvider passClient = getPasswordIdentityProvider();
-        if (!GenericUtils.isSameReference(passSession, passClient)) {
+        if (!UnaryEquator.isSameReference(passSession, passClient)) {
             if (debugEnabled) {
                 log.debug("setupDefaultSessionIdentities({}) password provider override", session);
             }
@@ -853,13 +855,13 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
                 try {
                     client.close();
                 } catch (Exception e) {
-                    err = GenericUtils.accumulateException(err, e);
+                    err = ExceptionUtils.accumulateException(err, e);
                 }
 
                 try {
                     client.stop();
                 } catch (Exception e) {
-                    err = GenericUtils.accumulateException(err, e);
+                    err = ExceptionUtils.accumulateException(err, e);
                 }
 
                 if (err != null) {
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java b/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
index c82c404..d960388 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
@@ -49,7 +49,7 @@ import org.apache.sshd.common.io.IoInputStream;
 import org.apache.sshd.common.io.IoOutputStream;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.util.EventNotifier;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
@@ -359,7 +359,7 @@ public abstract class AbstractClientChannel extends AbstractChannel implements C
             this.opened.set(true);
             this.openFuture.setOpened();
         } catch (Throwable t) {
-            Throwable e = GenericUtils.peelException(t);
+            Throwable e = ExceptionUtils.peelException(t);
             changeEvent = e.getClass().getName();
             signalChannelOpenFailure(e);
             this.openFuture.setException(e);
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/channel/PtyCapableChannelSession.java b/sshd-core/src/main/java/org/apache/sshd/client/channel/PtyCapableChannelSession.java
index 4a70dc4..88f8f03 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/channel/PtyCapableChannelSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/channel/PtyCapableChannelSession.java
@@ -31,6 +31,7 @@ import org.apache.sshd.common.channel.PtyChannelConfigurationMutator;
 import org.apache.sshd.common.channel.PtyMode;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
@@ -45,7 +46,7 @@ import org.apache.sshd.core.CoreModuleProperties;
  * <P>
  * A typical code snippet would be:
  * </P>
- * 
+ *
  * <pre>
  * <code>
  * try (client = SshClient.setUpDefaultClient()) {
@@ -93,7 +94,7 @@ public class PtyCapableChannelSession extends ChannelSession implements PtyChann
         this.config = PtyChannelConfigurationMutator.copyConfiguration(
                 configHolder, new PtyChannelConfiguration());
         this.config.setPtyType(resolvePtyType(this.config));
-        if (GenericUtils.isNotEmpty(env)) {
+        if (MapEntryUtils.isNotEmpty(env)) {
             for (Map.Entry<String, ?> ee : env.entrySet()) {
                 setEnv(ee.getKey(), ee.getValue());
             }
@@ -275,7 +276,7 @@ public class PtyCapableChannelSession extends ChannelSession implements PtyChann
             buffer.putInt(getPtyWidth());
 
             Map<PtyMode, Integer> ptyModes = getPtyModes();
-            int numModes = GenericUtils.size(ptyModes);
+            int numModes = MapEntryUtils.size(ptyModes);
             Buffer modes = new ByteArrayBuffer(numModes * (1 + Integer.BYTES) + Long.SIZE, false);
             if (numModes > 0) {
                 ptyModes.forEach((mode, value) -> {
@@ -288,7 +289,7 @@ public class PtyCapableChannelSession extends ChannelSession implements PtyChann
             writePacket(buffer);
         }
 
-        if (GenericUtils.size(env) > 0) {
+        if (MapEntryUtils.size(env) > 0) {
             if (debugEnabled) {
                 log.debug("doOpenPty({}) Send SSH_MSG_CHANNEL_REQUEST env: {}", this, env);
             }
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/config/SshClientConfigFileReader.java b/sshd-core/src/main/java/org/apache/sshd/client/config/SshClientConfigFileReader.java
index 742cda4..24878ea 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/config/SshClientConfigFileReader.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/config/SshClientConfigFileReader.java
@@ -30,6 +30,7 @@ import org.apache.sshd.common.PropertyResolverUtils;
 import org.apache.sshd.common.config.SshConfigFileReader;
 import org.apache.sshd.common.session.SessionHeartbeatController.HeartbeatType;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.core.CoreModuleProperties;
 
 /**
@@ -79,7 +80,7 @@ public final class SshClientConfigFileReader {
     }
 
     public static <C extends SshClient> C setupClientHeartbeat(C client, Map<String, ?> options) {
-        if ((client == null) || GenericUtils.isEmpty(options)) {
+        if ((client == null) || MapEntryUtils.isEmpty(options)) {
             return client;
         }
 
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java b/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
index b81ac20..c40ca7f 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
@@ -71,6 +71,7 @@ import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.session.SessionDisconnectHandler;
 import org.apache.sshd.common.session.helpers.AbstractConnectionService;
 import org.apache.sshd.common.session.helpers.AbstractSession;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
@@ -689,7 +690,7 @@ public abstract class AbstractClientSession extends AbstractSession implements C
                         setKexSeed(seed);
                     }
                 } catch (Exception e) {
-                    GenericUtils.rethrowAsIoException(e);
+                    ExceptionUtils.rethrowAsIoException(e);
                 }
             }
 
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/simple/AbstractSimpleClientSessionCreator.java b/sshd-core/src/main/java/org/apache/sshd/client/simple/AbstractSimpleClientSessionCreator.java
index e32bf28..dd08130 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/simple/AbstractSimpleClientSessionCreator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/simple/AbstractSimpleClientSessionCreator.java
@@ -31,7 +31,7 @@ import org.apache.sshd.client.future.ConnectFuture;
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.client.session.ClientSessionCreator;
 import org.apache.sshd.common.AttributeRepository;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 
 /**
@@ -120,13 +120,13 @@ public abstract class AbstractSimpleClientSessionCreator extends AbstractSimpleC
             auth.verify(getAuthenticationTimeout());
             session = null; // disable auto-close
         } catch (IOException e) {
-            err = GenericUtils.accumulateException(err, e);
+            err = ExceptionUtils.accumulateException(err, e);
         } finally {
             if (session != null) {
                 try {
                     session.close();
                 } catch (IOException e) {
-                    err = GenericUtils.accumulateException(err, e);
+                    err = ExceptionUtils.accumulateException(err, e);
                 }
             }
         }
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/channel/AbstractChannel.java b/sshd-core/src/main/java/org/apache/sshd/common/channel/AbstractChannel.java
index f888d74..01238f7 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/channel/AbstractChannel.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/channel/AbstractChannel.java
@@ -51,15 +51,16 @@ import org.apache.sshd.common.io.IoWriteFuture;
 import org.apache.sshd.common.session.ConnectionService;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.util.EventListenerUtils;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.Int2IntFunction;
-import org.apache.sshd.common.util.Invoker;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.BufferUtils;
 import org.apache.sshd.common.util.closeable.AbstractInnerCloseable;
 import org.apache.sshd.common.util.closeable.IoBaseCloseable;
+import org.apache.sshd.common.util.functors.Int2IntFunction;
 import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.io.functors.Invoker;
 import org.apache.sshd.common.util.threads.CloseableExecutorService;
 import org.apache.sshd.common.util.threads.ExecutorServiceCarrier;
 import org.apache.sshd.core.CoreModuleProperties;
@@ -381,7 +382,7 @@ public abstract class AbstractChannel extends AbstractInnerCloseable implements
 
             notifyStateChanged("init");
         } catch (Throwable err) {
-            Throwable e = GenericUtils.peelException(err);
+            Throwable e = ExceptionUtils.peelException(err);
             if (e instanceof IOException) {
                 throw (IOException) e;
             } else if (e instanceof RuntimeException) {
@@ -455,7 +456,7 @@ public abstract class AbstractChannel extends AbstractInnerCloseable implements
                 return null;
             });
         } catch (Throwable err) {
-            Throwable ignored = GenericUtils.peelException(err);
+            Throwable ignored = ExceptionUtils.peelException(err);
             debug("signalChannelOpenFailure({}) failed ({}) to inform listener of open failure={}: {}", this,
                     ignored.getClass().getSimpleName(), reason.getClass().getSimpleName(), ignored.getMessage(),
                     ignored);
@@ -477,7 +478,7 @@ public abstract class AbstractChannel extends AbstractInnerCloseable implements
                 return null;
             });
         } catch (Throwable err) {
-            Throwable e = GenericUtils.peelException(err);
+            Throwable e = ExceptionUtils.peelException(err);
             debug("notifyStateChanged({})[{}] {} while signal channel state change: {}", this, hint,
                     e.getClass().getSimpleName(), e.getMessage(), e);
         } finally {
@@ -726,7 +727,7 @@ public abstract class AbstractChannel extends AbstractInnerCloseable implements
                 return null;
             });
         } catch (Throwable err) {
-            Throwable e = GenericUtils.peelException(err);
+            Throwable e = ExceptionUtils.peelException(err);
             debug("signalChannelClosed({}) {} while signal channel closed: {}", this, e.getClass().getSimpleName(),
                     e.getMessage(), e);
         } finally {
@@ -757,7 +758,7 @@ public abstract class AbstractChannel extends AbstractInnerCloseable implements
             try {
                 invoker.invoke(l);
             } catch (Throwable t) {
-                err = GenericUtils.accumulateException(err, t);
+                err = ExceptionUtils.accumulateException(err, t);
             }
         }
 
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/channel/BufferedIoOutputStream.java b/sshd-core/src/main/java/org/apache/sshd/common/channel/BufferedIoOutputStream.java
index e8b81d8..477852e 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/channel/BufferedIoOutputStream.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/channel/BufferedIoOutputStream.java
@@ -34,10 +34,10 @@ import org.apache.sshd.common.channel.exception.SshChannelBufferedOutputExceptio
 import org.apache.sshd.common.future.SshFutureListener;
 import org.apache.sshd.common.io.IoOutputStream;
 import org.apache.sshd.common.io.IoWriteFuture;
-import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.closeable.AbstractInnerCloseable;
+import org.apache.sshd.common.util.functors.UnaryEquator;
 import org.apache.sshd.core.CoreModuleProperties;
 
 /**
@@ -167,7 +167,7 @@ public class BufferedIoOutputStream extends AbstractInnerCloseable implements Io
             IoWriteFutureImpl currentFuture = currentWrite.getAndSet(null);
             for (IoWriteFutureImpl pendingWrite : writes) {
                 // Checking reference by design
-                if (GenericUtils.isSameReference(pendingWrite, currentFuture)) {
+                if (UnaryEquator.isSameReference(pendingWrite, currentFuture)) {
                     continue;   // will be taken care of when its listener is eventually called
                 }
 
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultForwarder.java b/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultForwarder.java
index c1d24f1..1c25ec1 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultForwarder.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultForwarder.java
@@ -58,13 +58,14 @@ import org.apache.sshd.common.session.ConnectionService;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.session.SessionHolder;
 import org.apache.sshd.common.util.EventListenerUtils;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.Invoker;
 import org.apache.sshd.common.util.Readable;
 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.common.util.closeable.AbstractInnerCloseable;
+import org.apache.sshd.common.util.io.functors.Invoker;
 import org.apache.sshd.common.util.net.SshdSocketAddress;
 import org.apache.sshd.core.CoreModuleProperties;
 import org.apache.sshd.server.forward.TcpForwardingFilter;
@@ -874,15 +875,15 @@ public class DefaultForwarder
         try {
             invokePortEventListenerSignallerListeners(getDefaultListeners(), invoker);
         } catch (Throwable t) {
-            Throwable e = GenericUtils.peelException(t);
-            err = GenericUtils.accumulateException(err, e);
+            Throwable e = ExceptionUtils.peelException(t);
+            err = ExceptionUtils.accumulateException(err, e);
         }
 
         try {
             invokePortEventListenerSignallerHolders(managersHolder, invoker);
         } catch (Throwable t) {
-            Throwable e = GenericUtils.peelException(t);
-            err = GenericUtils.accumulateException(err, e);
+            Throwable e = ExceptionUtils.peelException(t);
+            err = ExceptionUtils.accumulateException(err, e);
         }
 
         if (err != null) {
@@ -907,8 +908,8 @@ public class DefaultForwarder
             try {
                 invoker.invoke(l);
             } catch (Throwable t) {
-                Throwable e = GenericUtils.peelException(t);
-                err = GenericUtils.accumulateException(err, e);
+                Throwable e = ExceptionUtils.peelException(t);
+                err = ExceptionUtils.accumulateException(err, e);
             }
         }
 
@@ -934,8 +935,8 @@ public class DefaultForwarder
                     invoker.invoke(listener);
                 }
             } catch (Throwable t) {
-                Throwable e = GenericUtils.peelException(t);
-                err = GenericUtils.accumulateException(err, e);
+                Throwable e = ExceptionUtils.peelException(t);
+                err = ExceptionUtils.accumulateException(err, e);
             }
 
             if (m instanceof PortForwardingEventListenerManagerHolder) {
@@ -943,8 +944,8 @@ public class DefaultForwarder
                     invokePortEventListenerSignallerHolders(
                             ((PortForwardingEventListenerManagerHolder) m).getRegisteredManagers(), invoker);
                 } catch (Throwable t) {
-                    Throwable e = GenericUtils.peelException(t);
-                    err = GenericUtils.accumulateException(err, e);
+                    Throwable e = ExceptionUtils.peelException(t);
+                    err = ExceptionUtils.accumulateException(err, e);
                 }
             }
         }
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/io/nio2/Nio2Connector.java b/sshd-core/src/main/java/org/apache/sshd/common/io/nio2/Nio2Connector.java
index 9b6b783..7055e51 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/io/nio2/Nio2Connector.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/io/nio2/Nio2Connector.java
@@ -31,7 +31,7 @@ import org.apache.sshd.common.io.IoConnector;
 import org.apache.sshd.common.io.IoHandler;
 import org.apache.sshd.common.io.IoServiceEventListener;
 import org.apache.sshd.common.io.IoSession;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 
 /**
@@ -69,7 +69,7 @@ public class Nio2Connector extends Nio2Service implements IoConnector {
                     address);
             socket.connect(address, null, completionHandler);
         } catch (Throwable exc) {
-            Throwable t = GenericUtils.peelException(exc);
+            Throwable t = ExceptionUtils.peelException(exc);
             debug("connect({}) failed ({}) to schedule connection: {}",
                     address, t.getClass().getSimpleName(), t.getMessage(), t);
 
@@ -161,7 +161,7 @@ public class Nio2Connector extends Nio2Service implements IoConnector {
                     session.startReading();
                 }
             } catch (Throwable exc) {
-                Throwable t = GenericUtils.peelException(exc);
+                Throwable t = ExceptionUtils.peelException(exc);
                 boolean debugEnabled = log.isDebugEnabled();
                 if (listener != null) {
                     try {
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/io/nio2/Nio2Session.java b/sshd-core/src/main/java/org/apache/sshd/common/io/nio2/Nio2Session.java
index af9d0e2..66474ea 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/io/nio2/Nio2Session.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/io/nio2/Nio2Session.java
@@ -40,7 +40,7 @@ import org.apache.sshd.common.future.CloseFuture;
 import org.apache.sshd.common.io.IoHandler;
 import org.apache.sshd.common.io.IoSession;
 import org.apache.sshd.common.io.IoWriteFuture;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.Readable;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.closeable.AbstractCloseable;
@@ -199,7 +199,7 @@ public class Nio2Session extends AbstractCloseable implements IoSession {
                 }
                 handler.exceptionCaught(this, exc);
             } catch (Throwable e) {
-                Throwable t = GenericUtils.peelException(e);
+                Throwable t = ExceptionUtils.peelException(e);
                 debug("exceptionCaught({}) Exception handler threw {}, closing the session: {}",
                         this, t.getClass().getSimpleName(), t.getMessage(), t);
             }
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/DefaultClientKexExtensionHandler.java b/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/DefaultClientKexExtensionHandler.java
index cd4af2c..09690a8 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/DefaultClientKexExtensionHandler.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/DefaultClientKexExtensionHandler.java
@@ -42,6 +42,8 @@ import org.apache.sshd.common.signature.BuiltinSignatures;
 import org.apache.sshd.common.signature.Signature;
 import org.apache.sshd.common.signature.SignatureFactory;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
+import org.apache.sshd.common.util.functors.UnaryEquator;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 
 /**
@@ -51,7 +53,7 @@ import org.apache.sshd.common.util.logging.AbstractLoggingBean;
  * factories (if not already added).
  *
  * <B>Note:</B> experimental - used for development purposes and as an example
- * 
+ *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class DefaultClientKexExtensionHandler extends AbstractLoggingBean implements KexExtensionHandler {
@@ -91,7 +93,7 @@ public class DefaultClientKexExtensionHandler extends AbstractLoggingBean implem
         // Check if client already sent its proposal - if not, we can still influence it
         Map<KexProposalOption, String> clientProposal = session.getAttribute(CLIENT_PROPOSAL_KEY);
         Map<KexProposalOption, String> serverProposal = session.getAttribute(SERVER_PROPOSAL_KEY);
-        if (GenericUtils.isNotEmpty(clientProposal)) {
+        if (MapEntryUtils.isNotEmpty(clientProposal)) {
             if (debugEnabled) {
                 log.debug("isKexExtensionsAvailable({})[{}] already sent proposal={} (server={})",
                         session, phase, clientProposal, serverProposal);
@@ -108,7 +110,7 @@ public class DefaultClientKexExtensionHandler extends AbstractLoggingBean implem
          *
          * Therefore we want to be sure the server declared its support for extensions before we declare ours.
          */
-        if (GenericUtils.isEmpty(serverProposal)) {
+        if (MapEntryUtils.isEmpty(serverProposal)) {
             if (debugEnabled) {
                 log.debug("isKexExtensionsAvailable({})[{}] no server proposal", session, phase);
             }
@@ -164,7 +166,7 @@ public class DefaultClientKexExtensionHandler extends AbstractLoggingBean implem
             throws IOException {
         List<NamedFactory<Signature>> available = session.getSignatureFactories();
         List<NamedFactory<Signature>> updated = resolveUpdatedSignatureFactories(session, available, extraAlgos);
-        if (!GenericUtils.isSameReference(available, updated)) {
+        if (!UnaryEquator.isSameReference(available, updated)) {
             if (log.isDebugEnabled()) {
                 log.debug("updateAvailableSignatureFactories({}) available={}, updated={}",
                         session, available, updated);
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractConnectionService.java b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractConnectionService.java
index d1f3ac0..147ef16 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractConnectionService.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractConnectionService.java
@@ -62,10 +62,10 @@ import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.session.UnknownChannelReferenceHandler;
 import org.apache.sshd.common.util.EventListenerUtils;
 import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.Int2IntFunction;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.closeable.AbstractInnerCloseable;
+import org.apache.sshd.common.util.functors.Int2IntFunction;
 import org.apache.sshd.core.CoreModuleProperties;
 import org.apache.sshd.server.x11.DefaultX11ForwardSupport;
 import org.apache.sshd.server.x11.X11ForwardSupport;
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
index 41eae68..7a01066 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
@@ -79,6 +79,7 @@ import org.apache.sshd.common.session.SessionDisconnectHandler;
 import org.apache.sshd.common.session.SessionListener;
 import org.apache.sshd.common.session.SessionWorkBuffer;
 import org.apache.sshd.common.util.EventListenerUtils;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.NumberUtils;
 import org.apache.sshd.common.util.Readable;
@@ -898,7 +899,7 @@ public abstract class AbstractSession extends SessionHelper {
                                               + " to check re-key necessity: " + e.getMessage()),
                         e);
             } catch (Exception e) {
-                GenericUtils.rethrowAsIoException(e);
+                ExceptionUtils.rethrowAsIoException(e);
             }
         }
     }
@@ -2119,7 +2120,7 @@ public abstract class AbstractSession extends SessionHelper {
                                           + " to generate keys for exchange: " + e.getMessage()),
                     e);
         } catch (Exception e) {
-            GenericUtils.rethrowAsIoException(e);
+            ExceptionUtils.rethrowAsIoException(e);
             return null;    // actually dead code
         }
 
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/SessionHelper.java b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/SessionHelper.java
index 4de89bf..09a7a47 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/SessionHelper.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/SessionHelper.java
@@ -66,12 +66,14 @@ import org.apache.sshd.common.session.SessionDisconnectHandler;
 import org.apache.sshd.common.session.SessionListener;
 import org.apache.sshd.common.session.UnknownChannelReferenceHandler;
 import org.apache.sshd.common.session.helpers.TimeoutIndicator.TimeoutStatus;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.Invoker;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.BufferUtils;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.apache.sshd.common.util.io.functors.Invoker;
 import org.apache.sshd.common.util.net.SshdSocketAddress;
 import org.apache.sshd.core.CoreModuleProperties;
 
@@ -217,7 +219,7 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
         try {
             signalSessionEvent(SessionListener.Event.Authenticated);
         } catch (Exception e) {
-            GenericUtils.rethrowAsIoException(e);
+            ExceptionUtils.rethrowAsIoException(e);
         }
     }
 
@@ -571,7 +573,7 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
                 return null;
             });
         } catch (Throwable err) {
-            Throwable e = GenericUtils.peelException(err);
+            Throwable e = ExceptionUtils.peelException(err);
             debug("Failed ({}) to announce session={} established: {}",
                     e.getClass().getSimpleName(), ioSession, e.getMessage(), e);
             if (e instanceof Exception) {
@@ -596,7 +598,7 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
                 return null;
             });
         } catch (Throwable err) {
-            Throwable e = GenericUtils.peelException(err);
+            Throwable e = ExceptionUtils.peelException(err);
             debug("Failed ({}) to announce session={} created: {}",
                     e.getClass().getSimpleName(), ioSession, e.getMessage(), e);
             if (e instanceof Exception) {
@@ -621,7 +623,7 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
                 return null;
             });
         } catch (Throwable err) {
-            Throwable e = GenericUtils.peelException(err);
+            Throwable e = ExceptionUtils.peelException(err);
             if (e instanceof Exception) {
                 throw (Exception) e;
             } else {
@@ -645,7 +647,7 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
                 return null;
             });
         } catch (Throwable err) {
-            Throwable e = GenericUtils.peelException(err);
+            Throwable e = ExceptionUtils.peelException(err);
             debug("signalReadPeerIdentificationLine({}) Failed ({}) to announce peer={}: {}",
                     this, e.getClass().getSimpleName(), line, e.getMessage(), e);
             if (e instanceof Exception) {
@@ -672,7 +674,7 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
                 return null;
             });
         } catch (Throwable err) {
-            Throwable e = GenericUtils.peelException(err);
+            Throwable e = ExceptionUtils.peelException(err);
             debug("signalPeerIdentificationReceived({}) Failed ({}) to announce peer={}: {}",
                     this, e.getClass().getSimpleName(), version, e.getMessage(), e);
             if (e instanceof Exception) {
@@ -705,7 +707,7 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
                 return null;
             });
         } catch (Throwable err) {
-            Throwable t = GenericUtils.peelException(err);
+            Throwable t = ExceptionUtils.peelException(err);
             debug("sendSessionEvent({})[{}] failed ({}) to inform listeners: {}",
                     this, event, t.getClass().getSimpleName(), t.getMessage(), t);
             if (t instanceof Exception) {
@@ -740,7 +742,7 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
             try {
                 invoker.invoke(l);
             } catch (Throwable t) {
-                err = GenericUtils.accumulateException(err, t);
+                err = ExceptionUtils.accumulateException(err, t);
             }
         }
 
@@ -1005,7 +1007,7 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
                 current.clear(); // debug breakpoint
             }
 
-            if (GenericUtils.isEmpty(proposal)) {
+            if (MapEntryUtils.isEmpty(proposal)) {
                 return proposal; // debug breakpoint
             }
 
@@ -1022,7 +1024,7 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
                 return null;
             });
         } catch (Throwable t) {
-            Throwable err = GenericUtils.peelException(t);
+            Throwable err = ExceptionUtils.peelException(t);
             if (err instanceof RuntimeException) {
                 throw (RuntimeException) err;
             } else if (err instanceof Error) {
@@ -1049,7 +1051,7 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
                 return null;
             });
         } catch (Throwable t) {
-            Throwable err = GenericUtils.peelException(t);
+            Throwable err = ExceptionUtils.peelException(t);
             if (err instanceof RuntimeException) {
                 throw (RuntimeException) err;
             } else if (err instanceof Error) {
@@ -1078,7 +1080,7 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
                 return null;
             });
         } catch (Throwable t) {
-            Throwable err = GenericUtils.peelException(t);
+            Throwable err = ExceptionUtils.peelException(t);
             if (err instanceof RuntimeException) {
                 throw (RuntimeException) err;
             } else if (err instanceof Error) {
@@ -1196,7 +1198,7 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
                 return null;
             });
         } catch (Throwable err) {
-            Throwable e = GenericUtils.peelException(err);
+            Throwable e = ExceptionUtils.peelException(err);
             debug("signalDisconnect({}) {}: {}",
                     this, e.getClass().getSimpleName(), e.getMessage(), e);
         }
@@ -1256,7 +1258,7 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
                 return null;
             });
         } catch (Throwable err) {
-            Throwable e = GenericUtils.peelException(err);
+            Throwable e = ExceptionUtils.peelException(err);
             debug("signalExceptionCaught({}) {}: {}",
                     this, e.getClass().getSimpleName(), e.getMessage(), e);
         }
@@ -1277,7 +1279,7 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
                 return null;
             });
         } catch (Throwable err) {
-            Throwable e = GenericUtils.peelException(err);
+            Throwable e = ExceptionUtils.peelException(err);
             debug("signalSessionClosed({}) {} while signal session closed: {}",
                     this, e.getClass().getSimpleName(), e.getMessage(), e);
             // Do not re-throw since session closed anyway
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/AuthorizedKeyEntriesPublickeyAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/AuthorizedKeyEntriesPublickeyAuthenticator.java
index 8064971..de11dbd 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/AuthorizedKeyEntriesPublickeyAuthenticator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/AuthorizedKeyEntriesPublickeyAuthenticator.java
@@ -32,6 +32,7 @@ import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 import org.apache.sshd.server.session.ServerSession;
 
@@ -78,7 +79,7 @@ public class AuthorizedKeyEntriesPublickeyAuthenticator extends AbstractLoggingB
 
     @Override
     public boolean authenticate(String username, PublicKey key, ServerSession session) {
-        if (GenericUtils.isEmpty(resolvedKeys)) {
+        if (MapEntryUtils.isEmpty(resolvedKeys)) {
             if (log.isDebugEnabled()) {
                 log.debug("authenticate(" + username + ")[" + session + "] no entries");
             }
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/channel/AbstractServerChannel.java b/sshd-core/src/main/java/org/apache/sshd/server/channel/AbstractServerChannel.java
index 4583942..788ac55 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/channel/AbstractServerChannel.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/channel/AbstractServerChannel.java
@@ -33,7 +33,7 @@ import org.apache.sshd.common.channel.Channel;
 import org.apache.sshd.common.channel.RequestHandler;
 import org.apache.sshd.common.channel.Window;
 import org.apache.sshd.common.session.Session;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.threads.CloseableExecutorService;
 
@@ -87,7 +87,7 @@ public abstract class AbstractServerChannel extends AbstractChannel implements S
             signalChannelOpenSuccess();
             f.setOpened();
         } catch (Throwable t) {
-            Throwable e = GenericUtils.peelException(t);
+            Throwable e = ExceptionUtils.peelException(t);
             changeEvent = e.getClass().getSimpleName();
             signalChannelOpenFailure(e);
             f.setException(e);
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/channel/PuttyRequestHandler.java b/sshd-core/src/main/java/org/apache/sshd/server/channel/PuttyRequestHandler.java
index 8ad6254..310a8e4 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/channel/PuttyRequestHandler.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/channel/PuttyRequestHandler.java
@@ -29,6 +29,7 @@ import org.apache.sshd.common.channel.PtyMode;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 
 /**
@@ -113,7 +114,7 @@ public class PuttyRequestHandler extends AbstractChannelRequestHandler {
 
     public static Map<PtyMode, Integer> resolveShellTtyOptions(Map<PtyMode, Integer> modes) {
         Map<PtyMode, Integer> resolved = PtyMode.createEnabledOptions(PUTTY_OPTIONS);
-        if (GenericUtils.size(modes) > 0) {
+        if (MapEntryUtils.size(modes) > 0) {
             resolved.putAll(modes); // TODO consider adding only non-overriding options
         }
 
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/config/SshServerConfigFileReader.java b/sshd-core/src/main/java/org/apache/sshd/server/config/SshServerConfigFileReader.java
index 9eb79aa..8a7057e 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/config/SshServerConfigFileReader.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/config/SshServerConfigFileReader.java
@@ -29,6 +29,7 @@ import org.apache.sshd.common.config.ConfigFileReaderSupport;
 import org.apache.sshd.common.config.SshConfigFileReader;
 import org.apache.sshd.common.session.SessionHeartbeatController.HeartbeatType;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.core.CoreModuleProperties;
 import org.apache.sshd.server.ServerBuilder;
@@ -81,7 +82,7 @@ public final class SshServerConfigFileReader {
     }
 
     public static <S extends ServerFactoryManager> S setupServerHeartbeat(S server, Map<String, ?> options) {
-        if ((server == null) || GenericUtils.isEmpty(options)) {
+        if ((server == null) || MapEntryUtils.isEmpty(options)) {
             return server;
         }
 
@@ -110,7 +111,7 @@ public final class SshServerConfigFileReader {
     }
 
     public static ForwardingFilter resolveServerForwarding(PropertyResolver options) {
-        if (GenericUtils.isEmpty(options)) {
+        if (PropertyResolver.isEmpty(options)) {
             return AcceptAllForwardingFilter.INSTANCE;
         }
 
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/ServerIdentity.java b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/ServerIdentity.java
index 8b08ca9..c1b38a9 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/ServerIdentity.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/ServerIdentity.java
@@ -36,6 +36,7 @@ import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.config.keys.IdentityUtils;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.server.SshServer;
@@ -154,7 +155,7 @@ public final class ServerIdentity {
 
     private static Map<String, Path> getLocations(String configPropKey, Properties props, LinkOption... options)
             throws IOException {
-        if (GenericUtils.isEmpty(props)) {
+        if (MapEntryUtils.isEmpty(props)) {
             return Collections.emptyMap();
         }
 
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/forward/TcpipServerChannel.java b/sshd-core/src/main/java/org/apache/sshd/server/forward/TcpipServerChannel.java
index 0581ed4..7ea6c72 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/forward/TcpipServerChannel.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/forward/TcpipServerChannel.java
@@ -52,7 +52,7 @@ import org.apache.sshd.common.io.IoServiceFactory;
 import org.apache.sshd.common.io.IoSession;
 import org.apache.sshd.common.io.IoWriteFuture;
 import org.apache.sshd.common.session.Session;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.Readable;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
@@ -305,12 +305,12 @@ public class TcpipServerChannel extends AbstractServerChannel implements Streami
                 return;
             }
 
-            Throwable problem = GenericUtils.peelException(future.getException());
+            Throwable problem = ExceptionUtils.peelException(future.getException());
             if (problem != null) {
                 handleChannelOpenFailure(f, problem);
             }
         } catch (RuntimeException t) {
-            Throwable e = GenericUtils.peelException(t);
+            Throwable e = ExceptionUtils.peelException(t);
             signalChannelOpenFailure(e);
             try {
                 f.setException(e);
@@ -328,7 +328,7 @@ public class TcpipServerChannel extends AbstractServerChannel implements Streami
             signalChannelOpenSuccess();
             f.setOpened();
         } catch (Throwable t) {
-            Throwable e = GenericUtils.peelException(t);
+            Throwable e = ExceptionUtils.peelException(t);
             changeEvent = e.getClass().getSimpleName();
             signalChannelOpenFailure(e);
             f.setException(e);
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/global/CancelTcpipForwardHandler.java b/sshd-core/src/main/java/org/apache/sshd/server/global/CancelTcpipForwardHandler.java
index d06fb0a..a2fc226 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/global/CancelTcpipForwardHandler.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/global/CancelTcpipForwardHandler.java
@@ -26,8 +26,8 @@ import org.apache.sshd.common.forward.Forwarder;
 import org.apache.sshd.common.session.ConnectionService;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.session.helpers.AbstractConnectionServiceRequestHandler;
-import org.apache.sshd.common.util.Int2IntFunction;
 import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.functors.Int2IntFunction;
 import org.apache.sshd.common.util.net.SshdSocketAddress;
 
 /**
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/global/TcpipForwardHandler.java b/sshd-core/src/main/java/org/apache/sshd/server/global/TcpipForwardHandler.java
index 9ccac7d..b64d1b2 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/global/TcpipForwardHandler.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/global/TcpipForwardHandler.java
@@ -26,8 +26,8 @@ import org.apache.sshd.common.forward.Forwarder;
 import org.apache.sshd.common.session.ConnectionService;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.session.helpers.AbstractConnectionServiceRequestHandler;
-import org.apache.sshd.common.util.Int2IntFunction;
 import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.functors.Int2IntFunction;
 import org.apache.sshd.common.util.net.SshdSocketAddress;
 
 /**
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java b/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java
index 5cff5f9..bb0fbe5 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java
@@ -56,6 +56,7 @@ import org.apache.sshd.common.session.SessionDisconnectHandler;
 import org.apache.sshd.common.session.helpers.AbstractSession;
 import org.apache.sshd.common.signature.SignatureFactory;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
@@ -552,7 +553,7 @@ public abstract class AbstractServerSession extends AbstractSession implements S
         IoSession networkSession = getIoSession();
         IoService service = networkSession.getService();
         Map<?, IoSession> sessionsMap = service.getManagedSessions();
-        if (GenericUtils.isEmpty(sessionsMap)) {
+        if (MapEntryUtils.isEmpty(sessionsMap)) {
             return 0;
         }
 
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShellWrapper.java b/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShellWrapper.java
index 29b6573..02762c5 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShellWrapper.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShellWrapper.java
@@ -27,6 +27,7 @@ import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 
 import org.apache.sshd.common.RuntimeSshException;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
@@ -148,7 +149,7 @@ public class InvertedShellWrapper extends AbstractLoggingBean implements Command
         } catch (Throwable e) {
             warn("destroy({}) failed ({}) to destroy shell: {}",
                     this, e.getClass().getSimpleName(), e.getMessage(), e);
-            err = GenericUtils.accumulateException(err, e);
+            err = ExceptionUtils.accumulateException(err, e);
         }
 
         if (shutdownExecutor && (executor instanceof ExecutorService)) {
@@ -157,7 +158,7 @@ public class InvertedShellWrapper extends AbstractLoggingBean implements Command
             } catch (Exception e) {
                 warn("destroy({}) failed ({}) to shut down executor: {}",
                         this, e.getClass().getSimpleName(), e.getMessage(), e);
-                err = GenericUtils.accumulateException(err, e);
+                err = ExceptionUtils.accumulateException(err, e);
             }
         }
 
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShell.java b/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShell.java
index 0bb881c..32a93cc 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShell.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShell.java
@@ -31,6 +31,7 @@ import java.util.Objects;
 
 import org.apache.sshd.common.channel.PtyMode;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
@@ -100,7 +101,7 @@ public class ProcessShell extends AbstractLoggingBean implements InvertedShell {
         }
 
         ProcessBuilder builder = new ProcessBuilder(command);
-        if (GenericUtils.size(varsMap) > 0) {
+        if (MapEntryUtils.size(varsMap) > 0) {
             try {
                 Map<String, String> procEnv = builder.environment();
                 procEnv.putAll(varsMap);
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/keyverifier/KnownHostsServerKeyVerifierTest.java b/sshd-core/src/test/java/org/apache/sshd/client/keyverifier/KnownHostsServerKeyVerifierTest.java
index 98fd5b0..67cdba0 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/keyverifier/KnownHostsServerKeyVerifierTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/keyverifier/KnownHostsServerKeyVerifierTest.java
@@ -50,6 +50,7 @@ import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
 import org.apache.sshd.common.mac.Mac;
 import org.apache.sshd.common.random.JceRandomFactory;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.net.SshdSocketAddress;
 import org.apache.sshd.util.test.BaseTestSupport;
@@ -395,7 +396,7 @@ public class KnownHostsServerKeyVerifierTest extends BaseTestSupport {
         assertTrue("Accepted on port=" + port2 + " ?", accepted2);
 
         Map<SshdSocketAddress, KnownHostEntry> updatedKeys = loadEntries(path);
-        assertEquals("Mismatched total entries count", 2, GenericUtils.size(updatedKeys));
+        assertEquals("Mismatched total entries count", 2, MapEntryUtils.size(updatedKeys));
     }
 
     private Path createKnownHostsCopy() throws IOException {
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/PropertyResolverUtilsTest.java b/sshd-core/src/test/java/org/apache/sshd/common/PropertyResolverUtilsTest.java
index 323fbaf..92471c9 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/PropertyResolverUtilsTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/PropertyResolverUtilsTest.java
@@ -29,7 +29,7 @@ import java.util.TreeMap;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.sshd.common.session.Session;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.util.test.JUnitTestSupport;
 import org.apache.sshd.util.test.NoIoTestCase;
 import org.junit.FixMethodOrder;
@@ -66,7 +66,7 @@ public class PropertyResolverUtilsTest extends JUnitTestSupport {
     public void testSyspropsResolver() {
         PropertyResolver resolver = SyspropsMapWrapper.SYSPROPS_RESOLVER;
         Map<String, ?> props = resolver.getProperties();
-        assertTrue("Unexpected initial resolver values: " + props, GenericUtils.isEmpty(props));
+        assertTrue("Unexpected initial resolver values: " + props, MapEntryUtils.isEmpty(props));
 
         final String propName = getCurrentTestName();
         assertNull("Unexpected initial resolved value", PropertyResolverUtils.getObject(resolver, propName));
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/auth/KeyboardInteractiveAuthenticationTest.java b/sshd-core/src/test/java/org/apache/sshd/common/auth/KeyboardInteractiveAuthenticationTest.java
index feeabf3..5a37857 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/auth/KeyboardInteractiveAuthenticationTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/auth/KeyboardInteractiveAuthenticationTest.java
@@ -32,6 +32,7 @@ import org.apache.sshd.client.auth.keyboard.UserInteraction;
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.MapEntryUtils.NavigableMapBuilder;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.core.CoreModuleProperties;
@@ -161,7 +162,7 @@ public class KeyboardInteractiveAuthenticationTest extends AuthenticationTestSup
                     ServerSession session, String username, List<String> responses)
                     throws Exception {
                 assertEquals("Unexpected authenticate call", 1, authCount.incrementAndGet());
-                assertEquals("Mismatched number of responses", GenericUtils.size(rspMap), GenericUtils.size(responses));
+                assertEquals("Mismatched number of responses", MapEntryUtils.size(rspMap), GenericUtils.size(responses));
 
                 int index = 0;
                 // Cannot use forEach because the index is not effectively final
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java b/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java
index c70e303..f9f1ead 100644
--- a/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java
@@ -65,6 +65,7 @@ import org.apache.sshd.common.session.helpers.AbstractSession;
 import org.apache.sshd.common.session.helpers.TimeoutIndicator;
 import org.apache.sshd.common.session.helpers.TimeoutIndicator.TimeoutStatus;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.MapEntryUtils.NavigableMapBuilder;
 import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.core.CoreModuleProperties;
@@ -774,7 +775,7 @@ public class ServerTest extends BaseTestSupport {
         assertNotNull("No environment set", cmdEnv);
 
         Map<String, String> vars = cmdEnv.getEnv();
-        assertTrue("Mismatched vars count", GenericUtils.size(vars) >= GenericUtils.size(expected));
+        assertTrue("Mismatched vars count", MapEntryUtils.size(vars) >= MapEntryUtils.size(expected));
         expected.forEach((key, expValue) -> {
             String actValue = vars.get(key);
             assertEquals("Mismatched value for " + key, expValue, actValue);
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/config/keys/ServerIdentityTest.java b/sshd-core/src/test/java/org/apache/sshd/server/config/keys/ServerIdentityTest.java
index 1665dc9..fb57a6d 100644
--- a/sshd-core/src/test/java/org/apache/sshd/server/config/keys/ServerIdentityTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/server/config/keys/ServerIdentityTest.java
@@ -33,6 +33,7 @@ import org.apache.sshd.common.config.keys.BuiltinIdentities;
 import org.apache.sshd.common.config.keys.IdentityUtils;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.util.test.BaseTestSupport;
 import org.apache.sshd.util.test.NoIoTestCase;
@@ -78,7 +79,7 @@ public class ServerIdentityTest extends BaseTestSupport {
         props.setProperty(ServerIdentity.HOST_KEY_CONFIG_PROP, GenericUtils.join(paths, ','));
 
         Map<String, KeyPair> ids = ServerIdentity.loadIdentities(props, options);
-        assertEquals("Mismatched loaded ids count", GenericUtils.size(paths), GenericUtils.size(ids));
+        assertEquals("Mismatched loaded ids count", GenericUtils.size(paths), MapEntryUtils.size(ids));
 
         Collection<KeyPair> pairs = new ArrayList<>(ids.size());
         for (BuiltinIdentities type : BuiltinIdentities.VALUES) {
diff --git a/sshd-git/src/main/java/org/apache/sshd/git/transport/GitSshdSessionFactory.java b/sshd-git/src/main/java/org/apache/sshd/git/transport/GitSshdSessionFactory.java
index c793ab3..f4b0b81 100644
--- a/sshd-git/src/main/java/org/apache/sshd/git/transport/GitSshdSessionFactory.java
+++ b/sshd-git/src/main/java/org/apache/sshd/git/transport/GitSshdSessionFactory.java
@@ -25,7 +25,7 @@ import org.apache.sshd.client.SshClient;
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.client.session.ClientSessionHolder;
 import org.apache.sshd.common.session.SessionHolder;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.functors.UnaryEquator;
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.transport.CredentialsProvider;
 import org.eclipse.jgit.transport.RemoteSession;
@@ -113,7 +113,7 @@ public class GitSshdSessionFactory
                 @Override
                 protected void disconnectSession(ClientSession sessionInstance) {
                     ClientSession thisSession = getClientSession();
-                    if (GenericUtils.isSameReference(thisSession, sessionInstance)) {
+                    if (UnaryEquator.isSameReference(thisSession, sessionInstance)) {
                         return; // do not use the session instance we were given
                     }
 
@@ -123,7 +123,7 @@ public class GitSshdSessionFactory
                 @Override
                 protected void disconnectClient(SshClient clientInstance) {
                     SshClient thisClient = getClient();
-                    if (GenericUtils.isSameReference(thisClient, clientInstance)) {
+                    if (UnaryEquator.isSameReference(thisClient, clientInstance)) {
                         return; // do not close the client the user gave us
                     }
 
diff --git a/sshd-ldap/src/test/java/org/apache/sshd/ldap/LdapPasswordAuthenticatorTest.java b/sshd-ldap/src/test/java/org/apache/sshd/ldap/LdapPasswordAuthenticatorTest.java
index a40978c..13b260d 100644
--- a/sshd-ldap/src/test/java/org/apache/sshd/ldap/LdapPasswordAuthenticatorTest.java
+++ b/sshd-ldap/src/test/java/org/apache/sshd/ldap/LdapPasswordAuthenticatorTest.java
@@ -27,7 +27,7 @@ import org.apache.directory.server.core.annotations.ApplyLdifFiles;
 import org.apache.directory.server.core.annotations.CreateDS;
 import org.apache.directory.server.core.annotations.CreatePartition;
 import org.apache.directory.server.core.integ.CreateLdapServerRule;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.server.session.ServerSession;
 import org.junit.ClassRule;
 import org.junit.FixMethodOrder;
@@ -59,7 +59,7 @@ public class LdapPasswordAuthenticatorTest extends BaseAuthenticatorTest {
     public void testPasswordComparison() throws Exception {
         usersMap = populateUsers(serverRule.getLdapServer().getDirectoryService(),
                 LdapPasswordAuthenticatorTest.class, LdapPasswordAuthenticator.DEFAULT_PASSWORD_ATTR_NAME);
-        assertFalse("No users retrieved", GenericUtils.isEmpty(usersMap));
+        assertFalse("No users retrieved", MapEntryUtils.isEmpty(usersMap));
 
         LdapPasswordAuthenticator auth = new LdapPasswordAuthenticator();
         auth.setHost(getHost(serverRule.getLdapServer()));
diff --git a/sshd-ldap/src/test/java/org/apache/sshd/ldap/LdapPublickeyAuthenticatorTest.java b/sshd-ldap/src/test/java/org/apache/sshd/ldap/LdapPublickeyAuthenticatorTest.java
index b7f1ac7..1d899e7 100644
--- a/sshd-ldap/src/test/java/org/apache/sshd/ldap/LdapPublickeyAuthenticatorTest.java
+++ b/sshd-ldap/src/test/java/org/apache/sshd/ldap/LdapPublickeyAuthenticatorTest.java
@@ -35,7 +35,7 @@ import org.apache.directory.server.core.integ.CreateLdapServerRule;
 import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.server.session.ServerSession;
 import org.junit.ClassRule;
 import org.junit.FixMethodOrder;
@@ -69,7 +69,7 @@ public class LdapPublickeyAuthenticatorTest extends BaseAuthenticatorTest {
     public void testPublicKeyComparison() throws Exception {
         Map<String, String> credentials = populateUsers(serverRule.getLdapServer().getDirectoryService(),
                 LdapPublickeyAuthenticatorTest.class, TEST_ATTR_NAME);
-        assertFalse("No keys retrieved", GenericUtils.isEmpty(credentials));
+        assertFalse("No keys retrieved", MapEntryUtils.isEmpty(credentials));
 
         // Cannot use forEach because of the potential GeneraSecurityException being thrown
         for (Map.Entry<String, String> ce : credentials.entrySet()) {
diff --git a/sshd-mina/src/main/java/org/apache/sshd/mina/MinaService.java b/sshd-mina/src/main/java/org/apache/sshd/mina/MinaService.java
index 99f4712..77c0a9a 100644
--- a/sshd-mina/src/main/java/org/apache/sshd/mina/MinaService.java
+++ b/sshd-mina/src/main/java/org/apache/sshd/mina/MinaService.java
@@ -37,7 +37,7 @@ import org.apache.sshd.common.Closeable;
 import org.apache.sshd.common.FactoryManager;
 import org.apache.sshd.common.Property;
 import org.apache.sshd.common.io.IoServiceEventListener;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.Readable;
 import org.apache.sshd.common.util.closeable.AbstractCloseable;
 import org.apache.sshd.core.CoreModuleProperties;
@@ -213,7 +213,7 @@ public abstract class MinaService extends AbstractCloseable
 
     protected void handleConfigurationError(
             SocketSessionConfig config, Property<?> property, Object propValue, RuntimeIoException t) {
-        Throwable e = GenericUtils.resolveExceptionCause(t);
+        Throwable e = ExceptionUtils.resolveExceptionCause(t);
         log.warn("handleConfigurationError({}={}) failed ({}) to configure: {}",
                 property.getName(), propValue, e.getClass().getSimpleName(), e.getMessage());
     }
diff --git a/sshd-mina/src/main/java/org/apache/sshd/mina/MinaSession.java b/sshd-mina/src/main/java/org/apache/sshd/mina/MinaSession.java
index 04c7ea7..722d82e 100644
--- a/sshd-mina/src/main/java/org/apache/sshd/mina/MinaSession.java
+++ b/sshd-mina/src/main/java/org/apache/sshd/mina/MinaSession.java
@@ -39,7 +39,7 @@ import org.apache.sshd.common.io.AbstractIoWriteFuture;
 import org.apache.sshd.common.io.IoService;
 import org.apache.sshd.common.io.IoSession;
 import org.apache.sshd.common.io.IoWriteFuture;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.NumberUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.closeable.AbstractInnerCloseable;
@@ -228,7 +228,7 @@ public class MinaSession extends AbstractInnerCloseable implements IoSession {
         try {
             channel = (Channel) NIO_SESSION_CHANNEL_FIELD.get(session);
         } catch (Exception t) {
-            Throwable e = GenericUtils.peelException(t);
+            Throwable e = ExceptionUtils.peelException(t);
             log.warn("shudownOutputStream({}) failed ({}) to retrieve embedded channel: {}",
                     session, e.getClass().getSimpleName(), e.getMessage());
             return;
diff --git a/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoSession.java b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoSession.java
index 01ab21d..143e661 100644
--- a/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoSession.java
+++ b/sshd-netty/src/main/java/org/apache/sshd/netty/NettyIoSession.java
@@ -47,6 +47,7 @@ import org.apache.sshd.common.io.IoHandler;
 import org.apache.sshd.common.io.IoService;
 import org.apache.sshd.common.io.IoSession;
 import org.apache.sshd.common.io.IoWriteFuture;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.closeable.AbstractCloseable;
@@ -186,7 +187,7 @@ public class NettyIoSession extends AbstractCloseable implements IoSession {
         try {
             channel = (SelectableChannel) NIO_JAVA_CHANNEL_METHOD.invoke(ch, GenericUtils.EMPTY_OBJECT_ARRAY);
         } catch (Exception t) {
-            Throwable e = GenericUtils.peelException(t);
+            Throwable e = ExceptionUtils.peelException(t);
             log.warn("shudownOutputStream({}) failed ({}) to retrieve embedded channel: {}",
                     this, e.getClass().getSimpleName(), e.getMessage());
             return;
diff --git a/sshd-openpgp/src/main/java/org/apache/sshd/openpgp/PGPAuthorizedEntriesTracker.java b/sshd-openpgp/src/main/java/org/apache/sshd/openpgp/PGPAuthorizedEntriesTracker.java
index 70b0f93..8788b18 100644
--- a/sshd-openpgp/src/main/java/org/apache/sshd/openpgp/PGPAuthorizedEntriesTracker.java
+++ b/sshd-openpgp/src/main/java/org/apache/sshd/openpgp/PGPAuthorizedEntriesTracker.java
@@ -38,6 +38,7 @@ import org.apache.sshd.common.config.keys.FilePasswordProviderManager;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.io.resource.PathResource;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 import org.apache.sshd.common.util.security.SecurityUtils;
@@ -123,7 +124,7 @@ public class PGPAuthorizedEntriesTracker
             PathResource resourceKey = f.toPathResource();
             Key container = f.loadPublicKey(session, resourceKey, provider);
             Map<String, Subkey> fpMap = PGPUtils.mapSubKeysByFingerprint(container);
-            int numSubKeys = GenericUtils.size(fpMap);
+            int numSubKeys = MapEntryUtils.size(fpMap);
             Collection<Subkey> matches = (numSubKeys <= 0)
                     ? Collections.emptyList()
                     : fpMap.entrySet()
diff --git a/sshd-openpgp/src/main/java/org/apache/sshd/openpgp/PGPAuthorizedKeyEntriesLoader.java b/sshd-openpgp/src/main/java/org/apache/sshd/openpgp/PGPAuthorizedKeyEntriesLoader.java
index 3f9225c..11fcdac 100644
--- a/sshd-openpgp/src/main/java/org/apache/sshd/openpgp/PGPAuthorizedKeyEntriesLoader.java
+++ b/sshd-openpgp/src/main/java/org/apache/sshd/openpgp/PGPAuthorizedKeyEntriesLoader.java
@@ -36,6 +36,7 @@ import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
 import org.apache.sshd.common.keyprovider.KeyTypeIndicator;
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.bouncycastle.openpgp.PGPException;
 
 /**
@@ -82,7 +83,7 @@ public interface PGPAuthorizedKeyEntriesLoader extends PGPPublicKeyExtractor, Pu
             SessionContext session, Collection<? extends PublicKeyEntry> entries, PublicKeyEntryResolver fallbackResolver)
             throws IOException, GeneralSecurityException, PGPException {
         Map<String, ? extends Collection<PublicKeyEntry>> typesMap = KeyTypeIndicator.groupByKeyType(entries);
-        if (GenericUtils.isEmpty(typesMap)) {
+        if (MapEntryUtils.isEmpty(typesMap)) {
             return Collections.emptyList();
         }
 
diff --git a/sshd-openpgp/src/main/java/org/apache/sshd/openpgp/PGPPublicRingWatcher.java b/sshd-openpgp/src/main/java/org/apache/sshd/openpgp/PGPPublicRingWatcher.java
index 8855e7d..7611338 100644
--- a/sshd-openpgp/src/main/java/org/apache/sshd/openpgp/PGPPublicRingWatcher.java
+++ b/sshd-openpgp/src/main/java/org/apache/sshd/openpgp/PGPPublicRingWatcher.java
@@ -41,6 +41,8 @@ import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
+import org.apache.sshd.common.util.functors.UnaryEquator;
 import org.apache.sshd.common.util.io.ModifiableFileWatcher;
 import org.apache.sshd.common.util.io.resource.IoResource;
 import org.apache.sshd.common.util.io.resource.PathResource;
@@ -86,7 +88,7 @@ public class PGPPublicRingWatcher extends ModifiableFileWatcher implements PGPAu
         }
 
         Map<String, PublicKey> keysMap = resolveRingKeys(session);
-        if (GenericUtils.isEmpty(keysMap)) {
+        if (MapEntryUtils.isEmpty(keysMap)) {
             return Collections.emptyList();
         }
 
@@ -109,7 +111,7 @@ public class PGPPublicRingWatcher extends ModifiableFileWatcher implements PGPAu
     protected NavigableMap<String, PublicKey> resolveRingKeys(SessionContext session)
             throws IOException, GeneralSecurityException, PGPException {
         NavigableMap<String, PublicKey> keysMap = ringKeys.get();
-        if (GenericUtils.isEmpty(keysMap) || checkReloadRequired()) {
+        if (MapEntryUtils.isEmpty(keysMap) || checkReloadRequired()) {
             ringKeys.set(Collections.emptyNavigableMap()); // mark stale
 
             if (!exists()) {
@@ -119,7 +121,7 @@ public class PGPPublicRingWatcher extends ModifiableFileWatcher implements PGPAu
             Path file = getPath();
             keysMap = reloadRingKeys(session, new PathResource(file));
 
-            int numKeys = GenericUtils.size(keysMap);
+            int numKeys = MapEntryUtils.size(keysMap);
             if (log.isDebugEnabled()) {
                 log.debug("resolveRingKeys({}) reloaded {} keys from {}", session, numKeys, file);
             }
@@ -184,7 +186,7 @@ public class PGPPublicRingWatcher extends ModifiableFileWatcher implements PGPAu
                     PublicKey effective = handleDuplicateKeyFingerprint(session, resourceKey, fp, sk, prev, pubKey);
                     if (effective == null) {
                         keysMap.remove(fp);
-                    } else if (!GenericUtils.isSameReference(effective, pubKey)) {
+                    } else if (!UnaryEquator.isSameReference(effective, pubKey)) {
                         keysMap.put(fp, effective);
                     }
                 }
diff --git a/sshd-openpgp/src/test/java/org/apache/sshd/openpgp/PGPPublicRingWatcherTest.java b/sshd-openpgp/src/test/java/org/apache/sshd/openpgp/PGPPublicRingWatcherTest.java
index 90868eb..fb8ebd7 100644
--- a/sshd-openpgp/src/test/java/org/apache/sshd/openpgp/PGPPublicRingWatcherTest.java
+++ b/sshd-openpgp/src/test/java/org/apache/sshd/openpgp/PGPPublicRingWatcherTest.java
@@ -25,7 +25,7 @@ import java.util.Map;
 import java.util.NavigableMap;
 
 import org.apache.sshd.common.config.keys.KeyUtils;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.io.resource.PathResource;
 import org.apache.sshd.util.test.CommonTestSupportUtils;
 import org.apache.sshd.util.test.JUnitTestSupport;
@@ -62,7 +62,7 @@ public class PGPPublicRingWatcherTest extends JUnitTestSupport {
                 detectSourcesFolder(), TEST_SUBFOLDER, RESOURCES_SUBFOLDER, "keyring");
         Path file = dir.resolve(PGPPublicRingWatcher.GPG_V1_PUBLIC_RING_FILENAME);
         Map<String, PublicKey> keys = testPublicRingWatcher(file);
-        assertFalse("No keys extracted", GenericUtils.isEmpty(keys));
+        assertFalse("No keys extracted", MapEntryUtils.isEmpty(keys));
     }
 
     @Test
@@ -71,13 +71,13 @@ public class PGPPublicRingWatcherTest extends JUnitTestSupport {
                 detectSourcesFolder(), TEST_SUBFOLDER, RESOURCES_SUBFOLDER, "kbx2ring");
         Path file = dir.resolve(PGPPublicRingWatcher.GPG_V2_PUBLIC_RING_FILENAME);
         Map<String, PublicKey> keys = testPublicRingWatcher(file);
-        assertFalse("No keys extracted", GenericUtils.isEmpty(keys));
+        assertFalse("No keys extracted", MapEntryUtils.isEmpty(keys));
     }
 
     private NavigableMap<String, PublicKey> testPublicRingWatcher(Path file) throws Exception {
         PGPPublicRingWatcher watcher = new PGPPublicRingWatcher(file);
         NavigableMap<String, PublicKey> keys = watcher.reloadRingKeys(null, new PathResource(file));
-        int numKeys = GenericUtils.size(keys);
+        int numKeys = MapEntryUtils.size(keys);
         outputDebugMessage("%s: Loaded %d keys from %s", getCurrentTestName(), numKeys, file);
 
         if (numKeys > 0) {
diff --git a/sshd-putty/src/test/java/org/apache/sshd/putty/PuttyKeyUtilsTest.java b/sshd-putty/src/test/java/org/apache/sshd/putty/PuttyKeyUtilsTest.java
index 239f3a0..25dd011 100644
--- a/sshd-putty/src/test/java/org/apache/sshd/putty/PuttyKeyUtilsTest.java
+++ b/sshd-putty/src/test/java/org/apache/sshd/putty/PuttyKeyUtilsTest.java
@@ -38,6 +38,7 @@ import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
 import org.apache.sshd.common.config.keys.loader.openssh.OpenSSHKeyPairResourceParser;
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.functors.UnaryEquator;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
 import org.apache.sshd.util.test.JUnitTestSupport;
@@ -88,7 +89,7 @@ public class PuttyKeyUtilsTest extends JUnitTestSupport {
     public void testCanDecodePuttyKeyFile() throws IOException, GeneralSecurityException {
         for (String resource : new String[] { regularFile, encryptedFile }) {
             URL url = getClass().getResource(resource);
-            if (GenericUtils.isSameReference(regularFile, resource)) {
+            if (UnaryEquator.isSameReference(regularFile, resource)) {
                 assertNotNull("Missing test resource: " + resource, url);
             } else {
                 if (url == null) {
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 54b865f..a970e8c 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
@@ -26,7 +26,7 @@ import java.util.Objects;
 
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.client.simple.SimpleClient;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.io.functors.IOFunction;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 
@@ -109,7 +109,7 @@ public class SimpleScpClientImpl extends AbstractLoggingBean implements SimpleSc
                 e.addSuppressed(t);
             }
 
-            GenericUtils.rethrowAsIoException(e);
+            ExceptionUtils.rethrowAsIoException(e);
             return null;    // actually dead code...
         }
     }
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 47bafb3..b207f31 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
@@ -47,6 +47,7 @@ import org.apache.sshd.client.subsystem.SubsystemClient;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.common.util.functors.UnaryEquator;
 import org.apache.sshd.sftp.SftpModuleProperties;
 import org.apache.sshd.sftp.client.extensions.BuiltinSftpClientExtensions;
 import org.apache.sshd.sftp.client.extensions.SftpClientExtension;
@@ -465,7 +466,7 @@ public interface SftpClient extends SubsystemClient {
 
     class DirEntry {
         public static final Comparator<DirEntry> BY_CASE_SENSITIVE_FILENAME = (e1, e2) -> {
-            if (GenericUtils.isSameReference(e1, e2)) {
+            if (UnaryEquator.isSameReference(e1, e2)) {
                 return 0;
             } else if (e1 == null) {
                 return 1;
@@ -477,7 +478,7 @@ public interface SftpClient extends SubsystemClient {
         };
 
         public static final Comparator<DirEntry> BY_CASE_INSENSITIVE_FILENAME = (e1, e2) -> {
-            if (GenericUtils.isSameReference(e1, e2)) {
+            if (UnaryEquator.isSameReference(e1, e2)) {
                 return 0;
             } else if (e1 == null) {
                 return 1;
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractSftpClientExtension.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractSftpClientExtension.java
index e57579a..61a5cbf 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractSftpClientExtension.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractSftpClientExtension.java
@@ -28,6 +28,7 @@ import java.util.Objects;
 
 import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
@@ -53,7 +54,7 @@ public abstract class AbstractSftpClientExtension extends AbstractLoggingBean im
     }
 
     protected AbstractSftpClientExtension(String name, SftpClient client, RawSftpClient raw, Map<String, byte[]> extensions) {
-        this(name, client, raw, GenericUtils.isNotEmpty(extensions) && extensions.containsKey(name));
+        this(name, client, raw, MapEntryUtils.isNotEmpty(extensions) && extensions.containsKey(name));
     }
 
     protected AbstractSftpClientExtension(String name, SftpClient client, RawSftpClient raw, boolean supported) {
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpFileSystemProvider.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpFileSystemProvider.java
index 7c3e9c1..c5e0f0d 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpFileSystemProvider.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpFileSystemProvider.java
@@ -81,6 +81,7 @@ import org.apache.sshd.common.auth.BasicCredentialsProvider;
 import org.apache.sshd.common.auth.MutableBasicCredentials;
 import org.apache.sshd.common.io.IoSession;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.NumberUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.io.IoUtils;
@@ -232,7 +233,7 @@ public class SftpFileSystemProvider extends FileSystemProvider {
                 session = initializer.createClientSession(this, context);
 
                 // Make any extra configuration parameters available to the session
-                if (GenericUtils.size(params) > 0) {
+                if (MapEntryUtils.size(params) > 0) {
                     // Cannot use forEach because the session is not effectively final
                     for (Map.Entry<String, ?> pe : params.entrySet()) {
                         String key = pe.getKey();
@@ -312,9 +313,9 @@ public class SftpFileSystemProvider extends FileSystemProvider {
 
     // NOTE: URI parameters override environment ones
     public static Map<String, Object> resolveFileSystemParameters(Map<String, ?> env, Map<String, Object> uriParams) {
-        if (GenericUtils.isEmpty(env)) {
-            return GenericUtils.isEmpty(uriParams) ? Collections.emptyMap() : uriParams;
-        } else if (GenericUtils.isEmpty(uriParams)) {
+        if (MapEntryUtils.isEmpty(env)) {
+            return MapEntryUtils.isEmpty(uriParams) ? Collections.emptyMap() : uriParams;
+        } else if (MapEntryUtils.isEmpty(uriParams)) {
             return Collections.unmodifiableMap(env);
         }
 
@@ -1329,7 +1330,7 @@ public class SftpFileSystemProvider extends FileSystemProvider {
         ValidateUtils.checkNotNullAndNotEmpty(host, "No host provided");
 
         String queryPart = null;
-        int numParams = GenericUtils.size(params);
+        int numParams = MapEntryUtils.size(params);
         if (numParams > 0) {
             StringBuilder sb = new StringBuilder(numParams * Short.SIZE);
             for (Map.Entry<String, ?> pe : params.entrySet()) {
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/DefaultSftpClient.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/DefaultSftpClient.java
index ccc15d0..1df279c 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/DefaultSftpClient.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/DefaultSftpClient.java
@@ -52,6 +52,7 @@ import org.apache.sshd.common.io.IoWriteFuture;
 import org.apache.sshd.common.session.ConnectionService;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
@@ -488,7 +489,7 @@ public class DefaultSftpClient extends AbstractSftpClient {
         List<Integer> availableVersions = Collections.emptyList();
         if ((GenericUtils.size(extensions) > 0)
                 && extensions.contains(SftpConstants.EXT_VERSION_SELECT)) {
-            Versions vers = GenericUtils.isEmpty(parsed)
+            Versions vers = MapEntryUtils.isEmpty(parsed)
                     ? null
                     : (Versions) parsed.get(SftpConstants.EXT_VERSIONS);
             availableVersions = (vers == null)
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 8cf82b0..c91e4b8 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
@@ -25,7 +25,7 @@ import java.security.KeyPair;
 
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.client.simple.SimpleClient;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.io.functors.IOFunction;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 import org.apache.sshd.sftp.client.SftpClient;
@@ -102,7 +102,7 @@ public class SimpleSftpClientImpl extends AbstractLoggingBean implements SimpleS
                 client = null; // disable auto-close at finally block
                 return closer;
             } catch (Exception e) {
-                err = GenericUtils.accumulateException(err, e);
+                err = ExceptionUtils.accumulateException(err, e);
             } finally {
                 if (client != null) {
                     try {
@@ -110,12 +110,12 @@ public class SimpleSftpClientImpl extends AbstractLoggingBean implements SimpleS
                     } catch (Exception t) {
                         debug("createSftpClient({}) failed ({}) to close client: {}",
                                 session, t.getClass().getSimpleName(), t.getMessage(), t);
-                        err = GenericUtils.accumulateException(err, t);
+                        err = ExceptionUtils.accumulateException(err, t);
                     }
                 }
             }
         } catch (Exception e) {
-            err = GenericUtils.accumulateException(err, e);
+            err = ExceptionUtils.accumulateException(err, e);
         }
 
         // This point is reached if error occurred
@@ -127,7 +127,7 @@ public class SimpleSftpClientImpl extends AbstractLoggingBean implements SimpleS
         } catch (Exception e) {
             debug("createSftpClient({}) failed ({}) to close session: {}",
                     session, e.getClass().getSimpleName(), e.getMessage(), e);
-            err = GenericUtils.accumulateException(err, e);
+            err = ExceptionUtils.accumulateException(err, e);
         }
 
         if (err instanceof IOException) {
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/SftpHelper.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/SftpHelper.java
index 3d9f7a3..68b67f7 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/SftpHelper.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/SftpHelper.java
@@ -56,6 +56,7 @@ import java.util.concurrent.TimeUnit;
 import org.apache.sshd.common.PropertyResolver;
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
@@ -414,7 +415,7 @@ public final class SftpHelper {
 
     /**
      * Converts a file type into a POSIX permission mask value
-     * 
+     *
      * @param  type File type - see {@code SSH_FILEXFER_TYPE_xxx} values
      * @return      The matching POSIX permission mask value
      */
@@ -655,7 +656,7 @@ public final class SftpHelper {
     }
 
     public static <B extends Buffer> B writeExtensions(B buffer, Map<?, ?> extensions) {
-        int numExtensions = GenericUtils.size(extensions);
+        int numExtensions = MapEntryUtils.size(extensions);
         buffer.putInt(numExtensions);
         if (numExtensions <= 0) {
             return buffer;
@@ -676,7 +677,7 @@ public final class SftpHelper {
     }
 
     public static NavigableMap<String, String> toStringExtensions(Map<String, ?> extensions) {
-        if (GenericUtils.isEmpty(extensions)) {
+        if (MapEntryUtils.isEmpty(extensions)) {
             return Collections.emptyNavigableMap();
         }
 
@@ -695,7 +696,7 @@ public final class SftpHelper {
     }
 
     public static NavigableMap<String, byte[]> toBinaryExtensions(Map<String, String> extensions) {
-        if (GenericUtils.isEmpty(extensions)) {
+        if (MapEntryUtils.isEmpty(extensions)) {
             return Collections.emptyNavigableMap();
         }
 
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/AclSupportedParser.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/AclSupportedParser.java
index a251ad2..f462664 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/AclSupportedParser.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/AclSupportedParser.java
@@ -32,6 +32,7 @@ import java.util.TreeSet;
 
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
@@ -112,8 +113,8 @@ public class AclSupportedParser extends AbstractParser<AclCapabilities> {
             private static final NavigableMap<Integer, String> ACL_VALUES_MAP
                     = LoggingUtils.generateMnemonicMap(SftpConstants.class, ACL_CAP_NAME_PREFIX);
             private static final NavigableMap<String, Integer> ACL_NAMES_MAP = Collections.unmodifiableNavigableMap(
-                    GenericUtils.flipMap(
-                            ACL_VALUES_MAP, GenericUtils.caseInsensitiveMap(), false));
+                    MapEntryUtils.flipMap(
+                            ACL_VALUES_MAP, MapEntryUtils.caseInsensitiveMap(), false));
 
             private LazyAclCapabilityNameHolder() {
                 throw new UnsupportedOperationException("No instance allowed");
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/ParserUtils.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/ParserUtils.java
index a3f77b6..d048aa6 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/ParserUtils.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/extensions/ParserUtils.java
@@ -35,6 +35,7 @@ import java.util.stream.Collectors;
 
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.sftp.common.extensions.Supported2Parser.Supported2;
 import org.apache.sshd.sftp.common.extensions.SupportedParser.Supported;
 import org.apache.sshd.sftp.common.extensions.openssh.FstatVfsExtensionParser;
@@ -69,7 +70,7 @@ public final class ParserUtils {
             BUILT_IN_PARSERS.stream()
                     .collect(Collectors.toMap(
                             NamedResource::getName, Function.identity(),
-                            GenericUtils.throwingMerger(), () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER))));
+                            MapEntryUtils.throwingMerger(), () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER))));
 
     private ParserUtils() {
         throw new UnsupportedOperationException("No instance");
@@ -136,7 +137,7 @@ public final class ParserUtils {
     }
 
     public static Set<String> supportedExtensions(Map<String, ?> parsed) {
-        if (GenericUtils.isEmpty(parsed)) {
+        if (MapEntryUtils.isEmpty(parsed)) {
             return Collections.emptySet();
         }
 
@@ -165,7 +166,7 @@ public final class ParserUtils {
      * @see               ExtensionParser#parse(byte[])
      */
     public static Map<String, Object> parse(Map<String, byte[]> extensions) {
-        if (GenericUtils.isEmpty(extensions)) {
+        if (MapEntryUtils.isEmpty(extensions)) {
             return Collections.emptyMap();
         }
 
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpEventListenerAdapter.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpEventListenerAdapter.java
index 80225b9..9d4fd4f 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpEventListenerAdapter.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpEventListenerAdapter.java
@@ -26,7 +26,7 @@ import java.nio.file.Path;
 import java.util.Collection;
 import java.util.Map;
 
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 import org.apache.sshd.server.session.ServerSession;
 
@@ -76,7 +76,7 @@ public abstract class AbstractSftpEventListenerAdapter extends AbstractLoggingBe
     @Override
     public void readEntries(ServerSession session, String remoteHandle, DirectoryHandle localHandle, Map<String, Path> entries)
             throws IOException {
-        int numEntries = GenericUtils.size(entries);
+        int numEntries = MapEntryUtils.size(entries);
         if (log.isDebugEnabled()) {
             log.debug("read(" + session + ")[" + localHandle.getFile() + "] " + numEntries + " entries");
         }
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java
index 0d539fb..926fb2c 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java
@@ -77,6 +77,7 @@ import org.apache.sshd.common.digest.Digest;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.util.EventListenerUtils;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.MapEntryUtils.NavigableMapBuilder;
 import org.apache.sshd.common.util.NumberUtils;
 import org.apache.sshd.common.util.OsUtils;
@@ -1744,7 +1745,7 @@ public abstract class AbstractSftpSubsystemHelper
         appendAclSupportedExtension(buffer, session);
 
         Map<String, OptionalFeature> extensions = getSupportedClientExtensions(session);
-        int numExtensions = GenericUtils.size(extensions);
+        int numExtensions = MapEntryUtils.size(extensions);
         List<String> extras = (numExtensions <= 0) ? Collections.emptyList() : new ArrayList<>(numExtensions);
         if (numExtensions > 0) {
             boolean debugEnabled = log.isDebugEnabled();
@@ -1963,7 +1964,7 @@ public abstract class AbstractSftpSubsystemHelper
      */
     protected void appendVendorIdExtension(
             Buffer buffer, Map<String, ?> versionProperties, ServerSession session) {
-        if (GenericUtils.isEmpty(versionProperties)) {
+        if (MapEntryUtils.isEmpty(versionProperties)) {
             return;
         }
 
@@ -2321,13 +2322,13 @@ public abstract class AbstractSftpSubsystemHelper
 
         for (String v : views) {
             Map<String, ?> ta = readFileAttributes(file, v, options);
-            if (GenericUtils.isNotEmpty(ta)) {
+            if (MapEntryUtils.isNotEmpty(ta)) {
                 attrs.putAll(ta);
             }
         }
 
         Map<String, ?> completions = resolveMissingFileAttributes(file, flags, attrs, options);
-        if (GenericUtils.isNotEmpty(completions)) {
+        if (MapEntryUtils.isNotEmpty(completions)) {
             attrs.putAll(completions);
         }
 
@@ -2360,7 +2361,7 @@ public abstract class AbstractSftpSubsystemHelper
         // Cannot use forEach because the attrs variable is not effectively final
         for (Map.Entry<String, FileInfoExtractor<?>> re : SftpFileSystemAccessor.FILEATTRS_RESOLVERS.entrySet()) {
             String name = re.getKey();
-            Object value = GenericUtils.isEmpty(current) ? null : current.get(name);
+            Object value = MapEntryUtils.isEmpty(current) ? null : current.get(name);
             FileInfoExtractor<?> x = re.getValue();
             try {
                 Object resolved = resolveMissingFileAttributeValue(file, name, value, x, options);
@@ -2405,7 +2406,7 @@ public abstract class AbstractSftpSubsystemHelper
             Path file, NavigableMap<String, Object> current,
             String name, FileInfoExtractor<?> x, LinkOption... options)
             throws IOException {
-        Object value = GenericUtils.isEmpty(current) ? null : current.get(name);
+        Object value = MapEntryUtils.isEmpty(current) ? null : current.get(name);
         if (value != null) { // already have the value
             return current;
         }
@@ -2431,7 +2432,7 @@ public abstract class AbstractSftpSubsystemHelper
             SftpFileSystemAccessor accessor = getFileSystemAccessor();
             Map<String, ?> attrs = accessor.readFileAttributes(
                     getServerSession(), this, file, view, options);
-            if (GenericUtils.isEmpty(attrs)) {
+            if (MapEntryUtils.isEmpty(attrs)) {
                 return Collections.emptyNavigableMap();
             }
 
@@ -2664,7 +2665,7 @@ public abstract class AbstractSftpSubsystemHelper
     protected void setFileExtensions(
             Path file, Map<String, byte[]> extensions, LinkOption... options)
             throws IOException {
-        if (GenericUtils.isEmpty(extensions)) {
+        if (MapEntryUtils.isEmpty(extensions)) {
             return;
         }
 
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/FileHandle.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/FileHandle.java
index 40c1056..a88f7b5 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/FileHandle.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/FileHandle.java
@@ -36,6 +36,7 @@ import java.util.Map;
 import java.util.Set;
 
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.server.session.ServerSession;
 import org.apache.sshd.sftp.common.SftpConstants;
@@ -187,7 +188,7 @@ public class FileHandle extends Handle {
     }
 
     public static Collection<FileAttribute<?>> toFileAttributes(Map<String, ?> attrs) {
-        if (GenericUtils.isEmpty(attrs)) {
+        if (MapEntryUtils.isEmpty(attrs)) {
             return Collections.emptyList();
         }
 
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 8739088..c67497e 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
@@ -71,6 +71,7 @@ import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
 import org.apache.sshd.common.random.Random;
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
@@ -750,7 +751,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
             @Override
             public void readEntries(
                     ServerSession session, String remoteHandle, DirectoryHandle localHandle, Map<String, Path> entries) {
-                int numEntries = GenericUtils.size(entries);
+                int numEntries = MapEntryUtils.size(entries);
                 entriesCount.addAndGet(numEntries);
 
                 if (log.isDebugEnabled()) {
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpVersionsTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpVersionsTest.java
index e71510e..feee734 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpVersionsTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpVersionsTest.java
@@ -46,6 +46,7 @@ import java.util.stream.IntStream;
 
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.MapEntryUtils.NavigableMapBuilder;
 import org.apache.sshd.server.channel.ChannelSession;
 import org.apache.sshd.server.command.Command;
@@ -232,7 +233,7 @@ public class SftpVersionsTest extends AbstractSftpClientTestSupport {
                     protected NavigableMap<String, Object> resolveFileAttributes(Path file, int flags, LinkOption... options)
                             throws IOException {
                         NavigableMap<String, Object> attrs = super.resolveFileAttributes(file, flags, options);
-                        if (GenericUtils.isEmpty(attrs)) {
+                        if (MapEntryUtils.isEmpty(attrs)) {
                             attrs = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
                         }
 
@@ -268,7 +269,7 @@ public class SftpVersionsTest extends AbstractSftpClientTestSupport {
             @Override
             public void modifyingAttributes(ServerSession session, Path path, Map<String, ?> attrs) {
                 @SuppressWarnings("unchecked")
-                List<AclEntry> aclActual = GenericUtils.isEmpty(attrs) ? null : (List<AclEntry>) attrs.get("acl");
+                List<AclEntry> aclActual = MapEntryUtils.isEmpty(attrs) ? null : (List<AclEntry>) attrs.get("acl");
                 if (getTestedVersion() > SftpConstants.SFTP_V3) {
                     assertListEquals("Mismatched modifying ACL for file=" + path, aclExpected, aclActual);
                 } else {
@@ -280,7 +281,7 @@ public class SftpVersionsTest extends AbstractSftpClientTestSupport {
             public void modifiedAttributes(
                     ServerSession session, Path path, Map<String, ?> attrs, Throwable thrown) {
                 @SuppressWarnings("unchecked")
-                List<AclEntry> aclActual = GenericUtils.isEmpty(attrs) ? null : (List<AclEntry>) attrs.get("acl");
+                List<AclEntry> aclActual = MapEntryUtils.isEmpty(attrs) ? null : (List<AclEntry>) attrs.get("acl");
                 if (getTestedVersion() > SftpConstants.SFTP_V3) {
                     assertListEquals("Mismatched modified ACL for file=" + path, aclExpected, aclActual);
                 } else {
@@ -351,7 +352,7 @@ public class SftpVersionsTest extends AbstractSftpClientTestSupport {
                     protected NavigableMap<String, Object> resolveFileAttributes(Path file, int flags, LinkOption... options)
                             throws IOException {
                         NavigableMap<String, Object> attrs = super.resolveFileAttributes(file, flags, options);
-                        if (GenericUtils.isEmpty(attrs)) {
+                        if (MapEntryUtils.isEmpty(attrs)) {
                             attrs = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
                         }
 
@@ -396,7 +397,7 @@ public class SftpVersionsTest extends AbstractSftpClientTestSupport {
             public void modifyingAttributes(ServerSession session, Path path, Map<String, ?> attrs) {
                 @SuppressWarnings("unchecked")
                 Map<String, byte[]> actExtensions
-                        = GenericUtils.isEmpty(attrs) ? null : (Map<String, byte[]>) attrs.get("extended");
+                        = MapEntryUtils.isEmpty(attrs) ? null : (Map<String, byte[]>) attrs.get("extended");
                 assertExtensionsMapEquals("modifying(" + path + ")", expExtensions, actExtensions);
             }
 
@@ -404,7 +405,7 @@ public class SftpVersionsTest extends AbstractSftpClientTestSupport {
             public void modifiedAttributes(ServerSession session, Path path, Map<String, ?> attrs, Throwable thrown) {
                 @SuppressWarnings("unchecked")
                 Map<String, byte[]> actExtensions
-                        = GenericUtils.isEmpty(attrs) ? null : (Map<String, byte[]>) attrs.get("extended");
+                        = MapEntryUtils.isEmpty(attrs) ? null : (Map<String, byte[]>) attrs.get("extended");
                 assertExtensionsMapEquals("modified(" + path + ")", expExtensions, actExtensions);
             }
         });
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpFileSystemTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpFileSystemTest.java
index 12552ed..76abb45 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpFileSystemTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpFileSystemTest.java
@@ -53,6 +53,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.MapEntryUtils.MapBuilder;
 import org.apache.sshd.common.util.OsUtils;
@@ -271,7 +272,7 @@ public class SftpFileSystemTest extends AbstractSftpFilesSystemSupport {
                 try {
                     fs.close();
                 } catch (IOException e) {
-                    err = GenericUtils.accumulateException(err, e);
+                    err = ExceptionUtils.accumulateException(err, e);
                 }
             }
 
diff --git a/sshd-spring-sftp/src/main/java/org/apache/sshd/sftp/spring/integration/ApacheSshdSftpSessionFactory.java b/sshd-spring-sftp/src/main/java/org/apache/sshd/sftp/spring/integration/ApacheSshdSftpSessionFactory.java
index f43c849..f0e1dc7 100644
--- a/sshd-spring-sftp/src/main/java/org/apache/sshd/sftp/spring/integration/ApacheSshdSftpSessionFactory.java
+++ b/sshd-spring-sftp/src/main/java/org/apache/sshd/sftp/spring/integration/ApacheSshdSftpSessionFactory.java
@@ -42,7 +42,9 @@ import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.config.keys.loader.pem.PEMResourceParserUtils;
 import org.apache.sshd.common.future.CloseFuture;
 import org.apache.sshd.common.future.SshFutureListener;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 import org.apache.sshd.sftp.client.SftpClient;
@@ -429,7 +431,7 @@ public class ApacheSshdSftpSessionFactory
                 }
             }
         } catch (Exception e) {
-            throw GenericUtils.toRuntimeException(e);
+            throw ExceptionUtils.toRuntimeException(e);
         }
     }
 
@@ -482,7 +484,7 @@ public class ApacheSshdSftpSessionFactory
     }
 
     protected ClientSession configureClientSessionProperties(ClientSession session, Properties props) throws IOException {
-        if (GenericUtils.isEmpty(props)) {
+        if (MapEntryUtils.isEmpty(props)) {
             return session;
         }
 
diff --git a/sshd-spring-sftp/src/main/java/org/apache/sshd/sftp/spring/integration/SpringSftpSession.java b/sshd-spring-sftp/src/main/java/org/apache/sshd/sftp/spring/integration/SpringSftpSession.java
index 07ef294..ee42140 100644
--- a/sshd-spring-sftp/src/main/java/org/apache/sshd/sftp/spring/integration/SpringSftpSession.java
+++ b/sshd-spring-sftp/src/main/java/org/apache/sshd/sftp/spring/integration/SpringSftpSession.java
@@ -30,6 +30,7 @@ import java.util.concurrent.Callable;
 import java.util.stream.Collectors;
 
 import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 import org.apache.sshd.common.util.net.SshdSocketAddress;
@@ -100,17 +101,17 @@ public class SpringSftpSession extends AbstractLoggingBean implements Session<Di
             SftpClient client = getClientInstance();
             closeClientInstance(client);
         } catch (Exception e) {
-            err = GenericUtils.accumulateException(err, e);
+            err = ExceptionUtils.accumulateException(err, e);
         }
 
         try {
             closeSessionInstance(getSessionCloser());
         } catch (Exception e) {
-            err = GenericUtils.accumulateException(err, e);
+            err = ExceptionUtils.accumulateException(err, e);
         }
 
         if (err != null) {
-            throw GenericUtils.toRuntimeException(err);
+            throw ExceptionUtils.toRuntimeException(err);
         }
     }
 


[mina-sshd] 02/07: [SSHD-1133] Added capability to specify a custom charset for parsing incoming commands to the ScpShell

Posted by lg...@apache.org.
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

commit 0dc159ab3bbac86ccd99a370c87518b9ea98866b
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri Feb 26 10:44:31 2021 +0200

    [SSHD-1133] Added capability to specify a custom charset for parsing incoming commands to the ScpShell
---
 CHANGES.md                                                  |  3 +++
 docs/scp.md                                                 | 13 +++++++++++++
 .../main/java/org/apache/sshd/scp/ScpModuleProperties.java  | 13 ++++++++++---
 .../src/main/java/org/apache/sshd/scp/server/ScpShell.java  |  5 +++--
 4 files changed, 29 insertions(+), 5 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 6e44df8..6de775d 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -16,6 +16,8 @@
 
 * [SSHD-1133](https://issues.apache.org/jira/browse/SSHD-1133) Re-factored locations and names of `ServerSession` and server-side `ChannelSession` related classes
 
+## Potential compatibility issues
+
 ## Minor code helpers
 
 * [SSHD-525](https://issues.apache.org/jira/browse/SSHD-525) Added support for SFTP **client-side** ["posix-rename@openssh.com"
@@ -43,3 +45,4 @@
 * [SSHD-1114](https://issues.apache.org/jira/browse/SSHD-1114) Added capability for interactive key based authentication participation via UserInteraction
 * [SSHD-1125](https://issues.apache.org/jira/browse/SSHD-1125) Added mechanism to throttle pending write requests in BufferedIoOutputStream
 * [SSHD-1127](https://issues.apache.org/jira/browse/SSHD-1127) Added capability to register a custom receiver for SFTP STDERR channel raw or stream data
+* [SSHD-1133](https://issues.apache.org/jira/browse/SSHD-1133) Added capability to specify a custom charset for parsing incoming commands to the `ScpShell`
\ No newline at end of file
diff --git a/docs/scp.md b/docs/scp.md
index d7274b1..06364c1 100644
--- a/docs/scp.md
+++ b/docs/scp.md
@@ -191,6 +191,19 @@ sshd.setShellFactory(factory);
 
 **Note:** a similar result can be achieved if activating SSHD from the command line by specifying `-o ShellFactory=scp`
 
+### Text encoding/decoding
+
+The SCP "shell" is text-based and therefore subject to character encoding/decoding of the data being exchanged. By default, the exchange is supposed to use the UTF-8 encoding which is the default/standard one for SSH. However, there are clients/servers "in the wild" that do not conform to this convention. For this purpose, it is possible to define a different  character encoding via the `SHELL_NAME_EN/DECODING_CHARSET` properties - e.g.:
+
+```java
+SshServer sshd = ...setup server...
+// Can also use the character name string rather than the object instance itself
+ScpModuleProperties.SHELL_NAME_ENCODING_CHARSET.set(sshd, Charset.forName("US-ASCII"));
+ScpModuleProperties.SHELL_NAME_DECODING_CHARSET.set(sshd, Charset.forName("US-ASCII"));
+```
+
+**Caveat emptor:** that the code does not enforce "symmetry" of the chosen character sets - in other words, user can either by design or error cause different encoding to be used for the incoming commands vs. the outgoing responses. It is important to bear in mind that if the text to be encoded/decoded contains characters that cannot be  safely handled by the chosen encoder/decoder than the result might not be correctly parsed/understood by the peer.
+
 ## Remote-to-remote transfer
 
 The code provides an `ScpTransferHelper` class that enables copying files between 2 remote accounts without going through
diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/ScpModuleProperties.java b/sshd-scp/src/main/java/org/apache/sshd/scp/ScpModuleProperties.java
index 4821f34..4bdae9a 100644
--- a/sshd-scp/src/main/java/org/apache/sshd/scp/ScpModuleProperties.java
+++ b/sshd-scp/src/main/java/org/apache/sshd/scp/ScpModuleProperties.java
@@ -53,12 +53,19 @@ public final class ScpModuleProperties {
             = Property.bool("scp-auto-sync-on-write", true);
 
     /**
-     * Used to indicate the {@link Charset} (or its name) for encoding referenced files/folders names - extracted from
-     * the client channel session when 1st initialized.
+     * Used to indicate the {@link Charset} (or its name) for encoding returned textual responses from the
+     * {@code ScpShell} - extracted from the channel session when shell initialized.
      */
-    public static final Property<Charset> NAME_ENCODING_CHARSET
+    public static final Property<Charset> SHELL_NAME_ENCODING_CHARSET
             = Property.charset("scp-shell-name-encoding-charset", StandardCharsets.UTF_8);
 
+    /**
+     * Used to indicate the {@link Charset} (or its name) for decoding incoming commands to be processed by the
+     * {@code ScpShell} - extracted from the channel session when shell initialized.
+     */
+    public static final Property<Charset> SHELL_NAME_DECODING_CHARSET
+            = Property.charset("scp-shell-name-decoding-charset", StandardCharsets.UTF_8);
+
     private ScpModuleProperties() {
         throw new UnsupportedOperationException("No instance");
     }
diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpShell.java b/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpShell.java
index ca413be..af31fbc 100644
--- a/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpShell.java
+++ b/sshd-scp/src/main/java/org/apache/sshd/scp/server/ScpShell.java
@@ -106,7 +106,7 @@ public class ScpShell extends AbstractFileSystemCommand implements ServerChannel
         super(null, executorService);
         this.channelSession = Objects.requireNonNull(channelSession, "No channel session provided");
 
-        nameEncodingCharset = ScpModuleProperties.NAME_ENCODING_CHARSET.getRequired(channelSession);
+        nameEncodingCharset = ScpModuleProperties.SHELL_NAME_ENCODING_CHARSET.getRequired(channelSession);
 
         if (sendSize < ScpHelper.MIN_SEND_BUFFER_SIZE) {
             throw new IllegalArgumentException(
@@ -185,9 +185,10 @@ public class ScpShell extends AbstractFileSystemCommand implements ServerChannel
 
             prepareEnvironment(getEnvironment());
 
+            Charset decodingCharset = ScpModuleProperties.SHELL_NAME_DECODING_CHARSET.getRequired(channel);
             // Use a special stream reader so that the stream can be used with the scp command
             try (InputStream inputStream = getInputStream();
-                 Reader r = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
+                 Reader r = new InputStreamReader(inputStream, decodingCharset)) {
                 for (int executedCommands = 0;; executedCommands++) {
                     command = readLine(r);
                     if (GenericUtils.isEmpty(command)) {