-- Copyright (C) 1984  by Xerox Corporation. All rights reserved. 
-- SetGMTUsingEthernet.mesa, HGM,  8-Jan-84  1:01:18

DIRECTORY
  EthernetFace USING [
    AddCleanup, ControlBlock, controlBlockSize, DeviceHandle, GetNextDevice,
    GetPacketLength, GetStatus, GlobalStatePtr, globalStateSize, nullDeviceHandle,
    QueueInput, QueueOutput, RemoveCleanup, TurnOff, TurnOn],
  Environment USING [Base, Byte, bytesPerWord, Long],
  HostNumbers USING [broadcastHostNumber, HostNumber],
  Inline USING [BITOR],
  NSConstants USING [timeServerSocket],
  PilotMP USING [cTimeNotAvailable],
  ProcessorFace USING [
    GetGreenwichMeanTime, GreenwichMeanTime, gmtEpoch, ProcessorID, processorID,
    SetGreenwichMeanTime, SetMP, mp],
  ResidentHeap USING [first64K, MakeNode],
  TemporarySetGMT USING [TimeZoneDirection],
  Zone USING [Status];

SetGMTUsingEthernet: PROGRAM
  IMPORTS EthernetFace, Inline, ProcessorFace, ResidentHeap
  EXPORTS TemporarySetGMT
  SHARES ProcessorFace =
  BEGIN OPEN ProcessorFace;

  SetGMT: PUBLIC PROC [
    earliestAcceptableTime, latestAcceptableTime: ProcessorFace.GreenwichMeanTime]
    RETURNS [
      paramsValid: BOOLEAN, zoneDirection: TemporarySetGMT.TimeZoneDirection,
      zone: [0..12], zoneMinutes: [0..59], beginDST: [0..366], endDST: [0..366]] =
    BEGIN
    teMP: CARDINAL = ProcessorFace.mp;
    ProcessorFace.SetMP[PilotMP.cTimeNotAvailable];
    DO
      networkTimeFound: BOOLEAN;
      gmt: ProcessorFace.GreenwichMeanTime;
      [networkTimeFound, gmt, zoneDirection, zone, zoneMinutes, beginDST, endDST]
        ← GetNetworkGMT[];
      IF networkTimeFound THEN {
        ProcessorFace.SetGreenwichMeanTime[gmt];
        ProcessorFace.SetMP[teMP];
        RETURN[TRUE, zoneDirection, zone, zoneMinutes, beginDST, endDST]};
      IF ((gmt ← ProcessorFace.GetGreenwichMeanTime[]) # ProcessorFace.gmtEpoch)
        AND
          ((PFSecondsSinceEpoch[gmt] > PFSecondsSinceEpoch[
             earliestAcceptableTime])
            OR (earliestAcceptableTime = ProcessorFace.gmtEpoch))
        AND
          ((PFSecondsSinceEpoch[gmt] <= PFSecondsSinceEpoch[latestAcceptableTime])
            OR (latestAcceptableTime = ProcessorFace.gmtEpoch)) THEN {
        SetMP[teMP]; RETURN[FALSE, , , , , ]};
      ENDLOOP;
    END;

  -- D'Lions run off the wall frequency, so their clock needs no fixing up once set
  FixupClock: PUBLIC PROCEDURE = {};

  Byte: TYPE = Environment.Byte;

  -- Following started as a copy of GTime.mesa
  -- as edited by Johnsson on July 12, 1979  8:31 AM
  NetTime: TYPE = MACHINE DEPENDENT RECORD [
    -- this is what comes in a TimeReply Packet
    timeHigh(0), timeLow(1): CARDINAL,
    direction(2): TemporarySetGMT.TimeZoneDirection,
    zone(3): [0..127],
    zoneMinutes(4): [0..255],
    beginDST(5), endDST(6): CARDINAL,
    errorAccurate(7): BOOLEAN,
    errorLow(8), errorHigh(9): CARDINAL];

  packetTypeOis: CARDINAL = 3000B;
  protocolTypeReqRep: CARDINAL = 4;  -- NSTypes.PacketType[packetExchange]
  packetExchangeClient: CARDINAL = 1;
  timeRequest: CARDINAL = 1;
  timeReply: CARDINAL = 2;
  tsProtocolVersion: CARDINAL = 2;

  Address: TYPE = MACHINE DEPENDENT RECORD [
    net: RECORD [a, b: WORD] ← [0, 0],
    host: HostNumbers.HostNumber,
    socket: WORD];

  Encapsulation: TYPE = MACHINE DEPENDENT RECORD [
    eDest, eSource: HostNumbers.HostNumber, packetType: CARDINAL];

  Header: TYPE = MACHINE DEPENDENT RECORD [
    checksum, length: CARDINAL,
    transportControl, oisType: Byte,
    dest, source: Address,
    id1, id2: WORD,
    clientType: WORD,
    tsVersion, tsPacketType: WORD];

  Request: TYPE = MACHINE DEPENDENT RECORD [encap: Encapsulation, head: Header];

  Reply: TYPE = MACHINE DEPENDENT RECORD [
    encap: Encapsulation, head: Header, data: NetTime];

  iocb: EthernetFace.ControlBlock;

  DriverNeedsSomeGlobalStorage: PUBLIC ERROR = CODE;
  ResidentHeapError: ERROR = CODE;

  GetNetworkGMT: PUBLIC PROC
    RETURNS [
      networkTimeFound: BOOLEAN, timeFromNetwork: ProcessorFace.GreenwichMeanTime,
      zoneDirection: TemporarySetGMT.TimeZoneDirection, zone: [0..12],
      zoneMinutes: [0..59], beginDST: [0..366], endDST: [0..366]] =
    BEGIN OPEN EthernetFace;
    minWordsPerEthernetPacket: CARDINAL = (64/2) - 2;
    --*** Should move to DriverTypes
    ether: DeviceHandle = GetNextDevice[nullDeviceHandle];
    global: GlobalStatePtr;  -- Allocate space when needed
    inputSize: CARDINAL = MAX[SIZE[Reply], 32] + slop;
    slop: CARDINAL = 12;
    in: ARRAY [0..inputSize + 3) OF CARDINAL;
    out: ARRAY [0..SIZE[Request] + 3) OF CARDINAL;
    request: POINTER TO Request = Inline.BITOR[@out, 3];
    reply: POINTER TO Reply = Inline.BITOR[@in, 3];
    id1: WORD = 1234B;
    id2: WORD = 56710B;
    socket: WORD = 111213B;

    IF ~EthernetExists[] THEN RETURN[FALSE, ProcessorFace.gmtEpoch, , , , , ];

    IF globalStateSize # 0 THEN ERROR DriverNeedsSomeGlobalStorage;

    request↑ ← [
      [
      eDest: HostNumbers.broadcastHostNumber, eSource: ProcessorFace.processorID,
      packetType: packetTypeOis], [
      checksum: 177777B, length: Environment.bytesPerWord*SIZE[Header],
      transportControl: 0, oisType: protocolTypeReqRep,
      dest: [
      host: HostNumbers.broadcastHostNumber,
      socket: LOOPHOLE[NSConstants.timeServerSocket]],
      source: [host: ProcessorFace.processorID, socket: socket], id1: id1,
      id2: id2, clientType: packetExchangeClient, tsVersion: tsProtocolVersion,
      tsPacketType: timeRequest]];

    AddCleanup[ether];
    THROUGH [0..3) DO
      THROUGH [0..3) DO
        TurnOff[ether];
        TurnOn[ether, LOOPHOLE[ProcessorFace.processorID], 0, 0, global];
        reply.encap.packetType ← 0;
        QueueOutput[
          ether, request, MAX[SIZE[Request], minWordsPerEthernetPacket], iocb];
        THROUGH [0..LAST[CARDINAL]/2) DO
          IF GetStatus[iocb] # pending THEN
            BEGIN
            IF GetStatus[iocb] = ok AND GetPacketLength[iocb] >= SIZE[Reply]
              AND reply.encap.packetType = packetTypeOis
              AND reply.head.dest.host = ProcessorFace.processorID
              AND reply.head.dest.socket = socket
              AND reply.head.source.socket =
                LOOPHOLE[NSConstants.timeServerSocket, WORD]
              AND reply.head.id1 = id1 AND reply.head.id2 = id2
              AND reply.head.clientType = packetExchangeClient
              AND reply.head.tsVersion = tsProtocolVersion
              AND reply.head.tsPacketType = timeReply THEN {
              nt: POINTER TO NetTime = @reply.data;
              LOOPHOLE[timeFromNetwork, Environment.Long] ← [
                num[lowbits: nt.timeLow, highbits: nt.timeHigh]];
              TurnOff[ether];
              RemoveCleanup[ether];
              RETURN[
                TRUE, timeFromNetwork, nt.direction, nt.zone, nt.zoneMinutes,
                  nt.beginDST, nt.endDST]};
            -- Start (another) read either because the send just
            -- finshed or we didn't like the
            -- packet that just arrived.
            QueueInput[ether, reply, inputSize, iocb];
            END;
          ENDLOOP;
        ENDLOOP;
      ENDLOOP;
    TurnOff[ether];
    RemoveCleanup[ether];
    timeFromNetwork ← ProcessorFace.gmtEpoch;
    RETURN[FALSE, timeFromNetwork, , , , , ]
    END;

  PFSecondsSinceEpoch: PROCEDURE [gmt: ProcessorFace.GreenwichMeanTime]
    RETURNS [LONG CARDINAL] = INLINE {RETURN[gmt - ProcessorFace.gmtEpoch]};

  EthernetExists: PROCEDURE RETURNS [BOOLEAN] =
    BEGIN OPEN EthernetFace;
    RETURN[GetNextDevice[nullDeviceHandle] # nullDeviceHandle];
    END;


  -- Initialization

  BEGIN
  iocbRelative: Environment.Base RELATIVE POINTER;
  status: Zone.Status;
  [iocbRelative, status] ← ResidentHeap.MakeNode[
    EthernetFace.controlBlockSize, a4];
  IF status # okay THEN ERROR ResidentHeapError;
  iocb ← LOOPHOLE[@ResidentHeap.first64K[iocbRelative]];
  END;

  END....