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