1 package org
.asamk
.signal
.jsonrpc
;
3 import com
.fasterxml
.jackson
.core
.TreeNode
;
4 import com
.fasterxml
.jackson
.core
.type
.TypeReference
;
5 import com
.fasterxml
.jackson
.databind
.JsonMappingException
;
6 import com
.fasterxml
.jackson
.databind
.JsonNode
;
7 import com
.fasterxml
.jackson
.databind
.ObjectMapper
;
8 import com
.fasterxml
.jackson
.databind
.node
.ContainerNode
;
9 import com
.fasterxml
.jackson
.databind
.node
.ObjectNode
;
11 import org
.asamk
.signal
.commands
.Command
;
12 import org
.asamk
.signal
.commands
.Commands
;
13 import org
.asamk
.signal
.commands
.JsonRpcMultiCommand
;
14 import org
.asamk
.signal
.commands
.JsonRpcRegistrationCommand
;
15 import org
.asamk
.signal
.commands
.JsonRpcSingleCommand
;
16 import org
.asamk
.signal
.commands
.exceptions
.CommandException
;
17 import org
.asamk
.signal
.commands
.exceptions
.IOErrorException
;
18 import org
.asamk
.signal
.commands
.exceptions
.UntrustedKeyErrorException
;
19 import org
.asamk
.signal
.commands
.exceptions
.UserErrorException
;
20 import org
.asamk
.signal
.json
.JsonReceiveMessageHandler
;
21 import org
.asamk
.signal
.manager
.Manager
;
22 import org
.asamk
.signal
.manager
.MultiAccountManager
;
23 import org
.asamk
.signal
.manager
.RegistrationManager
;
24 import org
.asamk
.signal
.output
.JsonWriter
;
25 import org
.asamk
.signal
.util
.Util
;
26 import org
.slf4j
.Logger
;
27 import org
.slf4j
.LoggerFactory
;
29 import java
.io
.IOException
;
30 import java
.util
.HashMap
;
32 import java
.util
.Objects
;
33 import java
.util
.function
.Supplier
;
35 public class SignalJsonRpcDispatcherHandler
{
37 private final static Logger logger
= LoggerFactory
.getLogger(SignalJsonRpcDispatcherHandler
.class);
39 private static final int USER_ERROR
= -1;
40 private static final int IO_ERROR
= -3;
41 private static final int UNTRUSTED_KEY_ERROR
= -4;
43 private final ObjectMapper objectMapper
;
44 private final JsonRpcSender jsonRpcSender
;
45 private final JsonRpcReader jsonRpcReader
;
46 private final boolean noReceiveOnStart
;
48 private MultiAccountManager c
;
49 private final Map
<Manager
, Manager
.ReceiveMessageHandler
> receiveHandlers
= new HashMap
<>();
53 public SignalJsonRpcDispatcherHandler(
54 final JsonWriter jsonWriter
, final Supplier
<String
> lineSupplier
, final boolean noReceiveOnStart
56 this.noReceiveOnStart
= noReceiveOnStart
;
57 this.objectMapper
= Util
.createJsonObjectMapper();
58 this.jsonRpcSender
= new JsonRpcSender(jsonWriter
);
59 this.jsonRpcReader
= new JsonRpcReader(jsonRpcSender
, lineSupplier
);
62 public void handleConnection(final MultiAccountManager c
) {
65 if (!noReceiveOnStart
) {
66 c
.getAccountNumbers().stream().map(c
::getManager
).filter(Objects
::nonNull
).forEach(this::subscribeReceive
);
67 c
.addOnManagerAddedHandler(this::subscribeReceive
);
68 c
.addOnManagerRemovedHandler(this::unsubscribeReceive
);
74 public void handleConnection(final Manager m
) {
77 if (!noReceiveOnStart
) {
81 final var currentThread
= Thread
.currentThread();
82 m
.addClosedListener(currentThread
::interrupt
);
87 private void subscribeReceive(final Manager m
) {
88 if (receiveHandlers
.containsKey(m
)) {
92 final var receiveMessageHandler
= new JsonReceiveMessageHandler(m
,
93 s
-> jsonRpcSender
.sendRequest(JsonRpcRequest
.forNotification("receive",
94 objectMapper
.valueToTree(s
),
96 m
.addReceiveHandler(receiveMessageHandler
);
97 receiveHandlers
.put(m
, receiveMessageHandler
);
99 while (!m
.hasCaughtUpWithOldMessages()) {
104 } catch (InterruptedException ignored
) {
109 void unsubscribeReceive(final Manager m
) {
110 final var receiveMessageHandler
= receiveHandlers
.remove(m
);
111 if (receiveMessageHandler
!= null) {
112 m
.removeReceiveHandler(receiveMessageHandler
);
116 private void handleConnection() {
118 jsonRpcReader
.readMessages((method
, params
) -> handleRequest(objectMapper
, method
, params
),
119 response
-> logger
.debug("Received unexpected response for id {}", response
.getId()));
121 receiveHandlers
.forEach(Manager
::removeReceiveHandler
);
122 receiveHandlers
.clear();
126 private JsonNode
handleRequest(
127 final ObjectMapper objectMapper
, final String method
, ContainerNode
<?
> params
128 ) throws JsonRpcException
{
129 var command
= getCommand(method
);
131 if (command
instanceof JsonRpcMultiCommand
<?
> jsonRpcCommand
) {
132 return runCommand(objectMapper
, params
, new MultiCommandRunnerImpl
<>(c
, jsonRpcCommand
));
134 if (command
instanceof JsonRpcRegistrationCommand
<?
> jsonRpcCommand
) {
135 try (var manager
= getRegistrationManagerFromParams(params
)) {
136 if (manager
!= null) {
137 return runCommand(objectMapper
,
139 new RegistrationCommandRunnerImpl
<>(manager
, c
, jsonRpcCommand
));
141 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INVALID_PARAMS
,
142 "Method requires valid account parameter",
145 } catch (IOException e
) {
146 logger
.warn("Failed to close registration manager", e
);
150 if (command
instanceof JsonRpcSingleCommand
<?
> jsonRpcCommand
) {
152 return runCommand(objectMapper
, params
, new CommandRunnerImpl
<>(m
, jsonRpcCommand
));
155 final var manager
= getManagerFromParams(params
);
156 if (manager
!= null) {
157 return runCommand(objectMapper
, params
, new CommandRunnerImpl
<>(manager
, jsonRpcCommand
));
159 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INVALID_PARAMS
,
160 "Method requires valid account parameter",
165 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.METHOD_NOT_FOUND
,
166 "Method not implemented",
170 private Manager
getManagerFromParams(final ContainerNode
<?
> params
) {
171 if (params
!= null && params
.has("account")) {
172 final var manager
= c
.getManager(params
.get("account").asText());
173 ((ObjectNode
) params
).remove("account");
179 private RegistrationManager
getRegistrationManagerFromParams(final ContainerNode
<?
> params
) {
180 if (params
!= null && params
.has("account")) {
182 final var registrationManager
= c
.getNewRegistrationManager(params
.get("account").asText());
183 ((ObjectNode
) params
).remove("account");
184 return registrationManager
;
185 } catch (IOException
| IllegalStateException e
) {
186 logger
.warn("Failed to load registration manager", e
);
193 private Command
getCommand(final String method
) {
194 if ("subscribeReceive".equals(method
)) {
195 return new SubscribeReceiveCommand();
197 if ("unsubscribeReceive".equals(method
)) {
198 return new UnsubscribeReceiveCommand();
200 return Commands
.getCommand(method
);
203 private record CommandRunnerImpl
<T
>(Manager m
, JsonRpcSingleCommand
<T
> command
) implements CommandRunner
<T
> {
206 public void handleCommand(final T request
, final JsonWriter jsonWriter
) throws CommandException
{
207 command
.handleCommand(request
, m
, jsonWriter
);
211 public TypeReference
<T
> getRequestType() {
212 return command
.getRequestType();
216 private record RegistrationCommandRunnerImpl
<T
>(
217 RegistrationManager m
, MultiAccountManager c
, JsonRpcRegistrationCommand
<T
> command
218 ) implements CommandRunner
<T
> {
221 public void handleCommand(final T request
, final JsonWriter jsonWriter
) throws CommandException
{
222 command
.handleCommand(request
, m
, jsonWriter
);
226 public TypeReference
<T
> getRequestType() {
227 return command
.getRequestType();
231 private record MultiCommandRunnerImpl
<T
>(
232 MultiAccountManager c
, JsonRpcMultiCommand
<T
> command
233 ) implements CommandRunner
<T
> {
236 public void handleCommand(final T request
, final JsonWriter jsonWriter
) throws CommandException
{
237 command
.handleCommand(request
, c
, jsonWriter
);
241 public TypeReference
<T
> getRequestType() {
242 return command
.getRequestType();
246 interface CommandRunner
<T
> {
248 void handleCommand(T request
, JsonWriter jsonWriter
) throws CommandException
;
250 TypeReference
<T
> getRequestType();
253 private JsonNode
runCommand(
254 final ObjectMapper objectMapper
, final ContainerNode
<?
> params
, final CommandRunner
<?
> command
255 ) throws JsonRpcException
{
256 final Object
[] result
= {null};
257 final JsonWriter commandJsonWriter
= s
-> {
258 if (result
[0] != null) {
259 throw new AssertionError("Command may only write one json result");
266 parseParamsAndRunCommand(objectMapper
, params
, commandJsonWriter
, command
);
267 } catch (JsonMappingException e
) {
268 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INVALID_REQUEST
,
271 } catch (UserErrorException e
) {
272 throw new JsonRpcException(new JsonRpcResponse
.Error(USER_ERROR
,
274 getErrorDataNode(objectMapper
, result
)));
275 } catch (IOErrorException e
) {
276 throw new JsonRpcException(new JsonRpcResponse
.Error(IO_ERROR
,
278 getErrorDataNode(objectMapper
, result
)));
279 } catch (UntrustedKeyErrorException e
) {
280 throw new JsonRpcException(new JsonRpcResponse
.Error(UNTRUSTED_KEY_ERROR
,
282 getErrorDataNode(objectMapper
, result
)));
283 } catch (Throwable e
) {
284 logger
.error("Command execution failed", e
);
285 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INTERNAL_ERROR
,
287 getErrorDataNode(objectMapper
, result
)));
290 Object output
= result
[0] == null ? Map
.of() : result
[0];
291 return objectMapper
.valueToTree(output
);
294 private JsonNode
getErrorDataNode(final ObjectMapper objectMapper
, final Object
[] result
) {
295 if (result
[0] == null) {
298 return objectMapper
.valueToTree(Map
.of("response", result
[0]));
301 private <T
> void parseParamsAndRunCommand(
302 final ObjectMapper objectMapper
,
303 final TreeNode params
,
304 final JsonWriter jsonWriter
,
305 final CommandRunner
<T
> command
306 ) throws CommandException
, JsonMappingException
{
307 T requestParams
= null;
308 final var requestType
= command
.getRequestType();
309 if (params
!= null && requestType
!= null) {
311 requestParams
= objectMapper
.readValue(objectMapper
.treeAsTokens(params
), requestType
);
312 } catch (JsonMappingException e
) {
314 } catch (IOException e
) {
315 throw new AssertionError(e
);
318 command
.handleCommand(requestParams
, jsonWriter
);
321 private class SubscribeReceiveCommand
implements JsonRpcSingleCommand
<Void
> {
324 public String
getName() {
325 return "subscribeReceive";
329 public void handleCommand(
330 final Void request
, final Manager m
, final JsonWriter jsonWriter
331 ) throws CommandException
{
336 private class UnsubscribeReceiveCommand
implements JsonRpcSingleCommand
<Void
> {
339 public String
getName() {
340 return "unsubscribeReceive";
344 public void handleCommand(
345 final Void request
, final Manager m
, final JsonWriter jsonWriter
346 ) throws CommandException
{
347 unsubscribeReceive(m
);