-- File: NewNetworkStreamImpl.mesa - last edit:
-- MXI                 27-Oct-87 15:04:05
-- Copyright (C) 1984, 1985, 1986, 1987 by Xerox Corporation. All rights reserved. 

DIRECTORY
  Buffer USING [],
  BufferOps USING [GetSizes],
  CommSwitches USING [xmtRateControl, xmtRateT1, xmtRate64],
  CommFlags USING [doDebug],
  CommUtil USING [PulsesToTicks],
  Driver USING [Device, Glitch, nilDevice],
  Environment USING [bytesPerWord, nullBlock],
  HostNumbers USING [IsMulticastID],
  Inline USING [BITAND, LongCOPY],
  NetworkStream USING [ConnectionFailed, ConnectionID, CreateTransducer,
    defaultWaitTime, infiniteWaitTime, ListenError, WaitTime],
  NetworkStreamInternal USING [ListenerHandle],
  NetworkStreamImpl USING [attn, connection, packetStreamObject, probe,
    rcvr, rexmtr, startByteStream, stopByteStream, xmtr],
  NewNetworkStream USING [NegotiationParameters,
    nullTypeValuePairs, TSap, TSapObject, TypeValuePairs],
  NSBuffer USING [Body, Buffer, Dequeue, GetBuffer, ReturnBuffer],
  NSTypes USING [bytesPerIDPHeader, bytesPerSppHeader,
    ConnectionID, maxDataBytesPerSpp, maxIDPBytesPerPacket, WaitTime],
  PacketStream USING [bytesPerSequencedPktHeader,
    ConnectionAlreadyThere, ConnectionFailed, Handle, unknownConnID],
  Protocol1 USING [Family, GetFamilyUnit, SetMaximumBufferSize],
  Process USING [DisableTimeout, EnableAborts, SetTimeout],
  Router USING [GetDelayToNet, NoTableEntryForNet],
  RouterInternal USING [SendErrorPacket],
  Runtime USING [GetCaller],
  SpecialSystem USING [HostNumber],
  SppNegotiation USING [NegotiationHints, NewMake],
  SppOps USING [DisableChecksums, DisableProbes, EnableChecksums, EnableProbes,
    GlobalFrameFromByteStream, GlobalFrameFromPacketStream,
    nullAttention, PacketStreamFromByteStream, sppWindowSize],
  Socket USING [ChannelHandle, Create, GetBufferPool,
    GetAssignedAddress, GetPacket, PutPacket, SetWaitTime, TimeOut],
  Stream USING [Handle],
  System USING [GetClockPulses, MicrosecondsToPulses, NetworkAddress,
    NetworkNumber, nullNetworkAddress, switches];

NewNetworkStreamImpl: MONITOR
  IMPORTS
    BufferOps, CommUtil, Driver, HostNumbers, Inline, NetworkStream,
    NSBuffer, PacketStream, Protocol1, Process, Router,
    RouterInternal, Runtime, SppNegotiation, SppOps, Socket, System
  EXPORTS
    Buffer, NetworkStream, NewNetworkStream, SppNegotiation, System =
  BEGIN
  
  --EXPORTED TYPE(S) and READONLY Variables

  HostNumber: PUBLIC <<System>> TYPE = SpecialSystem.HostNumber;
  ListenerHandle: PUBLIC <<NetworkStream>> TYPE = NetworkStreamInternal.ListenerHandle;
  ConnectionID: PUBLIC <<NetworkStream>> TYPE = NSTypes.ConnectionID;
  Device: PUBLIC <<Buffer>> TYPE = Driver.Device;
  
  -- Types
  NegotiationParameters: TYPE = NewNetworkStream.NegotiationParameters;
  TSap: TYPE = NewNetworkStream.TSap;
  TSapObject: TYPE = NewNetworkStream.TSapObject;
  
  -- Internal variables
  
  unknownCID, uniqueCID: ConnectionID = PacketStream.unknownConnID;
  maxAlloc: CARDINAL ← SppOps.sppWindowSize;
  
  uniqueTSapObject: TSapObject = [
    address: System.nullNetworkAddress, connectionID: uniqueCID];
    
  bytesPerHeader: CARDINAL = NSTypes.bytesPerIDPHeader + NSTypes.bytesPerSppHeader;
  maxSppBytesWithMaxBuffer: CARDINAL ← BufferOps.GetSizes[][maxBuffer] - bytesPerHeader;

  -- Following default value should be identical to ones in NetworkStreamImpl.
  
  xmtrDefaults: RECORD [allocation, rateControlPause, delay1Hop, delayNHopT1,
    delayNHop64, delayNHop96, totalQuench: CARDINAL] =
    [1, 50, 50, 100, 120, 300, 16]; 

  rcvrDefaults: RECORD [accept, duplicate: INTEGER] =
    [2, 20];
    
  rexmtrDefaults: RECORD [flushRoute, giveUp, <<numbers of packets>>
    initialLocal, initialRemote, ceiling, floor, calculationInterval: CARDINAL <<msecs>>] =
    [8, 30, 500, 6000, 24000, 200, 5000]; 
    
  probeDefaults: RECORD [giveUp, inactiveInterval, allocInterval, hopDelay: CARDINAL] =
    [8, 5000, 2000, 2000];
    
  streamParms: RECORD [send, receive, xmtrAlloc, rcvrAlloc: CARDINAL] =
    [3, 4, 0, 0];
    
  maxMsecToPulses: LONG CARDINAL = LAST[LONG CARDINAL] / 1000;

  -- Internal errors
  
  BadDestintationAddress: ERROR = CODE;  --for glitching
  
  -- Public procedures

  NewApprove: PUBLIC PROC[listenerH: ListenerHandle,
    negotiationParameters: NegotiationParameters ← NIL,
    streamTimeout: NetworkStream.WaitTime ← NetworkStream.infiniteWaitTime]
    RETURNS [sH: Stream.Handle] =
    BEGIN
    localTSapObject: TSapObject ← uniqueTSapObject;
    remoteTSapObject: TSapObject ← [
      listenerH.buffer.ns.source, listenerH.buffer.ns.sourceConnectionID];
    
    IF (listenerH = NIL) OR (listenerH.seal # listenerSeal)
      THEN ERROR NetworkStream.ListenError[illegalHandle];
    IF listenerH.state # pending THEN ERROR NetworkStream.ListenError[illegalState];
    IF GetNegBit[listenerH.buffer.ns] THEN
      sH ← NewTransducer[
	@localTSapObject, @remoteTSapObject, NIL,
	[TRUE, listenerH.buffer.fo.driver.device], streamTimeout!
	  UNWIND => {
	    NSBuffer.ReturnBuffer[listenerH.buffer]; listenerH.state ← idle}]
    ELSE
      sH ← NetworkStream.CreateTransducer[
	localTSapObject.address, listenerH.buffer.ns.source,
	Environment.nullBlock,
	unknownCID, listenerH.buffer.ns.sourceConnectionID,
	TRUE, streamTimeout, bulk !
	  UNWIND => {
	    NSBuffer.ReturnBuffer[listenerH.buffer]; listenerH.state ← idle}];
    NSBuffer.ReturnBuffer[listenerH.buffer];
    listenerH.state ← idle;
    END;

  NewCreate: PUBLIC PROC[
    remote: System.NetworkAddress,
    negotiationParameters: NegotiationParameters ← NIL,
    timeout: NetworkStream.WaitTime ← NetworkStream.defaultWaitTime]
    RETURNS [Stream.Handle] =
    BEGIN
    localTSapObject: TSapObject ← uniqueTSapObject;
    remoteTSapObject: TSapObject ← [remote, uniqueCID];
    
    RETURN[
      NewTransducer[
        @localTSapObject, @remoteTSapObject, NIL, [TRUE, ethernet], timeout]];
    END; --Create

  NewTransducer: PUBLIC PROC[
    local, remote: TSap,  --for the connection to be created
    negotiationParameters: NegotiationParameters ← NIL,
    hints: SppNegotiation.NegotiationHints,
    timeout: NetworkStream.WaitTime ← NetworkStream.defaultWaitTime]
    RETURNS [sH: Stream.Handle] =
    BEGIN
    psH: PacketStream.Handle;
    instanceN: LONG POINTER TO FRAME[NetworkStreamImpl];
    SELECT TRUE FROM
      (~HostNumbers.IsMulticastID[@remote.address.host]) => NULL;  --okay
      (~CommFlags.doDebug) => --DON'T LET HIM GET AWAY WITH THIS-- DO
	SIGNAL NetworkStream.ConnectionFailed[noTranslationForDestination];
	ENDLOOP;
      ENDCASE => Driver.Glitch[BadDestintationAddress];
    psH ← SppNegotiation.NewMake[
      local, remote, negotiationParameters, hints, timeout];
    instanceN ← SppOps.GlobalFrameFromPacketStream[psH];
    sH ← instanceN.startByteStream[psH];
    sH.delete ← Delete;  --don't use instance's version of delete
    END; --CreateTransducer

  NewListen: PUBLIC PROC[
    listenerH: ListenerHandle,
    listenTimeout: NetworkStream.WaitTime ← NetworkStream.infiniteWaitTime]
    RETURNS [remote: System.NetworkAddress] =
    BEGIN

    GetPacket: ENTRY PROC =
      BEGIN
      ENABLE UNWIND => NULL;
      WHILE (b ← NSBuffer.Dequeue[@listenerH.queue]) = NIL DO
        WAIT listenerH.condition; ENDLOOP;
      END;  --GetPacket

    b: NSBuffer.Buffer;
    IF (listenerH = NIL) OR (listenerH.seal # listenerSeal)
      THEN ERROR NetworkStream.ListenError[illegalHandle];
    b ← listenerH.buffer;
    SELECT listenerH.state FROM
      pending => RouterInternal.SendErrorPacket[b, listenerReject];
      listening => ERROR NetworkStream.ListenError[illegalState];
      ENDCASE;
    listenerH.state ← listening;
    Socket.SetWaitTime[listenerH.socket, listenTimeout];
    DO
    --until the packet is a sequenced packet and is not a duplicate,
    --or timeout, or aborted socket
      body: NSBuffer.Body;
      GetPacket[ ! UNWIND => listenerH.state ← idle];
      IF b = NIL THEN LOOP;  --nothing happening here
      body ← b.ns;  --makes for cheaper access
      --discard any cases with something wrong, ENDCASE is only acceptable packet
      SELECT TRUE FROM
	(b.fo.status # goodCompletion) =>
	  NSBuffer.ReturnBuffer[b];  --don't chance it with bad buffers
	(HostNumbers.IsMulticastID[@body.destination.host]) =>
	  NSBuffer.ReturnBuffer[b];  --he's insane, but can't error broadcast
	(HostNumbers.IsMulticastID[@body.source.host]) =>
	  NSBuffer.ReturnBuffer[b];  --he's busted, and I don't care
	(body.packetType # sequencedPacket) =>
	  RouterInternal.SendErrorPacket[b, invalidPacketType];
	(body.destinationConnectionID # unknownCID) =>
	  RouterInternal.SendErrorPacket[b, protocolViolation];
	(body.sourceConnectionID = unknownCID) =>
	  RouterInternal.SendErrorPacket[b, protocolViolation];
	(body.sequenceNumber # 0) =>
	  RouterInternal.SendErrorPacket[b, protocolViolation];
	(~body.systemPacket) =>
	  RouterInternal.SendErrorPacket[b, protocolViolation];
	(~PacketStream.ConnectionAlreadyThere[
	    body.source, body.sourceConnectionID]) =>
	  BEGIN
	  listenerH.buffer ← b;
	  listenerH.state ← pending;
	  RETURN [body.source];
	  END;
	ENDCASE => NSBuffer.ReturnBuffer[b];
      ENDLOOP;
    END; --Listen

  NewReject: PUBLIC PROC[listenerH: ListenerHandle] =
    BEGIN
    IF (listenerH = NIL) OR (listenerH.seal # listenerSeal)
      THEN ERROR NetworkStream.ListenError[illegalHandle];
    IF listenerH.state # pending THEN ERROR NetworkStream.ListenError[illegalState];
    -- Should send SppError packet. But currently let them timeout...
    NSBuffer.ReturnBuffer[listenerH.buffer];
    listenerH.state ← idle;
    END;

  InitializeInternal: PROC [hops: NATURAL,
    instance: LONG POINTER TO FRAME[NetworkStreamImpl]] =
    BEGIN
    time: LONG CARDINAL = System.GetClockPulses[];
  
    instance.xmtr.clientProcess ← NIL;
    instance.xmtr.maxSeq ← xmtrDefaults.allocation - 1;
    instance.xmtr.unackedSeq ← instance.xmtr.nextSeq ← 0;
    instance.xmtr.maxAlloc ← streamParms.xmtrAlloc + SppOps.sppWindowSize;
    instance.xmtr.blocked ← instance.xmtr.checksums ← TRUE;  --default & starting point
    instance.xmtr.transmitProc ← Socket.PutPacket;  --default & starting point

    instance.xmtr.rateControlling ← (hops # 0) AND
      (System.switches[CommSwitches.xmtRateControl] = down);
    instance.xmtr.interval ← SELECT TRUE FROM
      (~instance.xmtr.rateControlling) => 0,  --we're not doing this
      (hops = 1) => System.MicrosecondsToPulses[xmtrDefaults.delay1Hop],
      (System.switches[CommSwitches.xmtRateT1] = down) =>
        System.MicrosecondsToPulses[xmtrDefaults.delayNHopT1],
      (System.switches[CommSwitches.xmtRate64] = down) =>
        System.MicrosecondsToPulses[xmtrDefaults.delayNHop64],
      ENDCASE => System.MicrosecondsToPulses[xmtrDefaults.delayNHop96];
    instance.xmtr.interval ← 1000 * instance.xmtr.interval;  --we really wanted usecs
    instance.xmtr.quenchIncr ← 0;  --we haven't started yet
    instance.xmtr.lastXmt ← time;  --that's to start with

    Process.DisableTimeout[@instance.xmtr.newAllocation];
    Process.EnableAborts[@instance.xmtr.newAllocation];
    
    instance.probe.inactiveInterval ← 1000 * System.MicrosecondsToPulses[
      probeDefaults.inactiveInterval + (probeDefaults.hopDelay * hops)];
    instance.probe.allocInterval ← 1000 * System.MicrosecondsToPulses[
      probeDefaults.allocInterval + (probeDefaults.hopDelay * hops)];
    instance.probe.lastTime ← time;
    instance.probe.unacked ← 0;
    instance.probe.probing ← TRUE;
    
    instance.rexmtr.ceiling ← 1000 * System.MicrosecondsToPulses[rexmtrDefaults.ceiling];
    instance.rexmtr.floor ← 1000 * System.MicrosecondsToPulses[rexmtrDefaults.floor];
    instance.rexmtr.giveUp ← rexmtrDefaults.giveUp;
    instance.rexmtr.count ← 0;
    instance.rexmtr.delay ← 0;
    instance.rexmtr.interval ← 1000 * System.MicrosecondsToPulses[
      IF hops = 0 THEN rexmtrDefaults.initialLocal
      ELSE rexmtrDefaults.initialRemote];    
    instance.rexmtr.calculationInterval ← 1000 * System.MicrosecondsToPulses[
      rexmtrDefaults.calculationInterval];
    instance.rexmtr.calculation ← time;
    instance.rexmtr.process ← NIL;
    Process.EnableAborts[@instance.rexmtr.condition];
    Process.SetTimeout[
      @instance.rexmtr.condition, CommUtil.PulsesToTicks[[instance.rexmtr.floor / 2]]];
    
    instance.rcvr.nextSeq ← 0;
    instance.rcvr.blocked ← instance.rcvr.acksReqested ← FALSE;
    instance.rcvr.maxSeq ← instance.rcvr.maxAlloc ← streamParms.rcvrAlloc + SppOps.sppWindowSize;
    
    instance.rcvr.process ← NIL;
    instance.rcvr.table.slot ← ALL[NIL];
    instance.rcvr.table.index ← instance.rcvr.table.length ← 0;
    Process.EnableAborts[@instance.rcvr.table.condition];
    Process.DisableTimeout[@instance.rcvr.table.condition];
    
    instance.rexmtr.table.index ← instance.rexmtr.table.length ← 0;
    instance.rexmtr.table.slot ← ALL[NIL];
    Process.EnableAborts[@instance.rexmtr.table.condition];
    Process.DisableTimeout[@instance.rexmtr.table.condition];
    
    instance.attn.table.index ← instance.attn.table.length ← 0;
    instance.attn.table.slot ← ALL[SppOps.nullAttention];
    Process.EnableAborts[@instance.attn.table.condition];
    Process.DisableTimeout[@instance.attn.table.condition];

    END;  --InitializeInternal;
  
  InitializeSppState: PUBLIC PROC [
    local, remote: System.NetworkAddress,
    localID, remoteID: ConnectionID, timeout: NSTypes.WaitTime] =
    BEGIN
    hops: NATURAL;
    instance: LONG POINTER TO FRAME[NetworkStreamImpl] ← LOOPHOLE[Runtime.GetCaller[]];
    socket: Socket.ChannelHandle;
  
    hops ← Router.GetDelayToNet[remote.net!
      Router.NoTableEntryForNet => {hops ← 0; CONTINUE}];
    InitializeInternal[hops, instance];

    socket ← Socket.Create[
      socket: local.socket, type: sequencedPacket, send: 16, receive: 17];
    Socket.SetWaitTime[socket, LAST[NSTypes.WaitTime]];  --no timeouts here

    instance.connection ← [
      state: IF remoteID = unknownCID THEN unestablished ELSE established,
      stateBeforeSuspension: unestablished, whySuspended: notSuspended,
      maxSppBytes: NSTypes.maxDataBytesPerSpp, timeout: timeout, hops: hops,
      socket: socket, remoteAddr: remote, remoteID: remoteID,
      localAddr: Socket.GetAssignedAddress[socket], localID: localID,
      pool: Socket.GetBufferPool[socket], whyFailed: timeout];

    END;  --InitializeSppState;
    
  <<
  TABLE: Four case of establishment (local address/id is valid)
  
  startProc	remoteAddr/Id	actEst	state
  
  NewCreate	no (socket)/no	TRUE	idle -(TX)-> passiveNegotiation
  NewApprove	valid/valid	TRUE	activeNegotiation -(TX)-> waitForDecision
  NewTransducer	valid/no	TRUE	idle -(TX)-> passiveNegotiation
  NewTransducer	valid/no	FALSE	idle
  >>
    
  NegTypes: TYPE = MACHINE DEPENDENT {
  
    size(1),	-- (packets)
    		-- The size of the data portion of the SPP data unit.
    window(2),	-- (packets)
    		-- The number of packets to be advertised in the allocation window.
    xmti(3),	-- (millisecs) The minimum interpacket transmit interval.

    rxmtf(4),	-- (millisecs) The minimum value of the retransmission interval
    		-- that is calculated based on stream history.
    rxmtc(5),	-- (millisecs) The maximum value of the retransmission interval
    		-- that is calculated based on stream history.
    rxmtl(6),	-- (packets) The number of times a data packet should be retransmitted
    		-- at the current retransmit interval before the connection is abandoned.
    check(7),	-- (switch) Should the connection generate/check software checksums?

    probe(8)	-- (switch) Should the connection generate idle line probes?
    
    };
    
  NegState: TYPE = {idle, passiveNegotiation, activeNegotiation, negotiating, waitForDecision};
    
  NewInitializeSppState: PUBLIC PROC [
    local, remote: TSap,
    negotiationParameters: NegotiationParameters,
    hints: SppNegotiation.NegotiationHints, timeout: NSTypes.WaitTime] =
    BEGIN
    hops: NATURAL;
    instance: LONG POINTER TO FRAME[NetworkStreamImpl] ← LOOPHOLE[Runtime.GetCaller[]];
    socket: Socket.ChannelHandle;
    localParams: ARRAY [0..NegTypes.LAST.ORD - NegTypes.FIRST.ORD]
      OF NewNetworkStream.TypeValuePairs ← ALL[NewNetworkStream.nullTypeValuePairs];
    
    CollectNegotiationParams: PROC = BEGIN
      index: CARDINAL ← 0;
      
      IF negotiationParameters = NIL THEN BEGIN
	negotiationParameters ← DESCRIPTOR[localParams];
	FOR n: NegTypes IN NegTypes DO
	  negotiationParameters[index].type ← n.ORD;
	  index ← index + 1;
	  ENDLOOP;
	FOR index IN [0..negotiationParameters.LENGTH) DO
	  SELECT localParams[index].type FROM
	    NegTypes.size.ORD => localParams[index].value ← [value[
	      IF hops = 0 THEN maxSppBytesWithMaxBuffer ELSE instance.connection.maxSppBytes]];
	    NegTypes.window.ORD => localParams[index].value ← [value[
	      MAX [instance.xmtr.maxAlloc, MIN [hops * 2, 13]]]];
	    NegTypes.xmti.ORD => localParams[index].value ← [value[CARDINAL[
	      instance.xmtr.interval / 1000]]]; -- usec to msec
	    NegTypes.rxmtf.ORD => localParams[index].value ← [value[CARDINAL[
	      instance.rexmtr.floor / 1000]]]; -- usec to msec
	    NegTypes.rxmtc.ORD => localParams[index].value ← [value[CARDINAL[
	      instance.rexmtr.ceiling / 1000]]]; -- usec to msec
	    NegTypes.rxmtl.ORD => localParams[index].value ← [value[CARDINAL[
	      instance.rexmtr.interval / 1000]]]; -- usec to msec
	    NegTypes.check.ORD => localParams[index].value ← [switch[
	      IF hops = 0 AND hints.driver = ethernet THEN off ELSE on]];
	    NegTypes.probe.ORD => localParams[index].value ← [switch[on]];
	    ENDCASE;
	  ENDLOOP;
	END
      ELSE BEGIN  -- Copy client's data
        valid: CARDINAL ← 0;
        FOR index IN [0..negotiationParameters.LENGTH) DO
	  IF negotiationParameters[index].type IN
	    [NegTypes.FIRST.ORD..NegTypes.LAST.ORD] THEN BEGIN
	    localParams[index] ← negotiationParameters[index];
	    valid ← valid + 1;
	    END;
	  ENDLOOP;
	negotiationParameters ← DESCRIPTOR[@localParams, valid];
        END;
      END;
    
    ApplyNegotiationParams: PROC = BEGIN
      FOR index: CARDINAL IN [0..negotiationParameters.LENGTH) DO
	SELECT localParams[index].type FROM
	  NegTypes.size.ORD => instance.connection.maxSppBytes ← localParams[index].value.v;
	  NegTypes.window.ORD => instance.xmtr.maxAlloc ← localParams[index].value.v;
	  NegTypes.xmti.ORD => instance.xmtr.interval ← LONG[localParams[index].value.v];
	  NegTypes.rxmtf.ORD => instance.rexmtr.floor ← LONG[localParams[index].value.v];
	  NegTypes.rxmtc.ORD => instance.rexmtr.ceiling ← LONG[localParams[index].value.v];
	  NegTypes.rxmtl.ORD => instance.rexmtr.interval ← LONG[localParams[index].value.v];
	  NegTypes.check.ORD => SELECT localParams[index].value.s FROM
	    on => SppOps.EnableChecksums[@instance.packetStreamObject];
	    off => SppOps.DisableChecksums[@instance.packetStreamObject];
	    ENDCASE;
	  NegTypes.probe.ORD => SELECT localParams[index].value.s FROM
	    on => SppOps.EnableProbes[@instance.packetStreamObject];
	    off => SppOps.DisableProbes[@instance.packetStreamObject];
	    ENDCASE;
	  ENDCASE;
	ENDLOOP;
      END;
      
    systemPacketSize: CARDINAL = PacketStream.bytesPerSequencedPktHeader;
    bytesPerTypeValuePairs: CARDINAL =
      SIZE[NewNetworkStream.TypeValuePairs] * Environment.bytesPerWord;
    
    previousType: [0..17B);
    
    SendPacket: PROC [type: [0..17B)] = BEGIN
      buffer: NSBuffer.Buffer ← NIL;
      body: NSBuffer.Body ← NIL;
      paramsBytes: CARDINAL ← 0;
      
      IF type = paramType THEN
        paramsBytes ← negotiationParameters.LENGTH * bytesPerTypeValuePairs;
      
      buffer ← NSBuffer.GetBuffer[aH: instance.connection.pool,
        wait: TRUE, function: send, size: systemPacketSize + paramsBytes];
      body ← buffer.ns;
      body.pktLength ← systemPacketSize + paramsBytes;
      body.packetType ← sequencedPacket;
      body.destination ← instance.connection.remoteAddr;
      body.systemPacket ← body.sendAck ← TRUE;
      body.attention ← body.endOfMessage ← FALSE;
      body.unusedType ← type;
      body.subtype ← 0;
      body.sourceConnectionID ← instance.connection.localID;
      body.destinationConnectionID ← instance.connection.remoteID;
      body.sequenceNumber ← 0;
      body.acknowledgeNumber ← 0;
      body.allocationNumber ← maxAlloc;
      IF paramsBytes # 0 THEN Inline.LongCOPY[from: @localParams,
        nwords: paramsBytes / Environment.bytesPerWord, to: @body.sppWords];
      Socket.PutPacket[instance.connection.socket, buffer];
      previousType ← type;
      END;
      
    CheckParameters: PROC [body: NSBuffer.Body] RETURNS [BOOLEAN] = BEGIN
      remoteParams: NegotiationParameters ← DESCRIPTOR[
        @body.sppWords,
	(body.pktLength - systemPacketSize) / bytesPerTypeValuePairs];

      -- Do checking
      -- 'negotiationParameters' will points local parameters
      -- 'remoteParams' will points local parameters
      -- negotiated result should be stored in negotiationParameters
      -- Apply params will use negotiationParameters
      -- New proposition also have to be stored in local parameters
      RETURN[TRUE];  -- For now
      END;
      
    -- When this procedure is called,
    -- negotiated parameter should be stored in localParams
    SetMaxBufferSize: PROC = BEGIN
      maxSize: CARDINAL ← NSTypes.maxIDPBytesPerPacket;
      family: Protocol1.Family;

      -- Get negotiated max buffer size
      FOR i: CARDINAL IN [0..negotiationParameters.LENGTH) DO
        IF negotiationParameters[i].type # NegTypes.size.ORD THEN LOOP;
	maxSize ← bytesPerHeader + negotiationParameters[i].value.v;
	EXIT
        ENDLOOP;
      family ← Protocol1.GetFamilyUnit[ns];
      IF family.maxBufferSize < maxSize THEN
	Protocol1.SetMaximumBufferSize[Driver.nilDevice, family, maxSize];
      END;
      
    Negotiate: PROC RETURNS [succeed: BOOLEAN ← FALSE] = BEGIN
      state: NegState ← idle;
      buffer: NSBuffer.Buffer ← NIL;
      body: NSBuffer.Body ← NIL;
      continue: BOOLEAN ← TRUE;
      time, interval: LONG CARDINAL;

      -- Initial action
      SELECT TRUE FROM
        remote.connectionID # LOOPHOLE[unknownCID] => BEGIN
	  -- NewApprove case
	  SendPacket[paramType];
	  state ← waitForDecision;
	  END;
	hints.activeEstablish => BEGIN
	  -- NewCreate or NewTransducer (active side) send RFC
	  SendPacket[negType];
	  state ← passiveNegotiation;
	  END;
	ENDCASE => BEGIN
	  -- NewTransducer (passive side) wait RFC
	  state ← idle;
	  END;
	  
      -- Set timeout interval
      timeout ← MAX[4000, timeout];  -- Argument timeout could be updated here.
      interval ← IF timeout > maxMsecToPulses
        THEN LAST[LONG CARDINAL] 
	ELSE System.MicrosecondsToPulses[timeout*1000];
      time ← System.GetClockPulses[];
      Socket.SetWaitTime[socket, 2000];

      -- Wait for response
      WHILE continue DO
        buffer ← NIL;
        buffer ← Socket.GetPacket[
	  instance.connection.socket!Socket.TimeOut => CONTINUE];
        IF buffer = NIL THEN
	  IF System.GetClockPulses[] - time > interval THEN << Timeout >>
	    SIGNAL PacketStream.ConnectionFailed[timeout]
	  ELSE << Retry >> BEGIN
	    SendPacket[previousType]; LOOP;
	    END;
	time ← System.GetClockPulses[];
	body ← buffer.ns;
	IF body.packetType = sequencedPacket AND body.systemPacket THEN SELECT state FROM
	  idle => IF body.source = instance.connection.remoteAddr THEN BEGIN
	    instance.connection.remoteID ← body.sourceConnectionID;
	    IF GetNegBit[body] THEN SendPacket[paramType]  -- Send parameter
	    ELSE continue ← FALSE;  -- Remote doesn't understand negotiation. Failed...
	    END;
	  passiveNegotiation => IF body.source.host = instance.connection.remoteAddr.host THEN BEGIN
	    instance.connection.remoteAddr.socket ← body.source.socket;
	    instance.connection.remoteID ← body.sourceConnectionID;
	    IF GetParamBit[body] THEN
	      -- If parameter is acceptable then end negotiation
	      IF CheckParameters[body] THEN BEGIN
		continue ← FALSE;
		succeed ← TRUE;  --Yeah!!
		SetMaxBufferSize[];
		END
	      -- If parameter is noy acceptable then continue
	      -- Propose defferenct parameters
	      ELSE SendPacket[paramType]
	    ELSE continue ← FALSE;  -- Remote doesn't understand negotiation. Failed...
	    END;
	  waitForDecision => BEGIN
	    IF GetParamBit[body] THEN
	      -- Remote didn't agree to the parameters. They sent their proposition.
	      IF CheckParameters[body] THEN BEGIN
	      -- We agree to their proposition
	        continue ← FALSE;
	        succeed ← TRUE;  --Yeah!!
		SetMaxBufferSize[];
	        END
	      -- We don't agree to their proposition. Send our new proposition.
	      ELSE SendPacket[paramType]
	    ELSE BEGIN
	      -- Remote agreed to the parameters
	      continue ← FALSE;
	      succeed ← TRUE;  --Yeah!!
	      SetMaxBufferSize[];
	      END;
	    END;
	  ENDCASE;
	NSBuffer.ReturnBuffer[buffer];
        ENDLOOP;
      
      IF remote.connectionID # LOOPHOLE[unknownCID]
        THEN instance.connection.state ← established;
      Socket.SetWaitTime[socket, LAST[NSTypes.WaitTime]];  --no timeouts here
      END;
  
    hops ← Router.GetDelayToNet[remote.address.net!
      Router.NoTableEntryForNet => {hops ← 0; CONTINUE}];
    InitializeInternal[hops, instance];

    socket ← Socket.Create[
      socket: local.address.socket, type: sequencedPacket, send: 16, receive: 17];
    Socket.SetWaitTime[socket, LAST[NSTypes.WaitTime]];  --no timeouts here
    
    instance.connection ← [
      state: unestablished, stateBeforeSuspension: unestablished, whySuspended: notSuspended,
      maxSppBytes: NSTypes.maxDataBytesPerSpp, timeout: timeout, hops: hops,
      socket: socket, remoteAddr: remote.address, remoteID: remote.connectionID,
      localAddr: Socket.GetAssignedAddress[socket], localID: local.connectionID,
      pool: Socket.GetBufferPool[socket], whyFailed: timeout];

    CollectNegotiationParams[];
    IF Negotiate[] THEN ApplyNegotiationParams[];

    END;  --NewInitializeSppState;
  
  -- Internal procedures
  
  -- Assignment
  -- 0: systemPacket
  -- 1: sendAck
  -- 2: attention
  -- 3: endOfMessage
  -- 4: negotiation
  -- 5: parameter
  
  negMask: UNSPECIFIED = 0008H;
  paramMask: UNSPECIFIED = 0004H;
  
  negType: [0..17B) = 8H;
  paramType: [0..17B) = 4H;
  nullType: [0..17B) = 0H;
  
  GetNegBit: PROCEDURE [b: NSBuffer.Body] RETURNS [BOOLEAN] = INLINE BEGIN
    RETURN[Inline.BITAND[negMask, b.unusedType]];
    END;
  
  GetParamBit: PROCEDURE [b: NSBuffer.Body] RETURNS [BOOLEAN] = INLINE BEGIN
    RETURN[Inline.BITAND[paramMask, b.unusedType]];
    END;
  
  Delete: PROC[sH: Stream.Handle] =
    BEGIN
    --delete instance associcated with this stream
    instanceN: LONG POINTER TO FRAME[NetworkStreamImpl] =
      SppOps.GlobalFrameFromByteStream[sH];
    psH: PacketStream.Handle = SppOps.PacketStreamFromByteStream[sH];
    instanceN.stopByteStream[];  --shut down the bytestream
    psH.destroy[psH];  --then the underlying packet stream
    END;

  END.  -- NewNetworkStreamImpl

LOG 
27-Oct-87 15:05:06  MXI  Created for SPP negotiation.