Line data Source code
1 : /* SPDX-License-Identifier: GPL-3.0-or-later */
2 : /* Copyright 2026 Peter Csaszar */
3 :
4 : /**
5 : * @file domain/write/send.c
6 : * @brief messages.sendMessage — first write-capable domain.
7 : */
8 :
9 : #include "domain/write/send.h"
10 :
11 : #include "tl_serial.h"
12 : #include "tl_registry.h"
13 : #include "mtproto_rpc.h"
14 : #include "crypto.h"
15 : #include "logger.h"
16 :
17 : #include <stdlib.h>
18 : #include <string.h>
19 :
20 : #define CRC_messages_sendMessage 0x0d9d75a4u
21 :
22 : /* messages.Updates constructors we may see in the response. We only need
23 : * to recognise them enough to return success + optionally extract the
24 : * outgoing message id from updateShortSentMessage. */
25 : #define CRC_updateShortSentMessage 0x9015e101u
26 : #define CRC_updates TL_updates
27 : #define CRC_updatesCombined TL_updatesCombined
28 : #define CRC_updateShort TL_updateShort
29 :
30 19 : static int write_input_peer(TlWriter *w, const HistoryPeer *p) {
31 19 : switch (p->kind) {
32 16 : case HISTORY_PEER_SELF:
33 16 : tl_write_uint32(w, TL_inputPeerSelf);
34 16 : return 0;
35 3 : case HISTORY_PEER_USER:
36 3 : tl_write_uint32(w, TL_inputPeerUser);
37 3 : tl_write_int64 (w, p->peer_id);
38 3 : tl_write_int64 (w, p->access_hash);
39 3 : return 0;
40 0 : case HISTORY_PEER_CHAT:
41 0 : tl_write_uint32(w, TL_inputPeerChat);
42 0 : tl_write_int64 (w, p->peer_id);
43 0 : return 0;
44 0 : case HISTORY_PEER_CHANNEL:
45 0 : tl_write_uint32(w, TL_inputPeerChannel);
46 0 : tl_write_int64 (w, p->peer_id);
47 0 : tl_write_int64 (w, p->access_hash);
48 0 : return 0;
49 0 : default:
50 0 : return -1;
51 : }
52 : }
53 :
54 : #define CRC_inputReplyToMessage 0x22c0f6d5u
55 :
56 26 : int domain_send_message_reply(const ApiConfig *cfg,
57 : MtProtoSession *s, Transport *t,
58 : const HistoryPeer *peer,
59 : const char *message,
60 : int32_t reply_to_msg_id,
61 : int32_t *msg_id_out,
62 : RpcError *err) {
63 26 : if (!cfg || !s || !t || !peer || !message) return -1;
64 25 : size_t mlen = strlen(message);
65 25 : if (mlen == 0 || mlen > 4096) {
66 6 : logger_log(LOG_ERROR, "send: message length %zu out of bounds", mlen);
67 6 : return -1;
68 : }
69 :
70 : /* random_id — must be uniformly random 64-bit. */
71 19 : uint8_t rand_buf[8] = {0};
72 19 : int64_t random_id = 0;
73 19 : if (crypto_rand_bytes(rand_buf, sizeof(rand_buf)) == 0) {
74 19 : memcpy(&random_id, rand_buf, 8);
75 : } else {
76 : /* Fall back to non-crypto randomness; not security-critical. */
77 0 : random_id = (int64_t)rand() << 32 | (int64_t)rand();
78 : }
79 :
80 19 : TlWriter w; tl_writer_init(&w);
81 19 : tl_write_uint32(&w, CRC_messages_sendMessage);
82 19 : uint32_t flags = (reply_to_msg_id > 0) ? 1u : 0u; /* flags.0 = reply_to */
83 19 : tl_write_uint32(&w, flags);
84 19 : if (write_input_peer(&w, peer) != 0) {
85 0 : tl_writer_free(&w);
86 0 : return -1;
87 : }
88 19 : if (reply_to_msg_id > 0) {
89 : /* inputReplyToMessage#22c0f6d5 flags:# reply_to_msg_id:int
90 : * top_msg_id:flags.0?int
91 : * reply_to_peer_id:flags.1?InputPeer
92 : * quote_text:flags.2?string ... */
93 3 : tl_write_uint32(&w, CRC_inputReplyToMessage);
94 3 : tl_write_uint32(&w, 0); /* inner flags */
95 3 : tl_write_int32 (&w, reply_to_msg_id);
96 : }
97 19 : tl_write_string(&w, message);
98 19 : tl_write_int64 (&w, random_id);
99 :
100 : uint8_t query[8192];
101 19 : if (w.len > sizeof(query)) {
102 0 : tl_writer_free(&w);
103 0 : logger_log(LOG_ERROR, "send: query too large (%zu)", w.len);
104 0 : return -1;
105 : }
106 19 : memcpy(query, w.data, w.len);
107 19 : size_t qlen = w.len;
108 19 : tl_writer_free(&w);
109 :
110 19 : uint8_t resp[8192]; size_t resp_len = 0;
111 19 : if (api_call(cfg, s, t, query, qlen, resp, sizeof(resp), &resp_len) != 0) {
112 0 : logger_log(LOG_ERROR, "send: api_call failed");
113 0 : return -1;
114 : }
115 19 : if (resp_len < 4) return -1;
116 :
117 : uint32_t top;
118 19 : memcpy(&top, resp, 4);
119 19 : if (top == TL_rpc_error) {
120 5 : if (err) rpc_parse_error(resp, resp_len, err);
121 5 : return -1;
122 : }
123 :
124 14 : if (msg_id_out) *msg_id_out = 0;
125 :
126 : /* updateShortSentMessage#9015e101 flags:# out:flags.1?true id:int
127 : * pts:int pts_count:int date:int media:flags.9?MessageMedia
128 : * entities:flags.7?Vector<MessageEntity> ttl_period:flags.25?int */
129 14 : if (top == CRC_updateShortSentMessage) {
130 13 : TlReader r = tl_reader_init(resp, resp_len);
131 13 : tl_read_uint32(&r); /* crc */
132 13 : tl_read_uint32(&r); /* flags */
133 13 : if (tl_reader_ok(&r) && r.len - r.pos >= 4) {
134 13 : int32_t id = tl_read_int32(&r);
135 13 : if (msg_id_out) *msg_id_out = id;
136 : }
137 13 : return 0;
138 : }
139 :
140 : /* For updates / updatesCombined / updateShort we just accept success.
141 : * Message-id extraction from the full Updates envelope is future work. */
142 1 : if (top == CRC_updates || top == CRC_updatesCombined
143 1 : || top == CRC_updateShort) {
144 1 : return 0;
145 : }
146 :
147 0 : logger_log(LOG_WARN, "send: unexpected top 0x%08x — assuming success", top);
148 0 : return 0;
149 : }
150 :
151 23 : int domain_send_message(const ApiConfig *cfg,
152 : MtProtoSession *s, Transport *t,
153 : const HistoryPeer *peer,
154 : const char *message,
155 : int32_t *msg_id_out,
156 : RpcError *err) {
157 23 : return domain_send_message_reply(cfg, s, t, peer, message, 0,
158 : msg_id_out, err);
159 : }
|