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 &quot;pure&quot; 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 &quot;discriminator&quot; 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 &quot;empty&quot; {@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 &quot;:&quot;) 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 &quot;localhost&quot; or
+     * &quot;127.x.x.x&quot;.
+     */
+    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 &le; 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;
+    }
+}