aboutsummaryrefslogtreecommitdiffstats
path: root/rpc/json.go
blob: 1853a715faedac89108eb8e549008a6103abea89 (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
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
// 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 (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "reflect"
    "strconv"
    "strings"
    "sync"

    "github.com/dexon-foundation/dexon/log"
)

const (
    jsonrpcVersion           = "2.0"
    serviceMethodSeparator   = "_"
    subscribeMethodSuffix    = "_subscribe"
    unsubscribeMethodSuffix  = "_unsubscribe"
    notificationMethodSuffix = "_subscription"
)

type jsonRequest struct {
    Method  string          `json:"method"`
    Version string          `json:"jsonrpc"`
    Id      json.RawMessage `json:"id,omitempty"`
    Payload json.RawMessage `json:"params,omitempty"`
}

type jsonSuccessResponse struct {
    Version string      `json:"jsonrpc"`
    Id      interface{} `json:"id,omitempty"`
    Result  interface{} `json:"result"`
}

type jsonError struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data,omitempty"`
}

type jsonErrResponse struct {
    Version string      `json:"jsonrpc"`
    Id      interface{} `json:"id,omitempty"`
    Error   jsonError   `json:"error"`
}

type jsonSubscription struct {
    Subscription string      `json:"subscription"`
    Result       interface{} `json:"result,omitempty"`
}

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 {
    closer sync.Once                 // close closed channel once
    closed chan interface{}          // closed on Close
    decMu  sync.Mutex                // guards the decoder
    decode func(v interface{}) error // decoder to allow multiple transports
    encMu  sync.Mutex                // guards the encoder
    encode func(v interface{}) error // encoder to allow multiple transports
    rw     io.ReadWriteCloser        // connection
}

func (err *jsonError) Error() string {
    if err.Message == "" {
        return fmt.Sprintf("json-rpc error %d", err.Code)
    }
    return err.Message
}

func (err *jsonError) ErrorCode() int {
    return err.Code
}

// NewCodec creates a new RPC server codec with support for JSON-RPC 2.0 based
// on explicitly given encoding and decoding methods.
func NewCodec(rwc io.ReadWriteCloser, encode, decode func(v interface{}) error) ServerCodec {
    return &jsonCodec{
        closed: make(chan interface{}),
        encode: encode,
        decode: decode,
        rw:     rwc,
    }
}

// NewJSONCodec creates a new RPC server codec with support for JSON-RPC 2.0.
func NewJSONCodec(rwc io.ReadWriteCloser) ServerCodec {
    enc := json.NewEncoder(rwc)
    dec := json.NewDecoder(rwc)
    dec.UseNumber()

    return &jsonCodec{
        closed: make(chan interface{}),
        encode: enc.Encode,
        decode: dec.Decode,
        rw:     rwc,
    }
}

// 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, Error) {
    c.decMu.Lock()
    defer c.decMu.Unlock()

    var incomingMsg json.RawMessage
    if err := c.decode(&incomingMsg); err != nil {
        return nil, false, &invalidRequestError{err.Error()}
    }
    if isBatch(incomingMsg) {
        return parseBatchRequest(incomingMsg)
    }
    return parseRequest(incomingMsg)
}

// checkReqId returns an error when the given reqId isn't valid for RPC method calls.
// valid id's are strings, numbers or null
func checkReqId(reqId json.RawMessage) error {
    if len(reqId) == 0 {
        return fmt.Errorf("missing request id")
    }
    if _, err := strconv.ParseFloat(string(reqId), 64); err == nil {
        return nil
    }
    var str string
    if err := json.Unmarshal(reqId, &str); err == nil {
        return nil
    }
    return fmt.Errorf("invalid request id")
}

// 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, Error) {
    var in jsonRequest
    if err := json.Unmarshal(incomingMsg, &in); err != nil {
        return nil, false, &invalidMessageError{err.Error()}
    }

    if err := checkReqId(in.Id); err != nil {
        return nil, false, &invalidMessageError{err.Error()}
    }

    // subscribe are special, they will always use `subscribeMethod` as first param in the payload
    if strings.HasSuffix(in.Method, subscribeMethodSuffix) {
        reqs := []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 {
                log.Debug(fmt.Sprintf("Unable to parse subscription method: %v\n", err))
                return nil, false, &invalidRequestError{"Unable to parse subscription request"}
            }

            reqs[0].service, reqs[0].method = strings.TrimSuffix(in.Method, subscribeMethodSuffix), subscribeMethod[0]
            reqs[0].params = in.Payload
            return reqs, false, nil
        }
        return nil, false, &invalidRequestError{"Unable to parse subscription request"}
    }

    if strings.HasSuffix(in.Method, unsubscribeMethodSuffix) {
        return []rpcRequest{{id: &in.Id, isPubSub: true,
            method: in.Method, params: in.Payload}}, false, nil
    }

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

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

    return []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, Error) {
    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 err := checkReqId(r.Id); err != nil {
            return nil, false, &invalidMessageError{err.Error()}
        }

        id := &in[i].Id

        // subscribe are special, they will always use `subscriptionMethod` as first param in the payload
        if strings.HasSuffix(r.Method, subscribeMethodSuffix) {
            requests[i] = rpcRequest{id: id, isPubSub: true}
            if len(r.Payload) > 0 {
                // first param must be subscription name
                var subscribeMethod [1]string
                if err := json.Unmarshal(r.Payload, &subscribeMethod); err != nil {
                    log.Debug(fmt.Sprintf("Unable to parse subscription method: %v\n", err))
                    return nil, false, &invalidRequestError{"Unable to parse subscription request"}
                }

                requests[i].service, requests[i].method = strings.TrimSuffix(r.Method, subscribeMethodSuffix), subscribeMethod[0]
                requests[i].params = r.Payload
                continue
            }

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

        if strings.HasSuffix(r.Method, unsubscribeMethodSuffix) {
            requests[i] = rpcRequest{id: id, isPubSub: true, method: r.Method, params: r.Payload}
            continue
        }

        if len(r.Payload) == 0 {
            requests[i] = rpcRequest{id: id, params: nil}
        } else {
            requests[i] = rpcRequest{id: id, params: r.Payload}
        }
        if elem := strings.Split(r.Method, serviceMethodSeparator); len(elem) == 2 {
            requests[i].service, requests[i].method = elem[0], elem[1]
        } else {
            requests[i].err = &methodNotFoundError{r.Method, ""}
        }
    }

    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, Error) {
    if args, ok := params.(json.RawMessage); !ok {
        return nil, &invalidParamsError{"Invalid params supplied"}
    } else {
        return parsePositionalArguments(args, argTypes)
    }
}

// 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. Missing optional arguments are returned as reflect.Zero values.
func parsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([]reflect.Value, Error) {
    // Read beginning of the args array.
    dec := json.NewDecoder(bytes.NewReader(rawArgs))
    if tok, _ := dec.Token(); tok != json.Delim('[') {
        return nil, &invalidParamsError{"non-array args"}
    }
    // Read args.
    args := make([]reflect.Value, 0, len(types))
    for i := 0; dec.More(); i++ {
        if i >= len(types) {
            return nil, &invalidParamsError{fmt.Sprintf("too many arguments, want at most %d", len(types))}
        }
        argval := reflect.New(types[i])
        if err := dec.Decode(argval.Interface()); err != nil {
            return nil, &invalidParamsError{fmt.Sprintf("invalid argument %d: %v", i, err)}
        }
        if argval.IsNil() && types[i].Kind() != reflect.Ptr {
            return nil, &invalidParamsError{fmt.Sprintf("missing value for required argument %d", i)}
        }
        args = append(args, argval.Elem())
    }
    // Read end of args array.
    if _, err := dec.Token(); err != nil {
        return nil, &invalidParamsError{err.Error()}
    }
    // Set any missing args to nil.
    for i := len(args); i < len(types); i++ {
        if types[i].Kind() != reflect.Ptr {
            return nil, &invalidParamsError{fmt.Sprintf("missing value for required argument %d", i)}
        }
        args = append(args, reflect.Zero(types[i]))
    }
    return args, nil
}

// CreateResponse will create a JSON-RPC success response with the given id and reply as result.
func (c *jsonCodec) CreateResponse(id interface{}, reply interface{}) interface{} {
    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 interface{}, err Error) interface{} {
    return &jsonErrResponse{Version: jsonrpcVersion, Id: id, Error: jsonError{Code: err.ErrorCode(), 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 interface{}, err Error, info interface{}) interface{} {
    return &jsonErrResponse{Version: jsonrpcVersion, Id: id,
        Error: jsonError{Code: err.ErrorCode(), 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, namespace string, event interface{}) interface{} {
    return &jsonNotification{Version: jsonrpcVersion, Method: namespace + notificationMethodSuffix,
        Params: jsonSubscription{Subscription: subid, Result: event}}
}

// Write message to client
func (c *jsonCodec) Write(res interface{}) error {
    c.encMu.Lock()
    defer c.encMu.Unlock()

    return c.encode(res)
}

// Close the underlying connection
func (c *jsonCodec) Close() {
    c.closer.Do(func() {
        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
}