From 620cbdeb8e8d91f1977699b928d75f5f2d312f30 Mon Sep 17 00:00:00 2001 From: Javed Khan Date: Sat, 19 Apr 2014 14:27:53 +0530 Subject: [PATCH] Implemented BIP 0014 format for user agent the new function AddUserAgent adds the user agent to the stack and formats it as per BIP 0014 e.g: "/btcwire:0.1.4/myclient:1.2.3(optional; comments)/" the validation on UserAgent has been moved to a new function validateUserAgent --- README.md | 1 - doc.go | 1 + message_test.go | 4 ++-- msgversion.go | 56 +++++++++++++++++++++++++++++++++++----------- msgversion_test.go | 36 +++++++++++++++++++++++------ 5 files changed, 75 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index feb942e6..2ce3404d 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,6 @@ from a remote peer is: ## TODO -- Implement functions for [BIP 0014](https://en.bitcoin.it/wiki/BIP_0014) - Implement alert message decoding/encoding - Implement bloom filter messages (filterload, filteradd, filterclear, merkleblock) as defined in [BIP 0037](https://en.bitcoin.it/wiki/BIP_0037) diff --git a/doc.go b/doc.go index a04d077f..ac5e4b45 100644 --- a/doc.go +++ b/doc.go @@ -150,6 +150,7 @@ Bitcoin Improvement Proposals This package includes spec changes outlined by the following BIPs: + BIP0014 (https://en.bitcoin.it/wiki/BIP_0014) BIP0031 (https://en.bitcoin.it/wiki/BIP_0031) BIP0035 (https://en.bitcoin.it/wiki/BIP_0035) diff --git a/message_test.go b/message_test.go index a3f4cddb..a33fc977 100644 --- a/message_test.go +++ b/message_test.go @@ -51,7 +51,7 @@ func TestMessage(t *testing.T) { t.Errorf("NewNetAddress: %v", err) } me.Timestamp = time.Time{} // Version message has zero value timestamp. - msgVersion := btcwire.NewMsgVersion(me, you, 123123, "/test:0.0.1/", 0) + msgVersion := btcwire.NewMsgVersion(me, you, 123123, 0) msgVerack := btcwire.NewMsgVerAck() msgGetAddr := btcwire.NewMsgGetAddr() @@ -76,7 +76,7 @@ func TestMessage(t *testing.T) { btcnet btcwire.BitcoinNet // Network to use for wire encoding bytes int // Expected num bytes read/written }{ - {msgVersion, msgVersion, pver, btcwire.MainNet, 122}, + {msgVersion, msgVersion, pver, btcwire.MainNet, 125}, {msgVerack, msgVerack, pver, btcwire.MainNet, 24}, {msgGetAddr, msgGetAddr, pver, btcwire.MainNet, 24}, {msgAddr, msgAddr, pver, btcwire.MainNet, 25}, diff --git a/msgversion.go b/msgversion.go index 85feee63..26382625 100644 --- a/msgversion.go +++ b/msgversion.go @@ -9,6 +9,7 @@ import ( "fmt" "io" "net" + "strings" "time" ) @@ -16,6 +17,9 @@ import ( // version message (MsgVersion). const MaxUserAgentLen = 2000 +// DefaultUserAgent for btcwire in the stack +const DefaultUserAgent = "/btcwire:0.1.4/" + // MsgVersion implements the Message interface and represents a bitcoin version // message. It is used for a peer to advertise itself as soon as an outbound // connection is made. The remote peer then uses this information along with @@ -115,10 +119,9 @@ func (msg *MsgVersion) BtcDecode(r io.Reader, pver uint32) error { if err != nil { return err } - if len(userAgent) > MaxUserAgentLen { - str := fmt.Sprintf("user agent too long [len %v, max %v]", - len(userAgent), MaxUserAgentLen) - return messageError("MsgVersion.BtcDecode", str) + err = validateUserAgent(userAgent) + if err != nil { + return err } msg.UserAgent = userAgent } @@ -152,13 +155,12 @@ func (msg *MsgVersion) BtcDecode(r io.Reader, pver uint32) error { // BtcEncode encodes the receiver to w using the bitcoin protocol encoding. // This is part of the Message interface implementation. func (msg *MsgVersion) BtcEncode(w io.Writer, pver uint32) error { - if len(msg.UserAgent) > MaxUserAgentLen { - str := fmt.Sprintf("user agent too long [len %v, max %v]", - len(msg.UserAgent), MaxUserAgentLen) - return messageError("MsgVersion.BtcEncode", str) + err := validateUserAgent(msg.UserAgent) + if err != nil { + return err } - err := writeElements(w, msg.ProtocolVersion, msg.Services, + err = writeElements(w, msg.ProtocolVersion, msg.Services, msg.Timestamp.Unix()) if err != nil { return err @@ -224,7 +226,7 @@ func (msg *MsgVersion) MaxPayloadLength(pver uint32) uint32 { // Message interface using the passed parameters and defaults for the remaining // fields. func NewMsgVersion(me *NetAddress, you *NetAddress, nonce uint64, - userAgent string, lastBlock int32) *MsgVersion { + lastBlock int32) *MsgVersion { // Limit the timestamp to one second precision since the protocol // doesn't support better. @@ -235,7 +237,7 @@ func NewMsgVersion(me *NetAddress, you *NetAddress, nonce uint64, AddrYou: *you, AddrMe: *me, Nonce: nonce, - UserAgent: userAgent, + UserAgent: DefaultUserAgent, LastBlock: lastBlock, DisableRelayTx: false, } @@ -244,7 +246,7 @@ func NewMsgVersion(me *NetAddress, you *NetAddress, nonce uint64, // NewMsgVersionFromConn is a convenience function that extracts the remote // and local address from conn and returns a new bitcoin version message that // conforms to the Message interface. See NewMsgVersion. -func NewMsgVersionFromConn(conn net.Conn, nonce uint64, userAgent string, +func NewMsgVersionFromConn(conn net.Conn, nonce uint64, lastBlock int32) (*MsgVersion, error) { // Don't assume any services until we know otherwise. @@ -259,5 +261,33 @@ func NewMsgVersionFromConn(conn net.Conn, nonce uint64, userAgent string, return nil, err } - return NewMsgVersion(lna, rna, nonce, userAgent, lastBlock), nil + return NewMsgVersion(lna, rna, nonce, lastBlock), nil +} + +// validateUserAgent checks userAgent length against MaxUserAgentLen +func validateUserAgent(userAgent string) error { + if len(userAgent) > MaxUserAgentLen { + str := fmt.Sprintf("user agent too long [len %v, max %v]", + len(userAgent), MaxUserAgentLen) + return messageError("MsgVersion", str) + } + return nil +} + +// AddUserAgent adds a custom user agent +func (msg *MsgVersion) AddUserAgent(name string, version string, + comments ...string) error { + + newUserAgent := fmt.Sprintf("%s:%s", name, version) + if len(comments) != 0 { + newUserAgent = fmt.Sprintf("%s(%s)", newUserAgent, + strings.Join(comments, "; ")) + } + newUserAgent = fmt.Sprintf("%s%s/", msg.UserAgent, newUserAgent) + err := validateUserAgent(newUserAgent) + if err != nil { + return err + } + msg.UserAgent = newUserAgent + return nil } diff --git a/msgversion_test.go b/msgversion_test.go index 5e2c2147..bd8bd3aa 100644 --- a/msgversion_test.go +++ b/msgversion_test.go @@ -21,7 +21,6 @@ func TestVersion(t *testing.T) { pver := btcwire.ProtocolVersion // Create version message data. - userAgent := "/btcdtest:0.0.1/" lastBlock := int32(234234) tcpAddrMe := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8333} me, err := btcwire.NewNetAddress(tcpAddrMe, btcwire.SFNodeNetwork) @@ -39,7 +38,7 @@ func TestVersion(t *testing.T) { } // Ensure we get the correct data back out. - msg := btcwire.NewMsgVersion(me, you, nonce, userAgent, lastBlock) + msg := btcwire.NewMsgVersion(me, you, nonce, lastBlock) if msg.ProtocolVersion != int32(pver) { t.Errorf("NewMsgVersion: wrong protocol version - got %v, want %v", msg.ProtocolVersion, pver) @@ -56,9 +55,9 @@ func TestVersion(t *testing.T) { t.Errorf("NewMsgVersion: wrong nonce - got %v, want %v", msg.Nonce, nonce) } - if msg.UserAgent != userAgent { + if msg.UserAgent != btcwire.DefaultUserAgent { t.Errorf("NewMsgVersion: wrong user agent - got %v, want %v", - msg.UserAgent, userAgent) + msg.UserAgent, btcwire.DefaultUserAgent) } if msg.LastBlock != lastBlock { t.Errorf("NewMsgVersion: wrong last block - got %v, want %v", @@ -69,6 +68,29 @@ func TestVersion(t *testing.T) { "default - got %v, want %v", msg.DisableRelayTx, false) } + msg.AddUserAgent("myclient", "1.2.3", "optional", "comments") + customUserAgent := btcwire.DefaultUserAgent + "myclient:1.2.3(optional; comments)/" + if msg.UserAgent != customUserAgent { + t.Errorf("AddUserAgent: wrong user agent - got %s, want %s", + msg.UserAgent, customUserAgent) + } + + msg.AddUserAgent("mygui", "3.4.5") + customUserAgent += "mygui:3.4.5/" + if msg.UserAgent != customUserAgent { + t.Errorf("AddUserAgent: wrong user agent - got %s, want %s", + msg.UserAgent, customUserAgent) + } + + // accounting for ":", "/" + err = msg.AddUserAgent(strings.Repeat("t", + btcwire.MaxUserAgentLen-len(customUserAgent)-2+1), "") + if _, ok := err.(*btcwire.MessageError); !ok { + t.Errorf("AddUserAgent: expected error not received "+ + "- got %v, want %T", err, btcwire.MessageError{}) + + } + // Version message should not have any services set by default. if msg.Services != 0 { t.Errorf("NewMsgVersion: wrong default services - got %v, want %v", @@ -111,7 +133,7 @@ func TestVersion(t *testing.T) { // Use a fake connection. conn := &fakeConn{localAddr: tcpAddrMe, remoteAddr: tcpAddrYou} - msg, err = btcwire.NewMsgVersionFromConn(conn, nonce, userAgent, lastBlock) + msg, err = btcwire.NewMsgVersionFromConn(conn, nonce, lastBlock) if err != nil { t.Errorf("NewMsgVersionFromConn: %v", err) } @@ -131,7 +153,7 @@ func TestVersion(t *testing.T) { localAddr: &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8333}, remoteAddr: tcpAddrYou, } - msg, err = btcwire.NewMsgVersionFromConn(conn, nonce, userAgent, lastBlock) + msg, err = btcwire.NewMsgVersionFromConn(conn, nonce, lastBlock) if err != btcwire.ErrInvalidNetAddr { t.Errorf("NewMsgVersionFromConn: expected error not received "+ "- got %v, want %v", err, btcwire.ErrInvalidNetAddr) @@ -142,7 +164,7 @@ func TestVersion(t *testing.T) { localAddr: tcpAddrMe, remoteAddr: &net.UDPAddr{IP: net.ParseIP("192.168.0.1"), Port: 8333}, } - msg, err = btcwire.NewMsgVersionFromConn(conn, nonce, userAgent, lastBlock) + msg, err = btcwire.NewMsgVersionFromConn(conn, nonce, lastBlock) if err != btcwire.ErrInvalidNetAddr { t.Errorf("NewMsgVersionFromConn: expected error not received "+ "- got %v, want %v", err, btcwire.ErrInvalidNetAddr)