Blob


1 package p9p
3 import (
4 "fmt"
6 "context"
7 )
9 // NOTE(stevvooe): This file contains functions for negotiating version on the
10 // client and server. There are some nasty details to get right for
11 // downgrading the connection on the server-side that are not present yet.
12 // Really, these should be refactored into some sort of channel type that can
13 // support resets through version messages during the protocol exchange.
15 // clientnegotiate negiotiates the protocol version using channel, blocking
16 // until a response is received. The received value will be the version
17 // implemented by the server.
18 func clientnegotiate(ctx context.Context, ch Channel, version string) (string, error) {
19 req := newFcall(NOTAG, MessageTversion{
20 MSize: uint32(ch.MSize()),
21 Version: version,
22 })
24 if err := ch.WriteFcall(ctx, req); err != nil {
25 return "", err
26 }
28 resp := new(Fcall)
29 if err := ch.ReadFcall(ctx, resp); err != nil {
30 return "", err
31 }
33 switch v := resp.Message.(type) {
34 case MessageRversion:
36 if v.Version != version {
37 // TODO(stevvooe): A stubborn client indeed!
38 return "", fmt.Errorf("unsupported server version: %v", version)
39 }
41 if int(v.MSize) < ch.MSize() {
42 // upgrade msize if server differs.
43 ch.SetMSize(int(v.MSize))
44 }
46 return v.Version, nil
47 case error:
48 return "", v
49 default:
50 return "", ErrUnexpectedMsg
51 }
52 }
54 // servernegotiate blocks until a version message is received or a timeout
55 // occurs. The msize for the tranport will be set from the negotiation. If
56 // negotiate returns nil, a server may proceed with the connection.
57 //
58 // In the future, it might be better to handle the version messages in a
59 // separate object that manages the session. Each set of version requests
60 // effectively "reset" a connection, meaning all fids get clunked and all
61 // outstanding IO is aborted. This is probably slightly racy, in practice with
62 // a misbehaved client. The main issue is that we cannot tell which session
63 // messages belong to.
64 func servernegotiate(ctx context.Context, ch Channel, version string) error {
65 // wait for the version message over the transport.
66 req := new(Fcall)
67 if err := ch.ReadFcall(ctx, req); err != nil {
68 return err
69 }
71 mv, ok := req.Message.(MessageTversion)
72 if !ok {
73 return fmt.Errorf("expected version message: %v", mv)
74 }
76 respmsg := MessageRversion{
77 Version: version,
78 }
80 if mv.Version != version {
81 // TODO(stevvooe): Not the best place to do version handling. We need
82 // to have a way to pass supported versions into this method then have
83 // it return the actual version. For now, respond with 9P2000 for
84 // anything that doesn't match the provided version string.
85 //
86 // version(9) says "The server may respond with the client’s
87 // version string, or a version string identifying an earlier
88 // defined protocol version. Currently, the only defined
89 // version is the 6 characters 9P2000." Therefore, it is always
90 // OK to respond with this.
91 respmsg.Version = "9P2000"
92 }
94 if int(mv.MSize) < ch.MSize() {
95 // if the server msize is too large, use the client's suggested msize.
96 ch.SetMSize(int(mv.MSize))
97 respmsg.MSize = mv.MSize
98 } else {
99 respmsg.MSize = uint32(ch.MSize())
102 resp := newFcall(NOTAG, respmsg)
103 if err := ch.WriteFcall(ctx, resp); err != nil {
104 return err
107 if respmsg.Version == "unknown" {
108 return fmt.Errorf("bad version negotiation")
111 return nil