-- File: SptpImpl.mesa - last edit:
-- AOF                  3-Feb-88 11:26:40
-- Copyright (C) 1987, 1988 by Xerox Corporation. All rights reserved. 

DIRECTORY
  Buffer USING [Buffer, Byte, Dequeue, Device],
  CommFlags USING [doStats, driverStats],
  CommHeap USING [zone],
  CommunicationInternal USING [NSPackageDestroy, NSPackageMake],
  Driver USING [Device, GetInputBuffer, ReturnFreeBuffer],
  Environment USING [Byte, bytesPerWord],
  Inline USING [HighByte, LongCOPY, LowByte],
  Mopcodes USING [zADD, zAND, zLI1, zLINB],
  NetworkStreamImpl USING [xmtr, rcvr, rexmtr],
  NSBuffer USING [Body],
  NSTypes USING [bytesPerIDPHeader],
  PhoneAdoption USING [],
  PhoneNet USING [EntityClass, Negotiation],
  Protocol1 USING [
    AddFamilyMember, DecapsulatorProc, EncapsulatorProc, Family,
    GetContext, GetFamilyUnit, MatrixRecord, RemoveFamilyMember],
  RoutingTable USING [ContextObject, NetworkContext],
  RS232C USING [ChannelHandle, CommParamHandle],
  SptpOps USING [
    CreateDriver, defaultMaxRS232CBytes, GetDevice, GetEntityClass,
    GetVersion, maxRS232CBytes, minRS232CBytes, point5Duplex,
    ReservationObject, siuSupport, StatsRecord],
  SptpProtocol USING [
    Control, ControlBody, Encapsulation, EncapsulationFromBlock,
    EncapsulationObject, EntityClass, MasterSlaveRelationship,
    PacketType, Protocol, ProtocolProc, ProtocolVersion],
  SptpStats USING [Incr],
  SppOps USING [SetSppSpy, SppSpyProc],
  System USING [
    GetClockPulses, GetGreenwichMeanTime, HostNumber, MicrosecondsToPulses,
    nullHostNumber, nullNetworkNumber];

SptpImpl: MONITOR LOCKS protocol.lock↑ USING protocol: SptpProtocol.Protocol 
  IMPORTS
    Buffer, CommunicationInternal, CommHeap, Driver, Inline, Protocol1,
    SptpOps, SptpProtocol, SptpStats, SppOps, System
  EXPORTS Buffer, PhoneAdoption, PhoneNet, SptpProtocol =
  
  BEGIN
  --This doesn't support 1/2 duplex even though the interface says it does
  point5Duplex: BOOLEAN = ~SptpOps.point5Duplex;

  bpw: NATURAL = Environment.bytesPerWord;
  Device: PUBLIC <<Buffer>> TYPE = Driver.Device;
  Unsupported: PUBLIC <<PhoneNet>> ERROR = CODE;
  IllegalEntityClass: PUBLIC <<PhoneNet>> ERROR = CODE;
  StatsPointer: TYPE = LONG POINTER TO SptpOps.StatsRecord;
  InvalidLineNumber: PUBLIC <<PhoneNet, PhoneAdoption>> ERROR = CODE;
  pvVersionLow: SptpProtocol.ProtocolVersion = version3;  --really low
  pvVersionHigh: SptpProtocol.ProtocolVersion = version4;  --includes 'more' bit

  TwoBytes: TYPE = RECORD[hi, lo: Environment.Byte];
  Forty8Bitter: TYPE = MACHINE DEPENDENT RECORD[one, two, three: WORD];

  clock: RECORD[to1, to2, to3, to4, to5, to6: LONG CARDINAL];  --pulses

  <<
  The following variable assumes that we are connected via a single local
  rs232c port. Watch out for multi ports, etc.
  >>
  
  AdoptForNS: PUBLIC <<PhoneAdoption>> PROC[lineNumber: CARDINAL] =
    BEGIN
    family: Protocol1.Family;
    matrix: Protocol1.MatrixRecord;
    device: Device = SptpOps.GetDevice[lineNumber];
    IF device = NIL THEN ERROR InvalidLineNumber;
    family ← CommunicationInternal.NSPackageMake[];
    matrix ← [
      family: family, context: ,
      encapsulator: NsEncapsulation, decapsulator: NsDecapsulation];
    matrix.context ← CommHeap.zone.NEW[RoutingTable.ContextObject ← [
      netNumber: System.nullNetworkNumber, network: device, stats: NIL]];
    Protocol1.AddFamilyMember[device, @matrix];
    IF CommFlags.doStats THEN SptpStats.Incr[adoptNS];
    END;  --AdoptForNS
    
  DisownFromNS: PUBLIC <<PhoneAdoption>> PROC [lineNumber: CARDINAL] =
    BEGIN
    family: Protocol1.Family;
    context: RoutingTable.NetworkContext;
    device: Device = SptpOps.GetDevice[lineNumber];
    IF device = NIL THEN ERROR InvalidLineNumber;
    context ← Protocol1.GetContext[device, ns];
    family ← Protocol1.GetFamilyUnit[ns];
    Protocol1.RemoveFamilyMember[device, family];
    CommHeap.zone.FREE[@context];
    CommunicationInternal.NSPackageDestroy[];
    IF CommFlags.doStats THEN SptpStats.Incr[disownNS];
    END;  --DisownFromNS

  Initialize: PUBLIC <<PhoneNet>> PROC[
    lineNumber: CARDINAL, channel: RS232C.ChannelHandle,
    commParams: RS232C.CommParamHandle, negotiationMode: PhoneNet.Negotiation,  
    hardwareStatsAvailable: BOOLEAN, clientData: LONG UNSPECIFIED ← 0,
    ourEntityClass: PhoneNet.EntityClass,
    clientHostNumber: System.HostNumber ← System.nullHostNumber] =
    -- REPORTS IllegalEntityClass, Unsupported
    BEGIN
    reservation: SptpOps.ReservationObject ← [
      lineSpeed: commParams.lineSpeed, duplex: commParams.duplex,
      lineNumber: lineNumber, entity: ourEntityClass];
    --IF lineNumber # 0 THEN ERROR Unsupported;
    IF ~point5Duplex AND (reservation.duplex = half) THEN ERROR Unsupported;
    IF ourEntityClass = siu THEN ERROR IllegalEntityClass;
    SptpOps.CreateDriver[@reservation];
    IF CommFlags.doStats THEN SptpStats.Incr[createDriver];
    END;  --Initialize

  Destroy: PUBLIC <<PhoneNet>> PROC[lineNumber: CARDINAL] =
    BEGIN
    device: Driver.Device = SptpOps.GetDevice[lineNumber];
    IF device = NIL THEN RETURN WITH ERROR InvalidLineNumber;
    device.deleteDriver[];  --else delete the driver 
    END;  --Destroy
    
  --**************** Encapsulation and decapsulation **************
  
  NsDecapsulation: PUBLIC Protocol1.DecapsulatorProc =
    --PROCEDURE [b: Buffer.Buffer] RETURNS [type: Buffer.Type]
    BEGIN
    <<
    When coming in here we are assured that b.linkLayer.blockPointer
    points to the beginning of the physical frame. That frame starts with
    the sptp's encapsulation record. From that information we should be
    able to compute the address of the data portion.
    These calls to SptpOps.Get* are expensive. It would be nice to be able
    to get to some state object more cheaply to determine whether we are
    connected to an SIU or not.
    >>
    size: NATURAL;
    body: NSBuffer.Body; 
    device: Device = b.fo.network;  --get pointer to network
    version: SptpProtocol.ProtocolVersion = SptpOps.GetVersion[device];

    WITH e: LOOPHOLE[b.linkLayer.blockPointer, SptpProtocol.Encapsulation]
      SELECT FROM
      ns => size ←
        IF version > version2 THEN SIZE[ns SptpProtocol.EncapsulationObject]
	ELSE SIZE[siuSet SptpProtocol.EncapsulationObject];
      siuSet, siuEcho, siuDetermineLS => RETURN[orphan];  --gun these down
      <<
      SIU knows nothing but ns, so that's as good as packet type.
      The SIU doesn't always set the packet type to be ns.
      We'll **assume** that the packet is an ns packet, and count on the
      level 1 checksums to help us out.
      >>
      ENDCASE =>  --special test for SIUs
        IF SptpOps.GetEntityClass[device] # siu THEN RETURN[vagrant]  --go away
        ELSE size ← SIZE[siuSet SptpProtocol.EncapsulationObject];  --see above

    body ← LOOPHOLE[b.linkLayer.blockPointer + size];  --that's fair
    b.highLayer ← [LOOPHOLE[body], 0, body.pktLength];  --from the packet
    b.linkLayer.stopIndexPlusOne ← size ← (bpw * size);  --'size' now bytes
    SELECT TRUE FROM
      (b.fo.driver.length < NSTypes.bytesPerIDPHeader) =>
        BEGIN
	IF CommFlags.doStats THEN SptpStats.Incr[decapOrphan];
	RETURN[orphan];  --busted
	END;
      (body.pktLength < NSTypes.bytesPerIDPHeader) =>
        BEGIN
	IF CommFlags.doStats THEN SptpStats.Incr[decapOrphan];
	RETURN[orphan];  --busted
	END;
      ENDCASE =>
        BEGIN
	IF CommFlags.doStats THEN SptpStats.Incr[decapNS];
	RETURN[ns];  --busted
	END;
    END;  --NsDecapsulation

  NsEncapsulation: PUBLIC Protocol1.EncapsulatorProc =
    BEGIN
    <<
    Coming in here we know that the packet is an NS packet and that
    b.highLayer.blockPointer points to the NSBuffer.BodyBody. We need to back
    off some number of bytes from that point and stick in the proper encap-
    sulation.
    NB: The Inline.LongCOPYs are here because the compiler generates somewhat
    unuseful code if you use a more direct approach to things. It first blaps
    in a non-descriminated record which is several bytes longer than an
    ns SptpProtocol.EncapsulationObject. Then it goes back and fills in the
    particulars. Regettably by then the first 'n' bytes of the original
    packet are destroyed. 
    >>
    size: NATURAL;  --this variable is overloaded
    link: LONG POINTER;  --computed to point to data link fields
    encapsulation: SptpProtocol.EncapsulationObject;  --this is our local copy
    device: Device = b.fo.network;  --get pointer to network
    body: NSBuffer.Body = LOOPHOLE[b.highLayer.blockPointer];
    of: NATURAL = body.pktLength / SptpOps.defaultMaxRS232CBytes;
    version: SptpProtocol.ProtocolVersion = SptpOps.GetVersion[device];

    SELECT version FROM
      version4 =>
        BEGIN
        encapsulation ← [ns[LTA: FALSE, more: (of # 0), fragment: [0, of]]];
	size ← SIZE[ns SptpProtocol.EncapsulationObject];
	END;
      version3 =>
        BEGIN
        IF (of # 0) THEN ERROR Unsupported;
	encapsulation ← [ns[LTA: FALSE, more: FALSE, fragment: [0, 0]]];
	size ← SIZE[ns SptpProtocol.EncapsulationObject];
	END;
      version2 =>
	BEGIN
	encapsulation ← [siuSet[LTA: FALSE, reserved: 0,
	  immedDest: NARROW[immediate, LONG POINTER TO System.HostNumber]↑]];
	SmashPacketType[@encapsulation, ns];  --looks bad, feels worse!
	size ← SIZE[siuSet SptpProtocol.EncapsulationObject];
	END;
      ENDCASE => ERROR Unsupported; 

    link ← body - size;  --this is backoff to beginning of link layer
    Inline.LongCOPY[to: link, from: @encapsulation, nwords: size];
    size ← bpw * size;  -- now represents data link size in bytes
    b.linkLayer ← [link, 0, size];  --set the link layer values
    b.fo.driver.length ← Roundup[body.pktLength] + size;  --frame is sum of both
    IF CommFlags.doStats THEN SptpStats.Incr[encapNS];
    END;  --NsEncapsulation
    
  Roundup: PROC[NATURAL] RETURNS[NATURAL] = MACHINE CODE {
    Mopcodes.zLI1; Mopcodes.zADD; Mopcodes.zLINB, 376B; Mopcodes.zAND};

  SmashPacketType: PROC[
    p: SptpProtocol.Encapsulation, t: SptpProtocol.PacketType] = INLINE
    BEGIN
    PacketTypeFieldPointer: TYPE = LONG POINTER TO PacketTypeField;
    PacketTypeField: TYPE = MACHINE DEPENDENT RECORD[
      t(0: 0..7): SptpProtocol.PacketType, r(0: 8..15): BOOLEAN];
    LOOPHOLE[p, PacketTypeFieldPointer].t ← t;
    END;  --SmashPacketType

  --********* Protocol State Machine Procedures **********--
  
  ActiveNegotiation: PUBLIC <<SptpProtocol>> SptpProtocol.ProtocolProc =
    BEGIN
    b: Buffer.Buffer;
    timein: LONG CARDINAL = System.GetClockPulses[];
    UNTIL (System.GetClockPulses[] - timein) > clock.to3 DO
      timeout: LONG CARDINAL ← clock.to1;
      SendMyOptions[protocol, TRUE];  --get things rolling
      UNTIL (b ← WaitForControl[protocol, timeout]) = NIL DO
	ProcessControl[protocol, b];
	timeout ← LAST[LONG CARDINAL];
	ENDLOOP;
      IF protocol.object.state # option1 THEN EXIT;  --done here
      REPEAT FINISHED => protocol.object.state ← terminate2;  --give up
      ENDLOOP;
    END;  --ActiveNegotiation

  AwaitingOptions: PUBLIC <<SptpProtocol>> SptpProtocol.ProtocolProc =
    BEGIN
    b: Buffer.Buffer;
    timein: LONG CARDINAL = System.GetClockPulses[];
    UNTIL (System.GetClockPulses[] - timein) > clock.to3 DO
      timeout: LONG CARDINAL ← clock.to1;
      UNTIL (b ← WaitForControl[protocol, timeout]) = NIL DO
	ProcessControl[protocol, b];
	timeout ← LAST[LONG CARDINAL];
	ENDLOOP;
      IF protocol.object.state # option3 THEN EXIT;  --done here
      SendMyOptions[protocol, TRUE];  --ask again
      REPEAT FINISHED => protocol.object.state ← terminate2;  --give up
      ENDLOOP;
    END;  --AwaitingOptions

  AwaitingOptionAck: PUBLIC <<SptpProtocol>> SptpProtocol.ProtocolProc =
    BEGIN
    b: Buffer.Buffer;
    timein: LONG CARDINAL = System.GetClockPulses[];
    UNTIL (System.GetClockPulses[] - timein) > clock.to2 DO
      timeout: LONG CARDINAL ← clock.to1;
      UNTIL (b ← WaitForControl[protocol, timeout]) = NIL DO
	ProcessControl[protocol, b];
	timeout ← LAST[LONG CARDINAL];
	ENDLOOP;
      IF protocol.object.state # option4 THEN EXIT;  --done here
      SendMyOptions[protocol, TRUE];  --ask again
      REPEAT FINISHED => protocol.object.state ← terminate2;  --give up
      ENDLOOP;
    END;  --AwaitingOptionAck;

  AwaitTerminateReply: PUBLIC <<SptpProtocol>> SptpProtocol.ProtocolProc = 
    BEGIN
    b: Buffer.Buffer;
    timein: LONG CARDINAL = System.GetClockPulses[];
    UNTIL (System.GetClockPulses[] - timein) > clock.to6 DO
      timeout: LONG CARDINAL ← clock.to4;
      UNTIL (b ← WaitForControl[protocol, timeout]) = NIL DO
	ProcessControl[protocol, b]; timeout ← LAST[LONG CARDINAL];
	ENDLOOP;
      IF protocol.object.state # terminate1 THEN EXIT;  --done here
      SendTerminateRequest[protocol, TRUE];  --ask again
      REPEAT FINISHED => protocol.object.state ← terminate2;  --give up
      ENDLOOP;
    END;  --AwaitTerminateReply

  EntityClash: PROC[mine, his: SptpProtocol.EntityClass]
    RETURNS[BOOLEAN] = INLINE
    BEGIN
    --returns TRUE of there is a clash
    clash: <<PACKED>> ARRAY SptpProtocol.EntityClass OF
      <<PACKED>> ARRAY SptpProtocol.EntityClass OF BOOLEAN =
      --       inr    cnr    siu    ws
      --inr--[[FALSE, FALSE, FALSE, TRUE],
      --cnr-- [FALSE, TRUE,  TRUE,  FALSE],
      --siu-- [FALSE, TRUE,  TRUE,  FALSE],
      --ws--  [TRUE, FALSE,  FALSE, FALSE]];
    RETURN[clash[mine][his]];
    END;  --EntityClash

  FrameSize: PROC[hi, lo: Environment.Byte] RETURNS[va: WORD] = INLINE
    {OPEN bn: LOOPHOLE[va, TwoBytes]; bn.hi ← hi; bn.lo ← lo};  --FrameSize

  LatchSiu: PROC = INLINE {[] ← SppOps.SetSppSpy[SiuSingleBufferSpy]};

  PassiveNegotiation: PUBLIC <<SptpProtocol>> SptpProtocol.ProtocolProc =
    BEGIN
    b: Buffer.Buffer;
    IF (b ← WaitForControl[protocol, clock.to3]) # NIL THEN
      ProcessControl[protocol, b];
    END;  --PassiveNegotiation

  ProcessControl: PUBLIC <<SptpProtocol>>
    PROC[protocol: SptpProtocol.Protocol, b: Buffer.Buffer] =
    BEGIN
    WITH encapsulation:
      SptpProtocol.EncapsulationFromBlock[b.linkLayer.blockPointer] SELECT FROM
      control =>
	BEGIN
	hisAnswer: SptpProtocol.Control = LOOPHOLE[
	  @encapsulation + SIZE[control SptpProtocol.EncapsulationObject]];
	WITH c: hisAnswer SELECT encapsulation.control FROM
	  myOptions =>
	    BEGIN
	    frameSize: NATURAL = FrameSize[  --may be bogus
	      c.maxPktSizeHiByte, c.maxPktSizeLowByte];
	    IF CommFlags.doStats THEN SptpStats.Incr[rcvMyOptions];
	    SELECT TRUE FROM
	      (c.highestVersionNumber < pvVersionLow) =>
		BEGIN
		protocol.object.state ← terminate2;  --just drop the line
		GOTO returnBufferAndExit;  --get out of here
		END;
	      (c.lowestVersionNumber > pvVersionHigh) =>
		BEGIN
		protocol.object.state ← terminate2;  --just drop the line
		GOTO returnBufferAndExit;  --get out of here
		END;
	      ((protocol.object.him ← c.sourceHost) = protocol.object.me) =>
		BEGIN
		SendAddressReject[protocol];  --that's loopback mode
		protocol.object.state ← terminate1;  --and it's fatal
		GOTO returnBufferAndExit;  --get out of here
		END;
	      (EntityClash[protocol.object.ourEntityClass, c.entityClass]) =>
		BEGIN
		SendClassReject[protocol];  --tell him we're going down
		protocol.object.state ← terminate1;  --and it's fatal
		GOTO returnBufferAndExit;  --get out of here
		END;
	      (c.highestVersionNumber > protocol.object.protocolVersion) =>
		BEGIN
		SendVersionReject[protocol, FALSE];  --but not fatal
		SendMyOptions[protocol];  --send my preferences again
		GOTO returnBufferAndExit;  --get out of here
		END;
	      (frameSize > SptpOps.maxRS232CBytes) =>
		BEGIN
		SendSizeReject[protocol, FALSE];  --send him the bad news
		SendMyOptions[protocol];  --send my preferences again
		GOTO returnBufferAndExit;  --get out of here
		END;
	      ENDCASE;

	    IF point5Duplex
	      AND (protocol.object.duplex = half) THEN
	      protocol.object.master ← SetMasterSlaveRelationship[
		protocol.object.me, protocol.object.him];

	    protocol.myDevice.receiveBufferLen ← frameSize;
	    protocol.object.theirEntityClass ← c.entityClass;
	    protocol.object.protocolVersion ← c.highestVersionNumber;

	    SELECT protocol.object.state FROM
	      option1, option2 =>
	        BEGIN
		protocol.object.started ← System.GetGreenwichMeanTime[];
		protocol.object.state ← option4;  --advance the state
		SendOptionsAck[protocol, FALSE];  --but we can ack him
		SendMyOptions[protocol, TRUE];  --we're not in data yet
		END;
	      option3 =>
		BEGIN
		protocol.object.established ← System.GetGreenwichMeanTime[];
		IF CommFlags.driverStats THEN
		  BEGIN
		  OPEN s: NARROW[protocol.myDevice.stats, StatsPointer];
		  s.protocolUp ← s.protocolUp.SUCC;
		  END;
		protocol.myDevice.alive ← TRUE;  --what we were waiting for
		protocol.object.state ← data;  --and we're up
		SendOptionsAck[protocol];  --ack him
		SELECT TRUE FROM
		  (~SptpOps.siuSupport) => NULL;  --this isn't necessary
		  (protocol.object.theirEntityClass = siu) => LatchSiu[];
		  ENDCASE => UnlatchSiu[];
		END;
	      option4 =>
	        BEGIN
		SendOptionsAck[protocol, FALSE];  --ack him again
		SendMyOptions[protocol, TRUE];  --another copy of options
		END;
	      data => SendOptionsAck[protocol];  --ack him again
	      ENDCASE;
	    END;
	  optionsAck =>
	    BEGIN
	    IF CommFlags.doStats THEN SptpStats.Incr[rcvOptionsAck];
	    IF c.destinationHost # protocol.object.me THEN
	      BEGIN
	      SendAddressReject[protocol];
	      protocol.object.state ← terminate1;
	      END
	    ELSE
	      BEGIN
	      SELECT protocol.object.state FROM
		option1 => 
		  BEGIN
		  protocol.object.started ← System.GetGreenwichMeanTime[];
		  protocol.object.state ← option3;  --advance state
		  SendMyOptions[protocol];  --send him another copy
		  END;
		option3 => SendMyOptions[protocol];  --send him another copy
		option4 =>
		  BEGIN
		  IF CommFlags.driverStats THEN
		    BEGIN
		    OPEN s: NARROW[protocol.myDevice.stats, StatsPointer];
		    s.protocolUp ← s.protocolUp.SUCC;
		    END;
		  protocol.object.established ← System.GetGreenwichMeanTime[];
		  protocol.myDevice.alive ← TRUE;  --what we were waiting for
		  protocol.object.state ← data;  --and we're up
		  SendMyOptions[protocol];  --send him another copy
		  SELECT TRUE FROM
		    (~SptpOps.siuSupport) => NULL;  --this isn't necessary
		    (protocol.object.theirEntityClass = siu) => LatchSiu[];
		    ENDCASE => UnlatchSiu[];
		  END;
		ENDCASE => SendNull[protocol];  --to provide LTA if needed
	      END;
	    END;
	  terminateRequest =>
	    BEGIN
	    IF CommFlags.doStats THEN SptpStats.Incr[rcvTermReq];
	    SendTerminateReply[protocol];  --so reply to him
	    protocol.myDevice.alive ← FALSE;  --we're dropping out
	    protocol.object.state ← terminate2;  --he wants to quit
	    END;
	  terminateReply =>
	    BEGIN
	    IF CommFlags.doStats THEN SptpStats.Incr[rcvTermRep];
	    protocol.myDevice.alive ← FALSE;  --we're dropping out
	    protocol.object.state ← terminate2;  --he knows we want to quit
	    END;
	  versionReject =>
	    BEGIN
	    IF CommFlags.doStats THEN SptpStats.Incr[rcvVersionReject];
	    protocol.object.protocolVersion ← c.highestVersionNumber;
	    --protocol state should be active negotiation - still
	    END;
	  areYouThere =>
	    BEGIN
	    IF CommFlags.doStats THEN SptpStats.Incr[rcvYouThere];
	    SELECT protocol.object.state FROM
	      data => SendIAmHere[protocol];
	      terminate1, terminate2 => SendTerminateRequest[protocol];
	      ENDCASE => SendMyOptions[protocol];  --somewhere in options
	    END;
	  addressReject =>
	    BEGIN
	    IF CommFlags.doStats THEN SptpStats.Incr[rcvAddrReject];
	    SendTerminateRequest[protocol];  --okay, we'll give up
	    protocol.object.state ← terminate1;  --and get out of this state
	    END;
	  classReject =>
	    BEGIN
	    IF CommFlags.doStats THEN SptpStats.Incr[rcvClassReject];
	    SendTerminateRequest[protocol];  --okay, we'll give up
	    protocol.object.state ← terminate1;  --and get out of this state
	    END;
	  null => IF CommFlags.doStats THEN SptpStats.Incr[rcvNull];
	  ENDCASE;  --don't know what it was, but I'm going to ignore it
	EXITS returnBufferAndExit => NULL;
	END;
      ENDCASE;
    Driver.ReturnFreeBuffer[b];  --return the buffer
    END;  --ProcessControl

  SendAddressReject: SptpProtocol.ProtocolProc =
    BEGIN
    b: Buffer.Buffer;
    addressRejectLength: NATURAL = 2;
    AddressReject: TYPE = LONG POINTER TO AddressRejectFrame;
    AddressRejectFrame: TYPE = RECORD[
      encapsulation: SptpProtocol.EncapsulationObject.control,
      options: SptpProtocol.ControlBody.addressReject];

    UNTIL (b ← Driver.GetInputBuffer[TRUE, SptpOps.minRS232CBytes]) # NIL
      DO ENDLOOP;

    LOOPHOLE[b.linkLayer.blockPointer, AddressReject].encapsulation ← 
      [control[LTA: lta, reserved: FALSE, control: addressReject]];

    protocol.sendControlFrame[b, addressRejectLength];
    IF CommFlags.doStats THEN SptpStats.Incr[xmtAddrReject];
    END;  --SendAddressReject

  SendAreYouThere: PUBLIC <<SptpProtocol>> SptpProtocol.ProtocolProc =
    BEGIN
    b: Buffer.Buffer;
    areYouThereLength: NATURAL = 2;
    AreYouThere: TYPE = LONG POINTER TO AreYouThereFrame;
    AreYouThereFrame: TYPE = RECORD[
      encapsulation: control SptpProtocol.EncapsulationObject,
      options: areYouThere SptpProtocol.ControlBody];

    IF (b ← Driver.GetInputBuffer[FALSE, SptpOps.minRS232CBytes]) = NIL THEN
      RETURN;  --not too serious about sending this one

    LOOPHOLE[b.linkLayer.blockPointer, AreYouThere].encapsulation ← 
      [control[LTA: lta, reserved: FALSE, control: areYouThere]];

    protocol.sendControlFrame[b, areYouThereLength];
    IF CommFlags.doStats THEN SptpStats.Incr[xmtYouThere];
    END;  --SendAreYouThere

  SendClassReject: SptpProtocol.ProtocolProc =
    BEGIN
    b: Buffer.Buffer;
    classRejectLength: NATURAL = 2;
    ClassReject: TYPE = LONG POINTER TO ClassRejectFrame;
    ClassRejectFrame: TYPE = RECORD[
      encapsulation: control SptpProtocol.EncapsulationObject,
      options: classReject SptpProtocol.ControlBody];

    UNTIL (b ← Driver.GetInputBuffer[TRUE, SptpOps.minRS232CBytes]) # NIL
      DO ENDLOOP;

    LOOPHOLE[b.linkLayer.blockPointer, ClassReject].encapsulation ← 
        [control[LTA: lta, reserved: FALSE, control: classReject]];

    protocol.sendControlFrame[b, classRejectLength];
    IF CommFlags.doStats THEN SptpStats.Incr[xmtClassReject];
    END;  --SendClassReject

  SendIAmHere: SptpProtocol.ProtocolProc =
    BEGIN
    b: Buffer.Buffer;
    iAmHereLength: NATURAL = 2;
    IAmHere: TYPE = LONG POINTER TO IAmHereFrame;
    IAmHereFrame: TYPE = RECORD[
      encapsulation: control SptpProtocol.EncapsulationObject,
      options: iAmHere SptpProtocol.ControlBody];

    UNTIL (b ← Driver.GetInputBuffer[TRUE, SptpOps.minRS232CBytes]) # NIL
      DO ENDLOOP;

    LOOPHOLE[b.linkLayer.blockPointer, IAmHere].encapsulation ← 
        [control[LTA: lta, reserved: FALSE, control: iAmHere]];

    protocol.sendControlFrame[b, iAmHereLength];
    IF CommFlags.doStats THEN SptpStats.Incr[xmtIHere];
    END;  --SendIAmHere

  SendMyOptions: SptpProtocol.ProtocolProc =
    BEGIN
    b: Buffer.Buffer;
    myOptionsLength: NATURAL = 13;
    MyOptions: TYPE = LONG POINTER TO MyOptionsFrame;
    MyOptionsFrame: TYPE = RECORD[
      encapsulation: control SptpProtocol.EncapsulationObject,
      options: myOptions SptpProtocol.ControlBody];

    UNTIL (b ← Driver.GetInputBuffer[TRUE, SptpOps.minRS232CBytes]) # NIL
      DO ENDLOOP;

    LOOPHOLE[b.linkLayer.blockPointer, MyOptions]↑ ← [
      encapsulation: [control[LTA: lta, reserved: FALSE, control: myOptions]],
      options: [myOptions[
	highestVersionNumber: protocol.object.protocolVersion,
	lowestVersionNumber: pvVersionLow,
	sourceHost: protocol.object.me,
	entityClass: protocol.object.ourEntityClass,
	maxPktSizeHiByte: Inline.HighByte[SptpOps.maxRS232CBytes],
	maxPktSizeLowByte: Inline.LowByte[SptpOps.maxRS232CBytes]]]];

    protocol.sendControlFrame[b, myOptionsLength];
    IF CommFlags.doStats THEN SptpStats.Incr[xmtMyOptions];
    END;  --SendMyOptions

  SendNull: PUBLIC <<SptpProtocol>> SptpProtocol.ProtocolProc =
    BEGIN
    IF point5Duplex AND (protocol.object.duplex = half) THEN
      BEGIN
      b: Buffer.Buffer;
      nullFrameLength: NATURAL = 2;
      Null: TYPE = LONG POINTER TO NullFrame;
      NullFrame: TYPE = RECORD[
	encapsulation: control SptpProtocol.EncapsulationObject,
	options: null SptpProtocol.ControlBody];
  
    UNTIL (b ← Driver.GetInputBuffer[TRUE, SptpOps.minRS232CBytes]) # NIL
      DO ENDLOOP;

      LOOPHOLE[b.linkLayer.blockPointer, Null].encapsulation ← 
        [control[LTA: lta, reserved: FALSE, control: null]];
  
      protocol.sendControlFrame[b, nullFrameLength];  --now send the buffer
      END;
    END;  --SendNull

  SendOptionsAck: SptpProtocol.ProtocolProc =
    BEGIN
    b: Buffer.Buffer;
    optionsAckLength: NATURAL = 8;
    OptionsAck: TYPE = LONG POINTER TO OptionsAckFrame;
    OptionsAckFrame: TYPE = RECORD[
      encapsulation: control SptpProtocol.EncapsulationObject,
      options: optionsAck SptpProtocol.ControlBody];

    UNTIL (b ← Driver.GetInputBuffer[TRUE, SptpOps.minRS232CBytes]) # NIL
      DO ENDLOOP;

    LOOPHOLE[b.linkLayer.blockPointer, OptionsAck]↑ ← [
      encapsulation: [control[
	LTA: lta, reserved: FALSE, control: optionsAck]],
      options: [optionsAck[IF protocol.object.theirEntityClass = siu THEN
        protocol.object.me ELSE protocol.object.him]]];

    protocol.sendControlFrame[b, optionsAckLength];
    IF CommFlags.doStats THEN SptpStats.Incr[xmtOptionsAck];
    END;  --SendOptionsAck

  SendSizeReject: SptpProtocol.ProtocolProc =
    BEGIN
    b: Buffer.Buffer;
    sizeRejectLength: NATURAL = 2;
    SizeReject: TYPE = LONG POINTER TO SizeRejectFrame;
    SizeRejectFrame: TYPE = RECORD[
      encapsulation: control SptpProtocol.EncapsulationObject,
      options: sizeReject SptpProtocol.ControlBody];

    UNTIL (b ← Driver.GetInputBuffer[TRUE, SptpOps.minRS232CBytes]) # NIL
      DO ENDLOOP;

    LOOPHOLE[b.linkLayer.blockPointer, SizeReject].encapsulation ← 
      [control[LTA: lta, reserved: FALSE, control: sizeReject]];

    protocol.sendControlFrame[b, sizeRejectLength];
    IF CommFlags.doStats THEN SptpStats.Incr[xmtSizeReject];
    END;  --SendSizeReject

  SendTerminateRequest: PUBLIC <<SptpProtocol>> SptpProtocol.ProtocolProc =
    BEGIN
    b: Buffer.Buffer;
    terminateRequestLength: NATURAL = 2;
    TerminateRequest: TYPE = LONG POINTER TO TerminateRequestFrame;
    TerminateRequestFrame: TYPE = RECORD[
      encapsulation: control SptpProtocol.EncapsulationObject,
      options: terminateRequest SptpProtocol.ControlBody];

    UNTIL (b ← Driver.GetInputBuffer[TRUE, SptpOps.minRS232CBytes]) # NIL
      DO ENDLOOP;

    LOOPHOLE[b.linkLayer.blockPointer, TerminateRequest].encapsulation ← 
      [control[LTA: lta, reserved: FALSE, control: terminateRequest]];

    protocol.sendControlFrame[b, terminateRequestLength];
    IF CommFlags.doStats THEN SptpStats.Incr[xmtTermReq];
    END;  --SendTerminateRequest

  SendTerminateReply: SptpProtocol.ProtocolProc =
    BEGIN
    b: Buffer.Buffer;
    terminateReplyLength: NATURAL = 2;
    TerminateReply: TYPE = LONG POINTER TO TerminateReplyFrame;
    TerminateReplyFrame: TYPE = RECORD[
      encapsulation: control SptpProtocol.EncapsulationObject,
      options: terminateReply SptpProtocol.ControlBody];

    UNTIL (b ← Driver.GetInputBuffer[TRUE, SptpOps.minRS232CBytes]) # NIL
      DO ENDLOOP;

    LOOPHOLE[b.linkLayer.blockPointer, TerminateReply].encapsulation ← 
      [control[LTA: lta, reserved: FALSE, control: terminateReply]];

    protocol.sendControlFrame[b, terminateReplyLength];
    IF CommFlags.doStats THEN SptpStats.Incr[xmtTermRep];
    END;  --SendTerminateReply

  SendVersionReject: SptpProtocol.ProtocolProc =
    BEGIN
    b: Buffer.Buffer;
    versionRejectLength: NATURAL = 3;
    VersionReject: TYPE = LONG POINTER TO VersionRejectFrame;
    VersionRejectFrame: TYPE = RECORD[
      encapsulation: control SptpProtocol.EncapsulationObject,
      options: versionReject SptpProtocol.ControlBody];

    UNTIL (b ← Driver.GetInputBuffer[TRUE, SptpOps.minRS232CBytes]) # NIL
      DO ENDLOOP;

    LOOPHOLE[b.linkLayer.blockPointer, VersionReject]↑ ← [
      encapsulation: [control[
        LTA: lta, reserved: FALSE, control: versionReject]],
      options: [versionReject[
        highestVersionNumber: protocol.object.protocolVersion]]];

    protocol.sendControlFrame[b, versionRejectLength];
    IF CommFlags.doStats THEN SptpStats.Incr[xmtVersionReject];
    END;  --SendVersionReject

  SetMasterSlaveRelationship: PROC[me, him: System.HostNumber]
    RETURNS[SptpProtocol.MasterSlaveRelationship] = --INLINE
    BEGIN
    OPEN m: LOOPHOLE[me, Forty8Bitter], h: LOOPHOLE[him, Forty8Bitter];
    RETURN[SELECT TRUE FROM
      (him = System.nullHostNumber) => him,  --stupid ole SIU?
      (h.one > m.one) => him, (h.one < m.one) => me,
      (h.two > m.two) => him, (h.two < m.two) => me,
      (h.three > m.three) => him, (h.three < m.three) => me,
      ENDCASE => me];  --and if they're equal, welllllll!
    END;  --SetMasterSlaveRelationship

  SiuSingleBufferSpy: SppOps.SppSpyProc =
    BEGIN
    IF SptpOps.siuSupport THEN
      BEGIN
      gf: LONG POINTER TO FRAME[NetworkStreamImpl] ← LOOPHOLE[link];
      IF action = create THEN
	BEGIN
	gf.xmtr.maxAlloc ← 0;  --that's the most we'll try to send
	gf.rcvr.maxAlloc ← 0;  --that's the most we want sent to us
	gf.rexmtr.interval ← System.MicrosecondsToPulses[6000000];
	END;
      END;
    END;  --SiuSingleBufferSpy

  TerminationDally: PUBLIC <<SptpProtocol>> SptpProtocol.ProtocolProc =
    BEGIN
    b: Buffer.Buffer;
    UNTIL (b ← WaitForControl[protocol, clock.to5]) = NIL DO
      ProcessControl[protocol, b];  --keep this up until he quits
      ENDLOOP;
    protocol.object.state ← idle;  --now I'm idle
    END;  --TerminationDally

  UnlatchSiu: PROC = INLINE {[] ← SppOps.SetSppSpy[NIL]};

  WaitForControl: PUBLIC ENTRY <<SptpProtocol LOCKS protocol.lock↑>>
    PROC[protocol: SptpProtocol.Protocol, timeout: LONG CARDINAL]
    RETURNS[Buffer.Buffer] =
    BEGIN
    ENABLE UNWIND => NULL;
    timein: LONG CARDINAL = System.GetClockPulses[];  --when we enter
    WHILE protocol.q.length = 0 DO  --already have something to do
      IF timeout = LAST[LONG CARDINAL] THEN EXIT;  --just checking queue
      WAIT protocol.engine;  --wait for a notable event
      SELECT TRUE FROM
        (protocol.q.length > 0) => EXIT;  --we can do it now
	(timeout = 0) => EXIT;  --just waiting for the interrupt
	((System.GetClockPulses[] - timein) > timeout) => EXIT;  --timeout
	ENDCASE;
      ENDLOOP;
    RETURN[Buffer.Dequeue[@protocol.q]];
    END;  --WaitForControl

  clock.to1 ← System.MicrosecondsToPulses[2D6];  --2 seconds
  clock.to2 ← System.MicrosecondsToPulses[20D6];  --20 secs
  clock.to3 ← System.MicrosecondsToPulses[10D6];  --10 secs
  clock.to4 ← System.MicrosecondsToPulses[5D6];  --5 secs
  clock.to5 ← System.MicrosecondsToPulses[2D6];  --2 seconds
  clock.to6 ← System.MicrosecondsToPulses[20D6];  --20 secs

    
    
  END..
  
LOG

29-Sep-87 18:32:20  AOF  Created file.
 2-Nov-87 12:23:10  AOF  Adjusting window for SIUs.
 7-Nov-87 16:24:09  AOF  Moved in state machine procs from SptpDriver.
11-Nov-87 19:07:53  AOF  Export missing procedures and error.
13-Nov-87 12:00:54  AOF  Mods to Adoption routines.
 2-Dec-87 14:48:24  AOF  State machine cleanup.
 2-Dec-87 15:48:30  AOF  Set master-slave relationship sooner.
15-Dec-87 13:24:41  AOF  SIUs - they don't use same encapsulation.
18-Jan-88 14:38:49  AOF  Sutting down allocation for SIUs.
 3-Feb-88 11:26:19  AOF  PupGateway (no .5 duplex).