-- File: Pup10MBitImpl.mesa - last edit:
-- AOF                 22-Feb-86 17:03:19
-- HGM                  7-Dec-85 18:25:26
-- Copyright (C) 1985, 1986 by Xerox Corporation. All rights reserved. 

DIRECTORY
  Buffer USING [Buffer, GetBuffer, Type],
  CommFlags USING [doDebug],
  CommHeap USING [zone],
  Driver USING [Network],
  DriverTypes USING [Encapsulation],
  Process USING [
    DisableTimeout, EnableAborts, SecondsToTicks, SetPriority,
    SetTimeout, Abort],
  ProcessPriorities USING [priorityIOHigh],
  Protocol1 USING [EncapsulatorProc, DecapsulatorProc],
  PupPktOps USING [pupBuffers],
  Pup10MBit USING [],
  PupRouterDefs USING [ContextObject, NetworkContext],
  PupStream USING [],
  PupTypes USING [allHosts, PupHostID],
  SpecialSystem USING [GetProcessorID, HostNumber, ProcessorID],
  System USING [
    broadcastHostNumber, GetClockPulses, HostNumber, MicrosecondsToPulses,
    nullHostNumber, Pulses];

Pup10MBitImpl: MONITOR
  IMPORTS
    Process, Buffer, PupPktOps, CommHeap, SpecialSystem, System
  EXPORTS Buffer, Pup10MBit, System =

  BEGIN
  Network: PUBLIC TYPE = Driver.Network;
  HostNumber: PUBLIC TYPE = SpecialSystem.HostNumber;

  chain: Handle ← NIL;
  Handle: PUBLIC TYPE = LONG POINTER TO Object;
  Object: PUBLIC TYPE = RECORD[
    depth: CARDINAL ← 0,
    me, all, head: TranslateEntry ← NIL,
    event: CONDITION,
    demon: PROCESS ← NIL,
    driver: Network,
    next: Handle];

  TranslatePair: TYPE = LONG POINTER TO TranslateRecord;
  TranslateRecord: TYPE = MACHINE DEPENDENT RECORD [
    nsAddr: HostNumber, pupHost: PupTypes.PupHostID, filler: [0..377B]];

  TranslateEntry: TYPE = LONG POINTER TO TranslateObject;

  TranslateObject: TYPE = RECORD [
    nextLink: TranslateEntry,
    translatePair: TranslateRecord,
    tries: CARDINAL,
    timeStamp: System.Pulses,
    status: TranslateStatus];

  TranslateStatus: TYPE = {new, pending, active, zombie};

  --constants
  retryLimit: CARDINAL = 10B;
  retryPulses: System.Pulses ← System.MicrosecondsToPulses[2000000];
  deactivatePulses: System.Pulses ← System.MicrosecondsToPulses[180000000];
  translationRequest: CARDINAL = 10101B;
  translationResponse: CARDINAL = 7070B;
  myNSHost: SpecialSystem.ProcessorID = SpecialSystem.GetProcessorID[];

  Capsulators: PUBLIC PROC RETURNS[
    decap: Protocol1.DecapsulatorProc, encap: Protocol1.EncapsulatorProc] =
    {RETURN[DecapsulatePupEthernet, EncapsulatePupEthernet]};

  Create: PUBLIC PROC[driver: Network, host: PupTypes.PupHostID]
    RETURNS[xlate: Handle] =
    BEGIN
    xlate ← CommHeap.zone.NEW[Object];
    xlate.me ← xlate.all ← xlate.head ← NIL;
    xlate.demon ← NIL;
    xlate.driver ← driver;
    xlate.next ← NIL;
    Process.EnableAborts[@xlate.event];
    xlate.all ← AddTranslateRecord[xlate,
      [System.broadcastHostNumber, PupTypes.allHosts, 0]];
    xlate.me ← AddTranslateRecord[xlate, [myNSHost, [host], 0]];
    xlate.demon ← FORK Demon[driver, xlate];
    IF chain = NIL THEN chain ← xlate
    ELSE
      FOR finger: Handle ← chain, finger.next DO
        IF finger.next # NIL THEN LOOP;
	finger.next ← xlate;
	EXIT;
        ENDLOOP;
    END;  --Create

  Destroy: PUBLIC PROC[xlate: Handle] =
    BEGIN
    IF xlate.demon # NIL THEN {Process.Abort[xlate.demon]; JOIN xlate.demon};
    --cleanup in case demon was never running
    UNTIL xlate.head = NIL DO
      e: TranslateEntry ← xlate.head;
      xlate.head ← e.nextLink;
      CommHeap.zone.FREE[@e];
      ENDLOOP;
    IF chain = xlate THEN chain ← chain.next
    ELSE
      FOR finger: Handle ← chain, finger.next UNTIL finger.next = NIL DO
        IF finger.next # xlate THEN LOOP;
	finger.next ← finger.next.next;
	EXIT;
        ENDLOOP;
    END;  --Delete

  EncapsulatePupEthernet: PUBLIC Protocol1.EncapsulatorProc =
  --EncapsulatorProc: TYPE = PROC[b: Buffer.Buffer, immediate: LONG POINTER];
    BEGIN
    OPEN context: LOOPHOLE[b.context, PupRouterDefs.NetworkContext];
    destination: CARDINAL ← immediate↑;
    nsAddr: HostNumber ← Translate[context.protocol, [destination]];
    b.encapsulation ←
      [ethernet[ethernetDest: nsAddr,
      ethernetSource: myNSHost,
      ethernetType: pup]];
    b.driver.length ← (b.pup.pupLength + 1)/2 + SIZE[DriverTypes.Encapsulation];
    END;  --EncapsulatePupEthernet

  DecapsulatePupEthernet: PUBLIC Protocol1.DecapsulatorProc =
  --DecapsulatorProc: TYPE = PROC[b: Buffer.Buffer] RETURNS[Buffer.Type];
    BEGIN
    bytes: CARDINAL;
    ecap: CARDINAL = SIZE[DriverTypes.Encapsulation];
    IF b.driver.length < ecap THEN RETURN[orphan];  --bad no matter what
    SELECT b.encapsulation.ethernetType FROM
      translation => {ReceiveTranslate[b]; RETURN[orphan]};  --one of those
      # pup => RETURN[vagrant];  --still unknown
      ENDCASE;
    bytes ← (b.driver.length - ecap)*2;
    IF bytes < b.pup.pupLength THEN RETURN[orphan] ELSE RETURN[pup];
    END;  --DecapsulatePupEthernet

  Translate: PUBLIC ENTRY PROC[xlate: Handle, pupHost: PupTypes.PupHostID]
    RETURNS [nsAddr: HostNumber ← System.nullHostNumber] =
    BEGIN
    <<
    NOTE: Only the 'active' arm assigns 'nsAddr' a value that won't be discarded.
    The default value assigned above will cause the driver to drop the packet.
    >>
    e: TranslateEntry;
    SELECT TRUE FROM
      ((e ← FindTranslate[xlate, pupHost]) = NIL) =>
        BEGIN
	e  ← CommHeap.zone.NEW[TranslateObject];
	AddTranslate[xlate, e];
	e.translatePair.pupHost ← pupHost;
	NOTIFY xlate.event;
	e.status ← new; e.tries ← 0;
	e.timeStamp ← System.GetClockPulses[];
	END;
      (e.status = active) =>
	BEGIN
	IF e # xlate.head THEN
	  {RemoveTranslate[xlate, e]; AddTranslate[xlate, e]};
	nsAddr ← e.translatePair.nsAddr;
	e.timeStamp ← System.GetClockPulses[];
	END;
      (e.status = zombie) =>
	BEGIN
	IF e # xlate.head THEN
	  {RemoveTranslate[xlate, e]; AddTranslate[xlate, e]};
	NOTIFY xlate.event;
	e.status ← new; e.tries ← 0;
	e.timeStamp ← System.GetClockPulses[];
	END;
      --(e.status = pending | new) => NULL;
      ENDCASE;
    END;

  --interface

  FindTranslate: INTERNAL PROC[xlate: Handle, pupHost: PupTypes.PupHostID]
    RETURNS [entry: TranslateEntry] =
    BEGIN
    entry ← xlate.head;
    WHILE entry # NIL DO
      IF pupHost = entry.translatePair.pupHost THEN RETURN;
      entry ← entry.nextLink;
      ENDLOOP;
    END;

  AddTranslate: INTERNAL PROC[xlate: Handle, entry: TranslateEntry] =
    BEGIN
    entry.nextLink ← xlate.head;
    xlate.head ← entry;
    xlate.depth ← xlate.depth + 1;
    END;  --AddTranslate

  RemoveTranslate: INTERNAL PROC[xlate: Handle, entry: TranslateEntry] =
    BEGIN
    e, pred: TranslateEntry;
    IF (pred ← xlate.head) = entry THEN
      {xlate.head ← xlate.head.nextLink; RETURN};
    FOR e ← pred.nextLink, e.nextLink UNTIL e = NIL DO
      IF e # entry THEN pred ← e
      ELSE
        BEGIN
	pred.nextLink ← entry.nextLink;
	xlate.depth ← xlate.depth - 1;
	EXIT;
	END;
      ENDLOOP;
    END;

  AddTranslateRecord: ENTRY PROC[xlate: Handle, pair: TranslateRecord]
    RETURNS [e: TranslateEntry] =
    BEGIN
    e ← FindTranslate[xlate, pair.pupHost];
    SELECT e FROM
      (NIL) => {e ← CommHeap.zone.NEW[TranslateObject]; AddTranslate[xlate, e]};
      (xlate.all), (xlate.me) => RETURN;
      ENDCASE;
    e.translatePair ← pair; e.status ← active;
    e.timeStamp ← System.GetClockPulses[];
    END;

  Demon: ENTRY PROC[driver: Network, xlate: Handle] =
    BEGIN
    SendRequest: INTERNAL PROC[] =
      BEGIN
      b: Buffer.Buffer;
      request: TranslatePair;
      b ← Buffer.GetBuffer[, PupPktOps.pupBuffers, send, smallBuffer, FALSE];
      IF b # NIL THEN
	BEGIN
	b.encapsulation ←
	  [ethernet[
	    ethernetDest: System.broadcastHostNumber,
	    ethernetSource: myNSHost,
	    ethernetType: translation]];
	b.driver.length ← SIZE[DriverTypes.Encapsulation] +
	  2*SIZE[TranslateRecord] + 1;
	b.rawWords[0] ← translationRequest;
	request ← LOOPHOLE[@b.rawWords[1]];
	request↑ ← e.translatePair;
	--also send our addresses, so responder does not fault
	request ← request + SIZE[TranslateRecord];
	request↑ ← xlate.me.translatePair;
	driver.sendRawBuffer[b];
	END;
      END;  --SendRequest
    age: System.Pulses;
    e, nextE: TranslateEntry;
    pendingEntries: BOOLEAN;
    Process.SetPriority[ProcessPriorities.priorityIOHigh];

    --UNTIL ABORTED-- DO
      ENABLE ABORTED => EXIT;
      WAIT xlate.event;
      pendingEntries ← FALSE;
      FOR e ← xlate.head, nextE UNTIL e = NIL DO
	nextE ← e.nextLink;
	age ← [System.GetClockPulses[] - e.timeStamp];
	SELECT e.status FROM
	  active, zombie =>
	    BEGIN
	    SELECT TRUE FROM
	      (age <= deactivatePulses) => NULL;
	      (e = xlate.me) => e.timeStamp ← System.GetClockPulses[];
	      (e = xlate.all) => e.timeStamp ← System.GetClockPulses[];
	      ENDCASE => {RemoveTranslate[xlate, e]; CommHeap.zone.FREE[@e]};
	    END;
	  pending =>
	    BEGIN
	    SELECT TRUE FROM
	      (age < retryPulses) => NULL;
	      ((e.tries ← e.tries + 1) > retryLimit) => e.status ← zombie;
	      ENDCASE => {SendRequest[]; e.timeStamp ← System.GetClockPulses[]};
	    pendingEntries ← TRUE;
	    END;
	  new =>
	    BEGIN
	    e.status ← pending;
	    SendRequest[]; e.timeStamp ← System.GetClockPulses[];
	    pendingEntries ← TRUE;
	    END;
	  ENDCASE => IF CommFlags.doDebug THEN ERROR;
	ENDLOOP; --end of queue entries loop

      IF ~pendingEntries THEN Process.DisableTimeout[@xlate.event]
      ELSE Process.SetTimeout[@xlate.event, Process.SecondsToTicks[1]];
      ENDLOOP;

    FOR e ← xlate.head, nextE UNTIL e = NIL DO
      nextE ← e.nextLink; CommHeap.zone.FREE[@e]; e ← nextE; ENDLOOP;
    xlate.head ← xlate.me ← xlate.all ← NIL;

    END;  --Demon

  ReceiveTranslate: PROC[b: Buffer.Buffer] =
    BEGIN
    OPEN context: LOOPHOLE[b.context, PupRouterDefs.NetworkContext];
    xlate: Handle = context.protocol;
    SELECT b.rawWords[0] FROM
      translationRequest =>
        BEGIN
	a: Buffer.Buffer;
	requesterAddr: TranslatePair;
	request: TranslatePair ← LOOPHOLE[@b.rawWords[1]];
	request ← LOOPHOLE[@b.rawWords[1]];
	IF request.pupHost = xlate.me.translatePair.pupHost THEN
	  BEGIN
	  --since the requester is probably going to talk to us,
	  --add his address before we take a fault
	  requesterAddr ← request + SIZE[TranslateRecord];
	  [] ← AddTranslateRecord[xlate, requesterAddr↑];
	  request.nsAddr ← xlate.me.translatePair.nsAddr;
	  --send the ack back
	  a ← Buffer.GetBuffer[, PupPktOps.pupBuffers, send, smallBuffer, FALSE];
	  IF a = NIL THEN RETURN;  --nothing going to happen here
	  a.encapsulation ← [
	    ethernet[
	      ethernetDest: b.encapsulation.ethernetSource,
	      ethernetSource: myNSHost, ethernetType: translation]];
	  a.driver.length ← SIZE[DriverTypes.Encapsulation] +
	    SIZE[TranslateRecord] + 1;
	  a.rawWords[0] ← translationResponse;
	  LOOPHOLE[@a.rawWords[1], TranslatePair]↑ ← request↑;
	  -- UNIX checks to see if the answer is "for it"
	  -- MIN length roundup sends the extra bits
	  LOOPHOLE[@a.rawWords[1+SIZE[TranslateRecord]], TranslatePair]↑ ←
	    requesterAddr↑;
	  NARROW[b.network, Network].sendRawBuffer[a];
	  END;
	END;
      translationResponse =>
        BEGIN
	IF b.encapsulation.ethernetDest = xlate.me.translatePair.nsAddr THEN
	  [] ← AddTranslateRecord[xlate,
	    LOOPHOLE[@b.rawWords[1], TranslatePair]↑];
	END;
      ENDCASE;
    END;  --ReceiveTranslate

  -- NB: There aren't any reasonable values of PupHostID to reserve for starting/stopping
  -- So this routine is actually just a lookup.
  -- If the entry doesn't exist (or is dead) nullHostNumber is returned.
  EnumerateTranslation: PUBLIC ENTRY PROC [
    old: PupTypes.PupHostID, network: Driver.Network]
    RETURNS[new: PupTypes.PupHostID, translation: System.HostNumber] =
    BEGIN
    entry: TranslateEntry;
    translation ← System.nullHostNumber;
    new ← old;
    FOR xlate: Handle ← chain, xlate.next UNTIL xlate = NIL DO
      IF xlate.driver # network THEN LOOP;
      entry ← FindTranslate[xlate, old];
      IF entry # NIL AND entry.status = active THEN
        translation ← entry.translatePair.nsAddr;
      EXIT;
      ENDLOOP
    END;

  END...  --Pup10MBitImpl