Line data Source code
1 : /* SPDX-License-Identifier: GPL-3.0-or-later */
2 : /* Copyright 2026 Peter Csaszar */
3 :
4 : /**
5 : * @file domain/read/updates.c
6 : * @brief updates.getState / updates.getDifference minimal parsers.
7 : */
8 :
9 : #include "domain/read/updates.h"
10 :
11 : #include "tl_serial.h"
12 : #include "tl_registry.h"
13 : #include "tl_skip.h"
14 : #include "mtproto_rpc.h"
15 : #include "logger.h"
16 : #include "raii.h"
17 :
18 : #include <stdlib.h>
19 : #include <string.h>
20 :
21 : #define CRC_updates_getState 0xedd4882au
22 : #define CRC_updates_getDifference 0x19c2f763u
23 : #define CRC_updates_differenceTooLong 0x4afe8f6du
24 :
25 : /* All trailer flags now have skippers; factcheck (flags2.3) still halts. */
26 :
27 : /* Parse one Message, advance the cursor past it. Populates entry with
28 : * id/out/date/text; sets complex=1 if we had to bail on a trailing
29 : * unsupported flag. Return 0 = advanced cleanly, -1 = cursor is at an
30 : * unknown offset and iteration must stop. */
31 12 : static int parse_message(TlReader *r, HistoryEntry *out) {
32 12 : if (!tl_reader_ok(r)) return -1;
33 12 : uint32_t crc = tl_read_uint32(r);
34 12 : if (crc == TL_messageEmpty) {
35 0 : uint32_t flags = tl_read_uint32(r);
36 0 : out->id = tl_read_int32(r);
37 0 : if (flags & 1u) { tl_read_uint32(r); tl_read_int64(r); }
38 0 : return 0;
39 : }
40 12 : if (crc != TL_message && crc != TL_messageService) return -1;
41 :
42 12 : uint32_t flags = tl_read_uint32(r);
43 12 : uint32_t flags2 = 0;
44 12 : if (crc == TL_message) flags2 = tl_read_uint32(r);
45 12 : out->out = (flags & (1u << 1)) ? 1 : 0;
46 12 : out->id = tl_read_int32(r);
47 12 : if (crc == TL_messageService) {
48 : /* US-29: service messages surface through watch with the
49 : * rendered action in `text`. Delegate to the history parser so
50 : * the action table stays in one place. */
51 2 : return domain_history_parse_service(r, out, flags);
52 : }
53 :
54 10 : if (flags & (1u << 8)) if (tl_skip_peer(r) != 0) { out->complex=1; return -1; }
55 : /* peer_id:Peer — mandatory; extract the numeric id for filtering. */
56 : {
57 10 : if (r->len - r->pos < 12) { out->complex = 1; return -1; }
58 10 : uint32_t pcrc = tl_read_uint32(r);
59 10 : int64_t pid = tl_read_int64(r);
60 10 : switch (pcrc) {
61 10 : case TL_peerUser: case TL_peerChat: case TL_peerChannel:
62 10 : out->peer_id = pid;
63 10 : break;
64 0 : default:
65 0 : out->complex = 1; return -1;
66 : }
67 : }
68 10 : if (flags & (1u << 28)) if (tl_skip_peer(r) != 0) { out->complex=1; return -1; }
69 10 : if (flags & (1u << 2)) if (tl_skip_message_fwd_header(r) != 0) { out->complex=1; return -1; }
70 10 : if (flags & (1u << 11)) { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
71 10 : if (flags2 & (1u << 0)) { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
72 10 : if (flags & (1u << 3)) if (tl_skip_message_reply_header(r) != 0) { out->complex=1; return -1; }
73 10 : if (r->len - r->pos < 4) { out->complex=1; return -1; }
74 10 : out->date = (int64_t)(int32_t)tl_read_int32(r);
75 :
76 10 : char *msg = tl_read_string(r);
77 10 : if (msg) {
78 10 : size_t n = strlen(msg);
79 10 : if (n >= HISTORY_TEXT_MAX) { n = HISTORY_TEXT_MAX - 1; out->truncated = 1; }
80 10 : memcpy(out->text, msg, n);
81 10 : out->text[n] = '\0';
82 10 : free(msg);
83 : }
84 :
85 10 : if (flags & (1u << 9)) {
86 0 : MediaInfo mi = {0};
87 0 : if (tl_skip_message_media_ex(r, &mi) != 0) {
88 0 : out->media = mi.kind;
89 0 : out->complex = 1;
90 0 : return -1;
91 : }
92 0 : out->media = mi.kind;
93 0 : out->media_id = (mi.kind == MEDIA_PHOTO) ? mi.photo_id
94 0 : : (mi.kind == MEDIA_DOCUMENT) ? mi.document_id : 0;
95 0 : out->media_dc = mi.dc_id;
96 : }
97 10 : if (flags & (1u << 6)) if (tl_skip_reply_markup(r) != 0) { out->complex=1; return -1; }
98 10 : if (flags & (1u << 7)) if (tl_skip_message_entities_vector(r) != 0) { out->complex=1; return -1; }
99 10 : if (flags & (1u << 10)) { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int32(r); tl_read_int32(r); }
100 10 : if (flags & (1u << 23)) if (tl_skip_message_replies(r) != 0) { out->complex=1; return -1; }
101 10 : if (flags & (1u << 15)) { if (r->len - r->pos < 4) { out->complex=1; return -1; } tl_read_int32(r); }
102 10 : if (flags & (1u << 16)) if (tl_skip_string(r) != 0) { out->complex=1; return -1; }
103 10 : if (flags & (1u << 17)) { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
104 10 : if (flags & (1u << 20)) if (tl_skip_message_reactions(r) != 0) { out->complex=1; return -1; }
105 10 : if (flags & (1u << 22)) if (tl_skip_restriction_reason_vector(r) != 0) { out->complex=1; return -1; }
106 10 : if (flags & (1u << 25)) { if (r->len - r->pos < 4) { out->complex=1; return -1; } tl_read_int32(r); }
107 10 : if (flags2 & (1u << 30)) { if (r->len - r->pos < 4) { out->complex=1; return -1; } tl_read_int32(r); }
108 10 : if (flags2 & (1u << 2)) { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
109 10 : if (flags2 & (1u << 3)) if (tl_skip_factcheck(r) != 0) { out->complex=1; return -1; }
110 10 : return 0;
111 : }
112 :
113 10 : static int send_trivial(const ApiConfig *cfg,
114 : MtProtoSession *s, Transport *t,
115 : uint32_t method_crc,
116 : uint8_t *resp, size_t resp_cap, size_t *resp_len) {
117 : TlWriter w;
118 10 : tl_writer_init(&w);
119 10 : tl_write_uint32(&w, method_crc);
120 : uint8_t query[32];
121 10 : if (w.len > sizeof(query)) { tl_writer_free(&w); return -1; }
122 10 : memcpy(query, w.data, w.len);
123 10 : size_t qlen = w.len;
124 10 : tl_writer_free(&w);
125 :
126 10 : if (api_call(cfg, s, t, query, qlen, resp, resp_cap, resp_len) != 0) {
127 0 : logger_log(LOG_ERROR, "updates: api_call failed");
128 0 : return -1;
129 : }
130 10 : return 0;
131 : }
132 :
133 5 : static int parse_state(TlReader *r, UpdatesState *out) {
134 5 : uint32_t crc = tl_read_uint32(r);
135 5 : if (crc != TL_updates_state) {
136 0 : logger_log(LOG_ERROR, "updates: not updates.state (0x%08x)", crc);
137 0 : return -1;
138 : }
139 5 : out->pts = tl_read_int32(r);
140 5 : out->qts = tl_read_int32(r);
141 5 : out->date = (int64_t)(int32_t)tl_read_int32(r);
142 5 : out->seq = tl_read_int32(r);
143 5 : out->unread_count = tl_read_int32(r);
144 5 : return 0;
145 : }
146 :
147 11 : int domain_updates_state(const ApiConfig *cfg,
148 : MtProtoSession *s, Transport *t,
149 : UpdatesState *out) {
150 11 : if (!cfg || !s || !t || !out) return -1;
151 10 : memset(out, 0, sizeof(*out));
152 :
153 10 : RAII_STRING uint8_t *resp = (uint8_t *)malloc(4096);
154 10 : if (!resp) return -1;
155 10 : size_t resp_len = 0;
156 10 : if (send_trivial(cfg, s, t, CRC_updates_getState,
157 0 : resp, 4096, &resp_len) != 0) return -1;
158 10 : if (resp_len < 4) return -1;
159 :
160 : uint32_t top;
161 10 : memcpy(&top, resp, 4);
162 10 : if (top == TL_rpc_error) {
163 5 : RpcError err; rpc_parse_error(resp, resp_len, &err);
164 5 : logger_log(LOG_ERROR, "updates.getState RPC error %d: %s",
165 : err.error_code, err.error_msg);
166 5 : return -1;
167 : }
168 :
169 5 : TlReader r = tl_reader_init(resp, resp_len);
170 5 : return parse_state(&r, out);
171 : }
172 :
173 16 : int domain_updates_difference(const ApiConfig *cfg,
174 : MtProtoSession *s, Transport *t,
175 : const UpdatesState *in,
176 : UpdatesDifference *out) {
177 16 : if (!cfg || !s || !t || !in || !out) return -1;
178 15 : memset(out, 0, sizeof(*out));
179 15 : out->next_state = *in;
180 :
181 : /* Build getDifference(flags=0, pts, date, qts). */
182 15 : TlWriter w; tl_writer_init(&w);
183 15 : tl_write_uint32(&w, CRC_updates_getDifference);
184 15 : tl_write_uint32(&w, 0); /* flags = 0 (no pts_total_limit) */
185 15 : tl_write_int32 (&w, in->pts);
186 15 : tl_write_int32 (&w, (int32_t)in->date); /* wire is still int32 per TL schema */
187 15 : tl_write_int32 (&w, in->qts);
188 :
189 : uint8_t query[64];
190 15 : if (w.len > sizeof(query)) { tl_writer_free(&w); return -1; }
191 15 : memcpy(query, w.data, w.len);
192 15 : size_t qlen = w.len;
193 15 : tl_writer_free(&w);
194 :
195 15 : RAII_STRING uint8_t *resp = (uint8_t *)malloc(262144);
196 15 : if (!resp) return -1;
197 15 : size_t resp_len = 0;
198 15 : if (api_call(cfg, s, t, query, qlen, resp, 262144, &resp_len) != 0) return -1;
199 15 : if (resp_len < 4) return -1;
200 :
201 : uint32_t top;
202 15 : memcpy(&top, resp, 4);
203 15 : if (top == TL_rpc_error) {
204 2 : RpcError err; rpc_parse_error(resp, resp_len, &err);
205 2 : logger_log(LOG_ERROR, "updates.getDifference RPC error %d: %s",
206 : err.error_code, err.error_msg);
207 2 : return -1;
208 : }
209 :
210 13 : TlReader r = tl_reader_init(resp, resp_len);
211 13 : uint32_t crc = tl_read_uint32(&r);
212 :
213 13 : if (crc == TL_updates_differenceEmpty) {
214 5 : out->is_empty = 1;
215 : /* differenceEmpty: date:int seq:int */
216 5 : out->next_state.date = (int64_t)(int32_t)tl_read_int32(&r);
217 5 : out->next_state.seq = tl_read_int32(&r);
218 5 : return 0;
219 : }
220 8 : if (crc == CRC_updates_differenceTooLong) {
221 0 : out->is_too_long = 1;
222 : /* Next call must reseed via updates.getState. */
223 0 : out->next_state.pts = tl_read_int32(&r);
224 0 : return 0;
225 : }
226 8 : if (crc != TL_updates_difference && crc != TL_updates_differenceSlice) {
227 0 : logger_log(LOG_ERROR, "updates: unexpected Difference 0x%08x", crc);
228 0 : return -1;
229 : }
230 :
231 : /* difference / differenceSlice both start with:
232 : * new_messages:Vector<Message>
233 : * new_encrypted_messages:Vector<EncryptedMessage>
234 : * other_updates:Vector<Update>
235 : * chats:Vector<Chat>
236 : * users:Vector<User>
237 : * state:updates.State (only for plain difference)
238 : * intermediate_state:updates.State (only for differenceSlice)
239 : *
240 : * We only read the top-level vector counts so we can report
241 : * activity. Actual message/update bodies need a per-element TL
242 : * walker which is deferred to v2.
243 : */
244 : /* new_messages: Vector<Message> */
245 8 : uint32_t vec = tl_read_uint32(&r);
246 8 : if (vec != TL_vector) {
247 0 : logger_log(LOG_ERROR, "updates: expected Vector, got 0x%08x", vec);
248 0 : return -1;
249 : }
250 8 : uint32_t count = tl_read_uint32(&r);
251 8 : int32_t written = 0;
252 20 : for (uint32_t i = 0; i < count && written < UPDATES_MAX_MESSAGES; i++) {
253 12 : HistoryEntry e = {0};
254 12 : int rc = parse_message(&r, &e);
255 12 : if (e.id != 0 || e.text[0] != '\0') {
256 12 : out->new_messages[written++] = e;
257 : }
258 12 : if (rc != 0) break; /* iteration stopped */
259 : }
260 8 : out->new_messages_count = written;
261 8 : out->next_state = *in;
262 8 : return 0;
263 : }
|