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