from qmsk.net.socket.socket cimport *
from qmsk.net.socket.addr cimport sockaddr, build_sockaddr
cimport qmsk.net.socket.platform as platform
cimport qmsk.net.libc as libc, qmsk.net.py as py
from qmsk.net.py cimport raise_errno
cdef parse_sockaddr (platform.sockaddr **sa_ptr, platform.socklen_t *sa_len, sockaddr addr, int optional = 0) :
if addr is not None :
addr._get_sockaddr(sa_ptr, sa_len)
elif optional :
sa_ptr[0] = NULL
sa_len[0] = 0
else :
raise ValueError(addr)
cdef parse_buf (void **buf_ptr, size_t *buf_len, object buf, int optional = 0) :
cdef libc.ssize_t tmp_len
if buf is not None :
# XXX: test that except works right
# XXX: this complains about const...
py.PyObject_AsCharBuffer(buf, <char **> buf_ptr, &tmp_len)
# XXX: ensure that this is >= 0
buf_len[0] = tmp_len
elif optional :
buf_ptr[0] = NULL
buf_len[0] = 0
else :
raise ValueError(buf)
# XXX: do some GIL-releasin'
cdef class socket :
def __cinit__ (self) :
"""
Initialize the socket to set fd to -1, so that we dont't try and close stdin too often :)
"""
self.fd = -1
def __init__ (self, int family = platform.AF_INET, int socktype = platform.SOCK_STREAM, int protocol = 0, int fd = -1) :
"""
Create a new socket endpoint with the given family/domain, socktype and optionally, specific protocol,
unless the fd argument is given as >= 0, in which case it used directly.
family - one of AF_*
socktype - one of SOCK_*
protocol - one of IPPROTO_* or zero to select default
"""
if fd >= 0 :
# given fd
self.fd = fd
else :
# socket()
self.fd = platform.socket(family, socktype, protocol)
# trap
if self.fd < 0 :
raise_errno('socket')
def fileno (self) :
"""
Returns the OS-level file desriptor for this socket as an integer
"""
return self.fd
def bind (self, sockaddr addr) :
"""
Bind this socket to the given local socket address. The given sockaddr should be of the same or a
compatible address family.
addr - the local address to bind to. The port may be zero to let the system choose an unused
ephemeral port.
"""
cdef platform.sockaddr *sa
cdef platform.socklen_t sa_len
# XXX: require non-NULL addr?
parse_sockaddr(&sa, &sa_len, addr, 1)
# bind()
if platform.bind(self.fd, sa, sa_len) :
raise_errno('bind')
def listen (self, int backlog) :
"""
Listen for connections, marking this socket as a passive socket, which can accept incoming connection
requests using sock.accept().
It is customary to call .bind() before .listen().
backlog - maximum number of pending connections (those not yet .accept()'d).
"""
# listen()
if platform.listen(self.fd, backlog) :
raise_errno('listen')
def connect (self, sockaddr addr) :
"""
Initiate a connection, connecting this socket to the remote endpoint specified by `addr`. The given sockaddr
should be of the same or a compatible address family.
If the socket is in non-blocking mode, this will presumeably return errno.EINPROGRESS.
If the socket has not yet been bound (using .bind()), the system will pick an appropriate local address and
ephemeral port.
addr - the remote address to connect to.
"""
cdef platform.sockaddr *sa
cdef platform.socklen_t sa_len
# XXX: require non-NULL addr?
parse_sockaddr(&sa, &sa_len, addr, 1)
# connect()
if platform.connect(self.fd, sa, sa_len) :
raise_errno('connect')
def accept (self) :
"""
Accept a connection, dequeueing the first pending connection and returning a new sock object for it. This
socket must be a connection-based socket (SOCK_STREAM/SOCK_SEQPACKET) and in the passive listening mode
(.listen()).
This returns a (sock, src_addr) tuple:
sock - the newly created sock, corresponding to the incoming connection
src_addr - the remote address of the incoming connection
"""
# prep the sockaddr that we will return
cdef platform.sockaddr_storage ss
cdef platform.socklen_t ss_len = sizeof(ss)
# accept()
cdef socket_t sock_fd = platform.accept(self.fd, <platform.sockaddr *> &ss, &ss_len)
if sock_fd < 0 :
raise_errno('accept')
try :
# prep the new socket
sock_obj = socket(sock_fd)
except :
# XXX: don't leak the socket fd? How does socket.__init__ handle this?
platform.close(sock_fd)
raise
# prep the new addr
cdef sockaddr src_addr = build_sockaddr(<platform.sockaddr *> &ss, ss_len)
return sock_obj, src_addr
def send (self, object buf, int flags = 0) :
"""
Transmit a message to the connected remote endpoint.
buf - the data to send
flags - (optional) MSG_* flags to send with
Returns the number of bytes sent, which may be less than the length of buf.
"""
cdef void *buf_ptr
cdef size_t buf_len
cdef libc.ssize_t ret
parse_buf(&buf_ptr, &buf_len, buf, 0)
# send()
ret = platform.send(self.fd, buf_ptr, buf_len, flags)
if ret < 0 :
raise_errno('send')
else :
return ret
def sendto (self, object buf, int flags = 0, sockaddr addr = None) :
"""
Transmit a message to the given remote endpoint. If this socket is connected, the addr must not be
specified, and this acts like send()
buf - the data to send
flags - (optional) MSG_* flags to send with
addr - (optional) target address
Returns the number of bytes sent, which may be less than the length of buf.
"""
cdef void *buf_ptr
cdef size_t buf_len
cdef libc.ssize_t ret
cdef platform.sockaddr *sa
cdef platform.socklen_t sa_len
parse_sockaddr(&sa, &sa_len, addr, 1)
parse_buf(&buf_ptr, &buf_len, buf, 0)
# send()
ret = platform.sendto(self.fd, buf_ptr, buf_len, flags, sa, sa_len)
if ret < 0 :
raise_errno('sendto')
else :
return ret
def sendmsg (self, sockaddr addr = None, iov = None, control = None, int flags = 0) :
"""
Transmit an extended message to the given remote endpoint (or default for connected sockets) with the given
extra parameters.
addr - (optional) destination address (struct msghdr::msg_name)
iov - (optional) sequence of read-buffers to transmit
control - (optional) control message to transmit
flags - (optional) MSG_* flags to send with
Returns the number of bytes sent, which may be less than the total length of iov.
"""
cdef libc.ssize_t ret
cdef libc.iovec *iovec
cdef platform.msghdr msg
libc.memset(&msg, 0, sizeof(msg))
parse_sockaddr(<platform.sockaddr **> &msg.msg_name, &msg.msg_namelen, addr, 1)
parse_buf(&msg.msg_control, &msg.msg_controllen, control, 1)
# iov
if iov :
iov = tuple(iov)
# numerb of bufs = number of iovecs
msg.msg_iovlen = len(iov)
# alloca the required number of iovec's
msg.msg_iov = <libc.iovec *> libc.alloca(msg.msg_iovlen * sizeof(libc.iovec))
# fill in the iovecs
for i, buf in enumerate(iov) :
iovec = &msg.msg_iov[i]
parse_buf(&iovec.iov_base, &iovec.iov_len, buf, 1)
# sendmsg()
ret = platform.sendmsg(self.fd, &msg, flags)
if ret < 0 :
raise_errno('sendmsg')
else :
return ret
def write (self, object buf) :
"""
Write data to socket, mostly equivalent to send() with flags=0.
buf - the data to send
Returns the number of bytes sent, which may be less than the length of buf.
"""
cdef void *buf_ptr
cdef size_t buf_len
cdef libc.ssize_t ret
parse_buf(&buf_ptr, &buf_len, buf, 0)
# send()
ret = libc.write(self.fd, buf_ptr, buf_len)
if ret < 0 :
raise_errno('write')
else :
return ret
def writev (self, iov) :
"""
Write data to a socket from multiple read-buffers.
iov - sequence of read-buffers to transmit
Returns the number of bytes sent, which may be less than the total length of iov.
"""
# iov
cdef libc.iovec *iov_list = NULL
cdef size_t iov_count = 0
cdef libc.iovec *iovec
iov = tuple(iov)
# numerb of bufs = number of iovecs
iov_count = len(iov)
# alloca the required number of iovec's
iov_list = <libc.iovec *> libc.alloca(iov_count * sizeof(libc.iovec))
# fill in the iovecs
for i, buf in enumerate(iov) :
iovec = &iov_list[i]
parse_buf(&iovec.iov_base, &iovec.iov_len, buf, 1)
# sendmsg()
ret = libc.writev(self.fd, iov_list, iov_count)
if ret < 0 :
raise_errno('writev')
else :
return ret
def recv (self, size_t len, int flags = 0) :
"""
Recieve a message, reading and returning at most `len` bytes.
len - size of buffer to use for recv
flags - (optional) MSG_* flags to use for recv()
Returns the recieved data as a newly allocated string of the correct length.
"""
# alloc a new return str
# XXX: len overflow...
cdef object str = py.PyString_FromStringAndSize(NULL, len)
cdef char *buf = py.PyString_AS_STRING(str)
# recv()
cdef libc.ssize_t ret = platform.recv(self.fd, buf, len, flags)
if ret < 0 :
raise_errno('recv')
# XXX: figure out how to call _PyString_Resize
return str[:ret]
def recvfrom (self, size_t len, int flags = 0) :
"""
Recieve a message, reading at most `len` bytes, also returning the source address.
len - size of buffer to use for recv
flags - (optional) MSG_* flags to use for recvfrom()
Returns the recieved data and the source address as a (buf, src_addr) tuple :
buf - a newly allocated string containing the recieved data, of the correct length
src_addr - the source address the message was recieved from
"""
# alloc a new return str
# XXX: len overflow...
cdef object str = py.PyString_FromStringAndSize(NULL, len)
cdef char *buf = py.PyString_AS_STRING(str)
# prep the sockaddr that we will return
cdef platform.sockaddr_storage ss
cdef platform.socklen_t ss_len = sizeof(ss)
# recvfrom()
cdef libc.ssize_t ret = platform.recvfrom(self.fd, buf, len, flags, <platform.sockaddr *> &ss, &ss_len)
if ret < 0 :
raise_errno('recv')
# prep the new addr
cdef sock_addr = build_sockaddr(<platform.sockaddr *> &ss, ss_len)
# XXX: figure out how to call _PyString_Resize
return str[:ret], sock_addr
def recvmsg (self, bint recv_addr = True, object iov_lens = None, size_t control_len = 0, int flags = 0) :
"""
Recieve a message along with some extra data.
recv_addr - ask for and return a sockaddr for the source address of the message?
lenv - (optional) sequence of buffer sizes to use for the message data iov
control_len - (optional) amount of auxiliary data to recieve
flags - (optional) flags to pass to recvmsg()
Returns a (name, iov, control, flags) tuple :
name - the source address of the message, or None
iov - sequence of strings containing the recieved data, each at most lenv[x] bytes long
control - string containing recieved control message, if any
flags - recieved flags
"""
cdef platform.msghdr msg
libc.memset(&msg, 0, sizeof(msg))
# prep the sockaddr that we will return
cdef platform.sockaddr_storage ss
# ask for a name?
if recv_addr :
msg.msg_name = <void *> &ss
msg.msg_namelen = sizeof(ss)
# build iov?
if iov_lens :
# XXX: implement
pass
# build control buffer?
if control_len :
# XXX: implement
pass
# recvmsg()
cdef libc.ssize_t ret = platform.recvmsg(self.fd, &msg, flags)
if ret < 0 :
raise_errno('recvmsg')
# name?
cdef sockaddr name = None
if msg.msg_name and msg.msg_namelen :
name = build_sockaddr(<platform.sockaddr *> msg.msg_name, msg.msg_namelen)
# iov?
cdef object iov = None
if ret :
assert msg.msg_iov and msg.msg_iovlen
# XXX: implement
pass
# control?
cdef object control = None
if msg.msg_control and msg.msg_controllen :
# XXX: implement
pass
return name, iov, control, msg.msg_flags
def shutdown (self, how) :
"""
Shutdown part of a full-duplex connection.
how - one of SHUT_*
This does not affect this socket's fd.
"""
# shutdown()
if platform.shutdown(self.fd, how) :
raise_errno('shutdown')
def close (self) :
"""
Close the socket fd if we have one, invalidating it if succesful.
Note that this will raise an error and keep the fd if the system close() returns an error.
Calling this again after a succesfull close() does nothing.
XXX: SO_LINGER/blocking?
>>> s = socket()
>>> s.fd >= 0
True
>>> s.close()
>>> s.fd >= 0
False
>>> s.close()
"""
# ignore if already closed
if self.fd < 0 :
return
# close()
if libc.close(self.fd) :
raise_errno('close')
# invalidate
self.fd = -1
def __dealloc__ (self) :
"""
Close the socket fd if one is set, ignoring any errors from close
"""
if self.fd >= 0 :
if libc.close(self.fd) :
# XXX: at least warn... ?
pass