[player] Add a basic RTCP parser for Chromecast packets

Also put the endian includes in the header, both raop.c and cast.c will need
them.
This commit is contained in:
ejurgensen 2020-11-09 23:22:21 +01:00
parent cd9fa019dd
commit 06d1d7273d
2 changed files with 142 additions and 17 deletions

View File

@ -31,17 +31,6 @@
#include <limits.h>
#include <sys/param.h>
#ifdef HAVE_ENDIAN_H
# include <endian.h>
#elif defined(HAVE_SYS_ENDIAN_H)
# include <sys/endian.h>
#elif defined(HAVE_LIBKERN_OSBYTEORDER_H)
#include <libkern/OSByteOrder.h>
#define htobe16(x) OSSwapHostToBigInt16(x)
#define be16toh(x) OSSwapBigToHostInt16(x)
#define htobe32(x) OSSwapHostToBigInt32(x)
#endif
#include <gcrypt.h>
#include "logger.h"
@ -55,12 +44,6 @@
#define FRAC 4294967296. // 2^32 as a double
#define NTP_EPOCH_DELTA 0x83aa7e80 // 2208988800 - that's 1970 - 1900 in seconds
struct ntp_timestamp
{
uint32_t sec;
uint32_t frac;
};
static inline void
timespec_to_ntp(struct timespec *ts, struct ntp_timestamp *ns)
@ -289,3 +272,78 @@ rtp_sync_packet_next(struct rtp_session *session, struct rtcp_timestamp cur_stam
return &session->sync_packet_next;
}
int
rtcp_packet_parse(struct rtcp_packet *pkt, uint8_t *data, size_t size)
{
if (size < 8) // Must be large enough for at least SSRC
goto packet_malformed;
memset(pkt, 0, sizeof(struct rtcp_packet));
pkt->version = (data[0] & 0xc0) >> 6; // AND 11000000
if (pkt->version != 2)
goto packet_malformed;
pkt->padding = (data[0] & 0x20) >> 5; // AND 00100000
memcpy(&pkt->len, data + 2, 2);
pkt->len = 4 * (be16toh(pkt->len) + 1); // Input len is 32-bit words excl. the 32-bit header
memcpy(&pkt->ssrc, data + 4, 4);
pkt->ssrc = be32toh(pkt->ssrc);
if (size < pkt->len)
goto packet_malformed; // Possibly a partial read?
pkt->payload = data + pkt->len;
pkt->payload_len = size - pkt->len;
pkt->packet_type = data[1];
// TODO use a switch()
if (pkt->packet_type == RTCP_PACKET_RR) // 201, see RFC 1889
{
pkt->rr.report_count = data[0] & 0x1f; // AND 00011111
// TODO check total size of reports is smaller than size?
}
else if (pkt->packet_type == RTCP_PACKET_APP && size >= 12) // 204, see RFC 1889
{
pkt->app.subtype = data[0] & 0x1f; // AND 00011111
memcpy(pkt->app.name, data + 8, 4);
}
else if (pkt->packet_type == RTCP_PACKET_PSFB && size >= 12) // 206, see RFC 4585, payload specific feedback
{
pkt->psfb.message_type = data[0] & 0x1f; // AND 00011111
memcpy(&pkt->psfb.media_src, data + 8, 4);
pkt->psfb.media_src = be32toh(pkt->psfb.media_src);
pkt->psfb.fci = data + 12;
pkt->psfb.fci_len = size - 12;
}
else if (pkt->packet_type == RTCP_PACKET_XR && size >= 24) // 207, see RFC 3611, however we can handle only 1 block
{
pkt->xr.block_type = data[8];
pkt->xr.block_specific = data[9];
memcpy(&pkt->xr.block_len, data + 10, 2);
pkt->xr.block_len = 4 * be16toh(pkt->xr.block_len);
if (pkt->xr.block_type != 4 || pkt->xr.block_len != 8)
return 0; // We can only parse handle Receiver Reference Time Report with 8 byte NTP timestamp
memcpy(&pkt->xr.ntp.sec, data + 12, 4);
pkt->xr.ntp.sec = be32toh(pkt->xr.ntp.sec);
memcpy(&pkt->xr.ntp.frac, data + 16, 4);
pkt->xr.ntp.frac = be32toh(pkt->xr.ntp.frac);
}
else
return -1; // Don't know how to parse
/*
DPRINTF(E_DBG, L_PLAYER, "RTCP PACKET vers=%d, padding=%d, len=%" PRIu16 ", payload_len=%zu, ssrc=%" PRIu32 "\n", pkt->version, pkt->padding, pkt->len, pkt->payload_len, pkt->ssrc);
if (pkt->packet_type == RTCP_PACKET_APP)
DPRINTF(E_DBG, L_PLAYER, "RTCP APP PACKET subtype=%d, name=%s\n", pkt->app.subtype, pkt->app.name);
else if (pkt->packet_type == RTCP_PACKET_XR)
DPRINTF(E_DBG, L_PLAYER, "RTCP XR PACKET block_type=%d, block_len=%" PRIu16 "\n", pkt->xr.block_type, pkt->xr.block_len);
*/
return 0;
packet_malformed:
DPRINTF(E_SPAM, L_PLAYER, "Ignoring incoming packet, packet is non-RTCP, malformed or partial (size=%zu)\n", size);
return -1;
}

View File

@ -5,12 +5,30 @@
#include <inttypes.h>
#include <stdbool.h>
#ifdef HAVE_ENDIAN_H
# include <endian.h>
#elif defined(HAVE_SYS_ENDIAN_H)
# include <sys/endian.h>
#elif defined(HAVE_LIBKERN_OSBYTEORDER_H)
#include <libkern/OSByteOrder.h>
#define htobe16(x) OSSwapHostToBigInt16(x)
#define be16toh(x) OSSwapBigToHostInt16(x)
#define htobe32(x) OSSwapHostToBigInt32(x)
#define be32toh(x) OSSwapBigToHostInt32(x)
#endif
struct rtcp_timestamp
{
uint32_t pos;
struct timespec ts;
};
struct ntp_timestamp
{
uint32_t sec;
uint32_t frac;
};
struct rtp_packet
{
uint16_t seqnum; // Sequence number
@ -27,6 +45,52 @@ struct rtp_packet
size_t data_len; // Length of actual packet data
};
struct rtcp_packet
{
uint8_t version; // Always 2
bool padding;
uint16_t len;
uint32_t ssrc;
uint8_t *payload;
size_t payload_len;
enum rtcp_packet_type
{
RTCP_PACKET_RR = 201, // RFC 3550
RTCP_PACKET_APP = 204, // RFC 1889
RTCP_PACKET_PSFB = 206, // RFC 4585
RTCP_PACKET_XR = 207, // RFC 3611
} packet_type;
union
{
struct rtcp_packet_rr
{
uint8_t report_count;
} rr;
struct rtcp_packet_app
{
uint8_t subtype;
char name[5]; // Zero-terminated
} app;
struct rtcp_packet_psfb
{
uint8_t message_type;
uint32_t media_src;
uint8_t *fci;
size_t fci_len;
} psfb;
struct rtcp_packet_xr
{
uint8_t block_type;
uint8_t block_specific;
uint16_t block_len;
struct ntp_timestamp ntp;
} xr;
};
};
// An RTP session is characterised by all the receivers belonging to the session
// getting the same RTP and RTCP packets. So if you have clients that require
// different sample rates or where only some can accept encrypted payloads then
@ -101,4 +165,7 @@ rtp_sync_is_time(struct rtp_session *session);
struct rtp_packet *
rtp_sync_packet_next(struct rtp_session *session, struct rtcp_timestamp cur_stamp, char type);
int
rtcp_packet_parse(struct rtcp_packet *pkt, uint8_t *data, size_t size);
#endif /* !__RTP_COMMON_H__ */