introspection: add (nullable) annotations to return values
[platform/upstream/gstreamer.git] / gst / rtsp-server / rtsp-session-pool.c
1 /* GStreamer
2  * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 /**
20  * SECTION:rtsp-session-pool
21  * @short_description: An object for managing sessions
22  * @see_also: #GstRTSPSession
23  *
24  * The #GstRTSPSessionPool object manages a list of #GstRTSPSession objects.
25  *
26  * The maximum number of sessions can be configured with
27  * gst_rtsp_session_pool_set_max_sessions(). The current number of sessions can
28  * be retrieved with gst_rtsp_session_pool_get_n_sessions().
29  *
30  * Use gst_rtsp_session_pool_create() to create a new #GstRTSPSession object.
31  * The session object can be found again with its id and
32  * gst_rtsp_session_pool_find().
33  *
34  * All sessions can be iterated with gst_rtsp_session_pool_filter().
35  *
36  * Run gst_rtsp_session_pool_cleanup() periodically to remove timed out sessions
37  * or use gst_rtsp_session_pool_create_watch() to be notified when session
38  * cleanup should be performed.
39  *
40  * Last reviewed on 2013-07-11 (1.0.0)
41  */
42
43 #include "rtsp-session-pool.h"
44
45 #define GST_RTSP_SESSION_POOL_GET_PRIVATE(obj)  \
46          (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_RTSP_SESSION_POOL, GstRTSPSessionPoolPrivate))
47
48 struct _GstRTSPSessionPoolPrivate
49 {
50   GMutex lock;                  /* protects everything in this struct */
51   guint max_sessions;
52   GHashTable *sessions;
53 };
54
55 #define DEFAULT_MAX_SESSIONS 0
56
57 enum
58 {
59   PROP_0,
60   PROP_MAX_SESSIONS,
61   PROP_LAST
62 };
63
64 static const gchar session_id_charset[] =
65     { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
66   'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D',
67   'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
68   'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7',
69   '8', '9', '$', '-', '_', '.', '+'
70 };
71
72 GST_DEBUG_CATEGORY_STATIC (rtsp_session_debug);
73 #define GST_CAT_DEFAULT rtsp_session_debug
74
75 static void gst_rtsp_session_pool_get_property (GObject * object, guint propid,
76     GValue * value, GParamSpec * pspec);
77 static void gst_rtsp_session_pool_set_property (GObject * object, guint propid,
78     const GValue * value, GParamSpec * pspec);
79 static void gst_rtsp_session_pool_finalize (GObject * object);
80
81 static gchar *create_session_id (GstRTSPSessionPool * pool);
82 static GstRTSPSession *create_session (GstRTSPSessionPool * pool,
83     const gchar * id);
84
85 G_DEFINE_TYPE (GstRTSPSessionPool, gst_rtsp_session_pool, G_TYPE_OBJECT);
86
87 static void
88 gst_rtsp_session_pool_class_init (GstRTSPSessionPoolClass * klass)
89 {
90   GObjectClass *gobject_class;
91
92   g_type_class_add_private (klass, sizeof (GstRTSPSessionPoolPrivate));
93
94   gobject_class = G_OBJECT_CLASS (klass);
95
96   gobject_class->get_property = gst_rtsp_session_pool_get_property;
97   gobject_class->set_property = gst_rtsp_session_pool_set_property;
98   gobject_class->finalize = gst_rtsp_session_pool_finalize;
99
100   g_object_class_install_property (gobject_class, PROP_MAX_SESSIONS,
101       g_param_spec_uint ("max-sessions", "Max Sessions",
102           "the maximum amount of sessions (0 = unlimited)",
103           0, G_MAXUINT, DEFAULT_MAX_SESSIONS,
104           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
105
106   klass->create_session_id = create_session_id;
107   klass->create_session = create_session;
108
109   GST_DEBUG_CATEGORY_INIT (rtsp_session_debug, "rtspsessionpool", 0,
110       "GstRTSPSessionPool");
111 }
112
113 static void
114 gst_rtsp_session_pool_init (GstRTSPSessionPool * pool)
115 {
116   GstRTSPSessionPoolPrivate *priv = GST_RTSP_SESSION_POOL_GET_PRIVATE (pool);
117
118   pool->priv = priv;
119
120   g_mutex_init (&priv->lock);
121   priv->sessions = g_hash_table_new_full (g_str_hash, g_str_equal,
122       NULL, g_object_unref);
123   priv->max_sessions = DEFAULT_MAX_SESSIONS;
124 }
125
126 static void
127 gst_rtsp_session_pool_finalize (GObject * object)
128 {
129   GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);
130   GstRTSPSessionPoolPrivate *priv = pool->priv;
131
132   g_mutex_clear (&priv->lock);
133   g_hash_table_unref (priv->sessions);
134
135   G_OBJECT_CLASS (gst_rtsp_session_pool_parent_class)->finalize (object);
136 }
137
138 static void
139 gst_rtsp_session_pool_get_property (GObject * object, guint propid,
140     GValue * value, GParamSpec * pspec)
141 {
142   GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);
143
144   switch (propid) {
145     case PROP_MAX_SESSIONS:
146       g_value_set_uint (value, gst_rtsp_session_pool_get_max_sessions (pool));
147       break;
148     default:
149       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
150       break;
151   }
152 }
153
154 static void
155 gst_rtsp_session_pool_set_property (GObject * object, guint propid,
156     const GValue * value, GParamSpec * pspec)
157 {
158   GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);
159
160   switch (propid) {
161     case PROP_MAX_SESSIONS:
162       gst_rtsp_session_pool_set_max_sessions (pool, g_value_get_uint (value));
163       break;
164     default:
165       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
166       break;
167   }
168 }
169
170 /**
171  * gst_rtsp_session_pool_new:
172  *
173  * Create a new #GstRTSPSessionPool instance.
174  *
175  * Returns: (transfer full): A new #GstRTSPSessionPool. g_object_unref() after
176  * usage.
177  */
178 GstRTSPSessionPool *
179 gst_rtsp_session_pool_new (void)
180 {
181   GstRTSPSessionPool *result;
182
183   result = g_object_new (GST_TYPE_RTSP_SESSION_POOL, NULL);
184
185   return result;
186 }
187
188 /**
189  * gst_rtsp_session_pool_set_max_sessions:
190  * @pool: a #GstRTSPSessionPool
191  * @max: the maximum number of sessions
192  *
193  * Configure the maximum allowed number of sessions in @pool to @max.
194  * A value of 0 means an unlimited amount of sessions.
195  */
196 void
197 gst_rtsp_session_pool_set_max_sessions (GstRTSPSessionPool * pool, guint max)
198 {
199   GstRTSPSessionPoolPrivate *priv;
200
201   g_return_if_fail (GST_IS_RTSP_SESSION_POOL (pool));
202
203   priv = pool->priv;
204
205   g_mutex_lock (&priv->lock);
206   priv->max_sessions = max;
207   g_mutex_unlock (&priv->lock);
208 }
209
210 /**
211  * gst_rtsp_session_pool_get_max_sessions:
212  * @pool: a #GstRTSPSessionPool
213  *
214  * Get the maximum allowed number of sessions in @pool. 0 means an unlimited
215  * amount of sessions.
216  *
217  * Returns: the maximum allowed number of sessions.
218  */
219 guint
220 gst_rtsp_session_pool_get_max_sessions (GstRTSPSessionPool * pool)
221 {
222   GstRTSPSessionPoolPrivate *priv;
223   guint result;
224
225   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
226
227   priv = pool->priv;
228
229   g_mutex_lock (&priv->lock);
230   result = priv->max_sessions;
231   g_mutex_unlock (&priv->lock);
232
233   return result;
234 }
235
236 /**
237  * gst_rtsp_session_pool_get_n_sessions:
238  * @pool: a #GstRTSPSessionPool
239  *
240  * Get the amount of active sessions in @pool.
241  *
242  * Returns: the amount of active sessions in @pool.
243  */
244 guint
245 gst_rtsp_session_pool_get_n_sessions (GstRTSPSessionPool * pool)
246 {
247   GstRTSPSessionPoolPrivate *priv;
248   guint result;
249
250   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
251
252   priv = pool->priv;
253
254   g_mutex_lock (&priv->lock);
255   result = g_hash_table_size (priv->sessions);
256   g_mutex_unlock (&priv->lock);
257
258   return result;
259 }
260
261 /**
262  * gst_rtsp_session_pool_find:
263  * @pool: the pool to search
264  * @sessionid: the session id
265  *
266  * Find the session with @sessionid in @pool. The access time of the session
267  * will be updated with gst_rtsp_session_touch().
268  *
269  * Returns: (transfer full) (nullable): the #GstRTSPSession with @sessionid
270  * or %NULL when the session did not exist. g_object_unref() after usage.
271  */
272 GstRTSPSession *
273 gst_rtsp_session_pool_find (GstRTSPSessionPool * pool, const gchar * sessionid)
274 {
275   GstRTSPSessionPoolPrivate *priv;
276   GstRTSPSession *result;
277
278   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
279   g_return_val_if_fail (sessionid != NULL, NULL);
280
281   priv = pool->priv;
282
283   g_mutex_lock (&priv->lock);
284   result = g_hash_table_lookup (priv->sessions, sessionid);
285   if (result) {
286     g_object_ref (result);
287     gst_rtsp_session_touch (result);
288   }
289   g_mutex_unlock (&priv->lock);
290
291   return result;
292 }
293
294 static gchar *
295 create_session_id (GstRTSPSessionPool * pool)
296 {
297   gchar id[17];
298   gint i;
299
300   for (i = 0; i < 16; i++) {
301     id[i] =
302         session_id_charset[g_random_int_range (0,
303             G_N_ELEMENTS (session_id_charset))];
304   }
305   id[16] = 0;
306
307   return g_uri_escape_string (id, NULL, FALSE);
308 }
309
310 static GstRTSPSession *
311 create_session (GstRTSPSessionPool * pool, const gchar * id)
312 {
313   return gst_rtsp_session_new (id);
314 }
315
316 /**
317  * gst_rtsp_session_pool_create:
318  * @pool: a #GstRTSPSessionPool
319  *
320  * Create a new #GstRTSPSession object in @pool.
321  *
322  * Returns: (transfer full): a new #GstRTSPSession.
323  */
324 GstRTSPSession *
325 gst_rtsp_session_pool_create (GstRTSPSessionPool * pool)
326 {
327   GstRTSPSessionPoolPrivate *priv;
328   GstRTSPSession *result = NULL;
329   GstRTSPSessionPoolClass *klass;
330   gchar *id = NULL;
331   guint retry;
332
333   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
334
335   priv = pool->priv;
336
337   klass = GST_RTSP_SESSION_POOL_GET_CLASS (pool);
338
339   retry = 0;
340   do {
341     /* start by creating a new random session id, we assume that this is random
342      * enough to not cause a collision, which we will check later  */
343     if (klass->create_session_id)
344       id = klass->create_session_id (pool);
345     else
346       goto no_function;
347
348     if (id == NULL)
349       goto no_session;
350
351     g_mutex_lock (&priv->lock);
352     /* check session limit */
353     if (priv->max_sessions > 0) {
354       if (g_hash_table_size (priv->sessions) >= priv->max_sessions)
355         goto too_many_sessions;
356     }
357     /* check if the sessionid existed */
358     result = g_hash_table_lookup (priv->sessions, id);
359     if (result) {
360       /* found, retry with a different session id */
361       result = NULL;
362       retry++;
363       if (retry > 100)
364         goto collision;
365     } else {
366       /* not found, create session and insert it in the pool */
367       if (klass->create_session)
368         result = create_session (pool, id);
369       if (result == NULL)
370         goto too_many_sessions;
371       /* take additional ref for the pool */
372       g_object_ref (result);
373       g_hash_table_insert (priv->sessions,
374           (gchar *) gst_rtsp_session_get_sessionid (result), result);
375     }
376     g_mutex_unlock (&priv->lock);
377
378     g_free (id);
379   } while (result == NULL);
380
381   return result;
382
383   /* ERRORS */
384 no_function:
385   {
386     GST_WARNING ("no create_session_id vmethod in GstRTSPSessionPool %p", pool);
387     return NULL;
388   }
389 no_session:
390   {
391     GST_WARNING ("can't create session id with GstRTSPSessionPool %p", pool);
392     return NULL;
393   }
394 collision:
395   {
396     GST_WARNING ("can't find unique sessionid for GstRTSPSessionPool %p", pool);
397     g_mutex_unlock (&priv->lock);
398     g_free (id);
399     return NULL;
400   }
401 too_many_sessions:
402   {
403     GST_WARNING ("session pool reached max sessions of %d", priv->max_sessions);
404     g_mutex_unlock (&priv->lock);
405     g_free (id);
406     return NULL;
407   }
408 }
409
410 /**
411  * gst_rtsp_session_pool_remove:
412  * @pool: a #GstRTSPSessionPool
413  * @sess: (transfer none): a #GstRTSPSession
414  *
415  * Remove @sess from @pool, releasing the ref that the pool has on @sess.
416  *
417  * Returns: %TRUE if the session was found and removed.
418  */
419 gboolean
420 gst_rtsp_session_pool_remove (GstRTSPSessionPool * pool, GstRTSPSession * sess)
421 {
422   GstRTSPSessionPoolPrivate *priv;
423   gboolean found;
424
425   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), FALSE);
426   g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), FALSE);
427
428   priv = pool->priv;
429
430   g_mutex_lock (&priv->lock);
431   found =
432       g_hash_table_remove (priv->sessions,
433       gst_rtsp_session_get_sessionid (sess));
434   g_mutex_unlock (&priv->lock);
435
436   return found;
437 }
438
439 static gboolean
440 cleanup_func (gchar * sessionid, GstRTSPSession * sess, GTimeVal * now)
441 {
442   return gst_rtsp_session_is_expired (sess, now);
443 }
444
445 /**
446  * gst_rtsp_session_pool_cleanup:
447  * @pool: a #GstRTSPSessionPool
448  *
449  * Inspect all the sessions in @pool and remove the sessions that are inactive
450  * for more than their timeout.
451  *
452  * Returns: the amount of sessions that got removed.
453  */
454 guint
455 gst_rtsp_session_pool_cleanup (GstRTSPSessionPool * pool)
456 {
457   GstRTSPSessionPoolPrivate *priv;
458   guint result;
459   GTimeVal now;
460
461   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
462
463   priv = pool->priv;
464
465   g_get_current_time (&now);
466
467   g_mutex_lock (&priv->lock);
468   result =
469       g_hash_table_foreach_remove (priv->sessions, (GHRFunc) cleanup_func,
470       &now);
471   g_mutex_unlock (&priv->lock);
472
473   return result;
474 }
475
476 typedef struct
477 {
478   GstRTSPSessionPool *pool;
479   GstRTSPSessionPoolFilterFunc func;
480   gpointer user_data;
481   GList *list;
482 } FilterData;
483
484 static gboolean
485 filter_func (gchar * sessionid, GstRTSPSession * sess, FilterData * data)
486 {
487   GstRTSPFilterResult res;
488
489   if (data->func)
490     res = data->func (data->pool, sess, data->user_data);
491   else
492     res = GST_RTSP_FILTER_REF;
493
494   switch (res) {
495     case GST_RTSP_FILTER_REMOVE:
496       return TRUE;
497     case GST_RTSP_FILTER_REF:
498       /* keep ref */
499       data->list = g_list_prepend (data->list, g_object_ref (sess));
500       /* fallthrough */
501     default:
502     case GST_RTSP_FILTER_KEEP:
503       return FALSE;
504   }
505 }
506
507 /**
508  * gst_rtsp_session_pool_filter:
509  * @pool: a #GstRTSPSessionPool
510  * @func: (scope call) (allow-none): a callback
511  * @user_data: (closure): user data passed to @func
512  *
513  * Call @func for each session in @pool. The result value of @func determines
514  * what happens to the session. @func will be called with the session pool
515  * locked so no further actions on @pool can be performed from @func.
516  *
517  * If @func returns #GST_RTSP_FILTER_REMOVE, the session will be removed from
518  * @pool.
519  *
520  * If @func returns #GST_RTSP_FILTER_KEEP, the session will remain in @pool.
521  *
522  * If @func returns #GST_RTSP_FILTER_REF, the session will remain in @pool but
523  * will also be added with an additional ref to the result GList of this
524  * function..
525  *
526  * When @func is %NULL, #GST_RTSP_FILTER_REF will be assumed for all sessions.
527  *
528  * Returns: (element-type GstRTSPSession) (transfer full): a GList with all
529  * sessions for which @func returned #GST_RTSP_FILTER_REF. After usage, each
530  * element in the GList should be unreffed before the list is freed.
531  */
532 GList *
533 gst_rtsp_session_pool_filter (GstRTSPSessionPool * pool,
534     GstRTSPSessionPoolFilterFunc func, gpointer user_data)
535 {
536   GstRTSPSessionPoolPrivate *priv;
537   FilterData data;
538
539   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
540
541   priv = pool->priv;
542
543   data.pool = pool;
544   data.func = func;
545   data.user_data = user_data;
546   data.list = NULL;
547
548   g_mutex_lock (&priv->lock);
549   g_hash_table_foreach_remove (priv->sessions, (GHRFunc) filter_func, &data);
550   g_mutex_unlock (&priv->lock);
551
552   return data.list;
553 }
554
555 typedef struct
556 {
557   GSource source;
558   GstRTSPSessionPool *pool;
559   gint timeout;
560 } GstPoolSource;
561
562 static void
563 collect_timeout (gchar * sessionid, GstRTSPSession * sess, GstPoolSource * psrc)
564 {
565   gint timeout;
566   GTimeVal now;
567
568   g_get_current_time (&now);
569
570   timeout = gst_rtsp_session_next_timeout (sess, &now);
571   GST_INFO ("%p: next timeout: %d", sess, timeout);
572   if (psrc->timeout == -1 || timeout < psrc->timeout)
573     psrc->timeout = timeout;
574 }
575
576 static gboolean
577 gst_pool_source_prepare (GSource * source, gint * timeout)
578 {
579   GstRTSPSessionPoolPrivate *priv;
580   GstPoolSource *psrc;
581   gboolean result;
582
583   psrc = (GstPoolSource *) source;
584   psrc->timeout = -1;
585   priv = psrc->pool->priv;
586
587   g_mutex_lock (&priv->lock);
588   g_hash_table_foreach (priv->sessions, (GHFunc) collect_timeout, psrc);
589   g_mutex_unlock (&priv->lock);
590
591   if (timeout)
592     *timeout = psrc->timeout;
593
594   result = psrc->timeout == 0;
595
596   GST_INFO ("prepare %d, %d", psrc->timeout, result);
597
598   return result;
599 }
600
601 static gboolean
602 gst_pool_source_check (GSource * source)
603 {
604   GST_INFO ("check");
605
606   return gst_pool_source_prepare (source, NULL);
607 }
608
609 static gboolean
610 gst_pool_source_dispatch (GSource * source, GSourceFunc callback,
611     gpointer user_data)
612 {
613   gboolean res;
614   GstPoolSource *psrc = (GstPoolSource *) source;
615   GstRTSPSessionPoolFunc func = (GstRTSPSessionPoolFunc) callback;
616
617   GST_INFO ("dispatch");
618
619   if (func)
620     res = func (psrc->pool, user_data);
621   else
622     res = FALSE;
623
624   return res;
625 }
626
627 static void
628 gst_pool_source_finalize (GSource * source)
629 {
630   GstPoolSource *psrc = (GstPoolSource *) source;
631
632   GST_INFO ("finalize %p", psrc);
633
634   g_object_unref (psrc->pool);
635   psrc->pool = NULL;
636 }
637
638 static GSourceFuncs gst_pool_source_funcs = {
639   gst_pool_source_prepare,
640   gst_pool_source_check,
641   gst_pool_source_dispatch,
642   gst_pool_source_finalize
643 };
644
645 /**
646  * gst_rtsp_session_pool_create_watch:
647  * @pool: a #GstRTSPSessionPool
648  *
649  * Create a #GSource that will be dispatched when the session should be cleaned
650  * up.
651  *
652  * Returns: (transfer full): a #GSource
653  */
654 GSource *
655 gst_rtsp_session_pool_create_watch (GstRTSPSessionPool * pool)
656 {
657   GstPoolSource *source;
658
659   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
660
661   source = (GstPoolSource *) g_source_new (&gst_pool_source_funcs,
662       sizeof (GstPoolSource));
663   source->pool = g_object_ref (pool);
664
665   return (GSource *) source;
666 }