diff --git a/lib/connections/service.go b/lib/connections/service.go index 52b4136a..39fa4b80 100644 --- a/lib/connections/service.go +++ b/lib/connections/service.go @@ -8,7 +8,6 @@ package connections import ( "crypto/tls" - "encoding/binary" "errors" "fmt" "io" @@ -153,12 +152,21 @@ next: continue } - hello, err := exchangeHello(c, s.model.GetHello(remoteID)) + c.SetDeadline(time.Now().Add(20 * time.Second)) + hello, err := protocol.ExchangeHello(c, s.model.GetHello(remoteID)) if err != nil { - l.Infof("Failed to exchange Hello messages with %s (%s): %s", remoteID, c.RemoteAddr(), err) + if protocol.IsVersionMismatch(err) { + // The error will be a relatively user friendly description + // of what's wrong with the version compatibility + l.Warnf("Connecting to %s (%s): %s", remoteID, c.RemoteAddr(), err) + } else { + // It's something else - connection reset or whatever + l.Infof("Failed to exchange Hello messages with %s (%s): %s", remoteID, c.RemoteAddr(), err) + } c.Close() continue } + c.SetDeadline(time.Time{}) s.model.OnHello(remoteID, c.RemoteAddr(), hello) @@ -553,54 +561,6 @@ func (s *Service) getListenerFactory(cfg config.Configuration, uri *url.URL) (li return listenerFactory, nil } -func exchangeHello(c net.Conn, h protocol.HelloMessage) (protocol.HelloMessage, error) { - if err := c.SetDeadline(time.Now().Add(20 * time.Second)); err != nil { - return protocol.HelloMessage{}, err - } - defer c.SetDeadline(time.Time{}) - - header := make([]byte, 8) - msg := h.MustMarshalXDR() - - binary.BigEndian.PutUint32(header[:4], protocol.HelloMessageMagic) - binary.BigEndian.PutUint32(header[4:], uint32(len(msg))) - - if _, err := c.Write(header); err != nil { - return protocol.HelloMessage{}, err - } - - if _, err := c.Write(msg); err != nil { - return protocol.HelloMessage{}, err - } - - if _, err := io.ReadFull(c, header); err != nil { - return protocol.HelloMessage{}, err - } - - if binary.BigEndian.Uint32(header[:4]) != protocol.HelloMessageMagic { - return protocol.HelloMessage{}, fmt.Errorf("incorrect magic") - } - - msgSize := binary.BigEndian.Uint32(header[4:]) - if msgSize > 1024 { - return protocol.HelloMessage{}, fmt.Errorf("hello message too big") - } - - buf := make([]byte, msgSize) - - var hello protocol.HelloMessage - - if _, err := io.ReadFull(c, buf); err != nil { - return protocol.HelloMessage{}, err - } - - if err := hello.UnmarshalXDR(buf); err != nil { - return protocol.HelloMessage{}, err - } - - return hello, nil -} - func filterAndFindSleepDuration(nextDial map[string]time.Time, seen []string, now time.Time) (map[string]time.Time, time.Duration) { newNextDial := make(map[string]time.Time) diff --git a/lib/connections/structs.go b/lib/connections/structs.go index 33ce3802..da62bd72 100644 --- a/lib/connections/structs.go +++ b/lib/connections/structs.go @@ -66,11 +66,11 @@ type genericListener interface { type Model interface { protocol.Model - AddConnection(conn Connection, hello protocol.HelloMessage) + AddConnection(conn Connection, hello protocol.HelloResult) ConnectedTo(remoteID protocol.DeviceID) bool IsPaused(remoteID protocol.DeviceID) bool - OnHello(protocol.DeviceID, net.Addr, protocol.HelloMessage) - GetHello(protocol.DeviceID) protocol.HelloMessage + OnHello(protocol.DeviceID, net.Addr, protocol.HelloResult) + GetHello(protocol.DeviceID) protocol.Version13HelloMessage } // serviceFunc wraps a function to create a suture.Service without stop diff --git a/lib/model/model.go b/lib/model/model.go index e26fd058..7e0ec2fe 100644 --- a/lib/model/model.go +++ b/lib/model/model.go @@ -94,7 +94,7 @@ type Model struct { fmut sync.RWMutex // protects the above conn map[protocol.DeviceID]connections.Connection - helloMessages map[protocol.DeviceID]protocol.HelloMessage + helloMessages map[protocol.DeviceID]protocol.HelloResult deviceClusterConf map[protocol.DeviceID]protocol.ClusterConfigMessage devicePaused map[protocol.DeviceID]bool deviceDownloads map[protocol.DeviceID]*deviceDownloadState @@ -139,7 +139,7 @@ func NewModel(cfg *config.Wrapper, id protocol.DeviceID, deviceName, clientName, folderRunnerTokens: make(map[string][]suture.ServiceToken), folderStatRefs: make(map[string]*stats.FolderStatisticsReference), conn: make(map[protocol.DeviceID]connections.Connection), - helloMessages: make(map[protocol.DeviceID]protocol.HelloMessage), + helloMessages: make(map[protocol.DeviceID]protocol.HelloResult), deviceClusterConf: make(map[protocol.DeviceID]protocol.ClusterConfigMessage), devicePaused: make(map[protocol.DeviceID]bool), deviceDownloads: make(map[protocol.DeviceID]*deviceDownloadState), @@ -983,7 +983,7 @@ func (m *Model) SetIgnores(folder string, content []string) error { // OnHello is called when an device connects to us. // This allows us to extract some information from the Hello message // and add it to a list of known devices ahead of any checks. -func (m *Model) OnHello(remoteID protocol.DeviceID, addr net.Addr, hello protocol.HelloMessage) { +func (m *Model) OnHello(remoteID protocol.DeviceID, addr net.Addr, hello protocol.HelloResult) { for deviceID := range m.cfg.Devices() { if deviceID == remoteID { // Existing device, we will get the hello message in AddConnection @@ -1003,8 +1003,8 @@ func (m *Model) OnHello(remoteID protocol.DeviceID, addr net.Addr, hello protoco } // GetHello is called when we are about to connect to some remote device. -func (m *Model) GetHello(protocol.DeviceID) protocol.HelloMessage { - return protocol.HelloMessage{ +func (m *Model) GetHello(protocol.DeviceID) protocol.Version13HelloMessage { + return protocol.Version13HelloMessage{ DeviceName: m.deviceName, ClientName: m.clientName, ClientVersion: m.clientVersion, @@ -1014,7 +1014,7 @@ func (m *Model) GetHello(protocol.DeviceID) protocol.HelloMessage { // AddConnection adds a new peer connection to the model. An initial index will // be sent to the connected peer, thereafter index updates whenever the local // folder changes. -func (m *Model) AddConnection(conn connections.Connection, hello protocol.HelloMessage) { +func (m *Model) AddConnection(conn connections.Connection, hello protocol.HelloResult) { deviceID := conn.ID() m.pmut.Lock() diff --git a/lib/model/model_test.go b/lib/model/model_test.go index 138fa3df..839209dd 100644 --- a/lib/model/model_test.go +++ b/lib/model/model_test.go @@ -303,7 +303,7 @@ func BenchmarkRequest(b *testing.B) { Priority: 10, }, Connection: fc, - }, protocol.HelloMessage{}) + }, protocol.HelloResult{}) m.Index(device1, "default", files, 0, nil) b.ResetTimer() @@ -319,7 +319,7 @@ func BenchmarkRequest(b *testing.B) { } func TestDeviceRename(t *testing.T) { - hello := protocol.HelloMessage{ + hello := protocol.HelloResult{ ClientName: "syncthing", ClientVersion: "v0.9.4", } diff --git a/lib/protocol/hello.go b/lib/protocol/hello.go new file mode 100644 index 00000000..b7b324b2 --- /dev/null +++ b/lib/protocol/hello.go @@ -0,0 +1,105 @@ +// Copyright (C) 2016 The Protocol Authors. + +package protocol + +import ( + "encoding/binary" + "errors" + "fmt" + "io" +) + +// The HelloMessage interface is implemented by the version specific hello +// message. It knows its magic number and how to serialize itself to a byte +// buffer. +type HelloMessage interface { + Magic() uint32 + Marshal() ([]byte, error) +} + +// The HelloResult is the non version specific interpretation of the other +// side's Hello message. +type HelloResult struct { + DeviceName string + ClientName string + ClientVersion string +} + +var ( + // ErrTooOldVersion12 is returned by ExchangeHello when the other side + // speaks the older, incompatible version 0.12 of the protocol. + ErrTooOldVersion12 = errors.New("the remote device speaks an older version of the protocol (v0.12) not compatible with this version") + // ErrUnknownMagic is returned by ExchangeHellow when the other side + // speaks something entirely unknown. + ErrUnknownMagic = errors.New("the remote device speaks an unknown (newer?) version of the protocol") +) + +func ExchangeHello(c io.ReadWriter, h HelloMessage) (HelloResult, error) { + if err := writeHello(c, h); err != nil { + return HelloResult{}, err + } + return readHello(c) +} + +// IsVersionMismatch returns true if the error is a reliable indication of a +// version mismatch that we might want to alert the user about. +func IsVersionMismatch(err error) bool { + switch err { + case ErrTooOldVersion12, ErrUnknownMagic: + return true + default: + return false + } +} + +func readHello(c io.Reader) (HelloResult, error) { + header := make([]byte, 8) + if _, err := io.ReadFull(c, header); err != nil { + return HelloResult{}, err + } + + switch binary.BigEndian.Uint32(header[:4]) { + case Version13HelloMagic: + // This is a v0.13 Hello message in XDR format + msgSize := binary.BigEndian.Uint32(header[4:]) + if msgSize > 1024 { + return HelloResult{}, fmt.Errorf("hello message too big") + } + buf := make([]byte, msgSize) + if _, err := io.ReadFull(c, buf); err != nil { + return HelloResult{}, err + } + + var hello Version13HelloMessage + if err := hello.UnmarshalXDR(buf); err != nil { + return HelloResult{}, err + } + res := HelloResult{ + DeviceName: hello.DeviceName, + ClientName: hello.ClientName, + ClientVersion: hello.ClientVersion, + } + return res, nil + + case 0x00010001, 0x00010000: + // This is the first word of a v0.12 cluster config message. + // (Version 0, message ID 1, message type 0, compression enabled or disabled) + return HelloResult{}, ErrTooOldVersion12 + } + + return HelloResult{}, ErrUnknownMagic +} + +func writeHello(c io.Writer, h HelloMessage) error { + msg, err := h.Marshal() + if err != nil { + return err + } + + header := make([]byte, 8) + binary.BigEndian.PutUint32(header[:4], h.Magic()) + binary.BigEndian.PutUint32(header[4:], uint32(len(msg))) + + _, err = c.Write(append(header, msg...)) + return err +} diff --git a/lib/protocol/hello_test.go b/lib/protocol/hello_test.go new file mode 100644 index 00000000..01caad54 --- /dev/null +++ b/lib/protocol/hello_test.go @@ -0,0 +1,150 @@ +// Copyright (C) 2016 The Protocol Authors. + +package protocol + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "io" + "regexp" + "testing" +) + +var spaceRe = regexp.MustCompile(`\s`) + +func TestVersion13Hello(t *testing.T) { + // Tests that we can send and receive a version 0.13 hello message. + + expected := Version13HelloMessage{ + DeviceName: "test device", + ClientName: "syncthing", + ClientVersion: "v0.13.5", + } + msgBuf := expected.MustMarshalXDR() + + hdrBuf := make([]byte, 8) + binary.BigEndian.PutUint32(hdrBuf, Version13HelloMagic) + binary.BigEndian.PutUint32(hdrBuf[4:], uint32(len(msgBuf))) + + outBuf := new(bytes.Buffer) + outBuf.Write(hdrBuf) + outBuf.Write(msgBuf) + + inBuf := new(bytes.Buffer) + + conn := &readWriter{outBuf, inBuf} + + send := Version13HelloMessage{ + DeviceName: "this device", + ClientName: "other client", + ClientVersion: "v0.13.6", + } + + res, err := ExchangeHello(conn, send) + if err != nil { + t.Fatal(err) + } + + if res.ClientName != expected.ClientName { + t.Errorf("incorrect ClientName %q != expected %q", res.ClientName, expected.ClientName) + } + if res.ClientVersion != expected.ClientVersion { + t.Errorf("incorrect ClientVersion %q != expected %q", res.ClientVersion, expected.ClientVersion) + } + if res.DeviceName != expected.DeviceName { + t.Errorf("incorrect DeviceName %q != expected %q", res.DeviceName, expected.DeviceName) + } +} + +func TestVersion12Hello(t *testing.T) { + // Tests that we can correctly interpret the lack of a hello message + // from a v0.12 client. + + // This is the typical v0.12 connection start - our message header for a + // ClusterConfig message and then the cluster config message data. Taken + // from a protocol dump of a recent v0.12 client. + msg, _ := hex.DecodeString(spaceRe.ReplaceAllString(` + 00010001 + 0000014a + 7802000070000000027332000100a00973796e637468696e670e00b000000876 + 302e31322e32352400b00000000764656661756c741e00f01603000000204794 + 03ffdef496b5f5e5bc9c0a15221e70073164509fa30761af63094f6f945c3800 + 2073312f00f20b0001000000157463703a2f2f3132372e302e302e313a323230 + 301f00012400080500003000001000f1122064516fb94d24e7b637d20d9846eb + aeffb09556ef3968c8276fefc3fe24c144c2640002c0000034000f640002021f + 00004f00090400003000001100f11220dff67945f05bdab4270acd6057f1eacf + a3ac93cade07ce6a89384c181ad6b80e640010332b000fc80007021f00012400 + 080500046400041400f21f2dc2af5c5f28e38384295f2fc2af2052c3a46b736d + c3b67267c3a57320e58aa8e4bd9c20d090d0b4d180d0b5d18136001f026c01b8 + 90000000000000000000`, ``)) + + outBuf := new(bytes.Buffer) + outBuf.Write(msg) + + inBuf := new(bytes.Buffer) + + conn := &readWriter{outBuf, inBuf} + + send := Version13HelloMessage{ + DeviceName: "this device", + ClientName: "other client", + ClientVersion: "v0.13.6", + } + + _, err := ExchangeHello(conn, send) + if err != ErrTooOldVersion12 { + t.Errorf("unexpected error %v != ErrTooOld", err) + } +} + +func TestUnknownHello(t *testing.T) { + // Tests that we react correctly to a completely unknown magic number. + + // This is an unknown magic follow byte some message data. + msg, _ := hex.DecodeString(spaceRe.ReplaceAllString(` + 12345678 + 0000014a + 7802000070000000027332000100a00973796e637468696e670e00b000000876 + 302e31322e32352400b00000000764656661756c741e00f01603000000204794 + 03ffdef496b5f5e5bc9c0a15221e70073164509fa30761af63094f6f945c3800 + 2073312f00f20b0001000000157463703a2f2f3132372e302e302e313a323230 + 301f00012400080500003000001000f1122064516fb94d24e7b637d20d9846eb + aeffb09556ef3968c8276fefc3fe24c144c2640002c0000034000f640002021f + 00004f00090400003000001100f11220dff67945f05bdab4270acd6057f1eacf + a3ac93cade07ce6a89384c181ad6b80e640010332b000fc80007021f00012400 + 080500046400041400f21f2dc2af5c5f28e38384295f2fc2af2052c3a46b736d + c3b67267c3a57320e58aa8e4bd9c20d090d0b4d180d0b5d18136001f026c01b8 + 90000000000000000000`, ``)) + + outBuf := new(bytes.Buffer) + outBuf.Write(msg) + + inBuf := new(bytes.Buffer) + + conn := &readWriter{outBuf, inBuf} + + send := Version13HelloMessage{ + DeviceName: "this device", + ClientName: "other client", + ClientVersion: "v0.13.6", + } + + _, err := ExchangeHello(conn, send) + if err != ErrUnknownMagic { + t.Errorf("unexpected error %v != ErrUnknownMagic", err) + } +} + +type readWriter struct { + r io.Reader + w io.Writer +} + +func (rw *readWriter) Write(data []byte) (int, error) { + return rw.w.Write(data) +} + +func (rw *readWriter) Read(data []byte) (int, error) { + return rw.r.Read(data) +} diff --git a/lib/protocol/hello_v0.13.go b/lib/protocol/hello_v0.13.go new file mode 100644 index 00000000..e56f1d93 --- /dev/null +++ b/lib/protocol/hello_v0.13.go @@ -0,0 +1,24 @@ +// Copyright (C) 2016 The Protocol Authors. + +//go:generate -command genxdr go run ../../vendor/github.com/calmh/xdr/cmd/genxdr/main.go +//go:generate genxdr -o hello_v0.13_xdr.go hello_v0.13.go + +package protocol + +var ( + Version13HelloMagic uint32 = 0x9F79BC40 +) + +type Version13HelloMessage struct { + DeviceName string // max:64 + ClientName string // max:64 + ClientVersion string // max:64 +} + +func (m Version13HelloMessage) Magic() uint32 { + return Version13HelloMagic +} + +func (m Version13HelloMessage) Marshal() ([]byte, error) { + return m.MarshalXDR() +} diff --git a/lib/protocol/hello_v0.13_xdr.go b/lib/protocol/hello_v0.13_xdr.go new file mode 100644 index 00000000..ecf14967 --- /dev/null +++ b/lib/protocol/hello_v0.13_xdr.go @@ -0,0 +1,85 @@ +// ************************************************************ +// This file is automatically generated by genxdr. Do not edit. +// ************************************************************ + +package protocol + +import ( + "github.com/calmh/xdr" +) + +/* + +Version13HelloMessage Structure: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/ / +\ Device Name (length + padded data) \ +/ / ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/ / +\ Client Name (length + padded data) \ +/ / ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/ / +\ Client Version (length + padded data) \ +/ / ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +struct Version13HelloMessage { + string DeviceName<64>; + string ClientName<64>; + string ClientVersion<64>; +} + +*/ + +func (o Version13HelloMessage) XDRSize() int { + return 4 + len(o.DeviceName) + xdr.Padding(len(o.DeviceName)) + + 4 + len(o.ClientName) + xdr.Padding(len(o.ClientName)) + + 4 + len(o.ClientVersion) + xdr.Padding(len(o.ClientVersion)) +} + +func (o Version13HelloMessage) MarshalXDR() ([]byte, error) { + buf := make([]byte, o.XDRSize()) + m := &xdr.Marshaller{Data: buf} + return buf, o.MarshalXDRInto(m) +} + +func (o Version13HelloMessage) MustMarshalXDR() []byte { + bs, err := o.MarshalXDR() + if err != nil { + panic(err) + } + return bs +} + +func (o Version13HelloMessage) MarshalXDRInto(m *xdr.Marshaller) error { + if l := len(o.DeviceName); l > 64 { + return xdr.ElementSizeExceeded("DeviceName", l, 64) + } + m.MarshalString(o.DeviceName) + if l := len(o.ClientName); l > 64 { + return xdr.ElementSizeExceeded("ClientName", l, 64) + } + m.MarshalString(o.ClientName) + if l := len(o.ClientVersion); l > 64 { + return xdr.ElementSizeExceeded("ClientVersion", l, 64) + } + m.MarshalString(o.ClientVersion) + return m.Error +} + +func (o *Version13HelloMessage) UnmarshalXDR(bs []byte) error { + u := &xdr.Unmarshaller{Data: bs} + return o.UnmarshalXDRFrom(u) +} +func (o *Version13HelloMessage) UnmarshalXDRFrom(u *xdr.Unmarshaller) error { + o.DeviceName = u.UnmarshalStringMax(64) + o.ClientName = u.UnmarshalStringMax(64) + o.ClientVersion = u.UnmarshalStringMax(64) + return u.Error +} diff --git a/lib/protocol/message.go b/lib/protocol/message.go index 9e195661..e4bed2d1 100644 --- a/lib/protocol/message.go +++ b/lib/protocol/message.go @@ -12,16 +12,9 @@ import ( ) var ( - sha256OfEmptyBlock = sha256.Sum256(make([]byte, BlockSize)) - HelloMessageMagic uint32 = 0x9F79BC40 + sha256OfEmptyBlock = sha256.Sum256(make([]byte, BlockSize)) ) -type HelloMessage struct { - DeviceName string // max:64 - ClientName string // max:64 - ClientVersion string // max:64 -} - type IndexMessage struct { Folder string // max:256 Files []FileInfo // max:1000000 diff --git a/lib/protocol/message_xdr.go b/lib/protocol/message_xdr.go index 206bd394..32179642 100644 --- a/lib/protocol/message_xdr.go +++ b/lib/protocol/message_xdr.go @@ -10,82 +10,6 @@ import ( /* -HelloMessage Structure: - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -/ / -\ Device Name (length + padded data) \ -/ / -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -/ / -\ Client Name (length + padded data) \ -/ / -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -/ / -\ Client Version (length + padded data) \ -/ / -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - - -struct HelloMessage { - string DeviceName<64>; - string ClientName<64>; - string ClientVersion<64>; -} - -*/ - -func (o HelloMessage) XDRSize() int { - return 4 + len(o.DeviceName) + xdr.Padding(len(o.DeviceName)) + - 4 + len(o.ClientName) + xdr.Padding(len(o.ClientName)) + - 4 + len(o.ClientVersion) + xdr.Padding(len(o.ClientVersion)) -} - -func (o HelloMessage) MarshalXDR() ([]byte, error) { - buf := make([]byte, o.XDRSize()) - m := &xdr.Marshaller{Data: buf} - return buf, o.MarshalXDRInto(m) -} - -func (o HelloMessage) MustMarshalXDR() []byte { - bs, err := o.MarshalXDR() - if err != nil { - panic(err) - } - return bs -} - -func (o HelloMessage) MarshalXDRInto(m *xdr.Marshaller) error { - if l := len(o.DeviceName); l > 64 { - return xdr.ElementSizeExceeded("DeviceName", l, 64) - } - m.MarshalString(o.DeviceName) - if l := len(o.ClientName); l > 64 { - return xdr.ElementSizeExceeded("ClientName", l, 64) - } - m.MarshalString(o.ClientName) - if l := len(o.ClientVersion); l > 64 { - return xdr.ElementSizeExceeded("ClientVersion", l, 64) - } - m.MarshalString(o.ClientVersion) - return m.Error -} - -func (o *HelloMessage) UnmarshalXDR(bs []byte) error { - u := &xdr.Unmarshaller{Data: bs} - return o.UnmarshalXDRFrom(u) -} -func (o *HelloMessage) UnmarshalXDRFrom(u *xdr.Unmarshaller) error { - o.DeviceName = u.UnmarshalStringMax(64) - o.ClientName = u.UnmarshalStringMax(64) - o.ClientVersion = u.UnmarshalStringMax(64) - return u.Error -} - -/* - IndexMessage Structure: 0 1 2 3 diff --git a/lib/protocol/protocol_test.go b/lib/protocol/protocol_test.go index 82cdd075..2a6f5178 100644 --- a/lib/protocol/protocol_test.go +++ b/lib/protocol/protocol_test.go @@ -188,16 +188,6 @@ func TestClose(t *testing.T) { } } -func TestElementSizeExceededNested(t *testing.T) { - m := HelloMessage{ - ClientName: "longstringlongstringlongstringinglongstringlongstringlonlongstringlongstringlon", - } - _, err := m.MarshalXDR() - if err == nil { - t.Errorf("ID length %d > max 64, but no error", len(m.ClientName)) - } -} - func TestMarshalIndexMessage(t *testing.T) { if testing.Short() { quickCfg.MaxCount = 10