Line data Source code
1 : /* SPDX-License-Identifier: GPL-3.0-or-later */
2 : /* Copyright 2026 Peter Csaszar */
3 :
4 : /**
5 : * @file domain/read/history.c
6 : * @brief Minimal messages.getHistory parser (US-06 v1).
7 : */
8 :
9 : #include "domain/read/history.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_messages_getHistory 0x4423e6c5u
22 : #define CRC_inputPeerSelf_local TL_inputPeerSelf /* alias for readability */
23 :
24 : /* ---- MessageAction CRCs (US-29) ----
25 : * Stable across recent TL layers (170+). See docs/userstory/US-29 for
26 : * the rendering policy. Unknown action CRCs fall through to a labelled
27 : * "[service action 0x%08x]" string to keep group histories dense even
28 : * when Telegram adds new variants. */
29 : #define CRC_messageActionEmpty 0xb6aef7b0u
30 : #define CRC_messageActionChatCreate 0xbd47cbadu
31 : #define CRC_messageActionChatEditTitle 0xb5a1ce5au
32 : #define CRC_messageActionChatEditPhoto 0x7fcb13a8u
33 : #define CRC_messageActionChatDeletePhoto 0x95e3fbefu
34 : #define CRC_messageActionChatAddUser 0x15cefd00u
35 : #define CRC_messageActionChatDeleteUser 0xa43f30ccu
36 : #define CRC_messageActionChatJoinedByLink 0x031224c3u
37 : #define CRC_messageActionChannelCreate 0x95d2ac92u
38 : #define CRC_messageActionChatMigrateTo 0xe1037f92u
39 : #define CRC_messageActionChannelMigrateFrom 0xea3948e9u
40 : #define CRC_messageActionPinMessage 0x94bd38edu
41 : #define CRC_messageActionHistoryClear 0x9fbab604u
42 : #define CRC_messageActionPhoneCall 0x80e11a7fu
43 : #define CRC_messageActionScreenshotTaken 0x4792929bu
44 : #define CRC_messageActionCustomAction 0xfae69f56u
45 : #define CRC_messageActionGroupCall 0x7a0d7f42u
46 : #define CRC_messageActionGroupCallScheduled 0xb3a07661u
47 : #define CRC_messageActionInviteToGroupCall 0x502f92f7u
48 :
49 : /* Phone-call discard reason CRCs (flags.0 on PhoneCall). */
50 : #define CRC_phoneCallDiscardReasonMissed 0x85e42301u
51 : #define CRC_phoneCallDiscardReasonDisconnect 0xe095c1a0u
52 : #define CRC_phoneCallDiscardReasonHangup 0x57adc690u
53 : #define CRC_phoneCallDiscardReasonBusy 0xfaf7e8c9u
54 :
55 64 : static int write_input_peer(TlWriter *w, const HistoryPeer *p) {
56 64 : switch (p->kind) {
57 56 : case HISTORY_PEER_SELF:
58 56 : tl_write_uint32(w, TL_inputPeerSelf);
59 56 : return 0;
60 4 : case HISTORY_PEER_USER:
61 4 : tl_write_uint32(w, TL_inputPeerUser);
62 4 : tl_write_int64 (w, p->peer_id);
63 4 : tl_write_int64 (w, p->access_hash);
64 4 : return 0;
65 0 : case HISTORY_PEER_CHAT:
66 0 : tl_write_uint32(w, TL_inputPeerChat);
67 0 : tl_write_int64 (w, p->peer_id);
68 0 : return 0;
69 4 : case HISTORY_PEER_CHANNEL:
70 4 : tl_write_uint32(w, TL_inputPeerChannel);
71 4 : tl_write_int64 (w, p->peer_id);
72 4 : tl_write_int64 (w, p->access_hash);
73 4 : return 0;
74 0 : default:
75 0 : return -1;
76 : }
77 : }
78 :
79 : /* Field bit positions used to decide whether to skip the first-stage
80 : * Message prefix (before we abort and move on to the next message).
81 : * These correspond to layer 170+ but are stable across recent layers. */
82 : #define MSG_FLAG_OUT (1u << 1)
83 : #define MSG_FLAG_HAS_FROM_ID (1u << 8)
84 :
85 : /* All trailer flags now have skippers; only factcheck (flags2.3)
86 : * still halts iteration. */
87 :
88 64 : static int build_request(const HistoryPeer *peer, int32_t offset_id, int limit,
89 : uint8_t *buf, size_t cap, size_t *out_len) {
90 : TlWriter w;
91 64 : tl_writer_init(&w);
92 64 : tl_write_uint32(&w, CRC_messages_getHistory);
93 64 : if (write_input_peer(&w, peer) != 0) {
94 0 : tl_writer_free(&w);
95 0 : return -1;
96 : }
97 64 : tl_write_int32 (&w, offset_id);
98 64 : tl_write_int32 (&w, 0); /* offset_date */
99 64 : tl_write_int32 (&w, 0); /* add_offset */
100 64 : tl_write_int32 (&w, limit);
101 64 : tl_write_int32 (&w, 0); /* max_id */
102 64 : tl_write_int32 (&w, 0); /* min_id */
103 64 : tl_write_int64 (&w, 0); /* hash */
104 :
105 64 : int rc = -1;
106 64 : if (w.len <= cap) {
107 64 : memcpy(buf, w.data, w.len);
108 64 : *out_len = w.len;
109 64 : rc = 0;
110 : }
111 64 : tl_writer_free(&w);
112 64 : return rc;
113 : }
114 :
115 : /* Copy src into out->text with HISTORY_TEXT_MAX truncation. */
116 22 : static void set_text(HistoryEntry *out, const char *src) {
117 22 : if (!src) { out->text[0] = '\0'; return; }
118 22 : size_t n = strlen(src);
119 22 : if (n >= HISTORY_TEXT_MAX) { n = HISTORY_TEXT_MAX - 1; out->truncated = 1; }
120 22 : memcpy(out->text, src, n);
121 22 : out->text[n] = '\0';
122 : }
123 :
124 : /* Read and discard a Vector<long>. Returns 0 on success. */
125 2 : static int skip_long_vector(TlReader *r) {
126 2 : if (r->len - r->pos < 8) return -1;
127 2 : if (tl_read_uint32(r) != TL_vector) return -1;
128 2 : uint32_t n = tl_read_uint32(r);
129 2 : if (n > 4096) return -1; /* sanity cap */
130 2 : if (r->len - r->pos < (size_t)n * 8) return -1;
131 5 : for (uint32_t i = 0; i < n; i++) tl_read_int64(r);
132 2 : return 0;
133 : }
134 :
135 : /* Discard the remaining Vector<long>; return the first element (or 0). */
136 1 : static int read_long_vector_first(TlReader *r, int64_t *first) {
137 1 : if (r->len - r->pos < 8) return -1;
138 1 : if (tl_read_uint32(r) != TL_vector) return -1;
139 1 : uint32_t n = tl_read_uint32(r);
140 1 : if (n > 4096) return -1;
141 1 : if (r->len - r->pos < (size_t)n * 8) return -1;
142 1 : if (first) *first = 0;
143 2 : for (uint32_t i = 0; i < n; i++) {
144 1 : int64_t v = tl_read_int64(r);
145 1 : if (i == 0 && first) *first = v;
146 : }
147 1 : return 0;
148 : }
149 :
150 : /* Read a MessageAction and render a human-readable string into out->text.
151 : * Sets out->is_service=1 in all cases. Returns 0 if the action was fully
152 : * consumed (cursor past), -1 on truncation / unknown substructure. */
153 22 : static int parse_service_action(TlReader *r, HistoryEntry *out) {
154 22 : out->is_service = 1;
155 22 : if (r->len - r->pos < 4) { set_text(out, "[service action truncated]"); return -1; }
156 22 : uint32_t acrc = tl_read_uint32(r);
157 : char buf[HISTORY_TEXT_MAX];
158 :
159 22 : switch (acrc) {
160 1 : case CRC_messageActionEmpty:
161 1 : set_text(out, "");
162 1 : return 0;
163 1 : case CRC_messageActionChatCreate: {
164 : /* title:string users:Vector<long> */
165 2 : RAII_STRING char *title = tl_read_string(r);
166 1 : if (!title) return -1;
167 1 : if (skip_long_vector(r) != 0) return -1;
168 1 : snprintf(buf, sizeof(buf), "created group '%s'", title);
169 1 : set_text(out, buf);
170 1 : return 0;
171 : }
172 1 : case CRC_messageActionChatEditTitle: {
173 : /* title:string */
174 2 : RAII_STRING char *title = tl_read_string(r);
175 1 : if (!title) return -1;
176 1 : snprintf(buf, sizeof(buf), "changed title to '%s'", title);
177 1 : set_text(out, buf);
178 1 : return 0;
179 : }
180 1 : case CRC_messageActionChatEditPhoto: {
181 : /* photo:Photo — skip via tl_skip_photo */
182 1 : if (tl_skip_photo(r) != 0) { set_text(out, "changed group photo"); return -1; }
183 1 : set_text(out, "changed group photo");
184 1 : return 0;
185 : }
186 1 : case CRC_messageActionChatDeletePhoto:
187 1 : set_text(out, "removed group photo");
188 1 : return 0;
189 1 : case CRC_messageActionChatAddUser: {
190 : /* users:Vector<long> */
191 1 : int64_t first = 0;
192 1 : if (read_long_vector_first(r, &first) != 0) return -1;
193 1 : snprintf(buf, sizeof(buf), "added @%lld", (long long)first);
194 1 : set_text(out, buf);
195 1 : return 0;
196 : }
197 1 : case CRC_messageActionChatDeleteUser: {
198 : /* user_id:long */
199 1 : if (r->len - r->pos < 8) return -1;
200 1 : int64_t uid = tl_read_int64(r);
201 1 : snprintf(buf, sizeof(buf), "removed @%lld", (long long)uid);
202 1 : set_text(out, buf);
203 1 : return 0;
204 : }
205 1 : case CRC_messageActionChatJoinedByLink: {
206 : /* inviter_id:long */
207 1 : if (r->len - r->pos < 8) return -1;
208 1 : (void)tl_read_int64(r);
209 1 : set_text(out, "joined via invite link");
210 1 : return 0;
211 : }
212 1 : case CRC_messageActionChannelCreate: {
213 : /* title:string */
214 2 : RAII_STRING char *title = tl_read_string(r);
215 1 : if (!title) return -1;
216 1 : snprintf(buf, sizeof(buf), "created channel '%s'", title);
217 1 : set_text(out, buf);
218 1 : return 0;
219 : }
220 1 : case CRC_messageActionChatMigrateTo: {
221 : /* channel_id:long */
222 1 : if (r->len - r->pos < 8) return -1;
223 1 : int64_t cid = tl_read_int64(r);
224 1 : snprintf(buf, sizeof(buf), "migrated to channel %lld", (long long)cid);
225 1 : set_text(out, buf);
226 1 : return 0;
227 : }
228 1 : case CRC_messageActionChannelMigrateFrom: {
229 : /* title:string chat_id:long */
230 2 : RAII_STRING char *title = tl_read_string(r);
231 1 : if (!title) return -1;
232 1 : if (r->len - r->pos < 8) return -1;
233 1 : int64_t cid = tl_read_int64(r);
234 1 : snprintf(buf, sizeof(buf), "migrated from group %lld '%s'",
235 : (long long)cid, title);
236 1 : set_text(out, buf);
237 1 : return 0;
238 : }
239 2 : case CRC_messageActionPinMessage:
240 : /* Pinned message id is carried on the outer reply_to header, not
241 : * on the action itself. The outer parser captured it already. */
242 2 : if (out->text[0] == '\0') set_text(out, "pinned message");
243 2 : return 0;
244 1 : case CRC_messageActionHistoryClear:
245 1 : set_text(out, "history cleared");
246 1 : return 0;
247 2 : case CRC_messageActionPhoneCall: {
248 : /* flags:# video:flags.2?true call_id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int */
249 2 : if (r->len - r->pos < 4 + 8) return -1;
250 2 : uint32_t pflags = tl_read_uint32(r);
251 2 : (void)tl_read_int64(r); /* call_id */
252 2 : const char *reason = "completed";
253 2 : if (pflags & 1u) {
254 2 : if (r->len - r->pos < 4) return -1;
255 2 : uint32_t rcrc = tl_read_uint32(r);
256 2 : switch (rcrc) {
257 1 : case CRC_phoneCallDiscardReasonMissed: reason = "missed"; break;
258 0 : case CRC_phoneCallDiscardReasonDisconnect: reason = "disconnected"; break;
259 1 : case CRC_phoneCallDiscardReasonHangup: reason = "hangup"; break;
260 0 : case CRC_phoneCallDiscardReasonBusy: reason = "busy"; break;
261 0 : default: break;
262 : }
263 : }
264 2 : int duration = 0;
265 2 : if (pflags & (1u << 1)) {
266 1 : if (r->len - r->pos < 4) return -1;
267 1 : duration = tl_read_int32(r);
268 : }
269 2 : snprintf(buf, sizeof(buf), "called (duration %ds, %s)", duration, reason);
270 2 : set_text(out, buf);
271 2 : return 0;
272 : }
273 1 : case CRC_messageActionScreenshotTaken:
274 1 : set_text(out, "took screenshot");
275 1 : return 0;
276 1 : case CRC_messageActionCustomAction: {
277 : /* message:string — the raw action label chosen by the server. */
278 2 : RAII_STRING char *msg = tl_read_string(r);
279 1 : if (!msg) return -1;
280 1 : set_text(out, msg);
281 1 : return 0;
282 : }
283 1 : case CRC_messageActionGroupCall: {
284 : /* flags:# call:InputGroupCall duration:flags.0?int
285 : * inputGroupCall#d8aa840f id:long access_hash:long */
286 1 : if (r->len - r->pos < 4) return -1;
287 1 : uint32_t gflags = tl_read_uint32(r);
288 1 : if (r->len - r->pos < 4 + 16) return -1;
289 1 : tl_read_uint32(r); /* inputGroupCall crc */
290 1 : tl_read_int64(r); /* id */
291 1 : tl_read_int64(r); /* access_hash */
292 1 : if (gflags & 1u) {
293 0 : if (r->len - r->pos < 4) return -1;
294 0 : tl_read_int32(r); /* duration */
295 : }
296 1 : set_text(out, "started video chat");
297 1 : return 0;
298 : }
299 1 : case CRC_messageActionGroupCallScheduled: {
300 : /* call:InputGroupCall schedule_date:int */
301 1 : if (r->len - r->pos < 4 + 16 + 4) return -1;
302 1 : tl_read_uint32(r); tl_read_int64(r); tl_read_int64(r);
303 1 : int sched = tl_read_int32(r);
304 1 : snprintf(buf, sizeof(buf), "scheduled video chat for %d", sched);
305 1 : set_text(out, buf);
306 1 : return 0;
307 : }
308 1 : case CRC_messageActionInviteToGroupCall: {
309 : /* call:InputGroupCall users:Vector<long> */
310 1 : if (r->len - r->pos < 4 + 16) return -1;
311 1 : tl_read_uint32(r); tl_read_int64(r); tl_read_int64(r);
312 1 : if (skip_long_vector(r) != 0) return -1;
313 1 : set_text(out, "invited to video chat");
314 1 : return 0;
315 : }
316 1 : default:
317 1 : snprintf(buf, sizeof(buf), "[service action 0x%08x]", acrc);
318 1 : set_text(out, buf);
319 : /* Unknown trailing data: we cannot advance reliably, but the
320 : * enclosing vector iterator will stop on rc=-1 from parse_message.
321 : * Return 0 so the entry is emitted with its label. */
322 1 : return 0;
323 : }
324 : }
325 :
326 : /* Parse the messageService layout and render the action into out->text.
327 : * Cursor is positioned just after (flags, id) on entry — messageService
328 : * does not carry flags2 on the current TL schema. Shared with updates.c
329 : * and search.c via `domain_history_parse_service` (see header). */
330 22 : int domain_history_parse_service(TlReader *r, HistoryEntry *out, uint32_t flags) {
331 22 : out->is_service = 1;
332 22 : if (flags & MSG_FLAG_HAS_FROM_ID) {
333 0 : if (tl_skip_peer(r) != 0) { out->complex = 1; return -1; }
334 : }
335 22 : if (tl_skip_peer(r) != 0) { out->complex = 1; return -1; }
336 22 : if (flags & (1u << 3)) { /* reply_to — for pinMessage the target id lives here. */
337 : /* Peek the first 3 words (crc + flags + first optional) without
338 : * advancing: messageReplyHeader#afbc09db always has flags bit 4 =
339 : * reply_to_msg_id as the first int32 payload when present. */
340 2 : if (r->len - r->pos >= 12) {
341 : uint32_t rh_crc, rh_flags;
342 2 : memcpy(&rh_crc, r->data + r->pos, 4);
343 2 : memcpy(&rh_flags, r->data + r->pos + 4, 4);
344 2 : if (rh_crc == 0xafbc09dbu && (rh_flags & (1u << 4))) {
345 : int32_t pinned;
346 2 : memcpy(&pinned, r->data + r->pos + 8, 4);
347 : char buf[HISTORY_TEXT_MAX];
348 2 : snprintf(buf, sizeof(buf), "pinned message %d", pinned);
349 2 : set_text(out, buf);
350 : }
351 : }
352 2 : if (tl_skip_message_reply_header(r) != 0) { out->complex = 1; return -1; }
353 : }
354 22 : if (r->len - r->pos < 4) { out->complex = 1; return -1; }
355 22 : out->date = (int64_t)(int32_t)tl_read_int32(r);
356 : /* Preserve any pre-populated text (e.g. "pinned message N") across
357 : * parse_service_action for the PinMessage case, which has no body. */
358 : char saved_text[HISTORY_TEXT_MAX];
359 22 : memcpy(saved_text, out->text, sizeof(saved_text));
360 22 : int rc = parse_service_action(r, out);
361 22 : if (out->text[0] == '\0' && saved_text[0] != '\0')
362 0 : memcpy(out->text, saved_text, sizeof(saved_text));
363 : /* ttl_period (flags.25) — optional. Best-effort skip. */
364 22 : if (rc == 0 && (flags & (1u << 25))) {
365 0 : if (r->len - r->pos >= 4) tl_read_int32(r);
366 : }
367 22 : return rc;
368 : }
369 :
370 : /* Parse a Message. On success advance r past the whole object so the
371 : * caller can read the next vector element. Returns 0 on success, -1 on
372 : * parse failure, and sets `out->complex = 1` when we got the text/date
373 : * but had to bail before reaching the end of the object. */
374 1044 : static int parse_message(TlReader *r, HistoryEntry *out) {
375 1044 : if (!tl_reader_ok(r)) return -1;
376 1044 : uint32_t crc = tl_read_uint32(r);
377 :
378 1044 : if (crc == TL_messageEmpty) {
379 1 : uint32_t flags = tl_read_uint32(r);
380 1 : out->id = tl_read_int32(r);
381 1 : if (flags & 1u) { tl_read_uint32(r); tl_read_int64(r); }
382 1 : return 0;
383 : }
384 :
385 1043 : if (crc != TL_message && crc != TL_messageService) {
386 0 : logger_log(LOG_WARN, "history: unknown Message 0x%08x", crc);
387 0 : return -1;
388 : }
389 :
390 1043 : uint32_t flags = tl_read_uint32(r);
391 1043 : uint32_t flags2 = 0;
392 : /* TL_message carries flags2; TL_messageService does not on the upstream
393 : * schema. Tests and the in-process mock server mirror this layout. */
394 1043 : if (crc == TL_message) flags2 = tl_read_uint32(r);
395 1043 : out->out = (flags & MSG_FLAG_OUT) ? 1 : 0;
396 1043 : out->id = tl_read_int32(r);
397 :
398 1043 : if (crc == TL_messageService) {
399 : /* US-29 — render the action into out->text and return it as a
400 : * normal row. The enclosing vector iterator continues. */
401 21 : return domain_history_parse_service(r, out, flags);
402 : }
403 :
404 : /* Pre-text fields (message is read AFTER these). */
405 1022 : if (flags & MSG_FLAG_HAS_FROM_ID) {
406 0 : if (tl_skip_peer(r) != 0) { out->complex = 1; return -1; }
407 : }
408 1022 : if (tl_skip_peer(r) != 0) { out->complex = 1; return -1; }
409 1022 : if (flags & (1u << 28)) { /* saved_peer_id (layer 185+) */
410 1 : if (tl_skip_peer(r) != 0) { out->complex = 1; return -1; }
411 : }
412 1022 : if (flags & (1u << 2)) { /* fwd_from */
413 2 : if (tl_skip_message_fwd_header(r) != 0) { out->complex = 1; return -1; }
414 : }
415 1022 : if (flags & (1u << 11)) { /* via_bot_id */
416 3 : if (r->len - r->pos < 8) { out->complex = 1; return -1; }
417 3 : tl_read_int64(r);
418 : }
419 1022 : if (flags2 & (1u << 0)) { /* via_business_bot_id */
420 1 : if (r->len - r->pos < 8) { out->complex = 1; return -1; }
421 1 : tl_read_int64(r);
422 : }
423 1022 : if (flags & (1u << 3)) { /* reply_to */
424 2 : if (tl_skip_message_reply_header(r) != 0) { out->complex = 1; return -1; }
425 : }
426 1022 : if (r->len - r->pos < 4) { out->complex = 1; return -1; }
427 1022 : out->date = (int64_t)(int32_t)tl_read_int32(r);
428 :
429 : /* message:string — always present. */
430 2044 : RAII_STRING char *msg = tl_read_string(r);
431 1022 : if (msg) {
432 1022 : size_t n = strlen(msg);
433 1022 : if (n >= HISTORY_TEXT_MAX) { n = HISTORY_TEXT_MAX - 1; out->truncated = 1; }
434 1022 : memcpy(out->text, msg, n);
435 1022 : out->text[n] = '\0';
436 : }
437 :
438 : /* Skippable optionals after `message` — in schema order:
439 : * flags.9 media
440 : * flags.6 reply_markup (BAIL — no skipper yet)
441 : * flags.7 entities
442 : * flags.10 views + forwards
443 : * ... (more scalars below)
444 : *
445 : * Media is attempted first; if it fails the Message is left
446 : * mid-parse but we have at least captured id/date/text. */
447 1022 : if (flags & (1u << 9)) {
448 12 : MediaInfo mi = {0};
449 12 : if (tl_skip_message_media_ex(r, &mi) != 0) {
450 0 : out->media = mi.kind;
451 0 : out->complex = 1;
452 0 : return -1;
453 : }
454 12 : out->media = mi.kind;
455 24 : out->media_id = (mi.kind == MEDIA_PHOTO) ? mi.photo_id
456 12 : : (mi.kind == MEDIA_DOCUMENT) ? mi.document_id : 0;
457 12 : out->media_dc = mi.dc_id;
458 12 : out->media_info = mi;
459 : }
460 :
461 : /* Per-layer order: media → reply_markup → entities → views/forwards
462 : * → replies → edit_date → post_author → grouped_id → reactions →
463 : * restriction_reason → ttl_period → ... replies + restriction_reason
464 : * still don't have skippers. */
465 1022 : if (flags & (1u << 6)) { /* reply_markup */
466 0 : if (tl_skip_reply_markup(r) != 0) { out->complex = 1; return -1; }
467 : }
468 1022 : if (flags & (1u << 7)) { /* entities */
469 0 : if (tl_skip_message_entities_vector(r) != 0) { out->complex = 1; return -1; }
470 : }
471 1022 : if (flags & (1u << 10)) { /* views + forwards */
472 0 : if (r->len - r->pos < 8) { out->complex = 1; return -1; }
473 0 : tl_read_int32(r); tl_read_int32(r);
474 : }
475 1022 : if (flags & (1u << 23)) { /* replies */
476 0 : if (tl_skip_message_replies(r) != 0) { out->complex = 1; return -1; }
477 : }
478 1022 : if (flags & (1u << 15)) { /* edit_date */
479 0 : if (r->len - r->pos < 4) { out->complex = 1; return -1; }
480 0 : tl_read_int32(r);
481 : }
482 1022 : if (flags & (1u << 16)) { /* post_author */
483 0 : if (tl_skip_string(r) != 0) { out->complex = 1; return -1; }
484 : }
485 1022 : if (flags & (1u << 17)) { /* grouped_id */
486 0 : if (r->len - r->pos < 8) { out->complex = 1; return -1; }
487 0 : tl_read_int64(r);
488 : }
489 1022 : if (flags & (1u << 20)) { /* reactions */
490 0 : if (tl_skip_message_reactions(r) != 0) { out->complex = 1; return -1; }
491 : }
492 1022 : if (flags & (1u << 22)) { /* restriction_reason */
493 0 : if (tl_skip_restriction_reason_vector(r) != 0) {
494 0 : out->complex = 1; return -1;
495 : }
496 : }
497 1022 : if (flags & (1u << 25)) { /* ttl_period */
498 0 : if (r->len - r->pos < 4) { out->complex = 1; return -1; }
499 0 : tl_read_int32(r);
500 : }
501 1022 : if (flags2 & (1u << 30)) { /* quick_reply_shortcut_id */
502 0 : if (r->len - r->pos < 4) { out->complex = 1; return -1; }
503 0 : tl_read_int32(r);
504 : }
505 1022 : if (flags2 & (1u << 2)) { /* effect */
506 0 : if (r->len - r->pos < 8) { out->complex = 1; return -1; }
507 0 : tl_read_int64(r);
508 : }
509 1022 : if (flags2 & (1u << 3)) { /* factcheck */
510 0 : if (tl_skip_factcheck(r) != 0) { out->complex = 1; return -1; }
511 : }
512 1022 : return 0;
513 : }
514 :
515 64 : int domain_get_history(const ApiConfig *cfg,
516 : MtProtoSession *s, Transport *t,
517 : const HistoryPeer *peer,
518 : int32_t offset_id, int limit,
519 : HistoryEntry *out, int *out_count) {
520 64 : if (!cfg || !s || !t || !peer || !out || !out_count || limit <= 0) return -1;
521 64 : *out_count = 0;
522 :
523 : uint8_t query[128];
524 64 : size_t qlen = 0;
525 64 : if (build_request(peer, offset_id, limit, query, sizeof(query), &qlen) != 0) {
526 0 : logger_log(LOG_ERROR, "history: build_request overflow");
527 0 : return -1;
528 : }
529 :
530 64 : RAII_STRING uint8_t *resp = (uint8_t *)malloc(262144);
531 64 : if (!resp) return -1;
532 64 : size_t resp_len = 0;
533 64 : if (api_call(cfg, s, t, query, qlen, resp, 262144, &resp_len) != 0) {
534 1 : logger_log(LOG_ERROR, "history: api_call failed");
535 1 : return -1;
536 : }
537 63 : if (resp_len < 8) return -1;
538 :
539 : uint32_t top;
540 63 : memcpy(&top, resp, 4);
541 63 : if (top == TL_rpc_error) {
542 : RpcError err;
543 0 : rpc_parse_error(resp, resp_len, &err);
544 0 : logger_log(LOG_ERROR, "history: RPC error %d: %s",
545 : err.error_code, err.error_msg);
546 0 : return -1;
547 : }
548 :
549 63 : if (top != TL_messages_messages &&
550 11 : top != TL_messages_messagesSlice &&
551 3 : top != TL_messages_channelMessages) {
552 0 : logger_log(LOG_ERROR, "history: unexpected top 0x%08x", top);
553 0 : return -1;
554 : }
555 :
556 63 : TlReader r = tl_reader_init(resp, resp_len);
557 63 : tl_read_uint32(&r); /* top */
558 :
559 : /* messagesSlice/channelMessages prepend some counters we skip. */
560 63 : if (top == TL_messages_messagesSlice) {
561 8 : tl_read_uint32(&r); /* flags */
562 8 : tl_read_int32 (&r); /* count */
563 : /* next_rate + offset_id_offset are optional (flags.0/.2) — we
564 : * don't read them; the messages vector follows after them in the
565 : * wire. For robustness the parse stops after one entry anyway. */
566 55 : } else if (top == TL_messages_channelMessages) {
567 3 : tl_read_uint32(&r); /* flags */
568 3 : tl_read_int32 (&r); /* pts */
569 3 : tl_read_int32 (&r); /* count */
570 : /* optional offset_id_offset (flags.2) not read */
571 : }
572 :
573 63 : uint32_t vec = tl_read_uint32(&r);
574 63 : if (vec != TL_vector) {
575 0 : logger_log(LOG_ERROR, "history: expected Vector<Message>, got 0x%08x",
576 : vec);
577 0 : return -1;
578 : }
579 63 : uint32_t count = tl_read_uint32(&r);
580 63 : int written = 0;
581 1107 : for (uint32_t i = 0; i < count && written < limit; i++) {
582 1044 : HistoryEntry e = {0};
583 1044 : int rc2 = parse_message(&r, &e);
584 1044 : if (e.id != 0 || e.text[0] != '\0') {
585 1044 : out[written++] = e;
586 : }
587 1044 : if (rc2 != 0) break; /* could not advance past message — stop */
588 : }
589 63 : *out_count = written;
590 63 : return 0;
591 : }
592 :
593 54 : int domain_get_history_self(const ApiConfig *cfg,
594 : MtProtoSession *s, Transport *t,
595 : int32_t offset_id, int limit,
596 : HistoryEntry *out, int *out_count) {
597 54 : HistoryPeer self = { .kind = HISTORY_PEER_SELF };
598 54 : return domain_get_history(cfg, s, t, &self, offset_id, limit, out, out_count);
599 : }
|