lnwire: update and document message.go

This commit is contained in:
Olaoluwa Osuntokun committed 2016-05-23 13:49:14 -07:00
1 parent 0563205e6d
commit a30831aef8
1 file changed
+103 -67
+103 -67
View File
@@ -9,55 +9,65 @@ import (
"github.com/roasbeef/btcd/wire" "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 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 const MaxMessagePayload = 1024 * 1024 * 32 // 32MB
// Commands used in lightning message headers which detail the type of message.
const ( const (
// Funding channel open // Commands for opening a channel funded by both parties (dual funder).
CmdFundingRequest = uint32(200) CmdFundingRequest = uint32(200)
CmdFundingResponse = uint32(210) CmdFundingResponse = uint32(210)
CmdFundingSignAccept = uint32(220) CmdFundingSignAccept = uint32(220)
CmdFundingSignComplete = uint32(230) 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) CmdCloseRequest = uint32(300)
CmdCloseComplete = uint32(310) CmdCloseComplete = uint32(310)
// TODO Renumber to 1100 // Commands for negotiating HTLCs.
// HTLC payment CmdHTLCAddRequest = uint32(1000)
CmdHTLCAddRequest = uint32(1000) CmdHTLCAddAccept = uint32(1010)
CmdHTLCAddAccept = uint32(1010) CmdHTLCAddReject = uint32(1020)
CmdHTLCAddReject = uint32(1020) CmdHTLCSettleRequest = uint32(1100)
// TODO Renumber to 1200
// HTLC settlement
CmdHTLCSettleRequest = uint32(1100)
CmdHTLCSettleAccept = uint32(1110)
// HTLC timeout
CmdHTLCTimeoutRequest = uint32(1300) CmdHTLCTimeoutRequest = uint32(1300)
CmdHTLCTimeoutAccept = uint32(1310)
// Commitments // Commands for modifying commitment transactions.
CmdCommitSignature = uint32(2000) CmdCommitSignature = uint32(2000)
CmdCommitRevocation = uint32(2010) CmdCommitRevocation = uint32(2010)
// Error // Commands for reporting protocol errors.
CmdErrorGeneric = uint32(4000) 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 { type Message interface {
Decode(io.Reader, uint32) error // (io, protocol version) Decode(io.Reader, uint32) error
Encode(io.Writer, uint32) error // (io, protocol version) Encode(io.Writer, uint32) error
Command() uint32 // returns ID of the message Command() uint32
MaxPayloadLength(uint32) uint32 // (version) maxpayloadsize MaxPayloadLength(uint32) uint32
Validate() error // Validates the data struct Validate() error
String() string String() string
} }
// makeEmptyMessage creates a new empty message of the proper concrete type
// based on the command ID.
func makeEmptyMessage(command uint32) (Message, error) { func makeEmptyMessage(command uint32) (Message, error) {
var msg Message var msg Message
@@ -70,24 +80,28 @@ func makeEmptyMessage(command uint32) (Message, error) {
msg = &FundingSignAccept{} msg = &FundingSignAccept{}
case CmdFundingSignComplete: case CmdFundingSignComplete:
msg = &FundingSignComplete{} msg = &FundingSignComplete{}
case CmdSingleFundingRequest:
msg = &SingleFundingRequest{}
case CmdSingleFundingResponse:
msg = &SingleFundingResponse{}
case CmdSingleFundingComplete:
msg = &SingleFundingComplete{}
case CmdSingleFundingSignComplete:
msg = &SingleFundingSignComplete{}
case CmdSingleFundingOpenProof:
msg = &SingleFundingOpenProof{}
case CmdCloseRequest: case CmdCloseRequest:
msg = &CloseRequest{} msg = &CloseRequest{}
case CmdCloseComplete: case CmdCloseComplete:
msg = &CloseComplete{} msg = &CloseComplete{}
case CmdHTLCAddRequest: case CmdHTLCAddRequest:
msg = &HTLCAddRequest{} msg = &HTLCAddRequest{}
case CmdHTLCAddAccept:
msg = &HTLCAddAccept{}
case CmdHTLCAddReject: case CmdHTLCAddReject:
msg = &HTLCAddReject{} msg = &HTLCAddReject{}
case CmdHTLCSettleRequest: case CmdHTLCSettleRequest:
msg = &HTLCSettleRequest{} msg = &HTLCSettleRequest{}
case CmdHTLCSettleAccept:
msg = &HTLCSettleAccept{}
case CmdHTLCTimeoutRequest: case CmdHTLCTimeoutRequest:
msg = &HTLCTimeoutRequest{} msg = &HTLCTimeoutRequest{}
case CmdHTLCTimeoutAccept:
msg = &HTLCTimeoutAccept{}
case CmdCommitSignature: case CmdCommitSignature:
msg = &CommitSignature{} msg = &CommitSignature{}
case CmdCommitRevocation: case CmdCommitRevocation:
@@ -101,16 +115,22 @@ func makeEmptyMessage(command uint32) (Message, error) {
return msg, nil return msg, nil
} }
// messageHeader represents the header structure for all lightning protocol
// messages.
type messageHeader struct { type messageHeader struct {
// magic represents Which Blockchain Technology(TM) to use.
// NOTE(j): We don't need to worry about the magic overlapping with // NOTE(j): We don't need to worry about the magic overlapping with
// bitcoin since this is inside encrypted comms anyway, but maybe we // bitcoin since this is inside encrypted comms anyway, but maybe we
// should use the XOR (^wire.TestNet3) just in case??? // should use the XOR (^wire.TestNet3) just in case???
magic wire.BitcoinNet // which Blockchain Technology(TM) to use magic wire.BitcoinNet // 4 bytes
command uint32 command uint32 // 4 bytes
length uint32 length uint32 // 4 bytes
} }
// readMessageHeader reads a lightning protocol message header from r.
func readMessageHeader(r io.Reader) (int, *messageHeader, error) { 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 var headerBytes [MessageHeaderSize]byte
n, err := io.ReadFull(r, headerBytes[:]) n, err := io.ReadFull(r, headerBytes[:])
if err != nil { if err != nil {
@@ -118,8 +138,8 @@ func readMessageHeader(r io.Reader) (int, *messageHeader, error) {
} }
hr := bytes.NewReader(headerBytes[:]) hr := bytes.NewReader(headerBytes[:])
// Create and populate the message header from the raw header bytes.
hdr := messageHeader{} hdr := messageHeader{}
err = readElements(hr, err = readElements(hr,
&hdr.magic, &hdr.magic,
&hdr.command, &hdr.command,
@@ -131,10 +151,10 @@ func readMessageHeader(r io.Reader) (int, *messageHeader, error) {
return n, &hdr, nil return n, &hdr, nil
} }
// discardInput reads n bytes from reader r in chunks and discards the read // 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 // bytes. This is used to skip payloads when various errors occur and helps
// prevent rogue nodes from causing massive memory allocation through forging // prevent rogue nodes from causing massive memory allocation through forging
// header length. // header length.
func discardInput(r io.Reader, n uint32) { func discardInput(r io.Reader, n uint32) {
maxSize := uint32(10 * 1024) // 10k at a time maxSize := uint32(10 * 1024) // 10k at a time
numReads := n / maxSize 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 totalBytes := 0
cmd := msg.Command() cmd := msg.Command()
@@ -167,35 +191,38 @@ func WriteMessage(w io.Writer, msg Message, pver uint32, btcnet wire.BitcoinNet)
// Enforce maximum overall message payload // Enforce maximum overall message payload
if lenp > MaxMessagePayload { 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 // Enforce maximum message payload on the message type
mpl := msg.MaxPayloadLength(pver) mpl := msg.MaxPayloadLength(pver)
if uint32(lenp) > mpl { 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 // Create header for the message.
hdr := messageHeader{} hdr := messageHeader{magic: btcnet, command: cmd, length: uint32(lenp)}
hdr.magic = btcnet
hdr.command = cmd
hdr.length = uint32(lenp)
// Encode the header for the message. This is done to a buffer // Encode the header for the message. This is done to a buffer
// rather than directly to the writer since writeElements doesn't // rather than directly to the writer since writeElements doesn't
// return the number of bytes written. // return the number of bytes written.
hw := bytes.NewBuffer(make([]byte, 0, MessageHeaderSize)) 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()) n, err := w.Write(hw.Bytes())
totalBytes += n totalBytes += n
if err != nil { if err != nil {
return totalBytes, err return totalBytes, err
} }
// Write payload // Write payload the payload itself after the header.
n, err = w.Write(payload) n, err = w.Write(payload)
totalBytes += n totalBytes += n
if err != nil { if err != nil {
@@ -205,6 +232,11 @@ func WriteMessage(w io.Writer, msg Message, pver uint32, btcnet wire.BitcoinNet)
return totalBytes, nil 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) { func ReadMessage(r io.Reader, pver uint32, btcnet wire.BitcoinNet) (int, Message, []byte, error) {
totalBytes := 0 totalBytes := 0
n, hdr, err := readMessageHeader(r) 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 // Enforce maximum message payload
if hdr.length > MaxMessagePayload { 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 { if hdr.magic != btcnet {
discardInput(r, hdr.length) 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 command := hdr.command
msg, err := makeEmptyMessage(command) msg, err := makeEmptyMessage(command)
if err != nil { if err != nil {
discardInput(r, hdr.length) 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) mpl := msg.MaxPayloadLength(pver)
if hdr.length > mpl { if hdr.length > mpl {
discardInput(r, hdr.length) 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) payload := make([]byte, hdr.length)
n, err = io.ReadFull(r, payload) n, err = io.ReadFull(r, payload)
totalBytes += n totalBytes += n
@@ -247,19 +286,16 @@ func ReadMessage(r io.Reader, pver uint32, btcnet wire.BitcoinNet) (int, Message
return totalBytes, nil, nil, err return totalBytes, nil, nil, err
} }
// Unmarshal message // Unmarshal message.
pr := bytes.NewBuffer(payload) pr := bytes.NewBuffer(payload)
err = msg.Decode(pr, pver) if err = msg.Decode(pr, pver); err != nil {
if err != nil {
return totalBytes, nil, nil, err return totalBytes, nil, nil, err
} }
// Validate the data // Validate the data.
err = msg.Validate() if err = msg.Validate(); err != nil {
if err != nil {
return totalBytes, nil, nil, err return totalBytes, nil, nil, err
} }
// We're good!
return totalBytes, msg, payload, nil return totalBytes, msg, payload, nil
} }