X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gst%2Frtsp-server%2Frtsp-client.c;h=0822727f3b0911a1aece328a8f8403282391a4fd;hb=7a947e8dfe424664ffdfb5397d506f785397dcea;hp=db37e1e58ebe061681a979df99e879918a9af5fa;hpb=8da4171055c3706eed25b6724ca0fdb2d8ef2067;p=platform%2Fupstream%2Fgstreamer.git diff --git a/gst/rtsp-server/rtsp-client.c b/gst/rtsp-server/rtsp-client.c index db37e1e..0822727 100644 --- a/gst/rtsp-server/rtsp-client.c +++ b/gst/rtsp-server/rtsp-client.c @@ -16,6 +16,28 @@ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ +/** + * SECTION:rtsp-client + * @short_description: A client connection state + * @see_also: #GstRTSPServer, #GstRTSPThreadPool + * + * The client object handles the connection with a client for as long as a TCP + * connection is open. + * + * A #GstRTSPClient is created by #GstRTSPServer when a new connection is + * accepted and it inherits the #GstRTSPMountPoints, #GstRTSPSessionPool, + * #GstRTSPAuth and #GstRTSPThreadPool from the server. + * + * The client connection should be configured with the #GstRTSPConnection using + * gst_rtsp_client_set_connection() before it can be attached to a #GMainContext + * using gst_rtsp_client_attach(). From then on the client will handle requests + * on the connection. + * + * Use gst_rtsp_client_session_filter() to iterate or modify all the + * #GstRTSPSession objects managed by the client object. + * + * Last reviewed on 2013-07-11 (1.0.0) + */ #include #include @@ -24,19 +46,52 @@ #include "rtsp-sdp.h" #include "rtsp-params.h" +#define GST_RTSP_CLIENT_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_RTSP_CLIENT, GstRTSPClientPrivate)) + +/* locking order: + * send_lock, lock, tunnels_lock + */ + +struct _GstRTSPClientPrivate +{ + GMutex lock; /* protects everything else */ + GMutex send_lock; + GstRTSPConnection *connection; + GstRTSPWatch *watch; + guint close_seq; + gchar *server_ip; + gboolean is_ipv6; + + GstRTSPClientSendFunc send_func; /* protected by send_lock */ + gpointer send_data; /* protected by send_lock */ + GDestroyNotify send_notify; /* protected by send_lock */ + + GstRTSPSessionPool *session_pool; + GstRTSPMountPoints *mount_points; + GstRTSPAuth *auth; + GstRTSPThreadPool *thread_pool; + + /* used to cache the media in the last requested DESCRIBE so that + * we can pick it up in the next SETUP immediately */ + gchar *path; + GstRTSPMedia *media; + + GList *transports; + GList *sessions; +}; + static GMutex tunnels_lock; -static GHashTable *tunnels; +static GHashTable *tunnels; /* protected by tunnels_lock */ #define DEFAULT_SESSION_POOL NULL #define DEFAULT_MOUNT_POINTS NULL -#define DEFAULT_USE_CLIENT_SETTINGS FALSE enum { PROP_0, PROP_SESSION_POOL, PROP_MOUNT_POINTS, - PROP_USE_CLIENT_SETTINGS, PROP_LAST }; @@ -52,6 +107,7 @@ enum SIGNAL_TEARDOWN_REQUEST, SIGNAL_SET_PARAMETER_REQUEST, SIGNAL_GET_PARAMETER_REQUEST, + SIGNAL_HANDLE_RESPONSE, SIGNAL_LAST }; @@ -70,7 +126,17 @@ static GstSDPMessage *create_sdp (GstRTSPClient * client, GstRTSPMedia * media); static void client_session_finalized (GstRTSPClient * client, GstRTSPSession * session); static void unlink_session_transports (GstRTSPClient * client, - GstRTSPSession * session, GstRTSPSessionMedia * media); + GstRTSPSession * session, GstRTSPSessionMedia * sessmedia); +static gboolean default_configure_client_media (GstRTSPClient * client, + GstRTSPMedia * media, GstRTSPStream * stream, GstRTSPContext * ctx); +static gboolean default_configure_client_transport (GstRTSPClient * client, + GstRTSPContext * ctx, GstRTSPTransport * ct); +static GstRTSPResult default_params_set (GstRTSPClient * client, + GstRTSPContext * ctx); +static GstRTSPResult default_params_get (GstRTSPClient * client, + GstRTSPContext * ctx); +static gchar *default_make_path_from_uri (GstRTSPClient * client, + const GstRTSPUrl * uri); G_DEFINE_TYPE (GstRTSPClient, gst_rtsp_client, G_TYPE_OBJECT); @@ -79,6 +145,8 @@ gst_rtsp_client_class_init (GstRTSPClientClass * klass) { GObjectClass *gobject_class; + g_type_class_add_private (klass, sizeof (GstRTSPClientPrivate)); + gobject_class = G_OBJECT_CLASS (klass); gobject_class->get_property = gst_rtsp_client_get_property; @@ -86,6 +154,11 @@ gst_rtsp_client_class_init (GstRTSPClientClass * klass) gobject_class->finalize = gst_rtsp_client_finalize; klass->create_sdp = create_sdp; + klass->configure_client_media = default_configure_client_media; + klass->configure_client_transport = default_configure_client_transport; + klass->params_set = default_params_set; + klass->params_get = default_params_get; + klass->make_path_from_uri = default_make_path_from_uri; g_object_class_install_property (gobject_class, PROP_SESSION_POOL, g_param_spec_object ("session-pool", "Session Pool", @@ -99,12 +172,6 @@ gst_rtsp_client_class_init (GstRTSPClientClass * klass) GST_TYPE_RTSP_MOUNT_POINTS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (gobject_class, PROP_USE_CLIENT_SETTINGS, - g_param_spec_boolean ("use-client-settings", "Use Client Settings", - "Use client settings for ttl and destination in multicast", - DEFAULT_USE_CLIENT_SETTINGS, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - gst_rtsp_client_signals[SIGNAL_CLOSED] = g_signal_new ("closed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, closed), NULL, NULL, @@ -163,6 +230,12 @@ gst_rtsp_client_class_init (GstRTSPClientClass * klass) get_parameter_request), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); + gst_rtsp_client_signals[SIGNAL_HANDLE_RESPONSE] = + g_signal_new ("handle-response", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, + handle_response), NULL, NULL, g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); + tunnels = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); g_mutex_init (&tunnels_lock); @@ -173,39 +246,88 @@ gst_rtsp_client_class_init (GstRTSPClientClass * klass) static void gst_rtsp_client_init (GstRTSPClient * client) { - g_mutex_init (&client->lock); - client->use_client_settings = DEFAULT_USE_CLIENT_SETTINGS; - client->close_response_seq = 0; + GstRTSPClientPrivate *priv = GST_RTSP_CLIENT_GET_PRIVATE (client); + + client->priv = priv; + + g_mutex_init (&priv->lock); + g_mutex_init (&priv->send_lock); + priv->close_seq = 0; +} + +static GstRTSPFilterResult +filter_session (GstRTSPSession * sess, GstRTSPSessionMedia * sessmedia, + gpointer user_data) +{ + GstRTSPClient *client = GST_RTSP_CLIENT (user_data); + + gst_rtsp_session_media_set_state (sessmedia, GST_STATE_NULL); + unlink_session_transports (client, sess, sessmedia); + + /* unmanage the media in the session */ + return GST_RTSP_FILTER_REMOVE; } static void client_unlink_session (GstRTSPClient * client, GstRTSPSession * session) { /* unlink all media managed in this session */ - while (session->medias) { - GstRTSPSessionMedia *media = session->medias->data; + gst_rtsp_session_filter (session, filter_session, client); +} + +static void +client_watch_session (GstRTSPClient * client, GstRTSPSession * session) +{ + GstRTSPClientPrivate *priv = client->priv; + GList *walk; + + for (walk = priv->sessions; walk; walk = g_list_next (walk)) { + GstRTSPSession *msession = (GstRTSPSession *) walk->data; - gst_rtsp_session_media_set_state (media, GST_STATE_NULL); - unlink_session_transports (client, session, media); - /* unmanage the media in the session. this will modify session->medias */ - gst_rtsp_session_release_media (session, media); + /* we already know about this session */ + if (msession == session) + return; } + + GST_INFO ("watching session %p", session); + + g_object_weak_ref (G_OBJECT (session), (GWeakNotify) client_session_finalized, + client); + priv->sessions = g_list_prepend (priv->sessions, session); +} + +static void +client_unwatch_session (GstRTSPClient * client, GstRTSPSession * session) +{ + GstRTSPClientPrivate *priv = client->priv; + + GST_INFO ("unwatching session %p", session); + + g_object_weak_unref (G_OBJECT (session), + (GWeakNotify) client_session_finalized, client); + priv->sessions = g_list_remove (priv->sessions, session); +} + +static void +client_cleanup_session (GstRTSPClient * client, GstRTSPSession * session) +{ + g_object_weak_unref (G_OBJECT (session), + (GWeakNotify) client_session_finalized, client); + client_unlink_session (client, session); } static void client_cleanup_sessions (GstRTSPClient * client) { + GstRTSPClientPrivate *priv = client->priv; GList *sessions; /* remove weak-ref from sessions */ - for (sessions = client->sessions; sessions; sessions = g_list_next (sessions)) { - GstRTSPSession *session = (GstRTSPSession *) sessions->data; - g_object_weak_unref (G_OBJECT (session), - (GWeakNotify) client_session_finalized, client); - client_unlink_session (client, session); + for (sessions = priv->sessions; sessions; sessions = g_list_next (sessions)) { + client_cleanup_session (client, (GstRTSPSession *) sessions->data); } - g_list_free (client->sessions); - client->sessions = NULL; + g_list_free (priv->sessions); + priv->sessions = NULL; } /* A client is finalized when the connection is broken */ @@ -213,31 +335,38 @@ static void gst_rtsp_client_finalize (GObject * obj) { GstRTSPClient *client = GST_RTSP_CLIENT (obj); + GstRTSPClientPrivate *priv = client->priv; GST_INFO ("finalize client %p", client); - if (client->watch) - g_source_destroy ((GSource *) client->watch); + gst_rtsp_client_set_send_func (client, NULL, NULL, NULL); + + if (priv->watch) + g_source_destroy ((GSource *) priv->watch); client_cleanup_sessions (client); - gst_rtsp_connection_free (client->connection); - if (client->session_pool) - g_object_unref (client->session_pool); - if (client->mount_points) - g_object_unref (client->mount_points); - if (client->auth) - g_object_unref (client->auth); + if (priv->connection) + gst_rtsp_connection_free (priv->connection); + if (priv->session_pool) + g_object_unref (priv->session_pool); + if (priv->mount_points) + g_object_unref (priv->mount_points); + if (priv->auth) + g_object_unref (priv->auth); + if (priv->thread_pool) + g_object_unref (priv->thread_pool); - if (client->uri) - gst_rtsp_url_free (client->uri); - if (client->media) { - gst_rtsp_media_unprepare (client->media); - g_object_unref (client->media); + if (priv->path) + g_free (priv->path); + if (priv->media) { + gst_rtsp_media_unprepare (priv->media); + g_object_unref (priv->media); } - g_free (client->server_ip); - g_mutex_clear (&client->lock); + g_free (priv->server_ip); + g_mutex_clear (&priv->lock); + g_mutex_clear (&priv->send_lock); G_OBJECT_CLASS (gst_rtsp_client_parent_class)->finalize (obj); } @@ -255,10 +384,6 @@ gst_rtsp_client_get_property (GObject * object, guint propid, case PROP_MOUNT_POINTS: g_value_take_object (value, gst_rtsp_client_get_mount_points (client)); break; - case PROP_USE_CLIENT_SETTINGS: - g_value_set_boolean (value, - gst_rtsp_client_get_use_client_settings (client)); - break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec); } @@ -277,10 +402,6 @@ gst_rtsp_client_set_property (GObject * object, guint propid, case PROP_MOUNT_POINTS: gst_rtsp_client_set_mount_points (client, g_value_get_object (value)); break; - case PROP_USE_CLIENT_SETTINGS: - gst_rtsp_client_set_use_client_settings (client, - g_value_get_boolean (value)); - break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec); } @@ -304,68 +425,58 @@ gst_rtsp_client_new (void) } static void -send_response (GstRTSPClient * client, GstRTSPSession * session, - GstRTSPMessage * response, gboolean close) +send_message (GstRTSPClient * client, GstRTSPSession * session, + GstRTSPMessage * message, gboolean close) { - gst_rtsp_message_add_header (response, GST_RTSP_HDR_SERVER, + GstRTSPClientPrivate *priv = client->priv; + + gst_rtsp_message_add_header (message, GST_RTSP_HDR_SERVER, "GStreamer RTSP server"); /* remove any previous header */ - gst_rtsp_message_remove_header (response, GST_RTSP_HDR_SESSION, -1); + gst_rtsp_message_remove_header (message, GST_RTSP_HDR_SESSION, -1); /* add the new session header for new session ids */ if (session) { - gst_rtsp_message_take_header (response, GST_RTSP_HDR_SESSION, + gst_rtsp_message_take_header (message, GST_RTSP_HDR_SESSION, gst_rtsp_session_get_header (session)); } if (gst_debug_category_get_threshold (rtsp_client_debug) >= GST_LEVEL_LOG) { - gst_rtsp_message_dump (response); + gst_rtsp_message_dump (message); } - if (close) { - gst_rtsp_message_add_header (response, GST_RTSP_HDR_CONNECTION, "close"); - } - /* send the response and store the seq number so we can wait until it's - * written to the client to close the connection */ - gst_rtsp_watch_send_message (client->watch, response, close ? - &client->close_response_seq : NULL); - gst_rtsp_message_unset (response); -} + if (close) + gst_rtsp_message_add_header (message, GST_RTSP_HDR_CONNECTION, "close"); -static void -send_generic_response (GstRTSPClient * client, GstRTSPStatusCode code, - GstRTSPClientState * state) -{ - gst_rtsp_message_init_response (state->response, code, - gst_rtsp_status_as_text (code), state->request); + g_mutex_lock (&priv->send_lock); + if (priv->send_func) + priv->send_func (client, message, close, priv->send_data); + g_mutex_unlock (&priv->send_lock); - send_response (client, NULL, state->response, FALSE); + gst_rtsp_message_unset (message); } static void -handle_unauthorized_request (GstRTSPClient * client, GstRTSPAuth * auth, - GstRTSPClientState * state) +send_generic_response (GstRTSPClient * client, GstRTSPStatusCode code, + GstRTSPContext * ctx) { - gst_rtsp_message_init_response (state->response, GST_RTSP_STS_UNAUTHORIZED, - gst_rtsp_status_as_text (GST_RTSP_STS_UNAUTHORIZED), state->request); - - if (auth) { - /* and let the authentication manager setup the auth tokens */ - gst_rtsp_auth_setup_auth (auth, client, 0, state); - } + gst_rtsp_message_init_response (ctx->response, code, + gst_rtsp_status_as_text (code), ctx->request); - send_response (client, state->session, state->response, FALSE); + send_message (client, NULL, ctx->response, FALSE); } - static gboolean -compare_uri (const GstRTSPUrl * uri1, const GstRTSPUrl * uri2) +paths_are_equal (const gchar * path1, const gchar * path2, gint len2) { - if (uri1 == NULL || uri2 == NULL) + if (path1 == NULL || path2 == NULL) return FALSE; - if (strcmp (uri1->abspath, uri2->abspath)) + if (strlen (path1) != len2) + return FALSE; + + if (strncmp (path1, path2, len2)) return FALSE; return TRUE; @@ -375,107 +486,125 @@ compare_uri (const GstRTSPUrl * uri1, const GstRTSPUrl * uri2) * but is cached for when the same client (without breaking the connection) is * doing a setup for the exact same url. */ static GstRTSPMedia * -find_media (GstRTSPClient * client, GstRTSPClientState * state) +find_media (GstRTSPClient * client, GstRTSPContext * ctx, gchar * path, + gint * matched) { + GstRTSPClientPrivate *priv = client->priv; GstRTSPMediaFactory *factory; GstRTSPMedia *media; - GstRTSPAuth *auth; + gint path_len; - if (!compare_uri (client->uri, state->uri)) { - /* remove any previously cached values before we try to construct a new - * media for uri */ - if (client->uri) - gst_rtsp_url_free (client->uri); - client->uri = NULL; - if (client->media) { - gst_rtsp_media_unprepare (client->media); - g_object_unref (client->media); - } - client->media = NULL; + /* find the longest matching factory for the uri first */ + if (!(factory = gst_rtsp_mount_points_match (priv->mount_points, + path, matched))) + goto no_factory; - if (!client->mount_points) - goto no_mount_points; + ctx->factory = factory; - /* find the factory for the uri first */ - if (!(factory = - gst_rtsp_mount_points_find_factory (client->mount_points, - state->uri))) - goto no_factory; + if (!gst_rtsp_auth_check (GST_RTSP_AUTH_CHECK_MEDIA_FACTORY_ACCESS)) + goto no_factory_access; - state->factory = factory; + if (!gst_rtsp_auth_check (GST_RTSP_AUTH_CHECK_MEDIA_FACTORY_CONSTRUCT)) + goto not_authorized; + + if (matched) + path_len = *matched; + else + path_len = strlen (path); - /* check if we have access to the factory */ - if ((auth = gst_rtsp_media_factory_get_auth (factory))) { - if (!gst_rtsp_auth_check (auth, client, 0, state)) - goto not_allowed; + if (!paths_are_equal (priv->path, path, path_len)) { + GstRTSPThread *thread; - g_object_unref (auth); + /* remove any previously cached values before we try to construct a new + * media for uri */ + if (priv->path) + g_free (priv->path); + priv->path = NULL; + if (priv->media) { + gst_rtsp_media_unprepare (priv->media); + g_object_unref (priv->media); } + priv->media = NULL; /* prepare the media and add it to the pipeline */ - if (!(media = gst_rtsp_media_factory_construct (factory, state->uri))) + if (!(media = gst_rtsp_media_factory_construct (factory, ctx->uri))) goto no_media; - g_object_unref (factory); - factory = NULL; - state->factory = NULL; + ctx->media = media; - /* set ipv6 on the media before preparing */ - media->is_ipv6 = client->is_ipv6; - state->media = media; + thread = gst_rtsp_thread_pool_get_thread (priv->thread_pool, + GST_RTSP_THREAD_TYPE_MEDIA, ctx); + if (thread == NULL) + goto no_thread; /* prepare the media */ - if (!(gst_rtsp_media_prepare (media))) + if (!(gst_rtsp_media_prepare (media, thread))) goto no_prepare; /* now keep track of the uri and the media */ - client->uri = gst_rtsp_url_copy (state->uri); - client->media = media; + priv->path = g_strndup (path, path_len); + priv->media = media; } else { - /* we have seen this uri before, used cached media */ - media = client->media; - state->media = media; - GST_INFO ("reusing cached media %p", media); + /* we have seen this path before, used cached media */ + media = priv->media; + ctx->media = media; + GST_INFO ("reusing cached media %p for path %s", media, priv->path); } + g_object_unref (factory); + ctx->factory = NULL; + if (media) g_object_ref (media); return media; /* ERRORS */ -no_mount_points: +no_factory: { - GST_ERROR ("client %p: no mount points configured", client); - send_generic_response (client, GST_RTSP_STS_NOT_FOUND, state); + GST_ERROR ("client %p: no factory for path %s", client, path); + send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx); return NULL; } -no_factory: +no_factory_access: { - GST_ERROR ("client %p: no factory for uri", client); - send_generic_response (client, GST_RTSP_STS_NOT_FOUND, state); + GST_ERROR ("client %p: not authorized to see factory path %s", client, + path); + /* error reply is already sent */ return NULL; } -not_allowed: +not_authorized: { - GST_ERROR ("client %p: unauthorized request", client); - handle_unauthorized_request (client, auth, state); - g_object_unref (factory); - g_object_unref (auth); + GST_ERROR ("client %p: not authorized for factory path %s", client, path); + /* error reply is already sent */ return NULL; } no_media: { GST_ERROR ("client %p: can't create media", client); - send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, state); + send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); + g_object_unref (factory); + ctx->factory = NULL; + return NULL; + } +no_thread: + { + GST_ERROR ("client %p: can't create thread", client); + send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx); + g_object_unref (media); + ctx->media = NULL; g_object_unref (factory); + ctx->factory = NULL; return NULL; } no_prepare: { GST_ERROR ("client %p: can't prepare media", client); - send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, state); + send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx); g_object_unref (media); + ctx->media = NULL; + g_object_unref (factory); + ctx->factory = NULL; return NULL; } } @@ -483,6 +612,7 @@ no_prepare: static gboolean do_send_data (GstBuffer * buffer, guint8 channel, GstRTSPClient * client) { + GstRTSPClientPrivate *priv = client->priv; GstRTSPMessage message = { 0 }; GstMapInfo map_info; guint8 *data; @@ -496,9 +626,10 @@ do_send_data (GstBuffer * buffer, guint8 channel, GstRTSPClient * client) gst_rtsp_message_take_body (&message, map_info.data, map_info.size); - /* FIXME, client->watch could have been finalized here, we need to keep an - * extra refcount to the watch. */ - gst_rtsp_watch_send_message (client->watch, &message, NULL); + g_mutex_lock (&priv->send_lock); + if (priv->send_func) + priv->send_func (client, &message, FALSE, priv->send_data); + g_mutex_unlock (&priv->send_lock); gst_rtsp_message_steal_body (&message, &data, &usize); gst_buffer_unmap (buffer, &map_info); @@ -512,12 +643,15 @@ static void link_transport (GstRTSPClient * client, GstRTSPSession * session, GstRTSPStreamTransport * trans) { + GstRTSPClientPrivate *priv = client->priv; + GST_DEBUG ("client %p: linking transport %p", client, trans); + gst_rtsp_stream_transport_set_callbacks (trans, (GstRTSPSendFunc) do_send_data, (GstRTSPSendFunc) do_send_data, client, NULL); - client->transports = g_list_prepend (client->transports, trans); + priv->transports = g_list_prepend (priv->transports, trans); /* make sure our session can't expire */ gst_rtsp_session_prevent_expire (session); @@ -527,10 +661,13 @@ static void unlink_transport (GstRTSPClient * client, GstRTSPSession * session, GstRTSPStreamTransport * trans) { + GstRTSPClientPrivate *priv = client->priv; + GST_DEBUG ("client %p: unlinking transport %p", client, trans); + gst_rtsp_stream_transport_set_callbacks (trans, NULL, NULL, NULL, NULL); - client->transports = g_list_remove (client->transports, trans); + priv->transports = g_list_remove (priv->transports, trans); /* our session can now expire */ gst_rtsp_session_allow_expire (session); @@ -538,21 +675,22 @@ unlink_transport (GstRTSPClient * client, GstRTSPSession * session, static void unlink_session_transports (GstRTSPClient * client, GstRTSPSession * session, - GstRTSPSessionMedia * media) + GstRTSPSessionMedia * sessmedia) { guint n_streams, i; - n_streams = gst_rtsp_media_n_streams (media->media); + n_streams = + gst_rtsp_media_n_streams (gst_rtsp_session_media_get_media (sessmedia)); for (i = 0; i < n_streams; i++) { GstRTSPStreamTransport *trans; - GstRTSPTransport *tr; + const GstRTSPTransport *tr; /* get the transport, if there is no transport configured, skip this stream */ - trans = gst_rtsp_session_media_get_transport (media, i); + trans = gst_rtsp_session_media_get_transport (sessmedia, i); if (trans == NULL) continue; - tr = trans->transport; + tr = gst_rtsp_stream_transport_get_transport (trans); if (tr->lower_transport == GST_RTSP_LOWER_TRANS_TCP) { /* for TCP, unlink the stream from the TCP connection of the client */ @@ -564,65 +702,93 @@ unlink_session_transports (GstRTSPClient * client, GstRTSPSession * session, static void close_connection (GstRTSPClient * client) { + GstRTSPClientPrivate *priv = client->priv; const gchar *tunnelid; GST_DEBUG ("client %p: closing connection", client); - if ((tunnelid = gst_rtsp_connection_get_tunnelid (client->connection))) { + if ((tunnelid = gst_rtsp_connection_get_tunnelid (priv->connection))) { g_mutex_lock (&tunnels_lock); /* remove from tunnelids */ g_hash_table_remove (tunnels, tunnelid); g_mutex_unlock (&tunnels_lock); } - gst_rtsp_connection_close (client->connection); + gst_rtsp_connection_close (priv->connection); +} + +static gchar * +default_make_path_from_uri (GstRTSPClient * client, const GstRTSPUrl * uri) +{ + gchar *path; + + if (uri->query) + path = g_strconcat (uri->abspath, "?", uri->query, NULL); + else + path = g_strdup (uri->abspath); + + return path; } static gboolean -handle_teardown_request (GstRTSPClient * client, GstRTSPClientState * state) +handle_teardown_request (GstRTSPClient * client, GstRTSPContext * ctx) { + GstRTSPClientPrivate *priv = client->priv; + GstRTSPClientClass *klass; GstRTSPSession *session; - GstRTSPSessionMedia *media; + GstRTSPSessionMedia *sessmedia; GstRTSPStatusCode code; + gchar *path; + gint matched; - if (!state->session) + if (!ctx->session) goto no_session; - session = state->session; + session = ctx->session; + + if (!ctx->uri) + goto no_uri; + + klass = GST_RTSP_CLIENT_GET_CLASS (client); + path = klass->make_path_from_uri (client, ctx->uri); /* get a handle to the configuration of the media in the session */ - media = gst_rtsp_session_get_media (session, state->uri); - if (!media) + sessmedia = gst_rtsp_session_get_media (session, path, &matched); + if (!sessmedia) goto not_found; - state->sessmedia = media; + /* only aggregate control for now.. */ + if (path[matched] != '\0') + goto no_aggregate; + + g_free (path); + + ctx->sessmedia = sessmedia; + + /* we emit the signal before closing the connection */ + g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_TEARDOWN_REQUEST], + 0, ctx); /* unlink the all TCP callbacks */ - unlink_session_transports (client, session, media); + unlink_session_transports (client, session, sessmedia); /* remove the session from the watched sessions */ - g_object_weak_unref (G_OBJECT (session), - (GWeakNotify) client_session_finalized, client); - client->sessions = g_list_remove (client->sessions, session); + client_unwatch_session (client, session); - gst_rtsp_session_media_set_state (media, GST_STATE_NULL); + gst_rtsp_session_media_set_state (sessmedia, GST_STATE_NULL); /* unmanage the media in the session, returns false if all media session * are torn down. */ - if (!gst_rtsp_session_release_media (session, media)) { + if (!gst_rtsp_session_release_media (session, sessmedia)) { /* remove the session */ - gst_rtsp_session_pool_remove (client->session_pool, session); + gst_rtsp_session_pool_remove (priv->session_pool, session); } /* construct the response now */ code = GST_RTSP_STS_OK; - gst_rtsp_message_init_response (state->response, code, - gst_rtsp_status_as_text (code), state->request); + gst_rtsp_message_init_response (ctx->response, code, + gst_rtsp_status_as_text (code), ctx->request); - send_response (client, session, state->response, TRUE); - - /* we emit the signal before closing the connection */ - g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_TEARDOWN_REQUEST], - 0, state); + send_message (client, session, ctx->response, TRUE); return TRUE; @@ -630,42 +796,77 @@ handle_teardown_request (GstRTSPClient * client, GstRTSPClientState * state) no_session: { GST_ERROR ("client %p: no session", client); - send_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, state); + send_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx); + return FALSE; + } +no_uri: + { + GST_ERROR ("client %p: no uri supplied", client); + send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); return FALSE; } not_found: { GST_ERROR ("client %p: no media for uri", client); - send_generic_response (client, GST_RTSP_STS_NOT_FOUND, state); + send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx); + g_free (path); + return FALSE; + } +no_aggregate: + { + GST_ERROR ("client %p: no aggregate path %s", client, path); + send_generic_response (client, + GST_RTSP_STS_ONLY_AGGREGATE_OPERATION_ALLOWED, ctx); + g_free (path); return FALSE; } } +static GstRTSPResult +default_params_set (GstRTSPClient * client, GstRTSPContext * ctx) +{ + GstRTSPResult res; + + res = gst_rtsp_params_set (client, ctx); + + return res; +} + +static GstRTSPResult +default_params_get (GstRTSPClient * client, GstRTSPContext * ctx) +{ + GstRTSPResult res; + + res = gst_rtsp_params_get (client, ctx); + + return res; +} + static gboolean -handle_get_param_request (GstRTSPClient * client, GstRTSPClientState * state) +handle_get_param_request (GstRTSPClient * client, GstRTSPContext * ctx) { GstRTSPResult res; guint8 *data; guint size; - res = gst_rtsp_message_get_body (state->request, &data, &size); + res = gst_rtsp_message_get_body (ctx->request, &data, &size); if (res != GST_RTSP_OK) goto bad_request; if (size == 0) { /* no body, keep-alive request */ - send_generic_response (client, GST_RTSP_STS_OK, state); + send_generic_response (client, GST_RTSP_STS_OK, ctx); } else { /* there is a body, handle the params */ - res = gst_rtsp_params_get (client, state); + res = GST_RTSP_CLIENT_GET_CLASS (client)->params_get (client, ctx); if (res != GST_RTSP_OK) goto bad_request; - send_response (client, state->session, state->response, FALSE); + send_message (client, ctx->session, ctx->response, FALSE); } g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_GET_PARAMETER_REQUEST], - 0, state); + 0, ctx); return TRUE; @@ -673,36 +874,36 @@ handle_get_param_request (GstRTSPClient * client, GstRTSPClientState * state) bad_request: { GST_ERROR ("client %p: bad request", client); - send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, state); + send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); return FALSE; } } static gboolean -handle_set_param_request (GstRTSPClient * client, GstRTSPClientState * state) +handle_set_param_request (GstRTSPClient * client, GstRTSPContext * ctx) { GstRTSPResult res; guint8 *data; guint size; - res = gst_rtsp_message_get_body (state->request, &data, &size); + res = gst_rtsp_message_get_body (ctx->request, &data, &size); if (res != GST_RTSP_OK) goto bad_request; if (size == 0) { /* no body, keep-alive request */ - send_generic_response (client, GST_RTSP_STS_OK, state); + send_generic_response (client, GST_RTSP_STS_OK, ctx); } else { /* there is a body, handle the params */ - res = gst_rtsp_params_set (client, state); + res = GST_RTSP_CLIENT_GET_CLASS (client)->params_set (client, ctx); if (res != GST_RTSP_OK) goto bad_request; - send_response (client, state->session, state->response, FALSE); + send_message (client, ctx->session, ctx->response, FALSE); } g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_SET_PARAMETER_REQUEST], - 0, state); + 0, ctx); return TRUE; @@ -710,51 +911,66 @@ handle_set_param_request (GstRTSPClient * client, GstRTSPClientState * state) bad_request: { GST_ERROR ("client %p: bad request", client); - send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, state); + send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); return FALSE; } } static gboolean -handle_pause_request (GstRTSPClient * client, GstRTSPClientState * state) +handle_pause_request (GstRTSPClient * client, GstRTSPContext * ctx) { GstRTSPSession *session; - GstRTSPSessionMedia *media; + GstRTSPClientClass *klass; + GstRTSPSessionMedia *sessmedia; GstRTSPStatusCode code; + GstRTSPState rtspstate; + gchar *path; + gint matched; - if (!(session = state->session)) + if (!(session = ctx->session)) goto no_session; + if (!ctx->uri) + goto no_uri; + + klass = GST_RTSP_CLIENT_GET_CLASS (client); + path = klass->make_path_from_uri (client, ctx->uri); + /* get a handle to the configuration of the media in the session */ - media = gst_rtsp_session_get_media (session, state->uri); - if (!media) + sessmedia = gst_rtsp_session_get_media (session, path, &matched); + if (!sessmedia) goto not_found; - state->sessmedia = media; + if (path[matched] != '\0') + goto no_aggregate; + + g_free (path); + + ctx->sessmedia = sessmedia; + rtspstate = gst_rtsp_session_media_get_rtsp_state (sessmedia); /* the session state must be playing or recording */ - if (media->state != GST_RTSP_STATE_PLAYING && - media->state != GST_RTSP_STATE_RECORDING) + if (rtspstate != GST_RTSP_STATE_PLAYING && + rtspstate != GST_RTSP_STATE_RECORDING) goto invalid_state; /* unlink the all TCP callbacks */ - unlink_session_transports (client, session, media); + unlink_session_transports (client, session, sessmedia); /* then pause sending */ - gst_rtsp_session_media_set_state (media, GST_STATE_PAUSED); + gst_rtsp_session_media_set_state (sessmedia, GST_STATE_PAUSED); /* construct the response now */ code = GST_RTSP_STS_OK; - gst_rtsp_message_init_response (state->response, code, - gst_rtsp_status_as_text (code), state->request); + gst_rtsp_message_init_response (ctx->response, code, + gst_rtsp_status_as_text (code), ctx->request); - send_response (client, session, state->response, FALSE); + send_message (client, session, ctx->response, FALSE); /* the state is now READY */ - media->state = GST_RTSP_STATE_READY; + gst_rtsp_session_media_set_rtsp_state (sessmedia, GST_RTSP_STATE_READY); - g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PAUSE_REQUEST], - 0, state); + g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PAUSE_REQUEST], 0, ctx); return TRUE; @@ -762,58 +978,116 @@ handle_pause_request (GstRTSPClient * client, GstRTSPClientState * state) no_session: { GST_ERROR ("client %p: no seesion", client); - send_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, state); + send_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx); + return FALSE; + } +no_uri: + { + GST_ERROR ("client %p: no uri supplied", client); + send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); return FALSE; } not_found: { GST_ERROR ("client %p: no media for uri", client); - send_generic_response (client, GST_RTSP_STS_NOT_FOUND, state); + send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx); + g_free (path); + return FALSE; + } +no_aggregate: + { + GST_ERROR ("client %p: no aggregate path %s", client, path); + send_generic_response (client, + GST_RTSP_STS_ONLY_AGGREGATE_OPERATION_ALLOWED, ctx); + g_free (path); return FALSE; } invalid_state: { GST_ERROR ("client %p: not PLAYING or RECORDING", client); send_generic_response (client, GST_RTSP_STS_METHOD_NOT_VALID_IN_THIS_STATE, - state); + ctx); return FALSE; } } +/* convert @url and @path to a URL used as a content base for the factory + * located at @path */ +static gchar * +make_base_url (GstRTSPClient * client, GstRTSPUrl * url, gchar * path) +{ + GstRTSPUrl tmp; + gchar *result, *trail; + + /* check for trailing '/' and append one */ + trail = (path[strlen (path) - 1] != '/' ? "/" : ""); + + tmp = *url; + tmp.user = NULL; + tmp.passwd = NULL; + tmp.abspath = g_strdup_printf ("%s%s", path, trail); + tmp.query = NULL; + result = gst_rtsp_url_get_request_uri (&tmp); + g_free (tmp.abspath); + + return result; +} + static gboolean -handle_play_request (GstRTSPClient * client, GstRTSPClientState * state) +handle_play_request (GstRTSPClient * client, GstRTSPContext * ctx) { GstRTSPSession *session; - GstRTSPSessionMedia *media; + GstRTSPClientClass *klass; + GstRTSPSessionMedia *sessmedia; + GstRTSPMedia *media; GstRTSPStatusCode code; + GstRTSPUrl *uri; GString *rtpinfo; guint n_streams, i, infocount; gchar *str; GstRTSPTimeRange *range; GstRTSPResult res; + GstRTSPState rtspstate; + GstRTSPRangeUnit unit = GST_RTSP_RANGE_NPT; + gchar *path; + gint matched; - if (!(session = state->session)) + if (!(session = ctx->session)) goto no_session; + if (!(uri = ctx->uri)) + goto no_uri; + + klass = GST_RTSP_CLIENT_GET_CLASS (client); + path = klass->make_path_from_uri (client, uri); + /* get a handle to the configuration of the media in the session */ - media = gst_rtsp_session_get_media (session, state->uri); - if (!media) + sessmedia = gst_rtsp_session_get_media (session, path, &matched); + if (!sessmedia) goto not_found; - state->sessmedia = media; + if (path[matched] != '\0') + goto no_aggregate; + + ctx->sessmedia = sessmedia; + ctx->media = media = gst_rtsp_session_media_get_media (sessmedia); /* the session state must be playing or ready */ - if (media->state != GST_RTSP_STATE_PLAYING && - media->state != GST_RTSP_STATE_READY) + rtspstate = gst_rtsp_session_media_get_rtsp_state (sessmedia); + if (rtspstate != GST_RTSP_STATE_PLAYING && rtspstate != GST_RTSP_STATE_READY) goto invalid_state; + /* in play we first unsuspend, media could be suspended from SDP or PAUSED */ + if (!gst_rtsp_media_unsuspend (media)) + goto unsuspend_failed; + /* parse the range header if we have one */ - res = - gst_rtsp_message_get_header (state->request, GST_RTSP_HDR_RANGE, &str, 0); + res = gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_RANGE, &str, 0); if (res == GST_RTSP_OK) { if (gst_rtsp_range_parse (str, &range) == GST_RTSP_OK) { /* we have a range, seek to the position */ - gst_rtsp_media_seek (media->media, range); + unit = range->unit; + gst_rtsp_media_seek (media, range); gst_rtsp_range_free (range); } } @@ -821,67 +1095,73 @@ handle_play_request (GstRTSPClient * client, GstRTSPClientState * state) /* grab RTPInfo from the payloaders now */ rtpinfo = g_string_new (""); - n_streams = gst_rtsp_media_n_streams (media->media); + n_streams = gst_rtsp_media_n_streams (media); for (i = 0, infocount = 0; i < n_streams; i++) { GstRTSPStreamTransport *trans; - GstRTSPTransport *tr; - gchar *uristr; + GstRTSPStream *stream; + const GstRTSPTransport *tr; guint rtptime, seq; /* get the transport, if there is no transport configured, skip this stream */ - trans = gst_rtsp_session_media_get_transport (media, i); + trans = gst_rtsp_session_media_get_transport (sessmedia, i); if (trans == NULL) { GST_INFO ("stream %d is not configured", i); continue; } - tr = trans->transport; + tr = gst_rtsp_stream_transport_get_transport (trans); if (tr->lower_transport == GST_RTSP_LOWER_TRANS_TCP) { /* for TCP, link the stream to the TCP connection of the client */ link_transport (client, session, trans); } - if (gst_rtsp_stream_get_rtpinfo (trans->stream, &rtptime, &seq)) { + stream = gst_rtsp_stream_transport_get_stream (trans); + if (gst_rtsp_stream_get_rtpinfo (stream, &rtptime, &seq)) { + const GstRTSPUrl *url; + gchar *url_str; + if (infocount > 0) g_string_append (rtpinfo, ", "); - uristr = gst_rtsp_url_get_request_uri (state->uri); - g_string_append_printf (rtpinfo, "url=%s/stream=%d;seq=%u;rtptime=%u", - uristr, i, seq, rtptime); - g_free (uristr); + url = gst_rtsp_stream_transport_get_url (trans); + url_str = gst_rtsp_url_get_request_uri (url); + g_string_append_printf (rtpinfo, "url=%s;seq=%u;rtptime=%u", + url_str, seq, rtptime); + g_free (url_str); infocount++; } else { GST_WARNING ("RTP-Info cannot be determined for stream %d", i); } } + g_free (path); /* construct the response now */ code = GST_RTSP_STS_OK; - gst_rtsp_message_init_response (state->response, code, - gst_rtsp_status_as_text (code), state->request); + gst_rtsp_message_init_response (ctx->response, code, + gst_rtsp_status_as_text (code), ctx->request); /* add the RTP-Info header */ if (infocount > 0) { str = g_string_free (rtpinfo, FALSE); - gst_rtsp_message_take_header (state->response, GST_RTSP_HDR_RTP_INFO, str); + gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_RTP_INFO, str); } else { g_string_free (rtpinfo, TRUE); } /* add the range */ - str = gst_rtsp_media_get_range_string (media->media, TRUE); - gst_rtsp_message_take_header (state->response, GST_RTSP_HDR_RANGE, str); + str = gst_rtsp_media_get_range_string (media, TRUE, unit); + if (str) + gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_RANGE, str); - send_response (client, session, state->response, FALSE); + send_message (client, session, ctx->response, FALSE); /* start playing after sending the request */ - gst_rtsp_session_media_set_state (media, GST_STATE_PLAYING); + gst_rtsp_session_media_set_state (sessmedia, GST_STATE_PLAYING); - media->state = GST_RTSP_STATE_PLAYING; + gst_rtsp_session_media_set_rtsp_state (sessmedia, GST_RTSP_STATE_PLAYING); - g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PLAY_REQUEST], - 0, state); + g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PLAY_REQUEST], 0, ctx); return TRUE; @@ -889,20 +1169,42 @@ handle_play_request (GstRTSPClient * client, GstRTSPClientState * state) no_session: { GST_ERROR ("client %p: no session", client); - send_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, state); + send_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx); + return FALSE; + } +no_uri: + { + GST_ERROR ("client %p: no uri supplied", client); + send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); return FALSE; } not_found: { GST_ERROR ("client %p: media not found", client); - send_generic_response (client, GST_RTSP_STS_NOT_FOUND, state); + send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx); + return FALSE; + } +no_aggregate: + { + GST_ERROR ("client %p: no aggregate path %s", client, path); + send_generic_response (client, + GST_RTSP_STS_ONLY_AGGREGATE_OPERATION_ALLOWED, ctx); + g_free (path); return FALSE; } invalid_state: { GST_ERROR ("client %p: not PLAYING or READY", client); send_generic_response (client, GST_RTSP_STS_METHOD_NOT_VALID_IN_THIS_STATE, - state); + ctx); + g_free (path); + return FALSE; + } +unsuspend_failed: + { + GST_ERROR ("client %p: unsuspend failed", client); + send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx); + g_free (path); return FALSE; } } @@ -966,11 +1268,11 @@ parse_transport (const char *transport, GstRTSPLowerTrans supported, } static gboolean -handle_blocksize (GstRTSPMedia * media, GstRTSPStream * stream, - GstRTSPMessage * request) +default_configure_client_media (GstRTSPClient * client, GstRTSPMedia * media, + GstRTSPStream * stream, GstRTSPContext * ctx) { + GstRTSPMessage *request = ctx->request; gchar *blocksize_str; - gboolean ret = TRUE; if (gst_rtsp_message_get_header (request, GST_RTSP_HDR_BLOCKSIZE, &blocksize_str, 0) == GST_RTSP_OK) { @@ -978,33 +1280,61 @@ handle_blocksize (GstRTSPMedia * media, GstRTSPStream * stream, gchar *end; blocksize = g_ascii_strtoull (blocksize_str, &end, 10); - if (end == blocksize_str) { - GST_ERROR ("failed to parse blocksize"); - ret = FALSE; - } else { - /* we don't want to change the mtu when this media - * can be shared because it impacts other clients */ - if (gst_rtsp_media_is_shared (media)) - return TRUE; - - if (blocksize > G_MAXUINT) - blocksize = G_MAXUINT; - gst_rtsp_stream_set_mtu (stream, blocksize); - } + if (end == blocksize_str) + goto parse_failed; + + /* we don't want to change the mtu when this media + * can be shared because it impacts other clients */ + if (gst_rtsp_media_is_shared (media)) + goto done; + + if (blocksize > G_MAXUINT) + blocksize = G_MAXUINT; + + gst_rtsp_stream_set_mtu (stream, blocksize); + } +done: + return TRUE; + + /* ERRORS */ +parse_failed: + { + GST_ERROR_OBJECT (client, "failed to parse blocksize"); + send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); + return FALSE; } - return ret; } static gboolean -configure_client_transport (GstRTSPClient * client, GstRTSPClientState * state, - GstRTSPTransport * ct) +default_configure_client_transport (GstRTSPClient * client, + GstRTSPContext * ctx, GstRTSPTransport * ct) { + GstRTSPClientPrivate *priv = client->priv; + /* we have a valid transport now, set the destination of the client. */ if (ct->lower_transport == GST_RTSP_LOWER_TRANS_UDP_MCAST) { - if (ct->destination == NULL || !client->use_client_settings) { + gboolean use_client_settings; + + use_client_settings = + gst_rtsp_auth_check (GST_RTSP_AUTH_CHECK_TRANSPORT_CLIENT_SETTINGS); + + if (ct->destination && use_client_settings) { + GstRTSPAddress *addr; + + addr = gst_rtsp_stream_reserve_address (ctx->stream, ct->destination, + ct->port.min, ct->port.max - ct->port.min + 1, ct->ttl); + + if (addr == NULL) + goto no_address; + + gst_rtsp_address_free (addr); + } else { GstRTSPAddress *addr; + GSocketFamily family; + + family = priv->is_ipv6 ? G_SOCKET_FAMILY_IPV6 : G_SOCKET_FAMILY_IPV4; - addr = gst_rtsp_stream_get_address (state->stream); + addr = gst_rtsp_stream_get_multicast_address (ctx->stream, family); if (addr == NULL) goto no_address; @@ -1013,18 +1343,20 @@ configure_client_transport (GstRTSPClient * client, GstRTSPClientState * state, ct->port.min = addr->port; ct->port.max = addr->port + addr->n_ports - 1; ct->ttl = addr->ttl; + + gst_rtsp_address_free (addr); } } else { GstRTSPUrl *url; - url = gst_rtsp_connection_get_url (client->connection); + url = gst_rtsp_connection_get_url (priv->connection); g_free (ct->destination); ct->destination = g_strdup (url->host); if (ct->lower_transport & GST_RTSP_LOWER_TRANS_TCP) { /* check if the client selected channels for TCP */ if (ct->interleaved.min == -1 || ct->interleaved.max == -1) { - gst_rtsp_session_media_alloc_channels (state->sessmedia, + gst_rtsp_session_media_alloc_channels (ctx->sessmedia, &ct->interleaved); } } @@ -1040,10 +1372,12 @@ no_address: } static GstRTSPTransport * -make_server_transport (GstRTSPClient * client, GstRTSPClientState * state, +make_server_transport (GstRTSPClient * client, GstRTSPContext * ctx, GstRTSPTransport * ct) { GstRTSPTransport *st; + GInetAddress *addr; + GSocketFamily family; /* prepare the server transport */ gst_rtsp_transport_new (&st); @@ -1052,10 +1386,21 @@ make_server_transport (GstRTSPClient * client, GstRTSPClientState * state, st->profile = ct->profile; st->lower_transport = ct->lower_transport; + addr = g_inet_address_new_from_string (ct->destination); + + if (!addr) { + GST_ERROR ("failed to get inet addr from client destination"); + family = G_SOCKET_FAMILY_IPV4; + } else { + family = g_inet_address_get_family (addr); + g_object_unref (addr); + addr = NULL; + } + switch (st->lower_transport) { case GST_RTSP_LOWER_TRANS_UDP: st->client_port = ct->client_port; - st->server_port = state->stream->server_port; + gst_rtsp_stream_get_server_port (ctx->stream, &st->server_port, family); break; case GST_RTSP_LOWER_TRANS_UDP_MCAST: st->port = ct->port; @@ -1068,15 +1413,15 @@ make_server_transport (GstRTSPClient * client, GstRTSPClientState * state, break; } - if (state->stream->session) - g_object_get (state->stream->session, "internal-ssrc", &st->ssrc, NULL); + gst_rtsp_stream_get_ssrc (ctx->stream, &st->ssrc); return st; } static gboolean -handle_setup_request (GstRTSPClient * client, GstRTSPClientState * state) +handle_setup_request (GstRTSPClient * client, GstRTSPContext * ctx) { + GstRTSPClientPrivate *priv = client->priv; GstRTSPResult res; GstRTSPUrl *uri; gchar *transport; @@ -1085,199 +1430,259 @@ handle_setup_request (GstRTSPClient * client, GstRTSPClientState * state) GstRTSPStatusCode code; GstRTSPSession *session; GstRTSPStreamTransport *trans; - gchar *trans_str, *pos; - guint streamid; + gchar *trans_str; GstRTSPSessionMedia *sessmedia; GstRTSPMedia *media; GstRTSPStream *stream; + GstRTSPState rtspstate; + GstRTSPClientClass *klass; + gchar *path, *control; + gint matched; - uri = state->uri; - - /* the uri contains the stream number we added in the SDP config, which is - * always /stream=%d so we need to strip that off - * parse the stream we need to configure, look for the stream in the abspath - * first and then in the query. */ - if (uri->abspath == NULL || !(pos = strstr (uri->abspath, "/stream="))) { - if (uri->query == NULL || !(pos = strstr (uri->query, "/stream="))) - goto bad_request; - } - - /* we can mofify the parsed uri in place */ - *pos = '\0'; + if (!ctx->uri) + goto no_uri; - pos += strlen ("/stream="); - if (sscanf (pos, "%u", &streamid) != 1) - goto bad_request; + uri = ctx->uri; + klass = GST_RTSP_CLIENT_GET_CLASS (client); + path = klass->make_path_from_uri (client, uri); /* parse the transport */ res = - gst_rtsp_message_get_header (state->request, GST_RTSP_HDR_TRANSPORT, + gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_TRANSPORT, &transport, 0); if (res != GST_RTSP_OK) goto no_transport; - gst_rtsp_transport_new (&ct); - - /* our supported transports */ - supported = GST_RTSP_LOWER_TRANS_UDP | - GST_RTSP_LOWER_TRANS_UDP_MCAST | GST_RTSP_LOWER_TRANS_TCP; - - /* parse and find a usable supported transport */ - if (!parse_transport (transport, supported, ct)) - goto unsupported_transports; - /* we create the session after parsing stuff so that we don't make * a session for malformed requests */ - if (client->session_pool == NULL) + if (priv->session_pool == NULL) goto no_pool; - session = state->session; + session = ctx->session; if (session) { g_object_ref (session); /* get a handle to the configuration of the media in the session, this can * return NULL if this is a new url to manage in this session. */ - sessmedia = gst_rtsp_session_get_media (session, uri); + sessmedia = gst_rtsp_session_get_media (session, path, &matched); } else { - /* create a session if this fails we probably reached our session limit or - * something. */ - if (!(session = gst_rtsp_session_pool_create (client->session_pool))) - goto service_unavailable; - - state->session = session; - /* we need a new media configuration in this session */ sessmedia = NULL; } - /* we have no media, find one and manage it */ + /* we have no session media, find one and manage it */ if (sessmedia == NULL) { /* get a handle to the configuration of the media in the session */ - if ((media = find_media (client, state))) { - /* manage the media in our session now */ - sessmedia = gst_rtsp_session_manage_media (session, uri, media); - } + media = find_media (client, ctx, path, &matched); + } else { + if ((media = gst_rtsp_session_media_get_media (sessmedia))) + g_object_ref (media); + else + goto media_not_found; } + /* no media, not found then */ + if (media == NULL) + goto media_not_found_no_reply; - /* if we stil have no media, error */ - if (sessmedia == NULL) - goto not_found; + if (path[matched] == '\0') + goto control_not_found; - state->sessmedia = sessmedia; - state->media = media = sessmedia->media; + /* path is what matched. */ + path[matched] = '\0'; + /* control is remainder */ + control = &path[matched + 1]; - /* now get the stream */ - stream = gst_rtsp_media_get_stream (media, streamid); + /* find the stream now using the control part */ + stream = gst_rtsp_media_find_stream (media, control); if (stream == NULL) - goto not_found; + goto stream_not_found; - state->stream = stream; + /* now we have a uri identifying a valid media and stream */ + ctx->stream = stream; + ctx->media = media; - /* set blocksize on this stream */ - if (!handle_blocksize (media, stream, state->request)) - goto invalid_blocksize; + if (session == NULL) { + /* create a session if this fails we probably reached our session limit or + * something. */ + if (!(session = gst_rtsp_session_pool_create (priv->session_pool))) + goto service_unavailable; - /* update the client transport */ - if (!configure_client_transport (client, state, ct)) - goto unsupported_client_transport; + /* make sure this client is closed when the session is closed */ + client_watch_session (client, session); - /* set in the session media transport */ - trans = gst_rtsp_session_media_set_transport (sessmedia, stream, ct); + /* signal new session */ + g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_NEW_SESSION], 0, + session); - /* configure keepalive for this transport */ + ctx->session = session; + } + + if (sessmedia == NULL) { + /* manage the media in our session now, if not done already */ + sessmedia = gst_rtsp_session_manage_media (session, path, media); + /* if we stil have no media, error */ + if (sessmedia == NULL) + goto sessmedia_unavailable; + } else { + g_object_unref (media); + } + + ctx->sessmedia = sessmedia; + + if (!klass->configure_client_media (client, media, stream, ctx)) + goto configure_media_failed_no_reply; + + gst_rtsp_transport_new (&ct); + + /* our supported transports */ + supported = gst_rtsp_stream_get_protocols (stream); + + /* parse and find a usable supported transport */ + if (!parse_transport (transport, supported, ct)) + goto unsupported_transports; + + /* update the client transport */ + if (!klass->configure_client_transport (client, ctx, ct)) + goto unsupported_client_transport; + + /* set in the session media transport */ + trans = gst_rtsp_session_media_set_transport (sessmedia, stream, ct); + + /* configure the url used to set this transport, this we will use when + * generating the response for the PLAY request */ + gst_rtsp_stream_transport_set_url (trans, uri); + + /* configure keepalive for this transport */ gst_rtsp_stream_transport_set_keepalive (trans, (GstRTSPKeepAliveFunc) do_keepalive, session, NULL); /* create and serialize the server transport */ - st = make_server_transport (client, state, ct); + st = make_server_transport (client, ctx, ct); trans_str = gst_rtsp_transport_as_text (st); gst_rtsp_transport_free (st); /* construct the response now */ code = GST_RTSP_STS_OK; - gst_rtsp_message_init_response (state->response, code, - gst_rtsp_status_as_text (code), state->request); + gst_rtsp_message_init_response (ctx->response, code, + gst_rtsp_status_as_text (code), ctx->request); - gst_rtsp_message_add_header (state->response, GST_RTSP_HDR_TRANSPORT, + gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_TRANSPORT, trans_str); g_free (trans_str); - send_response (client, session, state->response, FALSE); + send_message (client, session, ctx->response, FALSE); /* update the state */ - switch (sessmedia->state) { + rtspstate = gst_rtsp_session_media_get_rtsp_state (sessmedia); + switch (rtspstate) { case GST_RTSP_STATE_PLAYING: case GST_RTSP_STATE_RECORDING: case GST_RTSP_STATE_READY: /* no state change */ break; default: - sessmedia->state = GST_RTSP_STATE_READY; + gst_rtsp_session_media_set_rtsp_state (sessmedia, GST_RTSP_STATE_READY); break; } g_object_unref (session); + g_free (path); - g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_SETUP_REQUEST], - 0, state); + g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_SETUP_REQUEST], 0, ctx); return TRUE; /* ERRORS */ -bad_request: +no_uri: { - GST_ERROR ("client %p: bad request", client); - send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, state); + GST_ERROR ("client %p: no uri", client); + send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); return FALSE; } -not_found: +no_transport: { - GST_ERROR ("client %p: media not found", client); - send_generic_response (client, GST_RTSP_STS_NOT_FOUND, state); - g_object_unref (session); - gst_rtsp_transport_free (ct); + GST_ERROR ("client %p: no transport", client); + send_generic_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT, ctx); + g_free (path); return FALSE; } -invalid_blocksize: +no_pool: { - GST_ERROR ("client %p: invalid blocksize", client); - send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, state); - g_object_unref (session); - gst_rtsp_transport_free (ct); + GST_ERROR ("client %p: no session pool configured", client); + send_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx); + g_free (path); return FALSE; } -unsupported_client_transport: +media_not_found_no_reply: { - GST_ERROR ("client %p: unsupported client transport", client); - send_generic_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT, state); - g_object_unref (session); - gst_rtsp_transport_free (ct); + GST_ERROR ("client %p: media '%s' not found", client, path); + g_free (path); + /* error reply is already sent */ return FALSE; } -no_transport: +media_not_found: { - GST_ERROR ("client %p: no transport", client); - send_generic_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT, state); + GST_ERROR ("client %p: media '%s' not found", client, path); + send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx); + g_free (path); return FALSE; } -unsupported_transports: +control_not_found: { - GST_ERROR ("client %p: unsupported transports", client); - send_generic_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT, state); - gst_rtsp_transport_free (ct); + GST_ERROR ("client %p: no control in path '%s'", client, path); + send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx); + g_object_unref (media); + g_free (path); return FALSE; } -no_pool: +stream_not_found: { - GST_ERROR ("client %p: no session pool configured", client); - send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, state); - gst_rtsp_transport_free (ct); + GST_ERROR ("client %p: stream '%s' not found", client, control); + send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx); + g_object_unref (media); + g_free (path); return FALSE; } service_unavailable: { GST_ERROR ("client %p: can't create session", client); - send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, state); + send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx); + g_object_unref (media); + g_free (path); + return FALSE; + } +sessmedia_unavailable: + { + GST_ERROR ("client %p: can't create session media", client); + send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx); + g_object_unref (media); + g_object_unref (session); + g_free (path); + return FALSE; + } +configure_media_failed_no_reply: + { + GST_ERROR ("client %p: configure_media failed", client); + g_object_unref (session); + g_free (path); + /* error reply is already sent */ + return FALSE; + } +unsupported_transports: + { + GST_ERROR ("client %p: unsupported transports", client); + send_generic_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT, ctx); gst_rtsp_transport_free (ct); + g_object_unref (session); + g_free (path); + return FALSE; + } +unsupported_client_transport: + { + GST_ERROR ("client %p: unsupported client transport", client); + send_generic_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT, ctx); + gst_rtsp_transport_free (ct); + g_object_unref (session); + g_free (path); return FALSE; } } @@ -1285,6 +1690,7 @@ service_unavailable: static GstSDPMessage * create_sdp (GstRTSPClient * client, GstRTSPMedia * media) { + GstRTSPClientPrivate *priv = client->priv; GstSDPMessage *sdp; GstSDPInfo info; const gchar *proto; @@ -1294,13 +1700,13 @@ create_sdp (GstRTSPClient * client, GstRTSPMedia * media) /* some standard things first */ gst_sdp_message_set_version (sdp, "0"); - if (client->is_ipv6) + if (priv->is_ipv6) proto = "IP6"; else proto = "IP4"; gst_sdp_message_set_origin (sdp, "-", "1188340656180883", "1", "IN", proto, - client->server_ip); + priv->server_ip); gst_sdp_message_set_session_name (sdp, "Session streamed with GStreamer"); gst_sdp_message_set_information (sdp, "rtsp-server"); @@ -1309,22 +1715,19 @@ create_sdp (GstRTSPClient * client, GstRTSPMedia * media) gst_sdp_message_add_attribute (sdp, "type", "broadcast"); gst_sdp_message_add_attribute (sdp, "control", "*"); - info.server_proto = proto; - info.server_ip = g_strdup (client->server_ip); + info.is_ipv6 = priv->is_ipv6; + info.server_ip = priv->server_ip; /* create an SDP for the media object */ if (!gst_rtsp_sdp_from_media (sdp, &info, media)) goto no_sdp; - g_free (info.server_ip); - return sdp; /* ERRORS */ no_sdp: { GST_ERROR ("client %p: could not create SDP", client); - g_free (info.server_ip); gst_sdp_message_free (sdp); return NULL; } @@ -1332,24 +1735,28 @@ no_sdp: /* for the describe we must generate an SDP */ static gboolean -handle_describe_request (GstRTSPClient * client, GstRTSPClientState * state) +handle_describe_request (GstRTSPClient * client, GstRTSPContext * ctx) { + GstRTSPClientPrivate *priv = client->priv; GstRTSPResult res; GstSDPMessage *sdp; - guint i, str_len; - gchar *str, *content_base; + guint i; + gchar *path, *str; GstRTSPMedia *media; GstRTSPClientClass *klass; klass = GST_RTSP_CLIENT_GET_CLASS (client); + if (!ctx->uri) + goto no_uri; + /* check what kind of format is accepted, we don't really do anything with it * and always return SDP for now. */ for (i = 0; i++;) { gchar *accept; res = - gst_rtsp_message_get_header (state->request, GST_RTSP_HDR_ACCEPT, + gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_ACCEPT, &accept, i); if (res == GST_RTSP_ENOTIMPL) break; @@ -1358,73 +1765,87 @@ handle_describe_request (GstRTSPClient * client, GstRTSPClientState * state) break; } + if (!priv->mount_points) + goto no_mount_points; + + if (!(path = gst_rtsp_mount_points_make_path (priv->mount_points, ctx->uri))) + goto no_path; + /* find the media object for the uri */ - if (!(media = find_media (client, state))) + if (!(media = find_media (client, ctx, path, NULL))) goto no_media; /* create an SDP for the media object on this client */ if (!(sdp = klass->create_sdp (client, media))) goto no_sdp; + /* we suspend after the describe */ + gst_rtsp_media_suspend (media); g_object_unref (media); - gst_rtsp_message_init_response (state->response, GST_RTSP_STS_OK, - gst_rtsp_status_as_text (GST_RTSP_STS_OK), state->request); + gst_rtsp_message_init_response (ctx->response, GST_RTSP_STS_OK, + gst_rtsp_status_as_text (GST_RTSP_STS_OK), ctx->request); - gst_rtsp_message_add_header (state->response, GST_RTSP_HDR_CONTENT_TYPE, + gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_CONTENT_TYPE, "application/sdp"); /* content base for some clients that might screw up creating the setup uri */ - str = gst_rtsp_url_get_request_uri (state->uri); - str_len = strlen (str); + str = make_base_url (client, ctx->uri, path); + g_free (path); - /* check for trailing '/' and append one */ - if (str[str_len - 1] != '/') { - content_base = g_malloc (str_len + 2); - memcpy (content_base, str, str_len); - content_base[str_len] = '/'; - content_base[str_len + 1] = '\0'; - g_free (str); - } else { - content_base = str; - } - - GST_INFO ("adding content-base: %s", content_base); - - gst_rtsp_message_add_header (state->response, GST_RTSP_HDR_CONTENT_BASE, - content_base); - g_free (content_base); + GST_INFO ("adding content-base: %s", str); + gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_CONTENT_BASE, str); /* add SDP to the response body */ str = gst_sdp_message_as_text (sdp); - gst_rtsp_message_take_body (state->response, (guint8 *) str, strlen (str)); + gst_rtsp_message_take_body (ctx->response, (guint8 *) str, strlen (str)); gst_sdp_message_free (sdp); - send_response (client, state->session, state->response, FALSE); + send_message (client, ctx->session, ctx->response, FALSE); g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_DESCRIBE_REQUEST], - 0, state); + 0, ctx); return TRUE; /* ERRORS */ +no_uri: + { + GST_ERROR ("client %p: no uri", client); + send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); + return FALSE; + } +no_mount_points: + { + GST_ERROR ("client %p: no mount points configured", client); + send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx); + return FALSE; + } +no_path: + { + GST_ERROR ("client %p: can't find path for url", client); + send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx); + return FALSE; + } no_media: { GST_ERROR ("client %p: no media", client); + g_free (path); /* error reply is already sent */ return FALSE; } no_sdp: { GST_ERROR ("client %p: can't create SDP", client); - send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, state); + send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx); + g_free (path); g_object_unref (media); return FALSE; } } static gboolean -handle_options_request (GstRTSPClient * client, GstRTSPClientState * state) +handle_options_request (GstRTSPClient * client, GstRTSPContext * ctx) { GstRTSPMethod options; gchar *str; @@ -1438,16 +1859,16 @@ handle_options_request (GstRTSPClient * client, GstRTSPClientState * state) str = gst_rtsp_options_as_text (options); - gst_rtsp_message_init_response (state->response, GST_RTSP_STS_OK, - gst_rtsp_status_as_text (GST_RTSP_STS_OK), state->request); + gst_rtsp_message_init_response (ctx->response, GST_RTSP_STS_OK, + gst_rtsp_status_as_text (GST_RTSP_STS_OK), ctx->request); - gst_rtsp_message_add_header (state->response, GST_RTSP_HDR_PUBLIC, str); + gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_PUBLIC, str); g_free (str); - send_response (client, state->session, state->response, FALSE); + send_message (client, ctx->session, ctx->response, FALSE); g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_OPTIONS_REQUEST], - 0, state); + 0, ctx); return TRUE; } @@ -1482,13 +1903,15 @@ sanitize_uri (GstRTSPUrl * uri) static void client_session_finalized (GstRTSPClient * client, GstRTSPSession * session) { + GstRTSPClientPrivate *priv = client->priv; + GST_INFO ("client %p: session %p finished", client, session); /* unlink all media managed in this session */ client_unlink_session (client, session); /* remove the session */ - if (!(client->sessions = g_list_remove (client->sessions, session))) { + if (!(priv->sessions = g_list_remove (priv->sessions, session))) { GST_INFO ("client %p: all sessions finalized, close the connection", client); close_connection (client); @@ -1496,160 +1919,262 @@ client_session_finalized (GstRTSPClient * client, GstRTSPSession * session) } static void -client_watch_session (GstRTSPClient * client, GstRTSPSession * session) -{ - GList *walk; - - for (walk = client->sessions; walk; walk = g_list_next (walk)) { - GstRTSPSession *msession = (GstRTSPSession *) walk->data; - - /* we already know about this session */ - if (msession == session) - return; - } - - GST_INFO ("watching session %p", session); - - g_object_weak_ref (G_OBJECT (session), (GWeakNotify) client_session_finalized, - client); - client->sessions = g_list_prepend (client->sessions, session); - - g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_NEW_SESSION], 0, - session); -} - -static void handle_request (GstRTSPClient * client, GstRTSPMessage * request) { + GstRTSPClientPrivate *priv = client->priv; GstRTSPMethod method; const gchar *uristr; - GstRTSPUrl *uri; + GstRTSPUrl *uri = NULL; GstRTSPVersion version; GstRTSPResult res; - GstRTSPSession *session; - GstRTSPClientState state = { NULL }; + GstRTSPSession *session = NULL; + GstRTSPContext sctx = { NULL }, *ctx; GstRTSPMessage response = { 0 }; gchar *sessid; - state.request = request; - state.response = &response; + if (!(ctx = gst_rtsp_context_get_current ())) { + ctx = &sctx; + ctx->auth = priv->auth; + gst_rtsp_context_push_current (ctx); + } + + ctx->conn = priv->connection; + ctx->client = client; + ctx->request = request; + ctx->response = &response; if (gst_debug_category_get_threshold (rtsp_client_debug) >= GST_LEVEL_LOG) { gst_rtsp_message_dump (request); } - GST_INFO ("client %p: received a request", client); - gst_rtsp_message_parse_request (request, &method, &uristr, &version); - if (version != GST_RTSP_VERSION_1_0) { - /* we can only handle 1.0 requests */ - send_generic_response (client, GST_RTSP_STS_RTSP_VERSION_NOT_SUPPORTED, - &state); - return; - } - state.method = method; + GST_INFO ("client %p: received a request %s %s %s", client, + gst_rtsp_method_as_text (method), uristr, + gst_rtsp_version_as_text (version)); + + /* we can only handle 1.0 requests */ + if (version != GST_RTSP_VERSION_1_0) + goto not_supported; + + ctx->method = method; /* we always try to parse the url first */ - if (gst_rtsp_url_parse (uristr, &uri) != GST_RTSP_OK) { - send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, &state); - return; - } + if (strcmp (uristr, "*") == 0) { + /* special case where we have * as uri, keep uri = NULL */ + } else if (gst_rtsp_url_parse (uristr, &uri) != GST_RTSP_OK) { + /* check if the uristr is an absolute path <=> scheme and host information + * is missing */ + gchar *scheme; + + scheme = g_uri_parse_scheme (uristr); + if (scheme == NULL && g_str_has_prefix (uristr, "/")) { + gchar *absolute_uristr = NULL; + + GST_WARNING_OBJECT (client, "request doesn't contain absolute url"); + if (priv->server_ip == NULL) { + GST_WARNING_OBJECT (client, "host information missing"); + goto bad_request; + } - /* sanitize the uri */ - sanitize_uri (uri); - state.uri = uri; + absolute_uristr = + g_strdup_printf ("rtsp://%s%s", priv->server_ip, uristr); + + GST_DEBUG_OBJECT (client, "absolute url: %s", absolute_uristr); + if (gst_rtsp_url_parse (absolute_uristr, &uri) != GST_RTSP_OK) { + g_free (absolute_uristr); + goto bad_request; + } + g_free (absolute_uristr); + } else { + g_free (scheme); + goto bad_request; + } + } /* get the session if there is any */ res = gst_rtsp_message_get_header (request, GST_RTSP_HDR_SESSION, &sessid, 0); if (res == GST_RTSP_OK) { - if (client->session_pool == NULL) + if (priv->session_pool == NULL) goto no_pool; /* we had a session in the request, find it again */ - if (!(session = gst_rtsp_session_pool_find (client->session_pool, sessid))) + if (!(session = gst_rtsp_session_pool_find (priv->session_pool, sessid))) goto session_not_found; /* we add the session to the client list of watched sessions. When a session * disappears because it times out, we will be notified. If all sessions are * gone, we will close the connection */ client_watch_session (client, session); - } else - session = NULL; + } - state.session = session; + /* sanitize the uri */ + if (uri) + sanitize_uri (uri); + ctx->uri = uri; + ctx->session = session; - if (client->auth) { - if (!gst_rtsp_auth_check (client->auth, client, 0, &state)) - goto not_authorized; - } + if (!gst_rtsp_auth_check (GST_RTSP_AUTH_CHECK_URL)) + goto not_authorized; /* now see what is asked and dispatch to a dedicated handler */ switch (method) { case GST_RTSP_OPTIONS: - handle_options_request (client, &state); + handle_options_request (client, ctx); break; case GST_RTSP_DESCRIBE: - handle_describe_request (client, &state); + handle_describe_request (client, ctx); break; case GST_RTSP_SETUP: - handle_setup_request (client, &state); + handle_setup_request (client, ctx); break; case GST_RTSP_PLAY: - handle_play_request (client, &state); + handle_play_request (client, ctx); break; case GST_RTSP_PAUSE: - handle_pause_request (client, &state); + handle_pause_request (client, ctx); break; case GST_RTSP_TEARDOWN: - handle_teardown_request (client, &state); + handle_teardown_request (client, ctx); break; case GST_RTSP_SET_PARAMETER: - handle_set_param_request (client, &state); + handle_set_param_request (client, ctx); break; case GST_RTSP_GET_PARAMETER: - handle_get_param_request (client, &state); + handle_get_param_request (client, ctx); break; case GST_RTSP_ANNOUNCE: case GST_RTSP_RECORD: case GST_RTSP_REDIRECT: - send_generic_response (client, GST_RTSP_STS_NOT_IMPLEMENTED, &state); - break; + goto not_implemented; case GST_RTSP_INVALID: default: - send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, &state); - break; + goto bad_request; } + +done: + if (ctx == &sctx) + gst_rtsp_context_pop_current (ctx); if (session) g_object_unref (session); - - gst_rtsp_url_free (uri); + if (uri) + gst_rtsp_url_free (uri); return; /* ERRORS */ +not_supported: + { + GST_ERROR ("client %p: version %d not supported", client, version); + send_generic_response (client, GST_RTSP_STS_RTSP_VERSION_NOT_SUPPORTED, + ctx); + goto done; + } +bad_request: + { + GST_ERROR ("client %p: bad request", client); + send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx); + goto done; + } no_pool: { GST_ERROR ("client %p: no pool configured", client); - send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, &state); - return; + send_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx); + goto done; } session_not_found: { GST_ERROR ("client %p: session not found", client); - send_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, &state); - return; + send_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx); + goto done; } not_authorized: { GST_ERROR ("client %p: not allowed", client); - handle_unauthorized_request (client, client->auth, &state); - return; + /* error reply is already sent */ + goto done; + } +not_implemented: + { + GST_ERROR ("client %p: method %d not implemented", client, method); + send_generic_response (client, GST_RTSP_STS_NOT_IMPLEMENTED, ctx); + goto done; + } +} + + +static void +handle_response (GstRTSPClient * client, GstRTSPMessage * response) +{ + GstRTSPClientPrivate *priv = client->priv; + GstRTSPResult res; + GstRTSPSession *session = NULL; + GstRTSPContext sctx = { NULL }, *ctx; + gchar *sessid; + + if (!(ctx = gst_rtsp_context_get_current ())) { + ctx = &sctx; + ctx->auth = priv->auth; + gst_rtsp_context_push_current (ctx); + } + + ctx->conn = priv->connection; + ctx->client = client; + ctx->request = NULL; + ctx->uri = NULL; + ctx->method = GST_RTSP_INVALID; + ctx->response = response; + + if (gst_debug_category_get_threshold (rtsp_client_debug) >= GST_LEVEL_LOG) { + gst_rtsp_message_dump (response); + } + + GST_INFO ("client %p: received a response", client); + + /* get the session if there is any */ + res = + gst_rtsp_message_get_header (response, GST_RTSP_HDR_SESSION, &sessid, 0); + if (res == GST_RTSP_OK) { + if (priv->session_pool == NULL) + goto no_pool; + + /* we had a session in the request, find it again */ + if (!(session = gst_rtsp_session_pool_find (priv->session_pool, sessid))) + goto session_not_found; + + /* we add the session to the client list of watched sessions. When a session + * disappears because it times out, we will be notified. If all sessions are + * gone, we will close the connection */ + client_watch_session (client, session); + } + + ctx->session = session; + + g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_HANDLE_RESPONSE], + 0, ctx); + +done: + if (ctx == &sctx) + gst_rtsp_context_pop_current (ctx); + if (session) + g_object_unref (session); + return; + +no_pool: + { + GST_ERROR ("client %p: no pool configured", client); + goto done; + } +session_not_found: + { + GST_ERROR ("client %p: session not found", client); + goto done; } } static void handle_data (GstRTSPClient * client, GstRTSPMessage * message) { + GstRTSPClientPrivate *priv = client->priv; GstRTSPResult res; guint8 channel; GList *walk; @@ -1668,16 +2193,15 @@ handle_data (GstRTSPClient * client, GstRTSPMessage * message) buffer = gst_buffer_new_wrapped (data, size); handled = FALSE; - for (walk = client->transports; walk; walk = g_list_next (walk)) { + for (walk = priv->transports; walk; walk = g_list_next (walk)) { GstRTSPStreamTransport *trans; GstRTSPStream *stream; - GstRTSPTransport *tr; + const GstRTSPTransport *tr; trans = walk->data; - /* we only add clients with a transport to the list */ - tr = trans->transport; - stream = trans->stream; + tr = gst_rtsp_stream_transport_get_transport (trans); + stream = gst_rtsp_stream_transport_get_stream (trans); /* check for TCP transport */ if (tr->lower_transport == GST_RTSP_LOWER_TRANS_TCP) { @@ -1711,16 +2235,19 @@ gst_rtsp_client_set_session_pool (GstRTSPClient * client, GstRTSPSessionPool * pool) { GstRTSPSessionPool *old; + GstRTSPClientPrivate *priv; g_return_if_fail (GST_IS_RTSP_CLIENT (client)); + priv = client->priv; + if (pool) g_object_ref (pool); - g_mutex_lock (&client->lock); - old = client->session_pool; - client->session_pool = pool; - g_mutex_unlock (&client->lock); + g_mutex_lock (&priv->lock); + old = priv->session_pool; + priv->session_pool = pool; + g_mutex_unlock (&priv->lock); if (old) g_object_unref (old); @@ -1737,14 +2264,17 @@ gst_rtsp_client_set_session_pool (GstRTSPClient * client, GstRTSPSessionPool * gst_rtsp_client_get_session_pool (GstRTSPClient * client) { + GstRTSPClientPrivate *priv; GstRTSPSessionPool *result; g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), NULL); - g_mutex_lock (&client->lock); - if ((result = client->session_pool)) + priv = client->priv; + + g_mutex_lock (&priv->lock); + if ((result = priv->session_pool)) g_object_ref (result); - g_mutex_unlock (&client->lock); + g_mutex_unlock (&priv->lock); return result; } @@ -1762,17 +2292,20 @@ void gst_rtsp_client_set_mount_points (GstRTSPClient * client, GstRTSPMountPoints * mounts) { + GstRTSPClientPrivate *priv; GstRTSPMountPoints *old; g_return_if_fail (GST_IS_RTSP_CLIENT (client)); + priv = client->priv; + if (mounts) g_object_ref (mounts); - g_mutex_lock (&client->lock); - old = client->mount_points; - client->mount_points = mounts; - g_mutex_unlock (&client->lock); + g_mutex_lock (&priv->lock); + old = priv->mount_points; + priv->mount_points = mounts; + g_mutex_unlock (&priv->lock); if (old) g_object_unref (old); @@ -1789,121 +2322,277 @@ gst_rtsp_client_set_mount_points (GstRTSPClient * client, GstRTSPMountPoints * gst_rtsp_client_get_mount_points (GstRTSPClient * client) { + GstRTSPClientPrivate *priv; GstRTSPMountPoints *result; g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), NULL); - g_mutex_lock (&client->lock); - if ((result = client->mount_points)) + priv = client->priv; + + g_mutex_lock (&priv->lock); + if ((result = priv->mount_points)) g_object_ref (result); - g_mutex_unlock (&client->lock); + g_mutex_unlock (&priv->lock); return result; } /** - * gst_rtsp_client_set_use_client_settings: + * gst_rtsp_client_set_auth: * @client: a #GstRTSPClient - * @use_client_settings: whether to use client settings for multicast + * @auth: a #GstRTSPAuth * - * Use client transport settings (destination and ttl) for multicast. - * When @use_client_settings is %FALSE, the server settings will be - * used. + * configure @auth to be used as the authentication manager of @client. */ void -gst_rtsp_client_set_use_client_settings (GstRTSPClient * client, - gboolean use_client_settings) +gst_rtsp_client_set_auth (GstRTSPClient * client, GstRTSPAuth * auth) { + GstRTSPClientPrivate *priv; + GstRTSPAuth *old; + g_return_if_fail (GST_IS_RTSP_CLIENT (client)); - g_mutex_lock (&client->lock); - client->use_client_settings = use_client_settings; - g_mutex_unlock (&client->lock); + priv = client->priv; + + if (auth) + g_object_ref (auth); + + g_mutex_lock (&priv->lock); + old = priv->auth; + priv->auth = auth; + g_mutex_unlock (&priv->lock); + + if (old) + g_object_unref (old); } + /** - * gst_rtsp_client_get_use_client_settings: + * gst_rtsp_client_get_auth: * @client: a #GstRTSPClient * - * Check if client transport settings (destination and ttl) for multicast - * will be used. + * Get the #GstRTSPAuth used as the authentication manager of @client. + * + * Returns: (transfer full): the #GstRTSPAuth of @client. g_object_unref() after + * usage. */ -gboolean -gst_rtsp_client_get_use_client_settings (GstRTSPClient * client) +GstRTSPAuth * +gst_rtsp_client_get_auth (GstRTSPClient * client) { - gboolean res; + GstRTSPClientPrivate *priv; + GstRTSPAuth *result; - g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), FALSE); + g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), NULL); - g_mutex_lock (&client->lock); - res = client->use_client_settings; - g_mutex_unlock (&client->lock); + priv = client->priv; - return res; + g_mutex_lock (&priv->lock); + if ((result = priv->auth)) + g_object_ref (result); + g_mutex_unlock (&priv->lock); + + return result; } /** - * gst_rtsp_client_set_auth: + * gst_rtsp_client_set_thread_pool: * @client: a #GstRTSPClient - * @auth: a #GstRTSPAuth + * @pool: a #GstRTSPThreadPool * - * configure @auth to be used as the authentication manager of @client. + * configure @pool to be used as the thread pool of @client. */ void -gst_rtsp_client_set_auth (GstRTSPClient * client, GstRTSPAuth * auth) +gst_rtsp_client_set_thread_pool (GstRTSPClient * client, + GstRTSPThreadPool * pool) { - GstRTSPAuth *old; + GstRTSPClientPrivate *priv; + GstRTSPThreadPool *old; g_return_if_fail (GST_IS_RTSP_CLIENT (client)); - if (auth) - g_object_ref (auth); + priv = client->priv; - g_mutex_lock (&client->lock); - old = client->auth; - client->auth = auth; - g_mutex_unlock (&client->lock); + if (pool) + g_object_ref (pool); + + g_mutex_lock (&priv->lock); + old = priv->thread_pool; + priv->thread_pool = pool; + g_mutex_unlock (&priv->lock); if (old) g_object_unref (old); } - /** - * gst_rtsp_client_get_auth: + * gst_rtsp_client_get_thread_pool: * @client: a #GstRTSPClient * - * Get the #GstRTSPAuth used as the authentication manager of @client. + * Get the #GstRTSPThreadPool used as the thread pool of @client. * - * Returns: (transfer full): the #GstRTSPAuth of @client. g_object_unref() after + * Returns: (transfer full): the #GstRTSPThreadPool of @client. g_object_unref() after * usage. */ -GstRTSPAuth * -gst_rtsp_client_get_auth (GstRTSPClient * client) +GstRTSPThreadPool * +gst_rtsp_client_get_thread_pool (GstRTSPClient * client) { - GstRTSPAuth *result; + GstRTSPClientPrivate *priv; + GstRTSPThreadPool *result; g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), NULL); - g_mutex_lock (&client->lock); - if ((result = client->auth)) + priv = client->priv; + + g_mutex_lock (&priv->lock); + if ((result = priv->thread_pool)) g_object_ref (result); - g_mutex_unlock (&client->lock); + g_mutex_unlock (&priv->lock); return result; } -static GstRTSPResult -message_received (GstRTSPWatch * watch, GstRTSPMessage * message, - gpointer user_data) +/** + * gst_rtsp_client_set_connection: + * @client: a #GstRTSPClient + * @conn: (transfer full): a #GstRTSPConnection + * + * Set the #GstRTSPConnection of @client. This function takes ownership of + * @conn. + * + * Returns: %TRUE on success. + */ +gboolean +gst_rtsp_client_set_connection (GstRTSPClient * client, + GstRTSPConnection * conn) { - GstRTSPClient *client = GST_RTSP_CLIENT (user_data); + GstRTSPClientPrivate *priv; + GSocket *read_socket; + GSocketAddress *address; + GstRTSPUrl *url; + GError *error = NULL; + + g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), FALSE); + g_return_val_if_fail (conn != NULL, FALSE); + + priv = client->priv; + + read_socket = gst_rtsp_connection_get_read_socket (conn); + + if (!(address = g_socket_get_local_address (read_socket, &error))) + goto no_address; + + g_free (priv->server_ip); + /* keep the original ip that the client connected to */ + if (G_IS_INET_SOCKET_ADDRESS (address)) { + GInetAddress *iaddr; + + iaddr = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (address)); + + /* socket might be ipv6 but adress still ipv4 */ + priv->is_ipv6 = g_inet_address_get_family (iaddr) == G_SOCKET_FAMILY_IPV6; + priv->server_ip = g_inet_address_to_string (iaddr); + g_object_unref (address); + } else { + priv->is_ipv6 = g_socket_get_family (read_socket) == G_SOCKET_FAMILY_IPV6; + priv->server_ip = g_strdup ("unknown"); + } + + GST_INFO ("client %p connected to server ip %s, ipv6 = %d", client, + priv->server_ip, priv->is_ipv6); + + url = gst_rtsp_connection_get_url (conn); + GST_INFO ("added new client %p ip %s:%d", client, url->host, url->port); + + priv->connection = conn; + + return TRUE; + + /* ERRORS */ +no_address: + { + GST_ERROR ("could not get local address %s", error->message); + g_error_free (error); + return FALSE; + } +} + +/** + * gst_rtsp_client_get_connection: + * @client: a #GstRTSPClient + * + * Get the #GstRTSPConnection of @client. + * + * Returns: (transfer none): the #GstRTSPConnection of @client. + * The connection object returned remains valid until the client is freed. + */ +GstRTSPConnection * +gst_rtsp_client_get_connection (GstRTSPClient * client) +{ + g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), NULL); + + return client->priv->connection; +} + +/** + * gst_rtsp_client_set_send_func: + * @client: a #GstRTSPClient + * @func: a #GstRTSPClientSendFunc + * @user_data: user data passed to @func + * @notify: called when @user_data is no longer in use + * + * Set @func as the callback that will be called when a new message needs to be + * sent to the client. @user_data is passed to @func and @notify is called when + * @user_data is no longer in use. + * + * By default, the client will send the messages on the #GstRTSPConnection that + * was configured with gst_rtsp_client_attach() was called. + */ +void +gst_rtsp_client_set_send_func (GstRTSPClient * client, + GstRTSPClientSendFunc func, gpointer user_data, GDestroyNotify notify) +{ + GstRTSPClientPrivate *priv; + GDestroyNotify old_notify; + gpointer old_data; + + g_return_if_fail (GST_IS_RTSP_CLIENT (client)); + + priv = client->priv; + + g_mutex_lock (&priv->send_lock); + priv->send_func = func; + old_notify = priv->send_notify; + old_data = priv->send_data; + priv->send_notify = notify; + priv->send_data = user_data; + g_mutex_unlock (&priv->send_lock); + + if (old_notify) + old_notify (old_data); +} + +/** + * gst_rtsp_client_handle_message: + * @client: a #GstRTSPClient + * @message: an #GstRTSPMessage + * + * Let the client handle @message. + * + * Returns: a #GstRTSPResult. + */ +GstRTSPResult +gst_rtsp_client_handle_message (GstRTSPClient * client, + GstRTSPMessage * message) +{ + g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), GST_RTSP_EINVAL); + g_return_val_if_fail (message != NULL, GST_RTSP_EINVAL); switch (message->type) { case GST_RTSP_MESSAGE_REQUEST: handle_request (client, message); break; case GST_RTSP_MESSAGE_RESPONSE: + handle_response (client, message); break; case GST_RTSP_MESSAGE_DATA: handle_data (client, message); @@ -1914,14 +2603,56 @@ message_received (GstRTSPWatch * watch, GstRTSPMessage * message, return GST_RTSP_OK; } +/** + * gst_rtsp_client_send_message: + * @client: a #GstRTSPClient + * @session: a #GstRTSPSession to send the message to or %NULL + * @message: The #GstRTSPMessage to send + * + * Send a message message to the remote end. @message must be a + * #GST_RTSP_MESSAGE_REQUEST or a #GST_RTSP_MESSAGE_RESPONSE. + */ +GstRTSPResult +gst_rtsp_client_send_message (GstRTSPClient * client, GstRTSPSession * session, + GstRTSPMessage * message) +{ + g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), GST_RTSP_EINVAL); + g_return_val_if_fail (message != NULL, GST_RTSP_EINVAL); + g_return_val_if_fail (message->type == GST_RTSP_MESSAGE_REQUEST || + message->type == GST_RTSP_MESSAGE_RESPONSE, GST_RTSP_EINVAL); + + send_message (client, session, message, FALSE); + + return GST_RTSP_OK; +} + +static GstRTSPResult +do_send_message (GstRTSPClient * client, GstRTSPMessage * message, + gboolean close, gpointer user_data) +{ + GstRTSPClientPrivate *priv = client->priv; + + /* send the response and store the seq number so we can wait until it's + * written to the client to close the connection */ + return gst_rtsp_watch_send_message (priv->watch, message, close ? + &priv->close_seq : NULL); +} + +static GstRTSPResult +message_received (GstRTSPWatch * watch, GstRTSPMessage * message, + gpointer user_data) +{ + return gst_rtsp_client_handle_message (GST_RTSP_CLIENT (user_data), message); +} + static GstRTSPResult message_sent (GstRTSPWatch * watch, guint cseq, gpointer user_data) { - GstRTSPClient *client; + GstRTSPClient *client = GST_RTSP_CLIENT (user_data); + GstRTSPClientPrivate *priv = client->priv; - client = GST_RTSP_CLIENT (user_data); - if (client->close_response_seq && client->close_response_seq == cseq) { - client->close_response_seq = 0; + if (priv->close_seq && priv->close_seq == cseq) { + priv->close_seq = 0; close_connection (client); } @@ -1932,17 +2663,20 @@ static GstRTSPResult closed (GstRTSPWatch * watch, gpointer user_data) { GstRTSPClient *client = GST_RTSP_CLIENT (user_data); + GstRTSPClientPrivate *priv = client->priv; const gchar *tunnelid; GST_INFO ("client %p: connection closed", client); - if ((tunnelid = gst_rtsp_connection_get_tunnelid (client->connection))) { + if ((tunnelid = gst_rtsp_connection_get_tunnelid (priv->connection))) { g_mutex_lock (&tunnels_lock); /* remove from tunnelids */ g_hash_table_remove (tunnels, tunnelid); g_mutex_unlock (&tunnels_lock); } + gst_rtsp_client_set_send_func (client, NULL, NULL, NULL); + return GST_RTSP_OK; } @@ -1968,8 +2702,8 @@ error_full (GstRTSPWatch * watch, GstRTSPResult result, str = gst_rtsp_strresult (result); GST_INFO - ("client %p: received an error %s when handling message %p with id %d", - client, str, message, id); + ("client %p: error when handling message %p with id %d: %s", + client, message, id, str); g_free (str); return GST_RTSP_OK; @@ -1978,10 +2712,11 @@ error_full (GstRTSPWatch * watch, GstRTSPResult result, static gboolean remember_tunnel (GstRTSPClient * client) { + GstRTSPClientPrivate *priv = client->priv; const gchar *tunnelid; /* store client in the pending tunnels */ - tunnelid = gst_rtsp_connection_get_tunnelid (client->connection); + tunnelid = gst_rtsp_connection_get_tunnelid (priv->connection); if (tunnelid == NULL) goto no_tunnelid; @@ -2015,12 +2750,11 @@ tunnel_existed: static GstRTSPStatusCode tunnel_start (GstRTSPWatch * watch, gpointer user_data) { - GstRTSPClient *client; - - client = GST_RTSP_CLIENT (user_data); + GstRTSPClient *client = GST_RTSP_CLIENT (user_data); + GstRTSPClientPrivate *priv = client->priv; GST_INFO ("client %p: tunnel start (connection %p)", client, - client->connection); + priv->connection); if (!remember_tunnel (client)) goto tunnel_error; @@ -2038,12 +2772,11 @@ tunnel_error: static GstRTSPResult tunnel_lost (GstRTSPWatch * watch, gpointer user_data) { - GstRTSPClient *client; - - client = GST_RTSP_CLIENT (user_data); + GstRTSPClient *client = GST_RTSP_CLIENT (user_data); + GstRTSPClientPrivate *priv = client->priv; GST_WARNING ("client %p: tunnel lost (connection %p)", client, - client->connection); + priv->connection); /* ignore error, it'll only be a problem when the client does a POST again */ remember_tunnel (client); @@ -2056,12 +2789,14 @@ tunnel_complete (GstRTSPWatch * watch, gpointer user_data) { const gchar *tunnelid; GstRTSPClient *client = GST_RTSP_CLIENT (user_data); + GstRTSPClientPrivate *priv = client->priv; GstRTSPClient *oclient; + GstRTSPClientPrivate *opriv; GST_INFO ("client %p: tunnel complete", client); /* find previous tunnel */ - tunnelid = gst_rtsp_connection_get_tunnelid (client->connection); + tunnelid = gst_rtsp_connection_get_tunnelid (priv->connection); if (tunnelid == NULL) goto no_tunnelid; @@ -2074,16 +2809,18 @@ tunnel_complete (GstRTSPWatch * watch, gpointer user_data) g_object_ref (oclient); g_hash_table_remove (tunnels, tunnelid); - if (oclient->watch == NULL) + opriv = oclient->priv; + + if (opriv->watch == NULL) goto tunnel_closed; g_mutex_unlock (&tunnels_lock); GST_INFO ("client %p: found tunnel %p (old %p, new %p)", client, oclient, - oclient->connection, client->connection); + opriv->connection, priv->connection); /* merge the tunnels into the first client */ - gst_rtsp_connection_do_tunnel (oclient->connection, client->connection); - gst_rtsp_watch_reset (oclient->watch); + gst_rtsp_connection_do_tunnel (opriv->connection, priv->connection); + gst_rtsp_watch_reset (opriv->watch); g_object_unref (oclient); return GST_RTSP_OK; @@ -2123,165 +2860,119 @@ static GstRTSPWatchFuncs watch_funcs = { static void client_watch_notify (GstRTSPClient * client) { + GstRTSPClientPrivate *priv = client->priv; + GST_INFO ("client %p: watch destroyed", client); - client->watch = NULL; + priv->watch = NULL; g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_CLOSED], 0, NULL); g_object_unref (client); } -static gboolean -setup_client (GstRTSPClient * client, GSocket * socket, - GstRTSPConnection * conn, GError ** error) -{ - GSocket *read_socket; - GSocketAddress *address; - GstRTSPUrl *url; - - read_socket = gst_rtsp_connection_get_read_socket (conn); - client->is_ipv6 = g_socket_get_family (socket) == G_SOCKET_FAMILY_IPV6; - - if (!(address = g_socket_get_remote_address (read_socket, error))) - goto no_address; - - g_free (client->server_ip); - /* keep the original ip that the client connected to */ - if (G_IS_INET_SOCKET_ADDRESS (address)) { - GInetAddress *iaddr; - - iaddr = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (address)); - - client->server_ip = g_inet_address_to_string (iaddr); - g_object_unref (address); - } else { - client->server_ip = g_strdup ("unknown"); - } - - GST_INFO ("client %p connected to server ip %s, ipv6 = %d", client, - client->server_ip, client->is_ipv6); - - url = gst_rtsp_connection_get_url (conn); - GST_INFO ("added new client %p ip %s:%d", client, url->host, url->port); - - client->connection = conn; - - return TRUE; - - /* ERRORS */ -no_address: - { - GST_ERROR ("could not get remote address %s", (*error)->message); - return FALSE; - } -} - /** - * gst_rtsp_client_use_socket: + * gst_rtsp_client_attach: * @client: a #GstRTSPClient - * @socket: a #GSocket - * @ip: the IP address of the remote client - * @port: the port used by the other end - * @initial_buffer: any zero terminated initial data that was already read from - * the socket - * @error: a #GError + * @context: (allow-none): a #GMainContext + * + * Attaches @client to @context. When the mainloop for @context is run, the + * client will be dispatched. When @context is %NULL, the default context will be + * used). * - * Take an existing network socket and use it for an RTSP connection. + * This function should be called when the client properties and urls are fully + * configured and the client is ready to start. * - * Returns: %TRUE on success. + * Returns: the ID (greater than 0) for the source within the GMainContext. */ -gboolean -gst_rtsp_client_use_socket (GstRTSPClient * client, GSocket * socket, - const gchar * ip, gint port, const gchar * initial_buffer, GError ** error) +guint +gst_rtsp_client_attach (GstRTSPClient * client, GMainContext * context) { - GstRTSPConnection *conn; - GstRTSPResult res; + GstRTSPClientPrivate *priv; + guint res; - g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), FALSE); - g_return_val_if_fail (G_IS_SOCKET (socket), FALSE); + g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), 0); + priv = client->priv; + g_return_val_if_fail (priv->connection != NULL, 0); + g_return_val_if_fail (priv->watch == NULL, 0); - GST_RTSP_CHECK (gst_rtsp_connection_create_from_socket (socket, ip, port, - initial_buffer, &conn), no_connection); + /* create watch for the connection and attach */ + priv->watch = gst_rtsp_watch_new (priv->connection, &watch_funcs, + g_object_ref (client), (GDestroyNotify) client_watch_notify); + gst_rtsp_client_set_send_func (client, do_send_message, priv->watch, + (GDestroyNotify) gst_rtsp_watch_unref); - return setup_client (client, socket, conn, error); + /* FIXME make this configurable. We don't want to do this yet because it will + * be superceeded by a cache object later */ + gst_rtsp_watch_set_send_backlog (priv->watch, 0, 100); - /* ERRORS */ -no_connection: - { - gchar *str = gst_rtsp_strresult (res); + GST_INFO ("attaching to context %p", context); + res = gst_rtsp_watch_attach (priv->watch, context); - GST_ERROR ("could not create connection from socket %p: %s", socket, str); - g_free (str); - return FALSE; - } + return res; } /** - * gst_rtsp_client_accept: + * gst_rtsp_client_session_filter: * @client: a #GstRTSPClient - * @socket: a #GSocket - * @context: the context to run in - * @cancellable: a #GCancellable - * @error: a #GError + * @func: (scope call) (allow-none): a callback + * @user_data: user data passed to @func + * + * Call @func for each session managed by @client. The result value of @func + * determines what happens to the session. @func will be called with @client + * locked so no further actions on @client can be performed from @func. * - * Accept a new connection for @client on @socket. + * If @func returns #GST_RTSP_FILTER_REMOVE, the session will be removed from + * @client. * - * Returns: %TRUE if the client could be accepted. + * If @func returns #GST_RTSP_FILTER_KEEP, the session will remain in @client. + * + * If @func returns #GST_RTSP_FILTER_REF, the session will remain in @client but + * will also be added with an additional ref to the result #GList of this + * function.. + * + * When @func is %NULL, #GST_RTSP_FILTER_REF will be assumed for each session. + * + * Returns: (element-type GstRTSPSession) (transfer full): a #GList with all + * sessions for which @func returned #GST_RTSP_FILTER_REF. After usage, each + * element in the #GList should be unreffed before the list is freed. */ -gboolean -gst_rtsp_client_accept (GstRTSPClient * client, GSocket * socket, - GCancellable * cancellable, GError ** error) +GList * +gst_rtsp_client_session_filter (GstRTSPClient * client, + GstRTSPClientSessionFilterFunc func, gpointer user_data) { - GstRTSPConnection *conn; - GstRTSPResult res; + GstRTSPClientPrivate *priv; + GList *result, *walk, *next; - g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), FALSE); - g_return_val_if_fail (G_IS_SOCKET (socket), FALSE); + g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), NULL); - /* a new client connected. */ - GST_RTSP_CHECK (gst_rtsp_connection_accept (socket, &conn, cancellable), - accept_failed); + priv = client->priv; - return setup_client (client, socket, conn, error); + result = NULL; - /* ERRORS */ -accept_failed: - { - gchar *str = gst_rtsp_strresult (res); + g_mutex_lock (&priv->lock); + for (walk = priv->sessions; walk; walk = next) { + GstRTSPSession *sess = walk->data; + GstRTSPFilterResult res; - GST_ERROR ("Could not accept client on server socket %p: %s", socket, str); - g_free (str); - return FALSE; - } -} + next = g_list_next (walk); -/** - * gst_rtsp_client_attach: - * @client: a #GstRTSPClient - * @context: (allow-none): a #GMainContext - * - * Attaches @client to @context. When the mainloop for @context is run, the - * client will be dispatched. When @context is NULL, the default context will be - * used). - * - * This function should be called when the client properties and urls are fully - * configured and the client is ready to start. - * - * Returns: the ID (greater than 0) for the source within the GMainContext. - */ -guint -gst_rtsp_client_attach (GstRTSPClient * client, GMainContext * context) -{ - guint res; - - g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), 0); - g_return_val_if_fail (client->watch == NULL, 0); + if (func) + res = func (client, sess, user_data); + else + res = GST_RTSP_FILTER_REF; - /* create watch for the connection and attach */ - client->watch = gst_rtsp_watch_new (client->connection, &watch_funcs, - g_object_ref (client), (GDestroyNotify) client_watch_notify); - - GST_INFO ("attaching to context %p", context); - res = gst_rtsp_watch_attach (client->watch, context); - gst_rtsp_watch_unref (client->watch); + switch (res) { + case GST_RTSP_FILTER_REMOVE: + /* stop watching the session and pretent it went away */ + client_cleanup_session (client, sess); + break; + case GST_RTSP_FILTER_REF: + result = g_list_prepend (result, g_object_ref (sess)); + break; + case GST_RTSP_FILTER_KEEP: + default: + break; + } + } + g_mutex_unlock (&priv->lock); - return res; + return result; }