]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/jsonrpc/SignalJsonRpcDispatcherHandler.java
9085609ca733c40246d9abe7c35049d6964989af
[signal-cli] / src / main / java / org / asamk / signal / jsonrpc / SignalJsonRpcDispatcherHandler.java
1 package org.asamk.signal.jsonrpc;
2
3 import com.fasterxml.jackson.core.TreeNode;
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
9 import org.asamk.signal.JsonReceiveMessageHandler;
10 import org.asamk.signal.JsonWriter;
11 import org.asamk.signal.OutputWriter;
12 import org.asamk.signal.commands.Commands;
13 import org.asamk.signal.commands.JsonRpcCommand;
14 import org.asamk.signal.commands.exceptions.CommandException;
15 import org.asamk.signal.commands.exceptions.IOErrorException;
16 import org.asamk.signal.commands.exceptions.UntrustedKeyErrorException;
17 import org.asamk.signal.commands.exceptions.UserErrorException;
18 import org.asamk.signal.manager.Manager;
19 import org.asamk.signal.util.Util;
20 import org.slf4j.Logger;
21 import org.slf4j.LoggerFactory;
22
23 import java.io.IOException;
24 import java.util.Map;
25 import java.util.function.Supplier;
26
27 public class SignalJsonRpcDispatcherHandler {
28
29 private final static Logger logger = LoggerFactory.getLogger(SignalJsonRpcDispatcherHandler.class);
30
31 private static final int USER_ERROR = -1;
32 private static final int IO_ERROR = -3;
33 private static final int UNTRUSTED_KEY_ERROR = -4;
34
35 private final Manager m;
36 private final JsonWriter outputWriter;
37 private final Supplier<String> lineSupplier;
38
39 public SignalJsonRpcDispatcherHandler(
40 final Manager m, final JsonWriter outputWriter, final Supplier<String> lineSupplier
41 ) {
42 this.m = m;
43 this.outputWriter = outputWriter;
44 this.lineSupplier = lineSupplier;
45 }
46
47 public void handleConnection() {
48 final var objectMapper = Util.createJsonObjectMapper();
49 final var jsonRpcSender = new JsonRpcSender(outputWriter);
50
51 final var receiveMessageHandler = new JsonReceiveMessageHandler(m,
52 s -> jsonRpcSender.sendRequest(JsonRpcRequest.forNotification("receive",
53 objectMapper.valueToTree(s),
54 null)));
55 try {
56 m.addReceiveHandler(receiveMessageHandler);
57
58 // Maybe this should be handled inside the Manager
59 while (!m.hasCaughtUpWithOldMessages()) {
60 try {
61 synchronized (m) {
62 m.wait();
63 }
64 } catch (InterruptedException ignored) {
65 }
66 }
67
68 final var jsonRpcReader = new JsonRpcReader(jsonRpcSender, lineSupplier);
69 jsonRpcReader.readRequests((method, params) -> handleRequest(m, objectMapper, method, params),
70 response -> logger.debug("Received unexpected response for id {}", response.getId()));
71 } finally {
72 m.removeReceiveHandler(receiveMessageHandler);
73 }
74 }
75
76 private JsonNode handleRequest(
77 final Manager m, final ObjectMapper objectMapper, final String method, ContainerNode<?> params
78 ) throws JsonRpcException {
79 final Object[] result = {null};
80 final JsonWriter commandOutputWriter = s -> {
81 if (result[0] != null) {
82 throw new AssertionError("Command may only write one json result");
83 }
84
85 result[0] = s;
86 };
87
88 var command = Commands.getCommand(method);
89 if (!(command instanceof JsonRpcCommand)) {
90 throw new JsonRpcException(new JsonRpcResponse.Error(JsonRpcResponse.Error.METHOD_NOT_FOUND,
91 "Method not implemented",
92 null));
93 }
94
95 try {
96 parseParamsAndRunCommand(m, objectMapper, params, commandOutputWriter, (JsonRpcCommand<?>) command);
97 } catch (JsonMappingException e) {
98 throw new JsonRpcException(new JsonRpcResponse.Error(JsonRpcResponse.Error.INVALID_REQUEST,
99 e.getMessage(),
100 null));
101 } catch (UserErrorException e) {
102 throw new JsonRpcException(new JsonRpcResponse.Error(USER_ERROR, e.getMessage(), null));
103 } catch (IOErrorException e) {
104 throw new JsonRpcException(new JsonRpcResponse.Error(IO_ERROR, e.getMessage(), null));
105 } catch (UntrustedKeyErrorException e) {
106 throw new JsonRpcException(new JsonRpcResponse.Error(UNTRUSTED_KEY_ERROR, e.getMessage(), null));
107 } catch (Throwable e) {
108 logger.error("Command execution failed", e);
109 throw new JsonRpcException(new JsonRpcResponse.Error(JsonRpcResponse.Error.INTERNAL_ERROR,
110 e.getMessage(),
111 null));
112 }
113
114 Object output = result[0] == null ? Map.of() : result[0];
115 return objectMapper.valueToTree(output);
116 }
117
118 private <T> void parseParamsAndRunCommand(
119 final Manager m,
120 final ObjectMapper objectMapper,
121 final TreeNode params,
122 final OutputWriter outputWriter,
123 final JsonRpcCommand<T> command
124 ) throws CommandException, JsonMappingException {
125 T requestParams = null;
126 final var requestType = command.getRequestType();
127 if (params != null && requestType != null) {
128 try {
129 requestParams = objectMapper.readValue(objectMapper.treeAsTokens(params), requestType);
130 } catch (JsonMappingException e) {
131 throw e;
132 } catch (IOException e) {
133 throw new AssertionError(e);
134 }
135 }
136 command.handleCommand(requestParams, m, outputWriter);
137 }
138 }