package client import ( "sync" "time" ) // Clock estimates clock_offset_ms = local − server from server_time in responses, with half-RTT correction // (the server stamped the response roughly in the middle of the request round-trip) and an EWMA. type Clock struct { mu sync.Mutex offset time.Duration samples int alpha float64 } // NewClock returns an EWMA clock with α = 0.3. func NewClock() *Clock { return &Clock{alpha: 0.3} } // Observe records one sample: request sent at t0 (local), took rtt, server said serverTime. func (c *Clock) Observe(t0 time.Time, rtt time.Duration, serverTime time.Time) { localMid := t0.Add(rtt / 2) sample := localMid.Sub(serverTime) c.mu.Lock() defer c.mu.Unlock() if c.samples == 0 { c.offset = sample } else { c.offset = time.Duration(float64(c.offset)*(1-c.alpha) + float64(sample)*c.alpha) } c.samples++ } // Set forces the offset from a single authoritative observation (401 skew resync). func (c *Clock) Set(localMid, serverTime time.Time) { c.mu.Lock() defer c.mu.Unlock() c.offset = localMid.Sub(serverTime) c.samples++ } // Offset returns the current estimate (local − server). func (c *Clock) Offset() time.Duration { c.mu.Lock() defer c.mu.Unlock() return c.offset } // OffsetMs returns the estimate in whole milliseconds. func (c *Clock) OffsetMs() int64 { return c.Offset().Milliseconds() } // Samples returns how many observations were folded in. func (c *Clock) Samples() int { c.mu.Lock() defer c.mu.Unlock() return c.samples } // ServerNow returns the local time corrected to the server's clock. func (c *Clock) ServerNow() time.Time { return time.Now().Add(-c.Offset()) }