2017-11-28 01:12:09 +01:00
|
|
|
// Copyright (c) 2013-2017 The btcsuite developers
|
|
|
|
// Copyright (c) 2015-2016 The Decred developers
|
2017-02-23 20:56:47 +01:00
|
|
|
// code derived from https://github .com/btcsuite/btcd/blob/master/wire/message.go
|
2022-02-06 21:16:47 +01:00
|
|
|
// Copyright (C) 2015-2022 The Lightning Network Developers
|
2017-11-28 01:12:09 +01:00
|
|
|
|
|
|
|
package lnwire
|
2017-02-23 20:56:47 +01:00
|
|
|
|
2015-12-28 12:24:16 +01:00
|
|
|
import (
|
|
|
|
"bytes"
|
2017-04-20 00:57:43 +02:00
|
|
|
"encoding/binary"
|
2015-12-28 12:24:16 +01:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2015-12-31 17:38:33 +01:00
|
|
|
)
|
|
|
|
|
2017-04-20 00:57:43 +02:00
|
|
|
// MessageType is the unique 2 byte big-endian integer that indicates the type
|
|
|
|
// of message on the wire. All messages have a very simple header which
|
|
|
|
// consists simply of 2-byte message type. We omit a length field, and checksum
|
2018-02-07 04:11:11 +01:00
|
|
|
// as the Lightning Protocol is intended to be encapsulated within a
|
2017-04-20 00:57:43 +02:00
|
|
|
// confidential+authenticated cryptographic messaging protocol.
|
|
|
|
type MessageType uint16
|
2015-12-31 10:19:54 +01:00
|
|
|
|
2017-04-21 00:50:06 +02:00
|
|
|
// The currently defined message types within this current version of the
|
|
|
|
// Lightning protocol.
|
2017-04-20 00:57:43 +02:00
|
|
|
const (
|
2022-05-16 18:07:48 +02:00
|
|
|
MsgWarning MessageType = 1
|
2023-12-07 01:44:59 +01:00
|
|
|
MsgStfu = 2
|
2022-05-16 18:07:48 +02:00
|
|
|
MsgInit = 16
|
2017-07-29 01:30:39 +02:00
|
|
|
MsgError = 17
|
|
|
|
MsgPing = 18
|
|
|
|
MsgPong = 19
|
|
|
|
MsgOpenChannel = 32
|
|
|
|
MsgAcceptChannel = 33
|
|
|
|
MsgFundingCreated = 34
|
|
|
|
MsgFundingSigned = 35
|
2023-03-15 21:44:15 +01:00
|
|
|
MsgChannelReady = 36
|
2017-11-28 21:15:21 +01:00
|
|
|
MsgShutdown = 38
|
|
|
|
MsgClosingSigned = 39
|
2024-01-03 03:45:39 +01:00
|
|
|
MsgClosingComplete = 40
|
|
|
|
MsgClosingSig = 41
|
2023-09-23 04:26:52 +02:00
|
|
|
MsgDynPropose = 111
|
|
|
|
MsgDynAck = 113
|
|
|
|
MsgDynReject = 115
|
2017-07-29 01:30:39 +02:00
|
|
|
MsgUpdateAddHTLC = 128
|
2018-02-07 04:11:11 +01:00
|
|
|
MsgUpdateFulfillHTLC = 130
|
2017-07-29 01:30:39 +02:00
|
|
|
MsgUpdateFailHTLC = 131
|
|
|
|
MsgCommitSig = 132
|
|
|
|
MsgRevokeAndAck = 133
|
2017-11-28 21:15:21 +01:00
|
|
|
MsgUpdateFee = 134
|
2017-07-29 01:30:39 +02:00
|
|
|
MsgUpdateFailMalformedHTLC = 135
|
2017-11-11 04:36:35 +01:00
|
|
|
MsgChannelReestablish = 136
|
2017-07-29 01:30:39 +02:00
|
|
|
MsgChannelAnnouncement = 256
|
|
|
|
MsgNodeAnnouncement = 257
|
|
|
|
MsgChannelUpdate = 258
|
|
|
|
MsgAnnounceSignatures = 259
|
2023-09-29 10:22:25 +02:00
|
|
|
MsgAnnounceSignatures2 = 260
|
2018-04-17 03:47:53 +02:00
|
|
|
MsgQueryShortChanIDs = 261
|
|
|
|
MsgReplyShortChanIDsEnd = 262
|
|
|
|
MsgQueryChannelRange = 263
|
|
|
|
MsgReplyChannelRange = 264
|
|
|
|
MsgGossipTimestampRange = 265
|
2023-10-19 05:28:51 +02:00
|
|
|
MsgKickoffSig = 777
|
2015-12-28 12:24:16 +01:00
|
|
|
)
|
|
|
|
|
2021-06-17 05:29:40 +02:00
|
|
|
// ErrorEncodeMessage is used when failed to encode the message payload.
|
|
|
|
func ErrorEncodeMessage(err error) error {
|
|
|
|
return fmt.Errorf("failed to encode message to buffer, got %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ErrorWriteMessageType is used when failed to write the message type.
|
|
|
|
func ErrorWriteMessageType(err error) error {
|
|
|
|
return fmt.Errorf("failed to write message type, got %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ErrorPayloadTooLarge is used when the payload size exceeds the
|
|
|
|
// MaxMsgBody.
|
|
|
|
func ErrorPayloadTooLarge(size int) error {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"message payload is too large - encoded %d bytes, "+
|
|
|
|
"but maximum message payload is %d bytes",
|
|
|
|
size, MaxMsgBody,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2017-05-02 01:29:15 +02:00
|
|
|
// String return the string representation of message type.
|
|
|
|
func (t MessageType) String() string {
|
|
|
|
switch t {
|
2022-05-16 18:07:48 +02:00
|
|
|
case MsgWarning:
|
|
|
|
return "Warning"
|
2023-12-07 01:44:59 +01:00
|
|
|
case MsgStfu:
|
|
|
|
return "Stfu"
|
2017-05-02 01:29:15 +02:00
|
|
|
case MsgInit:
|
|
|
|
return "Init"
|
2017-07-29 01:30:39 +02:00
|
|
|
case MsgOpenChannel:
|
|
|
|
return "MsgOpenChannel"
|
|
|
|
case MsgAcceptChannel:
|
|
|
|
return "MsgAcceptChannel"
|
|
|
|
case MsgFundingCreated:
|
|
|
|
return "MsgFundingCreated"
|
|
|
|
case MsgFundingSigned:
|
|
|
|
return "MsgFundingSigned"
|
2023-03-15 21:44:15 +01:00
|
|
|
case MsgChannelReady:
|
2023-03-15 22:36:58 +01:00
|
|
|
return "ChannelReady"
|
2017-05-02 01:29:15 +02:00
|
|
|
case MsgShutdown:
|
|
|
|
return "Shutdown"
|
|
|
|
case MsgClosingSigned:
|
|
|
|
return "ClosingSigned"
|
2023-09-23 04:26:52 +02:00
|
|
|
case MsgDynPropose:
|
|
|
|
return "DynPropose"
|
|
|
|
case MsgDynAck:
|
|
|
|
return "DynAck"
|
|
|
|
case MsgDynReject:
|
|
|
|
return "DynReject"
|
2023-10-19 05:28:51 +02:00
|
|
|
case MsgKickoffSig:
|
|
|
|
return "KickoffSig"
|
2017-05-02 01:29:15 +02:00
|
|
|
case MsgUpdateAddHTLC:
|
|
|
|
return "UpdateAddHTLC"
|
|
|
|
case MsgUpdateFailHTLC:
|
|
|
|
return "UpdateFailHTLC"
|
2018-02-07 04:11:11 +01:00
|
|
|
case MsgUpdateFulfillHTLC:
|
|
|
|
return "UpdateFulfillHTLC"
|
2017-05-02 01:29:15 +02:00
|
|
|
case MsgCommitSig:
|
|
|
|
return "CommitSig"
|
|
|
|
case MsgRevokeAndAck:
|
|
|
|
return "RevokeAndAck"
|
2017-06-28 17:22:23 +02:00
|
|
|
case MsgUpdateFailMalformedHTLC:
|
|
|
|
return "UpdateFailMalformedHTLC"
|
2017-07-09 00:12:36 +02:00
|
|
|
case MsgChannelReestablish:
|
|
|
|
return "ChannelReestablish"
|
2017-05-02 01:29:15 +02:00
|
|
|
case MsgError:
|
|
|
|
return "Error"
|
|
|
|
case MsgChannelAnnouncement:
|
|
|
|
return "ChannelAnnouncement"
|
|
|
|
case MsgChannelUpdate:
|
|
|
|
return "ChannelUpdate"
|
|
|
|
case MsgNodeAnnouncement:
|
|
|
|
return "NodeAnnouncement"
|
|
|
|
case MsgPing:
|
|
|
|
return "Ping"
|
|
|
|
case MsgAnnounceSignatures:
|
|
|
|
return "AnnounceSignatures"
|
|
|
|
case MsgPong:
|
|
|
|
return "Pong"
|
2017-08-22 07:25:32 +02:00
|
|
|
case MsgUpdateFee:
|
|
|
|
return "UpdateFee"
|
2018-04-17 03:47:53 +02:00
|
|
|
case MsgQueryShortChanIDs:
|
|
|
|
return "QueryShortChanIDs"
|
|
|
|
case MsgReplyShortChanIDsEnd:
|
|
|
|
return "ReplyShortChanIDsEnd"
|
|
|
|
case MsgQueryChannelRange:
|
|
|
|
return "QueryChannelRange"
|
|
|
|
case MsgReplyChannelRange:
|
|
|
|
return "ReplyChannelRange"
|
|
|
|
case MsgGossipTimestampRange:
|
|
|
|
return "GossipTimestampRange"
|
2024-01-03 03:45:39 +01:00
|
|
|
case MsgClosingComplete:
|
|
|
|
return "ClosingComplete"
|
|
|
|
case MsgClosingSig:
|
|
|
|
return "ClosingSig"
|
2023-09-29 10:22:25 +02:00
|
|
|
case MsgAnnounceSignatures2:
|
|
|
|
return "MsgAnnounceSignatures2"
|
2017-05-02 01:29:15 +02:00
|
|
|
default:
|
|
|
|
return "<unknown>"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-17 03:03:34 +01:00
|
|
|
// UnknownMessage is an implementation of the error interface that allows the
|
|
|
|
// creation of an error in response to an unknown message.
|
|
|
|
type UnknownMessage struct {
|
2017-04-20 00:57:43 +02:00
|
|
|
messageType MessageType
|
2017-01-17 03:03:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Error returns a human readable string describing the error.
|
|
|
|
//
|
|
|
|
// This is part of the error interface.
|
|
|
|
func (u *UnknownMessage) Error() string {
|
|
|
|
return fmt.Sprintf("unable to parse message of unknown type: %v",
|
|
|
|
u.messageType)
|
|
|
|
}
|
|
|
|
|
2017-06-08 19:55:41 +02:00
|
|
|
// Serializable is an interface which defines a lightning wire serializable
|
|
|
|
// object.
|
|
|
|
type Serializable interface {
|
|
|
|
// Decode reads the bytes stream and converts it to the object.
|
|
|
|
Decode(io.Reader, uint32) error
|
|
|
|
|
2017-07-29 01:30:39 +02:00
|
|
|
// Encode converts object to the bytes stream and write it into the
|
2021-06-17 08:17:30 +02:00
|
|
|
// write buffer.
|
|
|
|
Encode(*bytes.Buffer, uint32) error
|
2017-06-08 19:55:41 +02:00
|
|
|
}
|
|
|
|
|
2016-05-23 22:49:10 +02:00
|
|
|
// 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.
|
2015-12-28 12:24:16 +01:00
|
|
|
type Message interface {
|
2017-06-08 19:55:41 +02:00
|
|
|
Serializable
|
2017-04-20 00:57:43 +02:00
|
|
|
MsgType() MessageType
|
2015-12-28 12:24:16 +01:00
|
|
|
}
|
|
|
|
|
2024-03-03 20:53:39 +01:00
|
|
|
// LinkUpdater is an interface implemented by most messages in BOLT 2 that are
|
|
|
|
// allowed to update the channel state.
|
|
|
|
type LinkUpdater interface {
|
2024-03-03 20:59:57 +01:00
|
|
|
// All LinkUpdater messages are messages and so we embed the interface
|
|
|
|
// so that we can treat it as a message if all we know about it is that
|
|
|
|
// it is a LinkUpdater message.
|
|
|
|
Message
|
|
|
|
|
2024-03-03 20:53:39 +01:00
|
|
|
// TargetChanID returns the channel id of the link for which this
|
|
|
|
// message is intended.
|
|
|
|
TargetChanID() ChannelID
|
|
|
|
}
|
|
|
|
|
2016-05-23 22:49:10 +02:00
|
|
|
// makeEmptyMessage creates a new empty message of the proper concrete type
|
2017-04-20 00:57:43 +02:00
|
|
|
// based on the passed message type.
|
|
|
|
func makeEmptyMessage(msgType MessageType) (Message, error) {
|
2015-12-28 12:24:16 +01:00
|
|
|
var msg Message
|
|
|
|
|
2017-04-20 00:57:43 +02:00
|
|
|
switch msgType {
|
2022-05-16 18:07:48 +02:00
|
|
|
case MsgWarning:
|
|
|
|
msg = &Warning{}
|
2023-12-07 01:44:59 +01:00
|
|
|
case MsgStfu:
|
|
|
|
msg = &Stfu{}
|
2017-04-20 00:57:43 +02:00
|
|
|
case MsgInit:
|
2017-02-16 13:31:19 +01:00
|
|
|
msg = &Init{}
|
2017-07-29 01:30:39 +02:00
|
|
|
case MsgOpenChannel:
|
|
|
|
msg = &OpenChannel{}
|
|
|
|
case MsgAcceptChannel:
|
|
|
|
msg = &AcceptChannel{}
|
|
|
|
case MsgFundingCreated:
|
|
|
|
msg = &FundingCreated{}
|
|
|
|
case MsgFundingSigned:
|
|
|
|
msg = &FundingSigned{}
|
2023-03-15 21:44:15 +01:00
|
|
|
case MsgChannelReady:
|
2023-03-15 21:42:21 +01:00
|
|
|
msg = &ChannelReady{}
|
2017-03-09 00:32:11 +01:00
|
|
|
case MsgShutdown:
|
|
|
|
msg = &Shutdown{}
|
|
|
|
case MsgClosingSigned:
|
|
|
|
msg = &ClosingSigned{}
|
2023-09-23 04:26:52 +02:00
|
|
|
case MsgDynPropose:
|
|
|
|
msg = &DynPropose{}
|
|
|
|
case MsgDynAck:
|
|
|
|
msg = &DynAck{}
|
|
|
|
case MsgDynReject:
|
|
|
|
msg = &DynReject{}
|
2023-10-19 05:28:51 +02:00
|
|
|
case MsgKickoffSig:
|
|
|
|
msg = &KickoffSig{}
|
2017-04-20 00:57:43 +02:00
|
|
|
case MsgUpdateAddHTLC:
|
2017-02-16 13:08:34 +01:00
|
|
|
msg = &UpdateAddHTLC{}
|
2017-04-20 00:57:43 +02:00
|
|
|
case MsgUpdateFailHTLC:
|
2017-02-16 13:25:36 +01:00
|
|
|
msg = &UpdateFailHTLC{}
|
2018-02-07 04:11:11 +01:00
|
|
|
case MsgUpdateFulfillHTLC:
|
|
|
|
msg = &UpdateFulfillHTLC{}
|
2017-04-20 00:57:43 +02:00
|
|
|
case MsgCommitSig:
|
2017-02-16 13:04:58 +01:00
|
|
|
msg = &CommitSig{}
|
2017-04-20 00:57:43 +02:00
|
|
|
case MsgRevokeAndAck:
|
2017-02-10 00:28:32 +01:00
|
|
|
msg = &RevokeAndAck{}
|
2017-07-14 20:28:40 +02:00
|
|
|
case MsgUpdateFee:
|
|
|
|
msg = &UpdateFee{}
|
2017-06-28 17:22:23 +02:00
|
|
|
case MsgUpdateFailMalformedHTLC:
|
|
|
|
msg = &UpdateFailMalformedHTLC{}
|
2017-07-09 00:12:36 +02:00
|
|
|
case MsgChannelReestablish:
|
|
|
|
msg = &ChannelReestablish{}
|
2017-04-20 00:57:43 +02:00
|
|
|
case MsgError:
|
2017-04-17 00:23:40 +02:00
|
|
|
msg = &Error{}
|
2017-04-20 00:57:43 +02:00
|
|
|
case MsgChannelAnnouncement:
|
2024-08-21 08:37:50 +02:00
|
|
|
msg = &ChannelAnnouncement1{}
|
2017-04-20 00:57:43 +02:00
|
|
|
case MsgChannelUpdate:
|
2024-08-21 08:39:37 +02:00
|
|
|
msg = &ChannelUpdate1{}
|
2017-04-20 00:57:43 +02:00
|
|
|
case MsgNodeAnnouncement:
|
2016-12-07 16:46:22 +01:00
|
|
|
msg = &NodeAnnouncement{}
|
2017-04-20 00:57:43 +02:00
|
|
|
case MsgPing:
|
2016-11-11 02:09:14 +01:00
|
|
|
msg = &Ping{}
|
2017-04-20 00:57:43 +02:00
|
|
|
case MsgAnnounceSignatures:
|
2024-08-21 08:34:57 +02:00
|
|
|
msg = &AnnounceSignatures1{}
|
2017-04-20 00:57:43 +02:00
|
|
|
case MsgPong:
|
2016-11-11 02:09:14 +01:00
|
|
|
msg = &Pong{}
|
2018-04-17 03:47:53 +02:00
|
|
|
case MsgQueryShortChanIDs:
|
|
|
|
msg = &QueryShortChanIDs{}
|
|
|
|
case MsgReplyShortChanIDsEnd:
|
|
|
|
msg = &ReplyShortChanIDsEnd{}
|
|
|
|
case MsgQueryChannelRange:
|
|
|
|
msg = &QueryChannelRange{}
|
|
|
|
case MsgReplyChannelRange:
|
|
|
|
msg = &ReplyChannelRange{}
|
|
|
|
case MsgGossipTimestampRange:
|
|
|
|
msg = &GossipTimestampRange{}
|
2024-01-03 03:45:39 +01:00
|
|
|
case MsgClosingComplete:
|
|
|
|
msg = &ClosingComplete{}
|
|
|
|
case MsgClosingSig:
|
|
|
|
msg = &ClosingSig{}
|
2023-09-29 10:22:25 +02:00
|
|
|
case MsgAnnounceSignatures2:
|
|
|
|
msg = &AnnounceSignatures2{}
|
2015-12-28 12:24:16 +01:00
|
|
|
default:
|
2022-12-12 16:13:10 +01:00
|
|
|
// If the message is not within our custom range and has not
|
|
|
|
// specifically been overridden, return an unknown message.
|
|
|
|
//
|
|
|
|
// Note that we do not allow custom message overrides to replace
|
|
|
|
// known message types, only protocol messages that are not yet
|
|
|
|
// known to lnd.
|
|
|
|
if msgType < CustomTypeStart && !IsCustomOverride(msgType) {
|
2021-05-31 12:06:48 +02:00
|
|
|
return nil, &UnknownMessage{msgType}
|
|
|
|
}
|
2022-12-12 16:13:10 +01:00
|
|
|
|
2021-05-31 12:06:48 +02:00
|
|
|
msg = &Custom{
|
|
|
|
Type: msgType,
|
|
|
|
}
|
2015-12-28 12:24:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return msg, nil
|
|
|
|
}
|
|
|
|
|
2021-06-17 05:29:40 +02:00
|
|
|
// WriteMessage writes a lightning Message to a buffer including the necessary
|
|
|
|
// header information and returns the number of bytes written. If any error is
|
|
|
|
// encountered, the buffer passed will be reset to its original state since we
|
|
|
|
// don't want any broken bytes left. In other words, no bytes will be written
|
|
|
|
// if there's an error. Either all or none of the message bytes will be written
|
|
|
|
// to the buffer.
|
|
|
|
//
|
|
|
|
// NOTE: this method is not concurrent safe.
|
|
|
|
func WriteMessage(buf *bytes.Buffer, msg Message, pver uint32) (int, error) {
|
|
|
|
// Record the size of the bytes already written in buffer.
|
|
|
|
oldByteSize := buf.Len()
|
2015-12-28 12:24:16 +01:00
|
|
|
|
2021-06-17 05:29:40 +02:00
|
|
|
// cleanBrokenBytes is a helper closure that helps reset the buffer to
|
|
|
|
// its original state. It truncates all the bytes written in current
|
|
|
|
// scope.
|
|
|
|
var cleanBrokenBytes = func(b *bytes.Buffer) int {
|
|
|
|
b.Truncate(oldByteSize)
|
|
|
|
return 0
|
2015-12-28 12:24:16 +01:00
|
|
|
}
|
|
|
|
|
2021-06-17 05:29:40 +02:00
|
|
|
// Write the message type.
|
2017-04-20 01:13:08 +02:00
|
|
|
var mType [2]byte
|
|
|
|
binary.BigEndian.PutUint16(mType[:], uint16(msg.MsgType()))
|
2021-06-17 05:29:40 +02:00
|
|
|
msgTypeBytes, err := buf.Write(mType[:])
|
2015-12-28 12:24:16 +01:00
|
|
|
if err != nil {
|
2021-06-17 05:29:40 +02:00
|
|
|
return cleanBrokenBytes(buf), ErrorWriteMessageType(err)
|
2015-12-28 12:24:16 +01:00
|
|
|
}
|
|
|
|
|
2021-06-17 05:29:40 +02:00
|
|
|
// Use the write buffer to encode our message.
|
|
|
|
if err := msg.Encode(buf, pver); err != nil {
|
|
|
|
return cleanBrokenBytes(buf), ErrorEncodeMessage(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Enforce maximum overall message payload. The write buffer now has
|
|
|
|
// the size of len(originalBytes) + len(payload) + len(type). We want
|
|
|
|
// to enforce the payload here, so we subtract it by the length of the
|
|
|
|
// type and old bytes.
|
|
|
|
lenp := buf.Len() - oldByteSize - msgTypeBytes
|
|
|
|
if lenp > MaxMsgBody {
|
|
|
|
return cleanBrokenBytes(buf), ErrorPayloadTooLarge(lenp)
|
|
|
|
}
|
2017-04-20 01:13:08 +02:00
|
|
|
|
2021-06-17 05:29:40 +02:00
|
|
|
return buf.Len() - oldByteSize, nil
|
2015-12-28 12:24:16 +01:00
|
|
|
}
|
|
|
|
|
2017-04-21 00:41:43 +02:00
|
|
|
// ReadMessage reads, validates, and parses the next Lightning message from r
|
|
|
|
// for the provided protocol version.
|
|
|
|
func ReadMessage(r io.Reader, pver uint32) (Message, error) {
|
2017-04-20 01:13:08 +02:00
|
|
|
// First, we'll read out the first two bytes of the message so we can
|
|
|
|
// create the proper empty message.
|
|
|
|
var mType [2]byte
|
2017-04-21 00:41:43 +02:00
|
|
|
if _, err := io.ReadFull(r, mType[:]); err != nil {
|
|
|
|
return nil, err
|
2015-12-28 12:24:16 +01:00
|
|
|
}
|
|
|
|
|
2017-04-20 01:13:08 +02:00
|
|
|
msgType := MessageType(binary.BigEndian.Uint16(mType[:]))
|
2015-12-28 12:24:16 +01:00
|
|
|
|
2017-04-20 01:13:08 +02:00
|
|
|
// Now that we know the target message type, we can create the proper
|
|
|
|
// empty message type and decode the message into it.
|
|
|
|
msg, err := makeEmptyMessage(msgType)
|
2015-12-28 12:24:16 +01:00
|
|
|
if err != nil {
|
2017-04-21 00:41:43 +02:00
|
|
|
return nil, err
|
2015-12-28 12:24:16 +01:00
|
|
|
}
|
2017-04-20 01:13:08 +02:00
|
|
|
if err := msg.Decode(r, pver); err != nil {
|
2017-04-21 00:41:43 +02:00
|
|
|
return nil, err
|
2015-12-28 12:24:16 +01:00
|
|
|
}
|
|
|
|
|
2017-04-21 00:41:43 +02:00
|
|
|
return msg, nil
|
2015-12-28 12:24:16 +01:00
|
|
|
}
|