mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-08-28 06:32:18 +02:00
lnwire: add ExtraOpaqueData
helper functions and methods
Introduces a couple of new helper functions for both the ExtraOpaqueData and CustomRecords types along with new methods on the ExtraOpaqueData.
This commit is contained in:
@@ -11,6 +11,12 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
tlvType1 tlv.TlvType1
|
||||
tlvType2 tlv.TlvType2
|
||||
tlvType3 tlv.TlvType3
|
||||
)
|
||||
|
||||
// TestExtraOpaqueDataEncodeDecode tests that we're able to encode/decode
|
||||
// arbitrary payloads.
|
||||
func TestExtraOpaqueDataEncodeDecode(t *testing.T) {
|
||||
@@ -153,21 +159,18 @@ func TestPackRecords(t *testing.T) {
|
||||
|
||||
var (
|
||||
// Record type 1.
|
||||
tlvType1 tlv.TlvType1
|
||||
recordBytes1 = []byte("recordBytes1")
|
||||
tlvRecord1 = tlv.NewPrimitiveRecord[tlv.TlvType1](
|
||||
recordBytes1,
|
||||
)
|
||||
|
||||
// Record type 2.
|
||||
tlvType2 tlv.TlvType2
|
||||
recordBytes2 = []byte("recordBytes2")
|
||||
tlvRecord2 = tlv.NewPrimitiveRecord[tlv.TlvType2](
|
||||
recordBytes2,
|
||||
)
|
||||
|
||||
// Record type 3.
|
||||
tlvType3 tlv.TlvType3
|
||||
recordBytes3 = []byte("recordBytes3")
|
||||
tlvRecord3 = tlv.NewPrimitiveRecord[tlv.TlvType3](
|
||||
recordBytes3,
|
||||
@@ -203,3 +206,308 @@ func TestPackRecords(t *testing.T) {
|
||||
require.Equal(t, recordBytes2, extractedRecords[tlvType2.TypeVal()])
|
||||
require.Equal(t, recordBytes3, extractedRecords[tlvType3.TypeVal()])
|
||||
}
|
||||
|
||||
type dummyRecordProducer struct {
|
||||
typ tlv.Type
|
||||
scratchValue []byte
|
||||
expectedValue []byte
|
||||
}
|
||||
|
||||
func (d *dummyRecordProducer) Record() tlv.Record {
|
||||
return tlv.MakePrimitiveRecord(d.typ, &d.scratchValue)
|
||||
}
|
||||
|
||||
// TestExtraOpaqueData tests that we're able to properly encode/decode an
|
||||
// ExtraOpaqueData instance.
|
||||
func TestExtraOpaqueData(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
types tlv.TypeMap
|
||||
expectedData ExtraOpaqueData
|
||||
expectedTypes tlv.TypeMap
|
||||
decoders []tlv.RecordProducer
|
||||
}{
|
||||
{
|
||||
name: "empty map",
|
||||
expectedTypes: tlv.TypeMap{},
|
||||
expectedData: make([]byte, 0),
|
||||
},
|
||||
{
|
||||
name: "single record",
|
||||
types: tlv.TypeMap{
|
||||
tlvType1.TypeVal(): []byte{1, 2, 3},
|
||||
},
|
||||
expectedData: ExtraOpaqueData{
|
||||
0x01, 0x03, 1, 2, 3,
|
||||
},
|
||||
expectedTypes: tlv.TypeMap{
|
||||
tlvType1.TypeVal(): []byte{1, 2, 3},
|
||||
},
|
||||
decoders: []tlv.RecordProducer{
|
||||
&dummyRecordProducer{
|
||||
typ: tlvType1.TypeVal(),
|
||||
expectedValue: []byte{1, 2, 3},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple records",
|
||||
types: tlv.TypeMap{
|
||||
tlvType2.TypeVal(): []byte{4, 5, 6},
|
||||
tlvType1.TypeVal(): []byte{1, 2, 3},
|
||||
},
|
||||
expectedData: ExtraOpaqueData{
|
||||
0x01, 0x03, 1, 2, 3,
|
||||
0x02, 0x03, 4, 5, 6,
|
||||
},
|
||||
expectedTypes: tlv.TypeMap{
|
||||
tlvType1.TypeVal(): []byte{1, 2, 3},
|
||||
tlvType2.TypeVal(): []byte{4, 5, 6},
|
||||
},
|
||||
decoders: []tlv.RecordProducer{
|
||||
&dummyRecordProducer{
|
||||
typ: tlvType1.TypeVal(),
|
||||
expectedValue: []byte{1, 2, 3},
|
||||
},
|
||||
&dummyRecordProducer{
|
||||
typ: tlvType2.TypeVal(),
|
||||
expectedValue: []byte{4, 5, 6},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// First, test the constructor.
|
||||
opaqueData, err := NewExtraOpaqueData(tc.types)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, tc.expectedData, opaqueData)
|
||||
|
||||
// Now encode/decode.
|
||||
var b bytes.Buffer
|
||||
err = opaqueData.Encode(&b)
|
||||
require.NoError(t, err)
|
||||
|
||||
var decoded ExtraOpaqueData
|
||||
err = decoded.Decode(&b)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, opaqueData, decoded)
|
||||
|
||||
// Now RecordProducers/PackRecords.
|
||||
producers, err := opaqueData.RecordProducers()
|
||||
require.NoError(t, err)
|
||||
|
||||
var packed ExtraOpaqueData
|
||||
err = packed.PackRecords(producers...)
|
||||
require.NoError(t, err)
|
||||
|
||||
// PackRecords returns nil vs. an empty slice if there
|
||||
// are no records. We need to handle this case
|
||||
// separately.
|
||||
if len(producers) == 0 {
|
||||
// Make sure the packed data is empty.
|
||||
require.Empty(t, packed)
|
||||
|
||||
// Now change it to an empty slice for the
|
||||
// comparison below.
|
||||
packed = make([]byte, 0)
|
||||
}
|
||||
require.Equal(t, opaqueData, packed)
|
||||
|
||||
// ExtractRecords with an empty set of record producers
|
||||
// should return the original type map.
|
||||
extracted, err := opaqueData.ExtractRecords()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, tc.expectedTypes, extracted)
|
||||
|
||||
if len(tc.decoders) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// ExtractRecords with a set of record producers should
|
||||
// only return the types that weren't in the passed-in
|
||||
// set of producers.
|
||||
extracted, err = opaqueData.ExtractRecords(
|
||||
tc.decoders...,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
for parsedType := range tc.expectedTypes {
|
||||
remainder, ok := extracted[parsedType]
|
||||
require.True(t, ok)
|
||||
require.Nil(t, remainder)
|
||||
}
|
||||
|
||||
for _, dec := range tc.decoders {
|
||||
//nolint:forcetypeassert
|
||||
dec := dec.(*dummyRecordProducer)
|
||||
require.Equal(
|
||||
t, dec.expectedValue, dec.scratchValue,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestExtractAndMerge tests that the ParseAndExtractCustomRecords and
|
||||
// MergeAndEncode functions work as expected.
|
||||
func TestExtractAndMerge(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
knownRecords []tlv.RecordProducer
|
||||
extraData ExtraOpaqueData
|
||||
customRecords CustomRecords
|
||||
expectedErr string
|
||||
expectEncoded []byte
|
||||
}{
|
||||
{
|
||||
name: "invalid custom record",
|
||||
customRecords: CustomRecords{
|
||||
123: []byte("invalid"),
|
||||
},
|
||||
expectedErr: "custom records validation error",
|
||||
},
|
||||
{
|
||||
name: "empty everything",
|
||||
},
|
||||
{
|
||||
name: "just extra data",
|
||||
extraData: ExtraOpaqueData{
|
||||
0x01, 0x03, 1, 2, 3,
|
||||
0x02, 0x03, 4, 5, 6,
|
||||
},
|
||||
expectEncoded: []byte{
|
||||
0x01, 0x03, 1, 2, 3,
|
||||
0x02, 0x03, 4, 5, 6,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "extra data with known record",
|
||||
extraData: ExtraOpaqueData{
|
||||
0x04, 0x03, 4, 4, 4,
|
||||
0x05, 0x03, 5, 5, 5,
|
||||
},
|
||||
knownRecords: []tlv.RecordProducer{
|
||||
&dummyRecordProducer{
|
||||
typ: tlvType1.TypeVal(),
|
||||
scratchValue: []byte{1, 2, 3},
|
||||
expectedValue: []byte{1, 2, 3},
|
||||
},
|
||||
&dummyRecordProducer{
|
||||
typ: tlvType2.TypeVal(),
|
||||
scratchValue: []byte{4, 5, 6},
|
||||
expectedValue: []byte{4, 5, 6},
|
||||
},
|
||||
},
|
||||
expectEncoded: []byte{
|
||||
0x01, 0x03, 1, 2, 3,
|
||||
0x02, 0x03, 4, 5, 6,
|
||||
0x04, 0x03, 4, 4, 4,
|
||||
0x05, 0x03, 5, 5, 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "extra data and custom records with known record",
|
||||
extraData: ExtraOpaqueData{
|
||||
0x04, 0x03, 4, 4, 4,
|
||||
0x05, 0x03, 5, 5, 5,
|
||||
},
|
||||
customRecords: CustomRecords{
|
||||
MinCustomRecordsTlvType + 1: []byte{99, 99, 99},
|
||||
},
|
||||
knownRecords: []tlv.RecordProducer{
|
||||
&dummyRecordProducer{
|
||||
typ: tlvType1.TypeVal(),
|
||||
scratchValue: []byte{1, 2, 3},
|
||||
expectedValue: []byte{1, 2, 3},
|
||||
},
|
||||
&dummyRecordProducer{
|
||||
typ: tlvType2.TypeVal(),
|
||||
scratchValue: []byte{4, 5, 6},
|
||||
expectedValue: []byte{4, 5, 6},
|
||||
},
|
||||
},
|
||||
expectEncoded: []byte{
|
||||
0x01, 0x03, 1, 2, 3,
|
||||
0x02, 0x03, 4, 5, 6,
|
||||
0x04, 0x03, 4, 4, 4,
|
||||
0x05, 0x03, 5, 5, 5,
|
||||
0xfe, 0x0, 0x1, 0x0, 0x1, 0x3, 0x63, 0x63, 0x63,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "duplicate records",
|
||||
extraData: ExtraOpaqueData{
|
||||
0x01, 0x03, 4, 4, 4,
|
||||
0x05, 0x03, 5, 5, 5,
|
||||
},
|
||||
customRecords: CustomRecords{
|
||||
MinCustomRecordsTlvType + 1: []byte{99, 99, 99},
|
||||
},
|
||||
knownRecords: []tlv.RecordProducer{
|
||||
&dummyRecordProducer{
|
||||
typ: tlvType1.TypeVal(),
|
||||
scratchValue: []byte{1, 2, 3},
|
||||
expectedValue: []byte{1, 2, 3},
|
||||
},
|
||||
&dummyRecordProducer{
|
||||
typ: tlvType2.TypeVal(),
|
||||
scratchValue: []byte{4, 5, 6},
|
||||
expectedValue: []byte{4, 5, 6},
|
||||
},
|
||||
},
|
||||
expectedErr: "duplicate record type: 1",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
encoded, err := MergeAndEncode(
|
||||
tc.knownRecords, tc.extraData, tc.customRecords,
|
||||
)
|
||||
|
||||
if tc.expectedErr != "" {
|
||||
require.ErrorContains(t, err, tc.expectedErr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expectEncoded, encoded)
|
||||
|
||||
// Clear all the scratch values, to make sure they're
|
||||
// decoded from the data again.
|
||||
for _, dec := range tc.knownRecords {
|
||||
//nolint:forcetypeassert
|
||||
dec := dec.(*dummyRecordProducer)
|
||||
dec.scratchValue = nil
|
||||
}
|
||||
|
||||
pCR, pKR, pED, err := ParseAndExtractCustomRecords(
|
||||
encoded, tc.knownRecords...,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, tc.customRecords, pCR)
|
||||
require.Equal(t, tc.extraData, pED)
|
||||
|
||||
for _, dec := range tc.knownRecords {
|
||||
//nolint:forcetypeassert
|
||||
dec := dec.(*dummyRecordProducer)
|
||||
require.Equal(
|
||||
t, dec.expectedValue, dec.scratchValue,
|
||||
)
|
||||
|
||||
require.Contains(t, pKR, dec.typ)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user