;;; -*- Mode: Lisp; Package: CCL; Coding: utf-8; -*-

(chapter "Programming with Sockets"
  (defsection "Overview"
    (para "{CCL} supports the socket abstraction for
      interprocess communication. A socket represents a connection to
      another process, typically (but not necessarily) a TCP/IP
      network connection to a client or server running on some other
      machine on the network.")
    (para "IPv6 is supported by the :internet6 address family.
      Applications should use the "
      (ref (definition :function ccl::resolve-address)) "
      function to translate host and port specifications to socket
      addresses.  While host and port numbers can still be dealt with
      separately, it is preferable to use "
      (ref (definition :class ccl::socket-address)) " instances to specify socket
      endpoints for unified parsing of string representations and
      printing.")
    (para "All symbols mentioned in this chapter are exported from
      the CCL package. As of version 0.13, these symbols are
      additionally exported from the OPENMCL-SOCKET package.")
    (para "{CCL} supports three types of sockets: TCP sockets, UDP
      sockets, and Unix-domain sockets.  This should be enough for all
      but the most esoteric network situations.  All sockets are
      created by "
      (ref (definition :function make-socket)) ".  The type of socket
      depends on the arguments to it, as follows:")
    (listing :definition
      (item "tcp-stream" ccldoc::=> "A buffered bi-directional stream over a TCP/IP connection.
	    tcp-stream is a subclass of stream, and you can read and write to it
	    using all the usual stream functions. Created by (make-socket
	    :address-family :internet :type :stream :connect :active ...) or by
	    (accept-connection ...).")
      (item "file-socket-stream" ccldoc::=> #:|A buffered bi-directional stream over a "UNIX domain"
	    connection. file-socket-stream is a subclass of stream, and you can
	    read and write to it using all the usual stream functions. Created
	    by (make-socket :address-family :file :type :stream :connect :active
	    ...) or by (accept-connection ...),|)
      (item "listener-socket" ccldoc::=> "A passive socket used to listen for incoming TCP/IP
	    connections on a particular port. A listener-socket is not a stream.
	    It doesn't support I/O. It can only be used to create new
	    tcp-streams by accept-connection. Created by (make-socket :type
	    :stream :connect :passive ...)")
      (item "file-listener-socket" ccldoc::=> "A passive socket used to listen for incoming UNIX domain
	    connections named by a file in the local filesystem. A
	    listener-socket is not a stream. It doesn't support I/O. It can
	    only be used to create new file-socket-streams by accept-connection.
	    Created by (make-socket :address-family :file :type :stream :connect
	    :passive ...)")
      (item "udp-socket" ccldoc::=> "A socket representing a packet-based UDP/IP connection. A
	    udp-socket supports I/O but it is not a stream. Instead, you must
	    use the special functions send-to and receive-from to read and write
	    to it. Created by (make-socket :type :datagram ...)")))
  (defsection "Sockets Dictionary"
    (definition (:function make-socket)
      "make-socket {code &key} address-family type connect eol format remote-address remote-host remote-port local-address local-host local-port local-filename remote-filename keepalive reuse-address nodelay broadcast linger backlog input-timeout output-timeout connect-timeout auto-close deadline"
      "Make a socket."
      (defsection "Arguments and Values"
	(listing :definition
	  (item "{param address-family}" ccldoc::=> "The address/protocol family of this
		socket. Currently, :internet (the default), meaning
		IPv4, :internet6, meaning IPv6, and :file, referring
		to UNIX domain addresses, are supported.")
	  (item "{param type}" ccldoc::=> "One of :stream (the default) to request a
		connection-oriented socket, or :datagram to request a
		connectionless socket. The default is :stream.")
	  (item "{param connect}" ccldoc::=> "This argument is only relevant to sockets of type
		:stream. One of :active (the default) to request a :passive
		to request a file or TCP listener socket.")
	  (item "{param eol}" ccldoc::=> "This argument is currently ignored (it is accepted for
		compatibility with Franz Allegro).")
	  (item "{param format}" ccldoc::=> "One of :text (the default), :binary, or :bivalent.
		This argument is ignored for :stream sockets for now, as
		:stream sockets are currently always bivalent (i.e. they
		support both character and byte I/O). For :datagram sockets,
		this argument is ignored (the format of a datagram socket
		is always :binary).
		")
	  (item "{param remote}" ccldoc::=> "For TCP streams, it specifies the socket address
		to connect to, specified as socket-address instance.
		Ignored for listener sockets. For UDP sockets, it can
		be used to specify a default address for subsequent calls
		to send-to or receive-from.")
	  (item "{param remote-host}" ccldoc::=> "For TCP streams, it specifies the host to
		connect to (in any format acceptable to resolve-address).
		Ignored for listener sockets. For UDP sockets, it can be
		used to specify a default host for subsequent calls to
		send-to or receive-from.")
	  (item "{param remote-port}" ccldoc::=> "For TCP streams, it specifies the port to
		connect to (in any format acceptable to resolve-address).
		Ignored for listener sockets. For UDP sockets, it can be
		used to specify a default port for subsequent calls to for
		subsequent calls to send-to or receive-from.")
	  (item "{param remote-filename}" ccldoc::=> "For file-socket streams, it specifies the
		name of a file in the local filesystem (e.g., NOT mounted
		via NFS, AFP, SMB, ...) which names and controls access to a
		UNIX-domain socket.")
	  (item "{param local-address}" ccldoc::=> "Allows you to specify a local address for a
		listener or UDP socket, for the case where you want to
		restrict connections to those coming to a specific local
		address for security reasons.")
	  (item "{param local-host}" ccldoc::=> "Allows you to specify a local host address for a
		listener or UDP socket, for the case where you want to
		restrict connections to those coming to a specific local
		address for security reasons.")
	  (item "{param local-port}" ccldoc::=> "Specify a local port for a socket. Most useful for
		listener sockets, where it is the port on which the socket
		will listen for connections.")
	  (item "{param local-filename}" ccldoc::=> "For file-listener-sockets, specifies the name
		of a file in the local filesystem which is used to name a
		UNIX-domain socket. The actual filesystem file should not
		previously exist when the file-listener-socket is created;
		its parent directory should exist and be writable by the
		caller. The file used to name the socket will be deleted
		when the file-listener-socket is closed.")
	  (item "{param keepalive}" ccldoc::=> #:|If true, enables the periodic transmission of
		"keepalive" messages.|)
	  (item "{param reuse-address}" ccldoc::=> "If true, allows the reuse of local ports in listener
		sockets, overriding some TCP/IP protocol specifications. You
		will need this if you are debugging a server..")
	  (item "{param nodelay}" ccldoc::=> "If true, disables Nagle's algorithm, which tries
		to minimize TCP packet fragmentation by introducing
		transmission delays in the absence of replies. Try setting
		this if you are using a protocol which involves sending a
		steady stream of data with no replies and are seeing
		significant degradations in throughput.")
	  (item "{param broadcast}" ccldoc::=> "If true, requests permission to broadcast datagrams on
		a UDP socket.")
	  (item "{param linger}" ccldoc::=> "If specified and non-nil, should be the number of
		seconds the OS is allowed to wait for data to be pushed
		through when a close is done. Only relevant for TCP sockets.")
	  (item "{param backlog}" ccldoc::=> "For a listener socket, specifies the number of
		connections which can be pending but not accepted. The
		default is 5, which is also the maximum on some operating
		systems.")
	  (item "{param input-timeout}" ccldoc::=> "The number of seconds before an input operation
		times out.  Must be a real number between zero and one
		million.  If an input operation takes longer than the
		specified number of seconds, an
		{code input-timeout} error is signalled.
		(see {section Stream Timeouts and Deadlines})")
	  (item "{param output-timeout}" ccldoc::=> "The number of seconds before an output operation
		times out.  Must be a real number between zero and one
		million.  If an output operation takes longer than the
		specified number of seconds, an
		{code output-timeout} error is signalled.
		(see {section Stream Timeouts and Deadlines})")
	  (item "{param connect-timeout}" ccldoc::=> "The number of seconds before a connection
		attempt times out. [TODO: what are acceptable values?]
		If a connection attempt takes longer than the
		specified number of seconds, a
		{code socket-error} is signalled.  This
		can be useful if the specified interval is shorter
		than the interval that the OS's socket layer imposes,
		which is sometimes a minute or two.")
	  (item "{param auto-close}" ccldoc::=> "When non-nil, any resulting socket stream will
		be closed when the GC can prove that the stream is
		unreferenced.  This is done via CCL's termination
		mechanism [TODO add xref].")
	  (item "{param deadline}" ccldoc::=> "Specifies an absolute time in
		internal-time-units.  If an I/O operation on the
		stream does not complete before the deadline then a
		{code COMMUNICATION-DEADLINE-EXPIRED}
		error is signalled.  A deadline takes precedence over
		any input/output timeouts that may be set.  (see {section Stream Timeouts and Deadlines})")))
      (defsection "Description"
	(para "Creates and returns a new socket.  For :passive
	  sockets, the :local-address, :local-port or :local-filename
	  arguments are required, depending on the type of the socket.
	  For :active sockets, either the :remote-address, the
	  :remote-host and :remote-port, or the :remote-filename
	  arguments must be present, depending on the socket
	  type.")))

    (definition (:function accept-connection) "accept-connection (socket listener-socket) code &key (wait t)" nil
      (para "Extracts the first connection on the queue of pending
	  connections for {param socket}, accepts it (i.e. completes
	  the connection startup protocol), and returns a new
	  tcp-stream or file-socket-stream representing the newly
	  established connection. The tcp stream inherits any
	  properties of the listener socket that are relevant
	  (e.g. {code :keepalive}, {code :nodelay}, and so forth.) The
	  original listener socket continues to be open listening for
	  more connections, so you can call accept-connection on it
	  again.")

      (para "If {code :wait} is {code t}, and there are no connections
             waiting to be accepted, the function wait until one
             arrives.  Otherwise, {function accept-connection} will
             return {code nil} immediately."))

    (definition (:function ccl::resolve-address)
      "resolve-address {code &key} host port socket-type connect address-family numeric-host-p numeric-service-p singlep errorp"
      nil
      (para "Converts a host and/or port specification to one or more "
	(ref (definition :class ccl::socket-address)) " instances.
	  This function uses the getaddrinfo() system function
	  underneath which knows how to translate all standard address
	  and port formats and appropriately orders addresses for hosts
	  with multiple addresses.  It returns the best matching
	  translation or, if the {code :singlep} is false, all
	  matching translations for the given parameter
	  combination.")
      (defsection "Arguments and Values"
	(listing :definition
	  (item "{param host}" ccldoc::=> "Specification of the host, as a string.  This
		can be either a host name such as
		“clozure.com” or any of the literal address
		forms accepted by getaddrinfo().")
	  (item "{param port}" ccldoc::=> "Specification of the port.  This
		can be either a service name such as
		“http” or a port number.")
	  (item "{param socket-type}" ccldoc::=> "Service type for port lookups, can be either
		:stream for TCP services or :datagram for UDP.
		Defaults to :stream.")
	  (item "{param connect}" ccldoc::=> "Specifies how the returned " (ref (definition :class ccl::socket-address))
	    " instances will be used.
		If :active is passed, which is the default, the host
		address will default to the address of the loopback
		interface of the local host if not specified.  When
		:passive is used, the host address will default to the
		wildcard address for the given address family.  This
		parameter has no effect if the :host parameter is
		used.")
	  (item "{param address-family}" ccldoc::=> "Specifies the address family that should be
		returned, can be specified as either :internet or
		:internet6.  If it is specified, only addresses of
		that family are returned.")
	  (item "{param numeric-host-p}" ccldoc::=> "If this argument is true, no host name lookups
		will be performed for the host address.  A numeric
		address literal must be passed in this case.")
	  (item "{param numeric-port-p}" ccldoc::=> "If this argument is true, no service name
		lookups will be performed for the port address.  A
		numeric port number must be passed in this
		case.")
	  (item "{param singlep}" ccldoc::=> "If this argument is set to a true value, which
		is the default, only the first matching address is
		returned.  If it is passed as NIL, all matching
		addresses are returned as a list.")
	  (item "{param errorp}" ccldoc::=> "If this argument is set to a true value, which
		is the default, an error is signalled if the given
		host and/or port combination did not yield any
		matches.  If it is passed as NIL, the function returns
		NIL if no addresses matched the supplied
		arguments."))))

    (definition (:function dotted-to-ipaddr) "dotted-to-ipaddr dotted &key (errorp t)" nil
      "Converts {param dotted}, which should be a dotted quad string such
as “192.168.0.1”, into a integer representation.  If {code :errorp} is true,
an error is signaled if {param dotted} is invalid.  Otherwise, {code nil} is
returned.")

    (definition (:function ipaddr-to-dotted) "ipaddr-to-dotted ipaddr &key values" nil
      "This function converts {param ipaddr}, an integer representing
an IPv4 host, into a dotted quad string.  If {code :values} is true,
instead of a dotted quad string, it returns the four octets of the
address as multiple values.")

    (definition (:function ipaddr-to-hostname) "ipaddr-to-hostname ipaddr {code &key} ignore-cache" nil
      (para "Converts {param ipaddr}, a 32-bit unsigend integer, into
a host name string.  The keyword argument {code :ignore-cache} is
ignored (it is accepted for compatibility with Franz Allegro CL)."))

    (definition (:function lookup-hostname) "lookup-hostname host-spec" nil
      (para "Converts {param host-spec} into a 32-bit unsigned IPv4
	address.  IPv6-enabled applications should use the "
	(ref (definition :function ccl::resolve-address)) " function instead.")
      (para "Acceptable formats for {param host-spec} include a host name
string such as “www.clozure.com”, a dotted address string such as “192.168.0.1”,
or a 32-bit unsigned IPv4 address such as 3232235521."))

    (definition (:function lookup-port) "lookup-port port protocol" nil
      "Finds the numeric port number for the specified {param port} and
{param protocol}."

      "{param port} can be a string such as “http”, a symbol such as
{code :http}, or a port number.  Note that strings are case-sensitive.
Symbol names are converted to lower-case before lookup.  Protocol must
be one of “tcp” or “udp”.")

    (definition (:function receive-from) "receive-from (socket udp-socket) size code &key buffer extract offset want-socket-address-p" nil
      "Reads a UDP packet from {param socket}. If no packets are
available, waits for a packet to arrive.

If :want-socket-address-p is {code nil}, which is the
default, four values are returned:"
      (listing :number
	(item "The buffer with the data")
	(item "The number of bytes read")
	(item "The 32-bit unsigned IPv4 address or the 16 byte
	      IPv6 address of the sender of the data")
	(item "The port number of the sender of the data"))

      (para "If :want-socket-address-p is true, three values are
          returned:")
      (listing :number
	(item "The buffer with the data")
	(item "The number of bytes read")
	(item "The " (ref (definition :class ccl::socket-address)) " instance of
	      the sender of the data."))

      (defsection "Arguments and Values"
	(listing :definition
	  (item "{param socket}" ccldoc::=> "The socket to read from")
	  (item "{param size}" ccldoc::=> "Maximum number of bytes to read. If the packet is
		larger than this, any extra bytes are discarded.")
	  (item "{param buffer}" ccldoc::=> "If specified, must be an octet vector
		which will be used to read in the data. If not specified, a
		new buffer will be created (of type determined by
		socket-format).")
	  (item "{param extract}" ccldoc::=> "If true, the subsequence of the buffer corresponding
		only to the data read in is extracted and returned as the
		first value. If false (the default) the original buffer is
		returned even if it is only partially filled.")
	  (item "{param offset}" ccldoc::=> "Specifies the start offset into the buffer at which
		data is to be stored. The default is 0.")
	  (item "{param want-socket-address-p}" ccldoc::=> "Indicates that the address of the sender of the
		data should be returned as a "
	    (ref (definition :class ccl::socket-address)) " instance rather than as
		separate host and port values."))))

    (definition (:function send-to)
      "send-to (socket udp-socket) buffer size code &key remote remote-host remote-port offset" nil
      "This function sends a UDP packet over {param socket}."
      (defsection "Arguments and Values"
	(listing :definition
	  (item "{param socket}" ccldoc::=> "The socket to write to")
	  (item "{param buffer}" ccldoc::=> "A vector containing the data to send. It must be
		an octet vector.")
	  (item "{param size}" ccldoc::=> "Number of octets to send")
	  (item "{param remote}" ccldoc::=> "The remote address to send the packet to, as a
		"
	    (ref (definition :class ccl::socket-address)) " instance. The
		default is the remote address specified in the call to
		make-socket.")
	  (item "{param remote-host}" ccldoc::=> "The host to send the packet to, in any format
		acceptable to lookup-hostname. The default is the remote
		host specified in the call to make-socket.")
	  (item "{param remote-port}" ccldoc::=> "The port to send the packet to, in any format
		acceptable to lookup-port. The default is the remote port
		specified in the call to make-socket.")
	  (item "{param offset}" ccldoc::=> "The offset in the buffer where the packet data starts"))))

    (definition (:function shutdown) "shutdown socket &key direction" nil
      "Shuts down part of a bidirectional connection represented by
        {param socket}. Typically {param socket} will be a tcp-stream.
        One situation where this can be useful is when you need to
        read responses after sending an end-of-file signal.  The
        keyword argument {code :direction} may be either
        {code :input} (to disallow further input) or
        {code :output} (to disallow further output).
        Or, since release 1.11.5, {code :both}.")

    (definition (:function socket-os-fd) "socket-os-fd socket" nil
      (para "Returns the native OS's representation of {param
	  socket}, or {code nil} if the socket is closed. On Unix,
	  this will be a file descriptor. Note that it is rather
	  dangerous to mess around with tcp-stream file descriptors,
	  as there is all sorts of buffering and asynchronous I/O
	  going on above the OS level. listener-socket and udp-socket
	  file descriptors are safer to mess with directly as there is
	  less magic going on."))

    (definition (:function remote-host) "remote-host socket" nil
      (para "Returns the 32-bit unsigned IPv4 address of the remote host,
	  or {code nil} if {param socket} is not connected."))

    (definition (:function remote-port) "remote-port socket" nil
      (para "Returns the remote port number of {param socket}, or NIL if
{param socket} is not connected."))

    (definition (:function local-host) "local-host socket" nil
      (para "Returns 32-bit unsigned IPv4 address or the 16 byte
	  IPv6 address of the local host for {param socket}."))

    (definition (:function local-port) "local-port socket" nil
      "This function returns the local port number of {param socket}.")

    (definition (:function socket-address-family) "socket-address-family thing" nil
      "Returns :internet, :internet6 or :file, as appropriate for
{param thing}, which should be a socket or socket-address.")

    (definition (:function ccl::socket-address-host) "socket-address-host socket-address" nil
      "Returns the host portion of the given {param socket-address}.
       For :internet addresses, this is a 32-bit integer.  For
	  :internet6 addresses, a vector of 16 bytes returned.  For
	  :file addresses, it is the file name string.")

    (definition (:function ccl::socket-address-port) "socket-address-port socket-address" nil
      "This function returns the port number of the given {param
        socket-address}.  This function is available only for
        {code :internet} and {code :internet6} socket addresses.")

    (definition (:function socket-connect) "socket-connect socket" nil
      "This function examines {param socket} and returns
{code :active} for a tcp-stream, {code :passive} for listener-socket,
and {code nil} for a udp-socket.")

    (definition (:function socket-format) "socket-format socket" nil
      "Returns the format of {param socket} as specified by
	the :format argument to make-socket.")

    (definition (:function socket-type) "socket-type socket" nil
      (para "This function returns :stream if {param socket} is a
       tcp-stream or listener-socket.  If the socket is a udp-socket,
{function socket-type} will return :datagram."))

    (definition (:class ccl::socket-address) "socket-address" nil
      (para "This class is a representation of a socket endpoint address.
          Instances of this class are used to encapsulate the host and
          port of an IP socket endpoint or the filename of a file
          socket.  They can be created by applications from a possibly
          symbolic address representation by the "
	(ref (definition :function ccl::resolve-address)) " function."))

    (definition (:class socket-error) "socket-error" nil
      "The class of OS errors signaled by socket functions (a subclass
of {code simple-error}.")

    (definition (:function socket-error-code) "socket-error-code socket-error"
      nil
      "Returns the OS error code contained in {param socket-error}.")

    (definition (:function socket-error-identifier) "socket-error-identifier socket-error" nil
      "Returns a symbol representing the socket error code contained
       in {param socket-error}.  It will be one of
       {code :address-in-use}, {code :connection-aborted},
       {code :no-buffer-space}, {code :connection-timed-out},
       {code :connection-refused}, {code :host-unreachable},
       {code :host-down}, {code :network-down},
       {code :address-not-available}, {code :network-reset},
       {code :connection-reset}, {code :shutdown},
       {code :access-denied}, or {code :unknown}.")

    (definition (:function socket-error-situation) "socket-error-situation socket-error" nil
      "Returns a string describing the context where the error {param
socket-error} happened. On Linux, this is the name of the system call
which returned the error.")

    (definition (:method close) "close (socket socket) code &key abort" nil
      "Close {param socket}, releasing the operating system resources
associated with it.  Normally, any pending buffered I/O will be
finished up before closing, but if {code :abort} is {code t}, any
pending I/O will be aborted.  Note that for listener and udp sockets,
there is never any buffered I/O to clean up, so the value of
{code :abort} is effectively ignored.")

    (definition (:macro with-open-socket) "with-open-socket (var . make-socket-args) &body body" nil
      "Executes {param body} with {param var} bound to the result of
       applying {function make-socket} to {param
       make-socket-args}. The socket gets closed on exit.")))
