Xdr.java
/**
* Copyright 2016-2018 Dell Inc. or its subsidiaries. All rights reserved.
*
* <p>Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file
* except in compliance with the License. A copy of the License is located at
*
* <p>http://www.apache.org/licenses/LICENSE-2.0.txt
*
* <p>or in the "license" file accompanying this file. This file 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 com.emc.ecs.nfsclient.rpc;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
/**
* This class handles the serialization and deserialization of primitive types into and out of a
* buffer, as specified in RFC 1014 (https://tools.ietf.org/html/rfc1014).
*
* @author seibed
*/
public class Xdr {
/** The buffer used to hold XDR data. */
private byte[] _buffer;
/** The size (capacity) of the buffer in bytes. */
private int _size;
/** The current offset of the buffer in bytes. */
private int _offset;
/** The xdr id - used only by the client. */
private int _xid;
/** payload with this XDR */
private List<ByteBuffer> _payloads;
/** The current payload size in bytes. */
private int _payloadsSize;
/**
* Build an Xdr object for serialization.
*
* @param size Size of the buffer in bytes.
*/
public Xdr(int size) {
_buffer = new byte[size];
_size = size;
_offset = 0;
}
/**
* Build an Xdr object for deserialization.
*
* @param data The data for the buffer.
*/
public Xdr(byte[] data) {
_buffer = data.clone();
_size = data.length;
_offset = 0;
}
/**
* Skip a number of bytes. Note that the count is rounded up to the next BLOCK_SIZE.
*
* @param count of the buffer in bytes
*/
public void skip(int count) {
int bytesToPad = getBytesOfPadding(_offset += count);
_offset += bytesToPad;
}
/**
* Return the entire Xdr buffer
*
* @return The Xdr buffer
*/
public byte[] getBuffer() {
return _buffer.clone();
}
/**
* Return the current offset
*
* @return offset into XDR buffer
*/
public int getOffset() {
return _offset;
}
/**
* Set the current offset
*
* @param offset offset into XDR buffer
*/
public void setOffset(int offset) {
_offset = offset;
}
/**
* Return the current size of the XDR buffer
*
* @return size in bytes
*/
public int getSize() {
return _size;
}
/**
* Set the current size of the XDR buffer
*
* @param size Size of the buffer in bytes
*/
public void setSize(int size) {
_size = size;
}
/**
* Get an integer from the buffer
*
* @return integer
*/
public int getInt() {
return ((_buffer[_offset++] & 0xff) << 24
| (_buffer[_offset++] & 0xff) << 16
| (_buffer[_offset++] & 0xff) << 8
| (_buffer[_offset++] & 0xff));
}
/**
* Put an integer into the buffer
*
* @param i Integer to store in XDR buffer.
*/
public void putInt(int i) {
_buffer[_offset++] = (byte) (i >>> 24);
_buffer[_offset++] = (byte) (i >> 16);
_buffer[_offset++] = (byte) (i >> 8);
_buffer[_offset++] = (byte) i;
}
/**
* Get an unsigned integer from the buffer Note that Java has no unsigned integer type so we must
* return it as a long.
*
* @return long
*/
public long getUnsignedInt() {
return Integer.toUnsignedLong(getInt());
}
/**
* Put an unsigned integer into the buffer Note that Java has no unsigned integer type so we must
* pass it as a long.
*
* @param i unsigned integer to store in XDR buffer.
*/
public void putUnsignedInt(long i) {
_buffer[_offset++] = (byte) (i >>> 24 & 0xff);
_buffer[_offset++] = (byte) (i >> 16);
_buffer[_offset++] = (byte) (i >> 8);
_buffer[_offset++] = (byte) i;
}
/**
* Get a long from the buffer
*
* @return long
*/
public long getLong() {
return ((long) (_buffer[_offset++] & 0xff) << 56
| (long) (_buffer[_offset++] & 0xff) << 48
| (long) (_buffer[_offset++] & 0xff) << 40
| (long) (_buffer[_offset++] & 0xff) << 32
| (long) (_buffer[_offset++] & 0xff) << 24
| (long) (_buffer[_offset++] & 0xff) << 16
| (long) (_buffer[_offset++] & 0xff) << 8
| (long) (_buffer[_offset++] & 0xff));
}
/**
* Put a long into the buffer
*
* @param i long to store in XDR buffer
*/
public void putLong(long i) {
_buffer[_offset++] = (byte) (i >>> 56);
_buffer[_offset++] = (byte) ((i >> 48) & 0xff);
_buffer[_offset++] = (byte) ((i >> 40) & 0xff);
_buffer[_offset++] = (byte) ((i >> 32) & 0xff);
_buffer[_offset++] = (byte) ((i >> 24) & 0xff);
_buffer[_offset++] = (byte) ((i >> 16) & 0xff);
_buffer[_offset++] = (byte) ((i >> 8) & 0xff);
_buffer[_offset++] = (byte) (i & 0xff);
}
/*
* Note: we have no XDR routines for encoding/decoding unsigned longs. They
* exist in XDR but not in Java hence we cannot represent them. Best just to
* use xdr_hyper() and hope the sign bit is not used.
*/
/**
* Get a boolean from the buffer
*
* @return boolean
*/
public boolean getBoolean() {
return (getInt() != 0);
}
/**
* Put a boolean into the buffer
*
* @param b boolean
*/
public void putBoolean(boolean b) {
putInt(b ? 1 : 0);
}
/**
* Get a floating point number from the buffer
*
* @return float
*/
public float getFloat() {
return Float.intBitsToFloat(getInt());
}
/**
* Put a floating point number into the buffer
*
* @param f float
*/
public void putFloat(float f) {
putInt(Float.floatToIntBits(f));
}
/**
* Get a string from the buffer
*
* @return string
*/
public String getString() {
int len = getInt();
String s = new String(_buffer, _offset, len, RpcRequest.CHARSET);
skip(len);
return s;
}
/**
* Put a string into the buffer
*
* @param s string
*/
public void putString(String s) {
putByteArray(s.getBytes(RpcRequest.CHARSET));
}
/**
* Get a counted array of bytes from the buffer
*
* @return bytes
*/
public byte[] getByteArray() {
int lengthToCopy = getInt();
byte[] byteArray = (lengthToCopy == 0) ? null : new byte[lengthToCopy];
getBytes(lengthToCopy, byteArray, 0);
return byteArray;
}
/**
* Get bytes from the xdr buffer to the input buffer
*
* @param lengthToCopy Number of bytes to copy.
* @param copyArray Array to hold the copied data.
* @param copyOffset Where to start the copy.
*/
public void getBytes(int lengthToCopy, byte[] copyArray, int copyOffset) {
if (lengthToCopy > 0) {
System.arraycopy(_buffer, _offset, copyArray, copyOffset, lengthToCopy);
skip(lengthToCopy);
}
}
/**
* Put a counted array of bytes into the buffer. Note that the entire byte array is encoded.
*
* @param b byte array
*/
public void putByteArray(byte[] b) {
putByteArray(b, 0, b.length);
}
/**
* Put a counted array of bytes into the buffer
*
* @param b byte array
* @param len number of bytes to encode
*/
public void putByteArray(byte[] b, int len) {
putByteArray(b, 0, len);
}
/**
* Put a counted array of bytes into the buffer
*
* @param b byte array
* @param boff offset into byte array
* @param len number of bytes to encode
*/
public void putByteArray(byte[] b, int boff, int len) {
putInt(len);
putBytes(b, boff, len);
}
/**
* Put an Xdr buffer into the buffer This is used to encode the RPC credentials
*
* @param x XDR buffer
*/
public void putByteArray(Xdr x) {
putByteArray(x.getBuffer(), x.getOffset());
}
/**
* Put a counted array of bytes into the buffer. The length is not encoded.
*
* @param b byte array
* @param boff offset into byte array
* @param len number of bytes to encode
*/
public void putBytes(byte[] b, int boff, int len) {
System.arraycopy(b, boff, _buffer, _offset, len);
skip(len);
}
/** Returns the xid. */
public int getXid() {
return _xid;
}
/** Set xid */
public void setXid(int xid) {
_xid = xid;
}
/** add payloads, more than one can be added. */
public void putPayloads(List<ByteBuffer> payloads, int size) {
putInt(size);
if (_payloads == null) {
_payloads = new ArrayList<>(payloads);
} else {
_payloads.addAll(payloads);
}
_payloadsSize += size;
}
/** Returns the terminated list of payloads, properly padded and ready for sending. */
public List<ByteBuffer> getPayloads() {
ByteBuffer terminalPadding = getTerminalPadding();
if (terminalPadding != null) {
List<ByteBuffer> paddingList = new ArrayList<ByteBuffer>(1);
paddingList.add(terminalPadding);
putPayloads(paddingList, terminalPadding.remaining());
}
return _payloads == null ? null : new ArrayList<>(_payloads);
}
/**
* The representation of all items requires a multiple of four bytes (or 32 bits) of data. The
* bytes are numbered 0 through n-1. The bytes are read or written to some byte stream such that
* byte m always precedes byte m+1. If the n bytes needed to contain the data are not a multiple
* of four, then the n bytes are followed by enough (0 to 3) residual zero bytes, r, to make the
* total byte count a multiple of 4.
*/
private static int BLOCK_SIZE = 4;
/** The terminal padding - to avoid needing to reallocate this many times. */
private static final byte[] MAXIMUM_TERMINAL_PADDING = new byte[BLOCK_SIZE];
/**
* The representation of all items requires a multiple of four bytes (or 32 bits) of data. The
* bytes are numbered 0 through n-1. The bytes are read or written to some byte stream such that
* byte m always precedes byte m+1. If the n bytes needed to contain the data are not a multiple
* of four, then the n bytes are followed by enough (0 to 3) residual zero bytes, r, to make the
* total byte count a multiple of 4.
*
* <p>Package access used to allow testing.
*
* @return The proper amount of terminal padding.
*/
ByteBuffer getTerminalPadding() {
int bytesOfPadding = getBytesOfPadding(_offset + _payloadsSize);
if (bytesOfPadding == 0) {
return null;
} else {
return ByteBuffer.wrap(MAXIMUM_TERMINAL_PADDING, 0, bytesOfPadding);
}
}
/**
* The representation of each item requires a multiple of four bytes (or 32 bits) of data. The
* bytes are numbered 0 through n-1. The bytes are read or written to some byte stream such that
* byte m always precedes byte m+1. If the n bytes needed to contain the data are not a multiple
* of four, then the n bytes are followed by enough (0 to 3) residual zero bytes, r, to make the
* total byte count a multiple of 4.
*
* @param currentBytes The current number of bytes in the structure.
* @return The number of bytes of padding needed.
*/
private static int getBytesOfPadding(int currentBytes) {
int bytesOverLastXdrBlock = currentBytes % BLOCK_SIZE;
return (BLOCK_SIZE - bytesOverLastXdrBlock) % BLOCK_SIZE;
}
}