Coverage Report

Created: 2025-10-08 19:34

/work/toxav/msi.c
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
}