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
;
10 import org
.asamk
.signal
.JsonReceiveMessageHandler
;
11 import org
.asamk
.signal
.JsonWriter
;
12 import org
.asamk
.signal
.OutputWriter
;
13 import org
.asamk
.signal
.commands
.Command
;
14 import org
.asamk
.signal
.commands
.Commands
;
15 import org
.asamk
.signal
.commands
.JsonRpcCommand
;
16 import org
.asamk
.signal
.commands
.SignalCreator
;
17 import org
.asamk
.signal
.commands
.exceptions
.CommandException
;
18 import org
.asamk
.signal
.commands
.exceptions
.IOErrorException
;
19 import org
.asamk
.signal
.commands
.exceptions
.UntrustedKeyErrorException
;
20 import org
.asamk
.signal
.commands
.exceptions
.UserErrorException
;
21 import org
.asamk
.signal
.manager
.Manager
;
22 import org
.asamk
.signal
.util
.Util
;
23 import org
.slf4j
.Logger
;
24 import org
.slf4j
.LoggerFactory
;
26 import java
.io
.IOException
;
27 import java
.util
.HashMap
;
29 import java
.util
.Objects
;
30 import java
.util
.function
.Supplier
;
32 public class SignalJsonRpcDispatcherHandler
{
34 private final static Logger logger
= LoggerFactory
.getLogger(SignalJsonRpcDispatcherHandler
.class);
36 private static final int USER_ERROR
= -1;
37 private static final int IO_ERROR
= -3;
38 private static final int UNTRUSTED_KEY_ERROR
= -4;
40 private final ObjectMapper objectMapper
;
41 private final JsonRpcSender jsonRpcSender
;
42 private final JsonRpcReader jsonRpcReader
;
43 private final boolean noReceiveOnStart
;
45 private SignalCreator c
;
46 private final Map
<Manager
, Manager
.ReceiveMessageHandler
> receiveHandlers
= new HashMap
<>();
50 public SignalJsonRpcDispatcherHandler(
51 final JsonWriter outputWriter
, final Supplier
<String
> lineSupplier
, final boolean noReceiveOnStart
53 this.noReceiveOnStart
= noReceiveOnStart
;
54 this.objectMapper
= Util
.createJsonObjectMapper();
55 this.jsonRpcSender
= new JsonRpcSender(outputWriter
);
56 this.jsonRpcReader
= new JsonRpcReader(jsonRpcSender
, lineSupplier
);
59 public void handleConnection(final SignalCreator c
) {
62 if (!noReceiveOnStart
) {
63 c
.getAccountNumbers().stream().map(c
::getManager
).filter(Objects
::nonNull
).forEach(this::subscribeReceive
);
69 public void handleConnection(final Manager m
) {
72 if (!noReceiveOnStart
) {
79 private void subscribeReceive(final Manager m
) {
80 if (receiveHandlers
.containsKey(m
)) {
84 final var receiveMessageHandler
= new JsonReceiveMessageHandler(m
,
85 s
-> jsonRpcSender
.sendRequest(JsonRpcRequest
.forNotification("receive",
86 objectMapper
.valueToTree(s
),
88 m
.addReceiveHandler(receiveMessageHandler
);
89 receiveHandlers
.put(m
, receiveMessageHandler
);
91 while (!m
.hasCaughtUpWithOldMessages()) {
96 } catch (InterruptedException ignored
) {
101 void unsubscribeReceive(final Manager m
) {
102 final var receiveMessageHandler
= receiveHandlers
.remove(m
);
103 if (receiveMessageHandler
!= null) {
104 m
.removeReceiveHandler(receiveMessageHandler
);
108 private void handleConnection() {
110 jsonRpcReader
.readMessages((method
, params
) -> handleRequest(objectMapper
, method
, params
),
111 response
-> logger
.debug("Received unexpected response for id {}", response
.getId()));
113 receiveHandlers
.forEach(Manager
::removeReceiveHandler
);
114 receiveHandlers
.clear();
118 private JsonNode
handleRequest(
119 final ObjectMapper objectMapper
, final String method
, ContainerNode
<?
> params
120 ) throws JsonRpcException
{
121 var command
= getCommand(method
);
122 // TODO implement listAccounts, register, verify, link
123 if (command
instanceof JsonRpcCommand
<?
> jsonRpcCommand
) {
125 return runCommand(objectMapper
, params
, new CommandRunnerImpl
<>(m
, jsonRpcCommand
));
128 if (params
.has("account")) {
129 Manager manager
= c
.getManager(params
.get("account").asText());
130 if (manager
!= null) {
131 return runCommand(objectMapper
, params
, new CommandRunnerImpl
<>(manager
, jsonRpcCommand
));
134 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INVALID_PARAMS
,
135 "Method requires valid account parameter",
140 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.METHOD_NOT_FOUND
,
141 "Method not implemented",
145 private Command
getCommand(final String method
) {
146 if ("subscribeReceive".equals(method
)) {
147 return new SubscribeReceiveCommand();
149 if ("unsubscribeReceive".equals(method
)) {
150 return new UnsubscribeReceiveCommand();
152 return Commands
.getCommand(method
);
155 private record CommandRunnerImpl
<T
>(Manager m
, JsonRpcCommand
<T
> command
) implements CommandRunner
<T
> {
158 public void handleCommand(final T request
, final OutputWriter outputWriter
) throws CommandException
{
159 command
.handleCommand(request
, m
, outputWriter
);
163 public TypeReference
<T
> getRequestType() {
164 return command
.getRequestType();
168 interface CommandRunner
<T
> {
170 void handleCommand(T request
, OutputWriter outputWriter
) throws CommandException
;
172 TypeReference
<T
> getRequestType();
175 private JsonNode
runCommand(
176 final ObjectMapper objectMapper
, final ContainerNode
<?
> params
, final CommandRunner
<?
> command
177 ) throws JsonRpcException
{
178 final Object
[] result
= {null};
179 final JsonWriter commandOutputWriter
= s
-> {
180 if (result
[0] != null) {
181 throw new AssertionError("Command may only write one json result");
188 parseParamsAndRunCommand(objectMapper
, params
, commandOutputWriter
, command
);
189 } catch (JsonMappingException e
) {
190 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INVALID_REQUEST
,
193 } catch (UserErrorException e
) {
194 throw new JsonRpcException(new JsonRpcResponse
.Error(USER_ERROR
, e
.getMessage(), null));
195 } catch (IOErrorException e
) {
196 throw new JsonRpcException(new JsonRpcResponse
.Error(IO_ERROR
, e
.getMessage(), null));
197 } catch (UntrustedKeyErrorException e
) {
198 throw new JsonRpcException(new JsonRpcResponse
.Error(UNTRUSTED_KEY_ERROR
, e
.getMessage(), null));
199 } catch (Throwable e
) {
200 logger
.error("Command execution failed", e
);
201 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INTERNAL_ERROR
,
206 Object output
= result
[0] == null ? Map
.of() : result
[0];
207 return objectMapper
.valueToTree(output
);
210 private <T
> void parseParamsAndRunCommand(
211 final ObjectMapper objectMapper
,
212 final TreeNode params
,
213 final OutputWriter outputWriter
,
214 final CommandRunner
<T
> command
215 ) throws CommandException
, JsonMappingException
{
216 T requestParams
= null;
217 final var requestType
= command
.getRequestType();
218 if (params
!= null && requestType
!= null) {
220 requestParams
= objectMapper
.readValue(objectMapper
.treeAsTokens(params
), requestType
);
221 } catch (JsonMappingException e
) {
223 } catch (IOException e
) {
224 throw new AssertionError(e
);
227 command
.handleCommand(requestParams
, outputWriter
);
230 private class SubscribeReceiveCommand
implements JsonRpcCommand
<Void
> {
233 public String
getName() {
234 return "subscribeReceive";
238 public void handleCommand(
239 final Void request
, final Manager m
, final OutputWriter outputWriter
240 ) throws CommandException
{
245 private class UnsubscribeReceiveCommand
implements JsonRpcCommand
<Void
> {
248 public String
getName() {
249 return "unsubscribeReceive";
253 public void handleCommand(
254 final Void request
, final Manager m
, final OutputWriter outputWriter
255 ) throws CommandException
{
256 unsubscribeReceive(m
);