-- Copyright (C) 1984, 1985  by Xerox Corporation. All rights reserved. 
-- PhoneDriver.mesa, HGM, 28-Sep-85 19:49:26
-- WIrish,  5-Dec-86 16:46:23, fixed counting packets on output queue

DIRECTORY
  Environment USING [Byte],
  Inline USING [BITXOR, LowHalf],
  Process USING [DisableTimeout, InitializeCondition, MsecToTicks, SetPriority, Yield],
  ProcessPriorities USING [priorityPageFaultHigh],  -- Below Ethernet Drivers
  SpecialRuntime USING [AllocateNakedCondition],
  String USING [EqualString],
  System USING [
    GetClockPulses, GetGreenwichMeanTime, HostNumber, localHostNumber,
    MicrosecondsToPulses, NetworkNumber, nullHostNumber, nullNetworkNumber,
    Pulses, PulsesToMicroseconds],

  Buffer USING [AccessHandle, Buffer, MakePool, NSBuffer, PupBuffer, ReturnBuffer, Type],
  CommHeap USING [zone],
  CommunicationInternal USING [NSPackageMake],
  CommUtil USING [AllocateIocbs],
  Driver USING [
    NetworkObject, Network, PutOnGlobalDoneQueue,
    AddDeviceToChain, GetInputBuffer, PutOnGlobalInputQueue],
  DriverTypes USING [DeviceType, Encapsulation],
  ForwarderDefs USING [DoErrorPup],
  NSTypes USING [ErrorCode],
  PhoneNetFriends USING [NetEnumerator, nullNetEnumerate, PhoneNetInfo],
  PhoneNetExtras USING [leaf, leafBytesSend, leafDupsFiltered, leafPktsSend, nsBytesSend, nsCongestion, nsDupsFiltered, nsTooGreedy, pupBytesSend, pupCongestion, pupDupsFiltered, pupTooGreedy],
  Protocol1 USING [AddFamilyMember, Family, MatrixRecord],
  PupDefs USING [PupPackageMake],
  PupRouterDefs USING [ContextObject],
  PupTypes USING [PupErrorCode, PupHostID, PupSocketID],
  RouterInternal USING [SendErrorPacket],
  RoutingTable USING [ContextObject],

  DESFace USING [
    Block, CorrectParity, ECBDecrypt, ECBEncrypt, nullKey, Key],
  PhoneCreate USING [],
  PhoneFace USING [
    controlBlockSize, GetPacketLength, GetPacketsMissed, GetRecvStatus, GetSendStatus,
    QueueInput, QueueOutput, ResetLine, Status, TurnOn];

PhoneDriver: MONITOR LOCKS lock↑
  IMPORTS
    Inline, Process, SpecialRuntime, String, System,
    Buffer, CommHeap, CommunicationInternal, CommUtil, DESFace, Driver,
    ForwarderDefs, PhoneFace, Protocol1, PupDefs, RouterInternal
  EXPORTS Buffer, PhoneCreate, PhoneNetFriends =
  BEGIN
  
  -- EXPORTed TYPEs
  Network: PUBLIC TYPE = Driver.Network;
  
  congestionDiscard: NSTypes.ErrorCode = LOOPHOLE [1006B]; -- not yet in NSTypes def

  Byte: TYPE = Environment.Byte;
  
  doStats: BOOL ← TRUE;

  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;

  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]);
  
  lock: POINTER TO MONITORLOCK ← @myLock;
  myLock: MONITORLOCK;

  Info: TYPE = LONG POINTER TO InfoRecord;
  InfoRecord: TYPE = RECORD [
    lineNumber: CARDINAL,
    currentSendBuffer: Buffer.Buffer,  -- Beware: this buffer may be encrypted
    firstHiSendBuffer, lastHiSendBuffer: Buffer.Buffer,
    firstLoSendBuffer, lastLoSendBuffer: Buffer.Buffer,
    packetsOnSendQueue: CARDINAL,
    timeSendStarted: System.Pulses,
    firstRecvBuffer, lastRecvBuffer: Buffer.Buffer,
    timeLastRecv: System.Pulses,
    missed: CARDINAL,
    bitsPerSecond: LONG CARDINAL,
    network: Driver.NetworkObject,
    stats: PhoneNetFriends.PhoneNetInfo,
    encrypt: BOOLEAN,
    dlion: BOOLEAN,
    key: DESFace.Key,
    pool: Buffer.AccessHandle,
    sendIocb: LONG POINTER,
    recvIocbs: LONG POINTER,
    recvIocbCounter: CARDINAL,
    procs: Procs,
    next: Info];
    
  Procs: TYPE = RECORD [
    encapsulateNS: PROCEDURE [Buffer.PupBuffer, LONG POINTER TO System.HostNumber],
    decapsulateNS: PROCEDURE [Buffer.Buffer] RETURNS [Buffer.Type],
    encapsulatePup: PROCEDURE [Buffer.PupBuffer, LONG POINTER TO PupTypes.PupHostID],
    decapsulatePup: PROCEDURE [Buffer.Buffer] RETURNS [Buffer.Type] ];

  info: InfoRecord ← [
    lineNumber: ,
    currentSendBuffer: NIL,
    firstHiSendBuffer: NIL,
    lastHiSendBuffer: NIL,
    firstLoSendBuffer: NIL,
    lastLoSendBuffer: NIL,
    packetsOnSendQueue: 0,
    timeSendStarted: ,
    firstRecvBuffer: NIL,
    lastRecvBuffer: NIL,
    timeLastRecv: ,
    missed: 0,
    bitsPerSecond: 0,
    network: [
      next: NIL,
      activateDriver: ActivateDriver,
      deactivateDriver: KillDriver,
      matrix: DESCRIPTOR[NIL, 0],
      sendRawBuffer: SendRawBuffer,
      changeNumberOfInputBuffers: NIL,
      index: ,
      device: phonenet,
      alive: FALSE,
      buffers: 10,  -- This should be overkill
      lineSpeed: 0,
      lineNumber: 0,
      stats: @info.stats],
    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: active,
      ourEntityClass: internetworkRouter,
      theirEntityClass: internetworkRouter,
      halfDuplexMode: unknown,
      hardwareStatsAvailable: TRUE,
      clientData: 0,
      clientHostNumber: System.nullHostNumber,
      sendQueueLength: 0,
      stats: ALL [0] ],
    encrypt: FALSE,
    dlion: FALSE,
    key: TRASH,
    pool: NIL,
    sendIocb: NIL,
    recvIocbs: NIL,
    recvIocbCounter: 0,
    procs: [
      encapsulateNS: EncapsulateNS,
      decapsulateNS: DecapsulateNS,
      encapsulatePup: EncapsulatePup,
      decapsulatePup: DecapsulatePup ],
    next: NIL ];

  me: System.HostNumber = System.localHostNumber;
  
  -- Shared by all lines
  interrupt, watcher: PROCESS ← NIL;
  pleaseStop: BOOLEAN ← FALSE;
  hardware: LONG POINTER TO CONDITION ← NIL;
  interruptBits: WORD ← 0;
  
  
  DuplicateLineNumber: ERROR = CODE;
  


  -- **************** Interface to Router(s) ****************

  SendRawBuffer: PROCEDURE [b: Buffer.PupBuffer] =
    BEGIN
    -- Actually queued/sent by Encapsulation routines
    END;

  EncapsulatePup: PROCEDURE [b: Buffer.PupBuffer, immediate: LONG POINTER TO PupTypes.PupHostID] =
    BEGIN
    errorText: STRING;
    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;
    errorText ← QueuePup[b];
    IF errorText # NIL THEN ForwarderDefs.DoErrorPup[b, gatewayResourceLimitsPupErrorCode, errorText];
    END;

  QueuePup: ENTRY PROCEDURE [b: Buffer.PupBuffer] RETURNS [errorText: STRING] =
    BEGIN
    forwarding: BOOL ← b.pup.pupTransportControl > 0FH; -- 4 bits of hop, 4 spares
    high: BOOLEAN;
    dup, lic, ate, duplicate: BOOLEAN;
    friends, pups: CARDINAL ← 0;
    [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];
    duplicate ← dup OR lic OR ate;
    IF ((b.pup.pupType = aData OR b.pup.pupType = data) AND duplicate 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["Duplicate BSP probe or aData"]; };
    IF (b.pup.source.socket = leafSocket OR b.pup.dest.socket = leafSocket)
    AND duplicate THEN {
      StatIncr[@info.stats.stats[PhoneNetExtras.leafDupsFiltered]];
      RETURN["Duplicate Leaf packet"]; };
    IF friends > maxConnectionDepth THEN {
      StatIncr[@info.stats.stats[connTooGreedy]];
      StatIncr[@info.stats.stats[PhoneNetExtras.pupTooGreedy]];
      RETURN["Too many Pups from this connection"]; };
    IF pups > maxQueueDepth OR (forwarding AND pups > maxForwarderDepth) THEN {
      StatIncr[@info.stats.stats[congestion]];
      StatIncr[@info.stats.stats[PhoneNetExtras.pupCongestion]];
      RETURN["Too many Pups on this queue"]; };
    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[NIL];
    END;

  DecapsulatePup: 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;
    IF FALSE AND info.encrypt THEN
      -- It's already been decrypted by DecapsulateNS.
      -- BEWARE: This depends upon the order in which things get tried.
      BEGIN
      words: CARDINAL ← (b.driver.length+1)/2;
      blocks: CARDINAL = (words+SIZE[DESFace.Block]-1)/SIZE[DESFace.Block];
      data: LONG POINTER = @b.encapsulation + phoneEncapsulationOffset;
      DESFace.ECBDecrypt[info.key, blocks, data, data];
      END;
    bytes ← bytes - phoneEncapsulationBytes;
    SELECT encapsulation.pnType FROM
      pupPhonePacket =>
        BEGIN
	IF bytes < b.pup.pupLength THEN GOTO Rejected;
        type ← pup;
        info.stats.remoteHostNumber ← encapsulation.pnSrcID;
	END;
      ENDCASE => type ← vagrant;
    EXITS Rejected =>
      BEGIN
      type ← orphan;
      StatIncr[@info.stats.stats[pktsRejected]];
      END;
    END;
    
  EncapsulateNS: PROCEDURE [b: Buffer.PupBuffer, immediate: LONG POINTER TO System.HostNumber] =
    BEGIN
    errorText: STRING;
    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;
    errorText ← QueueNS[b];
    IF errorText # NIL THEN
      RouterInternal.SendErrorPacket[b, congestionDiscard, errorText.length];
    END;

  QueueNS: ENTRY PROCEDURE [b: Buffer.PupBuffer] RETURNS [errorText: STRING] =
    BEGIN
    forwarding: BOOL ← b.ns.transportControl.hopCount > 0;
    high: BOOLEAN;
    dup, lic, ate: BOOLEAN;
    friends, ns: CARDINAL ← 0;
    [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
      StatIncr[@info.stats.stats[PhoneNetExtras.nsDupsFiltered]];
      RETURN["Duplicate packet filtered"];
      END;
    IF friends > maxConnectionDepth THEN
      BEGIN
      StatIncr[@info.stats.stats[connTooGreedy]];
      StatIncr[@info.stats.stats[PhoneNetExtras.nsTooGreedy]];
      RETURN["Connection too greedy"];
      END;
    IF ns > maxQueueDepth OR (forwarding AND ns > maxForwarderDepth) THEN
      BEGIN
      StatIncr[@info.stats.stats[congestion]];
      StatIncr[@info.stats.stats[PhoneNetExtras.nsCongestion]];
      RETURN["Too many NS packets on queue already"];
      END;
    QueueOutputBuffer[b, high];
    StatIncr[@info.stats.stats[nsSent]];
    StatBump[@info.stats.stats[PhoneNetExtras.nsBytesSend], b.driver.length*2];
    RETURN[NIL];
    END;

  DecapsulateNS: 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;
    IF info.encrypt THEN
      BEGIN
      words: CARDINAL ← (b.driver.length+1)/2;
      blocks: CARDINAL = (words+SIZE[DESFace.Block]-1)/SIZE[DESFace.Block];
      data: LONG POINTER = @b.encapsulation + phoneEncapsulationOffset;
      DESFace.ECBDecrypt[info.key, blocks, data, data];
      END;
    bytes ← bytes - phoneEncapsulationBytes;
    SELECT encapsulation.pnType FROM
      nsPhonePacket =>
        BEGIN
	IF bytes < b.ns.pktLength THEN GOTO Rejected;
	type ← ns;
	END;
      ENDCASE => type ← vagrant;
    info.stats.remoteHostNumber ← encapsulation.pnSrcID;
    EXITS Rejected =>
      BEGIN
      type ← orphan;
      StatIncr[@info.stats.stats[pktsRejected]];
      END;
    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 {
              pupHeaderWords: CARDINAL = 11;
	      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.NSBuffer ← 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
    IF b.driver.iocb # NIL THEN ERROR;
    b.network ← LONG[@info.network];
    info.packetsOnSendQueue ← info.packetsOnSendQueue + 1;
    b.next ← NIL;
    IF high THEN
      BEGIN
      b.driver.faceStatus ← unknown[101B];
      IF info.firstHiSendBuffer = NIL THEN info.firstHiSendBuffer ← b
      ELSE info.lastHiSendBuffer.next ← b;
      info.lastHiSendBuffer ← b;
      END
    ELSE
      BEGIN
      b.driver.faceStatus ← unknown[102B];
      IF info.firstLoSendBuffer = NIL THEN info.firstLoSendBuffer ← b
      ELSE info.lastLoSendBuffer.next ← b;
      info.lastLoSendBuffer ← b;
      END;
    IF info.currentSendBuffer = NIL THEN StartSending[@info];
    END;
  
  StartSending: INTERNAL PROCEDURE [line: Info] =
    BEGIN
    b: Buffer.Buffer;
    SELECT TRUE FROM
      line.firstHiSendBuffer # NIL =>
        BEGIN
        b ← line.firstHiSendBuffer;
        line.firstHiSendBuffer ← b.next;
        END;
      line.firstLoSendBuffer # NIL =>
        BEGIN
        b ← line.firstLoSendBuffer;
        line.firstLoSendBuffer ← b.next;
        END;
      ENDCASE => RETURN;
    b.next ← NIL;
    line.currentSendBuffer ← b;
    IF line.encrypt THEN
      BEGIN
      words: CARDINAL ← b.driver.length;
      blocks: CARDINAL = (words+SIZE[DESFace.Block]-1)/SIZE[DESFace.Block];
      data: LONG POINTER = @b.encapsulation + phoneEncapsulationOffset;
      DESFace.ECBEncrypt[line.key, blocks, data, data];
      b.driver.length ← blocks*SIZE[DESFace.Block];  -- Round up to decrypt last block
      END;
    IF line.dlion THEN Wait[5];
    b.driver.iocb ← line.sendIocb;
    b.driver.faceStatus ← unknown[103B];
    PhoneFace.QueueOutput[
      line.lineNumber,
      b.driver.iocb,
      @b.encapsulation + phoneEncapsulationOffset,
      2*b.driver.length];
    line.timeSendStarted ← System.GetClockPulses[];
    END;
    
  Wait: PROCEDURE [ms: CARDINAL] =
    BEGIN
    start: System.Pulses = System.GetClockPulses[];
    microseconds: LONG CARDINAL = ms*LONG[1000];
    DO
      now: System.Pulses = System.GetClockPulses[];
      IF System.PulsesToMicroseconds[[now-start]] > microseconds THEN EXIT;
      Process.Yield[];  -- Yetch.  Pause is too crude
      ENDLOOP;
    END;

  FinishSending: PROCEDURE [line: Info, b: Buffer.Buffer] =
    BEGIN
    IF line.encrypt AND (b.allNets OR b.requeueProcedure # Buffer.ReturnBuffer) THEN
      BEGIN  -- Oops. Untrash it
      words: CARDINAL ← b.driver.length;
      blocks: CARDINAL = (words+SIZE[DESFace.Block]-1)/SIZE[DESFace.Block];
      data: LONG POINTER = @b.encapsulation + phoneEncapsulationOffset;
      DESFace.ECBDecrypt[line.key, blocks, data, data];
      END;
    line.currentSendBuffer ← NIL;
    IF b.driver.iocb # line.sendIocb THEN ERROR;
    b.driver.iocb ← NIL;
    b.driver.faceStatus ← unknown[104B];
    Driver.PutOnGlobalDoneQueue[b];
    END;
    
    
  -- **************** Watcher to timeout stuck things ****************
  
  -- 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;
    Process.InitializeCondition[@watch, Process.MsecToTicks[1000]];
    UNTIL pleaseStop DO
      WAIT watch;
      FOR line: Info ← @info, line.next UNTIL line = NIL DO
        flap: BOOLEAN ← FALSE;
	now: System.Pulses = System.GetClockPulses[];
	b: Buffer.Buffer ← line.currentSendBuffer;
	IF PhoneFace.GetPacketsMissed[line.lineNumber] # line.missed THEN
	  BEGIN
          oldMissed: CARDINAL ← line.missed;
          newMissed: CARDINAL ← PhoneFace.GetPacketsMissed[line.lineNumber];
	  line.missed ← newMissed;
          StatBump[@line.stats.stats[rcvErrorNoGet], (newMissed - oldMissed)];
	  END;
        IF b # NIL AND (now - line.timeSendStarted) > pulsesPerByte*b.driver.length*2 THEN
	  BEGIN
          StatIncr[@line.stats.stats[queueTooOld]];
	  flap ← TRUE;
	  END;
        IF (now - line.timeLastRecv) > recvIdleTimeout THEN
	  BEGIN
          StatIncr[@line.stats.stats[tooLongSinceLastReceive]];
	  flap ← TRUE;
	  END;
	IF flap THEN
	  BEGIN
	  line.stats.remoteHostNumber ← System.nullHostNumber;
	  PhoneFace.ResetLine[line.lineNumber];
	  IF line.currentSendBuffer # NIL THEN
	    BEGIN
	    FinishSending[line, line.currentSendBuffer];
	    END;
	  UNTIL line.firstHiSendBuffer = NIL DO
	    b: Buffer.Buffer ← line.firstHiSendBuffer;
	    line.firstHiSendBuffer ← b.next;
	    Driver.PutOnGlobalDoneQueue[b];
	    ENDLOOP;
	  UNTIL line.firstLoSendBuffer = NIL DO
	    b: Buffer.Buffer ← line.firstLoSendBuffer;
	    line.firstLoSendBuffer ← b.next;
	    Driver.PutOnGlobalDoneQueue[b];
	    ENDLOOP;
          line.packetsOnSendQueue ← 0;
	  FOR b: Buffer.Buffer ← line.firstRecvBuffer, b.next UNTIL b = NIL DO
            PhoneFace.QueueInput[
              line.lineNumber,
              b.driver.iocb,
              @b.encapsulation + phoneEncapsulationOffset,
              2*(b.driver.length - phoneEncapsulationOffset)];
	    ENDLOOP;
	  line.timeLastRecv ← System.GetClockPulses[];
	  END;
        ENDLOOP;
      ENDLOOP;
    END;
 

  -- **************** Interrupt Processing ****************
  
  Interrupt: ENTRY PROCEDURE =
    BEGIN
    Process.SetPriority[ProcessPriorities.priorityPageFaultHigh];
    UNTIL pleaseStop DO
      WAIT hardware;
      FOR line: Info ← @info, line.next UNTIL line = NIL DO
	DO
	  status: PhoneFace.Status;
	  b: Buffer.Buffer ← line.currentSendBuffer;
	  IF b = NIL THEN EXIT;
	  status ← PhoneFace.GetSendStatus[b.driver.iocb];
	  IF status = pending THEN EXIT;
	  IF status = ok THEN
	    BEGIN
	    bits: CARDINAL ← 7 + 16*b.driver.length + 16 + 7;
	    now: System.Pulses ← System.GetClockPulses[];
	    micro: LONG CARDINAL ← System.PulsesToMicroseconds[[now-line.timeSendStarted]];
	    -- ARHG!!! 4000 * 1000000 overflows a LONG CARDINAL
	    bitsPerSecond: LONG CARDINAL = ((100000*bits)/micro)*10;
	    line.bitsPerSecond ← (63*line.bitsPerSecond + bitsPerSecond)/64;
	    line.stats.speed ← Inline.LowHalf[line.bitsPerSecond/1000];
	    line.network.lineSpeed ← line.stats.speed;
	    StatIncr[@line.stats.stats[pktsSent]];
	    StatBump[@line.stats.stats[bytesSent], 2*b.driver.length];
	    END
	  ELSE StatIncr[@line.stats.stats[sendErrorBadStatus]];
          line.packetsOnSendQueue ← line.packetsOnSendQueue - 1;
	  FinishSending[line, b];
	  StartSending[line];
	  ENDLOOP;
	WHILE line.firstRecvBuffer # NIL DO
	  b: Buffer.Buffer ← line.firstRecvBuffer;
	  status: PhoneFace.Status ← PhoneFace.GetRecvStatus[b.driver.iocb];
	  newBuffer: Buffer.Buffer ← NIL;
	  IF status = pending THEN EXIT;
	  line.firstRecvBuffer ← b.next;
	  IF status # ok THEN
	    BEGIN
	    SELECT status FROM
	      overrun => StatIncr[@line.stats.stats[rcvDeviceError]];
	      packetTooLong => StatIncr[@line.stats.stats[rcvErrorDataLost]];
	      crc => StatIncr[@line.stats.stats[rcvErrorCRC]];
	      ENDCASE => StatIncr[@line.stats.stats[rcvErrorUnknown]];
	    newBuffer ← b;
	    b ← NIL;
	    END
	  ELSE
	    BEGIN
	    newBuffer ← Driver.GetInputBuffer[];
	    IF newBuffer = NIL THEN
	      BEGIN  -- Rats, recycle this packet
	      StatIncr[@line.stats.stats[rcvErrorNoGet]];
	      newBuffer ← b;
	      b ← NIL;
	      END
	    ELSE {
	      IF newBuffer.driver.iocb # NIL THEN ERROR;
	      newBuffer.driver.iocb ← b.driver.iocb; };
	    END;
	  IF b # NIL THEN
	    BEGIN
            encapsulation: LONG POINTER TO Encapsulation = LOOPHOLE[@b.encapsulation];
	    bytes: CARDINAL ← PhoneFace.GetPacketLength[b.driver.iocb];
	    b.network ← @line.network;
            b.driver.length ← bytes;
            b.driver.iocb ← NIL;
            b.driver.faceStatus ← unknown[202B];
	    Driver.PutOnGlobalInputQueue[b];
	    line.timeLastRecv ← System.GetClockPulses[];
	    StatIncr[@line.stats.stats[pktsReceived]];
	    StatBump[@line.stats.stats[bytesReceived], bytes];
	    END;
	  IF newBuffer.driver.iocb = line.recvIocbs THEN line.recvIocbCounter ← 0
	  ELSE {
	    line.recvIocbCounter ← line.recvIocbCounter + 1;
	    IF line.recvIocbCounter > line.network.buffers THEN ERROR; };
	  newBuffer.network ← @line.network;
          newBuffer.driver.faceStatus ← unknown[201B];
          PhoneFace.QueueInput[
            line.lineNumber,
	    newBuffer.driver.iocb,
	    @newBuffer.encapsulation + phoneEncapsulationOffset,
	    2*(newBuffer.driver.length - phoneEncapsulationOffset)];
          IF line.firstRecvBuffer = NIL THEN line.firstRecvBuffer ← newBuffer
          ELSE line.lastRecvBuffer.next ← newBuffer;
	  line.lastRecvBuffer ← newBuffer;
	  newBuffer.next ← NIL;
	  ENDLOOP;
        ENDLOOP;
      ENDLOOP;
    END;
  

  -- **************** Driver Management ****************

  CreatePhone: PUBLIC PROCEDURE [host, net, lineNumber: CARDINAL, password: LONG STRING] =
    BEGIN
    line: Info;
    IF watcher = NIL THEN
      BEGIN
      [cv: hardware, mask: interruptBits] ← SpecialRuntime.AllocateNakedCondition[];
      Process.DisableTimeout[hardware];
      PhoneFace.TurnOn[interruptBits];  -- Also gets it STARTed
      interrupt ← FORK Interrupt[];
      watcher ← FORK Watcher[];
      line ← @info;
      END
    ELSE
      BEGIN
      him: LONG POINTER TO FRAME[PhoneDriver] ← NEW PhoneDriver;
      START him;
      him.lock ← @myLock;
      line ← @him.info;
      FOR finger: Info ← @info, finger.next DO
        IF finger.lineNumber = lineNumber THEN ERROR DuplicateLineNumber;
        IF finger.next = NIL THEN
	  BEGIN
	  finger.next ← line;
	  EXIT;
	  END;
	ENDLOOP;
      END;
    line.network.lineNumber ← lineNumber;
    line.stats.lineNumber ← lineNumber;
    line.lineNumber ← lineNumber;
    SELECT TRUE FROM
      password = NIL => NULL;
      String.EqualString[password, "DLion"L] => line.dlion ← TRUE;
      ENDCASE =>
        BEGIN
        line.key ← MakeKey[password];
        line.encrypt ← TRUE;
        END;
    line.pool ← Buffer.MakePool[0, line.network.buffers];
    line.sendIocb ← CommUtil.AllocateIocbs[(line.network.buffers+1)*PhoneFace.controlBlockSize];
    line.recvIocbs ← line.sendIocb + PhoneFace.controlBlockSize;
    Driver.AddDeviceToChain[@line.network];
    AdoptForPup[line, host, net];
    AdoptForNS[line, System.nullNetworkNumber];
    END;
    
  AdoptForNS: PUBLIC PROC [info: Info, networkNumber: System.NetworkNumber] =
    BEGIN
    driver: Driver.Network ← @info.network;
    family: Protocol1.Family ← CommunicationInternal.NSPackageMake[];
    matrix: Protocol1.MatrixRecord ← [  --AddFamilyMember copies fields
      family: family, context: ,
      encapsulator: info.procs.encapsulateNS,
      decapsulator: info.procs.decapsulateNS];
    matrix.context ← CommHeap.zone.NEW[RoutingTable.ContextObject ← [
      netNumber: networkNumber, network: driver, stats: NIL]];
    Protocol1.AddFamilyMember[driver, @matrix];
    END;
    
  AdoptForPup: PROC [info: Info, pupHostNumber, pupNetNumber: CARDINAL] =
    BEGIN
    driver: Driver.Network ← @info.network;
    family: Protocol1.Family ← PupDefs.PupPackageMake[];
    matrix: Protocol1.MatrixRecord ← [  --AddFamilyMember copies fields
      family: family, context: ,
      encapsulator: info.procs.encapsulatePup,
      decapsulator: info.procs.decapsulatePup];
    matrix.context ← CommHeap.zone.NEW[PupRouterDefs.ContextObject ← [
      protocol: NIL, network: driver,
      pupNetNumber: pupNetNumber, pupHostNumber: pupHostNumber]];
    Protocol1.AddFamilyMember[driver, @matrix];
    matrix.family.stateChanged[driver, matrix.context, add]; -- Bug somewhere ##########
    END;
    
  -- Don't muck with this unless you are prepared to change both ends.
  -- This doesn't hide the string very well.
  MakeKey: PROCEDURE [source: LONG STRING] RETURNS [key: DESFace.Key] =
    BEGIN
    key ← DESFace.nullKey;
    FOR i: CARDINAL IN [0..source.length) DO
      j: CARDINAL = i MOD 8;
      c: CHARACTER =
        IF source[i] IN ['A..'Z] THEN 'a + (source[i] - 'A) ELSE source[i];
      key[j].b ← Inline.BITXOR[LOOPHOLE[c, [0..127]], key[j].b];
      ENDLOOP;
    DESFace.CorrectParity[@key];
    END;

  ActivateDriver: ENTRY PROCEDURE =
    BEGIN
    tick: CONDITION;
    Process.InitializeCondition[@tick, 1];
    PhoneFace.ResetLine[info.lineNumber];
    FOR i: CARDINAL IN [0..info.network.buffers) DO
      b: Buffer.Buffer;
      UNTIL (b ← Driver.GetInputBuffer[FALSE]) # NIL DO WAIT tick; ENDLOOP;
      b.driver.iocb ← info.recvIocbs + i*PhoneFace.controlBlockSize;
      IF info.firstRecvBuffer = NIL THEN info.firstRecvBuffer ← b
      ELSE info.lastRecvBuffer.next ← b;
      b.driver.faceStatus ← unknown[201B];
      PhoneFace.QueueInput[
        info.lineNumber,
        b.driver.iocb,
        @b.encapsulation + phoneEncapsulationOffset,
        2*(b.driver.length - phoneEncapsulationOffset)];
      info.lastRecvBuffer ← b;
      b.next ← NIL;
      ENDLOOP;
    info.timeLastRecv ← System.GetClockPulses[];
    info.missed ← PhoneFace.GetPacketsMissed[info.lineNumber];
    info.network.alive ← TRUE;
    END;

  KillDriver: PROCEDURE =
    BEGIN
    ERROR;
    END;


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

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

  StatBump: PROCEDURE [
    counter: LONG 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
    FOR finger: Info ← @info, finger.next UNTIL finger = NIL DO
      IF finger.network.alive AND finger.lineNumber = lineNumber THEN
        RETURN [TRUE, finger.stats];
      ENDLOOP;
    RETURN[FALSE, ];
    END;

 -- StatsPtrToInfo: PUBLIC PROCEDURE [stats: LONG POINTER]
 --   RETURNS [clientData: LONG POINTER TO PhoneNetFriends.PhoneNetInfo, lineNumber: CARDINAL] =
 --   BEGIN
 --   info: Info = LOOPHOLE[stats];
 --   RETURN[@info.stats, info.lineNumber];
 --   END;

  StatsPtrToStats: PUBLIC PROCEDURE [stats: LONG POINTER]
    RETURNS [PhoneNetFriends.PhoneNetInfo] =
    BEGIN
    p: LONG POINTER TO PhoneNetFriends.PhoneNetInfo = stats;
    RETURN[p↑];
    END;

  EnumerateDriverInfo: PUBLIC PROCEDURE [current: PhoneNetFriends.NetEnumerator]
    RETURNS [PhoneNetFriends.NetEnumerator, PhoneNetFriends.PhoneNetInfo] =
    BEGIN
    hisInfo: Info ← current;
    IF (current = PhoneNetFriends.nullNetEnumerate) AND info.network.alive THEN
      RETURN[@info, info.stats];
    IF hisInfo.next = NIL THEN RETURN [PhoneNetFriends.nullNetEnumerate, info.stats];
    RETURN [hisInfo.next, hisInfo.next.stats];
    END;
    
  END....