ptp/protocol/ptp4l.go (438 lines of code) (raw):
/*
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"
)
// 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 Timestamping = iota
TimestampingHardware
TimestampingLegacyHW
TimestampingOneStep
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, binary.LittleEndian, 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
}
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, binary.LittleEndian, p.PortState); err != nil {
return nil, err
}
if err := binary.Write(&bytes, binary.LittleEndian, p.Timestamping); err != nil {
return nil, err
}
interfaceBytes, err := p.Interface.MarshalBinary()
if err != nil {
return nil, err
}
if err := binary.Write(&bytes, binary.LittleEndian, 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, binary.LittleEndian, 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 = b[10]
e.ClockQuality.ClockAccuracy = b[11]
e.ClockQuality.OffsetScaledLogVariance = binary.BigEndian.Uint16(b[12:])
if b[14] == 0 {
e.Selected = false
} else {
e.Selected = true
}
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 = 0
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
}