Line | Count | Source (jump to first uncovered line) |
1 | | /* SPDX-License-Identifier: GPL-3.0-or-later |
2 | | * Copyright © 2016-2025 The TokTok team. |
3 | | * Copyright © 2013-2015 Tox project. |
4 | | */ |
5 | | #include "rtp.h" |
6 | | |
7 | | #include <assert.h> |
8 | | #include <stdlib.h> |
9 | | #include <string.h> |
10 | | |
11 | | #include <sodium.h> |
12 | | |
13 | | #include "bwcontroller.h" |
14 | | #include "toxav_hacks.h" |
15 | | |
16 | | #include "../toxcore/ccompat.h" |
17 | | #include "../toxcore/logger.h" |
18 | | #include "../toxcore/mono_time.h" |
19 | | #include "../toxcore/net_crypto.h" |
20 | | #include "../toxcore/network.h" |
21 | | #include "../toxcore/tox_private.h" |
22 | | #include "../toxcore/util.h" |
23 | | |
24 | | /** |
25 | | * The number of milliseconds we want to keep a keyframe in the buffer for, |
26 | | * even though there are no free slots for incoming frames. |
27 | | */ |
28 | 0 | #define VIDEO_KEEP_KEYFRAME_IN_BUFFER_FOR_MS 15 |
29 | | |
30 | | // allocate_len is NOT including header! |
31 | | static struct RTPMessage *new_message(const Logger *log, const struct RTPHeader *header, size_t allocate_len, |
32 | | const uint8_t *data, uint16_t data_length) |
33 | 107 | { |
34 | 107 | assert(allocate_len >= data_length); |
35 | 107 | struct RTPMessage *msg = (struct RTPMessage *)calloc(1, sizeof(struct RTPMessage) + allocate_len); |
36 | | |
37 | 107 | if (msg == nullptr) { |
38 | 0 | LOGGER_DEBUG(log, "Could not allocate RTPMessage buffer"); |
39 | 0 | return nullptr; |
40 | 0 | } |
41 | | |
42 | 107 | msg->len = data_length; // result without header |
43 | 107 | msg->header = *header; |
44 | 107 | memcpy(msg->data, data, msg->len); |
45 | 107 | return msg; |
46 | 107 | } |
47 | | |
48 | | /** |
49 | | * Instruct the caller to clear slot 0. |
50 | | */ |
51 | 143 | #define GET_SLOT_RESULT_DROP_OLDEST_SLOT (-1) |
52 | | |
53 | | /** |
54 | | * Instruct the caller to drop the incoming packet. |
55 | | */ |
56 | 143 | #define GET_SLOT_RESULT_DROP_INCOMING (-2) |
57 | | |
58 | | /** |
59 | | * Find the next free slot in work_buffer for the incoming data packet. |
60 | | * |
61 | | * - If the data packet belongs to a frame that's already in the work_buffer then |
62 | | * use that slot. |
63 | | * - If there is no free slot return GET_SLOT_RESULT_DROP_OLDEST_SLOT. |
64 | | * - If the data packet is too old return GET_SLOT_RESULT_DROP_INCOMING. |
65 | | * |
66 | | * If there is a keyframe being assembled in slot 0, keep it a bit longer and |
67 | | * do not kick it out right away if all slots are full instead kick out the new |
68 | | * incoming interframe. |
69 | | */ |
70 | | static int8_t get_slot(const Logger *log, struct RTPWorkBufferList *wkbl, bool is_keyframe, |
71 | | const struct RTPHeader *header, bool is_multipart) |
72 | 143 | { |
73 | 143 | if (is_multipart) { |
74 | | // This RTP message is part of a multipart frame, so we try to find an |
75 | | // existing slot with the previous parts of the frame in it. |
76 | 84 | for (uint8_t i = 0; i < wkbl->next_free_entry; ++i) { |
77 | 42 | const struct RTPWorkBuffer *slot = &wkbl->work_buffer[i]; |
78 | | |
79 | 42 | if ((slot->buf->header.sequnum == header->sequnum) && (slot->buf->header.timestamp == header->timestamp)) { |
80 | | // Sequence number and timestamp match, so this slot belongs to |
81 | | // the same frame. |
82 | | // |
83 | | // In reality, these will almost certainly either both match or |
84 | | // both not match. Only if somehow there were 65535 frames |
85 | | // between, the timestamp will matter. |
86 | 42 | return i; |
87 | 42 | } |
88 | 42 | } |
89 | 84 | } |
90 | | |
91 | | // The message may or may not be part of a multipart frame. |
92 | | // |
93 | | // If it is part of a multipart frame, then this is an entirely new frame |
94 | | // for which we did not have a slot *or* the frame is so old that its slot |
95 | | // has been evicted by now. |
96 | | // |
97 | | // |----------- time -----------> |
98 | | // _________________ |
99 | | // slot 0 | | |
100 | | // ----------------- |
101 | | // _________________ |
102 | | // slot 1 | | |
103 | | // ----------------- |
104 | | // ____________ |
105 | | // slot 2 | | -> frame too old, drop |
106 | | // ------------ |
107 | | // |
108 | | // |
109 | | // |
110 | | // |----------- time -----------> |
111 | | // _________________ |
112 | | // slot 0 | | |
113 | | // ----------------- |
114 | | // _________________ |
115 | | // slot 1 | | |
116 | | // ----------------- |
117 | | // ____________ |
118 | | // slot 2 | | -> ok, start filling in a new slot |
119 | | // ------------ |
120 | | |
121 | | // If there is a free slot: |
122 | 101 | if (wkbl->next_free_entry < USED_RTP_WORKBUFFER_COUNT) { |
123 | | // If there is at least one filled slot: |
124 | 101 | if (wkbl->next_free_entry > 0) { |
125 | | // Get the most recently filled slot. |
126 | 0 | const struct RTPWorkBuffer *slot = &wkbl->work_buffer[wkbl->next_free_entry - 1]; |
127 | | |
128 | | // If the incoming packet is older than our newest slot, drop it. |
129 | | // This is the first situation in the above diagram. |
130 | 0 | if (slot->buf->header.timestamp > header->timestamp) { |
131 | 0 | LOGGER_DEBUG(log, "workbuffer:2:timestamp too old"); |
132 | 0 | return GET_SLOT_RESULT_DROP_INCOMING; |
133 | 0 | } |
134 | 0 | } |
135 | | |
136 | | // Not all slots are filled, and the packet is newer than our most |
137 | | // recent slot, so it's a new frame we want to start assembling. This is |
138 | | // the second situation in the above diagram. |
139 | 101 | return wkbl->next_free_entry; |
140 | 101 | } |
141 | | |
142 | | // If the incoming frame is a key frame, then stop assembling the oldest |
143 | | // slot, regardless of whether there was a keyframe in that or not. |
144 | 0 | if (is_keyframe) { |
145 | 0 | return GET_SLOT_RESULT_DROP_OLDEST_SLOT; |
146 | 0 | } |
147 | | |
148 | | // The incoming slot is not a key frame, so we look at slot 0 to see what to |
149 | | // do next. |
150 | 0 | const struct RTPWorkBuffer *slot = &wkbl->work_buffer[0]; |
151 | | |
152 | | // The incoming frame is not a key frame, but the existing slot 0 is also |
153 | | // not a keyframe, so we stop assembling the existing frame and make space |
154 | | // for the new one. |
155 | 0 | if (!slot->is_keyframe) { |
156 | 0 | return GET_SLOT_RESULT_DROP_OLDEST_SLOT; |
157 | 0 | } |
158 | | |
159 | | // If this key frame is fully received, we also stop assembling and clear |
160 | | // slot 0. This also means sending the frame to the decoder. |
161 | 0 | if (slot->received_len == slot->buf->header.data_length_full) { |
162 | 0 | return GET_SLOT_RESULT_DROP_OLDEST_SLOT; |
163 | 0 | } |
164 | | |
165 | | // This is a key frame, not fully received yet, but it's already much older |
166 | | // than the incoming frame, so we stop assembling it and send whatever part |
167 | | // we did receive to the decoder. |
168 | 0 | if (slot->buf->header.timestamp + VIDEO_KEEP_KEYFRAME_IN_BUFFER_FOR_MS <= header->timestamp) { |
169 | 0 | return GET_SLOT_RESULT_DROP_OLDEST_SLOT; |
170 | 0 | } |
171 | | |
172 | | // This is a key frame, it's not too old yet, so we keep it in its slot for |
173 | | // a little longer. |
174 | 0 | LOGGER_INFO(log, "keep KEYFRAME in workbuffer"); |
175 | 0 | return GET_SLOT_RESULT_DROP_INCOMING; |
176 | 0 | } |
177 | | |
178 | | /** |
179 | | * Returns an assembled frame (as much data as we currently have for this frame, |
180 | | * some pieces may be missing) |
181 | | * |
182 | | * If there are no frames ready, we return NULL. If this function returns |
183 | | * non-NULL, it transfers ownership of the message to the caller, i.e. the |
184 | | * caller is responsible for storing it elsewhere or calling `free()`. |
185 | | */ |
186 | | static struct RTPMessage *process_frame(const Logger *log, struct RTPWorkBufferList *wkbl, uint8_t slot_id) |
187 | 101 | { |
188 | 101 | assert(wkbl->next_free_entry >= 0); |
189 | | |
190 | 101 | if (wkbl->next_free_entry == 0) { |
191 | | // There are no frames in any slot. |
192 | 0 | return nullptr; |
193 | 0 | } |
194 | | |
195 | | // Slot 0 contains a key frame, slot_id points at an interframe that is |
196 | | // relative to that key frame, so we don't use it yet. |
197 | 101 | if (wkbl->work_buffer[0].is_keyframe && slot_id != 0) { |
198 | 0 | LOGGER_DEBUG(log, "process_frame:KEYFRAME waiting in slot 0"); |
199 | 0 | return nullptr; |
200 | 0 | } |
201 | | |
202 | | // Either slot_id is 0 and slot 0 is a key frame, or there is no key frame |
203 | | // in slot 0 (and slot_id is anything). |
204 | 101 | struct RTPWorkBuffer *const slot = &wkbl->work_buffer[slot_id]; |
205 | | |
206 | | // Move ownership of the frame out of the slot into m_new. |
207 | 101 | struct RTPMessage *const m_new = slot->buf; |
208 | 101 | slot->buf = nullptr; |
209 | | |
210 | 101 | assert(wkbl->next_free_entry >= 1 && wkbl->next_free_entry <= USED_RTP_WORKBUFFER_COUNT); |
211 | | |
212 | 101 | if (slot_id != wkbl->next_free_entry - 1) { |
213 | | // The slot is not the last slot, so we created a gap. We move all the |
214 | | // entries after it one step up. |
215 | 0 | for (uint8_t i = slot_id; i < wkbl->next_free_entry - 1; ++i) { |
216 | | // Move entry (i+1) into entry (i). |
217 | 0 | wkbl->work_buffer[i] = wkbl->work_buffer[i + 1]; |
218 | 0 | } |
219 | 0 | } |
220 | | |
221 | | // We now have a free entry at the end of the array. |
222 | 101 | --wkbl->next_free_entry; |
223 | | |
224 | | // Clear the newly freed entry. |
225 | 101 | const struct RTPWorkBuffer empty = {0}; |
226 | 101 | wkbl->work_buffer[wkbl->next_free_entry] = empty; |
227 | | |
228 | | // Move ownership of the frame to the caller. |
229 | 101 | return m_new; |
230 | 101 | } |
231 | | |
232 | | /** |
233 | | * @param log A pointer to the Logger object. |
234 | | * @param wkbl The list of in-progress frames, i.e. all the slots. |
235 | | * @param slot_id The slot we want to fill the data into. |
236 | | * @param is_keyframe Whether the data is part of a key frame. |
237 | | * @param header The RTP header from the incoming packet. |
238 | | * @param incoming_data The pure payload without header. |
239 | | * @param incoming_data_length The length in bytes of the incoming data payload. |
240 | | */ |
241 | | static bool fill_data_into_slot(const Logger *log, struct RTPWorkBufferList *wkbl, const uint8_t slot_id, |
242 | | bool is_keyframe, const struct RTPHeader *header, |
243 | | const uint8_t *incoming_data, uint16_t incoming_data_length) |
244 | 143 | { |
245 | | // We're either filling the data into an existing slot, or in a new one that |
246 | | // is the next free entry. |
247 | 143 | assert(slot_id <= wkbl->next_free_entry); |
248 | 143 | struct RTPWorkBuffer *const slot = &wkbl->work_buffer[slot_id]; |
249 | | |
250 | 143 | assert(header != nullptr); |
251 | 143 | assert(is_keyframe == (bool)((header->flags & RTP_KEY_FRAME) != 0)); |
252 | | |
253 | 143 | if (slot->received_len == 0) { |
254 | 101 | assert(slot->buf == nullptr); |
255 | | |
256 | | // No data for this slot has been received, yet, so we create a new |
257 | | // message for it with enough memory for the entire frame. |
258 | 101 | struct RTPMessage *msg = (struct RTPMessage *)calloc(1, sizeof(struct RTPMessage) + header->data_length_full); |
259 | | |
260 | 101 | if (msg == nullptr) { |
261 | 0 | LOGGER_ERROR(log, "Out of memory while trying to allocate for frame of size %u", |
262 | 0 | (unsigned)header->data_length_full); |
263 | | // Out of memory: throw away the incoming data. |
264 | 0 | return false; |
265 | 0 | } |
266 | | |
267 | | // Unused in the new video receiving code, as it's 16 bit and can't hold |
268 | | // the full length of large frames. Instead, we use slot->received_len. |
269 | 101 | msg->len = 0; |
270 | 101 | msg->header = *header; |
271 | | |
272 | 101 | slot->buf = msg; |
273 | 101 | slot->is_keyframe = is_keyframe; |
274 | 101 | slot->received_len = 0; |
275 | | |
276 | 101 | assert(wkbl->next_free_entry < USED_RTP_WORKBUFFER_COUNT); |
277 | 101 | ++wkbl->next_free_entry; |
278 | 101 | } |
279 | | |
280 | | // We already checked this when we received the packet, but we rely on it |
281 | | // here, so assert again. |
282 | 143 | assert(header->offset_full < header->data_length_full); |
283 | | |
284 | | // Copy the incoming chunk of data into the correct position in the full |
285 | | // frame data array. |
286 | 143 | memcpy( |
287 | 143 | slot->buf->data + header->offset_full, |
288 | 143 | incoming_data, |
289 | 143 | incoming_data_length |
290 | 143 | ); |
291 | | |
292 | | // Update the total received length of this slot. |
293 | 143 | slot->received_len += incoming_data_length; |
294 | | |
295 | | // Update received length also in the header of the message, for later use. |
296 | 143 | slot->buf->header.received_length_full = slot->received_len; |
297 | | |
298 | 143 | return slot->received_len == header->data_length_full; |
299 | 143 | } |
300 | | |
301 | | static void update_bwc_values(RTPSession *session, const struct RTPMessage *msg) |
302 | 101 | { |
303 | 101 | if (session->first_packets_counter < DISMISS_FIRST_LOST_VIDEO_PACKET_COUNT) { |
304 | 54 | ++session->first_packets_counter; |
305 | 54 | } else { |
306 | 47 | const uint32_t data_length_full = msg->header.data_length_full; // without header |
307 | 47 | const uint32_t received_length_full = msg->header.received_length_full; // without header |
308 | 47 | bwc_add_recv(session->bwc, data_length_full); |
309 | | |
310 | 47 | if (received_length_full < data_length_full) { |
311 | 0 | LOGGER_DEBUG(session->log, "BWC: full length=%u received length=%u", data_length_full, received_length_full); |
312 | 0 | bwc_add_lost(session->bwc, data_length_full - received_length_full); |
313 | 0 | } |
314 | 47 | } |
315 | 101 | } |
316 | | |
317 | | /** |
318 | | * Handle a single RTP video packet. |
319 | | * |
320 | | * The packet may or may not be part of a multipart frame. This function will |
321 | | * find out and handle it appropriately. |
322 | | * |
323 | | * @param session The current RTP session with: |
324 | | * <code> |
325 | | * session->mcb == vc_queue_message() // this function is called from here |
326 | | * session->mp == struct RTPMessage * |
327 | | * session->cs == call->video.second // == VCSession created by vc_new() call |
328 | | * </code> |
329 | | * @param header The RTP header deserialised from the packet. |
330 | | * @param incoming_data The packet data *not* header, i.e. this is the actual |
331 | | * payload. |
332 | | * @param incoming_data_length The packet length *not* including header, i.e. |
333 | | * this is the actual payload length. |
334 | | * @param log A logger. |
335 | | * |
336 | | * @retval -1 on error. |
337 | | * @retval 0 on success. |
338 | | */ |
339 | | static int handle_video_packet(const Logger *log, RTPSession *session, const struct RTPHeader *header, |
340 | | const uint8_t *incoming_data, uint16_t incoming_data_length) |
341 | 143 | { |
342 | | // Full frame length in bytes. The frame may be split into multiple packets, |
343 | | // but this value is the complete assembled frame size. |
344 | 143 | const uint32_t full_frame_length = header->data_length_full; |
345 | | |
346 | | // The sender tells us whether this is a key frame. |
347 | 143 | const bool is_keyframe = (header->flags & RTP_KEY_FRAME) != 0; |
348 | | |
349 | 143 | LOGGER_DEBUG(log, "wkbl->next_free_entry:003=%d", session->work_buffer_list->next_free_entry); |
350 | | |
351 | 143 | const bool is_multipart = full_frame_length != incoming_data_length; |
352 | | |
353 | | /* The message was sent in single part */ |
354 | 143 | int8_t slot_id = get_slot(log, session->work_buffer_list, is_keyframe, header, is_multipart); |
355 | 143 | LOGGER_DEBUG(log, "slot num=%d", slot_id); |
356 | | |
357 | | // get_slot told us to drop the packet, so we ignore it. |
358 | 143 | if (slot_id == GET_SLOT_RESULT_DROP_INCOMING) { |
359 | 0 | return -1; |
360 | 0 | } |
361 | | |
362 | | // get_slot said there is no free slot. |
363 | 143 | if (slot_id == GET_SLOT_RESULT_DROP_OLDEST_SLOT) { |
364 | 0 | LOGGER_DEBUG(log, "there was no free slot, so we process the oldest frame"); |
365 | | // We now own the frame. |
366 | 0 | struct RTPMessage *m_new = process_frame(log, session->work_buffer_list, 0); |
367 | | |
368 | | // The process_frame function returns NULL if there is no slot 0, i.e. |
369 | | // the work buffer list is completely empty. It can't be empty, because |
370 | | // get_slot just told us it's full, so process_frame must return non-null. |
371 | 0 | assert(m_new != nullptr); |
372 | | |
373 | 0 | LOGGER_DEBUG(log, "-- handle_video_packet -- CALLBACK-001a b0=%d b1=%d", (int)m_new->data[0], |
374 | 0 | (int)m_new->data[1]); |
375 | 0 | update_bwc_values(session, m_new); |
376 | | // Pass ownership of m_new to the callback. |
377 | 0 | Mono_Time *mt = toxav_get_av_mono_time(session->toxav); |
378 | 0 | assert(mt != nullptr); |
379 | 0 | session->mcb(mt, session->cs, m_new); |
380 | | // Now we no longer own m_new. |
381 | 0 | m_new = nullptr; |
382 | | |
383 | | // Now we must have a free slot, so we either get that slot, i.e. >= 0, |
384 | | // or get told to drop the incoming packet if it's too old. |
385 | 0 | slot_id = get_slot(log, session->work_buffer_list, is_keyframe, header, /* is_multipart */false); |
386 | |
|
387 | 0 | if (slot_id == GET_SLOT_RESULT_DROP_INCOMING) { |
388 | | // The incoming frame is too old, so we drop it. |
389 | 0 | return -1; |
390 | 0 | } |
391 | 0 | } |
392 | | |
393 | | // We must have a valid slot here. |
394 | 143 | assert(slot_id >= 0); |
395 | | |
396 | 143 | LOGGER_DEBUG(log, "fill_data_into_slot.1"); |
397 | | |
398 | | // fill in this part into the slot buffer at the correct offset |
399 | 143 | if (!fill_data_into_slot( |
400 | 143 | log, |
401 | 143 | session->work_buffer_list, |
402 | 143 | slot_id, |
403 | 143 | is_keyframe, |
404 | 143 | header, |
405 | 143 | incoming_data, |
406 | 143 | incoming_data_length)) { |
407 | | // Memory allocation failed. Return error. |
408 | 42 | return -1; |
409 | 42 | } |
410 | | |
411 | 101 | struct RTPMessage *m_new = process_frame(log, session->work_buffer_list, slot_id); |
412 | | |
413 | 101 | if (m_new != nullptr) { |
414 | 101 | LOGGER_DEBUG(log, "-- handle_video_packet -- CALLBACK-003a b0=%d b1=%d", (int)m_new->data[0], |
415 | 101 | (int)m_new->data[1]); |
416 | 101 | update_bwc_values(session, m_new); |
417 | 101 | Mono_Time *mt = toxav_get_av_mono_time(session->toxav); |
418 | 101 | assert(mt != nullptr); |
419 | 101 | session->mcb(mt, session->cs, m_new); |
420 | | |
421 | 101 | m_new = nullptr; |
422 | 101 | } |
423 | | |
424 | 101 | return 0; |
425 | 101 | } |
426 | | |
427 | | /** |
428 | | * receive custom lossypackets and process them. they can be incoming audio or video packets |
429 | | */ |
430 | | void handle_rtp_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, void *user_data) |
431 | 260 | { |
432 | 260 | ToxAV *toxav = (ToxAV *)tox_get_av_object(tox); |
433 | | |
434 | 260 | if (toxav == nullptr) { |
435 | | // LOGGER_WARNING(log, "ToxAV is NULL!"); |
436 | 0 | return; |
437 | 0 | } |
438 | | |
439 | 260 | const Logger *log = toxav_get_logger(toxav); |
440 | | |
441 | 260 | if (length < RTP_HEADER_SIZE + 1) { |
442 | 0 | LOGGER_WARNING(log, "Invalid length of received buffer!"); |
443 | 0 | return; |
444 | 0 | } |
445 | | |
446 | 260 | ToxAVCall *call = call_get(toxav, friend_number); |
447 | | |
448 | 260 | if (call == nullptr) { |
449 | 10 | LOGGER_WARNING(log, "ToxAVCall is NULL!"); |
450 | 10 | return; |
451 | 10 | } |
452 | | |
453 | 250 | RTPSession *session = rtp_session_get(call, data[0]); |
454 | | |
455 | 250 | if (session == nullptr) { |
456 | 0 | LOGGER_WARNING(log, "No session!"); |
457 | 0 | return; |
458 | 0 | } |
459 | | |
460 | 250 | if (!session->rtp_receive_active) { |
461 | 0 | LOGGER_WARNING(log, "receiving not allowed!"); |
462 | 0 | return; |
463 | 0 | } |
464 | | |
465 | | // Get the packet type. |
466 | 250 | const uint8_t packet_type = data[0]; |
467 | 250 | const uint8_t *payload = &data[1]; |
468 | | // TODO(Zoff): is this ok? |
469 | 250 | const uint16_t payload_size = (uint16_t)length - 1; |
470 | | |
471 | | // Unpack the header. |
472 | 250 | struct RTPHeader header; |
473 | 250 | rtp_header_unpack(payload, &header); |
474 | | |
475 | 250 | if (header.pt != packet_type % 128) { |
476 | 0 | LOGGER_WARNING(log, "RTPHeader packet type and Tox protocol packet type did not agree: %d != %d", |
477 | 0 | header.pt, packet_type % 128); |
478 | 0 | return; |
479 | 0 | } |
480 | | |
481 | 250 | if (header.pt != session->payload_type % 128) { |
482 | 0 | LOGGER_WARNING(log, "RTPHeader packet type does not match this session's payload type: %d != %d", |
483 | 0 | header.pt, session->payload_type % 128); |
484 | 0 | return; |
485 | 0 | } |
486 | | |
487 | 250 | if ((header.flags & RTP_LARGE_FRAME) != 0 && header.offset_full >= header.data_length_full) { |
488 | 0 | LOGGER_ERROR(log, "Invalid video packet: frame offset (%u) >= full frame length (%u)", |
489 | 0 | (unsigned)header.offset_full, (unsigned)header.data_length_full); |
490 | 0 | return; |
491 | 0 | } |
492 | | |
493 | 250 | if (header.offset_lower >= header.data_length_lower) { |
494 | 0 | LOGGER_ERROR(log, "Invalid old protocol video packet: frame offset (%u) >= full frame length (%u)", |
495 | 0 | (unsigned)header.offset_lower, (unsigned)header.data_length_lower); |
496 | 0 | return; |
497 | 0 | } |
498 | | |
499 | 250 | LOGGER_DEBUG(log, "header.pt %d, video %d", (uint8_t)header.pt, RTP_TYPE_VIDEO % 128); |
500 | | |
501 | | // The sender uses the new large-frame capable protocol and is sending a |
502 | | // video packet. |
503 | 250 | if ((header.flags & RTP_LARGE_FRAME) != 0 && header.pt == (RTP_TYPE_VIDEO % 128)) { |
504 | 143 | handle_video_packet(log, session, &header, &payload[RTP_HEADER_SIZE], payload_size - RTP_HEADER_SIZE); |
505 | 143 | return; |
506 | 143 | } |
507 | | |
508 | | // everything below here is for the old 16 bit protocol ------------------ |
509 | | |
510 | 107 | if (header.data_length_lower == payload_size - RTP_HEADER_SIZE) { |
511 | | /* The message is sent in single part */ |
512 | | |
513 | | /* Message is not late; pick up the latest parameters */ |
514 | 107 | session->rsequnum = header.sequnum; |
515 | 107 | session->rtimestamp = header.timestamp; |
516 | 107 | bwc_add_recv(session->bwc, payload_size); |
517 | | |
518 | | /* Invoke processing of active multiparted message */ |
519 | 107 | if (session->mp != nullptr) { |
520 | 0 | Mono_Time *mt = toxav_get_av_mono_time(session->toxav); |
521 | 0 | assert(mt != nullptr); |
522 | 0 | session->mcb(mt, session->cs, session->mp); |
523 | 0 | session->mp = nullptr; |
524 | 0 | } |
525 | | |
526 | | /* The message came in the allowed time; |
527 | | */ |
528 | | |
529 | 107 | session->mp = new_message(log, &header, payload_size - RTP_HEADER_SIZE, &payload[RTP_HEADER_SIZE], payload_size - RTP_HEADER_SIZE); |
530 | 107 | Mono_Time *mt = toxav_get_av_mono_time(session->toxav); |
531 | 107 | assert(mt != nullptr); |
532 | 107 | session->mcb(mt, session->cs, session->mp); |
533 | 107 | session->mp = nullptr; |
534 | 107 | return; |
535 | 107 | } |
536 | | |
537 | | /* The message is sent in multiple parts */ |
538 | | |
539 | 0 | if (session->mp != nullptr) { |
540 | | /* There are 2 possible situations in this case: |
541 | | * 1) being that we got the part of already processing message. |
542 | | * 2) being that we got the part of a new/old message. |
543 | | * |
544 | | * We handle them differently as we only allow a single multiparted |
545 | | * processing message |
546 | | */ |
547 | 0 | if (session->mp->header.sequnum == header.sequnum && |
548 | 0 | session->mp->header.timestamp == header.timestamp) { |
549 | | /* First case */ |
550 | | |
551 | | /* Make sure we have enough allocated memory */ |
552 | 0 | if (session->mp->header.data_length_lower - session->mp->len < payload_size - RTP_HEADER_SIZE || |
553 | 0 | session->mp->header.data_length_lower <= header.offset_lower) { |
554 | | /* There happened to be some corruption on the stream; |
555 | | * continue wihtout this part |
556 | | */ |
557 | 0 | return; |
558 | 0 | } |
559 | | |
560 | 0 | memcpy(session->mp->data + header.offset_lower, &payload[RTP_HEADER_SIZE], |
561 | 0 | payload_size - RTP_HEADER_SIZE); |
562 | 0 | session->mp->len += payload_size - RTP_HEADER_SIZE; |
563 | 0 | bwc_add_recv(session->bwc, payload_size); |
564 | |
|
565 | 0 | if (session->mp->len == session->mp->header.data_length_lower) { |
566 | | /* Received a full message; now push it for the further |
567 | | * processing. |
568 | | */ |
569 | 0 | Mono_Time *mt = toxav_get_av_mono_time(session->toxav); |
570 | 0 | assert(mt != nullptr); |
571 | 0 | session->mcb(mt, session->cs, session->mp); |
572 | 0 | session->mp = nullptr; |
573 | 0 | } |
574 | 0 | } else { |
575 | | /* Second case */ |
576 | 0 | if (session->mp->header.timestamp > header.timestamp) { |
577 | | /* The received message part is from the old message; |
578 | | * discard it. |
579 | | */ |
580 | 0 | return; |
581 | 0 | } |
582 | | |
583 | | /* Push the previous message for processing */ |
584 | 0 | Mono_Time *mt = toxav_get_av_mono_time(session->toxav); |
585 | 0 | assert(mt != nullptr); |
586 | 0 | session->mcb(mt, session->cs, session->mp); |
587 | |
|
588 | 0 | session->mp = nullptr; |
589 | 0 | goto NEW_MULTIPARTED; |
590 | 0 | } |
591 | 0 | } else { |
592 | | /* In this case treat the message as if it was received in order |
593 | | */ |
594 | | /* This is also a point for new multiparted messages */ |
595 | 0 | NEW_MULTIPARTED: |
596 | | |
597 | | /* Message is not late; pick up the latest parameters */ |
598 | 0 | session->rsequnum = header.sequnum; |
599 | 0 | session->rtimestamp = header.timestamp; |
600 | 0 | bwc_add_recv(session->bwc, payload_size); |
601 | | |
602 | | /* Store message. |
603 | | */ |
604 | 0 | session->mp = new_message(log, &header, header.data_length_lower, &payload[RTP_HEADER_SIZE], payload_size - RTP_HEADER_SIZE); |
605 | |
|
606 | 0 | if (session->mp != nullptr) { |
607 | 0 | memmove(session->mp->data + header.offset_lower, session->mp->data, session->mp->len); |
608 | 0 | } else { |
609 | 0 | LOGGER_WARNING(log, "new_message() returned a null pointer"); |
610 | 0 | return; |
611 | 0 | } |
612 | 0 | } |
613 | | |
614 | 0 | return; |
615 | 0 | } |
616 | | |
617 | | size_t rtp_header_pack(uint8_t *const rdata, const struct RTPHeader *header) |
618 | 262 | { |
619 | 262 | uint8_t *p = rdata; |
620 | 262 | *p = (header->ve & 3) << 6 |
621 | 262 | | (header->pe & 1) << 5 |
622 | 262 | | (header->xe & 1) << 4 |
623 | 262 | | (header->cc & 0xf); |
624 | 262 | ++p; |
625 | 262 | *p = (header->ma & 1) << 7 |
626 | 262 | | (header->pt & 0x7f); |
627 | 262 | ++p; |
628 | | |
629 | 262 | p += net_pack_u16(p, header->sequnum); |
630 | 262 | p += net_pack_u32(p, header->timestamp); |
631 | 262 | p += net_pack_u32(p, header->ssrc); |
632 | 262 | p += net_pack_u64(p, header->flags); |
633 | 262 | p += net_pack_u32(p, header->offset_full); |
634 | 262 | p += net_pack_u32(p, header->data_length_full); |
635 | 262 | p += net_pack_u32(p, header->received_length_full); |
636 | | |
637 | 3.14k | for (size_t i = 0; i < RTP_PADDING_FIELDS; ++i) { |
638 | 2.88k | p += net_pack_u32(p, 0); |
639 | 2.88k | } |
640 | | |
641 | 262 | p += net_pack_u16(p, header->offset_lower); |
642 | 262 | p += net_pack_u16(p, header->data_length_lower); |
643 | 262 | assert(p == rdata + RTP_HEADER_SIZE); |
644 | 262 | return p - rdata; |
645 | 262 | } |
646 | | |
647 | | size_t rtp_header_unpack(const uint8_t *data, struct RTPHeader *header) |
648 | 251 | { |
649 | 251 | const uint8_t *p = data; |
650 | 251 | header->ve = (*p >> 6) & 3; |
651 | 251 | header->pe = (*p >> 5) & 1; |
652 | 251 | header->xe = (*p >> 4) & 1; |
653 | 251 | header->cc = *p & 0xf; |
654 | 251 | ++p; |
655 | | |
656 | 251 | header->ma = (*p >> 7) & 1; |
657 | 251 | header->pt = *p & 0x7f; |
658 | 251 | ++p; |
659 | | |
660 | 251 | p += net_unpack_u16(p, &header->sequnum); |
661 | 251 | p += net_unpack_u32(p, &header->timestamp); |
662 | 251 | p += net_unpack_u32(p, &header->ssrc); |
663 | 251 | p += net_unpack_u64(p, &header->flags); |
664 | 251 | p += net_unpack_u32(p, &header->offset_full); |
665 | 251 | p += net_unpack_u32(p, &header->data_length_full); |
666 | 251 | p += net_unpack_u32(p, &header->received_length_full); |
667 | | |
668 | 251 | p += sizeof(uint32_t) * RTP_PADDING_FIELDS; |
669 | | |
670 | 251 | p += net_unpack_u16(p, &header->offset_lower); |
671 | 251 | p += net_unpack_u16(p, &header->data_length_lower); |
672 | 251 | assert(p == data + RTP_HEADER_SIZE); |
673 | 251 | return p - data; |
674 | 251 | } |
675 | | |
676 | | static uint32_t rtp_random_u32(void) |
677 | 18 | { |
678 | | // HINT: uses libsodium function |
679 | 18 | return randombytes_random(); |
680 | 18 | } |
681 | | |
682 | | RTPSession *rtp_new(const Logger *log, const Memory *mem, int payload_type, Tox *tox, ToxAV *toxav, uint32_t friendnumber, |
683 | | BWController *bwc, void *cs, rtp_m_cb *mcb) |
684 | 36 | { |
685 | 36 | assert(mcb != nullptr); |
686 | 36 | assert(cs != nullptr); |
687 | | |
688 | 36 | RTPSession *session = (RTPSession *)calloc(1, sizeof(RTPSession)); |
689 | | |
690 | 36 | if (session == nullptr) { |
691 | 0 | LOGGER_WARNING(log, "Alloc failed! Program might misbehave!"); |
692 | 0 | return nullptr; |
693 | 0 | } |
694 | | |
695 | 36 | session->work_buffer_list = (struct RTPWorkBufferList *)calloc(1, sizeof(struct RTPWorkBufferList)); |
696 | | |
697 | 36 | if (session->work_buffer_list == nullptr) { |
698 | 0 | LOGGER_ERROR(log, "out of memory while allocating work buffer list"); |
699 | 0 | free(session); |
700 | 0 | return nullptr; |
701 | 0 | } |
702 | | |
703 | | // First entry is free. |
704 | 36 | session->work_buffer_list->next_free_entry = 0; |
705 | | |
706 | 36 | session->ssrc = payload_type == RTP_TYPE_VIDEO ? 0 : rtp_random_u32(); // Zoff: what is this?? |
707 | 36 | session->payload_type = payload_type; |
708 | 36 | session->log = log; |
709 | 36 | session->mem = mem; |
710 | 36 | session->tox = tox; |
711 | 36 | session->toxav = toxav; |
712 | 36 | session->friend_number = friendnumber; |
713 | 36 | session->rtp_receive_active = true; |
714 | | |
715 | | // set NULL just in case |
716 | 36 | session->mp = nullptr; |
717 | 36 | session->first_packets_counter = 1; |
718 | | |
719 | | /* Also set payload type as prefix */ |
720 | 36 | session->bwc = bwc; |
721 | 36 | session->cs = cs; |
722 | 36 | session->mcb = mcb; |
723 | | |
724 | 36 | return session; |
725 | 36 | } |
726 | | |
727 | | void rtp_kill(const Logger *log, RTPSession *session) |
728 | 36 | { |
729 | 36 | if (session == nullptr) { |
730 | 0 | LOGGER_WARNING(log, "No session"); |
731 | 0 | return; |
732 | 0 | } |
733 | | |
734 | 36 | LOGGER_DEBUG(log, "Terminated RTP session: %p", (void *)session); |
735 | 36 | LOGGER_DEBUG(log, "Terminated RTP session V3 work_buffer_list->next_free_entry: %d", |
736 | 36 | (int)session->work_buffer_list->next_free_entry); |
737 | | |
738 | 36 | for (int8_t i = 0; i < session->work_buffer_list->next_free_entry; ++i) { |
739 | 0 | free(session->work_buffer_list->work_buffer[i].buf); |
740 | 0 | } |
741 | 36 | free(session->work_buffer_list); |
742 | 36 | free(session); |
743 | 36 | } |
744 | | |
745 | | void rtp_allow_receiving_mark(RTPSession *session) |
746 | 25 | { |
747 | 25 | if (session != nullptr) { |
748 | 25 | session->rtp_receive_active = true; |
749 | 25 | } |
750 | 25 | } |
751 | | |
752 | | void rtp_stop_receiving_mark(RTPSession *session) |
753 | 15 | { |
754 | 15 | if (session != nullptr) { |
755 | 15 | session->rtp_receive_active = false; |
756 | 15 | } |
757 | 15 | } |
758 | | |
759 | | void rtp_allow_receiving(Tox *tox) |
760 | 6 | { |
761 | | // register callback |
762 | 6 | tox_callback_friend_lossy_packet_per_pktid(tox, handle_rtp_packet, RTP_TYPE_AUDIO); |
763 | 6 | tox_callback_friend_lossy_packet_per_pktid(tox, handle_rtp_packet, RTP_TYPE_VIDEO); |
764 | 6 | } |
765 | | |
766 | | void rtp_stop_receiving(Tox *tox) |
767 | 6 | { |
768 | | // UN-register callback |
769 | 6 | tox_callback_friend_lossy_packet_per_pktid(tox, nullptr, RTP_TYPE_AUDIO); |
770 | 6 | tox_callback_friend_lossy_packet_per_pktid(tox, nullptr, RTP_TYPE_VIDEO); |
771 | 6 | } |
772 | | |
773 | | /** |
774 | | * Log the neterror error if any. |
775 | | * |
776 | | * @param error the error from rtp_send_custom_lossy_packet. |
777 | | * @param rdata_size The package length to be shown in the log. |
778 | | */ |
779 | | static void rtp_report_error_maybe(const Logger *log, const Memory *mem, Tox_Err_Friend_Custom_Packet error, uint16_t rdata_size) |
780 | 260 | { |
781 | 260 | if (error != TOX_ERR_FRIEND_CUSTOM_PACKET_OK) { |
782 | 0 | Net_Strerror error_str; |
783 | 0 | const char *toxerror = tox_err_friend_custom_packet_to_string(error); |
784 | 0 | LOGGER_WARNING(log, "RTP send failed (len: %u)! tox error: %s net error: %s", |
785 | 0 | rdata_size, toxerror, net_strerror(net_error(), &error_str)); |
786 | 0 | } |
787 | 260 | } |
788 | | |
789 | | static void rtp_send_piece(const Logger *log, const Memory *mem, Tox *tox, uint32_t friend_number, const struct RTPHeader *header, |
790 | | const uint8_t *data, uint8_t *rdata, uint16_t length) |
791 | 260 | { |
792 | 260 | rtp_header_pack(rdata + 1, header); |
793 | 260 | memcpy(rdata + 1 + RTP_HEADER_SIZE, data, length); |
794 | | |
795 | 260 | const uint16_t rdata_size = length + RTP_HEADER_SIZE + 1; |
796 | | |
797 | 260 | Tox_Err_Friend_Custom_Packet error; |
798 | 260 | tox_friend_send_lossy_packet(tox, friend_number, rdata, rdata_size, &error); |
799 | | |
800 | 260 | rtp_report_error_maybe(log, mem, error, rdata_size); |
801 | 260 | } |
802 | | |
803 | | static struct RTPHeader rtp_default_header(const RTPSession *session, uint32_t length, bool is_keyframe) |
804 | 218 | { |
805 | 218 | uint16_t length_safe = (uint16_t)length; |
806 | | |
807 | 218 | if (length > UINT16_MAX) { |
808 | 0 | length_safe = UINT16_MAX; |
809 | 0 | } |
810 | | |
811 | 218 | struct RTPHeader header = {0}; |
812 | | |
813 | 218 | if (is_keyframe) { |
814 | 42 | header.flags |= RTP_KEY_FRAME; |
815 | 42 | } |
816 | | |
817 | 218 | if (session->payload_type == RTP_TYPE_VIDEO) { |
818 | 108 | header.flags |= RTP_LARGE_FRAME; |
819 | 108 | } |
820 | | |
821 | 218 | header.ve = 2; // this is unused in toxav |
822 | 218 | header.pe = 0; |
823 | 218 | header.xe = 0; |
824 | 218 | header.cc = 0; |
825 | 218 | header.ma = 0; |
826 | 218 | header.pt = session->payload_type % 128; |
827 | 218 | header.sequnum = session->sequnum; |
828 | 218 | const Mono_Time *mt = toxav_get_av_mono_time(session->toxav); |
829 | 218 | if (mt != nullptr) { |
830 | 218 | header.timestamp = current_time_monotonic(mt); |
831 | 218 | } else { |
832 | 0 | header.timestamp = 0; |
833 | 0 | } |
834 | 218 | header.ssrc = session->ssrc; |
835 | 218 | header.offset_lower = 0; |
836 | 218 | header.data_length_lower = length_safe; |
837 | 218 | header.data_length_full = length; // without header |
838 | 218 | header.offset_lower = 0; |
839 | 218 | header.offset_full = 0; |
840 | | |
841 | 218 | return header; |
842 | 218 | } |
843 | | |
844 | | /** |
845 | | * @brief Send a frame of audio or video data, chunked in @ref RTPMessage instances. |
846 | | * |
847 | | * @param session The A/V session to send the data for. |
848 | | * @param data A byte array of length @p length. |
849 | | * @param length The number of bytes to send from @p data. |
850 | | * @param is_keyframe Whether this video frame is a key frame. If it is an |
851 | | * audio frame, this parameter is ignored. |
852 | | */ |
853 | | int rtp_send_data(const Logger *log, RTPSession *session, const uint8_t *data, uint32_t length, |
854 | | bool is_keyframe) |
855 | 218 | { |
856 | 218 | if (session == nullptr) { |
857 | 0 | return -1; |
858 | 0 | } |
859 | | |
860 | 218 | const uint16_t rdata_size = min_u32(length + RTP_HEADER_SIZE + 1, MAX_CRYPTO_DATA_SIZE); |
861 | 218 | VLA(uint8_t, rdata, rdata_size); |
862 | 218 | memset(rdata, 0, rdata_size); |
863 | 218 | rdata[0] = session->payload_type; // packet id == payload_type |
864 | | |
865 | 218 | struct RTPHeader header = rtp_default_header(session, length, is_keyframe); |
866 | | |
867 | 218 | if (MAX_CRYPTO_DATA_SIZE > (length + RTP_HEADER_SIZE + 1)) { |
868 | | /* |
869 | | * The length is lesser than the maximum allowed length (including header) |
870 | | * Send the packet in single piece. |
871 | | */ |
872 | 176 | assert(length < UINT16_MAX); |
873 | 176 | rtp_send_piece(log, session->mem, session->tox, session->friend_number, &header, data, rdata, length); |
874 | 176 | } else { |
875 | | /* |
876 | | * The length is greater than the maximum allowed length (including header) |
877 | | * Send the packet in multiple pieces. |
878 | | */ |
879 | 42 | uint32_t sent = 0; |
880 | 42 | uint16_t piece = MAX_CRYPTO_DATA_SIZE - (RTP_HEADER_SIZE + 1); |
881 | | |
882 | 84 | while ((length - sent) + RTP_HEADER_SIZE + 1 > MAX_CRYPTO_DATA_SIZE) { |
883 | 42 | rtp_send_piece(log, session->mem, session->tox, session->friend_number, &header, data + sent, rdata, piece); |
884 | | |
885 | 42 | sent += piece; |
886 | 42 | header.offset_lower = sent; |
887 | 42 | header.offset_full = sent; // raw data offset, without any header |
888 | 42 | } |
889 | | |
890 | | /* Send remaining */ |
891 | 42 | piece = length - sent; |
892 | | |
893 | 42 | if (piece != 0) { |
894 | 42 | rtp_send_piece(log, session->mem, session->tox, session->friend_number, &header, data + sent, rdata, piece); |
895 | 42 | } |
896 | 42 | } |
897 | | |
898 | 218 | ++session->sequnum; |
899 | 218 | return 0; |
900 | 218 | } |