address-pool: add object to manage multicast addresses
[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 static gboolean
124 fill_address (const gchar * address, guint16 port, Addr * addr)
125 {
126   GInetAddress *inet;
127
128   inet = g_inet_address_new_from_string (address);
129   if (inet == NULL)
130     return FALSE;
131
132   addr->size = g_inet_address_get_native_size (inet);
133   memcpy (addr->bytes, g_inet_address_to_bytes (inet), addr->size);
134   g_object_unref (inet);
135   addr->port = port;
136
137   return TRUE;
138 }
139
140 /**
141  * gst_rtsp_address_pool_add_range:
142  * @pool: a #GstRTSPAddressPool
143  * @min_address: a minimum address to add
144  * @max_address: a maximum address to add
145  * @min_port: the minimum port
146  * @max_port: the maximum port
147  * @ttl: a TTL
148  *
149  * Adds the multicast addresses from @min_addess to @max_address (inclusive)
150  * to @pool. The valid port range for the addresses will be from @min_port to
151  * @max_port inclusive.
152  *
153  * Returns: %TRUE if the addresses could be added.
154  */
155 gboolean
156 gst_rtsp_address_pool_add_range (GstRTSPAddressPool * pool,
157     const gchar * min_address, const gchar * max_address,
158     guint16 min_port, guint16 max_port, guint8 ttl)
159 {
160   AddrRange *range;
161   GstRTSPAddressPoolPrivate *priv;
162
163   g_return_val_if_fail (GST_IS_RTSP_ADDRESS_POOL (pool), FALSE);
164   g_return_val_if_fail (min_port <= max_port, FALSE);
165
166   priv = pool->priv;
167
168   range = g_slice_new0 (AddrRange);
169
170   if (!fill_address (min_address, min_port, &range->min))
171     goto invalid;
172   if (!fill_address (max_address, max_port, &range->max))
173     goto invalid;
174
175   if (range->min.size != range->max.size)
176     goto invalid;
177   if (memcmp (range->min.bytes, range->max.bytes, range->min.size) > 0)
178     goto invalid;
179
180   range->ttl = ttl;
181
182   GST_DEBUG_OBJECT (pool, "adding %s-%s:%u-%u ttl %u", min_address, max_address,
183       min_port, max_port, ttl);
184
185   g_mutex_lock (&priv->lock);
186   priv->addresses = g_list_prepend (priv->addresses, range);
187   g_mutex_unlock (&priv->lock);
188
189   return TRUE;
190
191   /* ERRORS */
192 invalid:
193   {
194     GST_ERROR_OBJECT (pool, "invalid address range %s-%s", min_address,
195         max_address);
196     g_slice_free (AddrRange, range);
197     return FALSE;
198   }
199 }
200
201 static void
202 inc_address (GstRTSPAddressPool * pool, Addr * addr)
203 {
204   gint i;
205   guint carry;
206
207   carry = 1;
208   for (i = addr->size - 1; i >= 0 && carry > 0; i--) {
209     carry += addr->bytes[i];
210     addr->bytes[i] = carry & 0xff;
211     carry >>= 8;
212   }
213 }
214
215 static AddrRange *
216 split_range (GstRTSPAddressPool * pool, AddrRange * range, gint skip,
217     gint n_ports)
218 {
219   GstRTSPAddressPoolPrivate *priv = pool->priv;
220   AddrRange *temp;
221
222   if (!RANGE_IS_SINGLE (range)) {
223     /* min and max are not the same, we have more than one address. */
224     temp = g_slice_dup (AddrRange, range);
225     /* increment the range min address */
226     inc_address (pool, &temp->min);
227     /* and store back in pool */
228     priv->addresses = g_list_prepend (priv->addresses, temp);
229
230     /* adjust range with only the first address */
231     memcpy (range->max.bytes, range->min.bytes, range->min.size);
232   }
233
234   /* range now contains only one single address */
235   if (skip > 0) {
236     /* make a range with the skipped ports */
237     temp = g_slice_dup (AddrRange, range);
238     temp->max.port = temp->min.port + skip;
239     /* and store back in pool */
240     priv->addresses = g_list_prepend (priv->addresses, temp);
241
242     /* increment range port */
243     range->min.port += skip;
244   }
245   /* range now contains single address with desired port number */
246   if (range->max.port - range->min.port + 1 > n_ports) {
247     /* make a range with the remaining ports */
248     temp = g_slice_dup (AddrRange, range);
249     temp->min.port += n_ports;
250     /* and store back in pool */
251     priv->addresses = g_list_prepend (priv->addresses, temp);
252
253     /* and truncate port */
254     range->max.port = range->min.port + n_ports - 1;
255   }
256   return range;
257 }
258
259 /**
260  * gst_rtsp_address_pool_acquire_address:
261  * @pool: a #GstRTSPAddressPool
262  * @flags: flags
263  * @n_ports: the amount of ports
264  * @address: result address
265  * @port: result port
266  * @ttl: result TTL
267  *
268  * Take an address and ports from @pool. @flags can be used to control the
269  * allocation. @n_ports consecutive ports will be allocated of which the first
270  * one can be found in @port.
271  *
272  * Returns: a pointer that should be used to release the address with
273  *   gst_rtsp_address_pool_release_address() after usage or %NULL when no
274  *   address could be acquired.
275  */
276 gpointer
277 gst_rtsp_address_pool_acquire_address (GstRTSPAddressPool * pool,
278     GstRTSPAddressFlags flags, gint n_ports, gchar ** address,
279     guint16 * port, guint8 * ttl)
280 {
281   GstRTSPAddressPoolPrivate *priv;
282   GList *walk, *next;
283   AddrRange *result;
284
285   g_return_val_if_fail (GST_IS_RTSP_ADDRESS_POOL (pool), NULL);
286   g_return_val_if_fail (n_ports > 0, NULL);
287   g_return_val_if_fail (address != NULL, NULL);
288   g_return_val_if_fail (port != NULL, NULL);
289   g_return_val_if_fail (ttl != NULL, NULL);
290
291   priv = pool->priv;
292   result = NULL;
293
294   g_mutex_lock (&priv->lock);
295   /* go over available ranges */
296   for (walk = priv->addresses; walk; walk = next) {
297     AddrRange *range;
298     gint ports, skip;
299
300     range = walk->data;
301     next = walk->next;
302
303     /* check address type when given */
304     if (flags & GST_RTSP_ADDRESS_FLAG_IPV4 && !ADDR_IS_IPV4 (&range->min))
305       continue;
306     if (flags & GST_RTSP_ADDRESS_FLAG_IPV6 && !ADDR_IS_IPV6 (&range->min))
307       continue;
308
309     /* check for enough ports */
310     ports = range->max.port - range->min.port + 1;
311     if (flags & GST_RTSP_ADDRESS_FLAG_EVEN_PORT
312         && !ADDR_IS_EVEN_PORT (&range->min))
313       skip = 1;
314     else
315       skip = 0;
316     if (ports - skip < n_ports)
317       continue;
318
319     /* we found a range, remove from the list */
320     priv->addresses = g_list_delete_link (priv->addresses, walk);
321     /* now split and exit our loop */
322     result = split_range (pool, range, skip, n_ports);
323     priv->allocated = g_list_prepend (priv->allocated, result);
324     break;
325   }
326   g_mutex_unlock (&priv->lock);
327
328   if (result) {
329     GInetAddress *inet;
330
331     inet = g_inet_address_new_from_bytes (result->min.bytes,
332         result->min.size == 4 ? G_SOCKET_FAMILY_IPV4 : G_SOCKET_FAMILY_IPV6);
333
334     *address = g_inet_address_to_string (inet);
335     g_object_unref (inet);
336
337     *port = result->min.port;
338     *ttl = result->ttl;
339
340     GST_DEBUG_OBJECT (pool, "got address %s:%u ttl %u", *address, *port, *ttl);
341   }
342   return result;
343 }
344
345 /**
346  * gst_rtsp_address_pool_release_address:
347  * @pool: a #GstRTSPAddressPool
348  * @id: an address id
349  *
350  * Release a previously acquired address (with
351  * gst_rtsp_address_pool_acquire_address()) back into @pool.
352  */
353 void
354 gst_rtsp_address_pool_release_address (GstRTSPAddressPool * pool, gpointer id)
355 {
356   GstRTSPAddressPoolPrivate *priv;
357   GList *find;
358
359   g_return_if_fail (GST_IS_RTSP_ADDRESS_POOL (pool));
360   g_return_if_fail (id != NULL);
361
362   priv = pool->priv;
363
364   g_mutex_lock (&priv->lock);
365   find = g_list_find (priv->allocated, id);
366   if (find == NULL)
367     goto not_found;
368
369   priv->allocated = g_list_delete_link (priv->allocated, find);
370
371   /* FIXME, merge and do something clever */
372   priv->addresses = g_list_prepend (priv->addresses, id);
373   g_mutex_unlock (&priv->lock);
374
375   return;
376
377   /* ERRORS */
378 not_found:
379   {
380     g_mutex_unlock (&priv->lock);
381     return;
382   }
383 }
384
385 static void
386 dump_range (GstRTSPAddressPool * pool, AddrRange * range)
387 {
388   GInetAddress *inet;
389   gchar *addr1, *addr2;
390
391   inet = g_inet_address_new_from_bytes (range->min.bytes,
392       range->max.size == 4 ? G_SOCKET_FAMILY_IPV4 : G_SOCKET_FAMILY_IPV6);
393   addr1 = g_inet_address_to_string (inet);
394   g_object_unref (inet);
395
396   inet = g_inet_address_new_from_bytes (range->max.bytes,
397       range->max.size == 4 ? G_SOCKET_FAMILY_IPV4 : G_SOCKET_FAMILY_IPV6);
398   addr2 = g_inet_address_to_string (inet);
399   g_object_unref (inet);
400
401   g_print ("  address %s-%s, port %u-%u, ttl %u\n", addr1, addr2,
402       range->min.port, range->max.port, range->ttl);
403
404   g_free (addr1);
405   g_free (addr2);
406 }
407
408 void
409 gst_rtsp_address_pool_dump (GstRTSPAddressPool * pool)
410 {
411   GstRTSPAddressPoolPrivate *priv;
412   GList *walk;
413
414   g_return_if_fail (GST_IS_RTSP_ADDRESS_POOL (pool));
415
416   priv = pool->priv;
417
418   g_mutex_lock (&priv->lock);
419   g_print ("free:\n");
420   for (walk = priv->addresses; walk; walk = walk->next) {
421     dump_range (pool, (AddrRange *) walk->data);
422   }
423   g_print ("allocated:\n");
424   for (walk = priv->allocated; walk; walk = walk->next) {
425     dump_range (pool, (AddrRange *) walk->data);
426   }
427   g_mutex_unlock (&priv->lock);
428 }