address-pool: add clear method
[platform/upstream/gstreamer.git] / gst / rtsp-server / rtsp-address-pool.c
1 /* GStreamer
2  * Copyright (C) 2012 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 #include <string.h>
21 #include <gio/gio.h>
22
23 #include "rtsp-address-pool.h"
24
25 GST_DEBUG_CATEGORY_STATIC (rtsp_address_pool_debug);
26 #define GST_CAT_DEFAULT rtsp_address_pool_debug
27
28 #define GST_RTSP_ADDRESS_POOL_GET_PRIVATE(obj)  \
29      (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_RTSP_ADDRESS_POOL, GstRTSPAddressPoolPrivate))
30
31 struct _GstRTSPAddressPoolPrivate
32 {
33   GMutex lock;
34   GList *addresses;
35   GList *allocated;
36 };
37
38 #define ADDR_IS_IPV4(a)      ((a)->size == 4)
39 #define ADDR_IS_IPV6(a)      ((a)->size == 16)
40 #define ADDR_IS_EVEN_PORT(a) (((a)->port & 1) == 0)
41
42 typedef struct
43 {
44   guint8 bytes[16];
45   gsize size;
46   guint16 port;
47 } Addr;
48
49 typedef struct
50 {
51   Addr min;
52   Addr max;
53   guint8 ttl;
54 } AddrRange;
55
56 #define RANGE_IS_SINGLE(r) (memcmp ((r)->min.bytes, (r)->max.bytes, (r)->min.size) == 0)
57
58 #define gst_rtsp_address_pool_parent_class parent_class
59 G_DEFINE_TYPE (GstRTSPAddressPool, gst_rtsp_address_pool, G_TYPE_OBJECT);
60
61 static void gst_rtsp_address_pool_finalize (GObject * obj);
62
63 static void
64 gst_rtsp_address_pool_class_init (GstRTSPAddressPoolClass * klass)
65 {
66   GObjectClass *gobject_class;
67
68   gobject_class = G_OBJECT_CLASS (klass);
69
70   gobject_class->finalize = gst_rtsp_address_pool_finalize;
71
72   g_type_class_add_private (klass, sizeof (GstRTSPAddressPoolPrivate));
73
74   GST_DEBUG_CATEGORY_INIT (rtsp_address_pool_debug, "rtspaddresspool", 0,
75       "GstRTSPAddressPool");
76 }
77
78 static void
79 gst_rtsp_address_pool_init (GstRTSPAddressPool * pool)
80 {
81   pool->priv = GST_RTSP_ADDRESS_POOL_GET_PRIVATE (pool);
82
83   g_mutex_init (&pool->priv->lock);
84 }
85
86 static void
87 free_range (AddrRange * range)
88 {
89   g_slice_free (AddrRange, range);
90 }
91
92 static void
93 gst_rtsp_address_pool_finalize (GObject * obj)
94 {
95   GstRTSPAddressPool *pool;
96
97   pool = GST_RTSP_ADDRESS_POOL (obj);
98
99   g_list_free_full (pool->priv->addresses, (GDestroyNotify) free_range);
100   g_list_free_full (pool->priv->allocated, (GDestroyNotify) free_range);
101   g_mutex_clear (&pool->priv->lock);
102
103   G_OBJECT_CLASS (parent_class)->finalize (obj);
104 }
105
106 /**
107  * gst_rtsp_address_pool_new:
108  *
109  * Make a new #GstRTSPAddressPool.
110  *
111  * Returns: a new #GstRTSPAddressPool
112  */
113 GstRTSPAddressPool *
114 gst_rtsp_address_pool_new (void)
115 {
116   GstRTSPAddressPool *pool;
117
118   pool = g_object_new (GST_TYPE_RTSP_ADDRESS_POOL, NULL);
119
120   return pool;
121 }
122
123 /**
124  * gst_rtsp_address_pool_clear:
125  * @pool: a #GstRTSPAddressPool
126  *
127  * Clear all addresses in @pool. There should be no outstanding
128  * allocations.
129  */
130 void
131 gst_rtsp_address_pool_clear (GstRTSPAddressPool * pool)
132 {
133   GstRTSPAddressPoolPrivate *priv;
134
135   g_return_if_fail (GST_IS_RTSP_ADDRESS_POOL (pool));
136   g_return_if_fail (pool->priv->allocated == NULL);
137
138   priv = pool->priv;
139
140   g_mutex_lock (&priv->lock);
141   g_list_free_full (priv->addresses, (GDestroyNotify) free_range);
142   priv->addresses = NULL;
143   g_mutex_unlock (&priv->lock);
144 }
145
146 static gboolean
147 fill_address (const gchar * address, guint16 port, Addr * addr)
148 {
149   GInetAddress *inet;
150
151   inet = g_inet_address_new_from_string (address);
152   if (inet == NULL)
153     return FALSE;
154
155   addr->size = g_inet_address_get_native_size (inet);
156   memcpy (addr->bytes, g_inet_address_to_bytes (inet), addr->size);
157   g_object_unref (inet);
158   addr->port = port;
159
160   return TRUE;
161 }
162
163 static gchar *
164 get_address_string (Addr * addr)
165 {
166   gchar *res;
167   GInetAddress *inet;
168
169   inet = g_inet_address_new_from_bytes (addr->bytes,
170       addr->size == 4 ? G_SOCKET_FAMILY_IPV4 : G_SOCKET_FAMILY_IPV6);
171   res = g_inet_address_to_string (inet);
172   g_object_unref (inet);
173
174   return res;
175 }
176
177 /**
178  * gst_rtsp_address_pool_add_range:
179  * @pool: a #GstRTSPAddressPool
180  * @min_address: a minimum address to add
181  * @max_address: a maximum address to add
182  * @min_port: the minimum port
183  * @max_port: the maximum port
184  * @ttl: a TTL
185  *
186  * Adds the multicast addresses from @min_addess to @max_address (inclusive)
187  * to @pool. The valid port range for the addresses will be from @min_port to
188  * @max_port inclusive.
189  *
190  * Returns: %TRUE if the addresses could be added.
191  */
192 gboolean
193 gst_rtsp_address_pool_add_range (GstRTSPAddressPool * pool,
194     const gchar * min_address, const gchar * max_address,
195     guint16 min_port, guint16 max_port, guint8 ttl)
196 {
197   AddrRange *range;
198   GstRTSPAddressPoolPrivate *priv;
199
200   g_return_val_if_fail (GST_IS_RTSP_ADDRESS_POOL (pool), FALSE);
201   g_return_val_if_fail (min_port <= max_port, FALSE);
202
203   priv = pool->priv;
204
205   range = g_slice_new0 (AddrRange);
206
207   if (!fill_address (min_address, min_port, &range->min))
208     goto invalid;
209   if (!fill_address (max_address, max_port, &range->max))
210     goto invalid;
211
212   if (range->min.size != range->max.size)
213     goto invalid;
214   if (memcmp (range->min.bytes, range->max.bytes, range->min.size) > 0)
215     goto invalid;
216
217   range->ttl = ttl;
218
219   GST_DEBUG_OBJECT (pool, "adding %s-%s:%u-%u ttl %u", min_address, max_address,
220       min_port, max_port, ttl);
221
222   g_mutex_lock (&priv->lock);
223   priv->addresses = g_list_prepend (priv->addresses, range);
224   g_mutex_unlock (&priv->lock);
225
226   return TRUE;
227
228   /* ERRORS */
229 invalid:
230   {
231     GST_ERROR_OBJECT (pool, "invalid address range %s-%s", min_address,
232         max_address);
233     g_slice_free (AddrRange, range);
234     return FALSE;
235   }
236 }
237
238 static void
239 inc_address (GstRTSPAddressPool * pool, Addr * addr)
240 {
241   gint i;
242   guint carry;
243
244   carry = 1;
245   for (i = addr->size - 1; i >= 0 && carry > 0; i--) {
246     carry += addr->bytes[i];
247     addr->bytes[i] = carry & 0xff;
248     carry >>= 8;
249   }
250 }
251
252 static AddrRange *
253 split_range (GstRTSPAddressPool * pool, AddrRange * range, gint skip,
254     gint n_ports)
255 {
256   GstRTSPAddressPoolPrivate *priv = pool->priv;
257   AddrRange *temp;
258
259   if (!RANGE_IS_SINGLE (range)) {
260     /* min and max are not the same, we have more than one address. */
261     temp = g_slice_dup (AddrRange, range);
262     /* increment the range min address */
263     inc_address (pool, &temp->min);
264     /* and store back in pool */
265     priv->addresses = g_list_prepend (priv->addresses, temp);
266
267     /* adjust range with only the first address */
268     memcpy (range->max.bytes, range->min.bytes, range->min.size);
269   }
270
271   /* range now contains only one single address */
272   if (skip > 0) {
273     /* make a range with the skipped ports */
274     temp = g_slice_dup (AddrRange, range);
275     temp->max.port = temp->min.port + skip;
276     /* and store back in pool */
277     priv->addresses = g_list_prepend (priv->addresses, temp);
278
279     /* increment range port */
280     range->min.port += skip;
281   }
282   /* range now contains single address with desired port number */
283   if (range->max.port - range->min.port + 1 > n_ports) {
284     /* make a range with the remaining ports */
285     temp = g_slice_dup (AddrRange, range);
286     temp->min.port += n_ports;
287     /* and store back in pool */
288     priv->addresses = g_list_prepend (priv->addresses, temp);
289
290     /* and truncate port */
291     range->max.port = range->min.port + n_ports - 1;
292   }
293   return range;
294 }
295
296 /**
297  * gst_rtsp_address_pool_acquire_address:
298  * @pool: a #GstRTSPAddressPool
299  * @flags: flags
300  * @n_ports: the amount of ports
301  * @address: result address
302  * @port: result port
303  * @ttl: result TTL
304  *
305  * Take an address and ports from @pool. @flags can be used to control the
306  * allocation. @n_ports consecutive ports will be allocated of which the first
307  * one can be found in @port.
308  *
309  * Returns: a pointer that should be used to release the address with
310  *   gst_rtsp_address_pool_release_address() after usage or %NULL when no
311  *   address could be acquired.
312  */
313 gpointer
314 gst_rtsp_address_pool_acquire_address (GstRTSPAddressPool * pool,
315     GstRTSPAddressFlags flags, gint n_ports, gchar ** address,
316     guint16 * port, guint8 * ttl)
317 {
318   GstRTSPAddressPoolPrivate *priv;
319   GList *walk, *next;
320   AddrRange *result;
321
322   g_return_val_if_fail (GST_IS_RTSP_ADDRESS_POOL (pool), NULL);
323   g_return_val_if_fail (n_ports > 0, NULL);
324   g_return_val_if_fail (address != NULL, NULL);
325   g_return_val_if_fail (port != NULL, NULL);
326   g_return_val_if_fail (ttl != NULL, NULL);
327
328   priv = pool->priv;
329   result = NULL;
330
331   g_mutex_lock (&priv->lock);
332   /* go over available ranges */
333   for (walk = priv->addresses; walk; walk = next) {
334     AddrRange *range;
335     gint ports, skip;
336
337     range = walk->data;
338     next = walk->next;
339
340     /* check address type when given */
341     if (flags & GST_RTSP_ADDRESS_FLAG_IPV4 && !ADDR_IS_IPV4 (&range->min))
342       continue;
343     if (flags & GST_RTSP_ADDRESS_FLAG_IPV6 && !ADDR_IS_IPV6 (&range->min))
344       continue;
345
346     /* check for enough ports */
347     ports = range->max.port - range->min.port + 1;
348     if (flags & GST_RTSP_ADDRESS_FLAG_EVEN_PORT
349         && !ADDR_IS_EVEN_PORT (&range->min))
350       skip = 1;
351     else
352       skip = 0;
353     if (ports - skip < n_ports)
354       continue;
355
356     /* we found a range, remove from the list */
357     priv->addresses = g_list_delete_link (priv->addresses, walk);
358     /* now split and exit our loop */
359     result = split_range (pool, range, skip, n_ports);
360     priv->allocated = g_list_prepend (priv->allocated, result);
361     break;
362   }
363   g_mutex_unlock (&priv->lock);
364
365   if (result) {
366     *address = get_address_string (&result->min);
367     *port = result->min.port;
368     *ttl = result->ttl;
369
370     GST_DEBUG_OBJECT (pool, "got address %s:%u ttl %u", *address, *port, *ttl);
371   }
372   return result;
373 }
374
375 /**
376  * gst_rtsp_address_pool_release_address:
377  * @pool: a #GstRTSPAddressPool
378  * @id: an address id
379  *
380  * Release a previously acquired address (with
381  * gst_rtsp_address_pool_acquire_address()) back into @pool.
382  */
383 void
384 gst_rtsp_address_pool_release_address (GstRTSPAddressPool * pool, gpointer id)
385 {
386   GstRTSPAddressPoolPrivate *priv;
387   GList *find;
388
389   g_return_if_fail (GST_IS_RTSP_ADDRESS_POOL (pool));
390   g_return_if_fail (id != NULL);
391
392   priv = pool->priv;
393
394   g_mutex_lock (&priv->lock);
395   find = g_list_find (priv->allocated, id);
396   if (find == NULL)
397     goto not_found;
398
399   priv->allocated = g_list_delete_link (priv->allocated, find);
400
401   /* FIXME, merge and do something clever */
402   priv->addresses = g_list_prepend (priv->addresses, id);
403   g_mutex_unlock (&priv->lock);
404
405   return;
406
407   /* ERRORS */
408 not_found:
409   {
410     g_warning ("Released unknown id %p", id);
411     g_mutex_unlock (&priv->lock);
412     return;
413   }
414 }
415
416 static void
417 dump_range (AddrRange * range, GstRTSPAddressPool * pool)
418 {
419   gchar *addr1, *addr2;
420
421   addr1 = get_address_string (&range->min);
422   addr2 = get_address_string (&range->max);
423   g_print ("  address %s-%s, port %u-%u, ttl %u\n", addr1, addr2,
424       range->min.port, range->max.port, range->ttl);
425   g_free (addr1);
426   g_free (addr2);
427 }
428
429 /**
430  * gst_rtsp_address_pool_dump:
431  * @pool: a #GstRTSPAddressPool
432  *
433  * Dump the free and allocated addresses to stdout.
434  */
435 void
436 gst_rtsp_address_pool_dump (GstRTSPAddressPool * pool)
437 {
438   GstRTSPAddressPoolPrivate *priv;
439
440   g_return_if_fail (GST_IS_RTSP_ADDRESS_POOL (pool));
441
442   priv = pool->priv;
443
444   g_mutex_lock (&priv->lock);
445   g_print ("free:\n");
446   g_list_foreach (priv->addresses, (GFunc) dump_range, pool);
447   g_print ("allocated:\n");
448   g_list_foreach (priv->allocated, (GFunc) dump_range, pool);
449   g_mutex_unlock (&priv->lock);
450 }