/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(¬ify_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 | } |