conncheck.c 104 KB
Newer Older
1
2
3
/*
 * This file is part of the Nice GLib ICE library.
 *
4
5
6
 * (C) 2006-2009 Collabora Ltd.
 *  Contact: Youness Alaoui
 * (C) 2006-2009 Nokia Corporation. All rights reserved.
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 *  Contact: Kai Vehmanen
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Nice GLib ICE library.
 *
 * The Initial Developers of the Original Code are Collabora Ltd and Nokia
 * Corporation. All Rights Reserved.
 *
 * Contributors:
 *   Kai Vehmanen, Nokia
26
 *   Youness Alaoui, Collabora Ltd.
27
28
29
30
31
32
33
34
35
36
37
38
39
 *   Dafydd Harries, Collabora Ltd.
 *
 * Alternatively, the contents of this file may be used under the terms of the
 * the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which
 * case the provisions of LGPL are applicable instead of those above. If you
 * wish to allow use of your version of this file only under the terms of the
 * LGPL and not to allow others to use your version of this file under the
 * MPL, indicate your decision by deleting the provisions above and replace
 * them with the notice and other provisions required by the LGPL. If you do
 * not delete the provisions above, a recipient may use your version of this
 * file under either the MPL or the LGPL.
 */

40
/*
41
42
43
44
45
46
47
48
 * @file conncheck.c
 * @brief ICE connectivity checks
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

49
50
51
52
53
#include <errno.h>
#include <string.h>

#include <glib.h>

54
55
#include "debug.h"

56
57
58
59
#include "agent.h"
#include "agent-priv.h"
#include "conncheck.h"
#include "discovery.h"
60
#include "stun/usages/ice.h"
61
62
#include "stun/usages/bind.h"
#include "stun/usages/turn.h"
63

64
static void priv_update_check_list_failed_components (NiceAgent *agent, Stream *stream);
65
static void priv_update_check_list_state_for_ready (NiceAgent *agent, Stream *stream, Component *component);
66
static guint priv_prune_pending_checks (Stream *stream, guint component_id);
67
static gboolean priv_schedule_triggered_check (NiceAgent *agent, Stream *stream, Component *component, NiceSocket *local_socket, NiceCandidate *remote_cand, gboolean use_candidate);
68
static void priv_mark_pair_nominated (NiceAgent *agent, Stream *stream, Component *component, NiceCandidate *remotecand);
69
70
71
72
73
static size_t priv_create_username (NiceAgent *agent, Stream *stream,
    guint component_id, NiceCandidate *remote, NiceCandidate *local,
    uint8_t *dest, guint dest_len, gboolean inbound);
static size_t priv_get_password (NiceAgent *agent, Stream *stream,
    NiceCandidate *remote, uint8_t **password);
74

75
static int priv_timer_expired (GTimeVal *timer, GTimeVal *now)
76
77
78
79
80
{
  return (now->tv_sec == timer->tv_sec) ?
    now->tv_usec >= timer->tv_usec :
    now->tv_sec >= timer->tv_sec;
}
81

82
/*
83
84
85
86
87
88
 * Finds the next connectivity check in WAITING state.
 */
static CandidateCheckPair *priv_conn_check_find_next_waiting (GSList *conn_check_list)
{
  GSList *i;

89
90
  /* note: list is sorted in priority order to first waiting check has
   *       the highest priority */
91
92
93
94
95
96
97
98
99
100

  for (i = conn_check_list; i ; i = i->next) {
    CandidateCheckPair *p = i->data;
    if (p->state == NICE_CHECK_WAITING)
      return p;
  }

  return NULL;
}

101
/*
102
103
104
105
106
107
 * Initiates a new connectivity check for a ICE candidate pair.
 *
 * @return TRUE on success, FALSE on error
 */
static gboolean priv_conn_check_initiate (NiceAgent *agent, CandidateCheckPair *pair)
{
108
109
110
111
  /* XXX: from ID-16 onwards, the checks should not be sent
   * immediately, but be put into the "triggered queue",
   * see  "7.2.1.4 Triggered Checks"
   */
112
  g_get_current_time (&pair->next_tick);
113
  g_time_val_add (&pair->next_tick, agent->timer_ta * 1000);
114
  pair->state = NICE_CHECK_IN_PROGRESS;
115
  nice_debug ("Agent %p : pair %p state IN_PROGRESS", agent, pair);
116
117
118
119
  conn_check_send (agent, pair);
  return TRUE;
}

120
/*
121
 * Unfreezes the next connectivity check in the list. Follows the
122
 * algorithm (2.) defined in 5.7.4 (Computing States) of the ICE spec
123
 * (ID-19), with some exceptions (see comments in code).
124
125
126
 *
 * See also sect 7.1.2.2.3 (Updating Pair States), and
 * priv_conn_check_unfreeze_related().
127
128
129
 * 
 * @return TRUE on success, and FALSE if no frozen candidates were found.
 */
130
static gboolean priv_conn_check_unfreeze_next (NiceAgent *agent)
131
132
{
  CandidateCheckPair *pair = NULL;
133
  GSList *i, *j;
134

135
136
137
138
139
140
141
  /* XXX: the unfreezing is implemented a bit differently than in the
   *      current ICE spec, but should still be interoperate:
   *   - checks are not grouped by foundation
   *   - one frozen check is unfrozen (lowest component-id, highest
   *     priority)
   */

142
143
144
145
  for (i = agent->streams; i; i = i->next) {
    Stream *stream = i->data;
    guint64 max_frozen_priority = 0;

146

147
148
149
150
151
152
153
154
155
156
    for (j = stream->conncheck_list; j ; j = j->next) {
      CandidateCheckPair *p = j->data;

      /* XXX: the prio check could be removed as the pairs are sorted
       *       already */

      if (p->state == NICE_CHECK_FROZEN) {
	if (p->priority > max_frozen_priority) {
	  max_frozen_priority = p->priority;
	  pair = p;
157
	}
158
159
      }
    }
160
161
162

    if (pair) 
      break;
163
164
165
  }
  
  if (pair) {
166
    nice_debug ("Agent %p : Pair %p with s/c-id %u/%u (%s) unfrozen.", agent, pair, pair->stream_id, pair->component_id, pair->foundation);
167
    pair->state = NICE_CHECK_WAITING;
168
    nice_debug ("Agent %p : pair %p state WAITING", agent, pair);
169
170
171
172
173
174
    return TRUE;
  }

  return FALSE;
}

175
/*
176
 * Unfreezes the next next connectivity check in the list after
Youness Alaoui's avatar
Youness Alaoui committed
177
 * check 'success_check' has successfully completed.
178
 *
179
 * See sect 7.1.2.2.3 (Updating Pair States) of ICE spec (ID-19).
180
181
182
183
184
185
 * 
 * @param agent context
 * @param ok_check a connectivity check that has just completed
 *
 * @return TRUE on success, and FALSE if no frozen candidates were found.
 */
186
static void priv_conn_check_unfreeze_related (NiceAgent *agent, Stream *stream, CandidateCheckPair *ok_check)
187
188
189
190
191
192
{
  GSList *i, *j;
  guint unfrozen = 0;

  g_assert (ok_check);
  g_assert (ok_check->state == NICE_CHECK_SUCCEEDED);
193
194
  g_assert (stream);
  g_assert (stream->id == ok_check->stream_id);
195
196

  /* step: perform the step (1) of 'Updating Pair States' */
197
  for (i = stream->conncheck_list; i ; i = i->next) {
198
199
200
201
202
    CandidateCheckPair *p = i->data;
   
    if (p->stream_id == ok_check->stream_id) {
      if (p->state == NICE_CHECK_FROZEN &&
	  strcmp (p->foundation, ok_check->foundation) == 0) {
Youness Alaoui's avatar
Youness Alaoui committed
203
	nice_debug ("Agent %p : Unfreezing check %p (after successful check %p).", agent, p, ok_check);
204
	p->state = NICE_CHECK_WAITING;
205
        nice_debug ("Agent %p : pair %p state WAITING", agent, p);
206
	++unfrozen;
207
208
209
210
211
212
213
214
215
216
      }
    }
  }

  /* step: perform the step (2) of 'Updating Pair States' */
  stream = agent_find_stream (agent, ok_check->stream_id);
  if (stream_all_components_ready (stream)) {
    /* step: unfreeze checks from other streams */
    for (i = agent->streams; i ; i = i->next) {
      Stream *s = i->data;
217
      for (j = stream->conncheck_list; j ; j = j->next) {
218
219
220
221
222
223
	CandidateCheckPair *p = j->data;

	if (p->stream_id == s->id &&
	    p->stream_id != ok_check->stream_id) {
	  if (p->state == NICE_CHECK_FROZEN &&
	      strcmp (p->foundation, ok_check->foundation) == 0) {
Youness Alaoui's avatar
Youness Alaoui committed
224
	    nice_debug ("Agent %p : Unfreezing check %p from stream %u (after successful check %p).", agent, p, s->id, ok_check);
225
	    p->state = NICE_CHECK_WAITING;
226
            nice_debug ("Agent %p : pair %p state WAITING", agent, p);
227
228
229
230
231
232
233
234
235
236
237
238
	    ++unfrozen;
					    
	  }
	}
      }
      /* note: only unfreeze check from one stream at a time */
      if (unfrozen)
	break;
    }
  }    

  if (unfrozen == 0) 
239
    priv_conn_check_unfreeze_next (agent);
240
241
}

242
/*
243
244
245
246
247
248
249
250
251
252
 * Helper function for connectivity check timer callback that
 * runs through the stream specific part of the state machine. 
 *
 * @param schedule if TRUE, schedule a new check
 *
 * @return will return FALSE when no more pending timers.
 */
static gboolean priv_conn_check_tick_stream (Stream *stream, NiceAgent *agent, GTimeVal *now)
{
  gboolean keep_timer_going = FALSE;
253
254
  guint s_inprogress = 0, s_succeeded = 0, s_discovered = 0,
      s_nominated = 0, s_waiting_for_nomination = 0;
255
256
257
258
259
  guint frozen = 0, waiting = 0;
  GSList *i, *k;

  for (i = stream->conncheck_list; i ; i = i->next) {
    CandidateCheckPair *p = i->data;
260

261
    if (p->state == NICE_CHECK_IN_PROGRESS) {
262
      if (p->stun_message.buffer == NULL) {
263
	nice_debug ("Agent %p : STUN connectivity check was cancelled, marking as done.", agent);
264
	p->state = NICE_CHECK_FAILED;
265
        nice_debug ("Agent %p : pair %p state FAILED", agent, p);
266
      } else if (priv_timer_expired (&p->next_tick, now)) {
267
268
        switch (stun_timer_refresh (&p->timer)) {
          case STUN_USAGE_TIMER_RETURN_TIMEOUT:
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
            {
              /* case: error, abort processing */
              StunTransactionId id;

              nice_debug ("Agent %p : Retransmissions failed, giving up on connectivity check %p", agent, p);
              p->state = NICE_CHECK_FAILED;
              nice_debug ("Agent %p : pair %p state FAILED", agent, p);

              stun_message_id (&p->stun_message, id);
              stun_agent_forget_transaction (&agent->stun_agent, id);

              p->stun_message.buffer = NULL;
              p->stun_message.buffer_len = 0;


              break;
            }
286
          case STUN_USAGE_TIMER_RETURN_RETRANSMIT:
287
288
289
            {
              /* case: not ready, so schedule a new timeout */
              unsigned int timeout = stun_timer_remainder (&p->timer);
290
              nice_debug ("Agent %p :STUN transaction retransmitted (timeout %dms).",
291
292
                  agent, timeout);

293
              nice_socket_send (p->local->sockptr, &p->remote->addr,
294
295
296
297
298
299
300
301
                  stun_message_length (&p->stun_message),
                  (gchar *)p->stun_buffer);


              /* note: convert from milli to microseconds for g_time_val_add() */
              p->next_tick = *now;
              g_time_val_add (&p->next_tick, timeout * 1000);

Youness Alaoui's avatar
Youness Alaoui committed
302
303
304
              keep_timer_going = TRUE;
              break;
            }
305
          case STUN_USAGE_TIMER_RETURN_SUCCESS:
Youness Alaoui's avatar
Youness Alaoui committed
306
            {
307
308
              unsigned int timeout = stun_timer_remainder (&p->timer);

Youness Alaoui's avatar
Youness Alaoui committed
309
310
311
312
              /* note: convert from milli to microseconds for g_time_val_add() */
              p->next_tick = *now;
              g_time_val_add (&p->next_tick, timeout * 1000);

313
314
315
              keep_timer_going = TRUE;
              break;
            }
316
317
318
          default:
            /* Nothing to do. */
            break;
319
        }
320
321
      }
    }
322

323
324
325
326
327
328
329
330
    if (p->state == NICE_CHECK_FROZEN)
      ++frozen;
    else if (p->state == NICE_CHECK_IN_PROGRESS)
      ++s_inprogress;
    else if (p->state == NICE_CHECK_WAITING)
      ++waiting;
    else if (p->state == NICE_CHECK_SUCCEEDED)
      ++s_succeeded;
331
332
    else if (p->state == NICE_CHECK_DISCOVERED)
      ++s_discovered;
333

334
335
    if ((p->state == NICE_CHECK_SUCCEEDED || p->state == NICE_CHECK_DISCOVERED)
        && p->nominated)
336
      ++s_nominated;
337
338
    else if ((p->state == NICE_CHECK_SUCCEEDED ||
            p->state == NICE_CHECK_DISCOVERED) && !p->nominated)
339
      ++s_waiting_for_nomination;
340
  }
341

342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
    /* note: keep the timer going as long as there is work to be done */
  if (s_inprogress)
    keep_timer_going = TRUE;
  
    /* note: if some components have established connectivity,
     *       but yet no nominated pair, keep timer going */
  if (s_nominated < stream->n_components &&
      s_waiting_for_nomination) {
    keep_timer_going = TRUE;
    if (agent->controlling_mode) {
      guint n;
      for (n = 0; n < stream->n_components; n++) {
	for (k = stream->conncheck_list; k ; k = k->next) {
	  CandidateCheckPair *p = k->data;
	  /* note: highest priority item selected (list always sorted) */
	  if (p->state == NICE_CHECK_SUCCEEDED ||
	      p->state == NICE_CHECK_DISCOVERED) {
359
	    nice_debug ("Agent %p : restarting check %p as the nominated pair.", agent, p);
360
	    p->nominated = TRUE;
361
	    priv_conn_check_initiate (agent, p);
362
363
364
365
366
367
	    break; /* move to the next component */
	  }
	}
      }
    }
  }
368
    {
369
370
    static int tick_counter = 0;
    if (tick_counter++ % 50 == 0 || keep_timer_going != TRUE)
371
372
373
374
375
      nice_debug ("Agent %p : timer tick #%u: %u frozen, %u in-progress, "
          "%u waiting, %u succeeded, %u discovered, %u nominated, "
          "%u waiting-for-nom.", agent,
          tick_counter, frozen, s_inprogress, waiting, s_succeeded,
          s_discovered, s_nominated, s_waiting_for_nomination);
376
377
378
379
380
381
  }

  return keep_timer_going;

}

Youness Alaoui's avatar
Youness Alaoui committed
382

383
/*
384
385
386
387
388
389
390
 * Timer callback that handles initiating and managing connectivity
 * checks (paced by the Ta timer).
 *
 * This function is designed for the g_timeout_add() interface.
 *
 * @return will return FALSE when no more pending timers.
 */
Youness Alaoui's avatar
Youness Alaoui committed
391
static gboolean priv_conn_check_tick_unlocked (gpointer pointer)
392
393
394
395
{
  CandidateCheckPair *pair = NULL;
  NiceAgent *agent = pointer;
  gboolean keep_timer_going = FALSE;
396
  GSList *i, *j;
397
  GTimeVal now;
398
399
400

  /* step: process ongoing STUN transactions */
  g_get_current_time (&now);
401

402
403
404
  /* step: find the highest priority waiting check and send it */
  for (i = agent->streams; i ; i = i->next) {
    Stream *stream = i->data;
405

406
    pair = priv_conn_check_find_next_waiting (stream->conncheck_list);
407
408
409
    if (pair)
      break;
  }
410
411
412
413

  if (pair) {
    priv_conn_check_initiate (agent, pair);
    keep_timer_going = TRUE;
414
415
  } else {
    keep_timer_going = priv_conn_check_unfreeze_next (agent);
416
417
  }

418
419
  for (j = agent->streams; j; j = j->next) {
    Stream *stream = j->data;
420
421
422
423
    gboolean res =
      priv_conn_check_tick_stream (stream, agent, &now);
    if (res)
      keep_timer_going = res;
424
  }
425

426
  /* step: stop timer if no work left */
427
  if (keep_timer_going != TRUE) {
428
    nice_debug ("Agent %p : %s: stopping conncheck timer", agent, G_STRFUNC);
429
430
    for (i = agent->streams; i; i = i->next) {
      Stream *stream = i->data;
431
      priv_update_check_list_failed_components (agent, stream);
432
433
434
435
      for (j = stream->components; j; j = j->next) {
        Component *component = j->data;
        priv_update_check_list_state_for_ready (agent, stream, component);
      }
436
    }
437
438
439
440
441
442
443
444
445
446

    /* Stopping the timer so destroy the source.. this will allow
       the timer to be reset if we get a set_remote_candidates after this
       point */
    if (agent->conncheck_timer_source != NULL) {
      g_source_destroy (agent->conncheck_timer_source);
      g_source_unref (agent->conncheck_timer_source);
      agent->conncheck_timer_source = NULL;
    }

447
    /* XXX: what to signal, is all processing now really done? */
448
    nice_debug ("Agent %p : changing conncheck state to COMPLETED.", agent);
449
450
451
452
453
  }

  return keep_timer_going;
}

Youness Alaoui's avatar
Youness Alaoui committed
454
455
456
457
static gboolean priv_conn_check_tick (gpointer pointer)
{
  gboolean ret;

458
459
460
461
462
463
464
  agent_lock();
  if (g_source_is_destroyed (g_main_current_source ())) {
    nice_debug ("Source was destroyed. "
        "Avoided race condition in priv_conn_check_tick");
    agent_unlock ();
    return FALSE;
  }
Youness Alaoui's avatar
Youness Alaoui committed
465
  ret = priv_conn_check_tick_unlocked (pointer);
466
  agent_unlock();
Youness Alaoui's avatar
Youness Alaoui committed
467
468
469
470

  return ret;
}

471
472
473
474
static gboolean priv_conn_keepalive_retransmissions_tick (gpointer pointer)
{
  CandidatePair *pair = (CandidatePair *) pointer;

475
  agent_lock();
476

477
478
479
480
  /* A race condition might happen where the mutex above waits for the lock
   * and in the meantime another thread destroys the source.
   * In that case, we don't need to run our retransmission tick since it should
   * have been cancelled */
481
482
483
484
  if (g_source_is_destroyed (g_main_current_source ())) {
    nice_debug ("Source was destroyed. "
        "Avoided race condition in priv_conn_keepalive_retransmissions_tick");
    agent_unlock ();
485
486
487
    return FALSE;
  }

488
489
490
491
492
493
  g_source_destroy (pair->keepalive.tick_source);
  g_source_unref (pair->keepalive.tick_source);
  pair->keepalive.tick_source = NULL;

  switch (stun_timer_refresh (&pair->keepalive.timer)) {
    case STUN_USAGE_TIMER_RETURN_TIMEOUT:
494
495
496
497
498
499
500
501
      {
        /* Time out */
        StunTransactionId id;


        stun_message_id (&pair->keepalive.stun_message, id);
        stun_agent_forget_transaction (&pair->keepalive.agent->stun_agent, id);

502
503
504
505
506
507
508
509
510
511
512
513
514
        if (pair->keepalive.agent->media_after_tick) {
          nice_debug ("Agent %p : Keepalive conncheck timed out!! "
              "but media was received. Suspecting keepalive lost because of "
              "network bottleneck", pair->keepalive.agent);

          pair->keepalive.stun_message.buffer = NULL;
        } else {
          nice_debug ("Agent %p : Keepalive conncheck timed out!! "
              "peer probably lost connection", pair->keepalive.agent);
          agent_signal_component_state_change (pair->keepalive.agent,
              pair->keepalive.stream_id, pair->keepalive.component_id,
              NICE_COMPONENT_STATE_FAILED);
        }
515
516
        break;
      }
517
518
519
520
521
522
    case STUN_USAGE_TIMER_RETURN_RETRANSMIT:
      /* Retransmit */
      nice_socket_send (pair->local->sockptr, &pair->remote->addr,
          stun_message_length (&pair->keepalive.stun_message),
          (gchar *)pair->keepalive.stun_buffer);

523
524
      nice_debug ("Agent %p : Retransmitting keepalive conncheck",
          pair->keepalive.agent);
525
526
527
528
529
530
531
532
533
534
535
      pair->keepalive.tick_source =
          agent_timeout_add_with_context (pair->keepalive.agent,
          stun_timer_remainder (&pair->keepalive.timer),
          priv_conn_keepalive_retransmissions_tick, pair);
      break;
    case STUN_USAGE_TIMER_RETURN_SUCCESS:
      pair->keepalive.tick_source =
          agent_timeout_add_with_context (pair->keepalive.agent,
          stun_timer_remainder (&pair->keepalive.timer),
          priv_conn_keepalive_retransmissions_tick, pair);
      break;
536
537
538
    default:
      /* Nothing to do. */
      break;
539
540
541
  }


542
  agent_unlock ();
543
544
545
546
  return FALSE;
}


547
/*
548
549
550
551
552
553
554
 * Timer callback that handles initiating and managing connectivity
 * checks (paced by the Ta timer).
 *
 * This function is designed for the g_timeout_add() interface.
 *
 * @return will return FALSE when no more pending timers.
 */
555
static gboolean priv_conn_keepalive_tick_unlocked (NiceAgent *agent)
556
{
557
  GSList *i, *j, *k;
558
  int errors = 0;
559
  gboolean ret = FALSE;
560
  size_t buf_len = 0;
561
562

  /* case 1: session established and media flowing
563
   *         (ref ICE sect 10 "Keepalives" ID-19)  */
564
  for (i = agent->streams; i; i = i->next) {
565

566
    Stream *stream = i->data;
567
568
    for (j = stream->components; j; j = j->next) {
      Component *component = j->data;
569
      if (component->selected_pair.local != NULL) {
570
	CandidatePair *p = &component->selected_pair;
Youness Alaoui's avatar
Youness Alaoui committed
571

572
        if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE) {
573
574
575
576
577
578
579
580
581
582
583
584
          guint32 priority = nice_candidate_ice_priority_full (
                  NICE_CANDIDATE_TYPE_PREF_PEER_REFLEXIVE, 1,
                  p->local->component_id);
          uint8_t uname[NICE_STREAM_MAX_UNAME];
          size_t uname_len =
              priv_create_username (agent, agent_find_stream (agent, stream->id),
                  component->id, p->remote, p->local, uname, sizeof (uname),
                  FALSE);
          uint8_t *password = NULL;
          size_t password_len = priv_get_password (agent,
              agent_find_stream (agent, stream->id), p->remote, &password);

585
586
587
588
589
590
591
592
593
594
595
          if (nice_debug_is_enabled ()) {
            gchar tmpbuf[INET6_ADDRSTRLEN];
            nice_address_to_string (&p->remote->addr, tmpbuf);
            nice_debug ("Agent %p : Keepalive STUN-CC REQ to '%s:%u', "
                "socket=%u (c-id:%u), username='%s' (%" G_GSIZE_FORMAT "), "
                "password='%s' (%" G_GSIZE_FORMAT "), priority=%u.", agent,
                tmpbuf, nice_address_get_port (&p->remote->addr),
                g_socket_get_fd(((NiceSocket *)p->local->sockptr)->fileno),
                component->id, uname, uname_len, password, password_len,
                priority);
          }
596
597
598
599
600
601
602
          if (uname_len > 0) {
            buf_len = stun_usage_ice_conncheck_create (&agent->stun_agent,
                &p->keepalive.stun_message, p->keepalive.stun_buffer,
                sizeof(p->keepalive.stun_buffer),
                uname, uname_len, password, password_len,
                agent->controlling_mode, agent->controlling_mode, priority,
                agent->tie_breaker,
Jakub Adam's avatar
Jakub Adam committed
603
                NULL,
604
605
                agent_to_ice_compatibility (agent));

606
            nice_debug ("Agent %p: conncheck created %" G_GSIZE_FORMAT " - %p",
607
608
                agent, buf_len, p->keepalive.stun_message.buffer);

609
            if (buf_len > 0) {
610
611
              stun_timer_start (&p->keepalive.timer, STUN_TIMER_DEFAULT_TIMEOUT,
                  STUN_TIMER_DEFAULT_MAX_RETRANSMISSIONS);
612

613
614
              agent->media_after_tick = FALSE;

615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
              /* send the conncheck */
              nice_socket_send (p->local->sockptr, &p->remote->addr,
                  buf_len, (gchar *)p->keepalive.stun_buffer);

              if (p->keepalive.tick_source != NULL) {
                g_source_destroy (p->keepalive.tick_source);
                g_source_unref (p->keepalive.tick_source);
                p->keepalive.tick_source = NULL;
              }

              p->keepalive.stream_id = stream->id;
              p->keepalive.component_id = component->id;
              p->keepalive.agent = agent;

              p->keepalive.tick_source =
                  agent_timeout_add_with_context (p->keepalive.agent,
                      stun_timer_remainder (&p->keepalive.timer),
                      priv_conn_keepalive_retransmissions_tick, p);
            } else {
              ++errors;
635
636
637
638
639
640
641
            }
          }
        } else {
          buf_len = stun_usage_bind_keepalive (&agent->stun_agent,
              &p->keepalive.stun_message, p->keepalive.stun_buffer,
              sizeof(p->keepalive.stun_buffer));

642
643
644
          if (buf_len > 0) {
            nice_socket_send (p->local->sockptr, &p->remote->addr, buf_len,
                (gchar *)p->keepalive.stun_buffer);
645

646
647
648
            nice_debug ("Agent %p : stun_bind_keepalive for pair %p res %d.",
                agent, p, (int) buf_len);
          } else {
649
            ++errors;
650
          }
651
        }
652
      }
653
654
    }
  }
Youness Alaoui's avatar
Youness Alaoui committed
655

656
  /* case 2: connectivity establishment ongoing
657
   *         (ref ICE sect 4.1.1.4 "Keeping Candidates Alive" ID-19)  */
658
659
  for (i = agent->streams; i; i = i->next) {
    Stream *stream = i->data;
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
    for (j = stream->components; j; j = j->next) {
      Component *component = j->data;
      if (component->state < NICE_COMPONENT_STATE_READY &&
          agent->stun_server_ip) {
        NiceAddress stun_server;
        if (nice_address_set_from_string (&stun_server, agent->stun_server_ip)) {
          StunAgent stun_agent;
          uint8_t stun_buffer[STUN_MAX_MESSAGE_SIZE];
          StunMessage stun_message;
          size_t buffer_len = 0;

          nice_address_set_port (&stun_server, agent->stun_server_port);

          stun_agent_init (&stun_agent, STUN_ALL_KNOWN_ATTRIBUTES,
              STUN_COMPATIBILITY_RFC3489, 0);

          buffer_len = stun_usage_bind_create (&stun_agent,
              &stun_message, stun_buffer, sizeof(stun_buffer));

          for (k = component->local_candidates; k; k = k->next) {
            NiceCandidate *candidate = (NiceCandidate *) k->data;
            if (candidate->type == NICE_CANDIDATE_TYPE_HOST) {
              /* send the conncheck */
              nice_debug ("Agent %p : resending STUN on %s to keep the "
                  "candidate alive.", agent, candidate->foundation);
              nice_socket_send (candidate->sockptr, &stun_server,
                  buffer_len, (gchar *)stun_buffer);
            }
          }
        }
690
691
692
      }
    }
  }
Youness Alaoui's avatar
Youness Alaoui committed
693

694
  if (errors) {
695
    nice_debug ("Agent %p : %s: stopping keepalive timer", agent, G_STRFUNC);
696
    goto done;
697
698
  }

699
700
701
  ret = TRUE;

 done:
702
703
704
705
706
707
708
709
  return ret;
}

static gboolean priv_conn_keepalive_tick (gpointer pointer)
{
  NiceAgent *agent = pointer;
  gboolean ret;

710
711
712
713
714
715
716
717
  agent_lock();
  if (g_source_is_destroyed (g_main_current_source ())) {
    nice_debug ("Source was destroyed. "
        "Avoided race condition in priv_conn_keepalive_tick");
    agent_unlock ();
    return FALSE;
  }

718
  ret = priv_conn_keepalive_tick_unlocked (agent);
719
720
721
722
723
724
725
  if (ret == FALSE) {
    if (agent->keepalive_timer_source) {
      g_source_destroy (agent->keepalive_timer_source);
      g_source_unref (agent->keepalive_timer_source);
      agent->keepalive_timer_source = NULL;
    }
  }
726
  agent_unlock();
727
  return ret;
728
729
}

730

731
732
733
734
static gboolean priv_turn_allocate_refresh_retransmissions_tick (gpointer pointer)
{
  CandidateRefresh *cand = (CandidateRefresh *) pointer;

735
  agent_lock();
736

737
738
739
740
  /* A race condition might happen where the mutex above waits for the lock
   * and in the meantime another thread destroys the source.
   * In that case, we don't need to run our retransmission tick since it should
   * have been cancelled */
741
742
743
744
  if (g_source_is_destroyed (g_main_current_source ())) {
    nice_debug ("Source was destroyed. "
        "Avoided race condition in priv_turn_allocate_refresh_retransmissions_tick");
    agent_unlock ();
745
746
747
    return FALSE;
  }

748

749
750
751
752
  g_source_destroy (cand->tick_source);
  g_source_unref (cand->tick_source);
  cand->tick_source = NULL;

753
754
  switch (stun_timer_refresh (&cand->timer)) {
    case STUN_USAGE_TIMER_RETURN_TIMEOUT:
755
756
757
758
759
760
761
762
763
764
      {
        /* Time out */
        StunTransactionId id;

        stun_message_id (&cand->stun_message, id);
        stun_agent_forget_transaction (&cand->stun_agent, id);

        refresh_cancel (cand);
        break;
      }
765
    case STUN_USAGE_TIMER_RETURN_RETRANSMIT:
766
767
768
769
      /* Retransmit */
      nice_socket_send (cand->nicesock, &cand->server,
          stun_message_length (&cand->stun_message), (gchar *)cand->stun_buffer);

Youness Alaoui's avatar
Youness Alaoui committed
770
771
      cand->tick_source = agent_timeout_add_with_context (cand->agent,
          stun_timer_remainder (&cand->timer),
772
773
          priv_turn_allocate_refresh_retransmissions_tick, cand);
      break;
774
    case STUN_USAGE_TIMER_RETURN_SUCCESS:
Youness Alaoui's avatar
Youness Alaoui committed
775
776
      cand->tick_source = agent_timeout_add_with_context (cand->agent,
          stun_timer_remainder (&cand->timer),
777
778
          priv_turn_allocate_refresh_retransmissions_tick, cand);
      break;
779
780
781
    default:
      /* Nothing to do. */
      break;
782
783
784
  }


785
  agent_unlock ();
786
787
788
789
790
791
792
793
794
795
  return FALSE;
}

static void priv_turn_allocate_refresh_tick_unlocked (CandidateRefresh *cand)
{
  uint8_t *username;
  size_t username_len;
  uint8_t *password;
  size_t password_len;
  size_t buffer_len = 0;
796
797
  StunUsageTurnCompatibility turn_compat =
      agent_to_turn_compatibility (cand->agent);
798

799
800
801
802
  username = (uint8_t *)cand->turn->username;
  username_len = (size_t) strlen (cand->turn->username);
  password = (uint8_t *)cand->turn->password;
  password_len = (size_t) strlen (cand->turn->password);
803

804
805
  if (turn_compat == STUN_USAGE_TURN_COMPATIBILITY_MSN ||
      turn_compat == STUN_USAGE_TURN_COMPATIBILITY_OC2007) {
806
807
808
809
    username = g_base64_decode ((gchar *)username, &username_len);
    password = g_base64_decode ((gchar *)password, &password_len);
  }

810
  buffer_len = stun_usage_turn_create_refresh (&cand->stun_agent,
811
812
813
814
      &cand->stun_message,  cand->stun_buffer, sizeof(cand->stun_buffer),
      cand->stun_resp_msg.buffer == NULL ? NULL : &cand->stun_resp_msg, -1,
      username, username_len,
      password, password_len,
815
      turn_compat);
816

817
818
  if (turn_compat == STUN_USAGE_TURN_COMPATIBILITY_MSN ||
      turn_compat == STUN_USAGE_TURN_COMPATIBILITY_OC2007) {
819
820
821
822
823
824
    g_free (cand->msn_turn_username);
    g_free (cand->msn_turn_password);
    cand->msn_turn_username = username;
    cand->msn_turn_password = password;
  }

825
826
  nice_debug ("Agent %p : Sending allocate Refresh %" G_GSIZE_FORMAT,
      cand->agent, buffer_len);
827

828
829
830
831
832
833
  if (cand->tick_source != NULL) {
    g_source_destroy (cand->tick_source);
    g_source_unref (cand->tick_source);
    cand->tick_source = NULL;
  }

834
  if (buffer_len > 0) {
835
836
    stun_timer_start (&cand->timer, STUN_TIMER_DEFAULT_TIMEOUT,
        STUN_TIMER_DEFAULT_MAX_RETRANSMISSIONS);
837
838
839
840
841
842
843
844
845
846
847
848
849

    /* send the refresh */
    nice_socket_send (cand->nicesock, &cand->server,
        buffer_len, (gchar *)cand->stun_buffer);

    cand->tick_source = agent_timeout_add_with_context (cand->agent,
        stun_timer_remainder (&cand->timer),
        priv_turn_allocate_refresh_retransmissions_tick, cand);
  }

}


850
/*
851
852
853
854
855
856
857
858
859
860
 * Timer callback that handles refreshing TURN allocations
 *
 * This function is designed for the g_timeout_add() interface.
 *
 * @return will return FALSE when no more pending timers.
 */
static gboolean priv_turn_allocate_refresh_tick (gpointer pointer)
{
  CandidateRefresh *cand = (CandidateRefresh *) pointer;

861
862
863
864
865
866
867
868
  agent_lock();
  if (g_source_is_destroyed (g_main_current_source ())) {
    nice_debug ("Source was destroyed. "
        "Avoided race condition in priv_turn_allocate_refresh_tick");
    agent_unlock ();
    return FALSE;
  }

869
  priv_turn_allocate_refresh_tick_unlocked (cand);
870
  agent_unlock ();
871
872
873
874
875

  return FALSE;
}


876
/*
877
 * Initiates the next pending connectivity check.
878
879
 * 
 * @return TRUE if a pending check was scheduled
880
 */
881
gboolean conn_check_schedule_next (NiceAgent *agent)
882
{
883
  gboolean res = priv_conn_check_unfreeze_next (agent);
884
  nice_debug ("Agent %p : priv_conn_check_unfreeze_next returned %d", agent, res);
885

886
  if (agent->discovery_unsched_items > 0)
887
    nice_debug ("Agent %p : WARN: starting conn checks before local candidate gathering is finished.", agent);
888

889
890
891
  /* step: call once imediately */
  res = priv_conn_check_tick_unlocked ((gpointer) agent);
  nice_debug ("Agent %p : priv_conn_check_tick_unlocked returned %d", agent, res);
892

893
894
895
896
  /* step: schedule timer if not running yet */
  if (res && agent->conncheck_timer_source == NULL) {
    agent->conncheck_timer_source = agent_timeout_add_with_context (agent, agent->timer_ta, priv_conn_check_tick, agent);
  }
897

898
899
900
  /* step: also start the keepalive timer */
  if (agent->keepalive_timer_source == NULL) {
    agent->keepalive_timer_source = agent_timeout_add_with_context (agent, NICE_AGENT_TIMER_TR_DEFAULT, priv_conn_keepalive_tick, agent);
901
  }
902

903
  nice_debug ("Agent %p : conn_check_schedule_next returning %d", agent, res);
904
  return res;
905
906
}

907
/*
908
909
910
911
912
913
914
915
916
917
918
919
920
 * Compares two connectivity check items. Checkpairs are sorted
 * in descending priority order, with highest priority item at
 * the start of the list.
 */
gint conn_check_compare (const CandidateCheckPair *a, const CandidateCheckPair *b)
{
  if (a->priority > b->priority)
    return -1;
  else if (a->priority < b->priority)
    return 1;
  return 0;
}

921
/*
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
 * Preprocesses a new connectivity check by going through list 
 * of a any stored early incoming connectivity checks from 
 * the remote peer. If a matching incoming check has been already
 * received, update the state of the new outgoing check 'pair'.
 * 
 * @param agent context pointer
 * @param stream which stream (of the agent)
 * @param component pointer to component object to which 'pair'has been added
 * @param pair newly added connectivity check
 */
static void priv_preprocess_conn_check_pending_data (NiceAgent *agent, Stream *stream, Component *component, CandidateCheckPair *pair)
{
  GSList *i;
  for (i = component->incoming_checks; i; i = i->next) {
    IncomingCheck *icheck = i->data;
    if (nice_address_equal (&icheck->from, &pair->remote->addr) &&
	icheck->local_socket == pair->local->sockptr) {
939
      nice_debug ("Agent %p : Updating check %p with stored early-icheck %p, %p/%u/%u (agent/stream/component).", agent, pair, icheck, agent, stream->id, component->id);
940
941
942
943
944
945
946
      if (icheck->use_candidate)
	priv_mark_pair_nominated (agent, stream, component, pair->remote);
      priv_schedule_triggered_check (agent, stream, component, icheck->local_socket, pair->remote, icheck->use_candidate);
    }
  }
}

947
/*
948
949
950
951
952
953
954
955
956
 * Handle any processing steps for connectivity checks after
 * remote candidates have been set. This function handles
 * the special case where answerer has sent us connectivity
 * checks before the answer (containing candidate information),
 * reaches us. The special case is documented in sect 7.2 
 * if ICE spec (ID-19).
 */
void conn_check_remote_candidates_set(NiceAgent *agent)
{
957
  GSList *i, *j, *k, *l, *m, *n;
Youness Alaoui's avatar
Youness Alaoui committed
958

959
960
961
962
963
964
  for (i = agent->streams; i ; i = i->next) {
    Stream *stream = i->data;
    for (j = stream->conncheck_list; j ; j = j->next) {
      CandidateCheckPair *pair = j->data;
      Component *component = stream_find_component_by_id (stream, pair->component_id);
      gboolean match = FALSE;
Youness Alaoui's avatar
Youness Alaoui committed
965

966
967
968
969
970
971
      /* performn delayed processing of spec steps section 7.2.1.4,
	 and section 7.2.1.5 */
      priv_preprocess_conn_check_pending_data (agent, stream, component, pair);

      for (k = component->incoming_checks; k; k = k->next) {
	IncomingCheck *icheck = k->data;
972
	/* sect 7.2.1.3., "Learning Peer Reflexive Candidates", has to
973
974
975
976
977
978
979
980
981
	 * be handled separately */
	for (l = component->remote_candidates; l; l = l->next) {
	  NiceCandidate *cand = l->data;
	  if (nice_address_equal (&icheck->from, &cand->addr)) {
	    match = TRUE;
	    break;
	  }
	}
	if (match != TRUE) {
982
	  /* note: we have gotten an incoming connectivity check from
983
	   *       an address that is not a known remote candidate */
984
985
986
987
988

          NiceCandidate *local_candidate = NULL;
          NiceCandidate *remote_candidate = NULL;

          if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE ||
Jakub Adam's avatar
Jakub Adam committed
989
990
              agent->compatibility == NICE_COMPATIBILITY_MSN ||
              agent->compatibility == NICE_COMPATIBILITY_OC2007) {
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
            /* We need to find which local candidate was used */
            uint8_t uname[NICE_STREAM_MAX_UNAME];
            guint uname_len;

            nice_debug ("Agent %p: We have a peer-reflexive candidate in a "
                "stored pending check", agent);

            for (m = component->remote_candidates;
                 m != NULL && remote_candidate == NULL; m = m->next) {
              for (n = component->local_candidates; n; n = n->next) {
                NiceCandidate *rcand = m->data;
                NiceCandidate *lcand = n->data;

                uname_len = priv_create_username (agent, stream,
                    component->id,  rcand, lcand,
                    uname, sizeof (uname), TRUE);

                stun_debug ("pending check, comparing username '");
                stun_debug_bytes (icheck->username,
                    icheck->username? icheck->username_len : 0);
                stun_debug ("' (%d) with '", icheck->username_len);
                stun_debug_bytes (uname, uname_len);
                stun_debug ("' (%d) : %d\n",
                    uname_len, icheck->username &&
                    uname_len == icheck->username_len &&
                    memcmp (icheck->username, uname, uname_len) == 0);

                if (icheck->username &&
                    uname_len == icheck->username_len &&
                    memcmp (uname, icheck->username, icheck->username_len) == 0) {
                  local_candidate = lcand;
                  remote_candidate = rcand;
                  break;
                }
              }
            }
          }

          if (agent->compatibility == NICE_COMPATIBILITY_GOOGLE &&
              local_candidate == NULL) {
            /* if we couldn't match the username, then the matching remote
             * candidate hasn't been received yet.. we must wait */
            nice_debug ("Agent %p : Username check failed. pending check has "
                "to wait to be processed", agent);
          } else {
Youness Alaoui's avatar
Youness Alaoui committed
1036
1037
1038
1039
1040
            NiceCandidate *candidate;

            nice_debug ("Agent %p : Discovered peer reflexive from early i-check",
                agent);
            candidate =
1041
1042
1043
1044
1045
1046
1047
1048
                discovery_learn_remote_peer_reflexive_candidate (agent,
                    stream,
                    component,
                    icheck->priority,
                    &icheck->from,
                    icheck->local_socket,
                    local_candidate, remote_candidate);
            if (candidate) {
1049
1050
              conn_check_add_for_candidate (agent, stream->id, component, candidate);

1051
1052
              if (icheck->use_candidate)
                priv_mark_pair_nominated (agent, stream, component, candidate);
1053
1054
1055
1056
              priv_schedule_triggered_check (agent, stream, component, icheck->local_socket, candidate, icheck->use_candidate);
            }
          }
        }
1057
      }
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068

      /* Once we process the pending checks, we should free them to avoid
       * reprocessing them again if a dribble-mode set_remote_candidates
       * is called */
      for (m = component->incoming_checks; m; m = m->next) {
        IncomingCheck *icheck = m->data;
        g_free (icheck->username);
        g_slice_free (IncomingCheck, icheck);
      }
      g_slist_free (component->incoming_checks);
      component->incoming_checks = NULL;
1069
1070
1071
1072
    }
  }
}

1073
/*
1074
 * Enforces the upper limit for connectivity checks as described
1075
 * in ICE spec section 5.7.3 (ID-19). See also 
1076
1077
1078
1079
1080
1081
1082
1083
1084
 * conn_check_add_for_candidate().
 */
static GSList *priv_limit_conn_check_list_size (GSList *conncheck_list, guint upper_limit)
{
  guint list_len = g_slist_length (conncheck_list);
  guint c = 0;
  GSList *result = conncheck_list;

  if (list_len > upper_limit) {
Youness Alaoui's avatar
Youness Alaoui committed
1085
1086
    nice_debug ("Agent : Pruning candidates. Conncheck list has %d elements. "
        "Maximum connchecks allowed : %d", list_len, upper_limit);
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
    c = list_len - upper_limit;
    if (c == list_len) {
      /* case: delete whole list */
      g_slist_foreach (conncheck_list, conn_check_free_item, NULL);
      g_slist_free (conncheck_list),
	result = NULL;
    }
    else {
      /* case: remove 'c' items from list end (lowest priority) */
      GSList *i, *tmp;

      g_assert (c > 0);
      i = g_slist_nth (conncheck_list, c - 1);

      tmp = i->next;
      i->next = NULL;

      if (tmp) {
	/* delete the rest of the connectivity check list */
	g_slist_foreach (tmp, conn_check_free_item, NULL);
	g_slist_free (tmp);
      }
    }
  }

  return result;
}

1115
/*
1116
1117
1118
 * Changes the selected pair for the component if 'pair' is nominated
 * and has higher priority than the currently selected pair. See
 * ICE sect 11.1.1. "Procedures for Full Implementations" (ID-19).
1119
 */
1120
1121
1122
1123
1124
static gboolean priv_update_selected_pair (NiceAgent *agent, Component *component, CandidateCheckPair *pair)
{
  g_assert (component);
  g_assert (pair);
  if (pair->priority > component->selected_pair.priority) {
1125
    nice_debug ("Agent %p : changing SELECTED PAIR for component %u: %s:%s "
1126
1127
        "(prio:%" G_GUINT64_FORMAT ").", agent, component->id, pair->local->foundation,
        pair->remote->foundation, pair->priority);
1128
1129
1130
1131
1132
1133
1134
1135

    if (component->selected_pair.keepalive.tick_source != NULL) {
      g_source_destroy (component->selected_pair.keepalive.tick_source);
      g_source_unref (component->selected_pair.keepalive.tick_source);
      component->selected_pair.keepalive.tick_source = NULL;
    }

    memset (&component->selected_pair, 0, sizeof(CandidatePair));
1136
1137
1138
1139
    component->selected_pair.local = pair->local;
    component->selected_pair.remote = pair->remote;
    component->selected_pair.priority = pair->priority;

1140
1141
    priv_conn_keepalive_tick_unlocked (agent);

1142
    agent_signal_new_selected_pair (agent, pair->stream_id, component->id, pair->local->foundation, pair->remote->foundation);
1143

1144
1145
1146
1147
1148
  }

  return TRUE;
}

1149
/*
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
 * Updates the check list state.
 *
 * Implements parts of the algorithm described in 
 * ICE sect 8.1.2. "Updating States" (ID-19): if for any 
 * component, all checks have been completed and have
 * failed, mark that component's state to NICE_CHECK_FAILED.
 *
 * Sends a component state changesignal via 'agent'.
 */
static void priv_update_check_list_failed_components (NiceAgent *agent, Stream *stream)
{
  GSList *i;
  /* note: emitting a signal might cause the client 
   *       to remove the stream, thus the component count
   *       must be fetched before entering the loop*/
  guint c, components = stream->n_components;

  /* note: iterate the conncheck list for each component separately */
  for (c = 0; c < components; c++) {
1169
    Component *comp = NULL;
1170
1171
    if (!agent_find_component (agent, stream->id, c+1, NULL, &comp))
      continue;
1172

1173
1174
1175
1176
1177
1178
1179
1180
1181
    for (i = stream->conncheck_list; i; i = i->next) {
      CandidateCheckPair *p = i->data;
      
      if (p->stream_id == stream->id &&
	  p->component_id == (c + 1)) {
	if (p->state != NICE_CHECK_FAILED)
	  break;
      }
    }
1182
1183
1184
1185
1186
 
    /* note: all checks have failed
     * Set the component to FAILED only if it actually had remote candidates
     * that failed.. */
    if (i == NULL && comp != NULL && comp->remote_candidates != NULL)
1187
1188
1189
1190
1191
1192
1193
      agent_signal_component_state_change (agent, 
					   stream->id,
					   (c + 1), /* component-id */
					   NICE_COMPONENT_STATE_FAILED);
  }
}

1194
/*
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
 * Updates the check list state for a stream component.
 *
 * Implements the algorithm described in ICE sect 8.1.2 
 * "Updating States" (ID-19) as it applies to checks of 
 * a certain component. If there are any nominated pairs, 
 * ICE processing may be concluded, and component state is 
 * changed to READY.
 *
 * Sends a component state changesignal via 'agent'.
 */
static void priv_update_check_list_state_for_ready (NiceAgent *agent, Stream *stream, Component *component)
{
  GSList *i;
  guint succeeded = 0, nominated = 0;

  g_assert (component);

  /* step: search for at least one nominated pair */
  for (i = stream->conncheck_list; i; i = i->next) {
    CandidateCheckPair *p = i->data;
    if (p->component_id == component->id) {
      if (p->state == NICE_CHECK_SUCCEEDED ||
	  p->state == NICE_CHECK_DISCOVERED) {
	++succeeded;
	if (p->nominated == TRUE) {
1220
          ++nominated;
1221
1222
1223
1224
	}
      }
    }
  }
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234

  if (nominated > 0) {
    /* Only go to READY if no checks are left in progress. If there are
     * any that are kept, then this function will be called again when the
     * conncheck tick timer finishes them all */
    if (priv_prune_pending_checks (stream, component->id) == 0) {
      agent_signal_component_state_change (agent, stream->id,
          component->id, NICE_COMPONENT_STATE_READY);
    }
  }
1235
  nice_debug ("Agent %p : conn.check list status: %u nominated, %u succeeded, c-id %u.", agent, nominated, succeeded, component->id);
1236
1237
}

1238
/*
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
 * The remote party has signalled that the candidate pair
 * described by 'component' and 'remotecand' is nominated
 * for use.
 */
static void priv_mark_pair_nominated (NiceAgent *agent, Stream *stream, Component *component, NiceCandidate *remotecand)
{
  GSList *i;

  g_assert (component);

  /* step: search for at least one nominated pair */
  for (i = stream->conncheck_list; i; i = i->next) {
    CandidateCheckPair *pair = i->data;
    /* XXX: hmm, how to figure out to which local candidate the 
     *      check was sent to? let's mark all matching pairs
     *      as nominated instead */
    if (pair->remote == remotecand) {
1256
      nice_debug ("Agent %p : marking pair %p (%s) as nominated", agent, pair, pair->foundation);
1257
1258
1259
1260
1261
1262
1263
1264
1265
      pair->nominated = TRUE;
      if (pair->state == NICE_CHECK_SUCCEEDED ||
	  pair->state == NICE_CHECK_DISCOVERED)
	priv_update_selected_pair (agent, component, pair);
      priv_update_check_list_state_for_ready (agent, stream, component);
    }
  }
}

1266
/*