/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package daemon
import (
"fmt"
"os"
"time"
log "github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
yaml "gopkg.in/yaml.v2"
)
// Config represents configuration we expect to read from file
type Config struct {
PTPClientAddress string // where should fbclock connect to
RingSize int // must be at least the size of N samples we use in expressions
Math Math // configuration for calculation we'll be doing
Interval time.Duration // how often do we poll ptp4l and update data in shm
Iface string // network interface to use
LinearizabilityTestInterval time.Duration // perform the linearizability test every so often
SPTP bool // denotes whether we are running in sptp or ptp4l mode
LinearizabilityTestMaxGMOffset time.Duration // max offset between GMs before linearizability test considered failed
BootDelay time.Duration // postpone startup by this time after boot
EnableDataV2 bool // enable fbclock data v2
}
// EvalAndValidate makes sure config is valid and evaluates expressions for further use.
func (c *Config) EvalAndValidate() error {
if c.PTPClientAddress == "" {
return fmt.Errorf("bad config: 'ptpclientaddress'")
}
if c.RingSize <= 0 {
return fmt.Errorf("bad config: 'ringsize' must be >0")
}
if c.Interval <= 0 || c.Interval > time.Minute {
return fmt.Errorf("bad config: 'interval' must be between 0 and 1 minute")
}
if c.LinearizabilityTestInterval < 0 {
return fmt.Errorf("bad config: 'test interval' must be positive")
}
if c.LinearizabilityTestMaxGMOffset < 0 {
return fmt.Errorf("bad config: 'offset' must be positive")
}
return c.Math.Prepare()
}
// PostponeStart postpones startup by BootDelay
func (c *Config) PostponeStart() error {
uptime, err := uptime()
if err != nil {
return err
}
log.Debugf("system uptime: %s", uptime)
if uptime < c.BootDelay {
log.Infof("postponing startup by %s", c.BootDelay-uptime)
time.Sleep(c.BootDelay - uptime)
}
return nil
}
// uptime returns system boot time
func uptime() (time.Duration, error) {
var ts unix.Timespec
if err := unix.ClockGettime(unix.CLOCK_BOOTTIME, &ts); err != nil {
return 0, err
}
return time.Duration(ts.Nano()), nil
}
// ReadConfig reads config and unmarshals it from yaml into Config
func ReadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
c := Config{}
err = yaml.UnmarshalStrict(data, &c)
return &c, err
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package daemon
import (
"context"
"errors"
"fmt"
"math"
"os"
"sync"
"time"
log "github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
"github.com/facebook/time/fbclock"
"github.com/facebook/time/fbclock/stats"
"github.com/facebook/time/leapsectz"
"github.com/facebook/time/phc"
"github.com/facebook/time/ptp/linearizability"
ptp "github.com/facebook/time/ptp/protocol"
)
const (
utcOffsetOriginalS int32 = 10 // UTC-TAI offset was 10s before leap seconds started (1972)
leapDurationS uint64 = 62500 // 17.36 hours https://chrony-project.org/doc/4.6/chrony.conf.html
monPrefix string = "linearizability."
)
var errNotEnoughData = errors.New("not enough data points")
var errNoTestResults = errors.New("no test results")
var errNoPHC = errors.New("phc error")
var errCorrectness = errors.New("sanity checking data point error")
// defaultTargets is a list of targets if no available
var defaultTargets = []string{"::1", "::2", "::3"}
// DataPoint is what we store in DataPoint ring buffer
type DataPoint struct {
// IngressTimeNS represents ingress time in NanoSeconds
IngressTimeNS int64
// MasterOffsetNS represents master offset in NanoSeconds
MasterOffsetNS float64
// PathDelayNS represents path delay in NanoSeconds
PathDelayNS float64
// FreqAdjustmentPPB represents freq adjustment in parts per billion
FreqAdjustmentPPB float64
// ClockAccuracyNS represents clock accurary in nanoseconds
ClockAccuracyNS float64
}
// SanityCheck checks datapoint for correctness
func (d *DataPoint) SanityCheck() error {
if d.IngressTimeNS == 0 {
return fmt.Errorf("ingress time is 0")
}
if d.MasterOffsetNS == 0 {
return fmt.Errorf("master offset is 0")
}
if d.PathDelayNS == 0 {
return fmt.Errorf("path delay is 0")
}
if d.FreqAdjustmentPPB == 0 {
return fmt.Errorf("frequency adjustment is 0")
}
if d.ClockAccuracyNS == 0 {
return fmt.Errorf("clock accuracy is 0")
}
if time.Duration(d.ClockAccuracyNS) >= ptp.ClockAccuracyUnknown.Duration() {
return fmt.Errorf("clock accuracy is unknown")
}
return nil
}
// DataFetcher is the data fetcher interface
type DataFetcher interface {
//function to gm data
FetchGMs(cfg *Config) (targest []string, err error)
//function to fetch stats
FetchStats(cfg *Config) (*DataPoint, error)
}
// Daemon is a component of fbclock that
// runs continuously,
// talks to ptp4l,
// does the math
// and populates shared memory for client library to read from.
type Daemon struct {
DataFetcher
cfg *Config
state *daemonState
stats stats.Server
l Logger
// function to get PHC time from configured PHC device
getPHCTime func() (time.Time, error)
getPHCAndSysTime func() (time.Time, time.Time, uint32, error)
// function to get PHC freq from configured PHC device
getPHCFreqPPB func() (float64, error)
}
// minRingSize calculate how many DataPoint we need to have in a ring buffer
// in order to provide aggregate values over 1 minute
func minRingSize(configuredRingSize int, interval time.Duration) int {
size := configuredRingSize
if time.Duration(size)*interval < time.Minute {
size = int(math.Ceil(float64(time.Minute) / float64(interval)))
}
return size
}
type clockSmearing struct {
smearingStartS uint64 // time (TAI) when smearing starts
smearingEndS uint64 // time (TAI) when smearing ends
utcOffsetPreS int32 // DTAI offset prior to Leap Second Event Time
utcOffsetPostS int32 // DTAI offset post Leap Second Event Time
}
func leapSeconds() ([]leapsectz.LeapSecond, error) {
leaps, err := leapsectz.Parse("")
if err != nil {
return []leapsectz.LeapSecond{}, err
}
if len(leaps) < 2 {
return []leapsectz.LeapSecond{}, fmt.Errorf("not enough leap seconds in the file")
}
previousLeap := leaps[len(leaps)-2]
latestLeap := leaps[len(leaps)-1]
return []leapsectz.LeapSecond{previousLeap, latestLeap}, nil
}
func leapSecondSmearing(leaps []leapsectz.LeapSecond) *clockSmearing {
// need a minimum of 2 published leap second events in tzdata
if len(leaps) < 2 {
return &clockSmearing{}
}
latestLeap := leaps[len(leaps)-1]
previousLeap := leaps[len(leaps)-2]
utcOffsetPreS := previousLeap.Nleap + utcOffsetOriginalS
utcOffsetPostS := latestLeap.Nleap + utcOffsetOriginalS
// this is the leap second adjustment time which is either 23:59:60 UTC or 00:00:00 UTC of following day
// if we don't render a timestamp of 23:59:60 UTC
leapSecondEventTimeS := latestLeap.Tleap - uint64(latestLeap.Nleap) + 1
// smearing starts at leap second event time and ends 18.06 hours after
smearingStartS := leapSecondEventTimeS + uint64(utcOffsetPreS)
smearingEndS := leapSecondEventTimeS + leapDurationS + uint64(utcOffsetPreS)
return &clockSmearing{
smearingStartS: smearingStartS,
smearingEndS: smearingEndS,
utcOffsetPreS: utcOffsetPreS,
utcOffsetPostS: utcOffsetPostS,
}
}
// noTestResults generates a map of error test results
func noTestResults(targets []string) map[string]linearizability.TestResult {
r := map[string]linearizability.TestResult{}
for _, target := range targets {
r[target] = linearizability.SPTPTestResult{Error: errNoTestResults}
}
return r
}
// New creates new fbclock-daemon
func New(cfg *Config, stats stats.Server, l Logger) (*Daemon, error) {
// we need at least 1m of samples for aggregate values
effectiveRingSize := minRingSize(cfg.RingSize, cfg.Interval)
s := &Daemon{
stats: stats,
state: newDaemonState(effectiveRingSize),
cfg: cfg,
l: l,
}
if cfg.SPTP {
s.DataFetcher = &HTTPFetcher{}
} else {
s.DataFetcher = &SockFetcher{}
}
phcDevice, err := phc.IfaceToPHCDevice(cfg.Iface)
if err != nil {
return nil, fmt.Errorf("finding PHC device for %q: %w", cfg.Iface, err)
}
// Keep file open for the lifetime of the fbclock
f, err := os.OpenFile(phcDevice, os.O_RDWR, 0)
if err != nil {
return nil, err
}
dev := phc.FromFile(f)
// function to get time from phc
s.getPHCTime = func() (time.Time, error) { return dev.Time() }
s.getPHCAndSysTime = func() (time.Time, time.Time, uint32, error) {
data, err := dev.ReadSysoffExtended()
if err != nil {
return time.Time{}, time.Time{}, 0, err
}
best := data.BestSample()
phcTime := best.PHCTime
monoTime := best.SysTime
return phcTime, monoTime, uint32(best.SysClockID), nil //nolint:gosec
}
s.getPHCFreqPPB = func() (float64, error) { return dev.FreqPPB() }
// calculated values
s.stats.SetCounter("m_ns", 0)
s.stats.SetCounter("w_ns", 0)
s.stats.SetCounter("drift_ppb", 0)
// error counters
s.stats.SetCounter("data_error", 0)
s.stats.SetCounter("processing_error", 0)
s.stats.SetCounter("data_sanity_check_error", 0)
s.stats.SetCounter("monotonictime_error", 0)
s.stats.SetCounter("phc_read_error", 0)
// values collected from ptp4l
s.stats.SetCounter("ingress_time_ns", 0)
s.stats.SetCounter("master_offset_ns", 0)
s.stats.SetCounter("path_delay_ns", 0)
s.stats.SetCounter("freq_adj_ppb", 0)
s.stats.SetCounter("clock_accuracy_ns", 0)
// aggregated values
s.stats.SetCounter("master_offset_ns.60.abs_max", 0)
s.stats.SetCounter("path_delay_ns.60.abs_max", 0)
s.stats.SetCounter("freq_adj_ppb.60.abs_max", 0)
return s, nil
}
func (s *Daemon) calcW() (float64, error) {
lastN := s.state.takeDataPoint(s.cfg.RingSize)
params := prepareMathParameters(lastN)
logSample := &LogSample{
MasterOffsetNS: params["offset"][0],
MasterOffsetMeanNS: mean(params["offset"]),
MasterOffsetStddevNS: stddev(params["offset"]),
PathDelayNS: params["delay"][0],
PathDelayMeanNS: mean(params["delay"]),
PathDelayStddevNS: stddev(params["delay"]),
FreqAdjustmentPPB: params["freq"][0],
FreqAdjustmentMeanPPB: mean(params["freq"]),
FreqAdjustmentStddevPPB: stddev(params["freq"]),
ClockAccuracyMean: mean(params["clockaccuracie"]),
}
mRaw, err := s.cfg.Math.mExpr.Evaluate(mapOfInterface(params))
if err != nil {
return 0, err
}
m := mRaw.(float64)
logSample.MeasurementNS = m
s.stats.SetCounter("m_ns", int64(m))
// push m to ring buffer
s.state.pushM(m)
ms := s.state.takeM(s.cfg.RingSize)
if len(ms) != s.cfg.RingSize {
return 0, fmt.Errorf("%w getting W: want %d, got %d", errNotEnoughData, s.cfg.RingSize, len(ms))
}
parameters := map[string]interface{}{
"m": ms,
}
logSample.MeasurementMeanNS = mean(ms)
logSample.MeasurementStddevNS = stddev(ms)
wRaw, err := s.cfg.Math.wExpr.Evaluate(parameters)
if err != nil {
return 0, err
}
w := wRaw.(float64)
logSample.WindowNS = w
if err := s.l.Log(logSample); err != nil {
log.Errorf("failed to log sample: %v", err)
}
s.stats.SetCounter("w_ns", int64(w))
return w, nil
}
func (s *Daemon) calcDriftPPB() (float64, error) {
lastN := s.state.takeDataPoint(s.cfg.RingSize)
if len(lastN) != s.cfg.RingSize {
return 0, fmt.Errorf("%w calculating drift: want %d, got %d", errNotEnoughData, s.cfg.RingSize, len(lastN))
}
params := prepareMathParameters(lastN)
driftRaw, err := s.cfg.Math.driftExpr.Evaluate(mapOfInterface(params))
if err != nil {
return 0, err
}
drift := driftRaw.(float64)
return drift, nil
}
func (s *Daemon) calculateSHMData(data *DataPoint, leaps []leapsectz.LeapSecond) (*fbclock.Data, error) {
if err := data.SanityCheck(); err != nil {
s.stats.UpdateCounterBy("data_sanity_check_error", 1)
return nil, fmt.Errorf("%w: %w", errCorrectness, err)
}
s.stats.SetCounter("data_sanity_check_error", 0)
// store DataPoint in ring buffer
s.state.pushDataPoint(data)
// calculate W
w, err := s.calcW()
if err != nil {
return nil, fmt.Errorf("calculating W: %w", err)
}
wUint := uint64(w)
if wUint == 0 {
return nil, fmt.Errorf("value of W is 0")
}
// drift is in PPB, parts per billion.
// 1ns = 1/billions of second, so we can just say that
// hValue is measured in ns per second
hValue, err := s.calcDriftPPB()
if err != nil {
return nil, fmt.Errorf("calculating drift: %w", err)
}
s.stats.SetCounter("drift_ppb", int64(hValue))
clockSmearing := leapSecondSmearing(leaps)
return &fbclock.Data{
IngressTimeNS: data.IngressTimeNS,
ErrorBoundNS: wUint,
HoldoverMultiplierNS: hValue,
SmearingStartS: clockSmearing.smearingStartS,
SmearingEndS: clockSmearing.smearingEndS,
UTCOffsetPreS: clockSmearing.utcOffsetPreS,
UTCOffsetPostS: clockSmearing.utcOffsetPostS,
}, nil
}
func (s *Daemon) doWork(shm *fbclock.Shm, data *DataPoint) error {
// push stats
s.stats.SetCounter("master_offset_ns", int64(data.MasterOffsetNS))
s.stats.SetCounter("path_delay_ns", int64(data.PathDelayNS))
s.stats.SetCounter("ingress_time_ns", data.IngressTimeNS)
s.stats.SetCounter("freq_adj_ppb", int64(data.FreqAdjustmentPPB))
s.stats.SetCounter("clock_accuracy_ns", int64(data.ClockAccuracyNS))
// try and calculate how long ago was the ingress time
// use clock_gettime as the fastest and widely available method
phcTime, err := s.getPHCTime()
if err != nil {
return fmt.Errorf("Failed to get PHC time from %s: %w", s.cfg.Iface, errors.Join(errNoPHC, err))
}
if data.IngressTimeNS > 0 {
s.state.updateIngressTimeNS(data.IngressTimeNS)
}
it := s.state.ingressTimeNS()
if it > 0 {
timeSinceIngress := phcTime.UnixNano() - it
log.Debugf("Time since ingress: %dns", timeSinceIngress)
} else {
log.Warning("No data for time since ingress")
}
// read tzdata for leap seconds
leaps, err := leapSeconds()
if err != nil {
log.Warningf("Failed to get leap seconds: %v", err)
}
// store everything in shared memory
d, err := s.calculateSHMData(data, leaps)
if err != nil {
if errors.Is(err, errNotEnoughData) {
log.Warning(err)
return nil
}
return err
}
s.state.lastStoredData = d
if err := fbclock.StoreFBClockData(shm.File.Fd(), *d); err != nil {
return err
}
// aggregated stats over 1 minute
maxDp := s.state.aggregateDataPointsMax(minRingSize(s.cfg.RingSize, s.cfg.Interval))
s.stats.SetCounter("master_offset_ns.60.abs_max", int64(maxDp.MasterOffsetNS))
s.stats.SetCounter("path_delay_ns.60.abs_max", int64(maxDp.PathDelayNS))
s.stats.SetCounter("freq_adj_ppb.60.abs_max", int64(maxDp.FreqAdjustmentPPB))
return nil
}
func targetsDiff(oldTargets []string, targets []string) (added []string, removed []string) {
m := map[string]bool{}
for _, k := range oldTargets {
m[k] = true
}
for _, k := range targets {
if m[k] {
delete(m, k)
} else {
added = append(added, k)
}
}
for k := range m {
removed = append(removed, k)
}
return
}
func (s *Daemon) runLinearizabilityTests(ctx context.Context) {
testers := map[string]linearizability.Tester{}
oldTargets := []string{}
ctx, cancel := context.WithCancel(ctx)
defer cancel()
m := new(sync.Mutex)
ticker := time.NewTicker(s.cfg.LinearizabilityTestInterval)
defer ticker.Stop()
for ; true; <-ticker.C { // first run without delay, then at interval
eg := new(errgroup.Group)
currentResults := map[string]linearizability.TestResult{}
targets, err := s.DataFetcher.FetchGMs(s.cfg)
if err != nil {
log.Errorf("getting linearizability test targets: %v", err)
if len(oldTargets) > 0 {
linearizability.ProcessMonitoringResults(monPrefix, noTestResults(oldTargets), s.stats)
} else {
linearizability.ProcessMonitoringResults(monPrefix, noTestResults(defaultTargets), s.stats)
}
continue
}
log.Debugf("targets: %v, err: %v", targets, err)
// log when set of targets changes
added, removed := targetsDiff(oldTargets, targets)
if len(added) > 0 || len(removed) > 0 {
log.Infof("new set of linearizability test targets. Added: %v, Removed: %v. Resulting set: %v", added, removed, targets)
// we never remove testers, as stopping background listener goroutine is not easy
oldTargets = targets
}
for _, server := range targets {
server := server
log.Debugf("talking to %s", server)
lt, found := testers[server]
if !found {
if s.cfg.SPTP {
lt, err = linearizability.NewSPTPTester(server, fmt.Sprintf("http://%s/", s.cfg.PTPClientAddress), s.cfg.LinearizabilityTestMaxGMOffset)
} else {
lt, err = linearizability.NewPTP4lTester(server, s.cfg.Iface, false)
}
if err != nil {
log.Errorf("creating tester: %v", err)
continue
}
testers[server] = lt
}
eg.Go(func() error {
res := lt.RunTest(ctx)
m.Lock()
currentResults[server] = res
m.Unlock()
return nil
})
}
err = eg.Wait()
if err != nil && !errors.Is(err, context.Canceled) {
log.Error(err)
}
// get result, log it and push it into ring buffer
for _, res := range currentResults {
good, err := res.Good()
if err != nil {
log.Error(res.Explain())
continue
}
if !good {
log.Warning(res.Explain())
}
log.Debugf("got linearizability result: %s", res.Explain())
s.state.pushLinearizabilityTestResult(res)
}
// add stats from linearizability checks
linearizability.ProcessMonitoringResults(monPrefix, currentResults, s.stats)
}
}
func calcCoeffPPB(prev, cur *fbclock.DataV2) (int64, error) {
if prev.SysclockTimeNS == 0 {
// first run, no previous data
return 0, nil
}
// calculate the ratio of PHC time to system time (how faster (or slower) PHC ticks compared to system time)
mCoeff := float64(cur.PHCTimeNS-prev.PHCTimeNS) / float64(cur.SysclockTimeNS-prev.SysclockTimeNS)
// calculate the ratio in parts per billion (PPB)
coefPPB := int64(mCoeff*float64(time.Second) - float64(time.Second))
// check continuity of extrapolated PHC time
sysTimeDiff := cur.SysclockTimeNS - prev.SysclockTimeNS - 1
phcTimeFixed := prev.PHCTimeNS + sysTimeDiff + coefPPB*sysTimeDiff/time.Second.Nanoseconds()
var err error
if phcTimeFixed > cur.PHCTimeNS {
err = fmt.Errorf("PHC time is not monotonic: %d > %d", phcTimeFixed, cur.PHCTimeNS)
}
return coefPPB, err
}
// populateDataV2 populates fbclock.DataV2 with data from fbclock.Data plus calculated values
func (s *Daemon) populateDataV2(shmv2 *fbclock.Shm) {
prevDataV2 := fbclock.DataV2{}
fastTicker := time.NewTicker(10 * time.Millisecond)
defer fastTicker.Stop()
for ; true; <-fastTicker.C { // first run without delay, then at interval
if s.state.lastStoredData != nil {
// we need a copy of the latest v1 data to fill in parts of v2 data
// the pointer dereference is needed to avoid partial reads of the data
curData := *s.state.lastStoredData
phcTime, sysTime, clockID, err := s.getPHCAndSysTime()
if err != nil {
log.Errorf("reading PHC time from %s: %v", s.cfg.Iface, err)
s.stats.UpdateCounterBy("phc_read_error", 1)
continue
}
dataV2 := fbclock.DataV2{
IngressTimeNS: curData.IngressTimeNS,
ErrorBoundNS: curData.ErrorBoundNS,
HoldoverMultiplierNS: curData.HoldoverMultiplierNS,
SmearingStartS: curData.SmearingStartS,
UTCOffsetPreS: int16(curData.UTCOffsetPreS), //nolint:gosec
UTCOffsetPostS: int16(curData.UTCOffsetPostS), //nolint:gosec
PHCTimeNS: phcTime.UnixNano(),
SysclockTimeNS: sysTime.UnixNano(),
ClockID: clockID,
CoefPPB: 0,
}
if dataV2.CoefPPB, err = calcCoeffPPB(&prevDataV2, &dataV2); err != nil {
log.Warning(err)
s.stats.UpdateCounterBy("monotonictime_error", 1)
}
if err := fbclock.StoreFBClockDataV2(shmv2.File.Fd(), dataV2); err != nil {
log.Errorf("writing dataV2 to shm: %v", err)
}
prevDataV2 = dataV2
}
}
}
// Run a daemon
func (s *Daemon) Run(ctx context.Context) error {
shm, err := fbclock.OpenFBClockSHM()
if err != nil {
return fmt.Errorf("opening fbclock shm: %w", err)
}
defer shm.Close()
if s.cfg.LinearizabilityTestInterval != 0 {
go s.runLinearizabilityTests(ctx)
}
if s.cfg.EnableDataV2 {
shmv2, err := fbclock.OpenFBClockSHMv2()
if err != nil {
return fmt.Errorf("opening fbclock shm v2: %w", err)
}
defer shmv2.Close()
go s.populateDataV2(shmv2)
}
ticker := time.NewTicker(s.cfg.Interval)
defer ticker.Stop()
for ; true; <-ticker.C { // first run without delay, then at interval
data, err := s.DataFetcher.FetchStats(s.cfg)
if err != nil {
log.Error(err)
s.stats.UpdateCounterBy("data_error", 1)
continue
}
s.stats.SetCounter("data_error", 0)
// get PHC freq adjustment
freqPPB, err := s.getPHCFreqPPB()
if err != nil {
return err
}
data.FreqAdjustmentPPB = freqPPB
if err := s.doWork(shm, data); err != nil {
if errors.Is(err, errNoPHC) {
return err
} else if errors.Is(err, errCorrectness) {
log.Warning(err)
} else {
log.Error(err)
}
s.stats.UpdateCounterBy("processing_error", 1)
continue
}
s.stats.SetCounter("processing_error", 0)
}
return nil
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package daemon
import (
"fmt"
"github.com/facebook/time/ptp/sptp/stats"
log "github.com/sirupsen/logrus"
)
// HTTPFetcher provides data fetcher implementation using http
type HTTPFetcher struct {
DataFetcher
}
// FetchGMs fetches GMs via http
func (hf *HTTPFetcher) FetchGMs(cfg *Config) (targets []string, err error) {
url := fmt.Sprintf("http://%s/", cfg.PTPClientAddress)
sm, err := stats.FetchStats(url)
if err != nil {
return nil, err
}
for _, entry := range sm {
// skip the current best master
if entry.Selected {
continue
}
// skip GMs we didn't get announce from
if entry.Error != "" {
continue
}
targets = append(targets, entry.GMAddress)
}
return
}
// FetchStats fetches GMs via http
func (hf *HTTPFetcher) FetchStats(cfg *Config) (*DataPoint, error) {
url := fmt.Sprintf("http://%s/", cfg.PTPClientAddress)
sm, err := stats.FetchStats(url)
if err != nil {
return nil, err
}
log.Debugf("TIME_STATUS_NP: %+v", sm)
for _, s := range sm {
if s.Selected {
accuracyNS := s.ClockQuality.ClockAccuracy.Duration().Nanoseconds()
return &DataPoint{
IngressTimeNS: s.IngressTime,
MasterOffsetNS: s.Offset,
PathDelayNS: s.MeanPathDelay,
ClockAccuracyNS: float64(int64(s.GMPresent) * accuracyNS),
}, nil
}
}
return nil, fmt.Errorf("no selected grandmaster")
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package daemon
import (
"fmt"
"net"
"os"
"path/filepath"
"time"
ptp "github.com/facebook/time/ptp/protocol"
log "github.com/sirupsen/logrus"
)
// SockFetcher provides data fetcher implementation using ptp4l Sock
type SockFetcher struct {
DataFetcher
}
// FetchGMs fetches gm data from ptp4l socket
func (sf *SockFetcher) FetchGMs(cfg *Config) (targets []string, err error) {
local := filepath.Join("/var/run/", fmt.Sprintf("fbclock.%d.linear.sock", os.Getpid()))
timeout := cfg.Interval / 2
conn, err := connect(cfg.PTPClientAddress, local, timeout)
defer func() {
if conn != nil {
conn.Close()
if f, err := conn.File(); err == nil {
f.Close()
}
}
// make sure there is no leftover socket
os.RemoveAll(local)
}()
if err != nil {
return targets, fmt.Errorf("failed to connect to ptp4l: %w", err)
}
c := &ptp.MgmtClient{
Connection: conn,
}
tlv, err := c.UnicastMasterTableNP()
if err != nil {
return targets, fmt.Errorf("getting UNICAST_MASTER_TABLE_NP from ptp4l: %w", err)
}
for _, entry := range tlv.UnicastMasterTable.UnicastMasters {
// skip the current best master
if entry.Selected {
continue
}
// skip GMs we didn't get announce from
if entry.PortState == ptp.UnicastMasterStateWait {
continue
}
server := entry.Address.String()
targets = append(targets, server)
}
return
}
// FetchStats fetches stats from ptp4l socket
func (sf *SockFetcher) FetchStats(cfg *Config) (*DataPoint, error) {
local := filepath.Join("/var/run/", fmt.Sprintf("fbclock.%d.sock", os.Getpid()))
timeout := cfg.Interval / 2
conn, err := connect(cfg.PTPClientAddress, local, timeout)
defer func() {
if conn != nil {
conn.Close()
if f, err := conn.File(); err == nil {
f.Close()
}
}
// make sure there is no leftover socket
os.RemoveAll(local)
}()
if err != nil {
return nil, fmt.Errorf("failed to connect to ptp4l: %w", err)
}
c := &ptp.MgmtClient{
Connection: conn,
}
status, err := c.TimeStatusNP()
if err != nil {
return nil, fmt.Errorf("failed to get TIME_STATUS_NP: %w", err)
}
log.Debugf("TIME_STATUS_NP: %+v", status)
pds, err := c.ParentDataSet()
if err != nil {
return nil, fmt.Errorf("failed to get PARENT_DATA_SET: %w", err)
}
cds, err := c.CurrentDataSet()
if err != nil {
return nil, fmt.Errorf("failed to get CURRENT_DATA_SET: %w", err)
}
accuracyNS := pds.GrandmasterClockQuality.ClockAccuracy.Duration().Nanoseconds()
return &DataPoint{
IngressTimeNS: status.IngressTimeNS,
MasterOffsetNS: float64(status.MasterOffsetNS),
PathDelayNS: cds.MeanPathDelay.Nanoseconds(),
ClockAccuracyNS: float64(int64(status.GMPresent) * accuracyNS),
}, nil
}
// connect creates connection to unix socket in unixgram mode
func connect(address, local string, timeout time.Duration) (*net.UnixConn, error) {
deadline := time.Now().Add(timeout)
addr, err := net.ResolveUnixAddr("unixgram", address)
if err != nil {
return nil, err
}
localAddr, _ := net.ResolveUnixAddr("unixgram", local)
conn, err := net.DialUnix("unixgram", localAddr, addr)
if err != nil {
return nil, err
}
if err := os.Chmod(local, 0666); err != nil {
return nil, err
}
if err := conn.SetReadDeadline(deadline); err != nil {
return nil, err
}
log.Debugf("connected to %s", address)
return conn, nil
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package daemon
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/facebook/time/fbclock"
"github.com/facebook/time/phc"
log "github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
// ManagedPTPDevicePath is the path we will set up a copy of iface's PHC device,
// so that fbclock clients can access it without explicit configuration.
const ManagedPTPDevicePath = string(fbclock.PTPPath)
// SetupDeviceDir creates a PHC device path from the interface name
func SetupDeviceDir(iface string) error {
// explicitly convert to string to prevent GOPLS from panicking here
target := ManagedPTPDevicePath
dir := filepath.Dir(target)
wantMode := os.ModeCharDevice | os.ModeDevice | 0644
device, err := phc.IfaceToPHCDevice(iface)
if err != nil {
return fmt.Errorf("getting PHC device from iface %q: %w", iface, err)
}
devInfo, err := os.Stat(device)
if err != nil {
return fmt.Errorf("getting PTP device %q info: %w", device, err)
}
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("preparing dir %s: %w", dir, err)
}
// check if it's already there
if targetInfo, err := os.Stat(target); err == nil {
if os.SameFile(devInfo, targetInfo) && targetInfo.Mode() == wantMode {
log.Debugf("Device %s already exists, nothing to link", target)
return nil
}
// remove the wrong file
if err := os.RemoveAll(target); err != nil {
log.Debugf("Removing %s", target)
return fmt.Errorf("removing target file %q: %w", target, err)
}
}
log.Debugf("Linking %s to %s", device, target)
if err := os.Link(device, target); err != nil {
return fmt.Errorf("linking device %s to %s: %w", device, target, err)
}
return os.Chmod(target, wantMode)
}
// TimeMonotonicRaw returns the current time from CLOCK_MONOTONIC_RAW
func TimeMonotonicRaw() (time.Time, error) {
var ts unix.Timespec
if err := unix.ClockGettime(unix.CLOCK_MONOTONIC_RAW, &ts); err != nil {
return time.Time{}, fmt.Errorf("failed clock_gettime: %w", err)
}
return time.Unix(ts.Unix()), nil
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package daemon
import (
"encoding/csv"
"fmt"
"io"
"math/rand"
"strconv"
"time"
)
// LogSample has all the measurements we may want to log
type LogSample struct {
MasterOffsetNS float64
MasterOffsetMeanNS float64
MasterOffsetStddevNS float64
PathDelayNS float64
PathDelayMeanNS float64
PathDelayStddevNS float64
FreqAdjustmentPPB float64
FreqAdjustmentMeanPPB float64
FreqAdjustmentStddevPPB float64
MeasurementNS float64
MeasurementMeanNS float64
MeasurementStddevNS float64
WindowNS float64
ClockAccuracyMean float64
}
var header = []string{
"offset",
"offset_mean",
"offset_stddev",
"delay",
"delay_mean",
"delay_stddev",
"freq",
"freq_mean",
"freq_stddev",
"measurement",
"measurement_mean",
"measurement_stddev",
"window",
"clock_accuracy_mean",
}
func shouldLog(sampleRate int) bool {
if sampleRate < 1 {
return false
}
// randomized sampling
return rand.Intn(sampleRate) == 0
}
// CSVRecords returns all data from this sample as CSV. Must by synced with `header` variable.
func (s *LogSample) CSVRecords() []string {
return []string{
strconv.FormatFloat(s.MasterOffsetNS, 'f', -1, 64),
strconv.FormatFloat(s.MasterOffsetMeanNS, 'f', -1, 64),
strconv.FormatFloat(s.MasterOffsetStddevNS, 'f', -1, 64),
strconv.FormatFloat(s.PathDelayNS, 'f', -1, 64),
strconv.FormatFloat(s.PathDelayMeanNS, 'f', -1, 64),
strconv.FormatFloat(s.PathDelayStddevNS, 'f', -1, 64),
strconv.FormatFloat(s.FreqAdjustmentPPB, 'f', -1, 64),
strconv.FormatFloat(s.FreqAdjustmentMeanPPB, 'f', -1, 64),
strconv.FormatFloat(s.FreqAdjustmentStddevPPB, 'f', -1, 64),
strconv.FormatFloat(s.MeasurementNS, 'f', -1, 64),
strconv.FormatFloat(s.MeasurementMeanNS, 'f', -1, 64),
strconv.FormatFloat(s.MeasurementStddevNS, 'f', -1, 64),
strconv.FormatFloat(s.WindowNS, 'f', -1, 64),
strconv.FormatFloat(s.ClockAccuracyMean, 'f', -1, 64),
}
}
// Logger is something that can store LogSample somewhere
type Logger interface {
Log(*LogSample) error
}
// CSVLogger logs Sample as CSV into given writer
type CSVLogger struct {
csvwriter *csv.Writer
sampleRate int
printedHeader bool
}
// NewCSVLogger returns new CSVLogger
func NewCSVLogger(w io.Writer, sampleRate int) *CSVLogger {
return &CSVLogger{
csvwriter: csv.NewWriter(w),
sampleRate: sampleRate,
}
}
// Log implements Logger interface
func (l *CSVLogger) Log(s *LogSample) error {
if !l.printedHeader {
if err := l.csvwriter.Write(header); err != nil {
return err
}
l.printedHeader = true
}
if !shouldLog(l.sampleRate) {
return nil
}
csv := s.CSVRecords()
if err := l.csvwriter.Write(csv); err != nil {
return err
}
l.csvwriter.Flush()
return nil
}
// DummyLogger logs M and W to given writer
type DummyLogger struct {
w io.Writer
sampleRate int
}
// NewDummyLogger returns new DummyLogger
func NewDummyLogger(w io.Writer, sampleRate int) *DummyLogger {
return &DummyLogger{w: w, sampleRate: sampleRate}
}
// Log implements Logger interface
func (l *DummyLogger) Log(s *LogSample) error {
if !shouldLog(l.sampleRate) {
return nil
}
_, err := fmt.Fprintf(l.w, "m = %v, w = %v\n", time.Duration(s.MeasurementNS), time.Duration(s.WindowNS))
return err
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package daemon
import (
"fmt"
"math"
"github.com/Knetic/govaluate"
"github.com/eclesh/welford"
)
// MathHelp is a help message used by flags in main
const MathHelp = `When composing the -m and -w formulas, here is what you can do:
supported operations:
evaluation is done with govaluate, please check https://github.com/Knetic/govaluate/blob/master/MANUAL.md
supported variables:
offset (list of last offsets from GM, in ns)
delay (list of last path delays, in ns)
freq (list of last frequency adjustments, in PPM)
clockaccuracy (list of clock accuracy values received from GM)
freqchange (list of last changes in frequency)
freqchangeabs (list of last changes in frequency, abs values)
supported functions:
abs(value) - absolute value of single float64, for example abs(-1) = 1
mean(values, number) - mean of list of 'number' values, for example mean(offset, 10) will take 10 elements from array 'offset' and return mean for those values
variance(values, number) - variance of list of 'number' values, for example variance(offset, 10) will take 10 elements from array 'offset' and return variance for those values
stddev(values, number) - standard deviation of list of 'number' values, for example stddev(offset, 10) will take 10 elements from array 'offset' and return standard deviation for those values`
const (
// MathDefaultHistory is a default number of samples to keep
MathDefaultHistory = 100
// MathDefaultM is a default formula to calculate M
MathDefaultM = "mean(clockaccuracy, 100) + abs(mean(offset, 100)) + 1.0 * stddev(offset, 100)"
// MathDefaultW is a default formula to calculate W
MathDefaultW = "mean(m, 100) + 4.0 * stddev(m, 100)"
// MathDefaultDrift is a default formula to calculate default drift
MathDefaultDrift = "1.5 * mean(freqchangeabs, 99)"
)
// Math stores our math expressions for M ans W values in two forms: string and parsed
type Math struct {
M string // Measurement, our value for clock quality
mExpr *govaluate.EvaluableExpression
W string // window of uncertainty, without adjustment for potential holdover
wExpr *govaluate.EvaluableExpression
Drift string // drift in PPB, for holdover multiplier calculations
driftExpr *govaluate.EvaluableExpression
}
// Prepare will prepare all math expressions
func (m *Math) Prepare() error {
var err error
m.mExpr, err = prepareExpression(m.M)
if err != nil {
return fmt.Errorf("evaluating M: %w", err)
}
m.wExpr, err = prepareExpression(m.W)
if err != nil {
return fmt.Errorf("evaluating W: %w", err)
}
m.driftExpr, err = prepareExpression(m.Drift)
if err != nil {
return fmt.Errorf("evaluating Drift: %w", err)
}
return nil
}
func mean(input []float64) float64 {
s := welford.New()
for _, v := range input {
s.Add(v)
}
return s.Mean()
}
func variance(input []float64) float64 {
s := welford.New()
for _, v := range input {
s.Add(v)
}
return s.Variance()
}
func stddev(input []float64) float64 {
s := welford.New()
for _, v := range input {
s.Add(v)
}
return s.Stddev()
}
// convolve mixes two signals together
func convolve(input, coeffs []float64) ([]float64, error) {
if len(input) < len(coeffs) {
return nil, fmt.Errorf("not enough values")
}
output := make([]float64, len(input))
for i := 0; i < len(coeffs); i++ {
var sum float64
for j := 0; j <= i; j++ {
sum += (input[j] * coeffs[len(coeffs)-(1+i-j)])
}
output[i] = sum
}
for i := len(coeffs); i < len(input); i++ {
var sum float64
for j := 0; j < len(coeffs); j++ {
sum += (input[i-j] * coeffs[j])
}
output[i] = sum
}
return output, nil
}
var supportedVariables = []string{
"offset",
"delay",
"freq",
"m",
"clockaccuracy",
"freqchange",
"freqchangeabs",
}
func isSupportedVar(varName string) bool {
for _, v := range supportedVariables {
if v == varName {
return true
}
}
return false
}
// all the functions we support in expressions
var functions = map[string]govaluate.ExpressionFunction{
"abs": func(args ...interface{}) (interface{}, error) {
if len(args) != 1 {
return nil, fmt.Errorf("abs: wrong number of arguments: want 1, got %d", len(args))
}
val := args[0].(float64)
return math.Abs(val), nil
},
"mean": func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, fmt.Errorf("mean: wrong number of arguments: want 2, got %d", len(args))
}
vals := args[0].([]float64)
nSamples := int(args[1].(float64))
if len(vals) < nSamples {
return mean(vals), nil
}
return mean(vals[:nSamples]), nil
},
"variance": func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, fmt.Errorf("variance: wrong number of arguments: want 2, got %d", len(args))
}
vals := args[0].([]float64)
nSamples := int(args[1].(float64))
if len(vals) < nSamples {
return variance(vals), nil
}
return variance(vals[:nSamples]), nil
},
"stddev": func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, fmt.Errorf("stddev: wrong number of arguments: want 2, got %d", len(args))
}
vals := args[0].([]float64)
nSamples := int(args[1].(float64))
if len(vals) < nSamples {
return stddev(vals), nil
}
return stddev(vals[:nSamples]), nil
},
}
func prepareExpression(exprStr string) (*govaluate.EvaluableExpression, error) {
expr, err := govaluate.NewEvaluableExpressionWithFunctions(exprStr, functions)
if err != nil {
return nil, err
}
for _, v := range expr.Vars() {
if !isSupportedVar(v) {
return nil, fmt.Errorf("unsupported variable %q", v)
}
}
return expr, nil
}
func prepareMathParameters(lastN []*DataPoint) map[string][]float64 {
size := len(lastN)
offsets := make([]float64, size)
delays := make([]float64, size)
freqs := make([]float64, size)
clockAccuracies := make([]float64, size)
freqChanges := make([]float64, size-1)
freqChangesAbs := make([]float64, size-1)
prev := lastN[0]
for i := 0; i < size; i++ {
offsets[i] = lastN[i].MasterOffsetNS
delays[i] = lastN[i].PathDelayNS
freqs[i] = lastN[i].FreqAdjustmentPPB
clockAccuracies[i] = lastN[i].ClockAccuracyNS
if i != 0 {
freqChanges[i-1] = lastN[i].FreqAdjustmentPPB - prev.FreqAdjustmentPPB
freqChangesAbs[i-1] = math.Abs(lastN[i].FreqAdjustmentPPB - prev.FreqAdjustmentPPB)
}
prev = lastN[i]
}
return map[string][]float64{
"offset": offsets,
"delay": delays,
"freq": freqs,
"clockaccuracy": clockAccuracies,
"freqchange": freqChanges,
"freqchangeabs": freqChangesAbs,
}
}
func mapOfInterface(m map[string][]float64) map[string]interface{} {
mm := make(map[string]interface{}, len(m))
for k, v := range m {
mm[k] = v
}
return mm
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package daemon
import (
"container/ring"
"math"
"sync"
"github.com/facebook/time/fbclock"
"github.com/facebook/time/ptp/linearizability"
)
// state of the daemon, guarded by mutex
type daemonState struct {
sync.Mutex
DataPoints *ring.Ring // DataPoints we collected from ptp4l
mmms *ring.Ring // M values we calculated
linearizabilityTestResults *ring.Ring // linearizability test results
lastIngressTimeNS int64
lastStoredData *fbclock.Data
}
func newDaemonState(ringSize int) *daemonState {
s := &daemonState{
DataPoints: ring.New(ringSize),
mmms: ring.New(ringSize),
linearizabilityTestResults: ring.New(ringSize),
}
// init ring buffers with nils
for i := 0; i < ringSize; i++ {
s.DataPoints.Value = nil
s.DataPoints = s.DataPoints.Next()
s.mmms.Value = nil
s.mmms = s.mmms.Next()
s.linearizabilityTestResults.Value = nil
s.linearizabilityTestResults = s.linearizabilityTestResults.Next()
}
return s
}
func (s *daemonState) updateIngressTimeNS(it int64) {
s.Lock()
defer s.Unlock()
s.lastIngressTimeNS = it
}
func (s *daemonState) ingressTimeNS() int64 {
s.Lock()
defer s.Unlock()
return s.lastIngressTimeNS
}
func (s *daemonState) pushDataPoint(data *DataPoint) {
s.Lock()
defer s.Unlock()
s.DataPoints.Value = data
s.DataPoints = s.DataPoints.Next()
}
func (s *daemonState) takeDataPoint(n int) []*DataPoint {
s.Lock()
defer s.Unlock()
result := []*DataPoint{}
r := s.DataPoints.Prev()
for j := 0; j < n; j++ {
if r.Value == nil {
continue
}
result = append(result, r.Value.(*DataPoint))
r = r.Prev()
}
return result
}
func (s *daemonState) aggregateDataPointsMax(n int) *DataPoint {
s.Lock()
defer s.Unlock()
d := &DataPoint{}
r := s.DataPoints.Prev()
for j := 0; j < n; j++ {
if r.Value == nil {
continue
}
dp := r.Value.(*DataPoint)
if math.Abs(dp.MasterOffsetNS) > d.MasterOffsetNS {
d.MasterOffsetNS = math.Abs(dp.MasterOffsetNS)
}
if math.Abs(dp.PathDelayNS) > d.PathDelayNS {
d.PathDelayNS = math.Abs(dp.PathDelayNS)
}
if math.Abs(dp.FreqAdjustmentPPB) > d.FreqAdjustmentPPB {
d.FreqAdjustmentPPB = math.Abs(dp.FreqAdjustmentPPB)
}
r = r.Prev()
}
return d
}
func (s *daemonState) pushM(data float64) {
s.Lock()
defer s.Unlock()
s.mmms.Value = data
s.mmms = s.mmms.Next()
}
func (s *daemonState) takeM(n int) []float64 {
s.Lock()
defer s.Unlock()
result := []float64{}
r := s.mmms.Prev()
for j := 0; j < n; j++ {
if r.Value == nil {
continue
}
result = append(result, r.Value.(float64))
r = r.Prev()
}
return result
}
func (s *daemonState) pushLinearizabilityTestResult(data linearizability.TestResult) {
s.Lock()
defer s.Unlock()
s.linearizabilityTestResults.Value = data
s.linearizabilityTestResults = s.linearizabilityTestResults.Next()
}
func (s *daemonState) takeLinearizabilityTestResult(n int) []linearizability.TestResult {
s.Lock()
defer s.Unlock()
result := []linearizability.TestResult{}
r := s.linearizabilityTestResults.Prev()
for j := 0; j < n; j++ {
if r.Value == nil {
continue
}
result = append(result, r.Value.(linearizability.TestResult))
r = r.Prev()
}
return result
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package leaphash is a utility package for computing the hash value of the
// official leap-second.list document
package leaphash
import (
"crypto/sha1"
"fmt"
"strings"
)
// Compute returns the sha1 sum of all non whitespace characters in data
// excluding comments. Since it includes logic for special lines specific
// to the leap-second.list format its not a general purpose function and
// should be only used to verify the integrity of an official leap-second.list
// document.
func Compute(data string) string {
lines := strings.Split(data, "\n")
var filtered string
filterBlanks := func(r rune) rune {
if r == ' ' || r == '\t' {
return -1
}
return r
}
for i := 0; i < len(lines); i++ {
if strings.HasPrefix(lines[i], "#$") || strings.HasPrefix(lines[i], "#@") {
// "#$" - last modification time
// "#@" - the expiration time of the file
filtered += strings.Map(filterBlanks, lines[i][2:len(lines[i])])
} else if !strings.HasPrefix(lines[i], "#") {
// leap second lines, without comments and without any blank characters
line := lines[i]
commentPos := strings.Index(line, "#")
if commentPos != -1 {
line = line[0 : commentPos-1]
}
filtered += strings.Map(filterBlanks, line)
}
}
// checksum
hash := fmt.Sprintf("%x", sha1.Sum([]byte(filtered)))
var groupedHash string
// group checksum by 8 characters
for i := 0; i < 5; i++ {
if groupedHash != "" {
groupedHash += " "
}
groupedHash += hash[i*8 : (i+1)*8]
}
return groupedHash
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package leapsectz is a utility package for obtaining leap second
// information from the system timezone database
package leapsectz
import (
"bytes"
"encoding/binary"
"errors"
"io"
"os"
"time"
)
// leapFile is a file containing leap second information
var leapFile = "/usr/share/zoneinfo/right/UTC"
var errBadData = errors.New("malformed time zone information")
var errUnsupportedVersion = errors.New("unsupported version")
var errNoLeapSeconds = errors.New("no leap seconds information found")
// LeapSecond represents a leap second
type LeapSecond struct {
Tleap uint64
Nleap int32
}
// Header represents file header structure. Fields names are copied from doc
type Header struct {
// A four-octet unsigned integer specifying the number of UTC/local indicators contained in the body.
IsUtcCnt uint32
// A four-octet unsigned integer specifying the number of standard/wall indicators contained in the body.
IsStdCnt uint32
// A four-octet unsigned integer specifying the number of leap second records contained in the body.
LeapCnt uint32
// A four-octet unsigned integer specifying the number of transition times contained in the body.
TimeCnt uint32
// A four-octet unsigned integer specifying the number of local time type Records contained in the body - MUST NOT be zero.
TypeCnt uint32
// A four-octet unsigned integer specifying the total number of octets used by the set of time zone designations contained in the body.
CharCnt uint32
}
// Time returns when the leap second event occurs
func (l LeapSecond) Time() time.Time {
return time.Unix(int64(l.Tleap-uint64(l.Nleap)+1), 0)
}
// Parse returns the list of leap seconds from srcfile. Pass "" to use default file
func Parse(srcfile string) ([]LeapSecond, error) {
if srcfile == "" {
srcfile = leapFile
}
f, err := os.Open(srcfile)
if err != nil {
return nil, err
}
defer f.Close()
return parseVx(f)
}
// Latest returns the latest leap second from srcfile. Pass "" to use default file
func Latest(srcfile string) (*LeapSecond, error) {
res := LeapSecond{}
leapSeconds, err := Parse(srcfile)
if err != nil {
return nil, err
}
for _, leapSecond := range leapSeconds {
if leapSecond.Time().After(res.Time()) && leapSecond.Time().Before(time.Now()) {
res = leapSecond
}
}
return &res, nil
}
func parseVx(r io.Reader) ([]LeapSecond, error) {
var ret []LeapSecond
var v byte
for v = 0; v < 2; v++ {
// 4-byte magic "TZif"
magic := make([]byte, 4)
if _, _ = r.Read(magic); string(magic) != "TZif" {
return nil, errBadData
}
// 1-byte version, then 15 bytes of padding
var version byte
p := make([]byte, 16)
if n, _ := r.Read(p); n != 16 {
return nil, errBadData
}
version = p[0]
if version != 0 && version != '2' && version != '3' {
return nil, errUnsupportedVersion
}
if v > version {
return nil, errBadData
}
var hdr Header
err := binary.Read(r, binary.BigEndian, &hdr)
if err != nil {
return nil, err
}
// skip uninteresting data:
// tzh_timecnt (char [4] or char [8] for ver 2)s coded transition times a la time(2)
// tzh_timecnt (unsigned char)s types of local time starting at above
// tzh_typecnt repetitions of
// one (char [4]) coded UT offset in seconds
// one (unsigned char) used to set tm_isdst
// one (unsigned char) that's an abbreviation list index
// tzh_charcnt (char)s '\0'-terminated zone abbreviations
var skip int
if v == 0 {
skip = int(hdr.TimeCnt)*5 + int(hdr.TypeCnt)*6 + int(hdr.CharCnt)
} else {
skip = int(hdr.TimeCnt)*9 + int(hdr.TypeCnt)*6 + int(hdr.CharCnt)
}
// if it's first part of two parts file (version 2 or 3)
// then skip it completely
if v == 0 && version > 0 {
skip += int(hdr.LeapCnt)*8 + int(hdr.IsUtcCnt) + int(hdr.IsStdCnt)
}
if n, _ := io.CopyN(io.Discard, r, int64(skip)); n != int64(skip) {
return nil, errBadData
}
if v == 0 && version > 0 {
continue
}
// calculate the amount of bytes to skip after reading leap seconds array
skip = int(hdr.IsUtcCnt) + int(hdr.IsStdCnt)
for i := 0; i < int(hdr.LeapCnt); i++ {
var l LeapSecond
if version == 0 {
lsv0 := []uint32{0, 0}
err := binary.Read(r, binary.BigEndian, &lsv0)
if err != nil {
return nil, err
}
l.Tleap = uint64(lsv0[0])
l.Nleap = int32(lsv0[1])
} else {
err := binary.Read(r, binary.BigEndian, &l)
if err != nil {
return nil, err
}
}
ret = append(ret, l)
}
// we need to skip the rest of the data
_, _ = io.CopyN(io.Discard, r, int64(skip))
break
}
if len(ret) == 0 {
return nil, errNoLeapSeconds
}
return ret, nil
}
func prepareHeader(ver byte, lsCnt int, name string) []byte {
const magicHeader = "TZif"
h := new(bytes.Buffer)
hdr := Header{
IsUtcCnt: 1,
IsStdCnt: 1,
LeapCnt: uint32(lsCnt),
TimeCnt: 0,
TypeCnt: 1, //mandatory >0
CharCnt: uint32(len(name)),
}
h.WriteString(magicHeader)
h.WriteByte(ver)
padding := make([]byte, 15)
h.Write(padding)
_ = binary.Write(h, binary.BigEndian, hdr)
return h.Bytes()
}
func writePreData(f io.Writer, name string) error {
// we have zero-sized transition times array - skip it
// one mandatory "local time type Record"
var sixZeros = []byte{0, 0, 0, 0, 0, 0}
if _, err := f.Write(sixZeros); err != nil {
return err
}
// null terminated string of time zone
if _, err := io.WriteString(f, name); err != nil {
return err
}
return nil
}
func writePostData(f io.Writer) error {
var twoZeros = []byte{0, 0}
if _, err := f.Write(twoZeros); err != nil {
return err
}
return nil
}
// Write dumps arrays of leap seconds into file with newly created header
func Write(f io.Writer, ver byte, ls []LeapSecond, name string) error {
if ver != 0 && ver != '2' {
return errUnsupportedVersion
}
var nameFormatted string
if name == "" {
nameFormatted = "UTC\x00"
} else {
nameFormatted = name + "\x00"
}
// prepare header which will be reused in case of version 2
hdr := prepareHeader(ver, len(ls), nameFormatted)
// Write prepared header
if _, err := f.Write(hdr); err != nil {
return err
}
// data before array of leap seconds
if err := writePreData(f, nameFormatted); err != nil {
return err
}
// array of leap seconds
for i := 0; i < len(ls); i++ {
l := []uint32{uint32(ls[i].Tleap), uint32(ls[i].Nleap)}
if err := binary.Write(f, binary.BigEndian, &l); err != nil {
return err
}
}
// write data after leap seconds array
if err := writePostData(f); err != nil {
return err
}
if ver != '2' {
return nil
}
// now we have to write version 2 part of file
// prepared header could be reused
if _, err := f.Write(hdr); err != nil {
return err
}
// data before array of leap seconds
if err := writePreData(f, nameFormatted); err != nil {
return err
}
// array of leap seconds version 2
for i := 0; i < len(ls); i++ {
l := ls[i]
if err := binary.Write(f, binary.BigEndian, &l); err != nil {
return err
}
}
// write data after leap seconds array
if err := writePostData(f); err != nil {
return err
}
// and now we have to write POSIZ TZ string along with new line separators
// usually it's the same string as in the header
posixTz := "\n" + name + "\n"
if _, err := io.WriteString(f, posixTz); err != nil {
return err
}
return nil
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chrony
import (
"encoding/binary"
"io"
)
// Client talks to chronyd
type Client struct {
Connection io.ReadWriter
Sequence uint32
}
// Communicate sends the packet to chronyd, parse response into something usable
func (n *Client) Communicate(packet RequestPacket) (ResponsePacket, error) {
n.Sequence++
var err error
packet.SetSequence(n.Sequence)
err = binary.Write(n.Connection, binary.BigEndian, packet)
if err != nil {
return nil, err
}
response := make([]uint8, 1024)
read, err := n.Connection.Read(response)
if err != nil {
return nil, err
}
return decodePacket(response[:read])
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chrony
import (
"fmt"
"math"
"net"
"strconv"
"time"
)
// ChronySocketPath is the default path to chronyd socket
const ChronySocketPath = "/var/run/chrony/chronyd.sock"
// ChronyPortV6Regexp is a regexp to find anything that listens on port 323
// hex(323) = '0x143'
const ChronyPortV6Regexp = "[0-9]+: [0-9A-Z]+:0143 .*"
// This is used in timeSpec.SecHigh for 32-bit timestamps
const noHighSec uint32 = 0x7fffffff
// ip stuff
const (
ipAddrInet4 uint16 = 1
ipAddrInet6 uint16 = 2
)
// magic numbers to convert chronyFloat to normal float
const (
floatExpBits = 7
floatCoefBits = (4*8 - floatExpBits)
)
type ipAddr struct {
IP [16]uint8
Family uint16
Pad uint16
}
func (ip *ipAddr) ToNetIP() net.IP {
if ip.Family == ipAddrInet4 {
return net.IP(ip.IP[:4])
}
return net.IP(ip.IP[:])
}
func newIPAddr(ip net.IP) *ipAddr {
family := ipAddrInet6
if ip.To4() != nil {
family = ipAddrInet4
}
var nIP [16]byte
copy(nIP[:], ip)
return &ipAddr{
IP: nIP,
Family: family,
}
}
type timeSpec struct {
SecHigh uint32
SecLow uint32
Nsec uint32
}
func (t *timeSpec) ToTime() time.Time {
highU64 := uint64(t.SecHigh)
if t.SecHigh == noHighSec {
highU64 = 0
}
lowU64 := uint64(t.SecLow)
return time.Unix(int64(highU64<<32|lowU64), int64(t.Nsec))
}
/*
32-bit floating-point format consisting of 7-bit signed exponent
and 25-bit signed coefficient without hidden bit.
The result is calculated as: 2^(exp - 25) * coef
*/
type chronyFloat int32
// ToFloat does magic to decode float from int32.
// Code is copied and translated to Go from original C sources.
func (f chronyFloat) ToFloat() float64 {
var exp, coef int32
x := uint32(f)
exp = int32(x >> floatCoefBits)
if exp >= 1<<(floatExpBits-1) {
exp -= 1 << floatExpBits
}
exp -= floatCoefBits
coef = int32(x % (1 << floatCoefBits))
if coef >= 1<<(floatCoefBits-1) {
coef -= 1 << floatCoefBits
}
return float64(coef) * math.Pow(2.0, float64(exp))
}
// RefidAsHEX prints ref id as hex
func RefidAsHEX(refID uint32) string {
return fmt.Sprintf("%08X", refID)
}
// RefidToString decodes ASCII string encoded as uint32
func RefidToString(refID uint32) string {
result := []rune{}
for i := range 4 {
c := rune((refID >> (24 - uint(i)*8)) & 0xff)
if c == 0 {
continue
}
if strconv.IsPrint(c) {
result = append(result, c)
} else {
return RefidAsHEX(refID)
}
}
return string(result)
}
/* NTP tests from RFC 5905:
+--------------------------+----------------------------------------+
| Packet Type | Description |
+--------------------------+----------------------------------------+
| 1 duplicate packet | The packet is at best an old duplicate |
| | or at worst a replay by a hacker. |
| | This can happen in symmetric modes if |
| | the poll intervals are uneven. |
| 2 bogus packet | |
| 3 invalid | One or more timestamp fields are |
| | invalid. This normally happens in |
| | symmetric modes when one peer sends |
| | the first packet to the other and |
| | before the other has received its |
| | first reply. |
| 4 access denied | The access controls have blacklisted |
| | the source. |
| 5 authentication failure | The cryptographic message digest does |
| | not match the MAC. |
| 6 unsynchronized | The server is not synchronized to a |
| | valid source. |
| 7 bad header data | One or more header fields are invalid. |
+--------------------------+----------------------------------------+
chrony doesn't do test #4, but adds four extra tests:
* maximum delay
* delay ratio
* delay dev ratio
* synchronisation loop.
Those tests are roughly equivalent to ntpd 'flashers'
*/
// NTPTestDescMap maps bit mask with corresponding flash status
var NTPTestDescMap = map[uint16]string{
0x0001: "pkt_dup",
0x0002: "pkt_bogus",
0x0004: "pkt_invalid",
0x0008: "pkt_auth",
0x0010: "pkt_stratum",
0x0020: "pkt_header",
0x0040: "tst_max_delay",
0x0080: "tst_delay_ratio",
0x0100: "tst_delay_dev_ration",
0x0200: "tst_sync_loop",
}
// ReadNTPTestFlags returns list of failed ntp test flags (as strings)
func ReadNTPTestFlags(flags uint16) []string {
testFlags := flags & NTPFlagsTests
results := []string{}
for mask, message := range NTPTestDescMap {
if testFlags&mask == 0 {
results = append(results, message)
}
}
return results
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chrony
// LoggerInterface is an interface for debug logging.
type LoggerInterface interface {
Printf(format string, v ...interface{})
}
type noopLogger struct{}
func (noopLogger) Printf(_ string, _ ...interface{}) {}
// Logger is a default debug logger which simply discards all messages.
// It can be overridden by setting the global variable to a different implementation, like std log
//
// chrony.Logger = log.New(os.Stderr, "", 0)
//
// or logrus
//
// chrony.Logger = logrus.StandardLogger()
var Logger LoggerInterface = &noopLogger{}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chrony
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"time"
)
// original C++ versions of those consts/structs
// are in https://github.com/mlichvar/chrony/blob/master/candm.h
// ReplyType identifies reply packet type
type ReplyType uint16
// CommandType identifies command type in both request and repy
type CommandType uint16
// ModeType identifies source (peer) mode
type ModeType uint16
// SourceStateType identifies source (peer) state
type SourceStateType uint16
// ResponseStatusType identifies response status
type ResponseStatusType uint16
// PacketType - request or reply
type PacketType uint8
// we implement latest (at the moment) protocol version
const protoVersionNumber uint8 = 6
const maxDataLen = 396
// packet types
const (
pktTypeCmdRequest PacketType = 1
pktTypeCmdReply PacketType = 2
)
func (t PacketType) String() string {
switch t {
case pktTypeCmdRequest:
return "request"
case pktTypeCmdReply:
return "reply"
default:
return fmt.Sprintf("unknown (%d)", t)
}
}
// request types. Only those we support, there are more
const (
reqNSources CommandType = 14
reqSourceData CommandType = 15
reqTracking CommandType = 33
reqSourceStats CommandType = 34
reqActivity CommandType = 44
reqServerStats CommandType = 54
reqNTPData CommandType = 57
reqNTPSourceName CommandType = 65
reqSelectData CommandType = 69
)
// reply types
const (
RpyNSources ReplyType = 2
RpySourceData ReplyType = 3
RpyTracking ReplyType = 5
RpySourceStats ReplyType = 6
RpyActivity ReplyType = 12
RpyServerStats ReplyType = 14
RpyNTPData ReplyType = 16
RpyNTPSourceName ReplyType = 19
RpyServerStats2 ReplyType = 22
RpySelectData ReplyType = 23
RpyServerStats3 ReplyType = 24
RpyServerStats4 ReplyType = 25
RpyNTPData2 ReplyType = 26
)
// source modes
const (
SourceModeClient ModeType = 0
SourceModePeer ModeType = 1
SourceModeRef ModeType = 2
)
// source state
const (
SourceStateSync SourceStateType = 0
SourceStateUnreach SourceStateType = 1
SourceStateFalseTicker SourceStateType = 2
SourceStateJittery SourceStateType = 3
SourceStateCandidate SourceStateType = 4
SourceStateOutlier SourceStateType = 5
)
// source data flags
const (
FlagNoselect uint16 = 0x1
FlagPrefer uint16 = 0x2
FlagTrust uint16 = 0x4
FlagRequire uint16 = 0x8
)
// select data flags
const (
FlagSDOptionNoSelect uint16 = 0x1
FlagSDOptionPrefer uint16 = 0x2
FlagSDOptionTrust uint16 = 0x4
FlagSDOptionRequire uint16 = 0x8
)
// ntpdata flags
const (
NTPFlagsTests uint16 = 0x3ff
NTPFlagInterleaved uint16 = 0x4000
NTPFlagAuthenticated uint16 = 0x8000
)
// response status codes
const (
sttSuccess ResponseStatusType = 0
sttFailed ResponseStatusType = 1
sttUnauth ResponseStatusType = 2
sttInvalid ResponseStatusType = 3
sttNoSuchSource ResponseStatusType = 4
sttInvalidTS ResponseStatusType = 5
sttNotEnabled ResponseStatusType = 6
sttBadSubnet ResponseStatusType = 7
sttAccessAllowed ResponseStatusType = 8
sttAccessDenied ResponseStatusType = 9
sttNoHostAccess ResponseStatusType = 10
sttSourceAlreadyKnown ResponseStatusType = 11
sttTooManySources ResponseStatusType = 12
sttNoRTC ResponseStatusType = 13
sttBadRTCFile ResponseStatusType = 14
sttInactive ResponseStatusType = 15
sttBadSample ResponseStatusType = 16
sttInvalidAF ResponseStatusType = 17
sttBadPktVersion ResponseStatusType = 18
sttBadPktLength ResponseStatusType = 19
)
// StatusDesc provides mapping from ResponseStatusType to string
var StatusDesc = [20]string{
"SUCCESS",
"FAILED",
"UNAUTH",
"INVALID",
"NOSUCHSOURCE",
"INVALIDTS",
"NOTENABLED",
"BADSUBNET",
"ACCESSALLOWED",
"ACCESSDENIED",
"NOHOSTACCESS",
"SOURCEALREADYKNOWN",
"TOOMANYSOURCES",
"NORTC",
"BADRTCFILE",
"INACTIVE",
"BADSAMPLE",
"INVALIDAF",
"BADPKTVERSION",
"BADPKTLENGTH",
}
func (r ResponseStatusType) String() string {
if int(r) >= len(StatusDesc) {
return fmt.Sprintf("UNKNOWN (%d)", r)
}
return StatusDesc[r]
}
// SourceStateDesc provides mapping from SourceStateType to string
var SourceStateDesc = [6]string{
"sync",
"unreach",
"falseticker",
"jittery",
"candidate",
"outlier",
}
func (s SourceStateType) String() string {
if int(s) >= len(SourceStateDesc) {
return fmt.Sprintf("unknown (%d)", s)
}
return SourceStateDesc[s]
}
// ModeTypeDesc provides mapping from ModeType to string
var ModeTypeDesc = [3]string{
"client",
"peer",
"reference clock",
}
func (m ModeType) String() string {
if int(m) >= len(ModeTypeDesc) {
return fmt.Sprintf("unknown (%d)", m)
}
return ModeTypeDesc[m]
}
// RequestHead is the first (common) part of the request,
// in a format that can be directly passed to binary.Write
type RequestHead struct {
Version uint8
PKTType PacketType
Res1 uint8
Res2 uint8
Command CommandType
Attempt uint16
Sequence uint32
Pad1 uint32
Pad2 uint32
}
// GetCommand returns request packet command
func (r *RequestHead) GetCommand() CommandType {
return r.Command
}
// SetSequence sets request packet sequence number
func (r *RequestHead) SetSequence(n uint32) {
r.Sequence = n
}
// RequestPacket is an interface to abstract all different outgoing packets
type RequestPacket interface {
GetCommand() CommandType
SetSequence(n uint32)
}
// ResponsePacket is an interface to abstract all different incoming packets
type ResponsePacket interface {
GetCommand() CommandType
GetType() ReplyType
GetStatus() ResponseStatusType
}
// RequestSources - packet to request number of sources (peers)
type RequestSources struct {
RequestHead
// we actually need this to send proper packet
data [maxDataLen]uint8
}
// RequestSourceData - packet to request source data for source id
type RequestSourceData struct {
RequestHead
Index int32
EOR int32
// we pass i32 - 4 bytes
data [maxDataLen - 4]uint8
}
// RequestNTPData - packet to request NTP data for peer IP.
// As of now, it's only allowed by Chrony over unix socket connection.
type RequestNTPData struct {
RequestHead
IPAddr ipAddr
EOR int32
// we pass at max ipv6 addr - 16 bytes
data [maxDataLen - 16]uint8
}
// RequestNTPSourceName - packet to request source name for peer IP.
type RequestNTPSourceName struct {
RequestHead
IPAddr ipAddr
EOR int32
// we pass at max ipv6 addr - 16 bytes
data [maxDataLen - 16]uint8
}
// RequestServerStats - packet to request server stats
type RequestServerStats struct {
RequestHead
// we actually need this to send proper packet
data [maxDataLen]uint8
}
// RequestTracking - packet to request 'tracking' data
type RequestTracking struct {
RequestHead
// we actually need this to send proper packet
data [maxDataLen]uint8
}
// RequestSourceStats - packet to request 'sourcestats' data for source id
type RequestSourceStats struct {
RequestHead
Index int32
EOR int32
// we pass i32 - 4 bytes
data [maxDataLen - 4]uint8
}
// RequestActivity - packet to request 'activity' data
type RequestActivity struct {
RequestHead
// we actually need this to send proper packet
data [maxDataLen]uint8
}
// RequestSelectData - packet to request 'selectdata' data
type RequestSelectData struct {
RequestHead
Index int32
EOR int32
// we pass i32 - 4 bytes
data [maxDataLen - 4]uint8
}
// ReplyHead is the first (common) part of the reply packet,
// in a format that can be directly passed to binary.Read
type ReplyHead struct {
Version uint8
PKTType PacketType
Res1 uint8
Res2 uint8
Command CommandType
Reply ReplyType
Status ResponseStatusType
Pad1 uint16
Pad2 uint16
Pad3 uint16
Sequence uint32
Pad4 uint32
Pad5 uint32
}
// GetCommand returns reply packet command
func (r *ReplyHead) GetCommand() CommandType {
return r.Command
}
// GetType returns reply packet type
func (r *ReplyHead) GetType() ReplyType {
return r.Reply
}
// GetStatus returns reply packet status
func (r *ReplyHead) GetStatus() ResponseStatusType {
return r.Status
}
type replySourcesContent struct {
NSources uint32
}
// ReplySources is a usable version of a reply to 'sources' command
type ReplySources struct {
ReplyHead
NSources int
}
type replySourceDataContent struct {
IPAddr ipAddr
Poll int16
Stratum uint16
State SourceStateType
Mode ModeType
Flags uint16
Reachability uint16
SinceSample uint32
OrigLatestMeas chronyFloat
LatestMeas chronyFloat
LatestMeasErr chronyFloat
}
// SourceData contains parsed version of 'source data' reply
type SourceData struct {
IPAddr net.IP
Poll int16
Stratum uint16
State SourceStateType
Mode ModeType
Flags uint16
Reachability uint16
SinceSample uint32
OrigLatestMeas float64
LatestMeas float64
LatestMeasErr float64
}
func newSourceData(r *replySourceDataContent) *SourceData {
return &SourceData{
IPAddr: r.IPAddr.ToNetIP(),
Poll: r.Poll,
Stratum: r.Stratum,
State: r.State,
Mode: r.Mode,
Flags: r.Flags,
Reachability: r.Reachability,
SinceSample: r.SinceSample,
OrigLatestMeas: r.OrigLatestMeas.ToFloat(),
LatestMeas: r.LatestMeas.ToFloat(),
LatestMeasErr: r.LatestMeasErr.ToFloat(),
}
}
// ReplySourceData is a usable version of 'source data' reply for given source id
type ReplySourceData struct {
ReplyHead
SourceData
}
type replyTrackingContent struct {
RefID uint32
IPAddr ipAddr // our current sync source
Stratum uint16
LeapStatus uint16
RefTime timeSpec
CurrentCorrection chronyFloat
LastOffset chronyFloat
RMSOffset chronyFloat
FreqPPM chronyFloat
ResidFreqPPM chronyFloat
SkewPPM chronyFloat
RootDelay chronyFloat
RootDispersion chronyFloat
LastUpdateInterval chronyFloat
}
// Tracking contains parsed version of 'tracking' reply
type Tracking struct {
RefID uint32
IPAddr net.IP
Stratum uint16
LeapStatus uint16
RefTime time.Time
CurrentCorrection float64
LastOffset float64
RMSOffset float64
FreqPPM float64
ResidFreqPPM float64
SkewPPM float64
RootDelay float64
RootDispersion float64
LastUpdateInterval float64
}
func newTracking(r *replyTrackingContent) *Tracking {
return &Tracking{
RefID: r.RefID,
IPAddr: r.IPAddr.ToNetIP(),
Stratum: r.Stratum,
LeapStatus: r.LeapStatus,
RefTime: r.RefTime.ToTime(),
CurrentCorrection: r.CurrentCorrection.ToFloat(),
LastOffset: r.LastOffset.ToFloat(),
RMSOffset: r.RMSOffset.ToFloat(),
FreqPPM: r.FreqPPM.ToFloat(),
ResidFreqPPM: r.ResidFreqPPM.ToFloat(),
SkewPPM: r.SkewPPM.ToFloat(),
RootDelay: r.RootDelay.ToFloat(),
RootDispersion: r.RootDispersion.ToFloat(),
LastUpdateInterval: r.LastUpdateInterval.ToFloat(),
}
}
// ReplyTracking has usable 'tracking' response
type ReplyTracking struct {
ReplyHead
Tracking
}
type replySourceStatsContent struct {
RefID uint32
IPAddr ipAddr
NSamples uint32
NRuns uint32
SpanSeconds uint32
StandardDeviation chronyFloat
ResidFreqPPM chronyFloat
SkewPPM chronyFloat
EstimatedOffset chronyFloat
EstimatedOffsetErr chronyFloat
}
// SourceStats contains stats about the source
type SourceStats struct {
RefID uint32
IPAddr net.IP
NSamples uint32
NRuns uint32
SpanSeconds uint32
StandardDeviation float64
ResidFreqPPM float64
SkewPPM float64
EstimatedOffset float64
EstimatedOffsetErr float64
}
func newSourceStats(r *replySourceStatsContent) *SourceStats {
return &SourceStats{
RefID: r.RefID,
IPAddr: r.IPAddr.ToNetIP(),
NSamples: r.NSamples,
NRuns: r.NRuns,
SpanSeconds: r.SpanSeconds,
StandardDeviation: r.StandardDeviation.ToFloat(),
ResidFreqPPM: r.ResidFreqPPM.ToFloat(),
SkewPPM: r.SkewPPM.ToFloat(),
EstimatedOffset: r.EstimatedOffset.ToFloat(),
EstimatedOffsetErr: r.EstimatedOffsetErr.ToFloat(),
}
}
// ReplySourceStats has usable 'sourcestats' response
type ReplySourceStats struct {
ReplyHead
SourceStats
}
type replyNTPDataContent struct {
RemoteAddr ipAddr
LocalAddr ipAddr
RemotePort uint16
Leap uint8
Version uint8
Mode uint8
Stratum uint8
Poll int8
Precision int8
RootDelay chronyFloat
RootDispersion chronyFloat
RefID uint32
RefTime timeSpec
Offset chronyFloat
PeerDelay chronyFloat
PeerDispersion chronyFloat
ResponseTime chronyFloat
JitterAsymmetry chronyFloat
Flags uint16
TXTssChar uint8
RXTssChar uint8
TotalTXCount uint32
TotalRXCount uint32
TotalValidCount uint32
Reserved [4]uint32
}
// NTPData contains parsed version of 'ntpdata' reply
type NTPData struct {
RemoteAddr net.IP
LocalAddr net.IP
RemotePort uint16
Leap uint8
Version uint8
Mode uint8
Stratum uint8
Poll int8
Precision int8
RootDelay float64
RootDispersion float64
RefID uint32
RefTime time.Time
Offset float64
PeerDelay float64
PeerDispersion float64
ResponseTime float64
JitterAsymmetry float64
Flags uint16
TXTssChar uint8
RXTssChar uint8
TotalTXCount uint32
TotalRXCount uint32
TotalValidCount uint32
}
func newNTPData(r *replyNTPDataContent) *NTPData {
return &NTPData{
RemoteAddr: r.RemoteAddr.ToNetIP(),
LocalAddr: r.LocalAddr.ToNetIP(),
RemotePort: r.RemotePort,
Leap: r.Leap,
Version: r.Version,
Mode: r.Mode,
Stratum: r.Stratum,
Poll: r.Poll,
Precision: r.Precision,
RootDelay: r.RootDelay.ToFloat(),
RootDispersion: r.RootDispersion.ToFloat(),
RefID: r.RefID,
RefTime: r.RefTime.ToTime(),
Offset: r.Offset.ToFloat(),
PeerDelay: r.PeerDelay.ToFloat(),
PeerDispersion: r.PeerDispersion.ToFloat(),
ResponseTime: r.ResponseTime.ToFloat(),
JitterAsymmetry: r.JitterAsymmetry.ToFloat(),
Flags: r.Flags,
TXTssChar: r.TXTssChar,
RXTssChar: r.RXTssChar,
TotalTXCount: r.TotalTXCount,
TotalRXCount: r.TotalRXCount,
TotalValidCount: r.TotalValidCount,
}
}
// ReplyNTPData is a what end user will get in 'ntp data' response
type ReplyNTPData struct {
ReplyHead
NTPData
}
type replyNTPData2Content struct {
RemoteAddr ipAddr
LocalAddr ipAddr
RemotePort uint16
Leap uint8
Version uint8
Mode uint8
Stratum uint8
Poll int8
Precision int8
RootDelay chronyFloat
RootDispersion chronyFloat
RefID uint32
RefTime timeSpec
Offset chronyFloat
PeerDelay chronyFloat
PeerDispersion chronyFloat
ResponseTime chronyFloat
JitterAsymmetry chronyFloat
Flags uint16
TXTssChar uint8
RXTssChar uint8
TotalTXCount uint32
TotalRXCount uint32
TotalValidCount uint32
TotalKernelTXts uint32
TotalKernelRXts uint32
TotalHWTXts uint32
TotalHWRXts uint32
Reserved [4]int32
}
// NTPData2 contains parsed version of a new 'ntpdata' reply
type NTPData2 struct {
NTPData
TotalKernelTXts uint32
TotalKernelRXts uint32
TotalHWTXts uint32
TotalHWRXts uint32
}
func newNTPData2(r *replyNTPData2Content) *NTPData2 {
return &NTPData2{
NTPData: NTPData{
RemoteAddr: r.RemoteAddr.ToNetIP(),
LocalAddr: r.LocalAddr.ToNetIP(),
RemotePort: r.RemotePort,
Leap: r.Leap,
Version: r.Version,
Mode: r.Mode,
Stratum: r.Stratum,
Poll: r.Poll,
Precision: r.Precision,
RootDelay: r.RootDelay.ToFloat(),
RootDispersion: r.RootDispersion.ToFloat(),
RefID: r.RefID,
RefTime: r.RefTime.ToTime(),
Offset: r.Offset.ToFloat(),
PeerDelay: r.PeerDelay.ToFloat(),
PeerDispersion: r.PeerDispersion.ToFloat(),
ResponseTime: r.ResponseTime.ToFloat(),
JitterAsymmetry: r.JitterAsymmetry.ToFloat(),
Flags: r.Flags,
TXTssChar: r.TXTssChar,
RXTssChar: r.RXTssChar,
TotalTXCount: r.TotalTXCount,
TotalRXCount: r.TotalRXCount,
TotalValidCount: r.TotalValidCount,
},
TotalKernelTXts: r.TotalKernelTXts,
TotalKernelRXts: r.TotalKernelRXts,
TotalHWTXts: r.TotalHWTXts,
TotalHWRXts: r.TotalHWRXts,
}
}
// ReplyNTPData2 is a what end user will get in 'ntp data' response
type ReplyNTPData2 struct {
ReplyHead
NTPData2
}
type replyNTPSourceNameContent struct {
Name [256]uint8
}
// NTPSourceName contains parsed version of 'sourcename' reply
type NTPSourceName struct {
Name string
}
func newNTPSourceName(r *replyNTPSourceNameContent) *NTPSourceName {
return &NTPSourceName{
// this field is zero padded in chrony, so we need to trim it
Name: string(bytes.TrimRight(r.Name[:], "\x00")),
}
}
// ReplyNTPSourceName is a what end user will get in 'sourcename' response
type ReplyNTPSourceName struct {
ReplyHead
NTPSourceName
}
// Activity contains parsed version of 'activity' reply
type Activity struct {
Online int32
Offline int32
BurstOnline int32
BurstOffline int32
Unresolved int32
}
// ReplyActivity is a usable version of 'activity' response
type ReplyActivity struct {
ReplyHead
Activity
}
// ServerStats contains parsed version of 'serverstats' reply
type ServerStats struct {
NTPHits uint32
CMDHits uint32
NTPDrops uint32
CMDDrops uint32
LogDrops uint32
}
// ReplyServerStats is a usable version of 'serverstats' response
type ReplyServerStats struct {
ReplyHead
ServerStats
}
// ServerStats2 contains parsed version of 'serverstats2' reply
type ServerStats2 struct {
NTPHits uint32
NKEHits uint32
CMDHits uint32
NTPDrops uint32
NKEDrops uint32
CMDDrops uint32
LogDrops uint32
NTPAuthHits uint32
}
// ReplyServerStats2 is a usable version of 'serverstats2' response
type ReplyServerStats2 struct {
ReplyHead
ServerStats2
}
// ServerStats3 contains parsed version of 'serverstats3' reply
type ServerStats3 struct {
NTPHits uint32
NKEHits uint32
CMDHits uint32
NTPDrops uint32
NKEDrops uint32
CMDDrops uint32
LogDrops uint32
NTPAuthHits uint32
NTPInterleavedHits uint32
NTPTimestamps uint32
NTPSpanSeconds uint32
}
// ReplyServerStats3 is a usable version of 'serverstats3' response
type ReplyServerStats3 struct {
ReplyHead
ServerStats3
}
// ServerStats4 contains parsed version of 'serverstats4' reply
type ServerStats4 struct {
NTPHits uint64
NKEHits uint64
CMDHits uint64
NTPDrops uint64
NKEDrops uint64
CMDDrops uint64
LogDrops uint64
NTPAuthHits uint64
NTPInterleavedHits uint64
NTPTimestamps uint64
NTPSpanSeconds uint64
NTPDaemonRxtimestamps uint64
NTPDaemonTxtimestamps uint64
NTPKernelRxtimestamps uint64
NTPKernelTxtimestamps uint64
NTPHwRxTimestamps uint64
NTPHwTxTimestamps uint64
}
// ReplyServerStats4 is a usable version of 'serverstats4' response
type ReplyServerStats4 struct {
ReplyHead
ServerStats4
}
type replySelectData struct {
RefID uint32
IPAddr ipAddr
StateChar uint8
Authentication uint8
Leap uint8
Pad uint8
ConfOptions uint16
EFFOptions uint16
LastSampleAgo uint32
Score chronyFloat
LoLimit chronyFloat
HiLimit chronyFloat
}
// SelectData contains parsed version of 'selectdata' reply
type SelectData struct {
RefID uint32
IPAddr net.IP
StateChar uint8
Authentication uint8
Leap uint8
ConfOptions uint16
EFFOptions uint16
LastSampleAgo uint32
Score float64
LoLimit float64
HiLimit float64
}
// ReplySelectData is a usable version of 'selectdata' response
type ReplySelectData struct {
ReplyHead
SelectData
}
func newSelectData(r *replySelectData) *SelectData {
return &SelectData{
RefID: r.RefID,
IPAddr: r.IPAddr.ToNetIP(),
StateChar: r.StateChar,
Authentication: r.Authentication,
Leap: r.Leap,
ConfOptions: r.ConfOptions,
EFFOptions: r.EFFOptions,
LastSampleAgo: r.LastSampleAgo,
Score: r.Score.ToFloat(),
LoLimit: r.LoLimit.ToFloat(),
HiLimit: r.HiLimit.ToFloat(),
}
}
// here go request constructors
// NewSourcesPacket creates new packet to request number of sources (peers)
func NewSourcesPacket() *RequestSources {
return &RequestSources{
RequestHead: RequestHead{
Version: protoVersionNumber,
PKTType: pktTypeCmdRequest,
Command: reqNSources,
},
data: [maxDataLen]uint8{},
}
}
// NewTrackingPacket creates new packet to request 'tracking' information
func NewTrackingPacket() *RequestTracking {
return &RequestTracking{
RequestHead: RequestHead{
Version: protoVersionNumber,
PKTType: pktTypeCmdRequest,
Command: reqTracking,
},
data: [maxDataLen]uint8{},
}
}
// NewSourceStatsPacket creates a new packet to request 'sourcestats' information
func NewSourceStatsPacket(sourceID int32) *RequestSourceStats {
return &RequestSourceStats{
RequestHead: RequestHead{
Version: protoVersionNumber,
PKTType: pktTypeCmdRequest,
Command: reqSourceStats,
},
Index: sourceID,
data: [maxDataLen - 4]uint8{},
}
}
// NewSourceDataPacket creates new packet to request 'source data' information about source with given ID
func NewSourceDataPacket(sourceID int32) *RequestSourceData {
return &RequestSourceData{
RequestHead: RequestHead{
Version: protoVersionNumber,
PKTType: pktTypeCmdRequest,
Command: reqSourceData,
},
Index: sourceID,
data: [maxDataLen - 4]uint8{},
}
}
// NewNTPDataPacket creates new packet to request 'ntp data' information for given peer IP
func NewNTPDataPacket(ip net.IP) *RequestNTPData {
return &RequestNTPData{
RequestHead: RequestHead{
Version: protoVersionNumber,
PKTType: pktTypeCmdRequest,
Command: reqNTPData,
},
IPAddr: *newIPAddr(ip),
data: [maxDataLen - 16]uint8{},
}
}
// NewNTPSourceNamePacket creates new packet to request 'source name' information for given peer IP
func NewNTPSourceNamePacket(ip net.IP) *RequestNTPSourceName {
return &RequestNTPSourceName{
RequestHead: RequestHead{
Version: protoVersionNumber,
PKTType: pktTypeCmdRequest,
Command: reqNTPSourceName,
},
IPAddr: *newIPAddr(ip),
data: [maxDataLen - 16]uint8{},
}
}
// NewServerStatsPacket creates new packet to request 'serverstats' information
func NewServerStatsPacket() *RequestServerStats {
return &RequestServerStats{
RequestHead: RequestHead{
Version: protoVersionNumber,
PKTType: pktTypeCmdRequest,
Command: reqServerStats,
},
data: [maxDataLen]uint8{},
}
}
// NewActivityPacket creates new packet to request 'activity' information
func NewActivityPacket() *RequestActivity {
return &RequestActivity{
RequestHead: RequestHead{
Version: protoVersionNumber,
PKTType: pktTypeCmdRequest,
Command: reqActivity,
},
data: [maxDataLen]uint8{},
}
}
// NewSelectDataPacket creates new packet to request 'selectdata' information
func NewSelectDataPacket(sourceID int32) *RequestSelectData {
return &RequestSelectData{
RequestHead: RequestHead{
Version: protoVersionNumber,
PKTType: pktTypeCmdRequest,
Command: reqSelectData,
},
Index: sourceID,
data: [maxDataLen - 4]uint8{},
}
}
// possible clock sources
const (
ClockSourceUnspec = "unspec"
ClockSourcePPS = "pps"
ClockSourceLFRadio = "lf_radio"
ClockSourceHFRadio = "hf_radio"
ClockSourceUHFRadio = "uhf_radio"
ClockSourceLocal = "local"
ClockSourceNTP = "ntp"
ClockSourceOther = "other"
ClockSourceWristWatch = "wristwatch"
ClockSourceTelephone = "telephone"
)
// ClockSourceDesc stores human-readable descriptions of ClockSource field
var ClockSourceDesc = [10]string{
ClockSourceUnspec, // 00
ClockSourcePPS, // 01
ClockSourceLFRadio, // 02
ClockSourceHFRadio, // 03
ClockSourceUHFRadio, // 04
ClockSourceLocal, // 05
ClockSourceNTP, // 06
ClockSourceOther, // 07
ClockSourceWristWatch, // 08
ClockSourceTelephone, // 09
}
// decodePacket decodes bytes to valid response packet.
// an easy way to test this is to use 'testchrony' tool we have.
func decodePacket(response []byte) (ResponsePacket, error) {
var err error
r := bytes.NewReader(response)
head := new(ReplyHead)
if err = binary.Read(r, binary.BigEndian, head); err != nil {
return nil, err
}
Logger.Printf("response head: %+v", head)
if head.Status != sttSuccess {
return nil, fmt.Errorf("got status %s (%d)", head.Status, head.Status)
}
switch head.Reply {
case RpyNSources:
data := new(replySourcesContent)
if err = binary.Read(r, binary.BigEndian, data); err != nil {
return nil, err
}
Logger.Printf("response data: %+v", data)
return &ReplySources{
ReplyHead: *head,
NSources: int(data.NSources),
}, nil
case RpySourceData:
data := new(replySourceDataContent)
if err = binary.Read(r, binary.BigEndian, data); err != nil {
return nil, err
}
Logger.Printf("response data: %+v", data)
return &ReplySourceData{
ReplyHead: *head,
SourceData: *newSourceData(data),
}, nil
case RpyTracking:
data := new(replyTrackingContent)
if err = binary.Read(r, binary.BigEndian, data); err != nil {
return nil, err
}
Logger.Printf("response data: %+v", data)
return &ReplyTracking{
ReplyHead: *head,
Tracking: *newTracking(data),
}, nil
case RpySourceStats:
data := new(replySourceStatsContent)
if err = binary.Read(r, binary.BigEndian, data); err != nil {
return nil, err
}
Logger.Printf("response data: %+v", data)
return &ReplySourceStats{
ReplyHead: *head,
SourceStats: *newSourceStats(data),
}, nil
case RpyActivity:
data := new(Activity)
if err = binary.Read(r, binary.BigEndian, data); err != nil {
return nil, err
}
Logger.Printf("response data: %+v", data)
return &ReplyActivity{
ReplyHead: *head,
Activity: *data,
}, nil
case RpyServerStats:
data := new(ServerStats)
if err = binary.Read(r, binary.BigEndian, data); err != nil {
return nil, err
}
Logger.Printf("response data: %+v", data)
return &ReplyServerStats{
ReplyHead: *head,
ServerStats: *data,
}, nil
case RpyNTPData:
data := new(replyNTPDataContent)
if err = binary.Read(r, binary.BigEndian, data); err != nil {
return nil, err
}
Logger.Printf("response data: %+v", data)
return &ReplyNTPData{
ReplyHead: *head,
NTPData: *newNTPData(data),
}, nil
case RpyNTPData2:
data := new(replyNTPData2Content)
if err = binary.Read(r, binary.BigEndian, data); err != nil {
return nil, err
}
Logger.Printf("response data: %+v", data)
return &ReplyNTPData2{
ReplyHead: *head,
NTPData2: *newNTPData2(data),
}, nil
case RpyNTPSourceName:
data := new(replyNTPSourceNameContent)
if err = binary.Read(r, binary.BigEndian, data); err != nil {
return nil, err
}
Logger.Printf("response data: %+v", data)
return &ReplyNTPSourceName{
ReplyHead: *head,
NTPSourceName: *newNTPSourceName(data),
}, nil
case RpyServerStats2:
data := new(ServerStats2)
if err = binary.Read(r, binary.BigEndian, data); err != nil {
return nil, err
}
Logger.Printf("response data: %+v", data)
return &ReplyServerStats2{
ReplyHead: *head,
ServerStats2: *data,
}, nil
case RpyServerStats3:
data := new(ServerStats3)
if err = binary.Read(r, binary.BigEndian, data); err != nil {
return nil, err
}
Logger.Printf("response data: %+v", data)
return &ReplyServerStats3{
ReplyHead: *head,
ServerStats3: *data,
}, nil
case RpyServerStats4:
data := new(ServerStats4)
if err = binary.Read(r, binary.BigEndian, data); err != nil {
return nil, err
}
Logger.Printf("response data: %+v", data)
return &ReplyServerStats4{
ReplyHead: *head,
ServerStats4: *data,
}, nil
case RpySelectData:
data := new(replySelectData)
if err = binary.Read(r, binary.BigEndian, data); err != nil {
return nil, err
}
Logger.Printf("response data: %+v", data)
return &ReplySelectData{
ReplyHead: *head,
SelectData: *newSelectData(data),
}, nil
default:
return nil, fmt.Errorf("not implemented reply type %d from %+v", head.Reply, head)
}
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package control
import (
"bytes"
"encoding/binary"
"io"
)
// NTPClient is our client to talk to network. The main reason it exists is keeping track of Sequence number.
type NTPClient struct {
Sequence uint16
Connection io.ReadWriter
}
// CommunicateWithData sends package + data over connection, bumps Sequence num and parses (possibly multiple) response packets into NTPControlMsg packet.
// This function will always return single NTPControlMsg, even if under the hood it was split across multiple packets.
// Resulting NTPControlMsg will have Data section composed of combined Data sections from all packages.
func (n *NTPClient) CommunicateWithData(packet *NTPControlMsgHead, data []uint8) (*NTPControlMsg, error) {
packet.Sequence = n.Sequence
if len(data) > 0 {
packet.Count = uint16(len(data))
}
n.Sequence++
// create a buffer where we can compose full payload
buf := new(bytes.Buffer)
err := binary.Write(buf, binary.BigEndian, packet)
if err != nil {
return nil, err
}
err = binary.Write(buf, binary.BigEndian, data)
if err != nil {
return nil, err
}
// send full payload
_, err = n.Connection.Write(buf.Bytes())
if err != nil {
return nil, err
}
var resultHead *NTPControlMsgHead
resultData := make([]uint8, 0)
// read packets till More flag is not set
for {
response := make([]uint8, 1024)
head := new(NTPControlMsgHead)
_, err := n.Connection.Read(response)
if err != nil {
return nil, err
}
r := bytes.NewReader(response[:12])
if err = binary.Read(r, binary.BigEndian, head); err != nil {
return nil, err
}
data := make([]uint8, head.Count)
copy(data, response[12:12+head.Count])
resultData = append(resultData, data...)
if !head.HasMore() {
resultHead = head
break
}
}
return &NTPControlMsg{NTPControlMsgHead: *resultHead, Data: resultData}, nil
}
// Communicate sends package over connection, bumps Sequence num and parses (possibly multiple) response packets into NTPControlMsg packet.
// This function will always return single NTPControlMsg, even if under the hood it was split across multiple packets.
// Resulting NTPControlMsg will have Data section composed of combined Data sections from all packages.
func (n *NTPClient) Communicate(packet *NTPControlMsgHead) (*NTPControlMsg, error) {
return n.CommunicateWithData(packet, nil)
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package control
import (
"fmt"
"log"
"strings"
)
// Mode is NTP Control operation mode code
const Mode = 6
// Supported operation codes
const (
OpReadStatus = 1
OpReadVariables = 2
)
// NormalizeData turns bytes that contain kv ASCII string info a map[string]string
func NormalizeData(data []byte) (map[string]string, error) {
result := map[string]string{}
pairs := strings.Split(string(data), ",")
for _, pair := range pairs {
split := strings.Split(pair, "=")
if len(split) != 2 {
log.Printf("WARNING: Malformed packet, bad k=v pair '%s'", pair)
continue
}
k := strings.TrimSpace(split[0])
v := strings.TrimSpace(strings.Trim(split[1], `"`))
result[k] = v
}
if len(result) == 0 {
return result, fmt.Errorf("Malformed packet, no k=v pairs decoded")
}
return result, nil
}
// NTPControlMsgHead structure is described in NTPv3 RFC1305 Appendix B. NTP Control Messages.
// We don't have Data defined here as data size is variable and binary package
// simply doesn't support reading or writing structs with non-fixed fields.
type NTPControlMsgHead struct {
// 0: 00 Version(3bit) Mode(3bit)
VnMode uint8
// 1: Response Error More Operation(5bit)
REMOp uint8
// 2-3: Sequence (16bit)
Sequence uint16
// 4-5: Status (16bit)
Status uint16
// 6-7: Association ID (16bit)
AssociationID uint16
// 8-9: Offset (16bit)
Offset uint16
// 10-11: Count (16bit)
Count uint16
// 12+: Data (up to 468 bits)
// then goes [468]uint8 of data that we have in NTPControlMsg
}
// NTPControlMsg is just a NTPControlMsgHead with data.
type NTPControlMsg struct {
NTPControlMsgHead
Data []uint8
}
// LeapDesc stores human-readable descriptions of LI (leap indicator) field
var LeapDesc = [4]string{"none", "add_sec", "del_sec", "alarm"}
// ClockSourceDesc stores human-readable descriptions of ClockSource field
var ClockSourceDesc = [10]string{
"unspec", // 00
"pps", // 01
"lf_radio", // 02
"hf_radio", // 03
"uhf_radio", // 04
"local", // 05
"ntp", // 06
"other", // 07
"wristwatch", // 08
"telephone", // 09
}
// SystemEventDesc stores human-readable descriptions of SystemEvent field
var SystemEventDesc = [17]string{
"unspecified", // 00
"freq_not_set", // 01
"freq_set", // 02
"spike_detect", // 03
"freq_mode", // 04
"clock_sync", // 05
"restart", // 06
"panic_stop", // 07
"no_system_peer", // 08
"leap_armed", // 09
"leap_disarmed", // 0a
"leap_event", // 0b
"clock_step", // 0c
"kern", // 0d
"TAI...", // 0e
"stale leapsecond values", // 0f
"clockhop", // 10
}
// FlashDescMap maps bit mask with corresponding flash status
var FlashDescMap = map[uint16]string{
0x0001: "pkt_dup",
0x0002: "pkt_bogus",
0x0004: "pkt_unsync",
0x0008: "pkt_denied",
0x0010: "pkt_auth",
0x0020: "pkt_stratum",
0x0040: "pkt_header",
0x0080: "pkt_autokey",
0x0100: "pkt_crypto",
0x0200: "peer_stratum",
0x0400: "peer_dist",
0x0800: "peer_loop",
0x1000: "peer_unreach",
}
// ReadFlashStatusWord returns list of flashers (as strings) decoded from flash status word
func ReadFlashStatusWord(flash uint16) []string {
flashers := []string{}
for mask, message := range FlashDescMap {
if flash&mask > 0 {
flashers = append(flashers, message)
}
}
return flashers
}
// SystemStatusWord stores parsed SystemStatus 16bit word.
type SystemStatusWord struct {
LI uint8
ClockSource uint8
SystemEventCounter uint8
SystemEventCode uint8
}
// Word encodes SystemStatusWord as uint16 word
func (ssw *SystemStatusWord) Word() uint16 {
var out uint16
out |= uint16(ssw.LI) << 14
out |= uint16(ssw.ClockSource) << 8
out |= uint16(ssw.SystemEventCounter) << 4
out |= uint16(ssw.SystemEventCode)
return out
}
// ReadSystemStatusWord transforms SystemStatus 16bit word into usable struct.
func ReadSystemStatusWord(b uint16) *SystemStatusWord {
return &SystemStatusWord{
LI: uint8((b & 0xc000) >> 14), // first 2 bits
ClockSource: uint8((b & 0x3f00) >> 8), // next 6 bits
SystemEventCounter: uint8((b & 0xf0) >> 4), // 4 bits
SystemEventCode: uint8((b & 0xf)), // last 4 bits
}
}
// PeerStatus word decoded. Sadly values used by ntpd are different from RFC for v2 and v3 of NTP.
// Actual values are from http://doc.ntp.org/4.2.6/decode.html#peer
type PeerStatus struct {
Broadcast bool
Reachable bool
AuthEnabled bool
AuthOK bool
Configured bool
}
// Byte encodes PeerStatus as uint8
func (ps *PeerStatus) Byte() uint8 {
var out uint8
if ps.Configured {
out |= 0x10
}
if ps.AuthOK {
out |= 0x8
}
if ps.AuthEnabled {
out |= 0x4
}
if ps.Reachable {
out |= 0x2
}
if ps.Broadcast {
out |= 0x1
}
return out
}
// Here go peer selection statuses, as described in http://doc.ntp.org/current-stable/decode.html#peer
const (
// SelReject means peer is discarded as not valid (TEST10-TEST13)
SelReject uint8 = 0
// SelFalseTick means peer is discarded by intersection algorithm
SelFalseTick uint8 = 1
// SelExcess means peer is discarded by table overflow (not used)
SelExcess uint8 = 2
// SelOutlier means peer is discarded by the cluster algorithm
SelOutlier uint8 = 3
// SelCandidate means peer is included by the combine algorithm
SelCandidate uint8 = 4
// SelBackup means peer is a backup (more than tos maxclock sources)
SelBackup uint8 = 5
// SelSYSPeer means peer is a system peer (main synchronization source)
SelSYSPeer uint8 = 6
// SelPPSPeer means peer is a PPS peer (when the prefer peer is valid)
SelPPSPeer uint8 = 7
)
// PeerSelect maps PeerSelection uint8 to human-readable string taken from http://doc.ntp.org/4.2.6/decode.html#peer
var PeerSelect = [8]string{"reject", "falsetick", "excess", "outlyer", "candidate", "backup", "sys.peer", "pps.peer"}
// ReadPeerStatus transforms PeerStatus 8bit flag into usable struct
func ReadPeerStatus(b uint8) PeerStatus {
// 5 bit code with bits assigned different meanings
return PeerStatus{
Configured: b&0x10 != 0,
AuthOK: b&0x8 != 0,
AuthEnabled: b&0x4 != 0,
Reachable: b&0x2 != 0,
Broadcast: b&0x1 != 0,
}
}
// PeerStatusWord stores parsed PeerStatus 16bit word.
type PeerStatusWord struct {
PeerStatus PeerStatus
PeerSelection uint8
PeerEventCounter uint8
PeerEventCode uint8
}
// Word encodes PeerStatusWord as uint16 word
func (psw *PeerStatusWord) Word() uint16 {
var out uint16
psByte := uint16(psw.PeerStatus.Byte())
out |= psByte << 11
out |= uint16(psw.PeerSelection) << 8
out |= uint16(psw.PeerEventCounter) << 4
out |= uint16(psw.PeerEventCode)
return out
}
// ReadPeerStatusWord transforms PeerStatus 16bit word into usable struct
func ReadPeerStatusWord(b uint16) *PeerStatusWord {
status := uint8((b & 0xf800) >> 11) // first 5 bits
return &PeerStatusWord{
PeerStatus: ReadPeerStatus(status),
PeerSelection: uint8((b & 0x700) >> 8), // 3 bits
PeerEventCounter: uint8((b & 0xf0) >> 4), // 4 bits
PeerEventCode: uint8((b & 0xf)), // last 4 bits
}
}
// MakeVnMode composes uint8 with version and mode bits set
func MakeVnMode(version int, mode int) uint8 {
var out uint8
out |= uint8(version) << 3
out |= uint8(mode)
return out
}
// MakeREMOp composes uint8 with response error and more bits set, combined with operation code
func MakeREMOp(response, err, more bool, op int) uint8 {
var out uint8
if response {
out |= 0x80
}
if err {
out |= 0x40
}
if more {
out |= 0x20
}
out |= uint8(op)
return out
}
// GetVersion gets int version from Version+Mode 8bit word
func (n NTPControlMsgHead) GetVersion() int {
return int((n.VnMode & 0x38) >> 3) // get 3 bits offset by 3 bits
}
// GetMode gets int mode from Version+Mode 8bit word
func (n NTPControlMsgHead) GetMode() int {
return int(n.VnMode & 0x7) // get last 3 bits
}
// IsResponse returns true if packet is a response
func (n NTPControlMsgHead) IsResponse() bool {
return n.REMOp&0x80 != 0 // response, bit 7
}
// HasError returns true if packet has error flag set
func (n NTPControlMsgHead) HasError() bool {
return n.REMOp&0x40 != 0 // error flag, bit 6
}
// HasMore returns true if packet has More flag set
func (n NTPControlMsgHead) HasMore() bool {
return n.REMOp&0x20 != 0 // more flag, bit 5
}
// GetOperation returns int operation extracted from REMOp 8bit word
func (n NTPControlMsgHead) GetOperation() uint8 {
return n.REMOp & 0x1f // last 5 bits
}
// GetSystemStatus returns parsed SystemStatusWord struct if present
func (n NTPControlMsg) GetSystemStatus() (*SystemStatusWord, error) {
if n.GetOperation() != OpReadStatus {
return nil, fmt.Errorf("no System Status Word supported for operation=%d", n.GetOperation())
}
return ReadSystemStatusWord(n.Status), nil
}
// GetPeerStatus returns parsed PeerStatusWord struct if present
func (n NTPControlMsg) GetPeerStatus() (*PeerStatusWord, error) {
if n.GetOperation() != OpReadVariables {
return nil, fmt.Errorf("no Peer Status Word supported for operation=%d", n.GetOperation())
}
return ReadPeerStatusWord(n.Status), nil
}
// GetAssociations returns map of PeerStatusWord, basically peer information.
func (n NTPControlMsg) GetAssociations() (map[uint16]*PeerStatusWord, error) {
result := map[uint16]*PeerStatusWord{}
if n.GetOperation() != OpReadStatus {
return result, fmt.Errorf("no peer list supported for operation=%d", n.GetOperation())
}
for i := 0; i < int(n.Count/4); i++ {
assoc := n.Data[i*4 : i*4+4] // 2 uint16 encoded as 4 bytes
id := uint16(assoc[0])<<8 | uint16(assoc[1]) // uint16 from 2 uint8
peerStatus := uint16(assoc[2])<<8 | uint16(assoc[3]) // ditto
result[id] = ReadPeerStatusWord(peerStatus)
}
return result, nil
}
// GetAssociationInfo returns parsed normalized variables if present
func (n NTPControlMsg) GetAssociationInfo() (map[string]string, error) {
result := map[string]string{}
if n.GetOperation() != OpReadVariables {
return result, fmt.Errorf("no variables supported for operation=%d", n.GetOperation())
}
data, err := NormalizeData(n.Data)
if err != nil {
return result, err
}
return data, nil
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
Package protocol implements ntp packet and basic functions to work with.
It provides quick and transparent translation between 48 bytes and
simply accessible struct in the most efficient way.
*/
package protocol
import (
"time"
)
// NanosecondsToUnix is the difference between the start of NTP Era 0 and the Unix epoch in nanoseconds
// Jan-1 1900 00:00:00 UTC (start of NTP epoch Era 0) and Jan-1 1970 00:00:00 UTC (start of Unix epoch)
// Formula is 70 * (365 + 17) * 86400 (17 leap days)
const NanosecondsToUnix = int64(2_208_988_800_000_000_000)
// Time is converting Unix time to sec and frac NTP format
func Time(t time.Time) (seconds uint32, fractions uint32) {
nsec := t.UnixNano() + NanosecondsToUnix
sec := nsec / time.Second.Nanoseconds()
return uint32(sec), uint32((nsec - sec*time.Second.Nanoseconds()) << 32 / time.Second.Nanoseconds())
}
// Unix is converting NTP seconds and fractions into Unix time
func Unix(seconds, fractions uint32) time.Time {
secs := int64(seconds) - NanosecondsToUnix/time.Second.Nanoseconds()
nanos := (int64(fractions) * time.Second.Nanoseconds()) >> 32 // convert fractional to nanos
return time.Unix(secs, nanos)
}
// Offset uses NTP algorithm for clock offset
func Offset(originTime, serverReceiveTime, serverTransmitTime, clientReceiveTime time.Time) int64 {
outboundClockDelta := serverReceiveTime.Sub(originTime).Nanoseconds()
inboundClockDelta := serverTransmitTime.Sub(clientReceiveTime).Nanoseconds()
return (outboundClockDelta + inboundClockDelta) / 2
}
// RoundTripDelay uses NTP algorithm for roundtrip network delay
func RoundTripDelay(originTime, serverReceiveTime, serverTransmitTime, clientReceiveTime time.Time) int64 {
totalDelay := clientReceiveTime.Sub(originTime).Nanoseconds()
serverDelay := serverTransmitTime.Sub(serverReceiveTime).Nanoseconds()
return (totalDelay - serverDelay)
}
// CorrectTime returns the correct time based on computed offset
func CorrectTime(clientReceiveTime time.Time, offset int64) time.Time {
correctTime := clientReceiveTime.Add(time.Duration(offset))
return correctTime
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package protocol
import (
"bytes"
"encoding/binary"
"net"
)
// PacketSizeBytes sets the size of NTP packet
const PacketSizeBytes = 48
// ControlHeaderSizeBytes is a buffer to read packet header with Kernel timestamps
const ControlHeaderSizeBytes = 32
// Packet is an NTPv4 packet
/*
http://seriot.ch/ntp.php
https://tools.ietf.org/html/rfc958
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
0 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|LI | VN |Mode | Stratum | Poll | Precision |
4 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Root Delay |
8 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Root Dispersion |
12+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reference ID |
16+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Reference Timestamp (64) +
| |
24+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Origin Timestamp (64) +
| |
32+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Receive Timestamp (64) +
| |
40+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Transmit Timestamp (64) +
| |
48+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|LI | VN |Mode |
+-+-+-+-+-+-+-+-+
0 1 1 0 0 0 1 1
Setting = LI | VN |Mode. Client request example:
00 011 011 (or 0x1B)
| | +-- client mode (3)
| + ----- version (3)
+ -------- leap year indicator, 0 no warning
*/
type Packet struct {
Settings uint8 // leap year indicator, version number and mode
Stratum uint8 // stratum
Poll int8 // poll. Power of 2
Precision int8 // precision. Power of 2
RootDelay uint32 // total delay to the reference clock
RootDispersion uint32 // total dispersion to the reference clock
ReferenceID uint32 // identifier of server or a reference clock
RefTimeSec uint32 // last time local clock was updated sec
RefTimeFrac uint32 // last time local clock was updated frac
OrigTimeSec uint32 // client time sec
OrigTimeFrac uint32 // client time frac
RxTimeSec uint32 // receive time sec
RxTimeFrac uint32 // receive time frac
TxTimeSec uint32 // transmit time sec
TxTimeFrac uint32 // transmit time frac
}
const (
liNoWarning = 0
liAlarmCondition = 3
vnFirst = 1
vnLast = 4
modeClient = 3
)
// ValidSettingsFormat verifies that LI | VN |Mode fields are set correctly
// check the first byte,include:
// LN:must be 0 or 3
// VN:must be 1,2,3 or 4
// Mode:must be 3
func (p *Packet) ValidSettingsFormat() bool {
settings := p.Settings
var l = settings >> 6
var v = (settings << 2) >> 5
var m = (settings << 5) >> 5
if (l == liNoWarning) || (l == liAlarmCondition) {
if (v >= vnFirst) && (v <= vnLast) {
if m == modeClient {
return true
}
}
}
return false
}
// Bytes converts Packet to []bytes
func (p *Packet) Bytes() ([]byte, error) {
var bytes bytes.Buffer
err := binary.Write(&bytes, binary.BigEndian, p)
return bytes.Bytes(), err
}
// UnmarshalBinary fills the Packet from []bytes
func (p *Packet) UnmarshalBinary(b []byte) error {
reader := bytes.NewReader(b)
return binary.Read(reader, binary.BigEndian, p)
}
// BytesToPacket converts []bytes to Packet
func BytesToPacket(ntpPacketBytes []byte) (*Packet, error) {
packet := &Packet{}
return packet, packet.UnmarshalBinary(ntpPacketBytes)
}
// ReadNTPPacket reads incoming NTP packet
func ReadNTPPacket(conn *net.UDPConn) (ntp *Packet, remAddr net.Addr, err error) {
buf := make([]byte, PacketSizeBytes)
_, remAddr, err = conn.ReadFromUDP(buf)
if err != nil {
return nil, nil, err
}
ntp, err = BytesToPacket(buf)
return ntp, remAddr, err
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package protocol
import (
"bytes"
"encoding"
"encoding/binary"
"errors"
"fmt"
"io"
"os"
)
var identity PortIdentity
// ErrManagementMsgErrorStatus is what happens if we expected to get Management TLV in response, but received special ManagementErrorStatusTLV
var ErrManagementMsgErrorStatus = errors.New("received MANAGEMENT_ERROR_STATUS_TLV")
func init() {
// store our PID as identity that we use to talk to ptp daemon
identity.PortNumber = uint16(os.Getpid())
}
// Action indicate the action to be taken on receipt of the PTP message as defined in Table 57
type Action uint8
// actions as in Table 57 Values of the actionField
const (
GET Action = iota
SET
RESPONSE
COMMAND
ACKNOWLEDGE
)
// ManagementTLVHead Spec Table 58 - Management TLV fields
type ManagementTLVHead struct {
TLVHead
ManagementID ManagementID
}
// ManagementMsgHead Spec Table 56 - Management message fields
type ManagementMsgHead struct {
Header
TargetPortIdentity PortIdentity
StartingBoundaryHops uint8
BoundaryHops uint8
ActionField Action
Reserved uint8
}
// Action returns ActionField
func (p *ManagementMsgHead) Action() Action {
return p.ActionField
}
// MgmtID returns ManagementID
func (p *ManagementTLVHead) MgmtID() ManagementID {
return p.ManagementID
}
// Management packet, see '15. PTP management messages'
type Management struct {
ManagementMsgHead
TLV ManagementTLV
}
// UnmarshalBinary parses []byte and populates struct fields
func (p *Management) UnmarshalBinary(rawBytes []byte) error {
var err error
head := ManagementMsgHead{}
tlvHead := ManagementTLVHead{}
r := bytes.NewReader(rawBytes)
if err = binary.Read(r, binary.BigEndian, &head); err != nil {
return err
}
if err = binary.Read(r, binary.BigEndian, &tlvHead.TLVHead); err != nil {
return err
}
if tlvHead.TLVType == TLVManagementErrorStatus {
return ErrManagementMsgErrorStatus
}
if tlvHead.TLVType != TLVManagement {
return fmt.Errorf("got TLV type %q (0x%02X) instead of %q (0x%02X)", tlvHead.TLVType.String(), int(tlvHead.TLVType), TLVManagement.String(), int(TLVManagement))
}
if err = binary.Read(r, binary.BigEndian, &tlvHead.ManagementID); err != nil {
return err
}
headSize := binary.Size(tlvHead)
// seek back so we can read whole TLV
if _, err := r.Seek(-int64(headSize), io.SeekCurrent); err != nil {
return err
}
decoder, found := mgmtTLVDecoder[tlvHead.ManagementID]
if !found {
return fmt.Errorf("unsupported management TLV 0x%x", tlvHead.ManagementID)
}
tlvData, err := io.ReadAll(r)
if err != nil {
return err
}
tlv, err := decoder(tlvData)
if err != nil {
return err
}
p.ManagementMsgHead = head
p.TLV = tlv
return nil
}
// MarshalBinaryToBuf converts packet to bytes and writes those into provided buffer
func (p *Management) MarshalBinaryToBuf(bytes io.Writer) error {
if err := binary.Write(bytes, binary.BigEndian, p.ManagementMsgHead); err != nil {
return err
}
// interface smuggling
if pp, ok := p.TLV.(encoding.BinaryMarshaler); ok {
b, err := pp.MarshalBinary()
if err != nil {
return err
}
return binary.Write(bytes, binary.BigEndian, b)
}
return binary.Write(bytes, binary.BigEndian, p.TLV)
}
// MarshalBinary converts packet to []bytes
func (p *Management) MarshalBinary() ([]byte, error) {
var bytes bytes.Buffer
err := p.MarshalBinaryToBuf(&bytes)
return bytes.Bytes(), err
}
// ManagementErrorStatusTLV spec Table 108 MANAGEMENT_ERROR_STATUS TLV format
type ManagementErrorStatusTLV struct {
TLVHead
ManagementErrorID ManagementErrorID
ManagementID ManagementID
Reserved int32
DisplayData PTPText
}
// ManagementMsgErrorStatus is header + ManagementErrorStatusTLV
type ManagementMsgErrorStatus struct {
ManagementMsgHead
ManagementErrorStatusTLV
}
// UnmarshalBinary parses []byte and populates struct fields
func (p *ManagementMsgErrorStatus) UnmarshalBinary(rawBytes []byte) error {
reader := bytes.NewReader(rawBytes)
be := binary.BigEndian
if err := binary.Read(reader, be, &p.ManagementMsgHead); err != nil {
return fmt.Errorf("reading ManagementMsgErrorStatus ManagementMsgHead: %w", err)
}
if err := binary.Read(reader, be, &p.ManagementErrorStatusTLV.TLVHead); err != nil {
return fmt.Errorf("reading ManagementMsgErrorStatus TLVHead: %w", err)
}
if err := binary.Read(reader, be, &p.ManagementErrorStatusTLV.ManagementErrorID); err != nil {
return fmt.Errorf("reading ManagementMsgErrorStatus ManagementErrorID: %w", err)
}
if err := binary.Read(reader, be, &p.ManagementErrorStatusTLV.ManagementID); err != nil {
return fmt.Errorf("reading ManagementMsgErrorStatus ManagementID: %w", err)
}
if err := binary.Read(reader, be, &p.ManagementErrorStatusTLV.Reserved); err != nil {
return fmt.Errorf("reading ManagementMsgErrorStatus Reserved: %w", err)
}
// packet can have trailing bytes, let's make sure we don't try to read past given length
toRead := int(p.ManagementMsgHead.Header.MessageLength)
toRead -= binary.Size(p.ManagementMsgHead)
toRead -= binary.Size(p.ManagementErrorStatusTLV.TLVHead)
toRead -= binary.Size(p.ManagementErrorStatusTLV.ManagementErrorID)
toRead -= binary.Size(p.ManagementErrorStatusTLV.ManagementID)
toRead -= binary.Size(p.ManagementErrorStatusTLV.Reserved)
if reader.Len() == 0 || toRead <= 0 {
// DisplayData is completely optional
return nil
}
data := make([]byte, reader.Len())
if _, err := io.ReadFull(reader, data); err != nil {
return err
}
if err := p.DisplayData.UnmarshalBinary(data); err != nil {
return fmt.Errorf("reading ManagementMsgErrorStatus DisplayData: %w", err)
}
return nil
}
// MarshalBinaryToBuf converts packet to bytes and writes those into provided buffer
func (p *ManagementMsgErrorStatus) MarshalBinaryToBuf(bytes io.Writer) error {
be := binary.BigEndian
if err := binary.Write(bytes, be, &p.ManagementMsgHead); err != nil {
return fmt.Errorf("writing ManagementMsgErrorStatus ManagementMsgHead: %w", err)
}
if err := binary.Write(bytes, be, &p.ManagementErrorStatusTLV.TLVHead); err != nil {
return fmt.Errorf("writing ManagementMsgErrorStatus TLVHead: %w", err)
}
if err := binary.Write(bytes, be, &p.ManagementErrorStatusTLV.ManagementErrorID); err != nil {
return fmt.Errorf("writing ManagementMsgErrorStatus ManagementErrorID: %w", err)
}
if err := binary.Write(bytes, be, &p.ManagementErrorStatusTLV.ManagementID); err != nil {
return fmt.Errorf("writing ManagementMsgErrorStatus ManagementID: %w", err)
}
if err := binary.Write(bytes, be, &p.ManagementErrorStatusTLV.Reserved); err != nil {
return fmt.Errorf("writing ManagementMsgErrorStatus Reserved: %w", err)
}
if p.DisplayData != "" {
dd, err := p.DisplayData.MarshalBinary()
if err != nil {
return fmt.Errorf("writing ManagementMsgErrorStatus DisplayData: %w", err)
}
if _, err := bytes.Write(dd); err != nil {
return err
}
}
return nil
}
// MarshalBinary converts packet to []bytes
func (p *ManagementMsgErrorStatus) MarshalBinary() ([]byte, error) {
var bytes bytes.Buffer
err := p.MarshalBinaryToBuf(&bytes)
return bytes.Bytes(), err
}
// ManagementErrorID is an enum for possible management errors
type ManagementErrorID uint16
// Table 109 ManagementErrorID enumeration
const (
ErrorResponseTooBig ManagementErrorID = 0x0001 // The requested operation could not fit in a single response message
ErrorNoSuchID ManagementErrorID = 0x0002 // The managementId is not recognized
ErrorWrongLength ManagementErrorID = 0x0003 // The managementId was identified but the length of the data was wrong
ErrorWrongValue ManagementErrorID = 0x0004 // The managementId and length were correct but one or more values were wrong
ErrorNotSetable ManagementErrorID = 0x0005 // Some of the variables in the set command were not updated because they are not configurable
ErrorNotSupported ManagementErrorID = 0x0006 // The requested operation is not supported in this PTP Instance
ErrorUnpopulated ManagementErrorID = 0x0007 // The targetPortIdentity of the PTP management message refers to an entity that is not present in the PTP Instance at the time of the request
// some reserved and provile-specific ranges
ErrorGeneralError ManagementErrorID = 0xFFFE //An error occurred that is not covered by other ManagementErrorID values
)
// ManagementErrorIDToString is a map from ManagementErrorID to string
var ManagementErrorIDToString = map[ManagementErrorID]string{
ErrorResponseTooBig: "RESPONSE_TOO_BIG",
ErrorNoSuchID: "NO_SUCH_ID",
ErrorWrongLength: "WRONG_LENGTH",
ErrorWrongValue: "WRONG_VALUE",
ErrorNotSetable: "NOT_SETABLE",
ErrorNotSupported: "NOT_SUPPORTED",
ErrorUnpopulated: "UNPOPULATED",
ErrorGeneralError: "GENERAL_ERROR",
}
func (t ManagementErrorID) String() string {
s := ManagementErrorIDToString[t]
if s == "" {
return fmt.Sprintf("UNKNOWN_ERROR_ID=%d", t)
}
return s
}
func (t ManagementErrorID) Error() string {
return t.String()
}
func decodeMgmtPacket(data []byte) (Packet, error) {
packet := &Management{}
err := packet.UnmarshalBinary(data)
if errors.Is(err, ErrManagementMsgErrorStatus) {
errorPacket := new(ManagementMsgErrorStatus)
if err := errorPacket.UnmarshalBinary(data); err != nil {
return nil, fmt.Errorf("got Management Error in response but failed to decode it: %w", err)
}
return errorPacket, nil
}
if err != nil {
return nil, err
}
return packet, nil
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package protocol
// management client is used to talk to (presumably local) PTP server using Management packets
import (
"encoding/binary"
"fmt"
"io"
)
// MgmtClient talks to ptp server over unix socket
type MgmtClient struct {
Connection io.ReadWriter
Sequence uint16
}
// SendPacket sends packet, incrementing sequence counter
func (c *MgmtClient) SendPacket(packet *Management) error {
c.Sequence++
packet.SetSequence(c.Sequence)
b, err := packet.MarshalBinary()
if err != nil {
return err
}
return binary.Write(c.Connection, binary.BigEndian, b)
}
// Communicate sends the management the packet, parses response into something usable
func (c *MgmtClient) Communicate(packet *Management) (*Management, error) {
var err error
if err := c.SendPacket(packet); err != nil {
return nil, err
}
response := make([]uint8, 1024)
n, err := c.Connection.Read(response)
if err != nil {
return nil, err
}
res, err := decodeMgmtPacket(response[:n])
if err != nil {
return nil, err
}
errorPacket, ok := res.(*ManagementMsgErrorStatus)
if ok {
return nil, fmt.Errorf("got Management Error in response: %w", errorPacket.ManagementErrorStatusTLV.ManagementErrorID)
}
p, ok := res.(*Management)
if !ok {
return nil, fmt.Errorf("got unexpected management packet %T", res)
}
return p, nil
}
// ParentDataSet sends PARENT_DATA_SET request and returns response
func (c *MgmtClient) ParentDataSet() (*ParentDataSetTLV, error) {
req := ParentDataSetRequest()
p, err := c.Communicate(req)
if err != nil {
return nil, err
}
tlv, ok := p.TLV.(*ParentDataSetTLV)
if !ok {
return nil, fmt.Errorf("got unexpected management TLV %T, wanted %T", p.TLV, tlv)
}
return tlv, nil
}
// DefaultDataSet sends DEFAULT_DATA_SET request and returns response
func (c *MgmtClient) DefaultDataSet() (*DefaultDataSetTLV, error) {
req := DefaultDataSetRequest()
p, err := c.Communicate(req)
if err != nil {
return nil, err
}
tlv, ok := p.TLV.(*DefaultDataSetTLV)
if !ok {
return nil, fmt.Errorf("got unexpected management TLV %T, wanted %T", p.TLV, tlv)
}
return tlv, nil
}
// CurrentDataSet sends CURRENT_DATA_SET request and returns response
func (c *MgmtClient) CurrentDataSet() (*CurrentDataSetTLV, error) {
req := CurrentDataSetRequest()
p, err := c.Communicate(req)
if err != nil {
return nil, err
}
tlv, ok := p.TLV.(*CurrentDataSetTLV)
if !ok {
return nil, fmt.Errorf("got unexpected management TLV %T, wanted %T", p.TLV, tlv)
}
return tlv, nil
}
// ClockAccuracy sends CLOCK_ACCURACY request and returns response
func (c *MgmtClient) ClockAccuracy() (*ClockAccuracyTLV, error) {
req := ClockAccuracyRequest()
p, err := c.Communicate(req)
if err != nil {
return nil, err
}
tlv, ok := p.TLV.(*ClockAccuracyTLV)
if !ok {
return nil, fmt.Errorf("got unexpected management TLV %T, wanted %T", p.TLV, tlv)
}
return tlv, nil
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package protocol
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/facebook/time/hostendian"
)
// ManagementID is type for Management IDs
type ManagementID uint16
// Management IDs we support, from Table 59 managementId values
const (
IDNullPTPManagement ManagementID = 0x0000
IDClockDescription ManagementID = 0x0001
IDUserDescription ManagementID = 0x0002
IDSaveInNonVolatileStorage ManagementID = 0x0003
IDResetNonVolatileStorage ManagementID = 0x0004
IDInitialize ManagementID = 0x0005
IDFaultLog ManagementID = 0x0006
IDFaultLogReset ManagementID = 0x0007
IDDefaultDataSet ManagementID = 0x2000
IDCurrentDataSet ManagementID = 0x2001
IDParentDataSet ManagementID = 0x2002
IDTimePropertiesDataSet ManagementID = 0x2003
IDPortDataSet ManagementID = 0x2004
IDClockAccuracy ManagementID = 0x2010
// rest of Management IDs that we don't implement yet
)
// ManagementTLV abstracts away any ManagementTLV
type ManagementTLV interface {
TLV
MgmtID() ManagementID
}
// MgmtTLVDecoderFunc is the function we use to decode management TLV from bytes
type MgmtTLVDecoderFunc func(data []byte) (ManagementTLV, error)
// default decoders for TLVs we implemented ourselves
var mgmtTLVDecoder = map[ManagementID]MgmtTLVDecoderFunc{
IDDefaultDataSet: func(data []byte) (ManagementTLV, error) {
r := bytes.NewReader(data)
tlv := &DefaultDataSetTLV{}
if err := binary.Read(r, binary.BigEndian, tlv); err != nil {
return nil, err
}
return tlv, nil
},
IDCurrentDataSet: func(data []byte) (ManagementTLV, error) {
r := bytes.NewReader(data)
tlv := &CurrentDataSetTLV{}
if err := binary.Read(r, binary.BigEndian, tlv); err != nil {
return nil, err
}
return tlv, nil
},
IDParentDataSet: func(data []byte) (ManagementTLV, error) {
r := bytes.NewReader(data)
tlv := &ParentDataSetTLV{}
if err := binary.Read(r, binary.BigEndian, tlv); err != nil {
return nil, err
}
return tlv, nil
},
IDPortStatsNP: func(data []byte) (ManagementTLV, error) {
r := bytes.NewReader(data)
tlv := &PortStatsNPTLV{}
if err := binary.Read(r, binary.BigEndian, &tlv.ManagementTLVHead); err != nil {
return nil, err
}
if err := binary.Read(r, binary.BigEndian, &tlv.PortIdentity); err != nil {
return nil, err
}
// fun part that cost me few hours, this is sent over wire as host endian (which typically is LittlEndian), while EVERYTHING ELSE is BigEndian.
if err := binary.Read(r, hostendian.Order, &tlv.PortStats); err != nil {
return nil, err
}
return tlv, nil
},
IDTimeStatusNP: func(data []byte) (ManagementTLV, error) {
r := bytes.NewReader(data)
tlv := &TimeStatusNPTLV{}
if err := binary.Read(r, binary.BigEndian, tlv); err != nil {
return nil, err
}
return tlv, nil
},
IDPortServiceStatsNP: func(data []byte) (ManagementTLV, error) {
r := bytes.NewReader(data)
tlv := &PortServiceStatsNPTLV{}
if err := binary.Read(r, binary.BigEndian, &tlv.ManagementTLVHead); err != nil {
return nil, err
}
if err := binary.Read(r, binary.BigEndian, &tlv.PortIdentity); err != nil {
return nil, err
}
// host endian, just like with PortStatsNP
if err := binary.Read(r, hostendian.Order, &tlv.PortServiceStats); err != nil {
return nil, err
}
return tlv, nil
},
IDPortPropertiesNP: func(data []byte) (ManagementTLV, error) {
r := bytes.NewReader(data)
tlv := &PortPropertiesNPTLV{}
if err := binary.Read(r, binary.BigEndian, &tlv.ManagementTLVHead); err != nil {
return nil, err
}
if err := binary.Read(r, binary.BigEndian, &tlv.PortIdentity); err != nil {
return nil, err
}
if err := binary.Read(r, binary.BigEndian, &tlv.PortState); err != nil {
return nil, err
}
if err := binary.Read(r, binary.BigEndian, &tlv.Timestamping); err != nil {
return nil, err
}
if r.Len() == 0 {
return nil, fmt.Errorf("not enough data to read PortPropertiesNP Interface")
}
if err := tlv.Interface.UnmarshalBinary(data[len(data)-r.Len():]); err != nil {
return nil, fmt.Errorf("reading PortPropertiesNP Interface: %w", err)
}
return tlv, nil
},
IDUnicastMasterTableNP: func(data []byte) (ManagementTLV, error) {
r := bytes.NewReader(data)
tlv := &UnicastMasterTableNPTLV{}
if err := binary.Read(r, binary.BigEndian, &tlv.ManagementTLVHead); err != nil {
return nil, err
}
if err := binary.Read(r, binary.BigEndian, &tlv.UnicastMasterTable.ActualTableSize); err != nil {
return nil, err
}
tlv.UnicastMasterTable.UnicastMasters = make([]UnicastMasterEntry, int(tlv.UnicastMasterTable.ActualTableSize))
n := binary.Size(tlv.ManagementTLVHead) + binary.Size(tlv.UnicastMasterTable.ActualTableSize)
for i := 0; i < int(tlv.UnicastMasterTable.ActualTableSize); i++ {
entry := UnicastMasterEntry{}
if err := entry.UnmarshalBinary(data[n:]); err != nil {
return nil, err
}
tlv.UnicastMasterTable.UnicastMasters[i] = entry
n += 22 + len(entry.Address)
}
return tlv, nil
},
IDClockAccuracy: func(data []byte) (ManagementTLV, error) {
r := bytes.NewReader(data)
tlv := &ClockAccuracyTLV{}
if err := binary.Read(r, binary.BigEndian, &tlv.ManagementTLVHead); err != nil {
return nil, err
}
if err := binary.Read(r, binary.BigEndian, &tlv.ClockAccuracy); err != nil {
return nil, err
}
if err := binary.Read(r, binary.BigEndian, &tlv.Reserved); err != nil {
return nil, err
}
return tlv, nil
},
}
// RegisterMgmtTLVDecoder registers function we'll use to decode particular custom management TLV.
// IEEE1588-2019 specifies that range C000 – DFFF should be used for implementation-specific identifiers,
// and E000 – FFFE is to be assigned by alternate PTP Profile.
func RegisterMgmtTLVDecoder(id ManagementID, decoder MgmtTLVDecoderFunc) {
mgmtTLVDecoder[id] = decoder
}
// CurrentDataSetTLV Spec Table 84 - CURRENT_DATA_SET management TLV data field
type CurrentDataSetTLV struct {
ManagementTLVHead
StepsRemoved uint16
OffsetFromMaster TimeInterval
MeanPathDelay TimeInterval
}
// DefaultDataSetTLV Spec Table 69 - DEFAULT_DATA_SET management TLV data field
type DefaultDataSetTLV struct {
ManagementTLVHead
SoTSC uint8
Reserved0 uint8
NumberPorts uint16
Priority1 uint8
ClockQuality ClockQuality
Priority2 uint8
ClockIdentity ClockIdentity
DomainNumber uint8
Reserved1 uint8
}
// ParentDataSetTLV Spec Table 85 - PARENT_DATA_SET management TLV data field
type ParentDataSetTLV struct {
ManagementTLVHead
ParentPortIdentity PortIdentity
PS uint8
Reserved uint8
ObservedParentOffsetScaledLogVariance uint16
ObservedParentClockPhaseChangeRate uint32
GrandmasterPriority1 uint8
GrandmasterClockQuality ClockQuality
GrandmasterPriority2 uint8
GrandmasterIdentity ClockIdentity
}
// ClockAccuracyTLV is a TLV containing Clock Accuracy
type ClockAccuracyTLV struct {
ManagementTLVHead
ClockAccuracy ClockAccuracy
Reserved uint8
}
// CurrentDataSetRequest prepares request packet for CURRENT_DATA_SET request
func CurrentDataSetRequest() *Management {
headerSize := uint16(binary.Size(ManagementMsgHead{}))
size := uint16(binary.Size(CurrentDataSetTLV{}))
tlvHeadSize := uint16(binary.Size(TLVHead{}))
return &Management{
ManagementMsgHead: ManagementMsgHead{
Header: Header{
SdoIDAndMsgType: NewSdoIDAndMsgType(MessageManagement, 0),
Version: Version,
MessageLength: headerSize + size,
SourcePortIdentity: identity,
LogMessageInterval: MgmtLogMessageInterval,
},
TargetPortIdentity: DefaultTargetPortIdentity,
StartingBoundaryHops: 0,
BoundaryHops: 0,
ActionField: GET,
},
TLV: &CurrentDataSetTLV{
ManagementTLVHead: ManagementTLVHead{
TLVHead: TLVHead{
TLVType: TLVManagement,
LengthField: size - tlvHeadSize,
},
ManagementID: IDCurrentDataSet,
},
},
}
}
// DefaultDataSetRequest prepares request packet for DEFAULT_DATA_SET request
func DefaultDataSetRequest() *Management {
headerSize := uint16(binary.Size(ManagementMsgHead{}))
size := uint16(binary.Size(DefaultDataSetTLV{}))
tlvHeadSize := uint16(binary.Size(TLVHead{}))
return &Management{
ManagementMsgHead: ManagementMsgHead{
Header: Header{
SdoIDAndMsgType: NewSdoIDAndMsgType(MessageManagement, 0),
Version: Version,
MessageLength: headerSize + size,
SourcePortIdentity: identity,
LogMessageInterval: MgmtLogMessageInterval,
},
TargetPortIdentity: DefaultTargetPortIdentity,
StartingBoundaryHops: 0,
BoundaryHops: 0,
ActionField: GET,
},
TLV: &DefaultDataSetTLV{
ManagementTLVHead: ManagementTLVHead{
TLVHead: TLVHead{
TLVType: TLVManagement,
LengthField: size - tlvHeadSize,
},
ManagementID: IDDefaultDataSet,
},
},
}
}
// ParentDataSetRequest prepares request packet for PARENT_DATA_SET request
func ParentDataSetRequest() *Management {
headerSize := uint16(binary.Size(ManagementMsgHead{}))
size := uint16(binary.Size(ParentDataSetTLV{}))
tlvHeadSize := uint16(binary.Size(TLVHead{}))
return &Management{
ManagementMsgHead: ManagementMsgHead{
Header: Header{
SdoIDAndMsgType: NewSdoIDAndMsgType(MessageManagement, 0),
Version: Version,
MessageLength: headerSize + size,
SourcePortIdentity: identity,
LogMessageInterval: MgmtLogMessageInterval,
},
TargetPortIdentity: DefaultTargetPortIdentity,
StartingBoundaryHops: 0,
BoundaryHops: 0,
ActionField: GET,
},
TLV: &ParentDataSetTLV{
ManagementTLVHead: ManagementTLVHead{
TLVHead: TLVHead{
TLVType: TLVManagement,
LengthField: size - tlvHeadSize,
},
ManagementID: IDParentDataSet,
},
},
}
}
// ClockAccuracyRequest prepares request packet for CLOCK_ACCURACY request
func ClockAccuracyRequest() *Management {
headerSize := uint16(binary.Size(ManagementMsgHead{}))
size := uint16(binary.Size(ClockAccuracyTLV{}))
tlvHeadSize := uint16(binary.Size(TLVHead{}))
return &Management{
ManagementMsgHead: ManagementMsgHead{
Header: Header{
SdoIDAndMsgType: NewSdoIDAndMsgType(MessageManagement, 0),
Version: Version,
MessageLength: headerSize + size,
SourcePortIdentity: identity,
LogMessageInterval: MgmtLogMessageInterval,
},
TargetPortIdentity: DefaultTargetPortIdentity,
StartingBoundaryHops: 0,
BoundaryHops: 0,
ActionField: GET,
},
TLV: &ClockAccuracyTLV{
ManagementTLVHead: ManagementTLVHead{
TLVHead: TLVHead{
TLVType: TLVManagement,
LengthField: size - tlvHeadSize,
},
ManagementID: IDClockAccuracy,
},
},
}
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package protocol
// all references are given for IEEE 1588-2019 Standard
import (
"bytes"
"encoding"
"encoding/binary"
"fmt"
)
// what version of PTP protocol we implement
const (
MajorVersion uint8 = 2
MinorVersion uint8 = 1
Version uint8 = MinorVersion<<4 | MajorVersion
MajorVersionMask uint8 = 0x0f
)
/*
UDP port numbers:
The UDP destination port of a PTP event message shall be 319.
The UDP destination port of a multicast PTP general message shall be 320.
The UDP destination port of a unicast PTP general message that is addressed to a PTP Instance shall be 320.
The UDP destination port of a unicast PTP general message that is addressed to a manager shall be the UDP source
port value of the PTP message to which this is a response.
*/
var (
PortEvent = 319
PortGeneral = 320
)
// TrailingBytes - PTP over UDPv6 requires adding extra two bytes that
// may be modified by the initiator or an intermediate PTP Instance to ensure that the UDP checksum
// remains uncompromised after any modification of PTP fields.
// We simply always add them - in worst case they add extra 2 unused bytes when used over UDPv4.
const TrailingBytes = 2
var twoZeros = []byte{0, 0}
// MgmtLogMessageInterval is the default LogInterval value used in Management packets
const MgmtLogMessageInterval LogInterval = 0x7f // as per Table 42 Values of logMessageInterval field
// DefaultTargetPortIdentity is a port identity that means any port
var DefaultTargetPortIdentity = PortIdentity{
ClockIdentity: 0xffffffffffffffff,
PortNumber: 0xffff,
}
// Header Table 35 Common PTP message header
type Header struct {
SdoIDAndMsgType SdoIDAndMsgType // first 4 bits is SdoId, next 4 bytes are msgtype
Version uint8
MessageLength uint16
DomainNumber uint8
MinorSdoID uint8
FlagField uint16
CorrectionField Correction
MessageTypeSpecific uint32
SourcePortIdentity PortIdentity
SequenceID uint16
ControlField uint8 // the use of this field is obsolete according to IEEE, unless it's ipv4
LogMessageInterval LogInterval // see Table 42 Values of logMessageInterval field
}
const headerSize = 34 // bytes
// unmarshalHeader is not a Header.UnmarshalBinary to prevent all packets
// from having default (and incomplete) UnmarshalBinary implementation through embedding
func unmarshalHeader(p *Header, b []byte) {
p.SdoIDAndMsgType = SdoIDAndMsgType(b[0])
p.Version = b[1]
p.MessageLength = binary.BigEndian.Uint16(b[2:])
p.DomainNumber = b[4]
p.MinorSdoID = b[5]
p.FlagField = binary.BigEndian.Uint16(b[6:])
p.CorrectionField = Correction(binary.BigEndian.Uint64(b[8:]))
p.MessageTypeSpecific = binary.BigEndian.Uint32(b[16:])
p.SourcePortIdentity.ClockIdentity = ClockIdentity(binary.BigEndian.Uint64(b[20:]))
p.SourcePortIdentity.PortNumber = binary.BigEndian.Uint16(b[28:])
p.SequenceID = binary.BigEndian.Uint16(b[30:])
p.ControlField = b[32]
p.LogMessageInterval = LogInterval(b[33])
}
// MessageType returns MessageType
func (p *Header) MessageType() MessageType {
return p.SdoIDAndMsgType.MsgType()
}
// SetSequence populates sequence field
func (p *Header) SetSequence(sequence uint16) {
p.SequenceID = sequence
}
func checkPacketLength(p *Header, l int) error {
if int(p.MessageLength) > l {
return fmt.Errorf("cannot decode message of length %d from %d bytes", p.MessageLength, l)
}
return nil
}
// headerMarshalBinaryTo is not a Header.MarshalBinaryTo to prevent all packets
// from having default (and incomplete) MarshalBinaryTo implementation through embedding
func headerMarshalBinaryTo(p *Header, b []byte) int {
b[0] = byte(p.SdoIDAndMsgType)
b[1] = p.Version
binary.BigEndian.PutUint16(b[2:], p.MessageLength)
b[4] = p.DomainNumber
b[5] = p.MinorSdoID
binary.BigEndian.PutUint16(b[6:], p.FlagField)
binary.BigEndian.PutUint64(b[8:], uint64(p.CorrectionField))
binary.BigEndian.PutUint32(b[16:], p.MessageTypeSpecific)
binary.BigEndian.PutUint64(b[20:], uint64(p.SourcePortIdentity.ClockIdentity))
binary.BigEndian.PutUint16(b[28:], p.SourcePortIdentity.PortNumber)
binary.BigEndian.PutUint16(b[30:], p.SequenceID)
b[32] = p.ControlField
b[33] = byte(p.LogMessageInterval)
return headerSize
}
// flags used in FlagField as per Table 37 Values of flagField
const (
// first octet
FlagAlternateMaster uint16 = 1 << (8 + 0)
FlagTwoStep uint16 = 1 << (8 + 1)
FlagUnicast uint16 = 1 << (8 + 2)
FlagProfileSpecific1 uint16 = 1 << (8 + 5)
FlagProfileSpecific2 uint16 = 1 << (8 + 6)
// second octet
FlagLeap61 uint16 = 1 << 0
FlagLeap59 uint16 = 1 << 1
FlagCurrentUtcOffsetValid uint16 = 1 << 2
FlagPTPTimescale uint16 = 1 << 3
FlagTimeTraceable uint16 = 1 << 4
FlagFrequencyTraceable uint16 = 1 << 5
FlagSynchronizationUncertain uint16 = 1 << 6
)
// General PTP messages
// All packets are split in three parts: Header (which is common), body that is unique
// for most packets (both in length and structure), and finally a suffix of zero or more TLVs
// AnnounceBody Table 43 Announce message fields
type AnnounceBody struct {
OriginTimestamp Timestamp
CurrentUTCOffset int16
Reserved uint8
GrandmasterPriority1 uint8
GrandmasterClockQuality ClockQuality
GrandmasterPriority2 uint8
GrandmasterIdentity ClockIdentity
StepsRemoved uint16
TimeSource TimeSource
}
// Announce is a full Announce packet
type Announce struct {
Header
AnnounceBody
TLVs []TLV
}
// MarshalBinaryTo marshals bytes to Announce
func (p *Announce) MarshalBinaryTo(b []byte) (int, error) {
if len(b) < headerSize+30 {
return 0, fmt.Errorf("not enough buffer to write Announce")
}
n := headerMarshalBinaryTo(&p.Header, b)
copy(b[n:], p.OriginTimestamp.Seconds[:]) //uint48
binary.BigEndian.PutUint32(b[n+6:], p.OriginTimestamp.Nanoseconds)
binary.BigEndian.PutUint16(b[n+10:], uint16(p.CurrentUTCOffset))
b[n+12] = p.Reserved
b[n+13] = p.GrandmasterPriority1
b[n+14] = byte(p.GrandmasterClockQuality.ClockClass)
b[n+15] = byte(p.GrandmasterClockQuality.ClockAccuracy)
binary.BigEndian.PutUint16(b[n+16:], p.GrandmasterClockQuality.OffsetScaledLogVariance)
b[n+18] = p.GrandmasterPriority2
binary.BigEndian.PutUint64(b[n+19:], uint64(p.GrandmasterIdentity))
binary.BigEndian.PutUint16(b[n+27:], p.StepsRemoved)
b[n+29] = byte(p.TimeSource)
// marshal TLVs if present
pos := n + 30
tlvLen, err := writeTLVs(p.TLVs, b[pos:])
return pos + tlvLen, err
}
// UnmarshalBinary unmarshals bytes to Announce
func (p *Announce) UnmarshalBinary(b []byte) error {
if len(b) < headerSize+30 {
return fmt.Errorf("not enough data to decode Announce")
}
unmarshalHeader(&p.Header, b)
if err := checkPacketLength(&p.Header, len(b)); err != nil {
return err
}
n := headerSize
copy(p.OriginTimestamp.Seconds[:], b[n:]) //uint48
p.OriginTimestamp.Nanoseconds = binary.BigEndian.Uint32(b[n+6:])
p.CurrentUTCOffset = int16(binary.BigEndian.Uint16(b[n+10:]))
p.Reserved = b[n+12]
p.GrandmasterPriority1 = b[n+13]
p.GrandmasterClockQuality.ClockClass = ClockClass(b[n+14])
p.GrandmasterClockQuality.ClockAccuracy = ClockAccuracy(b[n+15])
p.GrandmasterClockQuality.OffsetScaledLogVariance = binary.BigEndian.Uint16(b[n+16:])
p.GrandmasterPriority2 = b[n+18]
p.GrandmasterIdentity = ClockIdentity(binary.BigEndian.Uint64(b[n+19:]))
p.StepsRemoved = binary.BigEndian.Uint16(b[n+27:])
p.TimeSource = TimeSource(b[n+29])
pos := n + 30
// unmarshal TLVs if present
var err error
p.TLVs, err = readTLVs(p.TLVs, int(p.MessageLength)-pos, b[pos:])
if err != nil {
return err
}
return nil
}
// MarshalBinary converts packet to []bytes
func (p *Announce) MarshalBinary() ([]byte, error) {
buf := make([]byte, 508)
n, err := p.MarshalBinaryTo(buf)
return buf[:n], err
}
// SyncDelayReqBody Table 44 Sync and Delay_Req message fields
type SyncDelayReqBody struct {
OriginTimestamp Timestamp
}
// SyncDelayReq is a full Sync/Delay_Req packet
type SyncDelayReq struct {
Header
SyncDelayReqBody
TLVs []TLV
}
// MarshalBinaryTo marshals bytes to SyncDelayReq
func (p *SyncDelayReq) MarshalBinaryTo(b []byte) (int, error) {
if len(b) < headerSize+10 {
return 0, fmt.Errorf("not enough buffer to write SyncDelayReq")
}
n := headerMarshalBinaryTo(&p.Header, b)
copy(b[n:], p.OriginTimestamp.Seconds[:]) //uint48
binary.BigEndian.PutUint32(b[n+6:], p.OriginTimestamp.Nanoseconds)
pos := n + 10
tlvLen, err := writeTLVs(p.TLVs, b[pos:])
return pos + tlvLen, err
}
// MarshalBinary converts packet to []bytes
func (p *SyncDelayReq) MarshalBinary() ([]byte, error) {
buf := make([]byte, 50)
n, err := p.MarshalBinaryTo(buf)
return buf[:n], err
}
// UnmarshalBinary unmarshals bytes to SyncDelayReq
func (p *SyncDelayReq) UnmarshalBinary(b []byte) error {
if len(b) < headerSize+10 {
return fmt.Errorf("not enough data to decode SyncDelayReq")
}
unmarshalHeader(&p.Header, b)
if err := checkPacketLength(&p.Header, len(b)); err != nil {
return err
}
copy(p.OriginTimestamp.Seconds[:], b[headerSize:]) //uint48
p.OriginTimestamp.Nanoseconds = binary.BigEndian.Uint32(b[headerSize+6:])
pos := headerSize + 10
var err error
p.TLVs, err = readTLVs(p.TLVs, int(p.MessageLength)-pos, b[pos:])
return err
}
// FollowUpBody Table 45 Follow_Up message fields
type FollowUpBody struct {
PreciseOriginTimestamp Timestamp
}
// FollowUp is a full Follow_Up packet
type FollowUp struct {
Header
FollowUpBody
}
// MarshalBinaryTo marshals bytes to FollowUp
func (p *FollowUp) MarshalBinaryTo(b []byte) (int, error) {
if len(b) < headerSize+10 {
return 0, fmt.Errorf("not enough buffer to write FollowUp")
}
n := headerMarshalBinaryTo(&p.Header, b)
copy(b[n:], p.PreciseOriginTimestamp.Seconds[:]) //uint48
binary.BigEndian.PutUint32(b[n+6:], p.PreciseOriginTimestamp.Nanoseconds)
return n + 10, nil
}
// MarshalBinary converts packet to []bytes
func (p *FollowUp) MarshalBinary() ([]byte, error) {
buf := make([]byte, 44)
n, err := p.MarshalBinaryTo(buf)
return buf[:n], err
}
// UnmarshalBinary unmarshals bytes to FollowUp
func (p *FollowUp) UnmarshalBinary(b []byte) error {
if len(b) < headerSize+10 {
return fmt.Errorf("not enough data to decode FollowUp")
}
unmarshalHeader(&p.Header, b)
if err := checkPacketLength(&p.Header, len(b)); err != nil {
return err
}
copy(p.PreciseOriginTimestamp.Seconds[:], b[headerSize:]) //uint48
p.PreciseOriginTimestamp.Nanoseconds = binary.BigEndian.Uint32(b[headerSize+6:])
return nil
}
// DelayRespBody Table 46 Delay_Resp message fields
type DelayRespBody struct {
ReceiveTimestamp Timestamp
RequestingPortIdentity PortIdentity
}
// DelayResp is a full Delay_Resp packet
type DelayResp struct {
Header
DelayRespBody
}
// MarshalBinaryTo marshals bytes to DelayResp
func (p *DelayResp) MarshalBinaryTo(b []byte) (int, error) {
if len(b) < headerSize+20 {
return 0, fmt.Errorf("not enough buffer to write DelayResp")
}
n := headerMarshalBinaryTo(&p.Header, b)
copy(b[n:], p.ReceiveTimestamp.Seconds[:]) //uint48
binary.BigEndian.PutUint32(b[n+6:], p.ReceiveTimestamp.Nanoseconds)
binary.BigEndian.PutUint64(b[n+10:], uint64(p.RequestingPortIdentity.ClockIdentity))
binary.BigEndian.PutUint16(b[n+18:], p.RequestingPortIdentity.PortNumber)
return n + 20, nil
}
// MarshalBinary converts packet to []bytes
func (p *DelayResp) MarshalBinary() ([]byte, error) {
buf := make([]byte, 54)
n, err := p.MarshalBinaryTo(buf)
return buf[:n], err
}
// UnmarshalBinary unmarshals bytes to DelayResp
func (p *DelayResp) UnmarshalBinary(b []byte) error {
if len(b) < headerSize+20 {
return fmt.Errorf("not enough data to decode DelayResp")
}
unmarshalHeader(&p.Header, b)
if err := checkPacketLength(&p.Header, len(b)); err != nil {
return err
}
copy(p.ReceiveTimestamp.Seconds[:], b[headerSize:]) //uint48
p.ReceiveTimestamp.Nanoseconds = binary.BigEndian.Uint32(b[headerSize+6:])
p.RequestingPortIdentity.ClockIdentity = ClockIdentity(binary.BigEndian.Uint64(b[headerSize+10:]))
p.RequestingPortIdentity.PortNumber = binary.BigEndian.Uint16(b[headerSize+18:])
return nil
}
// PDelayReqBody Table 47 Pdelay_Req message fields
type PDelayReqBody struct {
OriginTimestamp Timestamp
Reserved [10]uint8
}
// PDelayReq is a full Pdelay_Req packet
type PDelayReq struct {
Header
PDelayReqBody
}
// PDelayRespBody Table 48 Pdelay_Resp message fields
type PDelayRespBody struct {
RequestReceiptTimestamp Timestamp
RequestingPortIdentity PortIdentity
}
// PDelayResp is a full Pdelay_Resp packet
type PDelayResp struct {
Header
PDelayRespBody
}
// PDelayRespFollowUpBody Table 49 Pdelay_Resp_Follow_Up message fields
type PDelayRespFollowUpBody struct {
ResponseOriginTimestamp Timestamp
RequestingPortIdentity PortIdentity
}
// PDelayRespFollowUp is a full Pdelay_Resp_Follow_Up packet
type PDelayRespFollowUp struct {
Header
PDelayRespFollowUpBody
}
// Packet is an interface to abstract all different packets
type Packet interface {
MessageType() MessageType
SetSequence(uint16)
}
// BinaryMarshalerTo is an interface implemented by an object that can marshal itself into a binary form into provided []byte
type BinaryMarshalerTo interface {
MarshalBinaryTo([]byte) (int, error)
}
// BytesTo marshalls packets that support this optimized marshalling into []byte
func BytesTo(p BinaryMarshalerTo, buf []byte) (int, error) {
n, err := p.MarshalBinaryTo(buf)
if err != nil {
return 0, err
}
// add two zero bytes
buf[n] = 0x0
buf[n+1] = 0x0
return n + 2, nil
}
// Bytes converts any packet to []bytes
func Bytes(p Packet) ([]byte, error) {
// interface smuggling
if pp, ok := p.(encoding.BinaryMarshaler); ok {
b, err := pp.MarshalBinary()
return append(b, twoZeros...), err
}
var bytes bytes.Buffer
err := binary.Write(&bytes, binary.BigEndian, p)
if err != nil {
return nil, err
}
err = binary.Write(&bytes, binary.BigEndian, twoZeros)
return bytes.Bytes(), err
}
// FromBytes parses []byte into any packet
func FromBytes(rawBytes []byte, p Packet) error {
// interface smuggling
if pp, ok := p.(encoding.BinaryUnmarshaler); ok {
return pp.UnmarshalBinary(rawBytes)
}
reader := bytes.NewReader(rawBytes)
return binary.Read(reader, binary.BigEndian, p)
}
// DecodePacket provides single entry point to try and decode any []bytes to PTPv2 packet.
// It can be used for easy integration with anything that provides UDP packet payload as bytes.
// Resulting Packet user can then either switch based on MessageType(), or just with type switch.
func DecodePacket(b []byte) (Packet, error) {
r := bytes.NewReader(b)
head := &Header{}
if err := binary.Read(r, binary.BigEndian, head); err != nil {
return nil, err
}
msgType := head.MessageType()
var p Packet
switch msgType {
case MessageSync, MessageDelayReq:
p = &SyncDelayReq{}
case MessagePDelayReq:
p = &PDelayReq{}
case MessagePDelayResp:
p = &PDelayResp{}
case MessageFollowUp:
p = &FollowUp{}
case MessageDelayResp:
p = &DelayResp{}
case MessagePDelayRespFollowUp:
p = &PDelayRespFollowUp{}
case MessageAnnounce:
p = &Announce{}
case MessageSignaling:
p = &Signaling{}
case MessageManagement:
return decodeMgmtPacket(b)
default:
return nil, fmt.Errorf("unsupported type %s", msgType)
}
if err := FromBytes(b, p); err != nil {
return nil, err
}
return p, nil
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package protocol
// Support has been included for some non-standard extensions provided by the ptp4l implementation; the TLVs IDPortStatsNP and IDTimeStatusNP
// Implemented as present in linuxptp master d95f4cd6e4a7c6c51a220c58903110a2326885e7
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"github.com/facebook/time/hostendian"
)
// PTP4lSock is the default path to PTP4L socket
const PTP4lSock = "/var/run/ptp4l"
// ptp4l-specific management TLV ids
const (
IDTimeStatusNP ManagementID = 0xC000
IDPortPropertiesNP ManagementID = 0xC004
IDPortStatsNP ManagementID = 0xC005
IDPortServiceStatsNP ManagementID = 0xC007
IDUnicastMasterTableNP ManagementID = 0xC008
)
// UnicastMasterState is a enum describing the unicast master state in ptp4l unicast master table
type UnicastMasterState uint8
// possible states of unicast master in ptp4l unicast master table
const (
UnicastMasterStateWait UnicastMasterState = iota
UnicastMasterStateHaveAnnounce
UnicastMasterStateNeedSYDY
UnicastMasterStateHaveSYDY
)
// UnicastMasterStateToString is a map from UnicastMasterState to string
var UnicastMasterStateToString = map[UnicastMasterState]string{
UnicastMasterStateWait: "WAIT",
UnicastMasterStateHaveAnnounce: "HAVE_ANN",
UnicastMasterStateNeedSYDY: "NEED_SYDY",
UnicastMasterStateHaveSYDY: "HAVE_SYDY",
}
func (t UnicastMasterState) String() string {
return UnicastMasterStateToString[t]
}
// Timestamping is a ptp4l-specific enum describing timestamping type
type Timestamping uint8
const (
// TimestampingSoftware is a software timestamp const
TimestampingSoftware Timestamping = iota
// TimestampingHardware is a hardware timestamp const
TimestampingHardware
// TimestampingLegacyHW is a legacy hardware timestamp const
TimestampingLegacyHW
// TimestampingOneStep is a one step timestamp const
TimestampingOneStep
// TimestampingP2P1Step is a P2P one step timestamp const
TimestampingP2P1Step
)
// PortStats is a ptp4l struct containing port statistics
type PortStats struct {
RXMsgType [16]uint64
TXMsgType [16]uint64
}
// PortStatsNPTLV is a ptp4l struct containing port identinity and statistics
type PortStatsNPTLV struct {
ManagementTLVHead
PortIdentity PortIdentity
PortStats PortStats
}
// MarshalBinary converts packet to []bytes
func (p *PortStatsNPTLV) MarshalBinary() ([]byte, error) {
var bytes bytes.Buffer
if err := binary.Write(&bytes, binary.BigEndian, p.ManagementTLVHead); err != nil {
return nil, err
}
if err := binary.Write(&bytes, binary.BigEndian, p.PortIdentity); err != nil {
return nil, err
}
if err := binary.Write(&bytes, hostendian.Order, p.PortStats); err != nil {
return nil, err
}
return bytes.Bytes(), nil
}
// ScaledNS is some struct used by ptp4l to report phase change
type ScaledNS struct {
NanosecondsMSB uint16
NanosecondsLSB uint64
FractionalNanoseconds uint16
}
// TimeStatusNPTLV is a ptp4l struct containing actually useful instance metrics
type TimeStatusNPTLV struct {
ManagementTLVHead
MasterOffsetNS int64
IngressTimeNS int64 // this is PHC time
CumulativeScaledRateOffset int32
ScaledLastGmPhaseChange int32
GMTimeBaseIndicator uint16
LastGmPhaseChange ScaledNS
GMPresent int32
GMIdentity ClockIdentity
}
// PortPropertiesNPTLV is a ptp4l struct containing port properties
type PortPropertiesNPTLV struct {
ManagementTLVHead
PortIdentity PortIdentity
PortState PortState
Timestamping Timestamping
Interface PTPText
}
// MarshalBinary converts packet to []bytes
func (p *PortPropertiesNPTLV) MarshalBinary() ([]byte, error) {
var bytes bytes.Buffer
if err := binary.Write(&bytes, binary.BigEndian, p.ManagementTLVHead); err != nil {
return nil, err
}
if err := binary.Write(&bytes, binary.BigEndian, p.PortIdentity); err != nil {
return nil, err
}
if err := binary.Write(&bytes, hostendian.Order, p.PortState); err != nil {
return nil, err
}
if err := binary.Write(&bytes, hostendian.Order, p.Timestamping); err != nil {
return nil, err
}
interfaceBytes, err := p.Interface.MarshalBinary()
if err != nil {
return nil, err
}
if err := binary.Write(&bytes, hostendian.Order, interfaceBytes); err != nil {
return nil, err
}
return bytes.Bytes(), nil
}
// PortServiceStats is a ptp4l struct containing counters for different port events, which we added in linuxptp cfbb8bdb50f5a38687fcddccbe6a264c6a078bbd
type PortServiceStats struct {
AnnounceTimeout uint64 `json:"ptp.servicestats.announce_timeout"`
SyncTimeout uint64 `json:"ptp.servicestats.sync_timeout"`
DelayTimeout uint64 `json:"ptp.servicestats.delay_timeout"`
UnicastServiceTimeout uint64 `json:"ptp.servicestats.unicast_service_timeout"`
UnicastRequestTimeout uint64 `json:"ptp.servicestats.unicast_request_timeout"`
MasterAnnounceTimeout uint64 `json:"ptp.servicestats.master_announce_timeout"`
MasterSyncTimeout uint64 `json:"ptp.servicestats.master_sync_timeout"`
QualificationTimeout uint64 `json:"ptp.servicestats.qualification_timeout"`
SyncMismatch uint64 `json:"ptp.servicestats.sync_mismatch"`
FollowupMismatch uint64 `json:"ptp.servicestats.followup_mismatch"`
}
// PortServiceStatsNPTLV is a management TLV added in linuxptp cfbb8bdb50f5a38687fcddccbe6a264c6a078bbd
type PortServiceStatsNPTLV struct {
ManagementTLVHead
PortIdentity PortIdentity
PortServiceStats PortServiceStats
}
// MarshalBinary converts packet to []bytes
func (p *PortServiceStatsNPTLV) MarshalBinary() ([]byte, error) {
var bytes bytes.Buffer
if err := binary.Write(&bytes, binary.BigEndian, p.ManagementTLVHead); err != nil {
return nil, err
}
if err := binary.Write(&bytes, binary.BigEndian, p.PortIdentity); err != nil {
return nil, err
}
if err := binary.Write(&bytes, hostendian.Order, p.PortServiceStats); err != nil {
return nil, err
}
return bytes.Bytes(), nil
}
// UnicastMasterEntry is an entry in UnicastMasterTable that ptp4l exports via management TLV
type UnicastMasterEntry struct {
PortIdentity PortIdentity
ClockQuality ClockQuality
Selected bool
PortState UnicastMasterState
Priority1 uint8
Priority2 uint8
Address net.IP
}
// UnmarshalBinary implements Unmarshaller interface
func (e *UnicastMasterEntry) UnmarshalBinary(b []byte) error {
var err error
if len(b) < 26 { // 22 byte for struct, at least 4 for address)
return fmt.Errorf("not enough data to decode UnicastMasterEntry")
}
e.PortIdentity.ClockIdentity = ClockIdentity(binary.BigEndian.Uint64(b[0:]))
e.PortIdentity.PortNumber = binary.BigEndian.Uint16(b[8:])
e.ClockQuality.ClockClass = ClockClass(b[10])
e.ClockQuality.ClockAccuracy = ClockAccuracy(b[11])
e.ClockQuality.OffsetScaledLogVariance = binary.BigEndian.Uint16(b[12:])
if b[14] == 0 {
e.Selected = false
} else if b[14] == 1 {
e.Selected = true
} else {
return fmt.Errorf("unexpected 'selected' value %d", b[14])
}
e.PortState = UnicastMasterState(b[15])
e.Priority1 = b[16]
e.Priority2 = b[17]
pa := &PortAddress{}
if err := pa.UnmarshalBinary(b[18:]); err != nil {
return err
}
e.Address, err = pa.IP()
if err != nil {
return err
}
return nil
}
// MarshalBinary converts UnicastMasterEntry to []bytes
func (e *UnicastMasterEntry) MarshalBinary() ([]byte, error) {
var bytes bytes.Buffer
if err := binary.Write(&bytes, binary.BigEndian, e.PortIdentity); err != nil {
return nil, err
}
if err := binary.Write(&bytes, binary.BigEndian, e.ClockQuality); err != nil {
return nil, err
}
var selectedBin uint8
if e.Selected {
selectedBin = 1
}
if err := binary.Write(&bytes, binary.BigEndian, selectedBin); err != nil {
return nil, err
}
if err := binary.Write(&bytes, binary.BigEndian, e.PortState); err != nil {
return nil, err
}
if err := binary.Write(&bytes, binary.BigEndian, e.Priority1); err != nil {
return nil, err
}
if err := binary.Write(&bytes, binary.BigEndian, e.Priority2); err != nil {
return nil, err
}
var pa PortAddress
asIPv4 := e.Address.To4()
if asIPv4 != nil {
pa = PortAddress{
NetworkProtocol: TransportTypeUDPIPV4,
AddressLength: 4,
AddressField: asIPv4,
}
} else {
pa = PortAddress{
NetworkProtocol: TransportTypeUDPIPV6,
AddressLength: 16,
AddressField: e.Address,
}
}
portBytes, err := pa.MarshalBinary()
if err != nil {
return nil, err
}
if err := binary.Write(&bytes, binary.BigEndian, portBytes); err != nil {
return nil, err
}
return bytes.Bytes(), nil
}
// UnicastMasterTable is a table of UnicastMasterEntries
type UnicastMasterTable struct {
ActualTableSize uint16
UnicastMasters []UnicastMasterEntry
}
// UnicastMasterTableNPTLV is a custom management packet that exports unicast master table state
type UnicastMasterTableNPTLV struct {
ManagementTLVHead
UnicastMasterTable UnicastMasterTable
}
// MarshalBinary converts packet to []bytes
func (p *UnicastMasterTableNPTLV) MarshalBinary() ([]byte, error) {
var bytes bytes.Buffer
if err := binary.Write(&bytes, binary.BigEndian, p.ManagementTLVHead); err != nil {
return nil, err
}
if err := binary.Write(&bytes, binary.BigEndian, p.UnicastMasterTable.ActualTableSize); err != nil {
return nil, err
}
for _, e := range p.UnicastMasterTable.UnicastMasters {
entryBytes, err := e.MarshalBinary()
if err != nil {
return nil, err
}
if err := binary.Write(&bytes, binary.BigEndian, entryBytes); err != nil {
return nil, err
}
}
return bytes.Bytes(), nil
}
// PortStatsNPRequest prepares request packet for PORT_STATS_NP request
func PortStatsNPRequest() *Management {
headerSize := uint16(binary.Size(ManagementMsgHead{}))
tlvHeadSize := uint16(binary.Size(TLVHead{}))
// we send request with no portStats data just like pmc does
return &Management{
ManagementMsgHead: ManagementMsgHead{
Header: Header{
SdoIDAndMsgType: NewSdoIDAndMsgType(MessageManagement, 0),
Version: Version,
MessageLength: headerSize + tlvHeadSize + 2,
SourcePortIdentity: identity,
LogMessageInterval: MgmtLogMessageInterval,
},
TargetPortIdentity: DefaultTargetPortIdentity,
StartingBoundaryHops: 0,
BoundaryHops: 0,
ActionField: GET,
},
TLV: &ManagementTLVHead{
TLVHead: TLVHead{
TLVType: TLVManagement,
LengthField: 2,
},
ManagementID: IDPortStatsNP,
},
}
}
// PortStatsNP sends PORT_STATS_NP request and returns response
func (c *MgmtClient) PortStatsNP() (*PortStatsNPTLV, error) {
req := PortStatsNPRequest()
p, err := c.Communicate(req)
if err != nil {
return nil, err
}
tlv, ok := p.TLV.(*PortStatsNPTLV)
if !ok {
return nil, fmt.Errorf("got unexpected management TLV %T, wanted %T", p.TLV, tlv)
}
return tlv, nil
}
// TimeStatusNPRequest prepares request packet for TIME_STATUS_NP request
func TimeStatusNPRequest() *Management {
headerSize := uint16(binary.Size(ManagementMsgHead{}))
tlvHeadSize := uint16(binary.Size(TLVHead{}))
// we send request with no TimeStatusNP data just like pmc does
return &Management{
ManagementMsgHead: ManagementMsgHead{
Header: Header{
SdoIDAndMsgType: NewSdoIDAndMsgType(MessageManagement, 0),
Version: Version,
MessageLength: headerSize + tlvHeadSize + 2,
SourcePortIdentity: identity,
LogMessageInterval: MgmtLogMessageInterval,
},
TargetPortIdentity: DefaultTargetPortIdentity,
StartingBoundaryHops: 0,
BoundaryHops: 0,
ActionField: GET,
},
TLV: &ManagementTLVHead{
TLVHead: TLVHead{
TLVType: TLVManagement,
LengthField: 2,
},
ManagementID: IDTimeStatusNP,
},
}
}
// TimeStatusNP sends TIME_STATUS_NP request and returns response
func (c *MgmtClient) TimeStatusNP() (*TimeStatusNPTLV, error) {
req := TimeStatusNPRequest()
p, err := c.Communicate(req)
if err != nil {
return nil, err
}
tlv, ok := p.TLV.(*TimeStatusNPTLV)
if !ok {
return nil, fmt.Errorf("got unexpected management TLV %T, wanted %T", p.TLV, tlv)
}
return tlv, nil
}
// PortServiceStatsNPRequest prepares request packet for PORT_SERVICE_STATS_NP request
func PortServiceStatsNPRequest() *Management {
headerSize := uint16(binary.Size(ManagementMsgHead{}))
tlvHeadSize := uint16(binary.Size(TLVHead{}))
// we send request with no portServiceStats data just like pmc does
return &Management{
ManagementMsgHead: ManagementMsgHead{
Header: Header{
SdoIDAndMsgType: NewSdoIDAndMsgType(MessageManagement, 0),
Version: Version,
MessageLength: headerSize + tlvHeadSize + 2,
SourcePortIdentity: identity,
LogMessageInterval: MgmtLogMessageInterval,
},
TargetPortIdentity: DefaultTargetPortIdentity,
StartingBoundaryHops: 0,
BoundaryHops: 0,
ActionField: GET,
},
TLV: &ManagementTLVHead{
TLVHead: TLVHead{
TLVType: TLVManagement,
LengthField: 2,
},
ManagementID: IDPortServiceStatsNP,
},
}
}
// PortServiceStatsNP sends PORT_SERVICE_STATS_NP request and returns response
func (c *MgmtClient) PortServiceStatsNP() (*PortServiceStatsNPTLV, error) {
req := PortServiceStatsNPRequest()
p, err := c.Communicate(req)
if err != nil {
return nil, err
}
tlv, ok := p.TLV.(*PortServiceStatsNPTLV)
if !ok {
return nil, fmt.Errorf("got unexpected management TLV %T, wanted %T", p.TLV, tlv)
}
return tlv, nil
}
// PortPropertiesNPRequest prepares request packet for PORT_STATS_NP request
func PortPropertiesNPRequest() *Management {
headerSize := uint16(binary.Size(ManagementMsgHead{}))
tlvHeadSize := uint16(binary.Size(TLVHead{}))
// we send request with no portStats data just like pmc does
return &Management{
ManagementMsgHead: ManagementMsgHead{
Header: Header{
SdoIDAndMsgType: NewSdoIDAndMsgType(MessageManagement, 0),
Version: Version,
MessageLength: headerSize + tlvHeadSize + 2,
SourcePortIdentity: identity,
LogMessageInterval: MgmtLogMessageInterval,
},
TargetPortIdentity: DefaultTargetPortIdentity,
StartingBoundaryHops: 0,
BoundaryHops: 0,
ActionField: GET,
},
TLV: &ManagementTLVHead{
TLVHead: TLVHead{
TLVType: TLVManagement,
LengthField: 2,
},
ManagementID: IDPortPropertiesNP,
},
}
}
// PortPropertiesNP sends PORT_PROPERTIES_NP request and returns response
func (c *MgmtClient) PortPropertiesNP() (*PortPropertiesNPTLV, error) {
req := PortPropertiesNPRequest()
p, err := c.Communicate(req)
if err != nil {
return nil, err
}
tlv, ok := p.TLV.(*PortPropertiesNPTLV)
if !ok {
return nil, fmt.Errorf("got unexpected management TLV %T, wanted %T", p.TLV, tlv)
}
return tlv, nil
}
// UnicastMasterTableNPRequest creates new packet with UNICAST_MASTER_TABLE_NP request
func UnicastMasterTableNPRequest() *Management {
headerSize := uint16(binary.Size(ManagementMsgHead{}))
tlvHeadSize := uint16(binary.Size(TLVHead{}))
// we send request with no data just like pmc does
return &Management{
ManagementMsgHead: ManagementMsgHead{
Header: Header{
SdoIDAndMsgType: NewSdoIDAndMsgType(MessageManagement, 0),
Version: Version,
MessageLength: headerSize + tlvHeadSize + 2,
SourcePortIdentity: identity,
LogMessageInterval: MgmtLogMessageInterval,
},
TargetPortIdentity: DefaultTargetPortIdentity,
StartingBoundaryHops: 0,
BoundaryHops: 0,
ActionField: GET,
},
TLV: &ManagementTLVHead{
TLVHead: TLVHead{
TLVType: TLVManagement,
LengthField: 2,
},
ManagementID: IDUnicastMasterTableNP,
},
}
}
// UnicastMasterTableNP request UNICAST_MASTER_TABLE_NP from ptp4l, and returns the result
func (c *MgmtClient) UnicastMasterTableNP() (*UnicastMasterTableNPTLV, error) {
req := UnicastMasterTableNPRequest()
p, err := c.Communicate(req)
if err != nil {
return nil, err
}
tlv, ok := p.TLV.(*UnicastMasterTableNPTLV)
if !ok {
return nil, fmt.Errorf("got unexpected management TLV %T, wanted %T", p.TLV, tlv)
}
return tlv, nil
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package protocol
import (
"bytes"
"encoding/binary"
"fmt"
)
// TLV abstracts away any TLV
type TLV interface {
Type() TLVType
}
const tlvHeadSize = 4
// TLVHead is a common part of all TLVs
type TLVHead struct {
TLVType TLVType
LengthField uint16 // The length of all TLVs shall be an even number of octets
}
// Type implements TLV interface
func (t TLVHead) Type() TLVType {
return t.TLVType
}
func tlvHeadMarshalBinaryTo(t *TLVHead, b []byte) {
binary.BigEndian.PutUint16(b, uint16(t.TLVType))
binary.BigEndian.PutUint16(b[2:], t.LengthField)
}
func unmarshalTLVHeader(p *TLVHead, b []byte) error {
if len(b) < tlvHeadSize {
return fmt.Errorf("not enough data to decode PTP header")
}
p.TLVType = TLVType(binary.BigEndian.Uint16(b[0:]))
p.LengthField = binary.BigEndian.Uint16(b[2:])
return nil
}
func checkTLVLength(p *TLVHead, l, want int, strict bool) error {
if strict && int(p.LengthField) != want {
return fmt.Errorf("expected TLV of type %s (%d) to have length of %d, got %d in the header", p.TLVType, p.TLVType, want, p.LengthField)
}
if int(p.LengthField) < want {
return fmt.Errorf("expected TLV of type %s (%d) to have length of at least %d, got %d in the header", p.TLVType, p.TLVType, want, p.LengthField)
}
if tlvHeadSize+int(p.LengthField) > l {
return fmt.Errorf("cannot decode TLV of length %d from %d bytes", tlvHeadSize+int(p.LengthField), l)
}
return nil
}
func writeTLVs(tlvs []TLV, b []byte) (int, error) {
pos := 0
for _, tlv := range tlvs {
if ttlv, ok := tlv.(BinaryMarshalerTo); ok {
nn, err := ttlv.MarshalBinaryTo(b[pos:])
if err != nil {
return 0, err
}
pos += nn
continue
}
// very inefficient path for TLVs that don't support MarshalBinaryTo
buf := new(bytes.Buffer)
if err := binary.Write(buf, binary.BigEndian, tlv); err != nil {
return 0, err
}
bbytes := buf.Bytes()
copy(b[pos:], bbytes)
pos += len(bbytes)
}
return pos, nil
}
// readTLVs reads TLVs from the bytes.
// tlvs is passed to save on allocations and it's user's task to ensure it's empty
func readTLVs(tlvs []TLV, maxLength int, b []byte) ([]TLV, error) {
pos := 0
var tlvType TLVType
for {
// packet can have trailing bytes, let's make sure we don't try to read past given length
if pos+tlvHeadSize > maxLength {
break
}
tlvType = TLVType(binary.BigEndian.Uint16(b[pos:]))
switch tlvType {
case TLVAcknowledgeCancelUnicastTransmission:
tlv := &AcknowledgeCancelUnicastTransmissionTLV{}
if err := tlv.UnmarshalBinary(b[pos:]); err != nil {
return tlvs, err
}
tlvs = append(tlvs, tlv)
pos += tlvHeadSize + int(tlv.LengthField)
case TLVGrantUnicastTransmission:
tlv := &GrantUnicastTransmissionTLV{}
if err := tlv.UnmarshalBinary(b[pos:]); err != nil {
return tlvs, err
}
tlvs = append(tlvs, tlv)
pos += tlvHeadSize + int(tlv.LengthField)
case TLVRequestUnicastTransmission:
tlv := &RequestUnicastTransmissionTLV{}
if err := tlv.UnmarshalBinary(b[pos:]); err != nil {
return tlvs, err
}
tlvs = append(tlvs, tlv)
pos += tlvHeadSize + int(tlv.LengthField)
case TLVCancelUnicastTransmission:
tlv := &CancelUnicastTransmissionTLV{}
if err := tlv.UnmarshalBinary(b[pos:]); err != nil {
return tlvs, err
}
tlvs = append(tlvs, tlv)
pos += tlvHeadSize + int(tlv.LengthField)
case TLVPathTrace:
tlv := &PathTraceTLV{}
if err := tlv.UnmarshalBinary(b[pos:]); err != nil {
return tlvs, err
}
tlvs = append(tlvs, tlv)
pos += tlvHeadSize + int(tlv.LengthField)
case TLVAlternateTimeOffsetIndicator:
tlv := &AlternateTimeOffsetIndicatorTLV{}
if err := tlv.UnmarshalBinary(b[pos:]); err != nil {
return tlvs, err
}
tlvs = append(tlvs, tlv)
pos += tlvHeadSize + int(tlv.LengthField)
case TLVAlternateResponsePort:
tlv := &AlternateResponsePortTLV{}
if err := tlv.UnmarshalBinary(b[pos:]); err != nil {
return tlvs, err
}
tlvs = append(tlvs, tlv)
pos += tlvHeadSize + int(tlv.LengthField)
default:
return tlvs, fmt.Errorf("reading TLV %s (%d) is not yet implemented", tlvType, tlvType)
}
}
return tlvs, nil
}
// Unicast TLVs
// RequestUnicastTransmissionTLV Table 110 REQUEST_UNICAST_TRANSMISSION TLV format
type RequestUnicastTransmissionTLV struct {
TLVHead
MsgTypeAndReserved UnicastMsgTypeAndFlags // first 4 bits only, same enums as with normal message type
LogInterMessagePeriod LogInterval
DurationField uint32
}
// MarshalBinaryTo marshals bytes to RequestUnicastTransmissionTLV
func (t *RequestUnicastTransmissionTLV) MarshalBinaryTo(b []byte) (int, error) {
tlvHeadMarshalBinaryTo(&t.TLVHead, b)
b[tlvHeadSize] = byte(t.MsgTypeAndReserved)
b[tlvHeadSize+1] = byte(t.LogInterMessagePeriod)
binary.BigEndian.PutUint32(b[tlvHeadSize+2:], t.DurationField)
return tlvHeadSize + 6, nil
}
// UnmarshalBinary parses []byte and populates struct fields
func (t *RequestUnicastTransmissionTLV) UnmarshalBinary(b []byte) error {
if err := unmarshalTLVHeader(&t.TLVHead, b); err != nil {
return err
}
if err := checkTLVLength(&t.TLVHead, len(b), 6, true); err != nil {
return err
}
t.MsgTypeAndReserved = UnicastMsgTypeAndFlags(b[4])
t.LogInterMessagePeriod = LogInterval(b[5])
t.DurationField = binary.BigEndian.Uint32(b[6:])
return nil
}
// GrantUnicastTransmissionTLV Table 111 GRANT_UNICAST_TRANSMISSION TLV format
type GrantUnicastTransmissionTLV struct {
TLVHead
MsgTypeAndReserved UnicastMsgTypeAndFlags // first 4 bits only, same enums as with normal message type
LogInterMessagePeriod LogInterval
DurationField uint32
Reserved uint8
Renewal uint8
}
// MarshalBinaryTo marshals bytes to GrantUnicastTransmissionTLV
func (t *GrantUnicastTransmissionTLV) MarshalBinaryTo(b []byte) (int, error) {
tlvHeadMarshalBinaryTo(&t.TLVHead, b)
b[tlvHeadSize] = byte(t.MsgTypeAndReserved)
b[tlvHeadSize+1] = byte(t.LogInterMessagePeriod)
binary.BigEndian.PutUint32(b[tlvHeadSize+2:], t.DurationField)
b[tlvHeadSize+6] = t.Reserved
b[tlvHeadSize+7] = t.Renewal
return tlvHeadSize + 8, nil
}
// UnmarshalBinary parses []byte and populates struct fields
func (t *GrantUnicastTransmissionTLV) UnmarshalBinary(b []byte) error {
if err := unmarshalTLVHeader(&t.TLVHead, b); err != nil {
return err
}
if err := checkTLVLength(&t.TLVHead, len(b), 8, true); err != nil {
return err
}
t.MsgTypeAndReserved = UnicastMsgTypeAndFlags(b[4])
t.LogInterMessagePeriod = LogInterval(b[5])
t.DurationField = binary.BigEndian.Uint32(b[6:])
t.Reserved = b[10]
t.Renewal = b[11]
return nil
}
// CancelUnicastTransmissionTLV Table 112 CANCEL_UNICAST_TRANSMISSION TLV format
type CancelUnicastTransmissionTLV struct {
TLVHead
MsgTypeAndFlags UnicastMsgTypeAndFlags // first 4 bits is msg type, then flags R and/or G
Reserved uint8
}
// MarshalBinaryTo marshals bytes to CancelUnicastTransmissionTLV
func (t *CancelUnicastTransmissionTLV) MarshalBinaryTo(b []byte) (int, error) {
tlvHeadMarshalBinaryTo(&t.TLVHead, b)
b[tlvHeadSize] = byte(t.MsgTypeAndFlags)
b[tlvHeadSize+1] = t.Reserved
return tlvHeadSize + 2, nil
}
// UnmarshalBinary parses []byte and populates struct fields
func (t *CancelUnicastTransmissionTLV) UnmarshalBinary(b []byte) error {
if err := unmarshalTLVHeader(&t.TLVHead, b); err != nil {
return err
}
if err := checkTLVLength(&t.TLVHead, len(b), 2, true); err != nil {
return err
}
t.MsgTypeAndFlags = UnicastMsgTypeAndFlags(b[4])
t.Reserved = b[5]
return nil
}
// AcknowledgeCancelUnicastTransmissionTLV Table 113 ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION TLV format
type AcknowledgeCancelUnicastTransmissionTLV struct {
TLVHead
MsgTypeAndFlags UnicastMsgTypeAndFlags // first 4 bits is msg type, then flags R and/or G
Reserved uint8
}
// MarshalBinaryTo marshals bytes to AcknowledgeCancelUnicastTransmissionTLV
func (t *AcknowledgeCancelUnicastTransmissionTLV) MarshalBinaryTo(b []byte) (int, error) {
tlvHeadMarshalBinaryTo(&t.TLVHead, b)
b[tlvHeadSize] = byte(t.MsgTypeAndFlags)
b[tlvHeadSize+1] = t.Reserved
return tlvHeadSize + 2, nil
}
// UnmarshalBinary parses []byte and populates struct fields
func (t *AcknowledgeCancelUnicastTransmissionTLV) UnmarshalBinary(b []byte) error {
if err := unmarshalTLVHeader(&t.TLVHead, b); err != nil {
return err
}
if err := checkTLVLength(&t.TLVHead, len(b), 2, true); err != nil {
return err
}
t.MsgTypeAndFlags = UnicastMsgTypeAndFlags(b[4])
t.Reserved = b[5]
return nil
}
// other TLVs
// PathTraceTLV Table 115 PATH_TRACE TLV format
type PathTraceTLV struct {
TLVHead
// The value of the lengthField is 8N.
PathSequence []ClockIdentity // N
}
// MarshalBinaryTo marshals bytes to PathTraceTLV
func (t *PathTraceTLV) MarshalBinaryTo(b []byte) (int, error) {
tlvHeadMarshalBinaryTo(&t.TLVHead, b)
pos := tlvHeadSize
for _, ps := range t.PathSequence {
binary.BigEndian.PutUint64(b[pos:pos+8], uint64(ps))
pos += 8
}
return pos, nil
}
// UnmarshalBinary parses []byte and populates struct fields
func (t *PathTraceTLV) UnmarshalBinary(b []byte) error {
if err := unmarshalTLVHeader(&t.TLVHead, b); err != nil {
return err
}
if err := checkTLVLength(&t.TLVHead, len(b), 8, false); err != nil {
return err
}
t.PathSequence = []ClockIdentity{}
for i := 0; i*8 <= int(t.TLVHead.LengthField); i++ {
pos := tlvHeadSize + i*8
if pos+8 >= len(b) {
break
}
identity := ClockIdentity(binary.BigEndian.Uint64(b[pos:]))
t.PathSequence = append(t.PathSequence, identity)
}
return nil
}
// AlternateTimeOffsetIndicatorTLV is a Table 116 ALTERNATE_TIME_OFFSET_INDICATOR TLV format
type AlternateTimeOffsetIndicatorTLV struct {
TLVHead
KeyField uint8
CurrentOffset int32
JumpSeconds int32
TimeOfNextJump PTPSeconds // uint48
DisplayName PTPText
}
// MarshalBinaryTo marshals bytes to AlternateTimeOffsetIndicatorTLV
func (t *AlternateTimeOffsetIndicatorTLV) MarshalBinaryTo(b []byte) (int, error) {
tlvHeadMarshalBinaryTo(&t.TLVHead, b)
b[tlvHeadSize] = t.KeyField
binary.BigEndian.PutUint32(b[tlvHeadSize+1:], uint32(t.CurrentOffset))
binary.BigEndian.PutUint32(b[tlvHeadSize+5:], uint32(t.JumpSeconds))
copy(b[tlvHeadSize+9:], t.TimeOfNextJump[:]) //uint48
size := tlvHeadSize + 15
if t.DisplayName != "" {
dd, err := t.DisplayName.MarshalBinary()
if err != nil {
return 0, fmt.Errorf("writing AlternateTimeOffsetIndicatorTLV DisplayName: %w", err)
}
copy(b[tlvHeadSize+15:], dd)
size += len(dd)
}
return size, nil
}
// UnmarshalBinary parses []byte and populates struct fields
func (t *AlternateTimeOffsetIndicatorTLV) UnmarshalBinary(b []byte) error {
if err := unmarshalTLVHeader(&t.TLVHead, b); err != nil {
return err
}
if err := checkTLVLength(&t.TLVHead, len(b), 20, false); err != nil {
return err
}
t.KeyField = b[tlvHeadSize]
t.CurrentOffset = int32(binary.BigEndian.Uint32(b[tlvHeadSize+1:]))
t.JumpSeconds = int32(binary.BigEndian.Uint32(b[tlvHeadSize+5:]))
copy(t.TimeOfNextJump[:], b[tlvHeadSize+9:]) // uint48
if err := t.DisplayName.UnmarshalBinary(b[tlvHeadSize+15:]); err != nil {
return fmt.Errorf("reading AlternateTimeOffsetIndicatorTLV DisplayName: %w", err)
}
return nil
}
// AlternateResponsePortTLV is a CSPTP optional TLV to switch response source port of the server
// Offset flag indicates the number of the port steps, not the port number itself.
// Ex:
// 0 means no switch (use default port). For example 1234
// 1 means next port. For example 4567
// 2 means next next port. For example 6789
// etc
type AlternateResponsePortTLV struct {
TLVHead
Offset uint16
}
// MarshalBinaryTo marshals bytes to AlternateResponsePortTLV
func (a *AlternateResponsePortTLV) MarshalBinaryTo(b []byte) (int, error) {
tlvHeadMarshalBinaryTo(&a.TLVHead, b)
binary.BigEndian.PutUint16(b[tlvHeadSize:], a.Offset)
return tlvHeadSize + 2, nil
}
// UnmarshalBinary parses []byte and populates struct fields
func (a *AlternateResponsePortTLV) UnmarshalBinary(b []byte) error {
if err := unmarshalTLVHeader(&a.TLVHead, b); err != nil {
return err
}
if err := checkTLVLength(&a.TLVHead, len(b), 2, true); err != nil {
return err
}
a.Offset = binary.BigEndian.Uint16(b[tlvHeadSize:])
return nil
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package protocol
import (
"bytes"
"encoding/binary"
"fmt"
"math"
"net"
"time"
)
// 2 ** 16
const twoPow16 = 65536
// MessageType is type for Message Types
type MessageType uint8
// As per Table 36 Values of messageType field
const (
MessageSync MessageType = 0x0
MessageDelayReq MessageType = 0x1
MessagePDelayReq MessageType = 0x2
MessagePDelayResp MessageType = 0x3
MessageFollowUp MessageType = 0x8
MessageDelayResp MessageType = 0x9
MessagePDelayRespFollowUp MessageType = 0xA
MessageAnnounce MessageType = 0xB
MessageSignaling MessageType = 0xC
MessageManagement MessageType = 0xD
)
// MessageTypeToString is a map from MessageType to string
var MessageTypeToString = map[MessageType]string{
MessageSync: "SYNC",
MessageDelayReq: "DELAY_REQ",
MessagePDelayReq: "PDELAY_REQ",
MessagePDelayResp: "PDELAY_RES",
MessageFollowUp: "FOLLOW_UP",
MessageDelayResp: "DELAY_RESP",
MessagePDelayRespFollowUp: "PDELAY_RESP_FOLLOW_UP",
MessageAnnounce: "ANNOUNCE",
MessageSignaling: "SIGNALING",
MessageManagement: "MANAGEMENT",
}
func (m MessageType) String() string {
return MessageTypeToString[m]
}
// SdoIDAndMsgType is a uint8 where first 4 bites contain SdoID and last 4 bits MessageType
type SdoIDAndMsgType uint8
// MsgType extracts MessageType from SdoIDAndMsgType
func (m SdoIDAndMsgType) MsgType() MessageType {
return MessageType(m & 0xf) // last 4 bits
}
// NewSdoIDAndMsgType builds new SdoIDAndMsgType from MessageType and flags
func NewSdoIDAndMsgType(msgType MessageType, sdoID uint8) SdoIDAndMsgType {
return SdoIDAndMsgType(sdoID<<4 | uint8(msgType))
}
// ProbeMsgType reads first 8 bits of data and tries to decode it to SdoIDAndMsgType, then return MessageType
func ProbeMsgType(data []byte) (msg MessageType, err error) {
if len(data) < 1 {
return 0, fmt.Errorf("not enough data to probe MsgType")
}
return SdoIDAndMsgType(data[0]).MsgType(), nil
}
// TLVType is type for TLV types
type TLVType uint16
// As per Table 52 tlvType values
const (
TLVManagement TLVType = 0x0001
TLVManagementErrorStatus TLVType = 0x0002
TLVOrganizationExtension TLVType = 0x0003
TLVRequestUnicastTransmission TLVType = 0x0004
TLVGrantUnicastTransmission TLVType = 0x0005
TLVCancelUnicastTransmission TLVType = 0x0006
TLVAcknowledgeCancelUnicastTransmission TLVType = 0x0007
TLVPathTrace TLVType = 0x0008
TLVAlternateTimeOffsetIndicator TLVType = 0x0009
TLVAlternateResponsePort TLVType = 0x2007
// Remaining 52 tlvType TLVs not implemented
)
// TLVTypeToString is a map from TLVType to string
var TLVTypeToString = map[TLVType]string{
TLVManagement: "MANAGEMENT",
TLVManagementErrorStatus: "MANAGEMENT_ERROR_STATUS",
TLVOrganizationExtension: "ORGANIZATION_EXTENSION",
TLVRequestUnicastTransmission: "REQUEST_UNICAST_TRANSMISSION",
TLVGrantUnicastTransmission: "GRANT_UNICAST_TRANSMISSION",
TLVCancelUnicastTransmission: "CANCEL_UNICAST_TRANSMISSION",
TLVAcknowledgeCancelUnicastTransmission: "ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION",
TLVPathTrace: "PATH_TRACE",
TLVAlternateTimeOffsetIndicator: "ALTERNATE_TIME_OFFSET_INDICATOR",
TLVAlternateResponsePort: "ALTERNATE_RESPONSE_PORT",
}
func (t TLVType) String() string {
return TLVTypeToString[t]
}
// IntFloat is a float64 stored in int64
type IntFloat int64
// Value decodes IntFloat to float64
func (t IntFloat) Value() float64 {
return float64(t) / twoPow16
}
/*
TimeInterval is the time interval expressed in nanoseconds, multiplied by 2**16.
Positive or negative time intervals outside the maximum range of this data type shall be encoded as the largest
positive and negative values of the data type, respectively.
For example, 2.5 ns is expressed as 0000 0000 0002 8000 base 16
*/
type TimeInterval IntFloat
// Nanoseconds decodes TimeInterval to human-understandable nanoseconds
func (t TimeInterval) Nanoseconds() float64 {
return IntFloat(t).Value()
}
func (t TimeInterval) String() string {
return fmt.Sprintf("TimeInterval(%.3fns)", t.Nanoseconds())
}
// NewTimeInterval returns TimeInterval built from Nanoseconds
func NewTimeInterval(ns float64) TimeInterval {
return TimeInterval(ns * twoPow16)
}
/*
Correction is the value of the correction measured in nanoseconds and multiplied by 2**16.
For example, 2.5 ns is represented as 0000 0000 0002 8000 base 16
A value of one in all bits, except the most significant, of the field shall indicate that the correction is too big to be represented.
*/
type Correction IntFloat
// Nanoseconds decodes Correction to human-understandable nanoseconds
func (t Correction) Nanoseconds() float64 {
if t.TooBig() {
return math.Inf(1)
}
return IntFloat(t).Value()
}
// Duration converts PTP CorrectionField to time.Duration, ignoring
// case where correction is too big, and dropping fractions of nanoseconds
func (t Correction) Duration() time.Duration {
if !t.TooBig() {
return time.Duration(t.Nanoseconds())
}
return 0
}
func (t Correction) String() string {
if t.TooBig() {
return "Correction(Too big)"
}
return fmt.Sprintf("Correction(%.3fns)", t.Nanoseconds())
}
// TooBig means correction is too big to be represented.
func (t Correction) TooBig() bool {
return t == 0x7fffffffffffffff // one in all bits, except the most significant
}
// NewCorrection returns Correction built from Nanoseconds
func NewCorrection(ns float64) Correction {
t := ns * twoPow16
if t > 0x7fffffffffffffff {
return Correction(0x7fffffffffffffff)
}
return Correction(ns * twoPow16)
}
// The ClockIdentity type identifies unique entities within a PTP Network, e.g. a PTP Instance or an entity of a common service.
type ClockIdentity uint64
// String formats ClockIdentity same way ptp4l pmc client does
func (c ClockIdentity) String() string {
ptr := make([]byte, 8)
binary.BigEndian.PutUint64(ptr, uint64(c))
return fmt.Sprintf("%02x%02x%02x.%02x%02x.%02x%02x%02x",
ptr[0], ptr[1], ptr[2], ptr[3],
ptr[4], ptr[5], ptr[6], ptr[7],
)
}
// MAC turns ClockIdentity into the MAC address it was based upon. EUI-48 is assumed.
func (c ClockIdentity) MAC() net.HardwareAddr {
mac := make(net.HardwareAddr, 6)
mac[0] = byte(c >> 56)
mac[1] = byte(c >> 48)
mac[2] = byte(c >> 40)
mac[3] = byte(c >> 16)
mac[4] = byte(c >> 8)
mac[5] = byte(c)
return mac
}
// NewClockIdentity creates new ClockIdentity from MAC address
func NewClockIdentity(mac net.HardwareAddr) (ClockIdentity, error) {
b := [8]byte{}
macLen := len(mac)
switch macLen {
case 6: // EUI-48
b[0] = mac[0]
b[1] = mac[1]
b[2] = mac[2]
b[3] = 0xFF
b[4] = 0xFE
b[5] = mac[3]
b[6] = mac[4]
b[7] = mac[5]
case 8: // EUI-64
copy(b[:], mac)
default:
return 0, fmt.Errorf("unsupported MAC %v, must be either EUI48 or EUI64", mac)
}
return ClockIdentity(binary.BigEndian.Uint64(b[:])), nil
}
// The PortIdentity type identifies a PTP Port or a Link Port
type PortIdentity struct {
ClockIdentity ClockIdentity
PortNumber uint16
}
// String formats PortIdentity same way ptp4l pmc client does
func (p PortIdentity) String() string {
return fmt.Sprintf("%s-%d", p.ClockIdentity, p.PortNumber)
}
// Compare returns an integer comparing two port identities. The result will be 0 if p == q, -1 if p < q, and +1 if p > q.
// The definition of "less than" is the same as the Less method.
func (p PortIdentity) Compare(q PortIdentity) int {
cl1, cl2 := p.ClockIdentity, q.ClockIdentity
switch {
case cl1 < cl2:
return -1
case cl1 > cl2:
return 1
}
// cl1 == cl2
pn1, pn2 := p.PortNumber, q.PortNumber
switch {
case pn1 < pn2:
return -1
case pn1 > pn2:
return 1
}
// pn1 == pn2
return 0
}
// Less reports whether p sorts before q. Port identities sort first by clock identity, then their port numbers.
func (p PortIdentity) Less(q PortIdentity) bool { return p.Compare(q) == -1 }
// PTPSeconds type representing seconds
type PTPSeconds [6]uint8 // uint48
// Empty returns 0 seconds
func (s PTPSeconds) Empty() bool {
return s == [6]uint8{0, 0, 0, 0, 0, 0}
}
// Seconds returns number of seconds as uint64
func (s PTPSeconds) Seconds() uint64 {
return uint64(s[5]) | uint64(s[4])<<8 | uint64(s[3])<<16 | uint64(s[2])<<24 |
uint64(s[1])<<32 | uint64(s[0])<<40
}
// Time returns number of seconds in as Time
func (s PTPSeconds) Time() time.Time {
if s.Empty() {
return time.Time{}
}
return time.Unix(int64(s.Seconds()), 0)
}
// String returns number of seconds in as String
func (s PTPSeconds) String() string {
if s.Empty() {
return "PTPSeconds(empty)"
}
return fmt.Sprintf("PTPSeconds(%s)", s.Time())
}
// NewPTPSeconds creates a new instance of PTPSeconds
func NewPTPSeconds(t time.Time) PTPSeconds {
if t.IsZero() {
return PTPSeconds{}
}
v := uint64(t.Unix())
s := PTPSeconds{}
s[0] = byte(v >> 40)
s[1] = byte(v >> 32)
s[2] = byte(v >> 24)
s[3] = byte(v >> 16)
s[4] = byte(v >> 8)
s[5] = byte(v)
return s
}
/*
Timestamp type represents a positive time with respect to the epoch.
The secondsField member is the integer portion of the timestamp in units of seconds.
The nanosecondsField member is the fractional portion of the timestamp in units of nanoseconds.
The nanosecondsField member is always less than 10**9 .
For example:
+2.000000001 seconds is represented by secondsField = 0000 0000 0002 base 16 and nanosecondsField= 0000 0001 base 16.
*/
type Timestamp struct {
Seconds PTPSeconds
Nanoseconds uint32
}
// Time turns Timestamp into normal Go time.Time
func (t Timestamp) Time() time.Time {
if t.Empty() {
return time.Time{}
}
return time.Unix(int64(t.Seconds.Seconds()), int64(t.Nanoseconds))
}
// Empty timestamp
func (t Timestamp) Empty() bool {
return t.Nanoseconds == 0 && t.Seconds.Empty()
}
// String representation of the timestamp
func (t Timestamp) String() string {
if t.Empty() {
return "Timestamp(empty)"
}
return fmt.Sprintf("Timestamp(%s)", t.Time())
}
// NewTimestamp allows to create Timestamp from time.Time
func NewTimestamp(t time.Time) Timestamp {
if t.IsZero() {
return Timestamp{}
}
ts := Timestamp{
Nanoseconds: uint32(t.Nanosecond()),
}
v := uint64(t.Unix())
ts.Seconds[0] = byte(v >> 40)
ts.Seconds[1] = byte(v >> 32)
ts.Seconds[2] = byte(v >> 24)
ts.Seconds[3] = byte(v >> 16)
ts.Seconds[4] = byte(v >> 8)
ts.Seconds[5] = byte(v)
return ts
}
// ClockClass represents a PTP clock class
type ClockClass uint8
// Available Clock Classes
// https://datatracker.ietf.org/doc/html/rfc8173#section-7.6.2.4
const (
ClockClass6 ClockClass = 6
ClockClass7 ClockClass = 7
ClockClass13 ClockClass = 13
ClockClass14 ClockClass = 14
ClockClass52 ClockClass = 52
ClockClass58 ClockClass = 58
ClockClassSlaveOnly ClockClass = 255
)
// ClockAccuracy represents a PTP clock accuracy
type ClockAccuracy uint8
// Available Clock Accuracy
// https://datatracker.ietf.org/doc/html/rfc8173#section-7.6.2.5
const (
ClockAccuracyNanosecond25 ClockAccuracy = 0x20
ClockAccuracyNanosecond100 ClockAccuracy = 0x21
ClockAccuracyNanosecond250 ClockAccuracy = 0x22
ClockAccuracyMicrosecond1 ClockAccuracy = 0x23
ClockAccuracyMicrosecond2point5 ClockAccuracy = 0x24
ClockAccuracyMicrosecond10 ClockAccuracy = 0x25
ClockAccuracyMicrosecond25 ClockAccuracy = 0x26
ClockAccuracyMicrosecond100 ClockAccuracy = 0x27
ClockAccuracyMicrosecond250 ClockAccuracy = 0x28
ClockAccuracyMillisecond1 ClockAccuracy = 0x29
ClockAccuracyMillisecond2point5 ClockAccuracy = 0x2A
ClockAccuracyMillisecond10 ClockAccuracy = 0x2B
ClockAccuracyMillisecond25 ClockAccuracy = 0x2C
ClockAccuracyMillisecond100 ClockAccuracy = 0x2D
ClockAccuracyMillisecond250 ClockAccuracy = 0x2E
ClockAccuracySecond1 ClockAccuracy = 0x2F
ClockAccuracySecond10 ClockAccuracy = 0x30
ClockAccuracySecondGreater10 ClockAccuracy = 0x31
ClockAccuracyUnknown ClockAccuracy = 0xFE
)
// ClockAccuracyFromOffset returns PTP Clock Accuracy covering the time.Duration
func ClockAccuracyFromOffset(offset time.Duration) ClockAccuracy {
if offset < 0 {
offset *= -1
}
// https://datatracker.ietf.org/doc/html/rfc8173#section-7.6.2.4
if offset <= 25*time.Nanosecond {
return ClockAccuracyNanosecond25
} else if offset <= 100*time.Nanosecond {
return ClockAccuracyNanosecond100
} else if offset <= 250*time.Nanosecond {
return ClockAccuracyNanosecond250
} else if offset <= time.Microsecond {
return ClockAccuracyMicrosecond1
} else if offset <= 2500*time.Nanosecond {
return ClockAccuracyMicrosecond2point5
} else if offset <= 10*time.Microsecond {
return ClockAccuracyMicrosecond10
} else if offset <= 25*time.Microsecond {
return ClockAccuracyMicrosecond25
} else if offset <= 100*time.Microsecond {
return ClockAccuracyMicrosecond100
} else if offset <= 250*time.Microsecond {
return ClockAccuracyMicrosecond250
} else if offset <= time.Millisecond {
return ClockAccuracyMillisecond1
} else if offset <= 2500*time.Microsecond {
return ClockAccuracyMillisecond2point5
} else if offset <= 10*time.Millisecond {
return ClockAccuracyMillisecond10
} else if offset <= 25*time.Millisecond {
return ClockAccuracyMillisecond25
} else if offset <= 100*time.Millisecond {
return ClockAccuracyMillisecond100
} else if offset <= 250*time.Millisecond {
return ClockAccuracyMillisecond250
} else if offset <= time.Second {
return ClockAccuracySecond1
} else if offset <= 10*time.Second {
return ClockAccuracySecond10
}
return ClockAccuracySecondGreater10
}
// Duration returns matching time.Duration of PTP Clock Accuracy
func (c ClockAccuracy) Duration() time.Duration {
switch c {
case ClockAccuracyNanosecond25:
return 25 * time.Nanosecond
case ClockAccuracyNanosecond100:
return 100 * time.Nanosecond
case ClockAccuracyNanosecond250:
return 250 * time.Nanosecond
case ClockAccuracyMicrosecond1:
return 1000 * time.Nanosecond
case ClockAccuracyMicrosecond2point5:
return 2500 * time.Nanosecond
case ClockAccuracyMicrosecond10:
return 10 * time.Microsecond
case ClockAccuracyMicrosecond25:
return 25 * time.Microsecond
case ClockAccuracyMicrosecond100:
return 100 * time.Microsecond
case ClockAccuracyMicrosecond250:
return 250 * time.Microsecond
case ClockAccuracyMillisecond1:
return 1 * time.Millisecond
case ClockAccuracyMillisecond2point5:
return 2500 * time.Microsecond
case ClockAccuracyMillisecond10:
return 10 * time.Millisecond
case ClockAccuracyMillisecond25:
return 25 * time.Millisecond
case ClockAccuracyMillisecond100:
return 100 * time.Millisecond
case ClockAccuracyMillisecond250:
return 250 * time.Millisecond
case ClockAccuracySecond1:
return 1 * time.Second
case ClockAccuracySecond10:
return 10 * time.Second
}
return 25 * time.Second
}
// ClockQuality represents the quality of a clock.
type ClockQuality struct {
ClockClass ClockClass `json:"clock_class"`
ClockAccuracy ClockAccuracy `json:"clock_accuracy"`
OffsetScaledLogVariance uint16 `json:"offset_scaled_log_variance"`
}
// TimeSource indicates the immediate source of time used by the Grandmaster PTP Instance
type TimeSource uint8
// TimeSource values, Table 6 timeSource enumeration
const (
TimeSourceAtomicClock TimeSource = 0x10
TimeSourceGNSS TimeSource = 0x20
TimeSourceTerrestrialRadio TimeSource = 0x30
TimeSourceSerialTimeCode TimeSource = 0x39
TimeSourcePTP TimeSource = 0x40
TimeSourceNTP TimeSource = 0x50
TimeSourceHandSet TimeSource = 0x60
TimeSourceOther TimeSource = 0x90
TimeSourceInternalOscillator TimeSource = 0xa0
)
// TimeSourceToString is a map from TimeSource to string
var TimeSourceToString = map[TimeSource]string{
TimeSourceAtomicClock: "ATOMIC_CLOCK",
TimeSourceGNSS: "GNSS",
TimeSourceTerrestrialRadio: "TERRESTRIAL_RADIO",
TimeSourceSerialTimeCode: "SERIAL_TIME_CODE",
TimeSourcePTP: "PTP",
TimeSourceNTP: "NTP",
TimeSourceHandSet: "HAND_SET",
TimeSourceOther: "OTHER",
TimeSourceInternalOscillator: "INTERNAL_OSCILLATOR",
}
func (t TimeSource) String() string {
return TimeSourceToString[t]
}
// LogInterval shall be the logarithm, to base 2, of the requested period in seconds.
// In layman's terms, it's specified as a power of two in seconds.
type LogInterval int8
// Duration returns LogInterval as time.Duration
func (i LogInterval) Duration() time.Duration {
secs := math.Pow(2, float64(i))
return time.Duration(secs * float64(time.Second))
}
// NewLogInterval returns new LogInterval from time.Duration.
// The values of these logarithmic attributes shall be selected from integers in the range -128 to 127 subject to
// further limits established in the applicable PTP Profile.
func NewLogInterval(d time.Duration) (LogInterval, error) {
li := int(math.Log2(d.Seconds()))
if li > 127 {
return 0, fmt.Errorf("logInterval %d is too big", li)
}
if li < -128 {
return 0, fmt.Errorf("logInterval %d is too small", li)
}
return LogInterval(li), nil
}
/*
PTPText data type is used to represent textual material in PTP messages.
TextField is encoded as UTF-8.
The most significant byte of the leading text symbol shall be the element of the array with index 0.
UTF-8 encoding has variable length, thus LengthField can be larger than number of characters.
type PTPText struct {
LengthField uint8
TextField []byte
}
*/
type PTPText string
// UnmarshalBinary populates ptptext from bytes
func (p *PTPText) UnmarshalBinary(rawBytes []byte) error {
var length uint8
reader := bytes.NewReader(rawBytes)
if err := binary.Read(reader, binary.BigEndian, &length); err != nil {
return fmt.Errorf("reading PTPText LengthField: %w", err)
}
if length == 0 {
// can be zero len, just empty string
return nil
}
if len(rawBytes) < int(length+1) {
return fmt.Errorf("text field is too short, need %d got %d", len(rawBytes), length+1)
}
text := make([]byte, length)
if err := binary.Read(reader, binary.BigEndian, text); err != nil {
return fmt.Errorf("reading PTPText TextField of len=%d: %w", length, err)
}
*p = PTPText(text)
return nil
}
// MarshalBinary converts ptptext to []bytes
func (p *PTPText) MarshalBinary() ([]byte, error) {
rawText := []byte(*p)
if len(rawText) > 255 {
return nil, fmt.Errorf("text is too long")
}
length := uint8(len(rawText))
var bytes bytes.Buffer
if err := binary.Write(&bytes, binary.BigEndian, length); err != nil {
return nil, err
}
if err := binary.Write(&bytes, binary.BigEndian, rawText); err != nil {
return nil, err
}
// padding to make sure packet length is even
if length%2 != 0 {
if err := bytes.WriteByte(0); err != nil {
return nil, err
}
}
return bytes.Bytes(), nil
}
// PortState is a enum describing one of possible states of port state machines
type PortState uint8
// Table 20 PTP state enumeration
const (
PortStateInitializing PortState = iota + 1
PortStateFaulty
PortStateDisabled
PortStateListening
PortStatePreMaster
PortStateMaster
PortStatePassive
PortStateUncalibrated
PortStateSlave
PortStateGrandMaster /*non-standard extension*/
)
// PortStateToString is a map from PortState to string
var PortStateToString = map[PortState]string{
PortStateInitializing: "INITIALIZING",
PortStateFaulty: "FAULTY",
PortStateDisabled: "DISABLED",
PortStateListening: "LISTENING",
PortStatePreMaster: "PRE_MASTER",
PortStateMaster: "MASTER",
PortStatePassive: "PASSIVE",
PortStateUncalibrated: "UNCALIBRATED",
PortStateSlave: "SLAVE",
PortStateGrandMaster: "GRAND_MASTER",
}
func (ps PortState) String() string {
return PortStateToString[ps]
}
// TransportType is a enum describing network transport protocol types
type TransportType uint16
// Table 3 networkProtocol enumeration
const (
/* 0 is Reserved in spec. Use it for UDS */
TransportTypeUDS TransportType = iota
TransportTypeUDPIPV4
TransportTypeUDPIPV6
TransportTypeIEEE8023
TransportTypeDeviceNet
TransportTypeControlNet
TransportTypePROFINET
)
// TransportTypeToString is a map from TransportType to string
var TransportTypeToString = map[TransportType]string{
TransportTypeUDS: "UDS",
TransportTypeUDPIPV4: "UDP_IPV4",
TransportTypeUDPIPV6: "UDP_IPV6",
TransportTypeIEEE8023: "IEEE_802_3",
TransportTypeDeviceNet: "DEVICENET",
TransportTypeControlNet: "CONTROLNET",
TransportTypePROFINET: "PROFINET",
}
func (t TransportType) String() string {
return TransportTypeToString[t]
}
// PortAddress see 5.3.6 PortAddress
type PortAddress struct {
NetworkProtocol TransportType
AddressLength uint16
AddressField []byte
}
// UnmarshalBinary converts bytes to PortAddress
func (p *PortAddress) UnmarshalBinary(b []byte) error {
if len(b) < 8 {
return fmt.Errorf("not enough data to decode PortAddress")
}
p.NetworkProtocol = TransportType(binary.BigEndian.Uint16(b[0:]))
p.AddressLength = binary.BigEndian.Uint16(b[2:])
if len(b) < 4+int(p.AddressLength) {
return fmt.Errorf("not enough data to decode PortAddress address")
}
p.AddressField = make([]byte, p.AddressLength)
copy(p.AddressField, b[4:4+p.AddressLength])
return nil
}
// IP converts PortAddress to IP
func (p *PortAddress) IP() (net.IP, error) {
if p.NetworkProtocol != TransportTypeUDPIPV4 && p.NetworkProtocol != TransportTypeUDPIPV6 {
return nil, fmt.Errorf("unsupported network protocol %s (%d)", p.NetworkProtocol, p.NetworkProtocol)
}
if p.NetworkProtocol == TransportTypeUDPIPV4 && (p.AddressLength != 4 || len(p.AddressField) != 4) {
return nil, fmt.Errorf("unexpected length of IPv4: %d", len(p.AddressField))
}
if p.NetworkProtocol == TransportTypeUDPIPV6 && (p.AddressLength != 16 || len(p.AddressField) != 16) {
return nil, fmt.Errorf("unexpected length of IPv6: %d", len(p.AddressField))
}
return net.IP(p.AddressField), nil
}
// MarshalBinary converts PortAddress to []bytes
func (p *PortAddress) MarshalBinary() ([]byte, error) {
var bytes bytes.Buffer
if err := binary.Write(&bytes, binary.BigEndian, p.NetworkProtocol); err != nil {
return nil, err
}
if err := binary.Write(&bytes, binary.BigEndian, p.AddressLength); err != nil {
return nil, err
}
if err := binary.Write(&bytes, binary.BigEndian, p.AddressField); err != nil {
return nil, err
}
return bytes.Bytes(), nil
}
/*
Copyright (c) Facebook, Inc. and its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package protocol
import (
"encoding/binary"
"fmt"
)
// UnicastMsgTypeAndFlags is a uint8 where first 4 bites contain MessageType and last 4 bits contain some flags
type UnicastMsgTypeAndFlags uint8
// MsgType extracts MessageType from UnicastMsgTypeAndFlags
func (m UnicastMsgTypeAndFlags) MsgType() MessageType {
return MessageType(m >> 4)
}
// NewUnicastMsgTypeAndFlags builds new UnicastMsgTypeAndFlags from MessageType and flags
func NewUnicastMsgTypeAndFlags(msgType MessageType, flags uint8) UnicastMsgTypeAndFlags {
return UnicastMsgTypeAndFlags(uint8(msgType)<<4 | (flags & 0x0f))
}
// Signaling packet. As it's of variable size, we cannot just binary.Read/Write it.
type Signaling struct {
Header
TargetPortIdentity PortIdentity
TLVs []TLV
}
// MarshalBinaryTo marshals bytes to Signaling
func (p *Signaling) MarshalBinaryTo(b []byte) (int, error) {
if len(p.TLVs) == 0 {
return 0, fmt.Errorf("no TLVs in Signaling message, at least one required")
}
n := headerMarshalBinaryTo(&p.Header, b)
binary.BigEndian.PutUint64(b[n:], uint64(p.TargetPortIdentity.ClockIdentity))
binary.BigEndian.PutUint16(b[n+8:], p.TargetPortIdentity.PortNumber)
pos := n + 10
tlvLen, err := writeTLVs(p.TLVs, b[pos:])
return pos + tlvLen, err
}
// MarshalBinary converts packet to []bytes
func (p *Signaling) MarshalBinary() ([]byte, error) {
buf := make([]byte, 508)
n, err := p.MarshalBinaryTo(buf)
return buf[:n], err
}
// UnmarshalBinary parses []byte and populates struct fields
func (p *Signaling) UnmarshalBinary(b []byte) error {
if len(b) < headerSize+10+tlvHeadSize {
return fmt.Errorf("not enough data to decode Signaling")
}
unmarshalHeader(&p.Header, b)
if err := checkPacketLength(&p.Header, len(b)); err != nil {
return err
}
if p.SdoIDAndMsgType.MsgType() != MessageSignaling {
return fmt.Errorf("not a signaling message %v", b)
}
p.TargetPortIdentity.ClockIdentity = ClockIdentity(binary.BigEndian.Uint64(b[headerSize:]))
p.TargetPortIdentity.PortNumber = binary.BigEndian.Uint16(b[headerSize+8:])
pos := headerSize + 10
var err error
p.TLVs, err = readTLVs(p.TLVs, int(p.MessageLength)-pos, b[pos:])
if err != nil {
return err
}
if len(p.TLVs) == 0 {
return fmt.Errorf("no TLVs read for Signaling message, at least one required")
}
return nil
}