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/05/20 09:48:12 UTC

mina-sshd git commit: [SSHD-469] Implement java.nio.channels.Channel interface where applicable

Repository: mina-sshd
Updated Branches:
  refs/heads/master 9c9f4a614 -> a7e2e3fa7


[SSHD-469] Implement java.nio.channels.Channel interface 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/a7e2e3fa
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/a7e2e3fa
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/a7e2e3fa

Branch: refs/heads/master
Commit: a7e2e3fa75415f1ae660b309a20564fe81ee5b77
Parents: 9c9f4a6
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Wed May 20 10:48:00 2015 +0300
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Wed May 20 10:48:00 2015 +0300

----------------------------------------------------------------------
 .../java/org/apache/sshd/client/SftpClient.java |  24 +-
 .../sshd/client/sftp/DefaultSftpClient.java     | 288 ++++++++++++-------
 .../apache/sshd/client/sftp/SftpFileSystem.java |   9 +-
 .../java/org/apache/sshd/common/Closeable.java  |   4 +-
 .../sshd/common/channel/AbstractChannel.java    |   5 +
 .../common/channel/BufferedIoOutputStream.java  |   6 +-
 .../channel/ChannelAsyncOutputStream.java       |  10 +-
 .../common/channel/ChannelOutputStream.java     |  14 +-
 .../common/channel/ChannelPipedInputStream.java |   8 +-
 .../channel/ChannelPipedOutputStream.java       |  32 ++-
 .../sshd/common/channel/ChannelPipedSink.java   |  40 +++
 .../org/apache/sshd/common/channel/Window.java  |   4 +-
 .../common/channel/WindowClosedException.java   |   2 +
 .../apache/sshd/common/util/CloseableUtils.java |  10 +
 .../common/util/io/InputStreamWithChannel.java  |  32 +++
 .../common/util/io/OutputStreamWithChannel.java |  32 +++
 .../channel/ChannelPipedInputStreamTest.java    |  34 ++-
 .../channel/ChannelPipedOutputStreamTest.java   |  91 ++++++
 18 files changed, 509 insertions(+), 136 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a7e2e3fa/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java
index b4282b5..b0ca95e 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/SftpClient.java
@@ -30,6 +30,7 @@ import java.io.OutputStream;
 import java.nio.file.attribute.FileTime;
 import java.util.Collection;
 import java.util.EnumSet;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -70,7 +71,7 @@ public interface SftpClient extends Closeable {
     }
 
     public static class Attributes {
-        public EnumSet<Attribute> flags = EnumSet.noneOf(Attribute.class);
+        public final Set<Attribute> flags = EnumSet.noneOf(Attribute.class);
         public long size;
         public byte type;
         public int uid;
@@ -84,6 +85,23 @@ public interface SftpClient extends Closeable {
         public FileTime accessTime;
         public FileTime createTime;
         public FileTime modifyTime;
+
+        @Override
+        public String toString() {
+            return "type=" + type
+                 + ";size=" + size
+                 + ";uid=" + uid
+                 + ";gid=" + gid
+                 + ";perms=0x" + Integer.toHexString(perms)
+                 + ";flags=" + flags
+                 + ";owner=" + owner
+                 + ";group=" + group
+                 + ";aTime=(" + atime + ")[" + accessTime + "]"
+                 + ";cTime=(" + ctime + ")[" + createTime + "]"
+                 + ";mTime=(" + mtime + ")[" + modifyTime + "]"
+                 ;
+        }
+
         public Attributes size(long size) {
             flags.add(Attribute.Size);
             this.size = size;
@@ -243,10 +261,10 @@ public interface SftpClient extends Closeable {
 
     InputStream read(String path) throws IOException;
 
-    InputStream read(String path, EnumSet<OpenMode> mode) throws IOException;
+    InputStream read(String path, Collection<OpenMode> mode) throws IOException;
 
     OutputStream write(String path) throws IOException;
 
-    OutputStream write(String path, EnumSet<OpenMode> mode) throws IOException;
+    OutputStream write(String path, Collection<OpenMode> mode) throws IOException;
 
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a7e2e3fa/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java
index 4e5c395..6381046 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java
@@ -109,13 +109,16 @@ import org.apache.sshd.client.SftpClient;
 import org.apache.sshd.client.SftpException;
 import org.apache.sshd.client.channel.ChannelSubsystem;
 import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.util.AbstractLoggingBean;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.apache.sshd.common.util.io.InputStreamWithChannel;
+import org.apache.sshd.common.util.io.OutputStreamWithChannel;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class DefaultSftpClient implements SftpClient {
+public class DefaultSftpClient extends AbstractLoggingBean implements SftpClient {
     private final ClientSession clientSession;
     private final ChannelSubsystem channel;
     private final Map<Integer, Buffer> messages;
@@ -146,6 +149,7 @@ public class DefaultSftpClient implements SftpClient {
             throw (IOException) new InterruptedIOException().initCause(e);
         }
         this.channel.onClose(new Runnable() {
+            @SuppressWarnings("synthetic-access")
             @Override
             public void run() {
                 synchronized (messages) {
@@ -229,7 +233,7 @@ public class DefaultSftpClient implements SftpClient {
         int id = buffer.getInt();
         buffer.rpos(0);
         synchronized (messages) {
-            messages.put(id, buffer);
+            messages.put(Integer.valueOf(id), buffer);
             messages.notifyAll();
         }
     }
@@ -252,7 +256,7 @@ public class DefaultSftpClient implements SftpClient {
                 if (closing) {
                     throw new SshException("Channel has been closed");
                 }
-                Buffer buffer = messages.remove(id);
+                Buffer buffer = messages.remove(Integer.valueOf(id));
                 if (buffer != null) {
                     return buffer;
                 }
@@ -321,9 +325,13 @@ public class DefaultSftpClient implements SftpClient {
             int substatus = buffer.getInt();
             String msg = buffer.getString();
             String lang = buffer.getString();
+            if (log.isTraceEnabled()) {
+                log.trace("init(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+            }
+
             throw new SftpException(substatus, msg);
         } else {
-            throw new SshException("Unexpected SFTP packet received: " + type);
+            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
         }
     }
 
@@ -335,11 +343,15 @@ public class DefaultSftpClient implements SftpClient {
             int substatus = buffer.getInt();
             String msg = buffer.getString();
             String lang = buffer.getString();
+            if (log.isTraceEnabled()) {
+                log.trace("checkStatus(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+            }
+
             if (substatus != SSH_FX_OK) {
                 throw new SftpException(substatus, msg);
             }
         } else {
-            throw new SshException("Unexpected SFTP packet received: " + type);
+            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
         }
     }
 
@@ -351,12 +363,15 @@ public class DefaultSftpClient implements SftpClient {
             int substatus = buffer.getInt();
             String msg = buffer.getString();
             String lang = buffer.getString();
+            if (log.isTraceEnabled()) {
+                log.trace("checkHandle(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+            }
             throw new SftpException(substatus, msg);
         } else if (type == SSH_FXP_HANDLE) {
             String handle = buffer.getString();
             return new Handle(handle);
         } else {
-            throw new SshException("Unexpected SFTP packet received: " + type);
+            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
         }
     }
 
@@ -368,11 +383,14 @@ public class DefaultSftpClient implements SftpClient {
             int substatus = buffer.getInt();
             String msg = buffer.getString();
             String lang = buffer.getString();
+            if (log.isTraceEnabled()) {
+                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);
+            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
         }
     }
 
@@ -384,20 +402,26 @@ public class DefaultSftpClient implements SftpClient {
             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);
+            }
             throw new SftpException(substatus, msg);
         } else if (type == SSH_FXP_NAME) {
             int len = buffer.getInt();
             if (len != 1) {
                 throw new SshException("SFTP error: received " + len + " names instead of 1");
             }
-            String name = buffer.getString();
+            String name = buffer.getString(), longName = null;
             if (version == SFTP_V3) {
-                String longName = buffer.getString();
+                longName = buffer.getString();
             }
             Attributes attrs = readAttributes(buffer);
+            if (log.isTraceEnabled()) {
+                log.trace("checkOneName(id={}) ({})[{}]: {}", Integer.valueOf(id), name, longName, attrs);
+            }
             return name;
         } else {
-            throw new SshException("Unexpected SFTP packet received: " + type);
+            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
         }
     }
 
@@ -441,15 +465,16 @@ public class DefaultSftpClient implements SftpClient {
             
             // update the permissions according to the type
             switch (attrs.type) {
-            case SSH_FILEXFER_TYPE_REGULAR:
-                attrs.perms |= S_IFREG;
-                break;
-            case SSH_FILEXFER_TYPE_DIRECTORY:
-                attrs.perms |= S_IFDIR;
-                break;
-            case SSH_FILEXFER_TYPE_SYMLINK:
-                attrs.perms |= S_IFLNK;
-                break;
+                case SSH_FILEXFER_TYPE_REGULAR:
+                    attrs.perms |= S_IFREG;
+                    break;
+                case SSH_FILEXFER_TYPE_DIRECTORY:
+                    attrs.perms |= S_IFDIR;
+                    break;
+                case SSH_FILEXFER_TYPE_SYMLINK:
+                    attrs.perms |= S_IFLNK;
+                    break;
+                default:    // do nothing
             }
 
             if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
@@ -489,18 +514,19 @@ public class DefaultSftpClient implements SftpClient {
             int flags = 0;
             for (Attribute a : attributes.flags) {
                 switch (a) {
-                case Size:
-                    flags |= SSH_FILEXFER_ATTR_SIZE;
-                    break;
-                case UidGid:
-                    flags |= SSH_FILEXFER_ATTR_UIDGID;
-                    break;
-                case Perms:
-                    flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
-                    break;
-                case AcModTime:
-                    flags |= SSH_FILEXFER_ATTR_ACMODTIME;
-                    break;
+                    case Size:
+                        flags |= SSH_FILEXFER_ATTR_SIZE;
+                        break;
+                    case UidGid:
+                        flags |= SSH_FILEXFER_ATTR_UIDGID;
+                        break;
+                    case Perms:
+                        flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
+                        break;
+                    case AcModTime:
+                        flags |= SSH_FILEXFER_ATTR_ACMODTIME;
+                        break;
+                    default:    // do nothing
                 }
             }
             buffer.putInt(flags);
@@ -522,24 +548,25 @@ public class DefaultSftpClient implements SftpClient {
             int flags = 0;
             for (Attribute a : attributes.flags) {
                 switch (a) {
-                case Size:
-                    flags |= SSH_FILEXFER_ATTR_SIZE;
-                    break;
-                case OwnerGroup:
-                    flags |= SSH_FILEXFER_ATTR_OWNERGROUP;
-                    break;
-                case Perms:
-                    flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
-                    break;
-                case AccessTime:
-                    flags |= SSH_FILEXFER_ATTR_ACCESSTIME;
-                    break;
-                case ModifyTime:
-                    flags |= SSH_FILEXFER_ATTR_MODIFYTIME;
-                    break;
-                case CreateTime:
-                    flags |= SSH_FILEXFER_ATTR_CREATETIME;
-                    break;
+                    case Size:
+                        flags |= SSH_FILEXFER_ATTR_SIZE;
+                        break;
+                    case OwnerGroup:
+                        flags |= SSH_FILEXFER_ATTR_OWNERGROUP;
+                        break;
+                    case Perms:
+                        flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
+                        break;
+                    case AccessTime:
+                        flags |= SSH_FILEXFER_ATTR_ACCESSTIME;
+                        break;
+                    case ModifyTime:
+                        flags |= SSH_FILEXFER_ATTR_MODIFYTIME;
+                        break;
+                    case CreateTime:
+                        flags |= SSH_FILEXFER_ATTR_CREATETIME;
+                        break;
+                    default:    // do nothing
                 }
             }
             buffer.putInt(flags);
@@ -583,7 +610,7 @@ public class DefaultSftpClient implements SftpClient {
             }
             // TODO: acl
         } else {
-            throw new IllegalStateException();
+            throw new UnsupportedOperationException("writeAttributes(" + attributes + ") unsupported version: " + version);
         }
     }
 
@@ -595,24 +622,25 @@ public class DefaultSftpClient implements SftpClient {
             int mode = 0;
             for (OpenMode m : options) {
                 switch (m) {
-                case Read:
-                    mode |= SSH_FXF_READ;
-                    break;
-                case Write:
-                    mode |= SSH_FXF_WRITE;
-                    break;
-                case Append:
-                    mode |= SSH_FXF_APPEND;
-                    break;
-                case Create:
-                    mode |= SSH_FXF_CREAT;
-                    break;
-                case Truncate:
-                    mode |= SSH_FXF_TRUNC;
-                    break;
-                case Exclusive:
-                    mode |= SSH_FXF_EXCL;
-                    break;
+                    case Read:
+                        mode |= SSH_FXF_READ;
+                        break;
+                    case Write:
+                        mode |= SSH_FXF_WRITE;
+                        break;
+                    case Append:
+                        mode |= SSH_FXF_APPEND;
+                        break;
+                    case Create:
+                        mode |= SSH_FXF_CREAT;
+                        break;
+                    case Truncate:
+                        mode |= SSH_FXF_TRUNC;
+                        break;
+                    case Exclusive:
+                        mode |= SSH_FXF_EXCL;
+                        break;
+                    default:    // do nothing
                 }
             }
             buffer.putInt(mode);
@@ -676,12 +704,13 @@ public class DefaultSftpClient implements SftpClient {
             int opts = 0;
             for (CopyMode opt : options) {
                 switch (opt) {
-                case Atomic:
-                    opts |= SSH_FXP_RENAME_ATOMIC;
-                    break;
-                case Overwrite:
-                    opts |= SSH_FXP_RENAME_OVERWRITE;
-                    break;
+                    case Atomic:
+                        opts |= SSH_FXP_RENAME_ATOMIC;
+                        break;
+                    case Overwrite:
+                        opts |= SSH_FXP_RENAME_OVERWRITE;
+                        break;
+                    default:    // do nothing
                 }
             }
             buffer.putInt(opts);
@@ -701,7 +730,6 @@ public class DefaultSftpClient implements SftpClient {
     }
 
     protected int checkData(Buffer buffer, int dstoff, byte[] dst) throws IOException {
-        int len;
         int length = buffer.getInt();
         int type = buffer.getByte();
         int id = buffer.getInt();
@@ -709,16 +737,21 @@ public class DefaultSftpClient implements SftpClient {
             int substatus = buffer.getInt();
             String msg = buffer.getString();
             String lang = buffer.getString();
+            if (log.isTraceEnabled()) {
+                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) {
-            len = buffer.getInt();
+            int len = buffer.getInt();
             buffer.getRawBytes(dst, dstoff, len);
             return len;
         } else {
-            throw new SshException("Unexpected SFTP packet received: " + type);
+            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
         }
     }
 
@@ -778,6 +811,9 @@ public class DefaultSftpClient implements SftpClient {
             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;
             }
@@ -789,11 +825,15 @@ public class DefaultSftpClient implements SftpClient {
                 String name = buffer.getString();
                 String longName = (version == SFTP_V3) ? buffer.getString() : null;
                 Attributes attrs = readAttributes(buffer);
+                if (log.isTraceEnabled()) {
+                    log.trace("checkDir(id={})[{}] ({})[{}]: {}", Integer.valueOf(id), Integer.valueOf(i), name, longName, attrs);
+                }
+
                 entries[i] = new DirEntry(name, longName, attrs);
             }
             return entries;
         } else {
-            throw new SshException("Unexpected SFTP packet received: " + type);
+            throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
         }
     }
 
@@ -964,27 +1004,36 @@ public class DefaultSftpClient implements SftpClient {
     }
 
     @Override
-    public InputStream read(final String path, final EnumSet<OpenMode> mode) throws IOException {
-        return new InputStream() {
-            byte[] buffer = new byte[32 * 1024];
-            int index = 0;
-            int available = 0;
-            Handle handle = DefaultSftpClient.this.open(path, mode);
-            long offset;
+    public InputStream read(final String path, final Collection<OpenMode> mode) throws IOException {
+        return new InputStreamWithChannel() {
+            private byte[] bb = new byte[1];
+            private byte[] buffer = new byte[32 * 1024];
+            private int index;
+            private int available;
+            private Handle handle = DefaultSftpClient.this.open(path, mode);
+            private long offset;
+
+            @Override
+            public boolean isOpen() {
+                return handle != null;
+            }
+
             @Override
             public int read() throws IOException {
-                byte[] buffer = new byte[1];
-                int read = read(buffer, 0, 1);
+                int read = read(bb, 0, 1);
                 if (read > 0) {
-                    return buffer[0];
+                    return bb[0];
                 }
+
                 return read;
             }
+
             @Override
             public int read(byte[] b, int off, int len) throws IOException {
-                if (handle == null) {
+                if (!isOpen()) {
                     throw new IOException("Stream closed");
                 }
+
                 int idx = off;
                 while (len > 0) {
                     if (index >= available) {
@@ -1010,11 +1059,15 @@ public class DefaultSftpClient implements SftpClient {
                 }
                 return idx - off;
             }
+
             @Override
             public void close() throws IOException {
-                if (handle != null) {
-                    DefaultSftpClient.this.close(handle);
-                    handle = null;
+                if (isOpen()) {
+                    try {
+                        DefaultSftpClient.this.close(handle);
+                    } finally {
+                        handle = null;
+                    }
                 }
             }
         };
@@ -1026,20 +1079,31 @@ public class DefaultSftpClient implements SftpClient {
     }
 
     @Override
-    public OutputStream write(final String path, final EnumSet<OpenMode> mode) throws IOException {
-        return new OutputStream() {
-            byte[] buffer = new byte[32 * 1024];
-            int index = 0;
-            Handle handle = DefaultSftpClient.this.open(path, mode);
-            long offset;
+    public OutputStream write(final String path, final Collection<OpenMode> mode) throws IOException {
+        return new OutputStreamWithChannel() {
+            private byte[] bb = new byte[1];
+            private byte[] buffer = new byte[32 * 1024];
+            private int index;
+            private Handle handle = DefaultSftpClient.this.open(path, mode);
+            private long offset;
+
+            @Override
+            public boolean isOpen() {
+                return handle != null;
+            }
+
             @Override
             public void write(int b) throws IOException {
-                byte[] buffer = new byte[1];
-                buffer[0] = (byte) b;
-                write(buffer, 0, 1);
+                bb[0] = (byte) b;
+                write(bb, 0, 1);
             }
+
             @Override
             public void write(byte[] b, int off, int len) throws IOException {
+                if (!isOpen()) {
+                    throw new IOException("write(len=" + len + ") Stream is closed");
+                }
+
                 do {
                     int nb = Math.min(len, buffer.length - index);
                     System.arraycopy(b, off, buffer, index, nb);
@@ -1051,20 +1115,34 @@ public class DefaultSftpClient implements SftpClient {
                     len -= nb;
                 } while (len > 0);
             }
+
             @Override
             public void flush() throws IOException {
+                if (!isOpen()) {
+                    throw new IOException("flush() Stream is closed");
+                }
+
                 DefaultSftpClient.this.write(handle, offset, buffer, 0, index);
                 offset += index;
                 index = 0;
             }
+
             @Override
             public void close() throws IOException {
-                if (index > 0) {
-                    flush();
+                if (isOpen()) {
+                    try {
+                        try {
+                            if (index > 0) {
+                                flush();
+                            }
+                        } finally {
+                            DefaultSftpClient.this.close(handle);
+                        }
+                    } finally {
+                        handle = null;
+                    }
                 }
-                DefaultSftpClient.this.close(handle);
             }
         };
     }
-
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a7e2e3fa/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystem.java b/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystem.java
index 734a3e2..574d282 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystem.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystem.java
@@ -27,7 +27,6 @@ import java.nio.file.attribute.UserPrincipalLookupService;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.Queue;
 import java.util.Set;
@@ -64,6 +63,7 @@ public class SftpFileSystem extends BaseFileSystem<SftpPath> {
         return session;
     }
 
+    @SuppressWarnings("synthetic-access")
     public SftpClient getClient() throws IOException {
         Wrapper wrapper = wrappers.get();
         if (wrapper == null) {
@@ -105,6 +105,7 @@ public class SftpFileSystem extends BaseFileSystem<SftpPath> {
         return new DefaultUserPrincipalLookupService();
     }
 
+    @Override
     public SftpPath getDefaultDir() {
         return defaultDir;
     }
@@ -128,6 +129,7 @@ public class SftpFileSystem extends BaseFileSystem<SftpPath> {
             return false;
         }
 
+        @SuppressWarnings("synthetic-access")
         @Override
         public void close() throws IOException {
             if (count.decrementAndGet() == 0) {
@@ -248,7 +250,7 @@ public class SftpFileSystem extends BaseFileSystem<SftpPath> {
         }
 
         @Override
-        public InputStream read(String path, EnumSet<OpenMode> mode) throws IOException {
+        public InputStream read(String path, Collection<OpenMode> mode) throws IOException {
             return delegate.read(path, mode);
         }
 
@@ -258,7 +260,7 @@ public class SftpFileSystem extends BaseFileSystem<SftpPath> {
         }
 
         @Override
-        public OutputStream write(String path, EnumSet<OpenMode> mode) throws IOException {
+        public OutputStream write(String path, Collection<OpenMode> mode) throws IOException {
             return delegate.write(path, mode);
         }
 
@@ -302,6 +304,7 @@ public class SftpFileSystem extends BaseFileSystem<SftpPath> {
             this.name = name;
         }
 
+        @Override
         public String getName() {
             return name;
         }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a7e2e3fa/sshd-core/src/main/java/org/apache/sshd/common/Closeable.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/Closeable.java b/sshd-core/src/main/java/org/apache/sshd/common/Closeable.java
index 6afac75..70cfa71 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/Closeable.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/Closeable.java
@@ -18,6 +18,8 @@
  */
 package org.apache.sshd.common;
 
+import java.nio.channels.Channel;
+
 import org.apache.sshd.common.future.CloseFuture;
 
 /**
@@ -27,7 +29,7 @@ import org.apache.sshd.common.future.CloseFuture;
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public interface Closeable extends java.io.Closeable {
+public interface Closeable extends Channel {
 
     /**
      * Close this resource asynchronously and return a future.

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a7e2e3fa/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 7b8c7ef..c539f28 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
@@ -137,8 +137,13 @@ public abstract class AbstractChannel extends CloseableUtils.AbstractInnerClosea
                         session.writePacket(buffer);
                     }
                     return;
+                default:
+                    if (log.isTraceEnabled()) {
+                        log.trace("{}#process({}): {}", handler.getClass().getSimpleName(), req, result);
+                    }
             }
         }
+
         log.warn("Unknown channel request: {}", req);
         if (wantReply) {
             buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_FAILURE);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a7e2e3fa/sshd-core/src/main/java/org/apache/sshd/common/channel/BufferedIoOutputStream.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/channel/BufferedIoOutputStream.java b/sshd-core/src/main/java/org/apache/sshd/common/channel/BufferedIoOutputStream.java
index b9defba..6ea875a 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/channel/BufferedIoOutputStream.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/channel/BufferedIoOutputStream.java
@@ -43,6 +43,7 @@ public class BufferedIoOutputStream extends CloseableUtils.AbstractInnerCloseabl
         this.out = out;
     }
 
+    @Override
     public IoWriteFuture write(Buffer buffer) {
         final ChannelAsyncOutputStream.IoWriteFutureImpl future = new ChannelAsyncOutputStream.IoWriteFutureImpl(buffer);
         if (isClosing()) {
@@ -59,14 +60,17 @@ public class BufferedIoOutputStream extends CloseableUtils.AbstractInnerCloseabl
         if (future != null) {
             if (currentWrite.compareAndSet(null, future)) {
                 out.write(future.getBuffer()).addListener(new SshFutureListener<IoWriteFuture>() {
+                    @Override
                     public void operationComplete(IoWriteFuture f) {
                         if (f.isWritten()) {
-                            future.setValue(true);
+                            future.setValue(Boolean.TRUE);
                         } else {
                             future.setValue(f.getException());
                         }
                         finishWrite();
                     }
+
+                    @SuppressWarnings("synthetic-access")
                     private void finishWrite() {
                         writes.remove(future);
                         currentWrite.compareAndSet(future, null);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a7e2e3fa/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelAsyncOutputStream.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelAsyncOutputStream.java b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelAsyncOutputStream.java
index 11089c9..9fc41a7 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelAsyncOutputStream.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelAsyncOutputStream.java
@@ -48,6 +48,7 @@ public class ChannelAsyncOutputStream extends CloseableUtils.AbstractCloseable i
         doWriteIfPossible(true);
     }
 
+    @Override
     public synchronized IoWriteFuture write(final Buffer buffer) {
         final IoWriteFutureImpl future = new IoWriteFutureImpl(buffer);
         if (isClosing()) {
@@ -88,12 +89,14 @@ public class ChannelAsyncOutputStream extends CloseableUtils.AbstractCloseable i
                     channel.getRemoteWindow().consume(length);
                     try {
                         channel.getSession().writePacket(buf).addListener(new SshFutureListener<org.apache.sshd.common.io.IoWriteFuture>() {
+                            @SuppressWarnings("synthetic-access")
+                            @Override
                             public void operationComplete(org.apache.sshd.common.io.IoWriteFuture f) {
                                 if (total > length) {
                                     doWriteIfPossible(false);
                                 } else {
                                     pendingWrite.compareAndSet(future, null);
-                                    future.setValue(true);
+                                    future.setValue(Boolean.TRUE);
                                 }
                             }
                         });
@@ -105,7 +108,7 @@ public class ChannelAsyncOutputStream extends CloseableUtils.AbstractCloseable i
                 }
             } else {
                 pendingWrite.compareAndSet(future, null);
-                future.setValue(true);
+                future.setValue(Boolean.TRUE);
             }
         }
     }
@@ -128,6 +131,7 @@ public class ChannelAsyncOutputStream extends CloseableUtils.AbstractCloseable i
             return buffer;
         }
 
+        @Override
         public void verify() throws SshException {
             try {
                 await();
@@ -140,10 +144,12 @@ public class ChannelAsyncOutputStream extends CloseableUtils.AbstractCloseable i
             }
         }
 
+        @Override
         public boolean isWritten() {
             return getValue() instanceof Boolean;
         }
 
+        @Override
         public Throwable getException() {
             Object v = getValue();
             if (v instanceof Throwable) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a7e2e3fa/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelOutputStream.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelOutputStream.java b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelOutputStream.java
index fdc4215..92fee57 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelOutputStream.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelOutputStream.java
@@ -21,6 +21,7 @@ package org.apache.sshd.common.channel;
 import java.io.IOException;
 import java.io.InterruptedIOException;
 import java.io.OutputStream;
+import java.nio.channels.Channel;
 
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.SshException;
@@ -32,7 +33,7 @@ import org.slf4j.Logger;
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class ChannelOutputStream extends OutputStream {
+public class ChannelOutputStream extends OutputStream implements Channel {
 
     private final AbstractChannel channel;
     private final Window remoteWindow;
@@ -62,6 +63,11 @@ public class ChannelOutputStream extends OutputStream {
     }
 
     @Override
+    public boolean isOpen() {
+        return !closed;
+    }
+
+    @Override
     public synchronized void write(int w) throws IOException {
         b[0] = (byte) w;
         write(b, 0, 1);
@@ -69,7 +75,7 @@ public class ChannelOutputStream extends OutputStream {
 
     @Override
     public synchronized void write(byte[] buf, int s, int l) throws IOException {
-        if (closed) {
+        if (!isOpen()) {
             throw new SshException("write(len=" + l + ") channel already closed");
         }
 
@@ -109,7 +115,7 @@ public class ChannelOutputStream extends OutputStream {
 
     @Override
     public synchronized void flush() throws IOException {
-        if (closed) {
+        if (!isOpen()) {
             throw new SshException("flush(length=" + bufferLength + ") - stream is already closed");
         }
 
@@ -153,7 +159,7 @@ public class ChannelOutputStream extends OutputStream {
 
     @Override
     public synchronized void close() throws IOException {
-        if (!closed) {
+        if (isOpen()) {
             try {
                 flush();
             } finally {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a7e2e3fa/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
index d3e5b9e..e42b12c 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
@@ -35,7 +35,7 @@ import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class ChannelPipedInputStream extends InputStream {
+public class ChannelPipedInputStream extends InputStream implements ChannelPipedSink {
 
     private final Window localWindow;
     private final Buffer buffer = new ByteArrayBuffer();
@@ -98,7 +98,7 @@ public class ChannelPipedInputStream extends InputStream {
         lock.lock();
         try {
             for (;;) {
-                if (closed && writerClosed && eofSent || closed && !writerClosed) {
+                if ((closed && writerClosed && eofSent) || (closed && !writerClosed)) {
                     throw new IOException("Pipe closed");
                 }
                 if (buffer.available() > 0) {
@@ -136,6 +136,7 @@ public class ChannelPipedInputStream extends InputStream {
         return len;
     }
 
+    @Override
     public void eof() {
         lock.lock();
         try {
@@ -150,13 +151,14 @@ public class ChannelPipedInputStream extends InputStream {
     public void close() throws IOException {
         lock.lock();
         try {
-            closed = true;
             dataAvailable.signalAll();
         } finally {
+            closed = true;
             lock.unlock();
         }
     }
 
+    @Override
     public void receive(byte[] bytes, int off, int len) throws IOException {
         lock.lock();
         try {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a7e2e3fa/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedOutputStream.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedOutputStream.java b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedOutputStream.java
index f6ddc17..958be97 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedOutputStream.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedOutputStream.java
@@ -20,22 +20,24 @@ package org.apache.sshd.common.channel;
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.nio.channels.Channel;
 
 /**
  * TODO Add javadoc
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class ChannelPipedOutputStream extends OutputStream {
+public class ChannelPipedOutputStream extends OutputStream implements Channel {
 
-    private final ChannelPipedInputStream sink;
+    private final ChannelPipedSink sink;
     private final byte[] b = new byte[1];
     private boolean closed;
 
-    public ChannelPipedOutputStream(ChannelPipedInputStream sink) {
+    public ChannelPipedOutputStream(ChannelPipedSink sink) {
         this.sink = sink;
     }
 
+    @Override
     public void write(int i) throws IOException {
         synchronized (b) {
             b[0] = (byte) i;
@@ -45,12 +47,32 @@ public class ChannelPipedOutputStream extends OutputStream {
 
     @Override
     public void write(byte[] b, int off, int len) throws IOException {
+        if (!isOpen()) {
+            throw new IOException("write(len=" + len + ") Stream has been closed");
+        }
         sink.receive(b, off, len);
     }
 
     @Override
+    public boolean isOpen() {
+        return !closed;
+    }
+
+    @Override
+    public void flush() throws IOException {
+        if (!isOpen()) {
+            throw new IOException("flush() Stream has been closed");
+        }
+    }
+
+    @Override
     public void close() throws IOException {
-        sink.eof();
-        closed = true;
+        if (isOpen()) {
+            try {
+                sink.eof();
+            } finally {
+                closed = true;
+            }
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a7e2e3fa/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedSink.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedSink.java b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedSink.java
new file mode 100644
index 0000000..62dacaf
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedSink.java
@@ -0,0 +1,40 @@
+/*
+ * 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.channel;
+
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface ChannelPipedSink {
+    /**
+     * @param bytes Bytes to be sent to the sink
+     * @param off Offset in buffer
+     * @param len Number of bytes
+     * @throws IOException If failed to send the data
+     */
+    void receive(byte[] bytes, int off, int len) throws IOException;
+
+    /**
+     * Signal end of writing to the sink
+     */
+    void eof();
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a7e2e3fa/sshd-core/src/main/java/org/apache/sshd/common/channel/Window.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/channel/Window.java b/sshd-core/src/main/java/org/apache/sshd/common/channel/Window.java
index d1bce18..ba371e4 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/channel/Window.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/channel/Window.java
@@ -118,7 +118,9 @@ public class Window extends AbstractLoggingBean {
     public void waitAndConsume(int len) throws InterruptedException, WindowClosedException {
         synchronized (lock) {
             while (size < len && !closed) {
-                log.debug("Waiting for {} bytes on {}", len, name);
+                if (log.isDebugEnabled()) {
+                    log.debug("Waiting for {} bytes on {}", Integer.valueOf(len), name);
+                }
                 waiting = true;
                 lock.wait();
             }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a7e2e3fa/sshd-core/src/main/java/org/apache/sshd/common/channel/WindowClosedException.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/channel/WindowClosedException.java b/sshd-core/src/main/java/org/apache/sshd/common/channel/WindowClosedException.java
index a582601..b672f50 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/channel/WindowClosedException.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/channel/WindowClosedException.java
@@ -26,6 +26,8 @@ import org.apache.sshd.common.SshException;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class WindowClosedException extends SshException {
+    private static final long serialVersionUID = -5345787686165334234L;
+
     public WindowClosedException() {
         super("Already closed");
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a7e2e3fa/sshd-core/src/main/java/org/apache/sshd/common/util/CloseableUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/CloseableUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/CloseableUtils.java
index 7d4df66..1deeceb 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/CloseableUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/CloseableUtils.java
@@ -159,6 +159,16 @@ public class CloseableUtils {
 
         // TODO once JDK 8+ becomes the minimum for this project, make it a default method instead of this class
         @Override
+        public boolean isOpen() {
+            if (isClosed() || isClosing()) {
+                return false;
+            } else {
+                return true;
+            }
+        }
+
+        // TODO once JDK 8+ becomes the minimum for this project, make it a default method instead of this class
+        @Override
         public void close() throws IOException {
             CloseableUtils.close(this);
         }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a7e2e3fa/sshd-core/src/main/java/org/apache/sshd/common/util/io/InputStreamWithChannel.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/io/InputStreamWithChannel.java b/sshd-core/src/main/java/org/apache/sshd/common/util/io/InputStreamWithChannel.java
new file mode 100644
index 0000000..9bb4c30
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/io/InputStreamWithChannel.java
@@ -0,0 +1,32 @@
+/*
+ * 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.InputStream;
+import java.nio.channels.Channel;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class InputStreamWithChannel extends InputStream implements Channel {
+    protected InputStreamWithChannel() {
+        super();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a7e2e3fa/sshd-core/src/main/java/org/apache/sshd/common/util/io/OutputStreamWithChannel.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/io/OutputStreamWithChannel.java b/sshd-core/src/main/java/org/apache/sshd/common/util/io/OutputStreamWithChannel.java
new file mode 100644
index 0000000..9ab799a
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/io/OutputStreamWithChannel.java
@@ -0,0 +1,32 @@
+/*
+ * 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.OutputStream;
+import java.nio.channels.Channel;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class OutputStreamWithChannel extends OutputStream implements Channel {
+    protected OutputStreamWithChannel() {
+        super();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a7e2e3fa/sshd-core/src/test/java/org/apache/sshd/common/channel/ChannelPipedInputStreamTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/channel/ChannelPipedInputStreamTest.java b/sshd-core/src/test/java/org/apache/sshd/common/channel/ChannelPipedInputStreamTest.java
index 3b5e5c4..0b5b3e2 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/channel/ChannelPipedInputStreamTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/channel/ChannelPipedInputStreamTest.java
@@ -18,6 +18,7 @@
  */
 package org.apache.sshd.common.channel;
 
+import java.io.IOException;
 import java.util.Arrays;
 
 import org.apache.sshd.util.BaseTestSupport;
@@ -25,22 +26,39 @@ import org.apache.sshd.util.BogusChannel;
 import org.junit.Test;
 
 public class ChannelPipedInputStreamTest extends BaseTestSupport {
+    public ChannelPipedInputStreamTest() {
+        super();
+    }
 
     @Test
-    public void testAvailable() throws Exception {
+    public void testAvailable() throws IOException {
         Window window = new Window(new BogusChannel(), null, true, true);
         try(ChannelPipedInputStream stream = new ChannelPipedInputStream(window)) {
-            byte[] b = "test".getBytes();
+            byte[] b = getCurrentTestName().getBytes();
             stream.receive(b, 0, b.length);
-            assertEquals(b.length, stream.available());
+            assertEquals("Mismatched reported available size after receive", b.length, stream.available());
     
             stream.eof();
-            assertEquals(b.length, stream.available());
+            assertEquals("Mismatched reported available size after EOF", b.length, stream.available());
     
-            final byte[] readBytes = new byte[50];
-            assertEquals(b.length, stream.read(readBytes));
+            byte[] readBytes = new byte[b.length + Long.SIZE];
+            assertEquals("Mismatched reported read size", b.length, stream.read(readBytes));
             assertStreamEquals(b, readBytes);
-            assertEquals(-1, stream.available());
+            assertEquals("Unexpected data still available", -1, stream.available());
+        }
+    }
+
+    @Test
+    public void testIdempotentClose() throws IOException {
+        Window window = new Window(new BogusChannel(), null, true, true);
+        try(ChannelPipedInputStream stream = new ChannelPipedInputStream(window)) {
+            byte[] b = getCurrentTestName().getBytes();
+            stream.receive(b, 0, b.length);
+            stream.eof();
+            
+            for (int index=0; index < Byte.SIZE; index++) {
+                stream.close();
+            }
         }
     }
 
@@ -50,7 +68,7 @@ public class ChannelPipedInputStreamTest extends BaseTestSupport {
         } else {
             assertArrayEquals(expected, Arrays.copyOf(read, expected.length));
             for (int i = expected.length; i < read.length; i++) {
-                assertEquals('\0', read[i]);
+                assertTrue("Non-zero value at position " + i, read[i] == 0);
             }
         }
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a7e2e3fa/sshd-core/src/test/java/org/apache/sshd/common/channel/ChannelPipedOutputStreamTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/channel/ChannelPipedOutputStreamTest.java b/sshd-core/src/test/java/org/apache/sshd/common/channel/ChannelPipedOutputStreamTest.java
new file mode 100644
index 0000000..670d52f
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/common/channel/ChannelPipedOutputStreamTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.channel;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.util.BaseTestSupport;
+import org.junit.Test;
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class ChannelPipedOutputStreamTest extends BaseTestSupport {
+    public ChannelPipedOutputStreamTest() {
+        super();
+    }
+
+    @Test
+    public void testNioChannelImplementation() throws IOException {
+        ChannelPipedSink    sink = Mockito.mock(ChannelPipedSink.class);
+        final AtomicBoolean eofCalled = new AtomicBoolean(false);
+        Mockito.doAnswer(new Answer<Void>() {
+                @Override
+                public Void answer(InvocationOnMock invocation) throws Throwable {
+                    assertFalse("Multiple EOF calls", eofCalled.getAndSet(true));
+                    return null;
+                }
+            }).when(sink).eof();
+
+
+        final AtomicInteger receiveCount = new AtomicInteger(0);
+        Mockito.doAnswer(new Answer<Void>() {
+                @Override
+                public Void answer(InvocationOnMock invocation) throws Throwable {
+                    Number  len = invocation.getArgumentAt(2, Number.class);
+                    receiveCount.addAndGet(len.intValue());
+                    return null;
+                }
+            }).when(sink).receive(Matchers.any(byte[].class), Matchers.anyInt(), Matchers.anyInt());
+        try(ChannelPipedOutputStream stream = new ChannelPipedOutputStream(sink)) {
+            assertTrue("Stream not marked as initially open", stream.isOpen());
+            assertEquals("Unexpected initial receive count", 0, receiveCount.intValue());
+
+            byte[] b = getCurrentTestName().getBytes();
+            stream.write(b);
+            assertTrue("Stream not marked as still open after write data", stream.isOpen());
+            assertEquals("Mismatched write data count", b.length, receiveCount.intValue());
+            
+            stream.close();
+            assertFalse("Stream still marked as open after close", stream.isOpen());
+            assertTrue("Sink EOF not called on close", eofCalled.get());
+
+            try {
+                stream.write(b);
+                fail("Unexpected write success after close");
+            } catch(IOException e) {
+                // expected
+            }
+
+            try {
+                stream.flush();
+                fail("Unexpected flush success after close");
+            } catch(IOException e) {
+                // expected
+            }
+        }
+    }
+}