| 1 | package rpcwire |
| 2 | |
| 3 | import ( |
| 4 | "bufio" |
| 5 | "encoding/json" |
| 6 | "errors" |
| 7 | "io" |
| 8 | ) |
| 9 | |
| 10 | // StrictRequestFrame is one validated JSON-RPC 2.0 NDJSON request. Raw retains |
| 11 | // the exact bytes consumed from the reader, including line ending and harmless |
| 12 | // surrounding whitespace, so a bootstrap proxy can forward it unchanged. |
| 13 | type StrictRequestFrame struct { |
| 14 | Raw []byte |
| 15 | ID json.RawMessage |
| 16 | Method string |
| 17 | Params json.RawMessage |
| 18 | } |
| 19 | |
| 20 | // ResponseIDForError returns an ID that is safe to place in a JSON-RPC error |
| 21 | // response. Invalid request IDs must never be reflected back because doing so |
| 22 | // would make the error response invalid too. Missing and invalid IDs therefore |
| 23 | // become null; valid string, integer, and null IDs retain their value. |
| 24 | func ResponseIDForError(id json.RawMessage) json.RawMessage { |
| 25 | id = trimSpace(id) |
| 26 | if !validRPCID(id) { |
| 27 | return json.RawMessage("null") |
| 28 | } |
| 29 | return append(json.RawMessage(nil), id...) |
| 30 | } |
| 31 | |
| 32 | // ReadStrictRequestFrame reads exactly one request while retaining br and any |
| 33 | // buffered remainder for subsequent proxying. It shares rpcwire's strict frame |
| 34 | // validator and inbound byte-limit semantics. |
| 35 | func ReadStrictRequestFrame(br *bufio.Reader, maxBytes int) (StrictRequestFrame, error) { |
| 36 | var raw []byte |
| 37 | for { |
| 38 | chunk, err := br.ReadSlice('\n') |
| 39 | raw = append(raw, chunk...) |
| 40 | if maxBytes > 0 && len(raw) > maxBytes { |
| 41 | return StrictRequestFrame{Raw: append([]byte(nil), raw...)}, &FrameTooLargeError{Direction: "inbound", Size: len(raw), Limit: maxBytes} |
| 42 | } |
| 43 | if errors.Is(err, bufio.ErrBufferFull) { |
| 44 | continue |
| 45 | } |
| 46 | if err != nil && !errors.Is(err, io.EOF) { |
| 47 | return StrictRequestFrame{}, err |
| 48 | } |
| 49 | if len(raw) == 0 { |
| 50 | return StrictRequestFrame{}, io.EOF |
| 51 | } |
| 52 | break |
| 53 | } |
| 54 | |
| 55 | payload := trimSpace(raw) |
| 56 | var in inbound |
| 57 | if err := json.Unmarshal(payload, &in); err != nil { |
| 58 | return StrictRequestFrame{Raw: append([]byte(nil), raw...)}, err |
| 59 | } |
| 60 | frame := StrictRequestFrame{ |
| 61 | Raw: append([]byte(nil), raw...), ID: append(json.RawMessage(nil), in.ID...), |
| 62 | Method: in.Method, Params: append(json.RawMessage(nil), in.Params...), |
| 63 | } |
| 64 | if err := validateStrictFrame(payload, in); err != nil { |
| 65 | return frame, err |
| 66 | } |
| 67 | if in.Method == "" || len(in.ID) == 0 { |
| 68 | return frame, errors.New("rpcwire: bootstrap frame must be a request") |
| 69 | } |
| 70 | return frame, nil |
| 71 | } |
| 72 |