-- File: ArpaHostTableImpl.mesa - last edit:
-- AOF                 12-Mar-87 17:49:14
-- JAV                 25-Feb-87 14:58:51

-- Copyright (C) 1986, 1987 by Xerox Corporation. All rights reserved.

DIRECTORY
  ArpaAddressCache USING [AddEntry],
  ArpaAddressTranslation USING [StringToInternetAddress],
  ArpaHostTable USING [],
  ArpaRouter USING [GetAddress, InternetAddress, unknownInternetAddress],
  ArpaTypes USING [InternetAddress],
  Environment USING [Byte],
  ESCAlpha USING [aDAND, aDXOR],
  Heap USING [systemZone],
  Inline USING [DBITAND],
  Mopcodes USING [zESC, zIOR],
  Stream USING [Byte, EndOfStream, GetByte, Handle],
  String USING [
    AppendChar, AppendSubString, CopyToNewString, Equivalent, FreeString,
    MakeString, StringBoundsFault, SubStringDescriptor];

ArpaHostTableImpl: PROGRAM
  IMPORTS
    ArpaAddressCache, ArpaAddressTranslation, ArpaRouter, Heap, Inline,
    Stream, String 
  EXPORTS ArpaHostTable, ArpaRouter = 
  BEGIN
  
  zone: UNCOUNTED ZONE ← Heap.systemZone;
  Tab: Environment.Byte ← 11B;
  CR: Environment.Byte ← 15B;
  SP: Environment.Byte ← 40B;
  comma: Environment.Byte ← 54B;
  colon: Environment.Byte ← 72B;
  semiColon: Environment.Byte ← 73B;
  
  InternetAddress: PUBLIC TYPE = ArpaTypes.InternetAddress;
        
  SkipLine: PROCEDURE [stream: Stream.Handle] =
  BEGIN
  char: Environment.Byte ← 0;
    WHILE char # CR DO 
      char ← Stream.GetByte[stream ! Stream.EndOfStream => EXIT] ENDLOOP
  END;

  ParseHostsFile: PUBLIC PROCEDURE [fileStream: Stream.Handle]
    RETURNS [success: BOOLEAN ← TRUE] =
    BEGIN
    tokens: ARRAY [0..6) OF LONG STRING;
    EOF, charInHole: BOOLEAN ← FALSE;
    keyword: LONG STRING ← String.MakeString[zone, 256];
    backChar: Environment.Byte ← 0;
    netMask: ArpaRouter.InternetAddress ← ArpaRouter.unknownInternetAddress;
    
    GetKeyword: PROCEDURE [string: LONG STRING] =
    BEGIN
    char: Environment.Byte ← 0;
      WHILE ~EOF DO
	IF charInHole THEN {char ← backChar; charInHole ← FALSE}
	ELSE char ← Stream.GetByte[fileStream ! Stream.EndOfStream => {
	  EOF ← TRUE; CONTINUE}];
	IF char = semiColon THEN {SkipLine[fileStream]; LOOP};
	IF char = colon THEN EXIT;
	IF char = SP OR char = Tab THEN {
	  WHILE char # colon DO 
	    IF EOF THEN EXIT;
	    char ← Stream.GetByte[fileStream ! 
	      Stream.EndOfStream => {EOF ← TRUE; CONTINUE}];
	  ENDLOOP;
	  EXIT};
	IF ~EOF THEN String.AppendChar[string, LOOPHOLE[char]  !
	  String.StringBoundsFault => {
	    string ← String.CopyToNewString[s, zone, 30];
	    String.FreeString[zone, s];
	    RESUME[string]}]; 
      ENDLOOP;
    END;

  
    GetTokens: PROCEDURE [numberOfTokens: CARDINAL]
      RETURNS [EOF: BOOLEAN ← FALSE] =
      BEGIN
      i: CARDINAL ← 0;
      char: Environment.Byte ← 0;
      EOL, CRSeen: BOOLEAN ← FALSE;
	WHILE ~EOL DO
	  char ← Stream.GetByte[fileStream ! Stream.EndOfStream => {
	    EOL ← EOF ← TRUE; CONTINUE}];
	  SELECT char FROM
	    SP => IF EOL THEN CRSeen ← EOL ← FALSE;
	    Tab => NULL;
	    colon => {i ← i + 1; IF i > numberOfTokens THEN RETURN};
	    semiColon => {SkipLine[fileStream]; EOL ← TRUE; LOOP};
	    CR => CRSeen ← TRUE;
	  ENDCASE => 
	    IF CRSeen THEN {backChar ← char; charInHole ← EOL ← TRUE}
	    ELSE String.AppendChar[tokens[i], LOOPHOLE[char]  !
	      String.StringBoundsFault => {
		tokens[i] ← String.CopyToNewString[s, zone, 30];
		String.FreeString[zone, s];
		RESUME[tokens[i]]}];
	ENDLOOP;
      END;
      
      
      
    BuildNetMask: PROCEDURE [addr: ArpaRouter.InternetAddress]
      RETURNS [netMask: ArpaRouter.InternetAddress] =
      BEGIN
      InternetAddress: TYPE = MACHINE DEPENDENT RECORD[a, b: WORD];
      classAMask: InternetAddress ← [100000B, 0B];
      classBMask: InternetAddress ← [140000B, 0B];
      classCMask: InternetAddress ← [160000B, 0B];
      classA: InternetAddress ← [0B, 0B];
      classB: InternetAddress ← [100000B, 0B];
      classC: InternetAddress ← [140000B, 0B];
      Mask: InternetAddress ← [0B, 0B];
      SELECT TRUE FROM
	(Inline.DBITAND[LOOPHOLE[addr], LOOPHOLE[classAMask]] =
	  LOOPHOLE[classA, LONG UNSPECIFIED]) => Mask ← [177400B, 0B];
	(Inline.DBITAND[LOOPHOLE[addr], LOOPHOLE[classBMask]] =
	  LOOPHOLE[classB, LONG UNSPECIFIED]) => Mask ← [177777B, 0B];
	(Inline.DBITAND[LOOPHOLE[addr], LOOPHOLE[classCMask]] =
	  LOOPHOLE[classC, LONG UNSPECIFIED]) => Mask ← [177777B, 177400B];
	ENDCASE =>  --unimplemented extended addressing or unrecognizable.
	  Mask ← [0B, 0B];
      netMask ← LOOPHOLE[Mask];
      END;
      
    AddrMismatch: PROCEDURE [mask, test, target: ArpaRouter.InternetAddress]
      RETURNS[BOOLEAN] = MACHINE CODE
      {Mopcodes.zESC, ESCAlpha.aDXOR;
       Mopcodes.zESC, ESCAlpha.aDAND;
       Mopcodes.zIOR};
      
    LocalNet: PROCEDURE [addr: ArpaRouter.InternetAddress]
      RETURNS [true: BOOLEAN] =
      {RETURN[~AddrMismatch[netMask, ArpaRouter.GetAddress[], addr]]};
      
    GetAddr: PROCEDURE [addrStr: LONG STRING] RETURNS [
      addr: ArpaRouter.InternetAddress ← ArpaRouter.unknownInternetAddress] =
      BEGIN
      first: BOOLEAN ← TRUE;
      string: LONG STRING ← [256];
      commaPosition, prevComma: CARDINAL ← 0;
      tempSubString: String.SubStringDescriptor;
      firstAddr: ArpaRouter.InternetAddress ← addr;
	FOR i: CARDINAL IN [0..addrStr.length) DO
	  IF addrStr.text[i].ORD = comma OR (i = addrStr.length - 1) THEN
	    BEGIN
	    tempSubString ← [addrStr, prevComma,
	      i - prevComma + (IF i = addrStr.length - 1 THEN 1 ELSE 0)];
	    String.AppendSubString[string, @tempSubString ! 
	      String.StringBoundsFault => {
		prevComma ← i + 1; string.length ← 0; LOOP}];
	    prevComma ← i + 1;
	    addr ← ArpaAddressTranslation.StringToInternetAddress[string];
	    string.length ← 0;
	    IF first THEN {first ← FALSE; firstAddr ← addr};
	    IF LocalNet[addr] THEN RETURN[addr];
	    END;
	  ENDLOOP;
	IF firstAddr # ArpaRouter.unknownInternetAddress THEN RETURN[firstAddr];
	END;
      
      AddEntry: PROCEDURE =
      BEGIN
      commaPosition, prevComma: CARDINAL ← 0;
      string: LONG STRING ← [256];
      tempSubString: String.SubStringDescriptor;
      addr: ArpaRouter.InternetAddress ← ArpaRouter.unknownInternetAddress;
	netMask ← BuildNetMask[ArpaRouter.GetAddress[]];
	addr ← GetAddr[tokens[0]];
	FOR i: CARDINAL IN [0..tokens[1].length) DO
	  IF tokens[1].text[i].ORD = comma OR (i = tokens[1].length - 1) THEN
	    BEGIN
	    tempSubString ← [tokens[1], prevComma,
	      i - prevComma + (IF i = tokens[1].length - 1 THEN 1 ELSE 0)];
	    String.AppendSubString[string, @tempSubString ! 
	      String.StringBoundsFault => {
		prevComma ← i + 1; string.length ← 0; LOOP}];
	    prevComma ← i + 1;
	    ArpaAddressCache.AddEntry[string, addr];
	    string.length ← 0;
	    END;
	ENDLOOP;
      END;

    FOR index: CARDINAL IN [0..6) DO
      tokens[index] ← String.MakeString[zone, 50]; ENDLOOP;
    WHILE ~EOF DO
      GetKeyword[keyword];
      SELECT TRUE FROM
	String.Equivalent[keyword, "NET"L] => {
	  EOF ← GetTokens[2]; SkipLine[fileStream]};
	String.Equivalent[keyword, "GATEWAY"L] => {
	  EOF ← GetTokens[5]; SkipLine[fileStream]};
	String.Equivalent[keyword, "HOST"L] => {
	  EOF ← GetTokens[5]; AddEntry[]};
	<<String.Equivalent[keyword, "DOMAIN"L] => 
	  SkipLine[fileStream];>>
        ENDCASE => SkipLine[fileStream];
      keyword.length ← 0;
      FOR index: CARDINAL IN [0..6) DO
	tokens[index].length ← 0; ENDLOOP;
      ENDLOOP;
    FOR index: CARDINAL IN [0..6) DO
      String.FreeString[zone, tokens[index]]; ENDLOOP;
  END;
  
END.