-- File: TimeServerImpl.mesa - last edit:
-- KAM                 15-Apr-85 18:18:40
-- RKJ                  7-Apr-83 10:48:23

-- Copyright (C) 1983, 1984, 1985 by Xerox Corporation. All rights reserved.

DIRECTORY
  Buffer USING [NSBuffer],
  NSConstants USING [timeServerSocket],
  NSTimeServer USING [],
  NSTypes USING [ExchangeClientType, WaitTime],
  PacketExchange USING [ExchangeClientType],
  Process USING [Abort, Detach, priorityBackground, SetPriority],
  Router USING [FindMyHostID],
  RoutingTable USING [NetworkContext],
  Socket USING [
    ChannelHandle, Create, Delete, GetPacket, GetPacketBytes, PutPacket,
    ReturnBuffer, SetPacketWords, SetWaitTime, TimeOut, WaitTime],
  SpecialSystem USING [SetBackTooFar],
  System USING [
    broadcastHostNumber, GetLocalTimeParameters, GreenwichMeanTime,
    LocalTimeParameters, LocalTimeParametersUnknown, NetworkAddress,
    nullNetworkAddress, nullNetworkNumber],
  TimeServerClock USING [
    AdjustClock, Error, GetParameters, Invalid, Rate, Read, ResetClock, ResyncNow,
    SetParameters, Start, Stop, undefined],
  TimeServerFormat USING [
    GMTToWire, LongToWire, OldTimeResponse, TSPacket, Version, WireToLong],
  TimeServerLog USING [LogStart, LogStop, StartLogging, StopLogging],
  TimeServerOps USING [];


TimeServerImpl: MONITOR
  IMPORTS
    Process, Router, Socket, SpecialSystem, System, TimeServerClock, 
    TimeServerFormat, TimeServerLog
  EXPORTS NSTimeServer, TimeServerOps =
  BEGIN
  
  TimeServerState: TYPE = {uninitialized, active, stopped};
  serverState: TimeServerState ← uninitialized;
  serverResetting: BOOLEAN ← FALSE;
  
  numberRequests: LONG CARDINAL ← 0;
  numberOldRequests: LONG CARDINAL ← 0;
  agent: System.NetworkAddress ← System.nullNetworkAddress;
  
  serverExists: BOOLEAN ← FALSE;
  timeServer: PROCESS;
  
  version: WORD = TimeServerFormat.Version;
  
  --  PUBLIC procedures

  CreateServer: PUBLIC ENTRY PROCEDURE =
    BEGIN
    IF serverExists THEN RETURN;
    serverExists ← TRUE;
    serverState ← active;
    numberRequests ← 0;
    agent ← [
      net: System.nullNetworkNumber, host: Router.FindMyHostID[],
      socket: NSConstants.timeServerSocket];
    TimeServerLog.StartLogging[];
    TimeServerClock.Start[];
    timeServer ← FORK TimeServer[];
    END;
  
  InsertTime: PUBLIC ENTRY PROCEDURE [
    difference: LONG INTEGER, validError: BOOLEAN, error: LONG CARDINAL] = {
    TimeServerClock.AdjustClock[
      difference, IF validError THEN error ELSE TimeServerClock.undefined,
      validError ! SpecialSystem.SetBackTooFar => {WedgeServer[]; CONTINUE}]};
    
  DeleteServer: PUBLIC ENTRY PROCEDURE =
    BEGIN
    IF ~serverExists THEN RETURN;
    serverExists ← FALSE;
    IF serverState = active THEN TimeServerClock.Stop[];
    serverState ← uninitialized;
    Process.Abort[timeServer];
    JOIN timeServer;
    TimeServerLog.StopLogging[];
    END;

  GetClockDrift: PUBLIC ENTRY PROCEDURE
    RETURNS [base: LONG CARDINAL, drift: CARDINAL] =
    BEGIN
    rate: TimeServerClock.Rate = TimeServerClock.GetParameters[].drift;
    RETURN[rate.base, rate.amount];
    END;

  SetClockDrift: PUBLIC ENTRY PROCEDURE [base: LONG CARDINAL, drift: CARDINAL] =
    BEGIN
    wobble: TimeServerClock.Rate;
    resetPeriod: CARDINAL;
    [wobble: wobble, resetPeriod: resetPeriod] ← TimeServerClock.GetParameters[];
    TimeServerClock.SetParameters[[base, drift], wobble, resetPeriod];
    END;

  GetClockError: PUBLIC ENTRY PROCEDURE
    RETURNS [known: BOOLEAN, error: LONG CARDINAL] = {
    [accurate: known, error: error] ← TimeServerClock.Read[
      ! TimeServerClock.Invalid => {WedgeServer[]; RESUME}]};
  
  GetOldTimeRequests: PUBLIC ENTRY PROCEDURE
    RETURNS [LONG CARDINAL] = {RETURN [numberOldRequests]};
    
  GetTimeRequests: PUBLIC ENTRY PROCEDURE
    RETURNS [LONG CARDINAL] = {RETURN [numberRequests]};

  SetClockError: PUBLIC ENTRY PROCEDURE [error: LONG CARDINAL] = {
    TimeServerClock.AdjustClock[0, error, FALSE]};
  
  SetClockWobble: PUBLIC ENTRY PROCEDURE [base: LONG CARDINAL, drift: CARDINAL] =
    BEGIN
    rate: TimeServerClock.Rate;
    resetPeriod: CARDINAL;
    [drift: rate, resetPeriod: resetPeriod]  ← TimeServerClock.GetParameters[];
    TimeServerClock.SetParameters[rate, [base, drift], resetPeriod];
    END;

  Start: PUBLIC ENTRY PROCEDURE = {serverState ← active; TimeServerClock.Start[]};
  
  StartReset: PUBLIC ENTRY PROCEDURE [from: System.NetworkAddress] = {
    Reset[from]};

  Status: PUBLIC ENTRY PROCEDURE
    RETURNS [status: statisticResponse TimeServerFormat.TSPacket] = {
    ConstructStatisticsReply[@status]};

  Stop: PUBLIC ENTRY PROCEDURE = {serverState ← stopped; TimeServerClock.Stop[]};

  --  Private ENTRY procedures

  HandleInternalTimeRequest: ENTRY PROCEDURE [
    request: LONG POINTER TO internalTimeRequest TimeServerFormat.TSPacket,
    b: Buffer.NSBuffer, cH: Socket.ChannelHandle] RETURNS [replied: BOOLEAN] =
    BEGIN
    reply: LONG POINTER TO internalTimeResponse TimeServerFormat.TSPacket ← 
      LOOPHOLE[request];
    time: System.GreenwichMeanTime;
    error: TimeServerClock.Error;
    timeVersion: LONG CARDINAL;
    accurate: BOOLEAN;
    IF request.version # version THEN RETURN[FALSE];
    [time, error, accurate, timeVersion] ← TimeServerClock.Read[
      ! TimeServerClock.Invalid => {WedgeServer[]; RESUME}];
    IF Broadcast[b.ns.destination]
      AND serverState # active THEN RETURN[FALSE];
    Socket.SetPacketWords[b, 3 + SIZE[
      internalTimeResponse TimeServerFormat.TSPacket]];
    reply↑ ← [version: version, tsBody: internalTimeResponse[
      time: TimeServerFormat.GMTToWire[time],
      absoluteError: TimeServerFormat.LongToWire[
        IF accurate THEN error ELSE TimeServerClock.undefined],
      timeVersion: TimeServerFormat.LongToWire[timeVersion]]];
    b.ns.destination ← FillNetNumber[b.ns.source, b.context];
    Socket.PutPacket[cH, b];
    numberRequests ← numberRequests + 1;
    RETURN[TRUE];
    END;

  HandleNoteNewVersion: ENTRY PROCEDURE [
    note: LONG POINTER TO noteNewVersion TimeServerFormat.TSPacket]
    RETURNS [false: BOOLEAN] =
    BEGIN
    tsVersion: LONG CARDINAL = TimeServerClock.Read[
      ! TimeServerClock.Invalid => {WedgeServer[]; RESUME}].version;
    IF serverState # active THEN RETURN[FALSE];
    IF LOOPHOLE[
      TimeServerFormat.WireToLong[note.timeVersion], LONG CARDINAL] > tsVersion
      THEN TimeServerClock.ResyncNow[];
    RETURN[FALSE];
    END;

  HandleResetRequest: ENTRY PROCEDURE [
    request: LONG POINTER TO resetRequest TimeServerFormat.TSPacket,
    b: Buffer.NSBuffer, cH: Socket.ChannelHandle] RETURNS [accepted: BOOLEAN] =
    BEGIN
    reply: LONG POINTER TO resetResponse TimeServerFormat.TSPacket ← 
      LOOPHOLE[request];
    source: System.NetworkAddress;
    IF Broadcast[b.ns.destination]
      OR request.version # version THEN RETURN[FALSE];
    source ← request.timeSource;
    source.socket ← NSConstants.timeServerSocket;
    agent ← b.ns.destination ← FillNetNumber[b.ns.source, b.context];
    reply↑ ← [version: version, tsBody: resetResponse[]];
    Socket.SetPacketWords[b, 3 + SIZE[resetResponse TimeServerFormat.TSPacket]];
    Socket.PutPacket[cH, b];
    Reset[source];
    RETURN[TRUE];
    END;

  HandleStartRequest: ENTRY PROCEDURE [
    request: LONG POINTER TO startRequest TimeServerFormat.TSPacket,
    b: Buffer.NSBuffer, cH: Socket.ChannelHandle] RETURNS [replied: BOOLEAN] =
    BEGIN
    reply: LONG POINTER TO startResponse TimeServerFormat.TSPacket ← 
      LOOPHOLE[request];
    IF Broadcast[b.ns.destination] 
      OR request.version # version THEN RETURN[FALSE];
    IF serverState # active THEN {serverState ← active; TimeServerClock.Start[]};
    agent ← b.ns.destination ← FillNetNumber[b.ns.source, b.context];
    reply↑ ← [version: version, tsBody: startResponse[]];
    Socket.SetPacketWords[b, 3 + SIZE[startResponse TimeServerFormat.TSPacket]];
    Socket.PutPacket[cH, b];
    TimeServerLog.LogStart[agent];
    RETURN[TRUE]
    END;

  HandleStatisticsRequest: ENTRY PROCEDURE [
    request: LONG POINTER TO statisticRequest TimeServerFormat.TSPacket,
    b: Buffer.NSBuffer, cH: Socket.ChannelHandle] RETURNS [replied: BOOLEAN] =
    BEGIN
    reply: LONG POINTER TO statisticResponse TimeServerFormat.TSPacket ← 
      LOOPHOLE[request];
    IF request.version # version THEN RETURN[FALSE];
    Socket.SetPacketWords[
      b, 3 + SIZE[statisticResponse TimeServerFormat.TSPacket]];
    ConstructStatisticsReply[reply];
    b.ns.destination ← FillNetNumber[b.ns.source, b.context];
    Socket.PutPacket[cH, b];
    RETURN[TRUE];
    END;

  HandleStopRequest: ENTRY PROCEDURE [
    request: LONG POINTER TO stopRequest TimeServerFormat.TSPacket,
    b: Buffer.NSBuffer, cH: Socket.ChannelHandle] RETURNS [replied: BOOLEAN] =
    BEGIN
    reply: LONG POINTER TO stopResponse TimeServerFormat.TSPacket ← 
      LOOPHOLE[request];
    IF Broadcast[b.ns.destination]
      OR request.version # version THEN RETURN[FALSE];
    IF serverState = active THEN {serverState ← stopped; TimeServerClock.Stop[]};
    agent ← b.ns.destination ← FillNetNumber[b.ns.source, b.context];
    reply↑ ← [version: version, tsBody: stopResponse[]];
    Socket.SetPacketWords[b, 3 + SIZE[stopResponse TimeServerFormat.TSPacket]];
    Socket.PutPacket[cH, b];
    TimeServerLog.LogStop[agent];
    RETURN[TRUE]
    END;

  HandleTimeRequest: ENTRY PROCEDURE [
    request: LONG POINTER TO timeRequest TimeServerFormat.TSPacket,
    b: Buffer.NSBuffer, cH: Socket.ChannelHandle] RETURNS [replied: BOOLEAN] =
    BEGIN
    reply: LONG POINTER TO timeResponse TimeServerFormat.TSPacket ← 
      LOOPHOLE[request];
    parms: System.LocalTimeParameters;
    time: System.GreenwichMeanTime;
    error: TimeServerClock.Error;
    accurate: BOOLEAN;
    IF request.version # version THEN RETURN[FALSE];
    [time, error, accurate] ← TimeServerClock.Read[
      ! TimeServerClock.Invalid => {WedgeServer[]; RESUME}];
    IF Broadcast[b.ns.destination]
      AND serverState # active THEN RETURN[FALSE];
    parms ← System.GetLocalTimeParameters[
      ! System.LocalTimeParametersUnknown => GOTO CantReply];
    Socket.SetPacketWords[b, 3 + SIZE[timeResponse TimeServerFormat.TSPacket]];
    reply↑ ← [version: version, tsBody: timeResponse[
      time: TimeServerFormat.GMTToWire[time], zoneS: parms.direction,
      zoneH: parms.zone, zoneM: parms.zoneMinutes, beginDST: parms.beginDST,
      endDST: parms.endDST, errorAccurate: accurate,
      absoluteError: TimeServerFormat.LongToWire[error]]];
    b.ns.destination ← FillNetNumber[b.ns.source, b.context];
    Socket.PutPacket[cH, b];
    numberRequests ← numberRequests + 1;
    RETURN[TRUE];
    EXITS CantReply => RETURN[FALSE];
    END;

  OldTimeReply: ENTRY PROCEDURE [b: Buffer.NSBuffer, cH: Socket.ChannelHandle]
    RETURNS [replied: BOOLEAN] =
    BEGIN
    parms: System.LocalTimeParameters;
    reply: LONG POINTER TO TimeServerFormat.OldTimeResponse =
      LOOPHOLE[@b.ns.exchangeBody];
    time: System.GreenwichMeanTime ← TimeServerClock.Read[
      ! TimeServerClock.Invalid => {WedgeServer[]; RESUME}].time;
    IF serverState # active THEN GOTO CantReply;
    Socket.SetPacketWords[b, 3 + SIZE[TimeServerFormat.OldTimeResponse]];
    b.ns.nsWords[2] ← 2;
    parms ← System.GetLocalTimeParameters[
      ! System.LocalTimeParametersUnknown => GOTO CantReply];
    reply↑ ← [
      time: TimeServerFormat.GMTToWire[time], zoneS: parms.direction,
      zoneH: parms.zone, zoneM: parms.zoneMinutes, beginDST: parms.beginDST,
      endDST: parms.endDST, spare: ];
    b.ns.destination ← FillNetNumber[b.ns.source, b.context];
    Socket.PutPacket[cH, b];
    numberOldRequests ← numberOldRequests + 1;
    RETURN[TRUE];
    EXITS CantReply => RETURN[FALSE];
    END;

  --  Internal procedures

  Broadcast: INTERNAL PROC [address: System.NetworkAddress]
    RETURNS [BOOLEAN] = INLINE {
    RETURN[address.host = System.broadcastHostNumber]};

  ConstructStatisticsReply: INTERNAL PROC [
    target: LONG POINTER TO statisticResponse TimeServerFormat.TSPacket] =
    BEGIN
    timeLastReset: System.GreenwichMeanTime;
    sourceLastReset: System.NetworkAddress;
    changeLastReset: LONG INTEGER;
    [timeLastReset: timeLastReset, sourceLastReset: sourceLastReset,
      changeLastReset: changeLastReset] ← TimeServerClock.GetParameters[];
    target↑ ← [
      version: version, tsBody: statisticResponse[
        numberRequests: TimeServerFormat.LongToWire[
	  numberRequests + numberOldRequests],
	active: serverState = active,
	resetting: serverResetting, source: sourceLastReset,
        timeSet: TimeServerFormat.GMTToWire[timeLastReset],
	lastChange: TimeServerFormat.LongToWire[changeLastReset]]];
    END;

  FillNetNumber: INTERNAL PROC [
    address: System.NetworkAddress, context: RoutingTable.NetworkContext]
    RETURNS [System.NetworkAddress] =
    -- Make sure the packet is routed back on the same net it came in on
    --   this is important since some guys (notably 860s) do not know
    --   their net number, so they reply with unknownNetID.
    -- If network is NIL, this request was generated locally, so the existing
    --    net in address should do fine.
    BEGIN
    IF context # NIL THEN
      IF address.net = System.nullNetworkNumber
      THEN address.net ← context.netNumber;
    RETURN[address]
    END;
  
  Reset: INTERNAL PROCEDURE [from: System.NetworkAddress] = {
    Process.Detach[FORK ResetProcess[from]]};
  
  ResetProcess: PROCEDURE [from: System.NetworkAddress] =
    BEGIN
    serverResetting ← TRUE;
    [] ←  TimeServerClock.ResetClock[from
     ! SpecialSystem.SetBackTooFar => {WedgeServerEntry[]; CONTINUE}];
    serverResetting ← FALSE;
    END;
      
  WedgeServer: INTERNAL PROCEDURE = {serverState ← stopped};
  WedgeServerEntry: ENTRY PROCEDURE = {WedgeServer[]};

  --  Process

  TimeServer: -- EXTERNAL -- PROCEDURE =
    BEGIN
    b: Buffer.NSBuffer;
    bufferUsed: BOOLEAN;
    cH: Socket.ChannelHandle ← Socket.Create[NSConstants.timeServerSocket];
    Socket.SetWaitTime[cH, LAST[Socket.WaitTime]];
    Process.SetPriority[Process.priorityBackground];
    DO ENABLE ABORTED => GOTO GoAway;
      b ← Socket.GetPacket[cH ! Socket.TimeOut => RETRY];
      IF b.ns.exchangeType = PacketExchange.ExchangeClientType[timeService] THEN
        BEGIN
	p: LONG POINTER TO TimeServerFormat.TSPacket =
	  LOOPHOLE[@b.ns.exchangeBody];
	bufferUsed ← SELECT TRUE FROM
	  Socket.GetPacketBytes[b] <= 2*3 => OldTimeReply[b, cH],
	  
	  << Pilot 10 SetGMTUsingEthernet uses a packet type of oldTimeServer
	  rather than packetExchange, which is a protocol violation. In Pilot 12,
	  we won't have to support Pilot 10, and the following line can be 
	  reinstated (see AR 5417).
	  b.ns.packetType # packetExchange => FALSE, -- should never happen --
	  >>
	  
	  ENDCASE => WITH r: p↑ SELECT FROM
	    timeRequest => HandleTimeRequest[@r, b, cH],
	    statisticRequest => HandleStatisticsRequest[@r, b, cH],
	    startRequest => HandleStartRequest[@r, b, cH],
	    stopRequest => HandleStopRequest[@r, b, cH],
	    resetRequest => HandleResetRequest[@r, b, cH],
	    internalTimeRequest => HandleInternalTimeRequest[@r, b, cH],
	    noteNewVersion => HandleNoteNewVersion[@r],
	    ENDCASE => FALSE;
	IF ~bufferUsed THEN Socket.ReturnBuffer[b];
	END
      ELSE Socket.ReturnBuffer[b];
      REPEAT GoAway => Socket.Delete[cH];
      ENDLOOP;
    END;

  -- Start trap creates a server...
  CreateServer[];
  
  END.