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:[email protected]">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:[email protected]">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:[email protected]">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:[email protected]">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:[email protected]">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:[email protected]">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:[email protected]">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:[email protected]">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:[email protected]">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:[email protected]">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:[email protected]">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:[email protected]">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:[email protected]">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:[email protected]">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:[email protected]">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:[email protected]">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:[email protected]">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 + } + } +}
