All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.sshd.common.util.buffer.BufferUtils Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.sshd.common.util.buffer;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StreamCorruptedException;
import java.math.BigInteger;
import java.util.function.IntUnaryOperator;
import java.util.logging.Level;

import org.apache.sshd.common.CommonModuleProperties;
import org.apache.sshd.common.PropertyResolver;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.NumberUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.io.IoUtils;
import org.apache.sshd.common.util.logging.SimplifiedLog;

/**
 * General utilities for working with byte-encoded data.
 *
 * @author Apache MINA SSHD Project
 */
public final class BufferUtils {
    public static final char DEFAULT_HEX_SEPARATOR = ' ';
    public static final char EMPTY_HEX_SEPARATOR = '\0';
    public static final String HEX_DIGITS = "0123456789abcdef";

    public static final Level DEFAULT_HEXDUMP_LEVEL = Level.FINEST;

    public static final IntUnaryOperator DEFAULT_BUFFER_GROWTH_FACTOR = BufferUtils::getNextPowerOf2;

    /**
     * Maximum value of a {@code uint32} field
     */
    public static final long MAX_UINT32_VALUE = 0x0FFFFFFFFL;

    /**
     * Maximum value of a {@code uint8} field
     */
    public static final int MAX_UINT8_VALUE = 0x0FF;

    /**
     * Private Constructor
     */
    private BufferUtils() {
        throw new UnsupportedOperationException("No instance allowed");
    }

    /**
     * 

* Finds the index of the given value in the array starting at the given index and checking up to specified number * of elements. *

* *

* This method returns {@code -1}) for a {@code null} input array. *

* *

* A negative startIndex is treated as zero. A startIndex larger than the array length will return {@code -1}. *

* * @param array the array to search through for the object, may be {@code null} * @param valueToFind the value to find * @param startIndex the index to start searching at * @param len the number of elements to search from the start index * @return the index of the value within the array, {@code -1} if not found or {@code null} array input * or non-positive number of elements */ public static int indexOf(byte[] array, byte valueToFind, int startIndex, int len) { if (array == null) { return -1; } for (int i = Math.max(startIndex, 0), l = 0; l < len; i++, l++) { if (valueToFind == array[i]) { return i; } } return -1; } public static void dumpHex( SimplifiedLog logger, Level level, String prefix, PropertyResolver resolver, char sep, byte... data) { dumpHex(logger, level, prefix, resolver, sep, data, 0, NumberUtils.length(data)); } public static void dumpHex( SimplifiedLog logger, Level level, String prefix, PropertyResolver resolver, char sep, byte[] data, int offset, int len) { dumpHex(logger, level, prefix, sep, CommonModuleProperties.HEXDUMP_CHUNK_SIZE.getRequired(resolver), data, offset, len); } public static void dumpHex( SimplifiedLog logger, Level level, String prefix, char sep, int chunkSize, byte... data) { dumpHex(logger, level, prefix, sep, chunkSize, data, 0, NumberUtils.length(data)); } public static void dumpHex( SimplifiedLog logger, Level level, String prefix, char sep, int chunkSize, byte[] data, int offset, int len) { if ((logger == null) || (level == null) || (!logger.isEnabledLevel(level))) { return; } StringBuilder sb = new StringBuilder(chunkSize * 3 /* HEX */ + prefix.length() + Long.SIZE /* some extra */); sb.append(prefix); for (int remainLen = len, chunkIndex = 1, curOffset = offset, totalLen = 0; remainLen > 0; chunkIndex++) { sb.setLength(prefix.length()); // reset for next chunk sb.append(" [chunk #").append(chunkIndex).append(']'); int dumpSize = Math.min(chunkSize, remainLen); totalLen += dumpSize; sb.append('(').append(totalLen).append('/').append(len).append(')'); try { appendHex(sb.append(' '), data, curOffset, dumpSize, sep); } catch (IOException e) { // unexpected sb.append(e.getClass().getSimpleName()).append(": ").append(e.getMessage()); } // Pad the last (incomplete) line to align its data view for (int index = dumpSize; index < chunkSize; index++) { if (sep != EMPTY_HEX_SEPARATOR) { sb.append(' '); } sb.append(" "); } sb.append(" "); for (int pos = curOffset, l = 0; l < dumpSize; pos++, l++) { int b = data[pos] & 0xFF; if ((b > ' ') && (b < 0x7E)) { sb.append((char) b); } else { sb.append('.'); } } logger.log(level, sb.toString()); remainLen -= dumpSize; curOffset += dumpSize; } } public static String toHex(byte... array) { return toHex(array, 0, NumberUtils.length(array)); } public static String toHex(char sep, byte... array) { return toHex(array, 0, NumberUtils.length(array), sep); } public static String toHex(byte[] array, int offset, int len) { return toHex(array, offset, len, DEFAULT_HEX_SEPARATOR); } public static String toHex(byte[] array, int offset, int len, char sep) { if (len <= 0) { return ""; } try { return appendHex(new StringBuilder(len * 3 /* 2 HEX + sep */), array, offset, len, sep).toString(); } catch (IOException e) { // unexpected return e.getClass().getSimpleName() + ": " + e.getMessage(); } } public static A appendHex(A sb, char sep, byte... array) throws IOException { return appendHex(sb, array, 0, NumberUtils.length(array), sep); } public static A appendHex( A sb, byte[] array, int offset, int len, char sep) throws IOException { if (len <= 0) { return sb; } for (int curOffset = offset, maxOffset = offset + len; curOffset < maxOffset; curOffset++) { byte b = array[curOffset]; if ((curOffset > offset) && (sep != EMPTY_HEX_SEPARATOR)) { sb.append(sep); } sb.append(HEX_DIGITS.charAt((b >> 4) & 0x0F)); sb.append(HEX_DIGITS.charAt(b & 0x0F)); } return sb; } /** * @param separator The separator between the HEX values - may be {@link #EMPTY_HEX_SEPARATOR} * @param csq The {@link CharSequence} containing the HEX encoded bytes * @return The decoded bytes * @throws IllegalArgumentException If invalid HEX sequence length * @throws NumberFormatException If invalid HEX characters found * @see #decodeHex(char, CharSequence, int, int) */ public static byte[] decodeHex(char separator, CharSequence csq) { return decodeHex(separator, csq, 0, GenericUtils.length(csq)); } /** * @param separator The separator between the HEX values - may be {@link #EMPTY_HEX_SEPARATOR} * @param csq The {@link CharSequence} containing the HEX encoded bytes * @param start Start offset of the HEX sequence (inclusive) * @param end End offset of the HEX sequence (exclusive) * @return The decoded bytes * @throws IllegalArgumentException If invalid HEX sequence length * @throws NumberFormatException If invalid HEX characters found */ public static byte[] decodeHex(char separator, CharSequence csq, int start, int end) { int len = end - start; ValidateUtils.checkTrue(len >= 0, "Bad HEX sequence length: %d", len); if (len == 0) { return GenericUtils.EMPTY_BYTE_ARRAY; } int delta = 2; byte[] bytes; if (separator != EMPTY_HEX_SEPARATOR) { // last character cannot be the separator ValidateUtils.checkTrue((len % 3) == 2, "Invalid separated HEX sequence length: %d", len); bytes = new byte[(len + 1) / 3]; delta++; } else { ValidateUtils.checkTrue((len & 0x01) == 0, "Invalid contiguous HEX sequence length: %d", len); bytes = new byte[len >>> 1]; } int writeLen = 0; for (int curPos = start; curPos < end; curPos += delta, writeLen++) { bytes[writeLen] = fromHex(csq.charAt(curPos), csq.charAt(curPos + 1)); } assert writeLen == bytes.length; return bytes; } /** * @param The {@link OutputStream} generic type * @param stream The target {@link OutputStream} * @param separator The separator between the HEX values - may be {@link #EMPTY_HEX_SEPARATOR} * @param csq The {@link CharSequence} containing the HEX encoded bytes * @return The number of bytes written to the stream * @throws IOException If failed to write * @throws IllegalArgumentException If invalid HEX sequence length * @throws NumberFormatException If invalid HEX characters found * @see #decodeHex(OutputStream, char, CharSequence, int, int) */ public static int decodeHex( S stream, char separator, CharSequence csq) throws IOException { return decodeHex(stream, separator, csq, 0, GenericUtils.length(csq)); } /** * @param The {@link OutputStream} generic type * @param stream The target {@link OutputStream} * @param separator The separator between the HEX values - may be {@link #EMPTY_HEX_SEPARATOR} * @param csq The {@link CharSequence} containing the HEX encoded bytes * @param start Start offset of the HEX sequence (inclusive) * @param end End offset of the HEX sequence (exclusive) * @return The number of bytes written to the stream * @throws IOException If failed to write * @throws IllegalArgumentException If invalid HEX sequence length * @throws NumberFormatException If invalid HEX characters found */ public static int decodeHex( S stream, char separator, CharSequence csq, int start, int end) throws IOException { int len = end - start; ValidateUtils.checkTrue(len >= 0, "Bad HEX sequence length: %d", len); int delta = 2; if (separator != EMPTY_HEX_SEPARATOR) { // last character cannot be the separator ValidateUtils.checkTrue((len % 3) == 2, "Invalid separated HEX sequence length: %d", len); delta++; } else { ValidateUtils.checkTrue((len & 0x01) == 0, "Invalid contiguous HEX sequence length: %d", len); } int writeLen = 0; for (int curPos = start; curPos < end; curPos += delta, writeLen++) { stream.write(fromHex(csq.charAt(curPos), csq.charAt(curPos + 1)) & 0xFF); } return writeLen; } public static byte fromHex(char hi, char lo) throws NumberFormatException { int hiValue = HEX_DIGITS.indexOf(((hi >= 'A') && (hi <= 'F')) ? ('a' + (hi - 'A')) : hi); int loValue = HEX_DIGITS.indexOf(((lo >= 'A') && (lo <= 'F')) ? ('a' + (lo - 'A')) : lo); if ((hiValue < 0) || (loValue < 0)) { throw new NumberFormatException("fromHex(" + new String(new char[] { hi, lo }) + ") non-HEX characters"); } return (byte) ((hiValue << 4) + loValue); } /** * Read a 32-bit value in network order * * @param input The {@link InputStream} * @param buf Work buffer to use * @return The read 32-bit value * @throws IOException If failed to read 4 bytes or not enough room in work buffer * @see #readInt(InputStream, byte[], int, int) */ public static int readInt(InputStream input, byte[] buf) throws IOException { return readInt(input, buf, 0, NumberUtils.length(buf)); } /** * Read a 32-bit value in network order * * @param input The {@link InputStream} * @param buf Work buffer to use * @param offset Offset in buffer to us * @param len Available length - must have at least 4 bytes available * @return The read 32-bit value * @throws IOException If failed to read 4 bytes or not enough room in work buffer * @see #readUInt(InputStream, byte[], int, int) */ public static int readInt(InputStream input, byte[] buf, int offset, int len) throws IOException { return (int) readUInt(input, buf, offset, len); } /** * Read a 32-bit value in network order * * @param input The {@link InputStream} * @param buf Work buffer to use * @return The read 32-bit value * @throws IOException If failed to read 4 bytes or not enough room in work buffer * @see #readUInt(InputStream, byte[], int, int) */ public static long readUInt(InputStream input, byte[] buf) throws IOException { return readUInt(input, buf, 0, NumberUtils.length(buf)); } /** * Read a 32-bit value in network order * * @param input The {@link InputStream} * @param buf Work buffer to use * @param offset Offset in buffer to us * @param len Available length - must have at least 4 bytes available * @return The read 32-bit value * @throws IOException If failed to read 4 bytes or not enough room in work buffer * @see #getUInt(byte[], int, int) */ public static long readUInt(InputStream input, byte[] buf, int offset, int len) throws IOException { try { if (len < Integer.BYTES) { throw new IllegalArgumentException( "Not enough data for a UINT: required=" + Integer.BYTES + ", available=" + len); } IoUtils.readFully(input, buf, offset, Integer.BYTES); return getUInt(buf, offset, len); } catch (RuntimeException | Error e) { throw new StreamCorruptedException("Failed (" + e.getClass().getSimpleName() + ")" + " to read UINT value: " + e.getMessage()); } } /** * @param buf A buffer holding a 32-bit unsigned integer in big endian format. Note: if more than 4 * bytes are available, then only the first 4 bytes in the buffer will be used * @return The result as a {@code long} whose 32 high-order bits are zero * @see #getUInt(byte[], int, int) */ public static long getUInt(byte... buf) { return getUInt(buf, 0, NumberUtils.length(buf)); } /** * @param buf A buffer holding a 32-bit unsigned integer in big endian format. * @param off The offset of the data in the buffer * @param len The available data length. Note: if more than 4 bytes are available, then only the * first 4 bytes in the buffer will be used (starting at the specified offset) * @return The result as a {@code long} whose 32 high-order bits are zero */ public static long getUInt(byte[] buf, int off, int len) { if (len < Integer.BYTES) { throw new IllegalArgumentException("Not enough data for a UINT: required=" + Integer.BYTES + ", available=" + len); } long l = (buf[off] << 24) & 0xff000000L; l |= (buf[off + 1] << 16) & 0x00ff0000L; l |= (buf[off + 2] << 8) & 0x0000ff00L; l |= (buf[off + 3]) & 0x000000ffL; return l; } public static long getLong(byte[] buf, int off, int len) { if (len < Long.BYTES) { throw new IllegalArgumentException("Not enough data for a long: required=" + Long.BYTES + ", available=" + len); } long l = (long) buf[off] << 56; l |= ((long) buf[off + 1] & 0xff) << 48; l |= ((long) buf[off + 2] & 0xff) << 40; l |= ((long) buf[off + 3] & 0xff) << 32; l |= ((long) buf[off + 4] & 0xff) << 24; l |= ((long) buf[off + 5] & 0xff) << 16; l |= ((long) buf[off + 6] & 0xff) << 8; l |= (long) buf[off + 7] & 0xff; return l; } public static BigInteger fromMPIntBytes(byte[] mpInt) { if (NumberUtils.isEmpty(mpInt)) { return null; } if ((mpInt[0] & 0x80) != 0) { return new BigInteger(1, mpInt); } else { return new BigInteger(mpInt); } } /** * Writes a 32-bit value in network order (i.e., MSB 1st) * * @param output The {@link OutputStream} to write the value * @param value The 32-bit value * @param buf A work buffer to use - must have enough space to contain 4 bytes * @throws IOException If failed to write the value or work buffer too small * @see #writeInt(OutputStream, int, byte[], int, int) */ public static void writeInt(OutputStream output, int value, byte[] buf) throws IOException { writeUInt(output, value, buf, 0, NumberUtils.length(buf)); } /** * Writes a 32-bit value in network order (i.e., MSB 1st) * * @param output The {@link OutputStream} to write the value * @param value The 32-bit value * @param buf A work buffer to use - must have enough space to contain 4 bytes * @param off The offset to write the value * @param len The available space * @throws IOException If failed to write the value or work buffer too small * @see #writeUInt(OutputStream, long, byte[], int, int) */ public static void writeInt( OutputStream output, int value, byte[] buf, int off, int len) throws IOException { writeUInt(output, value & 0xFFFFFFFFL, buf, off, len); } /** * Writes a 32-bit value in network order (i.e., MSB 1st) * * @param output The {@link OutputStream} to write the value * @param value The 32-bit value * @param buf A work buffer to use - must have enough space to contain 4 bytes * @throws IOException If failed to write the value or work buffer too small * @see #writeUInt(OutputStream, long, byte[], int, int) */ public static void writeUInt(OutputStream output, long value, byte[] buf) throws IOException { writeUInt(output, value, buf, 0, NumberUtils.length(buf)); } /** * Writes a 32-bit value in network order (i.e., MSB 1st) * * @param output The {@link OutputStream} to write the value * @param value The 32-bit value * @param buf A work buffer to use - must have enough space to contain 4 bytes * @param off The offset to write the value * @param len The available space * @throws IOException If failed to write the value or work buffer to small * @see #putUInt(long, byte[], int, int) */ public static void writeUInt( OutputStream output, long value, byte[] buf, int off, int len) throws IOException { try { int writeLen = putUInt(value, buf, off, len); output.write(buf, off, writeLen); } catch (RuntimeException | Error e) { throw new StreamCorruptedException("Failed (" + e.getClass().getSimpleName() + ")" + " to write UINT value=" + value + ": " + e.getMessage()); } } /** * Writes a 32-bit value in network order (i.e., MSB 1st) * * @param value The 32-bit value * @param buf The buffer * @return The number of bytes used in the buffer * @throws IllegalArgumentException if not enough space available * @see #putUInt(long, byte[], int, int) */ public static int putUInt(long value, byte[] buf) { return putUInt(value, buf, 0, NumberUtils.length(buf)); } /** * Writes a 32-bit value in network order (i.e., MSB 1st) * * @param value The 32-bit value * @param buf The buffer * @param off The offset to write the value * @param len The available space * @return The number of bytes used in the buffer * @throws IllegalArgumentException if not enough space available */ public static int putUInt(long value, byte[] buf, int off, int len) { if (len < Integer.BYTES) { throw new IllegalArgumentException("Not enough data for a UINT: required=" + Integer.BYTES + ", available=" + len); } buf[off] = (byte) ((value >> 24) & 0xFF); buf[off + 1] = (byte) ((value >> 16) & 0xFF); buf[off + 2] = (byte) ((value >> 8) & 0xFF); buf[off + 3] = (byte) (value & 0xFF); return Integer.BYTES; } public static int putLong(long value, byte[] buf, int off, int len) { if (len < Long.BYTES) { throw new IllegalArgumentException("Not enough data for a long: required=" + Long.BYTES + ", available=" + len); } buf[off] = (byte) (value >> 56); buf[off + 1] = (byte) (value >> 48); buf[off + 2] = (byte) (value >> 40); buf[off + 3] = (byte) (value >> 32); buf[off + 4] = (byte) (value >> 24); buf[off + 5] = (byte) (value >> 16); buf[off + 6] = (byte) (value >> 8); buf[off + 7] = (byte) value; return Long.BYTES; } /** * Compares the contents of 2 arrays of bytes - Note: do not use it to execute security related comparisons * (e.g. MACs) since the method leaks timing information. Use {@code Mac#equals} method instead. * * @param a1 1st array * @param a2 2nd array * @return {@code true} if all bytes in the compared arrays are equal */ public static boolean equals(byte[] a1, byte[] a2) { int len1 = NumberUtils.length(a1); int len2 = NumberUtils.length(a2); if (len1 != len2) { return false; } else { return equals(a1, 0, a2, 0, len1); } } /** * Compares the contents of 2 arrays of bytes - Note: do not use it to execute security related comparisons * (e.g. MACs) since the method leaks timing information. Use {@code Mac#equals} method instead. * * @param a1 1st array * @param a1Offset Offset to start comparing in 1st array * @param a2 2nd array * @param a2Offset Offset to start comparing in 2nd array * @param length Number of bytes to compare * @return {@code true} if all bytes in the compared arrays are equal when compared from the specified * offsets and up to specified length */ @SuppressWarnings("PMD.AssignmentInOperand") public static boolean equals(byte[] a1, int a1Offset, byte[] a2, int a2Offset, int length) { int len1 = NumberUtils.length(a1); int len2 = NumberUtils.length(a2); if ((len1 < (a1Offset + length)) || (len2 < (a2Offset + length))) { return false; } while (length-- > 0) { if (a1[a1Offset++] != a2[a2Offset++]) { return false; } } return true; } public static int getNextPowerOf2(int value) { // for 0-7 return 8 return (value < Byte.SIZE) ? Byte.SIZE : (value > (1 << 30)) ? value : NumberUtils.getNextPowerOf2(value); } /** * Used for encodings where we don't know the data length before adding it to the buffer. The idea is to place a * 32-bit "placeholder", encode the data and then return back to the placeholder and update the length. * The method calculates the encoded data length, moves the write position to the specified placeholder position, * updates the length value and then moves the write position it back to its original value. * * @param buffer The {@link Buffer} * @param lenPos The offset in the buffer where the length placeholder is to be update - Note: assumption is * that the encoded data starts immediately after the placeholder * @return The amount of data that has been encoded */ public static int updateLengthPlaceholder(Buffer buffer, int lenPos) { int startPos = lenPos + Integer.BYTES; int endPos = buffer.wpos(); int dataLength = endPos - startPos; // NOTE: although data length is defined as UINT32, we do not expected sizes above Integer.MAX_VALUE ValidateUtils.checkTrue(dataLength >= 0, "Illegal data length: %d", dataLength); buffer.wpos(lenPos); buffer.putUInt(dataLength); buffer.wpos(endPos); return dataLength; } /** * Updates a 32-bit "placeholder" location for data length - moves the write position to the specified * placeholder position, updates the length value and then moves the write position it back to its original value. * * @param buffer The {@link Buffer} * @param lenPos The offset in the buffer where the length placeholder is to be update - Note: assumption * is that the encoded data starts immediately after the placeholder * @param dataLength The length to update - a UINT32 value as a {@code long} */ public static void updateLengthPlaceholder(Buffer buffer, int lenPos, long dataLength) { int curPos = buffer.wpos(); buffer.wpos(lenPos); buffer.putUInt(dataLength); buffer.wpos(curPos); } /** * Invokes {@link Buffer#clear()} * * @param The generic buffer type * @param buffer A {@link Buffer} instance - ignored if {@code null} * @return The same as the input instance */ public static B clear(B buffer) { if (buffer != null) { buffer.clear(); } return buffer; } public static long validateInt32Value(long value, String message) { ValidateUtils.checkTrue(isValidInt32Value(value), message, value); return value; } public static long validateInt32Value(long value, String format, Object arg) { ValidateUtils.checkTrue(isValidInt32Value(value), format, arg); return value; } public static long validateInt32Value(long value, String format, Object... args) { ValidateUtils.checkTrue(isValidInt32Value(value), format, args); return value; } public static boolean isValidInt32Value(long value) { return (value >= Integer.MIN_VALUE) && (value <= Integer.MAX_VALUE); } public static long validateUint32Value(long value, String message) { ValidateUtils.checkTrue(isValidUint32Value(value), message, value); return value; } public static long validateUint32Value(long value, String format, Object arg) { ValidateUtils.checkTrue(isValidUint32Value(value), format, arg); return value; } public static long validateUint32Value(long value, String format, Object... args) { ValidateUtils.checkTrue(isValidUint32Value(value), format, args); return value; } public static boolean isValidUint32Value(long value) { return (value >= 0L) && (value <= MAX_UINT32_VALUE); } }