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. */
23 : #define CRC_updateShortSentMessage 0x9015e101u
24 : #define CRC_updates TL_updates
25 : #define CRC_updatesCombined TL_updatesCombined
26 : #define CRC_updateShort TL_updateShort
27 : /* updateMessageID#4e90bfd6 id:int random_id:long — maps our random_id to the
28 : * assigned server message id inside the full updates/updatesCombined envelope. */
29 : #define CRC_updateMessageID 0x4e90bfd6u
30 :
31 19 : static int write_input_peer(TlWriter *w, const HistoryPeer *p) {
32 19 : switch (p->kind) {
33 16 : case HISTORY_PEER_SELF:
34 16 : tl_write_uint32(w, TL_inputPeerSelf);
35 16 : return 0;
36 3 : case HISTORY_PEER_USER:
37 3 : tl_write_uint32(w, TL_inputPeerUser);
38 3 : tl_write_int64 (w, p->peer_id);
39 3 : tl_write_int64 (w, p->access_hash);
40 3 : return 0;
41 0 : case HISTORY_PEER_CHAT:
42 0 : tl_write_uint32(w, TL_inputPeerChat);
43 0 : tl_write_int64 (w, p->peer_id);
44 0 : return 0;
45 0 : case HISTORY_PEER_CHANNEL:
46 0 : tl_write_uint32(w, TL_inputPeerChannel);
47 0 : tl_write_int64 (w, p->peer_id);
48 0 : tl_write_int64 (w, p->access_hash);
49 0 : return 0;
50 0 : default:
51 0 : return -1;
52 : }
53 : }
54 :
55 : #define CRC_inputReplyToMessage 0x22c0f6d5u
56 :
57 26 : int domain_send_message_reply(const ApiConfig *cfg,
58 : MtProtoSession *s, Transport *t,
59 : const HistoryPeer *peer,
60 : const char *message,
61 : int32_t reply_to_msg_id,
62 : int32_t *msg_id_out,
63 : RpcError *err) {
64 26 : if (!cfg || !s || !t || !peer || !message) return -1;
65 25 : size_t mlen = strlen(message);
66 25 : if (mlen == 0 || mlen > 4096) {
67 6 : logger_log(LOG_ERROR, "send: message length %zu out of bounds", mlen);
68 6 : return -1;
69 : }
70 :
71 : /* random_id — must be uniformly random 64-bit; kept for updateMessageID
72 : * matching in the response. */
73 19 : uint8_t rand_buf[8] = {0};
74 19 : int64_t random_id = 0;
75 19 : if (crypto_rand_bytes(rand_buf, sizeof(rand_buf)) == 0) {
76 19 : memcpy(&random_id, rand_buf, 8);
77 : } else {
78 : /* Fall back to non-crypto randomness; not security-critical. */
79 0 : random_id = (int64_t)rand() << 32 | (int64_t)rand();
80 : }
81 :
82 19 : TlWriter w; tl_writer_init(&w);
83 19 : tl_write_uint32(&w, CRC_messages_sendMessage);
84 19 : uint32_t flags = (reply_to_msg_id > 0) ? 1u : 0u; /* flags.0 = reply_to */
85 19 : tl_write_uint32(&w, flags);
86 19 : if (write_input_peer(&w, peer) != 0) {
87 0 : tl_writer_free(&w);
88 0 : return -1;
89 : }
90 19 : if (reply_to_msg_id > 0) {
91 : /* inputReplyToMessage#22c0f6d5 flags:# reply_to_msg_id:int
92 : * top_msg_id:flags.0?int
93 : * reply_to_peer_id:flags.1?InputPeer
94 : * quote_text:flags.2?string ... */
95 3 : tl_write_uint32(&w, CRC_inputReplyToMessage);
96 3 : tl_write_uint32(&w, 0); /* inner flags */
97 3 : tl_write_int32 (&w, reply_to_msg_id);
98 : }
99 19 : tl_write_string(&w, message);
100 19 : tl_write_int64 (&w, random_id);
101 :
102 : uint8_t query[8192];
103 19 : if (w.len > sizeof(query)) {
104 0 : tl_writer_free(&w);
105 0 : logger_log(LOG_ERROR, "send: query too large (%zu)", w.len);
106 0 : return -1;
107 : }
108 19 : memcpy(query, w.data, w.len);
109 19 : size_t qlen = w.len;
110 19 : tl_writer_free(&w);
111 :
112 19 : uint8_t resp[8192]; size_t resp_len = 0;
113 19 : if (api_call(cfg, s, t, query, qlen, resp, sizeof(resp), &resp_len) != 0) {
114 0 : logger_log(LOG_ERROR, "send: api_call failed");
115 0 : return -1;
116 : }
117 19 : if (resp_len < 4) return -1;
118 :
119 : uint32_t top;
120 19 : memcpy(&top, resp, 4);
121 19 : if (top == TL_rpc_error) {
122 5 : if (err) rpc_parse_error(resp, resp_len, err);
123 5 : return -1;
124 : }
125 :
126 14 : if (msg_id_out) *msg_id_out = 0;
127 :
128 : /* updateShortSentMessage#9015e101 flags:# out:flags.1?true id:int
129 : * pts:int pts_count:int date:int media:flags.9?MessageMedia
130 : * entities:flags.7?Vector<MessageEntity> ttl_period:flags.25?int */
131 14 : if (top == CRC_updateShortSentMessage) {
132 13 : TlReader r = tl_reader_init(resp, resp_len);
133 13 : tl_read_uint32(&r); /* crc */
134 13 : tl_read_uint32(&r); /* flags */
135 13 : if (tl_reader_ok(&r) && r.len - r.pos >= 4) {
136 13 : int32_t id = tl_read_int32(&r);
137 13 : if (msg_id_out) *msg_id_out = id;
138 : }
139 13 : return 0;
140 : }
141 :
142 : /* For updates / updatesCombined: scan for updateMessageID#4e90bfd6 which
143 : * carries the mapping: random_id → assigned server message id. */
144 1 : if (top == CRC_updates || top == CRC_updatesCombined) {
145 0 : if (msg_id_out && resp_len >= 16) {
146 0 : for (size_t i = 0; i <= resp_len - 16; i++) {
147 : uint32_t crc;
148 0 : memcpy(&crc, resp + i, 4);
149 0 : if (crc != CRC_updateMessageID) continue;
150 : int32_t mid; int64_t rid;
151 0 : memcpy(&mid, resp + i + 4, 4);
152 0 : memcpy(&rid, resp + i + 8, 8);
153 0 : if (rid == random_id) { *msg_id_out = mid; break; }
154 : }
155 : }
156 0 : return 0;
157 : }
158 1 : if (top == CRC_updateShort) {
159 1 : return 0;
160 : }
161 :
162 0 : logger_log(LOG_WARN, "send: unexpected top 0x%08x — assuming success", top);
163 0 : return 0;
164 : }
165 :
166 23 : int domain_send_message(const ApiConfig *cfg,
167 : MtProtoSession *s, Transport *t,
168 : const HistoryPeer *peer,
169 : const char *message,
170 : int32_t *msg_id_out,
171 : RpcError *err) {
172 23 : return domain_send_message_reply(cfg, s, t, peer, message, 0,
173 : msg_id_out, err);
174 : }
|