rename `NativeString` to `CString`
[nit.git] / lib / socket / socket.nit
index 2f7519d..2d17656 100644 (file)
 module socket
 
 private import socket_c
-intrude import standard::stream
+intrude import core::stream
 
-# A general TCP socket, either a `TCPStream` or a `TCPServer`
+# A general Socket, either TCP or UDP
 abstract class Socket
 
        # Underlying C socket
        private var native: NativeSocket is noinit
 
+       # Is this socket closed?
+       var closed = false
+
+       # Set whether calls to `accept` are blocking
+       fun blocking=(value: Bool)
+       do
+               # We use the opposite from the native version as the native API
+               # is closer to the C API. In the Nity API, we use a positive version
+               # of the name.
+               native.non_blocking = not value
+       end
+end
+
+# A general TCP socket, either a `TCPStream` or a `TCPServer`
+abstract class TCPSocket
+       super Socket
+
        # Port used by the socket
        var port: Int
 
@@ -33,14 +50,11 @@ abstract class Socket
        #
        # Formatted as xxx.xxx.xxx.xxx.
        var address: String is noinit
-
-       # Is this socket closed?
-       var closed = false
 end
 
 # Simple communication stream with a remote socket
 class TCPStream
-       super Socket
+       super TCPSocket
        super BufferedReader
        super Writer
        super PollableReader
@@ -57,7 +71,7 @@ class TCPStream
        # Creates a socket connection to host `host` on port `port`
        init connect(host: String, port: Int)
        do
-               _buffer = new NativeString(1024)
+               _buffer = new CString(1024)
                _buffer_pos = 0
                native = new NativeSocket.socket(new NativeSocketAddressFamilies.af_inet,
                        new NativeSocketTypes.sock_stream, new NativeSocketProtocolFamilies.pf_null)
@@ -75,8 +89,7 @@ class TCPStream
                var hostent = sys.gethostbyname(host.to_cstring)
                if hostent.address_is_null then
                        # Error in name lookup
-                       var err = sys.h_errno
-                       last_error = new IOError(err.to_s)
+                       last_error = new IOError.from_h_errno
 
                        closed = true
                        end_reached = true
@@ -95,7 +108,7 @@ class TCPStream
                end_reached = closed
                if closed then
                        # Connection failed
-                       last_error = new IOError(errno.strerror)
+                       last_error = new IOError.from_errno
                end
 
                prepare_buffer(1024)
@@ -136,12 +149,11 @@ class TCPStream
                return pollin(events, timeout).length != 0
        end
 
-       # Checks if the socket still is connected
+       # Is this socket still connected?
        fun connected: Bool
        do
                if closed then return false
-               var events = [new NativeSocketPollValues.pollhup, new NativeSocketPollValues.pollerr]
-               if pollin(events, 0).length == 0 then
+               if native.poll_hup_err == 0 then
                        return true
                else
                        closed = true
@@ -204,7 +216,7 @@ class TCPStream
                if _buffer_capacity >= len then return
                _buffer_capacity = len
 
-               var ns = new NativeString(_buffer_capacity)
+               var ns = new CString(_buffer_capacity)
                _buffer.copy_to(ns, _buffer_length - _buffer_pos, _buffer_pos, 0)
                _buffer = ns
        end
@@ -232,7 +244,7 @@ end
 #
 # Create streams to communicate with clients using `accept`.
 class TCPServer
-       super Socket
+       super TCPSocket
 
        private var addrin: NativeSocketAddrIn is noinit
 
@@ -287,15 +299,6 @@ class TCPServer
                return new TCPStream.server_side(native)
        end
 
-       # Set whether calls to `accept` are blocking
-       fun blocking=(value: Bool)
-       do
-               # We use the opposite from the native version as the native API
-               # is closer to the C API. In the Nity API, we use a positive version
-               # of the name.
-               native.non_blocking = not value
-       end
-
        # Close this socket
        fun close
        do
@@ -353,3 +356,164 @@ class SocketObserver
                return native.select(max.native, read_set.native, write_set.native, except_set.native, timeval) > 0
        end
 end
+
+# Socket over UDP, sends and receive data without the need for a connection
+class UDPSocket
+       super Socket
+
+       # Last error raised by this socket
+       var error: nullable Error = null
+
+       init do native = new NativeSocket.socket(
+                       new NativeSocketAddressFamilies.af_inet,
+                       new NativeSocketTypes.sock_dgram,
+                       new NativeSocketProtocolFamilies.pf_null)
+
+       # Bind this socket to an `address`, on `port` (to all addresses if `null`)
+       #
+       # On error, sets `error` appropriately.
+       fun bind(address: nullable Text, port: Int)
+       do
+               var addr_in = new NativeSocketAddrIn
+               addr_in.port = port
+               if address != null then
+                       # FIXME replace all use of gethostbyname with something not obsolete
+                       var hostent = sys.gethostbyname(address.to_cstring)
+                       if hostent.address_is_null then
+                               error = new IOError.from_h_errno
+                               addr_in.free
+                               return
+                       end
+
+                       addr_in.fill_from_hostent hostent
+               else
+                       addr_in.family = new NativeSocketAddressFamilies.af_inet
+                       addr_in.address_any
+               end
+
+               if native.bind(addr_in) != 0 then error = new IOError.from_errno
+
+               addr_in.free
+       end
+
+       # Receive `length` bytes of data from any sender
+       #
+       # On error, returns an empty string and sets `error` appropriately.
+       fun recv(length: Int): String
+       do
+               var buf = new CString(length)
+               var len = native.recvfrom(buf, length, 0, new NativeSocketAddrIn.nul)
+               if len == -1 then
+                       error = new IOError.from_errno
+                       return ""
+               end
+               return buf.to_s_with_length(len)
+       end
+
+       # Receive `length` bytes of data from any sender and store the sender info in `sender.item`
+       #
+       # On error, returns an empty string and sets `error` appropriately.
+       fun recv_from(length: Int, sender: Ref[nullable SocketAddress]): String
+       do
+               var src = new NativeSocketAddrIn
+               var buf = new CString(length)
+
+               var len = native.recvfrom(buf, length, 0, src)
+               if len == -1 then
+                       error = new IOError.from_errno
+                       src.free
+                       return ""
+               end
+
+               sender.item = new SocketAddress(src)
+               return buf.to_s_with_length(len)
+       end
+
+       # Send `data` to `dest_address` on `port`
+       #
+       # On error, sets `error` appropriately.
+       fun send_to(dest_address: Text, port: Int, data: Text)
+       do
+               var hostent = sys.gethostbyname(dest_address.to_cstring)
+               if hostent.address_is_null then
+                       error = new IOError.from_h_errno
+                       return
+               end
+
+               var dest = new NativeSocketAddrIn
+               dest.fill_from_hostent hostent
+               dest.port = port
+               native.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.broadcast, 1)
+
+               var buf = data.to_cstring
+               if native.sendto(buf, data.length, 0, dest) == -1 then
+                       error = new IOError.from_errno
+               end
+               dest.free
+       end
+
+       # Enable broadcasting for this socket
+       #
+       # On error, sets `error` appropriately.
+       fun enable_broadcast=(value: Bool) do
+               var res = native.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.broadcast, value.to_i)
+               if res == -1 then error = new IOError.from_errno
+       end
+
+       # Broadcast `data` on the network on `port`
+       #
+       # On error, sets `error` appropriately.
+       #
+       # Require: setting `enable_broadcast = true`
+       fun broadcast(port: Int, data: Text)
+       do
+               var addr_in = new NativeSocketAddrIn
+               addr_in.port = port
+               addr_in.family = new NativeSocketAddressFamilies.af_inet
+               addr_in.address_broadcast
+
+               var buf = data.to_cstring
+               if native.sendto(buf, data.length, 0, addr_in) == -1 then
+                       error = new IOError.from_errno
+               end
+
+               addr_in.free
+       end
+end
+
+# Address of a socket in the Internet namespace
+#
+# Used in one of the out parameters of `UDPSocket::recv_from`.
+class SocketAddress
+       super FinalizableOnce
+
+       # FIXME make init private
+
+       private var native: NativeSocketAddrIn
+
+       init
+       do
+               address = native.address.to_s
+               port = native.port
+       end
+
+       # Internet address
+       var address: String is noinit
+
+       # Port of the socket
+       var port: Int is noinit
+
+       redef fun ==(o) do return o isa SocketAddress and o.address == address and o.port == port
+
+       redef fun finalize_once do native.free
+end
+
+redef class IOError
+       # Fill a new `IOError` from the message of `errno`
+       init from_errno do init errno.strerror
+
+       # Fill a new `IOError` from the message of `h_errno`
+       #
+       # Used with `gethostbyname`.
+       init from_h_errno do init h_errno.to_s
+end