1
0

payload_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  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("reserved fields 6 and 7 are skipped", func(t *testing.T) {
  97. // Fields 6 and 7 are reserved in the proto definition
  98. var details []byte
  99. details = protowire.AppendTag(details, fieldInitiatorIndex, protowire.VarintType)
  100. details = protowire.AppendVarint(details, 100)
  101. details = protowire.AppendTag(details, 6, protowire.VarintType)
  102. details = protowire.AppendVarint(details, 1)
  103. details = protowire.AppendTag(details, 7, protowire.VarintType)
  104. details = protowire.AppendVarint(details, 2)
  105. var data []byte
  106. data = protowire.AppendTag(data, 1, protowire.BytesType)
  107. data = protowire.AppendBytes(data, details)
  108. got, err := UnmarshalPayload(data)
  109. require.NoError(t, err)
  110. assert.Equal(t, uint32(100), got.InitiatorIndex)
  111. })
  112. }
  113. func TestPayloadBytesConsumed(t *testing.T) {
  114. t.Run("all bytes consumed on valid input", func(t *testing.T) {
  115. original := Payload{
  116. Cert: []byte("cert"),
  117. CertVersion: 2,
  118. InitiatorIndex: 100,
  119. ResponderIndex: 200,
  120. Time: 999,
  121. }
  122. data := MarshalPayload(nil, original)
  123. got, err := UnmarshalPayload(data)
  124. require.NoError(t, err)
  125. // Re-marshal and compare — proves we consumed and reproduced all fields
  126. remarshaled := MarshalPayload(nil, got)
  127. assert.Equal(t, data, remarshaled)
  128. })
  129. }
  130. // wrapDetails wraps raw detail bytes in the outer NebulaHandshake envelope
  131. // so UnmarshalPayload can reach unmarshalPayloadDetails.
  132. func wrapDetails(details []byte) []byte {
  133. var out []byte
  134. out = protowire.AppendTag(out, 1, protowire.BytesType)
  135. out = protowire.AppendBytes(out, details)
  136. return out
  137. }
  138. func TestPayloadUnmarshalErrors(t *testing.T) {
  139. t.Run("nil input", func(t *testing.T) {
  140. got, err := UnmarshalPayload(nil)
  141. require.NoError(t, err)
  142. assert.Equal(t, uint32(0), got.InitiatorIndex)
  143. })
  144. t.Run("truncated outer tag", func(t *testing.T) {
  145. _, err := UnmarshalPayload([]byte{0x80})
  146. assert.Error(t, err)
  147. })
  148. t.Run("truncated outer details field", func(t *testing.T) {
  149. _, err := UnmarshalPayload([]byte{0x0a, 0x64, 0x01, 0x02, 0x03, 0x04, 0x05})
  150. assert.Error(t, err)
  151. })
  152. t.Run("truncated outer unknown field", func(t *testing.T) {
  153. // Valid tag for unknown field 99 varint, but no value follows
  154. var data []byte
  155. data = protowire.AppendTag(data, 99, protowire.VarintType)
  156. _, err := UnmarshalPayload(data)
  157. assert.Error(t, err)
  158. })
  159. t.Run("truncated details tag", func(t *testing.T) {
  160. _, err := UnmarshalPayload(wrapDetails([]byte{0x80}))
  161. assert.Error(t, err)
  162. })
  163. t.Run("truncated cert bytes", func(t *testing.T) {
  164. // Field 1 (cert), bytes type, length 10 but only 2 bytes
  165. var details []byte
  166. details = protowire.AppendTag(details, fieldCert, protowire.BytesType)
  167. details = append(details, 0x0a, 0x01, 0x02) // length 10, only 2 bytes
  168. _, err := UnmarshalPayload(wrapDetails(details))
  169. assert.Error(t, err)
  170. })
  171. t.Run("truncated initiator index varint", func(t *testing.T) {
  172. var details []byte
  173. details = protowire.AppendTag(details, fieldInitiatorIndex, protowire.VarintType)
  174. details = append(details, 0x80) // incomplete varint
  175. _, err := UnmarshalPayload(wrapDetails(details))
  176. assert.Error(t, err)
  177. })
  178. t.Run("truncated responder index varint", func(t *testing.T) {
  179. var details []byte
  180. details = protowire.AppendTag(details, fieldResponderIndex, protowire.VarintType)
  181. details = append(details, 0x80)
  182. _, err := UnmarshalPayload(wrapDetails(details))
  183. assert.Error(t, err)
  184. })
  185. t.Run("truncated time varint", func(t *testing.T) {
  186. var details []byte
  187. details = protowire.AppendTag(details, fieldTime, protowire.VarintType)
  188. details = append(details, 0x80)
  189. _, err := UnmarshalPayload(wrapDetails(details))
  190. assert.Error(t, err)
  191. })
  192. t.Run("truncated cert version varint", func(t *testing.T) {
  193. var details []byte
  194. details = protowire.AppendTag(details, fieldCertVersion, protowire.VarintType)
  195. details = append(details, 0x80)
  196. _, err := UnmarshalPayload(wrapDetails(details))
  197. assert.Error(t, err)
  198. })
  199. t.Run("truncated unknown field in details", func(t *testing.T) {
  200. var details []byte
  201. details = protowire.AppendTag(details, 50, protowire.VarintType)
  202. details = append(details, 0x80) // incomplete varint
  203. _, err := UnmarshalPayload(wrapDetails(details))
  204. assert.Error(t, err)
  205. })
  206. t.Run("cert with wrong wire type rejected", func(t *testing.T) {
  207. // fieldCert as Varint instead of Bytes.
  208. var details []byte
  209. details = protowire.AppendTag(details, fieldCert, protowire.VarintType)
  210. details = protowire.AppendVarint(details, 42)
  211. _, err := UnmarshalPayload(wrapDetails(details))
  212. assert.Error(t, err)
  213. })
  214. t.Run("initiator index with wrong wire type rejected", func(t *testing.T) {
  215. // fieldInitiatorIndex as Bytes instead of Varint.
  216. var details []byte
  217. details = protowire.AppendTag(details, fieldInitiatorIndex, protowire.BytesType)
  218. details = protowire.AppendBytes(details, []byte{1, 2, 3})
  219. _, err := UnmarshalPayload(wrapDetails(details))
  220. assert.Error(t, err)
  221. })
  222. t.Run("time with wrong wire type rejected", func(t *testing.T) {
  223. var details []byte
  224. details = protowire.AppendTag(details, fieldTime, protowire.BytesType)
  225. details = protowire.AppendBytes(details, []byte{1, 2, 3})
  226. _, err := UnmarshalPayload(wrapDetails(details))
  227. assert.Error(t, err)
  228. })
  229. t.Run("cert version with wrong wire type rejected", func(t *testing.T) {
  230. var details []byte
  231. details = protowire.AppendTag(details, fieldCertVersion, protowire.BytesType)
  232. details = protowire.AppendBytes(details, []byte{1, 2, 3})
  233. _, err := UnmarshalPayload(wrapDetails(details))
  234. assert.Error(t, err)
  235. })
  236. t.Run("repeated singular field follows proto3 last-wins", func(t *testing.T) {
  237. // Per proto3, multiple instances of a singular field are accepted and
  238. // the last value wins. We keep this behavior so that peers using
  239. // alternative encoders aren't rejected.
  240. var details []byte
  241. details = protowire.AppendTag(details, fieldInitiatorIndex, protowire.VarintType)
  242. details = protowire.AppendVarint(details, 1)
  243. details = protowire.AppendTag(details, fieldInitiatorIndex, protowire.VarintType)
  244. details = protowire.AppendVarint(details, 42)
  245. got, err := UnmarshalPayload(wrapDetails(details))
  246. require.NoError(t, err)
  247. assert.Equal(t, uint32(42), got.InitiatorIndex)
  248. })
  249. t.Run("initiator index varint overflow rejected", func(t *testing.T) {
  250. var details []byte
  251. details = protowire.AppendTag(details, fieldInitiatorIndex, protowire.VarintType)
  252. details = protowire.AppendVarint(details, math.MaxUint32+1)
  253. _, err := UnmarshalPayload(wrapDetails(details))
  254. assert.Error(t, err)
  255. })
  256. t.Run("cert version varint overflow rejected", func(t *testing.T) {
  257. var details []byte
  258. details = protowire.AppendTag(details, fieldCertVersion, protowire.VarintType)
  259. details = protowire.AppendVarint(details, math.MaxUint32+1)
  260. _, err := UnmarshalPayload(wrapDetails(details))
  261. assert.Error(t, err)
  262. })
  263. }
  264. // FuzzPayload feeds arbitrary bytes through UnmarshalPayload to confirm it
  265. // never panics, and for any input that parses cleanly, that re-marshal +
  266. // re-parse is a fix-point. Inputs come from an authenticated peer (post-
  267. // noise-decrypt), so the threat model is "valid peer behaving arbitrarily,"
  268. // not "unauthenticated injection."
  269. func FuzzPayload(f *testing.F) {
  270. // Seed corpus with a handful of known-good shapes.
  271. f.Add(MarshalPayload(nil, Payload{}))
  272. f.Add(MarshalPayload(nil, Payload{Cert: []byte{1, 2, 3}, CertVersion: 2}))
  273. f.Add(MarshalPayload(nil, Payload{InitiatorIndex: 42, Time: 1}))
  274. f.Add(MarshalPayload(nil, Payload{
  275. Cert: []byte("seed-cert"),
  276. InitiatorIndex: 1,
  277. ResponderIndex: 2,
  278. Time: 3,
  279. CertVersion: 2,
  280. }))
  281. f.Add([]byte{})
  282. f.Add([]byte{0xff})
  283. f.Fuzz(func(t *testing.T, data []byte) {
  284. p1, err := UnmarshalPayload(data)
  285. if err != nil {
  286. return
  287. }
  288. // For any input that parses, re-marshaling and re-parsing must
  289. // yield an equivalent Payload. This catches dispatch bugs (e.g.
  290. // emitting a field on marshal that we don't accept on parse) and
  291. // any non-idempotent parsing behavior.
  292. b2 := MarshalPayload(nil, p1)
  293. p2, err := UnmarshalPayload(b2)
  294. if err != nil {
  295. t.Fatalf("re-parse of self-marshaled payload failed: %v\nintermediate: %x\n", err, b2)
  296. }
  297. if !payloadsEqual(p1, p2) {
  298. t.Fatalf("re-marshal not idempotent\nfirst: %+v\nsecond: %+v", p1, p2)
  299. }
  300. })
  301. }
  302. func payloadsEqual(a, b Payload) bool {
  303. return bytes.Equal(a.Cert, b.Cert) &&
  304. a.InitiatorIndex == b.InitiatorIndex &&
  305. a.ResponderIndex == b.ResponderIndex &&
  306. a.Time == b.Time &&
  307. a.CertVersion == b.CertVersion
  308. }