// // openmapi.org - CompactTeaSharp - XdrTcpEncodingStream.cs // // C# port Copyright 2008 by Topalis AG // // Author (C# port): mazurin, Johannes Roith // // This library is based on the RemoteTea java library: // // Author: Harald Albrecht // // 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 COPYING.LIB for more // details); if not, write to the Free Software Foundation, Inc., // 675 Mass Ave, Cambridge, MA 02139, USA. // using System; using System.IO; using System.Net; using System.Net.Sockets; namespace CompactTeaSharp { /// /// Provides the necessary functionality to XdrEncodingStream to send /// XDR records to the network using TCP/IP. /// public class XdrTcpEncodingStream: XdrEncodingStream { private TcpClient client; // The streaming socket to be used when receiving this XDR stream's buffer contents. private Stream stream; // The output stream used to get rid of bytes going off to the network. private byte [] buffer; // The buffer which will be filled from the datagram socket and then be used to supply the information when decoding data. private Int32 bufferIndex; // The write pointer is an index into the buffer. private Int32 bufferHighmark; // Index of the last four byte word in the buffer. private Int32 bufferFragmentHeaderIndex; // Index of fragment header within buffer. private static byte [] paddingZeros = { 0, 0, 0, 0 }; // Some zeros, only needed for padding. /// /// Construct a new XdrTcpEncodingStream object and associate /// it with the given streamingSocket for TCP/IP-based /// communication. /// /// Socket to which XDR data is sent. /// Size of packet buffer for temporarily storing outgoing XDR data. public XdrTcpEncodingStream (TcpClient client, int bufferSize) { this.client = client; stream = client.GetStream (); // 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]; bufferFragmentHeaderIndex = 0; bufferIndex = 4; bufferHighmark = bufferSize - 4; } /// /// Returns the Internet address of the sender of the current XDR data. /// This method should only be called after {@link #beginEncoding}, /// otherwise it might return stale information. /// /// InetAddress of the sender of the current XDR data. public IPAddress SenderAddress { get { return ((IPEndPoint) client.Client.RemoteEndPoint).Address; } } /// /// Returns the port number of the sender of the current XDR data. /// This method should only be called after {@link #beginEncoding}, /// otherwise it might return stale information. /// /// Port number of the sender of the current XDR data. public int SenderPort { get { return ((IPEndPoint) client.Client.RemoteEndPoint).Port; } } /// /// Begins encoding a new XDR record. This typically involves resetting this /// encoding XDR stream back into a known state. /// /// Port number of the receiver. public override void BeginEncoding (IPAddress receiverAddress, int receiverPort) { // // Begin encoding with the four byte word after the fragment header, // which also four bytes wide. We have to remember where we can find // the fragment header as we support batching/pipelining calls, so // several requests (each in its own fragment) can be simultaneously // in the write buffer. // // bufferFragmentHeaderIndex = bufferIndex; // bufferIndex += 4; } /// /// Flushes this encoding XDR stream and forces any buffered output bytes /// to be written out. The general contract of endEncoding is that /// calling it is an indication that the current record is finished and any /// bytes previously encoded should immediately be written to their intended /// destination. /// public override void EndEncoding () { Flush (true, false); } /// /// Ends the current record fort this encoding XDR stream. If the parameter /// 'flush' is true any buffered output bytes are immediately written to /// their intended destination. Otherwise more than one record can be /// pipelined, for instance, to batch several ONC/RPC calls. In this /// case the ONC/RPC server MUST NOT send a reply (with the exception for the last /// call in a batch, which might be trigger a reply). Otherwise, you will /// most probably cause an interaction deadlock between client and server. /// public void EndEncoding (bool flush) { Flush (true, !flush); } /// /// Flushes the current contents of the buffer as one fragment to the /// network. /// /// true if this is the last fragment of /// the current XDR record. /// if last fragment and batch is /// true, then the buffer is not flushed to the network /// but instead we wait for more records to be encoded. private void Flush (bool lastFragment, bool batch) { // // Encode the fragment header. We have to take batching/pipelining // into account, so multiple complete fragments may be waiting in // the same write buffer. The variable bufferFragmentHeaderIndex // points to the place where we should store this fragment's header. // Int32 fragmentLength = bufferIndex - bufferFragmentHeaderIndex - 4; if (lastFragment) fragmentLength = (int) ((uint)fragmentLength | 0x80000000); buffer [bufferFragmentHeaderIndex] = (byte) (fragmentLength >> 24); buffer [bufferFragmentHeaderIndex + 1] = (byte) (fragmentLength >> 16); buffer [bufferFragmentHeaderIndex + 2] = (byte) (fragmentLength >> 8); buffer [bufferFragmentHeaderIndex + 3] = (byte) fragmentLength; if (!lastFragment // buffer is full, so we have to flush || !batch // buffer not full, but last fragment and not in batch || (bufferIndex >= bufferHighmark) // not enough space for next // fragment header and one int ) { // // Finally write the buffer's contents into the vastness of // network space. This has to be done when we do not need to // pipeline calls and if there is still enough space left in // the buffer for the fragment header and at least a single // int. // stream.Write(buffer, 0, bufferIndex); stream.Flush(); // // Reset write pointer after the fragment header int within // buffer, so the next bunch of data can be encoded. // bufferFragmentHeaderIndex = 0; bufferIndex = 4; } else { // // Batch/pipeline several consecuting XDR records. So do not // flush the buffer yet to the network but instead wait for more // data. // bufferFragmentHeaderIndex = bufferIndex; bufferIndex += 4; } } /// /// Closes this encoding XDR stream and releases any system resources /// associated with this stream. The general contract of close /// is that it closes the encoding XDR stream. A closed XDR stream cannot /// perform encoding operations and cannot be reopened. /// public override void Close () { buffer = null; client = null; } /// /// Encodes a 32 bit "XDR int" value and writes it down a XDR stream. /// public override void XdrEncodeInt (int value) { if (bufferIndex > bufferHighmark) Flush (false, false); // // There's enough space in the buffer, so encode this int as // four bytes in big endian order. // buffer [bufferIndex++] = (byte) (value >> 24); buffer [bufferIndex++] = (byte) (value >> 16); buffer [bufferIndex++] = (byte) (value >> 8); buffer [bufferIndex++] = (byte) value; } /// /// Encodes (aka "serializes") 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 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 given length is not a /// multiple of four, zero bytes will be used for padding. /// public override void XdrEncodeOpaque (byte [] value, int offset, int length) { int padding = (4 - (length & 3)) & 3; int toCopy; while (length > 0) { toCopy = bufferHighmark - bufferIndex + 4; if (toCopy >= length) { // The buffer has more free space than we need ... Array.Copy (value, offset, buffer, bufferIndex, 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. Array.Copy (value, offset, buffer, bufferIndex, toCopy); bufferIndex += toCopy; offset += toCopy; length -= toCopy; Flush (false, false); } } Array.Copy (paddingZeros, 0, buffer, bufferIndex, padding); bufferIndex += padding; } } }