Revert "rtsp-session-pool: Make sure session IDs are properly URI-escaped"
[platform/upstream/gstreamer.git] / gst / rtsp-server / rtsp-session-pool.c
index 1d00dc4..772390f 100644 (file)
  *
  * You should have received a copy of the GNU Library General Public
  * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+/**
+ * SECTION:rtsp-session-pool
+ * @short_description: An object for managing sessions
+ * @see_also: #GstRTSPSession
+ *
+ * The #GstRTSPSessionPool object manages a list of #GstRTSPSession objects.
+ *
+ * The maximum number of sessions can be configured with
+ * gst_rtsp_session_pool_set_max_sessions(). The current number of sessions can
+ * be retrieved with gst_rtsp_session_pool_get_n_sessions().
+ *
+ * Use gst_rtsp_session_pool_create() to create a new #GstRTSPSession object.
+ * The session object can be found again with its id and
+ * gst_rtsp_session_pool_find().
+ *
+ * All sessions can be iterated with gst_rtsp_session_pool_filter().
+ *
+ * Run gst_rtsp_session_pool_cleanup() periodically to remove timed out sessions
+ * or use gst_rtsp_session_pool_create_watch() to be notified when session
+ * cleanup should be performed.
+ *
+ * Last reviewed on 2013-07-11 (1.0.0)
  */
 
 #include "rtsp-session-pool.h"
 
-#undef DEBUG
+#define GST_RTSP_SESSION_POOL_GET_PRIVATE(obj)  \
+         (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_RTSP_SESSION_POOL, GstRTSPSessionPoolPrivate))
+
+struct _GstRTSPSessionPoolPrivate
+{
+  GMutex lock;                  /* protects everything in this struct */
+  guint max_sessions;
+  GHashTable *sessions;
+  guint sessions_cookie;
+};
 
 #define DEFAULT_MAX_SESSIONS 0
 
@@ -38,6 +70,14 @@ static const gchar session_id_charset[] =
   '8', '9', '$', '-', '_', '.', '+'
 };
 
+enum
+{
+  SIGNAL_SESSION_REMOVED,
+  SIGNAL_LAST
+};
+
+static guint gst_rtsp_session_pool_signals[SIGNAL_LAST] = { 0 };
+
 GST_DEBUG_CATEGORY_STATIC (rtsp_session_debug);
 #define GST_CAT_DEFAULT rtsp_session_debug
 
@@ -48,6 +88,8 @@ static void gst_rtsp_session_pool_set_property (GObject * object, guint propid,
 static void gst_rtsp_session_pool_finalize (GObject * object);
 
 static gchar *create_session_id (GstRTSPSessionPool * pool);
+static GstRTSPSession *create_session (GstRTSPSessionPool * pool,
+    const gchar * id);
 
 G_DEFINE_TYPE (GstRTSPSessionPool, gst_rtsp_session_pool, G_TYPE_OBJECT);
 
@@ -56,6 +98,8 @@ gst_rtsp_session_pool_class_init (GstRTSPSessionPoolClass * klass)
 {
   GObjectClass *gobject_class;
 
+  g_type_class_add_private (klass, sizeof (GstRTSPSessionPoolPrivate));
+
   gobject_class = G_OBJECT_CLASS (klass);
 
   gobject_class->get_property = gst_rtsp_session_pool_get_property;
@@ -68,7 +112,14 @@ gst_rtsp_session_pool_class_init (GstRTSPSessionPoolClass * klass)
           0, G_MAXUINT, DEFAULT_MAX_SESSIONS,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
+  gst_rtsp_session_pool_signals[SIGNAL_SESSION_REMOVED] =
+      g_signal_new ("session-removed", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPSessionPoolClass,
+          session_removed), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE,
+      1, GST_TYPE_RTSP_SESSION);
+
   klass->create_session_id = create_session_id;
+  klass->create_session = create_session;
 
   GST_DEBUG_CATEGORY_INIT (rtsp_session_debug, "rtspsessionpool", 0,
       "GstRTSPSessionPool");
@@ -77,19 +128,32 @@ gst_rtsp_session_pool_class_init (GstRTSPSessionPoolClass * klass)
 static void
 gst_rtsp_session_pool_init (GstRTSPSessionPool * pool)
 {
-  pool->lock = g_mutex_new ();
-  pool->sessions = g_hash_table_new_full (g_str_hash, g_str_equal,
+  GstRTSPSessionPoolPrivate *priv = GST_RTSP_SESSION_POOL_GET_PRIVATE (pool);
+
+  pool->priv = priv;
+
+  g_mutex_init (&priv->lock);
+  priv->sessions = g_hash_table_new_full (g_str_hash, g_str_equal,
       NULL, g_object_unref);
-  pool->max_sessions = DEFAULT_MAX_SESSIONS;
+  priv->max_sessions = DEFAULT_MAX_SESSIONS;
+}
+
+static GstRTSPFilterResult
+remove_sessions_func (GstRTSPSessionPool * pool, GstRTSPSession * session,
+    gpointer user_data)
+{
+  return GST_RTSP_FILTER_REMOVE;
 }
 
 static void
 gst_rtsp_session_pool_finalize (GObject * object)
 {
   GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);
+  GstRTSPSessionPoolPrivate *priv = pool->priv;
 
-  g_mutex_free (pool->lock);
-  g_hash_table_unref (pool->sessions);
+  gst_rtsp_session_pool_filter (pool, remove_sessions_func, NULL);
+  g_hash_table_unref (priv->sessions);
+  g_mutex_clear (&priv->lock);
 
   G_OBJECT_CLASS (gst_rtsp_session_pool_parent_class)->finalize (object);
 }
@@ -131,7 +195,8 @@ gst_rtsp_session_pool_set_property (GObject * object, guint propid,
  *
  * Create a new #GstRTSPSessionPool instance.
  *
- * Returns: A new #GstRTSPSessionPool. g_object_unref() after usage.
+ * Returns: (transfer full): A new #GstRTSPSessionPool. g_object_unref() after
+ * usage.
  */
 GstRTSPSessionPool *
 gst_rtsp_session_pool_new (void)
@@ -154,11 +219,15 @@ gst_rtsp_session_pool_new (void)
 void
 gst_rtsp_session_pool_set_max_sessions (GstRTSPSessionPool * pool, guint max)
 {
+  GstRTSPSessionPoolPrivate *priv;
+
   g_return_if_fail (GST_IS_RTSP_SESSION_POOL (pool));
 
-  g_mutex_lock (pool->lock);
-  pool->max_sessions = max;
-  g_mutex_unlock (pool->lock);
+  priv = pool->priv;
+
+  g_mutex_lock (&priv->lock);
+  priv->max_sessions = max;
+  g_mutex_unlock (&priv->lock);
 }
 
 /**
@@ -173,13 +242,16 @@ gst_rtsp_session_pool_set_max_sessions (GstRTSPSessionPool * pool, guint max)
 guint
 gst_rtsp_session_pool_get_max_sessions (GstRTSPSessionPool * pool)
 {
+  GstRTSPSessionPoolPrivate *priv;
   guint result;
 
   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
 
-  g_mutex_lock (pool->lock);
-  result = pool->max_sessions;
-  g_mutex_unlock (pool->lock);
+  priv = pool->priv;
+
+  g_mutex_lock (&priv->lock);
+  result = priv->max_sessions;
+  g_mutex_unlock (&priv->lock);
 
   return result;
 }
@@ -195,13 +267,16 @@ gst_rtsp_session_pool_get_max_sessions (GstRTSPSessionPool * pool)
 guint
 gst_rtsp_session_pool_get_n_sessions (GstRTSPSessionPool * pool)
 {
+  GstRTSPSessionPoolPrivate *priv;
   guint result;
 
   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
 
-  g_mutex_lock (pool->lock);
-  result = g_hash_table_size (pool->sessions);
-  g_mutex_unlock (pool->lock);
+  priv = pool->priv;
+
+  g_mutex_lock (&priv->lock);
+  result = g_hash_table_size (priv->sessions);
+  g_mutex_unlock (&priv->lock);
 
   return result;
 }
@@ -214,24 +289,27 @@ gst_rtsp_session_pool_get_n_sessions (GstRTSPSessionPool * pool)
  * Find the session with @sessionid in @pool. The access time of the session
  * will be updated with gst_rtsp_session_touch().
  *
- * Returns: the #GstRTSPSession with @sessionid or %NULL when the session did
- * not exist. g_object_unref() after usage.
+ * Returns: (transfer full) (nullable): the #GstRTSPSession with @sessionid
+ * or %NULL when the session did not exist. g_object_unref() after usage.
  */
 GstRTSPSession *
 gst_rtsp_session_pool_find (GstRTSPSessionPool * pool, const gchar * sessionid)
 {
+  GstRTSPSessionPoolPrivate *priv;
   GstRTSPSession *result;
 
   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
   g_return_val_if_fail (sessionid != NULL, NULL);
 
-  g_mutex_lock (pool->lock);
-  result = g_hash_table_lookup (pool->sessions, sessionid);
+  priv = pool->priv;
+
+  g_mutex_lock (&priv->lock);
+  result = g_hash_table_lookup (priv->sessions, sessionid);
   if (result) {
     g_object_ref (result);
     gst_rtsp_session_touch (result);
   }
-  g_mutex_unlock (pool->lock);
+  g_mutex_unlock (&priv->lock);
 
   return result;
 }
@@ -251,17 +329,24 @@ create_session_id (GstRTSPSessionPool * pool)
   return g_strndup (id, 16);
 }
 
+static GstRTSPSession *
+create_session (GstRTSPSessionPool * pool, const gchar * id)
+{
+  return gst_rtsp_session_new (id);
+}
+
 /**
  * gst_rtsp_session_pool_create:
  * @pool: a #GstRTSPSessionPool
  *
  * Create a new #GstRTSPSession object in @pool.
  *
- * Returns: a new #GstRTSPSession.
+ * Returns: (transfer full): a new #GstRTSPSession.
  */
 GstRTSPSession *
 gst_rtsp_session_pool_create (GstRTSPSessionPool * pool)
 {
+  GstRTSPSessionPoolPrivate *priv;
   GstRTSPSession *result = NULL;
   GstRTSPSessionPoolClass *klass;
   gchar *id = NULL;
@@ -269,6 +354,8 @@ gst_rtsp_session_pool_create (GstRTSPSessionPool * pool)
 
   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
 
+  priv = pool->priv;
+
   klass = GST_RTSP_SESSION_POOL_GET_CLASS (pool);
 
   retry = 0;
@@ -283,14 +370,14 @@ gst_rtsp_session_pool_create (GstRTSPSessionPool * pool)
     if (id == NULL)
       goto no_session;
 
-    g_mutex_lock (pool->lock);
+    g_mutex_lock (&priv->lock);
     /* check session limit */
-    if (pool->max_sessions > 0) {
-      if (g_hash_table_size (pool->sessions) >= pool->max_sessions)
+    if (priv->max_sessions > 0) {
+      if (g_hash_table_size (priv->sessions) >= priv->max_sessions)
         goto too_many_sessions;
     }
     /* check if the sessionid existed */
-    result = g_hash_table_lookup (pool->sessions, id);
+    result = g_hash_table_lookup (priv->sessions, id);
     if (result) {
       /* found, retry with a different session id */
       result = NULL;
@@ -299,12 +386,17 @@ gst_rtsp_session_pool_create (GstRTSPSessionPool * pool)
         goto collision;
     } else {
       /* not found, create session and insert it in the pool */
-      result = gst_rtsp_session_new (id);
+      if (klass->create_session)
+        result = create_session (pool, id);
+      if (result == NULL)
+        goto too_many_sessions;
       /* take additional ref for the pool */
       g_object_ref (result);
-      g_hash_table_insert (pool->sessions, result->sessionid, result);
+      g_hash_table_insert (priv->sessions,
+          (gchar *) gst_rtsp_session_get_sessionid (result), result);
+      priv->sessions_cookie++;
     }
-    g_mutex_unlock (pool->lock);
+    g_mutex_unlock (&priv->lock);
 
     g_free (id);
   } while (result == NULL);
@@ -325,14 +417,14 @@ no_session:
 collision:
   {
     GST_WARNING ("can't find unique sessionid for GstRTSPSessionPool %p", pool);
-    g_mutex_unlock (pool->lock);
+    g_mutex_unlock (&priv->lock);
     g_free (id);
     return NULL;
   }
 too_many_sessions:
   {
-    GST_WARNING ("session pool reached max sessions of %d", pool->max_sessions);
-    g_mutex_unlock (pool->lock);
+    GST_WARNING ("session pool reached max sessions of %d", priv->max_sessions);
+    g_mutex_unlock (&priv->lock);
     g_free (id);
     return NULL;
   }
@@ -341,7 +433,7 @@ too_many_sessions:
 /**
  * gst_rtsp_session_pool_remove:
  * @pool: a #GstRTSPSessionPool
- * @sess: a #GstRTSPSession
+ * @sess: (transfer none): a #GstRTSPSession
  *
  * Remove @sess from @pool, releasing the ref that the pool has on @sess.
  *
@@ -350,22 +442,50 @@ too_many_sessions:
 gboolean
 gst_rtsp_session_pool_remove (GstRTSPSessionPool * pool, GstRTSPSession * sess)
 {
+  GstRTSPSessionPoolPrivate *priv;
   gboolean found;
 
   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), FALSE);
   g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), FALSE);
 
-  g_mutex_lock (pool->lock);
-  found = g_hash_table_remove (pool->sessions, sess->sessionid);
-  g_mutex_unlock (pool->lock);
+  priv = pool->priv;
+
+  g_mutex_lock (&priv->lock);
+  g_object_ref (sess);
+  found =
+      g_hash_table_remove (priv->sessions,
+      gst_rtsp_session_get_sessionid (sess));
+  if (found)
+    priv->sessions_cookie++;
+  g_mutex_unlock (&priv->lock);
+
+  if (found)
+    g_signal_emit (pool, gst_rtsp_session_pool_signals[SIGNAL_SESSION_REMOVED],
+        0, sess);
+
+  g_object_unref (sess);
 
   return found;
 }
 
+typedef struct
+{
+  GTimeVal now;
+  GstRTSPSessionPool *pool;
+  GList *removed;
+} CleanupData;
+
 static gboolean
-cleanup_func (gchar * sessionid, GstRTSPSession * sess, GTimeVal * now)
+cleanup_func (gchar * sessionid, GstRTSPSession * sess, CleanupData * data)
 {
-  return gst_rtsp_session_is_expired (sess, now);
+  gboolean expired;
+
+  expired = gst_rtsp_session_is_expired (sess, &data->now);
+  if (expired) {
+    GST_DEBUG ("session expired");
+    data->removed = g_list_prepend (data->removed, g_object_ref (sess));
+  }
+  return expired;
 }
 
 /**
@@ -380,57 +500,52 @@ cleanup_func (gchar * sessionid, GstRTSPSession * sess, GTimeVal * now)
 guint
 gst_rtsp_session_pool_cleanup (GstRTSPSessionPool * pool)
 {
+  GstRTSPSessionPoolPrivate *priv;
   guint result;
-  GTimeVal now;
+  CleanupData data;
+  GList *walk;
 
   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
 
-  g_get_current_time (&now);
+  priv = pool->priv;
+
+  g_get_current_time (&data.now);
+  data.pool = pool;
+  data.removed = NULL;
 
-  g_mutex_lock (pool->lock);
+  g_mutex_lock (&priv->lock);
   result =
-      g_hash_table_foreach_remove (pool->sessions, (GHRFunc) cleanup_func,
-      &now);
-  g_mutex_unlock (pool->lock);
+      g_hash_table_foreach_remove (priv->sessions, (GHRFunc) cleanup_func,
+      &data);
+  if (result > 0)
+    priv->sessions_cookie++;
+  g_mutex_unlock (&priv->lock);
 
-  return result;
-}
+  for (walk = data.removed; walk; walk = walk->next) {
+    GstRTSPSession *sess = walk->data;
 
-typedef struct
-{
-  GstRTSPSessionPool *pool;
-  GstRTSPSessionFilterFunc func;
-  gpointer user_data;
-  GList *list;
-} FilterData;
+    g_signal_emit (pool,
+        gst_rtsp_session_pool_signals[SIGNAL_SESSION_REMOVED], 0, sess);
 
-static gboolean
-filter_func (gchar * sessionid, GstRTSPSession * sess, FilterData * data)
-{
-  switch (data->func (data->pool, sess, data->user_data)) {
-    case GST_RTSP_FILTER_REMOVE:
-      return TRUE;
-    case GST_RTSP_FILTER_REF:
-      /* keep ref */
-      data->list = g_list_prepend (data->list, g_object_ref (sess));
-      /* fallthrough */
-    default:
-    case GST_RTSP_FILTER_KEEP:
-      return FALSE;
+    g_object_unref (sess);
   }
+  g_list_free (data.removed);
+
+  return result;
 }
 
 /**
  * gst_rtsp_session_pool_filter:
  * @pool: a #GstRTSPSessionPool
- * @func: a callback
- * @user_data: user data passed to @func
+ * @func: (scope call) (allow-none): a callback
+ * @user_data: (closure): user data passed to @func
  *
  * Call @func for each session in @pool. The result value of @func determines
  * what happens to the session. @func will be called with the session pool
  * locked so no further actions on @pool can be performed from @func.
  *
- * If @func returns #GST_RTSP_FILTER_REMOVE, the session will be removed from
+ * If @func returns #GST_RTSP_FILTER_REMOVE, the session will be set to the
+ * expired state with gst_rtsp_session_set_expired() and removed from
  * @pool.
  *
  * If @func returns #GST_RTSP_FILTER_KEEP, the session will remain in @pool.
@@ -439,29 +554,100 @@ filter_func (gchar * sessionid, GstRTSPSession * sess, FilterData * data)
  * will also be added with an additional ref to the result GList of this
  * function..
  *
- * Returns: 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.
+ * When @func is %NULL, #GST_RTSP_FILTER_REF will be assumed for all sessions.
+ *
+ * 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.
  */
 GList *
 gst_rtsp_session_pool_filter (GstRTSPSessionPool * pool,
-    GstRTSPSessionFilterFunc func, gpointer user_data)
+    GstRTSPSessionPoolFilterFunc func, gpointer user_data)
 {
-  FilterData data;
+  GstRTSPSessionPoolPrivate *priv;
+  GHashTableIter iter;
+  gpointer key, value;
+  GList *result;
+  GHashTable *visited;
+  guint cookie;
 
   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
-  g_return_val_if_fail (func != NULL, NULL);
 
-  data.pool = pool;
-  data.func = func;
-  data.user_data = user_data;
-  data.list = NULL;
+  priv = pool->priv;
+
+  result = NULL;
+  if (func)
+    visited = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL);
+
+  g_mutex_lock (&priv->lock);
+restart:
+  g_hash_table_iter_init (&iter, priv->sessions);
+  cookie = priv->sessions_cookie;
+  while (g_hash_table_iter_next (&iter, &key, &value)) {
+    GstRTSPSession *session = value;
+    GstRTSPFilterResult res;
+    gboolean changed;
+
+    if (func) {
+      /* only visit each session once */
+      if (g_hash_table_contains (visited, session))
+        continue;
+
+      g_hash_table_add (visited, g_object_ref (session));
+      g_mutex_unlock (&priv->lock);
+
+      res = func (pool, session, user_data);
+
+      g_mutex_lock (&priv->lock);
+    } else
+      res = GST_RTSP_FILTER_REF;
+
+    changed = (cookie != priv->sessions_cookie);
+
+    switch (res) {
+      case GST_RTSP_FILTER_REMOVE:
+      {
+        gboolean removed = TRUE;
+
+        if (changed)
+          /* something changed, check if we still have the session */
+          removed = g_hash_table_remove (priv->sessions, key);
+        else
+          g_hash_table_iter_remove (&iter);
+
+        if (removed) {
+          /* if we managed to remove the session, update the cookie and
+           * signal */
+          cookie = ++priv->sessions_cookie;
+          g_mutex_unlock (&priv->lock);
+
+          g_signal_emit (pool,
+              gst_rtsp_session_pool_signals[SIGNAL_SESSION_REMOVED], 0,
+              session);
+
+          g_mutex_lock (&priv->lock);
+          /* cookie could have changed again, make sure we restart */
+          changed |= (cookie != priv->sessions_cookie);
+        }
+        break;
+      }
+      case GST_RTSP_FILTER_REF:
+        /* keep ref */
+        result = g_list_prepend (result, g_object_ref (session));
+        break;
+      case GST_RTSP_FILTER_KEEP:
+      default:
+        break;
+    }
+    if (changed)
+      goto restart;
+  }
+  g_mutex_unlock (&priv->lock);
 
-  g_mutex_lock (pool->lock);
-  g_hash_table_foreach_remove (pool->sessions, (GHRFunc) filter_func, &data);
-  g_mutex_unlock (pool->lock);
+  if (func)
+    g_hash_table_unref (visited);
 
-  return data.list;
+  return result;
 }
 
 typedef struct
@@ -488,15 +674,17 @@ collect_timeout (gchar * sessionid, GstRTSPSession * sess, GstPoolSource * psrc)
 static gboolean
 gst_pool_source_prepare (GSource * source, gint * timeout)
 {
+  GstRTSPSessionPoolPrivate *priv;
   GstPoolSource *psrc;
   gboolean result;
 
   psrc = (GstPoolSource *) source;
   psrc->timeout = -1;
+  priv = psrc->pool->priv;
 
-  g_mutex_lock (psrc->pool->lock);
-  g_hash_table_foreach (psrc->pool->sessions, (GHFunc) collect_timeout, psrc);
-  g_mutex_unlock (psrc->pool->lock);
+  g_mutex_lock (&priv->lock);
+  g_hash_table_foreach (priv->sessions, (GHFunc) collect_timeout, psrc);
+  g_mutex_unlock (&priv->lock);
 
   if (timeout)
     *timeout = psrc->timeout;
@@ -556,7 +744,10 @@ static GSourceFuncs gst_pool_source_funcs = {
  * gst_rtsp_session_pool_create_watch:
  * @pool: a #GstRTSPSessionPool
  *
- * A GSource that will be dispatched when the session should be cleaned up.
+ * Create a #GSource that will be dispatched when the session should be cleaned
+ * up.
+ *
+ * Returns: (transfer full): a #GSource
  */
 GSource *
 gst_rtsp_session_pool_create_watch (GstRTSPSessionPool * pool)