Skip to content

Commit

Permalink
[ice] add support for half trickle ICE (fixes #4)
Browse files Browse the repository at this point in the history
We now support receiving remote candidates incrementally.

For now we still gather local candidates in one go, as full trickle
ICE would have to be opt-in and require a breaking API change.
  • Loading branch information
jlaine committed Jun 21, 2018
1 parent 1b53b8d commit a9b729a
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 1 deletion.
13 changes: 12 additions & 1 deletion aioice/ice.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,12 @@ def add_remote_candidate(self, remote_candidate):
return

self._remote_candidates.append(remote_candidate)
for protocol in self._protocols:
if (protocol.local_candidate.can_pair_with(remote_candidate) and
not self._find_pair(protocol, remote_candidate)):
pair = CandidatePair(protocol, remote_candidate)
self._check_list.append(pair)
self.sort_check_list()

async def gather_candidates(self):
"""
Expand Down Expand Up @@ -339,7 +345,8 @@ async def connect(self):
# 5.7.1. Forming Candidate Pairs
for remote_candidate in self._remote_candidates:
for protocol in self._protocols:
if protocol.local_candidate.can_pair_with(remote_candidate):
if (protocol.local_candidate.can_pair_with(remote_candidate) and
not self._find_pair(protocol, remote_candidate)):
pair = CandidatePair(protocol, remote_candidate)
self._check_list.append(pair)
self.sort_check_list()
Expand Down Expand Up @@ -592,6 +599,10 @@ def check_periodic(self):
pair.handle = asyncio.ensure_future(self.check_start(pair))
return True

# if we expect more candidates, keep going
if not self._remote_candidates_end:
return not self._check_list_done

return False

async def check_start(self, pair):
Expand Down
66 changes: 66 additions & 0 deletions tests/test_ice_trickle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import asyncio
import unittest

from aioice import ice

from .utils import run


class IceTrickleTest(unittest.TestCase):
def assertCandidateTypes(self, conn, expected):
types = set([c.type for c in conn.local_candidates])
self.assertEqual(types, expected)

def test_connect(self):
conn_a = ice.Connection(ice_controlling=True)
conn_b = ice.Connection(ice_controlling=False)

# invite
run(conn_a.gather_candidates())
conn_b.remote_username = conn_a.local_username
conn_b.remote_password = conn_a.local_password

# accept
run(conn_b.gather_candidates())
conn_a.remote_username = conn_b.local_username
conn_a.remote_password = conn_b.local_password

# we should only have host candidates
self.assertCandidateTypes(conn_a, set(['host']))
self.assertCandidateTypes(conn_b, set(['host']))

# there should be a default candidate for component 1
candidate = conn_a.get_default_candidate(1)
self.assertIsNotNone(candidate)
self.assertEqual(candidate.type, 'host')

# there should not be a default candidate for component 2
candidate = conn_a.get_default_candidate(2)
self.assertIsNone(candidate)

async def add_candidates_later(a, b):
await asyncio.sleep(0.1)
for candidate in b.local_candidates:
a.add_remote_candidate(candidate)
await asyncio.sleep(0.1)
a.add_remote_candidate(None)

# connect
run(asyncio.gather(
conn_a.connect(), conn_b.connect(),
add_candidates_later(conn_a, conn_b),
add_candidates_later(conn_b, conn_a)))

# send data a -> b
run(conn_a.send(b'howdee'))
data = run(conn_b.recv())
self.assertEqual(data, b'howdee')

# send data b -> a
run(conn_b.send(b'gotcha'))
data = run(conn_a.recv())
self.assertEqual(data, b'gotcha')

# close
run(conn_a.close())
run(conn_b.close())

0 comments on commit a9b729a

Please sign in to comment.