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:
ffranr
2024-05-03 18:36:00 +01:00
committed by Oliver Gugger
parent 17c0a70b07
commit af50694643
4 changed files with 596 additions and 76 deletions

View File

@@ -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)
}
})
}
}