org.acplt.oncrpc.XdrTcpDecodingStream Maven / Gradle / Ivy
Show all versions of remotetea-oncrpc Show documentation
/*
* $Header: /home/harald/repos/remotetea.sf.net/remotetea/src/org/acplt/oncrpc/XdrTcpDecodingStream.java,v 1.2 2005/11/11 21:07:27 haraldalbrecht Exp $
*
* Copyright (c) 1999, 2000
* Lehrstuhl fuer Prozessleittechnik (PLT), RWTH Aachen
* D-52064 Aachen, Germany.
* All rights reserved.
*
* 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 LICENSE.txt for more
* details); if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.acplt.oncrpc;
import java.io.*;
import java.net.*;
/**
* The XdrTcpDecodingStream
class provides the necessary
* functionality to {@link XdrDecodingStream} to receive XDR records from the
* network using the stream-oriented TCP/IP.
*
* @version $Revision: 1.2 $ $Date: 2005/11/11 21:07:27 $ $State: Exp $ $Locker: $
* @author Harald Albrecht
*/
public class XdrTcpDecodingStream extends XdrDecodingStream {
/**
* Construct a new XdrTcpDecodingStream
object and associate
* it with the given streamingSocket
for TCP/IP-based
* communication.
*
* @param streamingSocket Socket from which XDR data is received.
* @param bufferSize Size of packet buffer for storing received XDR
* data.
*
* @throws IOException if the passed socket instance fails to provide an input stream.
*/
public XdrTcpDecodingStream(Socket streamingSocket,
int bufferSize)
throws IOException {
socket = streamingSocket;
stream = socket.getInputStream();
//
// If the given buffer size is too small, start with a more sensible
// size. Next, if bufferSize is not a multiple of four, round it up to
// the next multiple of four.
//
if ( bufferSize < 1024 ) {
bufferSize = 1024;
}
if ( (bufferSize & 3) != 0 ) {
bufferSize = (bufferSize + 4) & ~3;
}
//
// Set up the buffer and the buffer pointers (no, this is still
// Java).
//
buffer = new byte[bufferSize];
bufferIndex = 0;
bufferHighmark = -4;
lastFragment = false;
fragmentLength = 0;
}
/**
* Returns the Internet address of the sender of the current XDR data.
* This method should only be called after {@link #beginDecoding}, otherwise
* it might return stale information.
*
* @return InetAddress of the sender of the current XDR data.
*/
public InetAddress getSenderAddress() {
return socket.getInetAddress();
}
/**
* Returns the port number of the sender of the current XDR data.
* This method should only be called after {@link #beginDecoding}, otherwise
* it might return stale information.
*
* @return Port number of the sender of the current XDR data.
*/
public int getSenderPort() {
return socket.getPort();
}
/**
* Initiates decoding of the next XDR record. For TCP-based XDR decoding
* streams this reads in the next chunk of data from the network socket
* (a chunk of data is not necessary the same as a fragment, just enough
* to fill the internal buffer or receive the remaining part of a fragment).
*
* Read in the next bunch of bytes. This can be either a complete fragment,
* or if the fragments sent by the communication partner are too large for
* our buffer, only parts of fragments. In every case, this method ensures
* that there will be more data available in the buffer (or else an
* exception thrown).
*
* @throws OncRpcException if an ONC/RPC error occurs.
* @throws IOException if an I/O error occurs.
*/
public void beginDecoding()
throws OncRpcException, IOException {
fill();
}
/**
* Read into buffer exactly the amound of bytes specified.
*
* @param stream Input stream to read byte data from.
* @param bytes buffer receiving data.
* @param bytesToRead number of bytes to read into buffer.
*
* @throws OncRpcException if EOF is reached before all bytes could
* be read.
* @throws IOException if an I/O error occurs.
*/
private void readBuffer(InputStream stream, byte [] bytes, int bytesToRead)
throws IOException, OncRpcException {
int bytesRead;
int byteOffset = 0;
while ( bytesToRead > 0 ) {
bytesRead = stream.read(bytes, byteOffset, bytesToRead);
if ( bytesRead <= 0 ) {
//
// Stream is at EOF -- note that bytesRead is not allowed
// to be zero here, as we asked for at least one byte...
//
throw(new OncRpcException(OncRpcException.RPC_CANTRECV));
}
bytesToRead -= bytesRead;
byteOffset += bytesRead;
}
}
/**
* Fills the internal buffer with the next chunk of data. The chunk is
* either as long as the buffer or as long as the remaining part of the
* current XDR fragment, whichever is smaller.
*
*
This method does not accept empty XDR record fragments with the
* only exception of a final trailing empty fragment. This special case
* is accepted as some ONC/RPC implementations emit such trailing
* empty fragments whenever the encoded data is a full multiple of their
* internal record buffer size.
*
* @throws OncRpcException if an ONC/RPC error occurs.
* @throws IOException if an I/O error occurs.
*/
private void fill()
throws OncRpcException, IOException {
//
// If the buffer is empty but there are still bytes left to read,
// refill the buffer. We have also to take care of the record marking
// within the stream.
//
// Remember that lastFragment is reset by the endDecoding() method.
// This once used to be a while loop, but it has been dropped since
// we do not accept empty records any more -- with the only exception
// being a final trailing empty XDR record.
//
// Did we already read in all data belonging to the current XDR
// record, or are there bytes left to be read?
//
if ( fragmentLength <= 0 ) {
if ( lastFragment ) {
//
// In case there is no more data in the current XDR record
// (as we already saw the last fragment), throw an exception.
//
throw(new OncRpcException(OncRpcException.RPC_BUFFERUNDERFLOW));
}
//
// First read in the header of the next fragment.
//
byte bytes[] = new byte[4];
readBuffer(stream, bytes, 4);
//
// Watch the sign bit!
//
fragmentLength = bytes[0] & 0xFF;
fragmentLength = (fragmentLength << 8) + (bytes[1] & 0xFF);
fragmentLength = (fragmentLength << 8) + (bytes[2] & 0xFF);
fragmentLength = (fragmentLength << 8) + (bytes[3] & 0xFF);
if ( (fragmentLength & 0x80000000) != 0 ) {
fragmentLength &= 0x7FFFFFFF;
lastFragment = true;
} else {
lastFragment = false;
}
//
// Sanity check on incomming fragment length: the length must
// be at least four bytes long, otherwise this fragment does
// not make sense. There are ONC/RPC implementations that send
// empty trailing fragments, so we accept them here.
// Also check for fragment lengths which are not a multiple of
// four -- and thus are invalid.
//
if ( (fragmentLength & 3) != 0 ) {
throw(new IOException("ONC/RPC XDR fragment length is not a multiple of four"));
}
if ( (fragmentLength == 0) && !lastFragment ) {
throw(new IOException("empty ONC/RPC XDR fragment which is not a trailing fragment"));
}
}
//
// When the reach this stage, there is (still) data to be read for the
// current XDR record *fragment*.
//
// Now read in the next buffer. Depending on how much bytes are still
// to read within this frame, we either fill the buffer not completely
// (with still some bytes to read in from the next round) or
// completely.
//
bufferIndex = 0;
if ( fragmentLength < buffer.length ) {
readBuffer(stream, buffer, fragmentLength);
bufferHighmark = fragmentLength - 4;
fragmentLength = 0;
} else {
readBuffer(stream, buffer, buffer.length);
bufferHighmark = buffer.length - 4;
fragmentLength -= buffer.length;
}
}
/**
* End decoding of the current XDR record. The general contract of
* endDecoding
is that calling it is an indication that
* the current record is no more interesting to the caller and any
* allocated data for this record can be freed.
*
*
This method overrides {@link XdrDecodingStream#endDecoding}. It reads in
* and throws away fragments until it reaches the last fragment.
*
* @throws OncRpcException if an ONC/RPC error occurs.
* @throws IOException if an I/O error occurs.
*/
public void endDecoding()
throws OncRpcException, IOException {
try {
//
// Drain the stream until we reach the end of the current record.
//
while ( !lastFragment || (fragmentLength != 0) ) {
fill();
}
} finally {
//
// Try to reach a sane state, although this is rather questionable
// in case of timeouts in the middle of a record.
//
bufferIndex = 0;
bufferHighmark = -4;
lastFragment = false;
fragmentLength = 0;
}
}
/**
* Closes this decoding XDR stream and releases any system resources
* associated with this stream. A closed XDR stream cannot perform decoding
* operations and cannot be reopened.
*
*
This implementation frees the allocated buffer but does not close
* the associated datagram socket. It only throws away the reference to
* this socket.
*
* @throws OncRpcException if an ONC/RPC error occurs.
* @throws IOException if an I/O error occurs.
*/
public void close()
throws OncRpcException, IOException {
buffer = null;
stream = null;
socket = null;
}
/**
* 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.
*
* @return The decoded int value.
*
* @throws OncRpcException if an ONC/RPC error occurs.
* @throws IOException if an I/O error occurs.
*/
public int xdrDecodeInt()
throws OncRpcException, IOException {
//
// This might look funny in the first place, but this way we can
// properly handle trailing empty XDR record fragments. In this
// case fill() will return without any now data the first time
// and on the second time a buffer underflow exception is thrown.
//
while ( bufferIndex > bufferHighmark ) {
fill();
}
//
// There's enough space in the buffer to hold at least one
// XDR int. So let's retrieve it now.
// Note: buffer[...] gives a byte, which is signed. So if we
// add it to the value (which is int), it has to be widened
// to 32 bit, so its sign is propagated. To avoid this sign
// madness, we have to "and" it with 0xFF, so all unwanted
// bits are cut off after sign extension. Sigh.
//
int value = buffer[bufferIndex++] & 0xFF;
value = (value << 8) + (buffer[bufferIndex++] & 0xFF);
value = (value << 8) + (buffer[bufferIndex++] & 0xFF);
value = (value << 8) + (buffer[bufferIndex++] & 0xFF);
return value;
}
/**
* Decodes (aka "deserializes") an opaque value, which is nothing more
* than a series of octets (or 8 bits wide bytes). Because the length
* of the opaque value is given, we don't need to retrieve it from the
* XDR stream. This is different from
* {@link #xdrDecodeOpaque(byte[], int, int)} where
* first the length of the opaque value is retrieved from the XDR stream.
*
* @param length Length of opaque data to decode.
*
* @return Opaque data as a byte vector.
*
* @throws OncRpcException if an ONC/RPC error occurs.
* @throws IOException if an I/O error occurs.
*/
public byte [] xdrDecodeOpaque(int length)
throws OncRpcException, IOException {
int padding = (4 - (length & 3)) & 3;
int offset = 0; // current offset into bytes vector
int toCopy;
//
// Now allocate enough memory to hold the data to be retrieved and
// get part after part from the buffer.
//
byte [] bytes = new byte[length];
//
// As for the while loop, see the comment in xdrDecodeInt().
//
while ( bufferIndex > bufferHighmark ) {
fill();
}
while ( length > 0 ) {
toCopy = bufferHighmark - bufferIndex + 4;
if ( toCopy >= length ) {
//
// The buffer holds more data than we need. So copy the bytes
// and leave the stage.
//
System.arraycopy(buffer, bufferIndex, bytes, offset, length);
bufferIndex += length;
// No need to adjust "offset", because this is the last round.
break;
} else {
//
// We need to copy more data than currently available from our
// buffer, so we copy all we can get our hands on, then fill
// the buffer again and repeat this until we got all we want.
//
System.arraycopy(buffer, bufferIndex, bytes, offset, toCopy);
bufferIndex += toCopy;
offset += toCopy;
length -= toCopy;
// NB: no problems with trailing empty fragments, so we skip
// the while loop here.
fill();
}
}
bufferIndex += padding;
return bytes;
}
/**
* Decodes (aka "deserializes") a XDR opaque value, which is represented
* by a vector of byte values, and starts at offset
with a
* length of length
. Only the opaque value is decoded, so the
* caller has to know how long the opaque value will be. The decoded data
* is always padded to be a multiple of four (because that's what the
* sender does).
*
* @param opaque Byte vector which will receive the decoded opaque value.
* @param offset Start offset in the byte vector.
* @param length the number of bytes to decode.
*
* @throws OncRpcException if an ONC/RPC error occurs.
* @throws IOException if an I/O error occurs.
*/
public void xdrDecodeOpaque(byte [] opaque, int offset, int length)
throws OncRpcException, IOException {
int padding = (4 - (length & 3)) & 3;
int toCopy;
//
// Now get part after part and fill the byte vector.
//
if ( bufferIndex > bufferHighmark ) {
fill();
}
while ( length > 0 ) {
toCopy = bufferHighmark - bufferIndex + 4;
if ( toCopy >= length ) {
//
// The buffer holds more data than we need. So copy the bytes
// and leave the stage.
//
System.arraycopy(buffer, bufferIndex, opaque, offset, length);
bufferIndex += length;
// No need to adjust "offset", because this is the last round.
break;
} else {
//
// We need to copy more data than currently available from our
// buffer, so we copy all we can get our hands on, then fill
// the buffer again and repeat this until we got all we want.
//
System.arraycopy(buffer, bufferIndex, opaque, offset, toCopy);
bufferIndex += toCopy;
offset += toCopy;
length -= toCopy;
fill();
}
}
bufferIndex += padding;
}
/**
* The streaming socket to be used when receiving this XDR stream's
* buffer contents.
*/
private Socket socket;
/**
* The input stream used to pull the bytes off the network.
*/
InputStream stream;
/**
* The buffer which will be filled from the datagram socket and then
* be used to supply the information when decoding data.
*/
private byte [] buffer;
/**
* The read pointer is an index into the buffer
.
*/
private int bufferIndex;
/**
* Index of the last four byte word in the buffer, which has been read
* in from the datagram socket.
*/
private int bufferHighmark;
/**
* Remaining number of bytes in this fragment -- and still to read.
*/
private int fragmentLength;
/**
* Flag indicating that we've read the last fragment and thus reached
* the end of the record.
*/
private boolean lastFragment;
}
// End of XdrTcpDecodingStream