github.com/v2fly/tools@v0.100.0/internal/lsp/lsprpc/lsprpc.go (about)

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package lsprpc implements a jsonrpc2.StreamServer that may be used to
     6  // serve the LSP on a jsonrpc2 channel.
     7  package lsprpc
     8  
     9  import (
    10  	"context"
    11  	"encoding/json"
    12  	"fmt"
    13  	"log"
    14  	"net"
    15  	"os"
    16  	"strconv"
    17  	"strings"
    18  	"sync"
    19  	"sync/atomic"
    20  	"time"
    21  
    22  	"github.com/v2fly/tools/internal/event"
    23  	"github.com/v2fly/tools/internal/gocommand"
    24  	"github.com/v2fly/tools/internal/jsonrpc2"
    25  	"github.com/v2fly/tools/internal/lsp"
    26  	"github.com/v2fly/tools/internal/lsp/cache"
    27  	"github.com/v2fly/tools/internal/lsp/command"
    28  	"github.com/v2fly/tools/internal/lsp/debug"
    29  	"github.com/v2fly/tools/internal/lsp/debug/tag"
    30  	"github.com/v2fly/tools/internal/lsp/protocol"
    31  	errors "golang.org/x/xerrors"
    32  )
    33  
    34  // AutoNetwork is the pseudo network type used to signal that gopls should use
    35  // automatic discovery to resolve a remote address.
    36  const AutoNetwork = "auto"
    37  
    38  // Unique identifiers for client/server.
    39  var serverIndex int64
    40  
    41  // The StreamServer type is a jsonrpc2.StreamServer that handles incoming
    42  // streams as a new LSP session, using a shared cache.
    43  type StreamServer struct {
    44  	cache *cache.Cache
    45  	// daemon controls whether or not to log new connections.
    46  	daemon bool
    47  
    48  	// serverForTest may be set to a test fake for testing.
    49  	serverForTest protocol.Server
    50  }
    51  
    52  // NewStreamServer creates a StreamServer using the shared cache. If
    53  // withTelemetry is true, each session is instrumented with telemetry that
    54  // records RPC statistics.
    55  func NewStreamServer(cache *cache.Cache, daemon bool) *StreamServer {
    56  	return &StreamServer{cache: cache, daemon: daemon}
    57  }
    58  
    59  // ServeStream implements the jsonrpc2.StreamServer interface, by handling
    60  // incoming streams using a new lsp server.
    61  func (s *StreamServer) ServeStream(ctx context.Context, conn jsonrpc2.Conn) error {
    62  	client := protocol.ClientDispatcher(conn)
    63  	session := s.cache.NewSession(ctx)
    64  	server := s.serverForTest
    65  	if server == nil {
    66  		server = lsp.NewServer(session, client)
    67  		debug.GetInstance(ctx).AddService(server, session)
    68  	}
    69  	// Clients may or may not send a shutdown message. Make sure the server is
    70  	// shut down.
    71  	// TODO(rFindley): this shutdown should perhaps be on a disconnected context.
    72  	defer func() {
    73  		if err := server.Shutdown(ctx); err != nil {
    74  			event.Error(ctx, "error shutting down", err)
    75  		}
    76  	}()
    77  	executable, err := os.Executable()
    78  	if err != nil {
    79  		log.Printf("error getting gopls path: %v", err)
    80  		executable = ""
    81  	}
    82  	ctx = protocol.WithClient(ctx, client)
    83  	conn.Go(ctx,
    84  		protocol.Handlers(
    85  			handshaker(session, executable, s.daemon,
    86  				protocol.ServerHandler(server,
    87  					jsonrpc2.MethodNotFound))))
    88  	if s.daemon {
    89  		log.Printf("Session %s: connected", session.ID())
    90  		defer log.Printf("Session %s: exited", session.ID())
    91  	}
    92  	<-conn.Done()
    93  	return conn.Err()
    94  }
    95  
    96  // A Forwarder is a jsonrpc2.StreamServer that handles an LSP stream by
    97  // forwarding it to a remote. This is used when the gopls process started by
    98  // the editor is in the `-remote` mode, which means it finds and connects to a
    99  // separate gopls daemon. In these cases, we still want the forwarder gopls to
   100  // be instrumented with telemetry, and want to be able to in some cases hijack
   101  // the jsonrpc2 connection with the daemon.
   102  type Forwarder struct {
   103  	network, addr string
   104  
   105  	// goplsPath is the path to the current executing gopls binary.
   106  	goplsPath string
   107  
   108  	// configuration for the auto-started gopls remote.
   109  	remoteConfig remoteConfig
   110  
   111  	mu sync.Mutex
   112  	// Hold on to the server connection so that we can redo the handshake if any
   113  	// information changes.
   114  	serverConn jsonrpc2.Conn
   115  	serverID   string
   116  }
   117  
   118  type remoteConfig struct {
   119  	debug         string
   120  	listenTimeout time.Duration
   121  	logfile       string
   122  }
   123  
   124  // A RemoteOption configures the behavior of the auto-started remote.
   125  type RemoteOption interface {
   126  	set(*remoteConfig)
   127  }
   128  
   129  // RemoteDebugAddress configures the address used by the auto-started Gopls daemon
   130  // for serving debug information.
   131  type RemoteDebugAddress string
   132  
   133  func (d RemoteDebugAddress) set(cfg *remoteConfig) {
   134  	cfg.debug = string(d)
   135  }
   136  
   137  // RemoteListenTimeout configures the amount of time the auto-started gopls
   138  // daemon will wait with no client connections before shutting down.
   139  type RemoteListenTimeout time.Duration
   140  
   141  func (d RemoteListenTimeout) set(cfg *remoteConfig) {
   142  	cfg.listenTimeout = time.Duration(d)
   143  }
   144  
   145  // RemoteLogfile configures the logfile location for the auto-started gopls
   146  // daemon.
   147  type RemoteLogfile string
   148  
   149  func (l RemoteLogfile) set(cfg *remoteConfig) {
   150  	cfg.logfile = string(l)
   151  }
   152  
   153  func defaultRemoteConfig() remoteConfig {
   154  	return remoteConfig{
   155  		listenTimeout: 1 * time.Minute,
   156  	}
   157  }
   158  
   159  // NewForwarder creates a new Forwarder, ready to forward connections to the
   160  // remote server specified by network and addr.
   161  func NewForwarder(network, addr string, opts ...RemoteOption) *Forwarder {
   162  	gp, err := os.Executable()
   163  	if err != nil {
   164  		log.Printf("error getting gopls path for forwarder: %v", err)
   165  		gp = ""
   166  	}
   167  
   168  	rcfg := defaultRemoteConfig()
   169  	for _, opt := range opts {
   170  		opt.set(&rcfg)
   171  	}
   172  
   173  	fwd := &Forwarder{
   174  		network:      network,
   175  		addr:         addr,
   176  		goplsPath:    gp,
   177  		remoteConfig: rcfg,
   178  	}
   179  	return fwd
   180  }
   181  
   182  // QueryServerState queries the server state of the current server.
   183  func QueryServerState(ctx context.Context, addr string) (*ServerState, error) {
   184  	serverConn, err := dialRemote(ctx, addr)
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  	var state ServerState
   189  	if err := protocol.Call(ctx, serverConn, sessionsMethod, nil, &state); err != nil {
   190  		return nil, errors.Errorf("querying server state: %w", err)
   191  	}
   192  	return &state, nil
   193  }
   194  
   195  // dialRemote is used for making calls into the gopls daemon. addr should be a
   196  // URL, possibly on the synthetic 'auto' network (e.g. tcp://..., unix://...,
   197  // or auto://...).
   198  func dialRemote(ctx context.Context, addr string) (jsonrpc2.Conn, error) {
   199  	network, address := ParseAddr(addr)
   200  	if network == AutoNetwork {
   201  		gp, err := os.Executable()
   202  		if err != nil {
   203  			return nil, errors.Errorf("getting gopls path: %w", err)
   204  		}
   205  		network, address = autoNetworkAddress(gp, address)
   206  	}
   207  	netConn, err := net.DialTimeout(network, address, 5*time.Second)
   208  	if err != nil {
   209  		return nil, errors.Errorf("dialing remote: %w", err)
   210  	}
   211  	serverConn := jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(netConn))
   212  	serverConn.Go(ctx, jsonrpc2.MethodNotFound)
   213  	return serverConn, nil
   214  }
   215  
   216  func ExecuteCommand(ctx context.Context, addr string, id string, request, result interface{}) error {
   217  	serverConn, err := dialRemote(ctx, addr)
   218  	if err != nil {
   219  		return err
   220  	}
   221  	args, err := command.MarshalArgs(request)
   222  	if err != nil {
   223  		return err
   224  	}
   225  	params := protocol.ExecuteCommandParams{
   226  		Command:   id,
   227  		Arguments: args,
   228  	}
   229  	return protocol.Call(ctx, serverConn, "workspace/executeCommand", params, result)
   230  }
   231  
   232  // ServeStream dials the forwarder remote and binds the remote to serve the LSP
   233  // on the incoming stream.
   234  func (f *Forwarder) ServeStream(ctx context.Context, clientConn jsonrpc2.Conn) error {
   235  	client := protocol.ClientDispatcher(clientConn)
   236  
   237  	netConn, err := f.connectToRemote(ctx)
   238  	if err != nil {
   239  		return errors.Errorf("forwarder: connecting to remote: %w", err)
   240  	}
   241  	serverConn := jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(netConn))
   242  	server := protocol.ServerDispatcher(serverConn)
   243  
   244  	// Forward between connections.
   245  	serverConn.Go(ctx,
   246  		protocol.Handlers(
   247  			protocol.ClientHandler(client,
   248  				jsonrpc2.MethodNotFound)))
   249  
   250  	// Don't run the clientConn yet, so that we can complete the handshake before
   251  	// processing any client messages.
   252  
   253  	// Do a handshake with the server instance to exchange debug information.
   254  	index := atomic.AddInt64(&serverIndex, 1)
   255  	f.mu.Lock()
   256  	f.serverConn = serverConn
   257  	f.serverID = strconv.FormatInt(index, 10)
   258  	f.mu.Unlock()
   259  	f.handshake(ctx)
   260  	clientConn.Go(ctx,
   261  		protocol.Handlers(
   262  			f.handler(
   263  				protocol.ServerHandler(server,
   264  					jsonrpc2.MethodNotFound))))
   265  
   266  	select {
   267  	case <-serverConn.Done():
   268  		clientConn.Close()
   269  	case <-clientConn.Done():
   270  		serverConn.Close()
   271  	}
   272  
   273  	err = nil
   274  	if serverConn.Err() != nil {
   275  		err = errors.Errorf("remote disconnected: %v", err)
   276  	} else if clientConn.Err() != nil {
   277  		err = errors.Errorf("client disconnected: %v", err)
   278  	}
   279  	event.Log(ctx, fmt.Sprintf("forwarder: exited with error: %v", err))
   280  	return err
   281  }
   282  
   283  func (f *Forwarder) handshake(ctx context.Context) {
   284  	var (
   285  		hreq = handshakeRequest{
   286  			ServerID:  f.serverID,
   287  			GoplsPath: f.goplsPath,
   288  		}
   289  		hresp handshakeResponse
   290  	)
   291  	if di := debug.GetInstance(ctx); di != nil {
   292  		hreq.Logfile = di.Logfile
   293  		hreq.DebugAddr = di.ListenedDebugAddress()
   294  	}
   295  	if err := protocol.Call(ctx, f.serverConn, handshakeMethod, hreq, &hresp); err != nil {
   296  		// TODO(rfindley): at some point in the future we should return an error
   297  		// here.  Handshakes have become functional in nature.
   298  		event.Error(ctx, "forwarder: gopls handshake failed", err)
   299  	}
   300  	if hresp.GoplsPath != f.goplsPath {
   301  		event.Error(ctx, "", fmt.Errorf("forwarder: gopls path mismatch: forwarder is %q, remote is %q", f.goplsPath, hresp.GoplsPath))
   302  	}
   303  	event.Log(ctx, "New server",
   304  		tag.NewServer.Of(f.serverID),
   305  		tag.Logfile.Of(hresp.Logfile),
   306  		tag.DebugAddress.Of(hresp.DebugAddr),
   307  		tag.GoplsPath.Of(hresp.GoplsPath),
   308  		tag.ClientID.Of(hresp.SessionID),
   309  	)
   310  }
   311  
   312  func (f *Forwarder) connectToRemote(ctx context.Context) (net.Conn, error) {
   313  	return connectToRemote(ctx, f.network, f.addr, f.goplsPath, f.remoteConfig)
   314  }
   315  
   316  func ConnectToRemote(ctx context.Context, addr string, opts ...RemoteOption) (net.Conn, error) {
   317  	rcfg := defaultRemoteConfig()
   318  	for _, opt := range opts {
   319  		opt.set(&rcfg)
   320  	}
   321  	// This is not strictly necessary, as it won't be used if not connecting to
   322  	// the 'auto' remote.
   323  	goplsPath, err := os.Executable()
   324  	if err != nil {
   325  		return nil, fmt.Errorf("unable to resolve gopls path: %v", err)
   326  	}
   327  	network, address := ParseAddr(addr)
   328  	return connectToRemote(ctx, network, address, goplsPath, rcfg)
   329  }
   330  
   331  func connectToRemote(ctx context.Context, inNetwork, inAddr, goplsPath string, rcfg remoteConfig) (net.Conn, error) {
   332  	var (
   333  		netConn          net.Conn
   334  		err              error
   335  		network, address = inNetwork, inAddr
   336  	)
   337  	if inNetwork == AutoNetwork {
   338  		// f.network is overloaded to support a concept of 'automatic' addresses,
   339  		// which signals that the gopls remote address should be automatically
   340  		// derived.
   341  		// So we need to resolve a real network and address here.
   342  		network, address = autoNetworkAddress(goplsPath, inAddr)
   343  	}
   344  	// Attempt to verify that we own the remote. This is imperfect, but if we can
   345  	// determine that the remote is owned by a different user, we should fail.
   346  	ok, err := verifyRemoteOwnership(network, address)
   347  	if err != nil {
   348  		// If the ownership check itself failed, we fail open but log an error to
   349  		// the user.
   350  		event.Error(ctx, "unable to check daemon socket owner, failing open", err)
   351  	} else if !ok {
   352  		// We successfully checked that the socket is not owned by us, we fail
   353  		// closed.
   354  		return nil, fmt.Errorf("socket %q is owned by a different user", address)
   355  	}
   356  	const dialTimeout = 1 * time.Second
   357  	// Try dialing our remote once, in case it is already running.
   358  	netConn, err = net.DialTimeout(network, address, dialTimeout)
   359  	if err == nil {
   360  		return netConn, nil
   361  	}
   362  	// If our remote is on the 'auto' network, start it if it doesn't exist.
   363  	if inNetwork == AutoNetwork {
   364  		if goplsPath == "" {
   365  			return nil, fmt.Errorf("cannot auto-start remote: gopls path is unknown")
   366  		}
   367  		if network == "unix" {
   368  			// Sometimes the socketfile isn't properly cleaned up when gopls shuts
   369  			// down. Since we have already tried and failed to dial this address, it
   370  			// should *usually* be safe to remove the socket before binding to the
   371  			// address.
   372  			// TODO(rfindley): there is probably a race here if multiple gopls
   373  			// instances are simultaneously starting up.
   374  			if _, err := os.Stat(address); err == nil {
   375  				if err := os.Remove(address); err != nil {
   376  					return nil, errors.Errorf("removing remote socket file: %w", err)
   377  				}
   378  			}
   379  		}
   380  		args := []string{"serve",
   381  			"-listen", fmt.Sprintf(`%s;%s`, network, address),
   382  			"-listen.timeout", rcfg.listenTimeout.String(),
   383  		}
   384  		if rcfg.logfile != "" {
   385  			args = append(args, "-logfile", rcfg.logfile)
   386  		}
   387  		if rcfg.debug != "" {
   388  			args = append(args, "-debug", rcfg.debug)
   389  		}
   390  		if err := startRemote(goplsPath, args...); err != nil {
   391  			return nil, errors.Errorf("startRemote(%q, %v): %w", goplsPath, args, err)
   392  		}
   393  	}
   394  
   395  	const retries = 5
   396  	// It can take some time for the newly started server to bind to our address,
   397  	// so we retry for a bit.
   398  	for retry := 0; retry < retries; retry++ {
   399  		startDial := time.Now()
   400  		netConn, err = net.DialTimeout(network, address, dialTimeout)
   401  		if err == nil {
   402  			return netConn, nil
   403  		}
   404  		event.Log(ctx, fmt.Sprintf("failed attempt #%d to connect to remote: %v\n", retry+2, err))
   405  		// In case our failure was a fast-failure, ensure we wait at least
   406  		// f.dialTimeout before trying again.
   407  		if retry != retries-1 {
   408  			time.Sleep(dialTimeout - time.Since(startDial))
   409  		}
   410  	}
   411  	return nil, errors.Errorf("dialing remote: %w", err)
   412  }
   413  
   414  // handler intercepts messages to the daemon to enrich them with local
   415  // information.
   416  func (f *Forwarder) handler(handler jsonrpc2.Handler) jsonrpc2.Handler {
   417  	return func(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error {
   418  		// Intercept certain messages to add special handling.
   419  		switch r.Method() {
   420  		case "initialize":
   421  			if newr, err := addGoEnvToInitializeRequest(ctx, r); err == nil {
   422  				r = newr
   423  			} else {
   424  				log.Printf("unable to add local env to initialize request: %v", err)
   425  			}
   426  		case "workspace/executeCommand":
   427  			var params protocol.ExecuteCommandParams
   428  			if err := json.Unmarshal(r.Params(), &params); err == nil {
   429  				if params.Command == command.StartDebugging.ID() {
   430  					var args command.DebuggingArgs
   431  					if err := command.UnmarshalArgs(params.Arguments, &args); err == nil {
   432  						reply = f.replyWithDebugAddress(ctx, reply, args)
   433  					} else {
   434  						event.Error(ctx, "unmarshaling debugging args", err)
   435  					}
   436  				}
   437  			} else {
   438  				event.Error(ctx, "intercepting executeCommand request", err)
   439  			}
   440  		}
   441  		// The gopls workspace environment defaults to the process environment in
   442  		// which gopls daemon was started. To avoid discrepancies in Go environment
   443  		// between the editor and daemon, inject any unset variables in `go env`
   444  		// into the options sent by initialize.
   445  		//
   446  		// See also golang.org/issue/37830.
   447  		return handler(ctx, reply, r)
   448  	}
   449  }
   450  
   451  // addGoEnvToInitializeRequest builds a new initialize request in which we set
   452  // any environment variables output by `go env` and not already present in the
   453  // request.
   454  //
   455  // It returns an error if r is not an initialize requst, or is otherwise
   456  // malformed.
   457  func addGoEnvToInitializeRequest(ctx context.Context, r jsonrpc2.Request) (jsonrpc2.Request, error) {
   458  	var params protocol.ParamInitialize
   459  	if err := json.Unmarshal(r.Params(), &params); err != nil {
   460  		return nil, err
   461  	}
   462  	var opts map[string]interface{}
   463  	switch v := params.InitializationOptions.(type) {
   464  	case nil:
   465  		opts = make(map[string]interface{})
   466  	case map[string]interface{}:
   467  		opts = v
   468  	default:
   469  		return nil, fmt.Errorf("unexpected type for InitializationOptions: %T", v)
   470  	}
   471  	envOpt, ok := opts["env"]
   472  	if !ok {
   473  		envOpt = make(map[string]interface{})
   474  	}
   475  	env, ok := envOpt.(map[string]interface{})
   476  	if !ok {
   477  		return nil, fmt.Errorf(`env option is %T, expected a map`, envOpt)
   478  	}
   479  	goenv, err := getGoEnv(ctx, env)
   480  	if err != nil {
   481  		return nil, err
   482  	}
   483  	for govar, value := range goenv {
   484  		env[govar] = value
   485  	}
   486  	opts["env"] = env
   487  	params.InitializationOptions = opts
   488  	call, ok := r.(*jsonrpc2.Call)
   489  	if !ok {
   490  		return nil, fmt.Errorf("%T is not a *jsonrpc2.Call", r)
   491  	}
   492  	return jsonrpc2.NewCall(call.ID(), "initialize", params)
   493  }
   494  
   495  func getGoEnv(ctx context.Context, env map[string]interface{}) (map[string]string, error) {
   496  	var runEnv []string
   497  	for k, v := range env {
   498  		runEnv = append(runEnv, fmt.Sprintf("%s=%s", k, v))
   499  	}
   500  	runner := gocommand.Runner{}
   501  	output, err := runner.Run(ctx, gocommand.Invocation{
   502  		Verb: "env",
   503  		Args: []string{"-json"},
   504  		Env:  runEnv,
   505  	})
   506  	if err != nil {
   507  		return nil, err
   508  	}
   509  	envmap := make(map[string]string)
   510  	if err := json.Unmarshal(output.Bytes(), &envmap); err != nil {
   511  		return nil, err
   512  	}
   513  	return envmap, nil
   514  }
   515  
   516  func (f *Forwarder) replyWithDebugAddress(outerCtx context.Context, r jsonrpc2.Replier, args command.DebuggingArgs) jsonrpc2.Replier {
   517  	di := debug.GetInstance(outerCtx)
   518  	if di == nil {
   519  		event.Log(outerCtx, "no debug instance to start")
   520  		return r
   521  	}
   522  	return func(ctx context.Context, result interface{}, outerErr error) error {
   523  		if outerErr != nil {
   524  			return r(ctx, result, outerErr)
   525  		}
   526  		// Enrich the result with our own debugging information. Since we're an
   527  		// intermediary, the jsonrpc2 package has deserialized the result into
   528  		// maps, by default. Re-do the unmarshalling.
   529  		raw, err := json.Marshal(result)
   530  		if err != nil {
   531  			event.Error(outerCtx, "marshaling intermediate command result", err)
   532  			return r(ctx, result, err)
   533  		}
   534  		var modified command.DebuggingResult
   535  		if err := json.Unmarshal(raw, &modified); err != nil {
   536  			event.Error(outerCtx, "unmarshaling intermediate command result", err)
   537  			return r(ctx, result, err)
   538  		}
   539  		addr := args.Addr
   540  		if addr == "" {
   541  			addr = "localhost:0"
   542  		}
   543  		addr, err = di.Serve(outerCtx, addr)
   544  		if err != nil {
   545  			event.Error(outerCtx, "starting debug server", err)
   546  			return r(ctx, result, outerErr)
   547  		}
   548  		urls := []string{"http://" + addr}
   549  		modified.URLs = append(urls, modified.URLs...)
   550  		go f.handshake(ctx)
   551  		return r(ctx, modified, nil)
   552  	}
   553  }
   554  
   555  // A handshakeRequest identifies a client to the LSP server.
   556  type handshakeRequest struct {
   557  	// ServerID is the ID of the server on the client. This should usually be 0.
   558  	ServerID string `json:"serverID"`
   559  	// Logfile is the location of the clients log file.
   560  	Logfile string `json:"logfile"`
   561  	// DebugAddr is the client debug address.
   562  	DebugAddr string `json:"debugAddr"`
   563  	// GoplsPath is the path to the Gopls binary running the current client
   564  	// process.
   565  	GoplsPath string `json:"goplsPath"`
   566  }
   567  
   568  // A handshakeResponse is returned by the LSP server to tell the LSP client
   569  // information about its session.
   570  type handshakeResponse struct {
   571  	// SessionID is the server session associated with the client.
   572  	SessionID string `json:"sessionID"`
   573  	// Logfile is the location of the server logs.
   574  	Logfile string `json:"logfile"`
   575  	// DebugAddr is the server debug address.
   576  	DebugAddr string `json:"debugAddr"`
   577  	// GoplsPath is the path to the Gopls binary running the current server
   578  	// process.
   579  	GoplsPath string `json:"goplsPath"`
   580  }
   581  
   582  // ClientSession identifies a current client LSP session on the server. Note
   583  // that it looks similar to handshakeResposne, but in fact 'Logfile' and
   584  // 'DebugAddr' now refer to the client.
   585  type ClientSession struct {
   586  	SessionID string `json:"sessionID"`
   587  	Logfile   string `json:"logfile"`
   588  	DebugAddr string `json:"debugAddr"`
   589  }
   590  
   591  // ServerState holds information about the gopls daemon process, including its
   592  // debug information and debug information of all of its current connected
   593  // clients.
   594  type ServerState struct {
   595  	Logfile         string          `json:"logfile"`
   596  	DebugAddr       string          `json:"debugAddr"`
   597  	GoplsPath       string          `json:"goplsPath"`
   598  	CurrentClientID string          `json:"currentClientID"`
   599  	Clients         []ClientSession `json:"clients"`
   600  }
   601  
   602  const (
   603  	handshakeMethod = "gopls/handshake"
   604  	sessionsMethod  = "gopls/sessions"
   605  )
   606  
   607  func handshaker(session *cache.Session, goplsPath string, logHandshakes bool, handler jsonrpc2.Handler) jsonrpc2.Handler {
   608  	return func(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error {
   609  		switch r.Method() {
   610  		case handshakeMethod:
   611  			// We log.Printf in this handler, rather than event.Log when we want logs
   612  			// to go to the daemon log rather than being reflected back to the
   613  			// client.
   614  			var req handshakeRequest
   615  			if err := json.Unmarshal(r.Params(), &req); err != nil {
   616  				if logHandshakes {
   617  					log.Printf("Error processing handshake for session %s: %v", session.ID(), err)
   618  				}
   619  				sendError(ctx, reply, err)
   620  				return nil
   621  			}
   622  			if logHandshakes {
   623  				log.Printf("Session %s: got handshake. Logfile: %q, Debug addr: %q", session.ID(), req.Logfile, req.DebugAddr)
   624  			}
   625  			event.Log(ctx, "Handshake session update",
   626  				cache.KeyUpdateSession.Of(session),
   627  				tag.DebugAddress.Of(req.DebugAddr),
   628  				tag.Logfile.Of(req.Logfile),
   629  				tag.ServerID.Of(req.ServerID),
   630  				tag.GoplsPath.Of(req.GoplsPath),
   631  			)
   632  			resp := handshakeResponse{
   633  				SessionID: session.ID(),
   634  				GoplsPath: goplsPath,
   635  			}
   636  			if di := debug.GetInstance(ctx); di != nil {
   637  				resp.Logfile = di.Logfile
   638  				resp.DebugAddr = di.ListenedDebugAddress()
   639  			}
   640  			return reply(ctx, resp, nil)
   641  
   642  		case sessionsMethod:
   643  			resp := ServerState{
   644  				GoplsPath:       goplsPath,
   645  				CurrentClientID: session.ID(),
   646  			}
   647  			if di := debug.GetInstance(ctx); di != nil {
   648  				resp.Logfile = di.Logfile
   649  				resp.DebugAddr = di.ListenedDebugAddress()
   650  				for _, c := range di.State.Clients() {
   651  					resp.Clients = append(resp.Clients, ClientSession{
   652  						SessionID: c.Session.ID(),
   653  						Logfile:   c.Logfile,
   654  						DebugAddr: c.DebugAddress,
   655  					})
   656  				}
   657  			}
   658  			return reply(ctx, resp, nil)
   659  		}
   660  		return handler(ctx, reply, r)
   661  	}
   662  }
   663  
   664  func sendError(ctx context.Context, reply jsonrpc2.Replier, err error) {
   665  	err = errors.Errorf("%v: %w", err, jsonrpc2.ErrParse)
   666  	if err := reply(ctx, nil, err); err != nil {
   667  		event.Error(ctx, "", err)
   668  	}
   669  }
   670  
   671  // ParseAddr parses the address of a gopls remote.
   672  // TODO(rFindley): further document this syntax, and allow URI-style remote
   673  // addresses such as "auto://...".
   674  func ParseAddr(listen string) (network string, address string) {
   675  	// Allow passing just -remote=auto, as a shorthand for using automatic remote
   676  	// resolution.
   677  	if listen == AutoNetwork {
   678  		return AutoNetwork, ""
   679  	}
   680  	if parts := strings.SplitN(listen, ";", 2); len(parts) == 2 {
   681  		return parts[0], parts[1]
   682  	}
   683  	return "tcp", listen
   684  }