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:49 UTC

[39/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/SelectorUtils.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/SelectorUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/SelectorUtils.java
new file mode 100644
index 0000000..53bca90
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/SelectorUtils.java
@@ -0,0 +1,805 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.nio.file.FileSystem;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.StringTokenizer;
+
+/**
+ * <p>This is a utility class used by selectors and DirectoryScanner. The
+ * functionality more properly belongs just to selectors, but unfortunately
+ * DirectoryScanner exposed these as protected methods. Thus we have to
+ * support any subclasses of DirectoryScanner that may access these methods.
+ * </p>
+ * <p>This is a Singleton.</p>
+ *
+ * @author Arnout J. Kuiper
+ *         <a href="mailto:ajkuiper@wxs.nl">ajkuiper@wxs.nl</a>
+ * @author Magesh Umasankar
+ * @author <a href="mailto:bruce@callenish.com">Bruce Atherton</a>
+ * @version $Id$
+ * @since 1.5
+ */
+public final class SelectorUtils {
+
+    public static final String PATTERN_HANDLER_PREFIX = "[";
+
+    public static final String PATTERN_HANDLER_SUFFIX = "]";
+
+    public static final String REGEX_HANDLER_PREFIX = "%regex" + PATTERN_HANDLER_PREFIX;
+
+    public static final String ANT_HANDLER_PREFIX = "%ant" + PATTERN_HANDLER_PREFIX;
+
+    /**
+     * Private Constructor
+     */
+    private SelectorUtils() {
+        throw new UnsupportedOperationException("No instance allowed");
+    }
+
+    /**
+     * <p>Tests whether or not a given path matches the start of a given
+     * pattern up to the first "**".</p>
+     *
+     * <p>This is not a general purpose test and should only be used if you
+     * can live with false positives. For example, <code>pattern=**\a</code>
+     * and <code>str=b</code> will yield <code>true</code>.</p>
+     *
+     * @param pattern The pattern to match against. Must not be
+     *                {@code null}.
+     * @param str     The path to match, as a String. Must not be
+     *                {@code null}.
+     * @return whether or not a given path matches the start of a given
+     * pattern up to the first "**".
+     */
+    public static boolean matchPatternStart(String pattern, String str) {
+        return matchPatternStart(pattern, str, true);
+    }
+
+    /**
+     * <p>Tests whether or not a given path matches the start of a given
+     * pattern up to the first "**".</p>
+     *
+     * <p>This is not a general purpose test and should only be used if you
+     * can live with false positives. For example, <code>pattern=**\a</code>
+     * and <code>str=b</code> will yield <code>true</code>.</p>
+     *
+     * @param pattern         The pattern to match against. Must not be
+     *                        {@code null}.
+     * @param str             The path to match, as a String. Must not be
+     *                        {@code null}.
+     * @param isCaseSensitive Whether or not matching should be performed
+     *                        case sensitively.
+     * @return whether or not a given path matches the start of a given
+     * pattern up to the first "**".
+     */
+    public static boolean matchPatternStart(String pattern, String str,
+                                            boolean isCaseSensitive) {
+        if (pattern.length() > (REGEX_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1)
+                && pattern.startsWith(REGEX_HANDLER_PREFIX) && pattern.endsWith(PATTERN_HANDLER_SUFFIX)) {
+            // FIXME: ICK! But we can't do partial matches for regex, so we have to reserve judgement until we have
+            // a file to deal with, or we can definitely say this is an exclusion...
+            return true;
+        } else {
+            if (pattern.length() > (ANT_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1)
+                    && pattern.startsWith(ANT_HANDLER_PREFIX) && pattern.endsWith(PATTERN_HANDLER_SUFFIX)) {
+                pattern =
+                        pattern.substring(ANT_HANDLER_PREFIX.length(), pattern.length() - PATTERN_HANDLER_SUFFIX.length());
+            }
+
+            String altStr = str.replace('\\', '/');
+
+            return matchAntPathPatternStart(pattern, str, File.separator, isCaseSensitive)
+                    || matchAntPathPatternStart(pattern, altStr, "/", isCaseSensitive);
+        }
+    }
+
+    private static boolean matchAntPathPatternStart(String pattern, String str, String separator, boolean isCaseSensitive) {
+        // When str starts with a File.separator, pattern has to start with a
+        // File.separator.
+        // When pattern starts with a File.separator, str has to start with a
+        // File.separator.
+        if (str.startsWith(separator) != pattern.startsWith(separator)) {
+            return false;
+        }
+
+        List<String> patDirs = tokenizePath(pattern, separator);
+        List<String> strDirs = tokenizePath(str, separator);
+
+        int patIdxStart = 0;
+        int patIdxEnd = patDirs.size() - 1;
+        int strIdxStart = 0;
+        int strIdxEnd = strDirs.size() - 1;
+
+        // up to first '**'
+        while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
+            String patDir = patDirs.get(patIdxStart);
+            if (patDir.equals("**")) {
+                break;
+            }
+            if (!match(patDir, strDirs.get(strIdxStart),
+                    isCaseSensitive)) {
+                return false;
+            }
+            patIdxStart++;
+            strIdxStart++;
+        }
+
+        // CHECKSTYLE:OFF
+        if (strIdxStart > strIdxEnd) {
+            // String is exhausted
+            return true;
+        } else {
+            return patIdxStart <= patIdxEnd;
+        }
+        // CHECKSTYLE:ON
+    }
+
+    /**
+     * Tests whether or not a given path matches a given pattern.
+     *
+     * @param pattern The pattern to match against. Must not be
+     *                {@code null}.
+     * @param str     The path to match, as a String. Must not be
+     *                {@code null}.
+     * @return <code>true</code> if the pattern matches against the string,
+     * or <code>false</code> otherwise.
+     */
+    public static boolean matchPath(String pattern, String str) {
+        return matchPath(pattern, str, true);
+    }
+
+    /**
+     * Tests whether or not a given path matches a given pattern.
+     *
+     * @param pattern         The pattern to match against. Must not be
+     *                        {@code null}.
+     * @param str             The path to match, as a String. Must not be
+     *                        {@code null}.
+     * @param isCaseSensitive Whether or not matching should be performed
+     *                        case sensitively.
+     * @return <code>true</code> if the pattern matches against the string,
+     * or <code>false</code> otherwise.
+     */
+    public static boolean matchPath(String pattern, String str, boolean isCaseSensitive) {
+        if (pattern.length() > (REGEX_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1)
+                && pattern.startsWith(REGEX_HANDLER_PREFIX) && pattern.endsWith(PATTERN_HANDLER_SUFFIX)) {
+            pattern = pattern.substring(REGEX_HANDLER_PREFIX.length(), pattern.length()
+                    - PATTERN_HANDLER_SUFFIX.length());
+
+            return str.matches(pattern);
+        } else {
+            if (pattern.length() > (ANT_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1)
+                    && pattern.startsWith(ANT_HANDLER_PREFIX) && pattern.endsWith(PATTERN_HANDLER_SUFFIX)) {
+                pattern =
+                        pattern.substring(ANT_HANDLER_PREFIX.length(), pattern.length() - PATTERN_HANDLER_SUFFIX.length());
+            }
+
+            return matchAntPathPattern(pattern, str, isCaseSensitive);
+        }
+    }
+
+    private static boolean matchAntPathPattern(String pattern, String str, boolean isCaseSensitive) {
+        // When str starts with a File.separator, pattern has to start with a
+        // File.separator.
+        // When pattern starts with a File.separator, str has to start with a
+        // File.separator.
+        if (str.startsWith(File.separator) != pattern.startsWith(File.separator)) {
+            return false;
+        }
+
+        List<String> patDirs = tokenizePath(pattern, File.separator);
+        List<String> strDirs = tokenizePath(str, File.separator);
+
+        int patIdxStart = 0;
+        int patIdxEnd = patDirs.size() - 1;
+        int strIdxStart = 0;
+        int strIdxEnd = strDirs.size() - 1;
+
+        // up to first '**'
+        while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
+            String patDir = patDirs.get(patIdxStart);
+            if (patDir.equals("**")) {
+                break;
+            }
+            if (!match(patDir, strDirs.get(strIdxStart),
+                    isCaseSensitive)) {
+                patDirs = null;
+                strDirs = null;
+                return false;
+            }
+            patIdxStart++;
+            strIdxStart++;
+        }
+        if (strIdxStart > strIdxEnd) {
+            // String is exhausted
+            for (int i = patIdxStart; i <= patIdxEnd; i++) {
+                if (!patDirs.get(i).equals("**")) {
+                    patDirs = null;
+                    strDirs = null;
+                    return false;
+                }
+            }
+            return true;
+        } else {
+            if (patIdxStart > patIdxEnd) {
+                // String not exhausted, but pattern is. Failure.
+                patDirs = null;
+                strDirs = null;
+                return false;
+            }
+        }
+
+        // up to last '**'
+        while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
+            String patDir = patDirs.get(patIdxEnd);
+            if (patDir.equals("**")) {
+                break;
+            }
+            if (!match(patDir, strDirs.get(strIdxEnd),
+                    isCaseSensitive)) {
+                patDirs = null;
+                strDirs = null;
+                return false;
+            }
+            patIdxEnd--;
+            strIdxEnd--;
+        }
+        if (strIdxStart > strIdxEnd) {
+            // String is exhausted
+            for (int i = patIdxStart; i <= patIdxEnd; i++) {
+                if (!patDirs.get(i).equals("**")) {
+                    patDirs = null;
+                    strDirs = null;
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
+            int patIdxTmp = -1;
+            for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
+                if (patDirs.get(i).equals("**")) {
+                    patIdxTmp = i;
+                    break;
+                }
+            }
+            if (patIdxTmp == patIdxStart + 1) {
+                // '**/**' situation, so skip one
+                patIdxStart++;
+                continue;
+            }
+            // Find the pattern between padIdxStart & padIdxTmp in str between
+            // strIdxStart & strIdxEnd
+            int patLength = patIdxTmp - patIdxStart - 1;
+            int strLength = strIdxEnd - strIdxStart + 1;
+            int foundIdx = -1;
+            strLoop:
+            for (int i = 0; i <= strLength - patLength; i++) {
+                for (int j = 0; j < patLength; j++) {
+                    String subPat = patDirs.get(patIdxStart + j + 1);
+                    String subStr = strDirs.get(strIdxStart + i + j);
+                    if (!match(subPat, subStr, isCaseSensitive)) {
+                        continue strLoop;
+                    }
+                }
+
+                foundIdx = strIdxStart + i;
+                break;
+            }
+
+            if (foundIdx == -1) {
+                patDirs = null;
+                strDirs = null;
+                return false;
+            }
+
+            patIdxStart = patIdxTmp;
+            strIdxStart = foundIdx + patLength;
+        }
+
+        for (int i = patIdxStart; i <= patIdxEnd; i++) {
+            if (!patDirs.get(i).equals("**")) {
+                patDirs = null;
+                strDirs = null;
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Tests whether or not a string matches against a pattern.
+     * The pattern may contain two special characters:<br>
+     * '*' means zero or more characters<br>
+     * '?' means one and only one character
+     *
+     * @param pattern The pattern to match against.
+     *                Must not be {@code null}.
+     * @param str     The string which must be matched against the pattern.
+     *                Must not be {@code null}.
+     * @return <code>true</code> if the string matches against the pattern,
+     * or <code>false</code> otherwise.
+     */
+    public static boolean match(String pattern, String str) {
+        return match(pattern, str, true);
+    }
+
+    /**
+     * Tests whether or not a string matches against a pattern.
+     * The pattern may contain two special characters:<br>
+     * '*' means zero or more characters<br>
+     * '?' means one and only one character
+     *
+     * @param pattern         The pattern to match against.
+     *                        Must not be {@code null}.
+     * @param str             The string which must be matched against the pattern.
+     *                        Must not be {@code null}.
+     * @param isCaseSensitive Whether or not matching should be performed
+     *                        case sensitively.
+     * @return <code>true</code> if the string matches against the pattern,
+     * or <code>false</code> otherwise.
+     */
+    @SuppressWarnings("PMD.AssignmentInOperand")
+    public static boolean match(String pattern, String str, boolean isCaseSensitive) {
+        char[] patArr = pattern.toCharArray();
+        char[] strArr = str.toCharArray();
+        int patIdxStart = 0;
+        int patIdxEnd = patArr.length - 1;
+        int strIdxStart = 0;
+        int strIdxEnd = strArr.length - 1;
+        char ch;
+
+        boolean containsStar = false;
+        for (char aPatArr : patArr) {
+            if (aPatArr == '*') {
+                containsStar = true;
+                break;
+            }
+        }
+
+        if (!containsStar) {
+            // No '*'s, so we make a shortcut
+            if (patIdxEnd != strIdxEnd) {
+                return false; // Pattern and string do not have the same size
+            }
+            for (int i = 0; i <= patIdxEnd; i++) {
+                ch = patArr[i];
+                if ((ch != '?') && (!equals(ch, strArr[i], isCaseSensitive))) {
+                    return false; // Character mismatch
+                }
+            }
+            return true; // String matches against pattern
+        }
+
+        if (patIdxEnd == 0) {
+            return true; // Pattern contains only '*', which matches anything
+        }
+
+        // Process characters before first star
+        // CHECKSTYLE:OFF
+        while (((ch = patArr[patIdxStart]) != '*') && (strIdxStart <= strIdxEnd)) {
+            if ((ch != '?') && (!equals(ch, strArr[strIdxStart], isCaseSensitive))) {
+                return false; // Character mismatch
+            }
+            patIdxStart++;
+            strIdxStart++;
+        }
+        // CHECKSTYLE:ON
+
+        if (strIdxStart > strIdxEnd) {
+            // All characters in the string are used. Check if only '*'s are
+            // left in the pattern. If so, we succeeded. Otherwise failure.
+            for (int i = patIdxStart; i <= patIdxEnd; i++) {
+                if (patArr[i] != '*') {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        // Process characters after last star
+        // CHECKSTYLE:OFF
+        while (((ch = patArr[patIdxEnd]) != '*') && (strIdxStart <= strIdxEnd)) {
+            if ((ch != '?') && (!equals(ch, strArr[strIdxEnd], isCaseSensitive))) {
+                return false; // Character mismatch
+            }
+            patIdxEnd--;
+            strIdxEnd--;
+        }
+        // CHECKSTYLE:ON
+
+        if (strIdxStart > strIdxEnd) {
+            // All characters in the string are used. Check if only '*'s are
+            // left in the pattern. If so, we succeeded. Otherwise failure.
+            for (int i = patIdxStart; i <= patIdxEnd; i++) {
+                if (patArr[i] != '*') {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        // process pattern between stars. padIdxStart and patIdxEnd point always to a '*'.
+        while ((patIdxStart != patIdxEnd) && (strIdxStart <= strIdxEnd)) {
+            int patIdxTmp = -1;
+            for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
+                if (patArr[i] == '*') {
+                    patIdxTmp = i;
+                    break;
+                }
+            }
+            if (patIdxTmp == patIdxStart + 1) {
+                // Two stars next to each other, skip the first one.
+                patIdxStart++;
+                continue;
+            }
+            // Find the pattern between padIdxStart & padIdxTmp in str between
+            // strIdxStart & strIdxEnd
+            int patLength = patIdxTmp - patIdxStart - 1;
+            int strLength = strIdxEnd - strIdxStart + 1;
+            int foundIdx = -1;
+            strLoop:
+            for (int i = 0; i <= strLength - patLength; i++) {
+                for (int j = 0; j < patLength; j++) {
+                    ch = patArr[patIdxStart + j + 1];
+                    if (ch != '?' && !equals(ch, strArr[strIdxStart + i + j], isCaseSensitive)) {
+                        continue strLoop;
+                    }
+                }
+
+                foundIdx = strIdxStart + i;
+                break;
+            }
+
+            if (foundIdx == -1) {
+                return false;
+            }
+
+            patIdxStart = patIdxTmp;
+            strIdxStart = foundIdx + patLength;
+        }
+
+        // All characters in the string are used. Check if only '*'s are left
+        // in the pattern. If so, we succeeded. Otherwise failure.
+        for (int i = patIdxStart; i <= patIdxEnd; i++) {
+            if (patArr[i] != '*') {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Tests whether two characters are equal.
+     * @param c1 1st character
+     * @param c2 2nd character
+     * @param isCaseSensitive Whether to compare case sensitive
+     * @return {@code true} if equal characters
+     */
+    public static boolean equals(char c1, char c2, boolean isCaseSensitive) {
+        if (c1 == c2) {
+            return true;
+        }
+        if (!isCaseSensitive) {
+            // NOTE: Try both upper case and lower case as done by String.equalsIgnoreCase()
+            if (Character.toUpperCase(c1) == Character.toUpperCase(c2)
+                    || Character.toLowerCase(c1) == Character.toLowerCase(c2)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Breaks a path up into a Vector of path elements, tokenizing on
+     * <code>File.separator</code>.
+     *
+     * @param path Path to tokenize. Must not be {@code null}.
+     * @return a List of path elements from the tokenized path
+     */
+    public static List<String> tokenizePath(String path) {
+        return tokenizePath(path, File.separator);
+    }
+
+    public static List<String> tokenizePath(String path, String separator) {
+        List<String> ret = new ArrayList<>();
+        StringTokenizer st = new StringTokenizer(path, separator);
+        while (st.hasMoreTokens()) {
+            ret.add(st.nextToken());
+        }
+        return ret;
+    }
+
+    /**   /**
+     * Converts a path to one matching the target file system by applying the
+     * &quot;slashification&quot; rules, converting it to a local path and
+     * then translating its separator to the target file system one (if different
+     * than local one)
+     * @param path          The input path
+     * @param pathSeparator The separator used to build the input path
+     * @param fs            The target {@link FileSystem} - may not be {@code null}
+     * @return The transformed path
+     * @see #translateToLocalFileSystemPath(String, char, String)
+     */
+    public static String translateToLocalFileSystemPath(String path, char pathSeparator, FileSystem fs) {
+        return translateToLocalFileSystemPath(path, pathSeparator,  Objects.requireNonNull(fs, "No target file system").getSeparator());
+    }
+
+    /**
+     * Converts a path to one matching the target file system by applying the
+     * &quot;slashification&quot; rules, converting it to a local path and
+     * then translating its separator to the target file system one (if different
+     * than local one)
+     * @param path          The input path
+     * @param pathSeparator The separator used to build the input path
+     * @param fsSeparator   The target file system separator
+     * @return The transformed path
+     * @see #applySlashifyRules(String, char)
+     * @see #translateToLocalPath(String)
+     * @see #translateToFileSystemPath(String, String, String)
+     */
+    public static String translateToLocalFileSystemPath(String path, char pathSeparator, String fsSeparator) {
+        // In case double slashes and other patterns are used
+        String slashified = applySlashifyRules(path, pathSeparator);
+        // In case we are running on Windows
+        String localPath = translateToLocalPath(slashified);
+        return translateToFileSystemPath(localPath, File.separator, fsSeparator);
+    }
+
+    /**
+     * Applies the &quot;slashification&quot; rules as specified in
+     * <A HREF="http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap03.html#tag_03_266">Single Unix Specification version 3, section 3.266</A>
+     * and <A HREF="http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11">section 4.11 - Pathname resolution</A>
+     * @param path The original path - ignored if {@code null}/empty or does
+     * not contain any slashes
+     * @param sepChar The &quot;slash&quot; character
+     * @return The effective path - may be same as input if no changes required
+     */
+    public static String applySlashifyRules(String path, char sepChar) {
+        if (GenericUtils.isEmpty(path)) {
+            return path;
+        }
+
+        int curPos = path.indexOf(sepChar);
+        if (curPos < 0) {
+            return path;    // no slashes to handle
+        }
+
+        int lastPos = 0;
+        StringBuilder sb = null;
+        while (curPos < path.length()) {
+            curPos++;   // skip the 1st '/'
+
+            /*
+             * As per Single Unix Specification version 3, section 3.266:
+             *
+             *      Multiple successive slashes are considered to be the
+             *      same as one slash
+             */
+            int nextPos = curPos;
+            while ((nextPos < path.length()) && (path.charAt(nextPos) == sepChar)) {
+                nextPos++;
+            }
+
+            /*
+             * At this stage, nextPos is the first non-slash character after a
+             * possibly 'seqLen' sequence of consecutive slashes.
+             */
+            int seqLen = nextPos - curPos;
+            if (seqLen > 0) {
+                if (sb == null) {
+                    sb = new StringBuilder(path.length() - seqLen);
+                }
+
+                if (lastPos < curPos) {
+                    String clrText = path.substring(lastPos, curPos);
+                    sb.append(clrText);
+                }
+
+                lastPos = nextPos;
+            }
+
+            if (nextPos >= path.length()) {
+                break;  // no more data
+            }
+
+            curPos = path.indexOf(sepChar, nextPos);
+            if (curPos < nextPos) {
+                break;  // no more slashes
+            }
+        }
+
+        // check if any leftovers for the modified path
+        if (sb != null) {
+            if (lastPos < path.length()) {
+                String clrText = path.substring(lastPos);
+                sb.append(clrText);
+            }
+
+            path = sb.toString();
+        }
+
+        /*
+         * At this point we know for sure that 'path' contains only SINGLE
+         * slashes. According to section 4.11 - Pathname resolution
+         *
+         *      A pathname that contains at least one non-slash character
+         *      and that ends with one or more trailing slashes shall be
+         *      resolved as if a single dot character ( '.' ) were appended
+         *      to the pathname.
+         */
+        if ((path.length() > 1) && (path.charAt(path.length() - 1) == sepChar)) {
+            return path + ".";
+        } else {
+            return path;
+        }
+    }
+
+    /**
+     * Converts a possibly '/' separated path to a local path. <B>Note:</B>
+     * takes special care of Windows drive paths - e.g., {@code C:}
+     * by converting them to &quot;C:\&quot;
+     *
+     * @param path The original path - ignored if {@code null}/empty
+     * @return The local path
+     */
+    public static String translateToLocalPath(String path) {
+        if (GenericUtils.isEmpty(path) || (File.separatorChar == '/')) {
+            return path;
+        }
+
+        // This code is reached if we are running on Windows
+        String localPath = path.replace('/', File.separatorChar);
+        // check if '/c:' prefix
+        if ((localPath.charAt(0) == File.separatorChar) && isWindowsDriveSpecified(localPath, 1, localPath.length() - 1)) {
+            localPath = localPath.substring(1);
+        }
+        if (!isWindowsDriveSpecified(localPath)) {
+            return localPath;   // assume a relative path
+        }
+
+        /*
+         * Here we know that we have at least a "C:" string - make sure it
+         * is followed by the local file separator. Note: if all we have is
+         * just the drive, we will create a "C:\" path since this is the
+         * preferred Windows way to refer to root drives in the file system
+         */
+        if (localPath.length() == 2) {
+            return localPath + File.separator;  // all we have is "C:"
+        } else if (localPath.charAt(2) != File.separatorChar) {
+            // be nice and add the missing file separator - C:foo => C:\foo
+            return localPath.substring(0, 2) + File.separator + localPath.substring(2);
+        } else {
+            return localPath;
+        }
+    }
+
+    public static boolean isWindowsDriveSpecified(CharSequence cs) {
+        return isWindowsDriveSpecified(cs, 0, GenericUtils.length(cs));
+    }
+
+    public static boolean isWindowsDriveSpecified(CharSequence cs, int offset, int len) {
+        if ((len < 2) || (cs.charAt(offset + 1) != ':')) {
+            return false;
+        }
+
+        char drive = cs.charAt(offset);
+        return ((drive >= 'a') && (drive <= 'z')) || ((drive >= 'A') && (drive <= 'Z'));
+    }
+
+    /**
+     * Converts a path containing a specific separator to one using the
+     * specified file-system one
+     * @param path          The input path - ignored if {@code null}/empty
+     * @param pathSeparator The separator used to build the input path - may not
+     *                      be {@code null}/empty
+     * @param fs            The target {@link FileSystem} - may not be {@code null}
+     * @return The path where the separator used to build it is replaced by
+     * the file-system one (if different)
+     * @see FileSystem#getSeparator()
+     * @see #translateToFileSystemPath(String, String, String)
+     */
+    public static String translateToFileSystemPath(String path, String pathSeparator, FileSystem fs) {
+        return translateToFileSystemPath(path, pathSeparator, Objects.requireNonNull(fs, "No target file system").getSeparator());
+    }
+
+    /**
+     * Converts a path containing a specific separator to one using the
+     * specified file-system one
+     * @param path          The input path - ignored if {@code null}/empty
+     * @param pathSeparator The separator used to build the input path - may not
+     *                      be {@code null}/empty
+     * @param fsSeparator   The target file system separator - may not be {@code null}/empty
+     * @return The path where the separator used to build it is replaced by
+     * the file-system one (if different)
+     * @throws IllegalArgumentException if path or file-system separator are {@code null}/empty
+     * or if the separators are different and the path contains the target
+     * file-system separator as it would create an ambiguity
+     */
+    public static String translateToFileSystemPath(String path, String pathSeparator, String fsSeparator) {
+        ValidateUtils.checkNotNullAndNotEmpty(pathSeparator, "Missing path separator");
+        ValidateUtils.checkNotNullAndNotEmpty(fsSeparator, "Missing file-system separator");
+
+        if (GenericUtils.isEmpty(path) || Objects.equals(pathSeparator, fsSeparator)) {
+            return path;
+        }
+
+        // make sure path does not contain the target separator
+        if (path.contains(fsSeparator)) {
+            ValidateUtils.throwIllegalArgumentException("File system replacement may yield ambiguous result for %s with separator=%s", path, fsSeparator);
+        }
+
+        // check most likely case
+        if ((pathSeparator.length() == 1) && (fsSeparator.length() == 1)) {
+            return path.replace(pathSeparator.charAt(0), fsSeparator.charAt(0));
+        } else {
+            return path.replace(pathSeparator, fsSeparator);
+        }
+    }
+
+    /**
+     * Returns dependency information on these two files. If src has been
+     * modified later than target, it returns true. If target doesn't exist,
+     * it likewise returns true. Otherwise, target is newer than src and
+     * is not out of date, thus the method returns false. It also returns
+     * false if the src file doesn't even exist, since how could the
+     * target then be out of date.
+     *
+     * @param src         the original file
+     * @param target      the file being compared against
+     * @param granularity the amount in seconds of slack we will give in
+     *                    determining out of dateness
+     * @return whether the target is out of date
+     */
+    public static boolean isOutOfDate(File src, File target, int granularity) {
+        if (!src.exists()) {
+            return false;
+        }
+        if (!target.exists()) {
+            return true;
+        }
+        return (src.lastModified() - granularity) > target.lastModified();
+    }
+
+    /**
+     * "Flattens" a string by removing all whitespace (space, tab, linefeed,
+     * carriage return, and formfeed). This uses StringTokenizer and the
+     * default set of tokens as documented in the single arguement constructor.
+     *
+     * @param input a String to remove all whitespace.
+     * @return a String that has had all whitespace removed.
+     */
+    public static String removeWhitespace(String input) {
+        StringBuilder result = new StringBuilder();
+        if (input != null) {
+            StringTokenizer st = new StringTokenizer(input);
+            while (st.hasMoreTokens()) {
+                result.append(st.nextToken());
+            }
+        }
+        return result.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/SshdEventListener.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/SshdEventListener.java b/sshd-common/src/main/java/org/apache/sshd/common/util/SshdEventListener.java
new file mode 100644
index 0000000..ea6a3dd
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/SshdEventListener.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+import java.lang.reflect.Proxy;
+import java.util.EventListener;
+import java.util.Objects;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SshdEventListener extends EventListener {
+
+    /**
+     * Makes sure that the listener is neither {@code null} nor a proxy
+     *
+     * @param <L> Type of {@link SshdEventListener} being validation
+     * @param listener The listener instance
+     * @param prefix Prefix text to be prepended to validation failure messages
+     * @return The validated instance
+     */
+    static <L extends SshdEventListener> L validateListener(L listener, String prefix) {
+        Objects.requireNonNull(listener, prefix + ": no instance");
+        ValidateUtils.checkTrue(!Proxy.isProxyClass(listener.getClass()), prefix + ": proxies N/A");
+        return listener;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/ValidateUtils.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/ValidateUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/ValidateUtils.java
new file mode 100644
index 0000000..a55e5d6
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/ValidateUtils.java
@@ -0,0 +1,216 @@
+/*
+ * 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;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class ValidateUtils {
+    private ValidateUtils() {
+        throw new UnsupportedOperationException("No instance");
+    }
+
+    public static <T> T checkNotNull(T t, String message) {
+        checkTrue(t != null, message);
+        return t;
+    }
+
+    public static <T> T checkNotNull(T t, String message, Object arg) {
+        checkTrue(t != null, message, arg);
+        return t;
+    }
+
+    public static <T> T checkNotNull(T t, String message, long value) {
+        checkTrue(t != null, message, value);
+        return t;
+    }
+
+    public static <T> T checkNotNull(T t, String message, Object... args) {
+        checkTrue(t != null, message, args);
+        return t;
+    }
+
+    public static String checkNotNullAndNotEmpty(String t, String message) {
+        t = checkNotNull(t, message).trim();
+        checkTrue(GenericUtils.length(t) > 0, message);
+        return t;
+    }
+
+    public static String checkNotNullAndNotEmpty(String t, String message, Object arg) {
+        t = checkNotNull(t, message, arg).trim();
+        checkTrue(GenericUtils.length(t) > 0, message, arg);
+        return t;
+    }
+
+    public static String checkNotNullAndNotEmpty(String t, String message, Object... args) {
+        t = checkNotNull(t, message, args).trim();
+        checkTrue(GenericUtils.length(t) > 0, message, args);
+        return t;
+    }
+
+    public static <K, V, M extends Map<K, V>> M checkNotNullAndNotEmpty(M t, String message, Object... args) {
+        t = checkNotNull(t, message, args);
+        checkTrue(GenericUtils.size(t) > 0, message, args);
+        return t;
+    }
+
+    public static <T, C extends Collection<T>> C checkNotNullAndNotEmpty(C t, String message, Object... args) {
+        t = checkNotNull(t, message, args);
+        checkTrue(GenericUtils.size(t) > 0, message, args);
+        return t;
+    }
+
+    public static <T, C extends Iterable<T>> C checkNotNullAndNotEmpty(C t, String message, Object... args) {
+        t = checkNotNull(t, message, args);
+        checkTrue(GenericUtils.isNotEmpty(t), message, args);
+        return t;
+    }
+
+    public static byte[] checkNotNullAndNotEmpty(byte[] a, String message) {
+        a = checkNotNull(a, message);
+        checkTrue(NumberUtils.length(a) > 0, message);
+        return a;
+    }
+
+    public static byte[] checkNotNullAndNotEmpty(byte[] a, String message, Object... args) {
+        a = checkNotNull(a, message, args);
+        checkTrue(NumberUtils.length(a) > 0, message, args);
+        return a;
+    }
+
+    public static char[] checkNotNullAndNotEmpty(char[] a, String message) {
+        a = checkNotNull(a, message);
+        checkTrue(GenericUtils.length(a) > 0, message);
+        return a;
+    }
+
+    public static char[] checkNotNullAndNotEmpty(char[] a, String message, Object... args) {
+        a = checkNotNull(a, message, args);
+        checkTrue(GenericUtils.length(a) > 0, message, args);
+        return a;
+    }
+
+    public static int[] checkNotNullAndNotEmpty(int[] a, String message) {
+        a = checkNotNull(a, message);
+        checkTrue(NumberUtils.length(a) > 0, message);
+        return a;
+    }
+
+    public static int[] checkNotNullAndNotEmpty(int[] a, String message, Object... args) {
+        a = checkNotNull(a, message, args);
+        checkTrue(NumberUtils.length(a) > 0, message, args);
+        return a;
+    }
+
+    public static <T> T[] checkNotNullAndNotEmpty(T[] t, String message, Object... args) {
+        t = checkNotNull(t, message, args);
+        checkTrue(GenericUtils.length(t) > 0, message, args);
+        return t;
+    }
+
+    public static <T> T checkInstanceOf(Object v, Class<T> expected, String message, long value) {
+        Class<?> actual = checkNotNull(v, message, value).getClass();
+        checkTrue(expected.isAssignableFrom(actual), message, value);
+        return expected.cast(v);
+    }
+
+    public static <T> T checkInstanceOf(Object v, Class<T> expected, String message) {
+        return checkInstanceOf(v, expected, message, GenericUtils.EMPTY_OBJECT_ARRAY);
+    }
+
+    public static <T> T checkInstanceOf(Object v, Class<T> expected, String message, Object arg) {
+        Class<?> actual = checkNotNull(v, message, arg).getClass();
+        checkTrue(expected.isAssignableFrom(actual), message, arg);
+        return expected.cast(v);
+    }
+
+    public static <T> T checkInstanceOf(Object v, Class<T> expected, String message, Object... args) {
+        Class<?> actual = checkNotNull(v, message, args).getClass();
+        checkTrue(expected.isAssignableFrom(actual), message, args);
+        return expected.cast(v);
+    }
+
+    public static void checkTrue(boolean flag, String message) {
+        if (!flag) {
+            throwIllegalArgumentException(message, GenericUtils.EMPTY_OBJECT_ARRAY);
+        }
+    }
+
+    public static void checkTrue(boolean flag, String message, long value) {
+        if (!flag) {
+            throwIllegalArgumentException(message, value);
+        }
+    }
+
+    public static void checkTrue(boolean flag, String message, Object arg) {
+        if (!flag) {
+            throwIllegalArgumentException(message, arg);
+        }
+    }
+
+    public static void checkTrue(boolean flag, String message, Object... args) {
+        if (!flag) {
+            throwIllegalArgumentException(message, args);
+        }
+    }
+
+    public static void throwIllegalArgumentException(String format, Object... args) {
+        throw createFormattedException(IllegalArgumentException::new, format, args);
+    }
+
+    public static void checkState(boolean flag, String message) {
+        if (!flag) {
+            throwIllegalStateException(message, GenericUtils.EMPTY_OBJECT_ARRAY);
+        }
+    }
+
+    public static void checkState(boolean flag, String message, long value) {
+        if (!flag) {
+            throwIllegalStateException(message, value);
+        }
+    }
+
+    public static void checkState(boolean flag, String message, Object arg) {
+        if (!flag) {
+            throwIllegalStateException(message, arg);
+        }
+    }
+
+    public static void checkState(boolean flag, String message, Object... args) {
+        if (!flag) {
+            throwIllegalStateException(message, args);
+        }
+    }
+
+    public static void throwIllegalStateException(String format, Object... args) {
+        throw createFormattedException(IllegalStateException::new, format, args);
+    }
+
+    public static <T extends Throwable> T createFormattedException(
+            Function<? super String, ? extends T> constructor, String format, Object... args) {
+        String message = String.format(format, args);
+        return constructor.apply(message);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/VersionInfo.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/VersionInfo.java b/sshd-common/src/main/java/org/apache/sshd/common/util/VersionInfo.java
new file mode 100644
index 0000000..ed1aab7
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/VersionInfo.java
@@ -0,0 +1,137 @@
+/*
+ * 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;
+
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class VersionInfo implements Serializable, Comparable<VersionInfo> {
+    private static final long serialVersionUID = -9127482432228413836L;
+
+    private final int majorVersion;
+    private final int minorVersion;
+    private final int release;
+    private final int buildNumber;
+
+    public VersionInfo(int major, int minor) {
+        this(major, minor, 0, 0);
+    }
+
+    public VersionInfo(int major, int minor, int release, int build) {
+        this.majorVersion = major;
+        this.minorVersion = minor;
+        this.release = release;
+        this.buildNumber = build;
+    }
+
+    public final int getMajorVersion() {
+        return majorVersion;
+    }
+
+    public final int getMinorVersion() {
+        return minorVersion;
+    }
+
+    public final int getRelease() {
+        return release;
+    }
+
+    public final int getBuildNumber() {
+        return buildNumber;
+    }
+
+    @Override
+    public int hashCode() {
+        return NumberUtils.hashCode(getMajorVersion(), getMinorVersion(), getRelease(), getBuildNumber());
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (this == obj) {
+            return true;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        return compareTo((VersionInfo) obj) == 0;
+    }
+
+    @Override
+    public int compareTo(VersionInfo o) {
+        if (o == null) {
+            return -1;  // push nulls to end
+        }
+        if (o == this) {
+            return 0;
+        }
+
+        int nRes = Integer.compare(getMajorVersion(), o.getMajorVersion());
+        if (nRes == 0) {
+            nRes = Integer.compare(getMinorVersion(), o.getMinorVersion());
+        }
+        if (nRes == 0) {
+            nRes = Integer.compare(getRelease(), o.getRelease());
+        }
+        if (nRes == 0) {
+            nRes = Integer.compare(getBuildNumber(), o.getBuildNumber());
+        }
+
+        return nRes;
+    }
+
+    @Override
+    public String toString() {
+        return NumberUtils.join('.', getMajorVersion(), getMinorVersion(), getRelease(), getBuildNumber());
+    }
+
+    /**
+     * Parses a version string - assumed to contain at most 4 non-negative
+     * components separated by a '.'. If less than 4 components are found, then
+     * the rest are assumed to be zero. If more than 4 components found, then
+     * only the 1st ones are parsed.
+     *
+     * @param version The version string - ignored if {@code null}/empty
+     * @return The parsed {@link VersionInfo} - or {@code null} if empty input
+     * @throws NumberFormatException If failed to parse any of the components
+     * @throws IllegalArgumentException If any of the parsed components is negative
+     */
+    public static VersionInfo parse(String version) throws NumberFormatException {
+        String[] comps = GenericUtils.split(version, '.');
+        if (GenericUtils.isEmpty(comps)) {
+            return null;
+        }
+
+        int[] values = new int[4];
+        int maxValues = Math.min(comps.length, values.length);
+        for (int index = 0; index < maxValues; index++) {
+            String c = comps[index];
+            int v = Integer.parseInt(c);
+            ValidateUtils.checkTrue(v >= 0, "Invalid version component in %s", version);
+            values[index] = v;
+        }
+
+        return new VersionInfo(values[0], values[1], values[2], values[3]);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java
new file mode 100644
index 0000000..dd61473
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java
@@ -0,0 +1,798 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.common.util.buffer;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.DSAPrivateKeySpec;
+import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPrivateKeySpec;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.IntUnaryOperator;
+import java.util.logging.Level;
+
+import org.apache.sshd.common.PropertyResolver;
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.NumberUtils;
+import org.apache.sshd.common.util.Readable;
+import org.apache.sshd.common.util.buffer.keys.BufferPublicKeyParser;
+import org.apache.sshd.common.util.logging.SimplifiedLog;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * Provides an abstract message buffer for encoding SSH messages
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class Buffer implements Readable {
+    protected final byte[] workBuf = new byte[Long.BYTES];
+
+    protected Buffer() {
+        super();
+    }
+
+    public abstract int rpos();
+
+    public abstract void rpos(int rpos);
+
+    public abstract int wpos();
+
+    public abstract void wpos(int wpos);
+
+    public abstract int capacity();
+
+    public abstract byte[] array();
+
+    public abstract void compact();
+
+    public byte[] getCompactData() {
+        int l = available();
+        if (l > 0) {
+            byte[] b = new byte[l];
+            System.arraycopy(array(), rpos(), b, 0, l);
+            return b;
+        } else {
+            return GenericUtils.EMPTY_BYTE_ARRAY;
+        }
+    }
+
+    public void clear() {
+        clear(true);
+    }
+
+    public abstract void clear(boolean wipeData);
+
+    public boolean isValidMessageStructure(Class<?>... fieldTypes) {
+        return isValidMessageStructure(GenericUtils.isEmpty(fieldTypes) ? Collections.emptyList() : Arrays.asList(fieldTypes));
+    }
+
+    public boolean isValidMessageStructure(Collection<Class<?>> fieldTypes) {
+        if (GenericUtils.isEmpty(fieldTypes)) {
+            return true;
+        }
+
+        int remainLen = available();
+        int readOffset = 0;
+        for (Class<?> ft : fieldTypes) {
+            if ((ft == boolean.class) || (ft == Boolean.class)
+                    || (ft == byte.class) || (ft == Byte.class)) {
+                if (remainLen < Byte.BYTES) {
+                    return false;
+                }
+
+                remainLen -= Byte.BYTES;
+                readOffset += Byte.BYTES;
+            } else if ((ft == short.class) || (ft == Short.class)) {
+                if (remainLen < Short.BYTES) {
+                    return false;
+                }
+
+                remainLen -= Short.BYTES;
+                readOffset += Short.BYTES;
+            } else if ((ft == int.class) || (ft == Integer.class)) {
+                if (remainLen < Integer.BYTES) {
+                    return false;
+                }
+
+                remainLen -= Integer.BYTES;
+                readOffset += Integer.BYTES;
+            } else if ((ft == long.class) || (ft == Long.class)) {
+                if (remainLen < Long.BYTES) {
+                    return false;
+                }
+
+                remainLen -= Long.BYTES;
+                readOffset += Long.BYTES;
+            } else if ((ft == byte[].class) || (ft == String.class)) {
+                if (remainLen < Integer.BYTES) {
+                    return false;
+                }
+
+                copyRawBytes(readOffset, workBuf, 0, Integer.BYTES);
+                remainLen -= Integer.BYTES;
+                readOffset += Integer.BYTES;
+
+                long length = BufferUtils.getUInt(workBuf, 0, Integer.BYTES);
+                if (length > remainLen) {
+                    return false;
+                }
+
+                remainLen -= (int) length;
+                readOffset += (int) length;
+            }
+        }
+
+        return true;
+    }
+
+    protected abstract void copyRawBytes(int offset, byte[] buf, int pos, int len);
+
+    public String toHex() {
+        return BufferUtils.toHex(array(), rpos(), available());
+    }
+
+    public void dumpHex(SimplifiedLog logger, String prefix, PropertyResolver resolver) {
+        dumpHex(logger, BufferUtils.DEFAULT_HEXDUMP_LEVEL, prefix, resolver);
+    }
+
+    public void dumpHex(SimplifiedLog logger, Level level, String prefix, PropertyResolver resolver) {
+        BufferUtils.dumpHex(logger, level, prefix, resolver, BufferUtils.DEFAULT_HEX_SEPARATOR, array(), rpos(), available());
+    }
+
+    /*======================
+       Read methods
+     ======================*/
+
+    public int getUByte() {
+        return getByte() & 0xFF;
+    }
+
+    public byte getByte() {
+        ensureAvailable(Byte.BYTES);
+        getRawBytes(workBuf, 0, Byte.BYTES);
+        return workBuf[0];
+    }
+
+    public short getShort() {
+        ensureAvailable(Short.BYTES);
+        getRawBytes(workBuf, 0, Short.BYTES);
+        short v = (short) ((workBuf[1] << Byte.SIZE) & 0xFF00);
+        v |= (short) (workBuf[0] & 0xF);
+        return v;
+    }
+
+    public int getInt() {
+        return (int) getUInt();
+    }
+
+    public long getUInt() {
+        ensureAvailable(Integer.BYTES);
+        getRawBytes(workBuf, 0, Integer.BYTES);
+        return BufferUtils.getUInt(workBuf, 0, Integer.BYTES);
+    }
+
+    public long getLong() {
+        ensureAvailable(Long.BYTES);
+        getRawBytes(workBuf, 0, Long.BYTES);
+        long l = ((long) workBuf[0] << 56) & 0xff00000000000000L;
+        l |= ((long) workBuf[1] << 48) & 0x00ff000000000000L;
+        l |= ((long) workBuf[2] << 40) & 0x0000ff0000000000L;
+        l |= ((long) workBuf[3] << 32) & 0x000000ff00000000L;
+        l |= ((long) workBuf[4] << 24) & 0x00000000ff000000L;
+        l |= ((long) workBuf[5] << 16) & 0x0000000000ff0000L;
+        l |= ((long) workBuf[6] << 8) & 0x000000000000ff00L;
+        l |= (workBuf[7]) & 0x00000000000000ffL;
+        return l;
+    }
+
+    @SuppressWarnings("PMD.BooleanGetMethodName")
+    public boolean getBoolean() {
+        return getByte() != 0;
+    }
+
+    public String getString() {
+        return getString(StandardCharsets.UTF_8);
+    }
+
+    /**
+     * @param usePrependedLength If {@code true} then there is a 32-bit
+     *                           value indicating the number of strings to read. Otherwise, the
+     *                           method will use a &quot;greedy&quot; reading of strings while more
+     *                           data available
+     * @return A {@link Collection} of the read strings
+     * @see #getStringList(boolean, Charset)
+     */
+    public Collection<String> getStringList(boolean usePrependedLength) {
+        return getStringList(usePrependedLength, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * @param usePrependedLength If {@code true} then there is a 32-bit
+     *                           value indicating the number of strings to read. Otherwise, the
+     *                           method will use a &quot;greedy&quot; reading of strings while more
+     *                           data available
+     * @param charset            The {@link Charset} to use for the string
+     * @return A {@link Collection} of the read strings
+     * @see #getStringList(int, Charset)
+     * @see #getAvailableStrings()
+     */
+    public Collection<String> getStringList(boolean usePrependedLength, Charset charset) {
+        if (usePrependedLength) {
+            int count = getInt();
+            return getStringList(count, charset);
+        } else {
+            return getAvailableStrings(charset);
+        }
+    }
+
+    /**
+     * @return The remaining data as a list of strings
+     * @see #getAvailableStrings(Charset)
+     */
+    public Collection<String> getAvailableStrings() {
+        return getAvailableStrings(StandardCharsets.UTF_8);
+    }
+
+    /**
+     * @param charset The {@link Charset} to use for the strings
+     * @return The remaining data as a list of strings
+     * @see #available()
+     * @see #getString(Charset)
+     */
+    public Collection<String> getAvailableStrings(Charset charset) {
+        Collection<String> list = new LinkedList<>();
+        while (available() > 0) {
+            String s = getString(charset);
+            list.add(s);
+        }
+
+        return list;
+    }
+
+    /**
+     * @param count The <U>exact</U> number of strings to read - can be zero
+     * @return A {@link List} with the specified number of strings
+     * @see #getStringList(int, Charset)
+     */
+    public List<String> getStringList(int count) {
+        return getStringList(count, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * @param count   The <U>exact</U> number of strings to read - can be zero
+     * @param charset The {@link Charset} of the strings
+     * @return A {@link List} with the specified number of strings
+     * @see #getString(Charset)
+     */
+    public List<String> getStringList(int count, Charset charset) {
+        if (count == 0) {
+            return Collections.emptyList();
+        }
+
+        List<String> list = new ArrayList<>(count);
+        for (int index = 0; index < count; index++) {
+            String s = getString(charset);
+            list.add(s);
+        }
+
+        return list;
+    }
+
+    public abstract String getString(Charset charset);
+
+    public BigInteger getMPInt() {
+        return new BigInteger(getMPIntAsBytes());
+    }
+
+    public byte[] getMPIntAsBytes() {
+        return getBytes();
+    }
+
+    public byte[] getBytes() {
+        int len = getInt();
+        if (len < 0) {
+            throw new BufferException("Bad item length: " + len);
+        }
+        ensureAvailable(len);
+        byte[] b = new byte[len];
+        getRawBytes(b);
+        return b;
+    }
+
+    public void getRawBytes(byte[] buf) {
+        getRawBytes(buf, 0, buf.length);
+    }
+
+    public PublicKey getPublicKey() throws SshException {
+        return getPublicKey(BufferPublicKeyParser.DEFAULT);
+    }
+
+    /**
+     * @param parser A {@link BufferPublicKeyParser} to extract the key from the buffer
+     * - never {@code null}
+     * @return The extracted {@link PublicKey} - may be {@code null} if the parser so decided
+     * @throws SshException If failed to extract the key
+     * @see #getRawPublicKey(BufferPublicKeyParser)
+     */
+    public PublicKey getPublicKey(BufferPublicKeyParser<? extends PublicKey> parser) throws SshException {
+        int ow = wpos();
+        int len = getInt();
+        wpos(rpos() + len);
+        try {
+            return getRawPublicKey(parser);
+        } finally {
+            wpos(ow);
+        }
+    }
+
+    public PublicKey getRawPublicKey() throws SshException {
+        return getRawPublicKey(BufferPublicKeyParser.DEFAULT);
+    }
+
+    /**
+     * @param parser A {@link BufferPublicKeyParser} to extract the key from the buffer
+     * - never {@code null}
+     * @return The extracted {@link PublicKey} - may be {@code null} if the parser so decided
+     * @throws SshException If failed to extract the key
+     */
+    public PublicKey getRawPublicKey(BufferPublicKeyParser<? extends PublicKey> parser) throws SshException {
+        Objects.requireNonNull(parser, "No key data parser");
+        try {
+            String keyType = getString();
+            if (!parser.isKeyTypeSupported(keyType)) {
+                throw new NoSuchAlgorithmException("Key type=" + keyType + ") not supported by parser=" + parser);
+            }
+
+            return parser.getRawPublicKey(keyType, this);
+        } catch (GeneralSecurityException e) {
+            throw new SshException(e);
+        }
+    }
+
+    public KeyPair getKeyPair() throws SshException {
+        try {
+            final PublicKey pub;
+            final PrivateKey prv;
+            final String keyAlg = getString();
+            if (KeyPairProvider.SSH_RSA.equals(keyAlg)) {
+                BigInteger e = getMPInt();
+                BigInteger n = getMPInt();
+                BigInteger d = getMPInt();
+                BigInteger qInv = getMPInt();
+                BigInteger q = getMPInt();
+                BigInteger p = getMPInt();
+                BigInteger dP = d.remainder(p.subtract(BigInteger.valueOf(1)));
+                BigInteger dQ = d.remainder(q.subtract(BigInteger.valueOf(1)));
+                KeyFactory keyFactory = SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM);
+                pub = keyFactory.generatePublic(new RSAPublicKeySpec(n, e));
+                prv = keyFactory.generatePrivate(new RSAPrivateCrtKeySpec(n, e, d, p, q, dP, dQ, qInv));
+            } else if (KeyPairProvider.SSH_DSS.equals(keyAlg)) {
+                BigInteger p = getMPInt();
+                BigInteger q = getMPInt();
+                BigInteger g = getMPInt();
+                BigInteger y = getMPInt();
+                BigInteger x = getMPInt();
+                KeyFactory keyFactory = SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM);
+                pub = keyFactory.generatePublic(new DSAPublicKeySpec(y, p, q, g));
+                prv = keyFactory.generatePrivate(new DSAPrivateKeySpec(x, p, q, g));
+            } else if (KeyPairProvider.SSH_ED25519.equals(keyAlg)) {
+                return SecurityUtils.extractEDDSAKeyPair(this, keyAlg);
+            } else {
+                ECCurves curve = ECCurves.fromKeyType(keyAlg);
+                if (curve == null) {
+                    throw new NoSuchAlgorithmException("Unsupported key pair algorithm: " + keyAlg);
+                }
+                String curveName = curve.getName();
+                ECParameterSpec params = curve.getParameters();
+                return extractEC(curveName, params);
+            }
+
+            return new KeyPair(pub, prv);
+        } catch (GeneralSecurityException e) {
+            throw new SshException(e);
+        }
+    }
+
+    protected KeyPair extractEC(String expectedCurveName, ECParameterSpec spec) throws GeneralSecurityException {
+        String curveName = getString();
+        if (!expectedCurveName.equals(curveName)) {
+            throw new InvalidKeySpecException("extractEC(" + expectedCurveName + ") mismatched curve name: " + curveName);
+        }
+
+        byte[] groupBytes = getBytes();
+        BigInteger exponent = getMPInt();
+
+        if (spec == null) {
+            throw new InvalidKeySpecException("extractEC(" + expectedCurveName + ") missing parameters for curve");
+        }
+
+        ECPoint group;
+        try {
+            group = ECCurves.octetStringToEcPoint(groupBytes);
+        } catch (RuntimeException e) {
+            throw new InvalidKeySpecException("extractEC(" + expectedCurveName + ")"
+                    + " failed (" + e.getClass().getSimpleName() + ")"
+                    + " to decode EC group for curve: " + e.getMessage(),
+                    e);
+        }
+
+        KeyFactory keyFactory = SecurityUtils.getKeyFactory(KeyUtils.EC_ALGORITHM);
+        PublicKey pubKey = keyFactory.generatePublic(new ECPublicKeySpec(group, spec));
+        PrivateKey privKey = keyFactory.generatePrivate(new ECPrivateKeySpec(exponent, spec));
+        return new KeyPair(pubKey, privKey);
+    }
+
+    public void ensureAvailable(int reqLen) throws BufferException {
+        int availLen = available();
+        if (availLen < reqLen) {
+            throw new BufferException("Underflow: requested=" + reqLen + ", available=" + availLen);
+        }
+    }
+
+    /*======================
+       Write methods
+     ======================*/
+
+    public void putByte(byte b) {
+        ensureCapacity(Byte.BYTES);
+        workBuf[0] = b;
+        putRawBytes(workBuf, 0, Byte.BYTES);
+    }
+
+    public void putBuffer(Readable buffer) {
+        putBuffer(buffer, true);
+    }
+
+    public abstract int putBuffer(Readable buffer, boolean expand);
+
+    public abstract void putBuffer(ByteBuffer buffer);
+
+    /**
+     * Writes 16 bits
+     *
+     * @param i The 16-bit value
+     */
+    public void putShort(int i) {
+        ensureCapacity(Short.BYTES);
+        workBuf[0] = (byte) (i >> 8);
+        workBuf[1] = (byte) i;
+        putRawBytes(workBuf, 0, Short.BYTES);
+    }
+
+    /**
+     * Writes 32 bits
+     *
+     * @param i The 32-bit value
+     */
+    public void putInt(long i) {
+        BufferUtils.validateInt32Value(i, "Invalid 32-bit value: %d");
+        ensureCapacity(Integer.BYTES);
+        BufferUtils.putUInt(i, workBuf, 0, Integer.BYTES);
+        putRawBytes(workBuf, 0, Integer.BYTES);
+    }
+
+    /**
+     * Writes 64 bits
+     *
+     * @param i The 64-bit value
+     */
+    public void putLong(long i) {
+        ensureCapacity(Long.BYTES);
+        workBuf[0] = (byte) (i >> 56);
+        workBuf[1] = (byte) (i >> 48);
+        workBuf[2] = (byte) (i >> 40);
+        workBuf[3] = (byte) (i >> 32);
+        workBuf[4] = (byte) (i >> 24);
+        workBuf[5] = (byte) (i >> 16);
+        workBuf[6] = (byte) (i >> 8);
+        workBuf[7] = (byte) i;
+        putRawBytes(workBuf, 0, Long.BYTES);
+    }
+
+    public void putBoolean(boolean b) {
+        putByte(b ? (byte) 1 : (byte) 0);
+    }
+
+    /**
+     * Adds the bytes to the buffer and wipes the data from the
+     * input buffer <U>after</U> having added it - useful for sensitive
+     * information such as password
+     *
+     * @param b The buffer to add - OK if {@code null}
+     */
+    public void putAndWipeBytes(byte[] b) {
+        putAndWipeBytes(b, 0, NumberUtils.length(b));
+    }
+
+    public void putAndWipeBytes(byte[] b, int off, int len) {
+        putBytes(b, off, len);
+
+        for (int pos = off, index = 0; index < len; pos++, index++) {
+            b[pos] = (byte) 0;
+        }
+    }
+
+    public void putBytes(byte[] b) {
+        putBytes(b, 0, NumberUtils.length(b));
+    }
+
+    public void putBytes(byte[] b, int off, int len) {
+        putInt(len);
+        putRawBytes(b, off, len);
+    }
+
+    /**
+     * Encodes the {@link Objects#toString(Object, String) toString} value of each member.
+     *
+     * @param objects       The objects to be encoded in the buffer - OK if
+     *                      {@code null}/empty
+     * @param prependLength If {@code true} then the list is preceded by
+     *                      a 32-bit count of the number of members in the list
+     * @see #putStringList(Collection, Charset, boolean)
+     */
+    public void putStringList(Collection<?> objects, boolean prependLength) {
+        putStringList(objects, StandardCharsets.UTF_8, prependLength);
+    }
+
+    /**
+     * Encodes the {@link Objects#toString(Object, String) toString} value of each member
+     *
+     * @param objects       The objects to be encoded in the buffer - OK if
+     *                      {@code null}/empty
+     * @param charset       The {@link Charset} to use for encoding
+     * @param prependLength If {@code true} then the list is preceded by
+     *                      a 32-bit count of the number of members in the list
+     * @see #putString(String, Charset)
+     */
+    public void putStringList(Collection<?> objects, Charset charset, boolean prependLength) {
+        int numObjects = GenericUtils.size(objects);
+        if (prependLength) {
+            putInt(numObjects);
+        }
+
+        if (numObjects <= 0) {
+            return;
+        }
+
+        objects.forEach(o -> putString(Objects.toString(o, null), charset));
+    }
+
+    public void putString(String string) {
+        putString(string, StandardCharsets.UTF_8);
+    }
+
+    public void putString(String string, Charset charset) {
+        if (GenericUtils.isEmpty(string)) {
+            putBytes(GenericUtils.EMPTY_BYTE_ARRAY);
+        } else {
+            putBytes(string.getBytes(charset));
+        }
+    }
+
+    /**
+     * Zeroes the input array <U>after</U> having put the characters in the
+     * buffer - useful for sensitive information such as passwords
+     *
+     * @param chars The characters to put in the buffer - may be {@code null}/empty
+     * @see #putAndWipeChars(char[], Charset)
+     * @see #putChars(char[], Charset)
+     */
+    public void putAndWipeChars(char[] chars) {
+        putAndWipeChars(chars, 0, GenericUtils.length(chars));
+    }
+
+    public void putAndWipeChars(char[] chars, int offset, int len) {
+        putAndWipeChars(chars, offset, len, StandardCharsets.UTF_8);
+    }
+
+    public void putAndWipeChars(char[] chars, Charset charset) {
+        putAndWipeChars(chars, 0, GenericUtils.length(chars), charset);
+    }
+
+    public void putAndWipeChars(char[] chars, int offset, int len, Charset charset) {
+        putChars(chars, offset, len, charset);
+        for (int pos = offset, index = 0; index < len; index++, pos++) {
+            chars[pos] = '\0';
+        }
+    }
+
+    public void putChars(char[] chars) {
+        putChars(chars, 0, GenericUtils.length(chars));
+    }
+
+    public void putChars(char[] chars, int offset, int len) {
+        putChars(chars, offset, len, StandardCharsets.UTF_8);
+    }
+
+    public void putChars(char[] chars, Charset charset) {
+        putChars(chars, 0, GenericUtils.length(chars), charset);
+    }
+
+    public void putChars(char[] chars, int offset, int len, Charset charset) {
+        if (len <= 0) {
+            putBytes(GenericUtils.EMPTY_BYTE_ARRAY);
+        } else {
+            putBuffer(charset.encode(CharBuffer.wrap(chars, offset, len)));
+        }
+    }
+
+    public void putMPInt(BigInteger bi) {
+        putMPInt(bi.toByteArray());
+    }
+
+    public void putMPInt(byte[] foo) {
+        if ((foo[0] & 0x80) != 0) {
+            putInt(foo.length + 1 /* padding */);
+            putByte((byte) 0);
+        } else {
+            putInt(foo.length);
+        }
+        putRawBytes(foo);
+    }
+
+    public void putRawBytes(byte[] d) {
+        putRawBytes(d, 0, d.length);
+    }
+
+    public abstract void putRawBytes(byte[] d, int off, int len);
+
+    public void putPublicKey(PublicKey key) {
+        int ow = wpos();
+        putInt(0);
+        int ow1 = wpos();
+        putRawPublicKey(key);
+        int ow2 = wpos();
+        wpos(ow);
+        putInt(ow2 - ow1);
+        wpos(ow2);
+    }
+
+    public void putRawPublicKey(PublicKey key) {
+        Objects.requireNonNull(key, "No key");
+        if (key instanceof RSAPublicKey) {
+            RSAPublicKey rsaPub = (RSAPublicKey) key;
+
+            putString(KeyPairProvider.SSH_RSA);
+            putMPInt(rsaPub.getPublicExponent());
+            putMPInt(rsaPub.getModulus());
+        } else if (key instanceof DSAPublicKey) {
+            DSAPublicKey dsaPub = (DSAPublicKey) key;
+            DSAParams dsaParams = dsaPub.getParams();
+
+            putString(KeyPairProvider.SSH_DSS);
+            putMPInt(dsaParams.getP());
+            putMPInt(dsaParams.getQ());
+            putMPInt(dsaParams.getG());
+            putMPInt(dsaPub.getY());
+        } else if (key instanceof ECPublicKey) {
+            ECPublicKey ecKey = (ECPublicKey) key;
+            ECParameterSpec ecParams = ecKey.getParams();
+            ECCurves curve = ECCurves.fromCurveParameters(ecParams);
+            if (curve == null) {
+                throw new BufferException("Unsupported EC curve parameters");
+            }
+
+            putString(curve.getKeyType());
+            putString(curve.getName());
+            putBytes(ECCurves.encodeECPoint(ecKey.getW(), ecParams));
+        } else if (SecurityUtils.EDDSA.equals(key.getAlgorithm())) {
+            SecurityUtils.putRawEDDSAPublicKey(this, key);
+        } else {
+            throw new BufferException("Unsupported raw public key algorithm: " + key.getAlgorithm());
+        }
+    }
+
+    public void putKeyPair(KeyPair kp) {
+        PublicKey pubKey = kp.getPublic();
+        PrivateKey prvKey = kp.getPrivate();
+        if (prvKey instanceof RSAPrivateCrtKey) {
+            RSAPublicKey rsaPub = (RSAPublicKey) pubKey;
+            RSAPrivateCrtKey rsaPrv = (RSAPrivateCrtKey) prvKey;
+
+            putString(KeyPairProvider.SSH_RSA);
+            putMPInt(rsaPub.getPublicExponent());
+            putMPInt(rsaPub.getModulus());
+            putMPInt(rsaPrv.getPrivateExponent());
+            putMPInt(rsaPrv.getCrtCoefficient());
+            putMPInt(rsaPrv.getPrimeQ());
+            putMPInt(rsaPrv.getPrimeP());
+        } else if (pubKey instanceof DSAPublicKey) {
+            DSAPublicKey dsaPub = (DSAPublicKey) pubKey;
+            DSAParams dsaParams = dsaPub.getParams();
+            DSAPrivateKey dsaPrv = (DSAPrivateKey) prvKey;
+
+            putString(KeyPairProvider.SSH_DSS);
+            putMPInt(dsaParams.getP());
+            putMPInt(dsaParams.getQ());
+            putMPInt(dsaParams.getG());
+            putMPInt(dsaPub.getY());
+            putMPInt(dsaPrv.getX());
+        } else if (pubKey instanceof ECPublicKey) {
+            ECPublicKey ecPub = (ECPublicKey) pubKey;
+            ECPrivateKey ecPriv = (ECPrivateKey) prvKey;
+            ECParameterSpec ecParams = ecPub.getParams();
+            ECCurves curve = ECCurves.fromCurveParameters(ecParams);
+            if (curve == null) {
+                throw new BufferException("Unsupported EC curve parameters");
+            }
+
+            putString(curve.getKeyType());
+            putString(curve.getName());
+            putBytes(ECCurves.encodeECPoint(ecPub.getW(), ecParams));
+            putMPInt(ecPriv.getS());
+        } else if (SecurityUtils.EDDSA.equals(pubKey.getAlgorithm())) {
+            SecurityUtils.putEDDSAKeyPair(this, pubKey, prvKey);
+        } else {
+            throw new BufferException("Unsupported key pair algorithm: " + pubKey.getAlgorithm());
+        }
+    }
+
+    protected void ensureCapacity(int capacity) {
+        ensureCapacity(capacity, BufferUtils.DEFAULT_BUFFER_GROWTH_FACTOR);
+    }
+
+    /**
+     * @param capacity     The requires capacity
+     * @param growthFactor An {@link IntUnaryOperator} that is invoked
+     *                     if the current capacity is insufficient. The argument is the minimum
+     *                     required new data length, the function result should be the
+     *                     effective new data length to be allocated - if less than minimum
+     *                     then an exception is thrown
+     */
+    public abstract void ensureCapacity(int capacity, IntUnaryOperator growthFactor);
+
+    protected abstract int size();
+
+    @Override
+    public String toString() {
+        return "Buffer [rpos=" + rpos() + ", wpos=" + wpos() + ", size=" + size() + "]";
+    }
+}

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