/*
* Copyright (c) 2009 - 2016 Deutsches Elektronen-Synchroton,
* Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this program (see the file COPYING.LIB for more
* details); if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.dcache.xdr;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import org.glassfish.grizzly.Buffer;
public class Xdr implements XdrDecodingStream, XdrEncodingStream {
/**
* Maximal size of a XDR message.
*/
public final static int MAX_XDR_SIZE = 512 * 1024;
/**
* Initial size of a freshly-allocated Xdr message
*/
public final static int INITIAL_XDR_SIZE = 1024;
/**
* Byte buffer used by XDR record.
*/
protected volatile Buffer _buffer;
/**
* Create a new Xdr object with a buffer of given size.
*
* @param size of the buffer in bytes
*/
public Xdr(int size) {
this(GrizzlyMemoryManager.allocate(size));
}
/**
* Create a new XDR back ended with given {@link ByteBuffer}.
* @param body buffer to use
*/
public Xdr(Buffer body) {
_buffer = body;
_buffer.order(ByteOrder.BIG_ENDIAN);
}
@Override
public void beginDecoding() {
/*
* Set potision to the beginning of this XDR in back end buffer.
*/
_buffer.rewind();
}
@Override
public void endDecoding() {
_buffer.rewind();
}
@Override
public void beginEncoding() {
_buffer.clear();
}
@Override
public void endEncoding() {
_buffer.flip();
}
/**
* Tells whether there are any data available in the stream.
* @return true if, and only if, there is data available in the stream
*/
public boolean hasMoreData() {
return _buffer.hasRemaining();
}
/**
* Decodes (aka "deserializes") a "XDR int" value received from a
* XDR stream. A XDR int is 32 bits wide -- the same width Java's "int"
* data type has. This method is one of the basic methods all other
* methods can rely on. Because it's so basic, derived classes have to
* implement it.
*
* @return The decoded int value.
* @throws BadXdrOncRpcException if xdr stream can't be decoded.
*/
@Override
public int xdrDecodeInt() throws BadXdrOncRpcException {
ensureBytes(Integer.BYTES);
int val = _buffer.getInt();
return val;
}
/**
* Get next array of integers.
*
* @return the array on integers
* @throws BadXdrOncRpcException if xdr stream can't be decoded.
*/
@Override
public int[] xdrDecodeIntVector() throws BadXdrOncRpcException {
int len = xdrDecodeInt();
checkArraySize(len);
int[] ints = new int[len];
for (int i = 0; i < len; i++) {
ints[i] = xdrDecodeInt();
}
return ints;
}
/**
* Get next array of long.
*
* @return the array on integers
* @throws BadXdrOncRpcException if xdr stream can't be decoded.
*/
@Override
public long[] xdrDecodeLongVector() throws BadXdrOncRpcException {
int len = xdrDecodeInt();
checkArraySize(len);
long[] longs = new long[len];
for (int i = 0; i < len; i++) {
longs[i] = xdrDecodeLong();
}
return longs;
}
/**
* Decodes (aka "deserializes") a float (which is a 32 bits wide floating
* point entity) read from a XDR stream.
*
* @return Decoded float value.rs.
* @throws BadXdrOncRpcException if xdr stream can't be decoded.
*/
@Override
public float xdrDecodeFloat() throws BadXdrOncRpcException {
return Float.intBitsToFloat(xdrDecodeInt());
}
/**
* Decodes (aka "deserializes") a double (which is a 64 bits wide floating
* point entity) read from a XDR stream.
*
* @return Decoded double value.rs.
* @throws BadXdrOncRpcException if xdr stream can't be decoded.
*/
@Override
public double xdrDecodeDouble() throws BadXdrOncRpcException {
return Double.longBitsToDouble(xdrDecodeLong());
}
/**
* Decodes (aka "deserializes") a vector of doubles read from a XDR stream.
*
* @return Decoded double vector.
* @throws BadXdrOncRpcException if xdr stream can't be decoded.
*/
@Override
public double[] xdrDecodeDoubleVector() throws BadXdrOncRpcException {
int length = xdrDecodeInt();
checkArraySize(length);
return xdrDecodeDoubleFixedVector(length);
}
/**
* Decodes (aka "deserializes") a vector of doubles read from a XDR stream.
*
* @param length of vector to read.
*
* @return Decoded double vector.
* @throws BadXdrOncRpcException if xdr stream can't be decoded.
*/
@Override
public double[] xdrDecodeDoubleFixedVector(int length) throws BadXdrOncRpcException {
double[] value = new double[length];
for (int i = 0; i < length; ++i) {
value[i] = xdrDecodeDouble();
}
return value;
}
/**
* Decodes (aka "deserializes") a vector of floats read from a XDR stream.
*
* @return Decoded float vector.
* @throws BadXdrOncRpcException if xdr stream can't be decoded.
*/
@Override
public float[] xdrDecodeFloatVector() throws BadXdrOncRpcException {
int length = xdrDecodeInt();
checkArraySize(length);
return xdrDecodeFloatFixedVector(length);
}
/**
* Decodes (aka "deserializes") a vector of floats read from a XDR stream.
*
* @param length of vector to read.
*
* @return Decoded float vector.
* @throws BadXdrOncRpcException if xdr stream can't be decoded.
*/
@Override
public float[] xdrDecodeFloatFixedVector(int length) throws BadXdrOncRpcException {
float[] value = new float[length];
for (int i = 0; i < length; ++i) {
value[i] = xdrDecodeFloat();
}
return value;
}
/**
* Get next opaque data. The decoded data
* is always padded to be a multiple of four.
*
* @param buf buffer where date have to be stored
* @param offset in the buffer.
* @param len number of bytes to read.
* @throws BadXdrOncRpcException if xdr stream can't be decoded.
*/
@Override
public void xdrDecodeOpaque(byte[] buf, int offset, int len) throws BadXdrOncRpcException {
int padding = (4 - (len & 3)) & 3;
ensureBytes(len + padding);
_buffer.get(buf, offset, len);
_buffer.position(_buffer.position() + padding);
}
public void xdrDecodeOpaque(byte[] buf, int len) throws BadXdrOncRpcException {
xdrDecodeOpaque(buf, 0, len);
}
@Override
public byte[] xdrDecodeOpaque(int len) throws BadXdrOncRpcException {
byte[] opaque = new byte[len];
xdrDecodeOpaque(opaque, len);
return opaque;
}
/**
* Decodes (aka "deserializes") a XDR opaque value, which is represented
* by a vector of byte values. The length of the opaque value to decode
* is pulled off of the XDR stream, so the caller does not need to know
* the exact length in advance. The decoded data is always padded to be
* a multiple of four (because that's what the sender does).
* @throws BadXdrOncRpcException if xdr stream can't be decoded.
*/
@Override
public byte [] xdrDecodeDynamicOpaque() throws BadXdrOncRpcException {
int length = xdrDecodeInt();
checkArraySize(length);
byte [] opaque = new byte[length];
if ( length != 0 ) {
xdrDecodeOpaque(opaque, 0, length);
}
return opaque;
}
/**
* Get next String.
*
* @return decoded string
* @throws BadXdrOncRpcException if xdr stream can't be decoded.
*/
@Override
public String xdrDecodeString() throws BadXdrOncRpcException {
int len = xdrDecodeInt();
checkArraySize(len);
byte[] bytes = new byte[len];
xdrDecodeOpaque(bytes, 0, len);
return new String(bytes, StandardCharsets.UTF_8);
}
@Override
public boolean xdrDecodeBoolean() throws BadXdrOncRpcException {
int bool = xdrDecodeInt();
return bool != 0;
}
/**
* Decodes (aka "deserializes") a long (which is called a "hyper" in XDR
* babble and is 64 bits wide) read from a XDR stream.
*
* @return Decoded long value.
* @throws BadXdrOncRpcException if xdr stream can't be decoded.
*/
@Override
public long xdrDecodeLong() throws BadXdrOncRpcException {
ensureBytes(Long.BYTES);
return _buffer.getLong();
}
@Override
public ByteBuffer xdrDecodeByteBuffer() throws BadXdrOncRpcException {
int len = this.xdrDecodeInt();
checkArraySize(len);
int padding = (4 - (len & 3)) & 3;
ensureBytes(len + padding);
/*
* as of grizzly 2.2.1 toByteBuffer returns a ByteBuffer view of
* the backended heap. To be able to use rewind, flip and so on
* we have to use slice of it.
*/
ByteBuffer slice = _buffer.toByteBuffer().slice();
slice.rewind();
slice.limit(len);
_buffer.position(_buffer.position() + len + padding);
return slice;
}
/**
* Decodes (aka "deserializes") a vector of bytes, which is nothing more
* than a series of octets (or 8 bits wide bytes), each packed into its very
* own 4 bytes (XDR int). Byte vectors are decoded together with a
* preceeding length value. This way the receiver doesn't need to know the
* length of the vector in advance.
*
* @return The byte vector containing the decoded data.
* @throws BadXdrOncRpcException if xdr stream can't be decoded.
*/
@Override
public byte[] xdrDecodeByteVector() throws BadXdrOncRpcException {
int length = xdrDecodeInt();
checkArraySize(length);
return xdrDecodeByteFixedVector(length);
}
/**
* Decodes (aka "deserializes") a vector of bytes, which is nothing more
* than a series of octets (or 8 bits wide bytes), each packed into its very
* own 4 bytes (XDR int).
*
* @param length of vector to read.
*
* @return The byte vector containing the decoded data.
* @throws BadXdrOncRpcException if xdr stream can't be decoded.
*/
@Override
public byte[] xdrDecodeByteFixedVector(int length) throws BadXdrOncRpcException {
byte[] bytes = new byte[length];
for (int i = 0; i < length; ++i) {
bytes[i] = (byte) xdrDecodeInt();
}
return bytes;
}
/**
* Decodes (aka "deserializes") a byte read from this XDR stream.
*
* @return Decoded byte value.
* @throws BadXdrOncRpcException if xdr stream can't be decoded.
*/
@Override
public byte xdrDecodeByte() throws BadXdrOncRpcException {
return (byte) xdrDecodeInt();
}
/**
* Decodes (aka "deserializes") a short (which is a 16 bit quantity) read
* from this XDR stream.
*
* @return Decoded short value.
* @throws BadXdrOncRpcException if xdr stream can't be decoded.
*/
@Override
public short xdrDecodeShort() throws BadXdrOncRpcException {
return (short) xdrDecodeInt();
}
/**
* Decodes (aka "deserializes") a vector of short integers read from a XDR
* stream.
*
* @return Decoded vector of short integers.
* @throws BadXdrOncRpcException if xdr stream can't be decoded.
*/
@Override
public short[] xdrDecodeShortVector() throws BadXdrOncRpcException {
int length = xdrDecodeInt();
checkArraySize(length);
return xdrDecodeShortFixedVector(length);
}
/**
* Decodes (aka "deserializes") a vector of short integers read from a XDR
* stream.
*
* @param length of vector to read.
*
* @return Decoded vector of short integers.
* @throws BadXdrOncRpcException if xdr stream can't be decoded.
*/
@Override
public short[] xdrDecodeShortFixedVector(int length) throws BadXdrOncRpcException {
short[] value = new short[length];
for (int i = 0; i < length; ++i) {
value[i] = xdrDecodeShort();
}
return value;
}
////////////////////////////////////////////////////////////////////////////
//
// Encoder
//
////////////////////////////////////////////////////////////////////////////
/**
* Encodes (aka "serializes") a "XDR int" value and writes it down a
* XDR stream. A XDR int is 32 bits wide -- the same width Java's "int"
* data type has. This method is one of the basic methods all other
* methods can rely on.
*/
@Override
public void xdrEncodeInt(int value) {
ensureCapacity(Integer.BYTES);
_buffer.putInt(value);
}
/**
* Returns the {@link Buffer} that backs this xdr.
*
* <p>Modifications to this xdr's content will cause the returned
* buffer's content to be modified, and vice versa.
*
* @return The {@link Buffer} that backs this xdr
*/
public Buffer asBuffer() {
return _buffer;
}
/**
* Encodes (aka "serializes") a vector of ints and writes it down
* this XDR stream.
*
* @param values int vector to be encoded.
*
*/
@Override
public void xdrEncodeIntVector(int[] values) {
ensureCapacity(Integer.BYTES+Integer.BYTES*values.length);
_buffer.putInt(values.length);
for (int value: values) {
_buffer.putInt( value );
}
}
/**
* Encodes (aka "serializes") a vector of longs and writes it down
* this XDR stream.
*
* @param values long vector to be encoded.
*
*/
@Override
public void xdrEncodeLongVector(long[] values) {
ensureCapacity(Integer.BYTES+Long.BYTES*values.length);
_buffer.putInt(values.length);
for (long value : values) {
_buffer.putLong(value);
}
}
/**
* Encodes (aka "serializes") a float (which is a 32 bits wide floating
* point quantity) and write it down this XDR stream.
*
* @param value Float value to encode.
*/
@Override
public void xdrEncodeFloat(float value) {
xdrEncodeInt(Float.floatToIntBits(value));
}
/**
* Encodes (aka "serializes") a double (which is a 64 bits wide floating
* point quantity) and write it down this XDR stream.
*
* @param value Double value to encode.
*/
@Override
public void xdrEncodeDouble(double value) {
xdrEncodeLong(Double.doubleToLongBits(value));
}
/**
* Encodes (aka "serializes") a vector of floats and writes it down this XDR
* stream.
*
* @param value float vector to be encoded.
*/
@Override
public void xdrEncodeFloatVector(float[] value) {
int size = value.length;
xdrEncodeInt(size);
for (int i = 0; i < size; i++) {
xdrEncodeFloat(value[i]);
}
}
/**
* Encodes (aka "serializes") a vector of floats and writes it down this XDR
* stream.
*
* @param value float vector to be encoded.
* @param length of vector to write. This parameter is used as a sanity
* check.
*/
@Override
public void xdrEncodeFloatFixedVector(float[] value, int length) {
if (value.length != length) {
throw (new IllegalArgumentException("array size does not match protocol specification"));
}
for (int i = 0; i < length; i++) {
xdrEncodeFloat(value[i]);
}
}
/**
* Encodes (aka "serializes") a vector of doubles and writes it down this
* XDR stream.
*
* @param value double vector to be encoded.
*/
@Override
public void xdrEncodeDoubleVector(double[] value) {
int size = value.length;
xdrEncodeInt(size);
for (int i = 0; i < size; i++) {
xdrEncodeDouble(value[i]);
}
}
/**
* Encodes (aka "serializes") a vector of doubles and writes it down this
* XDR stream.
*
* @param value double vector to be encoded.
* @param length of vector to write. This parameter is used as a sanity
* check.
*/
@Override
public void xdrEncodeDoubleFixedVector(double[] value, int length) {
if (value.length != length) {
throw (new IllegalArgumentException("array size does not match protocol specification"));
}
for (int i = 0; i < length; i++) {
xdrEncodeDouble(value[i]);
}
}
/**
* Encodes (aka "serializes") a string and writes it down this XDR stream.
*
*/
@Override
public void xdrEncodeString(String string) {
if( string == null ) string = "";
xdrEncodeDynamicOpaque(string.getBytes(StandardCharsets.UTF_8));
}
private static final byte [] paddingZeros = { 0, 0, 0, 0 };
/**
* Encodes (aka "serializes") a XDR opaque value, which is represented
* by a vector of byte values. Only the opaque value is encoded, but
* no length indication is preceeding the opaque value, so the receiver
* has to know how long the opaque value will be. The encoded data is
* always padded to be a multiple of four. If the length of the given byte
* vector is not a multiple of four, zero bytes will be used for padding.
*/
@Override
public void xdrEncodeOpaque(byte[] bytes, int offset, int len) {
int padding = (4 - (len & 3)) & 3;
ensureCapacity(len+padding);
_buffer.put(bytes, offset, len);
_buffer.put(paddingZeros, 0, padding);
}
@Override
public void xdrEncodeOpaque(byte[] bytes, int len) {
xdrEncodeOpaque(bytes, 0, len);
}
/**
* Encodes (aka "serializes") a XDR opaque value, which is represented
* by a vector of byte values. The length of the opaque value is written
* to the XDR stream, so the receiver does not need to know
* the exact length in advance. The encoded data is always padded to be
* a multiple of four to maintain XDR alignment.
*
*/
@Override
public void xdrEncodeDynamicOpaque(byte [] opaque) {
xdrEncodeInt(opaque.length);
xdrEncodeOpaque(opaque, 0, opaque.length);
}
@Override
public void xdrEncodeBoolean(boolean bool) {
xdrEncodeInt( bool ? 1 : 0);
}
/**
* Encodes (aka "serializes") a long (which is called a "hyper" in XDR
* babble and is 64 bits wide) and write it down this XDR stream.
*/
@Override
public void xdrEncodeLong(long value) {
ensureCapacity(Long.BYTES);
_buffer.putLong(value);
}
@Override
public void xdrEncodeByteBuffer(ByteBuffer buf) {
buf.flip();
int len = buf.remaining();
int padding = (4 - (len & 3)) & 3;
xdrEncodeInt(len);
ensureCapacity(len+padding);
_buffer.put(buf);
_buffer.position(_buffer.position() + padding);
}
/**
* Encodes (aka "serializes") a vector of bytes, which is nothing more than
* a series of octets (or 8 bits wide bytes), each packed into its very own
* 4 bytes (XDR int). Byte vectors are encoded together with a preceeding
* length value. This way the receiver doesn't need to know the length of
* the vector in advance.
*
* @param value Byte vector to encode.
*/
@Override
public void xdrEncodeByteVector(byte[] value) {
int length = value.length; // well, silly optimizations appear here...
xdrEncodeInt(length);
//
// For speed reasons, we do sign extension here, but the higher bits
// will be removed again when deserializing.
//
for (int i = 0; i < length; ++i) {
xdrEncodeInt((int) value[i]);
}
}
/**
* Encodes (aka "serializes") a vector of bytes, which is nothing more than
* a series of octets (or 8 bits wide bytes), each packed into its very own
* 4 bytes (XDR int).
*
* @param value Byte vector to encode.
* @param length of vector to write. This parameter is used as a sanity
* check.
*/
@Override
public void xdrEncodeByteFixedVector(byte[] value, int length) {
if (value.length != length) {
throw (new IllegalArgumentException("array size does not match protocol specification"));
}
//
// For speed reasons, we do sign extension here, but the higher bits
// will be removed again when deserializing.
//
for (int i = 0; i < length; ++i) {
xdrEncodeInt((int) value[i]);
}
}
/**
* Encodes (aka "serializes") a byte and write it down this XDR stream.
*
* @param value Byte value to encode.
*
* @throws OncRpcException if an ONC/RPC error occurs.
* @throws IOException if an I/O error occurs.
*/
@Override
public void xdrEncodeByte(byte value) {
//
// For speed reasons, we do sign extension here, but the higher bits
// will be removed again when deserializing.
//
xdrEncodeInt((int) value);
}
/**
* Encodes (aka "serializes") a short (which is a 16 bits wide quantity) and
* write it down this XDR stream.
*
* @param value Short value to encode.
*/
@Override
public void xdrEncodeShort(short value) {
xdrEncodeInt((int) value);
}
/**
* Encodes (aka "serializes") a vector of short integers and writes it down
* this XDR stream.
*
* @param value short vector to be encoded.
*/
@Override
public void xdrEncodeShortVector(short[] value) {
int size = value.length;
xdrEncodeInt(size);
for (int i = 0; i < size; i++) {
xdrEncodeShort(value[i]);
}
}
/**
* Encodes (aka "serializes") a vector of short integers and writes it down
* this XDR stream.
*
* @param value short vector to be encoded.
* @param length of vector to write. This parameter is used as a sanity
* check.
*/
@Override
public void xdrEncodeShortFixedVector(short[] value, int length) {
if (value.length != length) {
throw (new IllegalArgumentException("array size does not match protocol specification"));
}
for (int i = 0; i < length; i++) {
xdrEncodeShort(value[i]);
}
}
public void close() {
_buffer.tryDispose();
}
private void ensureCapacity(int size) {
if(_buffer.remaining() < size) {
int oldCapacity = _buffer.capacity();
int newCapacity = Math.max((oldCapacity * 3) / 2 + 1, oldCapacity + size);
_buffer = GrizzlyMemoryManager.reallocate(_buffer, newCapacity);
}
}
private void ensureBytes(int size) throws BadXdrOncRpcException {
if (_buffer.remaining() < size) {
throw new BadXdrOncRpcException("xdr stream too short");
}
}
private void checkArraySize(int len) throws BadXdrOncRpcException {
if (len < 0) {
throw new BadXdrOncRpcException("corrupted xdr");
}
}
}