aboutsummaryrefslogtreecommitdiffstats
path: root/rpc
diff options
context:
space:
mode:
Diffstat (limited to 'rpc')
-rw-r--r--rpc/api/admin.go9
-rw-r--r--rpc/api/net.go3
-rw-r--r--rpc/api/personal.go22
-rw-r--r--rpc/api/personal_args.go22
-rw-r--r--rpc/api/shh_js.go (renamed from rpc/api/ssh_js.go)0
-rw-r--r--rpc/codec/codec.go2
-rw-r--r--rpc/codec/json.go39
-rw-r--r--rpc/comms/comms.go2
-rw-r--r--rpc/comms/inproc.go2
-rw-r--r--rpc/comms/ipc.go35
-rw-r--r--rpc/comms/ipc_unix.go9
-rw-r--r--rpc/comms/ipc_windows.go9
-rw-r--r--rpc/jeth.go89
-rw-r--r--rpc/useragent/agent.go24
-rw-r--r--rpc/useragent/remote_frontend.go141
15 files changed, 337 insertions, 71 deletions
diff --git a/rpc/api/admin.go b/rpc/api/admin.go
index 29f342ab6..5e392ae32 100644
--- a/rpc/api/admin.go
+++ b/rpc/api/admin.go
@@ -37,6 +37,7 @@ import (
"github.com/ethereum/go-ethereum/rpc/codec"
"github.com/ethereum/go-ethereum/rpc/comms"
"github.com/ethereum/go-ethereum/rpc/shared"
+ "github.com/ethereum/go-ethereum/rpc/useragent"
"github.com/ethereum/go-ethereum/xeth"
)
@@ -71,6 +72,7 @@ var (
"admin_httpGet": (*adminApi).HttpGet,
"admin_sleepBlocks": (*adminApi).SleepBlocks,
"admin_sleep": (*adminApi).Sleep,
+ "admin_enableUserAgent": (*adminApi).EnableUserAgent,
}
)
@@ -474,3 +476,10 @@ func (self *adminApi) HttpGet(req *shared.Request) (interface{}, error) {
return string(resp), nil
}
+
+func (self *adminApi) EnableUserAgent(req *shared.Request) (interface{}, error) {
+ if fe, ok := self.xeth.Frontend().(*useragent.RemoteFrontend); ok {
+ fe.Enable()
+ }
+ return true, nil
+}
diff --git a/rpc/api/net.go b/rpc/api/net.go
index 39c230e14..9c6369615 100644
--- a/rpc/api/net.go
+++ b/rpc/api/net.go
@@ -32,7 +32,7 @@ var (
netMapping = map[string]nethandler{
"net_peerCount": (*netApi).PeerCount,
"net_listening": (*netApi).IsListening,
- "net_version": (*netApi).Version,
+ "net_version": (*netApi).Version,
}
)
@@ -97,4 +97,3 @@ func (self *netApi) IsListening(req *shared.Request) (interface{}, error) {
func (self *netApi) Version(req *shared.Request) (interface{}, error) {
return self.xeth.NetworkVersion(), nil
}
-
diff --git a/rpc/api/personal.go b/rpc/api/personal.go
index e9942c1e5..6c73ac83d 100644
--- a/rpc/api/personal.go
+++ b/rpc/api/personal.go
@@ -17,6 +17,7 @@
package api
import (
+ "fmt"
"time"
"github.com/ethereum/go-ethereum/common"
@@ -125,18 +126,17 @@ func (self *personalApi) UnlockAccount(req *shared.Request) (interface{}, error)
return nil, shared.NewDecodeParamError(err.Error())
}
- var err error
+ if len(args.Passphrase) == 0 {
+ fe := self.xeth.Frontend()
+ if fe == nil {
+ return false, fmt.Errorf("No password provided")
+ }
+ return fe.UnlockAccount(common.HexToAddress(args.Address).Bytes()), nil
+ }
+
am := self.ethereum.AccountManager()
addr := common.HexToAddress(args.Address)
- if args.Duration == -1 {
- err = am.Unlock(addr, args.Passphrase)
- } else {
- err = am.TimedUnlock(addr, args.Passphrase, time.Duration(args.Duration)*time.Second)
- }
-
- if err == nil {
- return true, nil
- }
- return false, err
+ err := am.TimedUnlock(addr, args.Passphrase, time.Duration(args.Duration)*time.Second)
+ return err == nil, err
}
diff --git a/rpc/api/personal_args.go b/rpc/api/personal_args.go
index 7f00701e3..5a584fb0c 100644
--- a/rpc/api/personal_args.go
+++ b/rpc/api/personal_args.go
@@ -86,10 +86,10 @@ func (args *UnlockAccountArgs) UnmarshalJSON(b []byte) (err error) {
return shared.NewDecodeParamError(err.Error())
}
- args.Duration = -1
+ args.Duration = 0
- if len(obj) < 2 {
- return shared.NewInsufficientParamsError(len(obj), 2)
+ if len(obj) < 1 {
+ return shared.NewInsufficientParamsError(len(obj), 1)
}
if addrstr, ok := obj[0].(string); ok {
@@ -98,10 +98,18 @@ func (args *UnlockAccountArgs) UnmarshalJSON(b []byte) (err error) {
return shared.NewInvalidTypeError("address", "not a string")
}
- if passphrasestr, ok := obj[1].(string); ok {
- args.Passphrase = passphrasestr
- } else {
- return shared.NewInvalidTypeError("passphrase", "not a string")
+ if len(obj) >= 2 && obj[1] != nil {
+ if passphrasestr, ok := obj[1].(string); ok {
+ args.Passphrase = passphrasestr
+ } else {
+ return shared.NewInvalidTypeError("passphrase", "not a string")
+ }
+ }
+
+ if len(obj) >= 3 && obj[2] != nil {
+ if duration, ok := obj[2].(float64); ok {
+ args.Duration = int(duration)
+ }
}
return nil
diff --git a/rpc/api/ssh_js.go b/rpc/api/shh_js.go
index a92ad1644..a92ad1644 100644
--- a/rpc/api/ssh_js.go
+++ b/rpc/api/shh_js.go
diff --git a/rpc/codec/codec.go b/rpc/codec/codec.go
index 2fdb0d8f3..786080b44 100644
--- a/rpc/codec/codec.go
+++ b/rpc/codec/codec.go
@@ -31,6 +31,8 @@ type ApiCoder interface {
ReadRequest() ([]*shared.Request, bool, error)
// Parse response message from underlying stream
ReadResponse() (interface{}, error)
+ // Read raw message from underlying stream
+ Recv() (interface{}, error)
// Encode response to encoded form in underlying stream
WriteResponse(interface{}) error
// Decode single message from data
diff --git a/rpc/codec/json.go b/rpc/codec/json.go
index d811b2096..cfc449143 100644
--- a/rpc/codec/json.go
+++ b/rpc/codec/json.go
@@ -21,6 +21,7 @@ import (
"fmt"
"net"
"time"
+ "strings"
"github.com/ethereum/go-ethereum/rpc/shared"
)
@@ -73,35 +74,41 @@ func (self *JsonCodec) ReadRequest() (requests []*shared.Request, isBatch bool,
return nil, false, err
}
-func (self *JsonCodec) ReadResponse() (interface{}, error) {
- bytesInBuffer := 0
- buf := make([]byte, MAX_RESPONSE_SIZE)
+func (self *JsonCodec) Recv() (interface{}, error) {
+ var msg json.RawMessage
+ err := self.d.Decode(&msg)
+ if err != nil {
+ self.c.Close()
+ return nil, err
+ }
- deadline := time.Now().Add(READ_TIMEOUT * time.Second)
- if err := self.c.SetDeadline(deadline); err != nil {
+ return msg, err
+}
+
+func (self *JsonCodec) ReadResponse() (interface{}, error) {
+ in, err := self.Recv()
+ if err != nil {
return nil, err
}
- for {
- n, err := self.c.Read(buf[bytesInBuffer:])
- if err != nil {
- return nil, err
+ if msg, ok := in.(json.RawMessage); ok {
+ var req *shared.Request
+ if err = json.Unmarshal(msg, &req); err == nil && strings.HasPrefix(req.Method, "agent_") {
+ return req, nil
}
- bytesInBuffer += n
- var failure shared.ErrorResponse
- if err = json.Unmarshal(buf[:bytesInBuffer], &failure); err == nil && failure.Error != nil {
+ var failure *shared.ErrorResponse
+ if err = json.Unmarshal(msg, &failure); err == nil && failure.Error != nil {
return failure, fmt.Errorf(failure.Error.Message)
}
- var success shared.SuccessResponse
- if err = json.Unmarshal(buf[:bytesInBuffer], &success); err == nil {
+ var success *shared.SuccessResponse
+ if err = json.Unmarshal(msg, &success); err == nil {
return success, nil
}
}
- self.c.Close()
- return nil, fmt.Errorf("Unable to read response")
+ return in, err
}
// Decode data
diff --git a/rpc/comms/comms.go b/rpc/comms/comms.go
index f5eeae84f..731b2f62e 100644
--- a/rpc/comms/comms.go
+++ b/rpc/comms/comms.go
@@ -49,7 +49,7 @@ var (
)
type EthereumClient interface {
- // Close underlaying connection
+ // Close underlying connection
Close()
// Send request
Send(interface{}) error
diff --git a/rpc/comms/inproc.go b/rpc/comms/inproc.go
index f279f0163..e8058e32b 100644
--- a/rpc/comms/inproc.go
+++ b/rpc/comms/inproc.go
@@ -60,7 +60,7 @@ func (self *InProcClient) Send(req interface{}) error {
}
func (self *InProcClient) Recv() (interface{}, error) {
- return self.lastRes, self.lastErr
+ return *shared.NewRpcResponse(self.lastId, self.lastJsonrpc, self.lastRes, self.lastErr), nil
}
func (self *InProcClient) SupportedModules() (map[string]string, error) {
diff --git a/rpc/comms/ipc.go b/rpc/comms/ipc.go
index 0250aa01e..e982ada13 100644
--- a/rpc/comms/ipc.go
+++ b/rpc/comms/ipc.go
@@ -44,35 +44,18 @@ func (self *ipcClient) Close() {
func (self *ipcClient) Send(req interface{}) error {
var err error
- if r, ok := req.(*shared.Request); ok {
- if err = self.coder.WriteResponse(r); err != nil {
- if _, ok := err.(*net.OpError); ok { // connection lost, retry once
- if err = self.reconnect(); err == nil {
- err = self.coder.WriteResponse(r)
- }
+ if err = self.coder.WriteResponse(req); err != nil {
+ if _, ok := err.(*net.OpError); ok { // connection lost, retry once
+ if err = self.reconnect(); err == nil {
+ err = self.coder.WriteResponse(req)
}
}
- return err
}
-
- return fmt.Errorf("Invalid request (%T)", req)
+ return err
}
func (self *ipcClient) Recv() (interface{}, error) {
- res, err := self.coder.ReadResponse()
- if err != nil {
- return nil, err
- }
-
- if r, ok := res.(shared.SuccessResponse); ok {
- return r.Result, nil
- }
-
- if r, ok := res.(shared.ErrorResponse); ok {
- return r.Error, nil
- }
-
- return res, err
+ return self.coder.ReadResponse()
}
func (self *ipcClient) SupportedModules() (map[string]string, error) {
@@ -91,7 +74,7 @@ func (self *ipcClient) SupportedModules() (map[string]string, error) {
return nil, err
}
- if sucRes, ok := res.(shared.SuccessResponse); ok {
+ if sucRes, ok := res.(*shared.SuccessResponse); ok {
data, _ := json.Marshal(sucRes.Result)
modules := make(map[string]string)
err = json.Unmarshal(data, &modules)
@@ -109,8 +92,8 @@ func NewIpcClient(cfg IpcConfig, codec codec.Codec) (*ipcClient, error) {
}
// Start IPC server
-func StartIpc(cfg IpcConfig, codec codec.Codec, offeredApi shared.EthereumApi) error {
- return startIpc(cfg, codec, offeredApi)
+func StartIpc(cfg IpcConfig, codec codec.Codec, initializer func(conn net.Conn) (shared.EthereumApi, error)) error {
+ return startIpc(cfg, codec, initializer)
}
func newIpcConnId() int {
diff --git a/rpc/comms/ipc_unix.go b/rpc/comms/ipc_unix.go
index 432bf93b5..6968fa844 100644
--- a/rpc/comms/ipc_unix.go
+++ b/rpc/comms/ipc_unix.go
@@ -48,7 +48,7 @@ func (self *ipcClient) reconnect() error {
return err
}
-func startIpc(cfg IpcConfig, codec codec.Codec, api shared.EthereumApi) error {
+func startIpc(cfg IpcConfig, codec codec.Codec, initializer func(conn net.Conn) (shared.EthereumApi, error)) error {
os.Remove(cfg.Endpoint) // in case it still exists from a previous run
l, err := net.Listen("unix", cfg.Endpoint)
@@ -69,6 +69,13 @@ func startIpc(cfg IpcConfig, codec codec.Codec, api shared.EthereumApi) error {
id := newIpcConnId()
glog.V(logger.Debug).Infof("New IPC connection with id %06d started\n", id)
+ api, err := initializer(conn)
+ if err != nil {
+ glog.V(logger.Error).Infof("Unable to initialize IPC connection - %v\n", err)
+ conn.Close()
+ continue
+ }
+
go handle(id, conn, api, codec)
}
diff --git a/rpc/comms/ipc_windows.go b/rpc/comms/ipc_windows.go
index ee49f069b..b2fe2b29d 100644
--- a/rpc/comms/ipc_windows.go
+++ b/rpc/comms/ipc_windows.go
@@ -667,7 +667,7 @@ func (self *ipcClient) reconnect() error {
return err
}
-func startIpc(cfg IpcConfig, codec codec.Codec, api shared.EthereumApi) error {
+func startIpc(cfg IpcConfig, codec codec.Codec, initializer func(conn net.Conn) (shared.EthereumApi, error)) error {
os.Remove(cfg.Endpoint) // in case it still exists from a previous run
l, err := Listen(cfg.Endpoint)
@@ -687,6 +687,13 @@ func startIpc(cfg IpcConfig, codec codec.Codec, api shared.EthereumApi) error {
id := newIpcConnId()
glog.V(logger.Debug).Infof("New IPC connection with id %06d started\n", id)
+ api, err := initializer(conn)
+ if err != nil {
+ glog.V(logger.Error).Infof("Unable to initialize IPC connection - %v\n", err)
+ conn.Close()
+ continue
+ }
+
go handle(id, conn, api, codec)
}
diff --git a/rpc/jeth.go b/rpc/jeth.go
index 07add2bad..158bfb64c 100644
--- a/rpc/jeth.go
+++ b/rpc/jeth.go
@@ -18,12 +18,17 @@ package rpc
import (
"encoding/json"
-
"fmt"
+ "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/jsre"
+ "github.com/ethereum/go-ethereum/logger"
+ "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/rpc/comms"
"github.com/ethereum/go-ethereum/rpc/shared"
+ "github.com/ethereum/go-ethereum/rpc/useragent"
+ "github.com/ethereum/go-ethereum/xeth"
+
"github.com/robertkrimen/otto"
)
@@ -31,10 +36,21 @@ type Jeth struct {
ethApi shared.EthereumApi
re *jsre.JSRE
client comms.EthereumClient
+ fe xeth.Frontend
}
-func NewJeth(ethApi shared.EthereumApi, re *jsre.JSRE, client comms.EthereumClient) *Jeth {
- return &Jeth{ethApi, re, client}
+func NewJeth(ethApi shared.EthereumApi, re *jsre.JSRE, client comms.EthereumClient, fe xeth.Frontend) *Jeth {
+ // enable the jeth as the user agent
+ req := shared.Request{
+ Id: 0,
+ Method: useragent.EnableUserAgentMethod,
+ Jsonrpc: shared.JsonRpcVersion,
+ Params: []byte("[]"),
+ }
+ client.Send(&req)
+ client.Recv()
+
+ return &Jeth{ethApi, re, client, fe}
}
func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id interface{}) (response otto.Value) {
@@ -72,16 +88,34 @@ func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) {
if err != nil {
return self.err(call, -32603, err.Error(), req.Id)
}
- respif, err = self.client.Recv()
+ recv:
+ respif, err = self.client.Recv()
if err != nil {
return self.err(call, -32603, err.Error(), req.Id)
}
+ agentreq, isRequest := respif.(*shared.Request)
+ if isRequest {
+ self.handleRequest(agentreq)
+ goto recv // receive response after agent interaction
+ }
+
+ sucres, isSuccessResponse := respif.(*shared.SuccessResponse)
+ errres, isErrorResponse := respif.(*shared.ErrorResponse)
+ if !isSuccessResponse && !isErrorResponse {
+ return self.err(call, -32603, fmt.Sprintf("Invalid response type (%T)", respif), req.Id)
+ }
+
call.Otto.Set("ret_jsonrpc", shared.JsonRpcVersion)
call.Otto.Set("ret_id", req.Id)
- res, _ := json.Marshal(respif)
+ var res []byte
+ if isSuccessResponse {
+ res, err = json.Marshal(sucres.Result)
+ } else if isErrorResponse {
+ res, err = json.Marshal(errres.Error)
+ }
call.Otto.Set("ret_result", string(res))
call.Otto.Set("response_idx", i)
@@ -105,3 +139,48 @@ func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) {
return
}
+
+// handleRequest will handle user agent requests by interacting with the user and sending
+// the user response back to the geth service
+func (self *Jeth) handleRequest(req *shared.Request) bool {
+ var err error
+ var args []interface{}
+ if err = json.Unmarshal(req.Params, &args); err != nil {
+ glog.V(logger.Info).Infof("Unable to parse agent request - %v\n", err)
+ return false
+ }
+
+ switch req.Method {
+ case useragent.AskPasswordMethod:
+ return self.askPassword(req.Id, req.Jsonrpc, args)
+ case useragent.ConfirmTransactionMethod:
+ return self.confirmTransaction(req.Id, req.Jsonrpc, args)
+ }
+
+ return false
+}
+
+// askPassword will ask the user to supply the password for a given account
+func (self *Jeth) askPassword(id interface{}, jsonrpc string, args []interface{}) bool {
+ var err error
+ var passwd string
+ if len(args) >= 1 {
+ if account, ok := args[0].(string); ok {
+ fmt.Printf("Unlock account %s\n", account)
+ passwd, err = utils.PromptPassword("Passphrase: ", true)
+ } else {
+ return false
+ }
+ }
+
+ if err = self.client.Send(shared.NewRpcResponse(id, jsonrpc, passwd, err)); err != nil {
+ glog.V(logger.Info).Infof("Unable to send user agent ask password response - %v\n", err)
+ }
+
+ return err == nil
+}
+
+func (self *Jeth) confirmTransaction(id interface{}, jsonrpc string, args []interface{}) bool {
+ // Accept all tx which are send from this console
+ return self.client.Send(shared.NewRpcResponse(id, jsonrpc, true, nil)) == nil
+}
diff --git a/rpc/useragent/agent.go b/rpc/useragent/agent.go
new file mode 100644
index 000000000..df0739e65
--- /dev/null
+++ b/rpc/useragent/agent.go
@@ -0,0 +1,24 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+// package user agent provides frontends and agents which can interact with the user
+package useragent
+
+var (
+ AskPasswordMethod = "agent_askPassword"
+ ConfirmTransactionMethod = "agent_confirmTransaction"
+ EnableUserAgentMethod = "admin_enableUserAgent"
+)
diff --git a/rpc/useragent/remote_frontend.go b/rpc/useragent/remote_frontend.go
new file mode 100644
index 000000000..0dd4a6049
--- /dev/null
+++ b/rpc/useragent/remote_frontend.go
@@ -0,0 +1,141 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package useragent
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+
+ "github.com/ethereum/go-ethereum/accounts"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/logger"
+ "github.com/ethereum/go-ethereum/logger/glog"
+ "github.com/ethereum/go-ethereum/rpc/shared"
+)
+
+// remoteFrontend implements xeth.Frontend and will communicate with an external
+// user agent over a connection
+type RemoteFrontend struct {
+ enabled bool
+ mgr *accounts.Manager
+ d *json.Decoder
+ e *json.Encoder
+ n int
+}
+
+// NewRemoteFrontend creates a new frontend which will interact with an user agent
+// over the given connection
+func NewRemoteFrontend(conn net.Conn, mgr *accounts.Manager) *RemoteFrontend {
+ return &RemoteFrontend{false, mgr, json.NewDecoder(conn), json.NewEncoder(conn), 0}
+}
+
+// Enable will enable user interaction
+func (fe *RemoteFrontend) Enable() {
+ fe.enabled = true
+}
+
+// UnlockAccount asks the user agent for the user password and tries to unlock the account.
+// It will try 3 attempts before giving up.
+func (fe *RemoteFrontend) UnlockAccount(address []byte) bool {
+ if !fe.enabled {
+ return false
+ }
+
+ err := fe.send(AskPasswordMethod, common.Bytes2Hex(address))
+ if err != nil {
+ glog.V(logger.Error).Infof("Unable to send password request to agent - %v\n", err)
+ return false
+ }
+
+ passwdRes, err := fe.recv()
+ if err != nil {
+ glog.V(logger.Error).Infof("Unable to recv password response from agent - %v\n", err)
+ return false
+ }
+
+ if passwd, ok := passwdRes.Result.(string); ok {
+ err = fe.mgr.Unlock(common.BytesToAddress(address), passwd)
+ }
+
+ if err == nil {
+ return true
+ }
+
+ glog.V(logger.Debug).Infoln("3 invalid account unlock attempts")
+ return false
+}
+
+// ConfirmTransaction asks the user for approval
+func (fe *RemoteFrontend) ConfirmTransaction(tx string) bool {
+ if !fe.enabled {
+ return true // backwards compatibility
+ }
+
+ err := fe.send(ConfirmTransactionMethod, tx)
+ if err != nil {
+ glog.V(logger.Error).Infof("Unable to send tx confirmation request to agent - %v\n", err)
+ return false
+ }
+
+ confirmResponse, err := fe.recv()
+ if err != nil {
+ glog.V(logger.Error).Infof("Unable to recv tx confirmation response from agent - %v\n", err)
+ return false
+ }
+
+ if confirmed, ok := confirmResponse.Result.(bool); ok {
+ return confirmed
+ }
+
+ return false
+}
+
+// send request to the agent
+func (fe *RemoteFrontend) send(method string, params ...interface{}) error {
+ fe.n += 1
+
+ p, err := json.Marshal(params)
+ if err != nil {
+ glog.V(logger.Info).Infof("Unable to send agent request %v\n", err)
+ return err
+ }
+
+ req := shared.Request{
+ Method: method,
+ Jsonrpc: shared.JsonRpcVersion,
+ Id: fe.n,
+ Params: p,
+ }
+
+ return fe.e.Encode(&req)
+}
+
+// recv user response from agent
+func (fe *RemoteFrontend) recv() (*shared.SuccessResponse, error) {
+ var res json.RawMessage
+ if err := fe.d.Decode(&res); err != nil {
+ return nil, err
+ }
+
+ var response shared.SuccessResponse
+ if err := json.Unmarshal(res, &response); err == nil {
+ return &response, nil
+ }
+
+ return nil, fmt.Errorf("Invalid user agent response")
+}