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:46 UTC
[36/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/io/der/ASN1Object.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/ASN1Object.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/ASN1Object.java
new file mode 100644
index 0000000..1d32a40
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/ASN1Object.java
@@ -0,0 +1,338 @@
+/*
+ * 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.der;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.StreamCorruptedException;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.NumberUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class ASN1Object implements Serializable, Cloneable {
+ // Constructed Flag
+ public static final byte CONSTRUCTED = 0x20;
+
+ private static final long serialVersionUID = 4687581744706127265L;
+
+ private ASN1Class objClass;
+ private ASN1Type objType;
+ private boolean constructed;
+ private int length;
+ private byte[] value;
+
+ public ASN1Object() {
+ super();
+ }
+
+ /*
+ * <P>The first byte in DER encoding is made of following fields</P>
+ * <pre>
+ *-------------------------------------------------
+ *|Bit 8|Bit 7|Bit 6|Bit 5|Bit 4|Bit 3|Bit 2|Bit 1|
+ *-------------------------------------------------
+ *| Class | CF | Type |
+ *-------------------------------------------------
+ * </pre>
+ */
+ public ASN1Object(byte tag, int len, byte... data) {
+ this(ASN1Class.fromDERValue(tag), ASN1Type.fromDERValue(tag), (tag & CONSTRUCTED) == CONSTRUCTED, len, data);
+ }
+
+ public ASN1Object(ASN1Class c, ASN1Type t, boolean ctored, int len, byte... data) {
+ objClass = c;
+ objType = t;
+ constructed = ctored;
+ length = len;
+ value = data;
+ }
+
+ public ASN1Class getObjClass() {
+ return objClass;
+ }
+
+ public void setObjClass(ASN1Class c) {
+ objClass = c;
+ }
+
+ public ASN1Type getObjType() {
+ return objType;
+ }
+
+ public void setObjType(ASN1Type y) {
+ objType = y;
+ }
+
+ public boolean isConstructed() {
+ return constructed;
+ }
+
+ public void setConstructed(boolean c) {
+ constructed = c;
+ }
+
+ public int getLength() {
+ return length;
+ }
+
+ public void setLength(int l) {
+ length = l;
+ }
+
+ public byte[] getValue() {
+ return value;
+ }
+
+ // if length is less than value.length then returns copy of it
+ public byte[] getPureValueBytes() {
+ byte[] bytes = getValue();
+ int available = getLength();
+ int numBytes = NumberUtils.length(bytes);
+ if (numBytes == available) {
+ return bytes;
+ }
+
+ if (available == 0) {
+ return GenericUtils.EMPTY_BYTE_ARRAY;
+ }
+
+ byte[] pure = new byte[available];
+ System.arraycopy(bytes, 0, pure, 0, available);
+ return pure;
+ }
+
+ public void setValue(byte[] v) {
+ value = v;
+ }
+
+ public DERParser createParser() {
+ return new DERParser(getValue(), 0, getLength());
+ }
+
+ public Object asObject() throws IOException {
+ ASN1Type type = getObjType();
+ if (type == null) {
+ throw new IOException("No type set");
+ }
+
+ switch (type) {
+ case INTEGER:
+ return asInteger();
+
+ case NUMERIC_STRING:
+ case PRINTABLE_STRING:
+ case VIDEOTEX_STRING:
+ case IA5_STRING:
+ case GRAPHIC_STRING:
+ case ISO646_STRING:
+ case GENERAL_STRING:
+ case BMP_STRING:
+ case UTF8_STRING:
+ return asString();
+
+ case OBJECT_IDENTIFIER:
+ return asOID();
+
+ case SEQUENCE :
+ return getValue();
+
+ default:
+ throw new IOException("Invalid DER: unsupported type: " + type);
+ }
+ }
+
+ /**
+ * Get the value as {@link BigInteger}
+ * @return BigInteger
+ * @throws IOException if type not an {@link ASN1Type#INTEGER}
+ */
+ public BigInteger asInteger() throws IOException {
+ ASN1Type typeValue = getObjType();
+ if (ASN1Type.INTEGER.equals(typeValue)) {
+ return toInteger();
+ } else {
+ throw new IOException("Invalid DER: object is not integer: " + typeValue);
+ }
+ }
+
+ // does not check if this is an integer
+ public BigInteger toInteger() {
+ return new BigInteger(getPureValueBytes());
+ }
+
+ /**
+ * Get value as string. Most strings are treated as Latin-1.
+ * @return Java string
+ * @throws IOException if
+ */
+ public String asString() throws IOException {
+ ASN1Type type = getObjType();
+ if (type == null) {
+ throw new IOException("No type set");
+ }
+
+ final String encoding;
+ switch (type) {
+ // Not all are Latin-1 but it's the closest thing
+ case NUMERIC_STRING:
+ case PRINTABLE_STRING:
+ case VIDEOTEX_STRING:
+ case IA5_STRING:
+ case GRAPHIC_STRING:
+ case ISO646_STRING:
+ case GENERAL_STRING:
+ encoding = "ISO-8859-1";
+ break;
+
+ case BMP_STRING:
+ encoding = "UTF-16BE";
+ break;
+
+ case UTF8_STRING:
+ encoding = "UTF-8";
+ break;
+
+ case UNIVERSAL_STRING:
+ throw new IOException("Invalid DER: can't handle UCS-4 string");
+
+ default:
+ throw new IOException("Invalid DER: object is not a string: " + type);
+ }
+
+ return new String(getValue(), 0, getLength(), encoding);
+ }
+
+ public List<Integer> asOID() throws IOException {
+ ASN1Type typeValue = getObjType();
+ if (ASN1Type.OBJECT_IDENTIFIER.equals(typeValue)) {
+ return toOID();
+ } else {
+ throw new StreamCorruptedException("Invalid DER: object is not an OID: " + typeValue);
+ }
+ }
+
+ // Does not check that type is OID
+ public List<Integer> toOID() throws IOException {
+ int vLen = getLength();
+ if (vLen <= 0) {
+ throw new EOFException("Not enough data for an OID");
+ }
+
+ List<Integer> oid = new ArrayList<>(vLen + 1);
+ byte[] bytes = getValue();
+ int val1 = bytes[0] & 0xFF;
+ oid.add(Integer.valueOf(val1 / 40));
+ oid.add(Integer.valueOf(val1 % 40));
+
+ for (int curPos = 1; curPos < vLen; curPos++) {
+ int v = bytes[curPos] & 0xFF;
+ if (v <= 0x7F) { // short form
+ oid.add(Integer.valueOf(v));
+ continue;
+ }
+
+ long curVal = v & 0x7F;
+ curPos++;
+
+ for (int subLen = 1;; subLen++, curPos++) {
+ if (curPos >= vLen) {
+ throw new EOFException("Incomplete OID value");
+ }
+
+ if (subLen > 5) { // 32 bit values can span at most 5 octets
+ throw new StreamCorruptedException("OID component encoding beyond 5 bytes");
+ }
+
+ v = bytes[curPos] & 0xFF;
+ curVal = ((curVal << 7) & 0xFFFFFFFF80L) | (v & 0x7FL);
+ if (curVal > Integer.MAX_VALUE) {
+ throw new StreamCorruptedException("OID value exceeds 32 bits: " + curVal);
+ }
+
+ if (v <= 0x7F) { // found last octet ?
+ break;
+ }
+ }
+
+ oid.add(Integer.valueOf((int) (curVal & 0x7FFFFFFFL)));
+ }
+
+ return oid;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getObjClass(), getObjType())
+ + Boolean.hashCode(isConstructed())
+ + getLength()
+ + NumberUtils.hashCode(getValue(), 0, getLength());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (this == obj) {
+ return true;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+
+ ASN1Object other = (ASN1Object) obj;
+ return Objects.equals(this.getObjClass(), other.getObjClass())
+ && Objects.equals(this.getObjType(), other.getObjType())
+ && (this.isConstructed() == other.isConstructed())
+ && (this.getLength() == other.getLength())
+ && (NumberUtils.diffOffset(this.getValue(), 0, other.getValue(), 0, this.getLength()) < 0);
+ }
+
+ @Override
+ public ASN1Object clone() {
+ try {
+ ASN1Object cpy = getClass().cast(super.clone());
+ byte[] data = cpy.getValue();
+ if (data != null) {
+ cpy.setValue(data.clone());
+ }
+ return cpy;
+ } catch (CloneNotSupportedException e) {
+ throw new IllegalStateException("Unexpected clone failure: " + e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toString(getObjClass())
+ + "/" + getObjType()
+ + "/" + isConstructed()
+ + "[" + getLength() + "]"
+ + ": " + BufferUtils.toHex(getValue(), 0, getLength(), ':');
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/ASN1Type.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/ASN1Type.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/ASN1Type.java
new file mode 100644
index 0000000..3dd13ca
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/ASN1Type.java
@@ -0,0 +1,118 @@
+/*
+ * 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.der;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public enum ASN1Type {
+ ANY((byte) 0x00),
+ BOOLEAN((byte) 0x01),
+ INTEGER((byte) 0x02),
+ BIT_STRING((byte) 0x03),
+ OCTET_STRING((byte) 0x04),
+ NULL((byte) 0x05),
+ OBJECT_IDENTIFIER((byte) 0x06),
+ REAL((byte) 0x09),
+ ENUMERATED((byte) 0x0a),
+ RELATIVE_OID((byte) 0x0d),
+ SEQUENCE((byte) 0x10),
+ SET((byte) 0x11),
+ NUMERIC_STRING((byte) 0x12),
+ PRINTABLE_STRING((byte) 0x13),
+ T61_STRING((byte) 0x14),
+ VIDEOTEX_STRING((byte) 0x15),
+ IA5_STRING((byte) 0x16),
+ GRAPHIC_STRING((byte) 0x19),
+ ISO646_STRING((byte) 0x1A),
+ GENERAL_STRING((byte) 0x1B),
+ UTF8_STRING((byte) 0x0C),
+ UNIVERSAL_STRING((byte) 0x1C),
+ BMP_STRING((byte) 0x1E),
+ UTC_TIME((byte) 0x17),
+ GENERALIZED_TIME((byte) 0x18);
+
+ public static final Set<ASN1Type> VALUES =
+ Collections.unmodifiableSet(EnumSet.allOf(ASN1Type.class));
+
+ private final byte typeValue;
+
+ ASN1Type(byte typeVal) {
+ typeValue = typeVal;
+ }
+
+ public byte getTypeValue() {
+ return typeValue;
+ }
+
+ public static ASN1Type fromName(String s) {
+ if (GenericUtils.isEmpty(s)) {
+ return null;
+ }
+
+ for (ASN1Type t : VALUES) {
+ if (s.equalsIgnoreCase(t.name())) {
+ return t;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * <P>The first byte in DER encoding is made of following fields</P>
+ * <pre>
+ *-------------------------------------------------
+ *|Bit 8|Bit 7|Bit 6|Bit 5|Bit 4|Bit 3|Bit 2|Bit 1|
+ *-------------------------------------------------
+ *| Class | CF | Type |
+ *-------------------------------------------------
+ * </pre>
+ * @param value The original DER encoded byte
+ * @return The {@link ASN1Type} value - {@code null} if no match found
+ * @see #fromTypeValue(int)
+ */
+ public static ASN1Type fromDERValue(int value) {
+ return fromTypeValue(value & 0x1F);
+ }
+
+ /**
+ * @param value The "pure" type value - with no extra bits set
+ * @return The {@link ASN1Type} value - {@code null} if no match found
+ */
+ public static ASN1Type fromTypeValue(int value) {
+ if ((value < 0) || (value > 0x1F)) { // only 5 bits are used
+ return null;
+ }
+
+ for (ASN1Type t : VALUES) {
+ if (t.getTypeValue() == value) {
+ return t;
+ }
+ }
+
+ return null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/DERParser.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/DERParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/DERParser.java
new file mode 100644
index 0000000..5f37bd7
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/DERParser.java
@@ -0,0 +1,151 @@
+/*
+ * 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.der;
+
+import java.io.ByteArrayInputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StreamCorruptedException;
+import java.math.BigInteger;
+import java.util.Arrays;
+
+import org.apache.sshd.common.util.NumberUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+
+/**
+ * A bare minimum DER parser - just enough to be able to decode
+ * signatures and private keys
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DERParser extends FilterInputStream {
+ /**
+ * Maximum size of data allowed by {@link #readLength()} - it is a bit
+ * arbitrary since one can encode 32-bit length data, but it is good
+ * enough for the keys
+ */
+ public static final int MAX_DER_VALUE_LENGTH = 2 * Short.MAX_VALUE;
+
+ private final byte[] lenBytes = new byte[Integer.BYTES];
+
+ public DERParser(byte... bytes) {
+ this(bytes, 0, NumberUtils.length(bytes));
+ }
+
+ public DERParser(byte[] bytes, int offset, int len) {
+ this(new ByteArrayInputStream(bytes, offset, len));
+ }
+
+ public DERParser(InputStream s) {
+ super(s);
+ }
+
+ /**
+ * Decode the length of the field. Can only support length
+ * encoding up to 4 octets. In BER/DER encoding, length can
+ * be encoded in 2 forms:
+ * <ul>
+ * <li><p>
+ * Short form - One octet. Bit 8 has value "0" and bits 7-1
+ * give the length.
+ * </p></li>
+ *
+ * <li><p>
+ * Long form - Two to 127 octets (only 4 is supported here).
+ * Bit 8 of first octet has value "1" and bits 7-1 give the
+ * number of additional length octets. Second and following
+ * octets give the length, base 256, most significant digit
+ * first.
+ * </p></li>
+ * </ul>
+ *
+ * @return The length as integer
+ * @throws IOException If invalid format found
+ */
+ public int readLength() throws IOException {
+ int i = read();
+ if (i == -1) {
+ throw new StreamCorruptedException("Invalid DER: length missing");
+ }
+
+ // A single byte short length
+ if ((i & ~0x7F) == 0) {
+ return i;
+ }
+
+ int num = i & 0x7F;
+ // TODO We can't handle length longer than 4 bytes
+ if ((i >= 0xFF) || (num > lenBytes.length)) {
+ throw new StreamCorruptedException("Invalid DER: length field too big: " + i);
+ }
+
+ // place the read bytes last so that the 1st ones are zeroes as big endian
+ Arrays.fill(lenBytes, (byte) 0);
+ int n = read(lenBytes, 4 - num, num);
+ if (n < num) {
+ throw new StreamCorruptedException("Invalid DER: length data too short: expected=" + num + ", actual=" + n);
+ }
+
+ long len = BufferUtils.getUInt(lenBytes);
+ if (len < 0x7FL) { // according to standard: "the shortest possible length encoding must be used"
+ throw new StreamCorruptedException("Invalid DER: length not in shortest form: " + len);
+ }
+
+ if (len > MAX_DER_VALUE_LENGTH) {
+ throw new StreamCorruptedException("Invalid DER: data length too big: " + len + " (max=" + MAX_DER_VALUE_LENGTH + ")");
+ }
+
+ // we know the cast is safe since it is less than MAX_DER_VALUE_LENGTH which is ~64K
+ return (int) len;
+ }
+
+ public ASN1Object readObject() throws IOException {
+ int tag = read();
+ if (tag == -1) {
+ return null;
+ }
+
+ int length = readLength();
+ byte[] value = new byte[length];
+ int n = read(value);
+ if (n < length) {
+ throw new StreamCorruptedException("Invalid DER: stream too short, missing value: read " + n + " out of required " + length);
+ }
+
+ return new ASN1Object((byte) tag, length, value);
+ }
+
+ public BigInteger readBigInteger() throws IOException {
+ int type = read();
+ if (type != 0x02) {
+ throw new StreamCorruptedException("Invalid DER: data type is not an INTEGER: 0x" + Integer.toHexString(type));
+ }
+
+ int len = readLength();
+ byte[] value = new byte[len];
+ int n = read(value);
+ if (n < len) {
+ throw new StreamCorruptedException("Invalid DER: stream too short, missing value: read " + n + " out of required " + len);
+ }
+
+ return new BigInteger(value);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/DERWriter.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/DERWriter.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/DERWriter.java
new file mode 100644
index 0000000..bc603ee
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/DERWriter.java
@@ -0,0 +1,172 @@
+/*
+ * 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.der;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StreamCorruptedException;
+import java.math.BigInteger;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.sshd.common.util.NumberUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+
+/**
+ * A bare-minimum DER encoder - just enough so we can encoder signatures
+ * and keys data
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DERWriter extends FilterOutputStream {
+ private final byte[] lenBytes = new byte[Integer.BYTES];
+
+ public DERWriter() {
+ this(ByteArrayBuffer.DEFAULT_SIZE);
+ }
+
+ public DERWriter(int initialSize) {
+ this(new ByteArrayOutputStream(initialSize));
+ }
+
+ public DERWriter(OutputStream stream) {
+ super(Objects.requireNonNull(stream, "No output stream"));
+ }
+
+ public DERWriter startSequence() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ AtomicBoolean dataWritten = new AtomicBoolean(false);
+ @SuppressWarnings("resource")
+ DERWriter encloser = this;
+ return new DERWriter(baos) {
+ @Override
+ public void close() throws IOException {
+ baos.close();
+
+ if (!dataWritten.getAndSet(true)) { // detect repeated calls and write this only once
+ encloser.writeObject(new ASN1Object(ASN1Class.UNIVERSAL, ASN1Type.SEQUENCE, false, baos.size(), baos.toByteArray()));
+ }
+ }
+ };
+ }
+
+ public void writeBigInteger(BigInteger value) throws IOException {
+ writeBigInteger(Objects.requireNonNull(value, "No value").toByteArray());
+ }
+
+ /**
+ * The integer is always considered to be positive, so if the first byte is < 0,
+ * we pad with a zero to make it positive
+ *
+ * @param bytes {@link BigInteger} bytes
+ * @throws IOException If failed to write the bytes
+ */
+ public void writeBigInteger(byte... bytes) throws IOException {
+ writeBigInteger(bytes, 0, NumberUtils.length(bytes));
+ }
+
+ /**
+ * The integer is always considered to be positive, so if the first byte is < 0,
+ * we pad with a zero to make it positive
+ *
+ * @param bytes {@link BigInteger} bytes
+ * @param off Offset in bytes data
+ * @param len Number of bytes to write
+ * @throws IOException If failed to write the bytes
+ */
+ public void writeBigInteger(byte[] bytes, int off, int len) throws IOException {
+ // Strip leading zeroes
+ while (len > 1 && bytes[off] == 0 && isPositive(bytes[off + 1])) {
+ off++;
+ len--;
+ }
+ // indicate it is an INTEGER
+ write(0x02);
+ // Pad with a zero if needed
+ if (isPositive(bytes[off])) {
+ writeLength(len);
+ } else {
+ writeLength(len + 1);
+ write(0);
+ }
+ // Write data
+ write(bytes, off, len);
+ }
+
+ private boolean isPositive(byte b) {
+ return (b & 0x80) == 0;
+ }
+
+ public void writeObject(ASN1Object obj) throws IOException {
+ Objects.requireNonNull(obj, "No ASN.1 object");
+
+ ASN1Type type = obj.getObjType();
+ byte typeValue = type.getTypeValue();
+ ASN1Class clazz = obj.getObjClass();
+ byte classValue = clazz.getClassValue();
+ byte tagValue = (byte) (((classValue << 6) & 0xC0) | (typeValue & 0x1F));
+ writeObject(tagValue, obj.getLength(), obj.getValue());
+ }
+
+ public void writeObject(byte tag, int len, byte... data) throws IOException {
+ write(tag & 0xFF);
+ writeLength(len);
+ write(data, 0, len);
+ }
+
+ public void writeLength(int len) throws IOException {
+ ValidateUtils.checkTrue(len >= 0, "Invalid length: %d", len);
+
+ // short form - MSBit is zero
+ if (len <= 127) {
+ write(len);
+ return;
+ }
+
+ BufferUtils.putUInt(len, lenBytes);
+
+ int nonZeroPos = 0;
+ for (; nonZeroPos < lenBytes.length; nonZeroPos++) {
+ if (lenBytes[nonZeroPos] != 0) {
+ break;
+ }
+ }
+
+ if (nonZeroPos >= lenBytes.length) {
+ throw new StreamCorruptedException("All zeroes length representation for len=" + len);
+ }
+
+ int bytesLen = lenBytes.length - nonZeroPos;
+ write(0x80 | bytesLen); // indicate number of octets
+ write(lenBytes, nonZeroPos, bytesLen);
+ }
+
+ public byte[] toByteArray() throws IOException {
+ if (this.out instanceof ByteArrayOutputStream) {
+ return ((ByteArrayOutputStream) this.out).toByteArray();
+ } else {
+ throw new IOException("The underlying stream is not a byte[] stream");
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/functors/IOFunction.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/functors/IOFunction.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/functors/IOFunction.java
new file mode 100644
index 0000000..a55ac2e
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/functors/IOFunction.java
@@ -0,0 +1,86 @@
+/*
+ * 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.functors;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * Invokes some I/O function on the input returning some output
+ * and potentially throwing an {@link IOException} in the process
+ *
+ * @param <T> Type of input
+ * @param <R> Type of output
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FunctionalInterface
+public interface IOFunction<T, R> {
+ R apply(T t) throws IOException;
+
+ /**
+ * Returns a composed function that first applies the {@code before}
+ * function to its input, and then applies this function to the result.
+ * If evaluation of either function throws an exception, it is relayed to
+ * the caller of the composed function.
+ *
+ * @param <V> the type of input to the {@code before} function, and to the
+ * composed function
+ * @param before the function to apply before this function is applied
+ * @return a composed function that first applies the {@code before}
+ * function and then applies this function
+ * @throws NullPointerException if before is null
+ *
+ * @see #andThen(IOFunction)
+ */
+ default <V> IOFunction<V, R> compose(IOFunction<? super V, ? extends T> before) {
+ Objects.requireNonNull(before, "No composing function provided");
+ return (V v) -> apply(before.apply(v));
+ }
+
+ /**
+ * Returns a composed function that first applies this function to
+ * its input, and then applies the {@code after} function to the result.
+ * If evaluation of either function throws an exception, it is relayed to
+ * the caller of the composed function.
+ *
+ * @param <V> the type of output of the {@code after} function, and of the
+ * composed function
+ * @param after the function to apply after this function is applied
+ * @return a composed function that first applies this function and then
+ * applies the {@code after} function
+ * @throws NullPointerException if after is null
+ *
+ * @see #compose(IOFunction)
+ */
+ default <V> IOFunction<T, V> andThen(IOFunction<? super R, ? extends V> after) {
+ Objects.requireNonNull(after, "No composing function provided");
+ return (T t) -> after.apply(apply(t));
+ }
+
+ /**
+ * Returns a function that always returns its input argument.
+ *
+ * @param <T> the type of the input and output objects to the function
+ * @return a function that always returns its input argument
+ */
+ static <T> IOFunction<T, T> identity() {
+ return t -> t;
+ }
+}
\ 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/logging/AbstractLoggingBean.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/logging/AbstractLoggingBean.java b/sshd-common/src/main/java/org/apache/sshd/common/util/logging/AbstractLoggingBean.java
new file mode 100644
index 0000000..27fef2f
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/logging/AbstractLoggingBean.java
@@ -0,0 +1,72 @@
+/*
+ * 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.logging;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Serves as a common base class for the vast majority of classes that require
+ * some kind of logging. Facilitates quick and easy replacement of the actual used
+ * logger from one framework to another
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractLoggingBean {
+ protected final Logger log;
+ private final AtomicReference<SimplifiedLog> simplifiedLog = new AtomicReference<>();
+
+ /**
+ * Default constructor - creates a logger using the full class name
+ */
+ protected AbstractLoggingBean() {
+ this("");
+ }
+
+ /**
+ * Create a logger for instances of the same class for which we might
+ * want to have a "discriminator" for them
+ *
+ * @param discriminator The discriminator value - ignored if {@code null}
+ * or empty
+ */
+ protected AbstractLoggingBean(String discriminator) {
+ String name = getClass().getName();
+ if (GenericUtils.length(discriminator) > 0) {
+ name += "[" + discriminator + "]";
+ }
+ log = LoggerFactory.getLogger(name);
+ }
+
+ protected SimplifiedLog getSimplifiedLogger() {
+ SimplifiedLog logger;
+ synchronized (simplifiedLog) {
+ logger = simplifiedLog.get();
+ if (logger == null) {
+ logger = LoggingUtils.wrap(log);
+ }
+ }
+
+ return logger;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/logging/LoggingUtils.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/logging/LoggingUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/logging/LoggingUtils.java
new file mode 100644
index 0000000..674ed1a
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/logging/LoggingUtils.java
@@ -0,0 +1,549 @@
+/*
+ * 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.logging;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.logging.Level;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.NumberUtils;
+import org.apache.sshd.common.util.ReflectionUtils;
+import org.slf4j.Logger;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class LoggingUtils {
+
+ private LoggingUtils() {
+ throw new UnsupportedOperationException("No instance");
+ }
+
+ /**
+ * Scans using reflection API for all fields that are {@code public static final}
+ * that start with the given common prefix (case <U>sensitive</U>) and are of type
+ * {@link Number}.
+ *
+ * @param clazz The {@link Class} to query
+ * @param commonPrefix The expected common prefix
+ * @return A {@link NavigableMap} of all the matching fields, where key=the field's {@link Integer}
+ * value and mapping=the field's name
+ * @see #generateMnemonicMap(Class, Predicate)
+ */
+ public static NavigableMap<Integer, String> generateMnemonicMap(Class<?> clazz, final String commonPrefix) {
+ return generateMnemonicMap(clazz, f -> {
+ String name = f.getName();
+ return name.startsWith(commonPrefix);
+ });
+ }
+
+ /**
+ * Scans using reflection API for all <U>numeric {@code public static final}</U> fields
+ * that are also accepted by the predicate. Any field that is not such or fail to retrieve
+ * its value, or has a duplicate value is <U>silently</U> skipped.
+ *
+ * @param clazz The {@link Class} to query
+ * @param acceptor The {@link Predicate} used to decide whether to process the {@link Field}
+ * (besides being a {@link Number} and {@code public static final}).
+ * @return A {@link NavigableMap} of all the matching fields, where key=the field's {@link Integer}
+ * value and mapping=the field's name
+ * @see #getMnemonicFields(Class, Predicate)
+ */
+ public static NavigableMap<Integer, String> generateMnemonicMap(Class<?> clazz, Predicate<? super Field> acceptor) {
+ Collection<Field> fields = getMnemonicFields(clazz, acceptor);
+ if (GenericUtils.isEmpty(fields)) {
+ return Collections.emptyNavigableMap();
+ }
+
+ NavigableMap<Integer, String> result = new TreeMap<>(Comparator.naturalOrder());
+ for (Field f : fields) {
+ String name = f.getName();
+ try {
+ Number value = (Number) f.get(null);
+ String prev = result.put(NumberUtils.toInteger(value), name);
+ if (prev != null) {
+ //noinspection UnnecessaryContinue
+ continue; // debug breakpoint
+ }
+ } catch (Exception e) {
+ //noinspection UnnecessaryContinue
+ continue; // debug breakpoint
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Scans using reflection API for all <U>numeric {@code public static final}</U> fields
+ * that have a common prefix and whose value is used by several of the other
+ * matching fields
+ *
+ * @param clazz The {@link Class} to query
+ * @param commonPrefix The expected common prefix
+ * @return A {@link Map} of all the mnemonic fields names whose value is the same as other
+ * fields in this map. The key is the field's name and value is its associated opcode.
+ * @see #getAmbiguousMenmonics(Class, Predicate)
+ */
+ public static Map<String, Integer> getAmbiguousMenmonics(Class<?> clazz, String commonPrefix) {
+ return getAmbiguousMenmonics(clazz, f -> {
+ String name = f.getName();
+ return name.startsWith(commonPrefix);
+ });
+ }
+
+ /**
+ * Scans using reflection API for all <U>numeric {@code public static final}</U> fields
+ * that are also accepted by the predicate and whose value is used by several of the other
+ * matching fields
+ *
+ * @param clazz The {@link Class} to query
+ * @param acceptor The {@link Predicate} used to decide whether to process the {@link Field}
+ * (besides being a {@link Number} and {@code public static final}).
+ * @return A {@link Map} of all the mnemonic fields names whose value is the same as other
+ * fields in this map. The key is the field's name and value is its associated opcode.
+ * @see #getMnemonicFields(Class, Predicate)
+ */
+ public static Map<String, Integer> getAmbiguousMenmonics(Class<?> clazz, Predicate<? super Field> acceptor) {
+ Collection<Field> fields = getMnemonicFields(clazz, acceptor);
+ if (GenericUtils.isEmpty(fields)) {
+ return Collections.emptyMap();
+ }
+
+ Map<String, Integer> result = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ Map<Integer, List<String>> opcodesMap = new TreeMap<>(Comparator.naturalOrder());
+ for (Field f : fields) {
+ String name = f.getName();
+ try {
+ Number value = (Number) f.get(null);
+ Integer key = NumberUtils.toInteger(value);
+ List<String> nameList = opcodesMap.get(key);
+ if (nameList == null) {
+ nameList = new ArrayList<>();
+ opcodesMap.put(key, nameList);
+ }
+ nameList.add(name);
+
+ int numOpcodes = nameList.size();
+ if (numOpcodes > 1) {
+ result.put(name, key);
+ if (numOpcodes == 2) { // add the 1st name as well
+ result.put(nameList.get(0), key);
+ }
+ }
+ } catch (Exception e) {
+ continue; // debug breakpoint
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Scans using reflection API for all <U>numeric {@code public static final}</U> fields
+ * that are also accepted by the predicate.
+ *
+ * @param clazz The {@link Class} to query
+ * @param acceptor The {@link Predicate} used to decide whether to process the {@link Field}
+ * (besides being a {@link Number} and {@code public static final}).
+ * @return A {@link Collection} of all the fields that have satisfied all conditions
+ */
+ public static Collection<Field> getMnemonicFields(Class<?> clazz, Predicate<? super Field> acceptor) {
+ return ReflectionUtils.getMatchingFields(clazz, f -> {
+ int mods = f.getModifiers();
+ if ((!Modifier.isPublic(mods)) || (!Modifier.isStatic(mods)) || (!Modifier.isFinal(mods))) {
+ return false;
+ }
+
+ Class<?> type = f.getType();
+ if (!NumberUtils.isNumericClass(type)) {
+ return false;
+ }
+
+ return acceptor.test(f);
+ });
+ }
+
+ /**
+ * Verifies if the given level is above the required threshold for logging.
+ *
+ * @param level The {@link Level} to evaluate
+ * @param threshold The threshold {@link Level}
+ * @return {@code true} if the evaluated level is above the required
+ * threshold.
+ * <P>
+ * <B>Note(s):</B>
+ * </P>
+ * <UL>
+ * <LI><P>
+ * If either argument is {@code null} then result is {@code false}.
+ * </P></LI>
+ *
+ * <LI><P>
+ * If the evaluated level is {@link Level#OFF} then result is {@code false}
+ * regardless of the threshold.
+ * </P></LI>
+ *
+ * <LI><P>
+ * If the threshold is {@link Level#ALL} and the evaluated level is
+ * <U>not</U> {@link Level#OFF} the result is {@code true}.
+ * </P></LI>
+ *
+ * <LI><P>
+ * Otherwise, the evaluated level {@link Level#intValue()} must be
+ * greater or equal to the threshold.
+ * </P></LI>
+ * </UL>
+ */
+ public static boolean isLoggable(Level level, Level threshold) {
+ if ((level == null) || (threshold == null)) {
+ return false;
+ } else if (Level.OFF.equals(level) || Level.OFF.equals(threshold)) {
+ return false;
+ } else if (Level.ALL.equals(threshold)) {
+ return true;
+ } else {
+ return level.intValue() >= threshold.intValue();
+ }
+ }
+
+ public static SimplifiedLog wrap(final Logger logger) {
+ if (logger == null) {
+ return SimplifiedLog.EMPTY;
+ } else {
+ return new SimplifiedLog() {
+ @Override
+ public void log(Level level, Object message, Throwable t) {
+ if (isEnabled(level)) {
+ logMessage(logger, level, message, t);
+ }
+
+ }
+
+ @Override
+ public boolean isEnabled(Level level) {
+ return isLoggable(logger, level);
+ }
+ };
+ }
+ }
+
+ // NOTE: assume that level enabled has been checked !!!
+ public static void logMessage(Logger logger, Level level, Object message, Throwable t) {
+ if ((logger == null) || (level == null) || Level.OFF.equals(level)) {
+ return;
+ } else if (Level.SEVERE.equals(level)) {
+ logger.error(Objects.toString(message), t);
+ } else if (Level.WARNING.equals(level)) {
+ logger.warn(Objects.toString(message), t);
+ } else if (Level.INFO.equals(level) || Level.ALL.equals(level)) {
+ logger.info(Objects.toString(message), t);
+ } else if (Level.CONFIG.equals(level) || Level.FINE.equals(level)) {
+ logger.debug(Objects.toString(message), t);
+ } else {
+ logger.trace(Objects.toString(message), t);
+ }
+ }
+
+ /**
+ * @param logger The {@link Logger} instance - ignored if {@code null}
+ * @param level The validate log {@link Level} - ignored if {@code null}
+ * @return <P>{@code true} if the level is enabled for the logger. The
+ * mapping of the level to the logger is as follows:</P>
+ * <UL>
+ * <LI>{@link Level#OFF} always returns {@code false}</LI>
+ * <LI>{@link Level#SEVERE} returns {@link Logger#isErrorEnabled()}</LI>
+ * <LI>{@link Level#WARNING} returns {@link Logger#isWarnEnabled()}</LI>
+ * <LI>{@link Level#INFO} and {@link Level#ALL} returns {@link Logger#isInfoEnabled()}</LI>
+ * <LI>{@link Level#CONFIG} and {@link Level#FINE} returns {@link Logger#isDebugEnabled()}</LI>
+ * <LI>All other levels return {@link Logger#isTraceEnabled()}</LI>
+ * </UL>
+ */
+ public static boolean isLoggable(Logger logger, Level level) {
+ if ((logger == null) || (level == null) || Level.OFF.equals(level)) {
+ return false;
+ } else if (Level.SEVERE.equals(level)) {
+ return logger.isErrorEnabled();
+ } else if (Level.WARNING.equals(level)) {
+ return logger.isWarnEnabled();
+ } else if (Level.INFO.equals(level) || Level.ALL.equals(level)) {
+ return logger.isInfoEnabled();
+ } else if (Level.CONFIG.equals(level) || Level.FINE.equals(level)) {
+ return logger.isDebugEnabled();
+ } else {
+ return logger.isTraceEnabled();
+ }
+ }
+
+ /**
+ * @param <T> Generic message type consumer
+ * @param logger The {@link Logger} instance to use
+ * @param level The log {@link Level} mapped as follows:</BR>
+ *
+ * <UL>
+ * <LI>{@link Level#OFF} - {@link #nologClosure(Logger)}</LI>
+ * <LI>{@link Level#SEVERE} - {@link #errorClosure(Logger)}</LI>
+ * <LI>{@link Level#WARNING} - {@link #warnClosure(Logger)}</LI>
+ * <LI>{@link Level#INFO}/{@link Level#ALL} - {@link #infoClosure(Logger)}</LI>
+ * <LI>{@link Level#CONFIG}/{@link Level#FINE} - {@link #debugClosure(Logger)}</LI>
+ * <LI>All others - {@link #traceClosure(Logger)}</LI>
+ * </UL>
+ * @return A consumer whose {@link Consumer#accept(Object)} method logs the
+ * {@link String#valueOf(Object)} value of its argument if the specific level is enabled
+ */
+ public static <T> Consumer<T> loggingClosure(Logger logger, Level level) {
+ return loggingClosure(logger, level, null);
+ }
+
+ public static <T> Consumer<T> loggingClosure(Logger logger, Level level, Throwable t) {
+ Objects.requireNonNull(level, "No level provided");
+
+ if (Level.OFF.equals(level)) {
+ return nologClosure(logger);
+ } else if (Level.SEVERE.equals(level)) {
+ return errorClosure(logger, t);
+ } else if (Level.WARNING.equals(level)) {
+ return warnClosure(logger, t);
+ } else if (Level.INFO.equals(level) || Level.ALL.equals(level)) {
+ return infoClosure(logger, t);
+ } else if (Level.CONFIG.equals(level) || Level.FINE.equals(level)) {
+ return debugClosure(logger, t);
+ } else {
+ return traceClosure(logger, t);
+ }
+ }
+
+ /**
+ * @param <T> Generic message type consumer
+ * @param logger The {@link Logger} instance to use
+ * @return A consumer whose {@link Consumer#accept(Object)} method logs nothing when invoked
+ */
+ public static <T> Consumer<T> nologClosure(Logger logger) {
+ Objects.requireNonNull(logger, "No logger provided");
+ return t -> { /* do nothing */ };
+ }
+
+ /**
+ * @param <T> Generic message type consumer
+ * @param logger The {@link Logger} instance to use
+ * @return A consumer whose {@link Consumer#accept(Object)} method logs the
+ * {@link String#valueOf(Object)} value of its argument if {@link Logger#isErrorEnabled()}
+ */
+ public static <T> Consumer<T> errorClosure(Logger logger) {
+ return errorClosure(logger, null);
+ }
+
+ /**
+ * @param <T> Generic message type consumer
+ * @param logger The {@link Logger} instance to use
+ * @param thrown A {@link Throwable} to attach to the message - ignored if {@code null}
+ * @return A consumer whose {@link Consumer#accept(Object)} method logs the
+ * {@link String#valueOf(Object)} value of its argument if {@link Logger#isErrorEnabled()}
+ */
+ public static <T> Consumer<T> errorClosure(Logger logger, Throwable thrown) {
+ Objects.requireNonNull(logger, "No logger provided");
+ return new Consumer<T>() {
+ @Override
+ public void accept(T input) {
+ if (logger.isErrorEnabled()) {
+ String msg = String.valueOf(input);
+ if (thrown == null) {
+ logger.error(msg);
+ } else {
+ logger.error(msg, thrown);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ERROR";
+ }
+ };
+ }
+
+ /**
+ * @param <T> Generic message type consumer
+ * @param logger The {@link Logger} instance to use
+ * @return A consumer whose {@link Consumer#accept(Object)} method logs the
+ * {@link String#valueOf(Object)} value of its argument if {@link Logger#isWarnEnabled()}
+ */
+ public static <T> Consumer<T> warnClosure(Logger logger) {
+ return warnClosure(logger, null);
+ }
+
+ /**
+ * @param <T> Generic message type consumer
+ * @param logger The {@link Logger} instance to use
+ * @param thrown A {@link Throwable} to attach to the message - ignored if {@code null}
+ * @return A consumer whose {@link Consumer#accept(Object)} method logs the {@link String#valueOf(Object)}
+ * value of its argument if {@link Logger#isWarnEnabled()}
+ */
+ public static <T> Consumer<T> warnClosure(Logger logger, Throwable thrown) {
+ Objects.requireNonNull(logger, "No logger provided");
+ return new Consumer<T>() {
+ @Override
+ public void accept(T input) {
+ if (logger.isWarnEnabled()) {
+ String msg = String.valueOf(input);
+ if (thrown == null) {
+ logger.warn(msg);
+ } else {
+ logger.warn(msg, thrown);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "WARN";
+ }
+ };
+ }
+
+ /**
+ * @param <T> Generic message type consumer
+ * @param logger The {@link Logger} instance to use
+ * @return A consumer whose {@link Consumer#accept(Object)} method logs the {@link String#valueOf(Object)}
+ * value of its argument if {@link Logger#isInfoEnabled()}
+ */
+ public static <T> Consumer<T> infoClosure(Logger logger) {
+ return infoClosure(logger, null);
+ }
+
+ /**
+ * @param <T> Generic message type consumer
+ * @param logger The {@link Logger} instance to use
+ * @param thrown A {@link Throwable} to attach to the message - ignored if {@code null}
+ * @return A consumer whose {@link Consumer#accept(Object)} method logs the
+ * {@link String#valueOf(Object)} value of its argument if {@link Logger#isInfoEnabled()}
+ */
+ public static <T> Consumer<T> infoClosure(Logger logger, Throwable thrown) {
+ Objects.requireNonNull(logger, "No logger provided");
+ return new Consumer<T>() {
+ @Override
+ public void accept(T input) {
+ if (logger.isInfoEnabled()) {
+ String msg = String.valueOf(input);
+ if (thrown == null) {
+ logger.info(msg);
+ } else {
+ logger.info(msg, thrown);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "INFO";
+ }
+ };
+ }
+
+ /**
+ * @param <T> Generic message type consumer
+ * @param logger The {@link Logger} instance to use
+ * @return A consumer whose {@link Consumer#accept(Object)} method logs the
+ * {@link String#valueOf(Object)} value of its argument if {@link Logger#isDebugEnabled()}
+ */
+ public static <T> Consumer<T> debugClosure(Logger logger) {
+ return debugClosure(logger, null);
+ }
+
+ /**
+ * @param <T> Generic message type consumer
+ * @param logger The {@link Logger} instance to use
+ * @param thrown A {@link Throwable} to attach to the message - ignored if {@code null}
+ * @return A consumer whose {@link Consumer#accept(Object)} method logs the
+ * {@link String#valueOf(Object)} value of its argument if {@link Logger#isDebugEnabled()}
+ */
+ public static <T> Consumer<T> debugClosure(Logger logger, Throwable thrown) {
+ Objects.requireNonNull(logger, "No logger provided");
+ return new Consumer<T>() {
+ @Override
+ public void accept(T input) {
+ if (logger.isDebugEnabled()) {
+ String msg = String.valueOf(input);
+ if (thrown == null) {
+ logger.debug(msg);
+ } else {
+ logger.debug(msg, thrown);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "DEBUG";
+ }
+ };
+ }
+
+ /**
+ * @param <T> Generic message type consumer
+ * @param logger The {@link Logger} instance to use
+ * @return A consumer whose {@link Consumer#accept(Object)} method logs the
+ * {@link String#valueOf(Object)} value of its argument if {@link Logger#isTraceEnabled()}
+ */
+ public static <T> Consumer<T> traceClosure(Logger logger) {
+ return traceClosure(logger, null);
+ }
+
+ /**
+ * @param <T> Generic message type consumer
+ * @param logger The {@link Logger} instance to use
+ * @param thrown A {@link Throwable} to attach to the message - ignored if {@code null}
+ * @return A consumer whose {@link Consumer#accept(Object)} method logs the
+ * {@link String#valueOf(Object)} value of its argument if {@link Logger#isTraceEnabled()}
+ */
+ public static <T> Consumer<T> traceClosure(Logger logger, Throwable thrown) {
+ Objects.requireNonNull(logger, "No logger provided");
+ return new Consumer<T>() {
+ @Override
+ public void accept(T input) {
+ if (logger.isTraceEnabled()) {
+ String msg = String.valueOf(input);
+ if (thrown == null) {
+ logger.trace(msg);
+ } else {
+ logger.trace(msg, thrown);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "TRACE";
+ }
+ };
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/logging/SimplifiedLog.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/logging/SimplifiedLog.java b/sshd-common/src/main/java/org/apache/sshd/common/util/logging/SimplifiedLog.java
new file mode 100644
index 0000000..58d58ee
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/logging/SimplifiedLog.java
@@ -0,0 +1,55 @@
+/*
+ * 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.logging;
+
+import java.util.logging.Level;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SimplifiedLog {
+
+ /**
+ * An "empty" {@link SimplifiedLog} that does nothing
+ */
+ SimplifiedLog EMPTY = new SimplifiedLog() {
+ @Override
+ public boolean isEnabled(Level level) {
+ return false;
+ }
+
+ @Override
+ public void log(Level level, Object message, Throwable t) {
+ // ignored
+ }
+
+ @Override
+ public String toString() {
+ return "EMPTY";
+ }
+ };
+
+ boolean isEnabled(Level level);
+
+ default void log(Level level, Object message) {
+ log(level, message, null);
+ }
+
+ void log(Level level, Object message, Throwable t);
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/net/NetworkConnector.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/net/NetworkConnector.java b/sshd-common/src/main/java/org/apache/sshd/common/util/net/NetworkConnector.java
new file mode 100644
index 0000000..df7683a
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/net/NetworkConnector.java
@@ -0,0 +1,90 @@
+/*
+ * 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.net;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class NetworkConnector extends AbstractLoggingBean {
+ public static final String DEFAULT_HOST = SshdSocketAddress.LOCALHOST_IPV4;
+ public static final long DEFAULT_CONNECT_TIMEOUT = TimeUnit.SECONDS.toMillis(5L);
+ public static final long DEFAULT_READ_TIMEOUT = TimeUnit.SECONDS.toMillis(15L);
+
+ private String protocol;
+ private String host = DEFAULT_HOST;
+ private int port;
+ private long connectTimeout = DEFAULT_CONNECT_TIMEOUT;
+ private long readTimeout = DEFAULT_READ_TIMEOUT;
+
+ public NetworkConnector() {
+ super();
+ }
+
+ public String getProtocol() {
+ return protocol;
+ }
+
+ public void setProtocol(String protocol) {
+ this.protocol = protocol;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public long getConnectTimeout() {
+ return connectTimeout;
+ }
+
+ public void setConnectTimeout(long connectTimeout) {
+ this.connectTimeout = connectTimeout;
+ }
+
+ public long getReadTimeout() {
+ return readTimeout;
+ }
+
+ public void setReadTimeout(long readTimeout) {
+ this.readTimeout = readTimeout;
+ }
+
+ @Override
+ public String toString() {
+ return getProtocol() + "://" + getHost() + ":" + getPort()
+ + ";connect=" + getConnectTimeout()
+ + ";read=" + getReadTimeout();
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/net/SshdSocketAddress.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/net/SshdSocketAddress.java b/sshd-common/src/main/java/org/apache/sshd/common/util/net/SshdSocketAddress.java
new file mode 100644
index 0000000..b465d14
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/net/SshdSocketAddress.java
@@ -0,0 +1,639 @@
+/*
+ * 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.net;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.NumberUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * <P>A simple socket address holding the host name and port number. The reason
+ * it does not extend {@link InetSocketAddress} is twofold:</P>
+ * <OL>
+ * <LI><P>
+ * The {@link InetSocketAddress} performs a DNS resolution on the
+ * provided host name - which we don't want do use until we want to
+ * create a connection using this address (thus the {@link #toInetSocketAddress()}
+ * call which executes this query
+ * </P></LI>
+ *
+ * <LI><P>
+ * If empty host name is provided we replace it with the <I>any</I>
+ * address of 0.0.0.0
+ * </P></LI>
+ * </OL>
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SshdSocketAddress extends SocketAddress {
+ public static final String LOCALHOST_NAME = "localhost";
+ public static final String LOCALHOST_IPV4 = "127.0.0.1";
+ public static final String IPV4_ANYADDR = "0.0.0.0";
+
+ public static final Set<String> WELL_KNOWN_IPV4_ADDRESSES =
+ Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(LOCALHOST_IPV4, IPV4_ANYADDR)));
+
+ // 10.0.0.0 - 10.255.255.255
+ public static final String PRIVATE_CLASS_A_PREFIX = "10.";
+ // 172.16.0.0 - 172.31.255.255
+ public static final String PRIVATE_CLASS_B_PREFIX = "172.";
+ // 192.168.0.0 - 192.168.255.255
+ public static final String PRIVATE_CLASS_C_PREFIX = "192.168.";
+ // 100.64.0.0 - 100.127.255.255
+ public static final String CARRIER_GRADE_NAT_PREFIX = "100.";
+ // The IPv4 broadcast address
+ public static final String BROADCAST_ADDRESS = "255.255.255.255";
+
+ /** Max. number of hex groups (separated by ":") in an IPV6 address */
+ public static final int IPV6_MAX_HEX_GROUPS = 8;
+
+ /** Max. hex digits in each IPv6 group */
+ public static final int IPV6_MAX_HEX_DIGITS_PER_GROUP = 4;
+
+ public static final String IPV6_LONG_ANY_ADDRESS = "0:0:0:0:0:0:0:0";
+ public static final String IPV6_SHORT_ANY_ADDRESS = "::";
+
+ public static final String IPV6_LONG_LOCALHOST = "0:0:0:0:0:0:0:1";
+ public static final String IPV6_SHORT_LOCALHOST = "::1";
+
+ public static final Set<String> WELL_KNOWN_IPV6_ADDRESSES =
+ Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(
+ IPV6_LONG_LOCALHOST, IPV6_SHORT_LOCALHOST,
+ IPV6_LONG_ANY_ADDRESS, IPV6_SHORT_ANY_ADDRESS)));
+
+ /**
+ * A dummy placeholder that can be used instead of {@code null}s
+ */
+ public static final SshdSocketAddress LOCALHOST_ADDRESS = new SshdSocketAddress(LOCALHOST_IPV4, 0);
+
+ /**
+ * Compares {@link InetAddress}-es according to their {@link InetAddress#getHostAddress()}
+ * value case <U>insensitive</U>
+ *
+ * @see #toAddressString(InetAddress)
+ */
+ public static final Comparator<InetAddress> BY_HOST_ADDRESS = (a1, a2) -> {
+ String n1 = GenericUtils.trimToEmpty(toAddressString(a1));
+ String n2 = GenericUtils.trimToEmpty(toAddressString(a2));
+ return String.CASE_INSENSITIVE_ORDER.compare(n1, n2);
+ };
+
+ /**
+ * Compares {@link SocketAddress}-es according to their host case <U>insensitive</U>
+ * and if equals, then according to their port value (if any)
+ *
+ * @see #toAddressString(SocketAddress)
+ * @see #toAddressPort(SocketAddress)
+ */
+ public static final Comparator<SocketAddress> BY_HOST_AND_PORT = (a1, a2) -> {
+ String n1 = GenericUtils.trimToEmpty(toAddressString(a1));
+ String n2 = GenericUtils.trimToEmpty(toAddressString(a2));
+ int nRes = String.CASE_INSENSITIVE_ORDER.compare(n1, n2);
+ if (nRes != 0) {
+ return nRes;
+ }
+
+ int p1 = toAddressPort(a1);
+ int p2 = toAddressPort(a2);
+ nRes = Integer.compare(p1, p2);
+ if (nRes != 0) {
+ return nRes;
+ }
+
+ return 0;
+ };
+
+ private static final long serialVersionUID = 6461645947151952729L;
+
+ private final String hostName;
+ private final int port;
+
+ public SshdSocketAddress(int port) {
+ this(IPV4_ANYADDR, port);
+ }
+
+ public SshdSocketAddress(InetSocketAddress addr) {
+ Objects.requireNonNull(addr, "No address provided");
+
+ String host = addr.getHostString();
+ hostName = GenericUtils.isEmpty(host) ? IPV4_ANYADDR : host;
+ port = addr.getPort();
+ ValidateUtils.checkTrue(port >= 0, "Port must be >= 0: %d", port);
+ }
+
+ public SshdSocketAddress(String hostName, int port) {
+ Objects.requireNonNull(hostName, "Host name may not be null");
+ this.hostName = GenericUtils.isEmpty(hostName) ? IPV4_ANYADDR : hostName;
+
+ ValidateUtils.checkTrue(port >= 0, "Port must be >= 0: %d", port);
+ this.port = port;
+ }
+
+ public String getHostName() {
+ return hostName;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public InetSocketAddress toInetSocketAddress() {
+ return new InetSocketAddress(getHostName(), getPort());
+ }
+
+ @Override
+ public String toString() {
+ return getHostName() + ":" + getPort();
+ }
+
+ protected boolean isEquivalent(SshdSocketAddress that) {
+ if (that == null) {
+ return false;
+ } else if (that == this) {
+ return true;
+ } else {
+ return (this.getPort() == that.getPort())
+ && (GenericUtils.safeCompare(this.getHostName(), that.getHostName(), false) == 0);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null) {
+ return false;
+ }
+ if (getClass() != o.getClass()) {
+ return false;
+ }
+ return isEquivalent((SshdSocketAddress) o);
+ }
+
+ @Override
+ public int hashCode() {
+ return GenericUtils.hashCode(getHostName(), Boolean.FALSE) + getPort();
+ }
+
+
+ /**
+ * Returns the first external network address assigned to this
+ * machine or null if one is not found.
+ * @return Inet4Address associated with an external interface
+ * DevNote: We actually return InetAddress here, as Inet4Addresses are final and cannot be mocked.
+ */
+ public static InetAddress getFirstExternalNetwork4Address() {
+ List<? extends InetAddress> addresses = getExternalNetwork4Addresses();
+ return (GenericUtils.size(addresses) > 0) ? addresses.get(0) : null;
+ }
+
+ /**
+ * @return a {@link List} of local network addresses which are not multicast
+ * or localhost sorted according to {@link #BY_HOST_ADDRESS}
+ */
+ public static List<InetAddress> getExternalNetwork4Addresses() {
+ List<InetAddress> addresses = new ArrayList<>();
+ try {
+ for (Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces(); (nets != null) && nets.hasMoreElements();) {
+ NetworkInterface netint = nets.nextElement();
+ /* TODO - uncomment when 1.5 compatibility no longer required
+ if (!netint.isUp()) {
+ continue; // ignore non-running interfaces
+ }
+ */
+
+ for (Enumeration<InetAddress> inetAddresses = netint.getInetAddresses(); (inetAddresses != null) && inetAddresses.hasMoreElements();) {
+ InetAddress inetAddress = inetAddresses.nextElement();
+ if (isValidHostAddress(inetAddress)) {
+ addresses.add(inetAddress);
+ }
+ }
+ }
+ } catch (SocketException e) {
+ // swallow
+ }
+
+ if (GenericUtils.size(addresses) > 1) {
+ Collections.sort(addresses, BY_HOST_ADDRESS);
+ }
+
+ return addresses;
+ }
+
+ /**
+ * @param addr The {@link InetAddress} to be verified
+ * @return <P><code>true</code> if the address is:</P></BR>
+ * <UL>
+ * <LI>Not {@code null}</LI>
+ * <LI>An {@link Inet4Address}</LI>
+ * <LI>Not link local</LI>
+ * <LI>Not a multicast</LI>
+ * <LI>Not a loopback</LI>
+ * </UL>
+ * @see InetAddress#isLinkLocalAddress()
+ * @see InetAddress#isMulticastAddress()
+ * @see InetAddress#isMulticastAddress()
+ */
+ public static boolean isValidHostAddress(InetAddress addr) {
+ if (addr == null) {
+ return false;
+ }
+
+ if (addr.isLinkLocalAddress()) {
+ return false;
+ }
+
+ if (addr.isMulticastAddress()) {
+ return false;
+ }
+
+ if (!(addr instanceof Inet4Address)) {
+ return false; // TODO add support for IPv6 - see SSHD-746
+ }
+
+ return !isLoopback(addr);
+
+ }
+
+ /**
+ * @param addr The {@link InetAddress} to be considered
+ * @return <code>true</code> if the address is a loopback one.
+ * <B>Note:</B> if {@link InetAddress#isLoopbackAddress()}
+ * returns <code>false</code> the address <U>string</U> is checked
+ * @see #toAddressString(InetAddress)
+ * @see #isLoopback(String)
+ */
+ public static boolean isLoopback(InetAddress addr) {
+ if (addr == null) {
+ return false;
+ }
+
+ if (addr.isLoopbackAddress()) {
+ return true;
+ }
+
+ String ip = toAddressString(addr);
+ return isLoopback(ip);
+ }
+
+ /**
+ * @param ip IP value to be tested
+ * @return <code>true</code> if the IP is "localhost" or
+ * "127.x.x.x".
+ */
+ public static boolean isLoopback(String ip) {
+ if (GenericUtils.isEmpty(ip)) {
+ return false;
+ }
+
+ if (LOCALHOST_NAME.equals(ip) || LOCALHOST_IPV4.equals(ip)) {
+ return true;
+ }
+
+ // TODO add support for IPv6 - see SSHD-746
+ String[] values = GenericUtils.split(ip, '.');
+ if (GenericUtils.length(values) != 4) {
+ return false;
+ }
+
+ for (int index = 0; index < values.length; index++) {
+ String val = values[index];
+ if (!isValidIPv4AddressComponent(val)) {
+ return false;
+ }
+
+ if (index == 0) {
+ int number = Integer.parseInt(val);
+ if (number != 127) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public static SshdSocketAddress toSshdSocketAddress(SocketAddress addr) {
+ if (addr == null) {
+ return null;
+ } else if (addr instanceof SshdSocketAddress) {
+ return (SshdSocketAddress) addr;
+ } else if (addr instanceof InetSocketAddress) {
+ InetSocketAddress isockAddress = (InetSocketAddress) addr;
+ return new SshdSocketAddress(isockAddress.getHostName(), isockAddress.getPort());
+ } else {
+ throw new UnsupportedOperationException("Cannot convert " + addr.getClass().getSimpleName()
+ + "=" + addr + " to " + SshdSocketAddress.class.getSimpleName());
+ }
+ }
+
+ public static String toAddressString(SocketAddress addr) {
+ if (addr == null) {
+ return null;
+ } else if (addr instanceof InetSocketAddress) {
+ return ((InetSocketAddress) addr).getHostString();
+ } else if (addr instanceof SshdSocketAddress) {
+ return ((SshdSocketAddress) addr).getHostName();
+ } else {
+ return addr.toString();
+ }
+ }
+
+ /**
+ * Attempts to resolve the port value
+ *
+ * @param addr The {@link SocketAddress} to examine
+ * @return The associated port value - negative if failed to resolve
+ */
+ public static int toAddressPort(SocketAddress addr) {
+ if (addr instanceof InetSocketAddress) {
+ return ((InetSocketAddress) addr).getPort();
+ } else if (addr instanceof SshdSocketAddress) {
+ return ((SshdSocketAddress) addr).getPort();
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * <P>Converts a {@code SocketAddress} into an {@link InetSocketAddress} if possible:</P></BR>
+ * <UL>
+ * <LI>If already an {@link InetSocketAddress} then cast it as such</LI>
+ * <LI>If an {@code SshdSocketAddress} then invoke {@link #toInetSocketAddress()}</LI>
+ * <LI>Otherwise, throw an exception</LI>
+ * </UL>
+ *
+ * @param remoteAddress The {@link SocketAddress} - ignored if {@code null}
+ * @return The {@link InetSocketAddress} instance
+ * @throws ClassCastException if argument is not already an {@code InetSocketAddress}
+ * or a {@code SshdSocketAddress}
+ */
+ public static InetSocketAddress toInetSocketAddress(SocketAddress remoteAddress) {
+ if (remoteAddress == null) {
+ return null;
+ } else if (remoteAddress instanceof InetSocketAddress) {
+ return (InetSocketAddress) remoteAddress;
+ } else if (remoteAddress instanceof SshdSocketAddress) {
+ return ((SshdSocketAddress) remoteAddress).toInetSocketAddress();
+ } else {
+ throw new ClassCastException("Unknown remote address type: " + remoteAddress);
+ }
+ }
+
+ public static String toAddressString(InetAddress addr) {
+ String ip = (addr == null) ? null : addr.toString();
+ if (GenericUtils.isEmpty(ip)) {
+ return null;
+ } else {
+ return ip.replaceAll(".*/", "");
+ }
+ }
+
+ public static boolean isIPv4Address(String addr) {
+ addr = GenericUtils.trimToEmpty(addr);
+ if (GenericUtils.isEmpty(addr)) {
+ return false;
+ }
+
+ if (WELL_KNOWN_IPV4_ADDRESSES.contains(addr)) {
+ return true;
+ }
+
+ String[] comps = GenericUtils.split(addr, '.');
+ if (GenericUtils.length(comps) != 4) {
+ return false;
+ }
+
+ for (String c : comps) {
+ if (!isValidIPv4AddressComponent(c)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks if the address is one of the allocated private blocks
+ * @param addr The address string
+ * @return {@code true} if this is one of the allocated private
+ * blocks. <B>Note:</B> it assumes that the address string is
+ * indeed an IPv4 address
+ * @see #isIPv4Address(String)
+ * @see #PRIVATE_CLASS_A_PREFIX
+ * @see #PRIVATE_CLASS_B_PREFIX
+ * @see #PRIVATE_CLASS_C_PREFIX
+ * @see <A HREF="http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces">Wiki page</A>
+ */
+ public static boolean isPrivateIPv4Address(String addr) {
+ if (GenericUtils.isEmpty(addr)) {
+ return false;
+ }
+
+ if (addr.startsWith(PRIVATE_CLASS_A_PREFIX) || addr.startsWith(PRIVATE_CLASS_C_PREFIX)) {
+ return true;
+ }
+
+ // for 172.x.x.x we need further checks
+ if (!addr.startsWith(PRIVATE_CLASS_B_PREFIX)) {
+ return false;
+ }
+
+ int nextCompPos = addr.indexOf('.', PRIVATE_CLASS_B_PREFIX.length());
+ if (nextCompPos <= PRIVATE_CLASS_B_PREFIX.length()) {
+ return false;
+ }
+
+ String value = addr.substring(PRIVATE_CLASS_B_PREFIX.length(), nextCompPos);
+ if (!isValidIPv4AddressComponent(value)) {
+ return false;
+ }
+
+ int v = Integer.parseInt(value);
+ return (v >= 16) && (v <= 31);
+ }
+
+ /**
+ * @param addr The address to be checked
+ * @return {@code true} if the address is in the 100.64.0.0/10 range
+ * @see <A HREF="http://tools.ietf.org/html/rfc6598">RFC6598</A>
+ */
+ public static boolean isCarrierGradeNatIPv4Address(String addr) {
+ if (GenericUtils.isEmpty(addr)) {
+ return false;
+ }
+
+ if (!addr.startsWith(CARRIER_GRADE_NAT_PREFIX)) {
+ return false;
+ }
+
+ int nextCompPos = addr.indexOf('.', CARRIER_GRADE_NAT_PREFIX.length());
+ if (nextCompPos <= CARRIER_GRADE_NAT_PREFIX.length()) {
+ return false;
+ }
+
+ String value = addr.substring(CARRIER_GRADE_NAT_PREFIX.length(), nextCompPos);
+ if (!isValidIPv4AddressComponent(value)) {
+ return false;
+ }
+
+ int v = Integer.parseInt(value);
+ return (v >= 64) && (v <= 127);
+ }
+
+ /**
+ * <P>Checks if the provided argument is a valid IPv4 address component:</P></BR>
+ * <UL>
+ * <LI>Not {@code null}/empty</LI>
+ * <LI>Has at most 3 <U>digits</U></LI>
+ * <LI>Its value is ≤ 255</LI>
+ * </UL>
+ * @param c The {@link CharSequence} to be validate
+ * @return {@code true} if valid IPv4 address component
+ */
+ public static boolean isValidIPv4AddressComponent(CharSequence c) {
+ if (GenericUtils.isEmpty(c) || (c.length() > 3)) {
+ return false;
+ }
+
+ char ch = c.charAt(0);
+ if ((ch < '0') || (ch > '9')) {
+ return false;
+ }
+
+ if (!NumberUtils.isIntegerNumber(c)) {
+ return false;
+ }
+
+ int v = Integer.parseInt(c.toString());
+ return (v >= 0) && (v <= 255);
+ }
+
+ // Based on org.apache.commons.validator.routines.InetAddressValidator#isValidInet6Address
+ public static boolean isIPv6Address(String address) {
+ address = GenericUtils.trimToEmpty(address);
+ if (GenericUtils.isEmpty(address)) {
+ return false;
+ }
+
+ if (WELL_KNOWN_IPV6_ADDRESSES.contains(address)) {
+ return true;
+ }
+
+ boolean containsCompressedZeroes = address.contains("::");
+ if (containsCompressedZeroes && (address.indexOf("::") != address.lastIndexOf("::"))) {
+ return false;
+ }
+
+ if (((address.indexOf(':') == 0) && (!address.startsWith("::")))
+ || (address.endsWith(":") && (!address.endsWith("::")))) {
+ return false;
+ }
+
+ String[] splitOctets = GenericUtils.split(address, ':');
+ List<String> octetList = new ArrayList<>(Arrays.asList(splitOctets));
+ if (containsCompressedZeroes) {
+ if (address.endsWith("::")) {
+ // String.split() drops ending empty segments
+ octetList.add("");
+ } else if (address.startsWith("::") && (!octetList.isEmpty())) {
+ octetList.remove(0);
+ }
+ }
+
+ int numOctests = octetList.size();
+ if (numOctests > IPV6_MAX_HEX_GROUPS) {
+ return false;
+ }
+
+ int validOctets = 0;
+ int emptyOctets = 0; // consecutive empty chunks
+ for (int index = 0; index < numOctests; index++) {
+ String octet = octetList.get(index);
+ int pos = octet.indexOf('%'); // is it a zone index
+ if (pos >= 0) {
+ // zone index must come last
+ if (index != (numOctests - 1)) {
+ return false;
+ }
+
+ octet = (pos > 0) ? octet.substring(0, pos) : "";
+ }
+
+ int octetLength = octet.length();
+ if (octetLength == 0) {
+ emptyOctets++;
+ if (emptyOctets > 1) {
+ return false;
+ }
+
+ validOctets++;
+ continue;
+ }
+
+ emptyOctets = 0;
+
+ // Is last chunk an IPv4 address?
+ if ((index == (numOctests - 1)) && (octet.indexOf('.') > 0)) {
+ if (!isIPv4Address(octet)) {
+ return false;
+ }
+ validOctets += 2;
+ continue;
+ }
+
+ if (octetLength > IPV6_MAX_HEX_DIGITS_PER_GROUP) {
+ return false;
+ }
+
+ int octetInt = 0;
+ try {
+ octetInt = Integer.parseInt(octet, 16);
+ } catch (NumberFormatException e) {
+ return false;
+ }
+
+ if ((octetInt < 0) || (octetInt > 0x000ffff)) {
+ return false;
+ }
+
+ validOctets++;
+ }
+
+ if ((validOctets > IPV6_MAX_HEX_GROUPS)
+ || ((validOctets < IPV6_MAX_HEX_GROUPS) && (!containsCompressedZeroes))) {
+ return false;
+ }
+ return true;
+ }
+}