add CallbackEvent helper, and move getaddrinfo from endpoint into its own function
"""
Socket addresses at various levels:
sockaddr - specific network-level address for socket operations
addrinfo - information on a specific sockaddr including its socket parameters
endpoint - human-readable network address, corresponding to multiple sockaddr's
"""
cimport qmsk.net.libc as libc
cimport qmsk.net.socket.platform as platform
cdef class sockaddr :
"""
A network-level socket address
XXX: not abstract enough. Functions/properties like getnameinfo/addr/port do not work for e.g. sockaddr_un
>>> sockaddr().family
0
>>> sockaddr().port
Traceback (most recent call last):
...
NotImplementedError
>>> sockaddr().getnameinfo()
Traceback (most recent call last):
...
NotImplementedError
"""
# address family
# XXX: this should be a class constant! It's part of our type safety!
cdef readonly platform.sa_family_t family
cdef void _init_family (self, platform.sa_family_t family = ?)
# get the sockaddr/socklen
# each of these can be NULL to ignore it
cdef int _get_sockaddr (self, platform.sockaddr **sa_ptr, platform.socklen_t *sa_len) except -1
cdef platform.sockaddr* _get_sockaddr_ptr (self) except NULL
cdef platform.socklen_t _get_sockaddr_len (self) except -1
# set the sockaddr, socklen must match
cdef int _set_sockaddr (self, platform.sockaddr *sa, size_t sa_len) except -1
# build a sockaddr from the given sockaddr struct, based on sa_family
cdef sockaddr build_sockaddr (platform.sockaddr *sa, size_t sa_len)
cdef class addrinfo :
"""
A socket-level endpoint address, which contains the full socket parameters and an bind/connect address.
A full addrinfo struct is stored, but ai_canonname and ai_addr are stored as (optional) objects outside of the
addrinfo struct.
>>> from qmsk.net.socket import af_inet
>>> ai = addrinfo()
>>> ai.addr
>>> ai.canonname
>>> print addrinfo(addr=af_inet.sockaddr_in())
family=0, socktype=0, protocol=0, addr=0.0.0.0:0, canonname=None
"""
# canonname is not stored
# may be NULL
cdef platform.addrinfo ai
# may be NULL
cdef sockaddr ai_addr
cdef object ai_canonname
# update self.ai.ai_* to reflect self.ai_*
cdef _init_ai_members (self)
# set the contents of self.ai from the given real addrinfo
# this ignores the ai_canonname attribute
cdef _init_addrinfo (self, platform.addrinfo *ai)
# build and return a new addrinfo instance
cdef addrinfo build_addrinfo (platform.addrinfo *c_ai)
cdef class endpoint :
"""
A network-level socket endpoint. This is the level that humans mostly work with, but the tricky bit is that
an endpoint can map to more than one sockaddr...
Hence, endpoints are stored as human-readable hostname/service strings, which are then translated to sockaddrs
using getaddrinfo.
>>> from __future__ import absolute_import; import socket as _socket
>>> e = endpoint('127.0.0.1', 80)
>>> str(e)
'hostname=127.0.0.1, service=80'
>>> res = e.getaddrinfo(_socket.AF_UNSPEC, _socket.SOCK_STREAM)
>>> len(res)
1
>>> str(res[0])
'family=2, socktype=1, protocol=6, addr=127.0.0.1:80, canonname=None'
>>> e = endpoint('2001::5', 80)
>>> str(e)
'hostname=2001::5, service=80'
>>> res = e.getaddrinfo(_socket.AF_UNSPEC, _socket.SOCK_STREAM)
>>> len(res)
1
>>> str(res[0])
'family=10, socktype=1, protocol=6, addr=[2001::5]:80, canonname=None'
"""
# our defining attributes, set via __init__
cdef public object hostname, service