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 "placeholder",
+ * 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 "placeholder" 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., "ssh-rsa", "ssh-dss"
+ * @return {@code true} if this key type is supported by the parser
+ */
+ boolean isKeyTypeSupported(String keyType);
+
+ /**
+ * @param keyType The key type - e.g., "ssh-rsa", "ssh-dss"
+ * @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 "parent" 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 "parent" 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
+ }
+ }
+}