/* * Copyright 2010 netling project <http://netling.org> * * 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. * * This file may incorporate work covered by the following copyright and * permission notice: * * 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.netling.ssh.common; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.PublicKey; import java.util.Arrays; public class Buffer<T extends Buffer<T>> { public static class BufferException extends SSHRuntimeException { public BufferException(String message) { super(message); } } public static 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; protected static int getNextPowerOf2(int i) { int j = 1; while (j < i) j <<= 1; 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) { 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() { 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() { 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() { int len = readInt(); 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 putInt(len - off).putRawBytes(b, off, len); } public void readRawBytes(byte[] buf) { readRawBytes(buf, 0, buf.length); } public void readRawBytes(byte[] buf, int off, int len) { 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 readInt() { return (int) readLong(); } public long readLong() { 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 putInt(long uint32) { ensureCapacity(4); if (uint32 < 0 || uint32 > 0xffffffffL) throw new BufferException("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() { return new BigInteger(readMPIntAsBytes()); } /** * Writes an SSH multiple-precision integer from a {@code BigInteger} * * @param bi {@code BigInteger} to write * * @return this */ public T putMPInt(BigInteger bi) { return putMPInt(bi.toByteArray()); } /** * Writes an SSH multiple-precision integer from a Java byte-array * * @param foo byte-array * * @return this */ public T putMPInt(byte[] foo) { int i = foo.length; if ((foo[0] & 0x80) != 0) { i++; putInt(i); putByte((byte) 0); } else putInt(i); return putRawBytes(foo); } public byte[] readMPIntAsBytes() { return readBytes(); } public long readUINT64() { long uint64 = (readLong() << 32) + (readLong() & 0xffffffffL); if (uint64 < 0) throw new BufferException("Cannot handle values > Long.MAX_VALUE"); return uint64; } @SuppressWarnings("unchecked") public T putUINT64(long uint64) { if (uint64 < 0) throw new BufferException("Invalid value: " + 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 * * @return the string as a Java {@code String} */ public String readString() { int len = readInt(); if (len < 0 || len > 32768) throw new BufferException("Bad item length: " + len); ensureAvailable(len); String s; try { s = new String(data, rpos, len, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new SSHRuntimeException(e); } rpos += len; return s; } /** * Reads an SSH string * * @return the string as a byte-array */ public byte[] readStringAsBytes() { 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) { try { return putString(string.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { throw new SSHRuntimeException(e); } } /** * 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 passwd} is {@code null}, an empty string is * written. * * @param str (null-ok) the password as a character array * * @return this */ @SuppressWarnings("unchecked") public T putSensitiveString(char[] str) { if (str == null) return putString(""); putInt(str.length); ensureCapacity(str.length); for (char c : str) data[wpos++] = (byte) c; Arrays.fill(str, ' '); return (T) this; } public PublicKey readPublicKey() { try { final String type = readString(); return KeyType.fromString(type).readPubKeyFromBuffer(type, 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 + "]"; } }