package tests.org.acplt.oncrpc;

import java.io.IOException;
import java.net.InetAddress;

import org.acplt.oncrpc.OncRpcAuthStatus;
import org.acplt.oncrpc.OncRpcAuthType;
import org.acplt.oncrpc.OncRpcAuthenticationException;
import org.acplt.oncrpc.OncRpcException;
import org.acplt.oncrpc.OncRpcPortmapClient;
import org.acplt.oncrpc.OncRpcProtocols;
import org.acplt.oncrpc.XdrBufferDecodingStream;
import org.acplt.oncrpc.XdrBufferEncodingStream;
import org.acplt.oncrpc.XdrString;
import org.acplt.oncrpc.XdrVoid;
import org.acplt.oncrpc.server.OncRpcCallInformation;
import org.acplt.oncrpc.server.OncRpcDispatchable;
import org.acplt.oncrpc.server.OncRpcServerAuthShort;
import org.acplt.oncrpc.server.OncRpcServerAuthUnix;
import org.acplt.oncrpc.server.OncRpcServerTransport;
import org.acplt.oncrpc.server.OncRpcTcpServerTransport;
import org.acplt.oncrpc.server.OncRpcUdpServerTransport;

public class EchoServer implements OncRpcDispatchable {
    
    public boolean start() {
	/*
	 * Has this server already been started?
	 */
	if (! this.started) {	    
	    /*
	     * No. Then let's try to initialize and to
	     * start the echo server.
	     * First we try to contact the portmapper
	     * on the local host.
	     */
	    boolean portmapperAvailable = false;
	    
	    try {
		System.out.print("Try to connect to the portmapper: ");
		OncRpcPortmapClient portmapClient = new OncRpcPortmapClient(InetAddress.getByName("localhost"), OncRpcProtocols.ONCRPC_TCP);
		System.out.println("done.");
		
		portmapClient.close();
		portmapClient = null;
		
		portmapperAvailable = true;
	    } catch (Exception exception) {
		System.out.format("failed (%s).%n", exception);
		
		portmapperAvailable = false;
	    }
	    
	    /*
	     * Is a portmapper availabl on the local host? 
	     */
	    if (portmapperAvailable) {
		/*
		 * Next the server transports are created, registered and started.
		 */
		try {
		    
		    System.out.print("Creating server transports: ");
		    this.transports[0] = new OncRpcUdpServerTransport(this, 55555, 0x49679, 1, 8192);
		    this.transports[1] = new OncRpcTcpServerTransport(this, 55555, 0x49679, 1, 8192);
		    System.out.println("done.");

		    System.out.print("Registering server transports: ");
		    for (OncRpcServerTransport transport : this.transports) {
			transport.register();
		    }
		    System.out.println("done.");

		    System.out.print("Start listening on server transports: ");
		    for (OncRpcServerTransport transport : this.transports) {
			transport.listen();
		    }
		    System.out.println("done.");

		    this.started = true;
		} catch (Exception exception) {
		    System.out.format("failed (%s).%n", exception);

		    /*
		     * In case of failure the test server is shutdown.
		     */
		    this.started = true;
		    this.shutdown();
		    this.started = false;
		}
	    }
	} else {
	    System.out.println("The server has already been started.");
	}
	
	return this.started;
    }

    public void shutdown() {
	/*
	 * Let's see, whether the server has been started.
	 */
	if (this.started) {
	    /*
	     * Let's unergister and close the listening
	     * transports.
	     */
	    for (OncRpcServerTransport transport : this.transports) {
		try {
		    System.out.print("Unregister server transport: ");
		    transport.unregister();
		    System.out.println("done.");
		} catch (OncRpcException exception) {
		    System.out.format("failed (%s).", exception);
		}
		
		System.out.print("Close transport: ");
		transport.close();
		System.out.println("done.");
	    }
	    
	    System.out.println("Server transports are unregistered and closed.");
	    
	    this.started = false;
	}
    }

    //
    // Handle incomming calls...
    //
    public void dispatchOncRpcCall(OncRpcCallInformation call,
                                   int program, int version, int procedure)
           throws OncRpcException, IOException {
        //
        // Spit out some diagnosis messages...
        //
        System.out.println("Incoming call for program "
                           + Integer.toHexString(program)
                           + "; version " + version
                           + "; procedure " + Integer.toHexString(procedure)
                           + "; auth type " + call.callMessage.auth.getAuthenticationType());
        //
        // Handle incomming credentials...
        //
        if ( call.callMessage.auth.getAuthenticationType()
               == OncRpcAuthType.ONCRPC_AUTH_UNIX ) {
            OncRpcServerAuthUnix auth = (OncRpcServerAuthUnix) call.callMessage.auth;
            if ( (auth.uid != 42)
                 && (auth.gid != 815) ) {
                throw(new OncRpcAuthenticationException(
                              OncRpcAuthStatus.ONCRPC_AUTH_BADCRED));
            }
            //
            // Suggest shorthand authentication...
            //
            XdrBufferEncodingStream xdr = new XdrBufferEncodingStream(8);
            xdr.beginEncoding(null, 0);
            xdr.xdrEncodeInt(42);
            xdr.xdrEncodeInt(~42);
            xdr.endEncoding();
            //
            // ATTENTION: this will return the *whole* buffer created by the
            // constructor of XdrBufferEncodingStream(len) above. So make sure
            // that you really want to return the whole buffer!
            //
            auth.setShorthandVerifier(xdr.getXdrData());
        } else if ( call.callMessage.auth.getAuthenticationType()
                      == OncRpcAuthType.ONCRPC_AUTH_SHORT ) {
            //
            // Check shorthand credentials.
            //
            OncRpcServerAuthShort auth = (OncRpcServerAuthShort) call.callMessage.auth;
            XdrBufferDecodingStream xdr =
                new XdrBufferDecodingStream(auth.getShorthandCred());
            xdr.beginDecoding();
            int credv1 = xdr.xdrDecodeInt();
            int credv2 = xdr.xdrDecodeInt();
            xdr.endDecoding();
            if ( credv1 != ~credv2 ) {
                System.out.println("AUTH_SHORT rejected");
                throw(new OncRpcAuthenticationException(
                              OncRpcAuthStatus.ONCRPC_AUTH_REJECTEDCRED));
            }
            if ( (++shorthandCredCounter % 3) == 0 ) {
                System.out.println("AUTH_SHORT too old");
                throw(new OncRpcAuthenticationException(
                              OncRpcAuthStatus.ONCRPC_AUTH_REJECTEDCRED));
            }
            System.out.println("AUTH_SHORT accepted");
        }
        //
        // Now dispatch incomming calls...
        //
        switch ( procedure ) {
        case 0:
            //
            // The usual ONC/RPC PING (aka "NULL" procedure)
            //
            call.retrieveCall(XdrVoid.XDR_VOID);
            call.reply(XdrVoid.XDR_VOID);
            break;
        case 1: {
            //
            // echo string parameter
            //
            XdrString param = new XdrString();
            call.retrieveCall(param);
            System.out.println("Parameter: \"" + param.stringValue() + "\"");
            call.reply(param);
            break;
        }
        //
        // For all unknown calls, send back an error reply.
        //
        default:
            call.failProcedureUnavailable();
        }
    }
    
    private boolean started;
    private final OncRpcServerTransport[] transports = new OncRpcServerTransport[2]; 
    
    //
    // Shorthand credential use counter.
    //
    private int shorthandCredCounter = 0;



}
