address-pool: Add API to request a specific address from the pool
[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 GstRTSPAddress *
26 gst_rtsp_address_copy (GstRTSPAddress * addr)
27 {
28   GstRTSPAddress *copy;
29
30   g_return_val_if_fail (addr != NULL, NULL);
31
32   copy = g_slice_dup (GstRTSPAddress, addr);
33   /* only release to the pool when the original is freed. It's a bit
34    * weird but this will do for now as it avoid us to use refcounting. */
35   copy->pool = NULL;
36   copy->address = g_strdup (copy->address);
37
38   return copy;
39 }
40
41 static void gst_rtsp_address_pool_release_address (GstRTSPAddressPool * pool,
42     GstRTSPAddress * addr);
43
44 void
45 gst_rtsp_address_free (GstRTSPAddress * addr)
46 {
47   g_return_if_fail (addr != NULL);
48
49   if (addr->pool) {
50     /* unrefs the pool and sets it to NULL */
51     gst_rtsp_address_pool_release_address (addr->pool, addr);
52   }
53   g_free (addr->address);
54   g_slice_free (GstRTSPAddress, addr);
55 }
56
57
58 G_DEFINE_BOXED_TYPE (GstRTSPAddress, gst_rtsp_address,
59     (GBoxedCopyFunc) gst_rtsp_address_copy,
60     (GBoxedFreeFunc) gst_rtsp_address_free);
61
62 GST_DEBUG_CATEGORY_STATIC (rtsp_address_pool_debug);
63 #define GST_CAT_DEFAULT rtsp_address_pool_debug
64
65 #define GST_RTSP_ADDRESS_POOL_GET_PRIVATE(obj)  \
66      (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_RTSP_ADDRESS_POOL, GstRTSPAddressPoolPrivate))
67
68 struct _GstRTSPAddressPoolPrivate
69 {
70   GMutex lock;
71   GList *addresses;
72   GList *allocated;
73 };
74
75 #define ADDR_IS_IPV4(a)      ((a)->size == 4)
76 #define ADDR_IS_IPV6(a)      ((a)->size == 16)
77 #define ADDR_IS_EVEN_PORT(a) (((a)->port & 1) == 0)
78
79 typedef struct
80 {
81   guint8 bytes[16];
82   gsize size;
83   guint16 port;
84 } Addr;
85
86 typedef struct
87 {
88   Addr min;
89   Addr max;
90   guint8 ttl;
91 } AddrRange;
92
93 #define RANGE_IS_SINGLE(r) (memcmp ((r)->min.bytes, (r)->max.bytes, (r)->min.size) == 0)
94
95 #define gst_rtsp_address_pool_parent_class parent_class
96 G_DEFINE_TYPE (GstRTSPAddressPool, gst_rtsp_address_pool, G_TYPE_OBJECT);
97
98 static void gst_rtsp_address_pool_finalize (GObject * obj);
99
100 static void
101 gst_rtsp_address_pool_class_init (GstRTSPAddressPoolClass * klass)
102 {
103   GObjectClass *gobject_class;
104
105   gobject_class = G_OBJECT_CLASS (klass);
106
107   gobject_class->finalize = gst_rtsp_address_pool_finalize;
108
109   g_type_class_add_private (klass, sizeof (GstRTSPAddressPoolPrivate));
110
111   GST_DEBUG_CATEGORY_INIT (rtsp_address_pool_debug, "rtspaddresspool", 0,
112       "GstRTSPAddressPool");
113 }
114
115 static void
116 gst_rtsp_address_pool_init (GstRTSPAddressPool * pool)
117 {
118   pool->priv = GST_RTSP_ADDRESS_POOL_GET_PRIVATE (pool);
119
120   g_mutex_init (&pool->priv->lock);
121 }
122
123 static void
124 free_range (AddrRange * range)
125 {
126   g_slice_free (AddrRange, range);
127 }
128
129 static void
130 gst_rtsp_address_pool_finalize (GObject * obj)
131 {
132   GstRTSPAddressPool *pool;
133
134   pool = GST_RTSP_ADDRESS_POOL (obj);
135
136   g_list_free_full (pool->priv->addresses, (GDestroyNotify) free_range);
137   g_list_free_full (pool->priv->allocated, (GDestroyNotify) free_range);
138   g_mutex_clear (&pool->priv->lock);
139
140   G_OBJECT_CLASS (parent_class)->finalize (obj);
141 }
142
143 /**
144  * gst_rtsp_address_pool_new:
145  *
146  * Make a new #GstRTSPAddressPool.
147  *
148  * Returns: a new #GstRTSPAddressPool
149  */
150 GstRTSPAddressPool *
151 gst_rtsp_address_pool_new (void)
152 {
153   GstRTSPAddressPool *pool;
154
155   pool = g_object_new (GST_TYPE_RTSP_ADDRESS_POOL, NULL);
156
157   return pool;
158 }
159
160 /**
161  * gst_rtsp_address_pool_clear:
162  * @pool: a #GstRTSPAddressPool
163  *
164  * Clear all addresses in @pool. There should be no outstanding
165  * allocations.
166  */
167 void
168 gst_rtsp_address_pool_clear (GstRTSPAddressPool * pool)
169 {
170   GstRTSPAddressPoolPrivate *priv;
171
172   g_return_if_fail (GST_IS_RTSP_ADDRESS_POOL (pool));
173   g_return_if_fail (pool->priv->allocated == NULL);
174
175   priv = pool->priv;
176
177   g_mutex_lock (&priv->lock);
178   g_list_free_full (priv->addresses, (GDestroyNotify) free_range);
179   priv->addresses = NULL;
180   g_mutex_unlock (&priv->lock);
181 }
182
183 static gboolean
184 fill_address (const gchar * address, guint16 port, Addr * addr)
185 {
186   GInetAddress *inet;
187
188   inet = g_inet_address_new_from_string (address);
189   if (inet == NULL)
190     return FALSE;
191
192   addr->size = g_inet_address_get_native_size (inet);
193   memcpy (addr->bytes, g_inet_address_to_bytes (inet), addr->size);
194   g_object_unref (inet);
195   addr->port = port;
196
197   return TRUE;
198 }
199
200 static gchar *
201 get_address_string (Addr * addr)
202 {
203   gchar *res;
204   GInetAddress *inet;
205
206   inet = g_inet_address_new_from_bytes (addr->bytes,
207       addr->size == 4 ? G_SOCKET_FAMILY_IPV4 : G_SOCKET_FAMILY_IPV6);
208   res = g_inet_address_to_string (inet);
209   g_object_unref (inet);
210
211   return res;
212 }
213
214 /**
215  * gst_rtsp_address_pool_add_range:
216  * @pool: a #GstRTSPAddressPool
217  * @min_address: a minimum address to add
218  * @max_address: a maximum address to add
219  * @min_port: the minimum port
220  * @max_port: the maximum port
221  * @ttl: a TTL
222  *
223  * Adds the multicast addresses from @min_addess to @max_address (inclusive)
224  * to @pool. The valid port range for the addresses will be from @min_port to
225  * @max_port inclusive.
226  *
227  * Returns: %TRUE if the addresses could be added.
228  */
229 gboolean
230 gst_rtsp_address_pool_add_range (GstRTSPAddressPool * pool,
231     const gchar * min_address, const gchar * max_address,
232     guint16 min_port, guint16 max_port, guint8 ttl)
233 {
234   AddrRange *range;
235   GstRTSPAddressPoolPrivate *priv;
236
237   g_return_val_if_fail (GST_IS_RTSP_ADDRESS_POOL (pool), FALSE);
238   g_return_val_if_fail (min_port <= max_port, FALSE);
239
240   priv = pool->priv;
241
242   range = g_slice_new0 (AddrRange);
243
244   if (!fill_address (min_address, min_port, &range->min))
245     goto invalid;
246   if (!fill_address (max_address, max_port, &range->max))
247     goto invalid;
248
249   if (range->min.size != range->max.size)
250     goto invalid;
251   if (memcmp (range->min.bytes, range->max.bytes, range->min.size) > 0)
252     goto invalid;
253
254   range->ttl = ttl;
255
256   GST_DEBUG_OBJECT (pool, "adding %s-%s:%u-%u ttl %u", min_address, max_address,
257       min_port, max_port, ttl);
258
259   g_mutex_lock (&priv->lock);
260   priv->addresses = g_list_prepend (priv->addresses, range);
261   g_mutex_unlock (&priv->lock);
262
263   return TRUE;
264
265   /* ERRORS */
266 invalid:
267   {
268     GST_ERROR_OBJECT (pool, "invalid address range %s-%s", min_address,
269         max_address);
270     g_slice_free (AddrRange, range);
271     return FALSE;
272   }
273 }
274
275 /* increments the address by count */
276
277 static void
278 inc_address (Addr * addr, guint8 count)
279 {
280   gint i;
281   guint carry;
282
283   carry = count;
284   for (i = addr->size - 1; i >= 0 && carry > 0; i--) {
285     carry += addr->bytes[i];
286     addr->bytes[i] = carry & 0xff;
287     carry >>= 8;
288   }
289 }
290
291 /* tells us the number of addresses between min_addr and max_addr */
292
293 static guint
294 diff_address (Addr * max_addr, Addr * min_addr)
295 {
296   gint i;
297   guint result = 0;
298
299   g_return_val_if_fail (min_addr->size == max_addr->size, 0);
300
301   for (i = 0; i < min_addr->size; i++) {
302     g_return_val_if_fail (result < (1 << 24), result);
303
304     result <<= 8;
305     result += max_addr->bytes[i] - min_addr->bytes[i];
306   }
307
308   return result;
309 }
310
311
312 static AddrRange *
313 split_range (GstRTSPAddressPool * pool, AddrRange * range, guint skip_addr,
314     guint skip_port, gint n_ports)
315 {
316   GstRTSPAddressPoolPrivate *priv = pool->priv;
317   AddrRange *temp;
318
319   if (skip_addr) {
320     temp = g_slice_dup (AddrRange, range);
321     memcpy (temp->max.bytes, temp->min.bytes, temp->min.size);
322     inc_address (&temp->max, skip_addr - 1);
323     priv->addresses = g_list_prepend (priv->addresses, temp);
324
325     inc_address (&range->min, skip_addr);
326   }
327
328   if (!RANGE_IS_SINGLE (range)) {
329     /* min and max are not the same, we have more than one address. */
330     temp = g_slice_dup (AddrRange, range);
331     /* increment the range min address */
332     inc_address (&temp->min, 1);
333     /* and store back in pool */
334     priv->addresses = g_list_prepend (priv->addresses, temp);
335
336     /* adjust range with only the first address */
337     memcpy (range->max.bytes, range->min.bytes, range->min.size);
338   }
339
340   /* range now contains only one single address */
341   if (skip_port > 0) {
342     /* make a range with the skipped ports */
343     temp = g_slice_dup (AddrRange, range);
344     temp->max.port = temp->min.port + skip_port - 1;
345     /* and store back in pool */
346     priv->addresses = g_list_prepend (priv->addresses, temp);
347
348     /* increment range port */
349     range->min.port += skip_port;
350   }
351   /* range now contains single address with desired port number */
352   if (range->max.port - range->min.port + 1 > n_ports) {
353     /* make a range with the remaining ports */
354     temp = g_slice_dup (AddrRange, range);
355     temp->min.port += n_ports;
356     /* and store back in pool */
357     priv->addresses = g_list_prepend (priv->addresses, temp);
358
359     /* and truncate port */
360     range->max.port = range->min.port + n_ports - 1;
361   }
362   return range;
363 }
364
365 /**
366  * gst_rtsp_address_pool_acquire_address:
367  * @pool: a #GstRTSPAddressPool
368  * @flags: flags
369  * @n_ports: the amount of ports
370  *
371  * Take an address and ports from @pool. @flags can be used to control the
372  * allocation. @n_ports consecutive ports will be allocated of which the first
373  * one can be found in @port.
374  *
375  * Returns: a #GstRTSPAddress that should be freed with gst_rtsp_address_free
376  *   after use or %NULL when no address could be acquired.
377  */
378 GstRTSPAddress *
379 gst_rtsp_address_pool_acquire_address (GstRTSPAddressPool * pool,
380     GstRTSPAddressFlags flags, gint n_ports)
381 {
382   GstRTSPAddressPoolPrivate *priv;
383   GList *walk, *next;
384   AddrRange *result;
385   GstRTSPAddress *addr;
386
387   g_return_val_if_fail (GST_IS_RTSP_ADDRESS_POOL (pool), NULL);
388   g_return_val_if_fail (n_ports > 0, NULL);
389
390   priv = pool->priv;
391   result = NULL;
392   addr = NULL;
393
394   g_mutex_lock (&priv->lock);
395   /* go over available ranges */
396   for (walk = priv->addresses; walk; walk = next) {
397     AddrRange *range;
398     gint ports, skip;
399
400     range = walk->data;
401     next = walk->next;
402
403     /* check address type when given */
404     if (flags & GST_RTSP_ADDRESS_FLAG_IPV4 && !ADDR_IS_IPV4 (&range->min))
405       continue;
406     if (flags & GST_RTSP_ADDRESS_FLAG_IPV6 && !ADDR_IS_IPV6 (&range->min))
407       continue;
408
409     /* check for enough ports */
410     ports = range->max.port - range->min.port + 1;
411     if (flags & GST_RTSP_ADDRESS_FLAG_EVEN_PORT
412         && !ADDR_IS_EVEN_PORT (&range->min))
413       skip = 1;
414     else
415       skip = 0;
416     if (ports - skip < n_ports)
417       continue;
418
419     /* we found a range, remove from the list */
420     priv->addresses = g_list_delete_link (priv->addresses, walk);
421     /* now split and exit our loop */
422     result = split_range (pool, range, 0, skip, n_ports);
423     priv->allocated = g_list_prepend (priv->allocated, result);
424     break;
425   }
426   g_mutex_unlock (&priv->lock);
427
428   if (result) {
429     addr = g_slice_new0 (GstRTSPAddress);
430     addr->pool = g_object_ref (pool);
431     addr->address = get_address_string (&result->min);
432     addr->n_ports = n_ports;
433     addr->port = result->min.port;
434     addr->ttl = result->ttl;
435     addr->priv = result;
436
437     GST_DEBUG_OBJECT (pool, "got address %s:%u ttl %u", addr->address,
438         addr->port, addr->ttl);
439   }
440
441   return addr;
442 }
443
444 /**
445  * gst_rtsp_address_pool_release_address:
446  * @pool: a #GstRTSPAddressPool
447  * @id: an address id
448  *
449  * Release a previously acquired address (with
450  * gst_rtsp_address_pool_acquire_address()) back into @pool.
451  */
452 static void
453 gst_rtsp_address_pool_release_address (GstRTSPAddressPool * pool,
454     GstRTSPAddress * addr)
455 {
456   GstRTSPAddressPoolPrivate *priv;
457   GList *find;
458   AddrRange *range;
459
460   g_return_if_fail (GST_IS_RTSP_ADDRESS_POOL (pool));
461   g_return_if_fail (addr != NULL);
462   g_return_if_fail (addr->pool == pool);
463
464   priv = pool->priv;
465   range = addr->priv;
466
467   /* we don't want to free twice */
468   addr->priv = NULL;
469   addr->pool = NULL;
470
471   g_mutex_lock (&priv->lock);
472   find = g_list_find (priv->allocated, range);
473   if (find == NULL)
474     goto not_found;
475
476   priv->allocated = g_list_delete_link (priv->allocated, find);
477
478   /* FIXME, merge and do something clever */
479   priv->addresses = g_list_prepend (priv->addresses, range);
480   g_mutex_unlock (&priv->lock);
481
482   g_object_unref (pool);
483
484   return;
485
486   /* ERRORS */
487 not_found:
488   {
489     g_warning ("Released unknown address %p", addr);
490     g_mutex_unlock (&priv->lock);
491     return;
492   }
493 }
494
495 static void
496 dump_range (AddrRange * range, GstRTSPAddressPool * pool)
497 {
498   gchar *addr1, *addr2;
499
500   addr1 = get_address_string (&range->min);
501   addr2 = get_address_string (&range->max);
502   g_print ("  address %s-%s, port %u-%u, ttl %u\n", addr1, addr2,
503       range->min.port, range->max.port, range->ttl);
504   g_free (addr1);
505   g_free (addr2);
506 }
507
508 /**
509  * gst_rtsp_address_pool_dump:
510  * @pool: a #GstRTSPAddressPool
511  *
512  * Dump the free and allocated addresses to stdout.
513  */
514 void
515 gst_rtsp_address_pool_dump (GstRTSPAddressPool * pool)
516 {
517   GstRTSPAddressPoolPrivate *priv;
518
519   g_return_if_fail (GST_IS_RTSP_ADDRESS_POOL (pool));
520
521   priv = pool->priv;
522
523   g_mutex_lock (&priv->lock);
524   g_print ("free:\n");
525   g_list_foreach (priv->addresses, (GFunc) dump_range, pool);
526   g_print ("allocated:\n");
527   g_list_foreach (priv->allocated, (GFunc) dump_range, pool);
528   g_mutex_unlock (&priv->lock);
529 }
530
531 /**
532  * gst_rtsp_address_pool_reserve_address:
533  * @pool: a #GstRTSPAddressPool
534  * @address: The IP address to reserve
535  * @port: The first port to reserve
536  * @n_ports: The number of ports
537  * @ttl: The requested ttl
538  *
539  * Take a specific address and ports from @pool. @n_ports consecutive
540  * ports will be allocated of which the first one can be found in
541  * @port.
542  *
543  * Returns: a #GstRTSPAddress that should be freed with gst_rtsp_address_free
544  *   after use or %NULL when no address could be acquired.
545  */
546
547 GstRTSPAddress *
548 gst_rtsp_address_pool_reserve_address (GstRTSPAddressPool * pool,
549     const gchar * address, guint port, guint n_ports, guint ttl)
550 {
551   GstRTSPAddressPoolPrivate *priv;
552   Addr input_addr;
553   GList *walk, *next;
554   AddrRange *result;
555   GstRTSPAddress *addr;
556
557   g_return_val_if_fail (GST_IS_RTSP_ADDRESS_POOL (pool), NULL);
558   g_return_val_if_fail (address != NULL, NULL);
559   g_return_val_if_fail (port > 0, NULL);
560   g_return_val_if_fail (n_ports > 0, NULL);
561
562   priv = pool->priv;
563   result = NULL;
564   addr = NULL;
565
566   if (!fill_address (address, port, &input_addr)) {
567     GST_ERROR_OBJECT (pool, "invalid address %s", address);
568     return NULL;
569   }
570
571   g_mutex_lock (&priv->lock);
572   /* go over available ranges */
573   for (walk = priv->addresses; walk; walk = next) {
574     AddrRange *range;
575     gint skip_port, skip_addr;
576
577     range = walk->data;
578     next = walk->next;
579
580     /* Not the right type of address */
581     if (range->min.size != input_addr.size)
582       continue;
583
584     /* Check that the address is in the interval */
585     if (memcmp (range->min.bytes, input_addr.bytes, input_addr.size) > 0 ||
586         memcmp (range->max.bytes, input_addr.bytes, input_addr.size) < 0)
587       continue;
588
589     /* Make sure the requested ports are inside the range */
590     if (port < range->min.port || port + n_ports - 1 > range->max.port)
591       continue;
592
593     if (ttl != range->ttl)
594       continue;
595
596     skip_addr = diff_address (&input_addr, &range->min);
597     skip_port = port - range->min.port;
598
599     /* we found a range, remove from the list */
600     priv->addresses = g_list_delete_link (priv->addresses, walk);
601     /* now split and exit our loop */
602     result = split_range (pool, range, skip_addr, skip_port, n_ports);
603     priv->allocated = g_list_prepend (priv->allocated, result);
604     break;
605   }
606   g_mutex_unlock (&priv->lock);
607
608   if (result) {
609     addr = g_slice_new0 (GstRTSPAddress);
610     addr->pool = g_object_ref (pool);
611     addr->address = get_address_string (&result->min);
612     addr->n_ports = n_ports;
613     addr->port = result->min.port;
614     addr->ttl = result->ttl;
615     addr->priv = result;
616
617     GST_DEBUG_OBJECT (pool, "reserved address %s:%u ttl %u", addr->address,
618         addr->port, addr->ttl);
619   }
620
621   return addr;
622 }