-- File: PacketExchangeImpl.mesa - last edit:
-- AOF                 16-Sep-87 14:44:16
-- SMA                 27-May-86  9:26:21
-- Copyright (C) 1984, 1985, 1986, 1987 by Xerox Corporation. All rights reserved. 

DIRECTORY
  ByteBlt USING [ByteBlt],
  CommFlags USING [doStats],
  CommHeap USING [zone],
  Driver USING [Glitch],
  Environment USING [Block],
  Inline USING [DBITAND, DBITXOR, LowHalf],
  NSBuffer USING [Buffer, Body, GetBuffer, TransferStatus],
  NSTypes USING [
    bytesPerExchangeHeader, bytesPerIDPHeader, maxDataBytesPerExchange],
  PacketExchange USING [
    ErrorReason, RequestHandle, RequestObject, ExchangeClientType, ExchangeID,
    WaitTime, ExchWords],
  PacketExchangeInternal USING [ExchangeHandle, PacketExchangeObject],
  Process USING [InitializeCondition, MsecToTicks],
  Protocol1 USING [Family, GetFamilyUnit],
  RouterInternal USING [FlushCacheEntry, SendErrorPacket],
  Socket USING [
    AssignNetworkAddress, Create, Delete, GetPacket,
    PutPacket, ReturnBuffer, SetWaitTime, TimeOut],
  SocketInternal USING [SocketHandle],
  Stats USING [StatBump, StatIncr],
  System USING [
    broadcastHostNumber, GetClockPulses, NetworkAddress, SocketNumber];

PacketExchangeImpl: MONITOR
  IMPORTS
    ByteBlt, CommHeap, Driver, Inline, NSBuffer, Protocol1,
    PacketExchange, Process, RouterInternal, Socket, Stats, System
  EXPORTS PacketExchange, PacketExchangeInternal =
  BEGIN

  nullExchangeHandle: PUBLIC <<PacketExchange>> ExchangeHandle ← NIL;
  maxBlockLength: PUBLIC <<PacketExchange>>
    CARDINAL ← NSTypes.maxDataBytesPerExchange;
  ExchangeHandle: PUBLIC <<PacketExchange>>
    TYPE = PacketExchangeInternal.ExchangeHandle;
  ChannelHandle: <<LOOPHOLE>> TYPE = SocketInternal.SocketHandle;
  
  sendCompleted: CONDITION;
  bufferSent: CARDINAL = 2;
  sendAborted: CARDINAL = 3;
  sendInProgress: CARDINAL = 1;
  bytesPerExchangePacketHeader: CARDINAL =
    NSTypes.bytesPerIDPHeader + NSTypes.bytesPerExchangeHeader;

  Timeout: PUBLIC <<PacketExchange>> SIGNAL = CODE;
  IllegalUseOfRequeueProcedure: <<GLITCH>> ERROR = CODE;
  Error: PUBLIC <<PacketExchange>> ERROR [why: PacketExchange.ErrorReason] = CODE;

  --we now own rH and we if no errors are encountered free it
  Create: PUBLIC <<PacketExchange>> PROC[
    localSocket: System.SocketNumber, receiveRequestCount: CARDINAL,
    waitTime: PacketExchange.WaitTime]
    RETURNS [h: ExchangeHandle] =
    BEGIN
    sendRequestCount: CARDINAL = 1;
    parent: Protocol1.Family = Protocol1.GetFamilyUnit[ns];
    maxBlockLength ← parent.maxBufferSize - bytesPerExchangePacketHeader;
    h ← CommHeap.zone.NEW[PacketExchangeInternal.PacketExchangeObject ← [
      cH: Socket.Create[
        socket: localSocket, type: packetExchange, 
	send: sendRequestCount, receive: receiveRequestCount],
      exchangeID: System.GetClockPulses[], retries: 0]];
    Socket.SetWaitTime[h.cH, waitTime];
    END;  --CreateInternal

  CreateReplier: PUBLIC <<PacketExchange>> PROC[
    local: System.NetworkAddress, requestCount: CARDINAL,
    waitTime, retransmissionInterval: PacketExchange.WaitTime]
    RETURNS [h: ExchangeHandle] =
    BEGIN
    h ← Create[local.socket, requestCount, retransmissionInterval];
    SetWaitTimes[h, waitTime, retransmissionInterval];
    END;

  CreateRequestor: PUBLIC <<PacketExchange>> PROC[
    waitTime, retransmissionInterval: PacketExchange.WaitTime]
    RETURNS [h: ExchangeHandle] =
    BEGIN
    me: System.NetworkAddress ← Socket.AssignNetworkAddress[];
    h ← Create[me.socket, 2, MIN[waitTime, retransmissionInterval]];
    SetWaitTimes[h, waitTime, retransmissionInterval];
    END;

  Delete: PUBLIC <<PacketExchange>> PROC[h: ExchangeHandle] =
    BEGIN
    Socket.Delete[h.cH];
    CommHeap.zone.FREE[@h];
    END;  --Delete

  GetFreeSendPacket: PUBLIC <<PacketExchangeInternal>> PROC[h: ExchangeHandle]
    RETURNS [NSBuffer.Buffer] = {RETURN[GetBuffer[h, maxBlockLength]]};

  GetBuffer: PROC[h: ExchangeHandle, length: NATURAL]
    RETURNS [NSBuffer.Buffer] = 
      BEGIN
      OPEN sH: LOOPHOLE[h.cH, ChannelHandle];
      RETURN[NSBuffer.GetBuffer[
        sH.pool, send, TRUE, length + bytesPerExchangePacketHeader]];
      END;

  LostBuffer: PRIVATE PROC[b: NSBuffer.Buffer] =
    {Driver.Glitch[IllegalUseOfRequeueProcedure]};

  NotifySendCompleted: PRIVATE ENTRY PROC[b: NSBuffer.Buffer] =
    BEGIN
    b.requeueData ← bufferSent;
    b.requeueProcedure ← LostBuffer;
    BROADCAST sendCompleted;
    END;

  --Reject the request without sending a response to the requestor
  RejectRequest: PUBLIC <<PacketExchange>> PROC[
    h: ExchangeHandle, rH: PacketExchange.RequestHandle] =
    {CommHeap.zone.FREE[@rH]};

  --Set the wait times (the total and the retransmission interval) for the handle
  SetWaitTimes: PUBLIC <<PacketExchange>> PROC[
    h: ExchangeHandle, waitTime,
    retransmissionInterval: PacketExchange.WaitTime] =
    BEGIN
    retries: LONG CARDINAL;
    max: LONG CARDINAL = LAST[CARDINAL];
    Socket.SetWaitTime[h.cH, MIN[waitTime, retransmissionInterval]];
    retries ← (waitTime - 1) / retransmissionInterval;
    --The following is for the pretty bazarre situation.
    h.retries ← Inline.LowHalf[IF retries > max THEN max ELSE retries];
    END;  --SetWaitTime

  SendReply: PUBLIC <<PacketExchange>> PROC[
    h: ExchangeHandle, rH: PacketExchange.RequestHandle,
    replyBlk: Environment.Block, replyType: PacketExchange.ExchangeClientType] =
    BEGIN
    nsb: NSBuffer.Body;
    replyB: NSBuffer.Buffer;
    blkByteLen: CARDINAL = replyBlk.stopIndexPlusOne - replyBlk.startIndex;
    IF blkByteLen > maxBlockLength THEN ERROR Error[blockTooBig];
    replyB ← GetBuffer[h, blkByteLen];  --get a buffer to send
    nsb ← replyB.ns;  --make a local copy
    --move all info into the replyB
    nsb.pktLength ← bytesPerExchangePacketHeader + blkByteLen;
    nsb.packetType ← packetExchange;
    nsb.destination ← rH.requestorsAddress;
    nsb.exchangeType ← replyType;
    nsb.exchangeID ← rH.requestorsExchangeID;
    [] ← ByteBlt.ByteBlt[[@nsb.exchangeBytes, 0, blkByteLen], replyBlk];
    SendReplyPacket[h, replyB ! UNWIND => Socket.ReturnBuffer[replyB]];
    CommHeap.zone.FREE[@rH];
    END;  --SendReply  

  SendReplyPacket: PUBLIC <<PacketExchangeInternal>> PROC[
    h: ExchangeHandle, replyB: NSBuffer.Buffer] =
    BEGIN
    Socket.PutPacket[h.cH, replyB];
    IF CommFlags.doStats THEN
      BEGIN
      Stats.StatIncr[statDataPacketsSent];
      Stats.StatBump[statDataBytesSent,
        replyB.ns.pktLength - NSTypes.bytesPerExchangeHeader -
        NSTypes.bytesPerIDPHeader];
      END;
    END;  --SendReplyPacket
  
  SendRequest: PUBLIC <<PacketExchange>> PROC[
    h: ExchangeHandle, remote: System.NetworkAddress,
    requestBlk, replyBlk: Environment.Block,
    requestType: PacketExchange.ExchangeClientType]
    RETURNS [nBytes: CARDINAL, replyType: PacketExchange.ExchangeClientType] =
    BEGIN
    blkSizeOK: BOOLEAN;
    nsb: NSBuffer.Body;
    requestB, replyB: NSBuffer.Buffer;
    blkByteLen: CARDINAL ← requestBlk.stopIndexPlusOne - requestBlk.startIndex;
    h.exchangeID ← h.exchangeID + 1;  --increment by one
    IF blkByteLen > maxBlockLength THEN ERROR Error[blockTooBig];
    requestB ← GetBuffer[h, blkByteLen];
    nsb ← requestB.ns;  --make a local copy
    --move all info into the requestB
    nsb.exchangeID ← LOOPHOLE[PacketExchange.ExchWords[h.exchangeID]];
    nsb.pktLength ← bytesPerExchangePacketHeader + blkByteLen;
    nsb.packetType ← packetExchange;
    nsb.destination ← remote;
    nsb.exchangeType ← requestType;
    [] ← ByteBlt.ByteBlt[
      [@nsb.exchangeBytes, 0, blkByteLen], requestBlk];

    --Send the buffer (many times in case of timeouts) and extract
    --info from reply buffer.
    BEGIN
    ENABLE
    UNWIND => Socket.ReturnBuffer[requestB];
    replyB ← SendRequestPacket[h, requestB, unspecified !
      Error => IF why = timeout THEN {SIGNAL Timeout; RETRY}];
    nsb ← replyB.ns;  --BEWARE!! reusing local variable
    END;

    Socket.ReturnBuffer[requestB];
    replyType ← nsb.exchangeType;
    nBytes ← nsb.pktLength - bytesPerExchangePacketHeader;
    blkByteLen ← replyBlk.stopIndexPlusOne - replyBlk.startIndex;
    IF blkSizeOK ←
      ((nBytes <= blkByteLen)
        AND (replyBlk.startIndex <= replyBlk.stopIndexPlusOne)) THEN
      nBytes ← ByteBlt.ByteBlt[replyBlk, [@nsb.exchangeBytes, 0, nBytes]];
    Socket.ReturnBuffer[replyB];
    IF ~blkSizeOK THEN ERROR Error[blockTooSmall];
    END;  --SendRequest  

  <<
  Caller has responsibility for returning requestB ,
  and replyB if not NIL.  The pktLength, destination and exchangeType
  must be set by the caller.
  >>
  SendRequestPacket: PUBLIC <<PacketExchangeInternal>> PROC[
    h: ExchangeHandle, requestB: NSBuffer.Buffer,
    replyFilter: PacketExchange.ExchangeClientType,
    replyMask: LONG CARDINAL ← LAST[LONG CARDINAL]]
    RETURNS [replyB: NSBuffer.Buffer] =
    BEGIN

    WaitSendCompleted: ENTRY PROC[waitBuffer: NSBuffer.Buffer]
      RETURNS [NSBuffer.TransferStatus] = INLINE
      {WHILE (waitBuffer.requeueData = sendInProgress) DO
        WAIT sendCompleted; ENDLOOP; RETURN[waitBuffer.fo.status]};

    tries: CARDINAL;
    repBB: NSBuffer.Body;
    error: PacketExchange.ErrorReason;
    reqBB: NSBuffer.Body = requestB.ns;

    retryCount: CARDINAL ← h.retries;
    exchangeID: PacketExchange.ExchangeID ← reqBB.exchangeID;
    reqBB.packetType ← packetExchange;
    IF CommFlags.doStats THEN
      BEGIN
      Stats.StatIncr[statDataPacketsSent];
      Stats.StatBump[statDataBytesSent,
        reqBB.pktLength - NSTypes.bytesPerExchangeHeader -
        NSTypes.bytesPerIDPHeader];
      END; 
    FOR tries IN[0..retryCount] DO  --] => execute once if retryCount = 0
      IF CommFlags.doStats AND (tries # 0) THEN
        Stats.StatIncr[statDataPacketsRetransmitted];
      IF (tries MOD 8) = 7 THEN  --things are messed up
        RouterInternal.FlushCacheEntry[reqBB.destination.net]; 
      --UNTIL EXIT-- DO
	replyB ← NIL;
	requestB.requeueProcedure ← NotifySendCompleted;
	requestB.requeueData ← sendInProgress;
	Socket.PutPacket[h.cH, requestB];
	SELECT WaitSendCompleted[requestB] FROM
	  goodCompletion =>
	    DO  --do gets until satisfied or timed out
	      replyB ← Socket.GetPacket[h.cH ! Socket.TimeOut => EXIT];
	      repBB ← replyB.ns;  --make a local copy
	      SELECT TRUE FROM
		((reqBB.destination.host # System.broadcastHostNumber)
		  AND (repBB.source.host # reqBB.destination.host)) => NULL;
		(repBB.packetType = error) =>
		  BEGIN
		  error ← SELECT repBB.errorType FROM
		    noSocket => noReceiverAtDestination,
		    congestionWarning, congestionDiscard => timeout, 
		    resourceLimits => insufficientResourcesAtDestination,
		    listenerReject, invalidPacketType, protocolViolation =>
		      rejectedByReceiver,
		    ENDCASE => hardwareProblem;
		  IF CommFlags.doStats THEN
		    Stats.StatIncr[statErrorPacketsReceived];
		  Socket.ReturnBuffer[replyB];  --free buffer
		  GOTO badTimes;  --raise signal
		  END;
		(repBB.packetType # packetExchange) =>
		  BEGIN
		  IF CommFlags.doStats THEN
		    Stats.StatIncr[statPacketsRejectedBadType];
		  RouterInternal.SendErrorPacket[replyB, invalidPacketType];
		  replyB ← NIL; LOOP;
		  END;
		(repBB.source.socket # reqBB.destination.socket) =>
		  IF CommFlags.doStats THEN
		    Stats.StatIncr[statPacketsRejectedBadSource];
		(Inline.DBITAND[Inline.DBITXOR[
		 LOOPHOLE[repBB.exchangeID, LONG UNSPECIFIED],
		 LOOPHOLE[exchangeID, LONG UNSPECIFIED]], replyMask]#0) =>
		  IF CommFlags.doStats THEN
		    Stats.StatIncr[statPacketsRejectedBadID];
		((repBB.exchangeType # replyFilter)
		  AND (replyFilter # unspecified)) =>
		  IF CommFlags.doStats THEN
		    Stats.StatIncr[statPacketsRejectedBadID];
		ENDCASE =>
		  BEGIN
		  IF CommFlags.doStats THEN
		    BEGIN
		    Stats.StatIncr[statDataPacketsReceived];
		    Stats.StatBump[statDataBytesReceived,
		      reqBB.pktLength - NSTypes.bytesPerExchangeHeader -
		      NSTypes.bytesPerIDPHeader];
		    END;
		  RETURN;  --success
		  END;
		Socket.ReturnBuffer[replyB]; replyB ← NIL;
	      ENDLOOP;  --goodCompletion
	  aborted => LOOP;  --aborted by StuckOutput?
	  noRouteToNetwork => {error ← noRouteToDestination; GOTO badTimes};
	  invalidDestAddr => {error ← noReceiverAtDestination; GOTO badTimes};
	  ENDCASE => {error ← hardwareProblem; GOTO badTimes};
	EXIT;
	ENDLOOP;
      REPEAT badTimes => ERROR Error[error];
      ENDLOOP;
    Error[timeout];
    END;  --SendRequestPacket  

  WaitForRequest: PUBLIC <<PacketExchange>> PROC[
    h: ExchangeHandle, requestBlk: Environment.Block,
    requiredRequestType: PacketExchange.ExchangeClientType]
    RETURNS [PacketExchange.RequestHandle] =
    BEGIN
    blkSizeOK: BOOLEAN;
    reqBB: NSBuffer.Body;
    requestB: NSBuffer.Buffer;
    nBytes, blkByteLen: CARDINAL;
    rH: LONG POINTER TO PacketExchange.RequestObject;
    requestB ← WaitForRequestPacket[h, requiredRequestType !
      Error => IF why = timeout THEN {SIGNAL Timeout; RETRY}];
    reqBB ← requestB.ns;  --make a local copy
    nBytes ← reqBB.pktLength - bytesPerExchangePacketHeader;
    blkByteLen ← requestBlk.stopIndexPlusOne - requestBlk.startIndex;
    IF blkSizeOK ←
      ((nBytes <= blkByteLen)
        AND (requestBlk.startIndex <= requestBlk.stopIndexPlusOne)) THEN
      BEGIN
      rH ← CommHeap.zone.NEW[PacketExchange.RequestObject ← [
        nBytes: ByteBlt.ByteBlt[
        requestBlk, [@reqBB.exchangeBytes, 0, nBytes]],
        requestType: reqBB.exchangeType,
        requestorsAddress: reqBB.source,
        requestorsExchangeID: reqBB.exchangeID]];
      END;
    Socket.ReturnBuffer[requestB];
    IF ~blkSizeOK THEN ERROR Error[blockTooSmall];
    RETURN[LOOPHOLE[rH]];
    END;  --WaitForRequest  

  WaitForRequestPacket: PUBLIC <<PacketExchangeInternal>> PROC[
    h: ExchangeHandle, request: PacketExchange.ExchangeClientType]
    RETURNS [requestB: NSBuffer.Buffer] =
    BEGIN
    retryCount: CARDINAL ← h.retries;
    THROUGH [0..retryCount] DO  --execute loop if retryCount = 0
      requestB ← NIL;
      DO  --do gets until satisfied or timed out
        reqBB: NSBuffer.Body;
        requestB ← Socket.GetPacket[h.cH ! Socket.TimeOut => EXIT];
	reqBB ← requestB.ns;  --make a local copy
	SELECT TRUE FROM
	  (reqBB.packetType # packetExchange) =>
	    BEGIN
	    IF CommFlags.doStats THEN
	      Stats.StatIncr[statPacketsRejectedBadType];
	    RouterInternal.SendErrorPacket[requestB, invalidPacketType];
	    requestB ← NIL; LOOP;
	    END;
	  (request # unspecified) AND (reqBB.exchangeType # request) =>
	    IF CommFlags.doStats THEN
	      Stats.StatIncr[statPacketsRejectedBadType];
          ENDCASE =>
	    BEGIN
	    IF CommFlags.doStats THEN
	      BEGIN
	      Stats.StatIncr[statDataPacketsReceived];
	      Stats.StatBump[statDataBytesReceived,
	        reqBB.pktLength - NSTypes.bytesPerExchangeHeader -
		NSTypes.bytesPerIDPHeader];
	      END;
	    RETURN;
	    END;
	  Socket.ReturnBuffer[requestB];
	  requestB ← NIL;
        ENDLOOP;  --get loop
      ENDLOOP;
    ERROR Error[timeout];
    END;  --WaitForRequestPacket  

  --MainLine Code
  Process.InitializeCondition[@sendCompleted, Process.MsecToTicks[2000]];

  END.

LOG

17-May-84 11:03:36  AOF  Post Klamath
25-Apr-85  9:41:49  AOF  Remove LOOPHOLEs around .TransferStatus
 6-Feb-86  8:47:15  AOF  Merge in ExpeditedCourier changes
22-Apr-86 14:46:45  AOF  Free replyB in ...$SendReply
22-May-86 18:24:54  SMA  No dependencies on Buffer.
27-May-86  9:26:01  SMA  Socket.ReturnBuffer => Socket.ReturnBuffer.
18-Aug-86 17:54:19  AOF  Local caching of b.ns.
 8-Nov-86 20:01:40  AOF  Largish buffers.
 6-May-87 13:26:54  AOF  Flush cache after 8 retries.
16-Sep-87 14:44:11  AOF  Reordering of filtering of reply packet (AR#11862).