From a30831aef8b61ac1e503b813919fec2674a4d93d Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 23 May 2016 13:49:10 -0700 Subject: [PATCH] lnwire: update and document message.go --- lnwire/message.go | 170 ++++++++++++++++++++++++++++------------------ 1 file changed, 103 insertions(+), 67 deletions(-) diff --git a/lnwire/message.go b/lnwire/message.go index 84d50c83a..026a79016 100644 --- a/lnwire/message.go +++ b/lnwire/message.go @@ -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 }