]> sjero.net Git - linphone/blob - coreapi/upnp.c
Fix bug in upnp string compare function
[linphone] / coreapi / upnp.c
1 /*
2 linphone
3 Copyright (C) 2012  Belledonne Communications SARL
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 */
19
20 #include "upnp.h"
21 #include "private.h"
22 #include "lpconfig.h"
23 #include <ctype.h>
24
25 #define UPNP_STRINGIFY(x)     #x
26 #define UPNP_TOSTRING(x)      UPNP_STRINGIFY(x)
27
28 #define UPNP_ADD_MAX_RETRY    4
29 #define UPNP_REMOVE_MAX_RETRY 4
30 #define UPNP_SECTION_NAME     "uPnP"
31 #define UPNP_CORE_READY_CHECK 1 
32 #define UPNP_CORE_RETRY_DELAY 4
33 #define UPNP_CALL_RETRY_DELAY 1
34 #define UPNP_UUID_LEN         32
35 #define UPNP_UUID_LEN_STR     UPNP_TOSTRING(UPNP_UUID_LEN)
36 /*
37  * uPnP Definitions
38  */
39
40 typedef struct _UpnpPortBinding {
41         ms_mutex_t mutex;
42         LinphoneUpnpState state;
43         upnp_igd_ip_protocol protocol;
44         char *device_id;
45         char local_addr[LINPHONE_IPADDR_SIZE];
46         int local_port;
47         char external_addr[LINPHONE_IPADDR_SIZE];
48         int external_port;
49         int retry;
50         int ref;
51         bool_t to_remove;
52         bool_t to_add;
53         time_t last_update;
54 } UpnpPortBinding;
55
56 typedef struct _UpnpStream {
57         UpnpPortBinding *rtp;
58         UpnpPortBinding *rtcp;
59         LinphoneUpnpState state;
60 } UpnpStream;
61
62 struct _UpnpSession {
63         LinphoneCall *call;
64         UpnpStream *audio;
65         UpnpStream *video;
66         LinphoneUpnpState state;
67 };
68
69 struct _UpnpContext {
70         LinphoneCore *lc;
71         upnp_igd_context *upnp_igd_ctxt;
72         UpnpPortBinding *sip_tcp;
73         UpnpPortBinding *sip_tls;
74         UpnpPortBinding *sip_udp;
75         LinphoneUpnpState state;
76         MSList *removing_configs;
77         MSList *adding_configs;
78         MSList *pending_bindings;
79
80         ms_mutex_t mutex;
81         ms_cond_t empty_cond;
82         
83         time_t last_ready_check;
84         LinphoneUpnpState last_ready_state;
85 };
86
87
88 bool_t linphone_core_upnp_hook(void *data);
89 void linphone_core_upnp_refresh(UpnpContext *ctx);
90
91 UpnpPortBinding *linphone_upnp_port_binding_new();
92 UpnpPortBinding *linphone_upnp_port_binding_new_with_parameters(upnp_igd_ip_protocol protocol, int local_port, int external_port);
93 UpnpPortBinding *linphone_upnp_port_binding_new_or_collect(MSList *list, upnp_igd_ip_protocol protocol, int local_port, int external_port); 
94 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port);
95 void linphone_upnp_port_binding_set_device_id(UpnpPortBinding *port, const char * device_id);
96 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2);
97 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port);
98 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port);
99 void linphone_upnp_update_port_binding(UpnpContext *lupnp, UpnpPortBinding **port_mapping, upnp_igd_ip_protocol protocol, int port, int retry_delay); 
100 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port);
101 void linphone_upnp_port_binding_release(UpnpPortBinding *port);
102 void linphone_upnp_update_config(UpnpContext *lupnp);
103 void linphone_upnp_update_proxy(UpnpContext *lupnp, bool_t force);
104
105 // Configuration
106 MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc, const char *device_id);
107 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port);
108 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port);
109
110 // uPnP 
111 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry);
112 int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry);
113
114 static int linphone_upnp_strncmpi(const char *str1, const char *str2, int len) {
115         int i = 0;
116         char char1, char2;
117         while(true) {
118                 if(i >= len) {
119                         return 0;
120                 }
121                 char1 = toupper(*str1);
122                 char2 = toupper(*str2);
123                 if(char1 == '\0' || char2 == '\0' || char1 != char2) {
124                         return char1 - char2;
125                 }
126                 str1++;
127                 str2++;
128                 i++;
129         }
130 }
131
132 static int linphone_upnp_str_min(const char *str1, const char *str2) {
133         int len1 = strlen(str1);
134         int len2 = strlen(str2);
135         if(len1 > len2) {
136                 return len2;
137         }
138         return len1;
139 }
140
141 char * linphone_upnp_format_device_id(const char *device_id) {
142         char *ret = NULL;
143         char *tmp;
144         char tchar;
145         bool_t copy;    
146         if(device_id == NULL) {
147                 return ret;
148         }
149         ret = ms_new(char, UPNP_UUID_LEN + 1);
150         tmp = ret;
151         if(linphone_upnp_strncmpi(device_id, "uuid:", linphone_upnp_str_min(device_id, "uuid:")) == 0) {
152                 device_id += strlen("uuid:");
153         }
154         while(*device_id != '\0' && tmp - ret < UPNP_UUID_LEN) {
155                 copy = FALSE;
156                 tchar = *device_id;
157                 if(tchar >= '0' && tchar <= '9')
158                         copy = TRUE;
159                 if(!copy && tchar >= 'A' && tchar <= 'Z')
160                         copy = TRUE;
161                 if(!copy && tchar >= 'a' && tchar <= 'z')
162                         copy = TRUE;
163                 if(copy) {
164                         *tmp = *device_id;
165                         tmp++;
166                 }
167                 device_id++;
168         }
169         *tmp = '\0';
170         return ret;
171 }
172
173 /**
174  * uPnP Callbacks
175  */
176
177 /* Convert uPnP IGD logs to ortp logs */
178 void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list) {
179         int ortp_level = ORTP_DEBUG;
180         switch(level) {
181         case UPNP_IGD_MESSAGE:
182                 ortp_level = ORTP_MESSAGE;
183                 break;
184         case UPNP_IGD_WARNING:
185                 ortp_level = ORTP_DEBUG; // Too verbose otherwise
186                 break;
187         case UPNP_IGD_ERROR:
188                 ortp_level = ORTP_DEBUG; // Too verbose otherwise
189                 break;
190         default:
191                 break;
192         }
193         ortp_logv(ortp_level, fmt, list);
194 }
195
196 void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
197         UpnpContext *lupnp = (UpnpContext *)cookie;
198         upnp_igd_port_mapping *mapping = NULL;
199         UpnpPortBinding *port_mapping = NULL;
200         const char *ip_address = NULL;
201         const char *connection_status = NULL;
202         bool_t nat_enabled = FALSE;
203         LinphoneUpnpState old_state;
204
205         if(lupnp == NULL || lupnp->upnp_igd_ctxt == NULL) {
206                 ms_error("uPnP IGD: Invalid context in callback");
207                 return;
208         }
209
210         ms_mutex_lock(&lupnp->mutex);
211         old_state = lupnp->state;
212
213         switch(event) {
214         case UPNP_IGD_DEVICE_ADDED:
215         case UPNP_IGD_DEVICE_REMOVED:
216         case UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED:
217         case UPNP_IGD_NAT_ENABLED_CHANGED:
218         case UPNP_IGD_CONNECTION_STATUS_CHANGED:
219                 ip_address = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
220                 connection_status = upnp_igd_get_connection_status(lupnp->upnp_igd_ctxt);
221                 nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);
222
223                 if(ip_address == NULL || connection_status == NULL) {
224                         ms_message("uPnP IGD: Pending");
225                         lupnp->state = LinphoneUpnpStatePending;
226                 } else if(strcasecmp(connection_status, "Connected")  || !nat_enabled) {
227                         ms_message("uPnP IGD: Not Available");
228                         lupnp->state = LinphoneUpnpStateNotAvailable;
229                 } else {
230                         ms_message("uPnP IGD: Connected");
231                         lupnp->state = LinphoneUpnpStateOk;
232                         if(old_state != LinphoneUpnpStateOk) {
233                                 linphone_core_upnp_refresh(lupnp);
234                         }
235                 }
236
237                 break;
238
239         case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
240                 mapping = (upnp_igd_port_mapping *) arg;
241                 port_mapping = (UpnpPortBinding*) mapping->cookie;
242                 port_mapping->external_port = mapping->remote_port;
243                 port_mapping->state = LinphoneUpnpStateOk;
244                 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Added port binding", port_mapping);
245                 linphone_upnp_config_add_port_binding(lupnp, port_mapping);
246
247                 break;
248
249         case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
250                 mapping = (upnp_igd_port_mapping *) arg;
251                 port_mapping = (UpnpPortBinding*) mapping->cookie;
252                 port_mapping->external_port = -1; //Force random external port
253                 if(linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, TRUE) != 0) {
254                         linphone_upnp_port_binding_log(ORTP_ERROR, "Can't add port binding", port_mapping);
255                 }
256
257                 break;
258
259         case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
260                 mapping = (upnp_igd_port_mapping *) arg;
261                 port_mapping = (UpnpPortBinding*) mapping->cookie;
262                 port_mapping->state = LinphoneUpnpStateIdle;
263                 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Removed port binding", port_mapping);
264                 linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
265
266                 break;
267
268         case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
269                 mapping = (upnp_igd_port_mapping *) arg;
270                 port_mapping = (UpnpPortBinding*) mapping->cookie;
271                 if(linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE) != 0) {
272                         linphone_upnp_port_binding_log(ORTP_ERROR, "Can't remove port binding", port_mapping);
273                         linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
274                 }
275
276                 break;
277
278         default:
279                 break;
280         }
281
282         if(port_mapping != NULL) {
283                 /*
284                  * Execute delayed actions
285                  */
286                 if(port_mapping->to_remove) {
287                         if(port_mapping->state == LinphoneUpnpStateOk) {
288                                 port_mapping->to_remove = FALSE;
289                                 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, FALSE);
290                         } else if(port_mapping->state == LinphoneUpnpStateKo) {
291                                 port_mapping->to_remove = FALSE;
292                         }
293                 }
294                 if(port_mapping->to_add) {
295                         if(port_mapping->state == LinphoneUpnpStateIdle || port_mapping->state == LinphoneUpnpStateKo) {
296                                 port_mapping->to_add = FALSE;
297                                 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, FALSE);
298                         }
299                 }
300
301                 lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
302                 linphone_upnp_port_binding_release(port_mapping);
303         }
304
305         /*
306          * If there is no pending binding emit a signal
307          */
308         if(lupnp->pending_bindings == NULL) {
309                 ms_cond_signal(&lupnp->empty_cond);
310         }
311         ms_mutex_unlock(&lupnp->mutex);
312 }
313
314
315 /**
316  * uPnP Context
317  */
318
319 UpnpContext* linphone_upnp_context_new(LinphoneCore *lc) {
320         UpnpContext *lupnp = (UpnpContext *)ms_new0(UpnpContext,1);
321
322         ms_mutex_init(&lupnp->mutex, NULL);
323         ms_cond_init(&lupnp->empty_cond, NULL);
324
325         lupnp->last_ready_check = 0;
326         lupnp->last_ready_state = LinphoneUpnpStateIdle;
327
328         lupnp->lc = lc;
329         lupnp->pending_bindings = NULL;
330         lupnp->adding_configs = NULL;
331         lupnp->removing_configs = NULL;
332         lupnp->state = LinphoneUpnpStateIdle;
333         ms_message("uPnP IGD: New %p for core %p", lupnp, lc);
334
335         // Init ports
336         lupnp->sip_udp = NULL;
337         lupnp->sip_tcp = NULL;
338         lupnp->sip_tls = NULL;
339
340         linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lupnp);
341
342         lupnp->upnp_igd_ctxt = NULL;
343         lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lupnp);
344         if(lupnp->upnp_igd_ctxt == NULL) {
345                 lupnp->state = LinphoneUpnpStateKo;
346                 ms_error("Can't create uPnP IGD context");
347                 return NULL;
348         }
349
350         lupnp->state = LinphoneUpnpStatePending;
351         upnp_igd_start(lupnp->upnp_igd_ctxt);
352
353         return lupnp;
354 }
355
356 void linphone_upnp_context_destroy(UpnpContext *lupnp) {
357         linphone_core_remove_iterate_hook(lupnp->lc, linphone_core_upnp_hook, lupnp);
358
359         ms_mutex_lock(&lupnp->mutex);
360         
361         if(lupnp->lc->network_reachable) {
362                 /* Send port binding removes */
363                 if(lupnp->sip_udp != NULL) {
364                         linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_udp, TRUE);
365                 }
366                 if(lupnp->sip_tcp != NULL) {
367                         linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tcp, TRUE);
368                 }
369                 if(lupnp->sip_tls != NULL) {
370                         linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tls, TRUE);
371                 }
372         }
373
374         /* Wait all pending bindings are done */
375         if(lupnp->pending_bindings != NULL) {
376                 ms_message("uPnP IGD: Wait all pending port bindings ...");
377                 ms_cond_wait(&lupnp->empty_cond, &lupnp->mutex);
378         }
379         ms_mutex_unlock(&lupnp->mutex);
380
381         if(lupnp->upnp_igd_ctxt != NULL) {
382                 upnp_igd_destroy(lupnp->upnp_igd_ctxt);
383                 lupnp->upnp_igd_ctxt = NULL;
384         }
385         
386         /* No more multi threading here */
387
388         /* Run one more time configuration update and proxy */
389         linphone_upnp_update_config(lupnp);
390         linphone_upnp_update_proxy(lupnp, TRUE);
391
392         /* Release port bindings */
393         if(lupnp->sip_udp != NULL) {
394                 linphone_upnp_port_binding_release(lupnp->sip_udp);
395                 lupnp->sip_udp = NULL;
396         }
397         if(lupnp->sip_tcp != NULL) {
398                 linphone_upnp_port_binding_release(lupnp->sip_tcp);
399                 lupnp->sip_tcp = NULL;
400         }
401         if(lupnp->sip_tls != NULL) {
402                 linphone_upnp_port_binding_release(lupnp->sip_tls);
403                 lupnp->sip_tcp = NULL;
404         }
405
406         /* Release lists */
407         ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
408         lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
409         ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
410         lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
411         ms_list_for_each(lupnp->pending_bindings,(void (*)(void*))linphone_upnp_port_binding_release);
412         lupnp->pending_bindings = ms_list_free(lupnp->pending_bindings);
413         
414         ms_mutex_destroy(&lupnp->mutex);
415         ms_cond_destroy(&lupnp->empty_cond);
416
417         ms_message("uPnP IGD: destroy %p", lupnp);
418         ms_free(lupnp);
419 }
420
421 LinphoneUpnpState linphone_upnp_context_get_state(UpnpContext *lupnp) {
422         LinphoneUpnpState state = LinphoneUpnpStateKo;
423         if(lupnp != NULL) {
424                 ms_mutex_lock(&lupnp->mutex);
425                 state = lupnp->state;
426                 ms_mutex_unlock(&lupnp->mutex);
427         }
428         return state;
429 }
430
431 bool_t _linphone_upnp_context_is_ready_for_register(UpnpContext *lupnp) {
432         bool_t ready = TRUE;
433         
434         // 1 Check global uPnP state
435         ready = (lupnp->state == LinphoneUpnpStateOk);
436         
437         // 2 Check external ip address
438         if(ready) {
439                 if (upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt) == NULL) {
440                         ready = FALSE;
441                 }
442         }
443         
444         // 3 Check sip ports bindings
445         if(ready) {
446                 if(lupnp->sip_udp != NULL) {
447                         if(lupnp->sip_udp->state != LinphoneUpnpStateOk) {
448                                 ready = FALSE;
449                         }
450                 } else if(lupnp->sip_tcp != NULL) {
451                         if(lupnp->sip_tcp->state != LinphoneUpnpStateOk) {
452                                 ready = FALSE;
453                         }
454                 } else if(lupnp->sip_tls != NULL) {
455                         if(lupnp->sip_tls->state != LinphoneUpnpStateOk) {
456                                 ready = FALSE;
457                         }
458                 } else {
459                         ready = FALSE;
460                 }
461         }
462         
463         return ready;
464 }
465
466 bool_t linphone_upnp_context_is_ready_for_register(UpnpContext *lupnp) {
467         bool_t ready = FALSE;
468         if(lupnp != NULL) {
469                 ms_mutex_lock(&lupnp->mutex);
470                 ready = _linphone_upnp_context_is_ready_for_register(lupnp);
471                 ms_mutex_unlock(&lupnp->mutex);
472         }
473         return ready;
474 }
475
476 int linphone_upnp_context_get_external_port(UpnpContext *lupnp) {
477         int port = -1;
478         if(lupnp != NULL) {
479                 ms_mutex_lock(&lupnp->mutex);
480                 
481                 if(lupnp->sip_udp != NULL) {
482                         if(lupnp->sip_udp->state == LinphoneUpnpStateOk) {
483                                 port = lupnp->sip_udp->external_port;
484                         }
485                 } else if(lupnp->sip_tcp != NULL) {
486                         if(lupnp->sip_tcp->state == LinphoneUpnpStateOk) {
487                                 port = lupnp->sip_tcp->external_port;
488                         }
489                 } else if(lupnp->sip_tls != NULL) {
490                         if(lupnp->sip_tls->state == LinphoneUpnpStateOk) {
491                                 port = lupnp->sip_tls->external_port;
492                         }
493                 }
494                 
495                 ms_mutex_unlock(&lupnp->mutex);
496         }
497         return port;
498 }
499
500 void linphone_upnp_refresh(UpnpContext * lupnp) {
501         upnp_igd_refresh(lupnp->upnp_igd_ctxt);
502 }
503
504 const char* linphone_upnp_context_get_external_ipaddress(UpnpContext *lupnp) {
505         const char* addr = NULL;
506         if(lupnp != NULL) {
507                 ms_mutex_lock(&lupnp->mutex);
508                 addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
509                 ms_mutex_unlock(&lupnp->mutex);
510         }
511         return addr;
512 }
513
514 int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) {
515         upnp_igd_port_mapping mapping;
516         char description[128];
517         int ret;
518         
519         if(lupnp->state != LinphoneUpnpStateOk) {
520                 return -2;
521         }
522
523         // Compute port binding state
524         if(port->state != LinphoneUpnpStateAdding) {
525                 port->to_remove = FALSE;
526                 switch(port->state) {
527                         case LinphoneUpnpStateKo:
528                         case LinphoneUpnpStateIdle: {
529                                 port->retry = 0;
530                                 port->state = LinphoneUpnpStateAdding;
531                         }
532                         break;
533                         case LinphoneUpnpStateRemoving: {
534                                 port->to_add = TRUE;
535                                 return 0;
536                         }
537                         break;
538                         default:
539                                 return 0;
540                 }
541         }
542         
543         // No retry if specified
544         if(port->retry != 0 && !retry) {
545                 return -1;
546         }
547
548         if(port->retry >= UPNP_ADD_MAX_RETRY) {
549                 ret = -1;
550         } else {
551                 linphone_upnp_port_binding_set_device_id(port, upnp_igd_get_device_id(lupnp->upnp_igd_ctxt));
552                 mapping.cookie = linphone_upnp_port_binding_retain(port);
553                 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
554
555                 mapping.local_port = port->local_port;
556                 mapping.local_host = port->local_addr;
557                 if(port->external_port == -1)
558                         mapping.remote_port = rand()%(0xffff - 1024) + 1024;
559                 else
560                         mapping.remote_port = port->external_port;
561                 mapping.remote_host = "";
562                 snprintf(description, 128, "%s %s at %s:%d",
563                                 PACKAGE_NAME,
564                                 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP": "UDP",
565                                 port->local_addr, port->local_port);
566                 mapping.description = description;
567                 mapping.protocol = port->protocol;
568
569                 port->retry++;
570                 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to add port binding", port);
571                 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
572         }
573         if(ret != 0) {
574                 port->state = LinphoneUpnpStateKo;
575         }
576         return ret;
577 }
578
579 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) {
580         upnp_igd_port_mapping mapping;
581         int ret;
582         
583         if(lupnp->state != LinphoneUpnpStateOk) {
584                 return -2;
585         }
586
587         // Compute port binding state
588         if(port->state != LinphoneUpnpStateRemoving) {
589                 port->to_add = FALSE;
590                 switch(port->state) {
591                         case LinphoneUpnpStateOk: {
592                                 port->retry = 0;
593                                 port->state = LinphoneUpnpStateRemoving;
594                         }
595                         break;
596                         case LinphoneUpnpStateAdding: {
597                                 port->to_remove = TRUE;
598                                 return 0;
599                         }
600                         break;
601                         default:
602                                 return 0;
603                 }
604         }
605         
606         // No retry if specified
607         if(port->retry != 0 && !retry) {
608                 return 1;
609         }
610
611         if(port->retry >= UPNP_REMOVE_MAX_RETRY) {
612                 ret = -1;
613         } else {
614                 linphone_upnp_port_binding_set_device_id(port, upnp_igd_get_device_id(lupnp->upnp_igd_ctxt));
615                 mapping.cookie = linphone_upnp_port_binding_retain(port);
616                 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
617
618                 mapping.remote_port = port->external_port;
619                 mapping.remote_host = "";
620                 mapping.protocol = port->protocol;
621                 port->retry++;
622                 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to remove port binding", port);
623                 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
624         }
625         if(ret != 0) {
626                 port->state = LinphoneUpnpStateKo;
627         }
628         return ret;
629 }
630
631 /*
632  * uPnP Core interfaces
633  */
634
635 int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
636         LinphoneCore *lc = call->core;
637         UpnpContext *lupnp = lc->upnp;
638         int ret = -1;
639
640         if(lupnp == NULL) {
641                 return ret;
642         }
643
644         ms_mutex_lock(&lupnp->mutex);
645
646         // Don't handle when the call
647         if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
648                 ret = 0;
649
650                 /*
651                  * Audio part
652                  */
653                 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->audio->rtp, 
654                         UPNP_IGD_IP_PROTOCOL_UDP, (audio)? call->audio_port:0, UPNP_CALL_RETRY_DELAY);
655
656                 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->audio->rtcp, 
657                         UPNP_IGD_IP_PROTOCOL_UDP, (audio)? call->audio_port+1:0, UPNP_CALL_RETRY_DELAY);
658                 
659                 /*
660                  * Video part
661                  */
662                 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->video->rtp, 
663                         UPNP_IGD_IP_PROTOCOL_UDP, (video)? call->video_port:0, UPNP_CALL_RETRY_DELAY);
664
665                 linphone_upnp_update_port_binding(lupnp, &call->upnp_session->video->rtcp, 
666                         UPNP_IGD_IP_PROTOCOL_UDP, (video)? call->video_port+1:0, UPNP_CALL_RETRY_DELAY);
667         }
668
669         ms_mutex_unlock(&lupnp->mutex);
670
671         /*
672          * Update uPnP call state
673          */
674         linphone_upnp_call_process(call);
675
676         return ret;
677 }
678
679
680
681 int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
682         bool_t audio = FALSE;
683         bool_t video = FALSE;
684         int i;
685         const SalStreamDescription *stream;
686
687         for (i = 0; i < md->n_total_streams; i++) {
688                 stream = &md->streams[i];
689                 if(stream->type == SalAudio) {
690                         audio = TRUE;
691                 } else if(stream->type == SalVideo) {
692                         video = TRUE;
693                 }
694         }
695
696         return linphone_core_update_upnp_audio_video(call, audio, video);
697 }
698
699 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
700         return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
701 }
702
703 void linphone_core_update_upnp_state_in_call_stats(LinphoneCall *call) {
704         call->stats[LINPHONE_CALL_STATS_AUDIO].upnp_state = call->upnp_session->audio->state;
705         call->stats[LINPHONE_CALL_STATS_VIDEO].upnp_state = call->upnp_session->video->state;
706 }
707
708 void linphone_upnp_update_stream_state(UpnpStream *stream) {
709         if((stream->rtp == NULL || stream->rtp->state == LinphoneUpnpStateOk || stream->rtp->state == LinphoneUpnpStateIdle) &&
710            (stream->rtcp == NULL || stream->rtcp->state == LinphoneUpnpStateOk || stream->rtcp->state == LinphoneUpnpStateIdle)) {
711                 stream->state = LinphoneUpnpStateOk;
712         } else if((stream->rtp != NULL && 
713                      (stream->rtp->state == LinphoneUpnpStateAdding || stream->rtp->state == LinphoneUpnpStateRemoving)) ||
714                   (stream->rtcp != NULL && 
715                      (stream->rtcp->state == LinphoneUpnpStateAdding || stream->rtcp->state == LinphoneUpnpStateRemoving))) {
716                 stream->state = LinphoneUpnpStatePending;
717         } else if((stream->rtp != NULL && stream->rtp->state == LinphoneUpnpStateKo) ||
718                         (stream->rtcp != NULL && stream->rtcp->state == LinphoneUpnpStateKo)) {
719                 stream->state = LinphoneUpnpStateKo;
720         } else {
721                 ms_error("Invalid stream %p state", stream);            
722         }
723 }
724
725 int linphone_upnp_call_process(LinphoneCall *call) {
726         LinphoneCore *lc = call->core;
727         UpnpContext *lupnp = lc->upnp;
728         int ret = -1;
729         LinphoneUpnpState oldState = 0, newState = 0;
730
731         if(lupnp == NULL) {
732                 return ret;
733         }
734
735         ms_mutex_lock(&lupnp->mutex);
736
737         // Don't handle when the call
738         if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
739                 ret = 0;
740
741                 /*
742                  * Update Audio state
743                  */
744                 linphone_upnp_update_stream_state(call->upnp_session->audio);
745
746                 /*
747                  * Update Video state
748                  */
749                 linphone_upnp_update_stream_state(call->upnp_session->video);
750
751                 /*
752                  * Update stat
753                  */
754                 linphone_core_update_upnp_state_in_call_stats(call);
755                 
756                 /*
757                  * Update session state
758                  */
759                 oldState = call->upnp_session->state;
760                 if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
761                         call->upnp_session->video->state == LinphoneUpnpStateOk) {
762                         call->upnp_session->state = LinphoneUpnpStateOk;
763                 } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
764                                 call->upnp_session->video->state == LinphoneUpnpStatePending) {
765                         call->upnp_session->state = LinphoneUpnpStatePending;
766                 } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
767                                 call->upnp_session->video->state == LinphoneUpnpStateKo) {
768                         call->upnp_session->state = LinphoneUpnpStateKo;
769                 } else {
770                         call->upnp_session->state = LinphoneUpnpStateIdle;
771                 }
772                 newState = call->upnp_session->state;
773         }
774
775         ms_mutex_unlock(&lupnp->mutex);
776         
777         /* When change is done proceed update */
778         if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
779                         (newState == LinphoneUpnpStateOk || newState == LinphoneUpnpStateKo)) {
780                 if(call->upnp_session->state == LinphoneUpnpStateOk)
781                         ms_message("uPnP IGD: uPnP for Call %p is ok", call);
782                 else
783                         ms_message("uPnP IGD: uPnP for Call %p is ko", call);
784
785                 switch (call->state) {
786                         case LinphoneCallUpdating:
787                                 linphone_core_start_update_call(lc, call);
788                                 break;
789                         case LinphoneCallUpdatedByRemote:
790                                 linphone_core_start_accept_call_update(lc, call);
791                                 break;
792                         case LinphoneCallOutgoingInit:
793                                 linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
794                                 break;
795                         case LinphoneCallIdle:
796                                 linphone_core_notify_incoming_call(lc, call);
797                                 break;
798                         default:
799                                 break;
800                 }
801         }
802
803         return ret;
804 }
805
806 void linphone_core_upnp_refresh(UpnpContext *lupnp) {
807         MSList *global_list = NULL;
808         MSList *list = NULL;
809         MSList *item;
810         LinphoneCall *call;
811         UpnpPortBinding *port_mapping, *port_mapping2;
812
813         ms_message("uPnP IGD: Refresh mappings");
814
815         if(lupnp->sip_udp != NULL) {
816                 global_list = ms_list_append(global_list, lupnp->sip_udp);
817         }
818         if(lupnp->sip_tcp != NULL) {
819                 global_list = ms_list_append(global_list, lupnp->sip_tcp);
820         }
821         if(lupnp->sip_tls != NULL) {
822                 global_list = ms_list_append(global_list, lupnp->sip_tls);
823         }
824
825         list = lupnp->lc->calls;
826         while(list != NULL) {
827                 call = (LinphoneCall *)list->data;
828                 if(call->upnp_session != NULL) {
829                         if(call->upnp_session->audio->rtp != NULL) {
830                                 global_list = ms_list_append(global_list, call->upnp_session->audio->rtp);
831                         }
832                         if(call->upnp_session->audio->rtcp != NULL) {
833                                 global_list = ms_list_append(global_list, call->upnp_session->audio->rtcp);
834                         }
835                         if(call->upnp_session->video->rtp != NULL) {
836                                 global_list = ms_list_append(global_list, call->upnp_session->video->rtp);
837                         }
838                         if(call->upnp_session->video->rtcp != NULL) {
839                                 global_list = ms_list_append(global_list, call->upnp_session->video->rtcp);
840                         }
841                 }
842                 list = list->next;
843         }
844
845         list = linphone_upnp_config_list_port_bindings(lupnp->lc->config, upnp_igd_get_device_id(lupnp->upnp_igd_ctxt));
846         for(item = list;item != NULL; item = item->next) {
847                         port_mapping = (UpnpPortBinding *)item->data;
848                         port_mapping2 = linphone_upnp_port_binding_equivalent_in_list(global_list, port_mapping);
849                         if(port_mapping2 == NULL) {
850                                 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE);
851                         } else if(port_mapping2->state == LinphoneUpnpStateIdle){
852                                 /* Force to remove */
853                                 port_mapping2->state = LinphoneUpnpStateOk;
854                         }
855         }
856         ms_list_for_each(list, (void (*)(void*))linphone_upnp_port_binding_release);
857         list = ms_list_free(list);
858
859
860         // (Re)Add removed port bindings
861         list = global_list;
862         while(list != NULL) {
863                 port_mapping = (UpnpPortBinding *)list->data;
864                 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE);
865                 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, TRUE);
866                 list = list->next;
867         }
868         global_list = ms_list_free(global_list);
869 }
870
871 void linphone_upnp_update_port_binding(UpnpContext *lupnp, UpnpPortBinding **port_mapping, upnp_igd_ip_protocol protocol, int port, int retry_delay) {
872         const char *local_addr, *external_addr;
873         time_t now = time(NULL);
874         if(port != 0) {
875                 if(*port_mapping != NULL) {
876                         if(port != (*port_mapping)->local_port) {
877                                 linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
878                                 *port_mapping = NULL;
879                         }
880                 }
881                 if(*port_mapping == NULL) {
882                         *port_mapping = linphone_upnp_port_binding_new_or_collect(lupnp->pending_bindings, protocol, port, port);
883                 }
884                 
885                 // Get addresses
886                 local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
887                 external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
888
889                 // Force binding update on local address change
890                 if(local_addr != NULL) {
891                         if(strncmp((*port_mapping)->local_addr, local_addr, sizeof((*port_mapping)->local_addr))) {
892                                 linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
893                                 strncpy((*port_mapping)->local_addr, local_addr, sizeof((*port_mapping)->local_addr));
894                         }
895                 }
896                 if(external_addr != NULL) {
897                         strncpy((*port_mapping)->external_addr, external_addr, sizeof((*port_mapping)->external_addr));
898                 }
899
900                 // Add (if not already done) the binding
901                 if(now - (*port_mapping)->last_update >= retry_delay) {
902                         (*port_mapping)->last_update = now;
903                         linphone_upnp_context_send_add_port_binding(lupnp, *port_mapping, FALSE);
904                 }
905         } else {
906                 if(*port_mapping != NULL) {
907                         linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
908                         *port_mapping = NULL;
909                 }
910         }
911 }
912
913 void linphone_upnp_update_config(UpnpContext* lupnp) {
914         char key[64];
915         const MSList *item;
916         UpnpPortBinding *port_mapping;
917         
918         /* Add configs */
919         for(item = lupnp->adding_configs;item!=NULL;item=item->next) {
920                 port_mapping = (UpnpPortBinding *)item->data;
921                 snprintf(key, sizeof(key), "%s-%s-%d-%d",
922                                         port_mapping->device_id,
923                                         (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
924                                         port_mapping->external_port,
925                                         port_mapping->local_port);
926                 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, "uPnP");
927                 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
928         }
929         ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
930         lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
931
932         /* Remove configs */
933         for(item = lupnp->removing_configs;item!=NULL;item=item->next) {
934                 port_mapping = (UpnpPortBinding *)item->data;
935                 snprintf(key, sizeof(key), "%s-%s-%d-%d",
936                                         port_mapping->device_id,
937                                         (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
938                                         port_mapping->external_port,
939                                         port_mapping->local_port);
940                 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, NULL);
941                 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
942         }
943         ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
944         lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
945 }
946
947 void linphone_upnp_update_proxy(UpnpContext* lupnp, bool_t force) {
948         LinphoneUpnpState ready_state;
949         const MSList *item;
950         time_t now = (force)? (lupnp->last_ready_check + UPNP_CORE_READY_CHECK) : time(NULL);
951         
952         /* Refresh registers if we are ready */
953         if(now - lupnp->last_ready_check >= UPNP_CORE_READY_CHECK) {
954                 lupnp->last_ready_check = now;
955                 ready_state = (_linphone_upnp_context_is_ready_for_register(lupnp))? LinphoneUpnpStateOk: LinphoneUpnpStateKo;
956                 if(ready_state != lupnp->last_ready_state) {
957                         for(item=linphone_core_get_proxy_config_list(lupnp->lc);item!=NULL;item=item->next) {
958                                 LinphoneProxyConfig *cfg=(LinphoneProxyConfig*)item->data;
959                                 if (linphone_proxy_config_register_enabled(cfg)) {
960                                         if (ready_state != LinphoneUpnpStateOk) {
961                                                 // Only reset ithe registration if we require that upnp should be ok
962                                                 if(lupnp->lc->sip_conf.register_only_when_upnp_is_ok) {
963                                                         linphone_proxy_config_set_state(cfg, LinphoneRegistrationNone, "Registration impossible (uPnP not ready)");
964                                                 } else {
965                                                         cfg->commit=TRUE;
966                                                 }
967                                         } else {
968                                                 cfg->commit=TRUE;
969                                         }
970                                 }
971                         }
972                         lupnp->last_ready_state = ready_state;
973                 }
974         }
975 }
976
977 bool_t linphone_core_upnp_hook(void *data) {
978         LCSipTransports transport;
979         UpnpContext *lupnp = (UpnpContext *)data;
980
981         ms_mutex_lock(&lupnp->mutex);
982
983         /* Update ports */
984         if(lupnp->state == LinphoneUpnpStateOk) {
985                 linphone_core_get_sip_transports(lupnp->lc, &transport);
986                 linphone_upnp_update_port_binding(lupnp, &lupnp->sip_udp, UPNP_IGD_IP_PROTOCOL_UDP, transport.udp_port, UPNP_CORE_RETRY_DELAY);
987                 linphone_upnp_update_port_binding(lupnp, &lupnp->sip_tcp, UPNP_IGD_IP_PROTOCOL_TCP, transport.tcp_port, UPNP_CORE_RETRY_DELAY);
988                 linphone_upnp_update_port_binding(lupnp, &lupnp->sip_tls, UPNP_IGD_IP_PROTOCOL_TCP, transport.tls_port, UPNP_CORE_RETRY_DELAY);
989         }
990
991         linphone_upnp_update_proxy(lupnp, FALSE);       
992         linphone_upnp_update_config(lupnp);
993
994         ms_mutex_unlock(&lupnp->mutex);
995         return TRUE;
996 }
997
998 int linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
999         int i;
1000         SalStreamDescription *stream;
1001         UpnpStream *upnpStream;
1002
1003         for (i = 0; i < desc->n_active_streams; i++) {
1004                 stream = &desc->streams[i];
1005                 upnpStream = NULL;
1006                 if(stream->type == SalAudio) {
1007                         upnpStream = session->audio;
1008                 } else if(stream->type == SalVideo) {
1009                         upnpStream = session->video;
1010                 }
1011                 if(upnpStream != NULL) {
1012                         if(upnpStream->rtp != NULL && upnpStream->rtp->state == LinphoneUpnpStateOk) {
1013                                 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
1014                                 stream->rtp_port = upnpStream->rtp->external_port;
1015                         }
1016                         if(upnpStream->rtcp != NULL && upnpStream->rtcp->state == LinphoneUpnpStateOk) {
1017                                 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
1018                                 stream->rtcp_port = upnpStream->rtcp->external_port;
1019                         }
1020                 }
1021         }
1022         return 0;
1023 }
1024
1025
1026 /*
1027  * uPnP Port Binding
1028  */
1029
1030 UpnpPortBinding *linphone_upnp_port_binding_new() {
1031         UpnpPortBinding *port = NULL;
1032         port = ms_new0(UpnpPortBinding,1);
1033         ms_mutex_init(&port->mutex, NULL);
1034         port->state = LinphoneUpnpStateIdle;
1035         port->protocol = UPNP_IGD_IP_PROTOCOL_UDP;      
1036         port->device_id = NULL;
1037         port->local_addr[0] = '\0';
1038         port->local_port = -1;
1039         port->external_addr[0] = '\0';
1040         port->external_port = -1;
1041         port->to_remove = FALSE;
1042         port->to_add = FALSE;
1043         port->ref = 1;
1044         port->last_update = 0;
1045         return port;
1046 }
1047
1048 UpnpPortBinding *linphone_upnp_port_binding_new_with_parameters(upnp_igd_ip_protocol protocol, int local_port, int external_port) {
1049         UpnpPortBinding *port_binding = linphone_upnp_port_binding_new();
1050         port_binding->protocol = protocol;
1051         port_binding->local_port = local_port;
1052         port_binding->external_port = external_port;
1053         return port_binding;
1054 }
1055
1056 UpnpPortBinding *linphone_upnp_port_binding_new_or_collect(MSList *list, upnp_igd_ip_protocol protocol, int local_port, int external_port) {
1057         UpnpPortBinding *tmp_binding;
1058         UpnpPortBinding *end_binding;
1059         end_binding = linphone_upnp_port_binding_new_with_parameters(protocol, local_port, external_port);
1060         tmp_binding = linphone_upnp_port_binding_equivalent_in_list(list, end_binding);
1061         if(tmp_binding != NULL) {
1062                 linphone_upnp_port_binding_release(end_binding);
1063                 end_binding = tmp_binding;
1064         }
1065         return end_binding;     
1066
1067
1068 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port) {
1069         UpnpPortBinding *new_port = NULL;
1070         new_port = ms_new0(UpnpPortBinding,1);
1071         memcpy(new_port, port, sizeof(UpnpPortBinding));
1072         new_port->device_id = NULL;
1073         linphone_upnp_port_binding_set_device_id(new_port, port->device_id);
1074         ms_mutex_init(&new_port->mutex, NULL);
1075         new_port->ref = 1;
1076         return new_port;
1077 }
1078
1079 void linphone_upnp_port_binding_set_device_id(UpnpPortBinding *port, const char *device_id) {
1080         char *formated_device_id = linphone_upnp_format_device_id(device_id);
1081         if(formated_device_id != NULL && port->device_id != NULL) {
1082                 if(strcmp(formated_device_id, port->device_id) == 0) {
1083                         ms_free(formated_device_id);
1084                         return;
1085                 }
1086         }
1087         if(port->device_id != NULL) {
1088                 ms_free(port->device_id);
1089         }
1090         port->device_id = formated_device_id;
1091 }
1092
1093 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
1094         if(strlen(port->local_addr)) {
1095                 ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d (retry %d)", msg,
1096                                                         (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
1097                                                                         port->external_port,
1098                                                                         port->local_addr,
1099                                                                         port->local_port,
1100                                                                         port->retry - 1);
1101         } else {
1102                 ortp_log(level, "uPnP IGD: %s %s|%d->%d (retry %d)", msg,
1103                                                         (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
1104                                                                         port->external_port,
1105                                                                         port->local_port,
1106                                                                         port->retry - 1);
1107         }
1108 }
1109
1110 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
1111         return port1->protocol == port2->protocol &&
1112                         port1->local_port == port2->local_port &&
1113                         port1->external_port == port2->external_port;
1114 }
1115
1116 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port) {
1117         UpnpPortBinding *port_mapping;
1118         while(list != NULL) {
1119                 port_mapping = (UpnpPortBinding *)list->data;
1120                 if(linphone_upnp_port_binding_equal(port, port_mapping)) {
1121                         return port_mapping;
1122                 }
1123                 list = list->next;
1124         }
1125
1126         return NULL;
1127 }
1128
1129 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port) {
1130         ms_mutex_lock(&port->mutex);
1131         port->ref++;
1132         ms_mutex_unlock(&port->mutex);
1133         return port;
1134 }
1135
1136 void linphone_upnp_port_binding_release(UpnpPortBinding *port) {
1137         ms_mutex_lock(&port->mutex);
1138         if(--port->ref == 0) {
1139                 if(port->device_id != NULL) {
1140                         ms_free(port->device_id);
1141                 }
1142                 ms_mutex_unlock(&port->mutex);
1143                 ms_mutex_destroy(&port->mutex);
1144                 ms_free(port);
1145                 return;
1146         }
1147         ms_mutex_unlock(&port->mutex);
1148 }
1149
1150
1151 /*
1152  * uPnP Stream
1153  */
1154
1155 UpnpStream* linphone_upnp_stream_new() {
1156         UpnpStream *stream = ms_new0(UpnpStream,1);
1157         stream->state = LinphoneUpnpStateIdle;
1158         stream->rtp = NULL; 
1159         stream->rtcp = NULL;
1160         return stream;
1161 }
1162
1163 void linphone_upnp_stream_destroy(UpnpStream* stream) {
1164         if(stream->rtp != NULL) {
1165                 linphone_upnp_port_binding_release(stream->rtp);
1166                 stream->rtp = NULL;
1167         }
1168         if(stream->rtcp != NULL) {
1169                 linphone_upnp_port_binding_release(stream->rtcp);
1170                 stream->rtcp = NULL;
1171         }
1172         ms_free(stream);
1173 }
1174
1175
1176 /*
1177  * uPnP Session
1178  */
1179
1180 UpnpSession* linphone_upnp_session_new(LinphoneCall* call) {
1181         UpnpSession *session = ms_new0(UpnpSession,1);
1182         session->call = call;
1183         session->state = LinphoneUpnpStateIdle;
1184         session->audio = linphone_upnp_stream_new();
1185         session->video = linphone_upnp_stream_new();
1186         return session;
1187 }
1188
1189 void linphone_upnp_session_destroy(UpnpSession *session) {
1190         LinphoneCore *lc = session->call->core;
1191
1192         if(lc->upnp != NULL) {
1193                 /* Remove bindings */
1194                 if(session->audio->rtp != NULL) {
1195                         linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtp, TRUE);
1196                 }
1197                 if(session->audio->rtcp != NULL) {
1198                         linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtcp, TRUE);
1199                 }
1200                 if(session->video->rtp != NULL) {
1201                         linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtp, TRUE);
1202                 }
1203                 if(session->video->rtcp != NULL) {
1204                         linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtcp, TRUE);
1205                 }
1206         }
1207         
1208         session->call->stats[LINPHONE_CALL_STATS_AUDIO].upnp_state = LinphoneUpnpStateKo;
1209         session->call->stats[LINPHONE_CALL_STATS_VIDEO].upnp_state = LinphoneUpnpStateKo;
1210         
1211         linphone_upnp_stream_destroy(session->audio);
1212         linphone_upnp_stream_destroy(session->video);
1213         ms_free(session);
1214 }
1215
1216 LinphoneUpnpState linphone_upnp_session_get_state(UpnpSession *session) {
1217         return session->state;
1218 }
1219
1220
1221 /*
1222  * uPnP Config
1223  */
1224
1225 struct linphone_upnp_config_list_port_bindings_struct {
1226         struct _LpConfig *lpc;
1227         MSList *retList;
1228         const char *device_id;
1229 };
1230
1231 static void linphone_upnp_config_list_port_bindings_cb(const char *entry, struct linphone_upnp_config_list_port_bindings_struct *cookie) {
1232         char device_id[UPNP_UUID_LEN + 1];
1233         char protocol_str[4]; // TCP or UDP
1234         upnp_igd_ip_protocol protocol;
1235         int external_port;
1236         int local_port;
1237         int ret;
1238         bool_t valid = TRUE;
1239         UpnpPortBinding *port;
1240         
1241         ret = sscanf(entry, "%"UPNP_UUID_LEN_STR"s-%3s-%i-%i", device_id, protocol_str, &external_port, &local_port);
1242         if(ret == 4) {
1243                 // Handle only wanted device bindings
1244                 if(device_id != NULL && strcmp(cookie->device_id, device_id) != 0) {
1245                         return; 
1246                 }
1247                 if(linphone_upnp_strncmpi(protocol_str, "TCP", 3) == 0) {
1248                         protocol = UPNP_IGD_IP_PROTOCOL_TCP;
1249                 } else if(linphone_upnp_strncmpi(protocol_str, "UDP", 3) == 0) {
1250                         protocol = UPNP_IGD_IP_PROTOCOL_UDP;
1251                 } else {
1252                         valid = FALSE;
1253                 }
1254                 if(valid) {
1255                         port = linphone_upnp_port_binding_new();
1256                         linphone_upnp_port_binding_set_device_id(port, device_id);
1257                         port->state = LinphoneUpnpStateOk;
1258                         port->protocol = protocol;
1259                         port->external_port = external_port;
1260                         port->local_port = local_port;
1261                         cookie->retList = ms_list_append(cookie->retList, port);
1262                 }
1263         } else {
1264                 valid = FALSE;
1265         }
1266         if(!valid) {
1267                 ms_warning("uPnP configuration invalid line: %s", entry);
1268         }
1269 }
1270
1271 MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc, const char *device_id) {
1272         char *formated_device_id = linphone_upnp_format_device_id(device_id);
1273         struct linphone_upnp_config_list_port_bindings_struct cookie = {lpc, NULL, formated_device_id};
1274         lp_config_for_each_entry(lpc, UPNP_SECTION_NAME, (void(*)(const char *, void*))linphone_upnp_config_list_port_bindings_cb, &cookie);
1275         ms_free(formated_device_id);
1276         return cookie.retList;
1277 }
1278
1279 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1280         MSList *list;
1281         UpnpPortBinding *list_port;
1282         
1283         if(port->device_id == NULL) {
1284                 ms_error("Can't remove port binding without device_id");
1285                 return;
1286         }
1287
1288         list = lupnp->removing_configs;
1289         while(list != NULL) {
1290                 list_port = (UpnpPortBinding *)list->data;
1291                 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1292                         lupnp->removing_configs = ms_list_remove(lupnp->removing_configs, list_port);
1293                         linphone_upnp_port_binding_release(list_port);
1294                         return;
1295                 }
1296                 list = ms_list_next(list);
1297         }
1298
1299         list = lupnp->adding_configs;
1300         while(list != NULL) {
1301                 list_port = (UpnpPortBinding *)list->data;
1302                 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1303                         return;
1304                 }
1305                 list = ms_list_next(list);
1306         }
1307
1308         list_port = linphone_upnp_port_binding_copy(port);
1309         lupnp->adding_configs = ms_list_append(lupnp->adding_configs, list_port);
1310 }
1311
1312 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1313         MSList *list;
1314         UpnpPortBinding *list_port;
1315
1316         if(port->device_id == NULL) {
1317                 ms_error("Can't remove port binding without device_id");
1318                 return;
1319         }
1320
1321         list = lupnp->adding_configs;
1322         while(list != NULL) {
1323                 list_port = (UpnpPortBinding *)list->data;
1324                 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1325                         lupnp->adding_configs = ms_list_remove(lupnp->adding_configs, list_port);
1326                         linphone_upnp_port_binding_release(list_port);
1327                         return;
1328                 }
1329                 list = ms_list_next(list);
1330         }
1331
1332         list = lupnp->removing_configs;
1333         while(list != NULL) {
1334                 list_port = (UpnpPortBinding *)list->data;
1335                 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1336                         return;
1337                 }
1338                 list = ms_list_next(list);
1339         }
1340
1341         list_port = linphone_upnp_port_binding_copy(port);
1342         lupnp->removing_configs = ms_list_append(lupnp->removing_configs, list_port);
1343 }