commit deb98ab43c4d46aadb8214e05c8178b2eceb1fae from: Stephen J Day date: Thu Nov 05 04:50:20 2015 UTC fs/p9p/new: various client and server cleanup Signed-off-by: Stephen J Day commit - ba353f358bc496e69ab151ff377a4d041f306ad5 commit + deb98ab43c4d46aadb8214e05c8178b2eceb1fae blob - 452188d9186f024575da5ff0019c5a295a5dfa61 blob + 95fb256b47676759a4ae4face3f337bfea447540 --- client.go +++ client.go @@ -1,9 +1,6 @@ package p9pnew import ( - "fmt" - "log" - "golang.org/x/net/context" "net" @@ -43,178 +40,199 @@ func (c *client) Version() (int, string) { } func (c *client) Auth(ctx context.Context, afid Fid, uname, aname string) (Qid, error) { - panic("not implemented") + m := MessageTauth{ + Afid: afid, + Uname: uname, + Aname: aname, + } + + resp, err := c.transport.send(ctx, m) + if err != nil { + return Qid{}, nil + } + + rauth, ok := resp.(MessageRauth) + if !ok { + return Qid{}, ErrUnexpectedMsg + } + + return rauth.Qid, nil } func (c *client) Attach(ctx context.Context, fid, afid Fid, uname, aname string) (Qid, error) { - log.Println("client attach", fid, aname) - fcall := &Fcall{ - Type: Tattach, - Message: &MessageTattach{ - Fid: fid, - Afid: afid, - Uname: uname, - Aname: aname, - }, + m := MessageTattach{ + Fid: fid, + Afid: afid, + Uname: uname, + Aname: aname, } - resp, err := c.transport.send(ctx, fcall) + resp, err := c.transport.send(ctx, m) if err != nil { return Qid{}, err } - mrr, ok := resp.Message.(*MessageRattach) + rattach, ok := resp.(MessageRattach) if !ok { - return Qid{}, fmt.Errorf("invalid rpc response for attach message: %v", resp) + return Qid{}, ErrUnexpectedMsg } - return mrr.Qid, nil + return rattach.Qid, nil } func (c *client) Clunk(ctx context.Context, fid Fid) error { - fcall := newFcall(&MessageTclunk{ + resp, err := c.transport.send(ctx, MessageTclunk{ Fid: fid, }) - - resp, err := c.transport.send(ctx, fcall) if err != nil { return err } - if resp.Type != Rclunk { - return fmt.Errorf("incorrect response type: %v", resp) + _, ok := resp.(MessageRclunk) + if !ok { + return ErrUnexpectedMsg } return nil } func (c *client) Remove(ctx context.Context, fid Fid) error { - panic("not implemented") + resp, err := c.transport.send(ctx, MessageTremove{ + Fid: fid, + }) + if err != nil { + return err + } + + _, ok := resp.(MessageRremove) + if !ok { + return ErrUnexpectedMsg + } + + return nil } func (c *client) Walk(ctx context.Context, fid Fid, newfid Fid, names ...string) ([]Qid, error) { if len(names) > 16 { - return nil, fmt.Errorf("too many elements in wname") + return nil, ErrWalkLimit } - fcall := &Fcall{ - Type: Twalk, - Message: &MessageTwalk{ - Fid: fid, - Newfid: newfid, - Wnames: names, - }, - } - - resp, err := c.transport.send(ctx, fcall) + resp, err := c.transport.send(ctx, MessageTwalk{ + Fid: fid, + Newfid: newfid, + Wnames: names, + }) if err != nil { return nil, err } - mrr, ok := resp.Message.(*MessageRwalk) + rwalk, ok := resp.(MessageRwalk) if !ok { - return nil, fmt.Errorf("invalid rpc response for walk message: %v", resp) + return nil, ErrUnexpectedMsg } - return mrr.Qids, nil + return rwalk.Qids, nil } func (c *client) Read(ctx context.Context, fid Fid, p []byte, offset int64) (n int, err error) { - // TODO(stevvooe): Split up reads into multiple messages based on iounit. - // For now, we just support full blast. I mean, why not? - fcall := &Fcall{ - Type: Tread, - Message: &MessageTread{ - Fid: fid, - Offset: uint64(offset), - Count: uint32(len(p)), - }, - } - - resp, err := c.transport.send(ctx, fcall) + resp, err := c.transport.send(ctx, MessageTread{ + Fid: fid, + Offset: uint64(offset), + Count: uint32(len(p)), + }) if err != nil { return 0, err } - mrr, ok := resp.Message.(*MessageRread) + rread, ok := resp.(MessageRread) if !ok { - return 0, fmt.Errorf("invalid rpc response for read message: %v", resp) + return 0, ErrUnexpectedMsg } - return copy(p, mrr.Data), nil + return copy(p, rread.Data), nil } func (c *client) Write(ctx context.Context, fid Fid, p []byte, offset int64) (n int, err error) { - // TODO(stevvooe): Split up writes into multiple messages based on iounit. - // For now, we just support full blast. I mean, why not? - fcall := &Fcall{ - Type: Twrite, - Message: &MessageTwrite{ - Fid: fid, - Offset: uint64(offset), - Data: p, - }, - } - - resp, err := c.transport.send(ctx, fcall) + resp, err := c.transport.send(ctx, MessageTwrite{ + Fid: fid, + Offset: uint64(offset), + Data: p, + }) if err != nil { return 0, err } - mrr, ok := resp.Message.(*MessageRwrite) + rwrite, ok := resp.(MessageRwrite) if !ok { - return 0, fmt.Errorf("invalid rpc response for write message: %v", resp) + return 0, ErrUnexpectedMsg } - return int(mrr.Count), nil + return int(rwrite.Count), nil } -func (c *client) Open(ctx context.Context, fid Fid, mode uint8) (Qid, uint32, error) { - fcall := newFcall(&MessageTopen{ +func (c *client) Open(ctx context.Context, fid Fid, mode Flag) (Qid, uint32, error) { + resp, err := c.transport.send(ctx, MessageTopen{ Fid: fid, Mode: mode, }) - - resp, err := c.transport.send(ctx, fcall) if err != nil { return Qid{}, 0, err } - respmsg, ok := resp.Message.(*MessageRopen) + ropen, ok := resp.(MessageRopen) if !ok { - return Qid{}, 0, fmt.Errorf("invalid rpc response for open message: %v", resp) + return Qid{}, 0, ErrUnexpectedMsg } - return respmsg.Qid, respmsg.IOUnit, nil + return ropen.Qid, ropen.IOUnit, nil } -func (c *client) Create(ctx context.Context, parent Fid, name string, perm uint32, mode uint32) (Qid, uint32, error) { - panic("not implemented") +func (c *client) Create(ctx context.Context, parent Fid, name string, perm uint32, mode Flag) (Qid, uint32, error) { + resp, err := c.transport.send(ctx, MessageTcreate{ + Fid: parent, + Name: name, + Perm: perm, + Mode: mode, + }) + if err != nil { + return Qid{}, 0, err + } + + rcreate, ok := resp.(MessageRcreate) + if !ok { + return Qid{}, 0, ErrUnexpectedMsg + } + + return rcreate.Qid, rcreate.IOUnit, nil } func (c *client) Stat(ctx context.Context, fid Fid) (Dir, error) { - fcall := newFcall(MessageTstat{Fid: fid}) - - resp, err := c.transport.send(ctx, fcall) + resp, err := c.transport.send(ctx, MessageTstat{Fid: fid}) if err != nil { return Dir{}, err } - respmsg, ok := resp.Message.(*MessageRstat) + rstat, ok := resp.(MessageRstat) if !ok { - return Dir{}, fmt.Errorf("invalid rpc response for stat message: %v", resp) + return Dir{}, ErrUnexpectedMsg } - return respmsg.Stat, nil + return rstat.Stat, nil } -func (c *client) WStat(context.Context, Fid, Dir) error { - panic("not implemented") -} +func (c *client) WStat(ctx context.Context, fid Fid, dir Dir) error { + resp, err := c.transport.send(ctx, MessageTwstat{ + Fid: fid, + Stat: dir, + }) + if err != nil { + return err + } -func (c *client) flush(ctx context.Context, tag Tag) error { - // TODO(stevvooe): We need to fire and forget flush messages when a call - // context gets cancelled. + _, ok := resp.(MessageRwstat) + if !ok { + return ErrUnexpectedMsg + } - panic("not implemented") + return nil } blob - 2da30033b5ccc50bec99a071ca5793ebeaec32f0 blob + 91c7ce686abe1d94fdf3f2832597df6565738a67 --- cmd/9pr/main.go +++ cmd/9pr/main.go @@ -187,7 +187,7 @@ func (c *fsCommander) cmdls(ctx context.Context, args if iounit < 1 { msize, _ := c.session.Version() - iounit = msize - 24 // size of message max minus fcall io header (Rread) + iounit = uint32(msize - 24) // size of message max minus fcall io header (Rread) } p := make([]byte, iounit) @@ -198,10 +198,10 @@ func (c *fsCommander) cmdls(ctx context.Context, args } rd := bytes.NewReader(p[:n]) - + codec := p9pnew.NewCodec() // TODO(stevvooe): Need way to resolve codec based on session. for { var d p9pnew.Dir - if err := p9pnew.DecodeDir(rd, &d); err != nil { + if err := p9pnew.DecodeDir(codec, rd, &d); err != nil { if err == io.EOF { break } @@ -318,7 +318,7 @@ func (c *fsCommander) cmdcat(ctx context.Context, args if iounit < 1 { msize, _ := c.session.Version() - iounit = msize - 24 // size of message max minus fcall io header (Rread) + iounit = uint32(msize - 24) // size of message max minus fcall io header (Rread) } b := make([]byte, iounit) blob - 5d884701a2cbc04f08d9dff5e5ddfcf4c4e27e27 blob + 82597e172e28481545b1081509d53f1f7a7c13bb --- dispatcher.go +++ dispatcher.go @@ -33,7 +33,7 @@ func (d *dispatcher) handle(ctx context.Context, req * resp = newFcall(MessageRauth{Qid: qid}) case Tattach: - reqmsg, ok := req.Message.(*MessageTattach) + reqmsg, ok := req.Message.(MessageTattach) if !ok { return nil, fmt.Errorf("bad message: %v message=%#v", req, req.Message) } @@ -47,7 +47,7 @@ func (d *dispatcher) handle(ctx context.Context, req * Qid: qid, }) case Twalk: - reqmsg, ok := req.Message.(*MessageTwalk) + reqmsg, ok := req.Message.(MessageTwalk) if !ok { return nil, fmt.Errorf("bad message: %v message=%#v", req, req.Message) } @@ -62,11 +62,11 @@ func (d *dispatcher) handle(ctx context.Context, req * return nil, err } - resp = newFcall(&MessageRwalk{ + resp = newFcall(MessageRwalk{ Qids: qids, }) case Topen: - reqmsg, ok := req.Message.(*MessageTopen) + reqmsg, ok := req.Message.(MessageTopen) if !ok { return nil, fmt.Errorf("bad message: %v message=%v", req, req.Message) } @@ -76,17 +76,17 @@ func (d *dispatcher) handle(ctx context.Context, req * return nil, err } - resp = newFcall(&MessageRopen{ + resp = newFcall(MessageRopen{ Qid: qid, IOUnit: iounit, }) case Tcreate: - reqmsg, ok := req.Message.(*MessageTcreate) + reqmsg, ok := req.Message.(MessageTcreate) if !ok { return nil, fmt.Errorf("bad message: %v message=%v", req, req.Message) } - qid, iounit, err := d.session.Create(ctx, reqmsg.Fid, reqmsg.Name, reqmsg.Perm, uint32(reqmsg.Mode)) + qid, iounit, err := d.session.Create(ctx, reqmsg.Fid, reqmsg.Name, reqmsg.Perm, reqmsg.Mode) if err != nil { return nil, err } @@ -97,7 +97,7 @@ func (d *dispatcher) handle(ctx context.Context, req * }) case Tread: - reqmsg, ok := req.Message.(*MessageTread) + reqmsg, ok := req.Message.(MessageTread) if !ok { return nil, fmt.Errorf("bad message: %v message=%v", req, req.Message) } @@ -112,7 +112,7 @@ func (d *dispatcher) handle(ctx context.Context, req * Data: p[:n], }) case Twrite: - reqmsg, ok := req.Message.(*MessageTwrite) + reqmsg, ok := req.Message.(MessageTwrite) if !ok { return nil, fmt.Errorf("bad message: %v message=%v", req, req.Message) } @@ -126,7 +126,7 @@ func (d *dispatcher) handle(ctx context.Context, req * Count: uint32(n), }) case Tclunk: - reqmsg, ok := req.Message.(*MessageTclunk) + reqmsg, ok := req.Message.(MessageTclunk) if !ok { return nil, fmt.Errorf("bad message: %v message=%v", req, req.Message) } @@ -137,9 +137,9 @@ func (d *dispatcher) handle(ctx context.Context, req * return nil, err } - resp = newFcall(&MessageRclunk{}) + resp = newFcall(MessageRclunk{}) case Tremove: - reqmsg, ok := req.Message.(*MessageTremove) + reqmsg, ok := req.Message.(MessageTremove) if !ok { return nil, fmt.Errorf("bad message: %v message=%v", req, req.Message) } @@ -150,7 +150,7 @@ func (d *dispatcher) handle(ctx context.Context, req * resp = newFcall(&MessageRremove{}) case Tstat: - reqmsg, ok := req.Message.(*MessageTstat) + reqmsg, ok := req.Message.(MessageTstat) if !ok { return nil, fmt.Errorf("bad message: %v message=%v", req, req.Message) } @@ -160,7 +160,7 @@ func (d *dispatcher) handle(ctx context.Context, req * return nil, err } - resp = newFcall(&MessageRstat{ + resp = newFcall(MessageRstat{ Stat: dir, }) case Twstat: blob - bfce22d1890ddf9139f07f5af8b9f279299a429c blob + 326a180bfe49d872d8cbfae19d0b2e3f5e0d92fb --- encoding.go +++ encoding.go @@ -19,6 +19,9 @@ type Codec interface { // Marshal the value v into a byte slice. Marshal(v interface{}) ([]byte, error) + + // Size returns the encoded size for the target of v. + Size(v interface{}) int } func NewCodec() Codec { @@ -41,8 +44,43 @@ func (c codec9p) Marshal(v interface{}) ([]byte, error } return b.Bytes(), nil +} + +func (c codec9p) Size(v interface{}) int { + return int(size9p(v)) +} + +// DecodeDir decodes a directory entry from rd using the provided codec. +func DecodeDir(codec Codec, rd io.Reader, d *Dir) error { + var ll uint16 + + // pull the size off the wire + if err := binary.Read(rd, binary.LittleEndian, &ll); err != nil { + return err + } + + p := make([]byte, ll+2) + binary.LittleEndian.PutUint16(p, ll) // must have size at start + + // read out the rest of the record + if _, err := io.ReadFull(rd, p[2:]); err != nil { + return err + } + + return codec.Unmarshal(p, d) } +// EncodeDir writes the directory to wr. +func EncodeDir(codec Codec, wr io.Writer, d *Dir) error { + p, err := codec.Marshal(d) + if err != nil { + return err + } + + _, err = wr.Write(p) + return err +} + type encoder struct { wr io.Writer } @@ -50,8 +88,8 @@ type encoder struct { func (e *encoder) encode(vs ...interface{}) error { for _, v := range vs { switch v := v.(type) { - case uint8, uint16, uint32, uint64, FcallType, Tag, QType, Fid, - *uint8, *uint16, *uint32, *uint64, *FcallType, *Tag, *QType, *Fid: + case uint8, uint16, uint32, uint64, FcallType, Tag, QType, Fid, Flag, + *uint8, *uint16, *uint32, *uint64, *FcallType, *Tag, *QType, *Fid, *Flag: if err := binary.Write(e.wr, binary.LittleEndian, v); err != nil { return err } @@ -188,7 +226,7 @@ type decoder struct { func (d *decoder) decode(vs ...interface{}) error { for _, v := range vs { switch v := v.(type) { - case *uint8, *uint16, *uint32, *uint64, *FcallType, *Tag, *QType, *Fid: + case *uint8, *uint16, *uint32, *uint64, *FcallType, *Tag, *QType, *Fid, *Flag: if err := binary.Read(d.rd, binary.LittleEndian, v); err != nil { return err } @@ -352,8 +390,8 @@ func size9p(vs ...interface{}) uint32 { } switch v := v.(type) { - case uint8, uint16, uint32, uint64, FcallType, Tag, QType, Fid, - *uint8, *uint16, *uint32, *uint64, *FcallType, *Tag, *QType, *Fid: + case uint8, uint16, uint32, uint64, FcallType, Tag, QType, Fid, Flag, + *uint8, *uint16, *uint32, *uint64, *FcallType, *Tag, *QType, *Fid, *Flag: s += uint32(binary.Size(v)) case []byte: s += uint32(binary.Size(uint32(0)) + len(v)) blob - 000834bd884dcf2b5745120f03727e76a12f91f3 blob + 07a2464c0eb591b5e43f6f1877c7987e72d339a6 --- errors.go +++ errors.go @@ -2,13 +2,8 @@ package p9pnew import "errors" -// common errors returned by Session interface methods var ( - ErrClosed = errors.New("closed") -) - -// 9p wire errors returned by Session interface methods -var ( + // 9p wire errors returned by Session interface methods ErrBadattach = new9pError("unknown specifier in attach") ErrBadoffset = new9pError("bad offset") ErrBadcount = new9pError("bad count") @@ -30,9 +25,12 @@ var ( ErrWalknodir = new9pError("walk in non-directory") // extra errors not part of the normal protocol - ErrTimeout = new9pError("fcall timeout") // returned when timing out on the fcall - ErrUnknownTag = new9pError("unknown tag") - ErrUnknownMsg = new9pError("unknown message") // returned when encountering unknown message type + ErrTimeout = new9pError("fcall timeout") // returned when timing out on the fcall + ErrUnknownTag = new9pError("unknown tag") + ErrUnknownMsg = new9pError("unknown message") // returned when encountering unknown message type + ErrUnexpectedMsg = new9pError("unexpected message") // returned when an unexpected message is encountered + ErrWalkLimit = new9pError("too many wnames in walk") + ErrClosed = errors.New("closed") ) // new9pError returns a new 9p error ready for the wire. blob - a3c74c87f6bcdac3e0cbeefd612a8da2a49002b6 blob + 259e3bf04f20fa65449c543557ecd1a4ac6d0363 --- messages.go +++ messages.go @@ -190,7 +190,7 @@ type MessageRwalk struct { type MessageTopen struct { Fid Fid - Mode uint8 + Mode Flag } type MessageRopen struct { @@ -202,7 +202,7 @@ type MessageTcreate struct { Fid Fid Name string Perm uint32 - Mode uint8 + Mode Flag } type MessageRcreate struct { blob - 975a0aad7cfa145559b902deb64d7dfc36c64140 blob + 2fac1b1c58190f943b61509cb4cb951170a95b4f --- session.go +++ session.go @@ -25,8 +25,8 @@ type Session interface { Walk(ctx context.Context, fid Fid, newfid Fid, names ...string) ([]Qid, error) Read(ctx context.Context, fid Fid, p []byte, offset int64) (n int, err error) Write(ctx context.Context, fid Fid, p []byte, offset int64) (n int, err error) - Open(ctx context.Context, fid Fid, mode uint8) (Qid, uint32, error) - Create(ctx context.Context, parent Fid, name string, perm uint32, mode uint32) (Qid, uint32, error) + Open(ctx context.Context, fid Fid, mode Flag) (Qid, uint32, error) + Create(ctx context.Context, parent Fid, name string, perm uint32, mode Flag) (Qid, uint32, error) Stat(context.Context, Fid) (Dir, error) WStat(context.Context, Fid, Dir) error blob - 3caf076db643d3f72ce17f7e3d2b78ebcb38c588 blob + 9387f48e851e79a80260517317a258511a43253c --- transport.go +++ transport.go @@ -9,11 +9,11 @@ import ( ) // roundTripper manages the request and response from the client-side. A -// roundTripper must abide by many of the rules of http.RoundTripper. +// roundTripper must abide by similar rules to the http.RoundTripper. // Typically, the roundTripper will manage tag assignment and message // serialization. type roundTripper interface { - send(ctx context.Context, fc *Fcall) (*Fcall, error) + send(ctx context.Context, msg Message) (Message, error) } // transport plays the role of being a client channel manager. It multiplexes @@ -28,6 +28,8 @@ type transport struct { tags uint16 } +var _ roundTripper = &transport{} + func newTransport(ctx context.Context, ch *channel) roundTripper { t := &transport{ ctx: ctx, @@ -57,10 +59,10 @@ func newFcallRequest(ctx context.Context, fcall *Fcall } } -func (t *transport) send(ctx context.Context, fcall *Fcall) (*Fcall, error) { +func (t *transport) send(ctx context.Context, msg Message) (Message, error) { + fcall := newFcall(msg) req := newFcallRequest(ctx, fcall) - log.Println("dispatch", fcall) // dispatch the request. select { case <-t.closed: @@ -70,7 +72,6 @@ func (t *transport) send(ctx context.Context, fcall *F case t.requests <- req: } - log.Println("wait", fcall) // wait for the response. select { case <-t.closed: @@ -80,18 +81,17 @@ func (t *transport) send(ctx context.Context, fcall *F case err := <-req.err: return nil, err case resp := <-req.response: - log.Println("resp", resp) if resp.Type == Rerror { // pack the error into something useful - respmesg, ok := resp.Message.(*MessageRerror) + respmesg, ok := resp.Message.(MessageRerror) if !ok { return nil, fmt.Errorf("invalid error response: %v", resp) } - return nil, new9pError(respmesg.Ename) + return nil, respmesg } - return resp, nil + return resp.Message, nil } } @@ -178,14 +178,10 @@ func (t *transport) handle() { // the tag map and dealloc the tag. We may also want to send a // flush for the tag. if err := t.ch.WriteFcall(req.ctx, req.fcall); err != nil { - log.Println("error writing fcall", err, req.fcall) delete(outstanding, req.fcall.Tag) req.err <- err } - - log.Println("sent", req.fcall) case b := <-responses: - log.Println("recv", b) req, ok := outstanding[b.Tag] if !ok { panic("unknown tag received") @@ -203,6 +199,12 @@ func (t *transport) handle() { } } +func (t *transport) flush(ctx context.Context, tag Tag) error { + // TODO(stevvooe): We need to fire and forget flush messages when a call + // context gets cancelled. + panic("not implemented") +} + func (t *transport) Close() error { select { case <-t.closed: blob - 61918a21255f3ab026be2362a5ccebb7a78d1780 blob + ae206a0a4d8cb57bea8614eb2f1beb2b5933d616 --- types.go +++ types.go @@ -36,11 +36,14 @@ const ( DMEXEC = 0x1 // mode bit for execute permission ) +// Flag defines the flag type for use with open and create +type Flag uint8 + const ( - OREAD = 0x00 // open for read - OWRITE = 0x01 // write - ORDWR = 0x02 // read and write - OEXEC = 0x03 // execute, == read but check execute permission + OREAD Flag = 0x00 // open for read + OWRITE = 0x01 // write + ORDWR = 0x02 // read and write + OEXEC = 0x03 // execute, == read but check execute permission // PROPOSAL(stevvooe): Possible protocal extension to allow the create of // symlinks. Initially, the link is created with no value. Read and write @@ -53,16 +56,17 @@ const ( ORCLOSE = 0x40 // or'ed in, remove on close ) +// QType indicates the type of a resource within the Qid. type QType uint8 const ( QTDIR QType = 0x80 // type bit for directories - QTAPPEND QType = 0x40 // type bit for append only files - QTEXCL QType = 0x20 // type bit for exclusive use files - QTMOUNT QType = 0x10 // type bit for mounted channel - QTAUTH QType = 0x08 // type bit for authentication file - QTTMP QType = 0x04 // type bit for not-backed-up file - QTFILE QType = 0x00 // plain file + QTAPPEND = 0x40 // type bit for append only files + QTEXCL = 0x20 // type bit for exclusive use files + QTMOUNT = 0x10 // type bit for mounted channel + QTAUTH = 0x08 // type bit for authentication file + QTTMP = 0x04 // type bit for not-backed-up file + QTFILE = 0x00 // plain file ) func (qt QType) String() string { blob - c4c7133658b6c3bd3b428135ca0b61e56b00b033 blob + afcc1d2bf0f3c6eafe7ca67bd79d5221bee7fd82 --- version.go +++ version.go @@ -31,7 +31,7 @@ func clientnegotiate(ctx context.Context, ch Channel, } switch v := resp.Message.(type) { - case *MessageRversion: + case MessageRversion: if v.Version != version { // TODO(stevvooe): A stubborn client indeed! @@ -47,7 +47,7 @@ func clientnegotiate(ctx context.Context, ch Channel, case error: return "", v default: - return "", fmt.Errorf("invalid rpc response for version message: %v", resp) + return "", ErrUnexpectedMsg } }