payload_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. package handshake
  2. import (
  3. "bytes"
  4. "math"
  5. "testing"
  6. "github.com/stretchr/testify/assert"
  7. "github.com/stretchr/testify/require"
  8. "google.golang.org/protobuf/encoding/protowire"
  9. )
  10. func TestPayloadRoundTrip(t *testing.T) {
  11. t.Run("all fields set", func(t *testing.T) {
  12. data := MarshalPayload(nil, Payload{
  13. Cert: []byte("test-cert-bytes"),
  14. CertVersion: 2,
  15. InitiatorIndex: 12345,
  16. ResponderIndex: 67890,
  17. Time: 1234567890,
  18. })
  19. got, err := UnmarshalPayload(data)
  20. require.NoError(t, err)
  21. assert.Equal(t, []byte("test-cert-bytes"), got.Cert)
  22. assert.Equal(t, uint32(12345), got.InitiatorIndex)
  23. assert.Equal(t, uint32(67890), got.ResponderIndex)
  24. assert.Equal(t, uint64(1234567890), got.Time)
  25. assert.Equal(t, uint32(2), got.CertVersion)
  26. })
  27. t.Run("minimal fields", func(t *testing.T) {
  28. data := MarshalPayload(nil, Payload{InitiatorIndex: 1})
  29. got, err := UnmarshalPayload(data)
  30. require.NoError(t, err)
  31. assert.Equal(t, uint32(1), got.InitiatorIndex)
  32. assert.Equal(t, uint32(0), got.ResponderIndex)
  33. assert.Equal(t, uint64(0), got.Time)
  34. assert.Nil(t, got.Cert)
  35. })
  36. t.Run("empty payload", func(t *testing.T) {
  37. data := MarshalPayload(nil, Payload{})
  38. got, err := UnmarshalPayload(data)
  39. require.NoError(t, err)
  40. assert.Equal(t, uint32(0), got.InitiatorIndex)
  41. })
  42. t.Run("large cert bytes", func(t *testing.T) {
  43. bigCert := make([]byte, 4096)
  44. for i := range bigCert {
  45. bigCert[i] = byte(i % 256)
  46. }
  47. data := MarshalPayload(nil, Payload{
  48. Cert: bigCert,
  49. CertVersion: 2,
  50. InitiatorIndex: 999,
  51. })
  52. got, err := UnmarshalPayload(data)
  53. require.NoError(t, err)
  54. assert.Equal(t, bigCert, got.Cert)
  55. assert.Equal(t, uint32(999), got.InitiatorIndex)
  56. })
  57. t.Run("append to existing buffer", func(t *testing.T) {
  58. prefix := []byte("prefix")
  59. data := MarshalPayload(prefix, Payload{InitiatorIndex: 42})
  60. assert.Equal(t, []byte("prefix"), data[:6])
  61. got, err := UnmarshalPayload(data[6:])
  62. require.NoError(t, err)
  63. assert.Equal(t, uint32(42), got.InitiatorIndex)
  64. })
  65. }
  66. func TestPayloadUnknownFields(t *testing.T) {
  67. t.Run("unknown field in outer message is skipped", func(t *testing.T) {
  68. // Marshal a normal payload then append an unknown field (field 99, varint)
  69. data := MarshalPayload(nil, Payload{InitiatorIndex: 42})
  70. data = protowire.AppendTag(data, 99, protowire.VarintType)
  71. data = protowire.AppendVarint(data, 12345)
  72. got, err := UnmarshalPayload(data)
  73. require.NoError(t, err)
  74. assert.Equal(t, uint32(42), got.InitiatorIndex)
  75. })
  76. t.Run("unknown field in details is skipped", func(t *testing.T) {
  77. // Build details with a known field + unknown field
  78. var details []byte
  79. details = protowire.AppendTag(details, fieldInitiatorIndex, protowire.VarintType)
  80. details = protowire.AppendVarint(details, 77)
  81. // Unknown field 50, varint
  82. details = protowire.AppendTag(details, 50, protowire.VarintType)
  83. details = protowire.AppendVarint(details, 9999)
  84. // Another known field after the unknown one
  85. details = protowire.AppendTag(details, fieldResponderIndex, protowire.VarintType)
  86. details = protowire.AppendVarint(details, 88)
  87. // Wrap in outer message
  88. var data []byte
  89. data = protowire.AppendTag(data, 1, protowire.BytesType)
  90. data = protowire.AppendBytes(data, details)
  91. got, err := UnmarshalPayload(data)
  92. require.NoError(t, err)
  93. assert.Equal(t, uint32(77), got.InitiatorIndex)
  94. assert.Equal(t, uint32(88), got.ResponderIndex)
  95. })
  96. t.Run("unknown field inside LaneDetails is skipped", func(t *testing.T) {
  97. var lane []byte
  98. lane = protowire.AppendTag(lane, fieldLanePortCount, protowire.VarintType)
  99. lane = protowire.AppendVarint(lane, 4)
  100. lane = protowire.AppendTag(lane, 50, protowire.VarintType) // unknown subfield
  101. lane = protowire.AppendVarint(lane, 9999)
  102. lane = protowire.AppendTag(lane, fieldLaneBasePort, protowire.VarintType)
  103. lane = protowire.AppendVarint(lane, 4242)
  104. var details []byte
  105. details = protowire.AppendTag(details, fieldInitiatorIndex, protowire.VarintType)
  106. details = protowire.AppendVarint(details, 100)
  107. details = protowire.AppendTag(details, fieldInitiatorLanes, protowire.BytesType)
  108. details = protowire.AppendBytes(details, lane)
  109. got, err := UnmarshalPayload(wrapDetails(details))
  110. require.NoError(t, err)
  111. assert.Equal(t, uint32(100), got.InitiatorIndex)
  112. require.NotNil(t, got.InitiatorLanes)
  113. assert.Equal(t, uint32(4), got.InitiatorLanes.PortCount)
  114. assert.Equal(t, uint32(4242), got.InitiatorLanes.BasePort)
  115. })
  116. }
  117. func TestPayloadLaneDetails(t *testing.T) {
  118. t.Run("round trip both sides", func(t *testing.T) {
  119. data := MarshalPayload(nil, Payload{
  120. InitiatorIndex: 12345,
  121. Time: 999,
  122. InitiatorLanes: &LaneDetails{PortCount: 8, BasePort: 4242, LaneIndex: 3},
  123. ResponderLanes: &LaneDetails{PortCount: 4, BasePort: 5353},
  124. })
  125. got, err := UnmarshalPayload(data)
  126. require.NoError(t, err)
  127. require.NotNil(t, got.InitiatorLanes)
  128. assert.Equal(t, LaneDetails{PortCount: 8, BasePort: 4242, LaneIndex: 3}, *got.InitiatorLanes)
  129. require.NotNil(t, got.ResponderLanes)
  130. assert.Equal(t, LaneDetails{PortCount: 4, BasePort: 5353}, *got.ResponderLanes)
  131. })
  132. t.Run("zero-valued LaneDetails survives the round trip", func(t *testing.T) {
  133. // Presence is what negotiation keys on; an all-zero advert must not
  134. // decay to nil.
  135. data := MarshalPayload(nil, Payload{
  136. InitiatorIndex: 1,
  137. InitiatorLanes: &LaneDetails{},
  138. })
  139. got, err := UnmarshalPayload(data)
  140. require.NoError(t, err)
  141. require.NotNil(t, got.InitiatorLanes)
  142. assert.Equal(t, LaneDetails{}, *got.InitiatorLanes)
  143. assert.Nil(t, got.ResponderLanes)
  144. })
  145. t.Run("nil lanes marshal byte-identical to a vanilla payload", func(t *testing.T) {
  146. p := Payload{
  147. Cert: []byte("cert"),
  148. CertVersion: 2,
  149. InitiatorIndex: 100,
  150. Time: 999,
  151. }
  152. // The vanilla encoding of the same fields, built by hand in field order.
  153. var details []byte
  154. details = protowire.AppendTag(details, fieldCert, protowire.BytesType)
  155. details = protowire.AppendBytes(details, p.Cert)
  156. details = protowire.AppendTag(details, fieldInitiatorIndex, protowire.VarintType)
  157. details = protowire.AppendVarint(details, uint64(p.InitiatorIndex))
  158. details = protowire.AppendTag(details, fieldTime, protowire.VarintType)
  159. details = protowire.AppendVarint(details, p.Time)
  160. details = protowire.AppendTag(details, fieldCertVersion, protowire.VarintType)
  161. details = protowire.AppendVarint(details, uint64(p.CertVersion))
  162. assert.Equal(t, wrapDetails(details), MarshalPayload(nil, p))
  163. })
  164. t.Run("lane field with wrong wire type rejected", func(t *testing.T) {
  165. for _, field := range []protowire.Number{fieldInitiatorLanes, fieldResponderLanes} {
  166. var details []byte
  167. details = protowire.AppendTag(details, field, protowire.VarintType)
  168. details = protowire.AppendVarint(details, 1)
  169. _, err := UnmarshalPayload(wrapDetails(details))
  170. assert.Error(t, err)
  171. }
  172. })
  173. t.Run("lane subfield with wrong wire type rejected", func(t *testing.T) {
  174. var lane []byte
  175. lane = protowire.AppendTag(lane, fieldLanePortCount, protowire.BytesType)
  176. lane = protowire.AppendBytes(lane, []byte{1, 2, 3})
  177. var details []byte
  178. details = protowire.AppendTag(details, fieldInitiatorLanes, protowire.BytesType)
  179. details = protowire.AppendBytes(details, lane)
  180. _, err := UnmarshalPayload(wrapDetails(details))
  181. assert.Error(t, err)
  182. })
  183. t.Run("truncated LaneDetails submessage rejected", func(t *testing.T) {
  184. var details []byte
  185. details = protowire.AppendTag(details, fieldInitiatorLanes, protowire.BytesType)
  186. details = append(details, 0x0a, 0x01, 0x02) // length 10, only 2 bytes
  187. _, err := UnmarshalPayload(wrapDetails(details))
  188. assert.Error(t, err)
  189. })
  190. t.Run("truncated varint inside LaneDetails rejected", func(t *testing.T) {
  191. var lane []byte
  192. lane = protowire.AppendTag(lane, fieldLaneBasePort, protowire.VarintType)
  193. lane = append(lane, 0x80) // incomplete varint
  194. var details []byte
  195. details = protowire.AppendTag(details, fieldResponderLanes, protowire.BytesType)
  196. details = protowire.AppendBytes(details, lane)
  197. _, err := UnmarshalPayload(wrapDetails(details))
  198. assert.Error(t, err)
  199. })
  200. t.Run("lane subfield varint overflow rejected", func(t *testing.T) {
  201. var lane []byte
  202. lane = protowire.AppendTag(lane, fieldLaneLaneIndex, protowire.VarintType)
  203. lane = protowire.AppendVarint(lane, math.MaxUint32+1)
  204. var details []byte
  205. details = protowire.AppendTag(details, fieldInitiatorLanes, protowire.BytesType)
  206. details = protowire.AppendBytes(details, lane)
  207. _, err := UnmarshalPayload(wrapDetails(details))
  208. assert.Error(t, err)
  209. })
  210. }
  211. func TestPayloadBytesConsumed(t *testing.T) {
  212. t.Run("all bytes consumed on valid input", func(t *testing.T) {
  213. original := Payload{
  214. Cert: []byte("cert"),
  215. CertVersion: 2,
  216. InitiatorIndex: 100,
  217. ResponderIndex: 200,
  218. Time: 999,
  219. }
  220. data := MarshalPayload(nil, original)
  221. got, err := UnmarshalPayload(data)
  222. require.NoError(t, err)
  223. // Re-marshal and compare — proves we consumed and reproduced all fields
  224. remarshaled := MarshalPayload(nil, got)
  225. assert.Equal(t, data, remarshaled)
  226. })
  227. }
  228. // wrapDetails wraps raw detail bytes in the outer NebulaHandshake envelope
  229. // so UnmarshalPayload can reach unmarshalPayloadDetails.
  230. func wrapDetails(details []byte) []byte {
  231. var out []byte
  232. out = protowire.AppendTag(out, 1, protowire.BytesType)
  233. out = protowire.AppendBytes(out, details)
  234. return out
  235. }
  236. func TestPayloadUnmarshalErrors(t *testing.T) {
  237. t.Run("nil input", func(t *testing.T) {
  238. got, err := UnmarshalPayload(nil)
  239. require.NoError(t, err)
  240. assert.Equal(t, uint32(0), got.InitiatorIndex)
  241. })
  242. t.Run("truncated outer tag", func(t *testing.T) {
  243. _, err := UnmarshalPayload([]byte{0x80})
  244. assert.Error(t, err)
  245. })
  246. t.Run("truncated outer details field", func(t *testing.T) {
  247. _, err := UnmarshalPayload([]byte{0x0a, 0x64, 0x01, 0x02, 0x03, 0x04, 0x05})
  248. assert.Error(t, err)
  249. })
  250. t.Run("truncated outer unknown field", func(t *testing.T) {
  251. // Valid tag for unknown field 99 varint, but no value follows
  252. var data []byte
  253. data = protowire.AppendTag(data, 99, protowire.VarintType)
  254. _, err := UnmarshalPayload(data)
  255. assert.Error(t, err)
  256. })
  257. t.Run("truncated details tag", func(t *testing.T) {
  258. _, err := UnmarshalPayload(wrapDetails([]byte{0x80}))
  259. assert.Error(t, err)
  260. })
  261. t.Run("truncated cert bytes", func(t *testing.T) {
  262. // Field 1 (cert), bytes type, length 10 but only 2 bytes
  263. var details []byte
  264. details = protowire.AppendTag(details, fieldCert, protowire.BytesType)
  265. details = append(details, 0x0a, 0x01, 0x02) // length 10, only 2 bytes
  266. _, err := UnmarshalPayload(wrapDetails(details))
  267. assert.Error(t, err)
  268. })
  269. t.Run("truncated initiator index varint", func(t *testing.T) {
  270. var details []byte
  271. details = protowire.AppendTag(details, fieldInitiatorIndex, protowire.VarintType)
  272. details = append(details, 0x80) // incomplete varint
  273. _, err := UnmarshalPayload(wrapDetails(details))
  274. assert.Error(t, err)
  275. })
  276. t.Run("truncated responder index varint", func(t *testing.T) {
  277. var details []byte
  278. details = protowire.AppendTag(details, fieldResponderIndex, protowire.VarintType)
  279. details = append(details, 0x80)
  280. _, err := UnmarshalPayload(wrapDetails(details))
  281. assert.Error(t, err)
  282. })
  283. t.Run("truncated time varint", func(t *testing.T) {
  284. var details []byte
  285. details = protowire.AppendTag(details, fieldTime, protowire.VarintType)
  286. details = append(details, 0x80)
  287. _, err := UnmarshalPayload(wrapDetails(details))
  288. assert.Error(t, err)
  289. })
  290. t.Run("truncated cert version varint", func(t *testing.T) {
  291. var details []byte
  292. details = protowire.AppendTag(details, fieldCertVersion, protowire.VarintType)
  293. details = append(details, 0x80)
  294. _, err := UnmarshalPayload(wrapDetails(details))
  295. assert.Error(t, err)
  296. })
  297. t.Run("truncated unknown field in details", func(t *testing.T) {
  298. var details []byte
  299. details = protowire.AppendTag(details, 50, protowire.VarintType)
  300. details = append(details, 0x80) // incomplete varint
  301. _, err := UnmarshalPayload(wrapDetails(details))
  302. assert.Error(t, err)
  303. })
  304. t.Run("cert with wrong wire type rejected", func(t *testing.T) {
  305. // fieldCert as Varint instead of Bytes.
  306. var details []byte
  307. details = protowire.AppendTag(details, fieldCert, protowire.VarintType)
  308. details = protowire.AppendVarint(details, 42)
  309. _, err := UnmarshalPayload(wrapDetails(details))
  310. assert.Error(t, err)
  311. })
  312. t.Run("initiator index with wrong wire type rejected", func(t *testing.T) {
  313. // fieldInitiatorIndex as Bytes instead of Varint.
  314. var details []byte
  315. details = protowire.AppendTag(details, fieldInitiatorIndex, protowire.BytesType)
  316. details = protowire.AppendBytes(details, []byte{1, 2, 3})
  317. _, err := UnmarshalPayload(wrapDetails(details))
  318. assert.Error(t, err)
  319. })
  320. t.Run("time with wrong wire type rejected", func(t *testing.T) {
  321. var details []byte
  322. details = protowire.AppendTag(details, fieldTime, protowire.BytesType)
  323. details = protowire.AppendBytes(details, []byte{1, 2, 3})
  324. _, err := UnmarshalPayload(wrapDetails(details))
  325. assert.Error(t, err)
  326. })
  327. t.Run("cert version with wrong wire type rejected", func(t *testing.T) {
  328. var details []byte
  329. details = protowire.AppendTag(details, fieldCertVersion, protowire.BytesType)
  330. details = protowire.AppendBytes(details, []byte{1, 2, 3})
  331. _, err := UnmarshalPayload(wrapDetails(details))
  332. assert.Error(t, err)
  333. })
  334. t.Run("repeated singular field follows proto3 last-wins", func(t *testing.T) {
  335. // Per proto3, multiple instances of a singular field are accepted and
  336. // the last value wins. We keep this behavior so that peers using
  337. // alternative encoders aren't rejected.
  338. var details []byte
  339. details = protowire.AppendTag(details, fieldInitiatorIndex, protowire.VarintType)
  340. details = protowire.AppendVarint(details, 1)
  341. details = protowire.AppendTag(details, fieldInitiatorIndex, protowire.VarintType)
  342. details = protowire.AppendVarint(details, 42)
  343. got, err := UnmarshalPayload(wrapDetails(details))
  344. require.NoError(t, err)
  345. assert.Equal(t, uint32(42), got.InitiatorIndex)
  346. })
  347. t.Run("initiator index varint overflow rejected", func(t *testing.T) {
  348. var details []byte
  349. details = protowire.AppendTag(details, fieldInitiatorIndex, protowire.VarintType)
  350. details = protowire.AppendVarint(details, math.MaxUint32+1)
  351. _, err := UnmarshalPayload(wrapDetails(details))
  352. assert.Error(t, err)
  353. })
  354. t.Run("cert version varint overflow rejected", func(t *testing.T) {
  355. var details []byte
  356. details = protowire.AppendTag(details, fieldCertVersion, protowire.VarintType)
  357. details = protowire.AppendVarint(details, math.MaxUint32+1)
  358. _, err := UnmarshalPayload(wrapDetails(details))
  359. assert.Error(t, err)
  360. })
  361. }
  362. // FuzzPayload feeds arbitrary bytes through UnmarshalPayload to confirm it
  363. // never panics, and for any input that parses cleanly, that re-marshal +
  364. // re-parse is a fix-point. Inputs come from an authenticated peer (post-
  365. // noise-decrypt), so the threat model is "valid peer behaving arbitrarily,"
  366. // not "unauthenticated injection."
  367. func FuzzPayload(f *testing.F) {
  368. // Seed corpus with a handful of known-good shapes.
  369. f.Add(MarshalPayload(nil, Payload{}))
  370. f.Add(MarshalPayload(nil, Payload{Cert: []byte{1, 2, 3}, CertVersion: 2}))
  371. f.Add(MarshalPayload(nil, Payload{InitiatorIndex: 42, Time: 1}))
  372. f.Add(MarshalPayload(nil, Payload{
  373. Cert: []byte("seed-cert"),
  374. InitiatorIndex: 1,
  375. ResponderIndex: 2,
  376. Time: 3,
  377. CertVersion: 2,
  378. }))
  379. f.Add(MarshalPayload(nil, Payload{
  380. InitiatorIndex: 1,
  381. Time: 3,
  382. InitiatorLanes: &LaneDetails{PortCount: 8, BasePort: 4242, LaneIndex: 2},
  383. ResponderLanes: &LaneDetails{PortCount: 4, BasePort: 5353},
  384. }))
  385. f.Add([]byte{})
  386. f.Add([]byte{0xff})
  387. f.Fuzz(func(t *testing.T, data []byte) {
  388. p1, err := UnmarshalPayload(data)
  389. if err != nil {
  390. return
  391. }
  392. // For any input that parses, re-marshaling and re-parsing must
  393. // yield an equivalent Payload. This catches dispatch bugs (e.g.
  394. // emitting a field on marshal that we don't accept on parse) and
  395. // any non-idempotent parsing behavior.
  396. b2 := MarshalPayload(nil, p1)
  397. p2, err := UnmarshalPayload(b2)
  398. if err != nil {
  399. t.Fatalf("re-parse of self-marshaled payload failed: %v\nintermediate: %x\n", err, b2)
  400. }
  401. if !payloadsEqual(p1, p2) {
  402. t.Fatalf("re-marshal not idempotent\nfirst: %+v\nsecond: %+v", p1, p2)
  403. }
  404. })
  405. }
  406. func payloadsEqual(a, b Payload) bool {
  407. return bytes.Equal(a.Cert, b.Cert) &&
  408. a.InitiatorIndex == b.InitiatorIndex &&
  409. a.ResponderIndex == b.ResponderIndex &&
  410. a.Time == b.Time &&
  411. a.CertVersion == b.CertVersion &&
  412. laneDetailsEqual(a.InitiatorLanes, b.InitiatorLanes) &&
  413. laneDetailsEqual(a.ResponderLanes, b.ResponderLanes)
  414. }
  415. func laneDetailsEqual(a, b *LaneDetails) bool {
  416. if a == nil || b == nil {
  417. return a == b
  418. }
  419. return *a == *b
  420. }