//
// openmapi.org - CompactTeaSharp - OnRpcTcpClient.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.Net;
using System.Net.Sockets;
using System.IO;
namespace CompactTeaSharp
{
///
/// ONC/RPC client which communicates with ONC/RPC servers over the network
/// using the stream-oriented protocol TCP/IP.
///
public class OncRpcTcpClient: OncRpcClient
{
private TcpClient client; // TCP socket used for stream-oriented communication with an ONC/RPC server.
protected XdrTcpEncodingStream sendingXdr; // XDR encoding stream used for sending requests via TCP/IP to an ONC/RPC server.
protected XdrTcpDecodingStream receivingXdr; // XDR decoding stream used when receiving replies via TCP/IP from an ONC/RPC server.
protected int transmissionTimeout = 30000; // Timeout during the phase where data is sent within calls, or data is received within replies.
///
/// Set the timout for remote procedure calls to wait for an answer from
/// the ONC/RPC server. If the timeout expires,
/// #call (int, IXdrAble, IXdrAble) will raise a InterruptedIOException.
/// The default timeout value is 30 seconds (30,000 milliseconds). The
/// timeout must be > 0. A timeout of zero indicates a batched call, for
/// which no reply message is expected.
///
/// Timeout in milliseconds. A timeout of zero indicates
/// batched calls.
public override void SetTimeout (int milliseconds)
{
base.SetTimeout (milliseconds);
}
///
/// The current timeout used during transmission phases (call and reply phases).
///
public int TransmissionTimeout {
get { return transmissionTimeout; }
set {
// Set the timeout used during transmission of data. If the flow of data
// when sending calls or receiving replies blocks longer than the given
// timeout, an exception is thrown. The timeout must be > 0.
if (value <= 0)
throw new ArgumentException ("transmission timeout must be > 0");
transmissionTimeout = value;
}
}
///
/// Set the character encoding for (de-)serializing strings.
///
/// The host where the ONC/RPC server resides.
/// Program number of the ONC/RPC server to call.
/// Program version number.
// OncRpcException, IOException
public OncRpcTcpClient (IPAddress host, int program, int version):
this (host, program, version, 0, 0)
{
}
///
/// Constructs a new OncRpcTcpClient object, which connects
/// to the ONC/RPC server at host for calling remote procedures
/// of the given { program, version }.
///
/// The host where the ONC/RPC server resides.
/// Program number of the ONC/RPC server to call.
/// Program version number.
/// The port number where the ONC/RPC server can be contacted.
/// If 0, then the OncRpcUdpClient object will ask the portmapper at
/// host for the port number.
// OncRpcException, IOException
public OncRpcTcpClient (IPAddress host, int program, int version, int port):
this (host, program, version, port, 0)
{
}
///
/// Constructs a new OncRpcTcpClient object, which connects
/// to the ONC/RPC server at host for calling remote procedures
/// of the given { program, version }.
///
/// The host where the ONC/RPC server resides.
/// Program number of the ONC/RPC server to call.
/// Program version number.
/// The port number where the ONC/RPC server can be contacted.
/// If 0, then the OncRpcUdpClient object will
/// ask the portmapper at host for the port number.
/// Size of receive and send buffers. In contrast to
/// UDP-based ONC/RPC clients, messages larger than the specified
/// buffer size can still be sent and received. The buffer is only
/// necessary to handle the messages and the underlaying streams will
/// break up long messages automatically into suitable pieces.
/// Specifying zero will select the default buffer size (currently
/// 8192 bytes).
// OncRpcException, IOException
public OncRpcTcpClient (IPAddress host, int program, int version,
int port, int bufferSize) : this (host, program, version, port, bufferSize, -1)
{
}
///
/// Constructs a new OncRpcTcpClient object, which connects
/// to the ONC/RPC server at host for calling remote procedures
/// of the given { program, version }.
///
/// The host where the ONC/RPC server resides.
/// Program number of the ONC/RPC server to call.
/// Program version number.
/// The port number where the ONC/RPC server can be contacted.
/// If 0, then the OncRpcUdpClient object will
/// ask the portmapper at host for the port number.
/// Size of receive and send buffers. In contrast to
/// UDP-based ONC/RPC clients, messages larger than the specified
/// buffer size can still be sent and received. The buffer is only
/// necessary to handle the messages and the underlaying streams will
/// break up long messages automatically into suitable pieces.
/// Specifying zero will select the default buffer size (currently
/// 8192 bytes).
/// Maximum timeout in milliseconds when connecting to
/// the ONC/RPC server. If negative, a default implementation-specific
/// timeout setting will apply. Note that this timeout only applies
/// to the connection phase, but not to later communication.
// OncRpcException, IOException
public OncRpcTcpClient (IPAddress host, int program, int version,
int port, int bufferSize, int timeout) : this (host, program,
version, port, bufferSize, timeout, null)
{
}
public OncRpcTcpClient (IPAddress host, int program, int version, int port,
int bufferSize, int timeout, TcpClient tcpClient) : base (host, program, version, port, OncRpcProtocols.Tcp)
{
//
// Construct the inherited part of our object. This will also try to
// lookup the port of the desired ONC/RPC server, if no port number
// was specified (port = 0).
//
//
// Let the host operating system choose which port (and network
// interface) to use. Then set the buffer sizes for sending and
// receiving UDP datagrams. Finally set the destination of packets.
//
if (bufferSize < 1024)
bufferSize = 1024;
//
// Note that we use this.port at this time, because the superclass
// might have resolved the port number in case the caller specified
// simply 0 as the port number.
//
if (timeout < 0) // TODO: hack...
timeout = 0;
// If no tcp client was passed in, create one.
if (tcpClient == null) {
client = new TcpClient ();
client.Connect (host, this.port);
client.SendTimeout = timeout;
client.ReceiveTimeout = timeout;
client.NoDelay = true;
if (client.ReceiveBufferSize < bufferSize)
client.ReceiveBufferSize = bufferSize;
if (client.SendBufferSize < bufferSize)
client.SendBufferSize = bufferSize;
} else {
client = tcpClient;
}
//
// Create the necessary encoding and decoding streams, so we can
// communicate at all.
//
sendingXdr = new XdrTcpEncodingStream (client, bufferSize);
receivingXdr = new XdrTcpDecodingStream (client, bufferSize);
}
///
/// Close the connection to an ONC/RPC server and free all
/// network-related resources.
///
// OncRpcException
public override void Close ()
{
if (client != null) {
try {
client.Close ();
} catch {
// do nothing
}
client = null;
}
if (sendingXdr != null) {
try {
sendingXdr.Close ();
} catch {
// do nothing
}
sendingXdr = null;
}
if (receivingXdr != null) {
try {
receivingXdr.Close ();
} catch {
// do nothing
}
receivingXdr = null;
}
}
///
/// Calls a remote procedure on an ONC/RPC server.
///
/// Please note that while this method supports call batching by
/// setting the communication timeout to zero you should better use
/// BatchCall as it provides better control over when the
/// batch should be flushed to the server.
///
///
///
// OncRpcException
public override void Call (int procedureNumber, int versionNumber,
IXdrAble @params, IXdrAble result)
{
lock (typeof (OncRpcTcpClient)) {
Refresh:
int refreshesLeft = 1;
while (refreshesLeft-- >= 0) {
// for ( int refreshesLeft = 1; refreshesLeft >= 0; --refreshesLeft ) {
//
// First, build the ONC/RPC call header. Then put the sending
// stream into a known state and encode the parameters to be
// sent. Finally tell the encoding stream to send all its data
// to the server. Then wait for an answer, receive it and decode
// it. So that's the bottom line of what we do right here.
//
NextXid();
var callHeader = new OncRpcClientCallMessage (xid, program, versionNumber, procedureNumber, auth);
OncRpcClientReplyMessage replyHeader = new OncRpcClientReplyMessage (auth);
//
// Send call message to server. If we receive an IOException,
// then we'll throw the appropriate ONC/RPC (client) exception.
// Note that we use a connected stream, so we don't need to
// specify a destination when beginning serialization.
//
try {
// TODO check next replaced code
// socket.setSoTimeout(transmissionTimeout);
if (transmissionTimeout < 0) //TODO: hack
transmissionTimeout = 0;
client.SendTimeout = transmissionTimeout;
sendingXdr.BeginEncoding (null, 0);
callHeader.XdrEncode (sendingXdr);
@params.XdrEncode (sendingXdr);
if (timeout != 0)
sendingXdr.EndEncoding ();
else
sendingXdr.EndEncoding (false);
} catch (IOException e) {
throw new OncRpcException (OncRpcException.CANT_SEND, e.Message);
}
//
// Receive reply message from server -- at least try to do so...
// In case of batched calls we don't need no stinkin' answer, so
// we can do other, more interesting things.
//
if (timeout == 0)
return;
try {
//
// Keep receiving until we get the matching reply.
//
while (true) {
// TODO check replaced code socket.setSoTimeout(timeout);
if (timeout < 0) //TODO: hack
timeout = 0;
client.ReceiveTimeout = timeout;
receivingXdr.BeginDecoding ();
// TODO check replaced code socket.setSoTimeout(transmissionTimeout);
if (transmissionTimeout < 0) //TODO: hack
transmissionTimeout = 0;
client.ReceiveTimeout = transmissionTimeout;
//
// First, pull off the reply message header of the
// XDR stream. In case we also received a verifier
// from the server and this verifier was invalid, broken
// or tampered with, we will get an
// OncRpcAuthenticationException right here, which will
// propagate up to the caller. If the server reported
// an authentication problem itself, then this will
// be handled as any other rejected ONC/RPC call.
//
try {
replyHeader.XdrDecode (receivingXdr);
} catch (OncRpcException) {
//
// ** SF bug #1262106 **
//
// We ran into some sort of trouble. Usually this will have
// been a buffer underflow. Whatever, end the decoding process
// and ensure this way that the next call has a chance to start
// from a clean state.
//
receivingXdr.EndDecoding ();
throw;
}
//
// Only deserialize the result, if the reply matches the
// call. Otherwise skip this record.
//
if (replyHeader.MessageId == callHeader.MessageId)
break;
receivingXdr.EndDecoding ();
}
//
// Make sure that the call was accepted. In case of unsuccessful
// calls, throw an exception, if it's not an authentication
// exception. In that case try to refresh the credential first.
//
if ( !replyHeader.SuccessfullyAccepted () ) {
receivingXdr.EndDecoding ();
//
// Check whether there was an authentication
// problem. In this case first try to refresh the
// credentials.
//
if ( (refreshesLeft > 0)
&& (replyHeader.ReplyStatus == OncRpcReplyStatus.MsgDenied)
&& (replyHeader.RejectStatus == OncRpcRejectStatus.AuthenticationError)
&& (auth != null)
&& auth.CanRefreshCred)
{
goto Refresh;
}
// Nope. No chance. This gets tough.
throw replyHeader.newException ();
}
try {
result.XdrDecode (receivingXdr);
} catch (OncRpcException e) {
//
// ** SF bug #1262106 **
//
// We ran into some sort of trouble. Usually this will have
// been a buffer underflow. Whatever, end the decoding process
// and ensure this way that the next call has a chance to start
// from a clean state.
//
receivingXdr.EndDecoding ();
throw e;
}
//
// Free pending resources of buffer and exit the call loop,
// returning the reply to the caller through the result
// object.
//
receivingXdr.EndDecoding ();
return;
} catch (SocketException) {
// In case our time run out, we throw an exception.
throw new OncRpcTimeoutException ();
} catch (IOException e) {
//
// Argh. Trouble with the transport. Seems like we can't
// receive data. Gosh. Go away!
//
throw new OncRpcException (OncRpcException.CANT_RECV, e.Message);
}
} // for ( refreshesLeft )
}
}
///
/// Issues a batched call for a remote procedure to an ONC/RPC server.
/// Below is a small example (exception handling ommited for clarity):
///
///
/// OncRpcTcpClient client = new OncRpcTcpClient(
/// InetAddress.getByName("localhost"),
/// myprogramnumber, myprogramversion,
/// OncRpcProtocols.Tcp);
/// client.callBatch(42, myparams, false);
/// client.callBatch(42, myotherparams, false);
/// client.callBatch(42, myfinalparams, true);
///
///
/// In the example above, three calls are batched in a row and only be sent
/// all together with the third call. Note that batched calls must not expect
/// replies, with the only exception being the last call in a batch:
///
///
/// client.callBatch(42, myparams, false);
/// client.callBatch(42, myotherparams, false);
/// client.call(43, myfinalparams, myfinalresult);
///
///
/// Procedure number of the procedure to call.
/// The parameters of the procedure to call, contained
/// in an object which implements the {@link IXdrAble} interface.
/// Make sure that all pending batched calls are sent to
/// the server.
///
// OncRpcException
public void BatchCall (int procedureNumber,
IXdrAble prms, bool flush)
{
lock (typeof (OncRpcTcpClient)) {
//
// First, build the ONC/RPC call header. Then put the sending
// stream into a known state and encode the parameters to be
// sent. Finally tell the encoding stream to send all its data
// to the server. We don't then need to wait for an answer. And
// we don't need to take care of credential refreshes either.
//
NextXid ();
var callHeader = new OncRpcClientCallMessage (
xid, program, version, procedureNumber, auth);
//
// Send call message to server. If we receive an IOException,
// then we'll throw the appropriate ONC/RPC (client) exception.
// Note that we use a connected stream, so we don't need to
// specify a destination when beginning serialization.
//
try {
// TODO check replaced code socket.setSoTimeout(transmissionTimeout);
if (timeout < 0) // TODO: hack...
timeout = 0;
client.SendTimeout = transmissionTimeout;
sendingXdr.BeginEncoding (null, 0);
callHeader.XdrEncode (sendingXdr);
prms.XdrEncode (sendingXdr);
sendingXdr.EndEncoding (flush);
} catch (IOException e) {
throw new OncRpcException (OncRpcException.CANT_SEND, e.Message);
}
}
}
}
}