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
.JsonReceiveMessageHandler
;
12 import org
.asamk
.signal
.JsonWriter
;
13 import org
.asamk
.signal
.commands
.Command
;
14 import org
.asamk
.signal
.commands
.Commands
;
15 import org
.asamk
.signal
.commands
.JsonRpcMultiCommand
;
16 import org
.asamk
.signal
.commands
.JsonRpcRegistrationCommand
;
17 import org
.asamk
.signal
.commands
.JsonRpcSingleCommand
;
18 import org
.asamk
.signal
.commands
.exceptions
.CommandException
;
19 import org
.asamk
.signal
.commands
.exceptions
.IOErrorException
;
20 import org
.asamk
.signal
.commands
.exceptions
.UntrustedKeyErrorException
;
21 import org
.asamk
.signal
.commands
.exceptions
.UserErrorException
;
22 import org
.asamk
.signal
.manager
.Manager
;
23 import org
.asamk
.signal
.manager
.MultiAccountManager
;
24 import org
.asamk
.signal
.manager
.RegistrationManager
;
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
);
72 public void handleConnection(final Manager m
) {
75 if (!noReceiveOnStart
) {
82 private void subscribeReceive(final Manager m
) {
83 if (receiveHandlers
.containsKey(m
)) {
87 final var receiveMessageHandler
= new JsonReceiveMessageHandler(m
,
88 s
-> jsonRpcSender
.sendRequest(JsonRpcRequest
.forNotification("receive",
89 objectMapper
.valueToTree(s
),
91 m
.addReceiveHandler(receiveMessageHandler
);
92 receiveHandlers
.put(m
, receiveMessageHandler
);
94 while (!m
.hasCaughtUpWithOldMessages()) {
99 } catch (InterruptedException ignored
) {
104 void unsubscribeReceive(final Manager m
) {
105 final var receiveMessageHandler
= receiveHandlers
.remove(m
);
106 if (receiveMessageHandler
!= null) {
107 m
.removeReceiveHandler(receiveMessageHandler
);
111 private void handleConnection() {
113 jsonRpcReader
.readMessages((method
, params
) -> handleRequest(objectMapper
, method
, params
),
114 response
-> logger
.debug("Received unexpected response for id {}", response
.getId()));
116 receiveHandlers
.forEach(Manager
::removeReceiveHandler
);
117 receiveHandlers
.clear();
121 private JsonNode
handleRequest(
122 final ObjectMapper objectMapper
, final String method
, ContainerNode
<?
> params
123 ) throws JsonRpcException
{
124 var command
= getCommand(method
);
126 if (command
instanceof JsonRpcMultiCommand
<?
> jsonRpcCommand
) {
127 return runCommand(objectMapper
, params
, new MultiCommandRunnerImpl
<>(c
, jsonRpcCommand
));
129 if (command
instanceof JsonRpcRegistrationCommand
<?
> jsonRpcCommand
) {
130 try (var manager
= getRegistrationManagerFromParams(params
)) {
131 if (manager
!= null) {
132 return runCommand(objectMapper
,
134 new RegistrationCommandRunnerImpl
<>(manager
, c
, jsonRpcCommand
));
136 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INVALID_PARAMS
,
137 "Method requires valid account parameter",
140 } catch (IOException e
) {
141 logger
.warn("Failed to close registration manager", e
);
145 if (command
instanceof JsonRpcSingleCommand
<?
> jsonRpcCommand
) {
147 return runCommand(objectMapper
, params
, new CommandRunnerImpl
<>(m
, jsonRpcCommand
));
150 final var manager
= getManagerFromParams(params
);
151 if (manager
!= null) {
152 return runCommand(objectMapper
, params
, new CommandRunnerImpl
<>(manager
, jsonRpcCommand
));
154 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INVALID_PARAMS
,
155 "Method requires valid account parameter",
160 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.METHOD_NOT_FOUND
,
161 "Method not implemented",
165 private Manager
getManagerFromParams(final ContainerNode
<?
> params
) {
166 if (params
!= null && params
.has("account")) {
167 final var manager
= c
.getManager(params
.get("account").asText());
168 ((ObjectNode
) params
).remove("account");
174 private RegistrationManager
getRegistrationManagerFromParams(final ContainerNode
<?
> params
) {
175 if (params
!= null && params
.has("account")) {
177 final var registrationManager
= c
.getNewRegistrationManager(params
.get("account").asText());
178 ((ObjectNode
) params
).remove("account");
179 return registrationManager
;
180 } catch (IOException
| IllegalStateException e
) {
181 logger
.warn("Failed to load registration manager", e
);
188 private Command
getCommand(final String method
) {
189 if ("subscribeReceive".equals(method
)) {
190 return new SubscribeReceiveCommand();
192 if ("unsubscribeReceive".equals(method
)) {
193 return new UnsubscribeReceiveCommand();
195 return Commands
.getCommand(method
);
198 private record CommandRunnerImpl
<T
>(Manager m
, JsonRpcSingleCommand
<T
> command
) implements CommandRunner
<T
> {
201 public void handleCommand(final T request
, final JsonWriter jsonWriter
) throws CommandException
{
202 command
.handleCommand(request
, m
, jsonWriter
);
206 public TypeReference
<T
> getRequestType() {
207 return command
.getRequestType();
211 private record RegistrationCommandRunnerImpl
<T
>(
212 RegistrationManager m
, MultiAccountManager c
, JsonRpcRegistrationCommand
<T
> command
213 ) implements CommandRunner
<T
> {
216 public void handleCommand(final T request
, final JsonWriter jsonWriter
) throws CommandException
{
217 command
.handleCommand(request
, m
, jsonWriter
);
221 public TypeReference
<T
> getRequestType() {
222 return command
.getRequestType();
226 private record MultiCommandRunnerImpl
<T
>(
227 MultiAccountManager c
, JsonRpcMultiCommand
<T
> command
228 ) implements CommandRunner
<T
> {
231 public void handleCommand(final T request
, final JsonWriter jsonWriter
) throws CommandException
{
232 command
.handleCommand(request
, c
, jsonWriter
);
236 public TypeReference
<T
> getRequestType() {
237 return command
.getRequestType();
241 interface CommandRunner
<T
> {
243 void handleCommand(T request
, JsonWriter jsonWriter
) throws CommandException
;
245 TypeReference
<T
> getRequestType();
248 private JsonNode
runCommand(
249 final ObjectMapper objectMapper
, final ContainerNode
<?
> params
, final CommandRunner
<?
> command
250 ) throws JsonRpcException
{
251 final Object
[] result
= {null};
252 final JsonWriter commandJsonWriter
= s
-> {
253 if (result
[0] != null) {
254 throw new AssertionError("Command may only write one json result");
261 parseParamsAndRunCommand(objectMapper
, params
, commandJsonWriter
, command
);
262 } catch (JsonMappingException e
) {
263 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INVALID_REQUEST
,
266 } catch (UserErrorException e
) {
267 throw new JsonRpcException(new JsonRpcResponse
.Error(USER_ERROR
, e
.getMessage(), null));
268 } catch (IOErrorException e
) {
269 throw new JsonRpcException(new JsonRpcResponse
.Error(IO_ERROR
, e
.getMessage(), null));
270 } catch (UntrustedKeyErrorException e
) {
271 throw new JsonRpcException(new JsonRpcResponse
.Error(UNTRUSTED_KEY_ERROR
, e
.getMessage(), null));
272 } catch (Throwable e
) {
273 logger
.error("Command execution failed", e
);
274 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INTERNAL_ERROR
,
279 Object output
= result
[0] == null ? Map
.of() : result
[0];
280 return objectMapper
.valueToTree(output
);
283 private <T
> void parseParamsAndRunCommand(
284 final ObjectMapper objectMapper
,
285 final TreeNode params
,
286 final JsonWriter jsonWriter
,
287 final CommandRunner
<T
> command
288 ) throws CommandException
, JsonMappingException
{
289 T requestParams
= null;
290 final var requestType
= command
.getRequestType();
291 if (params
!= null && requestType
!= null) {
293 requestParams
= objectMapper
.readValue(objectMapper
.treeAsTokens(params
), requestType
);
294 } catch (JsonMappingException e
) {
296 } catch (IOException e
) {
297 throw new AssertionError(e
);
300 command
.handleCommand(requestParams
, jsonWriter
);
303 private class SubscribeReceiveCommand
implements JsonRpcSingleCommand
<Void
> {
306 public String
getName() {
307 return "subscribeReceive";
311 public void handleCommand(
312 final Void request
, final Manager m
, final JsonWriter jsonWriter
313 ) throws CommandException
{
318 private class UnsubscribeReceiveCommand
implements JsonRpcSingleCommand
<Void
> {
321 public String
getName() {
322 return "unsubscribeReceive";
326 public void handleCommand(
327 final Void request
, final Manager m
, final JsonWriter jsonWriter
328 ) throws CommandException
{
329 unsubscribeReceive(m
);