mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-09-03 10:43:55 +00:00
rtspsrc: client-managed MIKEY KeyMgmt
Some servers (e.g. Axis cameras) expect the client to propose the encryption key(s) to be used for SRTP / SRTCP. This is required to allow re-keying so as to evade cryptanalysis. Note that the behaviour is not specified by the RFCs. By setting the 'client-managed-mikey-mode' property to 'true', rtspsrc acts as follows: * For a secured profile (RTP/SAVP or RTP/SAVPF), any media in the SDP returned by the server for which a MIKEY key management applies is elligible for client managed mode. The MIKEY from the server is then ignored. * rtspsrc sends a SETUP with a MIKEY payload proposed by the user. The payload is formed by calling the 'request-rtp-key' signal for each elligible stream. During initialisation, 'request-rtcp-key' is also called as usual. The keys returned by both signals should be the same for a single stream, but the mechanism allows a different approach. * The user can start re-keying of a stream by calling SET_PARAMETER. The convenience signal 'set-mikey-parameter' can be used to build a 'KeyMgmt' parameter with a MIKEY payload. * After the server accepts the new parameter, the user can call 'remove-key' and prepare for the new key(s) to be served by signals 'request-rtp-key' & 'request-rtcp-key'. * The signals 'soft-limit' & 'hard-limit' are called when a key reaches the limits of its utilisation. This commit adds support for: * client-managed MIKEY mode to srtpsrc. * Master Key Index (MKI) parsing and encoding to GstMIKEYMessage. * re-keying using the signals 'set-mikey-parameter' & 'remove-key' and then by serving the new key via 'request-rtp-key' & 'request-rtcp-key'. * 'soft-limit' & 'hard-limit' signals, similar to those provided by srtpdec. See also: * https://www.rfc-editor.org/rfc/rfc3830 * https://www.rfc-editor.org/rfc/rfc4567 Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/7587>
This commit is contained in:
parent
efaaa6d888
commit
0f7be28eb1
6 changed files with 1487 additions and 86 deletions
|
@ -2258,7 +2258,7 @@ gst_mikey_message_new_from_caps (GstCaps * caps)
|
||||||
GstStructure *s;
|
GstStructure *s;
|
||||||
GstMapInfo info;
|
GstMapInfo info;
|
||||||
GstBuffer *srtpkey;
|
GstBuffer *srtpkey;
|
||||||
const GValue *val;
|
const GValue *val, *mki_val;
|
||||||
const gchar *cipher, *auth;
|
const gchar *cipher, *auth;
|
||||||
const gchar *srtpcipher, *srtpauth, *srtcpcipher, *srtcpauth;
|
const gchar *srtpcipher, *srtpauth, *srtcpcipher, *srtcpauth;
|
||||||
|
|
||||||
|
@ -2280,12 +2280,8 @@ gst_mikey_message_new_from_caps (GstCaps * caps)
|
||||||
srtcpcipher = gst_structure_get_string (s, "srtcp-cipher");
|
srtcpcipher = gst_structure_get_string (s, "srtcp-cipher");
|
||||||
srtcpauth = gst_structure_get_string (s, "srtcp-auth");
|
srtcpauth = gst_structure_get_string (s, "srtcp-auth");
|
||||||
|
|
||||||
/* we need srtp cipher/auth or srtcp cipher/auth */
|
/* Some mikey messages are intended to only set the SPI/MKI,
|
||||||
if ((srtpcipher == NULL || srtpauth == NULL)
|
* in which case cipher or auth can be absent. */
|
||||||
&& (srtcpcipher == NULL || srtcpauth == NULL)) {
|
|
||||||
GST_WARNING ("could not find the right SRTP parameters in caps");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* prefer srtp cipher over srtcp */
|
/* prefer srtp cipher over srtcp */
|
||||||
cipher = srtpcipher;
|
cipher = srtpcipher;
|
||||||
|
@ -2298,10 +2294,12 @@ gst_mikey_message_new_from_caps (GstCaps * caps)
|
||||||
auth = srtcpauth;
|
auth = srtcpauth;
|
||||||
|
|
||||||
/* get cipher and auth values */
|
/* get cipher and auth values */
|
||||||
if (!enc_alg_from_cipher_name (cipher, &enc_alg) ||
|
if (cipher && (!enc_alg_from_cipher_name (cipher, &enc_alg) ||
|
||||||
!auth_alg_from_cipher_name (cipher, &auth_alg) ||
|
!auth_alg_from_cipher_name (cipher, &auth_alg) ||
|
||||||
!enc_key_length_from_cipher_name (cipher, &enc_key_length) ||
|
!enc_key_length_from_cipher_name (cipher, &enc_key_length) ||
|
||||||
!auth_key_length_from_auth_cipher_name (auth, cipher, &auth_key_length)) {
|
(auth
|
||||||
|
&& !auth_key_length_from_auth_cipher_name (auth, cipher,
|
||||||
|
&auth_key_length)))) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2310,37 +2308,47 @@ gst_mikey_message_new_from_caps (GstCaps * caps)
|
||||||
gst_mikey_message_set_info (msg, GST_MIKEY_VERSION, GST_MIKEY_TYPE_PSK_INIT,
|
gst_mikey_message_set_info (msg, GST_MIKEY_VERSION, GST_MIKEY_TYPE_PSK_INIT,
|
||||||
FALSE, GST_MIKEY_PRF_MIKEY_1, g_random_int (), GST_MIKEY_MAP_TYPE_SRTP);
|
FALSE, GST_MIKEY_PRF_MIKEY_1, g_random_int (), GST_MIKEY_MAP_TYPE_SRTP);
|
||||||
|
|
||||||
/* timestamp is now */
|
if (cipher || auth) {
|
||||||
gst_mikey_message_add_t_now_ntp_utc (msg);
|
/* timestamp is now */
|
||||||
/* add some random data */
|
gst_mikey_message_add_t_now_ntp_utc (msg);
|
||||||
gst_mikey_message_add_rand_len (msg, 16);
|
/* add some random data */
|
||||||
|
gst_mikey_message_add_rand_len (msg, 16);
|
||||||
|
|
||||||
/* the policy '0' is SRTP */
|
/* the policy '0' is SRTP */
|
||||||
payload = gst_mikey_payload_new (GST_MIKEY_PT_SP);
|
payload = gst_mikey_payload_new (GST_MIKEY_PT_SP);
|
||||||
gst_mikey_payload_sp_set (payload, 0, GST_MIKEY_SEC_PROTO_SRTP);
|
gst_mikey_payload_sp_set (payload, 0, GST_MIKEY_SEC_PROTO_SRTP);
|
||||||
|
|
||||||
/* AES-CM or AES-GCM is supported */
|
if (cipher) {
|
||||||
gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_ENC_ALG, 1,
|
/* AES-CM or AES-GCM is supported */
|
||||||
&enc_alg);
|
gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_ENC_ALG, 1,
|
||||||
/* encryption key length */
|
&enc_alg);
|
||||||
gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_ENC_KEY_LEN, 1,
|
/* encryption key length */
|
||||||
&enc_key_length);
|
gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_ENC_KEY_LEN, 1,
|
||||||
/* HMAC-SHA1 or NULL in case of GCM */
|
&enc_key_length);
|
||||||
gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_AUTH_ALG, 1,
|
}
|
||||||
&auth_alg);
|
if (auth) {
|
||||||
/* authentication key length */
|
/* HMAC-SHA1 or NULL in case of GCM */
|
||||||
gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_AUTH_KEY_LEN, 1,
|
gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_AUTH_ALG, 1,
|
||||||
&auth_key_length);
|
&auth_alg);
|
||||||
/* we enable encryption on RTP and RTCP */
|
/* authentication key length */
|
||||||
byte = 1;
|
gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_AUTH_KEY_LEN,
|
||||||
gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_SRTP_ENC, 1,
|
1, &auth_key_length);
|
||||||
&byte);
|
}
|
||||||
gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_SRTCP_ENC, 1,
|
/* we enable encryption on RTP and RTCP */
|
||||||
&byte);
|
byte = 1;
|
||||||
/* we enable authentication on RTP and RTCP */
|
if (cipher) {
|
||||||
gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_SRTP_AUTH, 1,
|
gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_SRTP_ENC, 1,
|
||||||
&byte);
|
&byte);
|
||||||
gst_mikey_message_add_payload (msg, payload);
|
gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_SRTCP_ENC, 1,
|
||||||
|
&byte);
|
||||||
|
}
|
||||||
|
if (auth) {
|
||||||
|
/* we enable authentication on RTP and RTCP */
|
||||||
|
gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_SRTP_AUTH, 1,
|
||||||
|
&byte);
|
||||||
|
}
|
||||||
|
gst_mikey_message_add_payload (msg, payload);
|
||||||
|
}
|
||||||
|
|
||||||
/* make unencrypted KEMAC */
|
/* make unencrypted KEMAC */
|
||||||
payload = gst_mikey_payload_new (GST_MIKEY_PT_KEMAC);
|
payload = gst_mikey_payload_new (GST_MIKEY_PT_KEMAC);
|
||||||
|
@ -2351,6 +2359,22 @@ gst_mikey_message_new_from_caps (GstCaps * caps)
|
||||||
gst_mikey_payload_key_data_set_key (pkd, GST_MIKEY_KD_TEK, info.size,
|
gst_mikey_payload_key_data_set_key (pkd, GST_MIKEY_KD_TEK, info.size,
|
||||||
info.data);
|
info.data);
|
||||||
gst_buffer_unmap (srtpkey, &info);
|
gst_buffer_unmap (srtpkey, &info);
|
||||||
|
/* add optional SPI/MKI
|
||||||
|
* See: https://www.rfc-editor.org/rfc/rfc3830.html#section-6.14
|
||||||
|
*/
|
||||||
|
mki_val = gst_structure_get_value (s, "mki");
|
||||||
|
if (mki_val) {
|
||||||
|
GstBuffer *mki = gst_value_get_buffer (mki_val);
|
||||||
|
if (mki && GST_IS_BUFFER (mki) && gst_buffer_get_size (mki) > 0) {
|
||||||
|
gst_buffer_map (mki, &info, GST_MAP_READ);
|
||||||
|
gst_mikey_payload_key_data_set_spi (pkd, info.size, info.data);
|
||||||
|
gst_buffer_unmap (mki, &info);
|
||||||
|
} else {
|
||||||
|
GST_WARNING
|
||||||
|
("Failed to get 'mki' from caps as a buffer or the buffer is empty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
gst_mikey_payload_kemac_add_sub (payload, pkd);
|
gst_mikey_payload_kemac_add_sub (payload, pkd);
|
||||||
gst_mikey_message_add_payload (msg, payload);
|
gst_mikey_message_add_payload (msg, payload);
|
||||||
|
|
||||||
|
@ -2511,6 +2535,16 @@ gst_mikey_message_to_caps (const GstMIKEYMessage * msg, GstCaps * caps)
|
||||||
gst_buffer_unref (buf);
|
gst_buffer_unref (buf);
|
||||||
|
|
||||||
gst_caps_set_simple (caps, "roc", G_TYPE_UINT, srtp->roc, NULL);
|
gst_caps_set_simple (caps, "roc", G_TYPE_UINT, srtp->roc, NULL);
|
||||||
|
|
||||||
|
/* Get optional SPI/MKI
|
||||||
|
* See: https://www.rfc-editor.org/rfc/rfc3830.html#section-6.13
|
||||||
|
*/
|
||||||
|
if (pkd->kv_type == GST_MIKEY_KV_SPI && pkd->kv_len[0] > 0) {
|
||||||
|
GstBuffer *mki =
|
||||||
|
gst_buffer_new_memdup (pkd->kv_data[0], (gsize) pkd->kv_len[0]);
|
||||||
|
gst_caps_set_simple (caps, "mki", GST_TYPE_BUFFER, mki, NULL);
|
||||||
|
gst_buffer_unref (mki);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gst_caps_set_simple (caps,
|
gst_caps_set_simple (caps,
|
||||||
|
|
|
@ -21760,6 +21760,18 @@
|
||||||
"type": "GstRTSPSrcBufferMode",
|
"type": "GstRTSPSrcBufferMode",
|
||||||
"writable": true
|
"writable": true
|
||||||
},
|
},
|
||||||
|
"client-managed-mikey": {
|
||||||
|
"blurb": "Enable client-managed MIKEY mode",
|
||||||
|
"conditionally-available": false,
|
||||||
|
"construct": false,
|
||||||
|
"construct-only": false,
|
||||||
|
"controllable": false,
|
||||||
|
"default": "false",
|
||||||
|
"mutable": "null",
|
||||||
|
"readable": true,
|
||||||
|
"type": "gboolean",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
"connection-speed": {
|
"connection-speed": {
|
||||||
"blurb": "Network connection speed in kbps (0 = unknown)",
|
"blurb": "Network connection speed in kbps (0 = unknown)",
|
||||||
"conditionally-available": false,
|
"conditionally-available": false,
|
||||||
|
@ -22415,6 +22427,16 @@
|
||||||
],
|
],
|
||||||
"return-type": "void"
|
"return-type": "void"
|
||||||
},
|
},
|
||||||
|
"hard-limit": {
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "arg0",
|
||||||
|
"type": "guint"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"return-type": "void",
|
||||||
|
"when": "last"
|
||||||
|
},
|
||||||
"new-manager": {
|
"new-manager": {
|
||||||
"args": [
|
"args": [
|
||||||
{
|
{
|
||||||
|
@ -22464,6 +22486,17 @@
|
||||||
"return-type": "GstFlowReturn",
|
"return-type": "GstFlowReturn",
|
||||||
"when": "last"
|
"when": "last"
|
||||||
},
|
},
|
||||||
|
"remove-key": {
|
||||||
|
"action": true,
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "arg0",
|
||||||
|
"type": "guint"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"return-type": "gboolean",
|
||||||
|
"when": "last"
|
||||||
|
},
|
||||||
"request-rtcp-key": {
|
"request-rtcp-key": {
|
||||||
"args": [
|
"args": [
|
||||||
{
|
{
|
||||||
|
@ -22474,6 +22507,15 @@
|
||||||
"return-type": "GstCaps",
|
"return-type": "GstCaps",
|
||||||
"when": "last"
|
"when": "last"
|
||||||
},
|
},
|
||||||
|
"request-rtp-key": {
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "arg0",
|
||||||
|
"type": "guint"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"return-type": "GstCaps"
|
||||||
|
},
|
||||||
"select-stream": {
|
"select-stream": {
|
||||||
"args": [
|
"args": [
|
||||||
{
|
{
|
||||||
|
@ -22488,6 +22530,25 @@
|
||||||
"return-type": "gboolean",
|
"return-type": "gboolean",
|
||||||
"when": "last"
|
"when": "last"
|
||||||
},
|
},
|
||||||
|
"set-mikey-parameter": {
|
||||||
|
"action": true,
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "arg0",
|
||||||
|
"type": "guint"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "arg1",
|
||||||
|
"type": "GstCaps"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "arg2",
|
||||||
|
"type": "GstPromise"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"return-type": "gboolean",
|
||||||
|
"when": "last"
|
||||||
|
},
|
||||||
"set-parameter": {
|
"set-parameter": {
|
||||||
"action": true,
|
"action": true,
|
||||||
"args": [
|
"args": [
|
||||||
|
@ -22510,6 +22571,16 @@
|
||||||
],
|
],
|
||||||
"return-type": "gboolean",
|
"return-type": "gboolean",
|
||||||
"when": "last"
|
"when": "last"
|
||||||
|
},
|
||||||
|
"soft-limit": {
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "arg0",
|
||||||
|
"type": "guint"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"return-type": "void",
|
||||||
|
"when": "last"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,6 +93,30 @@
|
||||||
* rtspsrc does not know the difference and will send a PAUSE when you wanted
|
* rtspsrc does not know the difference and will send a PAUSE when you wanted
|
||||||
* a TEARDOWN. The workaround is to hook into the `before-send` signal and
|
* a TEARDOWN. The workaround is to hook into the `before-send` signal and
|
||||||
* return FALSE in this case.
|
* return FALSE in this case.
|
||||||
|
*
|
||||||
|
* Some servers (e.g. Axis cameras) expect the client to propose the encryption
|
||||||
|
* key(s) to be used for SRTP / SRTCP. This is required to allow re-keying so
|
||||||
|
* as to evade cryptanalysis. Note that the behaviour is not specified by the
|
||||||
|
* RFCs. By setting the 'client-managed-mikey-mode' property to 'true', rtspsrc
|
||||||
|
* acts as follows:
|
||||||
|
*
|
||||||
|
* * For a secured profile (RTP/SAVP or RTP/SAVPF), any media in the SDP
|
||||||
|
* returned by the server for which MIKEY key management applies is
|
||||||
|
* elligible for client managed mode. The MIKEY from the server is then
|
||||||
|
* ignored.
|
||||||
|
* * rtspsrc sends a SETUP with a MIKEY payload proposed by the user. The
|
||||||
|
* payload is formed by calling the 'request-rtp-key' signal for each
|
||||||
|
* elligible stream. During initialisation, 'request-rtcp-key' is also
|
||||||
|
* called as usual. The keys returned by both signals should be the same
|
||||||
|
* for a single stream, but the mechanism allows a different approach.
|
||||||
|
* * The user can start re-keying of a stream by calling SET_PARAMETER.
|
||||||
|
* The convenience signal 'set-mikey-parameter' can be used to build a
|
||||||
|
* 'KeyMgmt' parameter with a MIKEY payload.
|
||||||
|
* * After the server accepts the new parameter, the user can call
|
||||||
|
* 'remove-key' and prepare for the new key(s) to be served by signals
|
||||||
|
* 'request-rtp-key' & 'request-rtcp-key'.
|
||||||
|
* * The signals 'soft-limit' & 'hard-limit' are called when a key
|
||||||
|
* reaches the limits of its utilisation.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifdef HAVE_CONFIG_H
|
#ifdef HAVE_CONFIG_H
|
||||||
|
@ -147,6 +171,9 @@ enum
|
||||||
SIGNAL_SELECT_STREAM,
|
SIGNAL_SELECT_STREAM,
|
||||||
SIGNAL_NEW_MANAGER,
|
SIGNAL_NEW_MANAGER,
|
||||||
SIGNAL_REQUEST_RTCP_KEY,
|
SIGNAL_REQUEST_RTCP_KEY,
|
||||||
|
SIGNAL_REQUEST_RTP_KEY,
|
||||||
|
SIGNAL_SOFT_LIMIT,
|
||||||
|
SIGNAL_HARD_LIMIT,
|
||||||
SIGNAL_ACCEPT_CERTIFICATE,
|
SIGNAL_ACCEPT_CERTIFICATE,
|
||||||
SIGNAL_BEFORE_SEND,
|
SIGNAL_BEFORE_SEND,
|
||||||
SIGNAL_PUSH_BACKCHANNEL_BUFFER,
|
SIGNAL_PUSH_BACKCHANNEL_BUFFER,
|
||||||
|
@ -154,6 +181,8 @@ enum
|
||||||
SIGNAL_GET_PARAMETERS,
|
SIGNAL_GET_PARAMETERS,
|
||||||
SIGNAL_SET_PARAMETER,
|
SIGNAL_SET_PARAMETER,
|
||||||
SIGNAL_PUSH_BACKCHANNEL_SAMPLE,
|
SIGNAL_PUSH_BACKCHANNEL_SAMPLE,
|
||||||
|
SIGNAL_SET_MIKEY_PARAMETER,
|
||||||
|
SIGNAL_REMOVE_KEY,
|
||||||
LAST_SIGNAL
|
LAST_SIGNAL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -316,6 +345,7 @@ gst_rtsp_backchannel_get_type (void)
|
||||||
#define DEFAULT_IGNORE_X_SERVER_REPLY FALSE
|
#define DEFAULT_IGNORE_X_SERVER_REPLY FALSE
|
||||||
#define DEFAULT_TCP_TIMESTAMP FALSE
|
#define DEFAULT_TCP_TIMESTAMP FALSE
|
||||||
#define DEFAULT_FORCE_NON_COMPLIANT_URL FALSE
|
#define DEFAULT_FORCE_NON_COMPLIANT_URL FALSE
|
||||||
|
#define DEFAULT_CLIENT_MANAGED_MIKEY FALSE
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
|
@ -369,6 +399,7 @@ enum
|
||||||
PROP_EXTRA_HTTP_REQUEST_HEADERS,
|
PROP_EXTRA_HTTP_REQUEST_HEADERS,
|
||||||
PROP_TCP_TIMESTAMP,
|
PROP_TCP_TIMESTAMP,
|
||||||
PROP_FORCE_NON_COMPLIANT_URL,
|
PROP_FORCE_NON_COMPLIANT_URL,
|
||||||
|
PROP_CLIENT_MANAGED_MIKEY,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define GST_TYPE_RTSP_NAT_METHOD (gst_rtsp_nat_method_get_type())
|
#define GST_TYPE_RTSP_NAT_METHOD (gst_rtsp_nat_method_get_type())
|
||||||
|
@ -480,6 +511,17 @@ static GstFlowReturn gst_rtspsrc_push_backchannel_sample (GstRTSPSrc * src,
|
||||||
|
|
||||||
static void gst_rtspsrc_reset_flows (GstRTSPSrc * src);
|
static void gst_rtspsrc_reset_flows (GstRTSPSrc * src);
|
||||||
|
|
||||||
|
static GstCaps *signal_get_srtcp_params (GstRTSPSrc * src,
|
||||||
|
GstRTSPStream * stream);
|
||||||
|
|
||||||
|
static GstCaps *signal_get_srtp_params (GstRTSPSrc * src,
|
||||||
|
GstRTSPStream * stream);
|
||||||
|
|
||||||
|
static gboolean set_mikey_parameter (GstRTSPSrc * src, const guint id,
|
||||||
|
GstCaps * mikey, GstPromise * promise);
|
||||||
|
|
||||||
|
static gboolean remove_key (GstRTSPSrc * src, const guint id);
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
guint8 pt;
|
guint8 pt;
|
||||||
|
@ -1172,6 +1214,36 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass)
|
||||||
DEFAULT_FORCE_NON_COMPLIANT_URL,
|
DEFAULT_FORCE_NON_COMPLIANT_URL,
|
||||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GstRTSPSrc:client-managed-mikey
|
||||||
|
*
|
||||||
|
* Some servers (e.g. Axis Cameras) advertise a `key-mgmt:mikey` attribute in
|
||||||
|
* the SDP from the reply to DESCRIBE, but expect the client to provide the
|
||||||
|
* key for both the client (SRTCP/RR) and the server (SRTP/SRTCP SR).
|
||||||
|
*
|
||||||
|
* When this mode is enabled, for each eligible media, the server crypto
|
||||||
|
* policy is discarded. The crypto policy for this media is built from the
|
||||||
|
* key returned by the signals 'request-rtp-key' & 'request-rtcp-key'.
|
||||||
|
*
|
||||||
|
* A media is eligible for this mode if:
|
||||||
|
*
|
||||||
|
* * No session level MIKEY key-mgmt are defined and the media level key-mgmt
|
||||||
|
* is MIKEY.
|
||||||
|
* * A session level MIKEY key-mgmt is defined and no media level key-mgmts
|
||||||
|
* are defined.
|
||||||
|
* * A session level MIKEY key-mgmt is defined and the media level key-mgmt is
|
||||||
|
* also MIKEY.
|
||||||
|
*
|
||||||
|
* Since: 1.26
|
||||||
|
*/
|
||||||
|
g_object_class_install_property (gobject_class,
|
||||||
|
PROP_CLIENT_MANAGED_MIKEY,
|
||||||
|
g_param_spec_boolean ("client-managed-mikey",
|
||||||
|
"Client-managed MIKEY",
|
||||||
|
"Enable client-managed MIKEY mode",
|
||||||
|
DEFAULT_CLIENT_MANAGED_MIKEY,
|
||||||
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GstRTSPSrc::handle-request:
|
* GstRTSPSrc::handle-request:
|
||||||
* @rtspsrc: a #GstRTSPSrc
|
* @rtspsrc: a #GstRTSPSrc
|
||||||
|
@ -1263,6 +1335,58 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass)
|
||||||
g_signal_new ("request-rtcp-key", G_TYPE_FROM_CLASS (klass),
|
g_signal_new ("request-rtcp-key", G_TYPE_FROM_CLASS (klass),
|
||||||
0, 0, NULL, NULL, NULL, GST_TYPE_CAPS, 1, G_TYPE_UINT);
|
0, 0, NULL, NULL, NULL, GST_TYPE_CAPS, 1, G_TYPE_UINT);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GstRTSPSrc::request-rtp-key:
|
||||||
|
* @rtspsrc: a #GstRTSPSrc
|
||||||
|
* @num: the stream number
|
||||||
|
*
|
||||||
|
* Signal emitted to get the crypto parameters relevant to the RTP
|
||||||
|
* stream. User should provide the key and the RTP encryption ciphers
|
||||||
|
* and authentication, and return them wrapped in a GstCaps.
|
||||||
|
*
|
||||||
|
* Applications should connect to this signal when 'client-managed-mikey'
|
||||||
|
* mode is enabled. The crypto parameters are usually the same as those
|
||||||
|
* use by the 'request-rtcp-key' signal.
|
||||||
|
*
|
||||||
|
* Since: 1.26
|
||||||
|
*/
|
||||||
|
gst_rtspsrc_signals[SIGNAL_REQUEST_RTP_KEY] =
|
||||||
|
g_signal_new ("request-rtp-key", G_TYPE_FROM_CLASS (klass),
|
||||||
|
0, 0, NULL, NULL, NULL, GST_TYPE_CAPS, 1, G_TYPE_UINT);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GstRTSPSrc::soft-limit:
|
||||||
|
* @rtspsrc: a #GstRTSPSrc
|
||||||
|
* @id: The id of the stream
|
||||||
|
*
|
||||||
|
* When the 'client-managed-mikey' mode is enabled, this signal
|
||||||
|
* is emitted when the stream with @id has reached the
|
||||||
|
* soft limit of utilisation of it's master encryption key.
|
||||||
|
* User should start a re-keying procedure.
|
||||||
|
*
|
||||||
|
* Since: 1.26
|
||||||
|
*/
|
||||||
|
gst_rtspsrc_signals[SIGNAL_SOFT_LIMIT] =
|
||||||
|
g_signal_new ("soft-limit", G_TYPE_FROM_CLASS (klass),
|
||||||
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GstRTSPSrc::hard-limit:
|
||||||
|
* @rtspsrc: a #GstRTSPSrc
|
||||||
|
* @id: The id of the stream
|
||||||
|
*
|
||||||
|
* When the 'client-managed-mikey' mode is enabled, this signal
|
||||||
|
* is emitted when the stream with @id has reached the
|
||||||
|
* hard limit of utilisation of it's master encryption key.
|
||||||
|
* User should start a re-keying procedure. Failure to do so,
|
||||||
|
* will lead to buffers of this stream being dropped.
|
||||||
|
*
|
||||||
|
* Since: 1.26
|
||||||
|
*/
|
||||||
|
gst_rtspsrc_signals[SIGNAL_HARD_LIMIT] =
|
||||||
|
g_signal_new ("hard-limit", G_TYPE_FROM_CLASS (klass),
|
||||||
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GstRTSPSrc::accept-certificate:
|
* GstRTSPSrc::accept-certificate:
|
||||||
* @rtspsrc: a #GstRTSPSrc
|
* @rtspsrc: a #GstRTSPSrc
|
||||||
|
@ -1384,6 +1508,12 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass)
|
||||||
*
|
*
|
||||||
* Handle the SET_PARAMETER signal.
|
* Handle the SET_PARAMETER signal.
|
||||||
*
|
*
|
||||||
|
* The #GstPromise reply consists in the following fields:
|
||||||
|
*
|
||||||
|
* * 'rtsp-result': set to 0 if the HTTP request could be processed.
|
||||||
|
* * 'rtsp-code': the HTTP status code returned by the server.
|
||||||
|
* * 'rtsp-reason': a human-readable version of the HTTP status code.
|
||||||
|
*
|
||||||
* Returns: %TRUE when the command could be issued, %FALSE otherwise
|
* Returns: %TRUE when the command could be issued, %FALSE otherwise
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
@ -1393,6 +1523,55 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass)
|
||||||
set_parameter), NULL, NULL, NULL, G_TYPE_BOOLEAN, 4, G_TYPE_STRING,
|
set_parameter), NULL, NULL, NULL, G_TYPE_BOOLEAN, 4, G_TYPE_STRING,
|
||||||
G_TYPE_STRING, G_TYPE_STRING, GST_TYPE_PROMISE);
|
G_TYPE_STRING, G_TYPE_STRING, GST_TYPE_PROMISE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GstRTSPSrc::set-mikey-parameter:
|
||||||
|
* @rtspsrc: a #GstRTSPSrc
|
||||||
|
* @parameter: the id of the stream
|
||||||
|
* @parameter: the caps for the MIKEY payload
|
||||||
|
* @parameter: a pointer to #GstPromise
|
||||||
|
*
|
||||||
|
* Sends a SET_PARAMETER to the server with a MIKEY payload.
|
||||||
|
*
|
||||||
|
* This will call SET_PARAMETER with parameter 'KeyMgmt' and a MIKEY
|
||||||
|
* payload formed from the provided stream's ssrc and MIKEY caps.
|
||||||
|
*
|
||||||
|
* The #GstPromise reply consists in the following fields:
|
||||||
|
*
|
||||||
|
* * 'rtsp-result': set to 0 if the HTTP request could be processed.
|
||||||
|
* * 'rtsp-code': the HTTP status code returned by the server.
|
||||||
|
* * 'rtsp-reason': a human-readable version of the HTTP status code.
|
||||||
|
*
|
||||||
|
* Returns: %TRUE when the command could be issued, %FALSE otherwise
|
||||||
|
*
|
||||||
|
* Since: 1.26
|
||||||
|
*/
|
||||||
|
gst_rtspsrc_signals[SIGNAL_SET_MIKEY_PARAMETER] =
|
||||||
|
g_signal_new ("set-mikey-parameter", G_TYPE_FROM_CLASS (klass),
|
||||||
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstRTSPSrcClass,
|
||||||
|
set_mikey_parameter), NULL, NULL, NULL, G_TYPE_BOOLEAN, 3,
|
||||||
|
G_TYPE_UINT, GST_TYPE_CAPS, GST_TYPE_PROMISE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GstRTSPSrc::remove-key:
|
||||||
|
* @rtspsrc: a #GstRTSPSrc
|
||||||
|
* @parameter: the id of the stream for which to remove the key.
|
||||||
|
*
|
||||||
|
* Removes the key for a specific stream.
|
||||||
|
*
|
||||||
|
* When the 'client-managed-mikey' mode is enabled, this can be used
|
||||||
|
* after informing the server of the new crypto params (see signal
|
||||||
|
* 'set-mikey-parameter') to remove previous keys and force srtpdec
|
||||||
|
* to request new keys.
|
||||||
|
*
|
||||||
|
* Returns: %TRUE when the command could be issued, %FALSE otherwise
|
||||||
|
*
|
||||||
|
* Since: 1.26
|
||||||
|
*/
|
||||||
|
gst_rtspsrc_signals[SIGNAL_REMOVE_KEY] =
|
||||||
|
g_signal_new ("remove-key", G_TYPE_FROM_CLASS (klass),
|
||||||
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstRTSPSrcClass,
|
||||||
|
remove_key), NULL, NULL, NULL, G_TYPE_BOOLEAN, 1, G_TYPE_UINT);
|
||||||
|
|
||||||
gstelement_class->send_event = gst_rtspsrc_send_event;
|
gstelement_class->send_event = gst_rtspsrc_send_event;
|
||||||
gstelement_class->provide_clock = gst_rtspsrc_provide_clock;
|
gstelement_class->provide_clock = gst_rtspsrc_provide_clock;
|
||||||
gstelement_class->change_state = gst_rtspsrc_change_state;
|
gstelement_class->change_state = gst_rtspsrc_change_state;
|
||||||
|
@ -1413,6 +1592,8 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass)
|
||||||
klass->get_parameter = GST_DEBUG_FUNCPTR (get_parameter);
|
klass->get_parameter = GST_DEBUG_FUNCPTR (get_parameter);
|
||||||
klass->get_parameters = GST_DEBUG_FUNCPTR (get_parameters);
|
klass->get_parameters = GST_DEBUG_FUNCPTR (get_parameters);
|
||||||
klass->set_parameter = GST_DEBUG_FUNCPTR (set_parameter);
|
klass->set_parameter = GST_DEBUG_FUNCPTR (set_parameter);
|
||||||
|
klass->set_mikey_parameter = GST_DEBUG_FUNCPTR (set_mikey_parameter);
|
||||||
|
klass->remove_key = GST_DEBUG_FUNCPTR (remove_key);
|
||||||
|
|
||||||
gst_rtsp_ext_list_init ();
|
gst_rtsp_ext_list_init ();
|
||||||
|
|
||||||
|
@ -1971,6 +2152,9 @@ gst_rtspsrc_set_property (GObject * object, guint prop_id, const GValue * value,
|
||||||
case PROP_FORCE_NON_COMPLIANT_URL:
|
case PROP_FORCE_NON_COMPLIANT_URL:
|
||||||
rtspsrc->force_non_compliant_url = g_value_get_boolean (value);
|
rtspsrc->force_non_compliant_url = g_value_get_boolean (value);
|
||||||
break;
|
break;
|
||||||
|
case PROP_CLIENT_MANAGED_MIKEY:
|
||||||
|
rtspsrc->client_managed_mikey = g_value_get_boolean (value);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
|
@ -2153,6 +2337,9 @@ gst_rtspsrc_get_property (GObject * object, guint prop_id, GValue * value,
|
||||||
case PROP_FORCE_NON_COMPLIANT_URL:
|
case PROP_FORCE_NON_COMPLIANT_URL:
|
||||||
g_value_set_boolean (value, rtspsrc->force_non_compliant_url);
|
g_value_set_boolean (value, rtspsrc->force_non_compliant_url);
|
||||||
break;
|
break;
|
||||||
|
case PROP_CLIENT_MANAGED_MIKEY:
|
||||||
|
g_value_set_boolean (value, rtspsrc->client_managed_mikey);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
|
@ -3753,6 +3940,25 @@ stream_get_caps_for_pt (GstRTSPStream * stream, guint pt)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* @caps: (transfer-full) */
|
||||||
|
static void
|
||||||
|
stream_set_caps_for_pt (GstRTSPStream * stream, guint pt, GstCaps * caps)
|
||||||
|
{
|
||||||
|
guint i, len;
|
||||||
|
|
||||||
|
len = stream->ptmap->len;
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i);
|
||||||
|
if (item->pt == pt) {
|
||||||
|
if (item->caps)
|
||||||
|
gst_caps_unref (item->caps);
|
||||||
|
|
||||||
|
item->caps = caps;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static GstCaps *
|
static GstCaps *
|
||||||
request_pt_map (GstElement * manager, guint session, guint pt, GstRTSPSrc * src)
|
request_pt_map (GstElement * manager, guint session, guint pt, GstRTSPSrc * src)
|
||||||
{
|
{
|
||||||
|
@ -3941,17 +4147,87 @@ set_manager_buffer_mode (GstRTSPSrc * src)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
update_srtcp_params (GstRTSPStream * stream)
|
||||||
|
{
|
||||||
|
GstStructure *s = gst_caps_get_structure (stream->srtcpparams, 0);
|
||||||
|
if (s) {
|
||||||
|
GstBuffer *buf;
|
||||||
|
const gchar *str;
|
||||||
|
GType ciphertype, authtype;
|
||||||
|
GValue rtcp_cipher = G_VALUE_INIT, rtcp_auth = G_VALUE_INIT;
|
||||||
|
|
||||||
|
ciphertype = g_type_from_name ("GstSrtpCipherType");
|
||||||
|
authtype = g_type_from_name ("GstSrtpAuthType");
|
||||||
|
g_value_init (&rtcp_cipher, ciphertype);
|
||||||
|
g_value_init (&rtcp_auth, authtype);
|
||||||
|
|
||||||
|
str = gst_structure_get_string (s, "srtcp-cipher");
|
||||||
|
gst_value_deserialize (&rtcp_cipher, str);
|
||||||
|
str = gst_structure_get_string (s, "srtcp-auth");
|
||||||
|
gst_value_deserialize (&rtcp_auth, str);
|
||||||
|
gst_structure_get (s, "srtp-key", GST_TYPE_BUFFER, &buf, NULL);
|
||||||
|
|
||||||
|
g_object_set_property (G_OBJECT (stream->srtpenc), "rtp-cipher",
|
||||||
|
&rtcp_cipher);
|
||||||
|
g_object_set_property (G_OBJECT (stream->srtpenc), "rtp-auth", &rtcp_auth);
|
||||||
|
g_object_set_property (G_OBJECT (stream->srtpenc), "rtcp-cipher",
|
||||||
|
&rtcp_cipher);
|
||||||
|
g_object_set_property (G_OBJECT (stream->srtpenc), "rtcp-auth", &rtcp_auth);
|
||||||
|
g_object_set (stream->srtpenc, "key", buf, NULL);
|
||||||
|
|
||||||
|
g_value_unset (&rtcp_cipher);
|
||||||
|
g_value_unset (&rtcp_auth);
|
||||||
|
gst_buffer_unref (buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static GstCaps *
|
static GstCaps *
|
||||||
request_key (GstElement * srtpdec, guint ssrc, GstRTSPStream * stream)
|
request_key (GstElement * srtpdec, guint ssrc, GstRTSPStream * stream)
|
||||||
{
|
{
|
||||||
guint i;
|
guint i;
|
||||||
GstCaps *caps;
|
GstCaps *key_caps;
|
||||||
GstMIKEYMessage *msg = stream->mikey;
|
GstMIKEYMessage *msg = NULL;
|
||||||
|
GstRTSPSrc *rtspsrc = GST_RTSPSRC (stream->parent);
|
||||||
|
|
||||||
GST_DEBUG ("request key SSRC %u", ssrc);
|
GST_DEBUG_OBJECT (rtspsrc, "request key stream with id %u SSRC %u",
|
||||||
|
stream->id, ssrc);
|
||||||
|
|
||||||
caps = gst_caps_ref (stream_get_caps_for_pt (stream, stream->default_pt));
|
if (stream->mikey) {
|
||||||
caps = gst_caps_make_writable (caps);
|
key_caps =
|
||||||
|
gst_caps_ref (stream_get_caps_for_pt (stream, stream->default_pt));
|
||||||
|
} else if (rtspsrc->client_managed_mikey) {
|
||||||
|
key_caps = signal_get_srtp_params (rtspsrc, stream);
|
||||||
|
if (key_caps == NULL) {
|
||||||
|
GST_ERROR_OBJECT (rtspsrc, "no key caps returned for stream with id %u",
|
||||||
|
stream->id);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream->mikey = gst_mikey_message_new_from_caps (key_caps);
|
||||||
|
if (stream->mikey == NULL) {
|
||||||
|
GST_ERROR_OBJECT (rtspsrc, "failed to create MIKEY for stream with id %u",
|
||||||
|
stream->id);
|
||||||
|
gst_caps_unref (key_caps);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream_set_caps_for_pt (stream, stream->default_pt,
|
||||||
|
gst_caps_ref (key_caps));
|
||||||
|
|
||||||
|
/* also renew RTCP crypto params in case we are re-keying */
|
||||||
|
if (stream->srtcpparams)
|
||||||
|
gst_caps_unref (stream->srtcpparams);
|
||||||
|
|
||||||
|
stream->srtcpparams = signal_get_srtcp_params (rtspsrc, stream);
|
||||||
|
update_srtcp_params (stream);
|
||||||
|
} else {
|
||||||
|
GST_ERROR_OBJECT (rtspsrc, "No MIKEYs for stream with id %u", stream->id);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
key_caps = gst_caps_make_writable (key_caps);
|
||||||
|
|
||||||
/* parse crypto sessions and look for the SSRC rollover counter */
|
/* parse crypto sessions and look for the SSRC rollover counter */
|
||||||
msg = stream->mikey;
|
msg = stream->mikey;
|
||||||
|
@ -3959,12 +4235,44 @@ request_key (GstElement * srtpdec, guint ssrc, GstRTSPStream * stream)
|
||||||
const GstMIKEYMapSRTP *map = gst_mikey_message_get_cs_srtp (msg, i);
|
const GstMIKEYMapSRTP *map = gst_mikey_message_get_cs_srtp (msg, i);
|
||||||
|
|
||||||
if (ssrc == map->ssrc) {
|
if (ssrc == map->ssrc) {
|
||||||
gst_caps_set_simple (caps, "roc", G_TYPE_UINT, map->roc, NULL);
|
gst_caps_set_simple (key_caps, "roc", G_TYPE_UINT, map->roc, NULL);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return caps;
|
return key_caps;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GstCaps *
|
||||||
|
on_soft_limit (GstElement * srtpdec, guint ssrc, gpointer user_data)
|
||||||
|
{
|
||||||
|
GstRTSPStream *stream = (GstRTSPStream *) user_data;
|
||||||
|
GstRTSPSrc *rtspsrc = GST_RTSPSRC_CAST (stream->parent);
|
||||||
|
|
||||||
|
GST_DEBUG_OBJECT (rtspsrc,
|
||||||
|
"Emitting 'soft-limit' for stream with id %u SSRC %u", stream->id, ssrc);
|
||||||
|
|
||||||
|
g_signal_emit (rtspsrc, gst_rtspsrc_signals[SIGNAL_SOFT_LIMIT], 0,
|
||||||
|
stream->id);
|
||||||
|
|
||||||
|
/* don't return CAPS, user needs to start a re-keying procedure */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GstCaps *
|
||||||
|
on_hard_limit (GstElement * srtpdec, guint ssrc, gpointer user_data)
|
||||||
|
{
|
||||||
|
GstRTSPStream *stream = (GstRTSPStream *) user_data;
|
||||||
|
GstRTSPSrc *rtspsrc = GST_RTSPSRC_CAST (stream->parent);
|
||||||
|
|
||||||
|
GST_DEBUG_OBJECT (rtspsrc,
|
||||||
|
"Emitting 'hard-limit' for stream with id %u SSRC %u", stream->id, ssrc);
|
||||||
|
|
||||||
|
g_signal_emit (rtspsrc, gst_rtspsrc_signals[SIGNAL_HARD_LIMIT], 0,
|
||||||
|
stream->id);
|
||||||
|
|
||||||
|
/* don't return CAPS, user needs to start a re-keying procedure */
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static GstElement *
|
static GstElement *
|
||||||
|
@ -3980,6 +4288,7 @@ request_rtp_decoder (GstElement * rtpbin, guint session, GstRTSPStream * stream)
|
||||||
|
|
||||||
if (stream->srtpdec == NULL) {
|
if (stream->srtpdec == NULL) {
|
||||||
gchar *name;
|
gchar *name;
|
||||||
|
GstRTSPSrc *rtspsrc;
|
||||||
|
|
||||||
name = g_strdup_printf ("srtpdec_%u", session);
|
name = g_strdup_printf ("srtpdec_%u", session);
|
||||||
stream->srtpdec = gst_element_factory_make ("srtpdec", name);
|
stream->srtpdec = gst_element_factory_make ("srtpdec", name);
|
||||||
|
@ -3992,6 +4301,15 @@ request_rtp_decoder (GstElement * rtpbin, guint session, GstRTSPStream * stream)
|
||||||
}
|
}
|
||||||
g_signal_connect (stream->srtpdec, "request-key",
|
g_signal_connect (stream->srtpdec, "request-key",
|
||||||
(GCallback) request_key, stream);
|
(GCallback) request_key, stream);
|
||||||
|
|
||||||
|
rtspsrc = GST_RTSPSRC_CAST (stream->parent);
|
||||||
|
if (rtspsrc->client_managed_mikey) {
|
||||||
|
g_signal_connect (stream->srtpdec, "soft-limit",
|
||||||
|
(GCallback) on_soft_limit, stream);
|
||||||
|
|
||||||
|
g_signal_connect (stream->srtpdec, "hard-limit",
|
||||||
|
(GCallback) on_hard_limit, stream);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return gst_object_ref (stream->srtpdec);
|
return gst_object_ref (stream->srtpdec);
|
||||||
}
|
}
|
||||||
|
@ -4012,8 +4330,6 @@ request_rtcp_encoder (GstElement * rtpbin, guint session,
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (stream->srtpenc == NULL) {
|
if (stream->srtpenc == NULL) {
|
||||||
GstStructure *s;
|
|
||||||
|
|
||||||
name = g_strdup_printf ("srtpenc_%u", session);
|
name = g_strdup_printf ("srtpenc_%u", session);
|
||||||
stream->srtpenc = gst_element_factory_make ("srtpenc", name);
|
stream->srtpenc = gst_element_factory_make ("srtpenc", name);
|
||||||
g_free (name);
|
g_free (name);
|
||||||
|
@ -4025,38 +4341,7 @@ request_rtcp_encoder (GstElement * rtpbin, guint session,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* get RTCP crypto parameters from caps */
|
/* get RTCP crypto parameters from caps */
|
||||||
s = gst_caps_get_structure (stream->srtcpparams, 0);
|
update_srtcp_params (stream);
|
||||||
if (s) {
|
|
||||||
GstBuffer *buf;
|
|
||||||
const gchar *str;
|
|
||||||
GType ciphertype, authtype;
|
|
||||||
GValue rtcp_cipher = G_VALUE_INIT, rtcp_auth = G_VALUE_INIT;
|
|
||||||
|
|
||||||
ciphertype = g_type_from_name ("GstSrtpCipherType");
|
|
||||||
authtype = g_type_from_name ("GstSrtpAuthType");
|
|
||||||
g_value_init (&rtcp_cipher, ciphertype);
|
|
||||||
g_value_init (&rtcp_auth, authtype);
|
|
||||||
|
|
||||||
str = gst_structure_get_string (s, "srtcp-cipher");
|
|
||||||
gst_value_deserialize (&rtcp_cipher, str);
|
|
||||||
str = gst_structure_get_string (s, "srtcp-auth");
|
|
||||||
gst_value_deserialize (&rtcp_auth, str);
|
|
||||||
gst_structure_get (s, "srtp-key", GST_TYPE_BUFFER, &buf, NULL);
|
|
||||||
|
|
||||||
g_object_set_property (G_OBJECT (stream->srtpenc), "rtp-cipher",
|
|
||||||
&rtcp_cipher);
|
|
||||||
g_object_set_property (G_OBJECT (stream->srtpenc), "rtp-auth",
|
|
||||||
&rtcp_auth);
|
|
||||||
g_object_set_property (G_OBJECT (stream->srtpenc), "rtcp-cipher",
|
|
||||||
&rtcp_cipher);
|
|
||||||
g_object_set_property (G_OBJECT (stream->srtpenc), "rtcp-auth",
|
|
||||||
&rtcp_auth);
|
|
||||||
g_object_set (stream->srtpenc, "key", buf, NULL);
|
|
||||||
|
|
||||||
g_value_unset (&rtcp_cipher);
|
|
||||||
g_value_unset (&rtcp_auth);
|
|
||||||
gst_buffer_unref (buf);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
name = g_strdup_printf ("rtcp_sink_%d", session);
|
name = g_strdup_printf ("rtcp_sink_%d", session);
|
||||||
pad = gst_element_request_pad_simple (stream->srtpenc, name);
|
pad = gst_element_request_pad_simple (stream->srtpenc, name);
|
||||||
|
@ -7407,6 +7692,20 @@ signal_get_srtcp_params (GstRTSPSrc * src, GstRTSPStream * stream)
|
||||||
g_signal_emit (src, gst_rtspsrc_signals[SIGNAL_REQUEST_RTCP_KEY], 0,
|
g_signal_emit (src, gst_rtspsrc_signals[SIGNAL_REQUEST_RTCP_KEY], 0,
|
||||||
stream->id, &caps);
|
stream->id, &caps);
|
||||||
|
|
||||||
|
if (caps != NULL)
|
||||||
|
GST_DEBUG_OBJECT (src, "SRTCP parameters received");
|
||||||
|
|
||||||
|
return caps;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GstCaps *
|
||||||
|
signal_get_srtp_params (GstRTSPSrc * src, GstRTSPStream * stream)
|
||||||
|
{
|
||||||
|
GstCaps *caps = NULL;
|
||||||
|
|
||||||
|
g_signal_emit (src, gst_rtspsrc_signals[SIGNAL_REQUEST_RTP_KEY], 0,
|
||||||
|
stream->id, &caps);
|
||||||
|
|
||||||
if (caps != NULL)
|
if (caps != NULL)
|
||||||
GST_DEBUG_OBJECT (src, "SRTP parameters received");
|
GST_DEBUG_OBJECT (src, "SRTP parameters received");
|
||||||
|
|
||||||
|
@ -8093,6 +8392,127 @@ cleanup_error:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gchar *
|
||||||
|
gst_rtspsrc_stream_make_renew_keymgmt (GstRTSPSrc * src, GstRTSPStream * stream,
|
||||||
|
GstCaps * mikey)
|
||||||
|
{
|
||||||
|
gchar *base64, *result = NULL;
|
||||||
|
GstMIKEYMessage *mikey_msg;
|
||||||
|
|
||||||
|
mikey_msg = gst_mikey_message_new_from_caps (mikey);
|
||||||
|
if (mikey_msg == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* add policy '0' for our SSRC */
|
||||||
|
gst_mikey_message_add_cs_srtp (mikey_msg, 0, stream->ssrc, 0);
|
||||||
|
|
||||||
|
base64 = gst_mikey_message_base64_encode (mikey_msg);
|
||||||
|
gst_mikey_message_unref (mikey_msg);
|
||||||
|
|
||||||
|
if (base64) {
|
||||||
|
result = gst_sdp_make_keymgmt (stream->conninfo.location, base64);
|
||||||
|
g_free (base64);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
set_mikey_parameter (GstRTSPSrc * src, const guint id, GstCaps * mikey_caps,
|
||||||
|
GstPromise * promise)
|
||||||
|
{
|
||||||
|
gboolean res;
|
||||||
|
GstRTSPStream *stream;
|
||||||
|
gchar *keymgmt;
|
||||||
|
|
||||||
|
GST_LOG_OBJECT (src,
|
||||||
|
"setting MIKEY parameter for stream with id %u: %" GST_PTR_FORMAT, id,
|
||||||
|
mikey_caps);
|
||||||
|
|
||||||
|
if (mikey_caps == NULL) {
|
||||||
|
GST_ERROR_OBJECT (src, "invalid caps");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (src->state == GST_RTSP_STATE_INVALID) {
|
||||||
|
GST_ERROR_OBJECT (src, "invalid state");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
GST_OBJECT_LOCK (src);
|
||||||
|
|
||||||
|
stream = find_stream (src, &id, (gpointer) find_stream_by_id);
|
||||||
|
|
||||||
|
if (stream == NULL) {
|
||||||
|
GST_OBJECT_UNLOCK (src);
|
||||||
|
GST_ERROR_OBJECT (src, "no streams with id %u", id);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream->profile != GST_RTSP_PROFILE_SAVP &&
|
||||||
|
stream->profile != GST_RTSP_PROFILE_SAVPF) {
|
||||||
|
GST_OBJECT_UNLOCK (src);
|
||||||
|
GST_WARNING_OBJECT (src, "stream with id %u, is not encrypted", id);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
keymgmt = gst_rtspsrc_stream_make_renew_keymgmt (src, stream, mikey_caps);
|
||||||
|
|
||||||
|
GST_OBJECT_UNLOCK (src);
|
||||||
|
|
||||||
|
if (keymgmt == NULL) {
|
||||||
|
GST_ERROR_OBJECT (src,
|
||||||
|
"failed to build MIKEY for stream with id %u: %"
|
||||||
|
GST_PTR_FORMAT, id, mikey_caps);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = set_parameter (src, "KeyMgmt", keymgmt, NULL, promise);
|
||||||
|
g_free (keymgmt);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
remove_key (GstRTSPSrc * src, const guint id)
|
||||||
|
{
|
||||||
|
GstRTSPStream *stream;
|
||||||
|
|
||||||
|
GST_LOG_OBJECT (src, "Removing key for stream with id %u", id);
|
||||||
|
|
||||||
|
if (src->state == GST_RTSP_STATE_INVALID) {
|
||||||
|
GST_ERROR_OBJECT (src, "invalid state");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
GST_OBJECT_LOCK (src);
|
||||||
|
|
||||||
|
stream = find_stream (src, &id, (gpointer) find_stream_by_id);
|
||||||
|
|
||||||
|
if (stream == NULL) {
|
||||||
|
GST_ERROR_OBJECT (src, "no streams with id %u", id);
|
||||||
|
GST_OBJECT_UNLOCK (src);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream->profile != GST_RTSP_PROFILE_SAVP &&
|
||||||
|
stream->profile != GST_RTSP_PROFILE_SAVPF) {
|
||||||
|
GST_OBJECT_UNLOCK (src);
|
||||||
|
GST_WARNING_OBJECT (src, "stream with id %u, is not encrypted", id);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_signal_emit_by_name (stream->srtpdec, "remove-key", stream->ssrc, NULL);
|
||||||
|
if (stream->mikey) {
|
||||||
|
gst_mikey_message_unref (stream->mikey);
|
||||||
|
stream->mikey = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
GST_OBJECT_UNLOCK (src);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
gst_rtspsrc_parse_range (GstRTSPSrc * src, const gchar * range,
|
gst_rtspsrc_parse_range (GstRTSPSrc * src, const gchar * range,
|
||||||
GstSegment * segment, gboolean update_duration)
|
GstSegment * segment, gboolean update_duration)
|
||||||
|
@ -8336,6 +8756,99 @@ gst_rtspsrc_open_from_sdp (GstRTSPSrc * src, GstSDPMessage * sdp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (src->client_managed_mikey) {
|
||||||
|
guint len = gst_sdp_message_attributes_len (sdp);
|
||||||
|
|
||||||
|
GST_DEBUG_OBJECT (src, "client-managed-mikey mode enabled");
|
||||||
|
|
||||||
|
/* Remove key-mgmt:mikey attributes from incoming sdp.
|
||||||
|
* The key parameters will be requested by srtpdec when needed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* RFC 4567 § 3.1:
|
||||||
|
*
|
||||||
|
* > The attribute MAY be used at session level, media level, or at both
|
||||||
|
* > levels. An attribute defined at media level overrides an attribute
|
||||||
|
* > defined at session level.
|
||||||
|
*
|
||||||
|
* See: https://www.rfc-editor.org/rfc/rfc4567.html#section-3.1
|
||||||
|
*/
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
const GstSDPAttribute *attr = gst_sdp_message_get_attribute (sdp, i);
|
||||||
|
|
||||||
|
if (g_str_equal (attr->key, "key-mgmt")) {
|
||||||
|
if (g_str_has_prefix (attr->value, "mikey")) {
|
||||||
|
GST_DEBUG_OBJECT (src,
|
||||||
|
"client-managed-mikey mode enabled, "
|
||||||
|
"removing session level key-mgmt:mikey");
|
||||||
|
gst_sdp_message_remove_attribute (sdp, i);
|
||||||
|
} else {
|
||||||
|
GST_DEBUG_OBJECT (src, "Keeping session level key-mgmt:%s",
|
||||||
|
attr->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Not expecting more than one key-mgmt attribute at the session level
|
||||||
|
* aternatively, we could keep iterating and error out if several are
|
||||||
|
* found */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
len = gst_sdp_message_medias_len (sdp);
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
guint32 ssrc = 0;
|
||||||
|
guint j, jlen, mikey_id = 0;
|
||||||
|
gboolean mikey_found, other_keymgmt_found, ssrc_found = FALSE;
|
||||||
|
|
||||||
|
const GstSDPMedia *media = gst_sdp_message_get_media (sdp, i);
|
||||||
|
|
||||||
|
jlen = gst_sdp_media_attributes_len (media);
|
||||||
|
for (j = 0; j < jlen; j++) {
|
||||||
|
const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, j);
|
||||||
|
|
||||||
|
if (g_str_equal (attr->key, "key-mgmt")) {
|
||||||
|
/* Not expecting more than one key-mgmt attribute at the media level
|
||||||
|
* aternatively, we could keep iterating and error out if several are
|
||||||
|
* found */
|
||||||
|
|
||||||
|
if (g_str_has_prefix (attr->value, "mikey")) {
|
||||||
|
mikey_id = j;
|
||||||
|
mikey_found = TRUE;
|
||||||
|
} else {
|
||||||
|
other_keymgmt_found = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ssrc_found)
|
||||||
|
break;
|
||||||
|
} else if (g_str_equal (attr->key, "ssrc")) {
|
||||||
|
if (!sscanf (attr->value, "%u", &ssrc)) {
|
||||||
|
GST_ERROR_OBJECT (src, "Unexpected ssrc value: %s", attr->value);
|
||||||
|
res = GST_RTSP_ERROR;
|
||||||
|
goto setup_failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssrc_found = TRUE;
|
||||||
|
|
||||||
|
if (mikey_found)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ssrc_found && mikey_found) {
|
||||||
|
GST_DEBUG_OBJECT (src,
|
||||||
|
"replacing media level key-mgmt:mikey for ssrc: %"
|
||||||
|
G_GUINT32_FORMAT, ssrc);
|
||||||
|
|
||||||
|
gst_sdp_media_remove_attribute ((GstSDPMedia *) media, mikey_id);
|
||||||
|
} else if (ssrc_found && other_keymgmt_found) {
|
||||||
|
GST_INFO_OBJECT (src,
|
||||||
|
"Found non-MIKEY media level key-mgmt for ssrc: %"
|
||||||
|
G_GUINT32_FORMAT ", client-managed-mikey mode will not be used",
|
||||||
|
ssrc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* create streams */
|
/* create streams */
|
||||||
n_streams = gst_sdp_message_medias_len (sdp);
|
n_streams = gst_sdp_message_medias_len (sdp);
|
||||||
for (i = 0; i < n_streams; i++) {
|
for (i = 0; i < n_streams; i++) {
|
||||||
|
|
|
@ -282,6 +282,7 @@ struct _GstRTSPSrc {
|
||||||
GstStructure *prop_extra_http_request_headers;
|
GstStructure *prop_extra_http_request_headers;
|
||||||
gboolean tcp_timestamp;
|
gboolean tcp_timestamp;
|
||||||
gboolean force_non_compliant_url;
|
gboolean force_non_compliant_url;
|
||||||
|
gboolean client_managed_mikey;
|
||||||
|
|
||||||
/* state */
|
/* state */
|
||||||
GstRTSPState state;
|
GstRTSPState state;
|
||||||
|
@ -340,6 +341,8 @@ struct _GstRTSPSrcClass {
|
||||||
gboolean (*get_parameter) (GstRTSPSrc *rtsp, const gchar *parameter, const gchar *content_type, GstPromise *promise);
|
gboolean (*get_parameter) (GstRTSPSrc *rtsp, const gchar *parameter, const gchar *content_type, GstPromise *promise);
|
||||||
gboolean (*get_parameters) (GstRTSPSrc *rtsp, gchar **parameters, const gchar *content_type, GstPromise *promise);
|
gboolean (*get_parameters) (GstRTSPSrc *rtsp, gchar **parameters, const gchar *content_type, GstPromise *promise);
|
||||||
gboolean (*set_parameter) (GstRTSPSrc *rtsp, const gchar *name, const gchar *value, const gchar *content_type, GstPromise *promise);
|
gboolean (*set_parameter) (GstRTSPSrc *rtsp, const gchar *name, const gchar *value, const gchar *content_type, GstPromise *promise);
|
||||||
|
gboolean (*set_mikey_parameter) (GstRTSPSrc *rtsp, guint id, GstCaps *mikey, GstPromise *promise);
|
||||||
|
gboolean (*remove_key) (GstRTSPSrc *rtsp, guint id);
|
||||||
GstFlowReturn (*push_backchannel_buffer) (GstRTSPSrc *src, guint id, GstSample *sample);
|
GstFlowReturn (*push_backchannel_buffer) (GstRTSPSrc *src, guint id, GstSample *sample);
|
||||||
GstFlowReturn (*push_backchannel_sample) (GstRTSPSrc *src, guint id, GstSample *sample);
|
GstFlowReturn (*push_backchannel_sample) (GstRTSPSrc *src, guint id, GstSample *sample);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
executable('test-onvif', 'test-onvif.c',
|
examples = [
|
||||||
dependencies: [gst_dep],
|
'test-client-managed-mikey',
|
||||||
c_args : gst_plugins_good_args,
|
'test-onvif',
|
||||||
include_directories : [configinc],
|
]
|
||||||
install: false)
|
|
||||||
|
foreach example : examples
|
||||||
|
executable(example, '@0@.c'.format(example),
|
||||||
|
dependencies: [gst_dep],
|
||||||
|
c_args : gst_plugins_good_args,
|
||||||
|
include_directories : [configinc],
|
||||||
|
install: false)
|
||||||
|
endforeach
|
||||||
|
|
|
@ -0,0 +1,773 @@
|
||||||
|
#include <gst/gst.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
GST_DEBUG_CATEGORY_STATIC (srtp_client_debug);
|
||||||
|
#define GST_CAT_DEFAULT srtp_client_debug
|
||||||
|
|
||||||
|
#define ERROR_STR_NULL(err) \
|
||||||
|
((err) != NULL ? ((err)->message != NULL ? (err->message) : "(NULL)") \
|
||||||
|
: "(NULL)")
|
||||||
|
|
||||||
|
#define MAKE_AND_ADD(var, pipe, name, out_label, elem_name) \
|
||||||
|
G_STMT_START { \
|
||||||
|
if (G_UNLIKELY(!(var = (gst_element_factory_make(name, elem_name))))) { \
|
||||||
|
GST_ERROR("Could not create element %s", name); \
|
||||||
|
goto out_label; \
|
||||||
|
} \
|
||||||
|
if (G_UNLIKELY(!gst_bin_add(GST_BIN_CAST(pipe), var))) { \
|
||||||
|
GST_ERROR("Could not add element %s", name); \
|
||||||
|
goto out_label; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
G_STMT_END
|
||||||
|
|
||||||
|
static GMainLoop *loop = NULL;
|
||||||
|
static GstElement *pipeline = NULL;
|
||||||
|
static GstElement *rtspsrc = NULL;
|
||||||
|
static GList *streams = NULL;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
GMutex lock;
|
||||||
|
guint key_size;
|
||||||
|
guint32 mki;
|
||||||
|
GstCaps *key_caps;
|
||||||
|
GstCaps *rekey_caps;
|
||||||
|
} KeyParam;
|
||||||
|
|
||||||
|
static KeyParam *
|
||||||
|
key_param_new (guint key_size, guint32 mki)
|
||||||
|
{
|
||||||
|
KeyParam *key_param = NULL;
|
||||||
|
guint8 *data, *mki_data;
|
||||||
|
guint data_size = GST_ROUND_UP_4 (key_size);
|
||||||
|
GstBuffer *srtp_key, *mki_buf;
|
||||||
|
guint i;
|
||||||
|
|
||||||
|
key_param = g_malloc0 (sizeof (KeyParam));
|
||||||
|
|
||||||
|
g_mutex_init (&key_param->lock);
|
||||||
|
|
||||||
|
key_param->key_size = data_size;
|
||||||
|
key_param->mki = mki;
|
||||||
|
|
||||||
|
data = g_malloc (data_size);
|
||||||
|
for (i = 0; i < data_size; i += 4) {
|
||||||
|
GST_WRITE_UINT32_BE (data + i, g_random_int ());
|
||||||
|
}
|
||||||
|
srtp_key = gst_buffer_new_wrapped (data, key_size);
|
||||||
|
|
||||||
|
mki_data = g_malloc (sizeof (guint32));
|
||||||
|
GST_WRITE_UINT32_BE (mki_data, mki);
|
||||||
|
mki_buf = gst_buffer_new_wrapped (mki_data, sizeof (guint32));
|
||||||
|
|
||||||
|
/* parameters for MIKEY SETUP and srtpdec */
|
||||||
|
key_param->key_caps =
|
||||||
|
gst_caps_new_static_str_simple ("application/x-srtp", "srtp-key",
|
||||||
|
GST_TYPE_BUFFER, srtp_key, "srtp-cipher", G_TYPE_STRING, "aes-128-icm",
|
||||||
|
"srtp-auth", G_TYPE_STRING, "hmac-sha1-80", "mki", GST_TYPE_BUFFER,
|
||||||
|
mki_buf, "srtcp-cipher", G_TYPE_STRING, "aes-128-icm", "srtcp-auth",
|
||||||
|
G_TYPE_STRING, "hmac-sha1-80", NULL);
|
||||||
|
|
||||||
|
/* parameters for re-keying */
|
||||||
|
key_param->rekey_caps =
|
||||||
|
gst_caps_new_static_str_simple ("application/x-srtp", "srtp-key",
|
||||||
|
GST_TYPE_BUFFER, srtp_key, "mki", GST_TYPE_BUFFER, mki_buf, NULL);
|
||||||
|
|
||||||
|
gst_buffer_unref (mki_buf);
|
||||||
|
gst_buffer_unref (srtp_key);
|
||||||
|
|
||||||
|
return key_param;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
key_param_free (KeyParam * key_param)
|
||||||
|
{
|
||||||
|
g_mutex_lock (&key_param->lock);
|
||||||
|
|
||||||
|
gst_caps_unref (key_param->rekey_caps);
|
||||||
|
gst_caps_unref (key_param->key_caps);
|
||||||
|
|
||||||
|
g_mutex_unlock (&key_param->lock);
|
||||||
|
|
||||||
|
g_free (key_param);
|
||||||
|
}
|
||||||
|
|
||||||
|
static GstCaps *
|
||||||
|
key_param_get_srtp_param (KeyParam * key_param)
|
||||||
|
{
|
||||||
|
GstCaps *caps;
|
||||||
|
|
||||||
|
g_mutex_lock (&key_param->lock);
|
||||||
|
caps = gst_caps_ref (key_param->key_caps);
|
||||||
|
g_mutex_unlock (&key_param->lock);
|
||||||
|
|
||||||
|
return caps;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GstCaps *
|
||||||
|
key_param_get_rekey_mikey (KeyParam * key_param)
|
||||||
|
{
|
||||||
|
GstCaps *caps;
|
||||||
|
|
||||||
|
g_mutex_lock (&key_param->lock);
|
||||||
|
caps = gst_caps_ref (key_param->rekey_caps);
|
||||||
|
g_mutex_unlock (&key_param->lock);
|
||||||
|
|
||||||
|
return caps;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
key_param_inc_mki (KeyParam * key_param)
|
||||||
|
{
|
||||||
|
GstBuffer *mki_buf;
|
||||||
|
guint8 *mki_data = g_malloc (sizeof (guint32));
|
||||||
|
|
||||||
|
g_mutex_lock (&key_param->lock);
|
||||||
|
|
||||||
|
key_param->mki += 1;
|
||||||
|
GST_INFO ("Incrementing mki to: %u", key_param->mki);
|
||||||
|
|
||||||
|
GST_WRITE_UINT32_BE (mki_data, key_param->mki);
|
||||||
|
mki_buf = gst_buffer_new_wrapped (mki_data, sizeof (guint32));
|
||||||
|
|
||||||
|
key_param->key_caps = gst_caps_make_writable (key_param->key_caps);
|
||||||
|
gst_caps_set_simple_static_str (key_param->key_caps, "mki", GST_TYPE_BUFFER,
|
||||||
|
mki_buf, NULL);
|
||||||
|
|
||||||
|
key_param->rekey_caps = gst_caps_make_writable (key_param->rekey_caps);
|
||||||
|
gst_caps_set_simple_static_str (key_param->rekey_caps, "mki", GST_TYPE_BUFFER,
|
||||||
|
mki_buf, NULL);
|
||||||
|
|
||||||
|
g_mutex_unlock (&key_param->lock);
|
||||||
|
|
||||||
|
gst_buffer_unref (mki_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Called when a key is required:
|
||||||
|
*
|
||||||
|
* * When configuring srtpenc for RTCP.
|
||||||
|
* * When preparing the KeyMgmt parameter for the SETUP request.
|
||||||
|
* * When srtpdec needs a key to decrypt an incoming packet.
|
||||||
|
* * After 'remove-key', which we call when re-keying.
|
||||||
|
*/
|
||||||
|
static GstCaps *
|
||||||
|
request_key (G_GNUC_UNUSED GstElement * src,
|
||||||
|
G_GNUC_UNUSED guint stream, gpointer user_data)
|
||||||
|
{
|
||||||
|
GstCaps *caps;
|
||||||
|
KeyParam *key_param = (KeyParam *) user_data;
|
||||||
|
|
||||||
|
caps = key_param_get_srtp_param (key_param);
|
||||||
|
GST_DEBUG ("Got key: %" GST_PTR_FORMAT, caps);
|
||||||
|
|
||||||
|
return caps;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
KeyParam *key_param;
|
||||||
|
GList *streams_to_rekey;
|
||||||
|
} RekeyData;
|
||||||
|
|
||||||
|
static RekeyData *
|
||||||
|
rekey_data_new (KeyParam * key_param, GList * streams_to_renew)
|
||||||
|
{
|
||||||
|
RekeyData *this = g_malloc0 (sizeof (RekeyData));
|
||||||
|
this->key_param = key_param;
|
||||||
|
this->streams_to_rekey = streams_to_renew;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rekey_data_free (RekeyData * data)
|
||||||
|
{
|
||||||
|
g_list_free (data->streams_to_rekey);
|
||||||
|
/* key_param lifetime is handled elsewhere */
|
||||||
|
|
||||||
|
g_free (data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_rekey_reply (GstPromise * promise, gpointer user_data);
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
rekey_next_stream (gpointer user_data)
|
||||||
|
{
|
||||||
|
RekeyData *data = (RekeyData *) user_data;
|
||||||
|
GList *first;
|
||||||
|
guint stream_id;
|
||||||
|
GstCaps *mikey;
|
||||||
|
GstPromise *promise;
|
||||||
|
gboolean res;
|
||||||
|
|
||||||
|
first = g_list_first (data->streams_to_rekey);
|
||||||
|
if (!first) {
|
||||||
|
GST_DEBUG ("No more streams to re-key");
|
||||||
|
rekey_data_free (data);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream_id = GPOINTER_TO_UINT (first->data);
|
||||||
|
GST_INFO ("Re-keying stream with id %u", stream_id);
|
||||||
|
|
||||||
|
promise = gst_promise_new_with_change_func (on_rekey_reply, data, NULL);
|
||||||
|
|
||||||
|
mikey = key_param_get_rekey_mikey (data->key_param);
|
||||||
|
g_signal_emit_by_name (rtspsrc, "set-mikey-parameter", stream_id, mikey,
|
||||||
|
promise, &res);
|
||||||
|
|
||||||
|
if (!res) {
|
||||||
|
GST_ERROR ("Failed to emit set-mikey-parameter for stream with id %u",
|
||||||
|
stream_id);
|
||||||
|
rekey_data_free (data);
|
||||||
|
gst_promise_unref (promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
/* next stream will be processed when the promise is complete */
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_rekey_reply (GstPromise * promise, gpointer user_data)
|
||||||
|
{
|
||||||
|
RekeyData *data = (RekeyData *) user_data;
|
||||||
|
GList *first;
|
||||||
|
guint stream_id;
|
||||||
|
const GstStructure *reply;
|
||||||
|
gint result, code;
|
||||||
|
gboolean res;
|
||||||
|
|
||||||
|
first = g_list_first (data->streams_to_rekey);
|
||||||
|
if (!first) {
|
||||||
|
GST_WARNING ("on_rekey_reply called but there are no more streams");
|
||||||
|
goto unrecoverable_err;
|
||||||
|
}
|
||||||
|
stream_id = GPOINTER_TO_UINT (first->data);
|
||||||
|
|
||||||
|
if (gst_promise_wait (promise) != GST_PROMISE_RESULT_REPLIED) {
|
||||||
|
GST_WARNING ("set-mikey-parameter interrupted or expired");
|
||||||
|
/* will try again */
|
||||||
|
goto next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* First stream was either processed or there was an unrecoverable error */
|
||||||
|
data->streams_to_rekey =
|
||||||
|
g_list_remove (data->streams_to_rekey, GUINT_TO_POINTER (stream_id));
|
||||||
|
|
||||||
|
reply = gst_promise_get_reply (promise);
|
||||||
|
GST_DEBUG ("renew-mikey replied %" GST_PTR_FORMAT, reply);
|
||||||
|
|
||||||
|
if (!gst_structure_get_int (reply, "rtsp-result", &result) || result != 0) {
|
||||||
|
GST_ERROR ("Failed to send MIKEY parameter to server: %" GST_PTR_FORMAT,
|
||||||
|
reply);
|
||||||
|
goto unrecoverable_err;
|
||||||
|
}
|
||||||
|
if (!gst_structure_get_int (reply, "rtsp-code", &code) || code != 200) {
|
||||||
|
GST_ERROR ("Setting MIKEY failed for stream with id %u. Reply from server: "
|
||||||
|
"%" GST_PTR_FORMAT, stream_id, reply);
|
||||||
|
goto next;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_signal_emit_by_name (rtspsrc, "remove-key", stream_id, &res);
|
||||||
|
if (!res) {
|
||||||
|
GST_ERROR ("Failed to remove key from client for stream with id %u",
|
||||||
|
stream_id);
|
||||||
|
goto next;
|
||||||
|
}
|
||||||
|
|
||||||
|
GST_DEBUG ("Re-keying complete for stream with id %u", stream_id);
|
||||||
|
next:
|
||||||
|
if (data->streams_to_rekey)
|
||||||
|
g_idle_add (rekey_next_stream, data);
|
||||||
|
|
||||||
|
gst_promise_unref (promise);
|
||||||
|
return;
|
||||||
|
|
||||||
|
unrecoverable_err:
|
||||||
|
rekey_data_free (data);
|
||||||
|
gst_promise_unref (promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
rekey_all (gpointer user_data)
|
||||||
|
{
|
||||||
|
KeyParam *key_param = (KeyParam *) user_data;
|
||||||
|
RekeyData *data;
|
||||||
|
|
||||||
|
if (!rtspsrc) {
|
||||||
|
GST_DEBUG ("Skipping rekey_all because rtspsrc is not ready yet");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
key_param_inc_mki (key_param);
|
||||||
|
|
||||||
|
/* rtspsrc can only process one SET_PARAMETER at once.
|
||||||
|
* We will chain SET_PARAMETER then remove-key for each stream.
|
||||||
|
*/
|
||||||
|
data = rekey_data_new (key_param, g_list_copy (streams));
|
||||||
|
rekey_next_stream (data);
|
||||||
|
|
||||||
|
out:
|
||||||
|
return G_SOURCE_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_soft_limit (GstElement * rtspsrc, guint stream_id, gpointer user_data)
|
||||||
|
{
|
||||||
|
GST_INFO ("Reached soft-limit for stream with id %u", stream_id);
|
||||||
|
|
||||||
|
/* this is where we should re-new the key
|
||||||
|
* in this test, we wait for hard-limit though to show both signals.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_hard_limit (GstElement * rtspsrc, guint stream_id, gpointer user_data)
|
||||||
|
{
|
||||||
|
KeyParam *key_param = (KeyParam *) user_data;
|
||||||
|
GList *list = NULL;
|
||||||
|
RekeyData *data;
|
||||||
|
|
||||||
|
GST_INFO ("Reached hard-limit for stream with id %u", stream_id);
|
||||||
|
|
||||||
|
key_param_inc_mki (key_param);
|
||||||
|
|
||||||
|
list = g_list_append (list, GUINT_TO_POINTER (stream_id));
|
||||||
|
data = rekey_data_new (key_param, list);
|
||||||
|
|
||||||
|
g_idle_add (rekey_next_stream, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
setup_h264_pipeline (GstElement * element, GstPad * pad, GstPad ** decode_pad)
|
||||||
|
{
|
||||||
|
gboolean ret = FALSE;
|
||||||
|
GstObject *parent = NULL;
|
||||||
|
GstElement *depay = NULL;
|
||||||
|
GstElement *decode = NULL;
|
||||||
|
GstPad *sinkpad = NULL;
|
||||||
|
|
||||||
|
parent = gst_object_get_parent (GST_OBJECT (element));
|
||||||
|
|
||||||
|
MAKE_AND_ADD (depay, parent, "rtph264depay", out, NULL);
|
||||||
|
MAKE_AND_ADD (decode, parent, "avdec_h264", out, NULL);
|
||||||
|
|
||||||
|
if (!gst_element_link (depay, decode)) {
|
||||||
|
GST_ERROR ("failed linking h264 elements");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
sinkpad = gst_element_get_static_pad (depay, "sink");
|
||||||
|
if (gst_pad_link (pad, sinkpad) != GST_PAD_LINK_OK) {
|
||||||
|
GST_ERROR ("failed linking video depayloader");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void) gst_element_sync_state_with_parent (decode);
|
||||||
|
(void) gst_element_sync_state_with_parent (depay);
|
||||||
|
|
||||||
|
*decode_pad = gst_element_get_static_pad (decode, "src");
|
||||||
|
|
||||||
|
ret = TRUE;
|
||||||
|
|
||||||
|
out:
|
||||||
|
g_clear_object (&sinkpad);
|
||||||
|
gst_object_unref (parent);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
setup_h265_pipeline (GstElement * element, GstPad * pad, GstPad ** decode_pad)
|
||||||
|
{
|
||||||
|
gboolean ret = FALSE;
|
||||||
|
GstObject *parent = NULL;
|
||||||
|
GstElement *depay = NULL;
|
||||||
|
GstElement *decode = NULL;
|
||||||
|
GstPad *sinkpad = NULL;
|
||||||
|
|
||||||
|
parent = gst_object_get_parent (GST_OBJECT (element));
|
||||||
|
|
||||||
|
MAKE_AND_ADD (depay, parent, "rtph265depay", out, NULL);
|
||||||
|
MAKE_AND_ADD (decode, parent, "avdec_h265", out, NULL);
|
||||||
|
|
||||||
|
if (!gst_element_link (depay, decode)) {
|
||||||
|
GST_ERROR ("failed linking h265 elements");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
sinkpad = gst_element_get_static_pad (depay, "sink");
|
||||||
|
if (gst_pad_link (pad, sinkpad) != GST_PAD_LINK_OK) {
|
||||||
|
GST_ERROR ("failed linking video depayloader");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void) gst_element_sync_state_with_parent (decode);
|
||||||
|
(void) gst_element_sync_state_with_parent (depay);
|
||||||
|
|
||||||
|
*decode_pad = gst_element_get_static_pad (decode, "src");
|
||||||
|
|
||||||
|
ret = TRUE;
|
||||||
|
|
||||||
|
out:
|
||||||
|
g_clear_object (&sinkpad);
|
||||||
|
gst_object_unref (parent);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
setup_video_sink (GstElement * element, GstPad * pad, GstStructure * st)
|
||||||
|
{
|
||||||
|
GstObject *parent = NULL;
|
||||||
|
GstElement *scale = NULL;
|
||||||
|
GstElement *convert = NULL;
|
||||||
|
GstElement *queue = NULL;
|
||||||
|
GstElement *sink = NULL;
|
||||||
|
GstPad *decode_pad = NULL;
|
||||||
|
GstPad *sinkpad = NULL;
|
||||||
|
const gchar *encoding;
|
||||||
|
|
||||||
|
encoding = gst_structure_get_string (st, "encoding-name");
|
||||||
|
if (g_str_equal (encoding, "H264")) {
|
||||||
|
if (!setup_h264_pipeline (element, pad, &decode_pad)) {
|
||||||
|
GST_WARNING ("skipping H264 stream");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
} else if (g_str_equal (encoding, "H265")) {
|
||||||
|
if (!setup_h265_pipeline (element, pad, &decode_pad)) {
|
||||||
|
GST_WARNING ("skipping H265 stream");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* TODO: add more formats */
|
||||||
|
GST_FIXME ("unhandled encoding: %s", encoding);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent = gst_object_get_parent (GST_OBJECT (element));
|
||||||
|
|
||||||
|
MAKE_AND_ADD (scale, parent, "videoscale", out, NULL);
|
||||||
|
MAKE_AND_ADD (convert, parent, "videoconvert", out, NULL);
|
||||||
|
MAKE_AND_ADD (queue, parent, "queue", out, NULL);
|
||||||
|
g_object_set (queue, "max-size-buffers", 1, "max-size-bytes", 0,
|
||||||
|
"max-size-time", 0, NULL);
|
||||||
|
MAKE_AND_ADD (sink, parent, "autovideosink", out, NULL);
|
||||||
|
|
||||||
|
if (!gst_element_link_many (scale, convert, queue, sink, NULL)) {
|
||||||
|
GST_ERROR ("failed linking video elements");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
sinkpad = gst_element_get_static_pad (scale, "sink");
|
||||||
|
if (gst_pad_link (decode_pad, sinkpad) != GST_PAD_LINK_OK) {
|
||||||
|
GST_ERROR ("failed linking video pipeline");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void) gst_element_sync_state_with_parent (sink);
|
||||||
|
(void) gst_element_sync_state_with_parent (queue);
|
||||||
|
(void) gst_element_sync_state_with_parent (convert);
|
||||||
|
(void) gst_element_sync_state_with_parent (scale);
|
||||||
|
|
||||||
|
out:
|
||||||
|
g_clear_object (&decode_pad);
|
||||||
|
g_clear_object (&sinkpad);
|
||||||
|
gst_object_unref (parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
setup_aac_pipeline (GstElement * element, GstPad * pad, GstPad ** decode_pad)
|
||||||
|
{
|
||||||
|
gboolean ret = FALSE;
|
||||||
|
GstObject *parent = NULL;
|
||||||
|
GstElement *depay = NULL;
|
||||||
|
GstElement *decode = NULL;
|
||||||
|
GstPad *sinkpad = NULL;
|
||||||
|
|
||||||
|
parent = gst_object_get_parent (GST_OBJECT (element));
|
||||||
|
|
||||||
|
MAKE_AND_ADD (depay, parent, "rtpmp4gdepay", out, NULL);
|
||||||
|
MAKE_AND_ADD (decode, parent, "avdec_aac", out, NULL);
|
||||||
|
|
||||||
|
if (!gst_element_link (depay, decode)) {
|
||||||
|
GST_ERROR ("failed linking audio elements");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
sinkpad = gst_element_get_static_pad (depay, "sink");
|
||||||
|
if (gst_pad_link (pad, sinkpad) != GST_PAD_LINK_OK) {
|
||||||
|
GST_ERROR ("linking sink failed");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void) gst_element_sync_state_with_parent (decode);
|
||||||
|
(void) gst_element_sync_state_with_parent (depay);
|
||||||
|
|
||||||
|
*decode_pad = gst_element_get_static_pad (decode, "src");
|
||||||
|
|
||||||
|
ret = TRUE;
|
||||||
|
|
||||||
|
out:
|
||||||
|
g_clear_object (&sinkpad);
|
||||||
|
gst_object_unref (parent);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
setup_audio_sink (GstElement * element, GstPad * pad, GstStructure * st)
|
||||||
|
{
|
||||||
|
GstObject *parent = NULL;
|
||||||
|
GstElement *convert = NULL;
|
||||||
|
GstElement *queue = NULL;
|
||||||
|
GstElement *sink = NULL;
|
||||||
|
GstPad *decode_pad = NULL;
|
||||||
|
GstPad *sinkpad = NULL;
|
||||||
|
const gchar *encoding, *mode;
|
||||||
|
|
||||||
|
encoding = gst_structure_get_string (st, "encoding-name");
|
||||||
|
mode = gst_structure_get_string (st, "mode");
|
||||||
|
if (g_str_equal (encoding, "MPEG4-GENERIC") && g_str_has_prefix (mode, "AAC")) {
|
||||||
|
if (!setup_aac_pipeline (element, pad, &decode_pad)) {
|
||||||
|
GST_WARNING ("skipping aac stream");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GST_FIXME ("unhandled: encoding %s / mode: %s", encoding, mode);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent = gst_object_get_parent (GST_OBJECT (element));
|
||||||
|
|
||||||
|
MAKE_AND_ADD (convert, parent, "audioconvert", out, NULL);
|
||||||
|
MAKE_AND_ADD (queue, parent, "queue", out, NULL);
|
||||||
|
g_object_set (queue, "max-size-buffers", 1, "max-size-bytes", 0,
|
||||||
|
"max-size-time", 0, NULL);
|
||||||
|
MAKE_AND_ADD (sink, parent, "autoaudiosink", out, NULL);
|
||||||
|
|
||||||
|
if (!gst_element_link_many (convert, queue, sink, NULL)) {
|
||||||
|
GST_ERROR ("failed linking audio elements");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
sinkpad = gst_element_get_static_pad (convert, "sink");
|
||||||
|
if (gst_pad_link (decode_pad, sinkpad) != GST_PAD_LINK_OK) {
|
||||||
|
GST_ERROR ("failed linking audio pipeline");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void) gst_element_sync_state_with_parent (sink);
|
||||||
|
(void) gst_element_sync_state_with_parent (queue);
|
||||||
|
(void) gst_element_sync_state_with_parent (convert);
|
||||||
|
|
||||||
|
out:
|
||||||
|
g_clear_object (&decode_pad);
|
||||||
|
g_clear_object (&sinkpad);
|
||||||
|
gst_object_unref (parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
pad_added (GstElement * element, GstPad * pad, G_GNUC_UNUSED gpointer user_data)
|
||||||
|
{
|
||||||
|
GstCaps *caps;
|
||||||
|
GstStructure *st;
|
||||||
|
const gchar *name;
|
||||||
|
const gchar *media;
|
||||||
|
|
||||||
|
caps = gst_pad_get_current_caps (pad);
|
||||||
|
|
||||||
|
GST_DEBUG ("new pad %" GST_PTR_FORMAT " with caps %" GST_PTR_FORMAT, pad,
|
||||||
|
caps);
|
||||||
|
|
||||||
|
st = gst_caps_get_structure (caps, 0);
|
||||||
|
name = gst_structure_get_name (st);
|
||||||
|
|
||||||
|
if (!g_str_equal (name, "application/x-rtp")) {
|
||||||
|
GST_ERROR ("caps not understood");
|
||||||
|
gst_caps_unref (caps);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
media = gst_structure_get_string (st, "media");
|
||||||
|
if (media == NULL) {
|
||||||
|
GST_ERROR ("no media in caps");
|
||||||
|
gst_caps_unref (caps);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_str_equal (media, "video")) {
|
||||||
|
setup_video_sink (element, pad, st);
|
||||||
|
} else if (g_str_equal (media, "audio")) {
|
||||||
|
setup_audio_sink (element, pad, st);
|
||||||
|
} else {
|
||||||
|
GST_WARNING ("media not understood");
|
||||||
|
}
|
||||||
|
|
||||||
|
gst_caps_unref (caps);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
select_stream (G_GNUC_UNUSED GstElement * rtspsrc,
|
||||||
|
guint stream_id, GstCaps * caps, G_GNUC_UNUSED gpointer user_data)
|
||||||
|
{
|
||||||
|
GST_INFO ("Selecting stream with id: %u, %" GST_PTR_FORMAT, stream_id, caps);
|
||||||
|
streams = g_list_append (streams, GUINT_TO_POINTER (stream_id));
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
build_pipeline (const gchar * location, KeyParam * key_param)
|
||||||
|
{
|
||||||
|
GstElement *src = NULL;
|
||||||
|
gboolean ret = FALSE;
|
||||||
|
|
||||||
|
GST_DEBUG ("building pipeline for: %s", location);
|
||||||
|
|
||||||
|
pipeline = gst_pipeline_new ("srtp pipeline");
|
||||||
|
|
||||||
|
MAKE_AND_ADD (src, pipeline, "rtspsrc", out, NULL);
|
||||||
|
rtspsrc = gst_object_ref (src);
|
||||||
|
|
||||||
|
g_object_set (src, "location", location, "tls-validation-flags", 0x20,
|
||||||
|
"client-managed-mikey", TRUE, NULL);
|
||||||
|
|
||||||
|
g_signal_connect (src, "pad-added", G_CALLBACK (pad_added), NULL);
|
||||||
|
|
||||||
|
if (key_param == NULL) {
|
||||||
|
GST_WARNING ("no key available");
|
||||||
|
ret = TRUE;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_signal_connect (src, "select-stream", G_CALLBACK (select_stream), NULL);
|
||||||
|
|
||||||
|
g_signal_connect (src, "request-rtp-key", G_CALLBACK (request_key),
|
||||||
|
key_param);
|
||||||
|
g_signal_connect (src, "request-rtcp-key", G_CALLBACK (request_key),
|
||||||
|
key_param);
|
||||||
|
|
||||||
|
g_signal_connect (src, "soft-limit", G_CALLBACK (on_soft_limit), NULL);
|
||||||
|
g_signal_connect (src, "hard-limit", G_CALLBACK (on_hard_limit), key_param);
|
||||||
|
|
||||||
|
ret = TRUE;
|
||||||
|
|
||||||
|
out:
|
||||||
|
if (!ret) {
|
||||||
|
gst_object_unref (pipeline);
|
||||||
|
pipeline = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
bus_message (G_GNUC_UNUSED GstBus * bus, GstMessage * message,
|
||||||
|
G_GNUC_UNUSED gpointer user_data)
|
||||||
|
{
|
||||||
|
GST_TRACE ("got %" GST_PTR_FORMAT, message);
|
||||||
|
|
||||||
|
switch (GST_MESSAGE_TYPE (message)) {
|
||||||
|
case GST_MESSAGE_ERROR:{
|
||||||
|
GError *err = NULL;
|
||||||
|
gchar *name, *debug = NULL;
|
||||||
|
gst_message_parse_error (message, &err, &debug);
|
||||||
|
name = gst_object_get_path_string (message->src);
|
||||||
|
GST_ERROR ("ERROR from %s: %s", name, err->message);
|
||||||
|
if (debug != NULL) {
|
||||||
|
GST_ERROR ("debug; %s", debug);
|
||||||
|
}
|
||||||
|
g_error_free (err);
|
||||||
|
g_free (debug);
|
||||||
|
g_free (name);
|
||||||
|
}
|
||||||
|
/* fall through */
|
||||||
|
case GST_MESSAGE_EOS:
|
||||||
|
GST_DEBUG ("stopping the main loop");
|
||||||
|
g_main_loop_quit (loop);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
gint
|
||||||
|
main (gint argc, gchar ** argv)
|
||||||
|
{
|
||||||
|
gint res = EXIT_FAILURE;
|
||||||
|
GstBus *bus = NULL;
|
||||||
|
KeyParam *key_param = NULL;
|
||||||
|
gchar *location = NULL;
|
||||||
|
guint32 key_len, mki, rekey_int = 0;
|
||||||
|
if (argc != 5) {
|
||||||
|
g_printerr
|
||||||
|
("Usage:\n\ttest-client-managed-mikey KEY_LEN MKI REKEY_INT LOCATION\n"
|
||||||
|
"\n\tWhere:\n" "\t\tKEY_LEN : len of the key (e.g. 30)\n"
|
||||||
|
"\t\tMKI : Master Key Index (e.g. 1200)\n"
|
||||||
|
"\t\tREKEY_INT: re-keying interval in seconds (e.g. 10). 0 to disable\n"
|
||||||
|
"\t\tLOCATION : rtsps://user:pass@host:port/resource (e.g. port "
|
||||||
|
"322)\n");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (!sscanf (argv[1], "%u", &key_len)) {
|
||||||
|
g_printerr ("Expected an integer for KEY_LEN, got: %s", argv[1]);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (!sscanf (argv[2], "%u", &mki)) {
|
||||||
|
g_printerr ("Expected an integer for MKI, got: %s", argv[2]);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (!sscanf (argv[3], "%u", &rekey_int)) {
|
||||||
|
g_printerr ("Expected an integer for REKEY_INT, got: %s", argv[3]);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
location = argv[4];
|
||||||
|
gst_init (&argc, &argv);
|
||||||
|
GST_DEBUG_CATEGORY_INIT (srtp_client_debug, "test-client-managed-mikey", 0,
|
||||||
|
"test-client-managed-mikey debug");
|
||||||
|
loop = g_main_loop_new (NULL, TRUE);
|
||||||
|
key_param = key_param_new (key_len, mki);
|
||||||
|
if (!build_pipeline (location, key_param)) {
|
||||||
|
GST_ERROR ("Pipeline could not be built");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bus = gst_element_get_bus (pipeline);
|
||||||
|
if (bus == NULL) {
|
||||||
|
GST_ERROR ("Could not get the pipeline bus");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void) gst_bus_add_watch (bus, bus_message, NULL);
|
||||||
|
if (gst_element_set_state (pipeline, GST_STATE_PLAYING) ==
|
||||||
|
GST_STATE_CHANGE_FAILURE) {
|
||||||
|
GST_ERROR ("Could not set the pipeline in playing state");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rekey_int) {
|
||||||
|
/* Automatically renew MKI */
|
||||||
|
g_timeout_add_seconds (rekey_int, rekey_all, key_param);
|
||||||
|
} else {
|
||||||
|
GST_INFO ("Not using re-keying interval. Will wait for hard-limit");
|
||||||
|
}
|
||||||
|
|
||||||
|
g_main_loop_run (loop);
|
||||||
|
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||||
|
gst_bus_remove_watch (bus);
|
||||||
|
res = EXIT_SUCCESS;
|
||||||
|
|
||||||
|
out:
|
||||||
|
g_clear_pointer (&key_param, key_param_free);
|
||||||
|
g_clear_pointer (&streams, g_list_free);
|
||||||
|
g_clear_object (&rtspsrc);
|
||||||
|
g_clear_object (&bus);
|
||||||
|
g_clear_object (&pipeline);
|
||||||
|
g_clear_pointer (&loop, g_main_loop_unref);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
Loading…
Reference in a new issue