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

[38/51] [abbrv] mina-sshd git commit: [SSHD-842] Split common utilities code from sshd-core into sshd-common (new artifact)

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java
new file mode 100644
index 0000000..c13cd9e
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java
@@ -0,0 +1,604 @@
+/*
+ * 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.buffer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StreamCorruptedException;
+import java.util.function.IntUnaryOperator;
+import java.util.logging.Level;
+
+import org.apache.sshd.common.PropertyResolver;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.NumberUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.logging.SimplifiedLog;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class BufferUtils {
+    public static final char DEFAULT_HEX_SEPARATOR = ' ';
+    public static final char EMPTY_HEX_SEPARATOR = '\0';
+    public static final String HEX_DIGITS = "0123456789abcdef";
+
+    public static final String HEXDUMP_CHUNK_SIZE = "sshd-hexdump-chunk-size";
+    public static final int DEFAULT_HEXDUMP_CHUNK_SIZE = 64;
+    public static final Level DEFAULT_HEXDUMP_LEVEL = Level.FINEST;
+
+    public static final IntUnaryOperator DEFAULT_BUFFER_GROWTH_FACTOR = BufferUtils::getNextPowerOf2;
+
+    /**
+     * Maximum value of a {@code uint32} field
+     */
+    public static final long MAX_UINT32_VALUE = 0x0FFFFFFFFL;
+
+    /**
+     * Maximum value of a {@code uint8} field
+     */
+    public static final int MAX_UINT8_VALUE = 0x0FF;
+
+    /**
+     * Private Constructor
+     */
+    private BufferUtils() {
+        throw new UnsupportedOperationException("No instance allowed");
+    }
+
+    public static void dumpHex(SimplifiedLog logger, Level level, String prefix, PropertyResolver resolver, char sep, byte... data) {
+        dumpHex(logger, level, prefix, resolver, sep, data, 0, NumberUtils.length(data));
+    }
+
+    public static void dumpHex(SimplifiedLog logger, Level level, String prefix, PropertyResolver resolver, char sep, byte[] data, int offset, int len) {
+        dumpHex(logger, level, prefix, sep, resolver.getIntProperty(HEXDUMP_CHUNK_SIZE, DEFAULT_HEXDUMP_CHUNK_SIZE), data, offset, len);
+    }
+
+    public static void dumpHex(SimplifiedLog logger, Level level, String prefix, char sep, int chunkSize, byte... data) {
+        dumpHex(logger, level, prefix, sep, chunkSize, data, 0, NumberUtils.length(data));
+    }
+
+    public static void dumpHex(SimplifiedLog logger, Level level, String prefix, char sep, int chunkSize, byte[] data, int offset, int len) {
+        if ((logger == null) || (level == null) || (!logger.isEnabled(level))) {
+            return;
+        }
+
+        StringBuilder sb = new StringBuilder(chunkSize * 3 /* HEX */ + prefix.length() + Long.SIZE /* some extra */);
+        sb.append(prefix);
+        for (int remainLen = len, chunkIndex = 1, curOffset = offset, totalLen = 0; remainLen > 0; chunkIndex++) {
+            sb.setLength(prefix.length());    // reset for next chunk
+
+            sb.append(" [chunk #").append(chunkIndex).append(']');
+
+            int dumpSize = Math.min(chunkSize, remainLen);
+            totalLen += dumpSize;
+            sb.append('(').append(totalLen).append('/').append(len).append(')');
+
+            try {
+                appendHex(sb.append(' '), data, curOffset, dumpSize, sep);
+            } catch (IOException e) {   // unexpected
+                sb.append(e.getClass().getSimpleName()).append(": ").append(e.getMessage());
+            }
+
+            // Pad the last (incomplete) line to align its data view
+            for (int index = dumpSize; index < chunkSize; index++) {
+                if (sep != EMPTY_HEX_SEPARATOR) {
+                    sb.append(' ');
+                }
+                sb.append("  ");
+            }
+
+            sb.append("    ");
+            for (int pos = curOffset, l = 0; l < dumpSize; pos++, l++) {
+                int b = data[pos] & 0xFF;
+                if ((b > ' ') && (b < 0x7E)) {
+                    sb.append((char) b);
+                } else {
+                    sb.append('.');
+                }
+            }
+
+            logger.log(level, sb.toString());
+            remainLen -= dumpSize;
+            curOffset += dumpSize;
+        }
+    }
+
+    public static String toHex(byte... array) {
+        return toHex(array, 0, NumberUtils.length(array));
+    }
+
+    public static String toHex(char sep, byte... array) {
+        return toHex(array, 0, NumberUtils.length(array), sep);
+    }
+
+    public static String toHex(byte[] array, int offset, int len) {
+        return toHex(array, offset, len, DEFAULT_HEX_SEPARATOR);
+    }
+
+    public static String toHex(byte[] array, int offset, int len, char sep) {
+        if (len <= 0) {
+            return "";
+        }
+
+        try {
+            return appendHex(new StringBuilder(len * 3 /* 2 HEX + sep */), array, offset, len, sep).toString();
+        } catch (IOException e) {   // unexpected
+            return e.getClass().getSimpleName() + ": " + e.getMessage();
+        }
+    }
+
+    public static <A extends Appendable> A appendHex(A sb, char sep, byte... array) throws IOException {
+        return appendHex(sb, array, 0, NumberUtils.length(array), sep);
+    }
+
+    public static <A extends Appendable> A appendHex(A sb, byte[] array, int offset, int len, char sep) throws IOException {
+        if (len <= 0) {
+            return sb;
+        }
+
+        for (int curOffset = offset, maxOffset = offset + len; curOffset < maxOffset; curOffset++) {
+            byte b = array[curOffset];
+            if ((curOffset > offset) && (sep != EMPTY_HEX_SEPARATOR)) {
+                sb.append(sep);
+            }
+            sb.append(HEX_DIGITS.charAt((b >> 4) & 0x0F));
+            sb.append(HEX_DIGITS.charAt(b & 0x0F));
+        }
+
+        return sb;
+    }
+
+    /**
+     * @param separator The separator between the HEX values - may be {@link #EMPTY_HEX_SEPARATOR}
+     * @param csq The {@link CharSequence} containing the HEX encoded bytes
+     * @return The decoded bytes
+     * @throws IllegalArgumentException If invalid HEX sequence length
+     * @throws NumberFormatException If invalid HEX characters found
+     * @see #decodeHex(char, CharSequence, int, int)
+     */
+    public static byte[] decodeHex(char separator, CharSequence csq) {
+        return decodeHex(separator, csq, 0, GenericUtils.length(csq));
+    }
+
+    /**
+     * @param separator The separator between the HEX values - may be {@link #EMPTY_HEX_SEPARATOR}
+     * @param csq The {@link CharSequence} containing the HEX encoded bytes
+     * @param start Start offset of the HEX sequence (inclusive)
+     * @param end End offset of the HEX sequence (exclusive)
+     * @return The decoded bytes
+     * @throws IllegalArgumentException If invalid HEX sequence length
+     * @throws NumberFormatException If invalid HEX characters found
+     */
+    public static byte[] decodeHex(char separator, CharSequence csq, int start, int end) {
+        int len = end - start;
+        ValidateUtils.checkTrue(len >= 0, "Bad HEX sequence length: %d", len);
+        if (len == 0) {
+            return GenericUtils.EMPTY_BYTE_ARRAY;
+        }
+
+        int delta = 2;
+        byte[] bytes;
+        if (separator != EMPTY_HEX_SEPARATOR) {
+            // last character cannot be the separator
+            ValidateUtils.checkTrue((len % 3) == 2, "Invalid separated HEX sequence length: %d", len);
+            bytes = new byte[(len + 1) / 3];
+            delta++;
+        } else {
+            ValidateUtils.checkTrue((len & 0x01) == 0, "Invalid contiguous HEX sequence length: %d", len);
+            bytes = new byte[len >>> 1];
+        }
+
+        int writeLen = 0;
+        for (int curPos = start; curPos < end; curPos += delta, writeLen++) {
+            bytes[writeLen] = fromHex(csq.charAt(curPos), csq.charAt(curPos + 1));
+        }
+        assert writeLen == bytes.length;
+
+        return bytes;
+    }
+
+    /**
+     * @param <S> The {@link OutputStream} generic type
+     * @param stream The target {@link OutputStream}
+     * @param separator The separator between the HEX values - may be {@link #EMPTY_HEX_SEPARATOR}
+     * @param csq The {@link CharSequence} containing the HEX encoded bytes
+     * @return The number of bytes written to the stream
+     * @throws IOException If failed to write
+     * @throws IllegalArgumentException If invalid HEX sequence length
+     * @throws NumberFormatException If invalid HEX characters found
+     * @see #decodeHex(OutputStream, char, CharSequence, int, int)
+     */
+    public static <S extends OutputStream> int decodeHex(S stream, char separator, CharSequence csq) throws IOException {
+        return decodeHex(stream, separator, csq, 0, GenericUtils.length(csq));
+    }
+
+    /**
+     * @param <S> The {@link OutputStream} generic type
+     * @param stream The target {@link OutputStream}
+     * @param separator The separator between the HEX values - may be {@link #EMPTY_HEX_SEPARATOR}
+     * @param csq The {@link CharSequence} containing the HEX encoded bytes
+     * @param start Start offset of the HEX sequence (inclusive)
+     * @param end End offset of the HEX sequence (exclusive)
+     * @return The number of bytes written to the stream
+     * @throws IOException If failed to write
+     * @throws IllegalArgumentException If invalid HEX sequence length
+     * @throws NumberFormatException If invalid HEX characters found
+     */
+    public static <S extends OutputStream> int decodeHex(S stream, char separator, CharSequence csq, int start, int end) throws IOException {
+        int len = end - start;
+        ValidateUtils.checkTrue(len >= 0, "Bad HEX sequence length: %d", len);
+
+        int delta = 2;
+        if (separator != EMPTY_HEX_SEPARATOR) {
+            // last character cannot be the separator
+            ValidateUtils.checkTrue((len % 3) == 2, "Invalid separated HEX sequence length: %d", len);
+            delta++;
+        } else {
+            ValidateUtils.checkTrue((len & 0x01) == 0, "Invalid contiguous HEX sequence length: %d", len);
+        }
+
+        int writeLen = 0;
+        for (int curPos = start; curPos < end; curPos += delta, writeLen++) {
+            stream.write(fromHex(csq.charAt(curPos), csq.charAt(curPos + 1)) & 0xFF);
+        }
+
+        return writeLen;
+    }
+
+    public static byte fromHex(char hi, char lo) throws NumberFormatException {
+        int hiValue = HEX_DIGITS.indexOf(((hi >= 'A') && (hi <= 'F')) ? ('a' + (hi - 'A')) : hi);
+        int loValue = HEX_DIGITS.indexOf(((lo >= 'A') && (lo <= 'F')) ? ('a' + (lo - 'A')) : lo);
+        if ((hiValue < 0) || (loValue < 0)) {
+            throw new NumberFormatException("fromHex(" + new String(new char[]{hi, lo}) + ") non-HEX characters");
+        }
+
+        return (byte) ((hiValue << 4) + loValue);
+    }
+
+    /**
+     * Read a 32-bit value in network order
+     *
+     * @param input The {@link InputStream}
+     * @param buf   Work buffer to use
+     * @return The read 32-bit value
+     * @throws IOException If failed to read 4 bytes or not enough room in
+     * @see #readInt(InputStream, byte[], int, int)
+     */
+    public static int readInt(InputStream input, byte[] buf) throws IOException {
+        return readInt(input, buf, 0, NumberUtils.length(buf));
+    }
+
+    /**
+     * Read a 32-bit value in network order
+     *
+     * @param input  The {@link InputStream}
+     * @param buf    Work buffer to use
+     * @param offset Offset in buffer to us
+     * @param len    Available length - must have at least 4 bytes available
+     * @return The read 32-bit value
+     * @throws IOException If failed to read 4 bytes or not enough room in
+     *                     work buffer
+     * @see #readUInt(InputStream, byte[], int, int)
+     */
+    public static int readInt(InputStream input, byte[] buf, int offset, int len) throws IOException {
+        return (int) readUInt(input, buf, offset, len);
+    }
+
+    /**
+     * Read a 32-bit value in network order
+     *
+     * @param input The {@link InputStream}
+     * @param buf   Work buffer to use
+     * @return The read 32-bit value
+     * @throws IOException If failed to read 4 bytes or not enough room in
+     * @see #readUInt(InputStream, byte[], int, int)
+     */
+    public static long readUInt(InputStream input, byte[] buf) throws IOException {
+        return readUInt(input, buf, 0, NumberUtils.length(buf));
+    }
+
+    /**
+     * Read a 32-bit value in network order
+     *
+     * @param input  The {@link InputStream}
+     * @param buf    Work buffer to use
+     * @param offset Offset in buffer to us
+     * @param len    Available length - must have at least 4 bytes available
+     * @return The read 32-bit value
+     * @throws IOException If failed to read 4 bytes or not enough room in
+     *                     work buffer
+     * @see #getUInt(byte[], int, int)
+     */
+    public static long readUInt(InputStream input, byte[] buf, int offset, int len) throws IOException {
+        try {
+            if (len < Integer.BYTES) {
+                throw new IllegalArgumentException("Not enough data for a UINT: required=" + Integer.BYTES + ", available=" + len);
+            }
+
+            IoUtils.readFully(input, buf, offset, Integer.BYTES);
+            return getUInt(buf, offset, len);
+        } catch (RuntimeException | Error e) {
+            throw new StreamCorruptedException("Failed (" + e.getClass().getSimpleName() + ")"
+                    + " to read UINT value: " + e.getMessage());
+        }
+    }
+
+    /**
+     * @param buf A buffer holding a 32-bit unsigned integer in <B>big endian</B>
+     *            format. <B>Note:</B> if more than 4 bytes are available, then only the
+     *            <U>first</U> 4 bytes in the buffer will be used
+     * @return The result as a {@code long} whose 32 high-order bits are zero
+     * @see #getUInt(byte[], int, int)
+     */
+    public static long getUInt(byte... buf) {
+        return getUInt(buf, 0, NumberUtils.length(buf));
+    }
+
+    /**
+     * @param buf A buffer holding a 32-bit unsigned integer in <B>big endian</B>
+     *            format.
+     * @param off The offset of the data in the buffer
+     * @param len The available data length. <B>Note:</B> if more than 4 bytes
+     *            are available, then only the <U>first</U> 4 bytes in the buffer will be
+     *            used (starting at the specified <tt>offset</tt>)
+     * @return The result as a {@code long} whose 32 high-order bits are zero
+     */
+    public static long getUInt(byte[] buf, int off, int len) {
+        if (len < Integer.BYTES) {
+            throw new IllegalArgumentException("Not enough data for a UINT: required=" + Integer.BYTES + ", available=" + len);
+        }
+
+        long l = (buf[off] << 24) & 0xff000000L;
+        l |= (buf[off + 1] << 16) & 0x00ff0000L;
+        l |= (buf[off + 2] << 8) & 0x0000ff00L;
+        l |= (buf[off + 3]) & 0x000000ffL;
+        return l;
+    }
+
+    /**
+     * Writes a 32-bit value in network order (i.e., MSB 1st)
+     *
+     * @param output The {@link OutputStream} to write the value
+     * @param value  The 32-bit value
+     * @param buf    A work buffer to use - must have enough space to contain 4 bytes
+     * @throws IOException If failed to write the value or work buffer to small
+     * @see #writeInt(OutputStream, int, byte[], int, int)
+     */
+    public static void writeInt(OutputStream output, int value, byte[] buf) throws IOException {
+        writeUInt(output, value, buf, 0, NumberUtils.length(buf));
+    }
+
+    /**
+     * Writes a 32-bit value in network order (i.e., MSB 1st)
+     *
+     * @param output The {@link OutputStream} to write the value
+     * @param value  The 32-bit value
+     * @param buf    A work buffer to use - must have enough space to contain 4 bytes
+     * @param off    The offset to write the value
+     * @param len    The available space
+     * @throws IOException If failed to write the value or work buffer to small
+     * @see #writeUInt(OutputStream, long, byte[], int, int)
+     */
+    public static void writeInt(OutputStream output, int value, byte[] buf, int off, int len) throws IOException {
+        writeUInt(output, value & 0xFFFFFFFFL, buf, off, len);
+    }
+
+    /**
+     * Writes a 32-bit value in network order (i.e., MSB 1st)
+     *
+     * @param output The {@link OutputStream} to write the value
+     * @param value  The 32-bit value
+     * @param buf    A work buffer to use - must have enough space to contain 4 bytes
+     * @throws IOException If failed to write the value or work buffer to small
+     * @see #writeUInt(OutputStream, long, byte[], int, int)
+     */
+    public static void writeUInt(OutputStream output, long value, byte[] buf) throws IOException {
+        writeUInt(output, value, buf, 0, NumberUtils.length(buf));
+    }
+
+    /**
+     * Writes a 32-bit value in network order (i.e., MSB 1st)
+     *
+     * @param output The {@link OutputStream} to write the value
+     * @param value  The 32-bit value
+     * @param buf    A work buffer to use - must have enough space to contain 4 bytes
+     * @param off    The offset to write the value
+     * @param len    The available space
+     * @throws IOException If failed to write the value or work buffer to small
+     * @see #putUInt(long, byte[], int, int)
+     */
+    public static void writeUInt(OutputStream output, long value, byte[] buf, int off, int len) throws IOException {
+        try {
+            int writeLen = putUInt(value, buf, off, len);
+            output.write(buf, off, writeLen);
+        } catch (RuntimeException | Error e) {
+            throw new StreamCorruptedException("Failed (" + e.getClass().getSimpleName() + ")"
+                    + " to write UINT value=" + value + ": " + e.getMessage());
+        }
+    }
+
+    /**
+     * Writes a 32-bit value in network order (i.e., MSB 1st)
+     *
+     * @param value The 32-bit value
+     * @param buf   The buffer
+     * @return The number of bytes used in the buffer
+     * @throws IllegalArgumentException if not enough space available
+     * @see #putUInt(long, byte[], int, int)
+     */
+    public static int putUInt(long value, byte[] buf) {
+        return putUInt(value, buf, 0, NumberUtils.length(buf));
+    }
+
+    /**
+     * Writes a 32-bit value in network order (i.e., MSB 1st)
+     *
+     * @param value The 32-bit value
+     * @param buf   The buffer
+     * @param off   The offset to write the value
+     * @param len   The available space
+     * @return The number of bytes used in the buffer
+     * @throws IllegalArgumentException if not enough space available
+     */
+    public static int putUInt(long value, byte[] buf, int off, int len) {
+        if (len < Integer.BYTES) {
+            throw new IllegalArgumentException("Not enough data for a UINT: required=" + Integer.BYTES + ", available=" + len);
+        }
+
+        buf[off] = (byte) ((value >> 24) & 0xFF);
+        buf[off + 1] = (byte) ((value >> 16) & 0xFF);
+        buf[off + 2] = (byte) ((value >> 8) & 0xFF);
+        buf[off + 3] = (byte) (value & 0xFF);
+
+        return Integer.BYTES;
+    }
+
+    public static boolean equals(byte[] a1, byte[] a2) {
+        int len1 = NumberUtils.length(a1);
+        int len2 = NumberUtils.length(a2);
+        if (len1 != len2) {
+            return false;
+        } else {
+            return equals(a1, 0, a2, 0, len1);
+        }
+    }
+
+    @SuppressWarnings("PMD.AssignmentInOperand")
+    public static boolean equals(byte[] a1, int a1Offset, byte[] a2, int a2Offset, int length) {
+        int len1 = NumberUtils.length(a1);
+        int len2 = NumberUtils.length(a2);
+        if ((len1 < (a1Offset + length)) || (len2 < (a2Offset + length))) {
+            return false;
+        }
+
+        while (length-- > 0) {
+            if (a1[a1Offset++] != a2[a2Offset++]) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public static int getNextPowerOf2(int value) {
+        // for 0-7 return 8
+        return (value < Byte.SIZE) ? Byte.SIZE : NumberUtils.getNextPowerOf2(value);
+    }
+
+    /**
+     * Used for encodings where we don't know the data length before adding it
+     * to the buffer. The idea is to place a 32-bit &quot;placeholder&quot;,
+     * encode the data and then return back to the placeholder and update the
+     * length. The method calculates the encoded data length, moves the write
+     * position to the specified placeholder position, updates the length value
+     * and then moves the write position it back to its original value.
+     *
+     * @param buffer The {@link Buffer}
+     * @param lenPos The offset in the buffer where the length placeholder is
+     *               to be update - <B>Note:</B> assumption is that the encoded data starts
+     *               <U>immediately</U> after the placeholder
+     * @return The amount of data that has been encoded
+     */
+    public static int updateLengthPlaceholder(Buffer buffer, int lenPos) {
+        int startPos = lenPos + Integer.BYTES;
+        int endPos = buffer.wpos();
+        int dataLength = endPos - startPos;
+        // NOTE: although data length is defined as UINT32, we do not expected sizes above Integer.MAX_VALUE
+        ValidateUtils.checkTrue(dataLength >= 0, "Illegal data length: %d", dataLength);
+        buffer.wpos(lenPos);
+        buffer.putInt(dataLength);
+        buffer.wpos(endPos);
+        return dataLength;
+    }
+
+    /**
+     * Updates a 32-bit &quot;placeholder&quot; location for data length - moves
+     * the write position to the specified placeholder position, updates the length
+     * value and then moves the write position it back to its original value.
+     *
+     * @param buffer     The {@link Buffer}
+     * @param lenPos     The offset in the buffer where the length placeholder is
+     *                   to be update - <B>Note:</B> assumption is that the encoded data starts
+     *                   <U>immediately</U> after the placeholder
+     * @param dataLength The length to update
+     */
+    public static void updateLengthPlaceholder(Buffer buffer, int lenPos, int dataLength) {
+        int curPos = buffer.wpos();
+        buffer.wpos(lenPos);
+        buffer.putInt(dataLength);
+        buffer.wpos(curPos);
+    }
+
+    /**
+     * Invokes {@link Buffer#clear()}
+     *
+     * @param <B>    The generic buffer type
+     * @param buffer A {@link Buffer} instance - ignored if {@code null}
+     * @return The same as the input instance
+     */
+    public static <B extends Buffer> B clear(B buffer) {
+        if (buffer != null) {
+            buffer.clear();
+        }
+
+        return buffer;
+    }
+
+    public static long validateInt32Value(long value, String message) {
+        ValidateUtils.checkTrue(isValidInt32Value(value), message, value);
+        return value;
+    }
+
+    public static long validateInt32Value(long value, String format, Object arg) {
+        ValidateUtils.checkTrue(isValidInt32Value(value), format, arg);
+        return value;
+    }
+
+    public static long validateInt32Value(long value, String format, Object... args) {
+        ValidateUtils.checkTrue(isValidInt32Value(value), format, args);
+        return value;
+    }
+
+    public static boolean isValidInt32Value(long value) {
+        return (value >= Integer.MIN_VALUE) && (value <= Integer.MAX_VALUE);
+    }
+
+    public static long validateUint32Value(long value, String message) {
+        ValidateUtils.checkTrue(isValidUint32Value(value), message, value);
+        return value;
+    }
+
+    public static long validateUint32Value(long value, String format, Object arg) {
+        ValidateUtils.checkTrue(isValidUint32Value(value), format, arg);
+        return value;
+    }
+
+    public static long validateUint32Value(long value, String format, Object... args) {
+        ValidateUtils.checkTrue(isValidUint32Value(value), format, args);
+        return value;
+    }
+
+    public static boolean isValidUint32Value(long value) {
+        return (value >= 0L) && (value <= MAX_UINT32_VALUE);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/ByteArrayBuffer.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/ByteArrayBuffer.java b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/ByteArrayBuffer.java
new file mode 100644
index 0000000..6655ec1
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/ByteArrayBuffer.java
@@ -0,0 +1,253 @@
+/*
+ * 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.buffer;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.function.IntUnaryOperator;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.NumberUtils;
+import org.apache.sshd.common.util.Readable;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * Provides an implementation of {@link Buffer} using a backing byte array
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class ByteArrayBuffer extends Buffer {
+    public static final int DEFAULT_SIZE = 256;
+    public static final int MAX_LEN = 65536;
+
+    private byte[] data;
+    private int rpos;
+    private int wpos;
+
+    public ByteArrayBuffer() {
+        this(DEFAULT_SIZE);
+    }
+
+    public ByteArrayBuffer(int size) {
+        this(size, true);
+    }
+
+    public ByteArrayBuffer(int size, boolean roundOff) {
+        this(new byte[roundOff ? BufferUtils.getNextPowerOf2(size) : size], false);
+    }
+
+    public ByteArrayBuffer(byte[] data) {
+        this(data, 0, data.length, true);
+    }
+
+    public ByteArrayBuffer(byte[] data, boolean read) {
+        this(data, 0, data.length, read);
+    }
+
+    public ByteArrayBuffer(byte[] data, int off, int len) {
+        this(data, off, len, true);
+    }
+
+    public ByteArrayBuffer(byte[] data, int off, int len, boolean read) {
+        this.data = data;
+        this.rpos = off;
+        this.wpos = (read ? len : 0) + off;
+    }
+
+    @Override
+    public int rpos() {
+        return rpos;
+    }
+
+    @Override
+    public void rpos(int rpos) {
+        this.rpos = rpos;
+    }
+
+    @Override
+    public int wpos() {
+        return wpos;
+    }
+
+    @Override
+    public void wpos(int wpos) {
+        if (wpos > this.wpos) {
+            ensureCapacity(wpos - this.wpos);
+        }
+        this.wpos = wpos;
+    }
+
+    @Override
+    public int available() {
+        return wpos - rpos;
+    }
+
+    @Override
+    public int capacity() {
+        return data.length - wpos;
+    }
+
+    @Override
+    public byte[] array() {
+        return data;
+    }
+
+    @Override
+    public void compact() {
+        int avail = available();
+        if (avail > 0) {
+            System.arraycopy(data, rpos, data, 0, avail);
+        }
+        wpos -= rpos;
+        rpos = 0;
+    }
+
+    @Override
+    public void clear(boolean wipeData) {
+        rpos = 0;
+        wpos = 0;
+
+        if (wipeData) {
+            Arrays.fill(data, (byte) 0);
+        }
+    }
+
+    @Override
+    public byte getByte() {
+        ensureAvailable(Byte.BYTES);
+        return data[rpos++];
+    }
+
+    @Override
+    public void putByte(byte b) {
+        ensureCapacity(Byte.BYTES);
+        data[wpos++] = b;
+    }
+
+    @Override
+    public int putBuffer(Readable buffer, boolean expand) {
+        int r = expand ? buffer.available() : Math.min(buffer.available(), capacity());
+        ensureCapacity(r);
+        buffer.getRawBytes(data, wpos, r);
+        wpos += r;
+        return r;
+    }
+
+    @Override
+    public void putBuffer(ByteBuffer buffer) {
+        int required = buffer.remaining();
+        ensureCapacity(required + Integer.SIZE);
+        putInt(required);
+        buffer.get(data, wpos, required);
+        wpos += required;
+    }
+
+    @Override
+    public void putRawBytes(byte[] d, int off, int len) {
+        ValidateUtils.checkTrue(len >= 0, "Negative raw bytes length: %d", len);
+        ensureCapacity(len);
+        System.arraycopy(d, off, data, wpos, len);
+        wpos += len;
+    }
+
+    @Override
+    public String getString(Charset charset) {
+        int len = getInt();
+        if (len < 0) {
+            throw new BufferException("Bad item length: " + len);
+        }
+        ensureAvailable(len);
+
+        Objects.requireNonNull(charset, "No charset specified");
+        String s = new String(data, rpos, len, charset);
+        rpos += len;
+        return s;
+    }
+
+    @Override
+    public void getRawBytes(byte[] buf, int off, int len) {
+        ensureAvailable(len);
+        copyRawBytes(0, buf, off, len);
+        rpos += len;
+    }
+
+    @Override
+    protected void copyRawBytes(int offset, byte[] buf, int pos, int len) {
+        System.arraycopy(data, rpos + offset, buf, pos, len);
+    }
+
+    @Override
+    public void ensureCapacity(int capacity, IntUnaryOperator growthFactor) {
+        ValidateUtils.checkTrue(capacity >= 0, "Negative capacity requested: %d", capacity);
+
+        int maxSize = size();
+        int curPos = wpos();
+        int remaining = maxSize - curPos;
+        if (remaining < capacity) {
+            int minimum = curPos + capacity;
+            int actual = growthFactor.applyAsInt(minimum);
+            if (actual < minimum) {
+                throw new IllegalStateException("ensureCapacity(" + capacity + ") actual (" + actual + ") below min. (" + minimum + ")");
+            }
+            byte[] tmp = new byte[actual];
+            System.arraycopy(data, 0, tmp, 0, data.length);
+            data = tmp;
+        }
+    }
+
+    @Override
+    protected int size() {
+        return data.length;
+    }
+
+    /**
+     * Creates a compact buffer (i.e., one that starts at offset zero) containing a <U>copy</U>
+     * of the original data
+     *
+     * @param data   The original data buffer
+     * @return A {@link ByteArrayBuffer} containing a <U>copy</U> of the original data
+     * starting at zero read position
+     * @see #getCompactClone(byte[], int, int)
+     */
+    public static ByteArrayBuffer getCompactClone(byte[] data) {
+        return getCompactClone(data, 0, NumberUtils.length(data));
+    }
+
+    /**
+     * Creates a compact buffer (i.e., one that starts at offset zero) containing a <U>copy</U>
+     * of the original data
+     *
+     * @param data   The original data buffer
+     * @param offset The offset of the valid data in the buffer
+     * @param len    The size (in bytes) of of the valid data in the buffer
+     * @return A {@link ByteArrayBuffer} containing a <U>copy</U> of the original data
+     * starting at zero read position
+     */
+    public static ByteArrayBuffer getCompactClone(byte[] data, int offset, int len) {
+        byte[] clone = (len > 0) ? new byte[len] : GenericUtils.EMPTY_BYTE_ARRAY;
+        if (len > 0) {
+            System.arraycopy(data, offset, clone, 0, len);
+        }
+
+        return new ByteArrayBuffer(clone, true);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/AbstractBufferPublicKeyParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/AbstractBufferPublicKeyParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/AbstractBufferPublicKeyParser.java
new file mode 100644
index 0000000..aaf9641
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/AbstractBufferPublicKeyParser.java
@@ -0,0 +1,88 @@
+/*
+ * 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.buffer.keys;
+
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * @param <PUB> Type of {@link PublicKey} being extracted
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractBufferPublicKeyParser<PUB extends PublicKey> implements BufferPublicKeyParser<PUB> {
+    private final Class<PUB> keyClass;
+    private final Collection<String> supported;
+
+    protected AbstractBufferPublicKeyParser(Class<PUB> keyClass, String... supported) {
+        this(keyClass, GenericUtils.isEmpty(supported) ? Collections.emptyList() : Arrays.asList(supported));
+    }
+
+    protected AbstractBufferPublicKeyParser(Class<PUB> keyClass, Collection<String> supported) {
+        this.keyClass = Objects.requireNonNull(keyClass, "No key class");
+        this.supported = ValidateUtils.checkNotNullAndNotEmpty(supported, "No supported types for %s", keyClass.getSimpleName());
+    }
+
+    public Collection<String> getSupportedKeyTypes() {
+        return supported;
+    }
+
+    public final Class<PUB> getKeyClass() {
+        return keyClass;
+    }
+
+    @Override
+    public boolean isKeyTypeSupported(String keyType) {
+        Collection<String> keys = getSupportedKeyTypes();
+        return (GenericUtils.length(keyType) > 0)
+            && (GenericUtils.size(keys) > 0)
+            && keys.contains(keyType);
+    }
+
+    protected <S extends KeySpec> PUB generatePublicKey(String algorithm, S keySpec) throws GeneralSecurityException {
+        KeyFactory keyFactory = getKeyFactory(algorithm);
+        PublicKey key = keyFactory.generatePublic(keySpec);
+        Class<PUB> kc = getKeyClass();
+        if (!kc.isInstance(key)) {
+            throw new InvalidKeySpecException("Mismatched generated key types: expected=" + kc.getSimpleName() + ", actual=" + key);
+        }
+
+        return kc.cast(key);
+    }
+
+    protected KeyFactory getKeyFactory(String algorithm) throws GeneralSecurityException {
+        return SecurityUtils.getKeyFactory(algorithm);
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + " - supported=" + getSupportedKeyTypes();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/BufferPublicKeyParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/BufferPublicKeyParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/BufferPublicKeyParser.java
new file mode 100644
index 0000000..6f7cde6
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/BufferPublicKeyParser.java
@@ -0,0 +1,111 @@
+/*
+ * 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.buffer.keys;
+
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+
+/**
+ * Parses a raw {@link PublicKey} from a {@link Buffer}
+ *
+ * @param <PUB> Type of {@link PublicKey} being extracted
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface BufferPublicKeyParser<PUB extends PublicKey> {
+
+    BufferPublicKeyParser<PublicKey> EMPTY = new BufferPublicKeyParser<PublicKey>() {
+        @Override
+        public boolean isKeyTypeSupported(String keyType) {
+            return false;
+        }
+
+        @Override
+        public PublicKey getRawPublicKey(String keyType, Buffer buffer) throws GeneralSecurityException {
+            throw new NoSuchAlgorithmException(keyType);
+        }
+
+        @Override
+        public String toString() {
+            return "EMPTY";
+        }
+    };
+
+    BufferPublicKeyParser<PublicKey> DEFAULT = aggregate(
+            Arrays.asList(
+                    RSABufferPublicKeyParser.INSTANCE,
+                    DSSBufferPublicKeyParser.INSTANCE,
+                    ECBufferPublicKeyParser.INSTANCE,
+                    ED25519BufferPublicKeyParser.INSTANCE));
+
+    /**
+     * @param keyType The key type - e.g., &quot;ssh-rsa&quot, &quot;ssh-dss&quot;
+     * @return {@code true} if this key type is supported by the parser
+     */
+    boolean isKeyTypeSupported(String keyType);
+
+    /**
+     * @param keyType The key type - e.g., &quot;ssh-rsa&quot, &quot;ssh-dss&quot;
+     * @param buffer The {@link Buffer} containing the encoded raw public key
+     * @return The decoded {@link PublicKey}
+     * @throws GeneralSecurityException If failed to generate the key
+     */
+    PUB getRawPublicKey(String keyType, Buffer buffer) throws GeneralSecurityException;
+
+    static BufferPublicKeyParser<PublicKey> aggregate(Collection<? extends BufferPublicKeyParser<? extends PublicKey>> parsers) {
+        if (GenericUtils.isEmpty(parsers)) {
+            return EMPTY;
+        }
+
+        return new BufferPublicKeyParser<PublicKey>() {
+            @Override
+            public boolean isKeyTypeSupported(String keyType) {
+                for (BufferPublicKeyParser<? extends PublicKey> p : parsers) {
+                    if (p.isKeyTypeSupported(keyType)) {
+                        return true;
+                    }
+                }
+
+                return false;
+            }
+
+            @Override
+            public PublicKey getRawPublicKey(String keyType, Buffer buffer) throws GeneralSecurityException {
+                for (BufferPublicKeyParser<? extends PublicKey> p : parsers) {
+                    if (p.isKeyTypeSupported(keyType)) {
+                        return p.getRawPublicKey(keyType, buffer);
+                    }
+                }
+
+                throw new NoSuchAlgorithmException("No aggregate matcher for " + keyType);
+            }
+
+            @Override
+            public String toString() {
+                return String.valueOf(parsers);
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/DSSBufferPublicKeyParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/DSSBufferPublicKeyParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/DSSBufferPublicKeyParser.java
new file mode 100644
index 0000000..4eec49a
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/DSSBufferPublicKeyParser.java
@@ -0,0 +1,52 @@
+/*
+ * 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.buffer.keys;
+
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.interfaces.DSAPublicKey;
+import java.security.spec.DSAPublicKeySpec;
+
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DSSBufferPublicKeyParser extends AbstractBufferPublicKeyParser<DSAPublicKey> {
+    public static final DSSBufferPublicKeyParser INSTANCE = new DSSBufferPublicKeyParser();
+
+    public DSSBufferPublicKeyParser() {
+        super(DSAPublicKey.class, KeyPairProvider.SSH_DSS);
+    }
+
+    @Override
+    public DSAPublicKey getRawPublicKey(String keyType, Buffer buffer) throws GeneralSecurityException {
+        ValidateUtils.checkTrue(isKeyTypeSupported(keyType), "Unsupported key type: %s", keyType);
+        BigInteger p = buffer.getMPInt();
+        BigInteger q = buffer.getMPInt();
+        BigInteger g = buffer.getMPInt();
+        BigInteger y = buffer.getMPInt();
+
+        return generatePublicKey(KeyUtils.DSS_ALGORITHM, new DSAPublicKeySpec(y, p, q, g));
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/ECBufferPublicKeyParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/ECBufferPublicKeyParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/ECBufferPublicKeyParser.java
new file mode 100644
index 0000000..df0115a
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/ECBufferPublicKeyParser.java
@@ -0,0 +1,81 @@
+/*
+ * 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.buffer.keys;
+
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class ECBufferPublicKeyParser extends AbstractBufferPublicKeyParser<ECPublicKey> {
+    public static final ECBufferPublicKeyParser INSTANCE = new ECBufferPublicKeyParser();
+
+    public ECBufferPublicKeyParser() {
+        super(ECPublicKey.class, ECCurves.KEY_TYPES);
+    }
+
+    @Override
+    public ECPublicKey getRawPublicKey(String keyType, Buffer buffer) throws GeneralSecurityException {
+        ValidateUtils.checkTrue(isKeyTypeSupported(keyType), "Unsupported key type: %s", keyType);
+        ECCurves curve = ECCurves.fromKeyType(keyType);
+        if (curve == null) {
+            throw new NoSuchAlgorithmException("Unsupported raw public algorithm: " + keyType);
+        }
+
+        String curveName = curve.getName();
+        ECParameterSpec params = curve.getParameters();
+        return getRawECKey(curveName, params, buffer);
+    }
+
+    protected ECPublicKey getRawECKey(String expectedCurve, ECParameterSpec spec, Buffer buffer) throws GeneralSecurityException {
+        String curveName = buffer.getString();
+        if (!expectedCurve.equals(curveName)) {
+            throw new InvalidKeySpecException("getRawECKey(" + expectedCurve + ") curve name does not match expected: " + curveName);
+        }
+
+        if (spec == null) {
+            throw new InvalidKeySpecException("getRawECKey(" + expectedCurve + ") missing curve parameters");
+        }
+
+        byte[] octets = buffer.getBytes();
+        ECPoint w;
+        try {
+            w = ECCurves.octetStringToEcPoint(octets);
+        } catch (RuntimeException e) {
+            throw new InvalidKeySpecException("getRawECKey(" + expectedCurve + ")"
+                    + " cannot (" + e.getClass().getSimpleName() + ")"
+                    + " retrieve W value: " + e.getMessage(),
+                    e);
+        }
+
+        return generatePublicKey(KeyUtils.EC_ALGORITHM, new ECPublicKeySpec(w, spec));
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/ED25519BufferPublicKeyParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/ED25519BufferPublicKeyParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/ED25519BufferPublicKeyParser.java
new file mode 100644
index 0000000..61ce6ab
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/ED25519BufferPublicKeyParser.java
@@ -0,0 +1,47 @@
+/*
+ * 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.buffer.keys;
+
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * TODO complete this when SSHD-440 is done
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class ED25519BufferPublicKeyParser extends AbstractBufferPublicKeyParser<PublicKey> {
+    public static final ED25519BufferPublicKeyParser INSTANCE = new ED25519BufferPublicKeyParser();
+
+    public ED25519BufferPublicKeyParser() {
+        super(PublicKey.class, KeyPairProvider.SSH_ED25519);
+    }
+
+    @Override
+    public PublicKey getRawPublicKey(String keyType, Buffer buffer) throws GeneralSecurityException {
+        ValidateUtils.checkTrue(isKeyTypeSupported(keyType), "Unsupported key type: %s", keyType);
+        byte[] seed = buffer.getBytes();
+        return SecurityUtils.generateEDDSAPublicKey(keyType, seed);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/RSABufferPublicKeyParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/RSABufferPublicKeyParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/RSABufferPublicKeyParser.java
new file mode 100644
index 0000000..363b07e
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/RSABufferPublicKeyParser.java
@@ -0,0 +1,49 @@
+/*
+ * 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.buffer.keys;
+
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.RSAPublicKeySpec;
+
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class RSABufferPublicKeyParser extends AbstractBufferPublicKeyParser<RSAPublicKey> {
+    public static final RSABufferPublicKeyParser INSTANCE = new RSABufferPublicKeyParser();
+
+    public RSABufferPublicKeyParser() {
+        super(RSAPublicKey.class, KeyPairProvider.SSH_RSA);
+    }
+
+    @Override
+    public RSAPublicKey getRawPublicKey(String keyType, Buffer buffer) throws GeneralSecurityException {
+        ValidateUtils.checkTrue(isKeyTypeSupported(keyType), "Unsupported key type: %s", keyType);
+        BigInteger e = buffer.getMPInt();
+        BigInteger n = buffer.getMPInt();
+        return generatePublicKey(KeyUtils.RSA_ALGORITHM, new RSAPublicKeySpec(n, e));
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/AbstractCloseable.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/AbstractCloseable.java b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/AbstractCloseable.java
new file mode 100644
index 0000000..6413ebb
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/AbstractCloseable.java
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.common.util.closeable;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.DefaultCloseFuture;
+import org.apache.sshd.common.future.SshFuture;
+import org.apache.sshd.common.future.SshFutureListener;
+
+/**
+ * Provides some default implementations
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractCloseable extends IoBaseCloseable {
+
+    public enum State {
+        Opened, Graceful, Immediate, Closed
+    }
+
+    /**
+     * Lock object for this session state
+     */
+    protected final Object lock = new Object();
+
+    /**
+     * State of this object
+     */
+    protected final AtomicReference<AbstractCloseable.State> state = new AtomicReference<>(State.Opened);
+
+    /**
+     * A future that will be set 'closed' when the object is actually closed
+     */
+    protected final CloseFuture closeFuture;
+
+    protected AbstractCloseable() {
+        this("");
+    }
+
+    protected AbstractCloseable(String discriminator) {
+        super(discriminator);
+        closeFuture = new DefaultCloseFuture(discriminator, lock);
+    }
+
+    @Override
+    public void addCloseFutureListener(SshFutureListener<CloseFuture> listener) {
+        closeFuture.addListener(listener);
+    }
+
+    @Override
+    public void removeCloseFutureListener(SshFutureListener<CloseFuture> listener) {
+        closeFuture.removeListener(listener);
+    }
+
+    @Override
+    public final CloseFuture close(boolean immediately) {
+        boolean debugEnabled = log.isDebugEnabled();
+        if (immediately) {
+            if (state.compareAndSet(State.Opened, State.Immediate)
+                    || state.compareAndSet(State.Graceful, State.Immediate)) {
+                if (debugEnabled) {
+                    log.debug("close({}) Closing immediately", this);
+                }
+                preClose();
+                doCloseImmediately();
+                if (debugEnabled) {
+                    log.debug("close({})[Immediately] closed", this);
+                }
+            } else {
+                if (debugEnabled) {
+                    log.debug("close({})[Immediately] state already {}", this, state.get());
+                }
+            }
+        } else {
+            if (state.compareAndSet(State.Opened, State.Graceful)) {
+                if (debugEnabled) {
+                    log.debug("close({}) Closing gracefully", this);
+                }
+                preClose();
+                SshFuture<CloseFuture> grace = doCloseGracefully();
+                if (grace != null) {
+                    grace.addListener(future -> {
+                        if (state.compareAndSet(State.Graceful, State.Immediate)) {
+                            doCloseImmediately();
+                            if (debugEnabled) {
+                                log.debug("close({}][Graceful] - operationComplete() closed", AbstractCloseable.this);
+                            }
+                        }
+                    });
+                } else {
+                    if (state.compareAndSet(State.Graceful, State.Immediate)) {
+                        doCloseImmediately();
+                        if (debugEnabled) {
+                            log.debug("close({})[Graceful] closed", this);
+                        }
+                    }
+                }
+            } else {
+                if (debugEnabled) {
+                    log.debug("close({})[Graceful] state already {}", this, state.get());
+                }
+            }
+        }
+        return closeFuture;
+    }
+
+    @Override
+    public final boolean isClosed() {
+        return state.get() == State.Closed;
+    }
+
+    @Override
+    public final boolean isClosing() {
+        return state.get() != State.Opened;
+    }
+
+    /**
+     * preClose is guaranteed to be called before doCloseGracefully or doCloseImmediately.
+     * When preClose() is called, isClosing() == true
+     */
+    protected void preClose() {
+        // nothing
+    }
+
+    protected CloseFuture doCloseGracefully() {
+        return null;
+    }
+
+    /**
+     * <P>doCloseImmediately is called once and only once
+     * with state == Immediate</P>
+     *
+     * <P>Overriding methods should always call the base implementation.
+     * It may be called concurrently while preClose() or doCloseGracefully is executing</P>
+     */
+    protected void doCloseImmediately() {
+        closeFuture.setClosed();
+        state.set(State.Closed);
+    }
+
+    protected Builder builder() {
+        return new Builder(lock);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/AbstractInnerCloseable.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/AbstractInnerCloseable.java b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/AbstractInnerCloseable.java
new file mode 100644
index 0000000..6518d23
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/AbstractInnerCloseable.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.common.util.closeable;
+
+import org.apache.sshd.common.Closeable;
+import org.apache.sshd.common.future.CloseFuture;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractInnerCloseable extends AbstractCloseable {
+    protected AbstractInnerCloseable() {
+        this("");
+    }
+
+    protected AbstractInnerCloseable(String discriminator) {
+        super(discriminator);
+    }
+
+    protected abstract Closeable getInnerCloseable();
+
+    @Override
+    protected final CloseFuture doCloseGracefully() {
+        return getInnerCloseable().close(false);
+    }
+
+    @Override
+    @SuppressWarnings("synthetic-access")
+    protected final void doCloseImmediately() {
+        getInnerCloseable().close(true).addListener(future -> AbstractInnerCloseable.super.doCloseImmediately());
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/Builder.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/Builder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/Builder.java
new file mode 100644
index 0000000..847d49c
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/Builder.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.common.util.closeable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.sshd.common.Closeable;
+import org.apache.sshd.common.future.SshFuture;
+import org.apache.sshd.common.util.ObjectBuilder;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class Builder implements ObjectBuilder<Closeable> {
+    private final Object lock;
+    private final List<Closeable> closeables = new ArrayList<>();
+
+    public Builder(Object lock) {
+        this.lock = Objects.requireNonNull(lock, "No lock");
+    }
+
+    public Builder run(Object id, Runnable r) {
+        return close(new SimpleCloseable(id, lock) {
+            @Override
+            protected void doClose(boolean immediately) {
+                try {
+                    r.run();
+                } finally {
+                    super.doClose(immediately);
+                }
+            }
+        });
+    }
+
+    @SuppressWarnings("rawtypes")
+    public <T extends SshFuture> Builder when(SshFuture<T> future) {
+        if (future != null) {
+            when(future.getId(), Collections.singleton(future));
+        }
+        return this;
+    }
+
+    @SuppressWarnings("rawtypes")
+    @SafeVarargs
+    public final <T extends SshFuture> Builder when(SshFuture<T>... futures) {
+        return when(getClass().getSimpleName(), Arrays.asList(futures));
+    }
+
+    @SuppressWarnings("rawtypes")
+    public <T extends SshFuture> Builder when(Object id, Iterable<? extends SshFuture<T>> futures) {
+        return close(new FuturesCloseable<>(id, lock, futures));
+    }
+
+    public Builder sequential(Closeable... closeables) {
+        for (Closeable closeable : closeables) {
+            close(closeable);
+        }
+        return this;
+    }
+
+    public Builder sequential(Object id, Iterable<Closeable> closeables) {
+        return close(new SequentialCloseable(id, lock, closeables));
+    }
+
+    public Builder parallel(Closeable... closeables) {
+        if (closeables.length == 1) {
+            close(closeables[0]);
+        } else if (closeables.length > 0) {
+            parallel(getClass().getSimpleName(), Arrays.asList(closeables));
+        }
+        return this;
+    }
+
+    public Builder parallel(Object id, Iterable<? extends Closeable> closeables) {
+        return close(new ParallelCloseable(id, lock, closeables));
+    }
+
+    public Builder close(Closeable c) {
+        if (c != null) {
+            closeables.add(c);
+        }
+        return this;
+    }
+
+    @Override
+    public Closeable build() {
+        if (closeables.isEmpty()) {
+            return new SimpleCloseable(getClass().getSimpleName(), lock);
+        } else if (closeables.size() == 1) {
+            return closeables.get(0);
+        } else {
+            return new SequentialCloseable(getClass().getSimpleName(), lock, closeables);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/FuturesCloseable.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/FuturesCloseable.java b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/FuturesCloseable.java
new file mode 100644
index 0000000..af765b7
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/FuturesCloseable.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.common.util.closeable;
+
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.future.DefaultSshFuture;
+import org.apache.sshd.common.future.SshFuture;
+import org.apache.sshd.common.future.SshFutureListener;
+
+/**
+ * @param <T> Type of future
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class FuturesCloseable<T extends SshFuture> extends SimpleCloseable {
+
+    private final Iterable<? extends SshFuture<T>> futures;
+
+    public FuturesCloseable(Object id, Object lock, Iterable<? extends SshFuture<T>> futures) {
+        super(id, lock);
+        this.futures = (futures == null) ? Collections.emptyList() : futures;
+    }
+
+    @Override
+    protected void doClose(boolean immediately) {
+        if (immediately) {
+            for (SshFuture<?> f : futures) {
+                if (f instanceof DefaultSshFuture) {
+                    ((DefaultSshFuture<?>) f).setValue(new SshException("Closed"));
+                }
+            }
+            future.setClosed();
+        } else {
+            AtomicInteger count = new AtomicInteger(1);
+            boolean traceEnabled = log.isTraceEnabled();
+            SshFutureListener<T> listener = f -> {
+                int pendingCount = count.decrementAndGet();
+                if (traceEnabled) {
+                    log.trace("doClose(" + immediately + ") complete pending: " + pendingCount);
+                }
+                if (pendingCount == 0) {
+                    future.setClosed();
+                }
+            };
+
+            for (SshFuture<T> f : futures) {
+                if (f != null) {
+                    int pendingCount = count.incrementAndGet();
+                    if (traceEnabled) {
+                        log.trace("doClose(" + immediately + ") future pending: " + pendingCount);
+                    }
+                    f.addListener(listener);
+                }
+            }
+            listener.operationComplete(null);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/IoBaseCloseable.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/IoBaseCloseable.java b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/IoBaseCloseable.java
new file mode 100644
index 0000000..f4c6d1a
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/IoBaseCloseable.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.common.util.closeable;
+
+import org.apache.sshd.common.Closeable;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class IoBaseCloseable extends AbstractLoggingBean implements Closeable {
+    protected IoBaseCloseable() {
+        this("");
+    }
+
+    protected IoBaseCloseable(String discriminator) {
+        super(discriminator);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/ParallelCloseable.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/ParallelCloseable.java b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/ParallelCloseable.java
new file mode 100644
index 0000000..0900cd7
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/ParallelCloseable.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.common.util.closeable;
+
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.common.Closeable;
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.SshFutureListener;
+
+/**
+ * Waits for a group of {@link Closeable}s to complete in any order, then
+ * signals the completion by setting the &quot;parent&quot; future as closed
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class ParallelCloseable extends SimpleCloseable {
+    private final Iterable<? extends Closeable> closeables;
+
+    public ParallelCloseable(Object id, Object lock, Iterable<? extends Closeable> closeables) {
+        super(id, lock);
+        this.closeables = (closeables == null) ? Collections.emptyList() : closeables;
+    }
+
+    @Override
+    protected void doClose(boolean immediately) {
+        AtomicInteger count = new AtomicInteger(1);
+        boolean traceEnabled = log.isTraceEnabled();
+        SshFutureListener<CloseFuture> listener = f -> {
+            int pendingCount = count.decrementAndGet();
+            if (traceEnabled) {
+                log.trace("doClose(" + immediately + ") completed pending: " + pendingCount);
+            }
+            if (pendingCount == 0) {
+                future.setClosed();
+            }
+        };
+
+        for (Closeable c : closeables) {
+            if (c == null) {
+                continue;
+            }
+
+            int pendingCount = count.incrementAndGet();
+            if (traceEnabled) {
+                log.trace("doClose(" + immediately + ") pending closeables: " + pendingCount);
+            }
+            c.close(immediately).addListener(listener);
+        }
+        /*
+         * Trigger the last "decrementAndGet" so that the future is marked as closed
+         * when last "operationComplete" is invoked (which could be this call...)
+         */
+        listener.operationComplete(null);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/SequentialCloseable.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/SequentialCloseable.java b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/SequentialCloseable.java
new file mode 100644
index 0000000..6af51b8
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/SequentialCloseable.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.common.util.closeable;
+
+import java.util.Collections;
+import java.util.Iterator;
+
+import org.apache.sshd.common.Closeable;
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.SshFutureListener;
+
+/**
+ * Waits for a group of {@link Closeable}s to complete in the given order, then
+ * signals the completion by setting the &quot;parent&quot; future as closed
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SequentialCloseable extends SimpleCloseable {
+    private final Iterable<? extends Closeable> closeables;
+
+    public SequentialCloseable(Object id, Object lock, Iterable<? extends Closeable> closeables) {
+        super(id, lock);
+        this.closeables = (closeables == null) ? Collections.emptyList() : closeables;
+    }
+
+    @Override
+    protected void doClose(boolean immediately) {
+        Iterator<? extends Closeable> iterator = closeables.iterator();
+        SshFutureListener<CloseFuture> listener = new SshFutureListener<CloseFuture>() {
+            @SuppressWarnings("synthetic-access")
+            @Override
+            public void operationComplete(CloseFuture previousFuture) {
+                boolean traceEnabled = log.isTraceEnabled();
+                while (iterator.hasNext()) {
+                    Closeable c = iterator.next();
+                    if (c != null) {
+                        if (traceEnabled) {
+                            log.trace("doClose(" + immediately + ") closing " + c);
+                        }
+                        CloseFuture nextFuture = c.close(immediately);
+                        nextFuture.addListener(this);
+                        return;
+                    }
+                }
+                if (!iterator.hasNext()) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("doClose(" + immediately + ") signal close complete");
+                    }
+                    future.setClosed();
+                }
+            }
+        };
+        listener.operationComplete(null);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/SimpleCloseable.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/SimpleCloseable.java b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/SimpleCloseable.java
new file mode 100644
index 0000000..e360f13
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/SimpleCloseable.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.common.util.closeable;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.DefaultCloseFuture;
+import org.apache.sshd.common.future.SshFutureListener;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SimpleCloseable extends IoBaseCloseable {
+
+    protected final DefaultCloseFuture future;
+    protected final AtomicBoolean closing;
+
+    public SimpleCloseable(Object id, Object lock) {
+        future = new DefaultCloseFuture(id, lock);
+        closing = new AtomicBoolean(false);
+    }
+
+    @Override
+    public boolean isClosed() {
+        return future.isClosed();
+    }
+
+    @Override
+    public boolean isClosing() {
+        return closing.get();
+    }
+
+    @Override
+    public void addCloseFutureListener(SshFutureListener<CloseFuture> listener) {
+        future.addListener(listener);
+    }
+
+    @Override
+    public void removeCloseFutureListener(SshFutureListener<CloseFuture> listener) {
+        future.removeListener(listener);
+    }
+
+    @Override
+    public CloseFuture close(boolean immediately) {
+        if (closing.compareAndSet(false, true)) {
+            doClose(immediately);
+        }
+        return future;
+    }
+
+    protected void doClose(boolean immediately) {
+        future.setClosed();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/CloseableEmptyInputStream.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/CloseableEmptyInputStream.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/CloseableEmptyInputStream.java
new file mode 100644
index 0000000..c9eca70
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/CloseableEmptyInputStream.java
@@ -0,0 +1,96 @@
+/*
+ * 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.channels.Channel;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A {@code /dev/null} stream that can be closed - in which case it will throw
+ * {@link IOException}s if invoked after being closed
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class CloseableEmptyInputStream extends EmptyInputStream implements Channel {
+    private final AtomicBoolean open = new AtomicBoolean(true);
+
+    public CloseableEmptyInputStream() {
+        super();
+    }
+
+    @Override
+    public boolean isOpen() {
+        return open.get();
+    }
+
+    @Override
+    public int available() throws IOException {
+        if (isOpen()) {
+            return super.available();
+        } else {
+            throw new IOException("available() stream is closed");
+        }
+    }
+
+    @Override
+    public int read() throws IOException {
+        if (isOpen()) {
+            return super.read();
+        } else {
+            throw new IOException("read() stream is closed");
+        }
+    }
+
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException {
+        if (isOpen()) {
+            return super.read(b, off, len);
+        } else {
+            throw new IOException("read([])[" + off + "," + len + "] stream is closed");
+        }
+    }
+
+    @Override
+    public long skip(long n) throws IOException {
+        if (isOpen()) {
+            return super.skip(n);
+        } else {
+            throw new IOException("skip(" + n + ") stream is closed");
+        }
+    }
+
+    @Override
+    public synchronized void reset() throws IOException {
+        if (isOpen()) {
+            super.reset();
+        } else {
+            throw new IOException("reset() stream is closed");
+        }
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (open.getAndSet(false)) {
+            //noinspection UnnecessaryReturnStatement
+            return; // debug breakpoint
+        }
+    }
+}