1 package org
.asamk
.signal
.jsonrpc
;
3 import com
.fasterxml
.jackson
.core
.JsonParseException
;
4 import com
.fasterxml
.jackson
.databind
.JsonMappingException
;
5 import com
.fasterxml
.jackson
.databind
.JsonNode
;
6 import com
.fasterxml
.jackson
.databind
.ObjectMapper
;
7 import com
.fasterxml
.jackson
.databind
.node
.ContainerNode
;
8 import com
.fasterxml
.jackson
.databind
.node
.ObjectNode
;
9 import com
.fasterxml
.jackson
.databind
.node
.ValueNode
;
11 import org
.asamk
.signal
.util
.Util
;
12 import org
.slf4j
.Logger
;
13 import org
.slf4j
.LoggerFactory
;
15 import java
.io
.IOException
;
16 import java
.io
.InputStream
;
17 import java
.util
.Objects
;
18 import java
.util
.function
.Consumer
;
19 import java
.util
.function
.Supplier
;
20 import java
.util
.stream
.StreamSupport
;
22 public class JsonRpcReader
{
24 private final static Logger logger
= LoggerFactory
.getLogger(JsonRpcReader
.class);
26 private final JsonRpcSender jsonRpcSender
;
27 private final ObjectMapper objectMapper
;
28 private final InputStream input
;
29 private final Supplier
<String
> lineSupplier
;
31 public JsonRpcReader(final JsonRpcSender jsonRpcSender
, final Supplier
<String
> lineSupplier
) {
32 this.jsonRpcSender
= jsonRpcSender
;
34 this.lineSupplier
= lineSupplier
;
35 this.objectMapper
= Util
.createJsonObjectMapper();
38 public JsonRpcReader(final JsonRpcSender jsonRpcSender
, final InputStream input
) {
39 this.jsonRpcSender
= jsonRpcSender
;
41 this.lineSupplier
= null;
42 this.objectMapper
= Util
.createJsonObjectMapper();
45 public void readMessages(final RequestHandler requestHandler
, final Consumer
<JsonRpcResponse
> responseHandler
) {
47 JsonRpcMessage message
= parseJsonRpcMessage(input
);
48 if (message
== null) {
52 handleMessage(message
, requestHandler
, responseHandler
);
56 while (!Thread
.interrupted()) {
57 String input
= lineSupplier
.get();
59 logger
.trace("Reached end of JSON-RPC input stream.");
63 logger
.trace("Incoming JSON-RPC message: {}", input
);
64 JsonRpcMessage message
= parseJsonRpcMessage(input
);
65 if (message
== null) {
69 handleMessage(message
, requestHandler
, responseHandler
);
73 private void handleMessage(
74 final JsonRpcMessage message
,
75 final RequestHandler requestHandler
,
76 final Consumer
<JsonRpcResponse
> responseHandler
78 if (message
instanceof final JsonRpcRequest jsonRpcRequest
) {
79 logger
.debug("Received json rpc request, method: " + jsonRpcRequest
.getMethod());
80 final var response
= handleRequest(requestHandler
, jsonRpcRequest
);
81 if (response
!= null) {
82 jsonRpcSender
.sendResponse(response
);
84 } else if (message
instanceof JsonRpcResponse jsonRpcResponse
) {
85 responseHandler
.accept(jsonRpcResponse
);
87 final var responseList
= ((JsonRpcBatchMessage
) message
).getMessages().stream().map(jsonNode
-> {
88 final JsonRpcRequest request
;
90 request
= parseJsonRpcRequest(jsonNode
);
91 } catch (JsonRpcException e
) {
92 return JsonRpcResponse
.forError(e
.getError(), getId(jsonNode
));
95 return handleRequest(requestHandler
, request
);
96 }).filter(Objects
::nonNull
).toList();
98 if (responseList
.size() > 0) {
99 jsonRpcSender
.sendBatchResponses(responseList
);
104 private JsonRpcResponse
handleRequest(final RequestHandler requestHandler
, final JsonRpcRequest request
) {
106 final var result
= requestHandler
.apply(request
.getMethod(), request
.getParams());
107 if (request
.getId() != null) {
108 return JsonRpcResponse
.forSuccess(result
, request
.getId());
110 logger
.debug("Command '{}' succeeded but client didn't specify an id, dropping response",
111 request
.getMethod());
113 } catch (JsonRpcException e
) {
114 if (request
.getId() != null) {
115 return JsonRpcResponse
.forError(e
.getError(), request
.getId());
117 logger
.debug("Command '{}' failed but client didn't specify an id, dropping error: {}",
125 private JsonRpcMessage
parseJsonRpcMessage(final String input
) {
126 final JsonNode jsonNode
;
128 jsonNode
= objectMapper
.readTree(input
);
129 } catch (JsonParseException e
) {
130 jsonRpcSender
.sendResponse(JsonRpcResponse
.forError(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.PARSE_ERROR
,
134 } catch (IOException e
) {
135 throw new AssertionError(e
);
138 return parseJsonRpcMessage(jsonNode
);
141 private JsonRpcMessage
parseJsonRpcMessage(final InputStream input
) {
142 final JsonNode jsonNode
;
144 jsonNode
= objectMapper
.readTree(input
);
145 } catch (JsonParseException e
) {
146 jsonRpcSender
.sendResponse(JsonRpcResponse
.forError(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.PARSE_ERROR
,
150 } catch (IOException e
) {
151 throw new AssertionError(e
);
154 return parseJsonRpcMessage(jsonNode
);
157 private JsonRpcMessage
parseJsonRpcMessage(final JsonNode jsonNode
) {
158 if (jsonNode
== null) {
159 jsonRpcSender
.sendResponse(JsonRpcResponse
.forError(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INVALID_REQUEST
,
163 } else if (jsonNode
.isArray()) {
164 if (jsonNode
.size() == 0) {
165 jsonRpcSender
.sendResponse(JsonRpcResponse
.forError(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INVALID_REQUEST
,
170 return new JsonRpcBatchMessage(StreamSupport
.stream(jsonNode
.spliterator(), false).toList());
171 } else if (jsonNode
.isObject()) {
172 if (jsonNode
.has("result") || jsonNode
.has("error")) {
173 return parseJsonRpcResponse(jsonNode
);
176 return parseJsonRpcRequest(jsonNode
);
177 } catch (JsonRpcException e
) {
178 jsonRpcSender
.sendResponse(JsonRpcResponse
.forError(e
.getError(), getId(jsonNode
)));
183 jsonRpcSender
.sendResponse(JsonRpcResponse
.forError(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INVALID_REQUEST
,
184 "unexpected type: " + jsonNode
.getNodeType().name(),
190 private ValueNode
getId(JsonNode jsonNode
) {
191 final var id
= jsonNode
.get("id");
192 return id
instanceof ValueNode ?
(ValueNode
) id
: null;
195 private JsonRpcRequest
parseJsonRpcRequest(final JsonNode input
) throws JsonRpcException
{
196 if (input
instanceof ObjectNode i
&& input
.has("params") && input
.get("params").isNull()) {
197 // Workaround for clients that send a null params field instead of omitting it
200 JsonRpcRequest request
;
202 request
= objectMapper
.treeToValue(input
, JsonRpcRequest
.class);
203 } catch (JsonMappingException e
) {
204 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INVALID_REQUEST
,
207 } catch (IOException e
) {
208 throw new AssertionError(e
);
211 if (!"2.0".equals(request
.getJsonrpc())) {
212 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INVALID_REQUEST
,
213 "only jsonrpc version 2.0 is supported",
217 if (request
.getMethod() == null) {
218 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INVALID_REQUEST
,
219 "method field must be set",
226 private JsonRpcResponse
parseJsonRpcResponse(final JsonNode input
) {
227 JsonRpcResponse response
;
229 response
= objectMapper
.treeToValue(input
, JsonRpcResponse
.class);
230 } catch (JsonParseException
| JsonMappingException e
) {
231 logger
.debug("Received invalid jsonrpc response {}", e
.getMessage());
233 } catch (IOException e
) {
234 throw new AssertionError(e
);
237 if (!"2.0".equals(response
.getJsonrpc())) {
238 logger
.debug("Received invalid jsonrpc response with invalid version {}", response
.getJsonrpc());
242 if (response
.getResult() != null && response
.getError() != null) {
243 logger
.debug("Received invalid jsonrpc response with both result and error");
247 if (response
.getResult() == null && response
.getError() == null) {
248 logger
.debug("Received invalid jsonrpc response without result and error");
252 if (response
.getId() == null || response
.getId().isNull()) {
253 logger
.debug("Received invalid jsonrpc response without id");
260 public interface RequestHandler
{
262 JsonNode
apply(String method
, ContainerNode
<?
> params
) throws JsonRpcException
;