/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed 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 net.schmizz.sshj.common;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.util.Arrays;
public class Buffer<T extends Buffer<T>> {
public static class BufferException
extends SSHException {
public BufferException(String message) {
super(message);
}
}
public static final class PlainBuffer
extends Buffer<PlainBuffer> {
public PlainBuffer() {
super();
}
public PlainBuffer(Buffer<?> from) {
super(from);
}
public PlainBuffer(byte[] b) {
super(b);
}
public PlainBuffer(int size) {
super(size);
}
}
/** The default size for a {@code Buffer} (256 bytes) */
public static final int DEFAULT_SIZE = 256;
/** The maximum valid size of buffer (i.e. biggest power of two that can be represented as an int - 2^30) */
public static final int MAX_SIZE = (1 << 30);
/** Maximum size of a uint64 */
private static final BigInteger MAX_UINT64_VALUE = BigInteger.ONE
.shiftLeft(64)
.subtract(BigInteger.ONE);
protected static int getNextPowerOf2(int i) {
int j = 1;
while (j < i) {
j <<= 1;
if (j <= 0) throw new IllegalArgumentException("Cannot get next power of 2; "+i+" is too large");
}
return j;
}
protected byte[] data;
protected int rpos;
protected int wpos;
/** @see #DEFAULT_SIZE */
public Buffer() {
this(DEFAULT_SIZE);
}
public Buffer(Buffer<?> from) {
data = new byte[(wpos = from.wpos - from.rpos)];
System.arraycopy(from.data, from.rpos, data, 0, wpos);
}
public Buffer(byte[] data) {
this(data, true);
}
public Buffer(int size) {
this(new byte[getNextPowerOf2(size)], false);
}
private Buffer(byte[] data, boolean read) {
this.data = data;
rpos = 0;
wpos = read ? data.length : 0;
}
public byte[] array() {
return data;
}
public int available() {
return wpos - rpos;
}
/** Resets this buffer. The object becomes ready for reuse. */
public void clear() {
rpos = 0;
wpos = 0;
}
public int rpos() {
return rpos;
}
public void rpos(int rpos) {
this.rpos = rpos;
}
public int wpos() {
return wpos;
}
public void wpos(int wpos) {
ensureCapacity(wpos - this.wpos);
this.wpos = wpos;
}
protected void ensureAvailable(int a)
throws BufferException {
if (available() < a)
throw new BufferException("Underflow");
}
public void ensureCapacity(int capacity) {
if (data.length - wpos < capacity) {
int cw = wpos + capacity;
byte[] tmp = new byte[getNextPowerOf2(cw)];
System.arraycopy(data, 0, tmp, 0, data.length);
data = tmp;
}
}
/** Compact this {@link SSHPacket} */
public void compact() {
System.err.println("COMPACTING");
if (available() > 0)
System.arraycopy(data, rpos, data, 0, wpos - rpos);
wpos -= rpos;
rpos = 0;
}
public byte[] getCompactData() {
final int len = available();
if (len > 0) {
byte[] b = new byte[len];
System.arraycopy(data, rpos, b, 0, len);
return b;
} else
return new byte[0];
}
/**
* Read an SSH boolean byte
*
* @return the {@code true} or {@code false} value read
*/
public boolean readBoolean()
throws BufferException {
return readByte() != 0;
}
/**
* Puts an SSH boolean value
*
* @param b the value
*
* @return this
*/
public T putBoolean(boolean b) {
return putByte(b ? (byte) 1 : (byte) 0);
}
/**
* Read a byte from the buffer
*
* @return the byte read
*/
public byte readByte()
throws BufferException {
ensureAvailable(1);
return data[rpos++];
}
/**
* Writes a single byte into this buffer
*
* @param b
*
* @return this
*/
@SuppressWarnings("unchecked")
public T putByte(byte b) {
ensureCapacity(1);
data[wpos++] = b;
return (T) this;
}
/**
* Read an SSH byte-array
*
* @return the byte-array read
*/
public byte[] readBytes()
throws BufferException {
int len = readUInt32AsInt();
if (len < 0 || len > 32768)
throw new BufferException("Bad item length: " + len);
byte[] b = new byte[len];
readRawBytes(b);
return b;
}
/**
* Writes Java byte-array as an SSH byte-array
*
* @param b Java byte-array
*
* @return this
*/
public T putBytes(byte[] b) {
return putBytes(b, 0, b.length);
}
/**
* Writes Java byte-array as an SSH byte-array
*
* @param b Java byte-array
* @param off offset
* @param len length
*
* @return this
*/
public T putBytes(byte[] b, int off, int len) {
return putUInt32(len - off).putRawBytes(b, off, len);
}
public void readRawBytes(byte[] buf)
throws BufferException {
readRawBytes(buf, 0, buf.length);
}
public void readRawBytes(byte[] buf, int off, int len)
throws BufferException {
ensureAvailable(len);
System.arraycopy(data, rpos, buf, off, len);
rpos += len;
}
public T putRawBytes(byte[] d) {
return putRawBytes(d, 0, d.length);
}
@SuppressWarnings("unchecked")
public T putRawBytes(byte[] d, int off, int len) {
ensureCapacity(len);
System.arraycopy(d, off, data, wpos, len);
wpos += len;
return (T) this;
}
/**
* Copies the contents of provided buffer into this buffer
*
* @param buffer the {@code Buffer} to copy
*
* @return this
*/
@SuppressWarnings("unchecked")
public T putBuffer(Buffer<? extends Buffer<?>> buffer) {
if (buffer != null) {
int r = buffer.available();
ensureCapacity(r);
System.arraycopy(buffer.data, buffer.rpos, data, wpos, r);
wpos += r;
}
return (T) this;
}
public int readUInt32AsInt()
throws BufferException {
return (int) readUInt32();
}
public long readUInt32()
throws BufferException {
ensureAvailable(4);
return data[rpos++] << 24 & 0xff000000L |
data[rpos++] << 16 & 0x00ff0000L |
data[rpos++] << 8 & 0x0000ff00L |
data[rpos++] & 0x000000ffL;
}
/**
* Writes a uint32 integer
*
* @param uint32
*
* @return this
*/
@SuppressWarnings("unchecked")
public T putUInt32(long uint32) {
ensureCapacity(4);
if (uint32 < 0 || uint32 > 0xffffffffL)
throw new IllegalArgumentException("Invalid value: " + uint32);
data[wpos++] = (byte) (uint32 >> 24);
data[wpos++] = (byte) (uint32 >> 16);
data[wpos++] = (byte) (uint32 >> 8);
data[wpos++] = (byte) uint32;
return (T) this;
}
/**
* Read an SSH multiple-precision integer
*
* @return the MP integer as a {@code BigInteger}
*/
public BigInteger readMPInt()
throws BufferException {
return new BigInteger(readBytes());
}
public T putMPInt(BigInteger bi) {
final byte[] asBytes = bi.toByteArray();
putUInt32(asBytes.length);
return putRawBytes(asBytes);
}
public long readUInt64()
throws BufferException {
long uint64 = (readUInt32() << 32) + (readUInt32() & 0xffffffffL);
if (uint64 < 0)
throw new BufferException("Cannot handle values > Long.MAX_VALUE");
return uint64;
}
public BigInteger readUInt64AsBigInteger()
throws BufferException {
byte[] magnitude = new byte[8];
readRawBytes(magnitude);
return new BigInteger(1, magnitude);
}
public T putUInt64(long uint64) {
if (uint64 < 0)
throw new IllegalArgumentException("Invalid value: " + uint64);
return putUInt64Unchecked(uint64);
}
public T putUInt64(BigInteger uint64) {
if (uint64.compareTo(MAX_UINT64_VALUE) > 0 ||
uint64.compareTo(BigInteger.ZERO) < 0) {
throw new IllegalArgumentException("Invalid value: " + uint64);
}
return putUInt64Unchecked(uint64.longValue());
}
@SuppressWarnings("unchecked")
private T putUInt64Unchecked(long uint64) {
data[wpos++] = (byte) (uint64 >> 56);
data[wpos++] = (byte) (uint64 >> 48);
data[wpos++] = (byte) (uint64 >> 40);
data[wpos++] = (byte) (uint64 >> 32);
data[wpos++] = (byte) (uint64 >> 24);
data[wpos++] = (byte) (uint64 >> 16);
data[wpos++] = (byte) (uint64 >> 8);
data[wpos++] = (byte) uint64;
return (T) this;
}
/**
* Reads an SSH string
*
* @param cs the charset to use for decoding
*
* @return the string as a Java {@code String}
*/
public String readString(Charset cs)
throws BufferException {
int len = readUInt32AsInt();
if (len < 0 || len > 32768)
throw new BufferException("Bad item length: " + len);
ensureAvailable(len);
String s = new String(data, rpos, len, cs);
rpos += len;
return s;
}
/**
* Reads an SSH string using {@code UTF8}
*
* @return the string as a Java {@code String}
*/
public String readString()
throws BufferException {
return readString(IOUtils.UTF8);
}
/**
* Reads an SSH string
*
* @return the string as a byte-array
*/
public byte[] readStringAsBytes()
throws BufferException {
return readBytes();
}
public T putString(byte[] str) {
return putBytes(str);
}
public T putString(byte[] str, int offset, int len) {
return putBytes(str, offset, len);
}
public T putString(String string, Charset cs) {
return putString(string.getBytes(cs));
}
public T putString(String string) {
return putString(string, IOUtils.UTF8);
}
/**
* Writes a char-array as an SSH string and then blanks it out.
* <p/>
* This is useful when a plaintext password needs to be sent. If {@code str} is {@code null}, an empty string is
* written.
*
* @param str (null-ok) the string as a character array
*
* @return this
*/
@SuppressWarnings("unchecked")
public T putSensitiveString(char[] str) {
if (str == null)
return putString("");
putUInt32(str.length);
ensureCapacity(str.length);
for (char c : str)
data[wpos++] = (byte) c;
Arrays.fill(str, ' ');
return (T) this;
}
public PublicKey readPublicKey()
throws BufferException {
try {
return KeyType.fromString(readString()).readPubKeyFromBuffer(this);
} catch (GeneralSecurityException e) {
throw new SSHRuntimeException(e);
}
}
@SuppressWarnings("unchecked")
public T putPublicKey(PublicKey key) {
KeyType.fromKey(key).putPubKeyIntoBuffer(key, this);
return (T) this;
}
public T putSignature(String sigFormat, byte[] sigData) {
final byte[] sig = new PlainBuffer().putString(sigFormat).putBytes(sigData).getCompactData();
return putString(sig);
}
/**
* Gives a readable snapshot of the buffer in hex. This is useful for debugging.
*
* @return snapshot of the buffer as a hex string with each octet delimited by a space
*/
public String printHex() {
return ByteArrayUtils.printHex(array(), rpos(), available());
}
@Override
public String toString() {
return "Buffer [rpos=" + rpos + ", wpos=" + wpos + ", size=" + data.length + "]";
}
}