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