github.com/decred/dcrlnd@v0.7.6/htlcswitch/hop/payload.go (about)

     1  package hop
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  	"io"
     7  
     8  	"github.com/decred/dcrlnd/lnwire"
     9  	"github.com/decred/dcrlnd/record"
    10  	"github.com/decred/dcrlnd/tlv"
    11  	sphinx "github.com/decred/lightning-onion/v4"
    12  )
    13  
    14  // PayloadViolation is an enum encapsulating the possible invalid payload
    15  // violations that can occur when processing or validating a payload.
    16  type PayloadViolation byte
    17  
    18  const (
    19  	// OmittedViolation indicates that a type was expected to be found the
    20  	// payload but was absent.
    21  	OmittedViolation PayloadViolation = iota
    22  
    23  	// IncludedViolation indicates that a type was expected to be omitted
    24  	// from the payload but was present.
    25  	IncludedViolation
    26  
    27  	// RequiredViolation indicates that an unknown even type was found in
    28  	// the payload that we could not process.
    29  	RequiredViolation
    30  )
    31  
    32  // String returns a human-readable description of the violation as a verb.
    33  func (v PayloadViolation) String() string {
    34  	switch v {
    35  	case OmittedViolation:
    36  		return "omitted"
    37  
    38  	case IncludedViolation:
    39  		return "included"
    40  
    41  	case RequiredViolation:
    42  		return "required"
    43  
    44  	default:
    45  		return "unknown violation"
    46  	}
    47  }
    48  
    49  // ErrInvalidPayload is an error returned when a parsed onion payload either
    50  // included or omitted incorrect records for a particular hop type.
    51  type ErrInvalidPayload struct {
    52  	// Type the record's type that cause the violation.
    53  	Type tlv.Type
    54  
    55  	// Violation is an enum indicating the type of violation detected in
    56  	// processing Type.
    57  	Violation PayloadViolation
    58  
    59  	// FinalHop if true, indicates that the violation is for the final hop
    60  	// in the route (identified by next hop id), otherwise the violation is
    61  	// for an intermediate hop.
    62  	FinalHop bool
    63  }
    64  
    65  // Error returns a human-readable description of the invalid payload error.
    66  func (e ErrInvalidPayload) Error() string {
    67  	hopType := "intermediate"
    68  	if e.FinalHop {
    69  		hopType = "final"
    70  	}
    71  
    72  	return fmt.Sprintf("onion payload for %s hop %v record with type %d",
    73  		hopType, e.Violation, e.Type)
    74  }
    75  
    76  // Payload encapsulates all information delivered to a hop in an onion payload.
    77  // A Hop can represent either a TLV or legacy payload. The primary forwarding
    78  // instruction can be accessed via ForwardingInfo, and additional records can be
    79  // accessed by other member functions.
    80  type Payload struct {
    81  	// FwdInfo holds the basic parameters required for HTLC forwarding, e.g.
    82  	// amount, cltv, and next hop.
    83  	FwdInfo ForwardingInfo
    84  
    85  	// MPP holds the info provided in an option_mpp record when parsed from
    86  	// a TLV onion payload.
    87  	MPP *record.MPP
    88  
    89  	// AMP holds the info provided in an option_amp record when parsed from
    90  	// a TLV onion payload.
    91  	AMP *record.AMP
    92  
    93  	// customRecords are user-defined records in the custom type range that
    94  	// were included in the payload.
    95  	customRecords record.CustomSet
    96  }
    97  
    98  // NewLegacyPayload builds a Payload from the amount, cltv, and next hop
    99  // parameters provided by leegacy onion payloads.
   100  func NewLegacyPayload(f *sphinx.HopData) *Payload {
   101  	nextHop := binary.BigEndian.Uint64(f.NextAddress[:])
   102  
   103  	return &Payload{
   104  		FwdInfo: ForwardingInfo{
   105  			Network:         DecredNetwork,
   106  			NextHop:         lnwire.NewShortChanIDFromInt(nextHop),
   107  			AmountToForward: lnwire.MilliAtom(f.ForwardAmount),
   108  			OutgoingCTLV:    f.OutgoingCltv,
   109  		},
   110  		customRecords: make(record.CustomSet),
   111  	}
   112  }
   113  
   114  // NewPayloadFromReader builds a new Hop from the passed io.Reader. The reader
   115  // should correspond to the bytes encapsulated in a TLV onion payload.
   116  func NewPayloadFromReader(r io.Reader) (*Payload, error) {
   117  	var (
   118  		cid  uint64
   119  		amt  uint64
   120  		cltv uint32
   121  		mpp  = &record.MPP{}
   122  		amp  = &record.AMP{}
   123  	)
   124  
   125  	tlvStream, err := tlv.NewStream(
   126  		record.NewAmtToFwdRecord(&amt),
   127  		record.NewLockTimeRecord(&cltv),
   128  		record.NewNextHopIDRecord(&cid),
   129  		mpp.Record(),
   130  		amp.Record(),
   131  	)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	parsedTypes, err := tlvStream.DecodeWithParsedTypes(r)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	// Validate whether the sender properly included or omitted tlv records
   142  	// in accordance with BOLT 04.
   143  	nextHop := lnwire.NewShortChanIDFromInt(cid)
   144  	err = ValidateParsedPayloadTypes(parsedTypes, nextHop)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	// Check for violation of the rules for mandatory fields.
   150  	violatingType := getMinRequiredViolation(parsedTypes)
   151  	if violatingType != nil {
   152  		return nil, ErrInvalidPayload{
   153  			Type:      *violatingType,
   154  			Violation: RequiredViolation,
   155  			FinalHop:  nextHop == Exit,
   156  		}
   157  	}
   158  
   159  	// If no MPP field was parsed, set the MPP field on the resulting
   160  	// payload to nil.
   161  	if _, ok := parsedTypes[record.MPPOnionType]; !ok {
   162  		mpp = nil
   163  	}
   164  
   165  	// If no AMP field was parsed, set the MPP field on the resulting
   166  	// payload to nil.
   167  	if _, ok := parsedTypes[record.AMPOnionType]; !ok {
   168  		amp = nil
   169  	}
   170  
   171  	// Filter out the custom records.
   172  	customRecords := NewCustomRecords(parsedTypes)
   173  
   174  	return &Payload{
   175  		FwdInfo: ForwardingInfo{
   176  			Network:         DecredNetwork,
   177  			NextHop:         nextHop,
   178  			AmountToForward: lnwire.MilliAtom(amt),
   179  			OutgoingCTLV:    cltv,
   180  		},
   181  		MPP:           mpp,
   182  		AMP:           amp,
   183  		customRecords: customRecords,
   184  	}, nil
   185  }
   186  
   187  // ForwardingInfo returns the basic parameters required for HTLC forwarding,
   188  // e.g. amount, cltv, and next hop.
   189  func (h *Payload) ForwardingInfo() ForwardingInfo {
   190  	return h.FwdInfo
   191  }
   192  
   193  // NewCustomRecords filters the types parsed from the tlv stream for custom
   194  // records.
   195  func NewCustomRecords(parsedTypes tlv.TypeMap) record.CustomSet {
   196  	customRecords := make(record.CustomSet)
   197  	for t, parseResult := range parsedTypes {
   198  		if parseResult == nil || t < record.CustomTypeStart {
   199  			continue
   200  		}
   201  		customRecords[uint64(t)] = parseResult
   202  	}
   203  	return customRecords
   204  }
   205  
   206  // ValidateParsedPayloadTypes checks the types parsed from a hop payload to
   207  // ensure that the proper fields are either included or omitted. The finalHop
   208  // boolean should be true if the payload was parsed for an exit hop. The
   209  // requirements for this method are described in BOLT 04.
   210  func ValidateParsedPayloadTypes(parsedTypes tlv.TypeMap,
   211  	nextHop lnwire.ShortChannelID) error {
   212  
   213  	isFinalHop := nextHop == Exit
   214  
   215  	_, hasAmt := parsedTypes[record.AmtOnionType]
   216  	_, hasLockTime := parsedTypes[record.LockTimeOnionType]
   217  	_, hasNextHop := parsedTypes[record.NextHopOnionType]
   218  	_, hasMPP := parsedTypes[record.MPPOnionType]
   219  	_, hasAMP := parsedTypes[record.AMPOnionType]
   220  
   221  	switch {
   222  
   223  	// All hops must include an amount to forward.
   224  	case !hasAmt:
   225  		return ErrInvalidPayload{
   226  			Type:      record.AmtOnionType,
   227  			Violation: OmittedViolation,
   228  			FinalHop:  isFinalHop,
   229  		}
   230  
   231  	// All hops must include a cltv expiry.
   232  	case !hasLockTime:
   233  		return ErrInvalidPayload{
   234  			Type:      record.LockTimeOnionType,
   235  			Violation: OmittedViolation,
   236  			FinalHop:  isFinalHop,
   237  		}
   238  
   239  	// The exit hop should omit the next hop id. If nextHop != Exit, the
   240  	// sender must have included a record, so we don't need to test for its
   241  	// inclusion at intermediate hops directly.
   242  	case isFinalHop && hasNextHop:
   243  		return ErrInvalidPayload{
   244  			Type:      record.NextHopOnionType,
   245  			Violation: IncludedViolation,
   246  			FinalHop:  true,
   247  		}
   248  
   249  	// Intermediate nodes should never receive MPP fields.
   250  	case !isFinalHop && hasMPP:
   251  		return ErrInvalidPayload{
   252  			Type:      record.MPPOnionType,
   253  			Violation: IncludedViolation,
   254  			FinalHop:  isFinalHop,
   255  		}
   256  
   257  	// Intermediate nodes should never receive AMP fields.
   258  	case !isFinalHop && hasAMP:
   259  		return ErrInvalidPayload{
   260  			Type:      record.AMPOnionType,
   261  			Violation: IncludedViolation,
   262  			FinalHop:  isFinalHop,
   263  		}
   264  	}
   265  
   266  	return nil
   267  }
   268  
   269  // MultiPath returns the record corresponding the option_mpp parsed from the
   270  // onion payload.
   271  func (h *Payload) MultiPath() *record.MPP {
   272  	return h.MPP
   273  }
   274  
   275  // AMPRecord returns the record corresponding with option_amp parsed from the
   276  // onion payload.
   277  func (h *Payload) AMPRecord() *record.AMP {
   278  	return h.AMP
   279  }
   280  
   281  // CustomRecords returns the custom tlv type records that were parsed from the
   282  // payload.
   283  func (h *Payload) CustomRecords() record.CustomSet {
   284  	return h.customRecords
   285  }
   286  
   287  // getMinRequiredViolation checks for unrecognized required (even) fields in the
   288  // standard range and returns the lowest required type. Always returning the
   289  // lowest required type allows a failure message to be deterministic.
   290  func getMinRequiredViolation(set tlv.TypeMap) *tlv.Type {
   291  	var (
   292  		requiredViolation        bool
   293  		minRequiredViolationType tlv.Type
   294  	)
   295  	for t, parseResult := range set {
   296  		// If a type is even but not known to us, we cannot process the
   297  		// payload. We are required to understand a field that we don't
   298  		// support.
   299  		//
   300  		// We always accept custom fields, because a higher level
   301  		// application may understand them.
   302  		if parseResult == nil || t%2 != 0 ||
   303  			t >= record.CustomTypeStart {
   304  
   305  			continue
   306  		}
   307  
   308  		if !requiredViolation || t < minRequiredViolationType {
   309  			minRequiredViolationType = t
   310  		}
   311  		requiredViolation = true
   312  	}
   313  
   314  	if requiredViolation {
   315  		return &minRequiredViolationType
   316  	}
   317  
   318  	return nil
   319  }