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 "msi.h" |
6 | | |
7 | | #include <assert.h> |
8 | | #include <stdbool.h> |
9 | | #include <stdlib.h> |
10 | | #include <string.h> |
11 | | |
12 | | #include "toxav_hacks.h" |
13 | | |
14 | | #include "../toxcore/ccompat.h" |
15 | | #include "../toxcore/logger.h" |
16 | | #include "../toxcore/net_crypto.h" |
17 | | #include "../toxcore/tox.h" |
18 | | #include "../toxcore/tox_private.h" |
19 | | #include "../toxcore/util.h" |
20 | | |
21 | | #define MSI_MAXMSG_SIZE 256 |
22 | | |
23 | | /** |
24 | | * Protocol: |
25 | | * |
26 | | * `|id [1 byte]| |size [1 byte]| |data [$size bytes]| |...{repeat}| |0 {end byte}|` |
27 | | */ |
28 | | |
29 | | typedef enum MSIHeaderID { |
30 | | ID_REQUEST = 1, |
31 | | ID_ERROR, |
32 | | ID_CAPABILITIES, |
33 | | } MSIHeaderID; |
34 | | |
35 | | typedef enum MSIRequest { |
36 | | REQU_INIT, |
37 | | REQU_PUSH, |
38 | | REQU_POP, |
39 | | } MSIRequest; |
40 | | |
41 | | typedef struct MSIHeaderRequest { |
42 | | MSIRequest value; |
43 | | bool exists; |
44 | | } MSIHeaderRequest; |
45 | | |
46 | | typedef struct MSIHeaderError { |
47 | | MSIError value; |
48 | | bool exists; |
49 | | } MSIHeaderError; |
50 | | |
51 | | typedef struct MSIHeaderCapabilities { |
52 | | uint8_t value; |
53 | | bool exists; |
54 | | } MSIHeaderCapabilities; |
55 | | |
56 | | typedef struct MSIMessage { |
57 | | MSIHeaderRequest request; |
58 | | MSIHeaderError error; |
59 | | MSIHeaderCapabilities capabilities; |
60 | | } MSIMessage; |
61 | | |
62 | | static void msg_init(MSIMessage *dest, MSIRequest request); |
63 | | static void kill_call(const Logger *log, MSICall *call); |
64 | | static int msg_parse_in(const Logger *log, MSIMessage *dest, const uint8_t *data, uint16_t length); |
65 | | static uint8_t *msg_parse_header_out(MSIHeaderID id, uint8_t *dest, const uint8_t *value, uint8_t value_len, |
66 | | uint16_t *length); |
67 | | static int send_message(const Logger *log, Tox *tox, uint32_t friend_number, const MSIMessage *msg); |
68 | | static int send_error(const Logger *log, Tox *tox, uint32_t friend_number, MSIError error); |
69 | | static MSICall *get_call(MSISession *session, uint32_t friend_number); |
70 | | static MSICall *new_call(MSISession *session, uint32_t friend_number); |
71 | | static bool invoke_callback(const Logger *log, MSICall *call, MSICallbackID cb); |
72 | | static void handle_init(const Logger *log, MSICall *call, const MSIMessage *msg); |
73 | | static void handle_push(const Logger *log, MSICall *call, const MSIMessage *msg); |
74 | | static void handle_pop(const Logger *log, MSICall *call, const MSIMessage *msg); |
75 | | static void handle_msi_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, |
76 | | void *user_data); |
77 | | |
78 | | /* |
79 | | * Public functions |
80 | | */ |
81 | | |
82 | | void msi_callback_invite(MSISession *session, msi_action_cb *callback) |
83 | 6 | { |
84 | 6 | session->invite_callback = callback; |
85 | 6 | } |
86 | | void msi_callback_start(MSISession *session, msi_action_cb *callback) |
87 | 6 | { |
88 | 6 | session->start_callback = callback; |
89 | 6 | } |
90 | | void msi_callback_end(MSISession *session, msi_action_cb *callback) |
91 | 6 | { |
92 | 6 | session->end_callback = callback; |
93 | 6 | } |
94 | | void msi_callback_error(MSISession *session, msi_action_cb *callback) |
95 | 6 | { |
96 | 6 | session->error_callback = callback; |
97 | 6 | } |
98 | | void msi_callback_peertimeout(MSISession *session, msi_action_cb *callback) |
99 | 6 | { |
100 | 6 | session->peertimeout_callback = callback; |
101 | 6 | } |
102 | | void msi_callback_capabilities(MSISession *session, msi_action_cb *callback) |
103 | 6 | { |
104 | 6 | session->capabilities_callback = callback; |
105 | 6 | } |
106 | | |
107 | | MSISession *msi_new(const Logger *log, Tox *tox) |
108 | 6 | { |
109 | 6 | if (tox == nullptr) { |
110 | 0 | return nullptr; |
111 | 0 | } |
112 | | |
113 | 6 | MSISession *retu = (MSISession *)calloc(1, sizeof(MSISession)); |
114 | | |
115 | 6 | if (retu == nullptr) { |
116 | 0 | LOGGER_ERROR(log, "Allocation failed! Program might misbehave!"); |
117 | 0 | return nullptr; |
118 | 0 | } |
119 | | |
120 | 6 | if (create_recursive_mutex(retu->mutex) != 0) { |
121 | 0 | LOGGER_ERROR(log, "Failed to init mutex! Program might misbehave"); |
122 | 0 | free(retu); |
123 | 0 | return nullptr; |
124 | 0 | } |
125 | | |
126 | 6 | retu->tox = tox; |
127 | | |
128 | | // register callback |
129 | 6 | tox_callback_friend_lossless_packet_per_pktid(tox, handle_msi_packet, PACKET_ID_MSI); |
130 | | |
131 | 6 | LOGGER_DEBUG(log, "New msi session: %p ", (void *)retu); |
132 | 6 | return retu; |
133 | 6 | } |
134 | | |
135 | | int msi_kill(const Logger *log, Tox *tox, MSISession *session) |
136 | 6 | { |
137 | 6 | if (session == nullptr) { |
138 | 0 | LOGGER_ERROR(log, "Tried to terminate non-existing session"); |
139 | 0 | return -1; |
140 | 0 | } |
141 | | |
142 | | // UN-register callback |
143 | 6 | tox_callback_friend_lossless_packet_per_pktid(tox, nullptr, PACKET_ID_MSI); |
144 | | |
145 | 6 | if (pthread_mutex_trylock(session->mutex) != 0) { |
146 | 0 | LOGGER_ERROR(log, "Failed to acquire lock on msi mutex"); |
147 | 0 | return -1; |
148 | 0 | } |
149 | | |
150 | 6 | if (session->calls != nullptr) { |
151 | 0 | MSIMessage msg; |
152 | 0 | msg_init(&msg, REQU_POP); |
153 | |
|
154 | 0 | MSICall *it = get_call(session, session->calls_head); |
155 | |
|
156 | 0 | while (it != nullptr) { |
157 | 0 | send_message(log, session->tox, it->friend_number, &msg); |
158 | 0 | MSICall *temp_it = it; |
159 | 0 | it = it->next; |
160 | 0 | kill_call(log, temp_it); /* This will eventually free session->calls */ |
161 | 0 | } |
162 | 0 | } |
163 | | |
164 | 6 | pthread_mutex_unlock(session->mutex); |
165 | 6 | pthread_mutex_destroy(session->mutex); |
166 | | |
167 | 6 | LOGGER_DEBUG(log, "Terminated session: %p", (void *)session); |
168 | 6 | free(session); |
169 | 6 | return 0; |
170 | 6 | } |
171 | | |
172 | | /* |
173 | | * return true if friend is offline and the call was canceled. |
174 | | */ |
175 | | bool check_peer_offline_status(const Logger *log, const Tox *tox, MSISession *session, uint32_t friend_number) |
176 | 420 | { |
177 | 420 | if (tox == nullptr || session == nullptr) { |
178 | 0 | return false; |
179 | 0 | } |
180 | | |
181 | 420 | Tox_Err_Friend_Query f_con_query_error; |
182 | 420 | const Tox_Connection f_con_status = tox_friend_get_connection_status(tox, friend_number, &f_con_query_error); |
183 | | |
184 | 420 | if (f_con_status == TOX_CONNECTION_NONE) { |
185 | | /* Friend is now offline */ |
186 | 0 | LOGGER_DEBUG(log, "Friend %u is now offline", friend_number); |
187 | |
|
188 | 0 | pthread_mutex_lock(session->mutex); |
189 | 0 | MSICall *call = get_call(session, friend_number); |
190 | |
|
191 | 0 | if (call == nullptr) { |
192 | 0 | pthread_mutex_unlock(session->mutex); |
193 | 0 | return true; |
194 | 0 | } |
195 | | |
196 | 0 | invoke_callback(log, call, MSI_ON_PEERTIMEOUT); /* Failure is ignored */ |
197 | 0 | kill_call(log, call); |
198 | 0 | pthread_mutex_unlock(session->mutex); |
199 | 0 | return true; |
200 | 0 | } |
201 | | |
202 | 420 | return false; |
203 | 420 | } |
204 | | |
205 | | int msi_invite(const Logger *log, MSISession *session, MSICall **call, uint32_t friend_number, uint8_t capabilities) |
206 | 11 | { |
207 | 11 | LOGGER_DEBUG(log, "msi_invite:session:%p", (void *)session); |
208 | | |
209 | 11 | if (session == nullptr) { |
210 | 0 | return -1; |
211 | 0 | } |
212 | | |
213 | 11 | LOGGER_DEBUG(log, "Session: %p Inviting friend: %u", (void *)session, friend_number); |
214 | | |
215 | 11 | if (pthread_mutex_trylock(session->mutex) != 0) { |
216 | 0 | LOGGER_ERROR(log, "Failed to acquire lock on msi mutex"); |
217 | 0 | return -1; |
218 | 0 | } |
219 | | |
220 | 11 | if (get_call(session, friend_number) != nullptr) { |
221 | 0 | LOGGER_ERROR(log, "Already in a call"); |
222 | 0 | pthread_mutex_unlock(session->mutex); |
223 | 0 | return -1; |
224 | 0 | } |
225 | | |
226 | 11 | MSICall *temp = new_call(session, friend_number); |
227 | | |
228 | 11 | if (temp == nullptr) { |
229 | 0 | pthread_mutex_unlock(session->mutex); |
230 | 0 | return -1; |
231 | 0 | } |
232 | | |
233 | 11 | temp->self_capabilities = capabilities; |
234 | | |
235 | 11 | MSIMessage msg; |
236 | 11 | msg_init(&msg, REQU_INIT); |
237 | | |
238 | 11 | msg.capabilities.exists = true; |
239 | 11 | msg.capabilities.value = capabilities; |
240 | | |
241 | 11 | send_message(log, temp->session->tox, temp->friend_number, &msg); |
242 | | |
243 | 11 | temp->state = MSI_CALL_REQUESTING; |
244 | | |
245 | 11 | *call = temp; |
246 | | |
247 | 11 | LOGGER_DEBUG(log, "Invite sent"); |
248 | 11 | pthread_mutex_unlock(session->mutex); |
249 | 11 | return 0; |
250 | 11 | } |
251 | | |
252 | | int msi_hangup(const Logger *log, MSICall *call) |
253 | 11 | { |
254 | 11 | if (call == nullptr || call->session == nullptr) { |
255 | 0 | return -1; |
256 | 0 | } |
257 | | |
258 | 11 | MSISession *session = call->session; |
259 | | |
260 | 11 | LOGGER_DEBUG(log, "Session: %p Hanging up call with friend: %u", (void *)call->session, |
261 | 11 | call->friend_number); |
262 | | |
263 | 11 | if (pthread_mutex_trylock(session->mutex) != 0) { |
264 | 0 | LOGGER_ERROR(log, "Failed to acquire lock on msi mutex"); |
265 | 0 | return -1; |
266 | 0 | } |
267 | | |
268 | 11 | if (call->state == MSI_CALL_INACTIVE) { |
269 | 0 | LOGGER_ERROR(log, "Call is in invalid state!"); |
270 | 0 | pthread_mutex_unlock(session->mutex); |
271 | 0 | return -1; |
272 | 0 | } |
273 | | |
274 | 11 | MSIMessage msg; |
275 | 11 | msg_init(&msg, REQU_POP); |
276 | | |
277 | 11 | send_message(log, session->tox, call->friend_number, &msg); |
278 | | |
279 | 11 | kill_call(log, call); |
280 | 11 | pthread_mutex_unlock(session->mutex); |
281 | 11 | return 0; |
282 | 11 | } |
283 | | |
284 | | int msi_answer(const Logger *log, MSICall *call, uint8_t capabilities) |
285 | 9 | { |
286 | 9 | if (call == nullptr || call->session == nullptr) { |
287 | 0 | return -1; |
288 | 0 | } |
289 | | |
290 | 9 | MSISession *session = call->session; |
291 | | |
292 | 9 | LOGGER_DEBUG(log, "Session: %p Answering call from: %u", (void *)call->session, |
293 | 9 | call->friend_number); |
294 | | |
295 | 9 | if (pthread_mutex_trylock(session->mutex) != 0) { |
296 | 0 | LOGGER_ERROR(log, "Failed to acquire lock on msi mutex"); |
297 | 0 | return -1; |
298 | 0 | } |
299 | | |
300 | 9 | if (call->state != MSI_CALL_REQUESTED) { |
301 | | /* Though sending in invalid state will not cause anything weird |
302 | | * Its better to not do it like a maniac */ |
303 | 0 | LOGGER_ERROR(log, "Call is in invalid state!"); |
304 | 0 | pthread_mutex_unlock(session->mutex); |
305 | 0 | return -1; |
306 | 0 | } |
307 | | |
308 | 9 | call->self_capabilities = capabilities; |
309 | | |
310 | 9 | MSIMessage msg; |
311 | 9 | msg_init(&msg, REQU_PUSH); |
312 | | |
313 | 9 | msg.capabilities.exists = true; |
314 | 9 | msg.capabilities.value = capabilities; |
315 | | |
316 | 9 | send_message(log, session->tox, call->friend_number, &msg); |
317 | | |
318 | 9 | call->state = MSI_CALL_ACTIVE; |
319 | 9 | pthread_mutex_unlock(session->mutex); |
320 | | |
321 | 9 | return 0; |
322 | 9 | } |
323 | | |
324 | | int msi_change_capabilities(const Logger *log, MSICall *call, uint8_t capabilities) |
325 | 13 | { |
326 | 13 | if (call == nullptr || call->session == nullptr) { |
327 | 0 | return -1; |
328 | 0 | } |
329 | | |
330 | 13 | MSISession *session = call->session; |
331 | | |
332 | 13 | LOGGER_DEBUG(log, "Session: %p Trying to change capabilities to friend %u", (void *)call->session, |
333 | 13 | call->friend_number); |
334 | | |
335 | 13 | if (pthread_mutex_trylock(session->mutex) != 0) { |
336 | 0 | LOGGER_ERROR(log, "Failed to acquire lock on msi mutex"); |
337 | 0 | return -1; |
338 | 0 | } |
339 | | |
340 | 13 | if (call->state != MSI_CALL_ACTIVE) { |
341 | 0 | LOGGER_ERROR(log, "Call is in invalid state!"); |
342 | 0 | pthread_mutex_unlock(session->mutex); |
343 | 0 | return -1; |
344 | 0 | } |
345 | | |
346 | 13 | call->self_capabilities = capabilities; |
347 | | |
348 | 13 | MSIMessage msg; |
349 | 13 | msg_init(&msg, REQU_PUSH); |
350 | | |
351 | 13 | msg.capabilities.exists = true; |
352 | 13 | msg.capabilities.value = capabilities; |
353 | | |
354 | 13 | send_message(log, call->session->tox, call->friend_number, &msg); |
355 | | |
356 | 13 | pthread_mutex_unlock(session->mutex); |
357 | 13 | return 0; |
358 | 13 | } |
359 | | |
360 | | /** |
361 | | * Private functions |
362 | | */ |
363 | | static void msg_init(MSIMessage *dest, MSIRequest request) |
364 | 44 | { |
365 | 44 | memset(dest, 0, sizeof(*dest)); |
366 | 44 | dest->request.exists = true; |
367 | 44 | dest->request.value = request; |
368 | 44 | } |
369 | | |
370 | | static bool check_size(const Logger *log, const uint8_t *bytes, int *constraint, uint8_t size) |
371 | 77 | { |
372 | 77 | *constraint -= 2 + size; |
373 | | |
374 | 77 | if (*constraint < 1) { |
375 | 0 | LOGGER_ERROR(log, "Read over length!"); |
376 | 0 | return false; |
377 | 0 | } |
378 | | |
379 | 77 | if (bytes[1] != size) { |
380 | 0 | LOGGER_ERROR(log, "Invalid data size!"); |
381 | 0 | return false; |
382 | 0 | } |
383 | | |
384 | 77 | return true; |
385 | 77 | } |
386 | | |
387 | | /** Assumes size == 1 */ |
388 | | static bool check_enum_high(const Logger *log, const uint8_t *bytes, uint8_t enum_high) |
389 | 44 | { |
390 | 44 | if (bytes[2] > enum_high) { |
391 | 0 | LOGGER_ERROR(log, "Failed enum high limit!"); |
392 | 0 | return false; |
393 | 0 | } |
394 | | |
395 | 44 | return true; |
396 | 44 | } |
397 | | |
398 | | static const uint8_t *msg_parse_one(const Logger *log, MSIMessage *dest, const uint8_t *it, int *size_constraint) |
399 | 77 | { |
400 | 77 | switch (*it) { |
401 | 44 | case ID_REQUEST: { |
402 | 44 | if (!check_size(log, it, size_constraint, 1) || |
403 | 44 | !check_enum_high(log, it, REQU_POP)) { |
404 | 0 | return nullptr; |
405 | 0 | } |
406 | | |
407 | 44 | dest->request.value = (MSIRequest)it[2]; |
408 | 44 | dest->request.exists = true; |
409 | 44 | return it + 3; |
410 | 44 | } |
411 | | |
412 | 0 | case ID_ERROR: { |
413 | 0 | if (!check_size(log, it, size_constraint, 1) || |
414 | 0 | !check_enum_high(log, it, MSI_E_UNDISCLOSED)) { |
415 | 0 | return nullptr; |
416 | 0 | } |
417 | | |
418 | 0 | dest->error.value = (MSIError)it[2]; |
419 | 0 | dest->error.exists = true; |
420 | 0 | return it + 3; |
421 | 0 | } |
422 | | |
423 | 33 | case ID_CAPABILITIES: { |
424 | 33 | if (!check_size(log, it, size_constraint, 1)) { |
425 | 0 | return nullptr; |
426 | 0 | } |
427 | | |
428 | 33 | dest->capabilities.value = it[2]; |
429 | 33 | dest->capabilities.exists = true; |
430 | 33 | return it + 3; |
431 | 33 | } |
432 | | |
433 | 0 | default: { |
434 | 0 | LOGGER_ERROR(log, "Invalid id byte: %d", *it); |
435 | 0 | return nullptr; |
436 | 33 | } |
437 | 77 | } |
438 | 77 | } |
439 | | |
440 | | static int msg_parse_in(const Logger *log, MSIMessage *dest, const uint8_t *data, uint16_t length) |
441 | 44 | { |
442 | | /* Parse raw data received from socket into MSIMessage struct */ |
443 | 44 | assert(dest != nullptr); |
444 | | |
445 | 44 | if (length == 0 || data[length - 1] != 0) { /* End byte must have value 0 */ |
446 | 0 | LOGGER_ERROR(log, "Invalid end byte"); |
447 | 0 | return -1; |
448 | 0 | } |
449 | | |
450 | 44 | memset(dest, 0, sizeof(*dest)); |
451 | | |
452 | 44 | const uint8_t *it = data; |
453 | 44 | int size_constraint = length; |
454 | | |
455 | 121 | while (*it != 0) {/* until end byte is hit */ |
456 | 77 | it = msg_parse_one(log, dest, it, &size_constraint); |
457 | | |
458 | 77 | if (it == nullptr) { |
459 | 0 | return -1; |
460 | 0 | } |
461 | 77 | } |
462 | | |
463 | 44 | if (!dest->request.exists) { |
464 | 0 | LOGGER_ERROR(log, "Invalid request field!"); |
465 | 0 | return -1; |
466 | 0 | } |
467 | | |
468 | 44 | return 0; |
469 | 44 | } |
470 | | |
471 | | static uint8_t *msg_parse_header_out(MSIHeaderID id, uint8_t *dest, const uint8_t *value, uint8_t value_len, |
472 | | uint16_t *length) |
473 | 77 | { |
474 | | /* Parse a single header for sending */ |
475 | 77 | assert(dest != nullptr); |
476 | 77 | assert(value != nullptr); |
477 | 77 | assert(value_len != 0); |
478 | | |
479 | 77 | *dest = id; |
480 | 77 | ++dest; |
481 | 77 | *dest = value_len; |
482 | 77 | ++dest; |
483 | | |
484 | 77 | memcpy(dest, value, value_len); |
485 | | |
486 | 77 | *length += 2 + value_len; |
487 | | |
488 | 77 | return dest + value_len; /* Set to next position ready to be written */ |
489 | 77 | } |
490 | | |
491 | | /* Send an msi packet. |
492 | | * |
493 | | * return 1 on success |
494 | | * return 0 on failure |
495 | | */ |
496 | | static int m_msi_packet(Tox *tox, int32_t friendnumber, const uint8_t *data, uint16_t length) |
497 | 44 | { |
498 | | // TODO(Zoff): make this better later! ------------------- |
499 | | /* we need to prepend 1 byte (packet id) to data |
500 | | * do this without malloc, memcpy and free in the future |
501 | | */ |
502 | 44 | const size_t length_new = (size_t)length + 1; |
503 | 44 | uint8_t *data_new = (uint8_t *)malloc(length_new); |
504 | | |
505 | 44 | if (data_new == nullptr) { |
506 | 0 | return 0; |
507 | 0 | } |
508 | | |
509 | 44 | data_new[0] = PACKET_ID_MSI; |
510 | | |
511 | 44 | if (length != 0) { |
512 | 44 | memcpy(data_new + 1, data, length); |
513 | 44 | } |
514 | | |
515 | 44 | Tox_Err_Friend_Custom_Packet error; |
516 | 44 | tox_friend_send_lossless_packet(tox, friendnumber, data_new, length_new, &error); |
517 | | |
518 | 44 | free(data_new); |
519 | | |
520 | 44 | if (error == TOX_ERR_FRIEND_CUSTOM_PACKET_OK) { |
521 | 44 | return 1; |
522 | 44 | } |
523 | | |
524 | 0 | return 0; |
525 | 44 | } |
526 | | |
527 | | static int send_message(const Logger *log, Tox *tox, uint32_t friend_number, const MSIMessage *msg) |
528 | 44 | { |
529 | 44 | assert(tox != nullptr); |
530 | | |
531 | | /* Parse and send message */ |
532 | 44 | uint8_t parsed[MSI_MAXMSG_SIZE]; |
533 | | |
534 | 44 | uint8_t *it = parsed; |
535 | 44 | uint16_t size = 0; |
536 | | |
537 | 44 | if (msg->request.exists) { |
538 | 44 | uint8_t cast = msg->request.value; |
539 | 44 | it = msg_parse_header_out(ID_REQUEST, it, &cast, |
540 | 44 | sizeof(cast), &size); |
541 | 44 | } else { |
542 | 0 | LOGGER_DEBUG(log, "Must have request field"); |
543 | 0 | return -1; |
544 | 0 | } |
545 | | |
546 | 44 | if (msg->error.exists) { |
547 | 0 | uint8_t cast = msg->error.value; |
548 | 0 | it = msg_parse_header_out(ID_ERROR, it, &cast, |
549 | 0 | sizeof(cast), &size); |
550 | 0 | } |
551 | | |
552 | 44 | if (msg->capabilities.exists) { |
553 | 33 | it = msg_parse_header_out(ID_CAPABILITIES, it, &msg->capabilities.value, |
554 | 33 | sizeof(msg->capabilities.value), &size); |
555 | 33 | } |
556 | | |
557 | 44 | if (it == parsed) { |
558 | 0 | LOGGER_WARNING(log, "Parsing message failed; empty message"); |
559 | 0 | return -1; |
560 | 0 | } |
561 | | |
562 | 44 | *it = 0; |
563 | 44 | ++size; |
564 | | |
565 | 44 | if (m_msi_packet(tox, friend_number, parsed, size) == 1) { |
566 | 44 | LOGGER_DEBUG(log, "Sent message"); |
567 | 44 | return 0; |
568 | 44 | } |
569 | | |
570 | 0 | return -1; |
571 | 44 | } |
572 | | |
573 | | static int send_error(const Logger *log, Tox *tox, uint32_t friend_number, MSIError error) |
574 | 0 | { |
575 | 0 | assert(tox != nullptr); |
576 | | |
577 | | /* Send error message */ |
578 | 0 | LOGGER_DEBUG(log, "Sending error: %u to friend: %u", error, friend_number); |
579 | |
|
580 | 0 | MSIMessage msg; |
581 | 0 | msg_init(&msg, REQU_POP); |
582 | |
|
583 | 0 | msg.error.exists = true; |
584 | 0 | msg.error.value = error; |
585 | |
|
586 | 0 | send_message(log, tox, friend_number, &msg); |
587 | 0 | return 0; |
588 | 0 | } |
589 | | |
590 | | static int invoke_callback_inner(const Logger *log, MSICall *call, MSICallbackID id) |
591 | 44 | { |
592 | 44 | MSISession *session = call->session; |
593 | 44 | LOGGER_DEBUG(log, "invoking callback function: %u", id); |
594 | | |
595 | 44 | switch (id) { |
596 | 11 | case MSI_ON_INVITE: |
597 | 11 | return session->invite_callback(session->av, call); |
598 | | |
599 | 9 | case MSI_ON_START: |
600 | 9 | return session->start_callback(session->av, call); |
601 | | |
602 | 11 | case MSI_ON_END: |
603 | 11 | return session->end_callback(session->av, call); |
604 | | |
605 | 0 | case MSI_ON_ERROR: |
606 | 0 | return session->error_callback(session->av, call); |
607 | | |
608 | 0 | case MSI_ON_PEERTIMEOUT: |
609 | 0 | return session->peertimeout_callback(session->av, call); |
610 | | |
611 | 13 | case MSI_ON_CAPABILITIES: |
612 | 13 | return session->capabilities_callback(session->av, call); |
613 | 44 | } |
614 | | |
615 | 0 | LOGGER_FATAL(log, "invalid callback id: %u", id); |
616 | 0 | return -1; |
617 | 44 | } |
618 | | |
619 | | static bool invoke_callback(const Logger *log, MSICall *call, MSICallbackID cb) |
620 | 44 | { |
621 | 44 | assert(call != nullptr); |
622 | | |
623 | 44 | if (invoke_callback_inner(log, call, cb) != 0) { |
624 | 0 | LOGGER_WARNING(log, |
625 | 0 | "Callback state handling failed, sending error"); |
626 | | |
627 | | /* If no callback present or error happened while handling, |
628 | | * an error message will be sent to friend |
629 | | */ |
630 | 0 | if (call->error == MSI_E_NONE) { |
631 | 0 | call->error = MSI_E_HANDLE; |
632 | 0 | } |
633 | |
|
634 | 0 | return false; |
635 | 0 | } |
636 | | |
637 | 44 | return true; |
638 | 44 | } |
639 | | |
640 | | static MSICall *get_call(MSISession *session, uint32_t friend_number) |
641 | 55 | { |
642 | 55 | assert(session != nullptr); |
643 | | |
644 | 55 | if (session->calls == nullptr || session->calls_tail < friend_number) { |
645 | 22 | return nullptr; |
646 | 22 | } |
647 | | |
648 | 33 | return session->calls[friend_number]; |
649 | 55 | } |
650 | | |
651 | | static MSICall *new_call(MSISession *session, uint32_t friend_number) |
652 | 22 | { |
653 | 22 | assert(session != nullptr); |
654 | | |
655 | 22 | MSICall *rc = (MSICall *)calloc(1, sizeof(MSICall)); |
656 | | |
657 | 22 | if (rc == nullptr) { |
658 | 0 | return nullptr; |
659 | 0 | } |
660 | | |
661 | 22 | rc->session = session; |
662 | 22 | rc->friend_number = friend_number; |
663 | | |
664 | 22 | if (session->calls == nullptr) { /* Creating */ |
665 | 20 | session->calls = (MSICall **)calloc(friend_number + 1, sizeof(MSICall *)); |
666 | | |
667 | 20 | if (session->calls == nullptr) { |
668 | 0 | free(rc); |
669 | 0 | return nullptr; |
670 | 0 | } |
671 | | |
672 | 20 | session->calls_tail = friend_number; |
673 | 20 | session->calls_head = friend_number; |
674 | 20 | } else if (session->calls_tail < friend_number) { /* Appending */ |
675 | 2 | MSICall **tmp = (MSICall **)realloc(session->calls, (friend_number + 1) * sizeof(MSICall *)); |
676 | | |
677 | 2 | if (tmp == nullptr) { |
678 | 0 | free(rc); |
679 | 0 | return nullptr; |
680 | 0 | } |
681 | | |
682 | 2 | session->calls = tmp; |
683 | | |
684 | | /* Set fields in between to null */ |
685 | 2 | for (uint32_t i = session->calls_tail + 1; i < friend_number; ++i) { |
686 | 0 | session->calls[i] = nullptr; |
687 | 0 | } |
688 | | |
689 | 2 | rc->prev = session->calls[session->calls_tail]; |
690 | 2 | session->calls[session->calls_tail]->next = rc; |
691 | | |
692 | 2 | session->calls_tail = friend_number; |
693 | 2 | } else if (session->calls_head > friend_number) { /* Inserting at front */ |
694 | 0 | rc->next = session->calls[session->calls_head]; |
695 | 0 | session->calls[session->calls_head]->prev = rc; |
696 | 0 | session->calls_head = friend_number; |
697 | 0 | } |
698 | | |
699 | 22 | session->calls[friend_number] = rc; |
700 | 22 | return rc; |
701 | 22 | } |
702 | | |
703 | | static void kill_call(const Logger *log, MSICall *call) |
704 | 22 | { |
705 | | /* Assume that session mutex is locked */ |
706 | 22 | if (call == nullptr) { |
707 | 0 | return; |
708 | 0 | } |
709 | | |
710 | 22 | MSISession *session = call->session; |
711 | | |
712 | 22 | LOGGER_DEBUG(log, "Killing call: %p", (void *)call); |
713 | | |
714 | 22 | MSICall *prev = call->prev; |
715 | 22 | MSICall *next = call->next; |
716 | | |
717 | 22 | if (prev != nullptr) { |
718 | 0 | prev->next = next; |
719 | 22 | } else if (next != nullptr) { |
720 | 2 | session->calls_head = next->friend_number; |
721 | 20 | } else { |
722 | 20 | goto CLEAR_CONTAINER; |
723 | 20 | } |
724 | | |
725 | 2 | if (next != nullptr) { |
726 | 2 | next->prev = prev; |
727 | 2 | } else if (prev != nullptr) { |
728 | 0 | session->calls_tail = prev->friend_number; |
729 | 0 | } else { |
730 | 0 | goto CLEAR_CONTAINER; |
731 | 0 | } |
732 | | |
733 | 2 | session->calls[call->friend_number] = nullptr; |
734 | 2 | free(call); |
735 | 2 | return; |
736 | | |
737 | 20 | CLEAR_CONTAINER: |
738 | 20 | session->calls_head = 0; |
739 | 20 | session->calls_tail = 0; |
740 | 20 | free(session->calls); |
741 | 20 | free(call); |
742 | 20 | session->calls = nullptr; |
743 | 20 | } |
744 | | |
745 | | |
746 | | static bool try_handle_init(const Logger *log, MSICall *call, const MSIMessage *msg) |
747 | 11 | { |
748 | 11 | if (!msg->capabilities.exists) { |
749 | 0 | LOGGER_WARNING(log, "Session: %p Invalid capabilities on 'init'", (void *)call->session); |
750 | 0 | call->error = MSI_E_INVALID_MESSAGE; |
751 | 0 | return false; |
752 | 0 | } |
753 | | |
754 | 11 | switch (call->state) { |
755 | 11 | case MSI_CALL_INACTIVE: { |
756 | | /* Call requested */ |
757 | 11 | call->peer_capabilities = msg->capabilities.value; |
758 | 11 | call->state = MSI_CALL_REQUESTED; |
759 | | |
760 | 11 | if (!invoke_callback(log, call, MSI_ON_INVITE)) { |
761 | 0 | return false; |
762 | 0 | } |
763 | | |
764 | 11 | break; |
765 | 11 | } |
766 | | |
767 | 11 | case MSI_CALL_ACTIVE: { |
768 | | /* If peer sent init while the call is already |
769 | | * active it's probable that he is trying to |
770 | | * re-call us while the call is not terminated |
771 | | * on our side. We can assume that in this case |
772 | | * we can automatically answer the re-call. |
773 | | */ |
774 | |
|
775 | 0 | LOGGER_INFO(log, "Friend is recalling us"); |
776 | |
|
777 | 0 | MSIMessage out_msg; |
778 | 0 | msg_init(&out_msg, REQU_PUSH); |
779 | |
|
780 | 0 | out_msg.capabilities.exists = true; |
781 | 0 | out_msg.capabilities.value = call->self_capabilities; |
782 | |
|
783 | 0 | send_message(log, call->session->tox, call->friend_number, &out_msg); |
784 | | |
785 | | /* If peer changed capabilities during re-call they will |
786 | | * be handled accordingly during the next step |
787 | | */ |
788 | 0 | break; |
789 | 11 | } |
790 | | |
791 | 0 | case MSI_CALL_REQUESTED: // fall-through |
792 | 0 | case MSI_CALL_REQUESTING: { |
793 | 0 | LOGGER_WARNING(log, "Session: %p Invalid state on 'init'", (void *)call->session); |
794 | 0 | call->error = MSI_E_INVALID_STATE; |
795 | 0 | return false; |
796 | 0 | } |
797 | 11 | } |
798 | | |
799 | 11 | return true; |
800 | 11 | } |
801 | | |
802 | | static void handle_init(const Logger *log, MSICall *call, const MSIMessage *msg) |
803 | 11 | { |
804 | 11 | assert(call != nullptr); |
805 | 11 | LOGGER_DEBUG(log, |
806 | 11 | "Session: %p Handling 'init' friend: %u", (void *)call->session, call->friend_number); |
807 | | |
808 | 11 | if (!try_handle_init(log, call, msg)) { |
809 | 0 | send_error(log, call->session->tox, call->friend_number, call->error); |
810 | 0 | kill_call(log, call); |
811 | 0 | } |
812 | 11 | } |
813 | | |
814 | | static void handle_push(const Logger *log, MSICall *call, const MSIMessage *msg) |
815 | 22 | { |
816 | 22 | assert(call != nullptr); |
817 | | |
818 | 22 | LOGGER_DEBUG(log, "Session: %p Handling 'push' friend: %u", (void *)call->session, |
819 | 22 | call->friend_number); |
820 | | |
821 | 22 | if (!msg->capabilities.exists) { |
822 | 0 | LOGGER_WARNING(log, "Session: %p Invalid capabilities on 'push'", (void *)call->session); |
823 | 0 | call->error = MSI_E_INVALID_MESSAGE; |
824 | 0 | goto FAILURE; |
825 | 0 | } |
826 | | |
827 | 22 | switch (call->state) { |
828 | 13 | case MSI_CALL_ACTIVE: { |
829 | 13 | if (call->peer_capabilities != msg->capabilities.value) { |
830 | 13 | LOGGER_INFO(log, "Friend is changing capabilities to: %u", msg->capabilities.value); |
831 | | |
832 | 13 | call->peer_capabilities = msg->capabilities.value; |
833 | | |
834 | 13 | if (!invoke_callback(log, call, MSI_ON_CAPABILITIES)) { |
835 | 0 | goto FAILURE; |
836 | 0 | } |
837 | 13 | } |
838 | | |
839 | 13 | break; |
840 | 13 | } |
841 | | |
842 | 13 | case MSI_CALL_REQUESTING: { |
843 | 9 | LOGGER_INFO(log, "Friend answered our call"); |
844 | | |
845 | | /* Call started */ |
846 | 9 | call->peer_capabilities = msg->capabilities.value; |
847 | 9 | call->state = MSI_CALL_ACTIVE; |
848 | | |
849 | 9 | if (!invoke_callback(log, call, MSI_ON_START)) { |
850 | 0 | goto FAILURE; |
851 | 0 | } |
852 | | |
853 | 9 | break; |
854 | 9 | } |
855 | | |
856 | 9 | case MSI_CALL_INACTIVE: // fall-through |
857 | 0 | case MSI_CALL_REQUESTED: { |
858 | 0 | LOGGER_WARNING(log, "Ignoring invalid push"); |
859 | 0 | break; |
860 | 0 | } |
861 | 22 | } |
862 | | |
863 | 22 | return; |
864 | | |
865 | 22 | FAILURE: |
866 | 0 | send_error(log, call->session->tox, call->friend_number, call->error); |
867 | 0 | kill_call(log, call); |
868 | 0 | } |
869 | | |
870 | | static void handle_pop(const Logger *log, MSICall *call, const MSIMessage *msg) |
871 | 11 | { |
872 | 11 | assert(call != nullptr); |
873 | | |
874 | 11 | LOGGER_DEBUG(log, "Session: %p Handling 'pop', friend id: %u", (void *)call->session, |
875 | 11 | call->friend_number); |
876 | | |
877 | | /* callback errors are ignored */ |
878 | | |
879 | 11 | if (msg->error.exists) { |
880 | 0 | LOGGER_WARNING(log, "Friend detected an error: %u", msg->error.value); |
881 | 0 | call->error = msg->error.value; |
882 | 0 | invoke_callback(log, call, MSI_ON_ERROR); |
883 | 11 | } else { |
884 | 11 | switch (call->state) { |
885 | 0 | case MSI_CALL_INACTIVE: { |
886 | 0 | LOGGER_FATAL(log, "Handling what should be impossible case"); |
887 | 0 | break; |
888 | 0 | } |
889 | | |
890 | 9 | case MSI_CALL_ACTIVE: { |
891 | | /* Hangup */ |
892 | 9 | LOGGER_INFO(log, "Friend hung up on us"); |
893 | 9 | invoke_callback(log, call, MSI_ON_END); |
894 | 9 | break; |
895 | 0 | } |
896 | | |
897 | 1 | case MSI_CALL_REQUESTING: { |
898 | | /* Reject */ |
899 | 1 | LOGGER_INFO(log, "Friend rejected our call"); |
900 | 1 | invoke_callback(log, call, MSI_ON_END); |
901 | 1 | break; |
902 | 0 | } |
903 | | |
904 | 1 | case MSI_CALL_REQUESTED: { |
905 | | /* Cancel */ |
906 | 1 | LOGGER_INFO(log, "Friend canceled call invite"); |
907 | 1 | invoke_callback(log, call, MSI_ON_END); |
908 | 1 | break; |
909 | 0 | } |
910 | 11 | } |
911 | 11 | } |
912 | | |
913 | 11 | kill_call(log, call); |
914 | 11 | } |
915 | | |
916 | | static void handle_msi_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, |
917 | | void *user_data) |
918 | 44 | { |
919 | 44 | const ToxAV *toxav = (ToxAV *)tox_get_av_object(tox); |
920 | | |
921 | 44 | if (toxav == nullptr) { |
922 | 0 | return; |
923 | 0 | } |
924 | | |
925 | 44 | const Logger *log = toxav_get_logger(toxav); |
926 | | |
927 | 44 | if (length < 2) { |
928 | 0 | LOGGER_ERROR(log, "MSI packet is less than 2 bytes in size"); |
929 | | // we need more than the ID byte for MSI messages |
930 | 0 | return; |
931 | 0 | } |
932 | | |
933 | 44 | const uint16_t payload_length = (uint16_t)(length - 1); |
934 | | |
935 | | // Zoff: do not show the first byte, its always "PACKET_ID_MSI" |
936 | 44 | const uint8_t *data_strip_id_byte = data + 1; |
937 | | |
938 | 44 | LOGGER_DEBUG(log, "Got msi message"); |
939 | | |
940 | 44 | MSISession *session = tox_av_msi_get(toxav); |
941 | | |
942 | 44 | if (session == nullptr) { |
943 | 0 | return; |
944 | 0 | } |
945 | | |
946 | 44 | MSIMessage msg; |
947 | | |
948 | 44 | if (msg_parse_in(log, &msg, data_strip_id_byte, payload_length) == -1) { |
949 | 0 | LOGGER_WARNING(log, "Error parsing message"); |
950 | 0 | send_error(log, tox, friend_number, MSI_E_INVALID_MESSAGE); |
951 | 0 | return; |
952 | 0 | } |
953 | | |
954 | 44 | LOGGER_DEBUG(log, "Successfully parsed message"); |
955 | | |
956 | 44 | pthread_mutex_lock(session->mutex); |
957 | 44 | MSICall *call = get_call(session, friend_number); |
958 | | |
959 | 44 | if (call == nullptr) { |
960 | 11 | if (msg.request.value != REQU_INIT) { |
961 | 0 | send_error(log, tox, friend_number, MSI_E_STRAY_MESSAGE); |
962 | 0 | pthread_mutex_unlock(session->mutex); |
963 | 0 | return; |
964 | 0 | } |
965 | | |
966 | 11 | call = new_call(session, friend_number); |
967 | | |
968 | 11 | if (call == nullptr) { |
969 | 0 | send_error(log, tox, friend_number, MSI_E_SYSTEM); |
970 | 0 | pthread_mutex_unlock(session->mutex); |
971 | 0 | return; |
972 | 0 | } |
973 | 11 | } |
974 | | |
975 | 44 | switch (msg.request.value) { |
976 | 11 | case REQU_INIT: { |
977 | 11 | handle_init(log, call, &msg); |
978 | 11 | break; |
979 | 0 | } |
980 | | |
981 | 22 | case REQU_PUSH: { |
982 | 22 | handle_push(log, call, &msg); |
983 | 22 | break; |
984 | 0 | } |
985 | | |
986 | 11 | case REQU_POP: { |
987 | 11 | handle_pop(log, call, &msg); /* always kills the call */ |
988 | 11 | break; |
989 | 0 | } |
990 | 44 | } |
991 | | |
992 | 44 | pthread_mutex_unlock(session->mutex); |
993 | 44 | } |