
(********************************************************************)
(*                                                                  *)
(*  socket.s7i    Socket library                                    *)
(*  Copyright (C) 1989 - 2007  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.                       *)
(*                                                                  *)
(********************************************************************)


const type: socketAddress is newtype;
IN_PARAM_IS_REFERENCE(socketAddress);

const proc: destroy (ref socketAddress param) is                               action "BST_DESTR";
const proc: (ref socketAddress param) ::= (ref socketAddress param) is         action "BST_CREATE";
const proc: (inout socketAddress param) := (ref socketAddress param) is        action "BST_CPY";

const func socketAddress: _GENERATE_EMPTY_SOCKET_ADDRESS is                    action "BST_EMPTY";
const socketAddress: (attr socketAddress) . value is                           _GENERATE_EMPTY_SOCKET_ADDRESS;


(**
 *  Return an internet socket address of a port at a host.
 *)
const func socketAddress: inetSocketAddress (in string param,
                                             in integer param) is              action "SOC_INET_ADDR";


(**
 *  Return an internet socket address of a port at localhost.
 *)
const func socketAddress: inetSocketAddress (in integer param) is              action "SOC_INET_LOCAL_ADDR";


(**
 *  Return an internet listener socket address of a port at localhost.
 *)
const func socketAddress: inetListenerAddress (in integer param) is            action "SOC_INET_SERV_ADDR";


const type: PRIMITIVE_SOCKET is newtype;
IN_PARAM_IS_VALUE(PRIMITIVE_SOCKET);

const proc: destroy (ref PRIMITIVE_SOCKET param) is                            noop;
const proc: (ref PRIMITIVE_SOCKET param) ::= (ref PRIMITIVE_SOCKET param) is   action "SOC_CREATE";
const proc: (inout PRIMITIVE_SOCKET param) := (ref PRIMITIVE_SOCKET param) is  action "SOC_CPY";

const func boolean: (in PRIMITIVE_SOCKET param) = (in PRIMITIVE_SOCKET param) is  action "SOC_EQ";
const func boolean: (in PRIMITIVE_SOCKET param) <> (in PRIMITIVE_SOCKET param) is action "SOC_NE";

const func PRIMITIVE_SOCKET: _GENERATE_EMPTY_PRIMITIVE_SOCKET is               action "SOC_EMPTY";
const PRIMITIVE_SOCKET: (attr PRIMITIVE_SOCKET) . value is                     _GENERATE_EMPTY_PRIMITIVE_SOCKET;
const PRIMITIVE_SOCKET: PRIMITIVE_NULL_SOCKET is                               PRIMITIVE_SOCKET.value;

const proc: close (in PRIMITIVE_SOCKET param) is                               action "SOC_CLOSE";
const func char: getc (in PRIMITIVE_SOCKET param) is                           action "SOC_GETC";
const func string: gets (in PRIMITIVE_SOCKET param, in integer param) is       action "SOC_GETS";
const func string: word_read (in PRIMITIVE_SOCKET param, inout char param) is  action "SOC_WORD_READ";
const func string: line_read (in PRIMITIVE_SOCKET param, inout char param) is  action "SOC_LINE_READ";
const proc: write (in PRIMITIVE_SOCKET param, in string param) is              action "SOC_WRITE";
const func integer: recv (in PRIMITIVE_SOCKET param, inout string param,
                          in integer param, in integer param) is               action "SOC_RECV";
const func integer: recvfrom (in PRIMITIVE_SOCKET param, inout string param,
                          in integer param, in integer param,
                          inout socketAddress param) is                        action "SOC_RECVFROM";
const func integer: send (in PRIMITIVE_SOCKET param, in string param,
                          in integer param) is                                 action "SOC_SEND";
const func integer: sendto (in PRIMITIVE_SOCKET param, in string param,
                          in integer param, in socketAddress param) is         action "SOC_SENDTO";

const func PRIMITIVE_SOCKET: PRIMITIVE_SOCKET (in integer param,
                                               in integer param,
                                               in integer param) is            action "SOC_SOCKET";
const func PRIMITIVE_SOCKET: accept (in PRIMITIVE_SOCKET param,
                                     inout socketAddress param) is             action "SOC_ACCEPT";
const proc: bind (in PRIMITIVE_SOCKET param, in socketAddress param) is        action "SOC_BIND";
const proc: connect (in PRIMITIVE_SOCKET param, in socketAddress param) is     action "SOC_CONNECT";
const proc: listen (in PRIMITIVE_SOCKET param, in integer param) is            action "SOC_LISTEN";


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


(* Operations for socket files *)

const type: socket is sub null_file struct
    var PRIMITIVE_SOCKET: sock is PRIMITIVE_NULL_SOCKET;
    var socketAddress: addr is socketAddress.value;
  end struct;

type_implements_interface(socket, file);


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


(**
 *  Return a connected internet socket file at a port at localhost.
 *)
const func file: openInetSocket (in integer: portNumber) is func
  result
    var file: result is STD_NULL;
  local
    var PRIMITIVE_SOCKET: open_socket is PRIMITIVE_NULL_SOCKET;
    var socket: new_socket is socket.value;
  begin
    open_socket := PRIMITIVE_SOCKET(PF_INET, SOCK_STREAM, 0);
    if open_socket <> PRIMITIVE_NULL_SOCKET then
      new_socket.addr := inetSocketAddress(portNumber);
      connect(open_socket, new_socket.addr);
      new_socket.sock := open_socket;
      result := malloc(new_socket);
    end if;
  end func;


(**
 *  Return a connected internet socket file at a port at 'hostName'.
 *)
const func file: openInetSocket (in string: hostName, in integer: portNumber) is func
  result
    var file: result is STD_NULL;
  local
    var PRIMITIVE_SOCKET: open_socket is PRIMITIVE_NULL_SOCKET;
    var socket: new_socket is socket.value;
  begin
    open_socket := PRIMITIVE_SOCKET(PF_INET, SOCK_STREAM, 0);
    if open_socket <> PRIMITIVE_NULL_SOCKET then
      new_socket.addr := inetSocketAddress(hostName, portNumber);
      connect(open_socket, new_socket.addr);
      new_socket.sock := open_socket;
      result := malloc(new_socket);
    end if;
  end func;


(**
 *  Close the socket 'aSocket'.
 *)
const proc: close (in socket: aSocket) is func
  begin
    close(aSocket.sock);
  end func;


(**
 *  Flush the socket 'aSocket'.
 *)
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;


(**
 *  Return a character read from 'aSocket'.
 *)
const func char: getc (in socket: aSocket) is
  return getc(aSocket.sock);


(**
 *  Return a string read with a maximum length 'leng' from 'aSocket'.
 *)
const func string: gets (in socket: aSocket, in integer: leng) is
  return gets(aSocket.sock, leng);


(**
 *  Return a word read from 'aSocket'.
 *  Before reading the word it skips spaces and tabs. The function
 *  accepts words ending with " ", "\t", "\n", "\r\n" or 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 inFile.bufferChar contains ' ',
 *  '\t', '\n' or EOF.
 *)
const func string: getwd (inout socket: aSocket) is
  return word_read(aSocket.sock, aSocket.bufferChar);


(**
 *  Return a line read from 'aSocket'.
 *  The function accepts lines ending with "\n", "\r\n" or 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 inFile.bufferChar contains '\n' or
 *  EOF.
 *)
const func string: getln (inout socket: aSocket) is
  return line_read(aSocket.sock, aSocket.bufferChar);


(**
 *  Return TRUE if the end-of-file indicator is set, FALSE otherwise.
 *  @return FALSE since a socket never reaches end-of-file.
 *)
const func boolean: eof (in socket: aSocket) is
  return FALSE; # eof(aSocket.sock);


(**
 *  Return FALSE if 'getc' would return EOF, TRUE otherwise.
 *  @return TRUE since the next 'getc' will never return EOF.
 *)
const func boolean: hasNext (in socket: aSocket) is
  return TRUE; # hasNext(aSocket.sock);


(**
 *  Return the length of a socket.
 *)
const func integer: length (in socket: aSocket) is
  return 0; # length(aSocket.sock);


const proc: seek (in socket: aSocket, in integer: pos) is func
  begin
    # seek(aSocket.sock, pos);
    noop;
  end func;


const func integer: tell (in socket: aSocket) is
  return 0; # tell(aSocket.sock);


(* Operations for the listener *)

const type: listener is new struct
    var PRIMITIVE_SOCKET: sock is PRIMITIVE_NULL_SOCKET;
    var socketAddress: addr is socketAddress.value;
  end struct;


(**
 *  Return a bound internet listener at a port at localhost.
 *)
const func listener: openInetListener (in integer: portNumber) is func
  result
    var listener: result is listener.value;
  local
    var PRIMITIVE_SOCKET: open_socket is PRIMITIVE_NULL_SOCKET;
  begin
    open_socket := PRIMITIVE_SOCKET(PF_INET, SOCK_STREAM, 0);
    if open_socket <> PRIMITIVE_NULL_SOCKET then
      result.addr := inetListenerAddress(portNumber);
      bind(open_socket, result.addr);
      result.sock := open_socket;
    end if;
  end func;


(**
 *  Listen for socket connections and limit the incoming queue.
 *)
const proc: listen (in listener: aListener, in integer: backlog) is func
  begin
    listen(aListener.sock, backlog);
  end func;


(**
 *  Return a new accepted connection socket on a 'aListener'.
 *)
const func file: accept (in listener: aListener) is func
  result
    var file: result is STD_NULL;
  local
    var PRIMITIVE_SOCKET: open_socket is PRIMITIVE_NULL_SOCKET;
    var socket: new_socket is socket.value;
  begin
    open_socket := accept(aListener.sock, new_socket.addr);
    if open_socket <> PRIMITIVE_NULL_SOCKET then
      new_socket.sock := open_socket;
      result := malloc(new_socket);
    end if;
  end func;
