lnwire: update and document message.go
This commit is contained in:
parent
0563205e6d
commit
a30831aef8
@ -9,55 +9,65 @@ import (
|
||||
"github.com/roasbeef/btcd/wire"
|
||||
)
|
||||
|
||||
// 4-byte network + 4-byte message id + payload-length 4-byte
|
||||
// MessageHeaderSize is the number of bytes in a lightning message header.
|
||||
// The bytes are allocated as follows: network magic 4 bytes + command 4
|
||||
// bytes + payload length 4 bytes. Note that a checksum is omitted as lightning
|
||||
// messages are assumed to be transmitted over an AEAD secured connection which
|
||||
// provides integrity over the entire message.
|
||||
const MessageHeaderSize = 12
|
||||
|
||||
// MaxMessagePayload is the maximum bytes a message can be regardless of other
|
||||
// individual limits imposed by messages themselves.
|
||||
const MaxMessagePayload = 1024 * 1024 * 32 // 32MB
|
||||
|
||||
// Commands used in lightning message headers which detail the type of message.
|
||||
const (
|
||||
// Funding channel open
|
||||
// Commands for opening a channel funded by both parties (dual funder).
|
||||
CmdFundingRequest = uint32(200)
|
||||
CmdFundingResponse = uint32(210)
|
||||
CmdFundingSignAccept = uint32(220)
|
||||
CmdFundingSignComplete = uint32(230)
|
||||
|
||||
// Close channel
|
||||
// Commands for opening a channel funded by one party (single funder).
|
||||
CmdSingleFundingRequest = uint32(100)
|
||||
CmdSingleFundingResponse = uint32(110)
|
||||
CmdSingleFundingComplete = uint32(120)
|
||||
CmdSingleFundingSignComplete = uint32(130)
|
||||
CmdSingleFundingOpenProof = uint32(140)
|
||||
|
||||
// Commands for the workflow of cooperatively closing an active channel.
|
||||
CmdCloseRequest = uint32(300)
|
||||
CmdCloseComplete = uint32(310)
|
||||
|
||||
// TODO Renumber to 1100
|
||||
// HTLC payment
|
||||
CmdHTLCAddRequest = uint32(1000)
|
||||
CmdHTLCAddAccept = uint32(1010)
|
||||
CmdHTLCAddReject = uint32(1020)
|
||||
|
||||
// TODO Renumber to 1200
|
||||
// HTLC settlement
|
||||
CmdHTLCSettleRequest = uint32(1100)
|
||||
CmdHTLCSettleAccept = uint32(1110)
|
||||
|
||||
// HTLC timeout
|
||||
// Commands for negotiating HTLCs.
|
||||
CmdHTLCAddRequest = uint32(1000)
|
||||
CmdHTLCAddAccept = uint32(1010)
|
||||
CmdHTLCAddReject = uint32(1020)
|
||||
CmdHTLCSettleRequest = uint32(1100)
|
||||
CmdHTLCTimeoutRequest = uint32(1300)
|
||||
CmdHTLCTimeoutAccept = uint32(1310)
|
||||
|
||||
// Commitments
|
||||
// Commands for modifying commitment transactions.
|
||||
CmdCommitSignature = uint32(2000)
|
||||
CmdCommitRevocation = uint32(2010)
|
||||
|
||||
// Error
|
||||
// Commands for reporting protocol errors.
|
||||
CmdErrorGeneric = uint32(4000)
|
||||
)
|
||||
|
||||
// Every message has these functions:
|
||||
// Message is an interface that defines a lightning wire protocol message. The
|
||||
// interface is general in order to allow implementing types full control over
|
||||
// the representation of its data.
|
||||
type Message interface {
|
||||
Decode(io.Reader, uint32) error // (io, protocol version)
|
||||
Encode(io.Writer, uint32) error // (io, protocol version)
|
||||
Command() uint32 // returns ID of the message
|
||||
MaxPayloadLength(uint32) uint32 // (version) maxpayloadsize
|
||||
Validate() error // Validates the data struct
|
||||
Decode(io.Reader, uint32) error
|
||||
Encode(io.Writer, uint32) error
|
||||
Command() uint32
|
||||
MaxPayloadLength(uint32) uint32
|
||||
Validate() error
|
||||
String() string
|
||||
}
|
||||
|
||||
// makeEmptyMessage creates a new empty message of the proper concrete type
|
||||
// based on the command ID.
|
||||
func makeEmptyMessage(command uint32) (Message, error) {
|
||||
var msg Message
|
||||
|
||||
@ -70,24 +80,28 @@ func makeEmptyMessage(command uint32) (Message, error) {
|
||||
msg = &FundingSignAccept{}
|
||||
case CmdFundingSignComplete:
|
||||
msg = &FundingSignComplete{}
|
||||
case CmdSingleFundingRequest:
|
||||
msg = &SingleFundingRequest{}
|
||||
case CmdSingleFundingResponse:
|
||||
msg = &SingleFundingResponse{}
|
||||
case CmdSingleFundingComplete:
|
||||
msg = &SingleFundingComplete{}
|
||||
case CmdSingleFundingSignComplete:
|
||||
msg = &SingleFundingSignComplete{}
|
||||
case CmdSingleFundingOpenProof:
|
||||
msg = &SingleFundingOpenProof{}
|
||||
case CmdCloseRequest:
|
||||
msg = &CloseRequest{}
|
||||
case CmdCloseComplete:
|
||||
msg = &CloseComplete{}
|
||||
case CmdHTLCAddRequest:
|
||||
msg = &HTLCAddRequest{}
|
||||
case CmdHTLCAddAccept:
|
||||
msg = &HTLCAddAccept{}
|
||||
case CmdHTLCAddReject:
|
||||
msg = &HTLCAddReject{}
|
||||
case CmdHTLCSettleRequest:
|
||||
msg = &HTLCSettleRequest{}
|
||||
case CmdHTLCSettleAccept:
|
||||
msg = &HTLCSettleAccept{}
|
||||
case CmdHTLCTimeoutRequest:
|
||||
msg = &HTLCTimeoutRequest{}
|
||||
case CmdHTLCTimeoutAccept:
|
||||
msg = &HTLCTimeoutAccept{}
|
||||
case CmdCommitSignature:
|
||||
msg = &CommitSignature{}
|
||||
case CmdCommitRevocation:
|
||||
@ -101,16 +115,22 @@ func makeEmptyMessage(command uint32) (Message, error) {
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// messageHeader represents the header structure for all lightning protocol
|
||||
// messages.
|
||||
type messageHeader struct {
|
||||
// magic represents Which Blockchain Technology(TM) to use.
|
||||
// NOTE(j): We don't need to worry about the magic overlapping with
|
||||
// bitcoin since this is inside encrypted comms anyway, but maybe we
|
||||
// should use the XOR (^wire.TestNet3) just in case???
|
||||
magic wire.BitcoinNet // which Blockchain Technology(TM) to use
|
||||
command uint32
|
||||
length uint32
|
||||
magic wire.BitcoinNet // 4 bytes
|
||||
command uint32 // 4 bytes
|
||||
length uint32 // 4 bytes
|
||||
}
|
||||
|
||||
// readMessageHeader reads a lightning protocol message header from r.
|
||||
func readMessageHeader(r io.Reader) (int, *messageHeader, error) {
|
||||
// As the message header is a fixed size structure, read bytes for the
|
||||
// entire header at once.
|
||||
var headerBytes [MessageHeaderSize]byte
|
||||
n, err := io.ReadFull(r, headerBytes[:])
|
||||
if err != nil {
|
||||
@ -118,8 +138,8 @@ func readMessageHeader(r io.Reader) (int, *messageHeader, error) {
|
||||
}
|
||||
hr := bytes.NewReader(headerBytes[:])
|
||||
|
||||
// Create and populate the message header from the raw header bytes.
|
||||
hdr := messageHeader{}
|
||||
|
||||
err = readElements(hr,
|
||||
&hdr.magic,
|
||||
&hdr.command,
|
||||
@ -131,10 +151,10 @@ func readMessageHeader(r io.Reader) (int, *messageHeader, error) {
|
||||
return n, &hdr, nil
|
||||
}
|
||||
|
||||
// discardInput reads n bytes from reader r in chunks and discards the read
|
||||
// bytes. This is used to skip payloads when various errors occur and helps
|
||||
// prevent rogue nodes from causing massive memory allocation through forging
|
||||
// header length.
|
||||
// discardInput reads n bytes from reader r in chunks and discards the read
|
||||
// bytes. This is used to skip payloads when various errors occur and helps
|
||||
// prevent rogue nodes from causing massive memory allocation through forging
|
||||
// header length.
|
||||
func discardInput(r io.Reader, n uint32) {
|
||||
maxSize := uint32(10 * 1024) // 10k at a time
|
||||
numReads := n / maxSize
|
||||
@ -151,7 +171,11 @@ func discardInput(r io.Reader, n uint32) {
|
||||
}
|
||||
}
|
||||
|
||||
func WriteMessage(w io.Writer, msg Message, pver uint32, btcnet wire.BitcoinNet) (int, error) {
|
||||
// WriteMessage writes a lightning Message to w including the necessary header
|
||||
// information and returns the number of bytes written.
|
||||
func WriteMessage(w io.Writer, msg Message, pver uint32,
|
||||
btcnet wire.BitcoinNet) (int, error) {
|
||||
|
||||
totalBytes := 0
|
||||
|
||||
cmd := msg.Command()
|
||||
@ -167,35 +191,38 @@ func WriteMessage(w io.Writer, msg Message, pver uint32, btcnet wire.BitcoinNet)
|
||||
|
||||
// Enforce maximum overall message payload
|
||||
if lenp > MaxMessagePayload {
|
||||
return totalBytes, fmt.Errorf("message payload is too large - encoded %d bytes, but maximum message payload is %d bytes", lenp, MaxMessagePayload)
|
||||
return totalBytes, fmt.Errorf("message payload is too large - "+
|
||||
"encoded %d bytes, but maximum message payload is %d bytes",
|
||||
lenp, MaxMessagePayload)
|
||||
}
|
||||
|
||||
// Enforce maximum message payload on the message type
|
||||
mpl := msg.MaxPayloadLength(pver)
|
||||
if uint32(lenp) > mpl {
|
||||
return totalBytes, fmt.Errorf("message payload is too large - encoded %d bytes, but maximum message payload of type %x is %d bytes", lenp, cmd, mpl)
|
||||
return totalBytes, fmt.Errorf("message payload is too large - "+
|
||||
"encoded %d bytes, but maximum message payload of "+
|
||||
"type %x is %d bytes", lenp, cmd, mpl)
|
||||
}
|
||||
|
||||
// Create header for the message
|
||||
hdr := messageHeader{}
|
||||
hdr.magic = btcnet
|
||||
hdr.command = cmd
|
||||
hdr.length = uint32(lenp)
|
||||
// Create header for the message.
|
||||
hdr := messageHeader{magic: btcnet, command: cmd, length: uint32(lenp)}
|
||||
|
||||
// Encode the header for the message. This is done to a buffer
|
||||
// rather than directly to the writer since writeElements doesn't
|
||||
// return the number of bytes written.
|
||||
// Encode the header for the message. This is done to a buffer
|
||||
// rather than directly to the writer since writeElements doesn't
|
||||
// return the number of bytes written.
|
||||
hw := bytes.NewBuffer(make([]byte, 0, MessageHeaderSize))
|
||||
writeElements(hw, hdr.magic, hdr.command, hdr.length)
|
||||
if err := writeElements(hw, hdr.magic, hdr.command, hdr.length); err != nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Write header
|
||||
// Write the head first.
|
||||
n, err := w.Write(hw.Bytes())
|
||||
totalBytes += n
|
||||
if err != nil {
|
||||
return totalBytes, err
|
||||
}
|
||||
|
||||
// Write payload
|
||||
// Write payload the payload itself after the header.
|
||||
n, err = w.Write(payload)
|
||||
totalBytes += n
|
||||
if err != nil {
|
||||
@ -205,6 +232,11 @@ func WriteMessage(w io.Writer, msg Message, pver uint32, btcnet wire.BitcoinNet)
|
||||
return totalBytes, nil
|
||||
}
|
||||
|
||||
// ReadMessageN reads, validates, and parses the next bitcoin Message from r for
|
||||
// the provided protocol version and bitcoin network. It returns the number of
|
||||
// bytes read in addition to the parsed Message and raw bytes which comprise the
|
||||
// message. This function is the same as ReadMessage except it also returns the
|
||||
// number of bytes read.
|
||||
func ReadMessage(r io.Reader, pver uint32, btcnet wire.BitcoinNet) (int, Message, []byte, error) {
|
||||
totalBytes := 0
|
||||
n, hdr, err := readMessageHeader(r)
|
||||
@ -215,31 +247,38 @@ func ReadMessage(r io.Reader, pver uint32, btcnet wire.BitcoinNet) (int, Message
|
||||
|
||||
// Enforce maximum message payload
|
||||
if hdr.length > MaxMessagePayload {
|
||||
return totalBytes, nil, nil, fmt.Errorf("message payload is too large - header indicates %d bytes, but max message payload is %d bytes.", hdr.length, MaxMessagePayload)
|
||||
return totalBytes, nil, nil, fmt.Errorf("message payload is "+
|
||||
"too large - header indicates %d bytes, but max "+
|
||||
"message payload is %d bytes.", hdr.length,
|
||||
MaxMessagePayload)
|
||||
}
|
||||
|
||||
// Check for messages in the wrong bitcoin network
|
||||
// Check for messages in the wrong network.
|
||||
if hdr.magic != btcnet {
|
||||
discardInput(r, hdr.length)
|
||||
return totalBytes, nil, nil, fmt.Errorf("message from other network [%v]", hdr.magic)
|
||||
return totalBytes, nil, nil, fmt.Errorf("message from other "+
|
||||
"network [%v]", hdr.magic)
|
||||
}
|
||||
|
||||
// Create struct of appropriate message type based on the command
|
||||
// Create struct of appropriate message type based on the command.
|
||||
command := hdr.command
|
||||
msg, err := makeEmptyMessage(command)
|
||||
if err != nil {
|
||||
discardInput(r, hdr.length)
|
||||
return totalBytes, nil, nil, fmt.Errorf("ReadMessage %s", err.Error())
|
||||
return totalBytes, nil, nil, fmt.Errorf("ReadMessage %s",
|
||||
err.Error())
|
||||
}
|
||||
|
||||
// Check for maximum length based on the message type
|
||||
// Check for maximum length based on the message type.
|
||||
mpl := msg.MaxPayloadLength(pver)
|
||||
if hdr.length > mpl {
|
||||
discardInput(r, hdr.length)
|
||||
return totalBytes, nil, nil, fmt.Errorf("payload exceeds max length. indicates %v bytes, but max of message type %v is %v.", hdr.length, command, mpl)
|
||||
return totalBytes, nil, nil, fmt.Errorf("payload exceeds max "+
|
||||
"length. indicates %v bytes, but max of message type %v is %v.",
|
||||
hdr.length, command, mpl)
|
||||
}
|
||||
|
||||
// Read payload
|
||||
// Read payload.
|
||||
payload := make([]byte, hdr.length)
|
||||
n, err = io.ReadFull(r, payload)
|
||||
totalBytes += n
|
||||
@ -247,19 +286,16 @@ func ReadMessage(r io.Reader, pver uint32, btcnet wire.BitcoinNet) (int, Message
|
||||
return totalBytes, nil, nil, err
|
||||
}
|
||||
|
||||
// Unmarshal message
|
||||
// Unmarshal message.
|
||||
pr := bytes.NewBuffer(payload)
|
||||
err = msg.Decode(pr, pver)
|
||||
if err != nil {
|
||||
if err = msg.Decode(pr, pver); err != nil {
|
||||
return totalBytes, nil, nil, err
|
||||
}
|
||||
|
||||
// Validate the data
|
||||
err = msg.Validate()
|
||||
if err != nil {
|
||||
// Validate the data.
|
||||
if err = msg.Validate(); err != nil {
|
||||
return totalBytes, nil, nil, err
|
||||
}
|
||||
|
||||
// We're good!
|
||||
return totalBytes, msg, payload, nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user