
(********************************************************************)
(*                                                                  *)
(*  socket.s7i    File implementation type for sockets              *)
(*  Copyright (C) 2007 - 2009, 2011  Thomas Mertes                  *)
(*                                                                  *)
(*  This file is part of the Seed7 Runtime Library.                 *)
(*                                                                  *)
(*  The Seed7 Runtime Library is free software; you can             *)
(*  redistribute it and/or modify it under the terms of the GNU     *)
(*  Lesser General Public License as published by the Free Software *)
(*  Foundation; either version 2.1 of the License, or (at your      *)
(*  option) any later version.                                      *)
(*                                                                  *)
(*  The Seed7 Runtime Library is distributed in the hope that it    *)
(*  will be useful, but WITHOUT ANY WARRANTY; without even the      *)
(*  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *)
(*  PURPOSE.  See the GNU Lesser General Public License for more    *)
(*  details.                                                        *)
(*                                                                  *)
(*  You should have received a copy of the GNU Lesser General       *)
(*  Public License along with this program; if not, write to the    *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


include "null_file.s7i";
include "enable_io.s7i";
include "duration.s7i";
include "sockbase.s7i";


const integer: SOCK_STREAM is 1;
const integer: SOCK_DGRAM is 2;


(**
 *  Interface type for listeners.
 *  The listener interface is implemented with inetListener.
 *  A listener manages its accepted sockets.
 *)
const type: listener is sub object interface;

const proc: close (inout listener: aListener)                               is DYNAMIC;
const proc: listen (in listener: aListener, in integer: backlog)            is DYNAMIC;
const func file: accept (inout listener: aListener)                         is DYNAMIC;
const proc: signOn (inout listener: aListener, in file: sock)               is DYNAMIC;
const proc: signOff (inout listener: aListener, in file: sock)              is DYNAMIC;
const proc: waitForRequest (inout listener: aListener)                      is DYNAMIC;
const func file: getExistingConnection (in listener: aListener)             is DYNAMIC;
const func file: getNewConnection (in listener: aListener)                  is DYNAMIC;


const type: baseListener is new struct
  end struct;

type_implements_interface(baseListener, listener);

const listener: (attr listener) . value is baseListener.value;


(* Operations for socket files *)


(**
 *  [[file|File]] implementation type for OS sockets.
 *  This type supports communication via sockets. A ''socket'' is
 *  not seekable. The functions [[#length(in_null_file)|length]],
 *  [[#seek(in_null_file,in_integer)|seek]] and [[#tell(in_null_file)|tell]]
 *  raise FILE_ERROR.
 *)
const type: socket is sub null_file struct
    var PRIMITIVE_SOCKET: sock is PRIMITIVE_NULL_SOCKET;
    var socketAddress: addr is socketAddress.value;
    var string: service is "";
    var listener: acceptedFrom is listener.value;
  end struct;


type_implements_interface(socket, file);


const func string: service (in file param) is DYNAMIC;
const func string: service (in socket: aSocket) is
  return aSocket.service;

const func socketAddress: address (in file param) is DYNAMIC;
const func socketAddress: address (in socket: aSocket) is
  return address(aSocket.sock);

const func integer: port (in file param) is DYNAMIC;
const func integer: port (in socket: aSocket) is
#  return integer parse (aSocket.service);
  return integer parse service(aSocket.addr);


const func boolean: inputReady (in file param, in duration param) is DYNAMIC;
const func boolean: inputReady (in socket: aSocket, in duration: timeout) is
  return inputReady(aSocket.sock, toSeconds(timeout), timeout.micro_second);


const func socket: malloc (in socket: sock) is func
  result
    var socket: newSocket is socket.value;
  begin
    newSocket := sock;
  end func;


(**
 *  Return a connected socket file for the given [[sockbase|socket address]].
 *  @return the socket file opened, or STD_NULL if it could not be opened.
 *)
const func file: openSocket (in socketAddress: address) is func
  result
    var file: newSocket is STD_NULL;
  local
    var PRIMITIVE_SOCKET: open_socket is PRIMITIVE_NULL_SOCKET;
    var socket: new_socket is socket.value;
  begin
    if address <> socketAddress.value then
      open_socket := PRIMITIVE_SOCKET(addrFamily(address), SOCK_STREAM, 0);
      if open_socket <> PRIMITIVE_NULL_SOCKET then
        block
          connect(open_socket, address);
          new_socket.addr := address;
          new_socket.service := service(address);
          new_socket.sock := open_socket;
          newSocket := malloc(new_socket);
        exception
          catch FILE_ERROR:
            close(open_socket);
        end block;
      end if;
    end if;
  end func;


(**
 *  Return a connected internet socket file at a port at localhost.
 *  @return the socket file opened, or STD_NULL if it could not be opened.
 *  @exception FILE_ERROR A system function returns an error.
 *  @exception RANGE_ERROR The port is not in the range 0 to 65535.
 *  @exception MEMORY_ERROR An out of memory situation occurred.
 *)
const func file: openInetSocket (in integer: portNumber) is
  return openSocket(inetSocketAddress(portNumber));


(**
 *  Return a connected internet socket file at a port at ''hostName''.
 *  Here ''hostName'' is either a host name (e.g.: "www.example.org"),
 *  or an IPv4 address in standard dot notation (e.g.: "192.0.2.235").
 *  Operating systems supporting IPv6 may also accept an IPv6 address
 *  in colon notation.
 *  @return the socket file opened, or STD_NULL if it could not be opened.
 *  @exception FILE_ERROR A system function returns an error.
 *  @exception RANGE_ERROR The port is not in the range 0 to 65535.
 *  @exception MEMORY_ERROR An out of memory situation occurred.
 *)
const func file: openInetSocket (in string: hostName, in integer: portNumber) is
  return openSocket(inetSocketAddress(hostName, portNumber));


(**
 *  Close the socket ''aSocket''.
 *  A listener manages accepted sockets (its existing connections).
 *  When a socket was accepted from a listener it is signed off
 *  from the listener.
 *)
const proc: close (inout socket: aSocket) is func
  begin
    if aSocket.acceptedFrom <> listener.value then
      signOff(aSocket.acceptedFrom, aSocket);
    end if;
    close(aSocket.sock);
    aSocket.sock := PRIMITIVE_NULL_SOCKET;
  end func;


const proc: release (inout file: aFile) is DYNAMIC;


const proc: release (inout socket: aSocket) is func
  begin
    aSocket.acceptedFrom := listener.value;
  end func;


(**
 *  Forces that all buffered data of ''aFile'' is sent to its destination.
 *  Flushing a socket has no effect.
 *)
const proc: flush (in socket: aSocket) is func
  begin
    noop; # flush(aSocket.sock);
  end func;


(**
 *  Write the string ''stri'' to ''aSocket''.
 *)
const proc: write (in socket: aSocket, in string: stri) is func
  begin
    write(aSocket.sock, stri);
  end func;


(**
 *  Write newline character ('\n') to ''aSocket''.
 *)
const proc: writeln (in socket: aSocket) is func
  begin
    write(aSocket.sock, "\n");
  end func;


const proc: backSpace (in socket: aSocket) is func
  begin
    write(aSocket.sock, "\b \b");
  end func;


(**
 *  Read a character from ''aSocket''.
 *  @return the character read.
 *)
const func char: getc (inout socket: aSocket) is
  return getc(aSocket.sock, aSocket.bufferChar);


(**
 *  Read a string with a maximum length from ''aSocket''.
 *  @return the string read.
 *  @exception RANGE_ERROR The length is negative.
 *  @exception MEMORY_ERROR Not enough memory to represent the result.
 *)
const func string: gets (inout socket: aSocket, in integer: maxLength) is
  return gets(aSocket.sock, maxLength, aSocket.bufferChar);


(**
 *  Read a word from ''aSocket''.
 *  Before reading the word it skips spaces and tabs. The function
 *  accepts words ending with " ", "\t", "\n", "\r\n" or [[char#EOF|EOF]].
 *  The word ending characters are not copied into the string.
 *  That means that the "\r" of a "\r\n" sequence is silently removed.
 *  When the function is left the aSocket.bufferChar contains ' ',
 *  '\t', '\n' or EOF.
 *  @return the word read.
 *  @exception MEMORY_ERROR Not enough memory to represent the result.
 *)
const func string: getwd (inout socket: aSocket) is
  return word_read(aSocket.sock, aSocket.bufferChar);


(**
 *  Read a line from 'aSocket'.
 *  The function accepts lines ending with "\n", "\r\n" or [[char#EOF|EOF]].
 *  The line ending characters are not copied into the string. That
 *  means that the "\r" of a "\r\n" sequence is silently removed. When
 *  the function is left the aSocket.bufferChar contains '\n' or EOF.
 *  @return the line read.
 *  @exception MEMORY_ERROR Not enough memory to represent the result.
 *)
const func string: getln (inout socket: aSocket) is
  return line_read(aSocket.sock, aSocket.bufferChar);


(**
 *  Determine the end-of-file indicator.
 *  The end-of-file indicator is set when at least one request to read
 *  from the socket failed. The socket functions ''getc'', ''gets'',
 *  ''getln'' and ''getwd'' indicate the end-of-file situation by
 *  setting ''bufferChar'' to [[char#EOF|EOF]].
 *  @return TRUE if the end-of-file indicator is set, FALSE otherwise.
 *)
const func boolean: eof (in socket: aSocket) is
  return aSocket.bufferChar = EOF;


(**
 *  Determine if at least one character can be read successfully.
 *  This function allows a socket to be handled like an iterator.
 *  Since ''hasNext'' peeks the next character from the socket
 *  it may block.
 *  @return FALSE if ''getc'' would return EOF, TRUE otherwise.
 *)
const func boolean: hasNext (in socket: aSocket) is
  return hasNext(aSocket.sock);
