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
+ * "slashification" 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
+ * "slashification" 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 "slashification" 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 "slash" 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 "C:\"
+ *
+ * @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 "greedy" 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 "greedy" 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