-- Copyright (C) 1983, 1984, 1985  by Xerox Corporation. All rights reserved. 
-- SimplePhoneNetworkDriver.mesa, HGM, 10-Jun-85 22:17:10
-- SimplePhoneNetworkDriver.mesa, LSK,  9-Dec-84 18:36:28
-- Discarded Dialing and HalfDuplex, Copy Encapsulation from old DriverTypes
-- From PhoneNetworkDriver.mesa (last edited by: AOF on: 16-Feb-83  9:40:08)

DIRECTORY
  Environment USING [Block, Byte],
  Inline USING [LowHalf],
  Process USING [Detach, InitializeCondition, SetPriority, MsecToTicks],
  ProcessPriorities USING [priorityIOHigh],
  System USING [
    GetClockPulses, GetGreenwichMeanTime, HostNumber, localHostNumber, MicrosecondsToPulses,
    nullHostNumber, nullNetworkNumber, Pulses, PulsesToMicroseconds],
  
  Buffer USING [AccessHandle, Buffer, MakePool, NSBuffer, PupBuffer, Type],
  CommFlags USING [driverStats],
  CommUtil USING [GetEthernetHostNumber],
  Driver USING [
    NetworkObject, Network, PutOnGlobalDoneQueue,
    AddDeviceToChain, GetInputBuffer, PutOnGlobalInputQueue, ReturnFreeBuffer],
  DriverTypes USING [DeviceType, Encapsulation],
  PhoneCreate USING [],
  PhoneNetFriends USING [PhoneNetInfo, NetEnumerator, nullNetEnumerate],
  PhoneNetExtras USING [
    leaf, leafBytesSend, leafDupsFiltered, leafPktsSend,
    nsBytesSend, nsCongestion, nsDupsFiltered, nsTooGreedy,
    pupBytesSend, pupCongestion, pupDupsFiltered, pupTooGreedy],
  PupTypes USING [PupErrorCode, PupHostID, PupSocketID],
  RS232C USING [
    ChannelHandle, CompletionHandle, ChannelSuspended, CommParamObject, 
    DeviceStatus, Get, GetStatus, PhysicalRecord, PhysicalRecordHandle, Put,
    Restart, SetParameter, Suspend, TransmitNow, TransferStatus, TransferWait];

SimplePhoneNetworkDriver: MONITOR
  IMPORTS Buffer, CommUtil, Driver, Inline, Process, RS232C, System
  EXPORTS Buffer, PhoneCreate, PhoneNetFriends =
  BEGIN
  
  Byte: TYPE = Environment.Byte;

  PhonePacketType: TYPE = MACHINE DEPENDENT{
    pupPhonePacket(100B),
    nsPhonePacket(300B),
--  turnAroundPhonePacket(301B),
--  turnAroundMTSPhonePacket(302B),
--  terminatePhonePacket(303B),
--  terminateAckPhonePacket(304B),
    (LAST[Byte])};

  Encapsulation: TYPE = MACHINE DEPENDENT RECORD [
    SELECT OVERLAID DriverTypes.DeviceType FROM
      phonenet => [
	framing0, framing1, framing2, framing3, framing4, framing5: Byte,
	recognition: Byte, -- 0 for auto recognition of OISCP vs SDLC/HDLC
	pnType: PhonePacketType,
	pnSrcID: System.HostNumber],
      ENDCASE];

  phoneEncapsulationOffset: CARDINAL = 3;
  phoneEncapsulationBytes: CARDINAL = 8;

  encapsulationTrap: BOOLEAN [TRUE..TRUE] =
    (SIZE[Encapsulation] = SIZE[DriverTypes.Encapsulation]);
  
  -- EXPORTed TYPEs
  Network: PUBLIC TYPE = Driver.Network;

  InfoRecord: TYPE = RECORD [
    channelHandle: RS232C.ChannelHandle,
    packetToSend: CONDITION,
    currentSendBuffer: Buffer.Buffer,
    firstHiSendBuffer, lastHiSendBuffer: Buffer.Buffer,
    firstLoSendBuffer, lastLoSendBuffer: Buffer.Buffer,
    packetsOnSendQueue: CARDINAL,
    timeSendStarted: System.Pulses,
    timeLastRecv: System.Pulses,
    bitsPerSecond: LONG CARDINAL,
    pool: Buffer.AccessHandle,
    network: Driver.NetworkObject,  -- phone network object for Router
    stats: PhoneNetFriends.PhoneNetInfo,
    loophole: CrapForINRLoophole];
    
  CrapForINRLoophole: TYPE = MONITORED RECORD [
    clientData: LONG UNSPECIFIED,
    lineNumber: CARDINAL];

    
  maxHiPriWords: CARDINAL = (2 + 50 + 22)/2; -- 50 data bytes in a pup
  maxQueueDepth: CARDINAL ← 15; -- For each of Pup, NS, ...
  maxForwarderDepth: CARDINAL ← 10;
  maxConnectionDepth: CARDINAL ← 5;
  
  leafSocket: PupTypes.PupSocketID = PhoneNetExtras.leaf;
  
  outstandingGets: CARDINAL = 2;
  
  info: InfoRecord;
  me: System.HostNumber ← System.localHostNumber;

  CreateSimplePhoneNet: PUBLIC PROCEDURE [
    lineNumber: CARDINAL, chHandle: RS232C.ChannelHandle, commParams: RS232C.CommParamObject] =
    BEGIN
    Process.InitializeCondition[@info.packetToSend, Process.MsecToTicks[1000]];
    IF CommFlags.driverStats THEN info.stats ← [
      state: data,
      timeStarted: System.GetGreenwichMeanTime[],
      timeConnectionEstablished: System.GetGreenwichMeanTime[],
      remoteHostNumber: System.nullHostNumber,
      protocolVersion: old,
      lineNumber: NULL,
      duplexity: full,
      lineSpeed: bps9600,
      speed: 0, -- Observed, Kilobits/second
      negotiationMode: NULL,
      ourEntityClass: NULL,
      theirEntityClass: NULL,
      halfDuplexMode: NULL,
      hardwareStatsAvailable: TRUE,
      clientData: 0,
      clientHostNumber: NULL,
      sendQueueLength: 0,
      stats: ALL [0] ];
    info.currentSendBuffer ← NIL;
    info.currentSendBuffer ← NIL;
    info.firstHiSendBuffer ← NIL;
    info.lastHiSendBuffer ← NIL;
    info.firstLoSendBuffer ← NIL;
    info.lastLoSendBuffer ← NIL;
    info.packetsOnSendQueue ← 0;
    info.bitsPerSecond ← 0;
    info.network ← [
      next: NIL,
      decapsulateBuffer: Decapsulate,
      encapsulateAndSendPup: SendPup,
      encapsulateAndSendNS: SendNS,
      sendRawBuffer: SendRawBuffer,
      encapsulateAndForwardPup: ForwardPup,
      encapsulateAndForwardNS: ForwardNS,
      activateDriver: ActivateDriver,
      deactivateDriver: NIL,
      deleteDriver: NIL,
      changeNumberOfInputBuffers: NIL,
      index:,
      pupNetNumber: 0,
      pupHostNumber: CommUtil.GetEthernetHostNumber[],
      device: phonenet,
      alive: TRUE,
      buffers: 0,
      netNumber: System.nullNetworkNumber,
      pupStats: NIL,
      statsLevel0: @info.loophole,
      statsLevel1: NIL];
    info.stats.lineNumber ← lineNumber;
    info.stats.remoteHostNumber ← System.nullHostNumber;
    info.channelHandle ← chHandle;
    info.loophole.clientData ← @info.stats;
    info.loophole.lineNumber ← lineNumber;
    RS232C.SetParameter[info.channelHandle, [dataTerminalReady[TRUE]]];
    RS232C.SetParameter[info.channelHandle, [requestToSend[TRUE]]];
    info.pool ← Buffer.MakePool[1, outstandingGets];
    Driver.AddDeviceToChain[@info.network];
    Process.Detach[FORK Receiver[]];
    Process.Detach[FORK Sender[]];
    Process.Detach[FORK Watcher[]];
    END;
    

  -- 2400 baud is 300 bytes/sec.  Allow 10% for slop and whatever to get 270.
  pulsesPerByte: System.Pulses = [(System.MicrosecondsToPulses[1000000]/270)+1];
  recvIdleTimeout: System.Pulses = System.MicrosecondsToPulses[30000000];
  Watcher: ENTRY PROCEDURE =
    BEGIN
    watch: CONDITION;
    tick: CONDITION;
    Process.InitializeCondition[@watch, Process.MsecToTicks[1000]];
    Process.InitializeCondition[@tick, 1];
    DO
      now: System.Pulses = System.GetClockPulses[];
      b: Buffer.Buffer ← info.currentSendBuffer;
      IF b # NIL AND (now - info.timeSendStarted) > pulsesPerByte*b.driver.length*2 THEN
        BEGIN
        info.stats.remoteHostNumber ← System.nullHostNumber;
        RS232C.Suspend[info.channelHandle, output];
        UNTIL info.packetsOnSendQueue = 0 DO WAIT tick; ENDLOOP;
        RS232C.Restart[info.channelHandle, output];
        info.stats.remoteHostNumber ← System.nullHostNumber;
        END;
      WAIT watch;
      ENDLOOP;
    END;
  
  -- **************** Decapsulation ****************

  Decapsulate: PROCEDURE [b: Buffer.Buffer] RETURNS [type: Buffer.Type] =
    BEGIN
    encapsulation: LONG POINTER TO Encapsulation = LOOPHOLE[@b.encapsulation];
    bytes: CARDINAL ← 2*b.driver.length;
    IF bytes < phoneEncapsulationBytes THEN GOTO Rejected;
    bytes ← bytes - phoneEncapsulationBytes;
    SELECT encapsulation.pnType FROM
      nsPhonePacket =>
        BEGIN
	IF bytes < b.ns.pktLength THEN GOTO Rejected;
	type ← ns;
	END;
      pupPhonePacket =>
        BEGIN
	IF bytes < b.pup.pupLength THEN GOTO Rejected;
        type ← pup;
	END;
      ENDCASE => GOTO Rejected;
    info.stats.remoteHostNumber ← encapsulation.pnSrcID;
    EXITS Rejected =>
      BEGIN
      type ← rejected;
      IF CommFlags.driverStats THEN StatIncr[@info.stats.stats[pktsRejected]];
      END;
    END;
    
  -- **************** Packet Transport / Sending ****************

  SendPup: ENTRY PROCEDURE [b: Buffer.PupBuffer, host: PupTypes.PupHostID] =
    BEGIN
    high: BOOLEAN;
    friends, pups: CARDINAL ← 0;
    encapsulation: LONG POINTER TO Encapsulation = LOOPHOLE[@b.encapsulation];
    encapsulation↑ ← [
      phonenet[
	framing0: 0, framing1: 0, framing2: 0, framing3: 0, framing4: 0,
	framing5: 0, recognition: 0,
	pnType: pupPhonePacket, pnSrcID: me] ];
    b.driver.length ← (b.pup.pupLength + 1 + phoneEncapsulationBytes)/2;
    [friends, pups, ] ← CountFriends[b, info.firstLoSendBuffer, friends, pups];
    high ← b.driver.length < maxHiPriWords AND (friends = 0);
    [friends, pups, ] ← CountFriends[b, info.firstHiSendBuffer, friends, pups];
    IF friends > maxConnectionDepth THEN
      BEGIN
      Driver.PutOnGlobalDoneQueue[b];
      StatIncr[@info.stats.stats[connTooGreedy]];
      StatIncr[@info.stats.stats[PhoneNetExtras.pupTooGreedy]];
      RETURN;
      END;
    IF pups > maxQueueDepth THEN
      BEGIN
      Driver.PutOnGlobalDoneQueue[b];
      StatIncr[@info.stats.stats[congestion]];
      StatIncr[@info.stats.stats[PhoneNetExtras.pupCongestion]];
      RETURN;
      END;
    QueueOutputBuffer[b, high];
    StatIncr[@info.stats.stats[pupSent]];
    StatBump[@info.stats.stats[PhoneNetExtras.pupBytesSend], b.driver.length*2];
    IF b.pup.source.socket = leafSocket OR b.pup.dest.socket = leafSocket THEN {
      StatIncr[@info.stats.stats[PhoneNetExtras.leafPktsSend]];
      StatBump[@info.stats.stats[PhoneNetExtras.leafBytesSend], b.driver.length*2]; };
    END;

  SendNS: ENTRY PROCEDURE [b: Buffer.NSBuffer, host: System.HostNumber] =
    BEGIN
    high: BOOLEAN;
    friends, ns: CARDINAL ← 0;
    encapsulation: LONG POINTER TO Encapsulation = LOOPHOLE[@b.encapsulation];
    encapsulation↑ ← [
      phonenet[
	framing0: 0, framing1: 0, framing2: 0, framing3: 0, framing4: 0,
	framing5: 0, recognition: 0,
	pnType: nsPhonePacket, pnSrcID: me] ];
    b.driver.length ← (b.ns.pktLength + 1 + phoneEncapsulationBytes)/2;
    [friends, ns, ] ← CountFriends[b, info.firstLoSendBuffer, friends, ns];
    high ← b.driver.length < maxHiPriWords AND (friends = 0);
    [friends, ns, ] ← CountFriends[b, info.firstHiSendBuffer, friends, ns];
    IF friends > maxConnectionDepth THEN
      BEGIN
      Driver.PutOnGlobalDoneQueue[b];
      StatIncr[@info.stats.stats[connTooGreedy]];
      StatIncr[@info.stats.stats[PhoneNetExtras.nsTooGreedy]];
      RETURN;
      END;
    IF ns > maxQueueDepth THEN
      BEGIN
      Driver.PutOnGlobalDoneQueue[b];
      StatIncr[@info.stats.stats[congestion]];
      StatIncr[@info.stats.stats[PhoneNetExtras.nsCongestion]];
      RETURN;
      END;
    QueueOutputBuffer[b, high];
    StatIncr[@info.stats.stats[nsSent]];
    StatBump[@info.stats.stats[PhoneNetExtras.nsBytesSend], b.driver.length*2];
    END;

  SendRawBuffer: ENTRY PROCEDURE [b: Buffer.Buffer] =
    BEGIN
    IF info.packetsOnSendQueue > maxQueueDepth THEN
      BEGIN
      Driver.PutOnGlobalDoneQueue[b];
      StatIncr[@info.stats.stats[congestion]];
      RETURN;
      END;
    QueueOutputBuffer[b, FALSE];
    StatIncr[@info.stats.stats[rawSent]];
    END;

  ForwardPup: ENTRY PROCEDURE [b: Buffer.PupBuffer, host: PupTypes.PupHostID]
    RETURNS [PupTypes.PupErrorCode] =
    BEGIN
    high: BOOLEAN;
    dup, lic, ate: BOOLEAN;
    friends, pups: CARDINAL ← 0;
    encapsulation: LONG POINTER TO Encapsulation = LOOPHOLE[@b.encapsulation];
    encapsulation↑ ← [
      phonenet[
	framing0: 0, framing1: 0, framing2: 0, framing3: 0, framing4: 0,
	framing5: 0, recognition: 0,
	pnType: pupPhonePacket, pnSrcID: me] ];
    b.driver.length ← (b.pup.pupLength + 1 + phoneEncapsulationBytes)/2;
    [friends, pups, dup] ← CountFriends[b, info.firstLoSendBuffer, friends, pups];
    high ← b.driver.length < maxHiPriWords AND (friends = 0);
    [friends, pups, lic] ← CountFriends[b, info.firstHiSendBuffer, friends, pups];
    [friends, pups, ate] ← CountFriends[b, info.currentSendBuffer, friends, pups];
    IF ((b.pup.pupType = aData OR b.pup.pupType = data) AND (dup OR lic OR ate) OR
      (b.pup.pupType = aData AND friends # 0 AND b.pup.pupLength = 22)) THEN {
      -- Barf, what an ugly hack to put into a gateway
      -- Duplicate data/aData OR (empty aData (probe) when something on the queue)
      StatIncr[@info.stats.stats[PhoneNetExtras.pupDupsFiltered]];
      RETURN[LOOPHOLE[10002]]; };
    IF (b.pup.source.socket = leafSocket OR b.pup.dest.socket = leafSocket)
    AND (dup OR lic OR ate) THEN {
      StatIncr[@info.stats.stats[PhoneNetExtras.leafDupsFiltered]];
      RETURN[LOOPHOLE[10002]]; };
    IF friends > maxConnectionDepth THEN
      BEGIN
      StatIncr[@info.stats.stats[connTooGreedy]];
      StatIncr[@info.stats.stats[PhoneNetExtras.pupTooGreedy]];
      RETURN[gatewayResourceLimitsPupErrorCode];
      END;
    IF pups > maxForwarderDepth THEN
      BEGIN
      StatIncr[@info.stats.stats[congestion]];
      StatIncr[@info.stats.stats[PhoneNetExtras.pupCongestion]];
      RETURN[gatewayResourceLimitsPupErrorCode];
      END;
    QueueOutputBuffer[b, high];
    StatIncr[@info.stats.stats[pupSent]];
    StatBump[@info.stats.stats[PhoneNetExtras.pupBytesSend], b.driver.length*2];
    IF b.pup.source.socket = leafSocket OR b.pup.dest.socket = leafSocket THEN {
      StatIncr[@info.stats.stats[PhoneNetExtras.leafPktsSend]];
      StatBump[@info.stats.stats[PhoneNetExtras.leafBytesSend], b.driver.length*2]; };
    RETURN[noErrorPupErrorCode];
    END;

  ForwardNS: ENTRY PROCEDURE [b: Buffer.PupBuffer, host: System.HostNumber] =
    BEGIN
    high: BOOLEAN;
    dup, lic, ate: BOOLEAN;
    friends, ns: CARDINAL ← 0;
    encapsulation: LONG POINTER TO Encapsulation = LOOPHOLE[@b.encapsulation];
    encapsulation↑ ← [
      phonenet[
	framing0: 0, framing1: 0, framing2: 0, framing3: 0, framing4: 0,
	framing5: 0, recognition: 0,
	pnType: nsPhonePacket, pnSrcID: me] ];
    b.driver.length ←
      (b.ns.pktLength + 1 + phoneEncapsulationBytes)/2;
    [friends, ns, dup] ← CountFriends[b, info.firstLoSendBuffer, friends, ns];
    high ← b.driver.length < maxHiPriWords AND (friends = 0);
    [friends, ns, lic] ← CountFriends[b, info.firstHiSendBuffer, friends, ns];
    [friends, ns, ate] ← CountFriends[b, info.currentSendBuffer, friends, ns];
    IF dup OR lic OR ate THEN
      BEGIN
      Driver.PutOnGlobalDoneQueue[b];
      StatIncr[@info.stats.stats[PhoneNetExtras.nsDupsFiltered]];
      RETURN;
      END;
    IF friends > maxConnectionDepth THEN
      BEGIN
      Driver.PutOnGlobalDoneQueue[b];
      StatIncr[@info.stats.stats[connTooGreedy]];
      StatIncr[@info.stats.stats[PhoneNetExtras.nsTooGreedy]];
      RETURN;
      END;
    IF ns > maxForwarderDepth THEN
      BEGIN
      Driver.PutOnGlobalDoneQueue[b];
      StatIncr[@info.stats.stats[congestion]];
      StatIncr[@info.stats.stats[PhoneNetExtras.nsCongestion]];
      RETURN;
      END;
    QueueOutputBuffer[b, high];
    StatIncr[@info.stats.stats[nsSent]];
    StatBump[@info.stats.stats[PhoneNetExtras.nsBytesSend], b.driver.length*2];
    END;

  CountFriends: INTERNAL PROCEDURE [
    b: Buffer.Buffer, head: Buffer.Buffer, friends, neighbors: CARDINAL]
    RETURNS [newFriends, newNeighbors: CARDINAL, headerMatch: BOOLEAN] =
    BEGIN
    encapsulation: LONG POINTER TO Encapsulation = LOOPHOLE[@b.encapsulation];
    headerMatch ← FALSE;
    SELECT encapsulation.pnType FROM
      pupPhonePacket => {
	FOR maybe: Buffer.PupBuffer ← head, maybe.next UNTIL maybe = NIL DO
          encap: LONG POINTER TO Encapsulation = LOOPHOLE[@maybe.encapsulation];
	  IF encap.pnType = pupPhonePacket THEN {
	    neighbors ← neighbors + 1;
	    IF b.pup.dest = maybe.pup.dest THEN {
	      PupHeader: TYPE = ARRAY [0..11) OF WORD;
	      x: LONG POINTER TO PupHeader ← LOOPHOLE[@b.pup.pupLength];
	      y: LONG POINTER TO PupHeader ← LOOPHOLE[@maybe.pup.pupLength];
	      IF x↑ = y↑ THEN headerMatch ← TRUE;
	      friends ← friends + 1; }; };
	  ENDLOOP; };
      nsPhonePacket => {
	FOR maybe: Buffer.PupBuffer ← head, maybe.next UNTIL maybe = NIL DO
          encap: LONG POINTER TO Encapsulation = LOOPHOLE[@maybe.encapsulation];
	  IF encap.pnType = nsPhonePacket THEN {
	    neighbors ← neighbors + 1;
	    IF b.ns.destination = maybe.ns.destination THEN {
	      SELECT b.ns.packetType FROM
	        sequencedPacket => {
	          IF b.ns.sequenceNumber = maybe.ns.sequenceNumber THEN headerMatch ← TRUE; };
	        packetExchange => {
	          IF b.ns.exchangeID = maybe.ns.exchangeID THEN headerMatch ← TRUE; };
		ENDCASE => NULL;
	      friends ← friends + 1; }; };
	  ENDLOOP; };
      ENDCASE => NULL;  -- You get what you deserve
    RETURN[friends, neighbors, headerMatch]
    END;

  QueueOutputBuffer: INTERNAL PROCEDURE [b: Buffer.Buffer, high: BOOLEAN] =
    BEGIN
    b.network ← LONG[@info.network];
    info.packetsOnSendQueue ← info.packetsOnSendQueue + 1;
    b.next ← NIL;
    IF high THEN
      BEGIN
      IF info.firstHiSendBuffer = NIL THEN info.firstHiSendBuffer ← b
      ELSE info.lastHiSendBuffer.next ← b;
      info.lastHiSendBuffer ← b;
      END
    ELSE
      BEGIN
      IF info.firstLoSendBuffer = NIL THEN info.firstLoSendBuffer ← b
      ELSE info.lastLoSendBuffer.next ← b;
      info.lastLoSendBuffer ← b;
      END;
    NOTIFY info.packetToSend;
    END;
  
  Sender: PROCEDURE =
    BEGIN
    Process.SetPriority[ProcessPriorities.priorityIOHigh];
    DO
      StartSending[];
      SendIt[];
      FinishSending[];
      ENDLOOP;
    END;

  StartSending: ENTRY PROCEDURE =
    BEGIN
    b: Buffer.Buffer;
    UNTIL (info.packetsOnSendQueue # 0) DO WAIT info.packetToSend; ENDLOOP;
    SELECT TRUE FROM
      info.firstHiSendBuffer # NIL =>
        BEGIN
        b ← info.firstHiSendBuffer;
        info.firstHiSendBuffer ← b.next;
        END;
      info.firstLoSendBuffer # NIL =>
        BEGIN
        b ← info.firstLoSendBuffer;
        info.firstLoSendBuffer ← b.next;
        END;
      ENDCASE => RETURN;
    info.currentSendBuffer ← b;
    END;
  
  SendIt: PROCEDURE =
    BEGIN
    complHandle: RS232C.CompletionHandle;
    rec: RS232C.PhysicalRecord ← [header: [NIL, 0, 0], body:, trailer: [NIL, 0, 0]];
    xferstatus: RS232C.TransferStatus;
    b: Buffer.Buffer ← info.currentSendBuffer;
    rec.body.blockPointer ← LOOPHOLE[@b.encapsulation + phoneEncapsulationOffset];
    rec.body.startIndex ← 0;
    rec.body.stopIndexPlusOne ← b.driver.length*2; -- even bytes
    complHandle ← RS232C.Put[info.channelHandle, @rec !
      RS232C.ChannelSuspended => GOTO Zapped];
    info.timeSendStarted ← System.GetClockPulses[];
    [, xferstatus] ← RS232C.TransmitNow[info.channelHandle, complHandle];
    IF xferstatus = aborted THEN GOTO Zapped;
    BEGIN
    bits: CARDINAL ← 7 + 16*b.driver.length + 16 + 7;
    now: System.Pulses ← System.GetClockPulses[];
    micro: LONG CARDINAL ← System.PulsesToMicroseconds[[now-info.timeSendStarted]];
    -- ARHG!!! 4000 * 1000000 overflows a LONG CARDINAL
    bitsPerSecond: LONG CARDINAL ← ((100000*bits)/micro)*10;
    info.bitsPerSecond ← (63*info.bitsPerSecond + bitsPerSecond)/64;
    info.stats.speed ← Inline.LowHalf[info.bitsPerSecond/1000];
    END;
    IF CommFlags.driverStats THEN
      IF xferstatus = success THEN
	BEGIN
	StatIncr[@info.stats.stats[pktsSent]];
	StatBump[@info.stats.stats[bytesSent], b.driver.length*2];
	END
      ELSE StatIncr[@info.stats.stats[sendErrorBadStatus]];
    EXITS Zapped => StatIncr[@info.stats.stats[queueTooOld]];
    END;
    
  FinishSending: ENTRY PROCEDURE =
    BEGIN
    Driver.PutOnGlobalDoneQueue[info.currentSendBuffer];
    info.packetsOnSendQueue ← info.packetsOnSendQueue - 1;
    info.currentSendBuffer ← NIL;
    END;

  -- **************** Packet Transport / Receiving ****************

  Receiver: PROCEDURE =
    BEGIN
    recArray: ARRAY [0..outstandingGets) OF RS232C.PhysicalRecord ←
      ALL[[[NIL, 0, 0], [NIL, 0, 0], [NIL, 0, 0]]];
    bufferArray: ARRAY [0..outstandingGets) OF Buffer.Buffer ← ALL[NIL];
    complArray: ARRAY [0..outstandingGets) OF RS232C.CompletionHandle;
    bufCount: CARDINAL ← 0;
    nextBuf: Buffer.Buffer;
    Process.SetPriority[ProcessPriorities.priorityIOHigh];
    info.timeLastRecv ← System.GetClockPulses[];
    DO
      FOR i: CARDINAL IN [0..outstandingGets) DO
        nextBuf ← Driver.GetInputBuffer[];
	IF bufferArray[i] # NIL THEN
	  BEGIN
	  okToDispose: BOOLEAN ← TRUE;
	  IF nextBuf=NIL THEN
	    BEGIN
	    IF bufCount <= 1 THEN
	      BEGIN
	      okToDispose ← FALSE;
	      nextBuf ← bufferArray[i];  -- we keep this buffer because we can't get another
	      END
	    ELSE
	      BEGIN
	      bufCount ← bufCount - 1; 
	      END;
	    END;
	  AwaitAndDisposeOfFrame[complArray[i], bufferArray[i], okToDispose];
	  END -- non-NIL bufferArray[i]
	ELSE
	  BEGIN
	  IF nextBuf # NIL THEN bufCount ← bufCount + 1;
	  END;
	bufferArray[i] ← nextBuf;
        IF bufferArray[i] # NIL THEN
          BEGIN
          recHandle: RS232C.PhysicalRecordHandle ← @recArray[i];
	  b: Buffer.Buffer ← bufferArray[i];
          recHandle.body ← [
            LOOPHOLE[@b.encapsulation + phoneEncapsulationOffset],
              0, (b.driver.length - phoneEncapsulationOffset)*2];
          complArray[i] ← RS232C.Get[info.channelHandle, recHandle];
          END;
        ENDLOOP;
      ENDLOOP; -- of UNTIL
    END;

  AwaitAndDisposeOfFrame: PROCEDURE [
    complHandle: RS232C.CompletionHandle, b: Buffer.Buffer, okToDispose: BOOLEAN] =
    BEGIN
    transferStatus: RS232C.TransferStatus;
    globalStatus: RS232C.DeviceStatus;
    bytes: CARDINAL;
    checksum: CARDINAL = 2;
    [bytes, transferStatus] ← RS232C.TransferWait[info.channelHandle, complHandle];
    IF transferStatus = success THEN
      BEGIN
      encapsulation: LONG POINTER TO Encapsulation = LOOPHOLE[@b.encapsulation];
      info.timeLastRecv ← System.GetClockPulses[];
      IF ~okToDispose THEN RETURN;
      b.driver.length ← (bytes - checksum)/2;
      b.network ← LONG[@info.network];
      b.driver.faceStatus ← unknown[101H];
      b.time ← System.GetClockPulses[];  --record time received
      Driver.PutOnGlobalInputQueue[b];
      IF CommFlags.driverStats THEN
	BEGIN
	StatIncr[@info.stats.stats[pktsReceived]];
	StatBump[@info.stats.stats[bytesReceived], bytes - checksum];
	END;
      END
    ELSE
      BEGIN -- bad frame => free the buffer
      IF ~okToDispose THEN RETURN;
      Driver.ReturnFreeBuffer[b]; 
      IF CommFlags.driverStats AND (transferStatus # aborted) THEN
	BEGIN
        statsPtr: POINTER TO LONG CARDINAL;
	statsPtr ←
	  SELECT transferStatus FROM
	    dataLost => @info.stats.stats[rcvErrorDataLost],
	    checksumError => @info.stats.stats[rcvErrorCRC],
	    frameTimeout => @info.stats.stats[rcvErrorFrameTimeout],
	    deviceError => @info.stats.stats[rcvDeviceError],
	    ENDCASE => @info.stats.stats[rcvErrorUnknown];
	StatIncr[statsPtr];
	END;
      IF transferStatus = deviceError THEN
	BEGIN
	globalStatus ← RS232C.GetStatus[info.channelHandle];
	IF globalStatus.dataLost THEN -- clear the info lost latch bit
	  BEGIN
	  RS232C.SetParameter[info.channelHandle, [latchBitClear[globalStatus]]];
	  IF CommFlags.driverStats THEN StatIncr[@info.stats.stats[rcvErrorNoGet]];
	  END;
	END;
      END;
    END;
    
    
  -- **************** Driver Management (by Router) ****************

  ActivateDriver: PROCEDURE =
    BEGIN
    END;

    
  -- **************** Statistics ****************

  StatIncr: PROCEDURE [counter: POINTER TO LONG CARDINAL] = INLINE
    BEGIN
    counter↑ ← (counter↑ + 1);
    END;

  StatBump: PROCEDURE [
    counter: POINTER TO LONG CARDINAL, bumpAmount: CARDINAL] = INLINE
    BEGIN
    counter↑ ← (counter↑ + bumpAmount);
    END;

  -- **************** Keep Kluger/INR happy ****************
  
  GetDriverInfo: PUBLIC PROCEDURE [lineNumber: CARDINAL]
    RETURNS [--found:-- BOOLEAN, --info:-- PhoneNetFriends.PhoneNetInfo] =
    BEGIN
    IF info.network.alive AND lineNumber = info.stats.lineNumber THEN
      RETURN [TRUE, info.stats];
    RETURN[FALSE, ];
    END;

  StatsPtrToInfo: PUBLIC PROCEDURE [statsLevel0: LONG POINTER]
    RETURNS [clientData: LONG UNSPECIFIED, lineNumber: CARDINAL] =
    -- statsLevel0 argument is from a phonenet network object
    BEGIN
    stats: LONG POINTER TO CrapForINRLoophole = LOOPHOLE[statsLevel0];
    RETURN [stats.clientData, stats.lineNumber];
    END;
    
  EnumerateDriverInfo: PUBLIC PROCEDURE [current: PhoneNetFriends.NetEnumerator]
    RETURNS [PhoneNetFriends.NetEnumerator, PhoneNetFriends.PhoneNetInfo] =
    BEGIN
    nonNullNetEnumerate: PhoneNetFriends.NetEnumerator = LONG[LOOPHOLE [1]];
    IF (current = PhoneNetFriends.nullNetEnumerate) AND info.network.alive THEN
      RETURN [nonNullNetEnumerate, info.stats] ELSE
      RETURN [PhoneNetFriends.nullNetEnumerate, info.stats]; -- no more
    END;
    
  -- init
  
  info.network.alive ← FALSE;

  END....