├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── announcemsg.go ├── announcemsg_test.go ├── clockquality.go ├── delreqmsg.go ├── delrespmsg.go ├── followupmsg.go ├── followupmsg_test.go ├── header.go ├── header_test.go ├── mgmtmsg.go ├── pdelreqmsg.go ├── pdelreqmsg_test.go ├── pdelrespfollowupmsg.go ├── pdelrespfollowupmsg_test.go ├── pdelrespmsg.go ├── pdelrespmsg_test.go ├── ptp.go ├── ptp_test.go ├── signalmsg.go ├── signalmsg_test.go ├── syncmsg.go ├── syncmsg_test.go ├── tlv.go └── tlv_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.7.x 5 | - 1.8.x 6 | - 1.10.x 7 | - master 8 | 9 | install: 10 | - go get -v golang.org/x/tools/cmd/cover 11 | - go get -v github.com/mattn/goveralls 12 | 13 | script: 14 | - go test -v -covermode=count -coverprofile=coverage.out ./... 15 | - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Anton Glukhov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Precision Time Protocol 2 | 3 | [![Build Status](https://travis-ci.org/toxxin/go-ptp.svg?branch=master)](https://travis-ci.org/toxxin/go-ptp) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/toxxin/go-ptp)](https://goreportcard.com/report/github.com/toxxin/go-ptp) 5 | [![Coverage Status](https://coveralls.io/repos/toxxin/go-ptp/badge.svg?branch=master&service=github)](https://coveralls.io/github/toxxin/go-ptp?branch=master) 6 | 7 | IEEE1588v2 Precision Time Protocol implementation 8 | -------------------------------------------------------------------------------- /announcemsg.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | ) 7 | 8 | // AnnounceMsg ... 9 | type AnnounceMsg struct { 10 | Header 11 | GMClockQuality ClockQuality 12 | CurrentUtcOffset int16 13 | GMPriority1 uint8 14 | GMPriority2 uint8 15 | GMIdentity uint64 16 | StepsRemoved uint16 17 | TimeSource TimeSourceType 18 | PathTraceTlv 19 | } 20 | 21 | // MarshalBinary allocates a byte slice and marshals a Frame into binary form. 22 | func (t AnnounceMsg) MarshalBinary() ([]byte, error) { 23 | if t.Header.MessageType != AnnounceMsgType { 24 | return nil, ErrInvalidMsgType 25 | } 26 | 27 | tlvSlice, err := t.PathTraceTlv.MarshalBinary() 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | headerSlice, err := t.Header.MarshalBinary() 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | b := make([]byte, HeaderLen+AnnouncePayloadLen+len(tlvSlice)) 38 | 39 | copy(b[:HeaderLen], headerSlice) 40 | offset := HeaderLen 41 | 42 | // Reserved 10 bytes 43 | offset += Reserved10 44 | 45 | binary.BigEndian.PutUint16(b[offset:offset+CurrentUtcOffsetLen], uint16(t.CurrentUtcOffset)) 46 | offset += CurrentUtcOffsetLen 47 | 48 | // Reserved byte 49 | offset++ 50 | 51 | b[offset] = t.GMPriority1 52 | offset++ 53 | 54 | gmClockQualitySlice, err := t.GMClockQuality.MarshalBinary() 55 | if err != nil { 56 | return nil, err 57 | } 58 | copy(b[offset:offset+ClockQualityPayloadLen], gmClockQualitySlice) 59 | offset += ClockQualityPayloadLen 60 | 61 | b[offset] = t.GMPriority2 62 | offset++ 63 | 64 | binary.BigEndian.PutUint64(b[offset:offset+ClockIdentityLen], t.GMIdentity) 65 | offset += ClockIdentityLen 66 | 67 | binary.BigEndian.PutUint16(b[offset:offset+StepsRemovedLen], uint16(t.StepsRemoved)) 68 | offset += StepsRemovedLen 69 | 70 | b[offset] = uint8(t.TimeSource) 71 | offset++ 72 | 73 | copy(b[offset:offset+len(tlvSlice)], tlvSlice) 74 | 75 | return b, nil 76 | } 77 | 78 | // UnmarshalBinary unmarshals a byte slice into a Frame. 79 | func (t *AnnounceMsg) UnmarshalBinary(b []byte) error { 80 | if len(b) < HeaderLen+AnnouncePayloadLen { 81 | return io.ErrUnexpectedEOF 82 | } 83 | err := t.Header.UnmarshalBinary(b[:HeaderLen]) 84 | if err != nil { 85 | return err 86 | } 87 | 88 | if t.Header.MessageType != AnnounceMsgType { 89 | return ErrInvalidMsgType 90 | } 91 | 92 | offset := HeaderLen 93 | 94 | // Reserved 10 bytes 95 | offset += Reserved10 96 | 97 | utcoffset := binary.BigEndian.Uint16(b[offset : offset+2]) 98 | t.CurrentUtcOffset = int16(utcoffset) 99 | offset += 2 100 | 101 | // Reserved byte 102 | offset++ 103 | 104 | t.GMPriority1 = b[offset] 105 | offset++ 106 | 107 | err = t.GMClockQuality.UnmarshalBinary(b[offset : offset+ClockQualityPayloadLen]) 108 | if err != nil { 109 | return err 110 | } 111 | offset += ClockQualityPayloadLen 112 | 113 | t.GMPriority2 = b[offset] 114 | offset++ 115 | 116 | t.GMIdentity = binary.BigEndian.Uint64(b[offset : offset+GrandMasterIdentityLen]) 117 | offset += GrandMasterIdentityLen 118 | 119 | t.StepsRemoved = binary.BigEndian.Uint16(b[offset : offset+StepsRemovedLen]) 120 | offset += StepsRemovedLen 121 | 122 | t.TimeSource = TimeSourceType(b[offset]) 123 | if !isValidTimeSource(t.TimeSource) { 124 | return ErrInvalidTimeSource 125 | } 126 | 127 | return nil 128 | } 129 | -------------------------------------------------------------------------------- /announcemsg_test.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestMarshalAnnounce(t *testing.T) { 11 | var tests = []struct { 12 | desc string 13 | m *AnnounceMsg 14 | b []byte 15 | err error 16 | }{ 17 | { 18 | desc: "Correct structure", 19 | m: &AnnounceMsg{ 20 | Header: Header{ 21 | MessageType: AnnounceMsgType, 22 | MessageLength: HeaderLen + AnnouncePayloadLen, 23 | VersionPTP: Version2, 24 | CorrectionNs: 0, 25 | CorrectionSubNs: 0, 26 | ClockIdentity: 0x000af7fffe42a753, 27 | PortNumber: 2, 28 | SequenceID: 55330, 29 | LogMessagePeriod: 0, 30 | }, 31 | GMClockQuality: ClockQuality{ 32 | ClockClass: PrimarySyncRefClass, 33 | ClockAccuracy: ClockAccuracy100ns, 34 | ClockVariance: 200, 35 | }, 36 | CurrentUtcOffset: 36, 37 | GMPriority1: 128, 38 | GMPriority2: 128, 39 | GMIdentity: 0x001d7ffffe80024a, 40 | StepsRemoved: 0, 41 | TimeSource: TimeSourceGPS, 42 | PathTraceTlv: PathTraceTlv{}, 43 | }, 44 | b: append([]byte{0xb, 0x2, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 45 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 46 | 0x0, 0x0, 0x0, 0x0, 47 | // ClockIdentity 48 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x5, 0x0, 49 | // Message body 50 | // Reserved 10 bytes 51 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 52 | 0x0, 0x24, 53 | 0x0, 0x80, 54 | 0x6, 0x21, 0x0, 0xc8, 0x80, 55 | // GM Identity 56 | 0x0, 0x1d, 0x7f, 0xff, 0xfe, 0x80, 0x2, 0x4a, 57 | 0x0, 0x0, 0x20, 58 | // PathTraceTlv 59 | 0x0, 0x8, 0x0, 0x0, 60 | }), 61 | }, 62 | { 63 | desc: "Invalid message type", 64 | m: &AnnounceMsg{ 65 | Header: Header{ 66 | MessageType: SyncMsgType, 67 | MessageLength: HeaderLen + AnnouncePayloadLen, 68 | VersionPTP: Version2, 69 | CorrectionNs: 0, 70 | CorrectionSubNs: 0, 71 | ClockIdentity: 0x000af7fffe42a753, 72 | PortNumber: 2, 73 | SequenceID: 55330, 74 | LogMessagePeriod: 0, 75 | }, 76 | GMClockQuality: ClockQuality{ 77 | ClockClass: PrimarySyncRefClass, 78 | ClockAccuracy: ClockAccuracy100ns, 79 | ClockVariance: 200, 80 | }, 81 | CurrentUtcOffset: 36, 82 | GMPriority1: 128, 83 | GMPriority2: 128, 84 | GMIdentity: 0x001d7ffffe80024a, 85 | StepsRemoved: 0, 86 | TimeSource: TimeSourceGPS, 87 | PathTraceTlv: PathTraceTlv{}, 88 | }, 89 | err: ErrInvalidMsgType, 90 | }, 91 | } 92 | 93 | for _, tt := range tests { 94 | t.Run(tt.desc, func(t *testing.T) { 95 | b, err := tt.m.MarshalBinary() 96 | if err != nil { 97 | if want, got := tt.err, err; want != got { 98 | t.Fatalf("unexpected error: %v != %v", want, got) 99 | } 100 | 101 | return 102 | } 103 | 104 | if want, got := tt.b, b; !bytes.Equal(want, got) { 105 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 106 | } 107 | }) 108 | } 109 | } 110 | 111 | func TestUnmarshalAnnounce(t *testing.T) { 112 | var tests = []struct { 113 | desc string 114 | m *AnnounceMsg 115 | b []byte 116 | err error 117 | }{ 118 | { 119 | desc: "Correct structure", 120 | m: &AnnounceMsg{ 121 | Header: Header{ 122 | MessageType: AnnounceMsgType, 123 | MessageLength: HeaderLen + AnnouncePayloadLen, 124 | VersionPTP: Version2, 125 | CorrectionNs: 0, 126 | CorrectionSubNs: 0, 127 | ClockIdentity: 0x000af7fffe42a753, 128 | PortNumber: 2, 129 | SequenceID: 55330, 130 | LogMessagePeriod: 0, 131 | }, 132 | GMClockQuality: ClockQuality{ 133 | ClockClass: PrimarySyncRefClass, 134 | ClockAccuracy: ClockAccuracy100ns, 135 | ClockVariance: 200, 136 | }, 137 | CurrentUtcOffset: 36, 138 | GMPriority1: 128, 139 | GMPriority2: 128, 140 | GMIdentity: 0x001d7ffffe80024a, 141 | StepsRemoved: 0, 142 | TimeSource: TimeSourceGPS, 143 | }, 144 | b: append([]byte{0xb, 0x2, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 145 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 146 | 0x0, 0x0, 0x0, 0x0, 147 | // ClockIdentity 148 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x5, 0x0, 149 | // Message body 150 | // Reserved 10 bytes 151 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 152 | 0x0, 0x24, 153 | 0x0, 0x80, 154 | 0x6, 0x21, 0x0, 0xc8, 0x80, 155 | // GM Identity 156 | 0x0, 0x1d, 0x7f, 0xff, 0xfe, 0x80, 0x2, 0x4a, 157 | 0x0, 0x0, 0x20, 158 | }), 159 | }, 160 | { 161 | desc: "Invalid clock class", 162 | b: append([]byte{0xb, 0x2, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 163 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 164 | 0x0, 0x0, 0x0, 0x0, 165 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x5, 0xfc, 166 | // Message body 167 | // Reserved 10 bytes 168 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 169 | 0x0, 0x24, 170 | 0x0, 0x80, 171 | // Wrong class - 0x8 instead of {6,7,248,255} 172 | 0x8, 0x21, 0x0, 0xc8, 0x80, 173 | // GM Identity 174 | 0x0, 0x1d, 0x7f, 0xff, 0xfe, 0x80, 0x2, 0x4a, 175 | 0x0, 0x0, 0x20, 176 | }), 177 | err: ErrInvalidClockClass, 178 | }, 179 | { 180 | desc: "Invalid clock accuracy", 181 | b: append([]byte{0xb, 0x2, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 182 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 183 | 0x0, 0x0, 0x0, 0x0, 184 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x5, 0xfc, 185 | // Message body 186 | // Reserved 10 bytes 187 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 188 | 0x0, 0x24, 189 | 0x0, 0x80, 190 | // Wrong class accuracy - 0x1 191 | 0x6, 0x1, 0x0, 0xc8, 0x80, 192 | // GM Identity 193 | 0x0, 0x1d, 0x7f, 0xff, 0xfe, 0x80, 0x2, 0x4a, 194 | 0x0, 0x0, 0x20, 195 | }), 196 | err: ErrInvalidClockAccuracy, 197 | }, 198 | { 199 | desc: "Invalid time source", 200 | b: append([]byte{0xb, 0x2, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 201 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 202 | 0x0, 0x0, 0x0, 0x0, 203 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x5, 0xfc, 204 | // Message body 205 | // Reserved 10 bytes 206 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 207 | 0x0, 0x24, 208 | 0x0, 0x80, 209 | // Wrong class accuracy - 0x1 210 | 0x6, 0x21, 0x0, 0xc8, 0x80, 211 | // GM Identity 212 | 0x0, 0x1d, 0x7f, 0xff, 0xfe, 0x80, 0x2, 0x4a, 213 | 0x0, 0x0, 0x21, 214 | }), 215 | err: ErrInvalidTimeSource, 216 | }, 217 | { 218 | desc: "Invalid time source", 219 | b: append([]byte{0xb, 0x2, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 220 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 221 | 0x0, 0x0, 0x0, 0x0, 222 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x5, 0xfc, 223 | // Message body 224 | // Reserved 10 bytes 225 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 226 | 0x0, 0x24, 227 | 0x0, 0x80, 228 | // Wrong class accuracy - 0x1 229 | 0x6, 0x21, 0x0, 0xc8, 0x80, 230 | // GM Identity 231 | 0x0, 0x1d, 0x7f, 0xff, 0xfe, 0x80, 0x2, 0x4a, 232 | }), 233 | err: io.ErrUnexpectedEOF, 234 | }, 235 | { 236 | desc: "Invalid message type", 237 | b: append([]byte{0x1, 0x2, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 238 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 239 | 0x0, 0x0, 0x0, 0x0, 240 | // ClockIdentity 241 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x5, 0x0, 242 | // Message body 243 | // Reserved 10 bytes 244 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 245 | 0x0, 0x24, 246 | 0x0, 0x80, 247 | 0x6, 0x21, 0x0, 0xc8, 0x80, 248 | // GM Identity 249 | 0x0, 0x1d, 0x7f, 0xff, 0xfe, 0x80, 0x2, 0x4a, 250 | 0x0, 0x0, 0x20, 251 | }), 252 | err: ErrInvalidMsgType, 253 | }, 254 | { 255 | desc: "Invalid length", 256 | b: append([]byte{0x1, 0x2, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 257 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 258 | 0x0, 0x0, 0x0, 0x0, 259 | // ClockIdentity 260 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x5, 0x0, 261 | // Message body 262 | // Reserved 10 bytes 263 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 264 | 0x0, 0x24, 265 | 0x0, 0x80, 266 | 0x6, 0x21, 0x0, 0xc8, 0x80, 267 | // GM Identity 268 | 0x0, 0x1d, 0x7f, 0xff, 0xfe, 0x80, 0x2, 0x4a, 269 | 0x0, 0x0, 0x20, 0x0, 270 | }), 271 | err: ErrInvalidMsgType, 272 | }, 273 | } 274 | 275 | for _, tt := range tests { 276 | t.Run(tt.desc, func(t *testing.T) { 277 | m := new(AnnounceMsg) 278 | err := m.UnmarshalBinary(tt.b) 279 | if err != nil { 280 | if want, got := tt.err, err; want != got { 281 | t.Fatalf("unexpected error: %v != %v", want, got) 282 | } 283 | 284 | return 285 | } 286 | 287 | if want, got := tt.m, m; !reflect.DeepEqual(want, got) { 288 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 289 | } 290 | }) 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /clockquality.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | ) 7 | 8 | // ClockClassType Type 9 | type ClockClassType uint8 10 | 11 | // ClockClass types codes 12 | const ( 13 | PrimarySyncRefClass ClockClassType = 6 14 | LostSyncClass ClockClassType = 7 15 | DefaultClass ClockClassType = 248 16 | SlaveOnlyClass ClockClassType = 255 17 | ) 18 | 19 | // ClockAccuracyType Type 20 | type ClockAccuracyType uint8 21 | 22 | // ClockAccuracy types codes 23 | const ( 24 | ClockAccuracy25ns ClockAccuracyType = 32 25 | ClockAccuracy100ns ClockAccuracyType = 33 26 | ClockAccuracy250ns ClockAccuracyType = 34 27 | ClockAccuracy1mics ClockAccuracyType = 35 28 | ClockAccuracy2_5mics ClockAccuracyType = 36 29 | ClockAccuracy10mics ClockAccuracyType = 37 30 | ClockAccuracy25mics ClockAccuracyType = 38 31 | ClockAccuracy100mics ClockAccuracyType = 39 32 | ClockAccuracy250mics ClockAccuracyType = 40 33 | ClockAccuracy1ms ClockAccuracyType = 41 34 | ClockAccuracy2_5ms ClockAccuracyType = 42 35 | ClockAccuracy10ms ClockAccuracyType = 43 36 | ClockAccuracy25ms ClockAccuracyType = 44 37 | ClockAccuracy100ms ClockAccuracyType = 45 38 | ClockAccuracy250ms ClockAccuracyType = 46 39 | ClockAccuracy1s ClockAccuracyType = 47 40 | ClockAccuracy10s ClockAccuracyType = 48 41 | ClockAccuracyMore10s ClockAccuracyType = 49 42 | ClockAccuracyNotSupported ClockAccuracyType = 255 43 | ) 44 | 45 | // ClockQuality defines Grand Master Clock Quality 46 | type ClockQuality struct { 47 | ClockClass ClockClassType 48 | ClockAccuracy ClockAccuracyType 49 | ClockVariance uint16 50 | } 51 | 52 | func isValidClockClass(class ClockClassType) bool { 53 | switch class { 54 | case 55 | PrimarySyncRefClass, 56 | LostSyncClass, 57 | DefaultClass, 58 | SlaveOnlyClass: 59 | return true 60 | } 61 | return false 62 | } 63 | 64 | func isValidClockAccuracy(c ClockAccuracyType) bool { 65 | switch c { 66 | case 67 | ClockAccuracy25ns, 68 | ClockAccuracy100ns, 69 | ClockAccuracy250ns, 70 | ClockAccuracy1mics, 71 | ClockAccuracy2_5mics, 72 | ClockAccuracy10mics, 73 | ClockAccuracy25mics, 74 | ClockAccuracy100mics, 75 | ClockAccuracy250mics, 76 | ClockAccuracy1ms, 77 | ClockAccuracy2_5ms, 78 | ClockAccuracy10ms, 79 | ClockAccuracy25ms, 80 | ClockAccuracy100ms, 81 | ClockAccuracy250ms, 82 | ClockAccuracy1s, 83 | ClockAccuracy10s, 84 | ClockAccuracyMore10s, 85 | ClockAccuracyNotSupported: 86 | return true 87 | } 88 | return false 89 | } 90 | 91 | // MarshalBinary allocates a byte slice and marshals a Header into binary form. 92 | func (p ClockQuality) MarshalBinary() ([]byte, error) { 93 | 94 | b := make([]byte, 4) 95 | 96 | b[0] = uint8(p.ClockClass) 97 | b[1] = uint8(p.ClockAccuracy) 98 | 99 | binary.BigEndian.PutUint16(b[2:4], uint16(p.ClockVariance)) 100 | 101 | return b, nil 102 | } 103 | 104 | // UnmarshalBinary unmarshals a byte slice into a Header. 105 | func (p *ClockQuality) UnmarshalBinary(b []byte) error { 106 | if len(b) != ClockQualityPayloadLen { 107 | return io.ErrUnexpectedEOF 108 | } 109 | 110 | p.ClockClass = ClockClassType(b[0]) 111 | if !isValidClockClass(p.ClockClass) { 112 | return ErrInvalidClockClass 113 | } 114 | 115 | p.ClockAccuracy = ClockAccuracyType(b[1]) 116 | if !isValidClockAccuracy(p.ClockAccuracy) { 117 | return ErrInvalidClockAccuracy 118 | } 119 | 120 | p.ClockVariance = binary.BigEndian.Uint16(b[2:]) 121 | 122 | return nil 123 | } 124 | -------------------------------------------------------------------------------- /delreqmsg.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "io" 5 | "time" 6 | ) 7 | 8 | // DelReqMsg ... 9 | type DelReqMsg struct { 10 | Header 11 | OriginTimestamp time.Time 12 | } 13 | 14 | // MarshalBinary allocates a byte slice and marshals a Frame into binary form. 15 | func (t *DelReqMsg) MarshalBinary() ([]byte, error) { 16 | 17 | if t.Header.MessageType != DelayReqMsgType { 18 | return nil, ErrInvalidMsgType 19 | } 20 | 21 | b := make([]byte, HeaderLen+DelayReqPayloadLen) 22 | 23 | headerSlice, err := t.Header.MarshalBinary() 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | copy(b[:HeaderLen], headerSlice) 29 | 30 | time2OriginTimestamp(t.OriginTimestamp, b[HeaderLen:]) 31 | 32 | return b, nil 33 | } 34 | 35 | // UnmarshalBinary unmarshals a byte slice into a DelReqMsg. 36 | // 37 | // If the byte slice does not contain enough data to unmarshal a valid DelReqMsg, 38 | // io.ErrUnexpectedEOF is returned. 39 | func (t *DelReqMsg) UnmarshalBinary(b []byte) error { 40 | if len(b) < HeaderLen+DelayReqPayloadLen { 41 | return io.ErrUnexpectedEOF 42 | } 43 | 44 | err := t.Header.UnmarshalBinary(b[:HeaderLen]) 45 | if err != nil { 46 | return err 47 | } 48 | 49 | if t.OriginTimestamp, err = originTimestamp2Time(b[HeaderLen:]); err != nil { 50 | return err 51 | } 52 | 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /delrespmsg.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "time" 7 | ) 8 | 9 | // DelRespMsg ... 10 | type DelRespMsg struct { 11 | Header 12 | ReceiveTimestamp time.Time 13 | RequestingPortIdentity uint64 14 | RequestingPortID uint16 15 | } 16 | 17 | // MarshalBinary allocates a byte slice and marshals a Frame into binary form. 18 | func (t *DelRespMsg) MarshalBinary() ([]byte, error) { 19 | 20 | if t.Header.MessageType != DelayRespMsgType { 21 | return nil, ErrInvalidMsgType 22 | } 23 | 24 | b := make([]byte, HeaderLen+DelayRespPayloadLen) 25 | 26 | headerSlice, err := t.Header.MarshalBinary() 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | copy(b[:HeaderLen], headerSlice) 32 | offset := HeaderLen 33 | 34 | //TODO: add receiveTimestamp 35 | offset += 10 36 | 37 | portIdentitySlice := make([]byte, 8) 38 | binary.BigEndian.PutUint64(portIdentitySlice, t.RequestingPortIdentity) 39 | copy(b[offset:offset+8], portIdentitySlice) 40 | offset += 8 41 | 42 | portIDSlice := make([]byte, 2) 43 | binary.BigEndian.PutUint16(portIDSlice, t.RequestingPortID) 44 | copy(b[offset:offset+2], portIDSlice) 45 | 46 | return b, nil 47 | } 48 | 49 | // UnmarshalBinary unmarshals a byte slice into a DelRespMsg. 50 | // 51 | // If the byte slice does not contain enough data to unmarshal a valid DelRespMsg, 52 | // io.ErrUnexpectedEOF is returned. 53 | func (t *DelRespMsg) UnmarshalBinary(b []byte) error { 54 | // Must contain type and length values 55 | if len(b) < HeaderLen+DelayRespPayloadLen { 56 | return io.ErrUnexpectedEOF 57 | } 58 | 59 | return ErrInvalidFrame 60 | } 61 | -------------------------------------------------------------------------------- /followupmsg.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "io" 5 | "time" 6 | ) 7 | 8 | // FollowUpMsg ... 9 | type FollowUpMsg struct { 10 | Header 11 | PreciseOriginTimestamp time.Time 12 | } 13 | 14 | // MarshalBinary allocates a byte slice and marshals a Frame into binary form. 15 | func (t *FollowUpMsg) MarshalBinary() ([]byte, error) { 16 | 17 | if t.Header.MessageType != FollowUpMsgType { 18 | return nil, ErrInvalidMsgType 19 | } 20 | 21 | if t.Header.MessageLength == 0 { 22 | t.Header.MessageLength = HeaderLen + FollowUpPayloadLen 23 | } 24 | 25 | if t.Header.MessageLength != HeaderLen+FollowUpPayloadLen { 26 | return nil, io.ErrUnexpectedEOF 27 | } 28 | 29 | b := make([]byte, HeaderLen+FollowUpPayloadLen) 30 | 31 | headerSlice, err := t.Header.MarshalBinary() 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | copy(b[:HeaderLen], headerSlice) 37 | 38 | // Origin timestamp 39 | time2OriginTimestamp(t.PreciseOriginTimestamp, b[HeaderLen:]) 40 | 41 | return b, nil 42 | } 43 | 44 | // UnmarshalBinary unmarshals a byte slice into a FollowUpMsg. 45 | // 46 | // If the byte slice does not contain enough data to unmarshal a valid FollowUpMsg, 47 | // io.ErrUnexpectedEOF is returned. 48 | func (t *FollowUpMsg) UnmarshalBinary(b []byte) error { 49 | if len(b) < HeaderLen+FollowUpPayloadLen { 50 | return io.ErrUnexpectedEOF 51 | } 52 | err := t.Header.UnmarshalBinary(b[:HeaderLen]) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | if t.Header.MessageType != FollowUpMsgType { 58 | return ErrInvalidMsgType 59 | } 60 | 61 | if t.PreciseOriginTimestamp, err = originTimestamp2Time(b[HeaderLen:]); err != nil { 62 | return err 63 | } 64 | 65 | return nil 66 | } 67 | -------------------------------------------------------------------------------- /followupmsg_test.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "reflect" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestMarshalFollowUp(t *testing.T) { 12 | 13 | var tests = []struct { 14 | desc string 15 | m *FollowUpMsg 16 | b []byte 17 | err error 18 | }{ 19 | { 20 | desc: "Correct structure", 21 | m: &FollowUpMsg{ 22 | Header: Header{ 23 | MessageType: FollowUpMsgType, 24 | CorrectionNs: 0, 25 | CorrectionSubNs: 0, 26 | ClockIdentity: 0x000af7fffe42a753, 27 | PortNumber: 2, 28 | SequenceID: 55330, 29 | LogMessagePeriod: -4, 30 | }, 31 | PreciseOriginTimestamp: time.Unix(500, 200), 32 | }, 33 | b: append([]byte{0x8, 0x2, 0x0, 0x2c, 0x0, 0x0, 0x0, 0x0, 34 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 35 | 0x0, 0x0, 0x0, 0x0, 36 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x2, 0xfc, 37 | 0x0, 0x0, 0x0, 0x0, 0x1, 0xf4, 0x0, 0x0, 0x0, 0xc8}), 38 | }, 39 | { 40 | desc: "Invalid message type", 41 | m: &FollowUpMsg{ 42 | Header: Header{ 43 | MessageType: SyncMsgType, 44 | CorrectionNs: 0, 45 | CorrectionSubNs: 0, 46 | ClockIdentity: 0x000af7fffe42a753, 47 | PortNumber: 2, 48 | SequenceID: 55330, 49 | LogMessagePeriod: -4, 50 | }, 51 | }, 52 | err: ErrInvalidMsgType, 53 | }, 54 | } 55 | 56 | for _, tt := range tests { 57 | t.Run(tt.desc, func(t *testing.T) { 58 | b, err := tt.m.MarshalBinary() 59 | if err != nil { 60 | if want, got := tt.err, err; want != got { 61 | t.Fatalf("unexpected error: %v != %v", want, got) 62 | } 63 | 64 | return 65 | } 66 | 67 | if want, got := tt.b, b; !bytes.Equal(want, got) { 68 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 69 | } 70 | }) 71 | } 72 | } 73 | 74 | func TestUnmarshalFollowUp(t *testing.T) { 75 | var tests = []struct { 76 | desc string 77 | m *FollowUpMsg 78 | b []byte 79 | err error 80 | }{ 81 | { 82 | desc: "Correct structure", 83 | m: &FollowUpMsg{ 84 | Header: Header{ 85 | MessageType: FollowUpMsgType, 86 | MessageLength: HeaderLen + FollowUpPayloadLen, 87 | VersionPTP: Version2, 88 | CorrectionNs: 0, 89 | CorrectionSubNs: 0, 90 | ClockIdentity: 0x000af7fffe42a753, 91 | PortNumber: 2, 92 | SequenceID: 55330, 93 | LogMessagePeriod: -4, 94 | }, 95 | PreciseOriginTimestamp: time.Unix(500, 200), 96 | }, 97 | b: append([]byte{0x8, 0x2, 0x0, 0x2c, 0x0, 0x0, 0x0, 0x0, 98 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 99 | 0x0, 0x0, 0x0, 0x0, 100 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x0, 0xfc, 101 | 0x0, 0x0, 0x0, 0x0, 0x1, 0xf4, 0x0, 0x0, 0x0, 0xc8}), 102 | }, 103 | { 104 | desc: "Invalid length", 105 | b: append([]byte{0x8, 0x2, 0x0, 0x2c, 0x0, 0x0, 0x0, 0x0, 106 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 107 | 0x0, 0x0, 0x0, 0x0, 108 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x0, 0xfc, 109 | 0x0, 0x0, 0x0, 0x0, 0x1, 0xf4, 0x0, 0x0, 0x0}), 110 | err: io.ErrUnexpectedEOF, 111 | }, 112 | { 113 | desc: "Invalid message type", 114 | b: append([]byte{0x1, 0x2, 0x0, 0x2c, 0x0, 0x0, 0x0, 0x0, 115 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 116 | 0x0, 0x0, 0x0, 0x0, 117 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x0, 0xfc, 118 | 0x0, 0x0, 0x0, 0x0, 0x1, 0xf4, 0x0, 0x0, 0x0, 0xc8}), 119 | err: ErrInvalidMsgType, 120 | }, 121 | } 122 | 123 | for _, tt := range tests { 124 | t.Run(tt.desc, func(t *testing.T) { 125 | m := new(FollowUpMsg) 126 | err := m.UnmarshalBinary(tt.b) 127 | if err != nil { 128 | if want, got := tt.err, err; want != got { 129 | t.Fatalf("unexpected error: %v != %v", want, got) 130 | } 131 | 132 | return 133 | } 134 | if want, got := tt.m, m; !reflect.DeepEqual(want, got) { 135 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 136 | } 137 | }) 138 | } 139 | } 140 | 141 | func BenchmarkMarshalFollowUp(b *testing.B) { 142 | f := FollowUpMsg{ 143 | Header: Header{ 144 | MessageType: FollowUpMsgType, 145 | MessageLength: HeaderLen + FollowUpPayloadLen, 146 | VersionPTP: Version2, 147 | CorrectionNs: 0, 148 | CorrectionSubNs: 0, 149 | ClockIdentity: 0x000af7fffe42a753, 150 | PortNumber: 2, 151 | SequenceID: 55330, 152 | LogMessagePeriod: -4, 153 | }, 154 | PreciseOriginTimestamp: time.Unix(500, 200), 155 | } 156 | for i := 0; i < b.N; i++ { 157 | f.MarshalBinary() 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /header.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | ) 7 | 8 | // ProtoVersion is PTP protocol version 9 | type ProtoVersion uint8 10 | 11 | // Version numbers 12 | const ( 13 | _ ProtoVersion = iota 14 | Version1 15 | Version2 16 | Version3 17 | ) 18 | 19 | // Flags is header's field to indicate status 20 | type Flags struct { 21 | LI61 bool 22 | LI59 bool 23 | UtcReasonable bool 24 | TimeScale bool 25 | TimeTraceable bool 26 | FrequencyTraceable bool 27 | AlternateMaster bool 28 | TwoSteps bool 29 | Unicast bool 30 | ProfileSpecific1 bool 31 | ProfileSpecific2 bool 32 | Security bool 33 | } 34 | 35 | const ( 36 | lI61Bit uint16 = 1 << 0 37 | lI59Bit uint16 = 1 << 1 38 | utcReasonableBit uint16 = 1 << 2 39 | timeScaleBit uint16 = 1 << 3 40 | timeTraceableBit uint16 = 1 << 4 41 | frequencyTraceableBit uint16 = 1 << 5 42 | alternateMasterBit uint16 = 1 << 8 43 | twoStepsBit uint16 = 1 << 9 44 | unicastBit uint16 = 1 << 10 45 | profileSpecific1Bit uint16 = 1 << 13 46 | profileSpecific2Bit uint16 = 1 << 14 47 | securityBit uint16 = 1 << 15 48 | ) 49 | 50 | func b2i(b bool) uint16 { 51 | if b { 52 | return 1 53 | } 54 | return 0 55 | } 56 | 57 | // MarshalBinary returns Flags as uint16 value. 58 | func (f *Flags) MarshalBinary() uint16 { 59 | return (b2i(f.LI61)<<0 | 60 | b2i(f.LI59)<<1 | 61 | b2i(f.UtcReasonable)<<2 | 62 | b2i(f.TimeScale)<<3 | 63 | b2i(f.TimeTraceable)<<4 | 64 | b2i(f.FrequencyTraceable)<<5 | 65 | b2i(f.AlternateMaster)<<8 | 66 | b2i(f.TwoSteps)<<9 | 67 | b2i(f.Unicast)<<10 | 68 | b2i(f.ProfileSpecific1)<<13 | 69 | b2i(f.ProfileSpecific2)<<14 | 70 | b2i(f.Security)<<15) 71 | } 72 | 73 | func (f *Flags) UnmarshalBinary(b []byte) error { 74 | if len(b) != 2 { 75 | return io.ErrUnexpectedEOF 76 | } 77 | 78 | flags := binary.BigEndian.Uint16(b[:]) 79 | 80 | f.LI61 = flags&lI61Bit != 0 81 | f.LI59 = flags&lI59Bit != 0 82 | f.UtcReasonable = flags&utcReasonableBit != 0 83 | f.TimeScale = flags&timeScaleBit != 0 84 | f.TimeTraceable = flags&timeTraceableBit != 0 85 | f.FrequencyTraceable = flags&frequencyTraceableBit != 0 86 | f.AlternateMaster = flags&alternateMasterBit != 0 87 | f.TwoSteps = flags&twoStepsBit != 0 88 | f.Unicast = flags&unicastBit != 0 89 | f.ProfileSpecific1 = flags&profileSpecific1Bit != 0 90 | f.ProfileSpecific2 = flags&profileSpecific2Bit != 0 91 | f.Security = flags&securityBit != 0 92 | 93 | return nil 94 | } 95 | 96 | // Header struct describes the header of a PTP message. 97 | type Header struct { 98 | Flags 99 | MessageType MsgType 100 | MessageLength uint16 101 | VersionPTP ProtoVersion 102 | CorrectionNs uint64 103 | CorrectionSubNs uint16 104 | ClockIdentity uint64 105 | PortNumber uint16 106 | SequenceID uint16 107 | LogMessagePeriod int8 108 | } 109 | 110 | // MarshalBinary allocates a byte slice and marshals a Header into binary form. 111 | func (h *Header) MarshalBinary() ([]byte, error) { 112 | 113 | var correction uint64 114 | 115 | b := make([]byte, HeaderLen) 116 | offset := 0 117 | 118 | // Transport specific, messageId 119 | b[0] = 0x0 | uint8(h.MessageType) 120 | offset++ 121 | 122 | // PTP proto version 123 | b[1] = byte(Version2) 124 | offset++ 125 | 126 | // Message length 127 | b[offset] = byte(h.MessageLength >> 8) 128 | offset++ 129 | b[offset] = byte(h.MessageLength) 130 | offset++ 131 | 132 | // Subdomain number 133 | b[offset] = 0x0 134 | offset++ 135 | 136 | // Skip reserved byte 137 | offset++ 138 | 139 | flags := (&h.Flags).MarshalBinary() 140 | 141 | binary.BigEndian.PutUint16(b[offset:offset+FlagsLen], flags) 142 | offset += FlagsLen 143 | 144 | // Correction Ns & SubNs 145 | correction = (h.CorrectionNs << 2) | (uint64)(h.CorrectionSubNs) 146 | binary.BigEndian.PutUint64(b[offset:offset+CorrectionFullLen], correction) 147 | offset += CorrectionFullLen 148 | 149 | // Skip 4 reserved bytes 150 | offset += 4 151 | 152 | // Clock identity 153 | binary.BigEndian.PutUint64(b[offset:offset+ClockIdentityLen], h.ClockIdentity) 154 | offset += ClockIdentityLen 155 | 156 | // Source port 157 | binary.BigEndian.PutUint16(b[offset:offset+SourcePortNumberLen], h.PortNumber) 158 | offset += SourcePortNumberLen 159 | 160 | // Sequence ID 161 | binary.BigEndian.PutUint16(b[offset:offset+SequenceIDLen], h.SequenceID) 162 | offset += SequenceIDLen 163 | 164 | var msgCtrl MsgCtrlType 165 | switch h.MessageType { 166 | case (SyncMsgType): 167 | msgCtrl = SyncMsgCtrlType 168 | case (DelayReqMsgType): 169 | msgCtrl = DelayReqMsgCtrlType 170 | case (FollowUpMsgType): 171 | msgCtrl = FollowUpMsgCtrlType 172 | case (DelayRespMsgType): 173 | msgCtrl = DelayRespMsgCtrlType 174 | default: 175 | msgCtrl = OtherMsgCtrlType 176 | } 177 | 178 | b[offset] = byte(msgCtrl) 179 | offset++ 180 | 181 | b[offset] = (byte)(h.LogMessagePeriod) 182 | offset++ 183 | 184 | return b, nil 185 | } 186 | 187 | func isValidMsgType(msgtype MsgType) bool { 188 | switch msgtype { 189 | case 190 | SyncMsgType, 191 | DelayReqMsgType, 192 | PDelayReqMsgType, 193 | PDelayRespMsgType, 194 | FollowUpMsgType, 195 | DelayRespMsgType, 196 | PDelayRespFollowUpMsgType, 197 | AnnounceMsgType, 198 | SignalingMsgType, 199 | MgmtMsgType: 200 | return true 201 | } 202 | return false 203 | } 204 | 205 | // UnmarshalBinary unmarshals a byte slice into a Header. 206 | func (h *Header) UnmarshalBinary(b []byte) error { 207 | if len(b) != HeaderLen { 208 | return io.ErrUnexpectedEOF 209 | } 210 | 211 | h.MessageType = MsgType(b[0] & 0x0f) 212 | if !isValidMsgType(h.MessageType) { 213 | return ErrInvalidMsgType 214 | } 215 | 216 | // TODO: Add implementation another versions 217 | h.VersionPTP = ProtoVersion(0xf & b[1]) 218 | if h.VersionPTP != Version2 { 219 | return ErrUnsupportedVersion 220 | } 221 | 222 | h.MessageLength = binary.BigEndian.Uint16(b[2:4]) 223 | 224 | h.Flags.UnmarshalBinary(b[6:8]) 225 | 226 | // Correct Ns & SubNs 227 | tmpSlice := make([]byte, 8) 228 | copy(tmpSlice[2:], b[8:14]) 229 | 230 | h.CorrectionNs = binary.BigEndian.Uint64(tmpSlice) 231 | h.CorrectionSubNs = binary.BigEndian.Uint16(b[14:16]) 232 | 233 | h.ClockIdentity = binary.BigEndian.Uint64(b[20:28]) 234 | h.PortNumber = binary.BigEndian.Uint16(b[28:30]) 235 | 236 | h.SequenceID = binary.BigEndian.Uint16(b[30:32]) 237 | h.LogMessagePeriod = int8(b[33]) 238 | 239 | return nil 240 | } 241 | -------------------------------------------------------------------------------- /header_test.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestMarshalHeader(t *testing.T) { 11 | 12 | var tests = []struct { 13 | desc string 14 | h *Header 15 | b []byte 16 | err error 17 | }{ 18 | { 19 | desc: "Correct structure", 20 | h: &Header{ 21 | MessageType: PDelayReqMsgType, 22 | MessageLength: 44, 23 | VersionPTP: Version2, 24 | Flags: Flags{ 25 | Security: false, 26 | ProfileSpecific2: false, 27 | ProfileSpecific1: false, 28 | Unicast: false, 29 | TwoSteps: false, 30 | AlternateMaster: false, 31 | FrequencyTraceable: false, 32 | TimeTraceable: false, 33 | UtcReasonable: false, 34 | LI59: false, 35 | LI61: false, 36 | }, 37 | CorrectionNs: 0, 38 | CorrectionSubNs: 0, 39 | ClockIdentity: 0x000af7fffe42a753, 40 | PortNumber: 2, 41 | SequenceID: 55330, 42 | LogMessagePeriod: -4, 43 | }, 44 | b: append([]byte{0x2, 0x2, 0x0, 0x2c, 0x0, 0x0, 0x0, 0x0, 45 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 46 | 0x0, 0x0, 0x0, 0x0, 47 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x5, 0xfc}), 48 | }, 49 | } 50 | 51 | for _, tt := range tests { 52 | t.Run(tt.desc, func(t *testing.T) { 53 | b, err := tt.h.MarshalBinary() 54 | if err != nil { 55 | if want, got := tt.err, err; want != got { 56 | t.Fatalf("unexpected error: %v != %v", want, got) 57 | } 58 | 59 | return 60 | } 61 | 62 | if want, got := tt.b, b; !bytes.Equal(want, got) { 63 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 64 | } 65 | }) 66 | } 67 | } 68 | 69 | func TestUnmarshalHeader(t *testing.T) { 70 | 71 | var tests = []struct { 72 | desc string 73 | h *Header 74 | b []byte 75 | err error 76 | }{ 77 | { 78 | desc: "Correct structure", 79 | h: &Header{ 80 | MessageType: PDelayReqMsgType, 81 | MessageLength: 44, 82 | VersionPTP: Version2, 83 | Flags: Flags{ 84 | Security: false, 85 | ProfileSpecific2: false, 86 | ProfileSpecific1: false, 87 | Unicast: false, 88 | TwoSteps: false, 89 | AlternateMaster: false, 90 | FrequencyTraceable: false, 91 | TimeTraceable: false, 92 | UtcReasonable: false, 93 | LI59: false, 94 | LI61: false, 95 | }, 96 | CorrectionNs: 0, 97 | CorrectionSubNs: 0, 98 | ClockIdentity: 0x000af7fffe42a753, 99 | PortNumber: 2, 100 | SequenceID: 55330, 101 | LogMessagePeriod: -4, 102 | }, 103 | b: append([]byte{0x2, 0x2, 0x0, 0x2c, 0x0, 0x0, 0x0, 0x0, 104 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 105 | 0x0, 0x0, 0x0, 0x0, 106 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x5, 0xfc}), 107 | }, 108 | { 109 | desc: "Invalid message type", 110 | b: append([]byte{0x4, 0x2, 0x0, 0x2c, 0x0, 0x0, 0x0, 0x0, 111 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 112 | 0x0, 0x0, 0x0, 0x0, 113 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x5, 0xfc}), 114 | err: ErrInvalidMsgType, 115 | }, 116 | { 117 | desc: "Unsupported protocol version", 118 | b: append([]byte{0x2, 0x1, 0x0, 0x2c, 0x0, 0x0, 0x0, 0x0, 119 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 120 | 0x0, 0x0, 0x0, 0x0, 121 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x5, 0xfc}), 122 | err: ErrUnsupportedVersion, 123 | }, 124 | { 125 | desc: "Invalid length", 126 | b: append([]byte{0x2, 0x1, 0x0, 0x2c, 0x0, 0x0, 0x0, 0x0, 127 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 128 | 0x0, 0x0, 0x0, 0x0, 129 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x5, 0xfc, 0x0}), 130 | err: io.ErrUnexpectedEOF, 131 | }, 132 | } 133 | 134 | for _, tt := range tests { 135 | t.Run(tt.desc, func(t *testing.T) { 136 | h := new(Header) 137 | err := h.UnmarshalBinary(tt.b) 138 | if err != nil { 139 | if want, got := tt.err, err; want != got { 140 | t.Fatalf("unexpected error: %v != %v", want, got) 141 | } 142 | 143 | return 144 | } 145 | 146 | if want, got := tt.h, h; !reflect.DeepEqual(want, got) { 147 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 148 | } 149 | }) 150 | } 151 | } 152 | 153 | func BenchmarkMarshalHeader(b *testing.B) { 154 | h := Header{ 155 | MessageType: PDelayReqMsgType, 156 | MessageLength: 44, 157 | VersionPTP: Version2, 158 | Flags: Flags{ 159 | Security: false, 160 | ProfileSpecific2: false, 161 | ProfileSpecific1: false, 162 | Unicast: false, 163 | TwoSteps: false, 164 | AlternateMaster: false, 165 | FrequencyTraceable: false, 166 | TimeTraceable: false, 167 | UtcReasonable: false, 168 | LI59: false, 169 | LI61: false, 170 | }, 171 | CorrectionNs: 0, 172 | CorrectionSubNs: 0, 173 | ClockIdentity: 0x000af7fffe42a753, 174 | PortNumber: 2, 175 | SequenceID: 55330, 176 | LogMessagePeriod: -4, 177 | } 178 | for i := 0; i < b.N; i++ { 179 | h.MarshalBinary() 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /mgmtmsg.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | // ActionFieldType... 4 | type ActionFiledType uint8 5 | 6 | // ActionFieldType types codes 7 | const ( 8 | Get ActionFiledType = 0 9 | Set ActionFiledType = 1 10 | Response ActionFiledType = 2 11 | Command ActionFiledType = 3 12 | Acknowledge ActionFiledType = 4 13 | ) 14 | 15 | // MgmtMsg ... 16 | type MgmtMsg struct { 17 | Header 18 | ClockIdentity uint64 19 | PortNumber uint16 20 | StartingBoundaryHops uint8 21 | BoundaryHops uint8 22 | actionField ActionFiledType 23 | ManagementTlv 24 | } 25 | 26 | // MarshalBinary allocates a byte slice and marshals a Frame into binary form. 27 | func (t MgmtMsg) MarshalBinary() ([]byte, error) { 28 | return []byte{}, nil 29 | } 30 | 31 | // UnmarshalBinary unmarshals a byte slice into a Frame. 32 | func (t *MgmtMsg) UnmarshalBinary(b []byte) error { 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /pdelreqmsg.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | // PDelReqMsg ... 8 | type PDelReqMsg struct { 9 | Header 10 | } 11 | 12 | // MarshalBinary allocates a byte slice and marshals a Frame into binary form. 13 | func (t *PDelReqMsg) MarshalBinary() ([]byte, error) { 14 | 15 | if t.Header.MessageType != PDelayReqMsgType { 16 | return nil, ErrInvalidMsgType 17 | } 18 | 19 | if t.Header.MessageLength == 0 { 20 | t.Header.MessageLength = HeaderLen + PDelayReqPayloadLen 21 | } 22 | 23 | b := make([]byte, HeaderLen+PDelayReqPayloadLen) 24 | 25 | headerSlice, err := t.Header.MarshalBinary() 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | copy(b[:HeaderLen], headerSlice) 31 | 32 | // All the rest 20 bytes are reserved. Keep them zero values. 33 | 34 | return b, nil 35 | } 36 | 37 | // UnmarshalBinary unmarshals a byte slice into a PDelReqMsg. 38 | // 39 | // If the byte slice does not contain enough data to unmarshal a valid PDelReqMsg, 40 | // io.ErrUnexpectedEOF is returned. 41 | func (t *PDelReqMsg) UnmarshalBinary(b []byte) error { 42 | 43 | if len(b) != HeaderLen+PDelayReqPayloadLen { 44 | return io.ErrUnexpectedEOF 45 | } 46 | 47 | err := t.Header.UnmarshalBinary(b[:HeaderLen]) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | if t.Header.MessageType != PDelayReqMsgType { 53 | return ErrInvalidMsgType 54 | } 55 | 56 | // All the rest 20 bytes are reserved. Keep them zero values. 57 | 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /pdelreqmsg_test.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestMarshalPDelReq(t *testing.T) { 11 | 12 | var tests = []struct { 13 | desc string 14 | m *PDelReqMsg 15 | b []byte 16 | err error 17 | }{ 18 | { 19 | desc: "Correct structure", 20 | m: &PDelReqMsg{ 21 | Header: Header{ 22 | MessageType: PDelayReqMsgType, 23 | CorrectionNs: 0, 24 | CorrectionSubNs: 0, 25 | Flags: Flags{ 26 | Security: false, 27 | ProfileSpecific2: false, 28 | ProfileSpecific1: true, 29 | Unicast: false, 30 | TwoSteps: false, 31 | AlternateMaster: false, 32 | FrequencyTraceable: false, 33 | TimeTraceable: false, 34 | UtcReasonable: false, 35 | LI59: false, 36 | LI61: false, 37 | }, 38 | ClockIdentity: 0x000af7fffe42a753, 39 | PortNumber: 2, 40 | SequenceID: 55330, 41 | LogMessagePeriod: -4, 42 | }, 43 | }, 44 | b: append([]byte{0x2, 0x2, 0x0, 0x36, 0x0, 0x0, 0x20, 0x0, 45 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 46 | 0x0, 0x0, 0x0, 0x0, 47 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x5, 0xfc, 48 | // Reserved 20 bytes 49 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 50 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}), 51 | }, 52 | { 53 | desc: "Invalid message type", 54 | m: &PDelReqMsg{ 55 | Header: Header{ 56 | MessageType: SyncMsgType, 57 | CorrectionNs: 0, 58 | CorrectionSubNs: 0, 59 | ClockIdentity: 0x000af7fffe42a753, 60 | PortNumber: 2, 61 | SequenceID: 55330, 62 | LogMessagePeriod: -4, 63 | }, 64 | }, 65 | err: ErrInvalidMsgType, 66 | }, 67 | } 68 | 69 | for _, tt := range tests { 70 | t.Run(tt.desc, func(t *testing.T) { 71 | b, err := tt.m.MarshalBinary() 72 | if err != nil { 73 | if want, got := tt.err, err; want != got { 74 | t.Fatalf("unexpected error: %v != %v", want, got) 75 | } 76 | 77 | return 78 | } 79 | 80 | if want, got := tt.b, b; !bytes.Equal(want, got) { 81 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 82 | } 83 | }) 84 | } 85 | } 86 | 87 | func TestUnmarshalPDelReq(t *testing.T) { 88 | 89 | var tests = []struct { 90 | desc string 91 | m PDelReqMsg 92 | b []byte 93 | err error 94 | }{ 95 | { 96 | desc: "Correct structure", 97 | m: PDelReqMsg{ 98 | Header: Header{ 99 | MessageType: PDelayReqMsgType, 100 | MessageLength: HeaderLen + PDelayReqPayloadLen, 101 | VersionPTP: Version2, 102 | Flags: Flags{ 103 | Security: false, 104 | ProfileSpecific2: false, 105 | ProfileSpecific1: false, 106 | Unicast: false, 107 | TwoSteps: false, 108 | AlternateMaster: false, 109 | FrequencyTraceable: false, 110 | TimeTraceable: false, 111 | UtcReasonable: false, 112 | LI59: false, 113 | LI61: false, 114 | }, 115 | CorrectionNs: 0, 116 | CorrectionSubNs: 0, 117 | ClockIdentity: 0x000af7fffe42a753, 118 | PortNumber: 2, 119 | SequenceID: 55330, 120 | LogMessagePeriod: -4, 121 | }, 122 | }, 123 | b: append([]byte{0x2, 0x2, 0x0, 0x36, 0x0, 0x0, 0x0, 0x0, 124 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 125 | 0x0, 0x0, 0x0, 0x0, 126 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x5, 0xfc, 127 | // Reserved 20 bytes 128 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 129 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}), 130 | }, 131 | { 132 | desc: "Invalid length", 133 | b: append([]byte{0x2, 0x2, 0x0, 0x36, 0x0, 0x0, 0x0, 0x0, 134 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 135 | 0x0, 0x0, 0x0, 0x0, 136 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x5, 0xfc, 137 | // Reserved 20 bytes 138 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 139 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}), 140 | err: io.ErrUnexpectedEOF, 141 | }, 142 | { 143 | desc: "Invalid message type", 144 | b: append([]byte{0x1, 0x2, 0x0, 0x36, 0x0, 0x0, 0x0, 0x0, 145 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 146 | 0x0, 0x0, 0x0, 0x0, 147 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x5, 0xfc, 148 | // Reserved 20 bytes 149 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 150 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}), 151 | err: ErrInvalidMsgType, 152 | }, 153 | } 154 | 155 | for _, tt := range tests { 156 | t.Run(tt.desc, func(t *testing.T) { 157 | var m PDelReqMsg 158 | err := m.UnmarshalBinary(tt.b) 159 | if err != nil { 160 | if want, got := tt.err, err; want != got { 161 | t.Fatalf("unexpected error: %v != %v", want, got) 162 | } 163 | 164 | return 165 | } 166 | if want, got := tt.m, m; !reflect.DeepEqual(want, got) { 167 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 168 | } 169 | }) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /pdelrespfollowupmsg.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "time" 7 | ) 8 | 9 | // PDelRespFollowUpMsg ... 10 | type PDelRespFollowUpMsg struct { 11 | Header 12 | OriginTimestamp time.Time 13 | ClockIdentity uint64 14 | PortNumber uint16 15 | } 16 | 17 | // MarshalBinary allocates a byte slice and marshals a Frame into binary form. 18 | func (t *PDelRespFollowUpMsg) MarshalBinary() ([]byte, error) { 19 | 20 | if t.Header.MessageType != PDelayRespFollowUpMsgType { 21 | return nil, ErrInvalidMsgType 22 | } 23 | 24 | b := make([]byte, HeaderLen+PDelayRespFollowUpPayloadLen) 25 | 26 | headerSlice, err := t.Header.MarshalBinary() 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | copy(b[:HeaderLen], headerSlice) 32 | offset := HeaderLen 33 | 34 | // Origin timestamp 35 | time2OriginTimestamp(t.OriginTimestamp, b[offset:offset+OriginTimestampFullLen]) 36 | offset += OriginTimestampFullLen 37 | 38 | binary.BigEndian.PutUint64(b[offset:offset+ClockIdentityLen], t.ClockIdentity) 39 | offset += ClockIdentityLen 40 | 41 | binary.BigEndian.PutUint16(b[offset:offset+SourcePortNumberLen], t.PortNumber) 42 | 43 | return b, nil 44 | } 45 | 46 | // UnmarshalBinary unmarshals a byte slice into a PDelRespFollowUpMsg. 47 | // 48 | // If the byte slice does not contain enough data to unmarshal a valid PDelRespFollowUpMsg, 49 | // io.ErrUnexpectedEOF is returned. 50 | func (t *PDelRespFollowUpMsg) UnmarshalBinary(b []byte) error { 51 | 52 | if len(b) != HeaderLen+PDelayRespFollowUpPayloadLen { 53 | return io.ErrUnexpectedEOF 54 | } 55 | 56 | err := t.Header.UnmarshalBinary(b[:HeaderLen]) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | if t.Header.MessageType != PDelayRespFollowUpMsgType { 62 | return ErrInvalidMsgType 63 | } 64 | 65 | if t.OriginTimestamp, err = originTimestamp2Time(b[HeaderLen : HeaderLen+OriginTimestampFullLen]); err != nil { 66 | return err 67 | } 68 | offset := HeaderLen + OriginTimestampFullLen 69 | 70 | t.ClockIdentity = binary.BigEndian.Uint64(b[offset : offset+ClockIdentityLen]) 71 | offset += ClockIdentityLen 72 | 73 | t.PortNumber = binary.BigEndian.Uint16(b[offset : offset+SourcePortNumberLen]) 74 | 75 | return nil 76 | } 77 | -------------------------------------------------------------------------------- /pdelrespfollowupmsg_test.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import "testing" 4 | 5 | func TestMarshalPDelRespFollowUp(t *testing.T) { 6 | } 7 | 8 | func TestUnmarshalPDelRespFollowUp(t *testing.T) { 9 | } 10 | -------------------------------------------------------------------------------- /pdelrespmsg.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "time" 7 | ) 8 | 9 | // PDelRespMsg ... 10 | type PDelRespMsg struct { 11 | Header 12 | ReceiveTimestamp time.Time 13 | ClockIdentity uint64 14 | PortNumber uint16 15 | } 16 | 17 | // MarshalBinary allocates a byte slice and marshals a Frame into binary form. 18 | func (t *PDelRespMsg) MarshalBinary() ([]byte, error) { 19 | 20 | if t.Header.MessageType != PDelayRespMsgType { 21 | return nil, ErrInvalidMsgType 22 | } 23 | 24 | if t.Header.MessageLength == 0 { 25 | t.Header.MessageLength = HeaderLen + PDelayRespPayloadLen 26 | } 27 | 28 | b := make([]byte, HeaderLen+PDelayRespPayloadLen) 29 | 30 | headerSlice, err := t.Header.MarshalBinary() 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | copy(b[:HeaderLen], headerSlice) 36 | offset := HeaderLen 37 | 38 | time2OriginTimestamp(t.ReceiveTimestamp, b[offset:offset+OriginTimestampFullLen]) 39 | offset += OriginTimestampFullLen 40 | 41 | binary.BigEndian.PutUint64(b[offset:offset+ClockIdentityLen], t.ClockIdentity) 42 | offset += ClockIdentityLen 43 | 44 | binary.BigEndian.PutUint16(b[offset:offset+2], t.PortNumber) 45 | 46 | return b, nil 47 | } 48 | 49 | // UnmarshalBinary unmarshals a byte slice into a PDelRespMsg. 50 | // 51 | // If the byte slice does not contain enough data to unmarshal a valid PDelRespMsg, 52 | // io.ErrUnexpectedEOF is returned. 53 | func (t *PDelRespMsg) UnmarshalBinary(b []byte) error { 54 | 55 | if len(b) != HeaderLen+PDelayRespPayloadLen { 56 | return io.ErrUnexpectedEOF 57 | } 58 | 59 | err := t.Header.UnmarshalBinary(b[:HeaderLen]) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | if t.Header.MessageType != PDelayRespMsgType { 65 | return ErrInvalidMsgType 66 | } 67 | 68 | if t.ReceiveTimestamp, err = originTimestamp2Time(b[HeaderLen : HeaderLen+OriginTimestampFullLen]); err != nil { 69 | return err 70 | } 71 | offset := HeaderLen + OriginTimestampFullLen 72 | 73 | t.ClockIdentity = binary.BigEndian.Uint64(b[offset : offset+ClockIdentityLen]) 74 | offset += ClockIdentityLen 75 | 76 | t.PortNumber = binary.BigEndian.Uint16(b[offset : offset+SourcePortNumberLen]) 77 | 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /pdelrespmsg_test.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "reflect" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestMarshalPDelResp(t *testing.T) { 12 | 13 | var tests = []struct { 14 | desc string 15 | m *PDelRespMsg 16 | b []byte 17 | err error 18 | }{ 19 | { 20 | desc: "Correct structure", 21 | m: &PDelRespMsg{ 22 | Header: Header{ 23 | MessageType: PDelayRespMsgType, 24 | CorrectionNs: 0, 25 | CorrectionSubNs: 0, 26 | Flags: Flags{ 27 | TwoSteps: true, 28 | }, 29 | ClockIdentity: 0x0023aefffe5d688b, 30 | PortNumber: 1, 31 | SequenceID: 6365, 32 | LogMessagePeriod: 127, 33 | }, 34 | ReceiveTimestamp: time.Unix(1312261115, 89388000), 35 | ClockIdentity: 0x000c29fffe08e6e8, 36 | PortNumber: 1, 37 | }, 38 | b: append([]byte{0x3, 0x2, 0x0, 0x36, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 39 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0xae, 0xff, 0xfe, 0x5d, 0x68, 0x8b, 0x00, 0x01, 0x18, 0xdd, 40 | 0x05, 0x7f, 0x00, 0x00, 0x4e, 0x37, 0x83, 0xfb, 0x05, 0x53, 0xf3, 0xe0, 0x00, 0x0c, 0x29, 0xff, 41 | 0xfe, 0x08, 0xe6, 0xe8, 0x00, 0x01}), 42 | }, 43 | { 44 | desc: "Invalid message type", 45 | m: &PDelRespMsg{ 46 | Header: Header{ 47 | MessageType: SyncMsgType, 48 | CorrectionNs: 0, 49 | CorrectionSubNs: 0, 50 | ClockIdentity: 0x000af7fffe42a753, 51 | PortNumber: 2, 52 | SequenceID: 55330, 53 | LogMessagePeriod: -4, 54 | }, 55 | }, 56 | err: ErrInvalidMsgType, 57 | }, 58 | } 59 | 60 | for _, tt := range tests { 61 | t.Run(tt.desc, func(t *testing.T) { 62 | b, err := tt.m.MarshalBinary() 63 | if err != nil { 64 | if want, got := tt.err, err; want != got { 65 | t.Fatalf("unexpected error: %v != %v", want, got) 66 | } 67 | 68 | return 69 | } 70 | 71 | if want, got := tt.b, b; !bytes.Equal(want, got) { 72 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 73 | } 74 | }) 75 | } 76 | } 77 | 78 | func TestUnmarshalPDelResp(t *testing.T) { 79 | 80 | var tests = []struct { 81 | desc string 82 | m PDelRespMsg 83 | b []byte 84 | err error 85 | }{ 86 | { 87 | desc: "Correct structure", 88 | m: PDelRespMsg{ 89 | Header: Header{ 90 | MessageType: PDelayRespMsgType, 91 | MessageLength: HeaderLen + PDelayReqPayloadLen, 92 | VersionPTP: Version2, 93 | CorrectionNs: 0, 94 | CorrectionSubNs: 0, 95 | Flags: Flags{ 96 | TwoSteps: true, 97 | }, 98 | ClockIdentity: 0x0023aefffe5d688b, 99 | PortNumber: 1, 100 | SequenceID: 6365, 101 | LogMessagePeriod: 127, 102 | }, 103 | ReceiveTimestamp: time.Unix(1312261115, 89388000), 104 | ClockIdentity: 0x000c29fffe08e6e8, 105 | PortNumber: 1, 106 | }, 107 | b: append([]byte{0x3, 0x2, 0x0, 0x36, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 108 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0xae, 0xff, 0xfe, 0x5d, 0x68, 0x8b, 0x00, 0x01, 0x18, 0xdd, 109 | 0x05, 0x7f, 0x00, 0x00, 0x4e, 0x37, 0x83, 0xfb, 0x05, 0x53, 0xf3, 0xe0, 0x00, 0x0c, 0x29, 0xff, 110 | 0xfe, 0x08, 0xe6, 0xe8, 0x00, 0x01}), 111 | }, 112 | { 113 | desc: "Invalid length", 114 | b: append([]byte{0x3, 0x2, 0x0, 0x36, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 115 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0xae, 0xff, 0xfe, 0x5d, 0x68, 0x8b, 0x00, 0x01, 0x18, 0xdd, 116 | 0x05, 0x7f, 0x00, 0x00, 0x4e, 0x37, 0x83, 0xfb, 0x05, 0x53, 0xf3, 0xe0, 0x00, 0x0c, 0x29, 0xff, 117 | 0xfe, 0x08, 0xe6, 0xe8, 0x00, 0x01, 0x0}), 118 | err: io.ErrUnexpectedEOF, 119 | }, 120 | { 121 | desc: "Invalid message type", 122 | b: append([]byte{0x1, 0x2, 0x0, 0x36, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 123 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0xae, 0xff, 0xfe, 0x5d, 0x68, 0x8b, 0x00, 0x01, 0x18, 0xdd, 124 | 0x05, 0x7f, 0x00, 0x00, 0x4e, 0x37, 0x83, 0xfb, 0x05, 0x53, 0xf3, 0xe0, 0x00, 0x0c, 0x29, 0xff, 125 | 0xfe, 0x08, 0xe6, 0xe8, 0x00, 0x01}), 126 | err: ErrInvalidMsgType, 127 | }, 128 | } 129 | 130 | for _, tt := range tests { 131 | t.Run(tt.desc, func(t *testing.T) { 132 | var m PDelRespMsg 133 | err := m.UnmarshalBinary(tt.b) 134 | if err != nil { 135 | if want, got := tt.err, err; want != got { 136 | t.Fatalf("unexpected error: %v != %v", want, got) 137 | } 138 | 139 | return 140 | } 141 | if want, got := tt.m, m; !reflect.DeepEqual(want, got) { 142 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 143 | } 144 | }) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /ptp.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "io" 7 | "time" 8 | ) 9 | 10 | const ( 11 | PtpEtherType uint16 = 0x88f7 12 | AvtpEtherType uint16 = 0x22f0 13 | ) 14 | 15 | const ( 16 | GptpMulticast string = "01:23:45:67:89:ab" 17 | PDelayMulticast string = GptpMulticast 18 | OtherMulticast string = GptpMulticast 19 | TestStatusMulticast string = GptpMulticast 20 | ) 21 | 22 | // Errors 23 | var ( 24 | ErrInvalidFrame = errors.New("Invalid frame") 25 | ErrInvalidHeader = errors.New("Invalid header") 26 | ErrInvalidMsgType = errors.New("Invalid message type") 27 | ErrUnsupportedVersion = errors.New("Unsupported protocol version") 28 | ErrInvalidClockClass = errors.New("Invalid clock class") 29 | ErrInvalidClockAccuracy = errors.New("Invalid clock accuracy") 30 | ErrInvalidTimeSource = errors.New("Invalid time source") 31 | ErrInvalidTlvType = errors.New("Invalid TLV type") 32 | ErrInvalidTlvOrgId = errors.New("Invalid TLV organizationId") 33 | ErrInvalidTlvOrgSubType = errors.New("Invalid organization sub type") 34 | ) 35 | 36 | // MsgType Type 37 | type MsgType uint8 38 | 39 | // Message types codes 40 | const ( 41 | SyncMsgType MsgType = 0x0 42 | DelayReqMsgType MsgType = 0x1 43 | PDelayReqMsgType MsgType = 0x2 44 | PDelayRespMsgType MsgType = 0x3 45 | FollowUpMsgType MsgType = 0x8 46 | DelayRespMsgType MsgType = 0x9 47 | PDelayRespFollowUpMsgType MsgType = 0xA 48 | AnnounceMsgType MsgType = 0xB 49 | SignalingMsgType MsgType = 0xC 50 | MgmtMsgType MsgType = 0xD 51 | ) 52 | 53 | // MsgCtrlType Control Type 54 | type MsgCtrlType uint8 55 | 56 | // Message control codes 57 | const ( 58 | SyncMsgCtrlType MsgCtrlType = 0 59 | DelayReqMsgCtrlType MsgCtrlType = 1 60 | FollowUpMsgCtrlType MsgCtrlType = 2 61 | DelayRespMsgCtrlType MsgCtrlType = 3 62 | OtherMsgCtrlType MsgCtrlType = 5 63 | ) 64 | 65 | // MulticastType 66 | type MulticastType uint8 67 | 68 | const ( 69 | McastNone MulticastType = iota 70 | McastPdelay 71 | McastTestStatus 72 | McastOther 73 | ) 74 | 75 | // PortSate 76 | type PortState uint8 77 | 78 | const ( 79 | Initializing PortState = iota 80 | Faulty 81 | Disabled 82 | Listening 83 | PreMaster 84 | Master 85 | Passive 86 | Uncalibrated 87 | Slave 88 | ) 89 | 90 | // DelayMechanismType... 91 | type DelayMechanismType uint8 92 | 93 | const ( 94 | E2E DelayMechanismType = 0x1 95 | P2P DelayMechanismType = 0x2 96 | DISABLED DelayMechanismType = 0xfe 97 | ) 98 | 99 | // Length in octets of main fields 100 | const ( 101 | MessageLengthLen = 2 102 | FlagsLen = 2 103 | CorrectionNanoSecLen = 6 104 | CorrectionSubNanoSecLen = 2 105 | CorrectionFullLen = CorrectionNanoSecLen + CorrectionSubNanoSecLen 106 | ClockIdentityLen = 8 107 | SourcePortNumberLen = 2 108 | PortIdentityLen = ClockIdentityLen + SourcePortNumberLen 109 | SequenceIDLen = 2 110 | OriginTimestampSecLen = 6 111 | OriginTimestampNanoSecLen = 4 112 | OriginTimestampFullLen = OriginTimestampSecLen + OriginTimestampNanoSecLen 113 | CurrentUtcOffsetLen = 2 114 | ClockQualityLen = 4 115 | GrandMasterIdentityLen = 8 116 | StepsRemovedLen = 2 117 | Reserved4 = 4 118 | Reserved10 = 10 119 | ) 120 | 121 | // Length in octets of header and payloads 122 | const ( 123 | HeaderLen = 34 124 | SyncPayloadLen = OriginTimestampFullLen 125 | DelayReqPayloadLen = OriginTimestampFullLen 126 | FollowUpPayloadLen = OriginTimestampFullLen 127 | DelayRespPayloadLen = OriginTimestampFullLen + PortIdentityLen 128 | PDelayReqPayloadLen = Reserved10 + Reserved10 129 | PDelayRespPayloadLen = OriginTimestampFullLen + PortIdentityLen 130 | PDelayRespFollowUpPayloadLen = OriginTimestampFullLen + PortIdentityLen 131 | AnnouncePayloadLen = 30 132 | SignalingPayloadLen = 10 133 | ClockQualityPayloadLen = 4 134 | // SignalingPayloadLen depends on TLVs 135 | ) 136 | 137 | // TimeSourceType Type 138 | type TimeSourceType uint8 139 | 140 | // TimeSource types codes 141 | const ( 142 | TimeSourceAtomic TimeSourceType = 16 143 | TimeSourceGPS TimeSourceType = 32 144 | TimeSourceTRadio TimeSourceType = 48 145 | TimeSourcePTP TimeSourceType = 64 146 | TimeSourceNTP TimeSourceType = 80 147 | TimeSourceHandSet TimeSourceType = 96 148 | TimeSourceOther TimeSourceType = 144 149 | TimeSourceInternalOsc TimeSourceType = 160 150 | ) 151 | 152 | func isValidTimeSource(t TimeSourceType) bool { 153 | switch t { 154 | case 155 | TimeSourceAtomic, 156 | TimeSourceGPS, 157 | TimeSourceTRadio, 158 | TimeSourcePTP, 159 | TimeSourceNTP, 160 | TimeSourceHandSet, 161 | TimeSourceOther, 162 | TimeSourceInternalOsc: 163 | return true 164 | } 165 | return false 166 | } 167 | 168 | // time2OriginTimestamp converts time.Time into bytes slice 6+4(sec+nanosec) 169 | // accordingly with ptp timestamp format. 170 | func time2OriginTimestamp(t time.Time, b []byte) error { 171 | 172 | if len(b) != OriginTimestampFullLen { 173 | return io.ErrUnexpectedEOF 174 | } 175 | 176 | sec := t.Unix() 177 | nanosec := t.UnixNano() 178 | 179 | secHexSlice := make([]byte, 8) 180 | 181 | binary.BigEndian.PutUint64(secHexSlice, uint64(sec)) 182 | binary.BigEndian.PutUint32(b[6:], uint32(nanosec-sec*1000000000)) 183 | 184 | copy(b[:6], secHexSlice[2:]) 185 | 186 | return nil 187 | } 188 | 189 | // originTimestamp2Time converts 6+4(sec+nanosec) bytes slice into Time 190 | func originTimestamp2Time(b []byte) (time.Time, error) { 191 | 192 | if len(b) != OriginTimestampSecLen+OriginTimestampNanoSecLen { 193 | return time.Now(), io.ErrUnexpectedEOF 194 | } 195 | 196 | sec := binary.BigEndian.Uint64(append([]byte{0, 0}, b[:6]...)) 197 | 198 | nsecSlice := append([]byte{}, b[6:10]...) 199 | nsecSlice = append(nsecSlice, []byte{0, 0, 0, 0}...) 200 | nsec := binary.BigEndian.Uint32(nsecSlice) 201 | 202 | return time.Unix(int64(sec), int64(nsec)), nil 203 | } 204 | 205 | const UScaledNsLen = 12 206 | 207 | type UScaledNs struct { 208 | ms int32 209 | ls uint64 210 | } 211 | 212 | func NewUScaledNs(b []byte) (UScaledNs, error) { 213 | if len(b) != UScaledNsLen { 214 | return UScaledNs{}, io.ErrUnexpectedEOF 215 | } 216 | 217 | return UScaledNs{ 218 | ms: int32(binary.BigEndian.Uint32(b[:4])), 219 | ls: binary.BigEndian.Uint64(b[4:]), 220 | }, nil 221 | } 222 | 223 | // MarshalBinary allocates a byte slice and marshals a Frame into binary form. 224 | func (p *UScaledNs) MarshalBinary() ([]byte, error) { 225 | b := make([]byte, UScaledNsLen) 226 | 227 | binary.BigEndian.PutUint32(b[:4], uint32(p.ms)) 228 | 229 | binary.BigEndian.PutUint64(b[4:], p.ls) 230 | 231 | return b, nil 232 | } 233 | 234 | // UnmarshalBinary unmarshals a byte slice into a UScaledNs. 235 | // 236 | // If the byte slice does not contain enough data to unmarshal a valid UScaledNs, 237 | // io.ErrUnexpectedEOF is returned. 238 | func (p *UScaledNs) UnmarshalBinary(b []byte) error { 239 | if len(b) != UScaledNsLen { 240 | return io.ErrUnexpectedEOF 241 | } 242 | 243 | p.ms = int32(binary.BigEndian.Uint32(b[:4])) 244 | p.ls = binary.BigEndian.Uint64(b[4:]) 245 | 246 | return nil 247 | } 248 | 249 | // GetClockIdByMac takes MAC address as a slice and converts it 250 | // into slice of bytes(EUI-64) in accordance with IEEE 1588v2 spec. 251 | func GetClockIdByMac(b []byte) ([]byte, error) { 252 | if len(b) != 6 { 253 | return []byte{}, io.ErrUnexpectedEOF 254 | } 255 | 256 | res := make([]byte, ClockIdentityLen) 257 | 258 | copy(res[:3], b[:3]) 259 | b[3], b[4] = 0xff, 0xfe 260 | copy(res[5:], b[3:]) 261 | return res, nil 262 | } 263 | -------------------------------------------------------------------------------- /ptp_test.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestTime2OriginTimestamp(t *testing.T) { 11 | 12 | var tests = []struct { 13 | desc string 14 | t time.Time 15 | b []byte 16 | }{ 17 | { 18 | desc: "Correct not null timestamp", 19 | t: time.Unix(1169232201, 775045731), 20 | b: append([]byte{0x0, 0x0, 0x45, 0xb1, 0x11, 0x49, 0x2e, 0x32, 0x42, 0x63}), 21 | }, 22 | { 23 | desc: "Null timestamp", 24 | t: time.Unix(0, 0), 25 | b: append([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}), 26 | }, 27 | } 28 | 29 | for _, tt := range tests { 30 | t.Run(tt.desc, func(t *testing.T) { 31 | b := make([]byte, 10) 32 | time2OriginTimestamp(tt.t, b) 33 | 34 | if want, got := tt.b, b; !bytes.Equal(want, got) { 35 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 36 | } 37 | }) 38 | } 39 | } 40 | 41 | func TestOriginTimestamp2Time(t *testing.T) { 42 | 43 | var tests = []struct { 44 | desc string 45 | t time.Time 46 | b []byte 47 | err error 48 | }{ 49 | { 50 | desc: "Correct not null timestamp", 51 | t: time.Unix(1169232201, 775045731), 52 | b: append([]byte{0x0, 0x0, 0x45, 0xb1, 0x11, 0x49, 0x2e, 0x32, 0x42, 0x63}), 53 | }, 54 | { 55 | desc: "Null timestamp", 56 | t: time.Unix(0, 0), 57 | b: append([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}), 58 | }, 59 | } 60 | 61 | for _, tt := range tests { 62 | t.Run(tt.desc, func(t *testing.T) { 63 | tmp, err := originTimestamp2Time(tt.b) 64 | if err != nil { 65 | if want, got := tt.err, err; want != got { 66 | t.Fatalf("unexpected error: %v != %v", want, got) 67 | } 68 | 69 | return 70 | } 71 | 72 | if want, got := tt.t, tmp; !reflect.DeepEqual(want, got) { 73 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 74 | } 75 | }) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /signalmsg.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | ) 7 | 8 | // SignalingMsg ... 9 | type SignalingMsg struct { 10 | Header 11 | ClockIdentity uint64 12 | PortNumber uint16 13 | IntervalRequestTlv 14 | } 15 | 16 | // MarshalBinary allocates a byte slice and marshals a Frame into binary form. 17 | func (t *SignalingMsg) MarshalBinary() ([]byte, error) { 18 | 19 | if t.Header.MessageType != SignalingMsgType { 20 | return nil, ErrInvalidMsgType 21 | } 22 | 23 | if t.Header.MessageLength == 0 { 24 | t.Header.MessageLength = HeaderLen + SignalingPayloadLen + IntervalRequestTlvLen 25 | } 26 | 27 | headerSlice, err := t.Header.MarshalBinary() 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | tlvSlice, err := t.IntervalRequestTlv.MarshalBinary() 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | b := make([]byte, HeaderLen+SignalingPayloadLen+IntervalRequestTlvLen+4) 38 | 39 | copy(b[:HeaderLen], headerSlice) 40 | offset := HeaderLen 41 | 42 | binary.BigEndian.PutUint64(b[offset:offset+ClockIdentityLen], t.ClockIdentity) 43 | offset += ClockIdentityLen 44 | 45 | binary.BigEndian.PutUint16(b[offset:offset+SourcePortNumberLen], t.PortNumber) 46 | offset += SourcePortNumberLen 47 | 48 | copy(b[offset:offset+IntervalRequestTlvLen+4], tlvSlice) 49 | 50 | return b, nil 51 | } 52 | 53 | // UnmarshalBinary unmarshals a byte slice into a SignalingMsg. 54 | // 55 | // If the byte slice does not contain enough data to unmarshal a valid SignalingMsg, 56 | // io.ErrUnexpectedEOF is returned. 57 | func (t *SignalingMsg) UnmarshalBinary(b []byte) error { 58 | if len(b) != HeaderLen+SignalingPayloadLen+IntervalRequestTlvLen+4 { 59 | return io.ErrUnexpectedEOF 60 | } 61 | err := t.Header.UnmarshalBinary(b[:HeaderLen]) 62 | if err != nil { 63 | return err 64 | } 65 | 66 | if t.Header.MessageType != SignalingMsgType { 67 | return ErrInvalidMsgType 68 | } 69 | 70 | offset := HeaderLen 71 | 72 | t.ClockIdentity = binary.BigEndian.Uint64(b[offset : offset+ClockIdentityLen]) 73 | offset += ClockIdentityLen 74 | t.PortNumber = binary.BigEndian.Uint16(b[offset : offset+SourcePortNumberLen]) 75 | offset += SourcePortNumberLen 76 | 77 | if err = t.IntervalRequestTlv.UnmarshalBinary(b[offset:]); err != nil { 78 | return err 79 | } 80 | 81 | return nil 82 | } 83 | -------------------------------------------------------------------------------- /signalmsg_test.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestMarshalSignaling(t *testing.T) { 11 | var tests = []struct { 12 | desc string 13 | m *SignalingMsg 14 | b []byte 15 | err error 16 | }{ 17 | { 18 | desc: "Correct structure", 19 | m: &SignalingMsg{ 20 | Header: Header{ 21 | MessageType: SignalingMsgType, 22 | VersionPTP: Version2, 23 | CorrectionNs: 0, 24 | CorrectionSubNs: 0, 25 | ClockIdentity: 0x001d7ffffe80024a, 26 | PortNumber: 1, 27 | SequenceID: 27278, 28 | LogMessagePeriod: 127, 29 | }, 30 | ClockIdentity: 0x78baf9fffe0a435e, 31 | PortNumber: 1, 32 | IntervalRequestTlv: IntervalRequestTlv{ 33 | LinkDelayInterval: 1, 34 | TimeSyncInterval: 2, 35 | AnnounceInterval: 127, 36 | ComputeNeighborRateRatio: true, 37 | ComputeNeighborPropDelay: false, 38 | }, 39 | }, 40 | b: append([]byte{ 41 | 0x0c, 0x02, 0x0, 0x38, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 42 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 43 | 0x7f, 0xff, 0xfe, 0x80, 0x02, 0x4a, 0x00, 0x01, 0x6a, 0x8e, 0x05, 0x7f, 0x78, 0xba, 0xf9, 0xff, 44 | 0xfe, 0x0a, 0x43, 0x5e, 0x00, 0x01, 0x00, 0x03, 0x00, 0x0c, 0x0, 0x80, 0xc2, 0x0, 0x0, 0x2, 0x1, 0x2, 0x7f, 45 | 0x2, 0x0, 0x0, 46 | }), 47 | }, 48 | { 49 | desc: "Invalid message type", 50 | m: &SignalingMsg{ 51 | Header: Header{ 52 | MessageType: AnnounceMsgType, 53 | VersionPTP: Version2, 54 | CorrectionNs: 0, 55 | CorrectionSubNs: 0, 56 | ClockIdentity: 0x001d7ffffe80024a, 57 | PortNumber: 1, 58 | SequenceID: 27278, 59 | LogMessagePeriod: 127, 60 | }, 61 | ClockIdentity: 0x78baf9fffe0a435e, 62 | PortNumber: 1, 63 | IntervalRequestTlv: IntervalRequestTlv{ 64 | LinkDelayInterval: 1, 65 | TimeSyncInterval: 2, 66 | AnnounceInterval: 127, 67 | ComputeNeighborRateRatio: true, 68 | ComputeNeighborPropDelay: false, 69 | }, 70 | }, 71 | err: ErrInvalidMsgType, 72 | }, 73 | } 74 | 75 | for _, tt := range tests { 76 | t.Run(tt.desc, func(t *testing.T) { 77 | b, err := tt.m.MarshalBinary() 78 | if err != nil { 79 | if want, got := tt.err, err; want != got { 80 | t.Fatalf("unexpected error: %v != %v", want, got) 81 | } 82 | 83 | return 84 | } 85 | 86 | if want, got := tt.b, b; !bytes.Equal(want, got) { 87 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 88 | } 89 | }) 90 | } 91 | } 92 | 93 | func TestUnmarshalSignaling(t *testing.T) { 94 | var tests = []struct { 95 | desc string 96 | m *SignalingMsg 97 | b []byte 98 | err error 99 | }{ 100 | { 101 | desc: "Correct structure", 102 | m: &SignalingMsg{ 103 | Header: Header{ 104 | MessageType: SignalingMsgType, 105 | MessageLength: HeaderLen + SignalingPayloadLen + IntervalRequestTlvLen + 4, 106 | VersionPTP: Version2, 107 | CorrectionNs: 0, 108 | CorrectionSubNs: 0, 109 | ClockIdentity: 0x001d7ffffe80024a, 110 | PortNumber: 1, 111 | SequenceID: 27278, 112 | LogMessagePeriod: 127, 113 | }, 114 | ClockIdentity: 0x78baf9fffe0a435e, 115 | PortNumber: 1, 116 | IntervalRequestTlv: IntervalRequestTlv{ 117 | LinkDelayInterval: 1, 118 | TimeSyncInterval: 2, 119 | AnnounceInterval: 127, 120 | ComputeNeighborRateRatio: true, 121 | ComputeNeighborPropDelay: false, 122 | }, 123 | }, 124 | b: append([]byte{ 125 | 0x0c, 0x02, 0x0, 0x3c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 126 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 127 | 0x7f, 0xff, 0xfe, 0x80, 0x02, 0x4a, 0x00, 0x01, 0x6a, 0x8e, 0x05, 0x7f, 0x78, 0xba, 0xf9, 0xff, 128 | 0xfe, 0x0a, 0x43, 0x5e, 0x00, 0x01, 0x00, 0x03, 0x00, 0x0c, 0x0, 0x80, 0xc2, 0x0, 0x0, 0x2, 0x1, 0x2, 0x7f, 129 | 0x2, 0x0, 0x0, 130 | }), 131 | }, 132 | { 133 | desc: "Invalid length", 134 | b: append([]byte{0x0c, 0x02, 0x0, 0x3c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 135 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 136 | 0x7f, 0xff, 0xfe, 0x80, 0x02, 0x4a, 0x00, 0x01, 0x6a, 0x8e, 0x05, 0x7f, 0x78, 0xba, 0xf9, 0xff, 137 | 0xfe, 0x0a, 0x43, 0x5e, 0x00, 0x01, 0x00, 0x03, 0x00, 0x0c, 0x0, 0x80, 0xc2, 0x0, 0x0, 0x2, 0x1, 0x2, 0x7f, 138 | 0x2, 0x0, 0x0, 0x0}), 139 | err: io.ErrUnexpectedEOF, 140 | }, 141 | { 142 | desc: "Invalid message type", 143 | b: append([]byte{0x01, 0x02, 0x0, 0x3c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 144 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 145 | 0x7f, 0xff, 0xfe, 0x80, 0x02, 0x4a, 0x00, 0x01, 0x6a, 0x8e, 0x05, 0x7f, 0x78, 0xba, 0xf9, 0xff, 146 | 0xfe, 0x0a, 0x43, 0x5e, 0x00, 0x01, 0x00, 0x03, 0x00, 0x0c, 0x0, 0x80, 0xc2, 0x0, 0x0, 0x2, 0x1, 0x2, 0x7f, 147 | 0x2, 0x0, 0x0}), 148 | err: ErrInvalidMsgType, 149 | }, 150 | } 151 | 152 | for _, tt := range tests { 153 | t.Run(tt.desc, func(t *testing.T) { 154 | m := new(SignalingMsg) 155 | err := m.UnmarshalBinary(tt.b) 156 | if err != nil { 157 | if want, got := tt.err, err; want != got { 158 | t.Fatalf("unexpected error: %v != %v", want, got) 159 | } 160 | 161 | return 162 | } 163 | 164 | if want, got := tt.m, m; !reflect.DeepEqual(want, got) { 165 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 166 | } 167 | }) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /syncmsg.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "io" 5 | "time" 6 | ) 7 | 8 | // SyncMsg ... 9 | type SyncMsg struct { 10 | Header 11 | OriginTimestamp time.Time 12 | } 13 | 14 | // MarshalBinary allocates a byte slice and marshals a Frame into binary form. 15 | func (t *SyncMsg) MarshalBinary() ([]byte, error) { 16 | 17 | if t.Header.MessageType != SyncMsgType { 18 | return nil, ErrInvalidMsgType 19 | } 20 | 21 | if t.Header.MessageLength == 0 { 22 | t.Header.MessageLength = HeaderLen + SyncPayloadLen 23 | } 24 | 25 | if t.Header.MessageLength != HeaderLen+SyncPayloadLen { 26 | return nil, io.ErrUnexpectedEOF 27 | } 28 | 29 | b := make([]byte, HeaderLen+SyncPayloadLen) 30 | 31 | headerSlice, err := t.Header.MarshalBinary() 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | copy(b[:HeaderLen], headerSlice) 37 | 38 | // Origin timestamp 39 | time2OriginTimestamp(t.OriginTimestamp, b[HeaderLen:]) 40 | 41 | return b, nil 42 | } 43 | 44 | // UnmarshalBinary unmarshals a byte slice into a SyncMsg. 45 | // 46 | // If the byte slice does not contain enough data to unmarshal a valid SyncMsg, 47 | // io.ErrUnexpectedEOF is returned. 48 | func (t *SyncMsg) UnmarshalBinary(b []byte) error { 49 | if len(b) < HeaderLen+SyncPayloadLen { 50 | return io.ErrUnexpectedEOF 51 | } 52 | 53 | err := t.Header.UnmarshalBinary(b[:HeaderLen]) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | if t.Header.MessageType != SyncMsgType { 59 | return ErrInvalidMsgType 60 | } 61 | 62 | if t.OriginTimestamp, err = originTimestamp2Time(b[HeaderLen:]); err != nil { 63 | return err 64 | } 65 | 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /syncmsg_test.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "reflect" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestMarshalSync(t *testing.T) { 12 | 13 | var tests = []struct { 14 | desc string 15 | m *SyncMsg 16 | b []byte 17 | err error 18 | }{ 19 | { 20 | desc: "Correct structure", 21 | m: &SyncMsg{ 22 | Header: Header{ 23 | MessageType: SyncMsgType, 24 | CorrectionNs: 0, 25 | CorrectionSubNs: 0, 26 | ClockIdentity: 0x000af7fffe42a753, 27 | PortNumber: 2, 28 | SequenceID: 55330, 29 | LogMessagePeriod: -4, 30 | }, 31 | OriginTimestamp: time.Unix(500, 200), 32 | }, 33 | b: append([]byte{0x0, 0x2, 0x0, 0x2c, 0x0, 0x0, 0x0, 0x0, 34 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 35 | 0x0, 0x0, 0x0, 0x0, 36 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x0, 0xfc, 37 | 0x0, 0x0, 0x0, 0x0, 0x1, 0xf4, 0x0, 0x0, 0x0, 0xc8}), 38 | }, 39 | { 40 | desc: "Invalid message type", 41 | m: &SyncMsg{ 42 | Header: Header{ 43 | MessageType: FollowUpMsgType, 44 | CorrectionNs: 0, 45 | CorrectionSubNs: 0, 46 | ClockIdentity: 0x000af7fffe42a753, 47 | PortNumber: 2, 48 | SequenceID: 55330, 49 | LogMessagePeriod: -4, 50 | }, 51 | }, 52 | err: ErrInvalidMsgType, 53 | }, 54 | } 55 | 56 | for _, tt := range tests { 57 | t.Run(tt.desc, func(t *testing.T) { 58 | b, err := tt.m.MarshalBinary() 59 | if err != nil { 60 | if want, got := tt.err, err; want != got { 61 | t.Fatalf("unexpected error: %v != %v", want, got) 62 | } 63 | 64 | return 65 | } 66 | 67 | if want, got := tt.b, b; !bytes.Equal(want, got) { 68 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 69 | } 70 | }) 71 | } 72 | } 73 | 74 | func TestUnmarshalSync(t *testing.T) { 75 | 76 | var tests = []struct { 77 | desc string 78 | m *SyncMsg 79 | b []byte 80 | err error 81 | }{ 82 | { 83 | desc: "Correct structure", 84 | m: &SyncMsg{ 85 | Header: Header{ 86 | MessageType: SyncMsgType, 87 | MessageLength: HeaderLen + SyncPayloadLen, 88 | VersionPTP: Version2, 89 | CorrectionNs: 0, 90 | CorrectionSubNs: 0, 91 | ClockIdentity: 0x000af7fffe42a753, 92 | PortNumber: 2, 93 | SequenceID: 55330, 94 | LogMessagePeriod: -4, 95 | }, 96 | OriginTimestamp: time.Unix(500, 200), 97 | }, 98 | b: append([]byte{0x0, 0x2, 0x0, 0x2c, 0x0, 0x0, 0x0, 0x0, 99 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 100 | 0x0, 0x0, 0x0, 0x0, 101 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x0, 0xfc, 102 | 0x0, 0x0, 0x0, 0x0, 0x1, 0xf4, 0x0, 0x0, 0x0, 0xc8}), 103 | }, 104 | { 105 | desc: "Invalid length", 106 | b: append([]byte{0x0, 0x2, 0x0, 0x2c, 0x0, 0x0, 0x0, 0x0, 107 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 108 | 0x0, 0x0, 0x0, 0x0, 109 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x0, 0xfc, 110 | 0x0, 0x0, 0x0, 0x0, 0x1, 0xf4, 0x0, 0x0, 0x0}), 111 | err: io.ErrUnexpectedEOF, 112 | }, 113 | { 114 | desc: "Invalid message type", 115 | b: append([]byte{0x1, 0x2, 0x0, 0x2c, 0x0, 0x0, 0x0, 0x0, 116 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 117 | 0x0, 0x0, 0x0, 0x0, 118 | 0x0, 0xa, 0xf7, 0xff, 0xfe, 0x42, 0xa7, 0x53, 0x0, 0x2, 0xd8, 0x22, 0x0, 0xfc, 119 | 0x0, 0x0, 0x0, 0x0, 0x1, 0xf4, 0x0, 0x0, 0x0, 0xc8}), 120 | err: ErrInvalidMsgType, 121 | }, 122 | } 123 | 124 | for _, tt := range tests { 125 | t.Run(tt.desc, func(t *testing.T) { 126 | m := new(SyncMsg) 127 | err := m.UnmarshalBinary(tt.b) 128 | if err != nil { 129 | if want, got := tt.err, err; want != got { 130 | t.Fatalf("unexpected error: %v != %v", want, got) 131 | } 132 | 133 | return 134 | } 135 | if want, got := tt.m, m; !reflect.DeepEqual(want, got) { 136 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 137 | } 138 | }) 139 | } 140 | } 141 | 142 | func BenchmarkMarshalSync(b *testing.B) { 143 | f := SyncMsg{ 144 | Header: Header{ 145 | MessageType: SyncMsgType, 146 | CorrectionNs: 0, 147 | CorrectionSubNs: 0, 148 | ClockIdentity: 0x000af7fffe42a753, 149 | PortNumber: 2, 150 | SequenceID: 55330, 151 | LogMessagePeriod: -4, 152 | }, 153 | OriginTimestamp: time.Unix(500, 200), 154 | } 155 | for i := 0; i < b.N; i++ { 156 | f.MarshalBinary() 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /tlv.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "io" 7 | "time" 8 | ) 9 | 10 | // TLV payload length 11 | const ( 12 | FollowUpTlvLen = 28 13 | IntervalRequestTlvLen = 12 14 | CsnTlvLen = 46 15 | ) 16 | 17 | var organizationID = []byte{0x0, 0x80, 0xc2} 18 | 19 | // TlvType Type 20 | type TlvType uint16 21 | 22 | // Tlv types codes 23 | const ( 24 | // Standard TLVs 25 | Management TlvType = 0x0001 26 | ManagementErrorStatus TlvType = 0x0002 27 | OrganizationExtension TlvType = 0x0003 28 | 29 | // Optional unicast message negotiation TLVs 30 | RequestUnicastTransmission TlvType = 0x0004 31 | GrantUnicastTransmission TlvType = 0x0005 32 | CancelUnicastTransmission TlvType = 0x0006 33 | AcknowledgeCancelUnicastTransmission TlvType = 0x0007 34 | 35 | // Optional path trace mechanism TLV 36 | PathTrace TlvType = 0x0008 37 | 38 | // Optional alternate timescale TLV 39 | AlternateTimeOffsetIndicator TlvType = 0x0009 40 | 41 | // Reserved for standard TLVs 42 | // 000A – 1FFF 43 | 44 | // Security TLVs 45 | Authentication TlvType = 0x2000 46 | AuthenticationChallenge TlvType = 0x2001 47 | SecurityAssociationUpdate TlvType = 0x2002 48 | 49 | // Cumulative frequency scale factor offset 50 | CumFreqScaleFactorOffset TlvType = 0x2003 51 | 52 | // Reserved for Experimental TLVs 53 | // 2004 – 3FFF 54 | 55 | // Reserved 56 | // 4000 – FFFF 57 | ) 58 | 59 | // RequestUnicastTransmissionTlv... 60 | type RequestUnicastTransmissionTlv struct { 61 | MsgTypeValue MsgType 62 | LogInterMessagePeriod int8 63 | DurationField uint32 64 | } 65 | 66 | // PathTraceTlv ... 67 | type PathTraceTlv struct { 68 | pathSequence []uint64 69 | } 70 | 71 | // MarshalBinary allocates a byte slice and marshals a Frame into binary form. 72 | func (p *PathTraceTlv) MarshalBinary() ([]byte, error) { 73 | 74 | b := make([]byte, 4+8*len(p.pathSequence)) 75 | 76 | // TLV type 77 | binary.BigEndian.PutUint16(b[:2], uint16(PathTrace)) 78 | 79 | // TLV length 80 | binary.BigEndian.PutUint16(b[2:4], uint16(8*len(p.pathSequence))) 81 | 82 | for i, v := range p.pathSequence { 83 | binary.BigEndian.PutUint64(b[4+i*8:4+i*8+8], v) 84 | } 85 | 86 | return b, nil 87 | } 88 | 89 | // UnmarshalBinary unmarshals a byte slice into a PathTraceTlv. 90 | // 91 | // If the byte slice does not contain enough data to unmarshal a valid PathTraceTlv, 92 | // io.ErrUnexpectedEOF is returned. 93 | func (p *PathTraceTlv) UnmarshalBinary(b []byte) error { 94 | if len(b) < (2 + 2 + ClockIdentityLen) { 95 | return io.ErrUnexpectedEOF 96 | } 97 | 98 | // Length of b must be 2 + 2 + 8N 99 | // Сheck the remainder of division by 8 100 | tlvLen := binary.BigEndian.Uint16(b[2:4]) 101 | if int(tlvLen) != len(b[4:]) || tlvLen%8 != 0 { 102 | return io.ErrUnexpectedEOF 103 | } 104 | 105 | tlvType := TlvType(binary.BigEndian.Uint16(b[0:2])) 106 | if tlvType != PathTrace { 107 | return ErrInvalidTlvType 108 | } 109 | 110 | pathSeq := make([]uint64, tlvLen/8) 111 | for i := range pathSeq { 112 | pathSeq[i] = binary.BigEndian.Uint64(b[i*8+4 : i*8+8+4]) 113 | } 114 | 115 | p.pathSequence = pathSeq 116 | 117 | return nil 118 | } 119 | 120 | // IntervalRequestTlv ... 121 | type IntervalRequestTlv struct { 122 | // OrganizationSubType = 2 123 | LinkDelayInterval int8 124 | TimeSyncInterval int8 125 | AnnounceInterval int8 126 | ComputeNeighborRateRatio bool 127 | ComputeNeighborPropDelay bool 128 | } 129 | 130 | // MarshalBinary allocates a byte slice and marshals a Frame into binary form. 131 | func (p *IntervalRequestTlv) MarshalBinary() ([]byte, error) { 132 | 133 | b := make([]byte, IntervalRequestTlvLen+4) 134 | 135 | // TLV type 136 | binary.BigEndian.PutUint16(b[:2], uint16(OrganizationExtension)) 137 | 138 | // TLV length 139 | binary.BigEndian.PutUint16(b[2:4], uint16(IntervalRequestTlvLen)) 140 | 141 | copy(b[4:7], organizationID) 142 | 143 | // organizationSubType 144 | copy(b[7:10], []byte{0x0, 0x0, 0x2}) 145 | 146 | b[10] = uint8(p.LinkDelayInterval) 147 | 148 | b[11] = uint8(p.TimeSyncInterval) 149 | 150 | b[12] = uint8(p.AnnounceInterval) 151 | 152 | b[13] = uint8(b2i(p.ComputeNeighborRateRatio)<<1 | b2i(p.ComputeNeighborPropDelay)<<2) 153 | 154 | return b, nil 155 | } 156 | 157 | // UnmarshalBinary unmarshals a byte slice into a IntervalRequestTlv. 158 | // 159 | // If the byte slice does not contain enough data to unmarshal a valid IntervalRequestTlv, 160 | // io.ErrUnexpectedEOF is returned. 161 | func (p *IntervalRequestTlv) UnmarshalBinary(b []byte) error { 162 | if len(b) != (IntervalRequestTlvLen + 4) { 163 | return io.ErrUnexpectedEOF 164 | } 165 | 166 | tlvLen := binary.BigEndian.Uint16(b[2:4]) 167 | if int(tlvLen) != IntervalRequestTlvLen { 168 | return io.ErrUnexpectedEOF 169 | } 170 | 171 | tlvType := TlvType(binary.BigEndian.Uint16(b[0:2])) 172 | if tlvType != OrganizationExtension { 173 | return ErrInvalidTlvType 174 | } 175 | 176 | if !bytes.Equal(b[4:7], organizationID) { 177 | return ErrInvalidTlvOrgId 178 | } 179 | 180 | // The value of organizationSubType is 2 181 | if !bytes.Equal([]byte{0x0, 0x0, 0x2}, b[7:10]) { 182 | return ErrInvalidTlvOrgSubType 183 | } 184 | 185 | p.LinkDelayInterval = int8(b[10]) 186 | 187 | p.TimeSyncInterval = int8(b[11]) 188 | 189 | p.AnnounceInterval = int8(b[12]) 190 | 191 | p.ComputeNeighborRateRatio = (b[13] & 0x2) != 0 192 | p.ComputeNeighborPropDelay = (b[13] & 0x4) != 0 193 | 194 | return nil 195 | } 196 | 197 | // FollowUpTlv ... 198 | type FollowUpTlv struct { 199 | // OrganizationSubType = 3 200 | CumulativeScaledRateOffset int32 201 | GmTimeBaseIndicator uint16 202 | LastGmPhaseChange UScaledNs 203 | ScaledLastGmFreqChange int32 204 | } 205 | 206 | // MarshalBinary allocates a byte slice and marshals a Frame into binary form. 207 | func (p *FollowUpTlv) MarshalBinary() ([]byte, error) { 208 | 209 | b := make([]byte, FollowUpTlvLen+4) 210 | 211 | // TLV type 212 | binary.BigEndian.PutUint16(b[:2], uint16(OrganizationExtension)) 213 | 214 | // TLV length 215 | binary.BigEndian.PutUint16(b[2:4], uint16(FollowUpTlvLen)) 216 | 217 | copy(b[4:7], organizationID) 218 | 219 | // organizationSubType 220 | copy(b[7:10], []byte{0x0, 0x0, 0x1}) 221 | offset := 10 222 | 223 | binary.BigEndian.PutUint32(b[offset:offset+4], uint32(p.CumulativeScaledRateOffset)) 224 | offset += 4 225 | 226 | binary.BigEndian.PutUint16(b[offset:offset+2], p.GmTimeBaseIndicator) 227 | offset += 2 228 | 229 | lastGM, err := p.LastGmPhaseChange.MarshalBinary() 230 | if err != nil { 231 | return nil, err 232 | } 233 | copy(b[offset:offset+UScaledNsLen], lastGM) 234 | offset += UScaledNsLen 235 | 236 | binary.BigEndian.PutUint32(b[offset:offset+4], uint32(p.ScaledLastGmFreqChange)) 237 | 238 | return b, nil 239 | } 240 | 241 | // UnmarshalBinary unmarshals a byte slice into a FollowUpTlv. 242 | // 243 | // If the byte slice does not contain enough data to unmarshal a valid FollowUpTlv, 244 | // io.ErrUnexpectedEOF is returned. 245 | func (p *FollowUpTlv) UnmarshalBinary(b []byte) error { 246 | 247 | var err error 248 | 249 | if len(b) != (FollowUpTlvLen + 4) { 250 | return io.ErrUnexpectedEOF 251 | } 252 | 253 | tlvLen := binary.BigEndian.Uint16(b[2:4]) 254 | if int(tlvLen) != FollowUpTlvLen { 255 | return io.ErrUnexpectedEOF 256 | } 257 | 258 | tlvType := TlvType(binary.BigEndian.Uint16(b[0:2])) 259 | if tlvType != OrganizationExtension { 260 | return ErrInvalidTlvType 261 | } 262 | 263 | if !bytes.Equal(b[4:7], organizationID) { 264 | return ErrInvalidTlvOrgId 265 | } 266 | 267 | // The value of organizationSubType is 1 268 | if !bytes.Equal([]byte{0x0, 0x0, 0x1}, b[7:10]) { 269 | return ErrInvalidTlvOrgSubType 270 | } 271 | 272 | p.CumulativeScaledRateOffset = int32(binary.BigEndian.Uint32(b[10:14])) 273 | 274 | p.GmTimeBaseIndicator = binary.BigEndian.Uint16(b[14:16]) 275 | 276 | p.LastGmPhaseChange, err = NewUScaledNs(b[16:28]) 277 | if err != nil { 278 | return err 279 | } 280 | 281 | p.ScaledLastGmFreqChange = int32(binary.BigEndian.Uint32(b[28:32])) 282 | 283 | return nil 284 | } 285 | 286 | // CsnTlv ... 287 | type CsnTlv struct { 288 | UpstreamTxTime UScaledNs 289 | NeighborRateRatio int32 290 | NeighborPropDelay UScaledNs 291 | DelayAsymmetry UScaledNs 292 | } 293 | 294 | // MarshalBinary allocates a byte slice and marshals a Frame into binary form. 295 | func (p *CsnTlv) MarshalBinary() ([]byte, error) { 296 | 297 | b := make([]byte, CsnTlvLen+4) 298 | 299 | // TLV type 300 | binary.BigEndian.PutUint16(b[:2], uint16(OrganizationExtension)) 301 | 302 | // TLV length 303 | binary.BigEndian.PutUint16(b[2:4], uint16(CsnTlvLen)) 304 | 305 | copy(b[4:7], organizationID) 306 | 307 | // organizationSubType 308 | copy(b[7:10], []byte{0x0, 0x0, 0x3}) 309 | 310 | tx, err := p.UpstreamTxTime.MarshalBinary() 311 | if err != nil { 312 | return nil, err 313 | } 314 | 315 | offset := 10 316 | 317 | copy(b[offset:offset+UScaledNsLen], tx) 318 | offset += UScaledNsLen 319 | 320 | binary.BigEndian.PutUint32(b[offset:offset+4], uint32(p.NeighborRateRatio)) 321 | offset += 4 322 | 323 | nd, err := p.NeighborPropDelay.MarshalBinary() 324 | if err != nil { 325 | return nil, err 326 | } 327 | 328 | copy(b[offset:offset+UScaledNsLen], nd) 329 | offset += UScaledNsLen 330 | 331 | da, err := p.DelayAsymmetry.MarshalBinary() 332 | if err != nil { 333 | return nil, err 334 | } 335 | 336 | copy(b[offset:offset+UScaledNsLen], da) 337 | 338 | return b, nil 339 | } 340 | 341 | // UnmarshalBinary unmarshals a byte slice into a CsnTlv frame. 342 | // 343 | // If the byte slice does not contain enough data to unmarshal a valid CsnTlv frame, 344 | // io.ErrUnexpectedEOF is returned. 345 | func (p *CsnTlv) UnmarshalBinary(b []byte) error { 346 | 347 | var err error 348 | 349 | if len(b) != (CsnTlvLen + 4) { 350 | return io.ErrUnexpectedEOF 351 | } 352 | 353 | tlvLen := binary.BigEndian.Uint16(b[2:4]) 354 | if int(tlvLen) != CsnTlvLen { 355 | return io.ErrUnexpectedEOF 356 | } 357 | 358 | tlvType := TlvType(binary.BigEndian.Uint16(b[0:2])) 359 | if tlvType != OrganizationExtension { 360 | return ErrInvalidTlvType 361 | } 362 | 363 | if !bytes.Equal(b[4:7], organizationID) { 364 | return ErrInvalidTlvOrgId 365 | } 366 | 367 | // The value of organizationSubType is 3 368 | if !bytes.Equal([]byte{0x0, 0x0, 0x3}, b[7:10]) { 369 | return ErrInvalidTlvOrgSubType 370 | } 371 | 372 | offset := 10 373 | 374 | err = p.UpstreamTxTime.UnmarshalBinary(b[offset : offset+UScaledNsLen]) 375 | if err != nil { 376 | return err 377 | } 378 | 379 | offset += UScaledNsLen 380 | 381 | p.NeighborRateRatio = int32(binary.BigEndian.Uint32(b[offset : offset+4])) 382 | offset += 4 383 | 384 | err = p.NeighborPropDelay.UnmarshalBinary(b[offset : offset+UScaledNsLen]) 385 | if err != nil { 386 | return err 387 | } 388 | 389 | offset += UScaledNsLen 390 | 391 | err = p.DelayAsymmetry.UnmarshalBinary(b[offset : offset+UScaledNsLen]) 392 | if err != nil { 393 | return err 394 | } 395 | 396 | return nil 397 | } 398 | 399 | type ManagementIdType uint16 400 | 401 | const ( 402 | // Applicable to all node types 0000 – 1FFF 403 | NullManagement ManagementIdType = 0x0000 404 | ClockDescription ManagementIdType = 0x0001 405 | UserDescription ManagementIdType = 0x0002 406 | SaveInNonVolatileStorage ManagementIdType = 0x0003 407 | ResetNonVolatileStorage ManagementIdType = 0x0004 408 | Initialize ManagementIdType = 0x0005 409 | FaultLog ManagementIdType = 0x0006 410 | FaultLogReset ManagementIdType = 0x0007 411 | 412 | // Reserved 0008 – 1FFF 413 | 414 | // Applicable to ordinary and boundary clocks 2000 – 3FFF 415 | DefaultDataSet ManagementIdType = 0x2000 416 | CurrentDataSet ManagementIdType = 0x2001 417 | ParentDataSet ManagementIdType = 0x2002 418 | TimePropertiesDataSet ManagementIdType = 0x2003 419 | PortDataSet ManagementIdType = 0x2004 420 | Priority1 ManagementIdType = 0x2005 421 | Priority2 ManagementIdType = 0x2006 422 | Domain ManagementIdType = 0x2007 423 | SlaveOnly ManagementIdType = 0x2008 424 | LogAnnounceInterval ManagementIdType = 0x2009 425 | AnnounceReceiptTimeout ManagementIdType = 0x200a 426 | LogSyncInterval ManagementIdType = 0x200b 427 | VersionNumber ManagementIdType = 0x200c 428 | EneablePort ManagementIdType = 0x200d 429 | DisablePort ManagementIdType = 0x200e 430 | Time ManagementIdType = 0x200f 431 | ClockAccuracy ManagementIdType = 0x2010 432 | UtcProperties ManagementIdType = 0x2011 433 | TraceabilityProperties ManagementIdType = 0x2012 434 | TimescaleProperties ManagementIdType = 0x2013 435 | UnicastNegotiationEnable ManagementIdType = 0x2014 436 | PathTraceList ManagementIdType = 0x2015 437 | PathTraceEnable ManagementIdType = 0x2016 438 | GrandMasterClusterTable ManagementIdType = 0x2017 439 | UnicastMasterTable ManagementIdType = 0x2018 440 | UnicastMasterMaxTableSize ManagementIdType = 0x2019 441 | AcceptableMasterTable ManagementIdType = 0x201a 442 | AcceptableMasterTableEnabled ManagementIdType = 0x201b 443 | AcceptableMasterMaxTableSize ManagementIdType = 0x201c 444 | AlternateMaster ManagementIdType = 0x201d 445 | AlternateTimeOffsetEnable ManagementIdType = 0x201e 446 | AlternateTimeOffsetName ManagementIdType = 0x201f 447 | AlternateTimeOffsetMaxKey ManagementIdType = 0x2020 448 | AlternateTimeOffsetProperties ManagementIdType = 0x2021 449 | 450 | // Reserved 2022 – 3FFF 451 | 452 | // Applicable to transparent clocks 4000 – 5FFF 453 | TransparentClockDefaultDataSet ManagementIdType = 0x4000 454 | TransparentClockPortDataSet ManagementIdType = 0x4001 455 | PrimaryDomain ManagementIdType = 0x4002 456 | 457 | // Reserved 4003 – 5FFF 458 | 459 | // Applicable to ordinary, boundary, and transparent clocks 6000 – 7FFF 460 | DelayMechanism ManagementIdType = 0x6000 461 | LogMinPdelayReqInterval ManagementIdType = 0x6001 462 | 463 | // Reserved 6002 – BFFF 464 | 465 | // This range is to be used for implementation-specific identifiers C000 – DFFF 466 | // This range is to be assigned by an alternate PTP profile E000 – FFFE 467 | 468 | // Reserved FFFF 469 | 470 | ) 471 | 472 | type ClockType uint16 473 | 474 | const ( 475 | OrdinaryClock ClockType = 0 476 | BoundaryClock ClockType = 1 477 | PeerToPeerTransparentClock ClockType = 2 478 | EndToEndTransparentClock ClockType = 3 479 | ManagementNode ClockType = 4 480 | // Reserved 5–F 481 | ) 482 | 483 | type NetworkProtocolType uint16 484 | 485 | const ( 486 | // Reserved 0x0000 487 | 488 | UDP_IPv4 NetworkProtocolType = 0x1 489 | UDP_IPv6 NetworkProtocolType = 0x2 490 | IEEE_802_3 NetworkProtocolType = 0x3 491 | DeviceNet NetworkProtocolType = 0x4 492 | ControlNet NetworkProtocolType = 0x5 493 | PROFINET NetworkProtocolType = 0x6 494 | 495 | // Reserved for assignment by the Precise Networked Clock Working Group of the IM/ST Committee 0007-EFFF 496 | 497 | // Reserved for assignment in a PTP profile F000-FFFD 498 | 499 | UnknownProtocol = 0xfffe 500 | 501 | // Reserved 0xffff 502 | ) 503 | 504 | // PortAddress... 505 | type PortAddress struct { 506 | NetworkProtocol NetworkProtocolType 507 | AddressField []byte 508 | } 509 | 510 | // SeverityCode is FaultRecord.severityCode 511 | type SeverityCode uint8 512 | 513 | const ( 514 | Emergency SeverityCode = 0 515 | Alert 516 | Critical 517 | Error 518 | Warning 519 | Notice 520 | Informational 521 | Debug 522 | // Reserved 08–FF 523 | ) 524 | 525 | type ClockDescriptionTlv struct { 526 | } 527 | 528 | type UserDescriptionTlv struct { 529 | } 530 | 531 | type SaveInNonVolatileStorageTlv struct { 532 | } 533 | 534 | type ResetNonVolatileStorageTlv struct { 535 | } 536 | 537 | type InitializeTlv struct { 538 | InitializationKey uint16 539 | } 540 | 541 | type FaultLogTlv struct { 542 | } 543 | 544 | type FaultLogResetTlv struct { 545 | } 546 | 547 | type DefaultDataSetTlv struct { 548 | TSC bool 549 | SO bool 550 | NumberPorts uint16 551 | Priority1 uint8 552 | ClockQuality 553 | Priority2 uint8 554 | ClockIdentity uint64 555 | DomainNumber uint8 556 | } 557 | 558 | type CurrentDataSetTlv struct { 559 | } 560 | 561 | type ParentDataSetTlv struct { 562 | ClockIdentity uint64 563 | PortNumber uint16 564 | PS bool 565 | // Reserved 1byte 566 | ObservedParentOffsetScaledLogVariance uint16 567 | ObservedParentClockPhaseChangeRate uint32 568 | GrandmasterPriority1 uint8 569 | GrandmasterClockQuality ClockQuality 570 | GrandmasterPriority2 uint8 571 | GrandmasterIdentity uint64 572 | } 573 | 574 | type TimePropertiesDataSetTlv struct { 575 | CurrentUtcOffset uint16 576 | LI61 bool 577 | LI59 bool 578 | UTCV bool 579 | PTP bool 580 | TTRA bool 581 | FTRA bool 582 | TimeSource TimeSourceType 583 | } 584 | 585 | type PortDataSetTlv struct { 586 | } 587 | 588 | type Priority1Tlv struct { 589 | Priority1 uint8 590 | // Reserved 1byte 591 | } 592 | 593 | type Priority2Tlv struct { 594 | Priority2 uint8 595 | // Reserved 1byte 596 | } 597 | 598 | type DomainTlv struct { 599 | DomainNumber uint8 600 | // Reserved 1byte 601 | } 602 | 603 | type SlaveOnlyTlv struct { 604 | SO bool 605 | // Reserved 1byte 606 | } 607 | 608 | type LogAnnounceIntervalTlv struct { 609 | LogAnnounceInterval uint8 610 | // Reserved 1byte 611 | } 612 | 613 | type AnnounceReceiptTimeoutTlv struct { 614 | AnnounceReceiptTimeout uint8 615 | // Reserved 1byte 616 | } 617 | 618 | type LogSyncIntervalTlv struct { 619 | LogSyncInterval uint8 620 | // Reserved 1byte 621 | } 622 | 623 | type VersionNumberTlv struct { 624 | VersionNumber uint8 625 | // Reserved 1byte 626 | } 627 | 628 | type TimeTlv struct { 629 | time.Time 630 | } 631 | 632 | type ClockAccuracyTlv struct { 633 | clockAccuracy ClockAccuracyType 634 | } 635 | 636 | type UtcPropertiesTlv struct { 637 | CurrentUtcOffset uint16 638 | LI61 bool 639 | LI59 bool 640 | UTCV bool 641 | // Reserved 1byte 642 | } 643 | 644 | type TraceabilityPropertiesTlv struct { 645 | TTRA bool 646 | FTRA bool 647 | // Reserved 1byte 648 | } 649 | 650 | type TimescalePropertiesTlv struct { 651 | PTP bool 652 | TimeSource TimeSourceType 653 | } 654 | 655 | type EneablePortTlv struct { 656 | } 657 | 658 | type DisablePortTlv struct { 659 | } 660 | 661 | type TransparentClockDefaultDataSetTlv struct { 662 | ClockIdentity uint64 663 | PortNumber uint16 664 | DelayMechanism DelayMechanismType 665 | PrimaryDomain uint8 666 | } 667 | 668 | type TransparentClockPortDataSetTlv struct { 669 | } 670 | 671 | type PrimaryDomainTlv struct { 672 | PrimaryDomain uint8 673 | // Reserved 1byte 674 | } 675 | 676 | type DelayMechanismTlv struct { 677 | DelayMechanism DelayMechanismType 678 | // Reserved 1byte 679 | } 680 | 681 | type LogMinPdelayReqIntervalTlv struct { 682 | LogMinPdelayReqInterval uint8 683 | } 684 | 685 | type ManagementTlv struct { 686 | } 687 | -------------------------------------------------------------------------------- /tlv_test.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestMarshalPathTraceTlv(t *testing.T) { 11 | var tests = []struct { 12 | desc string 13 | m *PathTraceTlv 14 | b []byte 15 | err error 16 | }{ 17 | { 18 | desc: "Empty pathSequence", 19 | m: &PathTraceTlv{ 20 | pathSequence: []uint64{}, 21 | }, 22 | b: append([]byte{0x0, 0x8, 0x0, 0x0}), 23 | }, 24 | { 25 | desc: "Single clockId in pathSequence", 26 | m: &PathTraceTlv{ 27 | pathSequence: []uint64{0x0011223344556677}, 28 | }, 29 | b: append([]byte{0x0, 0x8, 0x0, 0x8, 30 | 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}), 31 | }, 32 | } 33 | 34 | for _, tt := range tests { 35 | t.Run(tt.desc, func(t *testing.T) { 36 | b, err := tt.m.MarshalBinary() 37 | if err != nil { 38 | if want, got := tt.err, err; want != got { 39 | t.Fatalf("unexpected error: %v != %v", want, got) 40 | } 41 | 42 | return 43 | } 44 | 45 | if want, got := tt.b, b; !bytes.Equal(want, got) { 46 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 47 | } 48 | }) 49 | } 50 | } 51 | 52 | func TestUnmarshalPathTraceTlv(t *testing.T) { 53 | var tests = []struct { 54 | desc string 55 | m *PathTraceTlv 56 | b []byte 57 | err error 58 | }{ 59 | { 60 | desc: "Empty pathSequence", 61 | b: append([]byte{0x0, 0x8, 0x0, 0x0}), 62 | err: io.ErrUnexpectedEOF, 63 | }, 64 | { 65 | desc: "Single clockId in pathSequence", 66 | m: &PathTraceTlv{ 67 | pathSequence: []uint64{0x0011223344556677}, 68 | }, 69 | b: append([]byte{0x0, 0x8, 0x0, 0x8, 70 | 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}), 71 | }, 72 | { 73 | desc: "Multiple clockId in pathSequence", 74 | m: &PathTraceTlv{ 75 | pathSequence: []uint64{0x0011223344556677, 76 | 0x5544226677889911}, 77 | }, 78 | b: append([]byte{0x0, 0x8, 0x0, 0x10, 79 | 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 80 | 0x55, 0x44, 0x22, 0x66, 0x77, 0x88, 0x99, 0x11}), 81 | }, 82 | { 83 | desc: "Invalid TLV type", 84 | b: append([]byte{0x1, 0x8, 0x0, 0x8, 85 | 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}), 86 | err: ErrInvalidTlvType, 87 | }, 88 | { 89 | desc: "Mismatch lengthField and actual amount of bytes", 90 | b: append([]byte{0x0, 0x8, 0x0, 0x8, 91 | 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 92 | 0x55, 0x44, 0x22, 0x66, 0x77, 0x88, 0x99, 0x11}), 93 | err: io.ErrUnexpectedEOF, 94 | }, 95 | { 96 | desc: "The number of bytes is not a multiple of 8", 97 | b: append([]byte{0x0, 0x8, 0x0, 0x9, 98 | 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 99 | 0x55}), 100 | err: io.ErrUnexpectedEOF, 101 | }, 102 | } 103 | 104 | for _, tt := range tests { 105 | t.Run(tt.desc, func(t *testing.T) { 106 | m := new(PathTraceTlv) 107 | err := m.UnmarshalBinary(tt.b) 108 | if err != nil { 109 | if want, got := tt.err, err; want != got { 110 | t.Fatalf("unexpected error: %v != %v", want, got) 111 | } 112 | 113 | return 114 | } 115 | 116 | if want, got := tt.m, m; !reflect.DeepEqual(want, got) { 117 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 118 | } 119 | }) 120 | } 121 | } 122 | 123 | func TestMarshalIntervalRequestTlv(t *testing.T) { 124 | var tests = []struct { 125 | desc string 126 | m *IntervalRequestTlv 127 | b []byte 128 | err error 129 | }{ 130 | { 131 | desc: "Correct TLV values", 132 | m: &IntervalRequestTlv{ 133 | LinkDelayInterval: 127, 134 | TimeSyncInterval: 127, 135 | AnnounceInterval: 127, 136 | ComputeNeighborRateRatio: true, 137 | ComputeNeighborPropDelay: false, 138 | }, 139 | b: append([]byte{0x0, 0x3, 0x0, 0xc, 140 | 0x0, 0x80, 0xc2, 0x0, 0x0, 0x2, 141 | 0x7f, 0x7f, 0x7f, 142 | // Flags 143 | 0x2, 144 | // Reserved 145 | 0x0, 0x0}), 146 | }, 147 | } 148 | 149 | for _, tt := range tests { 150 | t.Run(tt.desc, func(t *testing.T) { 151 | b, err := tt.m.MarshalBinary() 152 | if err != nil { 153 | if want, got := tt.err, err; want != got { 154 | t.Fatalf("unexpected error: %v != %v", want, got) 155 | } 156 | 157 | return 158 | } 159 | 160 | if want, got := tt.b, b; !bytes.Equal(want, got) { 161 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 162 | } 163 | }) 164 | } 165 | } 166 | 167 | func TestUnmarshalIntervalRequestTlv(t *testing.T) { 168 | var tests = []struct { 169 | desc string 170 | m *IntervalRequestTlv 171 | b []byte 172 | err error 173 | }{ 174 | { 175 | desc: "Correct TLV values", 176 | m: &IntervalRequestTlv{ 177 | LinkDelayInterval: 127, 178 | TimeSyncInterval: 127, 179 | AnnounceInterval: 127, 180 | ComputeNeighborRateRatio: true, 181 | ComputeNeighborPropDelay: false, 182 | }, 183 | b: append([]byte{0x0, 0x3, 0x0, 0xc, 184 | 0x0, 0x80, 0xc2, 0x0, 0x0, 0x2, 185 | 0x7f, 0x7f, 0x7f, 186 | // Flags 187 | 0x2, 188 | // Reserved 189 | 0x0, 0x0}), 190 | }, 191 | { 192 | desc: "Invalid TLV type", 193 | b: append([]byte{0x1, 0x3, 0x0, 0xc, 194 | 0x0, 0x80, 0xc2, 0x0, 0x0, 0x2, 195 | 0x7f, 0x7f, 0x7f, 196 | // Flags 197 | 0x2, 198 | // Reserved 199 | 0x0, 0x0}), 200 | err: ErrInvalidTlvType, 201 | }, 202 | { 203 | desc: "Invalid organizationId", 204 | b: append([]byte{0x0, 0x3, 0x0, 0xc, 205 | 0x1, 0x80, 0xc2, 0x0, 0x0, 0x2, 206 | 0x7f, 0x7f, 0x7f, 207 | // Flags 208 | 0x2, 209 | // Reserved 210 | 0x0, 0x0}), 211 | err: ErrInvalidTlvOrgId, 212 | }, 213 | { 214 | desc: "Invalid organizationSubType", 215 | b: append([]byte{0x0, 0x3, 0x0, 0xc, 216 | 0x0, 0x80, 0xc2, 0x0, 0x0, 0x1, 217 | 0x7f, 0x7f, 0x7f, 218 | // Flags 219 | 0x2, 220 | // Reserved 221 | 0x0, 0x0}), 222 | err: ErrInvalidTlvOrgSubType, 223 | }, 224 | { 225 | desc: "Mismatch lengthField and actual amount of bytes", 226 | b: append([]byte{0x0, 0x3, 0x0, 0xa, 227 | 0x0, 0x80, 0xc2, 0x0, 0x0, 0x2, 228 | 0x7f, 0x7f, 0x7f, 229 | // Flags 230 | 0x2, 231 | // Reserved 232 | 0x0, 0x0}), 233 | err: io.ErrUnexpectedEOF, 234 | }, 235 | { 236 | desc: "Invalid amount of bytes", 237 | b: append([]byte{0x0, 0x3, 0x0, 0xa, 238 | 0x0, 0x80, 0xc2, 0x0, 0x0, 0x2, 239 | 0x7f, 0x7f, 0x7f, 240 | // Flags 241 | 0x2, 242 | // Reserved 243 | 0x0}), 244 | err: io.ErrUnexpectedEOF, 245 | }, 246 | } 247 | 248 | for _, tt := range tests { 249 | t.Run(tt.desc, func(t *testing.T) { 250 | m := new(IntervalRequestTlv) 251 | err := m.UnmarshalBinary(tt.b) 252 | if err != nil { 253 | if want, got := tt.err, err; want != got { 254 | t.Fatalf("unexpected error: %v != %v", want, got) 255 | } 256 | 257 | return 258 | } 259 | 260 | if want, got := tt.m, m; !reflect.DeepEqual(want, got) { 261 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 262 | } 263 | }) 264 | } 265 | } 266 | 267 | func TestMarshalFollowUpTlv(t *testing.T) { 268 | var tests = []struct { 269 | desc string 270 | m *FollowUpTlv 271 | b []byte 272 | err error 273 | }{ 274 | { 275 | desc: "Correct TLV values", 276 | m: &FollowUpTlv{ 277 | CumulativeScaledRateOffset: 1, 278 | GmTimeBaseIndicator: 2, 279 | LastGmPhaseChange: UScaledNs{1, 2}, 280 | ScaledLastGmFreqChange: 7, 281 | }, 282 | b: append([]byte{0x0, 0x3, 0x0, 0x1c, 283 | 0x0, 0x80, 0xc2, 0x0, 0x0, 0x1, 284 | // cumulativeScaledRateOffset 285 | 0x0, 0x0, 0x0, 0x1, 286 | // gmTimeBaseIndicator 287 | 0x0, 0x2, 288 | // lastGmPhaseChange 289 | 0x0, 0x0, 0x0, 0x1, 290 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 291 | // scaledLastGmFreqChange 292 | 0x0, 0x0, 0x0, 0x7}), 293 | }, 294 | } 295 | 296 | for _, tt := range tests { 297 | t.Run(tt.desc, func(t *testing.T) { 298 | b, err := tt.m.MarshalBinary() 299 | if err != nil { 300 | if want, got := tt.err, err; want != got { 301 | t.Fatalf("unexpected error: %v != %v", want, got) 302 | } 303 | 304 | return 305 | } 306 | 307 | if want, got := tt.b, b; !bytes.Equal(want, got) { 308 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 309 | } 310 | }) 311 | } 312 | } 313 | 314 | func TestUnmarshalFollowUpTlv(t *testing.T) { 315 | var tests = []struct { 316 | desc string 317 | m *FollowUpTlv 318 | b []byte 319 | err error 320 | }{ 321 | { 322 | desc: "Correct TLV values", 323 | m: &FollowUpTlv{ 324 | CumulativeScaledRateOffset: 1, 325 | GmTimeBaseIndicator: 2, 326 | LastGmPhaseChange: UScaledNs{1, 2}, 327 | ScaledLastGmFreqChange: 7, 328 | }, 329 | b: append([]byte{0x0, 0x3, 0x0, 0x1c, 330 | 0x0, 0x80, 0xc2, 0x0, 0x0, 0x1, 331 | // cumulativeScaledRateOffset 332 | 0x0, 0x0, 0x0, 0x1, 333 | // gmTimeBaseIndicator 334 | 0x0, 0x2, 335 | // lastGmPhaseChange 336 | 0x0, 0x0, 0x0, 0x1, 337 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 338 | // scaledLastGmFreqChange 339 | 0x0, 0x0, 0x0, 0x7}), 340 | }, 341 | { 342 | desc: "Invalid TLV type", 343 | b: append([]byte{0x0, 0x2, 0x0, 0x1c, 344 | 0x0, 0x80, 0xc2, 0x0, 0x0, 0x1, 345 | // cumulativeScaledRateOffset 346 | 0x0, 0x0, 0x0, 0x1, 347 | // gmTimeBaseIndicator 348 | 0x0, 0x2, 349 | // lastGmPhaseChange 350 | 0x0, 0x0, 0x0, 0x1, 351 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 352 | // scaledLastGmFreqChange 353 | 0x0, 0x0, 0x0, 0x7}), 354 | err: ErrInvalidTlvType, 355 | }, 356 | { 357 | desc: "Invalid organizationId", 358 | b: append([]byte{0x0, 0x3, 0x0, 0x1c, 359 | 0x0, 0x81, 0xc2, 0x0, 0x0, 0x1, 360 | // cumulativeScaledRateOffset 361 | 0x0, 0x0, 0x0, 0x1, 362 | // gmTimeBaseIndicator 363 | 0x0, 0x2, 364 | // lastGmPhaseChange 365 | 0x0, 0x0, 0x0, 0x1, 366 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 367 | // scaledLastGmFreqChange 368 | 0x0, 0x0, 0x0, 0x7}), 369 | err: ErrInvalidTlvOrgId, 370 | }, 371 | { 372 | desc: "Invalid organizationSubType", 373 | b: append([]byte{0x0, 0x3, 0x0, 0x1c, 374 | 0x0, 0x80, 0xc2, 0x0, 0x0, 0x2, 375 | // cumulativeScaledRateOffset 376 | 0x0, 0x0, 0x0, 0x1, 377 | // gmTimeBaseIndicator 378 | 0x0, 0x2, 379 | // lastGmPhaseChange 380 | 0x0, 0x0, 0x0, 0x1, 381 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 382 | // scaledLastGmFreqChange 383 | 0x0, 0x0, 0x0, 0x7}), 384 | err: ErrInvalidTlvOrgSubType, 385 | }, 386 | { 387 | desc: "Mismatch lengthField and actual amount of bytes", 388 | b: append([]byte{0x0, 0x3, 0x0, 0x1b, 389 | 0x0, 0x80, 0xc2, 0x0, 0x0, 0x1, 390 | // cumulativeScaledRateOffset 391 | 0x0, 0x0, 0x0, 0x1, 392 | // gmTimeBaseIndicator 393 | 0x0, 0x2, 394 | // lastGmPhaseChange 395 | 0x0, 0x0, 0x0, 0x1, 396 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 397 | // scaledLastGmFreqChange 398 | 0x0, 0x0, 0x0, 0x7}), 399 | err: io.ErrUnexpectedEOF, 400 | }, 401 | { 402 | desc: "Invalid length", 403 | b: append([]byte{0x0, 0x3, 0x0, 0x1c, 404 | 0x0, 0x80, 0xc2, 0x0, 0x0, 0x1, 405 | // cumulativeScaledRateOffset 406 | 0x0, 0x0, 0x0, 0x1, 407 | // gmTimeBaseIndicator 408 | 0x0, 0x2, 409 | // lastGmPhaseChange 410 | 0x0, 0x0, 0x0, 0x1, 411 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 412 | // scaledLastGmFreqChange 413 | 0x0, 0x0, 0x0}), 414 | err: io.ErrUnexpectedEOF, 415 | }, 416 | } 417 | 418 | for _, tt := range tests { 419 | t.Run(tt.desc, func(t *testing.T) { 420 | m := new(FollowUpTlv) 421 | err := m.UnmarshalBinary(tt.b) 422 | if err != nil { 423 | if want, got := tt.err, err; want != got { 424 | t.Fatalf("unexpected error: %v != %v", want, got) 425 | } 426 | 427 | return 428 | } 429 | 430 | if want, got := tt.m, m; !reflect.DeepEqual(want, got) { 431 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 432 | } 433 | }) 434 | } 435 | } 436 | 437 | func TestMarshalCsnTlv(t *testing.T) { 438 | var tests = []struct { 439 | desc string 440 | m *CsnTlv 441 | b []byte 442 | err error 443 | }{ 444 | { 445 | desc: "Correct TLV values", 446 | m: &CsnTlv{ 447 | UpstreamTxTime: UScaledNs{1, 2}, 448 | NeighborRateRatio: 2, 449 | NeighborPropDelay: UScaledNs{3, 4}, 450 | DelayAsymmetry: UScaledNs{5, 6}, 451 | }, 452 | b: append([]byte{0x0, 0x3, 0x0, 0x2e, 453 | 0x0, 0x80, 0xc2, 0x0, 0x0, 0x3, 454 | // upstreamTxTime 455 | 0x0, 0x0, 0x0, 0x1, 456 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 457 | // neighborRateRatio 458 | 0x0, 0x0, 0x0, 0x2, 459 | // neighborPropDelay 460 | 0x0, 0x0, 0x0, 0x3, 461 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 462 | // delayAsymmetry 463 | 0x0, 0x0, 0x0, 0x5, 464 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 465 | }), 466 | }, 467 | } 468 | 469 | for _, tt := range tests { 470 | t.Run(tt.desc, func(t *testing.T) { 471 | b, err := tt.m.MarshalBinary() 472 | if err != nil { 473 | if want, got := tt.err, err; want != got { 474 | t.Fatalf("unexpected error: %v != %v", want, got) 475 | } 476 | 477 | return 478 | } 479 | 480 | if want, got := tt.b, b; !bytes.Equal(want, got) { 481 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 482 | } 483 | }) 484 | } 485 | } 486 | 487 | func TestUnmarshalCsnTlv(t *testing.T) { 488 | var tests = []struct { 489 | desc string 490 | m *CsnTlv 491 | b []byte 492 | err error 493 | }{ 494 | { 495 | desc: "Correct TLV values", 496 | m: &CsnTlv{ 497 | UpstreamTxTime: UScaledNs{1, 2}, 498 | NeighborRateRatio: 2, 499 | NeighborPropDelay: UScaledNs{3, 4}, 500 | DelayAsymmetry: UScaledNs{5, 6}, 501 | }, 502 | b: append([]byte{0x0, 0x3, 0x0, 0x2e, 503 | 0x0, 0x80, 0xc2, 0x0, 0x0, 0x3, 504 | // upstreamTxTime 505 | 0x0, 0x0, 0x0, 0x1, 506 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 507 | // neighborRateRatio 508 | 0x0, 0x0, 0x0, 0x2, 509 | // neighborPropDelay 510 | 0x0, 0x0, 0x0, 0x3, 511 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 512 | // delayAsymmetry 513 | 0x0, 0x0, 0x0, 0x5, 514 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 515 | }), 516 | }, 517 | { 518 | desc: "Invalid TLV type", 519 | b: append([]byte{0x0, 0x2, 0x0, 0x2e, 520 | 0x0, 0x80, 0xc2, 0x0, 0x0, 0x3, 521 | // upstreamTxTime 522 | 0x0, 0x0, 0x0, 0x1, 523 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 524 | // neighborRateRatio 525 | 0x0, 0x0, 0x0, 0x2, 526 | // neighborPropDelay 527 | 0x0, 0x0, 0x0, 0x3, 528 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 529 | // delayAsymmetry 530 | 0x0, 0x0, 0x0, 0x5, 531 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 532 | }), 533 | err: ErrInvalidTlvType, 534 | }, 535 | { 536 | desc: "Invalid organizationId", 537 | b: append([]byte{0x0, 0x3, 0x0, 0x2e, 538 | 0x0, 0x81, 0xc2, 0x0, 0x0, 0x3, 539 | // upstreamTxTime 540 | 0x0, 0x0, 0x0, 0x1, 541 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 542 | // neighborRateRatio 543 | 0x0, 0x0, 0x0, 0x2, 544 | // neighborPropDelay 545 | 0x0, 0x0, 0x0, 0x3, 546 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 547 | // delayAsymmetry 548 | 0x0, 0x0, 0x0, 0x5, 549 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 550 | }), 551 | err: ErrInvalidTlvOrgId, 552 | }, 553 | { 554 | desc: "Invalid organizationSubType", 555 | b: append([]byte{0x0, 0x3, 0x0, 0x2e, 556 | 0x0, 0x80, 0xc2, 0x0, 0x0, 0x2, 557 | // upstreamTxTime 558 | 0x0, 0x0, 0x0, 0x1, 559 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 560 | // neighborRateRatio 561 | 0x0, 0x0, 0x0, 0x2, 562 | // neighborPropDelay 563 | 0x0, 0x0, 0x0, 0x3, 564 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 565 | // delayAsymmetry 566 | 0x0, 0x0, 0x0, 0x5, 567 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 568 | }), 569 | err: ErrInvalidTlvOrgSubType, 570 | }, 571 | { 572 | desc: "Mismatch lengthField and actual amount of bytes", 573 | b: append([]byte{0x0, 0x3, 0x0, 0x2b, 574 | 0x0, 0x80, 0xc2, 0x0, 0x0, 0x3, 575 | // upstreamTxTime 576 | 0x0, 0x0, 0x0, 0x1, 577 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 578 | // neighborRateRatio 579 | 0x0, 0x0, 0x0, 0x2, 580 | // neighborPropDelay 581 | 0x0, 0x0, 0x0, 0x3, 582 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 583 | // delayAsymmetry 584 | 0x0, 0x0, 0x0, 0x5, 585 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 586 | }), 587 | err: io.ErrUnexpectedEOF, 588 | }, 589 | { 590 | desc: "Invalid length", 591 | b: append([]byte{0x0, 0x3, 0x0, 0x2b, 592 | 0x0, 0x80, 0xc2, 0x0, 0x0, 0x3, 593 | // upstreamTxTime 594 | 0x0, 0x0, 0x0, 0x1, 595 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 596 | // neighborRateRatio 597 | 0x0, 0x0, 0x0, 0x2, 598 | // neighborPropDelay 599 | 0x0, 0x0, 0x0, 0x3, 600 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 601 | // delayAsymmetry 602 | 0x0, 0x0, 0x0, 0x5, 603 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 604 | }), 605 | err: io.ErrUnexpectedEOF, 606 | }, 607 | } 608 | 609 | for _, tt := range tests { 610 | t.Run(tt.desc, func(t *testing.T) { 611 | m := new(CsnTlv) 612 | err := m.UnmarshalBinary(tt.b) 613 | if err != nil { 614 | if want, got := tt.err, err; want != got { 615 | t.Fatalf("unexpected error: %v != %v", want, got) 616 | } 617 | 618 | return 619 | } 620 | 621 | if want, got := tt.m, m; !reflect.DeepEqual(want, got) { 622 | t.Fatalf("unexpected Frame bytes:\n- want: %#v\n- got: %#v", want, got) 623 | } 624 | }) 625 | } 626 | } 627 | --------------------------------------------------------------------------------