;; -*- Mode: Lisp; Syntax: Common-Lisp -*-
;; $Id$

;; This file is based on SBCL's SB-BSD-SOCKET module and has been
;; heavily modified to work with ECL by Julian Stecklina.
;; Port to Windows Sockets contributed by M. Goffioul.

;; You may do whatever you want with this file. (PUBLIC DOMAIN)

;; Trivial stuff is copied from SBCL's SB-BSD-SOCKETS, which is also
;; in the public domain.

(defpackage "SB-BSD-SOCKETS"
  (:use "CL" "FFI" "SI")
  (:export "GET-HOST-BY-NAME" "GET-HOST-BY-ADDRESS"
           "SOCKET-BIND" "SOCKET-ACCEPT" "SOCKET-CONNECT"
           "SOCKET-PEERNAME" "SOCKET-NAME" "SOCKET-LISTEN"
           "SOCKET-RECEIVE" "SOCKET-CLOSE" "SOCKET-MAKE-STREAM"
           "GET-PROTOCOL-BY-NAME" "MAKE-INET-ADDRESS" "LOCAL-SOCKET"
           "UNKNOWN-PROTOCOL" "UNKNOWN-PROTOCOL-NAME"
           "SOCKET" "INET-SOCKET" "SOCKET-FILE-DESCRIPTOR" #+:win32 "NAMED-PIPE-SOCKET"
           "SOCKET-FAMILY" "SOCKET-PROTOCOL" "SOCKET-TYPE"
           "SOCKET-ERROR" "NAME-SERVICE-ERROR" "NON-BLOCKING-MODE"
           "HOST-ENT-NAME" "HOST-ENT-ALIASES" "HOST-ENT-ADDRESS-TYPE"
           "HOST-ENT-ADDRESSES" "HOST-ENT" "HOST-ENT-ADDRESS" "SOCKET-SEND"))

(in-package "SB-BSD-SOCKETS")

;; Obviously this requires the one or other form of BSD compatible
;; socket interface.

#+(or :win32 :mingw32)
(eval-when (:compile-toplevel :load-toplevel :execute)
  (pushnew :wsock *features*))

;; Include the neccessary headers
#-:wsock
(clines
 "#include <sys/types.h>"
 "#include <sys/socket.h>"
 "#include <sys/un.h>"
 "#define wincoerce(t,x) (x)"
 #-:win32
 "#include <sys/time.h>"
 "#include <netdb.h>"
 "#include <string.h>"
 "#include <unistd.h>"
 "#include <netinet/in.h>"
 "#include <netinet/tcp.h>"
 "#include <errno.h>"
 "#include <fcntl.h>"
 #+:cygwin
 "#ifndef MSG_WAITALL
  #define MSG_WAITALL 0
  #endif"
 "#ifndef MSG_CONFIRM"
 "#define MSG_CONFIRM 0"
 "#endif"
 "#ifndef MSG_NOSIGNAL"
 "#define MSG_NOSIGNAL 0"
 "#endif"
 "#ifndef MSG_DONTWAIT"
 "#define MSG_DONTWAIT 0"
 "#endif"
 "#ifndef MSG_EOR"
 "#define MSG_EOR 0"
 "#endif")
#+:wsock
(clines
 "#include <winsock2.h>"
 "typedef unsigned int uint32_t;"
 "#define wincoerce(t,x) ((t)(x))"
 #-:mingw32
 "typedef int ssize_t;"
 "typedef int socklen_t;"
 "#define MSG_WAITALL 0"
 "#define MSG_EOR 0"
 "#define MSG_DONTWAIT 0"
 "#define MSG_NOSIGNAL 0"
 "#define MSG_CONFIRM 0"
 "#define MSG_TRUNC 0"
 "#include <errno.h>"
 "#include <fcntl.h>"
 "#include <stdio.h>")

#+windows
(clines
 "#include <ws2tcpip.h>")

#+:wsock
(progn
  (defvar +wsock-initialized+ nil)
  (defun wsock-initialize ()
    (unless +wsock-initialized+
      (if (c-inline () () :object
        "
{
        WSADATA wsadata;
        cl_object output;
        ecl_disable_interrupts();
        if (WSAStartup(MAKEWORD(2,2), &wsadata) == NO_ERROR)
                output = ECL_T;
        else
                output = ECL_NIL;
        ecl_enable_interrupts();
        @(return) = output;
}")
        (setf +wsock-initialized+ t)
        (error "Unable to initialize Windows Socket library"))))
  (wsock-initialize)
); #+:wsock

(eval-when (:compile-toplevel :execute)
  (defmacro c-constant (c-name)
    `(c-inline () () :int ,c-name :one-liner t))
  (defmacro define-c-constants (&rest args)
    `(let () ; Prevents evaluation of constant value form
       ,@(loop
            for (lisp-name c-name) on args by #'cddr
            collect `(defconstant ,lisp-name (c-constant ,c-name))))))

#+:wsock
(Clines
 "#define AF_LOCAL -1"
)

(define-c-constants
  +af-inet+ "AF_INET"
  +af-local+ #-(or sun4sol2 aix) "AF_LOCAL" #+(or sun4sol2 aix) "AF_UNIX"
  +eagain+ "EAGAIN"
  +eintr+ "EINTR")

#+:android
(define-c-constants
  +ipproto_ip+ "IPPROTO_IP"
  +ipproto_icmp+ "IPPROTO_ICMP"
  +ipproto_igmp+ "IPPROTO_IGMP"
  +ipproto_ipip+ "IPPROTO_IPIP"
  +ipproto_tcp+ "IPPROTO_TCP"
  +ipproto_egp+ "IPPROTO_EGP"
  +ipproto_pup+ "IPPROTO_PUP"
  +ipproto_udp+ "IPPROTO_UDP"
  +ipproto_idp+ "IPPROTO_IDP"
  +ipproto_dccp+ "IPPROTO_DCCP"
  +ipproto_rsvp+ "IPPROTO_RSVP"
  +ipproto_gre+ "IPPROTO_GRE"
  +ipproto_ipv6+ "IPPROTO_IPV6"
  +ipproto_esp+ "IPPROTO_ESP"
  +ipproto_ah+ "IPPROTO_AH"
  +ipproto_pim+ "IPPROTO_PIM"
  +ipproto_comp+ "IPPROTO_COMP"
  +ipproto_sctp+ "IPPROTO_SCTP"
  +ipproto_raw+ "IPPROTO_RAW")

#+:wsock
(defconstant +af-named-pipe+ -2)

(Clines
 "
static void fill_inet_sockaddr(struct sockaddr_in *sockaddr, int port,
                               int a1, int a2, int a3, int a4)
{
#if defined(_MSC_VER) || defined(mingw32)
        memset(sockaddr,0,sizeof(struct sockaddr_in));
#else
        bzero(sockaddr,sizeof(struct sockaddr_in));
#endif
        sockaddr->sin_family = AF_INET;
        sockaddr->sin_port = htons(port);
        sockaddr->sin_addr.s_addr= htonl((uint32_t)a1<<24 | (uint32_t)a2<<16 | (uint32_t)a3<<8 | (uint32_t)a4) ;

}
")

;; Foreign functions

(defentry ff-socket (:int :int :int) (:int "socket") :no-interrupts t)
(defentry ff-listen (:int :int) (:int "listen") :no-interrupts t)
(defentry ff-close (:int) (:int "close") :no-interrupts t)
#+:wsock (defentry ff-closesocket (:int) (:int "closesocket") :no-interrupts t)

;;; This courtesy of Pierre Mai in comp.lang.lisp 08 Jan 1999 00:51:44 +0100
;;; Message-ID: <87lnjebq0f.fsf@orion.dent.isdn.cs.tu-berlin.de>

(defun split (string &optional max (ws '(#\Space #\Tab)))
  "Split `string' along whitespace as defined by the sequence `ws'.
The whitespace is elided from the result.  The whole string will be
split, unless `max' is a non-negative integer, in which case the
string will be split into `max' tokens at most, the last one
containing the whole rest of the given `string', if any."
  (flet ((is-ws (char) (find char ws)))
    (loop for start = (position-if-not #'is-ws string)
          then (position-if-not #'is-ws string :start index)
          for index = (and start
                           (if (and max (= (1+ word-count) max))
                               nil
                             (position-if #'is-ws string :start start)))
          while start
          collect (subseq string start index)
          count 1 into word-count
          while index)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; NAME RESOLVING
;;;

(defclass host-ent ()
  ((name :initarg :name :accessor host-ent-name)
   (aliases :initarg :aliases :accessor host-ent-aliases)
   (address-type :initarg :type :accessor host-ent-address-type)
                                        ; presently always AF_INET
   (addresses :initarg :addresses :accessor host-ent-addresses))
  (:documentation ""))

(defgeneric host-ent-address (host-ent)
  (:documentation ""))

(defmethod host-ent-address ((host-ent host-ent))
  (car (host-ent-addresses host-ent)))

(defun get-host-by-name (host-name)
  "Returns a HOST-ENT instance for HOST-NAME or throws some kind of condition.
HOST-NAME may also be an IP address in dotted quad notation or some
other weird stuff - see getaddrinfo(3) for details."
  (multiple-value-bind (errno canonical-name addresses aliases)
      (c-inline (host-name :test #'equalp) (:cstring :object :object)
                (values :int :object :object :object)
                "
{
    struct addrinfo hints;
    struct addrinfo *result;
    cl_object host_name = ECL_NIL;
    cl_object aliases = ECL_NIL;
    cl_object addresses = ECL_NIL;
    int err;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;                     /* IPv4 */
    hints.ai_socktype = 0;                         /* Any type */
    hints.ai_protocol = 0;                         /* Any protocol */
    hints.ai_flags = (AI_CANONNAME);               /* Get cannonname */
    hints.ai_addr = NULL;
    hints.ai_next = NULL;

    ecl_disable_interrupts();
    err = getaddrinfo(#0, NULL, &hints, &result);
    ecl_enable_interrupts();

    if (err == 0) {
        struct addrinfo *rp;

        for (rp = result; rp != NULL; rp = rp->ai_next) {
            if ( (rp == result) && (rp->ai_canonname != 0) ) {  /* first one may hold cannonname */
                host_name = ecl_make_simple_base_string( rp->ai_canonname, -1 );
            }
            struct sockaddr_in *ipv4 = (struct sockaddr_in *)rp->ai_addr;
            uint32_t ip = ntohl( ipv4->sin_addr.s_addr );
            cl_object vector = cl_make_array(1,ecl_make_fixnum(4));
            ecl_aset(vector,0, ecl_make_fixnum( ip>>24 ));
            ecl_aset(vector,1, ecl_make_fixnum( (ip>>16) & 0xFF));
            ecl_aset(vector,2, ecl_make_fixnum( (ip>>8) & 0xFF));
            ecl_aset(vector,3, ecl_make_fixnum( ip & 0xFF ));
            addresses = cl_adjoin(4, vector, addresses, #1, #2);
            if ( rp->ai_canonname != 0 ) {
                cl_object alias = ecl_make_simple_base_string( rp->ai_canonname, -1 );
                aliases = CONS(alias, aliases);
            }
        }
        freeaddrinfo(result);
    }
    @(return 0) = err;
    @(return 1) = host_name;
    @(return 2) = addresses;
    @(return 3) = aliases;
}"
                :side-effects t)
    (if (= errno 0)
        (make-instance 'host-ent
                       :name (or canonical-name host-name)
                       :aliases aliases
                       :type +af-inet+
                       :addresses addresses)
        (name-service-error "get-host-by-name" errno))))

;;; Use values from getnameinfo man page if NI_MAXHOST is not declared
(clines
 "#ifndef NI_MAXHOST
  #define NI_MAXHOST 1025
  #endif")

(defun get-host-by-address (address)
  (assert (and (typep address 'vector)
               (= (length address) 4)))
  (multiple-value-bind (errno name)
      (c-inline ((aref address 0) (aref address 1) (aref address 2) (aref address 3))
                (:int :int :int :int) (values :int t)
             "
{
    struct sockaddr_in addr;
    socklen_t addr_len = (socklen_t)sizeof(struct sockaddr_in);
    char host[NI_MAXHOST];
    int err;

    fill_inet_sockaddr(&addr, 0, #0, #1, #2, #3);

    ecl_disable_interrupts();
    err = getnameinfo((struct sockaddr *) &addr, addr_len, host, NI_MAXHOST, NULL, 0, NI_NAMEREQD);
    ecl_enable_interrupts();

    @(return 0) = err;
    @(return 1) = err ? ECL_NIL : ecl_make_simple_base_string(host,-1);
}"
             :side-effects t)
    (if (= errno 0)
        (make-instance 'host-ent
                       :name name
                       :aliases nil
                       :type +af-inet+
                       :addresses (list address))
        (name-service-error "get-host-by-address" errno))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; SOCKET BASE CLASS AND GENERIC FUNCTIONS
;;;

(defclass socket ()
  ((file-descriptor :initarg :descriptor
                    :reader socket-file-descriptor)
   (family :initform (error "No socket family")
           :reader socket-family)
   (protocol :initarg :protocol
             :reader socket-protocol
             :documentation "Protocol used by the socket. If a
keyword, the symbol-name of the keyword will be passed to
GET-PROTOCOL-BY-NAME downcased, and the returned value used as
protocol. Other values are used as-is.")
   (type  :initarg :type
          :reader socket-type
          :initform :stream
          :documentation "Type of the socket: :STREAM or :DATAGRAM.")
   (stream)
   #+:wsock
   (non-blocking-p :initform nil))
  (:documentation "Common base class of all sockets, not meant to be
directly instantiated."))

(defmethod print-object ((object socket) stream)
  (print-unreadable-object (object stream :type t :identity t)
    (if (slot-boundp object 'file-descriptor)
        (progn
          (princ "descriptor " stream)
          (princ (slot-value object 'file-descriptor) stream))
        (princ "(unbound descriptor)"))))

(defmethod shared-initialize :after ((socket socket) slot-names
                                     &key protocol type
                                     &allow-other-keys)
  (let* ((proto-num
          (cond ((and protocol (keywordp protocol))
                 (get-protocol-by-name (string-downcase (symbol-name protocol))))
                (protocol protocol)
                (t 0)))
         (fd (or (and (slot-boundp socket 'file-descriptor)
                      (socket-file-descriptor socket))
                 #+:wsock
                 (and (member (socket-family socket) (list +af-named-pipe+ +af-local+)) 0)
                 (ff-socket (socket-family socket)
                            (ecase (or type
                                       (socket-type socket))
                              ((:datagram) (c-constant "SOCK_DGRAM"))
                              ((:stream) (c-constant "SOCK_STREAM")))
                            proto-num))))
    (if (= fd -1) (socket-error "socket"))
    (setf (slot-value socket 'file-descriptor) fd
          (slot-value socket 'protocol) proto-num)
    #+ ignore
    (sb-ext:finalize socket (lambda () (sockint::close fd)))))

;; Generics

(defgeneric socket-bind (socket &rest address)
  (:documentation "Bind SOCKET to ADDRESS, which may vary according to
socket family.  For the INET family, pass ADDRESS and PORT as two
arguments; for FILE address family sockets, pass the filename string.
See also bind(2)"))

(defgeneric socket-accept (socket)
  (:documentation "Perform the accept(2) call, returning a
newly-created connected socket and the peer address as multiple
values"))

(defgeneric socket-connect (socket &rest address)
  (:documentation "Perform the connect(2) call to connect SOCKET to a
  remote PEER.  No useful return value."))

(defgeneric socket-peername (socket)
  (:documentation "Return the socket's peer; depending on the address
  family this may return multiple values"))

(defgeneric socket-name (socket)
  (:documentation "Return the address (as vector of bytes) and port
  that the socket is bound to, as multiple values."))

(defgeneric socket-listen (socket backlog)
  (:documentation "Mark SOCKET as willing to accept incoming connections.  BACKLOG
defines the maximum length that the queue of pending connections may
grow to before new connection attempts are refused.  See also listen(2)"))

(defgeneric socket-receive (socket buffer length
                            &key
                            oob peek waitall element-type)
  (:documentation "Read LENGTH octets from SOCKET into BUFFER (or a freshly-consed buffer if
NIL), using recvfrom(2).  If LENGTH is NIL, the length of BUFFER is
used, so at least one of these two arguments must be non-NIL.  If
BUFFER is supplied, it had better be of an element type one octet wide.
Returns the buffer, its length, and the address of the peer
that sent it, as multiple values.  On datagram sockets, sets MSG_TRUNC
so that the actual packet length is returned even if the buffer was too
small"))

(defgeneric socket-send (socket buffer length 
                         &key 
                         address external-format oob eor dontroute dontwait 
                         nosignal confirm more)
  (:documentation "Send length octets from buffer into socket, using sendto(2).
If buffer is a string, it will converted to octets according to external-format&
If length is nil, the length of the octet buffer is used. The format of address
depends on the socket type (for example for inet domain sockets it would be a 
list of an ip address and a port). If no socket address is provided, send(2) 
will be called instead. Returns the number of octets written."))


(defgeneric socket-close (socket &key abort)
  (:documentation "Close SOCKET.  May throw any kind of error that write(2) would have
thrown.  If SOCKET-MAKE-STREAM has been called, calls CLOSE using ABORT on that
stream instead"))

(defgeneric socket-make-stream (socket  &rest args)
    (:documentation "Find or create a STREAM that can be used for IO
on SOCKET (which must be connected).  ARGS are passed onto
SB-SYS:MAKE-FD-STREAM."))

(defgeneric non-blocking-mode (socket)
  (:documentation "Is SOCKET in non-blocking mode?"))

(defgeneric (setf non-blocking-mode) (non-blocking-p socket)
  (:documentation "Put SOCKET in non-blocking mode - or not, according to NON-BLOCKING-P"))

(defgeneric socket-close-low-level (socket)
  (:documentation "Close SOCKET at low level. Do not use directly."))

;; Methods

(defmethod socket-listen ((socket socket) backlog)
  (let ((r (ff-listen (socket-file-descriptor socket) backlog)))
    (if (= r -1)
        (socket-error "listen"))))

(defmethod socket-close-low-level ((socket socket))
  (ff-close (socket-file-descriptor socket)))

(defmethod socket-close ((socket socket) &key abort)
  ;; the close(2) manual page has all kinds of warning about not
  ;; checking the return value of close, on the grounds that an
  ;; earlier write(2) might have returned successfully w/o actually
  ;; writing the stuff to disk.  It then goes on to define the only
  ;; possible error return as EBADF (fd isn't a valid open file
  ;; descriptor).  Presumably this is an oversight and we could also
  ;; get anything that write(2) would have given us.

  ;; note that if you have a socket _and_ a stream on the same fd,
  ;; the socket will avoid doing anything to close the fd in case
  ;; the stream has done it already - if so, it may have been
  ;; reassigned to some other file, and closing it would be bad

  (let ((fd (socket-file-descriptor socket)))
    (unless (eql fd -1) ; already closed
      (cond ((slot-boundp socket 'stream)
             (let ((stream (slot-value socket 'stream)))
               #+threads
               (close (two-way-stream-input-stream stream) :abort abort)
               #+threads
               (close (two-way-stream-output-stream stream) :abort abort)
               #-threads
               (close stream :abort abort)) ;; closes fd indirectly
             (slot-makunbound socket 'stream))
            ((= (socket-close-low-level socket) -1)
             (socket-error "close")))
      (setf (slot-value socket 'file-descriptor) -1))))

(ffi::clines "
static void *
safe_buffer_pointer(cl_object x, cl_index size)
{
        cl_type t = ecl_t_of(x);
        int ok = 0;
        if (t == t_base_string) {
                ok = (size <= x->base_string.dim);
        } else if (t == t_vector) {
                cl_elttype aet = (cl_elttype)x->vector.elttype;
                if (aet == ecl_aet_b8 || aet == ecl_aet_i8 || aet == ecl_aet_bc) {
                        ok = (size <= x->vector.dim);
                } else if (aet == ecl_aet_fix || aet == ecl_aet_index) {
                        cl_index divisor = sizeof(cl_index);
                        size = (size + divisor - 1) / divisor;
                        ok = (size <= x->vector.dim);
                }
        }
        if (!ok) {
                FEerror(\"Lisp object is not a valid socket buffer: ~A\", 1, x);
        }
        return (void *)x->vector.self.t;
}
")

;; FIXME: How bad is manipulating fillp directly?
(defmethod socket-receive ((socket socket) buffer length
                           &key oob peek waitall (element-type 'ext:byte8))
  (unless (or buffer length) (error "You have to supply either buffer or length!"))
  (let ((buffer (or buffer (make-array length :element-type element-type)))
        (length (or length (length buffer)))
        (fd (socket-file-descriptor socket))
        (trunc (if (eql (socket-type socket) :datagram) t nil)))

    (multiple-value-bind (len-recv errno vector port)
           (c-inline (fd buffer length oob peek waitall trunc)
                     (:int :object :int :bool :bool :bool :bool)
                  (values :long :int :object :int)
                     "
{
        int flags = ( #3 ? MSG_OOB : 0 )  |
                    ( #4 ? MSG_PEEK : 0 ) |
                    ( #5 ? MSG_WAITALL : 0 ) |
                    ( #6 ? MSG_TRUNC : 0 );
        cl_type type = ecl_t_of(#1);
        ssize_t len;
        struct sockaddr_in sender;
        socklen_t addr_len = (socklen_t)sizeof(struct sockaddr_in);

        ecl_disable_interrupts();
        len = recvfrom(#0, wincoerce(char*, safe_buffer_pointer(#1, #2)),
                       #2, flags, (struct sockaddr *)&sender, &addr_len);
        ecl_enable_interrupts();
        if (len >= 0) {
               if (type == t_vector) { #1->vector.fillp = len; }
               else if (type == t_base_string) { #1->base_string.fillp = len; }
        }
        @(return 0) = len;
        @(return 1) = errno;
        @(return 2) = ECL_NIL;
        @(return 3) = 0;

        if (len >= 0) {
                uint32_t ip = ntohl(sender.sin_addr.s_addr);
                uint16_t port = ntohs(sender.sin_port);
                cl_object vector = cl_make_array(1,ecl_make_fixnum(4));

                ecl_aset(vector,0, ecl_make_fixnum( ip>>24 ));
                ecl_aset(vector,1, ecl_make_fixnum( (ip>>16) & 0xFF));
                ecl_aset(vector,2, ecl_make_fixnum( (ip>>8) & 0xFF));
                ecl_aset(vector,3, ecl_make_fixnum( ip & 0xFF ));

                @(return 2) = vector;
                @(return 3) = port;
        }
}
"
                  :one-liner nil)
      (cond ((and (= len-recv -1)
                  (member errno (list +eagain+ +eintr+)))
             nil)
            ((= len-recv -1)
             (socket-error "receive"))
            (t
             (values buffer len-recv vector port))))))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; INET SOCKETS
;;;

;; We could refactor a lot here, if we pass sockaddr_foo structs around in Lisp. But
;; I do not feel comfortable with that.

(define-condition unknown-protocol (error)
  ((name :initarg :name
         :reader unknown-protocol-name))
  (:report (lambda (condition stream)
             (format stream "Protocol not found: ~A"
                     (prin1-to-string (unknown-protocol-name condition))))))

(defun get-protocol-by-name (string-or-symbol)
  "Calls getprotobyname"
  #-:android
  (let* ((string (string string-or-symbol))
         (proto-num (c-inline (string) (:cstring) :int
                              "{
                                 struct protoent *pe;
                                 pe = getprotobyname(#0);
                                 @(return 0) = pe ? pe->p_proto : -1;
                               }
               ")))
    (if (= proto-num -1)
      (error 'unknown-protocol :name string)
      proto-num))
  ;; getprotobyname is not yet implemented on bionic
  #+:android
  (let ((proto (string-downcase (if (symbolp string-or-symbol)
				    (symbol-name string-or-symbol)
				    string-or-symbol))))
    (cond
      ((string= proto "ip") +ipproto_ip+)
      ((string= proto "icmp") +ipproto_icmp+)
      ((string= proto "igmp") +ipproto_igmp+)
      ((string= proto "ipip") +ipproto_ipip+)
      ((string= proto "tcp") +ipproto_tcp+)
      ((string= proto "egp") +ipproto_egp+)
      ((string= proto "pup") +ipproto_pup+)
      ((string= proto "udp") +ipproto_udp+)
      ((string= proto "idp") +ipproto_idp+)
      ((string= proto "dccp") +ipproto_dccp+)
      ((string= proto "rsvp") +ipproto_rsvp+)
      ((string= proto "gre") +ipproto_gre+)
      ((string= proto "ipv6") +ipproto_ipv6+)
      ((string= proto "esp") +ipproto_esp+)
      ((string= proto "ah") +ipproto_ah+)
      ((string= proto "pim") +ipproto_pim+)
      ((string= proto "comp") +ipproto_comp+)
      ((string= proto "sctp") +ipproto_sctp+)
      ((string= proto "raw") +ipproto_raw+))))

(defun make-inet-address (dotted-quads)
  "Return a vector of octets given a string DOTTED-QUADS in the format
\"127.0.0.1\""
  (map 'vector
       #'parse-integer
       (split dotted-quads nil '(#\.))))

(defclass inet-socket (socket)
  ((family :initform +af-inet+))
  (:documentation "Class representing TCP and UDP sockets.

Examples:

 (make-instance 'inet-socket :type :stream :protocol :tcp)

 (make-instance 'inet-socket :type :datagram :protocol :udp)
"))

(defun make-inet-socket (type protocol)
  "Make an INET socket.  Deprecated in favour of make-instance"
  (make-instance 'inet-socket :type type :protocol protocol))



(defmethod socket-bind ((socket inet-socket) &rest address)
  (assert (= 2 (length address)) (address) "Socket-bind needs three parameters for inet sockets.")
  (let ((ip (first address))
        (port (second address)))
    (if (= -1
           (c-inline (port (aref ip 0) (aref ip 1) (aref ip 2) (aref ip 3)
                           (socket-file-descriptor socket))
                     (:int :int :int :int :int :int)
                     :int
                     "
{
        struct sockaddr_in sockaddr;
        int output;
        ecl_disable_interrupts();
        fill_inet_sockaddr(&sockaddr, #0, #1, #2, #3, #4);
        output = bind(#5,(struct sockaddr*)&sockaddr, sizeof(struct sockaddr_in));
        ecl_enable_interrupts();
        @(return) = output;
}"
                     :side-effects t))
        (socket-error "bind"))))

(defmethod socket-accept ((socket inet-socket))
  (let ((sfd (socket-file-descriptor socket)))
    (multiple-value-bind (fd vector port)
      (c-inline (sfd) (:int) (values :int :object :int)
"{
        struct sockaddr_in sockaddr;
        socklen_t addr_len = (socklen_t)sizeof(struct sockaddr_in);
        int new_fd;

        ecl_disable_interrupts();
        new_fd = accept(#0, (struct sockaddr*)&sockaddr, &addr_len);
        ecl_enable_interrupts();

        @(return 0) = new_fd;
        @(return 1) = ECL_NIL;
        @(return 2) = 0;
        if (new_fd != -1) {
                uint32_t ip = ntohl(sockaddr.sin_addr.s_addr);
                uint16_t port = ntohs(sockaddr.sin_port);
                cl_object vector = cl_make_array(1,ecl_make_fixnum(4));

                ecl_aset(vector,0, ecl_make_fixnum( ip>>24 ));
                ecl_aset(vector,1, ecl_make_fixnum( (ip>>16) & 0xFF));
                ecl_aset(vector,2, ecl_make_fixnum( (ip>>8) & 0xFF));
                ecl_aset(vector,3, ecl_make_fixnum( ip & 0xFF ));

                @(return 1) = vector;
                @(return 2) = port;
        }
}")
      (cond
        ((= fd -1)
         (socket-error "accept"))
        (t
         (values
           (make-instance (class-of socket)
                          :type (socket-type socket)
                          :protocol (socket-protocol socket)
                          :descriptor fd)
           vector
           port))))))

(defmethod socket-connect ((socket inet-socket) &rest address)
  (let ((ip (first address))
        (port (second address)))
    (if (= -1
           (c-inline (port (aref ip 0) (aref ip 1) (aref ip 2) (aref ip 3)
                           (socket-file-descriptor socket))
                     (:int :int :int :int :int :int)
                     :int
                     "
{
        struct sockaddr_in sockaddr;
        int output;

        ecl_disable_interrupts();
        fill_inet_sockaddr(&sockaddr, #0, #1, #2, #3, #4);
        output = connect(#5,(struct sockaddr*)&sockaddr, sizeof(struct sockaddr_in));
        ecl_enable_interrupts();

        @(return) = output;
}"))
        (socket-error "connect"))))

(defmethod socket-peername ((socket inet-socket))
  (let* ((vector (make-array 4))
         (fd (socket-file-descriptor socket))
         (port (c-inline (fd vector) (:int t) :int
"@01;{
        struct sockaddr_in name;
        socklen_t len = sizeof(struct sockaddr_in);
        int ret;

        ecl_disable_interrupts();
        ret = getpeername(#0,(struct sockaddr*)&name,&len);
        ecl_enable_interrupts();

        if (ret == 0) {
                uint32_t ip = ntohl(name.sin_addr.s_addr);
                uint16_t port = ntohs(name.sin_port);

                ecl_aset(#1,0, ecl_make_fixnum( ip>>24 ));
                ecl_aset(#1,1, ecl_make_fixnum( (ip>>16) & 0xFF));
                ecl_aset(#1,2, ecl_make_fixnum( (ip>>8) & 0xFF));
                ecl_aset(#1,3, ecl_make_fixnum( ip & 0xFF ));

                @(return) = port;
         } else {
                @(return) = -1;
         }
}")))
    (if (>= port 0)
        (values vector port)
        (socket-error "getpeername"))))

(defmethod socket-name ((socket inet-socket))
  (let* ((vector (make-array 4))
         (fd (socket-file-descriptor socket))
         (port (c-inline (fd vector) (:int t) :int
"@01;{
        struct sockaddr_in name;
        socklen_t len = sizeof(struct sockaddr_in);
        int ret;

        ecl_disable_interrupts();
        ret = getsockname(#0,(struct sockaddr*)&name,&len);
        ecl_enable_interrupts();

        if (ret == 0) {
                uint32_t ip = ntohl(name.sin_addr.s_addr);
                uint16_t port = ntohs(name.sin_port);

                ecl_aset(#1,0, ecl_make_fixnum( ip>>24 ));
                ecl_aset(#1,1, ecl_make_fixnum( (ip>>16) & 0xFF));
                ecl_aset(#1,2, ecl_make_fixnum( (ip>>8) & 0xFF));
                ecl_aset(#1,3, ecl_make_fixnum( ip & 0xFF ));

                @(return) = port;
         } else {
                @(return) = -1;
         }
}")))
    (if (>= port 0)
        (values vector port)
        (socket-error "getsockname"))))

#+:wsock
(defmethod socket-close-low-level ((socket inet-socket))
  (ff-closesocket (socket-file-descriptor socket)))

(defmethod socket-send ((socket socket) buffer length
                           &key address external-format oob eor dontroute dontwait nosignal confirm more)
  (declare (ignore external-format more))
  (assert (or (stringp buffer)
                (typep buffer 'vector)))
  (let (;eh, here goes string->octet convertion... 
        ;When will ecl support Unicode?
        (length (or length (length buffer)))
        (fd (socket-file-descriptor socket)))
    (let ((len-sent
           (if address
               (progn
                 (assert (= 2 (length address)))
                 (c-inline (fd buffer length 
                               (second address)
                               (aref (first address) 0)
                               (aref (first address) 1)
                               (aref (first address) 2)
                               (aref (first address) 3)
                               oob eor dontroute dontwait nosignal confirm)
                     (:int :object :int
                           :int :int :int :int :int
                           :bool :bool :bool :bool :bool :bool)
                     :long
                     "
{
        int sock = #0;
        int length = #2;
        void *buffer = safe_buffer_pointer(#1, length);
        int flags = ( #8 ? MSG_OOB : 0 )  |
                    ( #9 ? MSG_EOR : 0 ) |
                    ( #a ? MSG_DONTROUTE : 0 ) |
                    ( #b ? MSG_DONTWAIT : 0 ) |
                    ( #c ? MSG_NOSIGNAL : 0 ) |
                    ( #d ? MSG_CONFIRM : 0 );
        cl_type type = ecl_t_of(#1);
        struct sockaddr_in sockaddr;
        ssize_t len;

        ecl_disable_interrupts();
        fill_inet_sockaddr(&sockaddr, #3, #4, #5, #6, #7);
##if (MSG_NOSIGNAL == 0) && defined(SO_NOSIGPIPE)
        {
                int sockopt = #c;
                setsockopt(#0,SOL_SOCKET,SO_NOSIGPIPE,
                           wincoerce(char *,&sockopt),
                           sizeof(int));
        }
##endif
        len = sendto(sock, wincoerce(char *,buffer),
                     length, flags,(struct sockaddr*)&sockaddr, 
                     sizeof(struct sockaddr_in));
        ecl_enable_interrupts();
        @(return) = len;
}
"
                     :one-liner nil))
               (c-inline (fd buffer length 
                             oob eor dontroute dontwait nosignal confirm)
                     (:int :object :int
                           :bool :bool :bool :bool :bool :bool)
                     :long
                     "
{
        int sock = #0;
        int length = #2;
        void *buffer = safe_buffer_pointer(#1, length);
        int flags = ( #3 ? MSG_OOB : 0 )  |
                    ( #4 ? MSG_EOR : 0 ) |
                    ( #5 ? MSG_DONTROUTE : 0 ) |
                    ( #6 ? MSG_DONTWAIT : 0 ) |
                    ( #7 ? MSG_NOSIGNAL : 0 ) |
                    ( #8 ? MSG_CONFIRM : 0 );
        cl_type type = ecl_t_of(#1);
        ssize_t len;
        ecl_disable_interrupts();
##if (MSG_NOSIGNAL == 0) && defined(SO_NOSIGPIPE)
        {
                int sockopt = #7;
                setsockopt(#0,SOL_SOCKET,SO_NOSIGPIPE,
                           wincoerce(char *,&sockopt),
                           sizeof(int));
        }
##endif
        len = send(sock, wincoerce(char *, buffer), length, flags);
        ecl_enable_interrupts();
        @(return) = len;
}
"
                     :one-liner nil))))
      (if (= len-sent -1)
          (socket-error "send")
          len-sent))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; UNIX SOCKETS
;;;

#-:wsock
(progn

(defclass local-socket (socket)
  ((family :initform +af-local+))
  (:documentation "Class representing local domain (AF_LOCAL) sockets,
also known as unix-domain sockets."))


(defmethod socket-bind ((socket local-socket) &rest address)
  (assert (= 1 (length address)) (address) "Socket-bind needs two parameters for local sockets.")
  (let ((name (first address))
        (fd (socket-file-descriptor socket))
        (family (socket-family socket)))
    (if (= -1
           (c-inline (fd name family) (:int :cstring :int) :int
                     "
{
        struct sockaddr_un sockaddr;
        size_t size;
        int output;
##ifdef BSD
        sockaddr.sun_len = sizeof(struct sockaddr_un);
##endif
        sockaddr.sun_family = #2;
        strncpy(sockaddr.sun_path,#1,sizeof(sockaddr.sun_path));
        sockaddr.sun_path[sizeof(sockaddr.sun_path)-1] = 0;

        ecl_disable_interrupts();
        output = bind(#0,(struct sockaddr*)&sockaddr, sizeof(struct sockaddr_un));
        ecl_enable_interrupts();

        @(return) = output;
}"))
        (socket-error "bind"))))

(defmethod socket-accept ((socket local-socket))
  (multiple-value-bind (fd name)
      (c-inline ((socket-file-descriptor socket)) (:int) (values :int :object)
"{
        struct sockaddr_un sockaddr;
        socklen_t addr_len = (socklen_t)sizeof(struct sockaddr_un);
        int new_fd;
        ecl_disable_interrupts();
        new_fd = accept(#0, (struct sockaddr *)&sockaddr, &addr_len);
        ecl_enable_interrupts();
        @(return 0) = new_fd;
        @(return 1) = (new_fd == -1) ? ECL_NIL : ecl_make_simple_base_string(sockaddr.sun_path,-1);
}")
    (cond
      ((= fd -1)
       (socket-error "accept"))
      (t
       (values
        (make-instance (class-of socket)
                       :type (socket-type socket)
                       :protocol (socket-protocol socket)
                       :descriptor fd)
        name)))))

(defmethod socket-connect ((socket local-socket) &rest address)
  (assert (= 1 (length address)) (address) "Socket-connect needs two parameters for local sockets.")
  (let ((path (first address))
        (fd (socket-file-descriptor socket))
        (family (socket-family socket)))
    (if (= -1
           (c-inline (fd family path) (:int :int :cstring) :int
                     "
{
        struct sockaddr_un sockaddr;
        int output;
##ifdef BSD
        sockaddr.sun_len = sizeof(struct sockaddr_un);
##endif
        sockaddr.sun_family = #1;
        strncpy(sockaddr.sun_path,#2,sizeof(sockaddr.sun_path));
        sockaddr.sun_path[sizeof(sockaddr.sun_path)-1] = 0;

        ecl_disable_interrupts();
        output = connect(#0,(struct sockaddr*)&sockaddr, sizeof(struct sockaddr_un));
        ecl_enable_interrupts();

        @(return) = output;
}"))
        (socket-error "connect"))))

(defmethod socket-peername ((socket local-socket))
  (let* ((fd (socket-file-descriptor socket))
         (peer (c-inline (fd) (:int) t
                         "
{
        struct sockaddr_un name;
        socklen_t len = sizeof(struct sockaddr_un);
        int ret;

        ecl_disable_interrupts();
        ret = getpeername(#0,(struct sockaddr*)&name,&len);
        ecl_enable_interrupts();

        if (ret == 0) {
                @(return) = ecl_make_simple_base_string(name.sun_path,-1);
        } else {
                @(return) = ECL_NIL;
        }
}")))
    (if peer
        peer
        (socket-error "getpeername"))))

) ;#-:wsock


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; UNIX SOCKETS [WIN32, using the cygwin way]
;;;

#+:wsock
(progn

(defclass local-socket (socket)
  ((family :initform +af-local+)
   proxy-socket local-path)
  (:documentation "Class representing local domain (AF_LOCAL) sockets,
also known as unix-domain sockets."))

(defmethod initialize-instance :after ((socket local-socket) &rest args)
  (declare (ignore args))
  (with-slots (protocol type) socket
    (setf (slot-value socket 'proxy-socket)
          (make-instance 'inet-socket :protocol protocol :type type))))

(defmethod socket-bind ((socket local-socket) &rest address)
  (assert (= 1 (length address)) (address) "Socket-bind needs two parameters for local sockets.")
  (with-slots (proxy-socket local-path) socket
    (socket-bind proxy-socket #(127 0 0 1) 0)
    (multiple-value-bind (ip port) (socket-peername proxy-socket)
      (handler-case 
        (with-open-file (fd (first address) :if-exists :error :if-does-not-exist :create :direction :output)
          (format fd "!<socket >~D 00000000-00000000-00000000-00000000" port))
        (file-error ()
          (socket-close proxy-socket)
          (c-inline () () nil "WSASetLastError(WSAEADDRINUSE)" :one-liner t)
          (socket-error "socket-bind")))
      (setf local-path (first address))
      socket)))

(defmethod socket-accept ((socket local-socket))
  (multiple-value-bind (new-socket addr) (socket-accept (slot-value socket 'proxy-socket))
    (values socket (slot-value socket 'local-path))))

(defmethod socket-connect ((socket local-socket) &rest address)
  (assert (= 1 (length address)) (address) "Socket-connect needs two parameters for local sockets.")
  (with-slots (proxy-socket local-path) socket
    (handler-case
      (with-open-file (fd (first address) :if-does-not-exist :error :direction :input)
        (let ((buf (make-string 128)) port)
          (read-sequence buf fd)
          (unless (and (string-equal "!<socket >" (subseq buf 0 10))
                       (typep (setq port (read-from-string (subseq buf 10) nil 'eof)) '(integer 0 65535)))
            (c-inline () () nil "WSASetLastError(WSAEFAULT)" :one-liner t)
            (socket-error "connect"))
          (prog1
            (socket-connect proxy-socket #(127 0 0 1) port)
            (setf local-path (first address)))))
      (file-error ()
        (socket-error "connect")))))

(defmethod socket-peername ((socket local-socket))
  (unless (slot-boundp socket 'local-path)
    (c-inline () () nil "WSASetLastError(WSAENOTCONN)" :one-liner t)
    (socket-error "socket-peername"))
  (slot-value socket 'local-path))

(defmethod socket-close ((socket local-socket) &key abort)
  (socket-close (slot-value socket 'proxy-socket) :abort abort)
  (slot-makunbound socket 'local-path))

(defmethod socket-make-stream ((socket local-socket) &rest args)
  (apply #'socket-make-stream (cons (slot-value socket 'proxy-socket) args)))

(defmethod non-blocking-mode ((socket local-socket))
  (non-blocking-mode (slot-value socket 'proxy-socket)))

(defmethod (setf non-blocking-mode) (non-blocking-p (socket local-socket))
  (setf (non-blocking-mode (slot-value socket 'proxy-socket)) non-blocking-p))

) ;#+:wsock

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; NAMED PIPE SOCKETS [WIN32]
;;;

#+:wsock
(progn

(defclass named-pipe-socket (socket)
  ((family :initform +af-named-pipe+)
   (pipe-name :initarg :pipe-name))
  (:documentation "Class representing Win32 named pipe, using a socket-like interface."))

(defmethod socket-bind ((socket named-pipe-socket) &rest address)
  (assert (= 1 (length address)) (address) "Socket-bind needs two parameters for local sockets.")
  (let* ((pipe-name (concatenate 'string "\\\\.\\pipe\\" (first address)))
         (hnd (c-inline (pipe-name) (:cstring) :int
                        "
{
        HANDLE hnd;
        ecl_disable_interrupts();
        hnd = CreateNamedPipe(
                        #0,
                        PIPE_ACCESS_DUPLEX,
                        PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
                        PIPE_UNLIMITED_INSTANCES,
                        4096,
                        4096,
                        NMPWAIT_USE_DEFAULT_WAIT,
                        NULL);
        ecl_enable_interrupts();
        if (hnd == INVALID_HANDLE_VALUE)
                @(return) = -1;
        else
                @(return) = _open_osfhandle((intptr_t)hnd, O_RDWR);
}")))
    (when (= hnd -1)
      (socket-error "CreateNamedPipe"))
    (setf (slot-value socket 'pipe-name) pipe-name)
    (setf (slot-value socket 'file-descriptor) hnd)))

(defmethod socket-accept ((socket named-pipe-socket))
  (let* ((fd (socket-file-descriptor socket))
         (afd (c-inline (fd) (:int) :int
                        "
{
        HANDLE hnd = (HANDLE)_get_osfhandle(#0), dupHnd;
        ecl_disable_interrupts();
        if (ConnectNamedPipe(hnd, NULL) != 0 || GetLastError() == ERROR_PIPE_CONNECTED) {
                @(return) = #0;
        } else
                @(return) = -1;
        ecl_enable_interrupts();
}"
                        :one-liner nil)))
    (cond
      ((= afd -1)
       (socket-error "accept"))
      (t
       ;; rebind the socket to create a new named pipe instance in the server
       (socket-bind socket (subseq (slot-value socket 'pipe-name) 9))
       (values
        (make-instance (class-of socket)
                       :type (socket-type socket)
                       :protocol (socket-protocol socket)
                       :descriptor afd
                       :pipe-name (slot-value socket 'pipe-name))
        (slot-value socket 'pipe-name))))))

(defmethod socket-connect ((socket named-pipe-socket) &rest address)
  (assert (= 1 (length address)) (address) "Socket-connect needs two parameters for local sockets.")
  (let* ((path (first address))
         (pipe-name (concatenate 'string "\\\\.\\pipe\\" path)))
    (if (= -1
           (setf (slot-value socket 'file-descriptor)
                 (c-inline (pipe-name) (:cstring) :int
                     "
{
        HANDLE hnd;
        ecl_disable_interrupts();
        hnd = CreateFile(
                        #0,
                        GENERIC_READ | GENERIC_WRITE,
                        0,
                        NULL,
                        OPEN_EXISTING,
                        0,
                        NULL);
        if (hnd == INVALID_HANDLE_VALUE)
                @(return) = -1;
        else
                @(return) = _open_osfhandle((intptr_t)hnd, O_RDWR);
        ecl_enable_interrupts();
}")))
        (socket-error "connect")
        (setf (slot-value socket 'pipe-name) pipe-name))))

(defmethod socket-peername ((socket named-pipe-socket))
  (slot-value socket 'pipe-name))

(defmethod (setf non-blocking-mode) (non-blocking-p (socket named-pipe-socket))
  (let ((fd (socket-file-descriptor socket)))
    (if (= 0
           (c-inline (fd non-blocking-p) (:int t) :int
                     "
{
        DWORD mode = PIPE_READMODE_BYTE | (#1 == ECL_T ? PIPE_NOWAIT : PIPE_WAIT);
        HANDLE h = (HANDLE)_get_osfhandle(#0);
        ecl_disable_interrupts();
        @(return) = SetNamedPipeHandleState(h, &mode, NULL, NULL);
        ecl_enable_interrupts();
}"
                     :one-liner nil))
      (socket-error "SetNamedPipeHandleState")
      (setf (slot-value socket 'non-blocking-p) non-blocking-p))))

(defmethod socket-close ((socket named-pipe-socket) &key abort)
  (declare (ignore abort))
  (let ((fd (socket-file-descriptor socket)))
    (unless (c-inline (fd) (:int) t
                  "
{
        DWORD flags;
        HANDLE h = (HANDLE)_get_osfhandle(#0);
        ecl_disable_interrupts();
        if (!GetNamedPipeInfo(h, &flags, NULL, NULL, NULL))
                @(return) = ECL_NIL;
        if (flags == PIPE_CLIENT_END || DisconnectNamedPipe(h))
                @(return) = ECL_T;
        else
                @(return) = ECL_NIL;
        ecl_enable_interrupts();
}"
                  :one-liner nil)
      (socket-error "DisconnectNamedPipe"))
    (call-next-method)))

) ;#+:wsock

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; NON-BLOCKING MODE
;;;

(defmethod non-blocking-mode ((socket socket))
  #-:wsock
  (let ((fd (socket-file-descriptor socket)))
    (not (zerop (c-inline (fd) (:int) :int "fcntl(#0,F_GETFL,NULL)&O_NONBLOCK" :one-liner t))))
  #+:wsock
  (slot-value socket 'non-blocking-p)
)

(defmethod (setf non-blocking-mode) (non-blocking-p (socket socket))
  (let ((fd (socket-file-descriptor socket))
        (nblock (if non-blocking-p 1 0)))
    (if (= -1 (c-inline (fd nblock) (:int :int) :int
              #+:wsock
              "
{
        int blocking_flag = (#1 ? 1 : 0);
        ecl_disable_interrupts();
        @(return) = ioctlsocket(#0, FIONBIO, (u_long*)&blocking_flag);
        ecl_enable_interrupts();
}"
              #-:wsock
              "
{
        int oldflags = fcntl(#0,F_GETFL,NULL);
        int newflags = (oldflags & ~O_NONBLOCK) |
                       (#1 ? O_NONBLOCK : 0);
        ecl_disable_interrupts();
        @(return) = fcntl(#0,F_SETFL,newflags);
        ecl_enable_interrupts();
}"))
        (socket-error #-:wsock "fcntl" #+:wsock "ioctlsocket")
        #-:wsock non-blocking-p
        #+:wsock (setf (slot-value socket 'non-blocking-p) non-blocking-p))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; STREAMS
;;;
;;; To actually read/write to/from the sockets, we use Lisp streams. The
;;; following functions take care of building the streams. Fortunately
;;; we do not have to care about things like buffering, binary streams,
;;; etc, but we rather reuse the code from the C core. (For instance
;;; the sockets will be closed upon garbage collection)
;;;

(defun dup (fd)
  (ffi:c-inline (fd) (:int) :int "dup(#0)" :one-liner t))

(defun auto-close-two-way-stream (stream)
  (declare (si::c-local))
  (ffi:c-inline (stream) (t) :void
                "(#0)->stream.flags |= ECL_STREAM_CLOSE_COMPONENTS"
                :one-liner t))

(defun socket-make-stream-inner (fd input output buffering element-type external-format)
  ;; In Unix we have to create one stream per channel. The reason is
  ;; that buffered I/O is done using ANSI C FILEs which do not support
  ;; concurrent reads and writes -- if one thread is listening to the
  ;; FILE it blocks all output.  The solution is to create a
  ;; two-way-stream when both input and output are T, and force that
  ;; stream to close its components (small hack in ECL). In Windows we
  ;; do not have this problem because we do not know how to wrap a
  ;; FILE around a socket.
  (cond ((and input output)
         #+wsock
         (make-stream-from-fd fd :input-output-wsock
                              :buffering buffering
                              :element-type element-type
                              :external-format external-format)
         #-wsock
         (let* ((in (socket-make-stream-inner (dup fd) t nil buffering
                                              element-type external-format))
                (out (socket-make-stream-inner fd nil t buffering
                                               element-type external-format))
                (stream (make-two-way-stream in out)))
           (auto-close-two-way-stream stream)
           stream))
        (input
         (make-stream-from-fd fd #-wsock :input #+wsock :input-wsock
                              :buffering buffering
                              :element-type element-type
                              :external-format external-format))
        (output
         (make-stream-from-fd fd #-wsock :output #+wsock :output-wsock
                              :buffering buffering
                              :element-type element-type
                              :external-format external-format))
        (t
         (error "SOCKET-MAKE-STREAM: at least one of :INPUT or :OUTPUT has to be true."))))

(defmethod socket-make-stream ((socket socket)
                               &key (input nil input-p)
                               (output nil output-p)
                               (buffering :full)
                               (element-type 'base-char)
                               (external-format :default))
  (let ((stream (and (slot-boundp socket 'stream)
                     (slot-value socket 'stream))))
    (unless stream
      ;; Complicated default logic for compatibility with previous releases
      ;; should disappear soon. (FIXME!)
      (unless (or input-p output-p)
        (setf input t output t))
      (setf stream (socket-make-stream-inner (socket-file-descriptor socket)
                                             input output buffering element-type
                                             external-format))
      (setf (slot-value socket 'stream) stream)
      #+ ignore
      (sb-ext:cancel-finalization socket))
    stream))

(defmethod ext::stream-fd ((socket socket))
  (socket-file-descriptor socket))

#+:wsock
(defmethod socket-make-stream ((socket named-pipe-socket)
                               &key input output
                               (buffering :full) (external-format :default))
  (let ((stream (and (slot-boundp socket 'stream)
                     (slot-value socket 'stream))))
    (unless stream
      (setf stream
            (let* ((fd (socket-file-descriptor socket))
                   (in (make-stream-from-fd fd :smm-input buffering external-format))
                   (out (make-stream-from-fd fd :smm-output buffering external-format)))
              (make-two-way-stream in out)))
      (setf (slot-value socket 'stream) stream))
    stream))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; ERROR HANDLING
;;;
;;; A plethora of conditions are defined below, almost one for each
;;; possible error produced by the socket or DNS interface.
;;;

#+:wsock
(defun get-win32-error-string (num)
  (c-inline (num) (:int) t
        "{char *lpMsgBuf;
          cl_object msg;
          ecl_disable_interrupts();
          FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
            NULL,
            #0,
            0,
            (LPTSTR)&lpMsgBuf,
            0,
            NULL);
          msg = ecl_make_simple_base_string(lpMsgBuf,-1);
          LocalFree(lpMsgBuf);
          ecl_enable_interrupts();
          @(return) = msg;}"
          :one-liner nil))

;;;
;;; 1) SOCKET ERRORS
;;;

(define-condition socket-error (error)
  ((errno :initform nil
          :initarg :errno
          :reader socket-error-errno)
   (symbol :initform nil :initarg :symbol :reader socket-error-symbol)
   (syscall  :initform "outer space" :initarg :syscall :reader socket-error-syscall))
  (:report (lambda (c s)
             (let ((num (socket-error-errno c)))
               (format s "Socket error in \"~A\": ~A (~A)"
                       (socket-error-syscall c)
                       (or (socket-error-symbol c) (socket-error-errno c))
                       #+:wsock
                       (get-win32-error-string num)
                       #-:wsock
                       (c-inline (num) (:int) :cstring
                                 "strerror(#0)" :one-liner t)))))
  (:documentation "Common base class of socket related conditions."))

(defmacro define-socket-condition (symbol name)
  `(let () ; Prevents evaluation of constant value at compilation time
     (defconstant ,symbol (c-constant ,(symbol-name symbol)))
     (define-condition ,name (socket-error)
       ((symbol :reader socket-error-symbol :initform (quote ,symbol))))
     (export ',name)
     (push (cons ,symbol (quote ,name)) *conditions-for-errno*)))

(defparameter *conditions-for-errno* nil)
;;; this needs the rest of the list adding to it, really.  They also
;;; need symbols to be added to constants.ccon
;;; I haven't yet thought of a non-kludgey way of keeping all this in
;;; the same place
#+:wsock
(Clines
  "#define EADDRINUSE WSAEADDRINUSE"
  "#define ECONNREFUSED WSAECONNREFUSED"
  "#define ETIMEDOUT WSAETIMEDOUT"
  "#define ENOBUFS WSAENOBUFS"
  "#define EOPNOTSUPP WSAEOPNOTSUPP"
  "#define EPROTONOSUPPORT WSAEPROTONOSUPPORT"
  "#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT"
  "#define ENETUNREACH WSAENETUNREACH"
)

#+:haiku
(clines
 "#define ESOCKTNOSUPPORT ENOTSUP")

(define-socket-condition EADDRINUSE address-in-use-error)
(define-socket-condition EAGAIN interrupted-error)
(define-socket-condition EBADF bad-file-descriptor-error)
(define-socket-condition ECONNREFUSED connection-refused-error)
(define-socket-condition ETIMEDOUT operation-timeout-error)
(define-socket-condition EINTR interrupted-error)
(define-socket-condition EINVAL invalid-argument-error)
(define-socket-condition ENOBUFS no-buffers-error)
(define-socket-condition ENOMEM out-of-memory-error)
(define-socket-condition EOPNOTSUPP operation-not-supported-error)
(define-socket-condition EPERM operation-not-permitted-error)
(define-socket-condition EPROTONOSUPPORT protocol-not-supported-error)
(define-socket-condition ESOCKTNOSUPPORT socket-type-not-supported-error)
(define-socket-condition ENETUNREACH network-unreachable-error)


(defun condition-for-errno (err)
  (or (cdr (assoc err *conditions-for-errno* :test #'eql)) 'socket-error))

(defun socket-error (where)
  (let* ((errno  (c-constant #-:wsock "errno" #+:wsock "WSAGetLastError()"))
         (condition (condition-for-errno errno)))
    (error condition :errno errno  :syscall where)))

;;;
;;; 2) DNS ERRORS
;;;

(clines
 "#ifndef EAI_SYSTEM
  #define EAI_SYSTEM 0
  #endif")

(defun name-service-error (where &optional (errno #+:wsock (c-constant "WSAGetLastError()")))
  (if (= errno (c-constant "EAI_SYSTEM"))
      (socket-error where)
      (let ((condition
             (condition-for-name-service-errno errno)))
        (error condition :errno errno :syscall where))))

(define-condition name-service-error (condition)
  ((errno :initform nil
          :initarg :errno
          :reader name-service-error-errno)
   (symbol :initform nil :initarg :symbol :reader name-service-error-symbol)
   (syscall :initform "an unknown location" :initarg :syscall :reader name-service-error-syscall))
  (:report (lambda (c s)
             (let ((num (name-service-error-errno c)))
               (format s "Name service error in \"~A\": ~A (~A)"
                       (name-service-error-syscall c)
                       (or (name-service-error-symbol c)
                           (name-service-error-errno c))
                       (get-name-service-error-message num))))))

(defmacro define-name-service-condition (symbol name)
  `(let ()
     (defconstant ,symbol (c-constant ,(symbol-name symbol)))
     (define-condition ,name (name-service-error)
       ((symbol :reader name-service-error-symbol :initform (quote ,symbol))))
     (push (cons ,symbol (quote ,name)) *conditions-for-name-service-errno*)
     (export (quote ,symbol))))

(defparameter *conditions-for-name-service-errno* nil)

;;; getaddrinfo/getnameinfo have more failure codes, but for
;;; compability with gethostbyname/gethostbyaddr, we only need the
;;; following
(define-name-service-condition EAI_NONAME host-not-found-error)
(define-name-service-condition EAI_AGAIN try-again-error)
(define-name-service-condition EAI_FAIL no-recovery-error)

(defun condition-for-name-service-errno (err)
  (or (cdr (assoc err *conditions-for-name-service-errno* :test #'eql))
      'name-service))

(defun get-name-service-error-message (num)
  #+:wsock
  (get-win32-error-string num)
  #-:wsock
  (c-inline (num) (:int) :cstring "gai_strerror(#0)" :one-liner t))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; SOCKET OPTIONS
;;;

(defun get-sockopt-int (fd level const)
  (let ((ret (c-inline (fd level const) (:int :int :int) t
"{
        int sockopt, ret;
        socklen_t socklen = sizeof(int);

        ecl_disable_interrupts();
        ret = getsockopt(#0,#1,#2,wincoerce(char*,&sockopt),&socklen);
        ecl_enable_interrupts();

        @(return) = (ret == 0) ? ecl_make_integer(sockopt) : ECL_NIL;
}")))
    (if ret
        ret
        (error "Sockopt error: ~A" (c-inline () () :cstring "strerror(errno)" :one-liner t)))))

(defun get-sockopt-bool (fd level const)
  (let ((ret (c-inline (fd level const) (:int :int :int) t
"{
        int sockopt, ret;
        socklen_t socklen = sizeof(int);

        ecl_disable_interrupts();
        ret = getsockopt(#0,#1,#2,wincoerce(char*,&sockopt),&socklen);
        ecl_enable_interrupts();

        @(return) = (ret == 0) ? ecl_make_integer(sockopt) : ECL_NIL;
}")))
    (if ret
        (/= ret 0)
        (error "Sockopt error: ~A" (c-inline () () :cstring "strerror(errno)" :one-liner t)))))

#+wsock
(defun get-sockopt-timeval (fd level const)
  (* 1000 (get-sockopt-int fd level const)))

#-wsock
(defun get-sockopt-timeval (fd level const)
  (let ((ret (c-inline (fd level const) (:int :int :int) t
"{
        struct timeval tv;
        socklen_t socklen = sizeof(struct timeval);
        int ret;

        ecl_disable_interrupts();
        ret = getsockopt(#0,#1,#2,wincoerce(char*,&tv),&socklen);
        ecl_enable_interrupts();

        @(return) = (ret == 0) ? ecl_make_double_float((double)tv.tv_sec
                                        + ((double)tv.tv_usec) / 1000000.0) : ECL_NIL;
}")))
    (if ret
        ret
        (error "Sockopt error: ~A" (c-inline () () :cstring "strerror(errno)" :one-liner t)))))

(defun get-sockopt-linger (fd level const)
  (let ((ret (c-inline (fd level const) (:int :int :int) t
"{
        struct linger sockopt;
        socklen_t socklen = sizeof(struct linger);
        int ret;

        ecl_disable_interrupts();
        ret = getsockopt(#0,#1,#2,wincoerce(char*,&sockopt),&socklen);
        ecl_enable_interrupts();

        @(return) = (ret == 0) ? ecl_make_integer((sockopt.l_onoff != 0) ? sockopt.l_linger : 0) : ECL_NIL;
}")))
    (if ret
        ret
        (error "Sockopt error: ~A" (c-inline () () :cstring "strerror(errno)" :one-liner t)))))

(defun set-sockopt-int (fd level const value)
  (let ((ret (c-inline (fd level const value) (:int :int :int :int) t
"{
        int sockopt = #3;
        int ret;

        ecl_disable_interrupts();
        ret = setsockopt(#0,#1,#2,wincoerce(char *,&sockopt),sizeof(int));
        ecl_enable_interrupts();

        @(return) = (ret == 0) ? ECL_T : ECL_NIL;
}")))
    (if ret
        value
        (error "Sockopt error: ~A" (c-inline () () :cstring "strerror(errno)" :one-liner t)))))

(defun set-sockopt-bool (fd level const value)
  (let ((ret (c-inline (fd level const value) (:int :int :int :object) t
"{
        int sockopt = (#3 == ECL_NIL) ? 0 : 1;
        int ret;

        ecl_disable_interrupts();
        ret = setsockopt(#0,#1,#2,wincoerce(char *,&sockopt),sizeof(int));
        ecl_enable_interrupts();

        @(return) = (ret == 0) ? ECL_T : ECL_NIL;
}")))
    (if ret
        value
        (error "Sockopt error: ~A" (c-inline () () :cstring "strerror(errno)" :one-liner t)))))

#-wsock
(defun set-sockopt-timeval (fd level const value)
  (let ((ret (c-inline (fd level const value) (:int :int :int :double) t
"{
        struct timeval tv;
        double tmp = #3;
        int ret;

        ecl_disable_interrupts();
        tv.tv_sec = (int)tmp;
        tv.tv_usec = (int)((tmp-floor(tmp))*1000000.0);
        ret = setsockopt(#0,#1,#2,&tv,sizeof(struct timeval));
        ecl_enable_interrupts();

        @(return) = (ret == 0) ? ECL_T : ECL_NIL;
}")))
    (if ret
        value
        (error "Sockopt error: ~A" (c-inline () () :cstring "strerror(errno)" :one-liner t)))))

#+wsock
(defun set-sockopt-timeval (fd level const value)
  (set-sockopt-int fd level const (* 1000 value)))

(defun set-sockopt-linger (fd level const value)
  (let ((ret (c-inline (fd level const value) (:int :int :int :int) t
"{
        struct linger sockopt = {0, 0};
        int value = #3;
        int ret;

        if (value > 0) {
                sockopt.l_onoff = 1;
                sockopt.l_linger = value;
        }

        ecl_disable_interrupts();
        ret = setsockopt(#0,#1,#2,wincoerce(char *,&sockopt),
                         sizeof(struct linger));
        ecl_enable_interrupts();

        @(return) = (ret == 0) ? ECL_T : ECL_NIL;
}")))
    (if ret
        value
        (error "Sockopt error: ~A" (c-inline () () :cstring "strerror(errno)" :one-liner t)))))

(eval-when (:compile-toplevel :load-toplevel)
  (defmacro define-sockopt (name c-level c-const type &optional (read-only nil))
    `(progn
       (export ',name)
       (defun ,name (socket)
         (,(intern (format nil "GET-SOCKOPT-~A" type))
           (socket-file-descriptor socket)
           (c-constant ,c-level)
           (c-constant ,c-const)))
       ,@(unless read-only
           `((defun (setf ,name) (value socket)
               (,(intern (format nil "SET-SOCKOPT-~A" type))
                 (socket-file-descriptor socket)
                 (c-constant ,c-level)
                 (c-constant ,c-const)
                 value)))))))

(define-sockopt sockopt-type "SOL_SOCKET" "SO_TYPE" int t)
(define-sockopt sockopt-receive-buffer "SOL_SOCKET" "SO_RCVBUF" int)
(define-sockopt sockopt-receive-timeout "SOL_SOCKET" "SO_RCVTIMEO" timeval)
(define-sockopt sockopt-send-timeout "SOL_SOCKET" "SO_SNDTIMEO" timeval)
(define-sockopt sockopt-reuse-address "SOL_SOCKET" "SO_REUSEADDR" bool)
(define-sockopt sockopt-keep-alive "SOL_SOCKET" "SO_KEEPALIVE" bool)
(define-sockopt sockopt-dont-route "SOL_SOCKET" "SO_DONTROUTE" bool)
(define-sockopt sockopt-linger "SOL_SOCKET" "SO_LINGER" linger)

#-(or :sun4sol2 :linux :android :wsock :cygwin)
(define-sockopt sockopt-reuse-port "SOL_SOCKET" "SO_REUSEPORT" bool)

(define-sockopt sockopt-tcp-nodelay "IPPROTO_TCP" "TCP_NODELAY" bool)

;; Add sockopts here as you need them...

;; Finished loading
(provide '#:sockets)
(provide '#:sb-bsd-sockets)
