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
.nio
.channels
.OverlappingFileLockException
;
31 import java
.util
.HashMap
;
33 import java
.util
.Objects
;
34 import java
.util
.function
.Supplier
;
36 public class SignalJsonRpcDispatcherHandler
{
38 private final static Logger logger
= LoggerFactory
.getLogger(SignalJsonRpcDispatcherHandler
.class);
40 private static final int USER_ERROR
= -1;
41 private static final int IO_ERROR
= -3;
42 private static final int UNTRUSTED_KEY_ERROR
= -4;
44 private final ObjectMapper objectMapper
;
45 private final JsonRpcSender jsonRpcSender
;
46 private final JsonRpcReader jsonRpcReader
;
47 private final boolean noReceiveOnStart
;
49 private MultiAccountManager c
;
50 private final Map
<Manager
, Manager
.ReceiveMessageHandler
> receiveHandlers
= new HashMap
<>();
54 public SignalJsonRpcDispatcherHandler(
55 final JsonWriter jsonWriter
, final Supplier
<String
> lineSupplier
, final boolean noReceiveOnStart
57 this.noReceiveOnStart
= noReceiveOnStart
;
58 this.objectMapper
= Util
.createJsonObjectMapper();
59 this.jsonRpcSender
= new JsonRpcSender(jsonWriter
);
60 this.jsonRpcReader
= new JsonRpcReader(jsonRpcSender
, lineSupplier
);
63 public void handleConnection(final MultiAccountManager c
) {
66 if (!noReceiveOnStart
) {
67 c
.getAccountNumbers().stream().map(c
::getManager
).filter(Objects
::nonNull
).forEach(this::subscribeReceive
);
68 c
.addOnManagerAddedHandler(this::subscribeReceive
);
69 c
.addOnManagerRemovedHandler(this::unsubscribeReceive
);
75 public void handleConnection(final Manager m
) {
78 if (!noReceiveOnStart
) {
82 final var currentThread
= Thread
.currentThread();
83 m
.addClosedListener(currentThread
::interrupt
);
88 private void subscribeReceive(final Manager m
) {
89 if (receiveHandlers
.containsKey(m
)) {
93 final var receiveMessageHandler
= new JsonReceiveMessageHandler(m
,
94 s
-> jsonRpcSender
.sendRequest(JsonRpcRequest
.forNotification("receive",
95 objectMapper
.valueToTree(s
),
97 m
.addReceiveHandler(receiveMessageHandler
);
98 receiveHandlers
.put(m
, receiveMessageHandler
);
100 while (!m
.hasCaughtUpWithOldMessages()) {
105 } catch (InterruptedException ignored
) {
110 void unsubscribeReceive(final Manager m
) {
111 final var receiveMessageHandler
= receiveHandlers
.remove(m
);
112 if (receiveMessageHandler
!= null) {
113 m
.removeReceiveHandler(receiveMessageHandler
);
117 private void handleConnection() {
119 jsonRpcReader
.readMessages((method
, params
) -> handleRequest(objectMapper
, method
, params
),
120 response
-> logger
.debug("Received unexpected response for id {}", response
.getId()));
122 receiveHandlers
.forEach(Manager
::removeReceiveHandler
);
123 receiveHandlers
.clear();
127 private JsonNode
handleRequest(
128 final ObjectMapper objectMapper
, final String method
, ContainerNode
<?
> params
129 ) throws JsonRpcException
{
130 var command
= getCommand(method
);
132 if (command
instanceof JsonRpcMultiCommand
<?
> jsonRpcCommand
) {
133 return runCommand(objectMapper
, params
, new MultiCommandRunnerImpl
<>(c
, jsonRpcCommand
));
135 if (command
instanceof JsonRpcRegistrationCommand
<?
> jsonRpcCommand
) {
136 try (var manager
= getRegistrationManagerFromParams(params
)) {
137 if (manager
!= null) {
138 return runCommand(objectMapper
,
140 new RegistrationCommandRunnerImpl
<>(manager
, c
, jsonRpcCommand
));
142 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INVALID_PARAMS
,
143 "Method requires valid account parameter",
146 } catch (IOException e
) {
147 logger
.warn("Failed to close registration manager", e
);
151 if (command
instanceof JsonRpcSingleCommand
<?
> jsonRpcCommand
) {
153 return runCommand(objectMapper
, params
, new CommandRunnerImpl
<>(m
, jsonRpcCommand
));
156 final var manager
= getManagerFromParams(params
);
157 if (manager
!= null) {
158 return runCommand(objectMapper
, params
, new CommandRunnerImpl
<>(manager
, jsonRpcCommand
));
160 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INVALID_PARAMS
,
161 "Method requires valid account parameter",
166 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.METHOD_NOT_FOUND
,
167 "Method not implemented",
171 private Manager
getManagerFromParams(final ContainerNode
<?
> params
) {
172 if (params
!= null && params
.has("account")) {
173 final var manager
= c
.getManager(params
.get("account").asText());
174 ((ObjectNode
) params
).remove("account");
180 private RegistrationManager
getRegistrationManagerFromParams(final ContainerNode
<?
> params
) {
181 if (params
!= null && params
.has("account")) {
183 final var registrationManager
= c
.getNewRegistrationManager(params
.get("account").asText());
184 ((ObjectNode
) params
).remove("account");
185 return registrationManager
;
186 } catch (OverlappingFileLockException e
) {
187 logger
.warn("Account is already in use");
189 } catch (IOException
| IllegalStateException e
) {
190 logger
.warn("Failed to load registration manager", e
);
197 private Command
getCommand(final String method
) {
198 if ("subscribeReceive".equals(method
)) {
199 return new SubscribeReceiveCommand();
201 if ("unsubscribeReceive".equals(method
)) {
202 return new UnsubscribeReceiveCommand();
204 return Commands
.getCommand(method
);
207 private record CommandRunnerImpl
<T
>(Manager m
, JsonRpcSingleCommand
<T
> command
) implements CommandRunner
<T
> {
210 public void handleCommand(final T request
, final JsonWriter jsonWriter
) throws CommandException
{
211 command
.handleCommand(request
, m
, jsonWriter
);
215 public TypeReference
<T
> getRequestType() {
216 return command
.getRequestType();
220 private record RegistrationCommandRunnerImpl
<T
>(
221 RegistrationManager m
, MultiAccountManager c
, JsonRpcRegistrationCommand
<T
> command
222 ) implements CommandRunner
<T
> {
225 public void handleCommand(final T request
, final JsonWriter jsonWriter
) throws CommandException
{
226 command
.handleCommand(request
, m
, jsonWriter
);
230 public TypeReference
<T
> getRequestType() {
231 return command
.getRequestType();
235 private record MultiCommandRunnerImpl
<T
>(
236 MultiAccountManager c
, JsonRpcMultiCommand
<T
> command
237 ) implements CommandRunner
<T
> {
240 public void handleCommand(final T request
, final JsonWriter jsonWriter
) throws CommandException
{
241 command
.handleCommand(request
, c
, jsonWriter
);
245 public TypeReference
<T
> getRequestType() {
246 return command
.getRequestType();
250 interface CommandRunner
<T
> {
252 void handleCommand(T request
, JsonWriter jsonWriter
) throws CommandException
;
254 TypeReference
<T
> getRequestType();
257 private JsonNode
runCommand(
258 final ObjectMapper objectMapper
, final ContainerNode
<?
> params
, final CommandRunner
<?
> command
259 ) throws JsonRpcException
{
260 final Object
[] result
= {null};
261 final JsonWriter commandJsonWriter
= s
-> {
262 if (result
[0] != null) {
263 throw new AssertionError("Command may only write one json result");
270 parseParamsAndRunCommand(objectMapper
, params
, commandJsonWriter
, command
);
271 } catch (JsonMappingException e
) {
272 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INVALID_REQUEST
,
275 } catch (UserErrorException e
) {
276 throw new JsonRpcException(new JsonRpcResponse
.Error(USER_ERROR
,
278 getErrorDataNode(objectMapper
, result
)));
279 } catch (IOErrorException e
) {
280 throw new JsonRpcException(new JsonRpcResponse
.Error(IO_ERROR
,
282 getErrorDataNode(objectMapper
, result
)));
283 } catch (UntrustedKeyErrorException e
) {
284 throw new JsonRpcException(new JsonRpcResponse
.Error(UNTRUSTED_KEY_ERROR
,
286 getErrorDataNode(objectMapper
, result
)));
287 } catch (Throwable e
) {
288 logger
.error("Command execution failed", e
);
289 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INTERNAL_ERROR
,
291 getErrorDataNode(objectMapper
, result
)));
294 Object output
= result
[0] == null ? Map
.of() : result
[0];
295 return objectMapper
.valueToTree(output
);
298 private JsonNode
getErrorDataNode(final ObjectMapper objectMapper
, final Object
[] result
) {
299 if (result
[0] == null) {
302 return objectMapper
.valueToTree(Map
.of("response", result
[0]));
305 private <T
> void parseParamsAndRunCommand(
306 final ObjectMapper objectMapper
,
307 final TreeNode params
,
308 final JsonWriter jsonWriter
,
309 final CommandRunner
<T
> command
310 ) throws CommandException
, JsonMappingException
{
311 T requestParams
= null;
312 final var requestType
= command
.getRequestType();
313 if (params
!= null && requestType
!= null) {
315 requestParams
= objectMapper
.readValue(objectMapper
.treeAsTokens(params
), requestType
);
316 } catch (JsonMappingException e
) {
318 } catch (IOException e
) {
319 throw new AssertionError(e
);
322 command
.handleCommand(requestParams
, jsonWriter
);
325 private class SubscribeReceiveCommand
implements JsonRpcSingleCommand
<Void
> {
328 public String
getName() {
329 return "subscribeReceive";
333 public void handleCommand(
334 final Void request
, final Manager m
, final JsonWriter jsonWriter
335 ) throws CommandException
{
340 private class UnsubscribeReceiveCommand
implements JsonRpcSingleCommand
<Void
> {
343 public String
getName() {
344 return "unsubscribeReceive";
348 public void handleCommand(
349 final Void request
, final Manager m
, final JsonWriter jsonWriter
350 ) throws CommandException
{
351 unsubscribeReceive(m
);