Coverage Report

Created: 2026-10-24 08:16

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