Coverage Report

Created: 2025-10-13 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/rtpproxy/modules/catch_dtmf/rtpp_catch_dtmf.c
Line
Count
Source
1
/*
2
 * Copyright (c) 2019-2020 Sippy Software, Inc., http://www.sippysoft.com
3
 * Copyright (c) 2019 Maxim Sobolev <sobomax@sippysoft.com>
4
 * Copyright (c) 2019 Razvan Crainea <razvan@opensips.org>
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions
8
 * are met:
9
 * 1. Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 * 2. Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25
 * SUCH DAMAGE.
26
 *
27
 */
28
29
#include <sys/types.h>
30
#include <sys/socket.h>
31
#include <arpa/inet.h>
32
#include <stdatomic.h>
33
#include <stddef.h>
34
#include <stdint.h>
35
#include <stdlib.h>
36
#include <stdio.h>
37
#include <string.h>
38
39
#include "config_pp.h"
40
41
#include "rtpp_types.h"
42
#include "rtpp_debug.h"
43
#include "rtpp_module.h"
44
#include "rtpp_module_wthr.h"
45
#include "rtpp_module_cplane.h"
46
#include "rtpp_log.h"
47
#include "rtpp_log_obj.h"
48
#include "rtpp_codeptr.h"
49
#include "rtpp_refcnt.h"
50
#include "rtpp_cfg.h"
51
#include "rtpp_wi.h"
52
#include "rtpp_wi_sgnl.h"
53
#include "rtpp_wi_data.h"
54
#include "rtpp_queue.h"
55
#include "rtpp_stream.h"
56
#include "rtp.h"
57
#include "rtpp_time.h"
58
#include "rtp_packet.h"
59
#include "rtpp_notify.h"
60
#include "rtpp_timeout_data.h"
61
#include "rtpp_command_args.h"
62
#include "rtpp_command_sub.h"
63
#include "rtpp_util.h"
64
#include "rtpp_session.h"
65
#include "rtpp_stream.h"
66
#include "rtpp_pipe.h"
67
#include "rtpp_linker_set.h"
68
#include "advanced/packet_processor.h"
69
#include "advanced/pproc_manager.h"
70
71
struct rtpp_module_priv {
72
    struct rtpp_notify *notifier;
73
    struct rtpp_minfo *mself;
74
};
75
76
struct catch_dtmf_einfo {
77
    int pending;
78
    int digit;
79
    uint32_t ts;
80
    uint16_t duration;
81
};
82
83
1.13k
#define EINFO_HST_DPTH 4
84
85
struct catch_dtmf_edata {
86
    struct rtpp_refcnt *rcnt;
87
    struct catch_dtmf_einfo hst[EINFO_HST_DPTH];
88
    int hst_next;
89
    enum rtpp_stream_side side;
90
};
91
92
struct catch_dtmf_stream_cfg {
93
    struct rtpp_refcnt *rcnt;
94
    _Atomic(int) pt;
95
    _Atomic(enum pproc_action) act;
96
    struct catch_dtmf_edata *edata;
97
    const struct rtpp_timeout_data *rtdp;
98
    struct rtpp_minfo *mself;
99
};
100
101
static struct rtpp_module_priv *rtpp_catch_dtmf_ctor(const struct rtpp_cfg *,
102
  struct rtpp_minfo *);
103
static void rtpp_catch_dtmf_dtor(struct rtpp_module_priv *);
104
static void rtpp_catch_dtmf_worker(const struct rtpp_wthrdata *);
105
static int rtpp_catch_dtmf_handle_command(struct rtpp_module_priv *,
106
  const struct rtpp_subc_ctx *);
107
static int rtp_packet_is_dtmf(struct pkt_proc_ctx *);
108
static struct pproc_act rtpp_catch_dtmf_enqueue(const struct pkt_proc_ctx *);
109
110
#ifdef RTPP_CHECK_LEAKS
111
#include "rtpp_memdeb_internal.h"
112
113
RTPP_MEMDEB_APP_STATIC;
114
#endif
115
116
const struct rtpp_minfo RTPP_MOD_SELF = {
117
    .descr.name = "catch_dtmf",
118
    .descr.ver = MI_VER_INIT(),
119
    .descr.module_id = 3,
120
    .proc.ctor = rtpp_catch_dtmf_ctor,
121
    .proc.dtor = rtpp_catch_dtmf_dtor,
122
    .wapi = &(const struct rtpp_wthr_handlers){
123
        .main_thread = rtpp_catch_dtmf_worker,
124
        .queue_size = RTPQ_MEDIUM_CB_LEN,
125
    },
126
    .capi = &(const struct rtpp_cplane_handlers){.ul_subc_handle = rtpp_catch_dtmf_handle_command},
127
    .fn = &(struct rtpp_minfo_fset){0},
128
#ifdef RTPP_CHECK_LEAKS
129
    .memdeb_p = &MEMDEB_SYM
130
#endif
131
};
132
#if defined(LIBRTPPROXY)
133
DATA_SET(rtpp_modules, RTPP_MOD_SELF);
134
#endif
135
136
static struct catch_dtmf_edata *
137
rtpp_catch_dtmf_edata_ctor(enum rtpp_stream_side side)
138
226
{
139
226
    struct catch_dtmf_edata *edata;
140
226
    int i;
141
142
226
    edata = mod_rzmalloc(sizeof(*edata), offsetof(struct catch_dtmf_edata, rcnt));
143
226
    if (edata == NULL) {
144
0
        goto e0;
145
0
    }
146
1.13k
    for (i = 0; i < EINFO_HST_DPTH; i++) {
147
904
        edata->hst[i].digit = -1;
148
904
    }
149
226
    edata->side = side;
150
226
    return edata;
151
0
e0:
152
0
    return (NULL);
153
226
}
154
155
struct wipkt {
156
    const struct rtp_packet *pkt;
157
    struct catch_dtmf_edata *edata;
158
    const struct rtpp_timeout_data *rtdp;
159
};
160
161
struct rtp_dtmf_event {
162
    unsigned int event:8;        /* event_id - digit */
163
#if BYTE_ORDER == BIG_ENDIAN
164
    unsigned int end:1;            /* indicates the end of the event */
165
    unsigned int res:1;            /* reserved - should be 0 */
166
    unsigned int volume:6;        /* volume */
167
#else
168
    unsigned int volume:6;        /* volume */
169
    unsigned int res:1;            /* reserved - should be 0 */
170
    unsigned int end:1;            /* indicates the end of the event */
171
#endif
172
    unsigned int duration:16;    /* duration */
173
} __attribute__((__packed__));
174
175
0
#define RTPP_MAX_NOTIFY_BUF 512
176
static const char *notyfy_type = "DTMF";
177
178
static void
179
rtpp_catch_dtmf_worker(const struct rtpp_wthrdata *wp)
180
4
{
181
4
    struct rtpp_module_priv *pvt;
182
4
    struct rtpp_wi *wi;
183
4
    struct wipkt *wip;
184
4
    char buf[RTPP_MAX_NOTIFY_BUF];
185
4
    const char dtmf_events[] = "0123456789*#ABCD ";
186
4
    struct catch_dtmf_einfo *eip, ei;
187
4
    int i;
188
189
4
    pvt = wp->mpvt;
190
4
    for (;;) {
191
4
        wi = rtpp_queue_get_item(wp->mod_q, 0);
192
4
        if (wi == wp->sigterm) {
193
4
            RTPP_OBJ_DECREF(wi);
194
4
            break;
195
4
        }
196
0
        wip = rtpp_wi_data_get_ptr(wi, sizeof(*wip), sizeof(*wip));
197
198
0
        struct rtp_dtmf_event *dtmf =
199
0
            (struct rtp_dtmf_event *)(wip->pkt->data.buf + sizeof(rtp_hdr_t));
200
0
        if (dtmf->event > sizeof(dtmf_events) - 1) {
201
0
            RTPP_LOG(pvt->mself->log, RTPP_LOG_DBUG, "Unhandled DTMF event %u", dtmf->event);
202
0
            goto skip;
203
0
        }
204
0
        ei.digit = dtmf_events[dtmf->event];
205
0
        ei.ts = ntohl(wip->pkt->data.header.ts);
206
0
        ei.duration = ntohs(dtmf->duration);
207
0
        eip = NULL;
208
0
        for (i = 1; i <= EINFO_HST_DPTH; i++) {
209
0
            int j = wip->edata->hst_next - i;
210
0
            if (j < 0)
211
0
                j = EINFO_HST_DPTH + j;
212
0
            if (wip->edata->hst[j].ts == ei.ts && wip->edata->hst[j].digit != -1) {
213
0
                eip = &wip->edata->hst[j];
214
0
                break;
215
0
            }
216
0
        }
217
218
0
        if (eip == NULL) {
219
            /* this is a new event */
220
0
            eip = &wip->edata->hst[wip->edata->hst_next];
221
0
            eip->ts = ei.ts;
222
0
            eip->pending = 1;
223
0
            eip->digit = ei.digit;
224
0
            eip->duration = ei.duration;
225
0
            wip->edata->hst_next += 1;
226
0
            if (wip->edata->hst_next == EINFO_HST_DPTH)
227
0
                wip->edata->hst_next = 0;
228
0
            goto skip;
229
0
        }
230
0
        if (!eip->pending) {
231
0
            if (!dtmf->end && eip->duration <= ei.duration)
232
0
                RTPP_LOG(pvt->mself->log, RTPP_LOG_WARN, "Received DTMF for %c without "
233
0
                        "start %d", ei.digit, eip->pending);
234
0
            goto skip;
235
0
        }
236
237
0
        if (ei.digit != eip->digit) {
238
0
            RTPP_LOG(pvt->mself->log, RTPP_LOG_WARN, "Received DTMF for %c "
239
0
                    "while processing %c", ei.digit, eip->digit);
240
0
            goto skip;
241
0
        }
242
0
        if (eip->duration < ei.duration)
243
0
            eip->duration = ei.duration;
244
245
0
        if (!dtmf->end)
246
0
            goto skip;
247
        /* we received the end of the DTMF */
248
        /* all good - send the notification */
249
0
        eip->pending = 0;
250
0
        rtpp_str_const_t notify_tag = {.s = buf};
251
0
        notify_tag.len = snprintf(buf, RTPP_MAX_NOTIFY_BUF, "%.*s %c %u %u %d",
252
0
          FMTSTR(wip->rtdp->notify_tag), ei.digit, dtmf->volume, eip->duration,
253
0
          (wip->edata->side == RTPP_SSIDE_CALLER) ? 0 : 1);
254
0
        CALL_METHOD(pvt->notifier, schedule, wip->rtdp->notify_target,
255
0
          rtpp_str_fix(&notify_tag), notyfy_type);
256
257
0
skip:
258
0
        RTPP_OBJ_DECREF(wi);
259
0
    }
260
4
}
261
262
static struct catch_dtmf_stream_cfg *
263
catch_dtmf_data_ctor(const struct rtpp_subc_ctx *ctxp, const rtpp_str_t *dtmf_tag,
264
  int new_pt, struct rtpp_minfo *mself)
265
226
{
266
226
    struct catch_dtmf_stream_cfg *rtps_c;
267
268
226
    rtps_c = mod_rzmalloc(sizeof(*rtps_c), offsetof(struct catch_dtmf_stream_cfg, rcnt));
269
226
    if (rtps_c == NULL) {
270
0
        goto e0;
271
0
    }
272
226
    rtps_c->mself = mself;
273
226
    RC_INCREF(mself->super_rcnt);
274
226
    RTPP_OBJ_DTOR_ATTACH_RC(rtps_c, mself->super_rcnt);
275
226
    rtps_c->edata = rtpp_catch_dtmf_edata_ctor(ctxp->strmp_in->side);
276
226
    if (!rtps_c->edata) {
277
0
        RTPP_LOG(mself->log, RTPP_LOG_ERR, "cannot create edata (sp=%p)",
278
0
          ctxp->strmp_in);
279
0
        goto e1;
280
0
    }
281
226
    RTPP_OBJ_DTOR_ATTACH_RC(rtps_c, rtps_c->edata->rcnt);
282
226
    rtps_c->rtdp = rtpp_timeout_data_ctor(ctxp->sessp->timeout_data->notify_target,
283
226
      dtmf_tag);
284
226
    if (rtps_c->rtdp == NULL) {
285
0
        goto e1;
286
0
    }
287
226
    RTPP_OBJ_DTOR_ATTACH_RC(rtps_c, rtps_c->rtdp->rcnt);
288
226
    atomic_init(&(rtps_c->pt), new_pt);
289
226
    atomic_init(&(rtps_c->act), PPROC_ACT_TEE_v);
290
226
    return (rtps_c);
291
0
e1:
292
0
    RTPP_OBJ_DECREF(rtps_c);
293
0
e0:
294
0
    return (NULL);
295
0
}
296
297
static int
298
rtpp_catch_dtmf_handle_command(struct rtpp_module_priv *pvt,
299
  const struct rtpp_subc_ctx *ctxp)
300
3.19k
{
301
3.19k
    struct catch_dtmf_stream_cfg *rtps_c;
302
3.19k
    int len;
303
3.19k
    int old_pt, new_pt = 101;
304
3.19k
    enum pproc_action old_act, new_act = PPROC_ACT_TEE_v;
305
3.19k
    rtpp_str_const_t dtmf_tag;
306
307
3.19k
    if (ctxp->sessp->timeout_data == NULL) {
308
203
        RTPP_LOG(pvt->mself->log, RTPP_LOG_ERR, "notification is not enabled (sp=%p)",
309
203
          ctxp->sessp);
310
203
        return (-1);
311
203
    }
312
2.98k
    if (ctxp->subc_args->c < 2) {
313
334
        RTPP_LOG(pvt->mself->log, RTPP_LOG_DBUG, "no tag specified (sp=%p)",
314
334
          ctxp->sessp);
315
334
        return (-1);
316
334
    }
317
318
2.65k
    if (ctxp->subc_args->c > 4) {
319
108
        RTPP_LOG(pvt->mself->log, RTPP_LOG_DBUG, "too many arguments (sp=%p)",
320
108
          ctxp->sessp);
321
108
        return (-1);
322
108
    }
323
324
2.54k
    dtmf_tag = ctxp->subc_args->v[1];
325
2.54k
    char *l_dtmf_tag = alloca(dtmf_tag.len + 1);
326
2.54k
    len = url_unquote2(dtmf_tag.s, l_dtmf_tag, dtmf_tag.len);
327
2.54k
    if (len == -1) {
328
307
        RTPP_LOG(pvt->mself->log, RTPP_LOG_ERR, "syntax error: invalid URL "
329
307
          "encoding");
330
307
        return (-1);
331
307
    }
332
2.23k
    l_dtmf_tag[len] = '\0';
333
2.23k
    dtmf_tag.s = l_dtmf_tag;
334
2.23k
    dtmf_tag.len = len;
335
336
2.23k
    if (ctxp->subc_args->c > 2) {
337
1.32k
        if (atoi_saferange(ctxp->subc_args->v[2].s, &new_pt, 0, 127)) {
338
483
            RTPP_LOG(pvt->mself->log, RTPP_LOG_ERR, "syntax error: invalid "
339
966
              "payload type: %.*s", FMTSTR(&ctxp->subc_args->v[2]));
340
483
            return (-1);
341
483
        }
342
841
        if (ctxp->subc_args->c > 3) {
343
2.42k
            for (const char *opt = ctxp->subc_args->v[3].s; *opt != '\0'; opt++) {
344
1.84k
                switch (*opt) {
345
1.30k
                case 'h':
346
1.75k
                case 'H':
347
1.75k
                    new_act = PPROC_ACT_DROP_v;
348
1.75k
                    break;
349
350
93
                default:
351
93
                    RTPP_LOG(pvt->mself->log, RTPP_LOG_ERR, "syntax error: "
352
93
                      "invalid modifier: \"%c\"", *opt);
353
93
                    return (-1);
354
1.84k
                }
355
1.84k
            }
356
674
        }
357
841
    }
358
359
1.66k
    struct packet_processor_if dtmf_poi;
360
361
1.66k
    if (CALL_SMETHOD(ctxp->strmp_in->pproc_manager, lookup, pvt, &dtmf_poi) == 0) {
362
226
        rtps_c = catch_dtmf_data_ctor(ctxp, rtpp_str_fix(&dtmf_tag), new_pt, pvt->mself);
363
226
        if (rtps_c == NULL) {
364
0
            return (-1);
365
0
        }
366
226
        dtmf_poi = (struct packet_processor_if) {
367
226
            .descr = "dtmf",
368
226
            .taste = rtp_packet_is_dtmf,
369
226
            .enqueue = rtpp_catch_dtmf_enqueue,
370
226
            .key = pvt,
371
226
            .arg = rtps_c,
372
226
            .rcnt = rtps_c->rcnt
373
226
        };
374
226
        if (CALL_SMETHOD(ctxp->strmp_in->pproc_manager, reg, PPROC_ORD_WITNESS, &dtmf_poi) < 0) {
375
0
            RTPP_OBJ_DECREF(&dtmf_poi);
376
0
            return (-1);
377
0
        }
378
1.43k
    } else {
379
1.43k
        rtps_c = dtmf_poi.arg;
380
1.43k
    }
381
382
1.66k
    old_pt = atomic_exchange(&(rtps_c->pt), new_pt);
383
1.66k
    if (old_pt != -1)
384
1.66k
        RTPP_LOG(pvt->mself->log, RTPP_LOG_DBUG, "sp=%p, pt=%d->%d",
385
1.66k
          ctxp->strmp_in, old_pt, new_pt);
386
1.66k
    old_act = atomic_exchange(&(rtps_c->act), new_act);
387
1.66k
    if (old_act != new_act)
388
1.66k
        RTPP_LOG(pvt->mself->log, RTPP_LOG_DBUG, "sp=%p, act=%d->%d",
389
1.11k
          ctxp->strmp_in, old_act, new_act);
390
1.66k
    RTPP_OBJ_DECREF(&dtmf_poi);
391
1.66k
    return (0);
392
1.66k
}
393
394
static int
395
rtp_packet_is_dtmf(struct pkt_proc_ctx *pktx)
396
0
{
397
0
    struct catch_dtmf_stream_cfg *rtps_c;
398
399
0
    if (pktx->strmp_in->pipe_type != PIPE_RTP)
400
0
        return (0);
401
0
    rtps_c = pktx->pproc->arg;
402
0
    if (atomic_load(&(rtps_c->pt)) != pktx->pktp->data.header.pt)
403
0
        return (0);
404
0
    pktx->auxp = rtps_c;
405
406
0
    return (1);
407
0
}
408
409
static struct pproc_act
410
rtpp_catch_dtmf_enqueue(const struct pkt_proc_ctx *pktx)
411
0
{
412
0
    struct rtpp_wi *wi;
413
0
    struct wipkt *wip;
414
0
    struct catch_dtmf_stream_cfg *rtps_c;
415
416
0
    rtps_c = (struct catch_dtmf_stream_cfg *)pktx->auxp;
417
    /* we duplicate the tag to make sure it does not vanish */
418
0
    wi = rtpp_wi_malloc_udata((void **)&wip, sizeof(struct wipkt));
419
0
    if (wi == NULL)
420
0
        return (PPROC_ACT_DROP);
421
0
    RTPP_OBJ_BORROW(wi, pktx->pktp);
422
    /* we need to duplicate the tag and state */
423
0
    wip->edata = rtps_c->edata;
424
0
    RTPP_OBJ_BORROW(wi, rtps_c->edata);
425
0
    wip->pkt = pktx->pktp;
426
0
    RTPP_OBJ_BORROW(wi, rtps_c->rtdp);
427
0
    wip->rtdp = rtps_c->rtdp;
428
0
    if (rtpp_queue_put_item(wi, rtps_c->mself->wthr.mod_q) != 0) {
429
0
        RTPP_OBJ_DECREF(wi);
430
0
        return (PPROC_ACT_DROP);
431
0
    }
432
0
    return (PPROC_ACT(atomic_load(&(rtps_c->act))));
433
0
}
434
435
static struct rtpp_module_priv *
436
rtpp_catch_dtmf_ctor(const struct rtpp_cfg *cfsp, struct rtpp_minfo *mself)
437
4
{
438
4
    struct rtpp_module_priv *pvt;
439
440
4
    pvt = mod_zmalloc(sizeof(struct rtpp_module_priv));
441
4
    if (pvt == NULL) {
442
0
        goto e0;
443
0
    }
444
4
    pvt->notifier = cfsp->rtpp_notify_cf;
445
4
    pvt->mself = mself;
446
4
    return (pvt);
447
448
0
e0:
449
0
    return (NULL);
450
4
}
451
452
static void
453
rtpp_catch_dtmf_dtor(struct rtpp_module_priv *pvt)
454
4
{
455
456
4
    mod_free(pvt);
457
4
    return;
458
4
}