/* GStreamer * Copyright (C) <2005,2006> Wim Taymans * <2006> Lutz Mueller * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /* * Unless otherwise indicated, Source Code is licensed under MIT license. * See further explanation attached in License Statement (distributed in the file * LICENSE). * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /** * SECTION:gstrtspmessage * @short_description: RTSP messages * @see_also: gstrtspconnection * * * * Provides methods for creating and parsing request, response and data messages. * * * * Last reviewed on 2007-07-25 (0.10.14) */ #include #include "gstrtspmessage.h" typedef struct _RTSPKeyValue { GstRTSPHeaderField field; gchar *value; } RTSPKeyValue; static void key_value_foreach (GArray * array, GFunc func, gpointer user_data) { guint i; g_return_if_fail (array != NULL); for (i = 0; i < array->len; i++) { (*func) (&g_array_index (array, RTSPKeyValue, i), user_data); } } /** * gst_rtsp_message_new: * @msg: a location for the new #GstRTSPMessage * * Create a new initialized #GstRTSPMessage. * * Returns: a #GstRTSPResult. Free with gst_rtsp_message_free(). */ GstRTSPResult gst_rtsp_message_new (GstRTSPMessage ** msg) { GstRTSPMessage *newmsg; g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL); newmsg = g_new0 (GstRTSPMessage, 1); *msg = newmsg; return gst_rtsp_message_init (newmsg); } /** * gst_rtsp_message_init: * @msg: a #GstRTSPMessage * * Initialize @msg. This function is mostly used when @msg is allocated on the * stack. The reverse operation of this is gst_rtsp_message_unset(). * * Returns: a #GstRTSPResult. */ GstRTSPResult gst_rtsp_message_init (GstRTSPMessage * msg) { g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL); gst_rtsp_message_unset (msg); msg->type = GST_RTSP_MESSAGE_INVALID; msg->hdr_fields = g_array_new (FALSE, FALSE, sizeof (RTSPKeyValue)); return GST_RTSP_OK; } /** * gst_rtsp_message_get_type: * @msg: a #GstRTSPMessage * * Get the message type of @msg. * * Returns: the message type. */ GstRTSPMsgType gst_rtsp_message_get_type (GstRTSPMessage * msg) { g_return_val_if_fail (msg != NULL, GST_RTSP_MESSAGE_INVALID); return msg->type; } /** * gst_rtsp_message_new_request: * @msg: a location for the new #GstRTSPMessage * @method: the request method to use * @uri: the uri of the request * * Create a new #GstRTSPMessage with @method and @uri and store the result * request message in @msg. * * Returns: a #GstRTSPResult. Free with gst_rtsp_message_free(). */ GstRTSPResult gst_rtsp_message_new_request (GstRTSPMessage ** msg, GstRTSPMethod method, const gchar * uri) { GstRTSPMessage *newmsg; g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL); g_return_val_if_fail (uri != NULL, GST_RTSP_EINVAL); newmsg = g_new0 (GstRTSPMessage, 1); *msg = newmsg; return gst_rtsp_message_init_request (newmsg, method, uri); } /** * gst_rtsp_message_init_request: * @msg: a #GstRTSPMessage * @method: the request method to use * @uri: the uri of the request * * Initialize @msg as a request message with @method and @uri. To clear @msg * again, use gst_rtsp_message_unset(). * * Returns: a #GstRTSPResult. */ GstRTSPResult gst_rtsp_message_init_request (GstRTSPMessage * msg, GstRTSPMethod method, const gchar * uri) { g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL); g_return_val_if_fail (uri != NULL, GST_RTSP_EINVAL); gst_rtsp_message_unset (msg); msg->type = GST_RTSP_MESSAGE_REQUEST; msg->type_data.request.method = method; msg->type_data.request.uri = g_strdup (uri); msg->type_data.request.version = GST_RTSP_VERSION_1_0; msg->hdr_fields = g_array_new (FALSE, FALSE, sizeof (RTSPKeyValue)); return GST_RTSP_OK; } /** * gst_rtsp_message_parse_request: * @msg: a #GstRTSPMessage * @method: location to hold the method * @uri: location to hold the uri * @version: location to hold the version * * Parse the request message @msg and store the values @method, @uri and * @version. The result locations can be #NULL if one is not interested in its * value. * * @uri remains valid for as long as @msg is valid and unchanged. * * Returns: a #GstRTSPResult. */ GstRTSPResult gst_rtsp_message_parse_request (GstRTSPMessage * msg, GstRTSPMethod * method, const gchar ** uri, GstRTSPVersion * version) { g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL); g_return_val_if_fail (msg->type != GST_RTSP_MESSAGE_REQUEST, GST_RTSP_EINVAL); if (method) *method = msg->type_data.request.method; if (uri) *uri = msg->type_data.request.uri; if (version) *version = msg->type_data.request.version; return GST_RTSP_OK; } /** * gst_rtsp_message_new_response: * @msg: a location for the new #GstRTSPMessage * @code: the status code * @reason: the status reason or #NULL * @request: the request that triggered the response or #NULL * * Create a new response #GstRTSPMessage with @code and @reason and store the * result message in @msg. * * When @reason is #NULL, the default reason for @code will be used. * * When @request is not #NULL, the relevant headers will be copied to the new * response message. * * Returns: a #GstRTSPResult. Free with gst_rtsp_message_free(). */ GstRTSPResult gst_rtsp_message_new_response (GstRTSPMessage ** msg, GstRTSPStatusCode code, const gchar * reason, const GstRTSPMessage * request) { GstRTSPMessage *newmsg; g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL); newmsg = g_new0 (GstRTSPMessage, 1); *msg = newmsg; return gst_rtsp_message_init_response (newmsg, code, reason, request); } /** * gst_rtsp_message_init_response: * @msg: a #GstRTSPMessage * @code: the status code * @reason: the status reason or #NULL * @request: the request that triggered the response or #NULL * * Initialize @msg with @code and @reason. * * When @reason is #NULL, the default reason for @code will be used. * * When @request is not #NULL, the relevant headers will be copied to the new * response message. * * Returns: a #GstRTSPResult. */ GstRTSPResult gst_rtsp_message_init_response (GstRTSPMessage * msg, GstRTSPStatusCode code, const gchar * reason, const GstRTSPMessage * request) { g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL); gst_rtsp_message_unset (msg); if (reason == NULL) reason = gst_rtsp_status_as_text (code); msg->type = GST_RTSP_MESSAGE_RESPONSE; msg->type_data.response.code = code; msg->type_data.response.reason = g_strdup (reason); msg->type_data.response.version = GST_RTSP_VERSION_1_0; msg->hdr_fields = g_array_new (FALSE, FALSE, sizeof (RTSPKeyValue)); if (request) { gchar *header; /* copy CSEQ */ if (gst_rtsp_message_get_header (request, GST_RTSP_HDR_CSEQ, &header, 0) == GST_RTSP_OK) { gst_rtsp_message_add_header (msg, GST_RTSP_HDR_CSEQ, header); } /* copy session id */ if (gst_rtsp_message_get_header (request, GST_RTSP_HDR_SESSION, &header, 0) == GST_RTSP_OK) { char *pos; header = g_strdup (header); if ((pos = strchr (header, ';'))) { *pos = '\0'; } g_strchomp (header); gst_rtsp_message_add_header (msg, GST_RTSP_HDR_SESSION, header); g_free (header); } /* FIXME copy more headers? */ } return GST_RTSP_OK; } /** * gst_rtsp_message_parse_response: * @msg: a #GstRTSPMessage * @code: location to hold the status code * @reason: location to hold the status reason * @version: location to hold the version * * Parse the response message @msg and store the values @code, @reason and * @version. The result locations can be #NULL if one is not interested in its * value. * * @reason remains valid for as long as @msg is valid and unchanged. * * Returns: a #GstRTSPResult. */ GstRTSPResult gst_rtsp_message_parse_response (GstRTSPMessage * msg, GstRTSPStatusCode * code, const gchar ** reason, GstRTSPVersion * version) { g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL); g_return_val_if_fail (msg->type != GST_RTSP_MESSAGE_RESPONSE, GST_RTSP_EINVAL); if (code) *code = msg->type_data.response.code; if (reason) *reason = msg->type_data.response.reason; if (version) *version = msg->type_data.response.version; return GST_RTSP_OK; } /** * gst_rtsp_message_new_data: * @msg: a location for the new #GstRTSPMessage * @channel: the channel * * Create a new data #GstRTSPMessage with @channel and store the * result message in @msg. * * Returns: a #GstRTSPResult. Free with gst_rtsp_message_free(). */ GstRTSPResult gst_rtsp_message_new_data (GstRTSPMessage ** msg, guint8 channel) { GstRTSPMessage *newmsg; g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL); newmsg = g_new0 (GstRTSPMessage, 1); *msg = newmsg; return gst_rtsp_message_init_data (newmsg, channel); } /** * gst_rtsp_message_init_data: * @msg: a #GstRTSPMessage * @channel: a channel * * Initialize a new data #GstRTSPMessage for @channel. * * Returns: a #GstRTSPResult. */ GstRTSPResult gst_rtsp_message_init_data (GstRTSPMessage * msg, guint8 channel) { g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL); gst_rtsp_message_unset (msg); msg->type = GST_RTSP_MESSAGE_DATA; msg->type_data.data.channel = channel; return GST_RTSP_OK; } /** * gst_rtsp_message_parse_data: * @msg: a #GstRTSPMessage * @channel: location to hold the channel * * Parse the data message @msg and store the channel in @channel. * * Returns: a #GstRTSPResult. */ GstRTSPResult gst_rtsp_message_parse_data (GstRTSPMessage * msg, guint8 * channel) { g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL); g_return_val_if_fail (msg->type != GST_RTSP_MESSAGE_DATA, GST_RTSP_EINVAL); if (channel) *channel = msg->type_data.data.channel; return GST_RTSP_OK; } /** * gst_rtsp_message_unset: * @msg: a #GstRTSPMessage * * Unset the concents of @msg so that it becomes an uninitialized * #GstRTSPMessage again. This function is mostly used in combination with * gst_rtsp_message_init_request(), gst_rtsp_message_init_response() and * gst_rtsp_message_init_data() on stack allocated #GstRTSPMessage structures. * * Returns: #GST_RTSP_OK. */ GstRTSPResult gst_rtsp_message_unset (GstRTSPMessage * msg) { g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL); switch (msg->type) { case GST_RTSP_MESSAGE_INVALID: break; case GST_RTSP_MESSAGE_REQUEST: g_free (msg->type_data.request.uri); break; case GST_RTSP_MESSAGE_RESPONSE: g_free (msg->type_data.response.reason); break; case GST_RTSP_MESSAGE_DATA: break; default: g_return_val_if_reached (GST_RTSP_EINVAL); } if (msg->hdr_fields != NULL) { gint i; for (i = 0; i < msg->hdr_fields->len; i++) { RTSPKeyValue *keyval = &g_array_index (msg->hdr_fields, RTSPKeyValue, i); g_free (keyval->value); } g_array_free (msg->hdr_fields, TRUE); } g_free (msg->body); memset (msg, 0, sizeof *msg); return GST_RTSP_OK; } /** * gst_rtsp_message_free: * @msg: a #GstRTSPMessage * * Free the memory used by @msg. * * Returns: a #GstRTSPResult. */ GstRTSPResult gst_rtsp_message_free (GstRTSPMessage * msg) { GstRTSPResult res; g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL); res = gst_rtsp_message_unset (msg); if (res == GST_RTSP_OK) g_free (msg); return res; } /** * gst_rtsp_message_add_header: * @msg: a #GstRTSPMessage * @field: a #GstRTSPHeaderField * @value: the value of the header * * Add a header with key @field and @value to @msg. * * Returns: a #GstRTSPResult. */ GstRTSPResult gst_rtsp_message_add_header (GstRTSPMessage * msg, GstRTSPHeaderField field, const gchar * value) { RTSPKeyValue key_value; g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL); g_return_val_if_fail (value != NULL, GST_RTSP_EINVAL); key_value.field = field; key_value.value = g_strdup (value); g_array_append_val (msg->hdr_fields, key_value); return GST_RTSP_OK; } /** * gst_rtsp_message_remove_header: * @msg: a #GstRTSPMessage * @field: a #GstRTSPHeaderField * @indx: the index of the header * * Remove the @indx header with key @field from @msg. If @indx equals -1, all * headers will be removed. * * Returns: a #GstRTSPResult. */ GstRTSPResult gst_rtsp_message_remove_header (GstRTSPMessage * msg, GstRTSPHeaderField field, gint indx) { GstRTSPResult res = GST_RTSP_ENOTIMPL; guint i = 0; gint cnt = 0; g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL); while (i < msg->hdr_fields->len) { RTSPKeyValue key_value = g_array_index (msg->hdr_fields, RTSPKeyValue, i); if (key_value.field == field && (indx == -1 || cnt++ == indx)) { g_array_remove_index (msg->hdr_fields, i); res = GST_RTSP_OK; if (indx != -1) break; } else { i++; } } return res; } /** * gst_rtsp_message_get_header: * @msg: a #GstRTSPMessage * @field: a #GstRTSPHeaderField * @value: pointer to hold the result * @indx: the index of the header * * Get the @indx header value with key @field from @msg. * * Returns: #GST_RTSP_OK when @field was found, #GST_RTSP_ENOTIMPL if the key * was not found. */ GstRTSPResult gst_rtsp_message_get_header (const GstRTSPMessage * msg, GstRTSPHeaderField field, gchar ** value, gint indx) { guint i; gint cnt = 0; g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL); for (i = 0; i < msg->hdr_fields->len; i++) { RTSPKeyValue key_value = g_array_index (msg->hdr_fields, RTSPKeyValue, i); if (key_value.field == field && cnt++ == indx) { if (value) *value = key_value.value; return GST_RTSP_OK; } } return GST_RTSP_ENOTIMPL; } /** * gst_rtsp_message_append_headers: * @msg: a #GstRTSPMessage * @str: a string * * Append the currently configured headers in @msg to the #GString @str suitable * for transmission. * * Returns: #GST_RTSP_OK. */ GstRTSPResult gst_rtsp_message_append_headers (const GstRTSPMessage * msg, GString * str) { guint i; g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL); g_return_val_if_fail (str != NULL, GST_RTSP_EINVAL); for (i = 0; i < msg->hdr_fields->len; i++) { RTSPKeyValue key_value = g_array_index (msg->hdr_fields, RTSPKeyValue, i); const gchar *keystr = gst_rtsp_header_as_text (key_value.field); g_string_append_printf (str, "%s: %s\r\n", keystr, key_value.value); } return GST_RTSP_OK; } /** * gst_rtsp_message_set_body: * @msg: a #GstRTSPMessage * @data: the data * @size: the size of @data * * Set the body of @msg to a copy of @data. * * Returns: #GST_RTSP_OK. */ GstRTSPResult gst_rtsp_message_set_body (GstRTSPMessage * msg, const guint8 * data, guint size) { g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL); return gst_rtsp_message_take_body (msg, g_memdup (data, size), size); } /** * gst_rtsp_message_take_body: * @msg: a #GstRTSPMessage * @data: the data * @size: the size of @data * * Set the body of @msg to @data and @size. This method takes ownership of * @data. * * Returns: #GST_RTSP_OK. */ GstRTSPResult gst_rtsp_message_take_body (GstRTSPMessage * msg, guint8 * data, guint size) { g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL); g_return_val_if_fail (data != NULL || size == 0, GST_RTSP_EINVAL); if (msg->body) g_free (msg->body); msg->body = data; msg->body_size = size; return GST_RTSP_OK; } /** * gst_rtsp_message_get_body: * @msg: a #GstRTSPMessage * @data: location for the data * @size: location for the size of @data * * Get the body of @msg. @data remains valid for as long as @msg is valid and * unchanged. * * Returns: #GST_RTSP_OK. */ GstRTSPResult gst_rtsp_message_get_body (const GstRTSPMessage * msg, guint8 ** data, guint * size) { g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL); g_return_val_if_fail (data != NULL, GST_RTSP_EINVAL); g_return_val_if_fail (size != NULL, GST_RTSP_EINVAL); *data = msg->body; *size = msg->body_size; return GST_RTSP_OK; } /** * gst_rtsp_message_steal_body: * @msg: a #GstRTSPMessage * @data: location for the data * @size: location for the size of @data * * Take the body of @msg and store it in @data and @size. After this method, * the body and size of @msg will be set to #NULL and 0 respectively. * * Returns: #GST_RTSP_OK. */ GstRTSPResult gst_rtsp_message_steal_body (GstRTSPMessage * msg, guint8 ** data, guint * size) { g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL); g_return_val_if_fail (data != NULL, GST_RTSP_EINVAL); g_return_val_if_fail (size != NULL, GST_RTSP_EINVAL); *data = msg->body; *size = msg->body_size; msg->body = NULL; msg->body_size = 0; return GST_RTSP_OK; } static void dump_mem (guint8 * mem, guint size) { guint i, j; GString *string = g_string_sized_new (50); GString *chars = g_string_sized_new (18); i = j = 0; while (i < size) { if (g_ascii_isprint (mem[i])) g_string_append_printf (chars, "%c", mem[i]); else g_string_append_printf (chars, "."); g_string_append_printf (string, "%02x ", mem[i]); j++; i++; if (j == 16 || i == size) { g_print ("%08x (%p): %-48.48s %-16.16s\n", i - j, mem + i - j, string->str, chars->str); g_string_set_size (string, 0); g_string_set_size (chars, 0); j = 0; } } g_string_free (string, TRUE); g_string_free (chars, TRUE); } static void dump_key_value (gpointer data, gpointer user_data) { RTSPKeyValue *key_value = (RTSPKeyValue *) data; g_print (" key: '%s', value: '%s'\n", gst_rtsp_header_as_text (key_value->field), key_value->value); } /** * gst_rtsp_message_dump: * @msg: a #GstRTSPMessage * * Dump the contents of @msg to stdout. * * Returns: #GST_RTSP_OK. */ GstRTSPResult gst_rtsp_message_dump (GstRTSPMessage * msg) { guint8 *data; guint size; g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL); switch (msg->type) { case GST_RTSP_MESSAGE_REQUEST: g_print ("RTSP request message %p\n", msg); g_print (" request line:\n"); g_print (" method: '%s'\n", gst_rtsp_method_as_text (msg->type_data.request.method)); g_print (" uri: '%s'\n", msg->type_data.request.uri); g_print (" version: '%s'\n", gst_rtsp_version_as_text (msg->type_data.request.version)); g_print (" headers:\n"); key_value_foreach (msg->hdr_fields, dump_key_value, NULL); g_print (" body:\n"); gst_rtsp_message_get_body (msg, &data, &size); dump_mem (data, size); break; case GST_RTSP_MESSAGE_RESPONSE: g_print ("RTSP response message %p\n", msg); g_print (" status line:\n"); g_print (" code: '%d'\n", msg->type_data.response.code); g_print (" reason: '%s'\n", msg->type_data.response.reason); g_print (" version: '%s'\n", gst_rtsp_version_as_text (msg->type_data.response.version)); g_print (" headers:\n"); key_value_foreach (msg->hdr_fields, dump_key_value, NULL); gst_rtsp_message_get_body (msg, &data, &size); g_print (" body: length %d\n", size); dump_mem (data, size); break; case GST_RTSP_MESSAGE_DATA: g_print ("RTSP data message %p\n", msg); g_print (" channel: '%d'\n", msg->type_data.data.channel); g_print (" size: '%d'\n", msg->body_size); gst_rtsp_message_get_body (msg, &data, &size); dump_mem (data, size); break; default: g_print ("unsupported message type %d\n", msg->type); return GST_RTSP_EINVAL; } return GST_RTSP_OK; }