]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/jsonrpc/JsonRpcReader.java
Implement jsonRpc command
[signal-cli] / src / main / java / org / asamk / signal / jsonrpc / JsonRpcReader.java
1 package org.asamk.signal.jsonrpc;
2
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.ValueNode;
9
10 import org.asamk.signal.util.Util;
11 import org.slf4j.Logger;
12 import org.slf4j.LoggerFactory;
13
14 import java.io.IOException;
15 import java.util.Objects;
16 import java.util.function.Consumer;
17 import java.util.function.Supplier;
18 import java.util.stream.Collectors;
19 import java.util.stream.StreamSupport;
20
21 public class JsonRpcReader {
22
23 private final static Logger logger = LoggerFactory.getLogger(JsonRpcReader.class);
24
25 private final JsonRpcSender jsonRpcSender;
26 private final ObjectMapper objectMapper;
27 private final Supplier<String> lineSupplier;
28
29 public JsonRpcReader(
30 final JsonRpcSender jsonRpcSender, final Supplier<String> lineSupplier
31 ) {
32 this.jsonRpcSender = jsonRpcSender;
33 this.lineSupplier = lineSupplier;
34 this.objectMapper = Util.createJsonObjectMapper();
35 }
36
37 public void readRequests(
38 final RequestHandler requestHandler, final Consumer<JsonRpcResponse> responseHandler
39 ) {
40 while (!Thread.interrupted()) {
41 JsonRpcMessage message = readMessage();
42 if (message == null) break;
43
44 if (message instanceof JsonRpcRequest) {
45 final var response = handleRequest(requestHandler, (JsonRpcRequest) message);
46 if (response != null) {
47 jsonRpcSender.sendResponse(response);
48 }
49 } else if (message instanceof JsonRpcResponse) {
50 responseHandler.accept((JsonRpcResponse) message);
51 } else {
52 final var responseList = ((JsonRpcBulkMessage) message).getMessages().stream().map(jsonNode -> {
53 final JsonRpcRequest request;
54 try {
55 request = parseJsonRpcRequest(jsonNode);
56 } catch (JsonRpcException e) {
57 return JsonRpcResponse.forError(e.getError(), getId(jsonNode));
58 }
59
60 return handleRequest(requestHandler, request);
61 }).filter(Objects::nonNull).collect(Collectors.toList());
62
63 jsonRpcSender.sendBulkResponses(responseList);
64 }
65 }
66 }
67
68 private JsonRpcResponse handleRequest(final RequestHandler requestHandler, final JsonRpcRequest request) {
69 try {
70 final var result = requestHandler.apply(request.getMethod(), request.getParams());
71 if (request.getId() != null) {
72 return JsonRpcResponse.forSuccess(result, request.getId());
73 }
74 } catch (JsonRpcException e) {
75 if (request.getId() != null) {
76 return JsonRpcResponse.forError(e.getError(), request.getId());
77 }
78 }
79 return null;
80 }
81
82 private JsonRpcMessage readMessage() {
83 while (!Thread.interrupted()) {
84 String input = lineSupplier.get();
85
86 if (input == null) {
87 // Reached end of input stream
88 break;
89 }
90
91 JsonRpcMessage message = parseJsonRpcMessage(input);
92 if (message == null) continue;
93
94 return message;
95 }
96
97 return null;
98 }
99
100 private JsonRpcMessage parseJsonRpcMessage(final String input) {
101 final JsonNode jsonNode;
102 try {
103 jsonNode = objectMapper.readTree(input);
104 } catch (JsonParseException e) {
105 jsonRpcSender.sendResponse(JsonRpcResponse.forError(new JsonRpcResponse.Error(JsonRpcResponse.Error.PARSE_ERROR,
106 e.getMessage(),
107 null), null));
108 return null;
109 } catch (IOException e) {
110 throw new AssertionError(e);
111 }
112
113 if (jsonNode == null) {
114 jsonRpcSender.sendResponse(JsonRpcResponse.forError(new JsonRpcResponse.Error(JsonRpcResponse.Error.INVALID_REQUEST,
115 "invalid request",
116 null), null));
117 return null;
118 } else if (jsonNode.isArray()) {
119 if (jsonNode.size() == 0) {
120 jsonRpcSender.sendResponse(JsonRpcResponse.forError(new JsonRpcResponse.Error(JsonRpcResponse.Error.INVALID_REQUEST,
121 "invalid request",
122 null), null));
123 return null;
124 }
125 return new JsonRpcBulkMessage(StreamSupport.stream(jsonNode.spliterator(), false)
126 .collect(Collectors.toList()));
127 } else if (jsonNode.isObject()) {
128 if (jsonNode.has("result") || jsonNode.has("error")) {
129 return parseJsonRpcResponse(jsonNode);
130 } else {
131 try {
132 return parseJsonRpcRequest(jsonNode);
133 } catch (JsonRpcException e) {
134 jsonRpcSender.sendResponse(JsonRpcResponse.forError(e.getError(), getId(jsonNode)));
135 return null;
136 }
137 }
138 } else {
139 jsonRpcSender.sendResponse(JsonRpcResponse.forError(new JsonRpcResponse.Error(JsonRpcResponse.Error.INVALID_REQUEST,
140 "unexpected type: " + jsonNode.getNodeType().name(),
141 null), null));
142 return null;
143 }
144 }
145
146 private ValueNode getId(JsonNode jsonNode) {
147 final var id = jsonNode.get("id");
148 return id instanceof ValueNode ? (ValueNode) id : null;
149 }
150
151 private JsonRpcRequest parseJsonRpcRequest(final JsonNode input) throws JsonRpcException {
152 JsonRpcRequest request;
153 try {
154 request = objectMapper.treeToValue(input, JsonRpcRequest.class);
155 } catch (JsonMappingException e) {
156 throw new JsonRpcException(new JsonRpcResponse.Error(JsonRpcResponse.Error.INVALID_REQUEST,
157 e.getMessage(),
158 null));
159 } catch (IOException e) {
160 throw new AssertionError(e);
161 }
162
163 if (!"2.0".equals(request.getJsonrpc())) {
164 throw new JsonRpcException(new JsonRpcResponse.Error(JsonRpcResponse.Error.INVALID_REQUEST,
165 "only jsonrpc version 2.0 is supported",
166 null));
167 }
168
169 if (request.getMethod() == null) {
170 throw new JsonRpcException(new JsonRpcResponse.Error(JsonRpcResponse.Error.INVALID_REQUEST,
171 "method field must be set",
172 null));
173 }
174
175 return request;
176 }
177
178 private JsonRpcResponse parseJsonRpcResponse(final JsonNode input) {
179 JsonRpcResponse response;
180 try {
181 response = objectMapper.treeToValue(input, JsonRpcResponse.class);
182 } catch (JsonParseException | JsonMappingException e) {
183 logger.debug("Received invalid jsonrpc response {}", e.getMessage());
184 return null;
185 } catch (IOException e) {
186 throw new AssertionError(e);
187 }
188
189 if (!"2.0".equals(response.getJsonrpc())) {
190 logger.debug("Received invalid jsonrpc response with invalid version {}", response.getJsonrpc());
191 return null;
192 }
193
194 if (response.getResult() != null && response.getError() != null) {
195 logger.debug("Received invalid jsonrpc response with both result and error");
196 return null;
197 }
198
199 if (response.getResult() == null && response.getError() == null) {
200 logger.debug("Received invalid jsonrpc response without result and error");
201 return null;
202 }
203
204 if (response.getId() == null || response.getId().isNull()) {
205 logger.debug("Received invalid jsonrpc response without id");
206 return null;
207 }
208
209 return response;
210 }
211
212 public interface RequestHandler {
213
214 JsonNode apply(String method, ContainerNode<?> params) throws JsonRpcException;
215 }
216 }