• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 14878980754

07 May 2025 08:39AM UTC coverage: 58.572% (-10.4%) from 68.992%
14878980754

Pull #9787

github

web-flow
Merge 3caa1f985 into 67a40c90a
Pull Request #9787: graph+lnwire: start validating that extra lnwire msg bytes are valid TLV

21 of 36 new or added lines in 8 files covered. (58.33%)

28248 existing lines in 449 files now uncovered.

97391 of 166276 relevant lines covered (58.57%)

1.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.5
/lnwire/extra_bytes.go
1
package lnwire
2

3
import (
4
        "bytes"
5
        "fmt"
6
        "io"
7

8
        "github.com/lightningnetwork/lnd/fn/v2"
9
        "github.com/lightningnetwork/lnd/tlv"
10
)
11

12
// ExtraOpaqueData is the set of data that was appended to this message, some
13
// of which we may not actually know how to iterate or parse. By holding onto
14
// this data, we ensure that we're able to properly validate the set of
15
// signatures that cover these new fields, and ensure we're able to make
16
// upgrades to the network in a forwards compatible manner.
17
type ExtraOpaqueData []byte
18

19
// NewExtraOpaqueData creates a new ExtraOpaqueData instance from a tlv.TypeMap.
20
func NewExtraOpaqueData(tlvMap tlv.TypeMap) (ExtraOpaqueData, error) {
3✔
21
        // If the tlv map is empty, we'll want to mirror the behavior of
3✔
22
        // decoding an empty extra opaque data field (see Decode method).
3✔
23
        if len(tlvMap) == 0 {
6✔
24
                return make([]byte, 0), nil
3✔
25
        }
3✔
26

27
        // Convert the TLV map into a slice of records.
UNCOV
28
        records := TlvMapToRecords(tlvMap)
×
UNCOV
29

×
UNCOV
30
        // Encode the records into the extra data byte slice.
×
UNCOV
31
        return EncodeRecords(records)
×
32
}
33

34
// Encode attempts to encode the raw extra bytes into the passed io.Writer.
35
func (e *ExtraOpaqueData) Encode(w *bytes.Buffer) error {
3✔
36
        eBytes := []byte((*e)[:])
3✔
37
        if err := WriteBytes(w, eBytes); err != nil {
3✔
38
                return err
×
39
        }
×
40

41
        return nil
3✔
42
}
43

44
// Decode attempts to unpack the raw bytes encoded in the passed-in io.Reader as
45
// a set of extra opaque data.
46
func (e *ExtraOpaqueData) Decode(r io.Reader) error {
3✔
47
        // First, we'll attempt to read a set of bytes contained within the
3✔
48
        // passed io.Reader (if any exist).
3✔
49
        rawBytes, err := io.ReadAll(r)
3✔
50
        if err != nil {
3✔
51
                return err
×
52
        }
×
53

54
        // If we _do_ have some bytes, then we'll swap out our backing pointer.
55
        // This ensures that any struct that embeds this type will properly
56
        // store the bytes once this method exits.
57
        if len(rawBytes) > 0 {
6✔
58
                *e = rawBytes
3✔
59
        } else {
6✔
60
                *e = make([]byte, 0)
3✔
61
        }
3✔
62

63
        return nil
3✔
64
}
65

66
// ValidateTLV checks that the raw bytes that make up the ExtraOpaqueData
67
// instance are a valid TLV stream.
68
func (e *ExtraOpaqueData) ValidateTLV() error {
3✔
69
        tlvStream, err := tlv.NewStream()
3✔
70
        if err != nil {
3✔
NEW
71
                return err
×
NEW
72
        }
×
73

74
        // Ensure that the TLV stream is valid by attempting to decode it.
75
        _, err = tlvStream.DecodeWithParsedTypesP2P(bytes.NewReader(*e))
3✔
76
        if err != nil {
3✔
NEW
77
                return fmt.Errorf("invalid TLV stream: %w: %v", err, *e)
×
NEW
78
        }
×
79

80
        return nil
3✔
81
}
82

83
// PackRecords attempts to encode the set of tlv records into the target
84
// ExtraOpaqueData instance. The records will be encoded as a raw TLV stream
85
// and stored within the backing slice pointer.
86
func (e *ExtraOpaqueData) PackRecords(
87
        recordProducers ...tlv.RecordProducer) error {
3✔
88

3✔
89
        // Assemble all the records passed in series, then encode them.
3✔
90
        records := ProduceRecordsSorted(recordProducers...)
3✔
91
        encoded, err := EncodeRecords(records)
3✔
92
        if err != nil {
3✔
93
                return err
×
94
        }
×
95

96
        *e = encoded
3✔
97

3✔
98
        return nil
3✔
99
}
100

101
// ExtractRecords attempts to decode any types in the internal raw bytes as if
102
// it were a tlv stream. The set of raw parsed types is returned, and any
103
// passed records (if found in the stream) will be parsed into the proper
104
// tlv.Record.
105
func (e *ExtraOpaqueData) ExtractRecords(
106
        recordProducers ...tlv.RecordProducer) (tlv.TypeMap, error) {
3✔
107

3✔
108
        // First, assemble all the records passed in series.
3✔
109
        records := ProduceRecordsSorted(recordProducers...)
3✔
110
        extraBytesReader := bytes.NewReader(*e)
3✔
111

3✔
112
        // Since ExtraOpaqueData is provided by a potentially malicious peer,
3✔
113
        // pass it into the P2P decoding variant.
3✔
114
        return DecodeRecordsP2P(extraBytesReader, records...)
3✔
115
}
3✔
116

117
// RecordProducers parses ExtraOpaqueData into a slice of TLV record producers
118
// by interpreting it as a TLV map.
119
func (e *ExtraOpaqueData) RecordProducers() ([]tlv.RecordProducer, error) {
3✔
120
        var recordProducers []tlv.RecordProducer
3✔
121

3✔
122
        // If the instance is nil or empty, return an empty slice.
3✔
123
        if e == nil || len(*e) == 0 {
6✔
124
                return recordProducers, nil
3✔
125
        }
3✔
126

127
        // Parse the extra opaque data as a TLV map.
UNCOV
128
        tlvMap, err := e.ExtractRecords()
×
UNCOV
129
        if err != nil {
×
130
                return nil, err
×
131
        }
×
132

133
        // Convert the TLV map into a slice of record producers.
UNCOV
134
        records := TlvMapToRecords(tlvMap)
×
UNCOV
135

×
UNCOV
136
        return RecordsAsProducers(records), nil
×
137
}
138

139
// EncodeMessageExtraData encodes the given recordProducers into the given
140
// extraData.
141
func EncodeMessageExtraData(extraData *ExtraOpaqueData,
142
        recordProducers ...tlv.RecordProducer) error {
3✔
143

3✔
144
        // Treat extraData as a mutable reference.
3✔
145
        if extraData == nil {
3✔
146
                return fmt.Errorf("extra data cannot be nil")
×
147
        }
×
148

149
        // Pack in the series of TLV records into this message. The order we
150
        // pass them in doesn't matter, as the method will ensure that things
151
        // are all properly sorted.
152
        return extraData.PackRecords(recordProducers...)
3✔
153
}
154

155
// ParseAndExtractCustomRecords parses the given extra data into the passed-in
156
// records, then returns any remaining records split into custom records and
157
// extra data.
158
func ParseAndExtractCustomRecords(allExtraData ExtraOpaqueData,
159
        knownRecords ...tlv.RecordProducer) (CustomRecords,
160
        fn.Set[tlv.Type], ExtraOpaqueData, error) {
3✔
161

3✔
162
        extraDataTlvMap, err := allExtraData.ExtractRecords(knownRecords...)
3✔
163
        if err != nil {
3✔
UNCOV
164
                return nil, nil, nil, err
×
UNCOV
165
        }
×
166

167
        // Remove the known and now extracted records from the leftover extra
168
        // data map.
169
        parsedKnownRecords := make(fn.Set[tlv.Type], len(knownRecords))
3✔
170
        for _, producer := range knownRecords {
6✔
171
                r := producer.Record()
3✔
172

3✔
173
                // Only remove the records if it was parsed (remainder is nil).
3✔
174
                // We'll just store the type so we can tell the caller which
3✔
175
                // records were actually parsed fully.
3✔
176
                val, ok := extraDataTlvMap[r.Type()]
3✔
177
                if ok && val == nil {
6✔
178
                        parsedKnownRecords.Add(r.Type())
3✔
179
                        delete(extraDataTlvMap, r.Type())
3✔
180
                }
3✔
181
        }
182

183
        // Any records from the extra data TLV map which are in the custom
184
        // records TLV type range will be included in the custom records field
185
        // and removed from the extra data field.
186
        customRecordsTlvMap := make(tlv.TypeMap, len(extraDataTlvMap))
3✔
187
        for k, v := range extraDataTlvMap {
6✔
188
                // Skip records that are not in the custom records TLV type
3✔
189
                // range.
3✔
190
                if k < MinCustomRecordsTlvType {
3✔
UNCOV
191
                        continue
×
192
                }
193

194
                // Include the record in the custom records map.
195
                customRecordsTlvMap[k] = v
3✔
196

3✔
197
                // Now that the record is included in the custom records map,
3✔
198
                // we can remove it from the extra data TLV map.
3✔
199
                delete(extraDataTlvMap, k)
3✔
200
        }
201

202
        // Set the custom records field to the custom records specific TLV
203
        // record map.
204
        customRecords, err := NewCustomRecords(customRecordsTlvMap)
3✔
205
        if err != nil {
3✔
206
                return nil, nil, nil, err
×
207
        }
×
208

209
        // Encode the remaining records back into the extra data field. These
210
        // records are not in the custom records TLV type range and do not
211
        // have associated fields in the struct that produced the records.
212
        extraData, err := NewExtraOpaqueData(extraDataTlvMap)
3✔
213
        if err != nil {
3✔
214
                return nil, nil, nil, err
×
215
        }
×
216

217
        // Help with unit testing where we might have the empty value (nil) for
218
        // the extra data instead of the default that's returned by the
219
        // constructor (empty slice).
220
        if len(extraData) == 0 {
6✔
221
                extraData = nil
3✔
222
        }
3✔
223

224
        return customRecords, parsedKnownRecords, extraData, nil
3✔
225
}
226

227
// MergeAndEncode merges the known records with the extra data and custom
228
// records, then encodes the merged records into raw bytes.
229
func MergeAndEncode(knownRecords []tlv.RecordProducer,
230
        extraData ExtraOpaqueData, customRecords CustomRecords) ([]byte,
231
        error) {
3✔
232

3✔
233
        // Construct a slice of all the records that we should include in the
3✔
234
        // message extra data field. We will start by including any records from
3✔
235
        // the extra data field.
3✔
236
        mergedRecords, err := extraData.RecordProducers()
3✔
237
        if err != nil {
3✔
238
                return nil, err
×
239
        }
×
240

241
        // Merge the known and extra data records.
242
        mergedRecords = append(mergedRecords, knownRecords...)
3✔
243

3✔
244
        // Include custom records in the extra data wire field if they are
3✔
245
        // present. Ensure that the custom records are validated before encoding
3✔
246
        // them.
3✔
247
        if err := customRecords.Validate(); err != nil {
3✔
UNCOV
248
                return nil, fmt.Errorf("custom records validation error: %w",
×
UNCOV
249
                        err)
×
UNCOV
250
        }
×
251

252
        // Extend the message extra data records slice with TLV records from the
253
        // custom records field.
254
        mergedRecords = append(
3✔
255
                mergedRecords, customRecords.RecordProducers()...,
3✔
256
        )
3✔
257

3✔
258
        // Now we can sort the records and make sure there are no records with
3✔
259
        // the same type that would collide when encoding.
3✔
260
        sortedRecords := ProduceRecordsSorted(mergedRecords...)
3✔
261
        if err := AssertUniqueTypes(sortedRecords); err != nil {
3✔
UNCOV
262
                return nil, err
×
UNCOV
263
        }
×
264

265
        return EncodeRecords(sortedRecords)
3✔
266
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc