2021-12-04 16:42:11 +00:00

338 lines
8.4 KiB
Go

// Copyright 2015-2017 Brett Vickers.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ntp
import (
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
const (
host = "0.beevik-ntp.pool.ntp.org"
refID = 0x58585858 // 'XXXX'
)
func isNil(t *testing.T, err error) bool {
switch {
case err == nil:
return true
case strings.Contains(err.Error(), "timeout"):
// log instead of error, so test isn't failed
t.Logf("[%s] Query timeout: %s", host, err)
return false
case strings.Contains(err.Error(), "kiss of death"):
// log instead of error, so test isn't failed
t.Logf("[%s] Query kiss of death: %s", host, err)
return false
default:
// error, so test fails
t.Errorf("[%s] Query failed: %s", host, err)
return false
}
}
func assertValid(t *testing.T, r *Response) {
err := r.Validate()
_ = isNil(t, err)
}
func assertInvalid(t *testing.T, r *Response) {
err := r.Validate()
if err == nil {
t.Errorf("[%s] Response unexpectedly valid\n", host)
}
}
func TestTime(t *testing.T) {
tm, err := Time(host)
now := time.Now()
if isNil(t, err) {
t.Logf("Local Time %v\n", now)
t.Logf("~True Time %v\n", tm)
t.Logf("Offset %v\n", tm.Sub(now))
}
}
func TestTimeFailure(t *testing.T) {
// Use a link-local IP address that won't have an NTP server listening
// on it. This should return the local system's time.
local, err := Time("169.254.122.229")
assert.NotNil(t, err)
now := time.Now()
// When the NTP time query fails, it should return the system time.
// Compare the "now" system time with the returned time. It should be
// about the same.
diffMinutes := now.Sub(local).Minutes()
assert.True(t, diffMinutes > -1 && diffMinutes < 1)
}
func TestQuery(t *testing.T) {
t.Logf("[%s] ----------------------", host)
t.Logf("[%s] NTP protocol version %d", host, 4)
r, err := QueryWithOptions(host, QueryOptions{Version: 4})
if !isNil(t, err) {
return
}
t.Logf("[%s] LocalTime: %v", host, time.Now())
t.Logf("[%s] XmitTime: %v", host, r.Time)
t.Logf("[%s] RefTime: %v", host, r.ReferenceTime)
t.Logf("[%s] RTT: %v", host, r.RTT)
t.Logf("[%s] Offset: %v", host, r.ClockOffset)
t.Logf("[%s] Poll: %v", host, r.Poll)
t.Logf("[%s] Precision: %v", host, r.Precision)
t.Logf("[%s] Stratum: %v", host, r.Stratum)
t.Logf("[%s] RefID: 0x%08x", host, r.ReferenceID)
t.Logf("[%s] RootDelay: %v", host, r.RootDelay)
t.Logf("[%s] RootDisp: %v", host, r.RootDispersion)
t.Logf("[%s] RootDist: %v", host, r.RootDistance)
t.Logf("[%s] MinError: %v", host, r.MinError)
t.Logf("[%s] Leap: %v", host, r.Leap)
t.Logf("[%s] KissCode: %v", host, stringOrEmpty(r.KissCode))
assertValid(t, r)
}
func stringOrEmpty(s string) string {
if s == "" {
return "<empty>"
}
return s
}
func TestValidate(t *testing.T) {
var m msg
var r *Response
m.Stratum = 1
m.ReferenceID = refID
m.ReferenceTime = 1 << 32
m.Precision = -1 // 500ms
// Zero RTT
m.OriginTime = 1 << 32
m.ReceiveTime = 1 << 32
m.TransmitTime = 1 << 32
r = parseTime(&m, 1<<32)
assertValid(t, r)
// Negative freshness
m.ReferenceTime = 2 << 32
r = parseTime(&m, 1<<32)
assertInvalid(t, r)
// Unfresh clock (48h)
m.OriginTime = 2 * 86400 << 32
m.ReceiveTime = 2 * 86400 << 32
m.TransmitTime = 2 * 86400 << 32
r = parseTime(&m, 2*86400<<32)
assertInvalid(t, r)
// Fresh clock (24h)
m.ReferenceTime = 1 * 86400 << 32
r = parseTime(&m, 2*86400<<32)
assertValid(t, r)
// Values indicating a negative RTT
m.RootDelay = 16 << 16
m.ReferenceTime = 1 << 32
m.OriginTime = 20 << 32
m.ReceiveTime = 10 << 32
m.TransmitTime = 15 << 32
r = parseTime(&m, 22<<32)
assert.NotNil(t, r)
assertValid(t, r)
assert.Equal(t, r.RTT, 0*time.Second)
assert.Equal(t, r.RootDistance, 8*time.Second)
}
func TestBadServerPort(t *testing.T) {
// Not NTP port.
tm, _, err := getTime(host, QueryOptions{Port: 9})
assert.Nil(t, tm)
assert.NotNil(t, err)
}
func TestTTL(t *testing.T) {
// TTL of 1 should cause a timeout.
tm, _, err := getTime(host, QueryOptions{TTL: 1})
assert.Nil(t, tm)
assert.NotNil(t, err)
}
func TestQueryTimeout(t *testing.T) {
// Force an immediate timeout.
tm, err := QueryWithOptions(host, QueryOptions{Version: 4, Timeout: time.Nanosecond})
assert.Nil(t, tm)
assert.NotNil(t, err)
}
func TestShortConversion(t *testing.T) {
var ts ntpTimeShort
ts = 0x00000000
assert.Equal(t, 0*time.Nanosecond, ts.Duration())
ts = 0x00000001
assert.Equal(t, 15259*time.Nanosecond, ts.Duration()) // well, it's actually 15258.789, but it's good enough
ts = 0x00008000
assert.Equal(t, 500*time.Millisecond, ts.Duration()) // precise
ts = 0x0000c000
assert.Equal(t, 750*time.Millisecond, ts.Duration()) // precise
ts = 0x0000ff80
assert.Equal(t, time.Second-(1000000000/512)*time.Nanosecond, ts.Duration()) // last precise sub-second value
ts = 0x00010000
assert.Equal(t, 1000*time.Millisecond, ts.Duration()) // precise
ts = 0x00018000
assert.Equal(t, 1500*time.Millisecond, ts.Duration()) // precise
ts = 0xffff0000
assert.Equal(t, 65535*time.Second, ts.Duration()) // precise
ts = 0xffffff80
assert.Equal(t, 65536*time.Second-(1000000000/512)*time.Nanosecond, ts.Duration()) // last precise value
}
func TestLongConversion(t *testing.T) {
ts := []ntpTime{0x0, 0xff800000, 0x1ff800000, 0x80000000ff800000, 0xffffffffff800000}
for _, v := range ts {
assert.Equal(t, v, toNtpTime(v.Time()))
}
}
func abs(d time.Duration) time.Duration {
switch {
case int64(d) < 0:
return -d
default:
return d
}
}
func TestOffsetCalculation(t *testing.T) {
now := time.Now()
t1 := toNtpTime(now)
t2 := toNtpTime(now.Add(20 * time.Second))
t3 := toNtpTime(now.Add(21 * time.Second))
t4 := toNtpTime(now.Add(5 * time.Second))
// expectedOffset := ((T2 - T1) + (T3 - T4)) / 2
// ((119 - 99) + (121 - 104)) / 2
// (20 + 17) / 2
// 37 / 2 = 18
expectedOffset := 18 * time.Second
offset := offset(t1, t2, t3, t4)
assert.Equal(t, expectedOffset, offset)
}
func TestOffsetCalculationNegative(t *testing.T) {
now := time.Now()
t1 := toNtpTime(now.Add(101 * time.Second))
t2 := toNtpTime(now.Add(102 * time.Second))
t3 := toNtpTime(now.Add(103 * time.Second))
t4 := toNtpTime(now.Add(105 * time.Second))
// expectedOffset := ((T2 - T1) + (T3 - T4)) / 2
// ((102 - 101) + (103 - 105)) / 2
// (1 + -2) / 2 = -1 / 2
expectedOffset := -time.Second / 2
offset := offset(t1, t2, t3, t4)
assert.Equal(t, expectedOffset, offset)
}
func TestMinError(t *testing.T) {
start := time.Now()
m := &msg{
Stratum: 1,
ReferenceID: refID,
ReferenceTime: toNtpTime(start),
OriginTime: toNtpTime(start.Add(1 * time.Second)),
ReceiveTime: toNtpTime(start.Add(2 * time.Second)),
TransmitTime: toNtpTime(start.Add(3 * time.Second)),
}
r := parseTime(m, toNtpTime(start.Add(4*time.Second)))
assertValid(t, r)
assert.Equal(t, r.MinError, time.Duration(0))
for org := 1 * time.Second; org <= 10*time.Second; org += time.Second {
for rec := 1 * time.Second; rec <= 10*time.Second; rec += time.Second {
for xmt := rec; xmt <= 10*time.Second; xmt += time.Second {
for dst := org; dst <= 10*time.Second; dst += time.Second {
m.OriginTime = toNtpTime(start.Add(org))
m.ReceiveTime = toNtpTime(start.Add(rec))
m.TransmitTime = toNtpTime(start.Add(xmt))
r = parseTime(m, toNtpTime(start.Add(dst)))
assertValid(t, r)
var error0, error1 time.Duration
if org >= rec {
error0 = org - rec
}
if xmt >= dst {
error1 = xmt - dst
}
var minError time.Duration
if error0 > error1 {
minError = error0
} else {
minError = error1
}
assert.Equal(t, r.MinError, minError)
}
}
}
}
}
func TestTimeConversions(t *testing.T) {
nowNtp := toNtpTime(time.Now())
now := nowNtp.Time()
startNow := now
for i := 0; i < 100; i++ {
nowNtp = toNtpTime(now)
now = nowNtp.Time()
}
assert.Equal(t, now, startNow)
}
func TestKissCode(t *testing.T) {
codes := []struct {
id uint32
str string
}{
{0x41435354, "ACST"},
{0x41555448, "AUTH"},
{0x4155544f, "AUTO"},
{0x42435354, "BCST"},
{0x43525950, "CRYP"},
{0x44454e59, "DENY"},
{0x44524f50, "DROP"},
{0x52535452, "RSTR"},
{0x494e4954, "INIT"},
{0x4d435354, "MCST"},
{0x4e4b4559, "NKEY"},
{0x52415445, "RATE"},
{0x524d4f54, "RMOT"},
{0x53544550, "STEP"},
{0x01010101, ""},
{0xfefefefe, ""},
{0x01544450, ""},
{0x41544401, ""},
}
for _, c := range codes {
assert.Equal(t, kissCode(c.id), c.str)
}
}