Module: MTProto::Updates::Parser

Defined in:
lib/mtproto/updates/parser.rb

Overview

Parses a raw Telegram update reply into normalized Events, shared by the live push path and the getDifference/getChannelDifference recovery paths so both yield one event shape. This module walks the ENVELOPE (which container, which vector, the trailing peers and state); EventBuilder turns each Update into an Event. Every vector entry is advanced via Schema#skip, so an update type we don't model never drops the ones around it.

Defined Under Namespace

Classes: Batch, Difference

Constant Summary collapse

UPDATES =
0x74ae4240
UPDATES_COMBINED =
0x725b04c3
UPDATES_TOO_LONG =
0xe317af7e
UPDATE_SHORT =
0x78d4dec1
UPDATES_DIFFERENCE =
0x00f49ca0
UPDATES_DIFFERENCE_SLICE =
0xa8fb1981
UPDATES_DIFFERENCE_EMPTY =
0x5d75a138
UPDATES_DIFFERENCE_TOO_LONG =
0x4afe8f6d
CHANNEL_DIFFERENCE =
0x2064674e
CHANNEL_DIFFERENCE_EMPTY =
0x3e11affb
CHANNEL_DIFFERENCE_TOO_LONG =
0xa4bcc6fe
UPDATE_CHANNEL_TOO_LONG =
0x108d941f

Class Method Summary collapse

Class Method Details

.attach_peers(events, users, chats) ⇒ Object



201
202
203
204
205
206
# File 'lib/mtproto/updates/parser.rb', line 201

def attach_peers(events, users, chats)
  events.each do |event|
    event.users = users
    event.chats = chats
  end
end

.channel_difference_body(data) ⇒ Object

channelDifference#2064674e flags:# final:flags.0?true pts:int timeout:flags.1?int new_messages:Vector other_updates:Vector chats users.



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/mtproto/updates/parser.rb', line 146

def channel_difference_body(data)
  flags = data[4, 4].unpack1('L<')
  pts = data[8, 4].unpack1('l<')
  offset = 12
  offset += 4 if flags.anybits?(1 << 1) # timeout

  msg_events, offset = messages_vector(data, offset, channel: true)
  upd_events, channel_too_long, offset = updates_vector(data, offset)
  chats, offset = TL::Dialogs.chats_at(data, offset)
  users, = TL::Dialogs.users_at(data, offset)

  events = msg_events + upd_events
  attach_peers(events, users, chats)
  Difference.new(type: :difference, final: flags.anybits?(1 << 0), pts: pts,
    events: events, users: users, chats: chats, channel_too_long: channel_too_long)
end

.difference_body(data, constructor) ⇒ Object

difference / differenceSlice: new_messages new_encrypted_messages other_updates chats users state. Channel messages ride in other_updates (with pts); common new_messages ride in new_messages (no pts).



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/mtproto/updates/parser.rb', line 127

def difference_body(data, constructor)
  msg_events, offset = messages_vector(data, 4, channel: false)
  offset = TL::Reader.schema.skip_vector(data, offset) # new_encrypted_messages
  upd_events, channel_too_long, offset = updates_vector(data, offset)
  chats, offset = TL::Dialogs.chats_at(data, offset)
  users, offset = TL::Dialogs.users_at(data, offset)
  state = TL::UpdatesState.deserialize(data[offset..])

  events = msg_events + upd_events
  attach_peers(events, users, chats)
  Difference.new(
    type: constructor == UPDATES_DIFFERENCE ? :difference : :slice, final: false,
    events: events, users: users, chats: chats, channel_too_long: channel_too_long,
    pts: state.pts, qts: state.qts, date: state.date, seq: state.seq
  )
end

.empty_difference(type: :empty, final: false, pts: nil, date: nil, seq: nil) ⇒ Object



119
120
121
122
# File 'lib/mtproto/updates/parser.rb', line 119

def empty_difference(type: :empty, final: false, pts: nil, date: nil, seq: nil)
  Difference.new(type: type, final: final, events: [], users: [], chats: [],
    channel_too_long: [], pts: pts, date: date, seq: seq)
end

.messages_vector(data, offset, channel:) ⇒ Object

A Vector (a difference's new_messages) → ordered :message / :join / :leave events WITHOUT pts (the protocol omits per-message pts here).



165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/mtproto/updates/parser.rb', line 165

def messages_vector(data, offset, channel:)
  offset += 4 # vector constructor
  count = data[offset, 4].unpack1('L<')
  offset += 4

  events = []
  count.times do
    event = EventBuilder.message_or_service(data, offset, data[offset, 4].unpack1('L<'), channel: channel)
    events << event if event
    offset = TL::Reader.schema.skip(data, offset)
  end
  [events, offset]
end

.parse_channel_difference(data) ⇒ Object

updates.channelDifference reply → a Difference carrying the channel's new pts and final. channel messages ride in new_messages without a per-message pts; the state machine advances the channel pts to this reply's pts.



105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/mtproto/updates/parser.rb', line 105

def parse_channel_difference(data)
  case data[0, 4].unpack1('L<')
  when CHANNEL_DIFFERENCE_EMPTY
    flags = data[4, 4].unpack1('L<')
    empty_difference(final: flags.anybits?(1 << 0), pts: data[8, 4].unpack1('l<'))
  when CHANNEL_DIFFERENCE
    channel_difference_body(data)
  when CHANNEL_DIFFERENCE_TOO_LONG
    empty_difference(type: :too_long, final: true)
  else
    empty_difference(final: true)
  end
end

.parse_container(constructor, data) ⇒ Object

A live updates / updatesCombined push container (or a bodyless updatesTooLong gap signal) → a Batch of ordered events plus the batch peers and gap signals.



45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/mtproto/updates/parser.rb', line 45

def parse_container(constructor, data)
  case constructor
  when UPDATES_TOO_LONG
    Batch.new(events: [], users: [], chats: [], channel_too_long: [], too_long: true, date: nil)
  when UPDATES, UPDATES_COMBINED
    parse_updates(data)
  when UPDATE_SHORT
    parse_short(data)
  else
    Batch.new(events: [], users: [], chats: [], channel_too_long: [], too_long: false, date: nil)
  end
end

.parse_difference(data) ⇒ Object

updates.difference / differenceSlice reply → a Difference with the recovered events and the new account position (in the trailing updates.State). Common new_messages arrive WITHOUT a per-message pts (the protocol omits it), so their events carry pts nil; the state machine advances to the batch-terminal state.pts.



89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/mtproto/updates/parser.rb', line 89

def parse_difference(data)
  case data[0, 4].unpack1('L<')
  when UPDATES_DIFFERENCE_EMPTY
    empty_difference(date: data[4, 4].unpack1('L<'), seq: data[8, 4].unpack1('L<'))
  when UPDATES_DIFFERENCE, UPDATES_DIFFERENCE_SLICE
    difference_body(data, data[0, 4].unpack1('L<'))
  when UPDATES_DIFFERENCE_TOO_LONG
    empty_difference(type: :too_long, pts: data[4, 4].unpack1('l<'))
  else
    empty_difference
  end
end

.parse_short(data) ⇒ Object

updateShort#78d4dec1 update:Update date:int — a single update with no peers attached. Channel messages never arrive this way, but edits/reactions do, so classify the one update (or surface an updateChannelTooLong gap signal).



61
62
63
64
65
66
67
68
69
70
71
# File 'lib/mtproto/updates/parser.rb', line 61

def parse_short(data)
  inner_ctor = data[4, 4].unpack1('L<')
  if inner_ctor == UPDATE_CHANNEL_TOO_LONG
    return Batch.new(events: [], users: [], chats: [], channel_too_long: [data[12, 8].unpack1('Q<')],
      too_long: false, date: nil)
  end

  event = EventBuilder.classify_update(data, 4, inner_ctor)
  Batch.new(events: [event].compact, users: [], chats: [], channel_too_long: [],
    too_long: false, date: data[TL::Reader.schema.skip(data, 4), 4].unpack1('L<'))
end

.parse_updates(data) ⇒ Object

updates: updates:Vector users:Vector chats:Vector date:int (updatesCombined only inserts seq_start before the trailing seq, past date).



75
76
77
78
79
80
81
82
83
# File 'lib/mtproto/updates/parser.rb', line 75

def parse_updates(data)
  events, channel_too_long, offset = updates_vector(data, 4)
  users, offset = TL::Dialogs.users_at(data, offset)
  chats, offset = TL::Dialogs.chats_at(data, offset)
  date = data[offset, 4].unpack1('L<')
  attach_peers(events, users, chats)
  Batch.new(events: events, users: users, chats: chats,
    channel_too_long: channel_too_long, too_long: false, date: date)
end

.updates_vector(data, offset) ⇒ Object

A Vector (a live container's updates, or a difference's other_updates) → [ordered events, updateChannelTooLong channel ids, offset past the vector].



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/mtproto/updates/parser.rb', line 181

def updates_vector(data, offset)
  offset += 4 # vector constructor
  count = data[offset, 4].unpack1('L<')
  offset += 4

  events = []
  channel_too_long = []
  count.times do
    ctor = data[offset, 4].unpack1('L<')
    if ctor == UPDATE_CHANNEL_TOO_LONG
      channel_too_long << data[offset + 8, 8].unpack1('Q<') # constructor(4) + flags(4)
    else
      event = EventBuilder.classify_update(data, offset, ctor)
      events << event if event
    end
    offset = TL::Reader.schema.skip(data, offset)
  end
  [events, channel_too_long, offset]
end