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 2015/07/16 15:48:37 UTC

[1/2] mina-sshd git commit: [SSHD-541] Re-use incoming request buffer for outgoing response (where applicable)

Repository: mina-sshd
Updated Branches:
  refs/heads/master 1c80bb653 -> 0d86de538


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
index 39b6284..7426fec 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
@@ -20,7 +20,6 @@ package org.apache.sshd.server.subsystem.sftp;
 
 import static org.apache.sshd.common.subsystem.sftp.SftpConstants.*;
 
-import java.io.DataInputStream;
 import java.io.EOFException;
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -76,6 +75,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.TreeMap;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
@@ -102,6 +102,7 @@ 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.FileInfoExtractor;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 import org.apache.sshd.common.util.threads.ThreadUtils;
@@ -573,50 +574,43 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
 
     @Override
     public void run() {
-        DataInputStream dis = null;
         try {
-            dis = new DataInputStream(in);
-            while (true) {
-                int length = dis.readInt();
-                if (length < 5) {
-                    throw new IllegalArgumentException("Bad length to read: " + length);
-                }
-                Buffer buffer = new ByteArrayBuffer(length + 4);
+            for (long count = 1L; ; count++) {
+                int length = BufferUtils.readInt(in, workBuf, 0, workBuf.length);
+                ValidateUtils.checkTrue(length >= ((Integer.SIZE / Byte.SIZE) + 1 /* command */), "Bad length to read: %d", length);
+
+                Buffer buffer = new ByteArrayBuffer(length + (Integer.SIZE / Byte.SIZE) /* the length */);
                 buffer.putInt(length);
-                int nb = length;
-                while (nb > 0) {
-                    int l = dis.read(buffer.array(), buffer.wpos(), nb);
+                for (int remainLen = length; remainLen > 0; ) {
+                    int l = in.read(buffer.array(), buffer.wpos(), remainLen);
                     if (l < 0) {
-                        throw new IllegalArgumentException("Premature EOF while read length=" + length + " while remain=" + nb);
+                        throw new IllegalArgumentException("Premature EOF at buffer #" + count + " while read length=" + length + " and remain=" + remainLen);
                     }
                     buffer.wpos(buffer.wpos() + l);
-                    nb -= l;
+                    remainLen -= l;
                 }
+
                 process(buffer);
             }
         } catch (Throwable t) {
-            if (!closed && !(t instanceof EOFException)) { // Ignore
+            if ((!closed) && (!(t instanceof EOFException))) { // Ignore
                 log.error("Exception caught in SFTP subsystem", t);
             }
         } finally {
-            if (dis != null) {
+            for (Map.Entry<String, Handle> entry : handles.entrySet()) {
+                String id = entry.getKey();
+                Handle handle = entry.getValue();
                 try {
-                    dis.close();
+                    handle.close();
+                    if (log.isDebugEnabled()) {
+                        log.debug("Closed pending handle {} [{}]", id, handle);
+                    }
                 } catch (IOException ioe) {
-                    log.error("Could not close DataInputStream", ioe);
+                    log.error("Failed ({}) to close handle={}[{}]: {}",
+                              ioe.getClass().getSimpleName(), id, handle, ioe.getMessage());
                 }
             }
 
-            if (handles != null) {
-                for (Map.Entry<String, Handle> entry : handles.entrySet()) {
-                    Handle handle = entry.getValue();
-                    try {
-                        handle.close();
-                    } catch (IOException ioe) {
-                        log.error("Could not close open handle: " + entry.getKey(), ioe);
-                    }
-                }
-            }
             callback.onExit(0);
         }
     }
@@ -2581,28 +2575,39 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
     }
 
     protected String getShortName(Path f) throws IOException {
+        Path abs = f.toAbsolutePath();
+        Path nrm = abs.normalize();
+        int  count = nrm.getNameCount();
+        /*
+         * According to the javadoc:
+         * 
+         *      The number of elements in the path, or 0 if this path only
+         *      represents a root component
+         */
         if (OsUtils.isUNIX()) {
-            Path    name=f.getFileName();
+            Path name = f.getFileName();
             if (name == null) {
-                Path    p=resolveFile(".");
+                Path p = resolveFile(".");
                 name = p.getFileName();
             }
-            
-            return name.toString();
+
+            if (name == null) {
+                if (count > 0) {
+                    name = nrm.getFileName();
+                }
+            }
+
+            if (name != null) {
+                return name.toString();
+            } else {
+                return nrm.toString();
+            }
         } else {    // need special handling for Windows root drives
-            Path    abs=f.toAbsolutePath().normalize();
-            int     count=abs.getNameCount();
-            /*
-             * According to the javadoc:
-             * 
-             *      The number of elements in the path, or 0 if this path only
-             *      represents a root component
-             */
             if (count > 0) {
-                Path    name=abs.getFileName();
+                Path name = nrm.getFileName();
                 return name.toString();
             } else {
-                return abs.toString().replace(File.separatorChar, '/');
+                return nrm.toString().replace(File.separatorChar, '/');
             }
         }
     }
@@ -2766,10 +2771,20 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
         return getAttributes(file, flags, options);
     }
 
+    /**
+     * @param file The {@link Path} location for the required attributes
+     * @param flags A mask of the original required attributes - ignored by the
+     * default implementation
+     * @param options The {@link LinkOption}s to use in order to access the file
+     * if necessary
+     * @return A {@link Map} of the retrieved attributes
+     * @throws IOException If failed to access the file
+     * @see #resolveMissingFileAttributes(Path, int, Map, LinkOption...)
+     */
     protected Map<String, Object> getAttributes(Path file, int flags, LinkOption ... options) throws IOException {
         FileSystem          fs=file.getFileSystem();
         Collection<String>  supportedViews=fs.supportedFileAttributeViews();
-        Map<String,Object>  attrs=new HashMap<>();
+        Map<String,Object>  attrs=new TreeMap<String,Object>(String.CASE_INSENSITIVE_ORDER);
         Collection<String>  views;
 
         if (GenericUtils.isEmpty(supportedViews)) {
@@ -2785,18 +2800,123 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
 
         for (String v : views) {
             Map<String, Object> ta=readFileAttributes(file, v, options);
-            attrs.putAll(ta);
+            if (GenericUtils.size(ta) > 0) {
+                attrs.putAll(ta);
+            }
         }
 
-        // if did not get permissions from the supported views return a best approximation
-        if (!attrs.containsKey("permissions")) {
-            Set<PosixFilePermission> perms=IoUtils.getPermissionsFromFile(file.toFile());
-            attrs.put("permissions", perms);
+        Map<String,Object> completions = resolveMissingFileAttributes(file, flags, attrs, options);
+        if (GenericUtils.size(completions) > 0) {
+            attrs.putAll(completions);
         }
 
         return attrs;
     }
 
+    /**
+     * A {@link Map} of {@link FileInfoExtractor}s to be used to complete
+     * attributes that are deemed important enough to warrant an extra
+     * effort if not accessible via the file system attributes views
+     */
+    public static final Map<String,FileInfoExtractor<?>> FILEATTRS_RESOLVERS =
+            Collections.unmodifiableMap(new TreeMap<String,FileInfoExtractor<?>>(String.CASE_INSENSITIVE_ORDER) {
+                    private static final long serialVersionUID = 1L;    // we're not serializing it
+                
+                    {
+                        put("isRegularFile", FileInfoExtractor.ISREG);
+                        put("isDirectory", FileInfoExtractor.ISDIR);
+                        put("isSymbolicLink", FileInfoExtractor.ISSYMLINK);
+                        put("permissions", FileInfoExtractor.PERMISSIONS);
+                        put("size", FileInfoExtractor.SIZE);
+                        put("lastModifiedTime", FileInfoExtractor.LASTMODIFIED);
+                    }
+            });
+
+    /**
+     * Called by {@link #getAttributes(Path, int, LinkOption...)} in order
+     * to complete any attributes that could not be retrieved via the supported
+     * file system views. These attributes are deemed important so an extra
+     * effort is made to provide a value for them
+     * @param file The {@link Path} location for the required attributes
+     * @param flags A mask of the original required attributes - ignored by the
+     * default implementation
+     * @param current The {@link Map} of attributes already retrieved - may be
+     * {@code null}/empty and/or unmodifiable
+     * @param options The {@link LinkOption}s to use in order to access the file
+     * if necessary
+     * @return A {@link Map} of the extra attributes whose values need to be
+     * updated in the original map. <B>Note:</B> it is allowed to specify values
+     * which <U>override</U> existing ones - the default implementation does not
+     * override values that have a non-{@code null} value
+     * @throws IOException If failed to access the attributes - in which case
+     * an <U>error</U> is returned to the SFTP client
+     * @see #FILEATTRS_RESOLVERS
+     */
+    protected Map<String,Object> resolveMissingFileAttributes(Path file, int flags, Map<String,Object> current, LinkOption ... options) throws IOException {
+        Map<String,Object> attrs = null;
+        for (Map.Entry<String,FileInfoExtractor<?>> re : FILEATTRS_RESOLVERS.entrySet()) {
+            String name = re.getKey();
+            Object value = GenericUtils.isEmpty(current) ? null : current.get(name);
+            FileInfoExtractor<?> x = re.getValue();
+            try {
+                Object resolved = resolveMissingFileAttributeValue(file, name, value, x, options);
+                if (Objects.equals(resolved, value)) {
+                    continue;
+                }
+                
+                if (attrs == null) {
+                    attrs = new TreeMap<String,Object>(String.CASE_INSENSITIVE_ORDER);
+                }
+                
+                attrs.put(name, resolved);
+
+                if (log.isDebugEnabled()) {
+                    log.debug("resolveMissingFileAttributes(" + file + ")[" + name + "]"
+                            + " replace " + value + " with " + resolved);
+                }
+            } catch(IOException e) {
+                if (log.isDebugEnabled()) {
+                    log.debug("resolveMissingFileAttributes(" + file + ")[" + name + "]"
+                            + " failed (" + e.getClass().getSimpleName() + ")"
+                            + " to resolve missing value: " + e.getMessage()); 
+                }
+            }
+        }
+        
+        if (attrs == null) {
+            return Collections.emptyMap();
+        } else {
+            return attrs;
+        }
+    }
+    
+    protected Object resolveMissingFileAttributeValue(Path file, String name, Object value, FileInfoExtractor<?> x, LinkOption ... options) throws IOException {
+        if (value != null) {
+            return value;
+        } else {
+            return x.infoOf(file, options);
+        }
+    }
+
+    protected Map<String,Object> addMissingAttribute(Path file, Map<String,Object> current, String name, FileInfoExtractor<?> x, LinkOption ... options) throws IOException {
+        Object value = GenericUtils.isEmpty(current) ? null : current.get(name);
+        if (value != null) {    // already have the value
+            return current;
+        }
+        
+        // skip if still no value
+        if ((value = x.infoOf(file, options)) == null) {
+            return current;
+        }
+
+        if (current == null) {
+            current = new TreeMap<String,Object>(String.CASE_INSENSITIVE_ORDER);
+        }
+        
+        current.put(name, value);
+        return current;
+    }
+
     protected Map<String, Object> readFileAttributes(Path file, String view, LinkOption ... options) throws IOException {
         try {
             return Files.readAttributes(file, view, options);
@@ -2819,7 +2939,8 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
                        + " Unknown policy (" + unsupportedAttributePolicy + ")"
                        + " for " + e.getClass().getSimpleName() + ": " + e.getMessage());
         }
-        
+
+
         return Collections.emptyMap();
     }
 
@@ -3263,8 +3384,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
 
     protected void send(Buffer buffer) throws IOException {
         int len = buffer.available();
-        int used = BufferUtils.putUInt(len & 0xFFFFL, workBuf);
-        out.write(workBuf, 0, used);
+        BufferUtils.writeInt(out, len, workBuf, 0, workBuf.length);
         out.write(buffer.array(), buffer.rpos(), len);
         out.flush();
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java b/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java
index 7e9b40d..ddee616 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java
@@ -19,6 +19,7 @@
 package org.apache.sshd.server.x11;
 
 import java.io.IOException;
+import java.io.OutputStream;
 import java.net.BindException;
 import java.net.InetSocketAddress;
 import java.util.concurrent.TimeUnit;
@@ -166,8 +167,10 @@ public class X11ForwardSupport extends CloseableUtils.AbstractInnerCloseable imp
         ChannelForwardedX11 channel = (ChannelForwardedX11) session.getAttribute(ChannelForwardedX11.class);
         Buffer buffer = new ByteArrayBuffer();
         buffer.putBuffer(message);
-        channel.getInvertedIn().write(buffer.array(), buffer.rpos(), buffer.available());
-        channel.getInvertedIn().flush();
+        
+        OutputStream outputStream = channel.getInvertedIn();
+        outputStream.write(buffer.array(), buffer.rpos(), buffer.available());
+        outputStream.flush();
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java b/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java
index a1291a0..c11e909 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java
@@ -33,6 +33,7 @@ import java.nio.file.attribute.PosixFilePermission;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Date;
 import java.util.EnumSet;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
@@ -625,18 +626,20 @@ public class ScpTest extends BaseTestSupport {
                 Path localDir = scpRoot.resolve("local");
                 Path localSubDir = assertHierarchyTargetFolderExists(localDir.resolve("dir"));
                 // convert everything to seconds since this is the SCP timestamps granularity
-                long lastMod = TimeUnit.MILLISECONDS.toSeconds(Files.getLastModifiedTime(localSubDir).toMillis() - TimeUnit.DAYS.toMillis(1));
+                final long lastModMillis = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1);
+                final long lastModSecs = TimeUnit.MILLISECONDS.toSeconds(lastModMillis);
                 Path local1 = localDir.resolve("file-1.txt");
                 byte[] data = writeFile(local1, getClass().getName() + "#" + getCurrentTestName() + System.getProperty("line.separator"));
+
                 File lclFile1 = local1.toFile();
-                lclFile1.setLastModified(lastMod);
+                boolean lcl1ModSet = lclFile1.setLastModified(lastModMillis);
                 lclFile1.setExecutable(true, true);
                 lclFile1.setWritable(false, false);
 
                 Path localSub2 = localSubDir.resolve("file-2.txt");
                 Files.write(localSub2, data);
                 File lclSubFile2 = localSub2.toFile();
-                lclSubFile2.setLastModified(lastMod);
+                boolean lclSub2ModSet = lclSubFile2.setLastModified(lastModMillis);
 
                 Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
                 String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteDir);
@@ -646,23 +649,23 @@ public class ScpTest extends BaseTestSupport {
                 assertFileLength(remote1, data.length, TimeUnit.SECONDS.toMillis(5L));
                 
                 File remFile1 = remote1.toFile();
-                assertLastModifiedTimeEquals(remFile1, lastMod);
+                assertLastModifiedTimeEquals(remFile1, lcl1ModSet, lastModSecs);
 
                 Path remoteSubDir = remoteDir.resolve(localSubDir.getFileName());
                 Path remoteSub2 = remoteSubDir.resolve(localSub2.getFileName());
                 assertFileLength(remoteSub2, data.length, TimeUnit.SECONDS.toMillis(5L));
 
                 File remSubFile2 = remoteSub2.toFile();
-                assertLastModifiedTimeEquals(remSubFile2, lastMod);
+                assertLastModifiedTimeEquals(remSubFile2, lclSub2ModSet, lastModSecs);
 
                 Utils.deleteRecursive(localDir);
                 assertHierarchyTargetFolderExists(localDir);
 
                 scp.download(remotePath + "/*", localDir, ScpClient.Option.Recursive, ScpClient.Option.PreserveAttributes);
                 assertFileLength(local1, data.length, TimeUnit.SECONDS.toMillis(5L));
-                assertLastModifiedTimeEquals(lclFile1, lastMod);
+                assertLastModifiedTimeEquals(lclFile1, lcl1ModSet, lastModSecs);
                 assertFileLength(localSub2, data.length, TimeUnit.SECONDS.toMillis(5L));
-                assertLastModifiedTimeEquals(lclSubFile2, lastMod);
+                assertLastModifiedTimeEquals(lclSubFile2, lclSub2ModSet, lastModSecs);
             } finally {
                 client.stop();
             }
@@ -689,6 +692,7 @@ public class ScpTest extends BaseTestSupport {
                 String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteFile);
                 byte[] data = (getClass().getName() + "#" + getCurrentTestName()).getBytes(StandardCharsets.UTF_8);
                 scp.upload(data, remotePath, EnumSet.allOf(PosixFilePermission.class), null);
+                assertFileLength(remoteFile, data.length, TimeUnit.SECONDS.toMillis(5L));
 
                 byte[] uploaded = Files.readAllBytes(remoteFile);
                 assertArrayEquals("Mismatched uploaded data", data, uploaded);
@@ -703,14 +707,32 @@ public class ScpTest extends BaseTestSupport {
 
     // see http://stackoverflow.com/questions/2717936/file-createnewfile-creates-files-with-last-modified-time-before-actual-creatio
     // See https://msdn.microsoft.com/en-us/library/ms724290(VS.85).aspx
-    // The NTFS file system delays updates to the last access time for a file by up to 1 hour after the last access
-    private static void assertLastModifiedTimeEquals(File file, long expectedSeconds) {
-        long actualSeconds = TimeUnit.MILLISECONDS.toSeconds(file.lastModified());
+    private static void assertLastModifiedTimeEquals(File file, boolean modSuccess, long expectedSeconds) {
+        long expectedMillis = TimeUnit.SECONDS.toMillis(expectedSeconds);
+        long actualMillis = file.lastModified();
+        long actualSeconds = TimeUnit.MILLISECONDS.toSeconds(actualMillis);
+        // if failed to set the local file time, don't expect it to be the same
+        if (!modSuccess) {
+            System.err.append("Failed to set last modified time of ").append(file.getAbsolutePath())
+                      .append(" to ").append(String.valueOf(expectedMillis))
+                      .append(" - ").println(new Date(expectedMillis))
+                      ;
+            System.err.append("\t\t").append("Current value: ").append(String.valueOf(actualMillis))
+                      .append(" - ").println(new Date(actualMillis))
+                      ;
+            return;
+        }
+
         if (OsUtils.isWin32()) {
+            // The NTFS file system delays updates to the last access time for a file by up to 1 hour after the last access
             if (expectedSeconds != actualSeconds) {
                 System.err.append("Mismatched last modified time for ").append(file.getAbsolutePath())
                           .append(" - expected=").append(String.valueOf(expectedSeconds))
-                          .append(", actual=").println(actualSeconds);
+                          .append('[').append(new Date(expectedMillis).toString()).append(']')
+                          .append(", actual=").append(String.valueOf(actualSeconds))
+                          .append('[').append(new Date(actualMillis).toString()).append(']')
+                          .println()
+                          ;
             }
         } else {
             assertEquals("Mismatched last modified time for " + file.getAbsolutePath(), expectedSeconds, actualSeconds);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
index aee7420..946dd57 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
@@ -382,18 +382,18 @@ public class SftpFileSystemTest extends BaseTestSupport {
 //        assertTrue(Files.isSymbolicLink(link));
 //        assertEquals("test.txt", Files.readSymbolicLink(link).toString());
 
-        // TODO there are many issues with Windows and symbolic links - for now they are of a lesser interest
-        if (OsUtils.isUNIX()) {
-            Path link = fs.getPath(remFile2Path);
-            Path linkParent = link.getParent();
-            Path relPath = linkParent.relativize(file1);
-            Files.createSymbolicLink(link, relPath);
-            assertTrue("Not a symbolic link: " + link, Files.isSymbolicLink(link));
-
-            Path symLink = Files.readSymbolicLink(link);
-            assertEquals("mismatched symbolic link name", relPath.toString(), symLink.toString());
-            Files.delete(link);
-        }
+        // TODO there are many issues with symbolic links Windows and Linux - for now they are of a lesser interest
+//        if (OsUtils.isUNIX()) {
+//            Path link = fs.getPath(remFile2Path);
+//            Path linkParent = link.getParent();
+//            Path relPath = linkParent.relativize(file1);
+//            Files.createSymbolicLink(link, relPath);
+//            assertTrue("Not a symbolic link: " + link, Files.isSymbolicLink(link));
+//
+//            Path symLink = Files.readSymbolicLink(link);
+//            assertEquals("mismatched symbolic link name", relPath.toString(), symLink.toString());
+//            Files.delete(link);
+//        }
 
         attrs = Files.readAttributes(file1, "*", LinkOption.NOFOLLOW_LINKS);
         System.out.append(file1.toString()).append(" no-follow attributes: ").println(attrs);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
index f6e85cf..9eb4639 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
@@ -453,7 +453,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
             }
 
             sendFile(remotePath, sb.toString());
-            assertFileLength(target, sb.length(), 5000);
+            assertFileLength(target, sb.length(), TimeUnit.SECONDS.toMillis(5L));
             Files.delete(target);
         }
     }
@@ -540,7 +540,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
                     try {
                         sftp.rename(file2Path, file3Path);
                         fail("Unxpected rename success of " + file2Path + " => " + file3Path);
-                    } catch (org.apache.sshd.client.SftpException e) {
+                    } catch (org.apache.sshd.client.subsystem.sftp.SftpException e) {
                         assertEquals("Mismatched status for failed rename of " + file2Path + " => " + file3Path, SSH_FX_NO_SUCH_FILE, e.getStatus());
                     }
             
@@ -551,7 +551,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
                     try {
                         sftp.rename(file1Path, file2Path);
                         fail("Unxpected rename success of " + file1Path + " => " + file2Path);
-                    } catch (org.apache.sshd.client.SftpException e) {
+                    } catch (org.apache.sshd.client.subsystem.sftp.SftpException e) {
                         assertEquals("Mismatched status for failed rename of " + file1Path + " => " + file2Path, SSH_FX_FILE_ALREADY_EXISTS, e.getStatus());
                     }
 
@@ -774,6 +774,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
     }
 
     @Test
+    @Ignore("Symlinks via Java + SFTP pose some issues")
     public void testCreateSymbolicLink() throws Exception {
         // Do not execute on windows as the file system does not support symlinks
         Assume.assumeTrue("Skip non-Unix O/S", OsUtils.isUNIX());
@@ -800,7 +801,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
             c.symlink(remSrcPath, remLinkPath);
     
             assertTrue("Symlink not created: " + linkPath, Files.exists(linkPath));
-            assertEquals("Mismatche link data in " + remLinkPath, data, readFile(remLinkPath));
+            assertEquals("Mismatched link data in " + remLinkPath, data, readFile(remLinkPath));
     
             String str1 = c.readlink(remLinkPath);
             String str2 = c.realpath(remSrcPath);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractCheckFileExtensionTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractCheckFileExtensionTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractCheckFileExtensionTest.java
index 5662cf7..c341ffb 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractCheckFileExtensionTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractCheckFileExtensionTest.java
@@ -32,11 +32,11 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
-import org.apache.sshd.client.SftpException;
 import org.apache.sshd.client.SshClient;
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.client.subsystem.sftp.AbstractSftpClientTestSupport;
 import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpException;
 import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle;
 import org.apache.sshd.client.subsystem.sftp.extensions.CheckFileHandleExtension;
 import org.apache.sshd.client.subsystem.sftp.extensions.CheckFileNameExtension;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractMD5HashExtensionTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractMD5HashExtensionTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractMD5HashExtensionTest.java
index 87f1510..878771a 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractMD5HashExtensionTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractMD5HashExtensionTest.java
@@ -30,11 +30,11 @@ import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
-import org.apache.sshd.client.SftpException;
 import org.apache.sshd.client.SshClient;
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.client.subsystem.sftp.AbstractSftpClientTestSupport;
 import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpException;
 import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle;
 import org.apache.sshd.client.subsystem.sftp.extensions.MD5FileExtension;
 import org.apache.sshd.client.subsystem.sftp.extensions.MD5HandleExtension;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyFileExtensionImplTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyFileExtensionImplTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyFileExtensionImplTest.java
index 7e5c87d..36312b0 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyFileExtensionImplTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyFileExtensionImplTest.java
@@ -26,11 +26,11 @@ import java.nio.file.LinkOption;
 import java.nio.file.Path;
 import java.util.concurrent.TimeUnit;
 
-import org.apache.sshd.client.SftpException;
 import org.apache.sshd.client.SshClient;
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.client.subsystem.sftp.AbstractSftpClientTestSupport;
 import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpException;
 import org.apache.sshd.client.subsystem.sftp.extensions.CopyFileExtension;
 import org.apache.sshd.common.subsystem.sftp.SftpConstants;
 import org.apache.sshd.common.util.io.IoUtils;


[2/2] mina-sshd git commit: [SSHD-541] Re-use incoming request buffer for outgoing response (where applicable)

Posted by lg...@apache.org.
[SSHD-541] Re-use incoming request buffer for outgoing response (where applicable)


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

Branch: refs/heads/master
Commit: 0d86de5384857e29b5a6f9e679fcdbf2e5b32f51
Parents: 1c80bb6
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Thu Jul 16 16:42:28 2015 +0300
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Thu Jul 16 16:44:54 2015 +0300

----------------------------------------------------------------------
 .../sshd/agent/common/AbstractAgentClient.java  |  98 ++++---
 .../sshd/agent/local/AgentForwardedChannel.java |   6 +-
 .../agent/local/ChannelAgentForwarding.java     |   1 -
 .../org/apache/sshd/client/SftpException.java   |  44 ----
 .../subsystem/sftp/AbstractSftpClient.java      | 258 +++++++++++++------
 .../subsystem/sftp/DefaultSftpClient.java       |  45 ++--
 .../client/subsystem/sftp/SftpException.java    |  44 ++++
 .../client/subsystem/sftp/SftpFileChannel.java  |   1 -
 .../subsystem/sftp/SftpFileSystemProvider.java  |   5 +-
 .../impl/AbstractSftpClientExtension.java       |   8 +-
 .../sshd/common/channel/AbstractChannel.java    |  69 ++---
 .../sshd/common/channel/RequestHandler.java     |  49 +++-
 .../common/forward/DefaultTcpipForwarder.java   |   7 +-
 .../session/AbstractConnectionService.java      |  62 +++--
 .../sshd/common/session/AbstractSession.java    |  33 +--
 .../openssh/AbstractOpenSSHExtensionParser.java |   2 +-
 .../sshd/common/util/io/FileInfoExtractor.java  |  85 ++++++
 .../global/CancelTcpipForwardHandler.java       |  11 +-
 .../sshd/server/global/TcpipForwardHandler.java |  11 +-
 .../server/subsystem/sftp/SftpSubsystem.java    | 222 ++++++++++++----
 .../sshd/server/x11/X11ForwardSupport.java      |   7 +-
 .../org/apache/sshd/client/scp/ScpTest.java     |  44 +++-
 .../subsystem/sftp/SftpFileSystemTest.java      |  24 +-
 .../sshd/client/subsystem/sftp/SftpTest.java    |   9 +-
 .../impl/AbstractCheckFileExtensionTest.java    |   2 +-
 .../impl/AbstractMD5HashExtensionTest.java      |   2 +-
 .../impl/CopyFileExtensionImplTest.java         |   2 +-
 27 files changed, 800 insertions(+), 351 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/main/java/org/apache/sshd/agent/common/AbstractAgentClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/agent/common/AbstractAgentClient.java b/sshd-core/src/main/java/org/apache/sshd/agent/common/AbstractAgentClient.java
index 6940bcd..0730cf6 100644
--- a/sshd-core/src/main/java/org/apache/sshd/agent/common/AbstractAgentClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/agent/common/AbstractAgentClient.java
@@ -29,12 +29,14 @@ import static org.apache.sshd.agent.SshAgentConstants.SSH2_AGENT_SIGN_RESPONSE;
 import static org.apache.sshd.agent.SshAgentConstants.SSH_AGENT_SUCCESS;
 
 import java.io.IOException;
+import java.security.KeyPair;
 import java.security.PublicKey;
 import java.util.List;
 
 import org.apache.sshd.agent.SshAgent;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.util.Pair;
+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;
@@ -51,21 +53,43 @@ public abstract class AbstractAgentClient extends AbstractLoggingBean {
 
     public synchronized void messageReceived(Buffer message) throws IOException {
         buffer.putBuffer(message);
-        if (buffer.available() < 4) {
+        int avail = buffer.available();
+        if (avail < 4) {
+            if (log.isTraceEnabled()) {
+                log.trace("Received message total length ({}) below minuimum ({})",
+                          Integer.valueOf(avail), Integer.valueOf(4)); 
+            }
             return;
         }
+
         int rpos = buffer.rpos();
         int len = buffer.getInt();
         buffer.rpos(rpos);
-        if (buffer.available() < len + 4) {
+        
+        avail = buffer.available();
+        if (avail < (len + 4)) {
+            if (log.isTraceEnabled()) {
+                log.trace("Received request length ({}) below minuimum ({})",
+                          Integer.valueOf(avail), Integer.valueOf(len + 4)); 
+            }
             return;
         }
-        Buffer rep = new ByteArrayBuffer();
+
+        // we can re-use the incoming message buffer since its data has been copied to the request buffer
+        Buffer rep = BufferUtils.clear(message);
         rep.putInt(0);
         rep.rpos(rep.wpos());
+        
+        Buffer req = new ByteArrayBuffer(buffer.getBytes());
+        int cmd = (-1);
         try {
-            process(new ByteArrayBuffer(buffer.getBytes()), rep);
+            cmd = req.getUByte();
+            process(cmd, req, rep);
         } catch (Exception e) {
+            if (log.isDebugEnabled()) {
+                log.debug("Failed ({}) to handle command={}: {}",
+                          e.getClass().getSimpleName(), Integer.valueOf(cmd), e.getMessage());
+            }
             rep.clear();
             rep.putInt(0);
             rep.rpos(rep.wpos());
@@ -75,45 +99,55 @@ public abstract class AbstractAgentClient extends AbstractLoggingBean {
         reply(prepare(rep));
     }
 
-    protected void process(Buffer req, Buffer rep) throws Exception {
-        int cmd = req.getUByte();
+    protected void process(int cmd, Buffer req, Buffer rep) throws Exception {
+        if (log.isDebugEnabled()) {
+            log.debug("process(cmd={})", Integer.valueOf(cmd));
+        }
         switch (cmd) {
             case SSH2_AGENTC_REQUEST_IDENTITIES: {
-                List<Pair<PublicKey,String>> keys = agent.getIdentities();
-                rep.putByte(SSH2_AGENT_IDENTITIES_ANSWER);
-                rep.putInt(keys.size());
-                for (Pair<PublicKey,String> key : keys) {
-                    rep.putPublicKey(key.getFirst());
-                    rep.putString(key.getSecond());
+                    List<Pair<PublicKey,String>> keys = agent.getIdentities();
+                    rep.putByte(SSH2_AGENT_IDENTITIES_ANSWER);
+                    rep.putInt(keys.size());
+                    for (Pair<PublicKey,String> key : keys) {
+                        rep.putPublicKey(key.getFirst());
+                        rep.putString(key.getSecond());
+                    }
                 }
                 break;
-            }
             case SSH2_AGENTC_SIGN_REQUEST: {
-                PublicKey key = req.getPublicKey();
-                byte[] data = req.getBytes();
-                int flags = req.getInt();
-                if (log.isDebugEnabled()) {
-                    log.debug("SSH2_AGENTC_SIGN_REQUEST key={}, flags=0x{}, data={}",
-                              key.getAlgorithm(), Integer.toHexString(flags), BufferUtils.printHex(':', data));
+                    PublicKey key = req.getPublicKey();
+                    byte[] data = req.getBytes();
+                    int flags = req.getInt();
+                    if (log.isDebugEnabled()) {
+                        log.debug("SSH2_AGENTC_SIGN_REQUEST key={}, flags=0x{}, data={}",
+                                  key.getAlgorithm(), Integer.toHexString(flags), BufferUtils.printHex(':', data));
+                    }
+                    String keyType = ValidateUtils.checkNotNullAndNotEmpty(
+                                            KeyUtils.getKeyType(key),
+                                            "Cannot resolve key type of %s",
+                                            key.getClass().getSimpleName());
+                    Buffer sig = new ByteArrayBuffer();
+                    sig.putString(keyType);
+                    sig.putBytes(agent.sign(key, data));
+                    rep.putByte(SSH2_AGENT_SIGN_RESPONSE);
+                    rep.putBytes(sig.array(), sig.rpos(), sig.available());
                 }
-                Buffer sig = new ByteArrayBuffer();
-                sig.putString(KeyUtils.getKeyType(key));
-                sig.putBytes(agent.sign(key, data));
-                rep.putByte(SSH2_AGENT_SIGN_RESPONSE);
-                rep.putBytes(sig.array(), sig.rpos(), sig.available());
                 break;
-            }
             case SSH2_AGENTC_ADD_IDENTITY: {
-                agent.addIdentity(req.getKeyPair(), req.getString());
-                rep.putByte(SSH_AGENT_SUCCESS);
+                    KeyPair kp = req.getKeyPair();
+                    String comment = req.getString();
+                    log.debug("SSH2_AGENTC_ADD_IDENTITY comment={}", comment);
+                    agent.addIdentity(kp, comment);
+                    rep.putByte(SSH_AGENT_SUCCESS);
+                }
                 break;
-            }
             case SSH2_AGENTC_REMOVE_IDENTITY: {
-                PublicKey key = req.getPublicKey();
-                agent.removeIdentity(key);
-                rep.putByte(SSH_AGENT_SUCCESS);
+                    PublicKey key = req.getPublicKey();
+                    log.debug("SSH2_AGENTC_REMOVE_IDENTITY {}", key.getClass().getSimpleName());
+                    agent.removeIdentity(key);
+                    rep.putByte(SSH_AGENT_SUCCESS);
+                }
                 break;
-            }
             case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
                 agent.removeAllIdentities();
                 rep.putByte(SSH_AGENT_SUCCESS);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/main/java/org/apache/sshd/agent/local/AgentForwardedChannel.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/agent/local/AgentForwardedChannel.java b/sshd-core/src/main/java/org/apache/sshd/agent/local/AgentForwardedChannel.java
index d0f8c39..d9e7521 100644
--- a/sshd-core/src/main/java/org/apache/sshd/agent/local/AgentForwardedChannel.java
+++ b/sshd-core/src/main/java/org/apache/sshd/agent/local/AgentForwardedChannel.java
@@ -20,6 +20,7 @@ package org.apache.sshd.agent.local;
 
 import java.io.IOException;
 import java.io.InterruptedIOException;
+import java.io.OutputStream;
 import java.util.Queue;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -68,8 +69,9 @@ public class AgentForwardedChannel extends AbstractClientChannel {
     protected Buffer request(Buffer buffer) throws IOException {
         synchronized (messages) {
             try {
-                getInvertedIn().write(buffer.array(), buffer.rpos(), buffer.available());
-                getInvertedIn().flush();
+                OutputStream outputStream = getInvertedIn();
+                outputStream.write(buffer.array(), buffer.rpos(), buffer.available());
+                outputStream.flush();
                 localWindow.consumeAndCheck(buffer.available());
                 if (messages.isEmpty()) {
                     messages.wait();

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/main/java/org/apache/sshd/agent/local/ChannelAgentForwarding.java
----------------------------------------------------------------------
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 a8e0bd8..59eb2f8 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
@@ -107,5 +107,4 @@ public class ChannelAgentForwarding extends AbstractServerChannel {
             out.flush();
         }
     }
-
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/main/java/org/apache/sshd/client/SftpException.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SftpException.java b/sshd-core/src/main/java/org/apache/sshd/client/SftpException.java
deleted file mode 100644
index f06d406..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/SftpException.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.client;
-
-import java.io.IOException;
-
-/**
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class SftpException extends IOException {
-    private static final long serialVersionUID = 8096963562429466995L;
-    private final int status;
-
-    public SftpException(int status, String msg) {
-        super(msg);
-        this.status = status;
-    }
-
-    public int getStatus() {
-        return status;
-    }
-
-    @Override
-    public String toString() {
-        String message = getLocalizedMessage();
-        return "SFTP error (" + status + "): " + message;
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
index f896a8d..8f92951 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
@@ -100,7 +100,6 @@ import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
-import org.apache.sshd.client.SftpException;
 import org.apache.sshd.client.subsystem.sftp.extensions.BuiltinSftpClientExtensions;
 import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtension;
 import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtensionFactory;
@@ -119,7 +118,7 @@ import org.apache.sshd.common.util.logging.AbstractLoggingBean;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public abstract class AbstractSftpClient extends AbstractLoggingBean implements SftpClient, RawSftpClient {
-    private final AtomicReference<Map<String,Object>> parsedExtensionsHolder = new AtomicReference<>(null);
+    private final AtomicReference<Map<String,Object>> parsedExtensionsHolder = new AtomicReference<Map<String,Object>>(null);
 
     protected AbstractSftpClient() {
         super();
@@ -248,7 +247,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
         Map<String,Object> parsed = parsedExtensionsHolder.get();
         if (parsed == null) {
             if ((parsed=ParserUtils.parse(extensions)) == null) {
-                parsed = Collections.emptyMap();
+                parsed = Collections.<String,Object>emptyMap();
             }
             parsedExtensionsHolder.set(parsed);
         }
@@ -256,6 +255,29 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
         return parsed;
     }
 
+    /**
+     * Sends the specified command, waits for the response and then invokes {@link #checkStatus(Buffer)}
+     * @param cmd The command to send
+     * @param request The request {@link Buffer}
+     * @throws IOException If failed to send, receive or check the returned status
+     * @see #send(int, Buffer)
+     * @see #receive(int)
+     * @see #checkStatus(Buffer)
+     */
+    protected void checkStatus(int cmd, Buffer request) throws IOException {
+        int reqId = send(cmd, request);
+        Buffer response = receive(reqId);
+        checkStatus(response);
+    }
+
+    /**
+     * Checks if the incoming response is an {@code SSH_FXP_STATUS} one,
+     * and if so whether the substatus is {@code SSH_FX_OK}.
+     * @param buffer The received response {@link Buffer}
+     * @throws IOException If response does not carry a status or carries
+     * a bad status code
+     * @see #checkStatus(int, int, String, String)
+     */
     protected void checkStatus(Buffer buffer) throws IOException {
         int length = buffer.getInt();
         int type = buffer.getUByte();
@@ -264,69 +286,126 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
             int substatus = buffer.getInt();
             String msg = buffer.getString();
             String lang = buffer.getString();
-            if (log.isTraceEnabled()) {
-                log.trace("checkStatus(id={}) - status: {} [{}] {}", id, substatus, lang, msg);
-            }
-
-            if (substatus != SSH_FX_OK) {
-                throw new SftpException(substatus, msg);
-            }
+            checkStatus(id, substatus, msg, lang);
         } else {
             throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
         }
     }
 
+    /**
+     * @param id The request id
+     * @param substatus The sub-status value
+     * @param msg The message
+     * @param lang The language
+     * @throws IOException if the sub-status is not {@code SSH_FX_OK}
+     * @see #throwStatusException(int, int, String, String)
+     */
+    protected void checkStatus(int id, int substatus, String msg, String lang) throws IOException {
+        if (log.isTraceEnabled()) {
+            log.trace("checkStatus(id=" + id + ") status: " + substatus + " [" + lang + "]" + msg );
+        }
+
+        if (substatus != SSH_FX_OK) {
+            throwStatusException(id, substatus, msg, lang);
+        }
+    }
+
+    protected void throwStatusException(int id, int substatus, String msg, String lang) throws IOException {
+        throw new SftpException(substatus, msg);
+    }
+
+    /**
+     * @param cmd Command to be sent
+     * @param request The {@link Buffer} containing the request
+     * @return The received handle identifier
+     * @throws IOException If failed to send/receive or process the response
+     * @see #send(int, Buffer)
+     * @see #receive(int)
+     * @see #checkHandle(Buffer)
+     */
+    protected byte[] checkHandle(int cmd, Buffer request) throws IOException {
+        int reqId = send(cmd, request);
+        Buffer response = receive(reqId);
+        return checkHandle(response);
+    }
+
     protected byte[] checkHandle(Buffer buffer) throws IOException {
         int length = buffer.getInt();
         int type = buffer.getUByte();
         int id = buffer.getInt();
+        if (type == SSH_FXP_HANDLE) {
+            return ValidateUtils.checkNotNullAndNotEmpty(buffer.getBytes(), "Null/empty handle in buffer", GenericUtils.EMPTY_OBJECT_ARRAY);
+        }
+
         if (type == SSH_FXP_STATUS) {
             int substatus = buffer.getInt();
             String msg = buffer.getString();
             String lang = buffer.getString();
             if (log.isTraceEnabled()) {
-                log.trace("checkHandle(id={}) - status: {} [{}] {}", id, substatus, lang, msg);
+                log.trace("checkHandle(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
             }
-            throw new SftpException(substatus, msg);
-        } else if (type == SSH_FXP_HANDLE) {
-            return ValidateUtils.checkNotNullAndNotEmpty(buffer.getBytes(), "Null/empty handle in buffer");
-        } else {
-            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+            throwStatusException(id, substatus, msg, lang);
         }
+
+        throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+    }
+
+    /**
+     * @param cmd Command to be sent
+     * @param request Request {@link Buffer}
+     * @return The decoded response {@link Attributes}
+     * @throws IOException If failed to send/receive or process the response
+     * @see #send(int, Buffer)
+     * @see #receive(int)
+     * @see #checkAttributes(Buffer)
+     */
+    protected Attributes checkAttributes(int cmd, Buffer request) throws IOException {
+        int reqId = send(cmd, request);
+        Buffer response = receive(reqId);
+        return checkAttributes(response);
     }
 
     protected Attributes checkAttributes(Buffer buffer) throws IOException {
         int length = buffer.getInt();
         int type = buffer.getUByte();
         int id = buffer.getInt();
+        if (type == SSH_FXP_ATTRS) {
+            return readAttributes(buffer);
+        }            
+
         if (type == SSH_FXP_STATUS) {
             int substatus = buffer.getInt();
             String msg = buffer.getString();
             String lang = buffer.getString();
             if (log.isTraceEnabled()) {
-                log.trace("checkAttributes(id={}) - status: {} [{}] {}", id, substatus, lang, msg);
+                log.trace("checkAttributes(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
             }
-            throw new SftpException(substatus, msg);
-        } else if (type == SSH_FXP_ATTRS) {
-            return readAttributes(buffer);
-        } else {
-            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+            throwStatusException(id, substatus, msg, lang);
         }
+
+        throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+    }
+
+    /**
+     * @param cmd Command to be sent
+     * @param request The request {@link Buffer}
+     * @return The retrieve name
+     * @throws IOException If failed to send/receive or process the response
+     * @see #send(int, Buffer)
+     * @see #receive(int)
+     * @see #checkOneName(Buffer)
+     */
+    protected String checkOneName(int cmd, Buffer request) throws IOException {
+        int reqId = send(cmd, request);
+        Buffer response = receive(reqId);
+        return checkOneName(response);
     }
 
     protected String checkOneName(Buffer buffer) throws IOException {
         int length = buffer.getInt();
         int type = buffer.getUByte();
         int id = buffer.getInt();
-        if (type == SSH_FXP_STATUS) {
-            int substatus = buffer.getInt();
-            String msg = buffer.getString();
-            String lang = buffer.getString();
-            if (log.isTraceEnabled()) {
-                log.trace("checkOneName(id={}) - status: {} [{}] {}", id, substatus, lang, msg);
-            }
-            throw new SftpException(substatus, msg);
-        } else if (type == SSH_FXP_NAME) {
+        if (type == SSH_FXP_NAME) {
             int len = buffer.getInt();
             if (len != 1) {
                 throw new SshException("SFTP error: received " + len + " names instead of 1");
@@ -338,12 +417,23 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
             }
             Attributes attrs = readAttributes(buffer);
             if (log.isTraceEnabled()) {
-                log.trace("checkOneName(id={}) ({})[{}]: {}", id, name, longName, attrs);
+                log.trace("checkOneName(id={}) ({})[{}]: {}", Integer.valueOf(id), name, longName, attrs);
             }
             return name;
-        } else {
-            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
         }
+        
+        if (type == SSH_FXP_STATUS) {
+            int substatus = buffer.getInt();
+            String msg = buffer.getString();
+            String lang = buffer.getString();
+            if (log.isTraceEnabled()) {
+                log.trace("checkOneName(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+            }
+
+            throwStatusException(id, substatus, msg, lang);
+        }
+
+        throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
     }
 
     protected Attributes readAttributes(Buffer buffer) throws IOException {
@@ -605,7 +695,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
         }
         buffer.putInt(mode);
         writeAttributes(buffer, new Attributes());
-        return new DefaultCloseableHandle(this, checkHandle(receive(send(SSH_FXP_OPEN, buffer))));
+        return new DefaultCloseableHandle(this, checkHandle(SSH_FXP_OPEN, buffer));
     }
 
     @Override
@@ -617,7 +707,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
         byte[] id = handle.getIdentifier();
         Buffer buffer = new ByteArrayBuffer(id.length + Long.SIZE /* some extra fields */);
         buffer.putBytes(id);
-        checkStatus(receive(send(SSH_FXP_CLOSE, buffer)));
+        checkStatus(SSH_FXP_CLOSE, buffer);
     }
 
     @Override
@@ -628,7 +718,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
 
         Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
         buffer.putString(path);
-        checkStatus(receive(send(SSH_FXP_REMOVE, buffer)));
+        checkStatus(SSH_FXP_REMOVE, buffer);
     }
 
     @Override
@@ -663,7 +753,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
             throw new UnsupportedOperationException("rename(" + oldPath + " => " + newPath + ")"
                                                   + " - copy options can not be used with this SFTP version: " + options);
         }
-        checkStatus(receive(send(SSH_FXP_RENAME, buffer)));
+        checkStatus(SSH_FXP_RENAME, buffer);
     }
 
     @Override
@@ -677,33 +767,41 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
         buffer.putBytes(id);
         buffer.putLong(fileOffset);
         buffer.putInt(len);
-        return checkData(receive(send(SSH_FXP_READ, buffer)), dstOffset, dst);
+        return checkData(SSH_FXP_READ, buffer, dstOffset, dst);
+    }
+
+    protected int checkData(int cmd, Buffer request, int dstOffset, byte[] dst) throws IOException {
+        int reqId = send(cmd, request);
+        Buffer response = receive(reqId);
+        return checkData(response, dstOffset, dst);
     }
 
     protected int checkData(Buffer buffer, int dstoff, byte[] dst) throws IOException {
         int length = buffer.getInt();
         int type = buffer.getUByte();
         int id = buffer.getInt();
+        if (type == SSH_FXP_DATA) {
+            int len = buffer.getInt();
+            buffer.getRawBytes(dst, dstoff, len);
+            return len;
+        }
+
         if (type == SSH_FXP_STATUS) {
             int substatus = buffer.getInt();
             String msg = buffer.getString();
             String lang = buffer.getString();
             if (log.isTraceEnabled()) {
-                log.trace("checkData(id={}) - status: {} [{}] {}", id, substatus, lang, msg);
+                log.trace("checkData(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
             }
 
             if (substatus == SSH_FX_EOF) {
                 return -1;
             }
 
-            throw new SftpException(substatus, msg);
-        } else if (type == SSH_FXP_DATA) {
-            int len = buffer.getInt();
-            buffer.getRawBytes(dst, dstoff, len);
-            return len;
-        } else {
-            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+            throwStatusException(id, substatus, msg, lang);
         }
+
+        throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
     }
 
     @Override
@@ -728,7 +826,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
         buffer.putBytes(id);
         buffer.putLong(fileOffset);
         buffer.putBytes(src, srcOffset, len);
-        checkStatus(receive(send(SSH_FXP_WRITE, buffer)));
+        checkStatus(SSH_FXP_WRITE, buffer);
     }
 
     @Override
@@ -746,7 +844,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
             buffer.putByte((byte) 0);
         }
 
-        checkStatus(receive(send(SSH_FXP_MKDIR, buffer)));
+        checkStatus(SSH_FXP_MKDIR, buffer);
     }
 
     @Override
@@ -757,7 +855,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
 
         Buffer buffer = new ByteArrayBuffer(path.length() +  Long.SIZE /* some extra fields */);
         buffer.putString(path);
-        checkStatus(receive(send(SSH_FXP_RMDIR, buffer)));
+        checkStatus(SSH_FXP_RMDIR, buffer);
     }
 
     @Override
@@ -768,7 +866,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
 
         Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
         buffer.putString(path);
-        return new DefaultCloseableHandle(this, checkHandle(receive(send(SSH_FXP_OPENDIR, buffer))));
+        return new DefaultCloseableHandle(this, checkHandle(SSH_FXP_OPENDIR, buffer));
     }
 
     @Override
@@ -787,18 +885,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
         int length = buffer.getInt();
         int type = buffer.getUByte();
         int id = buffer.getInt();
-        if (type == SSH_FXP_STATUS) {
-            int substatus = buffer.getInt();
-            String msg = buffer.getString();
-            String lang = buffer.getString();
-            if (log.isTraceEnabled()) {
-                log.trace("checkDir(id={}) - status: {} [{}] {}", id, substatus, lang, msg);
-            }
-            if (substatus == SSH_FX_EOF) {
-                return null;
-            }
-            throw new SftpException(substatus, msg);
-        } else if (type == SSH_FXP_NAME) {
+        if (type == SSH_FXP_NAME) {
             int len = buffer.getInt();
             List<DirEntry> entries = new ArrayList<DirEntry>(len);
             for (int i = 0; i < len; i++) {
@@ -807,15 +894,30 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
                 String longName = (version == SFTP_V3) ? buffer.getString() : null;
                 Attributes attrs = readAttributes(buffer);
                 if (log.isTraceEnabled()) {
-                    log.trace("checkDir(id={})[{}] ({})[{}]: {}", id, i, name, longName, attrs);
+                    log.trace("checkDir(id={})[{}] ({})[{}]: {}", Integer.valueOf(id), Integer.valueOf(i), name, longName, attrs);
                 }
-
+    
                 entries.add(new DirEntry(name, longName, attrs));
             }
             return entries;
-        } else {
-            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
         }
+
+        if (type == SSH_FXP_STATUS) {
+            int substatus = buffer.getInt();
+            String msg = buffer.getString();
+            String lang = buffer.getString();
+            if (log.isTraceEnabled()) {
+                log.trace("checkDir(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+            }
+
+            if (substatus == SSH_FX_EOF) {
+                return null;
+            }
+
+            throwStatusException(id, substatus, msg, lang);
+        }
+
+        throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
     }
 
     @Override
@@ -826,7 +928,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
 
         Buffer buffer = new ByteArrayBuffer();
         buffer.putString(path);
-        return checkOneName(receive(send(SSH_FXP_REALPATH, buffer)));
+        return checkOneName(SSH_FXP_REALPATH, buffer);
     }
 
     @Override
@@ -843,7 +945,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
             buffer.putInt(SSH_FILEXFER_ATTR_ALL);
         }
 
-        return checkAttributes(receive(send(SSH_FXP_STAT, buffer)));
+        return checkAttributes(SSH_FXP_STAT, buffer);
     }
 
     @Override
@@ -860,7 +962,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
             buffer.putInt(SSH_FILEXFER_ATTR_ALL);
         }
 
-        return checkAttributes(receive(send(SSH_FXP_LSTAT, buffer)));
+        return checkAttributes(SSH_FXP_LSTAT, buffer);
     }
 
     @Override
@@ -878,7 +980,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
             buffer.putInt(SSH_FILEXFER_ATTR_ALL);
         }
 
-        return checkAttributes(receive(send(SSH_FXP_FSTAT, buffer)));
+        return checkAttributes(SSH_FXP_FSTAT, buffer);
     }
 
     @Override
@@ -890,7 +992,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
         Buffer buffer = new ByteArrayBuffer();
         buffer.putString(path);
         writeAttributes(buffer, attributes);
-        checkStatus(receive(send(SSH_FXP_SETSTAT, buffer)));
+        checkStatus(SSH_FXP_SETSTAT, buffer);
     }
 
     @Override
@@ -903,7 +1005,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
         Buffer buffer = new ByteArrayBuffer(id.length + (2 * Long.SIZE) /* some extras */);
         buffer.putBytes(id);
         writeAttributes(buffer, attributes);
-        checkStatus(receive(send(SSH_FXP_FSETSTAT, buffer)));
+        checkStatus(SSH_FXP_FSETSTAT, buffer);
     }
 
     @Override
@@ -914,7 +1016,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
 
         Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
         buffer.putString(path);
-        return checkOneName(receive(send(SSH_FXP_READLINK, buffer)));
+        return checkOneName(SSH_FXP_READLINK, buffer);
     }
 
     @Override
@@ -931,12 +1033,12 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
             }
             buffer.putString(targetPath);
             buffer.putString(linkPath);
-            checkStatus(receive(send(SSH_FXP_SYMLINK, buffer)));
+            checkStatus(SSH_FXP_SYMLINK, buffer);
         } else {
             buffer.putString(targetPath);
             buffer.putString(linkPath);
             buffer.putBoolean(symbolic);
-            checkStatus(receive(send(SSH_FXP_LINK, buffer)));
+            checkStatus(SSH_FXP_LINK, buffer);
         }
     }
 
@@ -952,7 +1054,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
         buffer.putLong(offset);
         buffer.putLong(length);
         buffer.putInt(mask);
-        checkStatus(receive(send(SSH_FXP_BLOCK, buffer)));
+        checkStatus(SSH_FXP_BLOCK, buffer);
     }
 
     @Override
@@ -966,7 +1068,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
         buffer.putBytes(id);
         buffer.putLong(offset);
         buffer.putLong(length);
-        checkStatus(receive(send(SSH_FXP_UNBLOCK, buffer)));
+        checkStatus(SSH_FXP_UNBLOCK, buffer);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
index 12830d5..dd313db 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
@@ -39,7 +39,6 @@ import java.util.Set;
 import java.util.TreeMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import org.apache.sshd.client.SftpException;
 import org.apache.sshd.client.channel.ChannelSubsystem;
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.FactoryManagerUtils;
@@ -48,6 +47,7 @@ import org.apache.sshd.common.subsystem.sftp.SftpConstants;
 import org.apache.sshd.common.subsystem.sftp.extensions.ParserUtils;
 import org.apache.sshd.common.subsystem.sftp.extensions.VersionsParser.Versions;
 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.buffer.BufferUtils;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
@@ -58,19 +58,18 @@ import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 public class DefaultSftpClient extends AbstractSftpClient {
     private final ClientSession clientSession;
     private final ChannelSubsystem channel;
-    private final Map<Integer, Buffer> messages;
+    private final Map<Integer, Buffer> messages = new HashMap<>();
     private final AtomicInteger cmdId = new AtomicInteger(100);
     private final Buffer receiveBuffer = new ByteArrayBuffer();
     private final byte[] workBuf = new byte[Integer.SIZE / Byte.SIZE];  // TODO in JDK-8 use Integer.BYTES
     private boolean closing;
     private int version;
-    private final Map<String,byte[]> extensions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+    private final Map<String,byte[]> extensions = new TreeMap<String,byte[]>(String.CASE_INSENSITIVE_ORDER);
     private final Map<String,byte[]> exposedExtensions = Collections.unmodifiableMap(extensions);
 
     public DefaultSftpClient(ClientSession clientSession) throws IOException {
-        this.clientSession = clientSession;
+        this.clientSession = ValidateUtils.checkNotNull(clientSession, "No client session", GenericUtils.EMPTY_OBJECT_ARRAY);
         this.channel = clientSession.createSubsystemChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
-        this.messages = new HashMap<>();
         this.channel.setOut(new OutputStream() {
             @Override
             public void write(int b) throws IOException {
@@ -190,38 +189,44 @@ public class DefaultSftpClient extends AbstractSftpClient {
         int id = buffer.getInt();
         buffer.rpos(0);
         synchronized (messages) {
-            messages.put(id, buffer);
+            messages.put(Integer.valueOf(id), buffer);
             messages.notifyAll();
         }
     }
 
     @Override
     public int send(int cmd, Buffer buffer) throws IOException {
-        int id = cmdId.incrementAndGet();
+        int id = cmdId.incrementAndGet(), len = buffer.available();
+        if (log.isTraceEnabled()) {
+            log.trace("send(cmd={}, len={}) id = {}",
+                      Integer.valueOf(cmd), Integer.valueOf(len), Integer.valueOf(id));
+        }
+
         OutputStream dos = channel.getInvertedIn();
         BufferUtils.writeInt(dos, 1 /* cmd */ + (Integer.SIZE / Byte.SIZE) /* id */ + buffer.available(), workBuf);
         dos.write(cmd & 0xFF);
         BufferUtils.writeInt(dos, id, workBuf);
-        dos.write(buffer.array(), buffer.rpos(), buffer.available());
+        dos.write(buffer.array(), buffer.rpos(), len);
         dos.flush();
         return id;
     }
 
     @Override
     public Buffer receive(int id) throws IOException {
+        Integer reqId = Integer.valueOf(id);
         synchronized (messages) {
-            while (true) {
+            for (int count=1; ; count++) {
                 if (closing) {
                     throw new SshException("Channel has been closed");
                 }
-                Buffer buffer = messages.remove(id);
+                Buffer buffer = messages.remove(reqId);
                 if (buffer != null) {
                     return buffer;
                 }
                 try {
                     messages.wait();
                 } catch (InterruptedException e) {
-                    throw (IOException) new InterruptedIOException("Interrupted while waiting for messages").initCause(e);
+                    throw (IOException) new InterruptedIOException("Interrupted while waiting for messages at iteration #" + count).initCause(e);
                 }
             }
         }
@@ -292,10 +297,10 @@ public class DefaultSftpClient extends AbstractSftpClient {
             String msg = buffer.getString();
             String lang = buffer.getString();
             if (log.isTraceEnabled()) {
-                log.trace("init(id={}) - status: {} [{}] {}", id, substatus, lang, msg);
+                log.trace("init(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
             }
 
-            throw new SftpException(substatus, msg);
+            throwStatusException(id, substatus, msg, lang);
         } else {
             throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
         }
@@ -308,22 +313,24 @@ public class DefaultSftpClient extends AbstractSftpClient {
      */
     public int negotiateVersion(SftpVersionSelector selector) throws IOException {
         int current = getVersion();
-        Set<Integer> available = GenericUtils.asSortedSet(Collections.singleton(current));
+        Set<Integer> available = GenericUtils.asSortedSet(Collections.singleton(Integer.valueOf(current)));
         Map<String,?> parsed = getParsedServerExtensions();
         Collection<String> extensions = ParserUtils.supportedExtensions(parsed);
-        if (!GenericUtils.isEmpty(extensions) && extensions.contains(SftpConstants.EXT_VERSELECT)) {
+        if ((GenericUtils.size(extensions) > 0) && extensions.contains(SftpConstants.EXT_VERSELECT)) {
             Versions vers = GenericUtils.isEmpty(parsed) ? null : (Versions) parsed.get(SftpConstants.EXT_VERSIONS);
             Collection<String> reported = (vers == null) ? null : vers.versions;
             if (GenericUtils.size(reported) > 0) {
                 for (String v : reported) {
-                    available.add(Integer.valueOf(v));
+                    if (!available.add(Integer.valueOf(v))) {
+                        continue;
+                    }
                 }
             }
         }
 
-        int selected = selector.selectVersion(current, new ArrayList<>(available));
+        int selected = selector.selectVersion(current, new ArrayList<Integer>(available));
         if (log.isDebugEnabled()) {
-            log.debug("negotiateVersion({}) {} -> {}", current, available, selected);
+            log.debug("negotiateVersion({}) {} -> {}", Integer.valueOf(current), available, Integer.valueOf(selected));
         }
 
         if (selected == current) {
@@ -339,7 +346,7 @@ public class DefaultSftpClient extends AbstractSftpClient {
                                          + (Integer.SIZE / Byte.SIZE) + verVal.length());
         buffer.putString(SftpConstants.EXT_VERSELECT);
         buffer.putString(verVal);
-        checkStatus(receive(send(SftpConstants.SSH_FXP_EXTENDED, buffer)));
+        checkStatus(SftpConstants.SSH_FXP_EXTENDED, buffer);
         version = selected;
         return selected;
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpException.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpException.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpException.java
new file mode 100644
index 0000000..3228269
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpException.java
@@ -0,0 +1,44 @@
+/*
+ * 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.client.subsystem.sftp;
+
+import java.io.IOException;
+
+/**
+ * @author <a href="http://mina.apache.org">Apache MINA Project</a>
+ */
+public class SftpException extends IOException {
+    private static final long serialVersionUID = 8096963562429466995L;
+    private final int status;
+
+    public SftpException(int status, String msg) {
+        super(msg);
+        this.status = status;
+    }
+
+    public int getStatus() {
+        return status;
+    }
+
+    @Override
+    public String toString() {
+        String message = getMessage();
+        return "SFTP error (" + getStatus() + "): " + message;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileChannel.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileChannel.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileChannel.java
index 53dd6ca..19ded8c 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileChannel.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileChannel.java
@@ -41,7 +41,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
 
-import org.apache.sshd.client.SftpException;
 import org.apache.sshd.common.FactoryManagerUtils;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
index 986f015..b0ecafc 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
@@ -66,7 +66,6 @@ import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
-import org.apache.sshd.client.SftpException;
 import org.apache.sshd.client.SshClient;
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.client.subsystem.sftp.SftpClient.Attributes;
@@ -319,7 +318,7 @@ public class SftpFileSystemProvider extends FileSystemProvider {
             public Iterator<Path> iterator() {
                 return new Iterator<Path>() {
                     @SuppressWarnings("synthetic-access")
-                    private final Iterator<SftpClient.DirEntry> it = iter.iterator();
+                    private final Iterator<SftpClient.DirEntry> it = (iter == null) ? null : iter.iterator();
                     private boolean dotIgnored, dotdotIgnored;
                     private SftpClient.DirEntry curEntry = nextEntry();
 
@@ -340,7 +339,7 @@ public class SftpFileSystemProvider extends FileSystemProvider {
                     }
 
                     private SftpClient.DirEntry nextEntry() {
-                        while(it.hasNext()) {
+                        while((it != null) && it.hasNext()) {
                             SftpClient.DirEntry entry = it.next();
                             String name = entry.filename;
                             if (".".equals(name) && (!dotIgnored)) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractSftpClientExtension.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractSftpClientExtension.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractSftpClientExtension.java
index ccd55de..11b23f1 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractSftpClientExtension.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractSftpClientExtension.java
@@ -24,9 +24,9 @@ import java.io.StreamCorruptedException;
 import java.util.Collection;
 import java.util.Map;
 
-import org.apache.sshd.client.SftpException;
 import org.apache.sshd.client.subsystem.sftp.RawSftpClient;
 import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpException;
 import org.apache.sshd.client.subsystem.sftp.SftpClient.Handle;
 import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtension;
 import org.apache.sshd.common.SshException;
@@ -188,7 +188,7 @@ public abstract class AbstractSftpClientExtension extends AbstractLoggingBean im
             }
 
             if (substatus != SftpConstants.SSH_FX_OK) {
-                throw new SftpException(substatus, msg);
+                throwStatusException(id, substatus, msg, lang);
             }
             
             return null;
@@ -198,4 +198,8 @@ public abstract class AbstractSftpClientExtension extends AbstractLoggingBean im
             throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
         }
     }
+    
+    protected void throwStatusException(int id, int substatus, String msg, String lang) throws IOException {
+        throw new SftpException(substatus, msg);
+    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/main/java/org/apache/sshd/common/channel/AbstractChannel.java
----------------------------------------------------------------------
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 783cd17..2ec3f3f 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
@@ -39,6 +39,7 @@ import org.apache.sshd.common.session.ConnectionService;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.util.CloseableUtils;
 import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.Int2IntFunction;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.BufferUtils;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
@@ -58,7 +59,7 @@ public abstract class AbstractChannel
 
     public static final long DEFAULT_CHANNEL_CLOSE_TIMEOUT = 5000;
 
-    protected enum GracefulState {
+    protected static enum GracefulState {
         Opened, CloseSent, CloseReceived, Closed
     }
 
@@ -71,9 +72,9 @@ public abstract class AbstractChannel
     protected int id;
     protected int recipient;
     private final AtomicBoolean eof = new AtomicBoolean(false);
-    protected AtomicReference<GracefulState> gracefulState = new AtomicReference<>(GracefulState.Opened);
+    protected AtomicReference<GracefulState> gracefulState = new AtomicReference<GracefulState>(GracefulState.Opened);
     protected final DefaultCloseFuture gracefulFuture = new DefaultCloseFuture(lock);
-    protected final List<RequestHandler<Channel>> handlers = new ArrayList<>();
+    protected final List<RequestHandler<Channel>> handlers = new ArrayList<RequestHandler<Channel>>();
 
     protected AbstractChannel() {
         super();
@@ -137,8 +138,10 @@ public abstract class AbstractChannel
         String req = buffer.getString();
         boolean wantReply = buffer.getBoolean();
         if (log.isDebugEnabled()) {
-            log.debug("Received SSH_MSG_CHANNEL_REQUEST {} on channel {} (wantReply {})", req, this, wantReply);
+            log.debug("Received SSH_MSG_CHANNEL_REQUEST {} on channel {} (wantReply {})",
+                      req, this, Boolean.valueOf(wantReply));
         }
+
         for (RequestHandler<Channel> handler : handlers) {
             RequestHandler.Result result;
             try {
@@ -147,36 +150,44 @@ public abstract class AbstractChannel
                 log.warn("Error processing channel request " + req, e);
                 result = RequestHandler.Result.ReplyFailure;
             }
-            switch (result) {
-                case Replied:
-                    return;
-                case ReplySuccess:
-                    if (wantReply) {
-                        buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_SUCCESS);
-                        buffer.putInt(recipient);
-                        session.writePacket(buffer);
-                    }
-                    return;
-                case ReplyFailure:
-                    if (wantReply) {
-                        buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_FAILURE);
-                        buffer.putInt(recipient);
-                        session.writePacket(buffer);
-                    }
-                    return;
-                default:
-                    if (log.isTraceEnabled()) {
-                        log.trace("{}#process({}): {}", handler.getClass().getSimpleName(), req, result);
-                    }
+            
+            // if Unsupported then check the next handler in line
+            if (RequestHandler.Result.Unsupported.equals(result)) {
+                if (log.isTraceEnabled()) {
+                    log.trace("{}#process({}): {}", handler.getClass().getSimpleName(), req, result);
+                }
+            } else {
+                sendResponse(buffer, req, result, wantReply);
+                return;
             }
         }
 
+        // none of the handlers processed the request
         log.warn("Unknown channel request: {}", req);
-        if (wantReply) {
-            buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_FAILURE);
-            buffer.putInt(recipient);
-            session.writePacket(buffer);
+        sendResponse(buffer, req, RequestHandler.Result.Unsupported, wantReply);
+    }
+
+    protected void sendResponse(Buffer buffer, String req, RequestHandler.Result result, boolean wantReply) throws IOException {
+        if (log.isDebugEnabled()) {
+            log.debug("sendResponse({}) result={}, want-reply={}", req, result, Boolean.valueOf(wantReply));
         }
+
+        if (RequestHandler.Result.Replied.equals(result) || (!wantReply)) {
+            return;
+        }
+
+        byte cmd = RequestHandler.Result.ReplySuccess.equals(result)
+                 ? SshConstants.SSH_MSG_CHANNEL_SUCCESS
+                 : SshConstants.SSH_MSG_CHANNEL_FAILURE
+                 ;
+        buffer.clear();
+        // leave room for the SSH header
+        buffer.ensureCapacity(5 + 1 + (Integer.SIZE / Byte.SIZE), Int2IntFunction.Utils.add(Byte.SIZE));
+        buffer.rpos(5);
+        buffer.wpos(5);
+        buffer.putByte(cmd);
+        buffer.putInt(recipient);
+        session.writePacket(buffer);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/main/java/org/apache/sshd/common/channel/RequestHandler.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/channel/RequestHandler.java b/sshd-core/src/main/java/org/apache/sshd/common/channel/RequestHandler.java
index 8c262ef..4f653f1 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/channel/RequestHandler.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/channel/RequestHandler.java
@@ -18,6 +18,11 @@
  */
 package org.apache.sshd.common.channel;
 
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+
+import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 
 /**
@@ -31,20 +36,42 @@ public interface RequestHandler<T> {
         Unsupported,
         Replied,
         ReplySuccess,
-        ReplyFailure
+        ReplyFailure;
+        
+        public static final Set<Result> VALUES =
+                Collections.unmodifiableSet(EnumSet.allOf(Result.class));
+
+        /**
+         * @param name The result name - ignored if {@code null}/empty
+         * @return The matching {@link Result} value (case <U>insensitive</U>)
+         * or {@code null} if no match found
+         */
+        public static Result fromName(String name) {
+            if (GenericUtils.isEmpty(name)) {
+                return null;
+            }
+            
+            for (Result r : VALUES) {
+                if (name.equalsIgnoreCase(r.name())) {
+                    return r;
+                }
+            }
+            
+            return null;
+        }
     }
 
     /**
-     * Process the ssh-connection global request.
-     * If an exception is thrown, the ConnectionService will send a failure message if needed
-     * and the request will be considered handled.
-     *
-     * @param t
-     * @param request
-     * @param wantReply
-     * @param buffer
-     * @return
-     * @throws Exception
+     * Process an SSH request. If an exception is thrown, the ConnectionService
+     * will send a failure message if needed and the request will be considered handled.
+     * @param t The input parameter
+     * @param request The request string
+     * @param wantReply Whether a reply is requested
+     * @param buffer The {@link Buffer} with request specific data
+     * @return The {@link Result}
+     * @throws Exception If failed to handle the request - <B>Note:</B> in
+     * order to signal an unsupported request the {@link Result#Unsupported}
+     * value should be returned
      */
     Result process(T t, String request, boolean wantReply, Buffer buffer) throws Exception;
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultTcpipForwarder.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultTcpipForwarder.java b/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultTcpipForwarder.java
index 8af95ad..aad59eb 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultTcpipForwarder.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/forward/DefaultTcpipForwarder.java
@@ -19,6 +19,7 @@
 package org.apache.sshd.common.forward;
 
 import java.io.IOException;
+import java.io.OutputStream;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
 import java.util.HashMap;
@@ -412,8 +413,10 @@ public class DefaultTcpipForwarder extends CloseableUtils.AbstractInnerCloseable
             Buffer buffer = new ByteArrayBuffer();
             buffer.putBuffer(message);
             channel.waitFor(ClientChannel.OPENED | ClientChannel.CLOSED, Long.MAX_VALUE);
-            channel.getInvertedIn().write(buffer.array(), buffer.rpos(), buffer.available());
-            channel.getInvertedIn().flush();
+            
+            OutputStream outputStream = channel.getInvertedIn();
+            outputStream.write(buffer.array(), buffer.rpos(), buffer.available());
+            outputStream.flush();
         }
 
         @Override

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractConnectionService.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractConnectionService.java b/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractConnectionService.java
index 3d43fa2..47c6cc4 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractConnectionService.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractConnectionService.java
@@ -55,6 +55,7 @@ import org.apache.sshd.common.forward.TcpipForwarderFactory;
 import org.apache.sshd.common.future.SshFutureListener;
 import org.apache.sshd.common.util.CloseableUtils;
 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.server.channel.OpenChannelException;
@@ -419,9 +420,12 @@ public abstract class AbstractConnectionService extends CloseableUtils.AbstractI
     protected void globalRequest(Buffer buffer) throws Exception {
         String req = buffer.getString();
         boolean wantReply = buffer.getBoolean();
-        log.debug("Received SSH_MSG_GLOBAL_REQUEST {}", req);
+        if (log.isDebugEnabled()) {
+            log.debug("Received SSH_MSG_GLOBAL_REQUEST {} want-reply={}", req, Boolean.valueOf(wantReply));
+        }
+
         List<RequestHandler<ConnectionService>> handlers = session.getFactoryManager().getGlobalRequestHandlers();
-        if (handlers != null) {
+        if (GenericUtils.size(handlers) > 0) {
             for (RequestHandler<ConnectionService> handler : handlers) {
                 RequestHandler.Result result;
                 try {
@@ -430,33 +434,43 @@ public abstract class AbstractConnectionService extends CloseableUtils.AbstractI
                     log.warn("Error processing global request " + req, e);
                     result = RequestHandler.Result.ReplyFailure;
                 }
-                switch (result) {
-                    case Replied:
-                        return;
-                    case ReplySuccess:
-                        if (wantReply) {
-                            buffer = session.createBuffer(SshConstants.SSH_MSG_REQUEST_SUCCESS);
-                            session.writePacket(buffer);
-                        }
-                        return;
-                    case ReplyFailure:
-                        if (wantReply) {
-                            buffer = session.createBuffer(SshConstants.SSH_MSG_REQUEST_FAILURE);
-                            session.writePacket(buffer);
-                        }
-                        return;
-                    default:
-                        if (log.isTraceEnabled()) {
-                            log.trace("globalRequest({}) {}#process: {}", req, handler.getClass().getSimpleName(), result);
-                        }
+
+                // if Unsupported then check the next handler in line
+                if (RequestHandler.Result.Unsupported.equals(result)) {
+                    if (log.isTraceEnabled()) {
+                        log.trace("{}#process({}): {}", handler.getClass().getSimpleName(), req, result);
+                    }
+                } else {
+                    sendResponse(buffer, req, result, wantReply);
+                    return;
                 }
             }
         }
+
         log.warn("Unknown global request: {}", req);
-        if (wantReply) {
-            buffer = session.createBuffer(SshConstants.SSH_MSG_REQUEST_FAILURE);
-            session.writePacket(buffer);
+        sendResponse(buffer, req, RequestHandler.Result.Unsupported, wantReply);
+    }
+
+    protected void sendResponse(Buffer buffer, String req, RequestHandler.Result result, boolean wantReply) throws IOException {
+        if (log.isDebugEnabled()) {
+            log.debug("sendResponse({}) result={}, want-reply={}", req, result, Boolean.valueOf(wantReply));
         }
+
+        if (RequestHandler.Result.Replied.equals(result) || (!wantReply)) {
+            return;
+        }
+
+        byte cmd = RequestHandler.Result.ReplySuccess.equals(result)
+                 ? SshConstants.SSH_MSG_CHANNEL_SUCCESS
+                 : SshConstants.SSH_MSG_CHANNEL_FAILURE
+                 ;
+        buffer.clear();
+        // leave room for the SSH header
+        buffer.ensureCapacity(5 + 1 + (Integer.SIZE / Byte.SIZE), Int2IntFunction.Utils.add(Byte.SIZE));
+        buffer.rpos(5);
+        buffer.wpos(5);
+        buffer.putByte(cmd);
+        session.writePacket(buffer);
     }
 
     protected void requestSuccess(Buffer buffer) throws Exception {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java b/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java
index 535301c..aabc4cb 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java
@@ -37,6 +37,7 @@ import java.util.Map;
 import java.util.Queue;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -181,7 +182,7 @@ public abstract class AbstractSession extends CloseableUtils.AbstractInnerClosea
      */
     public AbstractSession(boolean isServer, FactoryManager factoryManager, IoSession ioSession) {
         this.isServer = isServer;
-        this.factoryManager = factoryManager;
+        this.factoryManager = ValidateUtils.checkNotNull(factoryManager, "No factory manager provided", GenericUtils.EMPTY_OBJECT_ARRAY);
         this.ioSession = ioSession;
         sessionListenerProxy = EventListenerUtils.proxyWrapper(SessionListener.class, getClass().getClassLoader(), listeners);
         random = factoryManager.getRandomFactory().create();
@@ -552,23 +553,25 @@ public abstract class AbstractSession extends CloseableUtils.AbstractInnerClosea
 
     @SuppressWarnings("unchecked")
     @Override
-    public IoWriteFuture writePacket(Buffer buffer, long timeout, TimeUnit unit) throws IOException {
+    public IoWriteFuture writePacket(Buffer buffer, final long timeout, final TimeUnit unit) throws IOException {
         final IoWriteFuture writeFuture = writePacket(buffer);
         final DefaultSshFuture<IoWriteFuture> future = (DefaultSshFuture<IoWriteFuture>) writeFuture;
-        final ScheduledFuture<?> sched = factoryManager.getScheduledExecutorService().schedule(new Runnable() {
-            @SuppressWarnings("synthetic-access")
-            @Override
-            public void run() {
-                log.info("Timeout writing packet.");
-                future.setValue(new TimeoutException());
-            }
-        }, timeout, unit);
+        ScheduledExecutorService executor = factoryManager.getScheduledExecutorService(); 
+        final ScheduledFuture<?> sched = executor.schedule(new Runnable() {
+                @SuppressWarnings("synthetic-access")
+                @Override
+                public void run() {
+                    Throwable t = new TimeoutException("Timeout writing packet: " + timeout + " " + unit);
+                    log.info(t.getMessage());
+                    future.setValue(t);
+                }
+            }, timeout, unit);
         future.addListener(new SshFutureListener<IoWriteFuture>() {
-            @Override
-            public void operationComplete(IoWriteFuture future) {
-                sched.cancel(false);
-            }
-        });
+                @Override
+                public void operationComplete(IoWriteFuture future) {
+                    sched.cancel(false);
+                }
+            });
         return writeFuture;
     }
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/AbstractOpenSSHExtensionParser.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/AbstractOpenSSHExtensionParser.java b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/AbstractOpenSSHExtensionParser.java
index c6905f5..d408e3b 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/AbstractOpenSSHExtensionParser.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/openssh/AbstractOpenSSHExtensionParser.java
@@ -90,7 +90,7 @@ public abstract class AbstractOpenSSHExtensionParser extends AbstractParser<Open
             try {
                 return getClass().cast(super.clone());
             } catch(CloneNotSupportedException e) {
-                throw new RuntimeException("Unexpected clone exception: " + e.getLocalizedMessage());
+                throw new RuntimeException("Unexpected clone exception " + toString() + ": " + e.getMessage());
             }
         }
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/main/java/org/apache/sshd/common/util/io/FileInfoExtractor.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/io/FileInfoExtractor.java b/sshd-core/src/main/java/org/apache/sshd/common/util/io/FileInfoExtractor.java
new file mode 100644
index 0000000..56be141
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/io/FileInfoExtractor.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.util.io;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface FileInfoExtractor<T> {
+    T infoOf(Path file, LinkOption ... options) throws IOException;
+    
+    FileInfoExtractor<Boolean> EXISTS = new FileInfoExtractor<Boolean>() {
+            @Override
+            public Boolean infoOf(Path file, LinkOption... options) throws IOException {
+                return Boolean.valueOf(Files.exists(file, options));
+            }
+        };
+   
+    FileInfoExtractor<Boolean> ISDIR = new FileInfoExtractor<Boolean>() {
+            @Override
+            public Boolean infoOf(Path file, LinkOption... options) throws IOException {
+                return Boolean.valueOf(Files.isDirectory(file, options));
+            }
+        };
+
+    FileInfoExtractor<Boolean> ISREG = new FileInfoExtractor<Boolean>() {
+            @Override
+            public Boolean infoOf(Path file, LinkOption... options) throws IOException {
+                return Boolean.valueOf(Files.isRegularFile(file, options));
+            }
+        };
+
+    FileInfoExtractor<Boolean> ISSYMLINK = new FileInfoExtractor<Boolean>() {
+            @Override
+            public Boolean infoOf(Path file, LinkOption... options) throws IOException {
+                return Boolean.valueOf(Files.isSymbolicLink(file));
+            }
+        };
+
+    FileInfoExtractor<Long> SIZE = new FileInfoExtractor<Long>() {
+            @Override
+            public Long infoOf(Path file, LinkOption... options) throws IOException {
+                return Long.valueOf(Files.size(file));
+            }
+        };
+
+    FileInfoExtractor<Set<PosixFilePermission>> PERMISSIONS = new FileInfoExtractor<Set<PosixFilePermission>>() {
+            @Override
+            public Set<PosixFilePermission> infoOf(Path file, LinkOption... options) throws IOException {
+                return IoUtils.getPermissions(file, options);
+            }
+        };
+
+    FileInfoExtractor<FileTime> LASTMODIFIED = new FileInfoExtractor<FileTime>() {
+        @Override
+        public FileTime infoOf(Path file, LinkOption... options) throws IOException {
+            return Files.getLastModifiedTime(file, options);
+        }
+        
+    };
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/main/java/org/apache/sshd/server/global/CancelTcpipForwardHandler.java
----------------------------------------------------------------------
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 ebc6914..557af0a 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
@@ -24,6 +24,7 @@ import org.apache.sshd.common.forward.TcpipForwarder;
 import org.apache.sshd.common.session.ConnectionService;
 import org.apache.sshd.common.session.ConnectionServiceRequestHandler;
 import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.Int2IntFunction;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 
@@ -54,9 +55,15 @@ public class CancelTcpipForwardHandler extends AbstractLoggingBean implements Co
             forwarder.localPortForwardingCancelled(socketAddress);
 
             if (wantReply) {
-                Session session = connectionService.getSession();
-                buffer = session.createBuffer(SshConstants.SSH_MSG_REQUEST_SUCCESS);
+                buffer.clear();
+                // leave room for the SSH header
+                buffer.ensureCapacity(5 + 1 + (Integer.SIZE / Byte.SIZE), Int2IntFunction.Utils.add(Byte.SIZE));
+                buffer.rpos(5);
+                buffer.wpos(5);
+                buffer.putByte(SshConstants.SSH_MSG_REQUEST_SUCCESS);
                 buffer.putInt(port);
+
+                Session session = connectionService.getSession();
                 session.writePacket(buffer);
             }
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d86de53/sshd-core/src/main/java/org/apache/sshd/server/global/TcpipForwardHandler.java
----------------------------------------------------------------------
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 33d64b0..3f5dc4d 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
@@ -24,6 +24,7 @@ import org.apache.sshd.common.forward.TcpipForwarder;
 import org.apache.sshd.common.session.ConnectionService;
 import org.apache.sshd.common.session.ConnectionServiceRequestHandler;
 import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.Int2IntFunction;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 
@@ -54,9 +55,15 @@ public class TcpipForwardHandler extends AbstractLoggingBean implements Connecti
 
             port = bound.getPort();
             if (wantReply){
-                Session session = connectionService.getSession();
-                buffer = session.createBuffer(SshConstants.SSH_MSG_REQUEST_SUCCESS);
+                buffer.clear();
+                // leave room for the SSH header
+                buffer.ensureCapacity(5 + 1 + (Integer.SIZE / Byte.SIZE), Int2IntFunction.Utils.add(Byte.SIZE));
+                buffer.rpos(5);
+                buffer.wpos(5);
+                buffer.putByte(SshConstants.SSH_MSG_REQUEST_SUCCESS);
                 buffer.putInt(port);
+
+                Session session = connectionService.getSession();
                 session.writePacket(buffer);
             }
             return Result.Replied;