/*
 * rpcInit.c --
 *
 *	Initialize the data structures needed by the RPC system.
 *
 * Copyright (C) 1985 Regents of the University of California
 * All rights reserved.
 */

#ifndef lint
static char rcsid[] = "$Header: /r3/kupfer/spriteserver/src/sprited/rpc/RCS/rpcInit.c,v 1.3 92/01/21 16:14:16 kupfer Exp $ SPRITE (Berkeley)";
#endif /* not lint */

#include <sprite.h>
#include <rpc.h>
#include <rpcInt.h>
#include <rpcClient.h>
#include <rpcServer.h>
#include <rpcTrace.h>
#include <vm.h>
#include <timer.h>
#include <net.h>

/*
 * These are the rpc version numbers, both in native format and in
 * byte-swapped format. 
 */

int	rpc_NativeVersion = RPC_NATIVE_VERSION;
int	rpc_SwappedVersion = RPC_SWAPPED_VERSION;
int	rpc_NativeVersionNew = RPC_NATIVE_VERSION_NEW;
int	rpc_SwappedVersionNew = RPC_SWAPPED_VERSION_NEW;

int main_NumRpcServers	= 2;	 /* # of rpc servers to create */

/*
 * Constant parameters for the protocol.  The RpcConst structure keeps
 * the complete set of constants.  Two sets of constants are defined,
 * one that is appropriate for a local ethernet, one that has longer
 * timeouts and is better for talking across gateways.
 */
RpcConst rpcEtherConst;
RpcConst rpcInetConst;

void RpcBufferInit();


/*
 *----------------------------------------------------------------------
 *
 * Rpc_Init --
 *
 *      Allocate and set up the tables used by the RPC system.  This
 *      should be called after virtual memory allocation can be done and
 *      before any RPCs are attempted.  This allocates the Client Channel
 *	data structures and some stuff for the Rpc Servers' state.  The
 *	number of client channels is fixed by rpcNumChannels, but the
 *	number of RPC server processes can grow via the Rpc_Deamon process.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Allocate space for tables, and set the initial state for the
 *	client channels and the servers.
 *
 *----------------------------------------------------------------------
 */
void
Rpc_Init()
{
    int i;
    register int frag;
    extern void	RpcInitServerTraces();

    /*
     * Initialize sets of time parameters.  These structures are used in
     * the RpcDoCall code.  This is much too hard coded, and someone
     * should figure out how to dynamically determine these parameters.
     *
     * Ethernet - retry after 0.1 seconds, .5 seconds if fragmenting.
     *	Double this until the retry interval is 1 second.
     *	Retry at most 8 times, for a total timeout period of
     *		.1 + .2 + .4 + .8 + 1.0 + 1.0 + 1.0 + 1.0 = 5.5
     *		.5 + 1.0 + 1.0 + 1.0 + 1.0 + 1.0 + 1.0 + 1.0 = 7.5 (frag)
     *	If we are recieving acks, then back off and probe every 5 seconds.
     *	If we get acked 10 times then give a warning.
     */
    rpcEtherConst.retryMsec = 100;
    Time_Multiply(time_OneMillisecond, rpcEtherConst.retryMsec,
		  &rpcEtherConst.retryWait);
    rpcEtherConst.fragRetryMsec = 500;
    Time_Multiply(time_OneMillisecond, rpcEtherConst.fragRetryMsec,
		  &rpcEtherConst.fragRetryWait);
    rpcEtherConst.maxAckMsec = 5000;
    Time_Multiply(time_OneMillisecond, rpcEtherConst.maxAckMsec,
		  &rpcEtherConst.maxAckWait);
    rpcEtherConst.maxTimeoutMsec = 1000;
    Time_Multiply(time_OneMillisecond, rpcEtherConst.maxTimeoutMsec,
		  &rpcEtherConst.maxTimeoutWait);
    rpcEtherConst.maxTries = 8;
    rpcEtherConst.maxAcks = 10;
    /*
     * Internet (IP) - retry after 0.5 seconds, 1.0 if fragmenting.
     *	Double this until the retry interval is 4 seconds.
     *	Retry at most 8 times, for a total timeout period of
     *		.5 + 1.0 + 2.0 + 4.0 + 4.0 + 4.0 + 4.0 + 4.0 = 23.5
     *		1.0 + 2.0 + 4.0 + 4.0 + 4.0 + 4.0 + 4.0 + 4.0 = 27.0 (frag)
     *	If we are recieving acks, then back off and probe every 5 seconds.
     *	If we get acked 10 times then give a warning.
     */
    rpcInetConst.retryMsec = 500;
    Time_Multiply(time_OneMillisecond, rpcInetConst.retryMsec,
		  &rpcInetConst.retryWait);
    rpcInetConst.fragRetryMsec = 1000;
    Time_Multiply(time_OneMillisecond, rpcInetConst.fragRetryMsec,
		  &rpcInetConst.fragRetryWait);
    rpcInetConst.maxAckMsec = 5000;
    Time_Multiply(time_OneMillisecond, rpcInetConst.maxAckMsec,
		  &rpcInetConst.maxAckWait);
    rpcInetConst.maxTimeoutMsec = 4000;
    Time_Multiply(time_OneMillisecond, rpcInetConst.maxTimeoutMsec,
		  &rpcInetConst.maxTimeoutWait);
    rpcInetConst.maxTries = 8;
    rpcInetConst.maxAcks = 10;

    /*
     * TRACE and HISTOGRAM initialization.
     */
    Trace_Init(rpcTraceHdrPtr, RPC_TRACE_LEN, sizeof(RpcHdr), 0);

    rpcServiceTime[0] = (Rpc_Histogram *)NIL;
    rpcCallTime[0] = (Rpc_Histogram *)NIL;
    for (i=1 ; i<=RPC_LAST_COMMAND ; i++) {
	rpcServiceTime[i] = Rpc_HistInit(RPC_NUM_HIST_BUCKETS, 1024);
	rpcCallTime[i] = Rpc_HistInit(RPC_NUM_HIST_BUCKETS, 1024);
    }

    /*
     * Set our preferred inter-fragment delay based on machine type.
     * This is a microsecond value.  Our output rate starts the same
     * as the input rate, although MyDelay could increase if a machine
     * senses that it is overloaded.
     */

    RpcGetMachineDelay(&rpcMyDelay, &rpcOutputRate);

    /*
     * The client channel table is kept as a pointer to an array of pointers
     * to client channels.  First allocate the table of pointers and then
     * allocate storage for each channel.
     */
    rpcChannelPtrPtr = (RpcClientChannel **)
	    Vm_RawAlloc(rpcNumChannels * sizeof(RpcClientChannel *));

    /*
     * Query the net module of the maximum size protocol header buffer 
     * needed.
     */
    for (i=0 ; i<rpcNumChannels ; i++) {
	register RpcClientChannel *chanPtr;

	chanPtr = (RpcClientChannel *)Vm_RawAlloc(sizeof(RpcClientChannel));
	rpcChannelPtrPtr[i] = chanPtr;

	chanPtr->state = CHAN_FREE;
	chanPtr->index = i;
	chanPtr->serverID = -1;
	Sync_SemInitDynamic(&chanPtr->mutex,"Rpc:RpcClientChannel.mutex");
	Sync_SemRegister(&chanPtr->mutex);
	Sync_ConditionInit(&chanPtr->waitCondition,
			   "rpc:chanWaitCondition", TRUE);

	/*
	 * Set up header storage and the scatter/gather sets used to
	 * refer to a whole message.  This is done for each type
	 * of packet (request, reply ack), plus an array of these
	 * things used for fragmenting our request.
	 */
	RpcBufferInit((RpcHdr *) &chanPtr->requestRpcHdr, &chanPtr->request,
			chanPtr->index, -1);
	RpcBufferInit(&chanPtr->replyRpcHdr, &chanPtr->reply,
			chanPtr->index, -1);
	RpcBufferInit(&chanPtr->ackHdr, &chanPtr->ack,
			chanPtr->index, -1);

	for (frag=0 ; frag < RPC_MAX_NUM_FRAGS ; frag++) {
	    RpcBufferInit(&chanPtr->fragRpcHdr[frag], &chanPtr->fragment[frag],
			    chanPtr->index, -1);
	}

    }
    /*
     * Initialize server nack info.
     */
    Sync_SemInitDynamic(&rpcNack.mutex,"Rpc:RpcNackData.mutex");
    Sync_SemRegister(&rpcNack.mutex);
    /*
     * Set nack buffers to NIL until allocated when rpc system turned on.
     */
    rpcNack.numFree = 0;
    rpcNack.rpcHdrArray = (RpcHdr *) NIL;
    rpcNack.hdrState = (int *) NIL;
    rpcNack.bufferSet = (RpcBufferSet *) NIL;

    /*
     * Initialize client's table on whether servers are sending negative
     * acknowledgements or not (if channel ramping-down is used).
     */
    RpcInitServerChannelState();

    /*
     * Initialize neg-ack back-off constants on clients.
     */
    Time_Multiply(time_OneSecond, 2, &rpcNackRetryWait);
    Time_Multiply(time_OneSecond, 15, &rpcMaxNackWait);

    /*
     * Initialize the servers' state table.  Most slots are left
     * uninitialized.  They get filled in by Rpc_Deamon when it creates
     * new server processes.  After creation, a server process
     * claims a table entry with RpcServerInstall.
     */
    rpcServerPtrPtr = (RpcServerState **)
	    Vm_RawAlloc(rpcAbsoluteMaxServers * sizeof(RpcServerState *));
    for (i=0 ; i<rpcMaxServers ; i++) {
	rpcServerPtrPtr[i] = (RpcServerState *)NIL;
    }

    /*
     * Ask the net module to set up our Sprite ID.  It uses either
     * existing (compiled in) addresses or Reverse ARP.  If we can't
     * figure out our ID we use zero and rely on the RPC server to
     * propogate our Sprite ID back in the first RPC reply message.
     * We try all the interfaces until we get one that works.
     */
    for (i = 0; ; i++) {
	Net_Interface	*interPtr;
	interPtr = Net_NextInterface(TRUE, &i);
	if (interPtr == (Net_Interface *) NIL) {
	    break;
	}
	rpc_SpriteID = Net_AddrToID(interPtr->netType, NET_PROTO_RAW,
				&interPtr->netAddress[NET_PROTO_RAW]);
	if (rpc_SpriteID > 0) {
	    break;
	}
    }
    if (rpc_SpriteID < 0) {
	rpc_SpriteID = 0;
    }

    RpcInitServerTraces();

    return;
}

/*
 *----------------------------------------------------------------------
 *
 * RpcBufferInit --
 *
 *	Initialize a packet buffer for one of the various packets sent
 *	via an RPC channel.  They all share the same packet format and
 *	buffering system, and this call is used to initialize it.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Allocate memory with Vm_RawAlloc.  Give initial values to
 *	the fields of the RPC header that never change.  These are:
 *	version		# byte ordering version.
 *	channel		# channel port number.
 *	delay		# interfragment delay.
 *	paramSize 0	# size of the parameter area of the message.
 *	dataSize 0	# size of the data area of the message.
 *	numFrags 0	# >0 if fragmented
 *	fragMask 0	# fragmentID
 *
 *----------------------------------------------------------------------
 */
void
RpcBufferInit(rpcHdrPtr, bufferSetPtr, channel, serverHint)
    RpcHdr *rpcHdrPtr;		/* Storage for packet header */
    RpcBufferSet *bufferSetPtr;	/* Scatter/gather vector for whole message */
    int channel;		/* chanPtr->index */
    int serverHint;		/* srvPtr->index */
{
    unsigned int maxHdrSize = NET_MAX_HEADER_SIZE;

    bufferSetPtr->protoHdrBuffer.length = maxHdrSize;
    bufferSetPtr->protoHdrBuffer.bufAddr = Vm_RawAlloc(maxHdrSize);

    bufferSetPtr->rpcHdrBuffer.length = sizeof(RpcHdr);
    bufferSetPtr->rpcHdrBuffer.bufAddr = (Address)rpcHdrPtr;

    bufferSetPtr->paramBuffer.length = 0;
    bufferSetPtr->paramBuffer.bufAddr = (Address)NIL;

    bufferSetPtr->dataBuffer.length = 0;
    bufferSetPtr->dataBuffer.bufAddr = (Address)NIL;
    /*
     * Set up RPC header fields that don't change.
     */
    rpcHdrPtr->version = rpc_NativeVersion;
    rpcHdrPtr->delay = rpcMyDelay;

    rpcHdrPtr->clientID = rpc_SpriteID;
    rpcHdrPtr->channel = channel;
    rpcHdrPtr->serverID = rpc_SpriteID;
    rpcHdrPtr->serverHint = serverHint;
    /*
     * And some that might not have to change.
     */
    rpcHdrPtr->numFrags = 0;
    rpcHdrPtr->fragMask = 0;
    rpcHdrPtr->paramSize = 0;
    rpcHdrPtr->dataSize = 0;
}

/*
 *----------------------------------------------------------------------
 *
 * RpcInitServerState --
 *
 *	Initialize a server state table entry.  This is called before
 *	a server process is created to set up its state.  The state is
 *	used as the primary communication mechanism between the server
 *	process and the rest of the world.
 *
 * Results:
 *	A pointer to an initialized server state table entry.  This
 *	value needs to be saved in a table somewhere by the caller.
 *
 * Side effects:
 *	Allocate memory with Vm_RawAlloc.  Give initial values to
 *	all the elements of the table entry.  The "state" field of
 *	the table is set to SRV_NOTREADY and a server process has
 *	to claim the table entry with RpcServerInstall.
 *
 *----------------------------------------------------------------------
 */
RpcServerState *
RpcInitServerState(index)
    int index;		/* Caller's index of returned info.  This is saved
			 * in the table and used as a hint to clients */
{
    register RpcServerState *srvPtr;	/* Server state that is initialized */
    register int frag;			/* Index into array of headers used
					 * for fragmenting */
    static Sync_Semaphore mutexInit =
	Sync_SemInitStatic("RpcServerState->mutex");

    Sync_SemRegister(&mutexInit);

    srvPtr = (RpcServerState *)Vm_RawAlloc(sizeof(RpcServerState));

    srvPtr->state = SRV_NOTREADY;
    srvPtr->ID = 0;
    srvPtr->freeReplyProc = (int (*)())NIL;
    srvPtr->freeReplyData = (ClientData)NIL;
    srvPtr->index = index;
    srvPtr->clientID = -1;
    srvPtr->channel = -1;
    srvPtr->mutex = mutexInit;
    Sync_ConditionInit(&srvPtr->waitCondition, "rpc:srvWaitCondition",
		       TRUE);

    /*
     * Set up the buffer address for the RPC header of replies
     * and acks to point to the headers kept here in the server's state.
     */
    RpcBufferInit(&srvPtr->replyRpcHdr, &srvPtr->reply, -1, srvPtr->index);
    RpcBufferInit(&srvPtr->ackRpcHdr, &srvPtr->ack, -1, srvPtr->index);
    for (frag=0 ; frag < RPC_MAX_NUM_FRAGS ; frag++) {
	RpcBufferInit(&srvPtr->fragRpcHdr[frag], &srvPtr->fragment[frag],
			-1, srvPtr->index);
    }
    /*
     * Set up the scatter vector for input requests to the server.
     * Allocate buffer space for the largest possible request.
     */
    RpcBufferInit(&srvPtr->requestRpcHdr, &srvPtr->request, -1, srvPtr->index);
    srvPtr->request.paramBuffer.bufAddr = Vm_RawAlloc(RPC_MAX_PARAMSIZE);
    srvPtr->request.paramBuffer.length = RPC_MAX_PARAMSIZE;

    srvPtr->request.dataBuffer.bufAddr = Vm_RawAlloc(RPC_MAX_DATASIZE);
    srvPtr->request.dataBuffer.length = RPC_MAX_DATASIZE;

    /*
     * Initialize temporaries.
     */
    srvPtr->actualParamSize = 0;
    srvPtr->actualDataSize = 0;

    return(srvPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * Rpc_Start --
 *
 *      Conduct the preliminary RPC's neccesary to start up the client
 *      side of the RPC system.  A Get Time RPC is done to initialize the
 *      boot time stamp.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Do a Get Time RPC to initialize rpcBootID;
 *
 *----------------------------------------------------------------------
 */
void
Rpc_Start()
{
    Time 	bootTime;	/* Time returned from the default server */
    int 	tzMinutes;	/* Minutes west of Greenwich */
    int 	tzDST;		/* Daylight savings flag */
    ReturnStatus status;	/* Status code from the RPC */
    char 	dateString[40];/* To hold a printable version of the time */
    int 	seconds;
    int 	spriteID;
    Net_Route		*routePtr;
    Sync_Semaphore	tmpMutex;
    int			i;

    /*
     * Do a Sprite reverse Arp to discover our Sprite ID.  If it's still
     * zero after this that inhibits the RPC system.  In that case we'd
     * better be a diskfull machine so we find out our SpriteID by
     * the user program that installs routes.  See Net_InstallRoute.
     *
     * We do a reverse arp on all the broadcast routes until one
     * works.
     */
    spriteID = -1;
    i = 0;
    Sync_SemInitDynamic(&tmpMutex, "Rpc_Start:tmpMutex");
    while (1) {
	routePtr = Net_IDToRoute(NET_BROADCAST_HOSTID, i, FALSE, 
			(Sync_Semaphore *) NIL, 0);
	if (routePtr == (Net_Route *) NIL) {
	    break;
	}
	MASTER_LOCK(&tmpMutex);
	spriteID = Net_RevArp(routePtr, NET_PROTO_RAW,  
			(Net_Address *) NIL, &tmpMutex); 
	MASTER_UNLOCK(&tmpMutex);
	Net_ReleaseRoute(routePtr);
	if (spriteID > 0) {
	    rpc_SpriteID = spriteID;
	    printf("Reverse Arp, setting Sprite ID to %d\n", spriteID);
	    break;
	}
	i++;
    }

    Rpc_StampTest();

    status = Rpc_GetTime(RPC_BROADCAST_SERVER_ID, &bootTime, &tzMinutes,
						 &tzDST);
    if (status != SUCCESS) {
	Timer_Ticks ticks;

	printf("Rpc_Start: GetTime RPC failed: %s\n", Stat_GetMsg(status));
	Timer_GetCurrentTicks(&ticks);
	Timer_TicksToTime(ticks, &bootTime);
    } else {
#ifdef SPRITED_SETTIME
	Timer_SetTimeOfDay(bootTime, tzMinutes, tzDST);
#endif
    }
    rpcBootID = bootTime.seconds;

    /*
     * Convert from Greenwich Standard minutes to local minutes
     * and print the time on the console.
     */
    seconds = bootTime.seconds + tzMinutes * 60;
    Time_ToAscii(seconds, FALSE, dateString);
    printf("RPC time %s\n", dateString);

#ifndef SPRITED_SETTIME
    Timer_GetTimeOfDay(&bootTime, (int *)NULL, (Boolean *)NULL);
    seconds = bootTime.seconds + tzMinutes * 60;
    Time_ToAscii(seconds, FALSE, dateString);
    printf("Mach time %s\n", dateString);
#endif
}

/*
 *----------------------------------------------------------------------
 *
 * Rpc_MaxSizes --
 *
 *      This function returns the maximum amount of data that can be sent
 *      in one RPC.  A remote procedure has its inputs and outputs packed
 *      into two buffers called the "data area" and the "parameter area".
 *      Two values are returned, the maximums for the parameter and data
 *      areas.
 *
 * Results:
 *	The first parameter gets the maximum data size, the
 *	second gets the maximum parameter size.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */
void
Rpc_MaxSizes(maxDataSizePtr, maxParamSizePtr)
    int *maxDataSizePtr;
    int *maxParamSizePtr;
{
    if (maxDataSizePtr != (int *)NIL){
	*maxDataSizePtr = RPC_MAX_DATASIZE;
    }
    if (maxParamSizePtr != (int *)NIL){
	*maxParamSizePtr = RPC_MAX_PARAMSIZE;
    }
}
