-- Copyright (C) 1984, 1985  by Xerox Corporation. All rights reserved. 
-- TrapBadPups.mesa, HGM,  6-Aug-85 21:04:11

DIRECTORY
  Ascii USING [CR, SP],
  CmFile USING [Handle, TableError],
  Heap USING [Create],
  Put USING [Text],
  String USING [AppendChar, AppendLongDecimal, AppendNumber, AppendString],
  StringLookUp USING [noMatch, TableDesc],
  System USING [
    AdjustGreenwichMeanTime, GetGreenwichMeanTime, gmtEpoch, GreenwichMeanTime],
  Time USING [AppendCurrent],
  Token USING [Decimal, Filtered, FreeTokenString, Item, Line],
  
  Indirect USING [Close, NextValue, OpenSection],
  Mailer USING [Level, SendGVMail],
  Buffer USING [PupBuffer],
  Driver USING [Network],
  DriverTypes USING [Encapsulation],
  PupDefs USING [GetPupContentsBytes],
  ForwarderDefs USING [],
  PupRouterDefs USING [NetworkContext, RejectPupWithBadChecksum, SetPupChecksum];

TrapBadPups: MONITOR
  IMPORTS
    CmFile, Heap, Put, String, System, Time, Token, Indirect, Mailer, PupDefs,
    PupRouterDefs
  EXPORTS Buffer, ForwarderDefs
  SHARES Buffer =
  BEGIN

  -- EXPORTed TYPEs
  Network: PUBLIC TYPE = Driver.Network;

  to, cc: LONG STRING ← NIL;
  troubles: LONG STRING ← NIL;  -- Also used as from
  z: UNCOUNTED ZONE = Heap.Create[1];

  badSequenceNumber: LONG CARDINAL ← 0;
  
  recent: CARDINAL ← 0;
  clumpSize: CARDINAL ← 3;
  secondsPerPacket: CARDINAL ← 3600; -- one/hour
  lastTime: System.GreenwichMeanTime ← System.GetGreenwichMeanTime[];

  CollectInfo: PROCEDURE =
    BEGIN
    cmFile: CmFile.Handle;
    Option: TYPE = MACHINE DEPENDENT{
      troubles(0), to, cc, clumpSize, secondsPerPacket, noMatch(StringLookUp.noMatch)};
    DefinedOption: TYPE = Option [troubles..secondsPerPacket];
    CheckType: PROCEDURE [h: CmFile.Handle, table: StringLookUp.TableDesc]
      RETURNS [index: CARDINAL] = Indirect.NextValue;
    MyNextValue: PROCEDURE [
      h: CmFile.Handle,
      table: LONG DESCRIPTOR FOR ARRAY DefinedOption OF LONG STRING]
      RETURNS [index: Option] = LOOPHOLE[CheckType];
    optionTable: ARRAY DefinedOption OF LONG STRING ← [
      troubles: "Troubles"L, to: "to"L, cc: "cc"L,
      clumpSize: "Clump Size"L, secondsPerPacket: "Seconds Per Packet"L];
    cmFile ← Indirect.OpenSection["TrapBadPups"L];
    IF cmFile = NIL THEN
      BEGIN
      Message["Can't find [TrapBadPups] section in parameter file."L];
      RETURN;
      END;
    DO
      option: Option;
      option ← MyNextValue[cmFile, DESCRIPTOR[optionTable] !
        CmFile.TableError =>
          BEGIN
	  IF name[0] # '; THEN Message["Unrecognized parameter: ", name];
	  RETRY;
	  END];
      SELECT option FROM
        noMatch => EXIT;
        troubles =>
          BEGIN
	  temp: LONG STRING;
          z.FREE[@troubles];
          temp ← Token.Item[cmFile];
	  troubles ← z.NEW[StringBody[temp.length]];
	  String.AppendString[troubles, temp];
	  [] ← Token.FreeTokenString[temp];
          CheckForRegistry[troubles];
          END;
        to =>
          BEGIN
	  temp: LONG STRING ← Token.Filtered[cmFile, NIL, Token.Line, whiteSpace, FALSE];
          z.FREE[@to];
	  to ← z.NEW[StringBody[temp.length]];
	  String.AppendString[to, temp];
	  [] ← Token.FreeTokenString[temp];
          CheckForRegistry[to];
          END;
        cc =>
          BEGIN
	  temp: LONG STRING ← Token.Filtered[cmFile, NIL, Token.Line, whiteSpace, FALSE];
          z.FREE[@cc];
	  cc ← z.NEW[StringBody[temp.length]];
	  String.AppendString[cc, temp];
	  [] ← Token.FreeTokenString[temp];
          CheckForRegistry[cc];
          END;
        clumpSize =>
          BEGIN
	  clumpSize ← Token.Decimal[cmFile];
          END;
        secondsPerPacket =>
          BEGIN
	  clumpSize ← Token.Decimal[cmFile];
          END;
        ENDCASE => ERROR;
      ENDLOOP;
    Indirect.Close[cmFile];
    IF troubles = NIL THEN
      Message["Please specify somebody in case of TROUBLES"L];
    IF to = NIL THEN Message["Please specify somebody to send the message to"L];
    RETURN;
    END;

  CheckForRegistry: PROCEDURE [s: LONG STRING] =
    BEGIN
    dot: BOOLEAN ← FALSE;
    FOR i: CARDINAL IN [0..s.length) DO
      SELECT s[i] FROM
        '. => dot ← TRUE;
        ', =>
          BEGIN
          IF ~dot THEN
            BEGIN Message["Registry expected in arg: "L, s]; RETURN; END;
          dot ← FALSE;
          END;
        ENDCASE => NULL;
      ENDLOOP;
    IF ~dot THEN BEGIN Message["Registry expected in arg: "L, s]; RETURN; END;
    END;

  Message: PROCEDURE [one, two, three: LONG STRING ← NIL] =
    BEGIN
    text: STRING = [100];
    Time.AppendCurrent[text];
    String.AppendString[text, "  TrapBadPups: "L];
    String.AppendString[text, one];
    IF two # NIL THEN String.AppendString[text, two];
    IF three # NIL THEN String.AppendString[text, three];
    LogString[text];
    END;

  Filter: PROCEDURE RETURNS [ok: BOOLEAN ← FALSE] =
    BEGIN
    -- Limit to avg of 1/hour, but allow clumps of 3.
    IF lastTime # System.gmtEpoch THEN
      BEGIN
      seconds: LONG CARDINAL ← System.GetGreenwichMeanTime[] - lastTime;
      WHILE seconds > secondsPerPacket DO
        IF recent = 0 THEN
	  BEGIN
          lastTime ← System.GetGreenwichMeanTime[];
	  EXIT;
	  END;
        recent ← recent - 1;
	lastTime ← System.AdjustGreenwichMeanTime[lastTime, secondsPerPacket];
	ENDLOOP;
      END;
    IF recent < clumpSize THEN ok ← TRUE;
    IF ok THEN recent ← recent + 1;
    END;

  PrintBadPup: PUBLIC PROCEDURE [b: Buffer.PupBuffer] =
    BEGIN
    IF lastTime = System.gmtEpoch THEN lastTime ← System.GetGreenwichMeanTime[];
    IF b = NIL THEN RETURN;
    badSequenceNumber ← badSequenceNumber + 1;
    IF Filter[] THEN MailBadPup[b];
    PupRouterDefs.RejectPupWithBadChecksum[b];
    END;

  maxWords: CARDINAL = 300;
  maxBytes: CARDINAL = 500 + maxWords*9;

  MailBadPup: PUBLIC PROCEDURE [b: Buffer.PupBuffer] =
    BEGIN
    body: STRING = [maxBytes];
    size: CARDINAL ← (b.pup.pupLength - 1)/2;
    checksumLoc: LONG POINTER ← @b.pup.pupLength + size;
    words: CARDINAL ← (PupDefs.GetPupContentsBytes[b] + 1)/2;
    p: LONG POINTER;
    context: PupRouterDefs.NetworkContext = b.context;
    hack: BOOLEAN ← b.pup.pupType = echoMe OR b.pup.pupType = iAmEcho;
    Info: PROCEDURE [s: LONG STRING, level: Mailer.Level] =
      BEGIN  -- Krock because Put.Line is not Atomic
      copy: LONG STRING ← z.NEW[StringBody[s.length + 2]];
      String.AppendString[copy, s];
      LogString[copy];
      z.FREE[@copy];
      END;
    O7: PROCEDURE [n: WORD] =
      BEGIN
      temp: STRING = [10];
      String.AppendNumber[temp, n, 8];
      THROUGH [temp.length..7) DO String.AppendChar[body, Ascii.SP]; ENDLOOP;
      String.AppendNumber[body, n, 8];
      END;
    Time.AppendCurrent[body];
    String.AppendString[body, "  *****  Bad software checksum on Pup from net "L];
    String.AppendNumber[body, context.pupNetNumber, 8];
    String.AppendChar[body, Ascii.CR];
    String.AppendString[body, "Bad pup number "L];
    String.AppendLongDecimal[body, badSequenceNumber];
    String.AppendString[body, " has a checksum of "L];
    String.AppendNumber[body, checksumLoc↑, 8];
    PupRouterDefs.SetPupChecksum[b];  -- Fix it up
    String.AppendString[body, ", but it should be "L];
    String.AppendNumber[body, checksumLoc↑, 8];
    String.AppendChar[body, Ascii.CR];
    String.AppendString[body, "Driver length: "L];
    String.AppendNumber[body, b.driver.length, 10];
    String.AppendChar[body, Ascii.CR];
    String.AppendString[body, "Encap: "L];
    p ← @b.encapsulation;
    FOR i: CARDINAL IN [0..SIZE[DriverTypes.Encapsulation]) DO
      O7[(p + i)↑];
      IF hack THEN String.AppendChar[body, ' ];
      ENDLOOP;
    String.AppendChar[body, Ascii.CR];
    String.AppendString[body, "Header:"L];
    p ← @b.bufferBody;
    FOR i: CARDINAL IN [0..11 - 1) DO
      O7[(p + i)↑];
      IF hack THEN String.AppendChar[body, ' ];
      ENDLOOP;
    FOR i: CARDINAL ← 0, i + 1 UNTIL i >= MIN[words, maxWords] DO
      IF (i MOD 10) = 0 THEN
        BEGIN
        String.AppendChar[body, Ascii.CR];
        String.AppendString[body, IF i = 0 THEN "Body:  "L ELSE "       "L];
        END;
      O7[b.pup.pupWords[i]];
      IF hack THEN
        BEGIN
	IF b.pup.pupBytes[2*i] # (2*i MOD 400B)
	  OR b.pup.pupBytes[2*i + 1] # ((2*i + 1) MOD 400B) THEN
	  String.AppendChar[body, '←]
	ELSE String.AppendChar[body, ' ];
	END;
      ENDLOOP;
    String.AppendChar[body, Ascii.CR];
    Put.Text[NIL, body];
    IF troubles # NIL AND to # NIL THEN
      BEGIN
      [] ← Mailer.SendGVMail[
        "Bad Pup Checksum info"L, to, cc, body, troubles, Info];
      END;
    END;

  LogString: PROCEDURE [s: LONG STRING] =
    BEGIN
    String.AppendChar[s, '.];
    String.AppendChar[s, Ascii.CR];
    Put.Text[NIL, s];
    END;

  -- initialization
  CollectInfo[];
  END.