aboutsummaryrefslogtreecommitdiffstats
path: root/rpc/json.go
blob: 8bdb4665dfec17814e5c6140cce81c4cab5dbb42 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
// 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 rpc

import (
    "encoding/json"
    "fmt"
    "io"
    "reflect"
    "strings"
    "sync/atomic"

    "github.com/ethereum/go-ethereum/logger"
    "github.com/ethereum/go-ethereum/logger/glog"
)

const (
    jsonRPCVersion         = "2.0"
    serviceMethodSeparator = "_"
    subscribeMethod        = "eth_subscribe"
    unsubscribeMethod      = "eth_unsubscribe"
    notificationMethod     = "eth_subscription"
)

// JSON-RPC request
type JSONRequest struct {
    Method  string          `json:"method"`
    Version string          `json:"jsonrpc"`
    Id      *int64          `json:"id,omitempty"`
    Payload json.RawMessage `json:"params"`
}

// JSON-RPC response
type JSONSuccessResponse struct {
    Version string      `json:"jsonrpc"`
    Id      int64       `json:"id"`
    Result  interface{} `json:"result,omitempty"`
}

// JSON-RPC error object
type JSONError struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data,omitempty"`
}

// JSON-RPC error response
type JSONErrResponse struct {
    Version string    `json:"jsonrpc"`
    Id      *int64    `json:"id,omitempty"`
    Error   JSONError `json:"error"`
}

// JSON-RPC notification payload
type jsonSubscription struct {
    Subscription string      `json:"subscription"`
    Result       interface{} `json:"result,omitempty"`
}

// JSON-RPC notification
type jsonNotification struct {
    Version string           `json:"jsonrpc"`
    Method  string           `json:"method"`
    Params  jsonSubscription `json:"params"`
}

// jsonCodec reads and writes JSON-RPC messages to the underlying connection. It also has support for parsing arguments
// and serializing (result) objects.
type jsonCodec struct {
    closed   chan interface{}
    isClosed int32
    d        *json.Decoder
    e        *json.Encoder
    req      JSONRequest
    rw       io.ReadWriteCloser
}

// NewJSONCodec creates a new RPC server codec with support for JSON-RPC 2.0
func NewJSONCodec(rwc io.ReadWriteCloser) ServerCodec {
    d := json.NewDecoder(rwc)
    d.UseNumber()
    return &jsonCodec{closed: make(chan interface{}), d: d, e: json.NewEncoder(rwc), rw: rwc, isClosed: 0}
}

// isBatch returns true when the first non-whitespace characters is '['
func isBatch(msg json.RawMessage) bool {
    for _, c := range msg {
        // skip insignificant whitespace (http://www.ietf.org/rfc/rfc4627.txt)
        if c == 0x20 || c == 0x09 || c == 0x0a || c == 0x0d {
            continue
        }
        return c == '['
    }
    return false
}

// ReadRequestHeaders will read new requests without parsing the arguments. It will return a collection of requests, an
// indication if these requests are in batch form or an error when the incoming message could not be read/parsed.
func (c *jsonCodec) ReadRequestHeaders() ([]rpcRequest, bool, RPCError) {
    var incomingMsg json.RawMessage
    if err := c.d.Decode(&incomingMsg); err != nil {
        return nil, false, &invalidRequestError{err.Error()}
    }

    if isBatch(incomingMsg) {
        return parseBatchRequest(incomingMsg)
    }

    return parseRequest(incomingMsg)
}

// parseRequest will parse a single request from the given RawMessage. It will return the parsed request, an indication
// if the request was a batch or an error when the request could not be parsed.
func parseRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCError) {
    var in JSONRequest
    if err := json.Unmarshal(incomingMsg, &in); err != nil {
        return nil, false, &invalidMessageError{err.Error()}
    }

    if in.Id == nil {
        return nil, false, &invalidMessageError{"Server cannot handle notifications"}
    }

    // subscribe are special, they will always use `subscribeMethod` as service method
    if in.Method == subscribeMethod {
        reqs := []rpcRequest{rpcRequest{id: *in.Id, isPubSub: true}}
        if len(in.Payload) > 0 {
            // first param must be subscription name
            var subscribeMethod [1]string
            if err := json.Unmarshal(in.Payload, &subscribeMethod); err != nil {
                glog.V(logger.Debug).Infof("Unable to parse subscription method: %v\n", err)
                return nil, false, &invalidRequestError{"Unable to parse subscription request"}
            }

            // all subscriptions are made on the eth service
            reqs[0].service, reqs[0].method = "eth", subscribeMethod[0]
            reqs[0].params = in.Payload
            return reqs, false, nil
        }
        return nil, false, &invalidRequestError{"Unable to parse subscription request"}
    }

    if in.Method == unsubscribeMethod {
        return []rpcRequest{rpcRequest{id: *in.Id, isPubSub: true,
            method: unsubscribeMethod, params: in.Payload}}, false, nil
    }

    // regular RPC call
    elems := strings.Split(in.Method, serviceMethodSeparator)
    if len(elems) != 2 {
        return nil, false, &methodNotFoundError{in.Method, ""}
    }

    if len(in.Payload) == 0 {
        return []rpcRequest{rpcRequest{service: elems[0], method: elems[1], id: *in.Id}}, false, nil
    }

    return []rpcRequest{rpcRequest{service: elems[0], method: elems[1], id: *in.Id, params: in.Payload}}, false, nil
}

// parseBatchRequest will parse a batch request into a collection of requests from the given RawMessage, an indication
// if the request was a batch or an error when the request could not be read.
func parseBatchRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCError) {
    var in []JSONRequest
    if err := json.Unmarshal(incomingMsg, &in); err != nil {
        return nil, false, &invalidMessageError{err.Error()}
    }

    requests := make([]rpcRequest, len(in))
    for i, r := range in {
        if r.Id == nil {
            return nil, true, &invalidMessageError{"Server cannot handle notifications"}
        }

        // (un)subscribe are special, they will always use the same service.method
        if r.Method == subscribeMethod {
            requests[i] = rpcRequest{id: *r.Id, isPubSub: true}
            if len(r.Payload) > 0 {
                var subscribeMethod [1]string
                if err := json.Unmarshal(r.Payload, &subscribeMethod); err != nil {
                    glog.V(logger.Debug).Infof("Unable to parse subscription method: %v\n", err)
                    return nil, false, &invalidRequestError{"Unable to parse subscription request"}
                }

                // all subscriptions are made on the eth service
                requests[i].service, requests[i].method = "eth", subscribeMethod[0]
                requests[i].params = r.Payload
                continue
            }

            return nil, true, &invalidRequestError{"Unable to parse (un)subscribe request arguments"}
        }

        if r.Method == unsubscribeMethod {
            requests[i] = rpcRequest{id: *r.Id, isPubSub: true, method: unsubscribeMethod, params: r.Payload}
            continue
        }

        elems := strings.Split(r.Method, serviceMethodSeparator)
        if len(elems) != 2 {
            return nil, true, &methodNotFoundError{r.Method, ""}
        }

        if len(r.Payload) == 0 {
            requests[i] = rpcRequest{service: elems[0], method: elems[1], id: *r.Id, params: nil}
        } else {
            requests[i] = rpcRequest{service: elems[0], method: elems[1], id: *r.Id, params: r.Payload}
        }
    }

    return requests, true, nil
}

// ParseRequestArguments tries to parse the given params (json.RawMessage) with the given types. It returns the parsed
// values or an error when the parsing failed.
func (c *jsonCodec) ParseRequestArguments(argTypes []reflect.Type, params interface{}) ([]reflect.Value, RPCError) {
    if args, ok := params.(json.RawMessage); !ok {
        return nil, &invalidParamsError{"Invalid params supplied"}
    } else {
        return parsePositionalArguments(args, argTypes)
    }
}

func countArguments(args json.RawMessage) (int, error) {
    var cnt []interface{}
    if err := json.Unmarshal(args, &cnt); err != nil {
        return -1, nil
    }
    return len(cnt), nil
}

// parsePositionalArguments tries to parse the given args to an array of values with the given types. It returns the
// parsed values or an error when the args could not be parsed.
func parsePositionalArguments(args json.RawMessage, argTypes []reflect.Type) ([]reflect.Value, RPCError) {
    argValues := make([]reflect.Value, len(argTypes))
    params := make([]interface{}, len(argTypes))

    n, err := countArguments(args)
    if err != nil {
        return nil, &invalidParamsError{err.Error()}
    }
    if n != len(argTypes) {
        return nil, &invalidParamsError{fmt.Sprintf("insufficient params, want %d have %d", len(argTypes), n)}
    }

    for i, t := range argTypes {
        if t.Kind() == reflect.Ptr {
            // values must be pointers for the Unmarshal method, reflect.
            // Dereference otherwise reflect.New would create **SomeType
            argValues[i] = reflect.New(t.Elem())
            params[i] = argValues[i].Interface()

            // when not specified blockNumbers are by default latest (-1)
            if blockNumber, ok := params[i].(*BlockNumber); ok {
                *blockNumber = BlockNumber(-1)
            }
        } else {
            argValues[i] = reflect.New(t)
            params[i] = argValues[i].Interface()

            // when not specified blockNumbers are by default latest (-1)
            if blockNumber, ok := params[i].(*BlockNumber); ok {
                *blockNumber = BlockNumber(-1)
            }
        }
    }

    if err := json.Unmarshal(args, &params); err != nil {
        return nil, &invalidParamsError{err.Error()}
    }

    // Convert pointers back to values where necessary
    for i, a := range argValues {
        if a.Kind() != argTypes[i].Kind() {
            argValues[i] = reflect.Indirect(argValues[i])
        }
    }

    return argValues, nil
}

// CreateResponse will create a JSON-RPC success response with the given id and reply as result.
func (c *jsonCodec) CreateResponse(id int64, reply interface{}) interface{} {
    if isHexNum(reflect.TypeOf(reply)) {
        return &JSONSuccessResponse{Version: jsonRPCVersion, Id: id, Result: fmt.Sprintf(`%#x`, reply)}
    }
    return &JSONSuccessResponse{Version: jsonRPCVersion, Id: id, Result: reply}
}

// CreateErrorResponse will create a JSON-RPC error response with the given id and error.
func (c *jsonCodec) CreateErrorResponse(id *int64, err RPCError) interface{} {
    return &JSONErrResponse{Version: jsonRPCVersion, Id: id, Error: JSONError{Code: err.Code(), Message: err.Error()}}
}

// CreateErrorResponseWithInfo will create a JSON-RPC error response with the given id and error.
// info is optional and contains additional information about the error. When an empty string is passed it is ignored.
func (c *jsonCodec) CreateErrorResponseWithInfo(id *int64, err RPCError, info interface{}) interface{} {
    return &JSONErrResponse{Version: jsonRPCVersion, Id: id,
        Error: JSONError{Code: err.Code(), Message: err.Error(), Data: info}}
}

// CreateNotification will create a JSON-RPC notification with the given subscription id and event as params.
func (c *jsonCodec) CreateNotification(subid string, event interface{}) interface{} {
    if isHexNum(reflect.TypeOf(event)) {
        return &jsonNotification{Version: jsonRPCVersion, Method: notificationMethod,
            Params: jsonSubscription{Subscription: subid, Result: fmt.Sprintf(`%#x`, event)}}
    }

    return &jsonNotification{Version: jsonRPCVersion, Method: notificationMethod,
        Params: jsonSubscription{Subscription: subid, Result: event}}
}

// Write message to client
func (c *jsonCodec) Write(res interface{}) error {
    return c.e.Encode(res)
}

// Close the underlying connection
func (c *jsonCodec) Close() {
    if atomic.CompareAndSwapInt32(&c.isClosed, 0, 1) {
        close(c.closed)
        c.rw.Close()
    }
}

// Closed returns a channel which will be closed when Close is called
func (c *jsonCodec) Closed() <-chan interface{} {
    return c.closed
}