Some more session timeout handling
[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., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include "rtsp-session-pool.h"
21
22 #undef DEBUG
23
24 #define DEFAULT_MAX_SESSIONS 0
25
26 enum
27 {
28   PROP_0,
29   PROP_MAX_SESSIONS,
30   PROP_LAST
31 };
32
33 static void gst_rtsp_session_pool_get_property (GObject *object, guint propid,
34     GValue *value, GParamSpec *pspec);
35 static void gst_rtsp_session_pool_set_property (GObject *object, guint propid,
36     const GValue *value, GParamSpec *pspec);
37 static void gst_rtsp_session_pool_finalize (GObject * object);
38
39 static gchar * create_session_id (GstRTSPSessionPool *pool);
40
41 G_DEFINE_TYPE (GstRTSPSessionPool, gst_rtsp_session_pool, G_TYPE_OBJECT);
42
43 static void
44 gst_rtsp_session_pool_class_init (GstRTSPSessionPoolClass * klass)
45 {
46   GObjectClass *gobject_class;
47
48   gobject_class = G_OBJECT_CLASS (klass);
49
50   gobject_class->get_property = gst_rtsp_session_pool_get_property;
51   gobject_class->set_property = gst_rtsp_session_pool_set_property;
52   gobject_class->finalize = gst_rtsp_session_pool_finalize;
53
54   g_object_class_install_property (gobject_class, PROP_MAX_SESSIONS,
55       g_param_spec_uint ("max-sessions", "Max Sessions",
56           "the maximum amount of sessions (0 = unlimited)",
57           0, G_MAXUINT, DEFAULT_MAX_SESSIONS,
58           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
59
60   klass->create_session_id = create_session_id;
61 }
62
63 static void
64 gst_rtsp_session_pool_init (GstRTSPSessionPool * pool)
65 {
66   pool->lock = g_mutex_new ();
67   pool->sessions = g_hash_table_new_full (g_str_hash, g_str_equal,
68                   NULL, g_object_unref);
69   pool->max_sessions = DEFAULT_MAX_SESSIONS;
70 }
71
72 static void
73 gst_rtsp_session_pool_finalize (GObject * object)
74 {
75   GstRTSPSessionPool * pool = GST_RTSP_SESSION_POOL (object);
76
77   g_mutex_free (pool->lock);
78   g_hash_table_unref (pool->sessions);
79   
80   G_OBJECT_CLASS (gst_rtsp_session_pool_parent_class)->finalize (object);
81 }
82
83 static void
84 gst_rtsp_session_pool_get_property (GObject *object, guint propid,
85     GValue *value, GParamSpec *pspec)
86 {
87   GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);
88
89   switch (propid) {
90     case PROP_MAX_SESSIONS:
91       g_value_set_uint (value, gst_rtsp_session_pool_get_max_sessions (pool));
92       break;
93     default:
94       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
95       break;
96   }
97 }
98
99 static void
100 gst_rtsp_session_pool_set_property (GObject *object, guint propid,
101     const GValue *value, GParamSpec *pspec)
102 {
103   GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);
104
105   switch (propid) {
106     case PROP_MAX_SESSIONS:
107       gst_rtsp_session_pool_set_max_sessions (pool, g_value_get_uint (value));
108       break;
109     default:
110       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
111       break;
112   }
113 }
114
115 /**
116  * gst_rtsp_session_pool_new:
117  *
118  * Create a new #GstRTSPSessionPool instance.
119  *
120  * Returns: A new #GstRTSPSessionPool. g_object_unref() after usage.
121  */
122 GstRTSPSessionPool *
123 gst_rtsp_session_pool_new (void)
124 {
125   GstRTSPSessionPool *result;
126
127   result = g_object_new (GST_TYPE_RTSP_SESSION_POOL, NULL);
128
129   return result;
130 }
131
132 /**
133  * gst_rtsp_session_pool_set_max_sessions:
134  * @pool: a #GstRTSPSessionPool
135  * @max: the maximum number of sessions
136  *
137  * Configure the maximum allowed number of sessions in @pool to @max.
138  * A value of 0 means an unlimited amount of sessions.
139  */
140 void
141 gst_rtsp_session_pool_set_max_sessions (GstRTSPSessionPool *pool, guint max)
142 {
143   g_return_if_fail (GST_IS_RTSP_SESSION_POOL (pool));
144
145   g_mutex_lock (pool->lock);
146   pool->max_sessions = max;
147   g_mutex_unlock (pool->lock);
148 }
149
150 /**
151  * gst_rtsp_session_pool_get_max_sessions:
152  * @pool: a #GstRTSPSessionPool
153  *
154  * Get the maximum allowed number of sessions in @pool. 0 means an unlimited
155  * amount of sessions.
156  *
157  * Returns: the maximum allowed number of sessions.
158  */
159 guint
160 gst_rtsp_session_pool_get_max_sessions (GstRTSPSessionPool *pool)
161 {
162   guint result;
163
164   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
165
166   g_mutex_lock (pool->lock);
167   result = pool->max_sessions;
168   g_mutex_unlock (pool->lock);
169
170   return result;
171 }
172
173 /**
174  * gst_rtsp_session_pool_get_n_sessions:
175  * @pool: a #GstRTSPSessionPool
176  *
177  * Get the amount of active sessions in @pool.
178  *
179  * Returns: the amount of active sessions in @pool.
180  */
181 guint
182 gst_rtsp_session_pool_get_n_sessions (GstRTSPSessionPool *pool)
183 {
184   guint result;
185
186   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
187
188   g_mutex_lock (pool->lock);
189   result = g_hash_table_size (pool->sessions);
190   g_mutex_unlock (pool->lock);
191
192   return result;
193 }
194
195 /**
196  * gst_rtsp_session_pool_find:
197  * @pool: the pool to search
198  * @sessionid: the session id
199  *
200  * Find the session with @sessionid in @pool. The access time of the session
201  * will be updated with gst_rtsp_session_touch().
202  *
203  * Returns: the #GstRTSPSession with @sessionid or %NULL when the session did
204  * not exist. g_object_unref() after usage.
205  */
206 GstRTSPSession *
207 gst_rtsp_session_pool_find (GstRTSPSessionPool *pool, const gchar *sessionid)
208 {
209   GstRTSPSession *result;
210
211   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
212   g_return_val_if_fail (sessionid != NULL, NULL);
213
214   g_mutex_lock (pool->lock);
215   result = g_hash_table_lookup (pool->sessions, sessionid);
216   if (result) 
217     g_object_ref (result);
218   g_mutex_unlock (pool->lock);
219
220   if (result) {
221     gst_rtsp_session_touch (result);
222   }
223
224   return result;
225 }
226
227 static gchar *
228 create_session_id (GstRTSPSessionPool *pool)
229 {
230   gchar id[16];
231   gint i;
232
233   for (i = 0; i < 16; i++) {
234     id[i] = g_random_int_range ('a', 'z');
235   }
236
237   return g_strndup (id, 16);
238 }
239
240 /**
241  * gst_rtsp_session_pool_create:
242  * @pool: a #GstRTSPSessionPool
243  *
244  * Create a new #GstRTSPSession object in @pool.
245  *
246  * Returns: a new #GstRTSPSession.
247  */
248 GstRTSPSession *
249 gst_rtsp_session_pool_create (GstRTSPSessionPool *pool)
250 {
251   GstRTSPSession *result = NULL;
252   GstRTSPSessionPoolClass *klass;
253   gchar *id = NULL;
254   guint retry;
255
256   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
257
258   klass = GST_RTSP_SESSION_POOL_GET_CLASS (pool);
259
260   retry = 0;
261   do {
262     /* start by creating a new random session id, we assume that this is random
263      * enough to not cause a collision, which we will check later  */
264     if (klass->create_session_id)
265       id = klass->create_session_id (pool);
266     else
267       goto no_function;
268
269     if (id == NULL)
270       goto no_session;
271
272     g_mutex_lock (pool->lock);
273     /* check session limit */
274     if (pool->max_sessions > 0) {
275       if (g_hash_table_size (pool->sessions) >= pool->max_sessions)
276         goto too_many_sessions;
277     }
278     /* check if the sessionid existed */
279     result = g_hash_table_lookup (pool->sessions, id);
280     if (result) {
281       /* found, retry with a different session id */
282       result = NULL;
283       retry++;
284       if (retry > 100)
285         goto collision;
286     }
287     else {
288       /* not found, create session and insert it in the pool */
289       result = gst_rtsp_session_new (id); 
290       /* take additional ref for the pool */
291       g_object_ref (result);
292       g_hash_table_insert (pool->sessions, result->sessionid, result);
293     }
294     g_mutex_unlock (pool->lock);
295
296     g_free (id);
297   } while (result == NULL);
298
299   return result;
300
301   /* ERRORS */
302 no_function:
303   {
304     g_warning ("no create_session_id vmethod in GstRTSPSessionPool %p", pool);
305     return NULL;
306   }
307 no_session:
308   {
309     g_warning ("can't create session id with GstRTSPSessionPool %p", pool);
310     return NULL;
311   }
312 collision:
313   {
314     g_warning ("can't find unique sessionid for GstRTSPSessionPool %p", pool);
315     g_mutex_unlock (pool->lock);
316     g_free (id);
317     return NULL;
318   }
319 too_many_sessions:
320   {
321     g_warning ("session pool reached max sessions of %d", pool->max_sessions);
322     g_mutex_unlock (pool->lock);
323     g_free (id);
324     return NULL;
325   }
326 }
327
328 /**
329  * gst_rtsp_session_pool_remove:
330  * @pool: a #GstRTSPSessionPool
331  * @sess: a #GstRTSPSession
332  *
333  * Remove @sess from @pool, releasing the ref that the pool has on @sess.
334  *
335  * Returns: %TRUE if the session was found and removed.
336  */
337 gboolean
338 gst_rtsp_session_pool_remove (GstRTSPSessionPool *pool, GstRTSPSession *sess)
339 {
340   gboolean found;
341
342   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), FALSE);
343   g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), FALSE);
344
345   g_mutex_lock (pool->lock);
346   found = g_hash_table_remove (pool->sessions, sess->sessionid);
347   g_mutex_unlock (pool->lock);
348
349   return found;
350 }
351
352 static gboolean
353 cleanup_func (gchar *sessionid, GstRTSPSession *sess, GstRTSPSessionPool *pool)
354 {
355   return gst_rtsp_session_is_expired (sess);
356 }
357
358 /**
359  * gst_rtsp_session_pool_cleanup:
360  * @pool: a #GstRTSPSessionPool
361  *
362  * Inspect all the sessions in @pool and remove the sessions that are inactive
363  * for more than their timeout.
364  *
365  * Returns: the amount of sessions that got removed.
366  */
367 guint
368 gst_rtsp_session_pool_cleanup (GstRTSPSessionPool *pool)
369 {
370   guint result;
371
372   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
373
374   g_mutex_lock (pool->lock);
375   result = g_hash_table_foreach_remove (pool->sessions, (GHRFunc) cleanup_func, pool);
376   g_mutex_unlock (pool->lock);
377
378   return result;
379 }