1 package org
.asamk
.signal
.commands
;
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
;
9 import net
.sourceforge
.argparse4j
.impl
.Arguments
;
10 import net
.sourceforge
.argparse4j
.inf
.Namespace
;
11 import net
.sourceforge
.argparse4j
.inf
.Subparser
;
13 import org
.asamk
.signal
.JsonReceiveMessageHandler
;
14 import org
.asamk
.signal
.JsonWriter
;
15 import org
.asamk
.signal
.OutputType
;
16 import org
.asamk
.signal
.OutputWriter
;
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
.jsonrpc
.JsonRpcException
;
22 import org
.asamk
.signal
.jsonrpc
.JsonRpcReader
;
23 import org
.asamk
.signal
.jsonrpc
.JsonRpcRequest
;
24 import org
.asamk
.signal
.jsonrpc
.JsonRpcResponse
;
25 import org
.asamk
.signal
.jsonrpc
.JsonRpcSender
;
26 import org
.asamk
.signal
.manager
.Manager
;
27 import org
.asamk
.signal
.util
.Util
;
28 import org
.slf4j
.Logger
;
29 import org
.slf4j
.LoggerFactory
;
31 import java
.io
.BufferedReader
;
32 import java
.io
.IOException
;
33 import java
.io
.InputStreamReader
;
34 import java
.util
.List
;
36 import java
.util
.concurrent
.TimeUnit
;
38 public class JsonRpcDispatcherCommand
implements LocalCommand
{
40 private final static Logger logger
= LoggerFactory
.getLogger(JsonRpcDispatcherCommand
.class);
42 private static final int USER_ERROR
= -1;
43 private static final int IO_ERROR
= -3;
44 private static final int UNTRUSTED_KEY_ERROR
= -4;
47 public String
getName() {
52 public void attachToSubparser(final Subparser subparser
) {
53 subparser
.help("Take commands from standard input as line-delimited JSON RPC while receiving messages.");
54 subparser
.addArgument("--ignore-attachments")
55 .help("Don’t download attachments of received messages.")
56 .action(Arguments
.storeTrue());
60 public List
<OutputType
> getSupportedOutputTypes() {
61 return List
.of(OutputType
.JSON
);
65 public void handleCommand(
66 final Namespace ns
, final Manager m
, final OutputWriter outputWriter
67 ) throws CommandException
{
68 final boolean ignoreAttachments
= Boolean
.TRUE
.equals(ns
.getBoolean("ignore-attachments"));
69 m
.setIgnoreAttachments(ignoreAttachments
);
71 final var objectMapper
= Util
.createJsonObjectMapper();
72 final var jsonRpcSender
= new JsonRpcSender((JsonWriter
) outputWriter
);
74 final var receiveThread
= receiveMessages(s
-> jsonRpcSender
.sendRequest(JsonRpcRequest
.forNotification(
76 objectMapper
.valueToTree(s
),
79 // Maybe this should be handled inside the Manager
80 while (!m
.hasCaughtUpWithOldMessages()) {
85 } catch (InterruptedException ignored
) {
89 final BufferedReader reader
= new BufferedReader(new InputStreamReader(System
.in));
91 final var jsonRpcReader
= new JsonRpcReader(jsonRpcSender
, () -> {
93 return reader
.readLine();
94 } catch (IOException e
) {
95 throw new AssertionError(e
);
98 jsonRpcReader
.readRequests((method
, params
) -> handleRequest(m
, objectMapper
, method
, params
),
99 response
-> logger
.debug("Received unexpected response for id {}", response
.getId()));
101 receiveThread
.interrupt();
103 receiveThread
.join();
104 } catch (InterruptedException ignored
) {
108 private JsonNode
handleRequest(
109 final Manager m
, final ObjectMapper objectMapper
, final String method
, ContainerNode
<?
> params
110 ) throws JsonRpcException
{
111 final Object
[] result
= {null};
112 final JsonWriter commandOutputWriter
= s
-> {
113 if (result
[0] != null) {
114 throw new AssertionError("Command may only write one json result");
120 var command
= Commands
.getCommand(method
);
121 if (!(command
instanceof JsonRpcCommand
)) {
122 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.METHOD_NOT_FOUND
,
123 "Method not implemented",
128 parseParamsAndRunCommand(m
, objectMapper
, params
, commandOutputWriter
, (JsonRpcCommand
<?
>) command
);
129 } catch (JsonMappingException e
) {
130 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INVALID_REQUEST
,
133 } catch (UserErrorException e
) {
134 throw new JsonRpcException(new JsonRpcResponse
.Error(USER_ERROR
, e
.getMessage(), null));
135 } catch (IOErrorException e
) {
136 throw new JsonRpcException(new JsonRpcResponse
.Error(IO_ERROR
, e
.getMessage(), null));
137 } catch (UntrustedKeyErrorException e
) {
138 throw new JsonRpcException(new JsonRpcResponse
.Error(UNTRUSTED_KEY_ERROR
, e
.getMessage(), null));
139 } catch (Throwable e
) {
140 logger
.error("Command execution failed", e
);
141 throw new JsonRpcException(new JsonRpcResponse
.Error(JsonRpcResponse
.Error
.INTERNAL_ERROR
,
146 Object output
= result
[0] == null ? Map
.of() : result
[0];
147 return objectMapper
.valueToTree(output
);
150 private <T
> void parseParamsAndRunCommand(
152 final ObjectMapper objectMapper
,
153 final TreeNode params
,
154 final OutputWriter outputWriter
,
155 final JsonRpcCommand
<T
> command
156 ) throws CommandException
, JsonMappingException
{
157 T requestParams
= null;
158 final var requestType
= command
.getRequestType();
159 if (params
!= null && requestType
!= null) {
161 requestParams
= objectMapper
.readValue(objectMapper
.treeAsTokens(params
), requestType
);
162 } catch (JsonMappingException e
) {
164 } catch (IOException e
) {
165 throw new AssertionError(e
);
168 command
.handleCommand(requestParams
, m
, outputWriter
);
171 private Thread
receiveMessages(JsonWriter jsonWriter
, Manager m
) {
172 final var thread
= new Thread(() -> {
173 while (!Thread
.interrupted()) {
175 final var receiveMessageHandler
= new JsonReceiveMessageHandler(m
, jsonWriter
);
176 m
.receiveMessages(1, TimeUnit
.HOURS
, false, receiveMessageHandler
);
178 } catch (IOException e
) {
179 logger
.warn("Receiving messages failed, retrying", e
);