Skip to content

Commit deff28d

Browse files
committed
Implement loop.create_datagram_endpoint
1 parent cc10f73 commit deff28d

File tree

7 files changed

+753
-5
lines changed

7 files changed

+753
-5
lines changed

tests/test_udp.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import asyncio
2+
import logging
3+
import socket
4+
import uvloop
5+
import ssl
6+
import warnings
7+
8+
from asyncio import test_utils
9+
from uvloop import _testbase as tb
10+
11+
12+
class MyDatagramProto(asyncio.DatagramProtocol):
13+
done = None
14+
15+
def __init__(self, loop=None):
16+
self.state = 'INITIAL'
17+
self.nbytes = 0
18+
if loop is not None:
19+
self.done = asyncio.Future(loop=loop)
20+
21+
def connection_made(self, transport):
22+
self.transport = transport
23+
assert self.state == 'INITIAL', self.state
24+
self.state = 'INITIALIZED'
25+
26+
def datagram_received(self, data, addr):
27+
assert self.state == 'INITIALIZED', self.state
28+
self.nbytes += len(data)
29+
30+
def error_received(self, exc):
31+
assert self.state == 'INITIALIZED', self.state
32+
raise exc
33+
34+
def connection_lost(self, exc):
35+
assert self.state == 'INITIALIZED', self.state
36+
self.state = 'CLOSED'
37+
if self.done:
38+
self.done.set_result(None)
39+
40+
41+
class _TestUDP:
42+
def test_create_datagram_endpoint_addrs(self):
43+
class TestMyDatagramProto(MyDatagramProto):
44+
def __init__(inner_self):
45+
super().__init__(loop=self.loop)
46+
47+
def datagram_received(self, data, addr):
48+
super().datagram_received(data, addr)
49+
self.transport.sendto(b'resp:' + data, addr)
50+
51+
for lc in (('127.0.0.1', 0), None):
52+
if lc is None and not isinstance(self.loop, uvloop._Loop):
53+
# TODO This looks like a bug in asyncio -- if no local_addr
54+
# and no remote_addr are specified, the connection
55+
# that asyncio creates is not bound anywhere.
56+
return
57+
58+
with self.subTest(local_addr=lc):
59+
coro = self.loop.create_datagram_endpoint(
60+
TestMyDatagramProto, local_addr=lc, family=socket.AF_INET)
61+
s_transport, server = self.loop.run_until_complete(coro)
62+
host, port = s_transport.get_extra_info('sockname')
63+
64+
self.assertIsInstance(server, TestMyDatagramProto)
65+
self.assertEqual('INITIALIZED', server.state)
66+
self.assertIs(server.transport, s_transport)
67+
68+
coro = self.loop.create_datagram_endpoint(
69+
lambda: MyDatagramProto(loop=self.loop),
70+
family=socket.AF_INET,
71+
remote_addr=None if lc is None else (host, port))
72+
transport, client = self.loop.run_until_complete(coro)
73+
74+
self.assertIsInstance(client, MyDatagramProto)
75+
self.assertEqual('INITIALIZED', client.state)
76+
self.assertIs(client.transport, transport)
77+
78+
transport.sendto(b'xxx', (host, port) if lc is None else None)
79+
test_utils.run_until(self.loop, lambda: server.nbytes)
80+
self.assertEqual(3, server.nbytes)
81+
test_utils.run_until(self.loop, lambda: client.nbytes)
82+
83+
# received
84+
self.assertEqual(8, client.nbytes)
85+
86+
# extra info is available
87+
self.assertIsNotNone(transport.get_extra_info('sockname'))
88+
89+
# close connection
90+
transport.close()
91+
self.loop.run_until_complete(client.done)
92+
self.assertEqual('CLOSED', client.state)
93+
server.transport.close()
94+
self.loop.run_until_complete(server.done)
95+
96+
def test_create_datagram_endpoint_sock(self):
97+
sock = None
98+
local_address = ('127.0.0.1', 0)
99+
infos = self.loop.run_until_complete(
100+
self.loop.getaddrinfo(
101+
*local_address, type=socket.SOCK_DGRAM))
102+
for family, type, proto, cname, address in infos:
103+
try:
104+
sock = socket.socket(family=family, type=type, proto=proto)
105+
sock.setblocking(False)
106+
sock.bind(address)
107+
except:
108+
pass
109+
else:
110+
break
111+
else:
112+
assert False, 'Can not create socket.'
113+
114+
with sock:
115+
f = self.loop.create_connection(
116+
lambda: MyDatagramProto(loop=self.loop), sock=sock)
117+
tr, pr = self.loop.run_until_complete(f)
118+
self.assertIsInstance(pr, MyDatagramProto)
119+
tr.close()
120+
self.loop.run_until_complete(pr.done)
121+
122+
123+
class Test_UV_UDP(_TestUDP, tb.UVTestCase):
124+
pass
125+
126+
127+
class Test_AIO_UDP(_TestUDP, tb.AIOTestCase):
128+
pass

uvloop/dns.pyx

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,62 @@ cdef __convert_sockaddr_to_pyaddr(system.sockaddr* addr):
3939
raise RuntimeError("cannot convert sockaddr into Python object")
4040

4141

42+
cdef __convert_pyaddr_to_sockaddr(int family, object addr,
43+
system.sockaddr* res):
44+
cdef:
45+
int err
46+
int port
47+
int addr_len
48+
int scope_id = 0
49+
int flowinfo = 0
50+
51+
if family == uv.AF_INET:
52+
if not isinstance(addr, tuple):
53+
raise TypeError('AF_INET address must be tuple')
54+
if len(addr) != 2:
55+
raise ValueError('AF_INET address must be tuple of (host, port)')
56+
host, port = addr
57+
if isinstance(host, str):
58+
host = host.encode()
59+
if not isinstance(host, (bytes, bytearray)):
60+
raise TypeError('host must be a string or bytes object')
61+
62+
err = uv.uv_ip4_addr(host, port, <system.sockaddr_in*>res)
63+
if err < 0:
64+
raise convert_error(err)
65+
66+
elif family == uv.AF_INET6:
67+
if not isinstance(addr, tuple):
68+
raise TypeError('AF_INET6 address must be tuple')
69+
70+
addr_len = len(addr)
71+
if addr_len < 2 or addr_len > 4:
72+
raise ValueError(
73+
'AF_INET6 must be a tuple of 2-4 parameters: '
74+
'(host, port, flowinfo?, scope_id?)')
75+
76+
host = addr[0]
77+
if isinstance(host, str):
78+
host = host.encode()
79+
80+
port = addr[1]
81+
if addr_len > 2:
82+
flowinfo = addr[2]
83+
if addr_len > 3:
84+
scope_id = addr[3]
85+
86+
err = uv.uv_ip6_addr(host, port, <system.sockaddr_in6*>res)
87+
if err < 0:
88+
raise convert_error(err)
89+
90+
(<system.sockaddr_in6*>res).sin6_flowinfo = flowinfo
91+
(<system.sockaddr_in6*>res).sin6_scope_id = scope_id
92+
93+
else:
94+
raise ValueError(
95+
'epected AF_INET or AF_INET6 family, got {}'.format(family))
96+
97+
4298
@cython.freelist(DEFAULT_FREELIST_SIZE)
4399
cdef class AddrInfo:
44100
cdef:
@@ -58,6 +114,7 @@ cdef class AddrInfo:
58114
cdef unpack(self):
59115
cdef:
60116
list result = []
117+
system.addrinfo *ptr
61118

62119
if self.data is NULL:
63120
raise RuntimeError('AddrInfo.data is NULL')

uvloop/handles/udp.pxd

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
cdef class UVUDP(UVBaseTransport):
2+
cdef:
3+
bint __receiving
4+
int _family
5+
6+
bint _address_set
7+
system.sockaddr _address
8+
object _cached_py_address
9+
10+
cdef _init(self, Loop loop, unsigned int family)
11+
12+
cdef _set_remote_address(self, system.sockaddr address)
13+
14+
cdef _bind(self, system.sockaddr* addr, bint reuse_addr)
15+
cdef _open(self, int family, int sockfd)
16+
cdef _set_broadcast(self, bint on)
17+
18+
cdef inline __receiving_started(self)
19+
cdef inline __receiving_stopped(self)
20+
21+
cdef _send(self, object data, object addr)
22+
23+
cdef _on_receive(self, bytes data, object exc, object addr)
24+
cdef _on_sent(self, object exc)

0 commit comments

Comments
 (0)