1 /* $Id$ */
2 /*
3  * Copyright (C) 2018 Teluu Inc. (http://www.teluu.com)
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19 
20 #include <pjmedia/rtcp_fb.h>
21 #include <pjmedia/codec.h>
22 #include <pjmedia/endpoint.h>
23 #include <pjmedia/errno.h>
24 #include <pjmedia/vid_codec.h>
25 #include <pj/assert.h>
26 #include <pj/log.h>
27 #include <pj/os.h>
28 #include <pj/sock.h>
29 #include <pj/string.h>
30 
31 #define THIS_FILE "rtcp_fb.c"
32 
33 #define RTCP_RTPFB	205
34 #define RTCP_PSFB	206
35 
36 /*
37  * Build an RTCP-FB Generic NACK packet.
38  */
pjmedia_rtcp_fb_build_nack(pjmedia_rtcp_session * session,void * buf,pj_size_t * length,unsigned nack_cnt,const pjmedia_rtcp_fb_nack nack[])39 PJ_DEF(pj_status_t) pjmedia_rtcp_fb_build_nack(
40 					pjmedia_rtcp_session *session,
41 					void *buf,
42 					pj_size_t *length,
43 					unsigned nack_cnt,
44 					const pjmedia_rtcp_fb_nack nack[])
45 {
46     pjmedia_rtcp_common *hdr;
47     pj_uint8_t *p;
48     unsigned len, i;
49 
50     PJ_ASSERT_RETURN(session && buf && length && nack_cnt && nack, PJ_EINVAL);
51 
52     len = (3 + nack_cnt) * 4;
53     if (len > *length)
54 	return PJ_ETOOSMALL;
55 
56     /* Build RTCP-FB NACK header */
57     hdr = (pjmedia_rtcp_common*)buf;
58     pj_memcpy(hdr, &session->rtcp_rr_pkt.common,  sizeof(*hdr));
59     hdr->pt = RTCP_RTPFB;
60     hdr->count = 1; /* FMT = 1 */
61     hdr->length = pj_htons((pj_uint16_t)(len/4 - 1));
62 
63     /* Build RTCP-FB NACK FCI */
64     p = (pj_uint8_t*)hdr + sizeof(*hdr);
65     for (i = 0; i < nack_cnt; ++i) {
66 	pj_uint16_t val;
67 	val = pj_htons((pj_uint16_t)nack[i].pid);
68 	pj_memcpy(p, &val, 2);
69 	val = pj_htons(nack[i].blp);
70 	pj_memcpy(p+2, &val, 2);
71 	p += 4;
72     }
73 
74     /* Finally */
75     *length = len;
76 
77     return PJ_SUCCESS;
78 }
79 
80 
81 /*
82  * Build an RTCP-FB Picture Loss Indication (PLI) packet.
83  */
pjmedia_rtcp_fb_build_pli(pjmedia_rtcp_session * session,void * buf,pj_size_t * length)84 PJ_DEF(pj_status_t) pjmedia_rtcp_fb_build_pli(
85 					pjmedia_rtcp_session *session,
86 					void *buf,
87 					pj_size_t *length)
88 {
89     pjmedia_rtcp_common *hdr;
90     unsigned len;
91 
92     PJ_ASSERT_RETURN(session && buf && length, PJ_EINVAL);
93 
94     len = 12;
95     if (len > *length)
96 	return PJ_ETOOSMALL;
97 
98     /* Build RTCP-FB PLI header */
99     hdr = (pjmedia_rtcp_common*)buf;
100     pj_memcpy(hdr, &session->rtcp_rr_pkt.common,  sizeof(*hdr));
101     hdr->pt = RTCP_PSFB;
102     hdr->count = 1; /* FMT = 1 */
103     hdr->length = pj_htons((pj_uint16_t)(len/4 - 1));
104 
105     /* Finally */
106     *length = len;
107 
108     return PJ_SUCCESS;
109 }
110 
111 
112 /*
113  * Build an RTCP-FB Slice Loss Indication (SLI) packet.
114  */
pjmedia_rtcp_fb_build_sli(pjmedia_rtcp_session * session,void * buf,pj_size_t * length,unsigned sli_cnt,const pjmedia_rtcp_fb_sli sli[])115 PJ_DEF(pj_status_t) pjmedia_rtcp_fb_build_sli(
116 					pjmedia_rtcp_session *session,
117 					void *buf,
118 					pj_size_t *length,
119 					unsigned sli_cnt,
120 					const pjmedia_rtcp_fb_sli sli[])
121 {
122     pjmedia_rtcp_common *hdr;
123     pj_uint8_t *p;
124     unsigned len, i;
125 
126     PJ_ASSERT_RETURN(session && buf && length && sli_cnt && sli, PJ_EINVAL);
127 
128     len = (3 + sli_cnt) * 4;
129     if (len > *length)
130 	return PJ_ETOOSMALL;
131 
132     /* Build RTCP-FB SLI header */
133     hdr = (pjmedia_rtcp_common*)buf;
134     pj_memcpy(hdr, &session->rtcp_rr_pkt.common,  sizeof(*hdr));
135     hdr->pt = RTCP_PSFB;
136     hdr->count = 2; /* FMT = 2 */
137     hdr->length = pj_htons((pj_uint16_t)(len/4 - 1));
138 
139     /* Build RTCP-FB SLI FCI */
140     p = (pj_uint8_t*)hdr + sizeof(*hdr);
141     for (i = 0; i < sli_cnt; ++i) {
142 	/* 'first' takes 13 bit */
143 	*p++  = (pj_uint8_t)((sli[i].first >> 5) & 0xFF);   /* 8 MSB bits */
144 	*p    = (pj_uint8_t)((sli[i].first & 31) << 3);	    /* 5 LSB bits */
145 	/* 'number' takes 13 bit */
146 	*p++ |= (pj_uint8_t)((sli[i].number >> 10) & 7);    /* 3 MSB bits */
147 	*p++  = (pj_uint8_t)((sli[i].number >> 2) & 0xFF);  /* 8 mid bits */
148 	*p    = (pj_uint8_t)((sli[i].number & 3) << 6);	    /* 2 LSB bits */
149 	/* 'pict_id' takes 6 bit */
150 	*p++ |= (sli[i].pict_id & 63);
151     }
152 
153     /* Finally */
154     *length = len;
155 
156     return PJ_SUCCESS;
157 }
158 
159 
160 /*
161  * Build an RTCP-FB Slice Loss Indication (SLI) packet.
162  */
pjmedia_rtcp_fb_build_rpsi(pjmedia_rtcp_session * session,void * buf,pj_size_t * length,const pjmedia_rtcp_fb_rpsi * rpsi)163 PJ_DEF(pj_status_t) pjmedia_rtcp_fb_build_rpsi(
164 					    pjmedia_rtcp_session *session,
165 					    void *buf,
166 					    pj_size_t *length,
167 					    const pjmedia_rtcp_fb_rpsi *rpsi)
168 {
169     pjmedia_rtcp_common *hdr;
170     pj_uint8_t *p;
171     unsigned bitlen, padlen, len;
172 
173     PJ_ASSERT_RETURN(session && buf && length && rpsi, PJ_EINVAL);
174 
175     bitlen = rpsi->rpsi_bit_len + 16;
176     padlen = (32 - (bitlen % 32)) % 32;
177     len = (3 + (bitlen+padlen)/32) * 4;
178     if (len > *length)
179 	return PJ_ETOOSMALL;
180 
181     /* Build RTCP-FB RPSI header */
182     hdr = (pjmedia_rtcp_common*)buf;
183     pj_memcpy(hdr, &session->rtcp_rr_pkt.common,  sizeof(*hdr));
184     hdr->pt = RTCP_PSFB;
185     hdr->count = 3; /* FMT = 3 */
186     hdr->length = pj_htons((pj_uint16_t)(len/4 - 1));
187 
188     /* Build RTCP-FB RPSI FCI */
189     p = (pj_uint8_t*)hdr + sizeof(*hdr);
190     /* PB (number of padding bits) */
191     *p++ = (pj_uint8_t)padlen;
192     /* Payload type */
193     *p++ = rpsi->pt & 0x7F;
194     /* RPSI bit string */
195     pj_memcpy(p, rpsi->rpsi.ptr, rpsi->rpsi_bit_len/8);
196     p += rpsi->rpsi_bit_len/8;
197     if (rpsi->rpsi_bit_len % 8) {
198 	*p++ = *(rpsi->rpsi.ptr + rpsi->rpsi_bit_len/8);
199     }
200     /* Zero padding */
201     if (padlen >= 8)
202 	pj_bzero(p, padlen/8);
203 
204     /* Finally */
205     *length = len;
206 
207     return PJ_SUCCESS;
208 }
209 
210 
211 /*
212  * Initialize RTCP Feedback setting with default values.
213  */
pjmedia_rtcp_fb_setting_default(pjmedia_rtcp_fb_setting * opt)214 PJ_DEF(pj_status_t) pjmedia_rtcp_fb_setting_default(
215 					pjmedia_rtcp_fb_setting *opt)
216 {
217     pj_bzero(opt, sizeof(*opt));
218     opt->dont_use_avpf = PJ_TRUE;
219 
220     return PJ_SUCCESS;
221 }
222 
223 
pjmedia_rtcp_fb_cap_dup(pj_pool_t * pool,pjmedia_rtcp_fb_cap * dst,const pjmedia_rtcp_fb_cap * src)224 static void pjmedia_rtcp_fb_cap_dup(pj_pool_t *pool,
225 				    pjmedia_rtcp_fb_cap *dst,
226 				    const pjmedia_rtcp_fb_cap *src)
227 {
228     pj_strdup(pool, &dst->codec_id, &src->codec_id);
229     dst->type = src->type;
230     pj_strdup(pool, &dst->type_name, &src->type_name);
231     pj_strdup(pool, &dst->param, &src->param);
232 }
233 
234 
235 /*
236  * Duplicate RTCP Feedback setting.
237  */
pjmedia_rtcp_fb_setting_dup(pj_pool_t * pool,pjmedia_rtcp_fb_setting * dst,const pjmedia_rtcp_fb_setting * src)238 PJ_DEF(void) pjmedia_rtcp_fb_setting_dup( pj_pool_t *pool,
239 					  pjmedia_rtcp_fb_setting *dst,
240 					  const pjmedia_rtcp_fb_setting *src)
241 {
242     unsigned i;
243 
244     pj_assert(pool && dst && src);
245 
246     pj_memcpy(dst, src, sizeof(pjmedia_rtcp_fb_setting));
247     for (i = 0; i < src->cap_count; ++i) {
248 	pjmedia_rtcp_fb_cap_dup(pool, &dst->caps[i], &src->caps[i]);
249     }
250 }
251 
252 
253 /*
254  * Duplicate RTCP Feedback info.
255  */
pjmedia_rtcp_fb_info_dup(pj_pool_t * pool,pjmedia_rtcp_fb_info * dst,const pjmedia_rtcp_fb_info * src)256 PJ_DEF(void) pjmedia_rtcp_fb_info_dup( pj_pool_t *pool,
257 				       pjmedia_rtcp_fb_info *dst,
258 				       const pjmedia_rtcp_fb_info *src)
259 {
260     unsigned i;
261 
262     pj_assert(pool && dst && src);
263 
264     pj_memcpy(dst, src, sizeof(pjmedia_rtcp_fb_info));
265     for (i = 0; i < src->cap_count; ++i) {
266 	pjmedia_rtcp_fb_cap_dup(pool, &dst->caps[i], &src->caps[i]);
267     }
268 }
269 
270 
271 
272 struct rtcp_fb_type_name_t
273 {
274     pjmedia_rtcp_fb_type     type;
275     const char		    *name;
276 } rtcp_fb_type_name[] =
277 {
278     {PJMEDIA_RTCP_FB_ACK,	"ack"},
279     {PJMEDIA_RTCP_FB_NACK,	"nack"},
280     {PJMEDIA_RTCP_FB_TRR_INT,	"trr-int"}
281 };
282 
283 /* Generate a=rtcp-fb based on the specified PT & RTCP-FB capability */
add_sdp_attr_rtcp_fb(pj_pool_t * pool,const char * pt,const pjmedia_rtcp_fb_cap * cap,pjmedia_sdp_media * m)284 static pj_status_t add_sdp_attr_rtcp_fb( pj_pool_t *pool,
285 					 const char *pt,
286 					 const pjmedia_rtcp_fb_cap *cap,
287 					 pjmedia_sdp_media *m)
288 {
289     pjmedia_sdp_attr *a;
290     char tmp[128];
291     pj_str_t val;
292     pj_str_t type_name = {0};
293 
294     if (cap->type < PJMEDIA_RTCP_FB_OTHER)
295 	pj_cstr(&type_name, rtcp_fb_type_name[cap->type].name);
296     else if (cap->type == PJMEDIA_RTCP_FB_OTHER)
297 	type_name = cap->type_name;
298 
299     if (type_name.slen == 0)
300 	return PJ_EINVAL;
301 
302     /* Generate RTCP FB param */
303     if (cap->param.slen) {
304 	pj_ansi_snprintf(tmp, sizeof(tmp), "%s %.*s %.*s", pt,
305 			 (int)type_name.slen, type_name.ptr,
306 			 (int)cap->param.slen, cap->param.ptr);
307     } else {
308 	pj_ansi_snprintf(tmp, sizeof(tmp), "%s %.*s", pt,
309 			 (int)type_name.slen, type_name.ptr);
310     }
311     pj_strset2(&val, tmp);
312 
313     /* Generate and add SDP attribute a=rtcp-fb */
314     a = pjmedia_sdp_attr_create(pool, "rtcp-fb", &val);
315     m->attr[m->attr_count++] = a;
316 
317     return PJ_SUCCESS;
318 }
319 
320 /* SDP codec info (ID and PT) */
321 typedef struct sdp_codec_info_t
322 {
323     char	 id[32];
324     unsigned	 pt;
325 } sdp_codec_info_t;
326 
327 
328 /* Populate codec ID/name and PT in SDP */
get_codec_info_from_sdp(pjmedia_endpt * endpt,const pjmedia_sdp_media * m,unsigned * sci_cnt,sdp_codec_info_t sci[])329 static pj_status_t get_codec_info_from_sdp(pjmedia_endpt *endpt,
330 					   const pjmedia_sdp_media *m,
331 					   unsigned *sci_cnt,
332 					   sdp_codec_info_t sci[])
333 {
334     pjmedia_codec_mgr *codec_mgr;
335     unsigned j, cnt = 0;
336     pjmedia_type type = PJMEDIA_TYPE_UNKNOWN;
337     pj_status_t status;
338 
339     type = pjmedia_get_type(&m->desc.media);
340     if (type != PJMEDIA_TYPE_AUDIO && type != PJMEDIA_TYPE_VIDEO)
341 	return PJMEDIA_EUNSUPMEDIATYPE;
342 
343     codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
344     for (j = 0; j < m->desc.fmt_count && cnt < *sci_cnt; ++j) {
345 	unsigned pt = 0;
346 	pt = pj_strtoul(&m->desc.fmt[j]);
347 	if (pt < 96) {
348 	    if (type == PJMEDIA_TYPE_AUDIO) {
349 		const pjmedia_codec_info *ci;
350 		status = pjmedia_codec_mgr_get_codec_info(codec_mgr, pt, &ci);
351 		if (status != PJ_SUCCESS)
352 		    continue;
353 
354 		pjmedia_codec_info_to_id(ci, sci[cnt].id, sizeof(sci[0].id));
355 	    } else {
356 #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
357 		const pjmedia_vid_codec_info *ci;
358 		status = pjmedia_vid_codec_mgr_get_codec_info(NULL, pt, &ci);
359 		if (status != PJ_SUCCESS)
360 		    continue;
361 
362 		pjmedia_vid_codec_info_to_id(ci, sci[cnt].id,
363 					     sizeof(sci[0].id));
364 #else
365 		continue;
366 #endif
367 	    }
368 	} else {
369 	    pjmedia_sdp_attr *a;
370 	    pjmedia_sdp_rtpmap r;
371 	    a = pjmedia_sdp_media_find_attr2(m, "rtpmap",
372 					     &m->desc.fmt[j]);
373 	    if (a == NULL)
374 		continue;
375 	    status = pjmedia_sdp_attr_get_rtpmap(a, &r);
376 	    if (status != PJ_SUCCESS)
377 		continue;
378 
379 	    if (type == PJMEDIA_TYPE_AUDIO) {
380 		/* Audio codec id format: "name/clock-rate/channel-count" */
381 		if (r.param.slen) {
382 		    pj_ansi_snprintf(sci[cnt].id, sizeof(sci[0].id),
383 				     "%.*s/%d/%.*s",
384 				     (int)r.enc_name.slen, r.enc_name.ptr,
385 				     r.clock_rate,
386 				     (int)r.param.slen, r.param.ptr);
387 		} else {
388 		    pj_ansi_snprintf(sci[cnt].id, sizeof(sci[0].id),
389 				     "%.*s/%d/1",
390 				     (int)r.enc_name.slen, r.enc_name.ptr,
391 				     r.clock_rate);
392 		}
393 	    } else {
394 		/* Video codec id format: "name/payload-type" */
395 		pj_ansi_snprintf(sci[cnt].id, sizeof(sci[0].id),
396 				 "%.*s/%d",
397 				 (int)r.enc_name.slen, r.enc_name.ptr, pt);
398 	    }
399 	}
400 	sci[cnt++].pt = pt;
401     }
402     *sci_cnt = cnt;
403 
404     return PJ_SUCCESS;
405 }
406 
407 /*
408  * Encode RTCP Feedback specific information into the SDP according to
409  * the provided RTCP Feedback setting.
410  */
pjmedia_rtcp_fb_encode_sdp(pj_pool_t * pool,pjmedia_endpt * endpt,const pjmedia_rtcp_fb_setting * opt,pjmedia_sdp_session * sdp_local,unsigned med_idx,const pjmedia_sdp_session * sdp_remote)411 PJ_DEF(pj_status_t) pjmedia_rtcp_fb_encode_sdp(
412 				    pj_pool_t *pool,
413 				    pjmedia_endpt *endpt,
414 				    const pjmedia_rtcp_fb_setting *opt,
415 				    pjmedia_sdp_session *sdp_local,
416 				    unsigned med_idx,
417 				    const pjmedia_sdp_session *sdp_remote)
418 {
419     pjmedia_sdp_media *m = sdp_local->media[med_idx];
420     unsigned i;
421     unsigned sci_cnt = 0;
422     sdp_codec_info_t sci[PJMEDIA_MAX_SDP_FMT];
423     pj_status_t status;
424 
425     PJ_UNUSED_ARG(sdp_remote);
426 
427     PJ_ASSERT_RETURN(pool && endpt && opt && sdp_local, PJ_EINVAL);
428     PJ_ASSERT_RETURN(med_idx < sdp_local->media_count, PJ_EINVAL);
429 
430     /* Add RTCP Feedback profile (AVPF), if configured to */
431     if (!opt->dont_use_avpf) {
432 	unsigned proto = pjmedia_sdp_transport_get_proto(&m->desc.transport);
433 	if (!PJMEDIA_TP_PROTO_HAS_FLAG(proto, PJMEDIA_TP_PROFILE_RTCP_FB)) {
434 	    pj_str_t new_tp;
435 	    pj_strdup_with_null(pool, &new_tp, &m->desc.transport);
436 	    new_tp.ptr[new_tp.slen++] = 'F';
437 	    m->desc.transport = new_tp;
438 	}
439     }
440 
441     /* Add RTCP Feedback capability to SDP */
442     for (i = 0; i < opt->cap_count; ++i) {
443 	unsigned j;
444 
445 	/* All codecs */
446 	if (pj_strcmp2(&opt->caps[i].codec_id, "*") == 0) {
447 	    status = add_sdp_attr_rtcp_fb(pool, "*", &opt->caps[i], m);
448 	    if (status != PJ_SUCCESS) {
449 		PJ_PERROR(3, (THIS_FILE, status,
450 			  "Failed generating SDP a=rtcp-fb:*"));
451 	    }
452 	    continue;
453 	}
454 
455 	/* Specific codec */
456 	if (sci_cnt == 0) {
457 	    sci_cnt = PJ_ARRAY_SIZE(sci);
458 	    status = get_codec_info_from_sdp(endpt, m, &sci_cnt, sci);
459 	    if (status != PJ_SUCCESS) {
460 		PJ_PERROR(3, (THIS_FILE, status,
461 			  "Failed populating codec info from SDP"));
462 		return status;
463 	    }
464 	}
465 
466 	for (j = 0; j < sci_cnt; ++j) {
467 	    if (pj_strnicmp2(&opt->caps[i].codec_id, sci[j].id,
468 			     opt->caps[i].codec_id.slen) == 0)
469 	    {
470 		char tmp[4];
471 		snprintf(tmp, sizeof(tmp), "%d", sci[j].pt);
472 		status = add_sdp_attr_rtcp_fb(pool, tmp, &opt->caps[i], m);
473 		if (status != PJ_SUCCESS) {
474 		    PJ_PERROR(3, (THIS_FILE, status,
475 			      "Failed generating SDP a=rtcp-fb:%d (%s)",
476 			      sci[j].pt, opt->caps[i].codec_id.ptr));
477 		}
478 		break;
479 	    }
480 	}
481 	if (j == sci_cnt) {
482 	    /* Codec ID not found in SDP (perhaps better ignore this error
483 	     * as app may configure audio and video in single setting).
484 	     */
485 	    PJ_PERROR(6, (THIS_FILE, PJ_ENOTFOUND,
486 		      "Failed generating SDP a=rtcp-fb for %s",
487 		      opt->caps[i].codec_id.ptr));
488 	}
489     }
490 
491     return PJ_SUCCESS;
492 }
493 
494 
495 /*
496  * Decode RTCP Feedback specific information from SDP media.
497  */
pjmedia_rtcp_fb_decode_sdp(pj_pool_t * pool,pjmedia_endpt * endpt,const void * opt,const pjmedia_sdp_session * sdp,unsigned med_idx,pjmedia_rtcp_fb_info * info)498 PJ_DEF(pj_status_t) pjmedia_rtcp_fb_decode_sdp(
499 				    pj_pool_t *pool,
500 				    pjmedia_endpt *endpt,
501 				    const void *opt,
502 				    const pjmedia_sdp_session *sdp,
503 				    unsigned med_idx,
504 				    pjmedia_rtcp_fb_info *info)
505 {
506     return pjmedia_rtcp_fb_decode_sdp2(pool, endpt, opt, sdp, med_idx, -1,
507 				       info);
508 }
509 
510 /*
511  * Decode RTCP Feedback specific information from SDP media.
512  */
pjmedia_rtcp_fb_decode_sdp2(pj_pool_t * pool,pjmedia_endpt * endpt,const void * opt,const pjmedia_sdp_session * sdp,unsigned med_idx,int pt,pjmedia_rtcp_fb_info * info)513 PJ_DEF(pj_status_t) pjmedia_rtcp_fb_decode_sdp2(
514 				    pj_pool_t *pool,
515 				    pjmedia_endpt *endpt,
516 				    const void *opt,
517 				    const pjmedia_sdp_session *sdp,
518 				    unsigned med_idx,
519 				    int pt,
520 				    pjmedia_rtcp_fb_info *info)
521 {
522     unsigned sci_cnt = PJMEDIA_MAX_SDP_FMT;
523     sdp_codec_info_t sci[PJMEDIA_MAX_SDP_FMT];
524     const pjmedia_sdp_media *m;
525     pj_status_t status;
526     unsigned i;
527 
528     PJ_UNUSED_ARG(opt);
529 
530     PJ_ASSERT_RETURN(pool && endpt && opt==NULL && sdp, PJ_EINVAL);
531     PJ_ASSERT_RETURN(med_idx < sdp->media_count, PJ_EINVAL);
532     PJ_ASSERT_RETURN(pt <= 127, PJ_EINVAL);
533 
534     m = sdp->media[med_idx];
535     status = get_codec_info_from_sdp(endpt, m, &sci_cnt, sci);
536     if (status != PJ_SUCCESS)
537 	return status;
538 
539     pj_bzero(info, sizeof(*info));
540 
541     /* Iterate all SDP attribute a=rtcp-fb in the SDP media */
542     for (i = 0; i < m->attr_count; ++i) {
543 	const pjmedia_sdp_attr *a = m->attr[i];
544 	pj_str_t token;
545 	pj_ssize_t tok_idx;
546 	unsigned j;
547 	const char *codec_id = NULL;
548 	pj_str_t type_name = {0};
549 	pjmedia_rtcp_fb_type type = PJMEDIA_RTCP_FB_OTHER;
550 
551 	/* Skip non a=rtcp-fb */
552 	if (pj_strcmp2(&a->name, "rtcp-fb") != 0)
553 	    continue;
554 
555 	/* Get PT */
556 	tok_idx = pj_strtok2(&a->value, " \t", &token, 0);
557 	if (tok_idx == a->value.slen)
558 	    continue;
559 
560 	if (pj_strcmp2(&token, "*") == 0) {
561 	    /* All codecs */
562 	    codec_id = "*";
563 	} else {
564 	    /* Specific PT/codec */
565 	    unsigned pt_ = (unsigned) pj_strtoul2(&token, NULL, 10);
566 	    for (j = 0; j < sci_cnt; ++j) {
567 		/* Check if payload type is valid and requested */
568 		if (pt_ == sci[j].pt && (pt < 0 || pt == (int)pt_)) {
569 		    codec_id = sci[j].id;
570 		    break;
571 		}
572 	    }
573 	}
574 
575 	/* Skip this a=rtcp-fb if PT is not recognized or not requested */
576 	if (!codec_id)
577 	    continue;
578 
579 	/* Get RTCP-FB type */
580 	tok_idx = pj_strtok2(&a->value, " \t", &token, tok_idx + token.slen);
581 	if (tok_idx == a->value.slen)
582 	    continue;
583 
584 	for (j = 0; j < PJ_ARRAY_SIZE(rtcp_fb_type_name); ++j) {
585 	    if (pj_strcmp2(&token, rtcp_fb_type_name[j].name) == 0) {
586 		type = rtcp_fb_type_name[j].type;
587 		break;
588 	    }
589 	}
590 	if (type == PJMEDIA_RTCP_FB_OTHER)
591 	    type_name = token;
592 
593 	/* Got all the mandatory fields, let's initialize RTCP-FB cap */
594 	pj_strdup2(pool, &info->caps[info->cap_count].codec_id, codec_id);
595 	info->caps[info->cap_count].type = type;
596 	if (type == PJMEDIA_RTCP_FB_OTHER)
597 	    pj_strdup(pool, &info->caps[info->cap_count].type_name, &type_name);
598 
599 	/* Get RTCP-FB param */
600 	tok_idx = pj_strtok2(&a->value, " \t", &token, tok_idx + token.slen);
601 	if (tok_idx != a->value.slen)
602 	    pj_strdup(pool, &info->caps[info->cap_count].param, &token);
603 
604 	/* Next */
605 	if (++info->cap_count == PJMEDIA_RTCP_FB_MAX_CAP)
606 	    break;
607     }
608 
609     return PJ_SUCCESS;
610 }
611 
612 
613 /*
614  * Check whether the specified payload contains RTCP feedback generic NACK
615  * message, and parse the payload if it does.
616  */
pjmedia_rtcp_fb_parse_nack(const void * buf,pj_size_t length,unsigned * nack_cnt,pjmedia_rtcp_fb_nack nack[])617 PJ_DEF(pj_status_t) pjmedia_rtcp_fb_parse_nack(
618 					const void *buf,
619 					pj_size_t length,
620 					unsigned *nack_cnt,
621 					pjmedia_rtcp_fb_nack nack[])
622 {
623     pjmedia_rtcp_common *hdr = (pjmedia_rtcp_common*) buf;
624     pj_uint8_t *p;
625     unsigned cnt, i;
626 
627     PJ_ASSERT_RETURN(buf && nack_cnt && nack, PJ_EINVAL);
628     PJ_ASSERT_RETURN(length >= sizeof(pjmedia_rtcp_common), PJ_ETOOSMALL);
629 
630     /* Generic NACK uses pt==RTCP_RTPFB and FMT==1 */
631     if (hdr->pt != RTCP_RTPFB || hdr->count != 1)
632 	return PJ_ENOTFOUND;
633 
634     cnt = pj_ntohs((pj_uint16_t)hdr->length) - 2;
635     if (length < (cnt+3)*4)
636 	return PJ_ETOOSMALL;
637 
638     *nack_cnt = PJ_MIN(*nack_cnt, cnt);
639 
640     p = (pj_uint8_t*)hdr + sizeof(*hdr);
641     for (i = 0; i < *nack_cnt; ++i) {
642 	pj_uint16_t val;
643 
644 	pj_memcpy(&val, p, 2);
645 	nack[i].pid = pj_ntohs(val);
646 	pj_memcpy(&val, p+2, 2);
647 	nack[i].blp = pj_ntohs(val);
648 	p += 4;
649     }
650 
651     return PJ_SUCCESS;
652 }
653 
654 
655 /*
656  * Check whether the specified payload contains RTCP feedback Picture Loss
657  * Indication (PLI) message.
658  */
pjmedia_rtcp_fb_parse_pli(const void * buf,pj_size_t length)659 PJ_DEF(pj_status_t) pjmedia_rtcp_fb_parse_pli(
660 					const void *buf,
661 					pj_size_t length)
662 {
663     pjmedia_rtcp_common *hdr = (pjmedia_rtcp_common*) buf;
664 
665     PJ_ASSERT_RETURN(buf, PJ_EINVAL);
666     PJ_ASSERT_RETURN(length >= 12, PJ_ETOOSMALL);
667 
668     /* PLI uses pt==RTCP_PSFB and FMT==1 */
669     if (hdr->pt != RTCP_PSFB || hdr->count != 1)
670 	return PJ_ENOTFOUND;
671 
672     return PJ_SUCCESS;
673 }
674 
675 
676 /*
677  * Check whether the specified payload contains RTCP feedback Slice Loss
678  * Indication (SLI) message, and parse the payload if it does.
679  */
pjmedia_rtcp_fb_parse_sli(const void * buf,pj_size_t length,unsigned * sli_cnt,pjmedia_rtcp_fb_sli sli[])680 PJ_DEF(pj_status_t) pjmedia_rtcp_fb_parse_sli(
681 					const void *buf,
682 					pj_size_t length,
683 					unsigned *sli_cnt,
684 					pjmedia_rtcp_fb_sli sli[])
685 {
686     pjmedia_rtcp_common *hdr = (pjmedia_rtcp_common*) buf;
687     pj_uint8_t *p;
688     unsigned cnt, i;
689 
690     PJ_ASSERT_RETURN(buf && sli_cnt && sli, PJ_EINVAL);
691     PJ_ASSERT_RETURN(length >= sizeof(pjmedia_rtcp_common), PJ_ETOOSMALL);
692 
693     /* PLI uses pt==RTCP_PSFB and FMT==2 */
694     if (hdr->pt != RTCP_PSFB || hdr->count != 2)
695 	return PJ_ENOTFOUND;
696 
697     cnt = pj_ntohs((pj_uint16_t)hdr->length) - 2;
698     if (length < (cnt+3)*4)
699 	return PJ_ETOOSMALL;
700 
701     *sli_cnt = PJ_MIN(*sli_cnt, cnt);
702 
703     p = (pj_uint8_t*)hdr + sizeof(*hdr);
704     for (i = 0; i < *sli_cnt; ++i) {
705 	/* 'first' takes 13 bit */
706 	sli[i].first = (p[0] << 5) + ((p[1] & 0xF8) >> 3);
707 	/* 'number' takes 13 bit */
708 	sli[i].number = ((p[1] & 0x07) << 10) +
709 			(p[2] << 2) +
710 			((p[3] & 0xC0) >> 6);
711 	/* 'pict_id' takes 6 bit */
712 	sli[i].pict_id = (p[3] & 0x3F);
713 	p += 4;
714     }
715 
716     return PJ_SUCCESS;
717 }
718 
719 
720 /*
721  * Check whether the specified payload contains RTCP feedback Reference
722  * Picture Selection Indication (RPSI) message, and parse the payload
723  * if it does.
724  */
pjmedia_rtcp_fb_parse_rpsi(const void * buf,pj_size_t length,pjmedia_rtcp_fb_rpsi * rpsi)725 PJ_DEF(pj_status_t) pjmedia_rtcp_fb_parse_rpsi(
726 					const void *buf,
727 					pj_size_t length,
728 					pjmedia_rtcp_fb_rpsi *rpsi)
729 {
730     pjmedia_rtcp_common *hdr = (pjmedia_rtcp_common*) buf;
731     pj_uint8_t *p;
732     pj_uint8_t padlen;
733     pj_size_t rpsi_len;
734 
735     PJ_ASSERT_RETURN(buf && rpsi, PJ_EINVAL);
736     PJ_ASSERT_RETURN(length >= sizeof(pjmedia_rtcp_common), PJ_ETOOSMALL);
737 
738     /* RPSI uses pt==RTCP_PSFB and FMT==3 */
739     if (hdr->pt != RTCP_PSFB || hdr->count != 3)
740 	return PJ_ENOTFOUND;
741 
742     rpsi_len = (pj_ntohs((pj_uint16_t)hdr->length)-2) * 4;
743     if (length < rpsi_len + 12)
744 	return PJ_ETOOSMALL;
745 
746     p = (pj_uint8_t*)hdr + sizeof(*hdr);
747     padlen = *p++;
748     rpsi->pt = (*p++ & 0x7F);
749     rpsi->rpsi_bit_len = rpsi_len*8 - 16 - padlen;
750     pj_strset(&rpsi->rpsi, (char*)p, (rpsi->rpsi_bit_len + 7)/8);
751 
752     return PJ_SUCCESS;
753 }
754