-- File: TimeServerClockImpl.mesa - last edit: -- HGM 25-Jun-85 2:47:16 - version to LogClockChange -- KAM 15-Apr-85 18:02:42 -- Tim Diebert 6-Aug-87 5:34:46 added TimeServerClockExtras for home WS. -- Copyright (C) 1984, 1985 by Xerox Corporation. All rights reserved. DIRECTORY Buffer USING [NSBuffer], Inline USING [DIVMOD, LowHalf], NSConstants USING [timeServerSocket], PacketExchange USING [ExchangeClientType], Process USING [ Abort, Pause, priorityBackground, SecondsToTicks, SetPriority, Ticks], Router USING [ endEnumeration, EnumerateRoutingTable, FillRoutingTable, FindMyHostID, GetDelayToNet, infinity, NoTableEntryForNet, startEnumeration], Socket USING [ BroadcastPacketToAllConnectedNets, ChannelHandle, Create, Delete, GetPacket, GetSendBuffer, PutPacket, ReturnBuffer, SetPacketWords, SetWaitTime, TimeOut], SpecialSystem USING [AdjustClock, clockSlipping, SetBackTooFar, TimeLastSet], System USING [ AdjustGreenwichMeanTime, broadcastHostNumber, GetClockPulses, GetGreenwichMeanTime, GreenwichMeanTime, HostNumber, Microseconds, NetworkAddress, NetworkNumber, nullNetworkAddress, nullNetworkNumber, nullSocketNumber, Pulses, PulsesToMicroseconds, SecondsSinceEpoch], TimeServerClock USING [Error, Rate, undefined], TimeServerClockExtras USING [], TimeServerFormat USING [LongToWire, TSPacket, Version, WireToGMT, WireToLong], TimeServerLog USING [LogClockChange, LogInconsistancy]; TimeServerClockImpl: MONITOR IMPORTS Inline, Process, Router, Socket, SpecialSystem, System, TimeServerFormat, TimeServerLog EXPORTS TimeServerClock, TimeServerClockExtras = BEGIN -- Initial values to these variables are assigned by the procedure Init tsError: TimeServerClock.Error; tsDrift, tsWobble: TimeServerClock.Rate; tsResetPeriod: CARDINAL; clockInvalid: BOOLEAN ← FALSE; -- set if SpecialSystem.SetBackTooFar is raised ignoringServer: BOOLEAN ← FALSE; ignoringServerAddress: System.HostNumber; tsSourceLastReset: System.NetworkAddress ← System.nullNetworkAddress; tsLastChange: LONG INTEGER ← 0; tsVersion: LONG CARDINAL ← 0; started: BOOLEAN ← FALSE; surveyorProcess: PROCESS; lastID: LONG CARDINAL ← 1; oneSecond: Process.Ticks = Process.SecondsToTicks[1]; triesPerNetwork: CARDINAL = 3; resetTries: CARDINAL = 10; Inconsistent: PUBLIC ERROR = CODE; Invalid: PUBLIC SIGNAL = CODE; -- public procedures AdjustClock: PUBLIC PROCEDURE [ amount: LONG INTEGER, error: TimeServerClock.Error, newVersion: BOOLEAN] = BEGIN UpVersion: ENTRY PROCEDURE [update: BOOLEAN] RETURNS [LONG CARDINAL] = { RETURN[IF update THEN SUCC[tsVersion] ELSE tsVersion]}; AdjustClockEntry[ amount, error, UpVersion[newVersion], 0, System.nullNetworkAddress, TRUE]; END; GetParameters: PUBLIC ENTRY PROCEDURE RETURNS [ drift, wobble: TimeServerClock.Rate, resetPeriod: CARDINAL, timeLastReset: System.GreenwichMeanTime, sourceLastReset: System.NetworkAddress, changeLastReset: LONG INTEGER] = { RETURN[ tsDrift, tsWobble, tsResetPeriod, SpecialSystem.TimeLastSet[], tsSourceLastReset, tsLastChange]}; Read: PUBLIC ENTRY PROCEDURE RETURNS [ time: System.GreenwichMeanTime, error: TimeServerClock.Error, accurate: BOOLEAN, version: LONG CARDINAL] = BEGIN IF clockInvalid THEN SIGNAL Invalid; error ← ErrorInternal[]; RETURN[ System.GetGreenwichMeanTime[], error, (error # TimeServerClock.undefined) AND NOT SpecialSystem.clockSlipping, tsVersion]; END; ResetClock: PUBLIC PROCEDURE [from: System.NetworkAddress] RETURNS [reset: BOOLEAN ← FALSE] = BEGIN cH: Socket.ChannelHandle ← Socket.Create[System.nullSocketNumber]; b: Buffer.NSBuffer ← NIL; Socket.SetWaitTime[cH, WaitForHops[Router.GetDelayToNet[ from.net ! Router.NoTableEntryForNet => { Socket.Delete[cH]; GOTO NoReset}]]]; IF from.host # System.broadcastHostNumber THEN BEGIN ENABLE UNWIND => IF b # NIL THEN Socket.ReturnBuffer[b]; time: LONG INTEGER; error, delay, version: LONG CARDINAL; sendMsec: System.Microseconds; MaxVersion: ENTRY PROCEDURE RETURNS [LONG CARDINAL] = { RETURN[MAX[tsVersion, version]]}; FOR tries: CARDINAL IN [0.. 2*resetTries) UNTIL reset DO sendMsec ← System.PulsesToMicroseconds[System.GetClockPulses[]]; SendTimeRequest[cH, from, TRUE, (tries < resetTries)]; b ← Socket.GetPacket[cH ! Socket.TimeOut => LOOP]; IF b.ns.packetType = packetExchange AND b.ns.exchangeType = PacketExchange.ExchangeClientType[timeService] AND LOOPHOLE[b.ns.exchangeID, LONG CARDINAL] = lastID THEN BEGIN p: LONG POINTER TO TimeServerFormat.TSPacket = LOOPHOLE[@b.ns.exchangeBody]; delay ← ( System.PulsesToMicroseconds[System.GetClockPulses[]] - sendMsec) /1000; WITH r: p↑ SELECT FROM internalTimeResponse => BEGIN reset ← TRUE; time ← System.SecondsSinceEpoch[TimeServerFormat.WireToGMT[r.time]] - System.SecondsSinceEpoch[System.GetGreenwichMeanTime[]]; error ← TimeServerFormat.WireToLong[r.absoluteError]; IF error # TimeServerClock.undefined THEN error ← error + delay + 1000; version ← TimeServerFormat.WireToLong[r.timeVersion]; END; timeResponse => BEGIN reset ← TRUE; time ← System.SecondsSinceEpoch[TimeServerFormat.WireToGMT[r.time]] - System.SecondsSinceEpoch[System.GetGreenwichMeanTime[]]; error ← IF NOT r.errorAccurate THEN TimeServerClock.undefined ELSE TimeServerFormat.WireToLong[r.absoluteError] + delay + 1000; version ← 0; END; ENDCASE; IF reset THEN AdjustClockEntry[ time, error, MaxVersion[], delay, from, TRUE]; END; Socket.ReturnBuffer[b]; ENDLOOP; END ELSE FOR tries: CARDINAL IN [0.. resetTries) UNTIL reset DO from.socket ← NSConstants.timeServerSocket; BEGIN ENABLE Inconsistent => RETRY; pulsesSent: System.Pulses = System.GetClockPulses[]; SendTimeRequest[cH, from, (tries = 0), TRUE]; reset ← CollectTimeReplies[cH, pulsesSent]; END; ENDLOOP; Socket.Delete[cH]; EXITS NoReset => NULL; END; ResyncNow: PUBLIC ENTRY PROCEDURE = { IF started THEN Process.Abort[surveyorProcess]}; SetParameters: PUBLIC ENTRY PROCEDURE [ drift, wobble: TimeServerClock.Rate, resetPeriod: CARDINAL] = BEGIN tsDrift ← drift; tsWobble ← wobble; tsResetPeriod ← resetPeriod + Random[]; IF started THEN Process.Abort[surveyorProcess]; -- signal new wait time END; Start: PUBLIC ENTRY PROCEDURE = BEGIN ENABLE UNWIND => NULL; IF started THEN RETURN; clockInvalid ← FALSE; tsVersion ← 0; surveyorProcess ← FORK SurveyorProcess[]; started ← TRUE; END; Stop: PUBLIC ENTRY PROCEDURE = BEGIN ENABLE UNWIND => NULL; IF NOT started THEN RETURN; started ← FALSE; Process.Abort[surveyorProcess]; -- do after setting started to FALSE JOIN surveyorProcess; END; -- private procedures AdjustClockEntry: ENTRY PROCEDURE [ amount: LONG INTEGER, error: TimeServerClock.Error, version: LONG CARDINAL, delay: LONG CARDINAL, who: System.NetworkAddress, useAlways: BOOLEAN] = BEGIN ENABLE UNWIND => NULL; me: TimeServerClock.Error = ErrorInternal[]; IF useAlways OR version > tsVersion OR (version = tsVersion AND error < me) THEN BEGIN TimeServerLog.LogClockChange[ System.GetGreenwichMeanTime[], amount, who, (me = TimeServerClock.undefined), me, error, delay, version]; SpecialSystem.AdjustClock[amount]; tsError ← error; tsSourceLastReset ← who; tsLastChange ← amount; IF tsVersion < version THEN SendNewVersionNote[tsVersion ← version]; END; END; BroadcastAddress: PROCEDURE [net: System.NetworkNumber] RETURNS [System.NetworkAddress] = INLINE { RETURN[ [net: net, host: System.broadcastHostNumber, socket: NSConstants.timeServerSocket]]}; CheckForConsistency: ENTRY PROCEDURE [ amount: LONG INTEGER, error: TimeServerClock.Error, version: LONG CARDINAL, who: System.NetworkAddress] = BEGIN ENABLE UNWIND => NULL; MarkInconsistent: INTERNAL PROCEDURE = BEGIN now: System.GreenwichMeanTime = System.GetGreenwichMeanTime[]; TimeServerLog.LogInconsistancy[ who, now, me, System.AdjustGreenwichMeanTime[now, amount], error]; ignoringServer ← TRUE; ignoringServerAddress ← who.host; tsError ← TimeServerClock.undefined; END; me: TimeServerClock.Error = ErrorInternal[]; IF tsVersion = version AND me # TimeServerClock.undefined AND ABS[amount*1000] > error + me THEN { MarkInconsistent[]; ERROR Inconsistent}; END; CollectTimeReplies: PROCEDURE [ cH: Socket.ChannelHandle, sent: System.Pulses] RETURNS [success: BOOLEAN] = BEGIN sendMsec: System.Microseconds = System.PulsesToMicroseconds[sent]; bestTime, time: LONG INTEGER; bestError: LONG CARDINAL ← TimeServerClock.undefined; bestVersion: LONG CARDINAL ← 0; error, delay, version, bestDelay: LONG CARDINAL; bestServer: System.NetworkAddress; myHostNumber: System.HostNumber = Router.FindMyHostID[]; b: Buffer.NSBuffer; success ← FALSE; DO b ← Socket.GetPacket[cH ! Socket.TimeOut => EXIT; ABORTED => IF started THEN RETRY]; IF b.ns.packetType = packetExchange AND b.ns.exchangeType = PacketExchange.ExchangeClientType[timeService] AND LOOPHOLE[b.ns.exchangeID, LONG CARDINAL] = lastID AND b.ns.source.host # myHostNumber AND (NOT ignoringServer OR b.ns.source.host # ignoringServerAddress) THEN BEGIN p: LONG POINTER TO TimeServerFormat.TSPacket = LOOPHOLE[@b.ns.exchangeBody]; delay ← (System.PulsesToMicroseconds[System.GetClockPulses[]]-sendMsec) /1000; WITH r: p↑ SELECT FROM internalTimeResponse => BEGIN error ← TimeServerFormat.WireToLong[r.absoluteError]; IF error # TimeServerClock.undefined THEN BEGIN success ← TRUE; error ← error + delay + 1000; time ← System.SecondsSinceEpoch[TimeServerFormat.WireToGMT[r.time]] - System.SecondsSinceEpoch[System.GetGreenwichMeanTime[]]; version ← TimeServerFormat.WireToLong[r.timeVersion]; IF version > bestVersion OR (version = bestVersion AND error <= bestError) THEN { bestTime ← time; bestError ← error; bestVersion ← version; bestServer ← b.ns.source; bestDelay ← delay}; END; END; ENDCASE; END; Socket.ReturnBuffer[b]; ENDLOOP; IF success THEN BEGIN CheckForConsistency[bestTime, bestError, bestVersion, bestServer]; AdjustClockEntry[bestTime, bestError, bestVersion, bestDelay, bestServer, FALSE ! SpecialSystem.SetBackTooFar => {clockInvalid ← TRUE; CONTINUE}]; END; END; ErrorInternal: INTERNAL PROCEDURE RETURNS [error: LONG CARDINAL] = BEGIN wobbleBase, driftBase: LONG CARDINAL; IF tsError = TimeServerClock.undefined THEN RETURN [tsError]; driftBase ← System.GetGreenwichMeanTime[] - SpecialSystem.TimeLastSet[]; wobbleBase ← MIN[tsWobble.base, driftBase]; error ← tsWobble.amount*wobbleBase/tsWobble.base + tsDrift.amount*driftBase/tsDrift.base; RETURN[error + tsError]; END; Init: PROCEDURE = BEGIN tsError ← TimeServerClock.undefined; tsDrift ← [base: 86400, amount: 1000]; tsWobble ← [base: 3600, amount: 1000]; tsResetPeriod ← 15 + Random[]; END; Random: PROCEDURE RETURNS [CARDINAL] = -- This generates a "random" (across servers) number between 0 and 10 -- which is added to the reset rate in order to keep the server polling -- unsynchronized BEGIN words: ARRAY [0.. SIZE[System.HostNumber]) OF CARDINAL = LOOPHOLE[Router.FindMyHostID[]]; RETURN[Inline.DIVMOD[words[SIZE[System.HostNumber]-1], 10].remainder]; END; RequestTime: PUBLIC PROCEDURE = BEGIN haveReply: BOOLEAN ← FALSE; incID: BOOLEAN; pulsesSent: System.Pulses; cH: Socket.ChannelHandle ← Socket.Create[System.nullSocketNumber, 1, 5]; BEGIN ENABLE UNWIND => {Socket.Delete[cH]; Router.FillRoutingTable[0]}; Router.FillRoutingTable[]; Process.Pause[2*oneSecond]; FOR hops: CARDINAL IN [0.. Router.infinity) UNTIL haveReply DO Socket.SetWaitTime[cH, WaitForHops[hops]]; incID ← TRUE; THROUGH [0.. triesPerNetwork) UNTIL haveReply DO pulsesSent ← System.GetClockPulses[]; IF hops = 0 THEN BEGIN SendTimeRequest[ cH, BroadcastAddress[System.nullNetworkNumber], TRUE, TRUE]; incID ← FALSE; END ELSE FOR net: System.NetworkNumber ← Router.EnumerateRoutingTable[Router.startEnumeration, hops], Router.EnumerateRoutingTable[net, hops] UNTIL net = Router.endEnumeration DO SendTimeRequest[cH, BroadcastAddress[net], incID, TRUE]; incID ← FALSE; ENDLOOP; -- if incID is FALSE here, then a time request was sent IF NOT incID THEN haveReply ← CollectTimeReplies[cH, pulsesSent]; ENDLOOP; ENDLOOP; Socket.Delete[cH]; Router.FillRoutingTable[0]; END; END; SendNewVersionNote: INTERNAL PROCEDURE [new: LONG CARDINAL] = BEGIN cH: Socket.ChannelHandle ← Socket.Create[System.nullSocketNumber]; b: Buffer.NSBuffer = Socket.GetSendBuffer[cH]; note: LONG POINTER TO noteNewVersion TimeServerFormat.TSPacket = LOOPHOLE[@b.ns.exchangeBody]; b.ns.packetType ← packetExchange; b.ns.exchangeID ← LOOPHOLE[lastID]; b.ns.exchangeType ← PacketExchange.ExchangeClientType[timeService]; b.ns.destination ← BroadcastAddress[System.nullNetworkNumber]; Socket.SetPacketWords[b, 3 + SIZE[noteNewVersion TimeServerFormat.TSPacket]]; note↑ ← [ version: TimeServerFormat.Version, tsBody: noteNewVersion[TimeServerFormat.LongToWire[new]]]; Socket.BroadcastPacketToAllConnectedNets[cH, b]; Socket.Delete[cH]; END; SendTimeRequest: PROCEDURE [ cH: Socket.ChannelHandle, who: System.NetworkAddress, incrementID: BOOLEAN, internalFormat: BOOLEAN] = BEGIN b: Buffer.NSBuffer = Socket.GetSendBuffer[cH]; iReq: LONG POINTER TO internalTimeRequest TimeServerFormat.TSPacket = LOOPHOLE[@b.ns.exchangeBody]; eReq: LONG POINTER TO timeRequest TimeServerFormat.TSPacket = LOOPHOLE[@b.ns.exchangeBody]; IF incrementID THEN lastID ← lastID + 1; b.ns.packetType ← packetExchange; b.ns.exchangeID ← LOOPHOLE[lastID]; b.ns.exchangeType ← PacketExchange.ExchangeClientType[timeService]; b.ns.destination ← who; Socket.SetPacketWords[b, 3 + ( IF internalFormat THEN SIZE[internalTimeRequest TimeServerFormat.TSPacket] ELSE SIZE[timeRequest TimeServerFormat.TSPacket])]; IF internalFormat THEN iReq↑ ← [version: TimeServerFormat.Version, tsBody: internalTimeRequest[]] ELSE eReq↑ ← [version: TimeServerFormat.Version, tsBody: timeRequest[]]; (IF who.net = System.nullNetworkNumber THEN Socket.BroadcastPacketToAllConnectedNets ELSE Socket.PutPacket)[cH, b] END; SurveyorProcess: PROCEDURE = BEGIN -- broadcasts a time request every tsResetPeriod minutes Process.SetPriority[Process.priorityBackground]; UNTIL NOT started DO ENABLE ABORTED => LOOP; IF NOT SpecialSystem.clockSlipping THEN RequestTime[! Inconsistent => RETRY]; ignoringServer ← FALSE; Wait[tsResetPeriod]; ENDLOOP; END; Wait: PROCEDURE [time: CARDINAL -- minutes --] = BEGIN ENABLE ABORTED => GOTO StopWaiting; oneMinute: LONG CARDINAL = LONG[Process.SecondsToTicks[60]]; ticksToWait: LONG CARDINAL = oneMinute*LONG[time]; maxTicks: Process.Ticks = LAST[Process.Ticks]; ticksLeft: LONG CARDINAL; FOR ticksLeft ← ticksToWait, ticksLeft - MIN[ticksLeft, maxTicks] UNTIL ticksLeft = 0 DO Process.Pause[Inline.LowHalf[MIN[ticksLeft, maxTicks]]]; ENDLOOP; EXITS StopWaiting => NULL; END; WaitForHops: PROCEDURE [hops: CARDINAL] RETURNS [delay: CARDINAL] = INLINE { RETURN[MAX[2+3*(hops/2), 6]*1000]}; Init[]; END...