Skip to content

Commit

Permalink
introduce ALTERNATE_RESPONSE_PORT TLV (#428)
Browse files Browse the repository at this point in the history
Summary:

Introduce a new TLV as a POC to support switching port on the GM side.
This is an important step torwards the asymmetry compensation.
We use a count flag which is uint8 (256 combinations) which ensures consistency:
```
count 0 = noop
count 1 = switch to the next available port
count 2 = switch to the next next available port
...
count 255 = you get the idea
```

As server doesn't keep the state, TLV with a desired offset count must be passed all the time

Differential Revision: D66656872
  • Loading branch information
leoleovich authored and facebook-github-bot committed Dec 3, 2024
1 parent 4b2e8e7 commit e1745ab
Show file tree
Hide file tree
Showing 12 changed files with 153 additions and 50 deletions.
4 changes: 2 additions & 2 deletions ptp/linearizability/linearizability_ptp4l.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ func reqDelay(clockID ptp.ClockIdentity, port uint16) *ptp.SyncDelayReq {
Header: ptp.Header{
SdoIDAndMsgType: ptp.NewSdoIDAndMsgType(ptp.MessageDelayReq, 0),
Version: ptp.Version,
SequenceID: 0, // will be populated on sending
MessageLength: uint16(binary.Size(ptp.SyncDelayReq{})),
SequenceID: 0, // will be populated on sending
MessageLength: uint16(binary.Size(ptp.Header{}) + binary.Size(ptp.SyncDelayReqBody{})), //#nosec G115
FlagField: ptp.FlagUnicast,
SourcePortIdentity: ptp.PortIdentity{
PortNumber: port,
Expand Down
27 changes: 18 additions & 9 deletions ptp/protocol/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ var (
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

Expand Down Expand Up @@ -242,6 +250,7 @@ type SyncDelayReqBody struct {
type SyncDelayReq struct {
Header
SyncDelayReqBody
TLVs []TLV
}

// MarshalBinaryTo marshals bytes to SyncDelayReq
Expand All @@ -252,12 +261,14 @@ func (p *SyncDelayReq) MarshalBinaryTo(b []byte) (int, error) {
n := headerMarshalBinaryTo(&p.Header, b)
copy(b[n:], p.OriginTimestamp.Seconds[:]) //uint48
binary.BigEndian.PutUint32(b[n+6:], p.OriginTimestamp.Nanoseconds)
return n + 10, nil
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, 44)
buf := make([]byte, 49)
n, err := p.MarshalBinaryTo(buf)
return buf[:n], err
}
Expand All @@ -273,7 +284,11 @@ func (p *SyncDelayReq) UnmarshalBinary(b []byte) error {
}
copy(p.OriginTimestamp.Seconds[:], b[headerSize:]) //uint48
p.OriginTimestamp.Nanoseconds = binary.BigEndian.Uint32(b[headerSize+6:])
return nil

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
Expand Down Expand Up @@ -426,13 +441,7 @@ func BytesTo(p BinaryMarshalerTo, buf []byte) (int, error) {
return n + 2, nil
}

var twoZeros = []byte{0, 0}

// Bytes converts any packet to []bytes
// 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.
func Bytes(p Packet) ([]byte, error) {
// interface smuggling
if pp, ok := p.(encoding.BinaryMarshaler); ok {
Expand Down
44 changes: 42 additions & 2 deletions ptp/protocol/tlvs.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ func writeTLVs(tlvs []TLV, b []byte) (int, error) {
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
Expand All @@ -102,22 +104,22 @@ func readTLVs(tlvs []TLV, maxLength int, b []byte) ([]TLV, error) {
tlvType = TLVType(binary.BigEndian.Uint16(b[pos:]))

switch tlvType {
case TLVNone:
continue
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 {
Expand Down Expand Up @@ -146,6 +148,13 @@ func readTLVs(tlvs []TLV, maxLength int, b []byte) ([]TLV, error) {
}
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)
}
Expand Down Expand Up @@ -365,3 +374,34 @@ func (t *AlternateTimeOffsetIndicatorTLV) UnmarshalBinary(b []byte) error {
}
return nil
}

// AlternateResponsePortTLV is a CSPTP optional TLV to switch response source port of the server
// Count 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
Count uint8
}

// MarshalBinaryTo marshals bytes to AlternateResponsePortTLV
func (a *AlternateResponsePortTLV) MarshalBinaryTo(b []byte) (int, error) {
tlvHeadMarshalBinaryTo(&a.TLVHead, b)
b[tlvHeadSize] = a.Count
return tlvHeadSize + 1, 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), 1, true); err != nil {
return err
}
a.Count = b[4]
return nil
}
44 changes: 39 additions & 5 deletions ptp/protocol/tlvs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -83,12 +82,12 @@ func TestParseAnnounceWithPathTrace(t *testing.T) {
require.Equal(t, want, *packet)
b, err := Bytes(packet)
require.Nil(t, err)
assert.Equal(t, raw, b)
require.Equal(t, raw, b)

// test generic DecodePacket as well
pp, err := DecodePacket(b)
require.Nil(t, err)
assert.Equal(t, &want, pp)
require.Equal(t, &want, pp)
}

func TestParseAnnounceWithAlternateTimeOffsetIndicator(t *testing.T) {
Expand Down Expand Up @@ -142,10 +141,45 @@ func TestParseAnnounceWithAlternateTimeOffsetIndicator(t *testing.T) {
require.Equal(t, want, *packet)
b, err := Bytes(packet)
require.Nil(t, err)
assert.Equal(t, raw, b)
require.Equal(t, raw, b)

// test generic DecodePacket as well
pp, err := DecodePacket(raw)
require.Nil(t, err)
assert.Equal(t, &want, pp)
require.Equal(t, &want, pp)
}

func TestParseSyncDelayReqWithAlternateResponsePort(t *testing.T) {
raw := []byte{1, 18, 0, 49, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 184, 206, 246, 255, 254, 68, 148, 144, 0, 1, 138, 207, 0, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 0, 1, 207, 0, 0}
packet := new(SyncDelayReq)
err := FromBytes(raw, packet)
require.Nil(t, err)

want := SyncDelayReq{
Header: Header{
SdoIDAndMsgType: NewSdoIDAndMsgType(MessageDelayReq, 0),
Version: Version,
SequenceID: 35535,
MessageLength: 49,
FlagField: FlagUnicast | FlagProfileSpecific1,
SourcePortIdentity: PortIdentity{
PortNumber: 1,
ClockIdentity: 13316852727524136080,
},
LogMessageInterval: 0x7f,
},
TLVs: []TLV{&AlternateResponsePortTLV{
TLVHead: TLVHead{TLVType: TLVAlternateResponsePort, LengthField: uint16(1)},
Count: uint8(207),
}},
}
require.Equal(t, want, *packet)
b, err := Bytes(packet)
require.Nil(t, err)
require.Equal(t, raw, b)

// test generic DecodePacket as well
pp, err := DecodePacket(raw)
require.Nil(t, err)
require.Equal(t, &want, pp)
}
4 changes: 4 additions & 0 deletions ptp/protocol/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ type TLVType uint16

// As per Table 52 tlvType values
const (
TLVNone TLVType = 0x0000
TLVManagement TLVType = 0x0001
TLVManagementErrorStatus TLVType = 0x0002
TLVOrganizationExtension TLVType = 0x0003
Expand All @@ -98,11 +99,13 @@ const (
TLVAcknowledgeCancelUnicastTransmission TLVType = 0x0007
TLVPathTrace TLVType = 0x0008
TLVAlternateTimeOffsetIndicator TLVType = 0x0009
TLVAlternateResponsePort TLVType = 0x0042
// Remaining 52 tlvType TLVs not implemented
)

// TLVTypeToString is a map from TLVType to string
var TLVTypeToString = map[TLVType]string{
TLVNone: "NONE",
TLVManagement: "MANAGEMENT",
TLVManagementErrorStatus: "MANAGEMENT_ERROR_STATUS",
TLVOrganizationExtension: "ORGANIZATION_EXTENSION",
Expand All @@ -112,6 +115,7 @@ var TLVTypeToString = map[TLVType]string{
TLVAcknowledgeCancelUnicastTransmission: "ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION",
TLVPathTrace: "PATH_TRACE",
TLVAlternateTimeOffsetIndicator: "ALTERNATE_TIME_OFFSET_INDICATOR",
TLVAlternateResponsePort: "ALTERNATE_RESPONSE_PORT",
}

func (t TLVType) String() string {
Expand Down
31 changes: 16 additions & 15 deletions ptp/protocol/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -82,6 +81,7 @@ func TestMessageTypeString(t *testing.T) {
}

func TestTLVTypeString(t *testing.T) {
require.Equal(t, "NONE", TLVNone.String())
require.Equal(t, "MANAGEMENT", TLVManagement.String())
require.Equal(t, "MANAGEMENT_ERROR_STATUS", TLVManagementErrorStatus.String())
require.Equal(t, "ORGANIZATION_EXTENSION", TLVOrganizationExtension.String())
Expand All @@ -91,6 +91,7 @@ func TestTLVTypeString(t *testing.T) {
require.Equal(t, "ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION", TLVAcknowledgeCancelUnicastTransmission.String())
require.Equal(t, "PATH_TRACE", TLVPathTrace.String())
require.Equal(t, "ALTERNATE_TIME_OFFSET_INDICATOR", TLVAlternateTimeOffsetIndicator.String())
require.Equal(t, "ALTERNATE_RESPONSE_PORT", TLVAlternateResponsePort.String())
}

func TestTimeSourceString(t *testing.T) {
Expand Down Expand Up @@ -178,7 +179,7 @@ func TestTimeIntervalNanoseconds(t *testing.T) {
require.Equal(t, tt.wantStr, tt.in.String())
// then convert time.Time we just got back to Timestamp
gotTI := NewTimeInterval(got)
assert.Equal(t, tt.in, gotTI)
require.Equal(t, tt.in, gotTI)
})
}
}
Expand Down Expand Up @@ -208,7 +209,7 @@ func TestPTPSeconds(t *testing.T) {
require.Equal(t, tt.wantStr, tt.in.String())
// then convert time.Time we just got back to PTPSeconds
gotTS := NewPTPSeconds(got)
assert.Equal(t, tt.in, gotTS)
require.Equal(t, tt.in, gotTS)
})
}
}
Expand Down Expand Up @@ -244,7 +245,7 @@ func TestTimestamp(t *testing.T) {
require.Equal(t, tt.wantStr, tt.in.String())
// then convert time.Time we just got back to Timestamp
gotTS := NewTimestamp(got)
assert.Equal(t, tt.in, gotTS)
require.Equal(t, tt.in, gotTS)
})
}
}
Expand Down Expand Up @@ -341,7 +342,7 @@ func TestLogInterval(t *testing.T) {
// then convert time.Duration we just got back to LogInterval
gotLI, err := NewLogInterval(gotDuration)
require.Nil(t, err)
assert.Equal(t, tt.in, gotLI)
require.Equal(t, tt.in, gotLI)
})
}
}
Expand All @@ -353,11 +354,11 @@ func TestClockIdentity(t *testing.T) {
got, err := NewClockIdentity(mac)
require.Nil(t, err)
want := ClockIdentity(0xc42a1fffe6d7ca6)
assert.Equal(t, want, got)
require.Equal(t, want, got)
wantStr := "0c42a1.fffe.6d7ca6"
assert.Equal(t, wantStr, got.String())
require.Equal(t, wantStr, got.String())
back := got.MAC()
assert.Equal(t, mac, back)
require.Equal(t, mac, back)
}

func TestPTPText(t *testing.T) {
Expand Down Expand Up @@ -415,14 +416,14 @@ func TestPTPText(t *testing.T) {
var text PTPText
err := text.UnmarshalBinary(tt.in)
if tt.wantErr {
assert.Error(t, err)
require.Error(t, err)
} else {
require.Nil(t, err)
assert.Equal(t, tt.want, string(text))
require.Equal(t, tt.want, string(text))

gotBytes, err := text.MarshalBinary()
require.Nil(t, err)
assert.Equal(t, tt.in, gotBytes)
require.Equal(t, tt.in, gotBytes)
}
})
}
Expand Down Expand Up @@ -512,7 +513,7 @@ func TestPortAddress(t *testing.T) {
var addr PortAddress
err := addr.UnmarshalBinary(tt.in)
if tt.wantErr {
assert.Error(t, err)
require.Error(t, err)
} else {
require.Nil(t, err)
ip, err := addr.IP()
Expand All @@ -522,12 +523,12 @@ func TestPortAddress(t *testing.T) {
}
require.Nil(t, err)

assert.Equal(t, *tt.want, addr)
assert.True(t, tt.wantIP.Equal(ip), "expect parsed IP %v to be equal to %v", ip, tt.wantIP)
require.Equal(t, *tt.want, addr)
require.True(t, tt.wantIP.Equal(ip), "expect parsed IP %v to be equal to %v", ip, tt.wantIP)

gotBytes, err := addr.MarshalBinary()
require.Nil(t, err)
assert.Equal(t, tt.in, gotBytes)
require.Equal(t, tt.in, gotBytes)
}
})
}
Expand Down
Loading

0 comments on commit e1745ab

Please sign in to comment.