From 682b52365254d28215c2f071d083a4b6e0b21ae5 Mon Sep 17 00:00:00 2001 From: Hyunjun Ko Date: Wed, 7 Oct 2015 18:50:18 +0900 Subject: [PATCH] sdp: add helper fuctions from/to sdp from/to caps GstCaps* gst_sdp_media_get_caps_from_media (const GstSDPMedia *media, gint pt); GstSDPResult gst_sdp_media_set_media_from_caps (const GstCaps* caps, GstSDPMedia *media); gchar * gst_sdp_make_keymgmt (const gchar *uri, const gchar *base64); GstSDPResult gst_sdp_message_attributes_to_caps (GstSDPMessage *msg, GstCaps *caps); GstSDPResult gst_sdp_media_attributes_to_caps (GstSDPMedia *media, GstCaps *caps); GstMIKEYMessage * gst_mikey_message_new_from_caps (GstCaps *caps); gchar * gst_mikey_message_base64_encode (GstMIKEYMessage* msg); https://bugzilla.gnome.org/show_bug.cgi?id=745880 --- docs/libs/gst-plugins-base-libs-sections.txt | 7 + gst-libs/gst/sdp/Makefile.am | 2 +- gst-libs/gst/sdp/gstmikey.c | 167 +++++ gst-libs/gst/sdp/gstmikey.h | 3 + gst-libs/gst/sdp/gstsdpmessage.c | 701 ++++++++++++++++++- gst-libs/gst/sdp/gstsdpmessage.h | 5 + tests/check/libs/sdp.c | 105 +++ win32/common/libgstsdp.def | 7 + 8 files changed, 995 insertions(+), 2 deletions(-) diff --git a/docs/libs/gst-plugins-base-libs-sections.txt b/docs/libs/gst-plugins-base-libs-sections.txt index fd3afdd0f9..410c2ccc64 100644 --- a/docs/libs/gst-plugins-base-libs-sections.txt +++ b/docs/libs/gst-plugins-base-libs-sections.txt @@ -1793,6 +1793,11 @@ gst_sdp_media_insert_attribute gst_sdp_media_replace_attribute gst_sdp_media_remove_attribute gst_sdp_media_add_attribute +gst_sdp_media_get_caps_from_media +gst_sdp_media_set_media_from_caps +gst_sdp_make_keymgmt +gst_sdp_message_attributes_to_caps +gst_sdp_media_attributes_to_caps gst_sdp_message_get_type @@ -1807,8 +1812,10 @@ GstMIKEYMessage gst_mikey_message_new gst_mikey_message_new_from_bytes +gst_mikey_message_new_from_caps gst_mikey_message_new_from_data gst_mikey_message_to_bytes +gst_mikey_message_base64_encode gst_mikey_message_copy gst_mikey_message_ref gst_mikey_message_unref diff --git a/gst-libs/gst/sdp/Makefile.am b/gst-libs/gst/sdp/Makefile.am index f4f4137d89..2d35703c55 100644 --- a/gst-libs/gst/sdp/Makefile.am +++ b/gst-libs/gst/sdp/Makefile.am @@ -10,7 +10,7 @@ lib_LTLIBRARIES = libgstsdp-@GST_API_VERSION@.la libgstsdp_@GST_API_VERSION@_la_SOURCES = gstsdpmessage.c gstmikey.c libgstsdp_@GST_API_VERSION@_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) $(GIO_CFLAGS) -libgstsdp_@GST_API_VERSION@_la_LIBADD = $(GST_LIBS) $(GIO_LIBS) +libgstsdp_@GST_API_VERSION@_la_LIBADD = $(top_builddir)/gst-libs/gst/rtp/libgstrtp-@GST_API_VERSION@.la $(GST_LIBS) $(GIO_LIBS) libgstsdp_@GST_API_VERSION@_la_LDFLAGS = $(GST_LIB_LDFLAGS) $(GST_ALL_LDFLAGS) $(GST_LT_LDFLAGS) if HAVE_INTROSPECTION diff --git a/gst-libs/gst/sdp/gstmikey.c b/gst-libs/gst/sdp/gstmikey.c index 406d49818d..6aa394136e 100644 --- a/gst-libs/gst/sdp/gstmikey.c +++ b/gst-libs/gst/sdp/gstmikey.c @@ -2148,3 +2148,170 @@ parse_error: return NULL; } } + +#define AES_128_KEY_LEN 16 +#define AES_256_KEY_LEN 32 +#define HMAC_32_KEY_LEN 4 +#define HMAC_80_KEY_LEN 10 + +static guint8 +enc_key_length_from_cipher_name (const gchar * cipher) +{ + if (g_strcmp0 (cipher, "aes-128-icm") == 0) + return AES_128_KEY_LEN; + else if (g_strcmp0 (cipher, "aes-256-icm") == 0) + return AES_256_KEY_LEN; + else { + GST_ERROR ("encryption algorithm '%s' not supported", cipher); + return 0; + } +} + +static guint8 +auth_key_length_from_auth_name (const gchar * auth) +{ + if (g_strcmp0 (auth, "hmac-sha1-32") == 0) + return HMAC_32_KEY_LEN; + else if (g_strcmp0 (auth, "hmac-sha1-80") == 0) + return HMAC_80_KEY_LEN; + else { + GST_ERROR ("authentication algorithm '%s' not supported", auth); + return 0; + } +} + +/** + * gst_mikey_message_new_from_caps: + * @caps: a #GstCaps, including SRTP parameters (srtp/srtcp cipher, authorization, key data) + * + * Makes mikey message including: + * - Security Policy Payload + * - Key Data Transport Payload + * - Key Data Sub-Payload + * + * Returns: (transfer full): a #GstMIKEYMessage, + * or %NULL if there is no srtp information in the caps. + * + * Since: 1.8 + */ +GstMIKEYMessage * +gst_mikey_message_new_from_caps (GstCaps * caps) +{ + GstMIKEYMessage *msg; + GstMIKEYPayload *payload, *pkd; + guint8 byte; + GstStructure *s; + GstMapInfo info; + GstBuffer *srtpkey; + const GValue *val; + const gchar *srtpcipher, *srtpauth, *srtcpcipher, *srtcpauth; + + g_return_val_if_fail (caps != NULL && GST_IS_CAPS (caps), NULL); + + s = gst_caps_get_structure (caps, 0); + g_return_val_if_fail (s != NULL, NULL); + + val = gst_structure_get_value (s, "srtp-key"); + if (!val) + goto no_key; + + srtpkey = gst_value_get_buffer (val); + if (!srtpkey || !GST_IS_BUFFER (srtpkey)) + goto no_key; + + srtpcipher = gst_structure_get_string (s, "srtp-cipher"); + srtpauth = gst_structure_get_string (s, "srtp-auth"); + srtcpcipher = gst_structure_get_string (s, "srtcp-cipher"); + srtcpauth = gst_structure_get_string (s, "srtcp-auth"); + + if (srtpcipher == NULL || srtpauth == NULL || srtcpcipher == NULL || + srtcpauth == NULL) { + GST_WARNING ("could not find the right SRTP parameters in caps"); + return NULL; + } + + msg = gst_mikey_message_new (); + /* unencrypted MIKEY message, we send this over TLS so this is allowed */ + 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); + + /* timestamp is now */ + gst_mikey_message_add_t_now_ntp_utc (msg); + /* add some random data */ + gst_mikey_message_add_rand_len (msg, 16); + + /* the policy '0' is SRTP */ + payload = gst_mikey_payload_new (GST_MIKEY_PT_SP); + gst_mikey_payload_sp_set (payload, 0, GST_MIKEY_SEC_PROTO_SRTP); + + /* only AES-CM is supported */ + byte = 1; + gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_ENC_ALG, 1, &byte); + /* encryption key length */ + byte = enc_key_length_from_cipher_name (srtpcipher); + gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_ENC_KEY_LEN, 1, + &byte); + /* only HMAC-SHA1 */ + gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_AUTH_ALG, 1, + &byte); + /* authentication key length */ + byte = auth_key_length_from_auth_name (srtpauth); + gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_AUTH_KEY_LEN, 1, + &byte); + /* we enable encryption on RTP and RTCP */ + gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_SRTP_ENC, 1, + &byte); + gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_SRTCP_ENC, 1, + &byte); + /* 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 */ + payload = gst_mikey_payload_new (GST_MIKEY_PT_KEMAC); + gst_mikey_payload_kemac_set (payload, GST_MIKEY_ENC_NULL, GST_MIKEY_MAC_NULL); + /* add the key in KEMAC */ + pkd = gst_mikey_payload_new (GST_MIKEY_PT_KEY_DATA); + gst_buffer_map (srtpkey, &info, GST_MAP_READ); + gst_mikey_payload_key_data_set_key (pkd, GST_MIKEY_KD_TEK, info.size, + info.data); + gst_buffer_unmap (srtpkey, &info); + gst_mikey_payload_kemac_add_sub (payload, pkd); + gst_mikey_message_add_payload (msg, payload); + + return msg; + +no_key: + GST_INFO ("No srtp key"); + return NULL; +} + +/** + * gst_mikey_message_base64_encode: + * @msg: a #GstMIKEYMessage + * + * Returns: (transfer full): a #gchar, base64-encoded data + * + * Since: 1.8 + */ +gchar * +gst_mikey_message_base64_encode (GstMIKEYMessage * msg) +{ + GBytes *bytes; + gchar *base64; + const guint8 *data; + gsize size; + + g_return_val_if_fail (msg != NULL, NULL); + + /* serialize mikey message to bytes */ + bytes = gst_mikey_message_to_bytes (msg, NULL, NULL); + + /* and make it into base64 */ + data = g_bytes_get_data (bytes, &size); + base64 = g_base64_encode (data, size); + g_bytes_unref (bytes); + + return base64; +} diff --git a/gst-libs/gst/sdp/gstmikey.h b/gst-libs/gst/sdp/gstmikey.h index bbd06b3a63..a72a4efb84 100644 --- a/gst-libs/gst/sdp/gstmikey.h +++ b/gst-libs/gst/sdp/gstmikey.h @@ -559,6 +559,9 @@ GstMIKEYMessage * gst_mikey_message_new_from_bytes (GBytes *bytes, GError **error); GBytes * gst_mikey_message_to_bytes (GstMIKEYMessage *msg, GstMIKEYEncryptInfo *info, GError **error); +GstMIKEYMessage * gst_mikey_message_new_from_caps (GstCaps *caps); +gchar * gst_mikey_message_base64_encode (GstMIKEYMessage* msg); + /** * gst_mikey_message_ref: * @message: The message to refcount diff --git a/gst-libs/gst/sdp/gstsdpmessage.c b/gst-libs/gst/sdp/gstsdpmessage.c index 0bd235cc46..9878d044e5 100644 --- a/gst-libs/gst/sdp/gstsdpmessage.c +++ b/gst-libs/gst/sdp/gstsdpmessage.c @@ -56,12 +56,15 @@ #include "config.h" #endif +#include #include #include #include +#include #include "gstsdpmessage.h" +#include "gstmikey.h" #define FREE_STRING(field) g_free (field); (field) = NULL #define REPLACE_STRING(field, val) FREE_STRING(field); (field) = g_strdup (val) @@ -2802,7 +2805,7 @@ gst_sdp_parse_line (SDPContext * c, gchar type, gchar * buffer) switch (type) { case 'v': if (buffer[0] != '0') - g_warning ("wrong SDP version"); + GST_WARNING ("wrong SDP version"); gst_sdp_message_set_version (c->msg, buffer); break; case 'o': @@ -3165,3 +3168,699 @@ gst_sdp_message_dump (const GstSDPMessage * msg) } return GST_SDP_OK; } + +static const gchar * +gst_sdp_get_attribute_for_pt (const GstSDPMedia * media, const gchar * name, + gint pt) +{ + guint i; + + for (i = 0;; i++) { + const gchar *attr; + gint val; + + if ((attr = gst_sdp_media_get_attribute_val_n (media, name, i)) == NULL) + break; + + if (sscanf (attr, "%d ", &val) != 1) + continue; + + if (val == pt) + return attr; + } + return NULL; +} + +#define PARSE_INT(p, del, res) \ +G_STMT_START { \ + gchar *t = p; \ + p = strstr (p, del); \ + if (p == NULL) \ + res = -1; \ + else { \ + *p = '\0'; \ + p++; \ + res = atoi (t); \ + } \ +} G_STMT_END + +#define PARSE_STRING(p, del, res) \ +G_STMT_START { \ + gchar *t = p; \ + p = strstr (p, del); \ + if (p == NULL) { \ + res = NULL; \ + p = t; \ + } \ + else { \ + *p = '\0'; \ + p++; \ + res = t; \ + } \ +} G_STMT_END + +#define SKIP_SPACES(p) \ + while (*p && g_ascii_isspace (*p)) \ + p++; + +/* rtpmap contains: + * + * /[/] + */ +static gboolean +gst_sdp_parse_rtpmap (const gchar * rtpmap, gint * payload, gchar ** name, + gint * rate, gchar ** params) +{ + gchar *p, *t; + + p = (gchar *) rtpmap; + + PARSE_INT (p, " ", *payload); + if (*payload == -1) + return FALSE; + + SKIP_SPACES (p); + if (*p == '\0') + return FALSE; + + PARSE_STRING (p, "/", *name); + if (*name == NULL) { + GST_DEBUG ("no rate, name %s", p); + /* no rate, assume -1 then, this is not supposed to happen but RealMedia + * streams seem to omit the rate. */ + *name = p; + *rate = -1; + return TRUE; + } + + t = p; + p = strstr (p, "/"); + if (p == NULL) { + *rate = atoi (t); + return TRUE; + } + *p = '\0'; + p++; + *rate = atoi (t); + + t = p; + if (*p == '\0') + return TRUE; + *params = t; + + return TRUE; +} + +/** + * gst_sdp_media_get_caps_from_media: + * @media: a #GstSDPMedia + * @pt: a payload type + * + * Mapping of caps from SDP fields: + * + * a=rtpmap:(payload) (encoding_name)/(clock_rate)[/(encoding_params)] + * + * a=framesize:(payload) (width)-(height) + * + * a=fmtp:(payload) (param)[=(value)];... + * + * Returns: a #GstCaps, or %NULL if an error happened + * + * Since: 1.8 + */ +GstCaps * +gst_sdp_media_get_caps_from_media (const GstSDPMedia * media, gint pt) +{ + GstCaps *caps; + const gchar *rtpmap; + const gchar *fmtp; + const gchar *framesize; + gchar *name = NULL; + gint rate = -1; + gchar *params = NULL; + gchar *tmp; + GstStructure *s; + gint payload = 0; + gboolean ret; + + /* get and parse rtpmap */ + rtpmap = gst_sdp_get_attribute_for_pt (media, "rtpmap", pt); + + if (rtpmap) { + ret = gst_sdp_parse_rtpmap (rtpmap, &payload, &name, &rate, ¶ms); + if (!ret) { + GST_ERROR ("error parsing rtpmap, ignoring"); + rtpmap = NULL; + } + } + /* dynamic payloads need rtpmap or we fail */ + if (rtpmap == NULL && pt >= 96) + goto no_rtpmap; + + /* check if we have a rate, if not, we need to look up the rate from the + * default rates based on the payload types. */ + if (rate == -1) { + const GstRTPPayloadInfo *info; + + if (GST_RTP_PAYLOAD_IS_DYNAMIC (pt)) { + /* dynamic types, use media and encoding_name */ + tmp = g_ascii_strdown (media->media, -1); + info = gst_rtp_payload_info_for_name (tmp, name); + g_free (tmp); + } else { + /* static types, use payload type */ + info = gst_rtp_payload_info_for_pt (pt); + } + + if (info) { + if ((rate = info->clock_rate) == 0) + rate = -1; + } + /* we fail if we cannot find one */ + if (rate == -1) + goto no_rate; + } + + tmp = g_ascii_strdown (media->media, -1); + caps = gst_caps_new_simple ("application/x-unknown", + "media", G_TYPE_STRING, tmp, "payload", G_TYPE_INT, pt, NULL); + g_free (tmp); + s = gst_caps_get_structure (caps, 0); + + gst_structure_set (s, "clock-rate", G_TYPE_INT, rate, NULL); + + /* encoding name must be upper case */ + if (name != NULL) { + tmp = g_ascii_strup (name, -1); + gst_structure_set (s, "encoding-name", G_TYPE_STRING, tmp, NULL); + g_free (tmp); + } + + /* params must be lower case */ + if (params != NULL) { + tmp = g_ascii_strdown (params, -1); + gst_structure_set (s, "encoding-params", G_TYPE_STRING, tmp, NULL); + g_free (tmp); + } + + /* parse optional fmtp: field */ + if ((fmtp = gst_sdp_get_attribute_for_pt (media, "fmtp", pt))) { + gchar *p; + gint payload = 0; + + p = (gchar *) fmtp; + + /* p is now of the format [=];... */ + PARSE_INT (p, " ", payload); + if (payload != -1 && payload == pt) { + gchar **pairs; + gint i; + + /* [=] are separated with ';' */ + pairs = g_strsplit (p, ";", 0); + for (i = 0; pairs[i]; i++) { + gchar *valpos; + const gchar *val, *key; + gint j; + const gchar *reserved_keys[] = + { "media", "payload", "clock-rate", "encoding-name", + "encoding-params" + }; + + /* the key may not have a '=', the value can have other '='s */ + valpos = strstr (pairs[i], "="); + if (valpos) { + /* we have a '=' and thus a value, remove the '=' with \0 */ + *valpos = '\0'; + /* value is everything between '=' and ';'. We split the pairs at ; + * boundaries so we can take the remainder of the value. Some servers + * put spaces around the value which we strip off here. Alternatively + * we could strip those spaces in the depayloaders should these spaces + * actually carry any meaning in the future. */ + val = g_strstrip (valpos + 1); + } else { + /* simple ;.. is translated into =1;... */ + val = "1"; + } + /* strip the key of spaces, convert key to lowercase but not the value. */ + key = g_strstrip (pairs[i]); + + /* skip keys from the fmtp, which we already use ourselves for the + * caps. Some software is adding random things like clock-rate into + * the fmtp, and we would otherwise here set a string-typed clock-rate + * in the caps... and thus fail to create valid RTP caps + */ + for (j = 0; j < G_N_ELEMENTS (reserved_keys); j++) { + if (g_ascii_strcasecmp (reserved_keys[j], key) == 0) { + key = ""; + break; + } + } + + if (strlen (key) > 1) { + tmp = g_ascii_strdown (key, -1); + gst_structure_set (s, tmp, G_TYPE_STRING, val, NULL); + g_free (tmp); + } + } + g_strfreev (pairs); + } + } + + /* parse framesize: field */ + if ((framesize = gst_sdp_media_get_attribute_val (media, "framesize"))) { + gchar *p; + + /* p is now of the format - */ + p = (gchar *) framesize; + + PARSE_INT (p, " ", payload); + if (payload != -1 && payload == pt) { + gst_structure_set (s, "a-framesize", G_TYPE_STRING, p, NULL); + } + } + + return caps; + + /* ERRORS */ +no_rtpmap: + { + GST_ERROR ("rtpmap type not given for dynamic payload %d", pt); + return NULL; + } +no_rate: + { + GST_ERROR ("rate unknown for payload type %d", pt); + return NULL; + } +} + +/** + * gst_sdp_media_set_media_from_caps: + * @caps: a #GstCaps + * @media: a #GstSDPMedia + * + * Mapping of caps to SDP fields: + * + * a=rtpmap:(payload) (encoding_name) or (clock_rate)[or (encoding_params)] + * + * a=framesize:(payload) (width)-(height) + * + * a=fmtp:(payload) (param)[=(value)];... + * + * Returns: a #GstSDPResult. + * + * Since: 1.8 + */ +GstSDPResult +gst_sdp_media_set_media_from_caps (const GstCaps * caps, GstSDPMedia * media) +{ + const gchar *caps_str, *caps_enc, *caps_params; + gchar *tmp; + gint caps_pt, caps_rate; + guint n_fields, j; + gboolean first; + GString *fmtp; + GstStructure *s; + + g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); + g_return_val_if_fail (caps != NULL && GST_IS_CAPS (caps), GST_SDP_EINVAL); + + s = gst_caps_get_structure (caps, 0); + if (s == NULL) { + GST_ERROR ("ignoring stream without media type"); + goto error; + } + + /* get media type and payload for the m= line */ + caps_str = gst_structure_get_string (s, "media"); + gst_sdp_media_set_media (media, caps_str); + + gst_structure_get_int (s, "payload", &caps_pt); + tmp = g_strdup_printf ("%d", caps_pt); + gst_sdp_media_add_format (media, tmp); + g_free (tmp); + + /* get clock-rate, media type and params for the rtpmap attribute */ + gst_structure_get_int (s, "clock-rate", &caps_rate); + caps_enc = gst_structure_get_string (s, "encoding-name"); + caps_params = gst_structure_get_string (s, "encoding-params"); + + if (caps_enc) { + if (caps_params) + tmp = g_strdup_printf ("%d %s/%d/%s", caps_pt, caps_enc, caps_rate, + caps_params); + else + tmp = g_strdup_printf ("%d %s/%d", caps_pt, caps_enc, caps_rate); + + gst_sdp_media_add_attribute (media, "rtpmap", tmp); + g_free (tmp); + } + + /* collect all other properties and add them to fmtp or attributes */ + fmtp = g_string_new (""); + g_string_append_printf (fmtp, "%d ", caps_pt); + first = TRUE; + n_fields = gst_structure_n_fields (s); + for (j = 0; j < n_fields; j++) { + const gchar *fname, *fval; + + fname = gst_structure_nth_field_name (s, j); + + /* filter out standard properties */ + if (!strcmp (fname, "media")) + continue; + if (!strcmp (fname, "payload")) + continue; + if (!strcmp (fname, "clock-rate")) + continue; + if (!strcmp (fname, "encoding-name")) + continue; + if (!strcmp (fname, "encoding-params")) + continue; + if (!strcmp (fname, "ssrc")) + continue; + if (!strcmp (fname, "timestamp-offset")) + continue; + if (!strcmp (fname, "seqnum-offset")) + continue; + if (g_str_has_prefix (fname, "srtp-")) + continue; + if (g_str_has_prefix (fname, "srtcp-")) + continue; + /* handled later */ + if (g_str_has_prefix (fname, "x-gst-rtsp-server-rtx-time")) + continue; + + if (!strcmp (fname, "a-framesize")) { + /* a-framesize attribute */ + if ((fval = gst_structure_get_string (s, fname))) { + tmp = g_strdup_printf ("%d %s", caps_pt, fval); + gst_sdp_media_add_attribute (media, fname + 2, tmp); + g_free (tmp); + } + continue; + } + + if (g_str_has_prefix (fname, "a-")) { + /* attribute */ + if ((fval = gst_structure_get_string (s, fname))) + gst_sdp_media_add_attribute (media, fname + 2, fval); + continue; + } + if (g_str_has_prefix (fname, "x-")) { + /* attribute */ + if ((fval = gst_structure_get_string (s, fname))) + gst_sdp_media_add_attribute (media, fname, fval); + continue; + } + + if ((fval = gst_structure_get_string (s, fname))) { + g_string_append_printf (fmtp, "%s%s=%s", first ? "" : ";", fname, fval); + first = FALSE; + } + } + + if (!first) { + tmp = g_string_free (fmtp, FALSE); + gst_sdp_media_add_attribute (media, "fmtp", tmp); + g_free (tmp); + } else { + g_string_free (fmtp, TRUE); + } + + return GST_SDP_OK; + + /* ERRORS */ +error: + { + GST_DEBUG ("ignoring stream"); + return GST_SDP_EINVAL; + } +} + +/** + * gst_sdp_make_keymgmt: + * @uri: a #gchar URI + * @base64: a #gchar base64-encoded key data + * + * Makes key management data + * + * Returns: (transfer full): a #gchar key-mgmt data, + * + * Since: 1.8 + */ +gchar * +gst_sdp_make_keymgmt (const gchar * uri, const gchar * base64) +{ + g_return_val_if_fail (uri != NULL, NULL); + g_return_val_if_fail (base64 != NULL, NULL); + + return g_strdup_printf ("prot=mikey;uri=\"%s\";data=\"%s\"", uri, base64); +} + +#define AES_128_KEY_LEN 16 +#define AES_256_KEY_LEN 32 +#define HMAC_32_KEY_LEN 4 +#define HMAC_80_KEY_LEN 10 + +static gboolean +gst_sdp_parse_keymgmt (const gchar * keymgmt, GstCaps * caps) +{ + gboolean res = FALSE; + gsize size; + guchar *data; + GstMIKEYMessage *msg; + const GstMIKEYPayload *payload; + const gchar *srtp_cipher; + const gchar *srtp_auth; + gchar *orig_value; + gchar *p, *kmpid; + + p = orig_value = g_strdup (keymgmt); + + SKIP_SPACES (p); + if (*p == '\0') { + g_free (orig_value); + return FALSE; + } + + PARSE_STRING (p, " ", kmpid); + if (kmpid == NULL || !g_str_equal (kmpid, "mikey")) { + g_free (orig_value); + return FALSE; + } + data = g_base64_decode (p, &size); + g_free (orig_value); /* Don't need this any more */ + + if (data == NULL) + return FALSE; + + msg = gst_mikey_message_new_from_data (data, size, NULL, NULL); + g_free (data); + if (msg == NULL) + return FALSE; + + srtp_cipher = "aes-128-icm"; + srtp_auth = "hmac-sha1-80"; + + /* check the Security policy if any */ + if ((payload = gst_mikey_message_find_payload (msg, GST_MIKEY_PT_SP, 0))) { + GstMIKEYPayloadSP *p = (GstMIKEYPayloadSP *) payload; + guint len, i; + + if (p->proto != GST_MIKEY_SEC_PROTO_SRTP) + goto done; + + len = gst_mikey_payload_sp_get_n_params (payload); + for (i = 0; i < len; i++) { + const GstMIKEYPayloadSPParam *param = + gst_mikey_payload_sp_get_param (payload, i); + + switch (param->type) { + case GST_MIKEY_SP_SRTP_ENC_ALG: + switch (param->val[0]) { + case 0: + srtp_cipher = "null"; + break; + case 2: + case 1: + srtp_cipher = "aes-128-icm"; + break; + default: + break; + } + break; + case GST_MIKEY_SP_SRTP_ENC_KEY_LEN: + switch (param->val[0]) { + case AES_128_KEY_LEN: + srtp_cipher = "aes-128-icm"; + break; + case AES_256_KEY_LEN: + srtp_cipher = "aes-256-icm"; + break; + default: + break; + } + break; + case GST_MIKEY_SP_SRTP_AUTH_ALG: + switch (param->val[0]) { + case 0: + srtp_auth = "null"; + break; + case 2: + case 1: + srtp_auth = "hmac-sha1-80"; + break; + default: + break; + } + break; + case GST_MIKEY_SP_SRTP_AUTH_KEY_LEN: + switch (param->val[0]) { + case HMAC_32_KEY_LEN: + srtp_auth = "hmac-sha1-32"; + break; + case HMAC_80_KEY_LEN: + srtp_auth = "hmac-sha1-80"; + break; + default: + break; + } + break; + case GST_MIKEY_SP_SRTP_SRTP_ENC: + break; + case GST_MIKEY_SP_SRTP_SRTCP_ENC: + break; + default: + break; + } + } + } + + if (!(payload = gst_mikey_message_find_payload (msg, GST_MIKEY_PT_KEMAC, 0))) + goto done; + else { + GstMIKEYPayloadKEMAC *p = (GstMIKEYPayloadKEMAC *) payload; + const GstMIKEYPayload *sub; + GstMIKEYPayloadKeyData *pkd; + GstBuffer *buf; + + if (p->enc_alg != GST_MIKEY_ENC_NULL || p->mac_alg != GST_MIKEY_MAC_NULL) + goto done; + + if (!(sub = gst_mikey_payload_kemac_get_sub (payload, 0))) + goto done; + + if (sub->type != GST_MIKEY_PT_KEY_DATA) + goto done; + + pkd = (GstMIKEYPayloadKeyData *) sub; + buf = + gst_buffer_new_wrapped (g_memdup (pkd->key_data, pkd->key_len), + pkd->key_len); + gst_caps_set_simple (caps, "srtp-key", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + } + + gst_caps_set_simple (caps, + "srtp-cipher", G_TYPE_STRING, srtp_cipher, + "srtp-auth", G_TYPE_STRING, srtp_auth, + "srtcp-cipher", G_TYPE_STRING, srtp_cipher, + "srtcp-auth", G_TYPE_STRING, srtp_auth, NULL); + + res = TRUE; +done: + gst_mikey_message_unref (msg); + + return res; +} + +static GstSDPResult +sdp_addtributes_to_caps (GArray * attributes, GstCaps * caps) +{ + if (attributes->len > 0) { + GstStructure *s; + guint i; + + s = gst_caps_get_structure (caps, 0); + + for (i = 0; i < attributes->len; i++) { + GstSDPAttribute *attr = &g_array_index (attributes, GstSDPAttribute, i); + gchar *tofree, *key; + + key = attr->key; + + /* skip some of the attribute we already handle */ + if (!strcmp (key, "fmtp")) + continue; + if (!strcmp (key, "rtpmap")) + continue; + if (!strcmp (key, "control")) + continue; + if (!strcmp (key, "range")) + continue; + if (!strcmp (key, "framesize")) + continue; + if (g_str_equal (key, "key-mgmt")) { + gst_sdp_parse_keymgmt (attr->value, caps); + continue; + } + + /* string must be valid UTF8 */ + if (!g_utf8_validate (attr->value, -1, NULL)) + continue; + + if (!g_str_has_prefix (key, "x-")) + tofree = key = g_strdup_printf ("a-%s", key); + else + tofree = NULL; + + GST_DEBUG ("adding caps: %s=%s", key, attr->value); + gst_structure_set (s, key, G_TYPE_STRING, attr->value, NULL); + g_free (tofree); + } + } + + return GST_SDP_OK; +} + +/** + * gst_sdp_message_attributes_to_caps: + * @msg: a #GstSDPMessage + * @caps: a #GstCaps + * + * Mapping of attributes of #GstSDPMessage to #GstCaps + * + * Returns: a #GstSDPResult. + * + * Since: 1.8 + */ +GstSDPResult +gst_sdp_message_attributes_to_caps (const GstSDPMessage * msg, GstCaps * caps) +{ + g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); + g_return_val_if_fail (caps != NULL && GST_IS_CAPS (caps), GST_SDP_EINVAL); + + return sdp_addtributes_to_caps (msg->attributes, caps); +} + +/** + * gst_sdp_media_attributes_to_caps: + * @media: a #GstSDPMedia + * @caps: a #GstCaps + * + * Mapping of attributes of #GstSDPMedia to #GstCaps + * + * Returns: a #GstSDPResult. + * + * Since: 1.8 + */ +GstSDPResult +gst_sdp_media_attributes_to_caps (const GstSDPMedia * media, GstCaps * caps) +{ + g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); + g_return_val_if_fail (caps != NULL && GST_IS_CAPS (caps), GST_SDP_EINVAL); + + return sdp_addtributes_to_caps (media->attributes, caps); +} diff --git a/gst-libs/gst/sdp/gstsdpmessage.h b/gst-libs/gst/sdp/gstsdpmessage.h index ea9aed813a..deffb4cb62 100644 --- a/gst-libs/gst/sdp/gstsdpmessage.h +++ b/gst-libs/gst/sdp/gstsdpmessage.h @@ -501,6 +501,11 @@ GstSDPResult gst_sdp_media_replace_attribute (GstSDPMedia *media, GstSDPResult gst_sdp_media_remove_attribute (GstSDPMedia *media, guint idx); GstSDPResult gst_sdp_media_add_attribute (GstSDPMedia *media, const gchar *key, const gchar *value); +GstCaps* gst_sdp_media_get_caps_from_media (const GstSDPMedia *media, gint pt); +GstSDPResult gst_sdp_media_set_media_from_caps (const GstCaps* caps, GstSDPMedia *media); +gchar * gst_sdp_make_keymgmt (const gchar *uri, const gchar *base64); +GstSDPResult gst_sdp_message_attributes_to_caps (const GstSDPMessage *msg, GstCaps *caps); +GstSDPResult gst_sdp_media_attributes_to_caps (const GstSDPMedia *media, GstCaps *caps); #ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstSDPMessage, gst_sdp_message_free) diff --git a/tests/check/libs/sdp.c b/tests/check/libs/sdp.c index 0cf5fc471e..c9192d70f1 100644 --- a/tests/check/libs/sdp.c +++ b/tests/check/libs/sdp.c @@ -62,6 +62,18 @@ static const gchar *sdp = "v=0\r\n" "a=sendrecv\r\n" "m=audio 4545 RTP/AVP 14\r\n" "a=sendrecv\r\n" "m=audio 1010 TCP 14\r\n"; +static const gchar caps_video_string1[] = + "application/x-unknown, media=(string)video, payload=(int)96, " + "clock-rate=(int)90000, encoding-name=(string)MP4V-ES"; + +static const gchar caps_video_string2[] = + "application/x-unknown, media=(string)video, payload=(int)97, " + "clock-rate=(int)90000, encoding-name=(string)H263-1998"; + +static const gchar caps_audio_string[] = + "application/x-unknown, media=(string)audio, payload=(int)14, " + "clock-rate=(int)90000"; + GST_START_TEST (boxed) { GValue value = G_VALUE_INIT; @@ -193,6 +205,97 @@ GST_START_TEST (modify) gst_sdp_message_free (message); } +GST_END_TEST +GST_START_TEST (caps_from_media) +{ + GstSDPMessage *message; + glong length = -1; + const GstSDPMedia *media1, *media2, *media3; + GstCaps *caps_video1, *caps_video2, *caps_audio; + GstCaps *result_video1, *result_video2, *result_audio; + + gst_sdp_message_new (&message); + gst_sdp_message_parse_buffer ((guint8 *) sdp, length, message); + + media1 = gst_sdp_message_get_media (message, 0); + fail_unless (media1 != NULL); + + media2 = gst_sdp_message_get_media (message, 1); + fail_unless (media2 != NULL); + + media3 = gst_sdp_message_get_media (message, 2); + fail_unless (media2 != NULL); + + caps_video1 = gst_sdp_media_get_caps_from_media (media1, 96); + caps_video2 = gst_sdp_media_get_caps_from_media (media1, 97); + caps_audio = gst_sdp_media_get_caps_from_media (media3, 14); + + result_video1 = gst_caps_from_string (caps_video_string1); + fail_unless (gst_caps_is_strictly_equal (caps_video1, result_video1)); + gst_caps_unref (result_video1); + gst_caps_unref (caps_video1); + + result_video2 = gst_caps_from_string (caps_video_string2); + fail_unless (gst_caps_is_strictly_equal (caps_video2, result_video2)); + gst_caps_unref (result_video2); + gst_caps_unref (caps_video2); + + result_audio = gst_caps_from_string (caps_audio_string); + fail_unless (gst_caps_is_strictly_equal (caps_audio, result_audio)); + gst_caps_unref (result_audio); + gst_caps_unref (caps_audio); + + gst_sdp_message_free (message); +} + +GST_END_TEST +GST_START_TEST (media_from_caps) +{ + GstSDPResult ret = GST_SDP_OK; + GstSDPMessage *message; + glong length = -1; + GstSDPMedia *media_video, *media_audio; + const GstSDPMedia *result_video, *result_audio; + GstCaps *caps_video, *caps_audio; + const gchar *media1_text, *media2_text, *media3_text, *media4_text; + + caps_video = gst_caps_from_string (caps_video_string1); + caps_audio = gst_caps_from_string (caps_audio_string); + + gst_sdp_media_new (&media_video); + fail_unless (media_video != NULL); + gst_sdp_media_new (&media_audio); + fail_unless (media_audio != NULL); + + ret = gst_sdp_media_set_media_from_caps (caps_video, media_video); + fail_unless (ret == GST_SDP_OK); + gst_caps_unref (caps_video); + ret = gst_sdp_media_set_media_from_caps (caps_audio, media_audio); + fail_unless (ret == GST_SDP_OK); + gst_caps_unref (caps_audio); + + gst_sdp_message_new (&message); + gst_sdp_message_parse_buffer ((guint8 *) sdp, length, message); + + result_video = gst_sdp_message_get_media (message, 0); + fail_unless (result_video != NULL); + + result_audio = gst_sdp_message_get_media (message, 2); + fail_unless (result_audio != NULL); + + media1_text = gst_sdp_media_get_attribute_val (media_video, "rtpmap"); + media2_text = gst_sdp_media_get_attribute_val (result_video, "rtpmap"); + media3_text = gst_sdp_media_get_format (media_audio, 0); + media4_text = gst_sdp_media_get_format (result_audio, 0); + + fail_if (g_strcmp0 (media1_text, media2_text) != 0); + fail_if (g_strcmp0 (media3_text, media4_text) != 0); + + gst_sdp_media_free (media_video); + gst_sdp_media_free (media_audio); + gst_sdp_message_free (message); +} + GST_END_TEST /* * End of test cases @@ -207,6 +310,8 @@ sdp_suite (void) tcase_add_test (tc_chain, copy); tcase_add_test (tc_chain, boxed); tcase_add_test (tc_chain, modify); + tcase_add_test (tc_chain, caps_from_media); + tcase_add_test (tc_chain, media_from_caps); return s; } diff --git a/win32/common/libgstsdp.def b/win32/common/libgstsdp.def index 6ff1e6b693..2c31ff7327 100644 --- a/win32/common/libgstsdp.def +++ b/win32/common/libgstsdp.def @@ -6,6 +6,7 @@ EXPORTS gst_mikey_message_add_rand_len gst_mikey_message_add_t gst_mikey_message_add_t_now_ntp_utc + gst_mikey_message_base64_encode gst_mikey_message_find_payload gst_mikey_message_get_cs_srtp gst_mikey_message_get_n_cs @@ -16,6 +17,7 @@ EXPORTS gst_mikey_message_insert_payload gst_mikey_message_new gst_mikey_message_new_from_bytes + gst_mikey_message_new_from_caps gst_mikey_message_new_from_data gst_mikey_message_remove_cs_srtp gst_mikey_message_remove_payload @@ -49,12 +51,14 @@ EXPORTS gst_sdp_bandwidth_set gst_sdp_connection_clear gst_sdp_connection_set + gst_sdp_make_keymgmt gst_sdp_media_add_attribute gst_sdp_media_add_bandwidth gst_sdp_media_add_connection gst_sdp_media_add_format gst_sdp_media_as_text gst_sdp_media_attributes_len + gst_sdp_media_attributes_to_caps gst_sdp_media_bandwidths_len gst_sdp_media_connections_len gst_sdp_media_copy @@ -64,6 +68,7 @@ EXPORTS gst_sdp_media_get_attribute_val gst_sdp_media_get_attribute_val_n gst_sdp_media_get_bandwidth + gst_sdp_media_get_caps_from_media gst_sdp_media_get_connection gst_sdp_media_get_format gst_sdp_media_get_information @@ -89,6 +94,7 @@ EXPORTS gst_sdp_media_set_information gst_sdp_media_set_key gst_sdp_media_set_media + gst_sdp_media_set_media_from_caps gst_sdp_media_set_port_info gst_sdp_media_set_proto gst_sdp_media_uninit @@ -102,6 +108,7 @@ EXPORTS gst_sdp_message_as_text gst_sdp_message_as_uri gst_sdp_message_attributes_len + gst_sdp_message_attributes_to_caps gst_sdp_message_bandwidths_len gst_sdp_message_copy gst_sdp_message_dump