4040import asyncio
4141from collections import namedtuple
4242
43+ from .. import error
4344from ..numbers import COAP_PORT
4445from .. import interfaces
4546from .generic_udp import GenericMessageInterface
@@ -87,7 +88,7 @@ class _DatagramServerSocketSimple(asyncio.DatagramProtocol):
8788 _Address = _Address
8889
8990 @classmethod
90- async def create (cls , bind , log , loop , new_message_callback , new_error_callback ):
91+ async def create (cls , bind , log , loop , message_interface : "GenericMessageInterface" ):
9192 if bind is None or bind [0 ] in ('::' , '0.0.0.0' , '' , None ):
9293 # If you feel tempted to remove this check, think about what
9394 # happens if two configured addresses can both route to a
@@ -100,7 +101,7 @@ async def create(cls, bind, log, loop, new_message_callback, new_error_callback)
100101 ready = asyncio .get_running_loop ().create_future ()
101102
102103 transport , protocol = await loop .create_datagram_endpoint (
103- lambda : cls (ready .set_result , new_message_callback , new_error_callback , log ),
104+ lambda : cls (ready .set_result , message_interface , log ),
104105 local_addr = bind ,
105106 reuse_port = defaults .has_reuse_port (),
106107 )
@@ -110,12 +111,13 @@ async def create(cls, bind, log, loop, new_message_callback, new_error_callback)
110111 # hostinfo), and can thus store the local hostinfo without distinction
111112 protocol .hostinfo_local = hostportjoin (bind [0 ], bind [1 ] if bind [1 ] != COAP_PORT else None )
112113
113- return await ready
114+ self = await ready
115+ self ._loop = loop
116+ return self
114117
115- def __init__ (self , ready_callback , new_message_callback , new_error_callback , log ):
118+ def __init__ (self , ready_callback , message_interface : "GenericMessageInterface" , log ):
116119 self ._ready_callback = ready_callback
117- self ._new_message_callback = new_message_callback
118- self ._new_error_callback = new_error_callback
120+ self ._message_interface = message_interface
119121 self .log = log
120122
121123 async def shutdown (self ):
@@ -124,10 +126,26 @@ async def shutdown(self):
124126 # interface like _DatagramClientSocketpoolSimple6
125127
126128 async def connect (self , sockaddr ):
127- # FIXME it might be necessary to resolve the address now to get a
128- # canonical form that can be recognized later when a package comes back
129+ # FIXME this is not regularly tested either
130+
129131 self .log .warning ("Sending initial messages via a server socket is not recommended" )
130- return self ._Address (self , sockaddr )
132+ # A legitimate case is when something stores return addresses as
133+ # URI(part)s and not as remotes. (In similar transports this'd also be
134+ # the case if the address's connection is dropped from the pool, but
135+ # that doesn't happen here since there is no pooling as there is no
136+ # per-connection state).
137+
138+ # getaddrinfo is not only to needed to resolve any host names (which
139+ # would not be recognized otherwise), but also to get a complete (host,
140+ # port, zoneinfo, whatwasthefourth) tuple from what is passed in as a
141+ # (host, port) tuple.
142+ addresses = await self ._loop .getaddrinfo (* sockaddr , family = self ._transport .get_extra_info ('socket' ).family )
143+ if not addresses :
144+ raise error .NetworkError ("No addresses found for %s" % sockaddr [0 ])
145+ # FIXME could do happy eyebals
146+ address = addresses [0 ][4 ]
147+ address = self ._Address (self , address )
148+ return address
131149
132150 # datagram protocol interface
133151
@@ -137,19 +155,23 @@ def connection_made(self, transport):
137155 del self ._ready_callback
138156
139157 def datagram_received (self , data , sockaddr ):
140- self ._new_message_callback (self ._Address (self , sockaddr ), data )
158+ self ._message_interface . _received_datagram (self ._Address (self , sockaddr ), data )
141159
142160 def error_received (self , exception ):
143161 # This is why this whole implementation is a bad idea (but still the best we got on some platforms)
144162 self .log .warning ("Ignoring error because it can not be mapped to any connection: %s" , exception )
145163
146164 def connection_lost (self , exception ):
147165 if exception is None :
148- pass
166+ pass # regular shutdown
149167 else :
150168 self .log .error ("Received unexpected connection loss: %s" , exception )
151169
152170class MessageInterfaceSimpleServer (GenericMessageInterface ):
171+ # for alteration by tinydtls_server
172+ _default_port = COAP_PORT
173+ _serversocket = _DatagramServerSocketSimple
174+
153175 @classmethod
154176 async def create_server (cls , bind , ctx : interfaces .MessageManager , log , loop ):
155177 self = cls (ctx , log , loop )
@@ -158,11 +180,17 @@ async def create_server(cls, bind, ctx: interfaces.MessageManager, log, loop):
158180 # servers that want a random port (eg. when the service URLs are
159181 # advertised out-of-band anyway). LwM2M clients should use simple6
160182 # instead as outlined there.
161- bind = (bind [0 ], COAP_PORT if bind [1 ] is None else bind [1 ])
183+ bind = (bind [0 ], self . _default_port if bind [1 ] is None else bind [1 ] + ( self . _default_port - COAP_PORT ) )
162184
163- self ._pool = await _DatagramServerSocketSimple .create (bind , log , self ._loop , self ._received_datagram , self ._received_exception )
185+ # Cyclic reference broken during shutdown
186+ self ._pool = await self ._serversocket .create (bind , log , self ._loop , self )
164187
165188 return self
166189
167190 async def recognize_remote (self , remote ):
168- return isinstance (remote , _Address ) and remote in remote .serversocket is self ._pool
191+ # FIXME: This is never tested (as is the connect method) because all
192+ # tests create client contexts client-side (which don't build a
193+ # simplesocketserver), and because even when a server context is
194+ # created, there's a simple6 that grabs such addresses before a request
195+ # is sent out
196+ return isinstance (remote , _Address ) and remote .serversocket is self ._pool
0 commit comments