github.com/decred/dcrlnd@v0.7.6/htlcswitch/hop/payload_test.go (about) 1 package hop_test 2 3 import ( 4 "bytes" 5 "reflect" 6 "testing" 7 8 "github.com/decred/dcrlnd/htlcswitch/hop" 9 "github.com/decred/dcrlnd/lnwire" 10 "github.com/decred/dcrlnd/record" 11 "github.com/stretchr/testify/require" 12 ) 13 14 const testUnknownRequiredType = 0x10 15 16 type decodePayloadTest struct { 17 name string 18 payload []byte 19 expErr error 20 expCustomRecords map[uint64][]byte 21 shouldHaveMPP bool 22 shouldHaveAMP bool 23 } 24 25 var decodePayloadTests = []decodePayloadTest{ 26 { 27 name: "final hop valid", 28 payload: []byte{0x02, 0x00, 0x04, 0x00}, 29 }, 30 { 31 name: "intermediate hop valid", 32 payload: []byte{0x02, 0x00, 0x04, 0x00, 0x06, 0x08, 0x01, 0x00, 33 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 34 }, 35 }, 36 { 37 name: "final hop no amount", 38 payload: []byte{0x04, 0x00}, 39 expErr: hop.ErrInvalidPayload{ 40 Type: record.AmtOnionType, 41 Violation: hop.OmittedViolation, 42 FinalHop: true, 43 }, 44 }, 45 { 46 name: "intermediate hop no amount", 47 payload: []byte{0x04, 0x00, 0x06, 0x08, 0x01, 0x00, 0x00, 0x00, 48 0x00, 0x00, 0x00, 0x00, 49 }, 50 expErr: hop.ErrInvalidPayload{ 51 Type: record.AmtOnionType, 52 Violation: hop.OmittedViolation, 53 FinalHop: false, 54 }, 55 }, 56 { 57 name: "final hop no expiry", 58 payload: []byte{0x02, 0x00}, 59 expErr: hop.ErrInvalidPayload{ 60 Type: record.LockTimeOnionType, 61 Violation: hop.OmittedViolation, 62 FinalHop: true, 63 }, 64 }, 65 { 66 name: "intermediate hop no expiry", 67 payload: []byte{0x02, 0x00, 0x06, 0x08, 0x01, 0x00, 0x00, 0x00, 68 0x00, 0x00, 0x00, 0x00, 69 }, 70 expErr: hop.ErrInvalidPayload{ 71 Type: record.LockTimeOnionType, 72 Violation: hop.OmittedViolation, 73 FinalHop: false, 74 }, 75 }, 76 { 77 name: "final hop next sid present", 78 payload: []byte{0x02, 0x00, 0x04, 0x00, 0x06, 0x08, 0x00, 0x00, 79 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 80 }, 81 expErr: hop.ErrInvalidPayload{ 82 Type: record.NextHopOnionType, 83 Violation: hop.IncludedViolation, 84 FinalHop: true, 85 }, 86 }, 87 { 88 name: "required type after omitted hop id", 89 payload: []byte{ 90 0x02, 0x00, 0x04, 0x00, 91 testUnknownRequiredType, 0x00, 92 }, 93 expErr: hop.ErrInvalidPayload{ 94 Type: testUnknownRequiredType, 95 Violation: hop.RequiredViolation, 96 FinalHop: true, 97 }, 98 }, 99 { 100 name: "required type after included hop id", 101 payload: []byte{ 102 0x02, 0x00, 0x04, 0x00, 0x06, 0x08, 0x01, 0x00, 0x00, 103 0x00, 0x00, 0x00, 0x00, 0x00, 104 testUnknownRequiredType, 0x00, 105 }, 106 expErr: hop.ErrInvalidPayload{ 107 Type: testUnknownRequiredType, 108 Violation: hop.RequiredViolation, 109 FinalHop: false, 110 }, 111 }, 112 { 113 name: "required type zero final hop", 114 payload: []byte{0x00, 0x00, 0x02, 0x00, 0x04, 0x00}, 115 expErr: hop.ErrInvalidPayload{ 116 Type: 0, 117 Violation: hop.RequiredViolation, 118 FinalHop: true, 119 }, 120 }, 121 { 122 name: "required type zero final hop zero sid", 123 payload: []byte{0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x06, 0x08, 124 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 125 }, 126 expErr: hop.ErrInvalidPayload{ 127 Type: record.NextHopOnionType, 128 Violation: hop.IncludedViolation, 129 FinalHop: true, 130 }, 131 }, 132 { 133 name: "required type zero intermediate hop", 134 payload: []byte{0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x06, 0x08, 135 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 136 }, 137 expErr: hop.ErrInvalidPayload{ 138 Type: 0, 139 Violation: hop.RequiredViolation, 140 FinalHop: false, 141 }, 142 }, 143 { 144 name: "required type in custom range", 145 payload: []byte{0x02, 0x00, 0x04, 0x00, 146 0xfe, 0x00, 0x01, 0x00, 0x00, 0x02, 0x10, 0x11, 147 }, 148 expCustomRecords: map[uint64][]byte{ 149 65536: {0x10, 0x11}, 150 }, 151 }, 152 { 153 name: "valid intermediate hop", 154 payload: []byte{0x02, 0x00, 0x04, 0x00, 0x06, 0x08, 0x01, 0x00, 155 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 156 }, 157 expErr: nil, 158 }, 159 { 160 name: "valid final hop", 161 payload: []byte{0x02, 0x00, 0x04, 0x00}, 162 expErr: nil, 163 }, 164 { 165 name: "intermediate hop with mpp", 166 payload: []byte{ 167 // amount 168 0x02, 0x00, 169 // cltv 170 0x04, 0x00, 171 // next hop id 172 0x06, 0x08, 173 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 174 // mpp 175 0x08, 0x21, 176 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 177 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 178 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 179 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 180 0x08, 181 }, 182 expErr: hop.ErrInvalidPayload{ 183 Type: record.MPPOnionType, 184 Violation: hop.IncludedViolation, 185 FinalHop: false, 186 }, 187 }, 188 { 189 name: "intermediate hop with amp", 190 payload: []byte{ 191 // amount 192 0x02, 0x00, 193 // cltv 194 0x04, 0x00, 195 // next hop id 196 0x06, 0x08, 197 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 198 // amp 199 0x0e, 0x41, 200 // amp.root_share 201 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 202 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 203 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 204 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 205 // amp.set_id 206 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 207 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 208 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 209 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 210 // amp.child_index 211 0x09, 212 }, 213 expErr: hop.ErrInvalidPayload{ 214 Type: record.AMPOnionType, 215 Violation: hop.IncludedViolation, 216 FinalHop: false, 217 }, 218 }, 219 { 220 name: "final hop with mpp", 221 payload: []byte{ 222 // amount 223 0x02, 0x00, 224 // cltv 225 0x04, 0x00, 226 // mpp 227 0x08, 0x21, 228 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 229 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 230 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 231 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 232 0x08, 233 }, 234 expErr: nil, 235 shouldHaveMPP: true, 236 }, 237 { 238 name: "final hop with amp", 239 payload: []byte{ 240 // amount 241 0x02, 0x00, 242 // cltv 243 0x04, 0x00, 244 // amp 245 0x0e, 0x41, 246 // amp.root_share 247 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 248 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 249 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 250 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 251 // amp.set_id 252 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 253 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 254 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 255 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 256 // amp.child_index 257 0x09, 258 }, 259 shouldHaveAMP: true, 260 }, 261 } 262 263 // TestDecodeHopPayloadRecordValidation asserts that parsing the payloads in the 264 // tests yields the expected errors depending on whether the proper fields were 265 // included or omitted. 266 func TestDecodeHopPayloadRecordValidation(t *testing.T) { 267 for _, test := range decodePayloadTests { 268 t.Run(test.name, func(t *testing.T) { 269 testDecodeHopPayloadValidation(t, test) 270 }) 271 } 272 } 273 274 func testDecodeHopPayloadValidation(t *testing.T, test decodePayloadTest) { 275 var ( 276 testTotalMAtoms = lnwire.MilliAtom(8) 277 testAddr = [32]byte{ 278 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 279 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 280 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 281 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 282 } 283 284 testRootShare = [32]byte{ 285 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 286 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 287 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 288 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 289 } 290 testSetID = [32]byte{ 291 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 292 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 293 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 294 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 295 } 296 testChildIndex = uint32(9) 297 ) 298 299 p, err := hop.NewPayloadFromReader(bytes.NewReader(test.payload)) 300 if !reflect.DeepEqual(test.expErr, err) { 301 t.Fatalf("expected error mismatch, want: %v, got: %v", 302 test.expErr, err) 303 } 304 if err != nil { 305 return 306 } 307 308 // Assert MPP fields if we expect them. 309 if test.shouldHaveMPP { 310 if p.MPP == nil { 311 t.Fatalf("payload should have MPP record") 312 } 313 if p.MPP.TotalMAtoms() != testTotalMAtoms { 314 t.Fatalf("invalid total msat") 315 } 316 if p.MPP.PaymentAddr() != testAddr { 317 t.Fatalf("invalid payment addr") 318 } 319 } else if p.MPP != nil { 320 t.Fatalf("unexpected MPP payload") 321 } 322 323 if test.shouldHaveAMP { 324 if p.AMP == nil { 325 t.Fatalf("payload should have AMP record") 326 } 327 require.Equal(t, testRootShare, p.AMP.RootShare()) 328 require.Equal(t, testSetID, p.AMP.SetID()) 329 require.Equal(t, testChildIndex, p.AMP.ChildIndex()) 330 } else if p.AMP != nil { 331 t.Fatalf("unexpected AMP payload") 332 } 333 334 // Convert expected nil map to empty map, because we always expect an 335 // initiated map from the payload. 336 expCustomRecords := make(record.CustomSet) 337 if test.expCustomRecords != nil { 338 expCustomRecords = test.expCustomRecords 339 } 340 if !reflect.DeepEqual(expCustomRecords, p.CustomRecords()) { 341 t.Fatalf("invalid custom records") 342 } 343 }