-- spark_sockets.ads
--
-- Copyright (C) 2006-2023 wolfSSL Inc.
--
-- This file is part of wolfSSL.
--
-- wolfSSL is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- wolfSSL 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 General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program; if not, write to the Free Software
-- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
--

--  GNAT Library packages.
with GNAT.Sockets;

--  The WolfSSL package.
with WolfSSL;

--  This is a wrapper package around the GNAT.Sockets package.
--  GNAT.Sockets raises exceptions to signal errors but exceptions
--  are not supported by SPARK. This package converts raised exceptions
--  into returned enumeration values by functions indicating success
--  or failure.
--
--  The intended use of this package is to demonstrate the usage
--  of the WolfSSL Ada binding in Ada/SPARK code.
package SPARK_Sockets with SPARK_Mode is

   subtype Byte_Array is WolfSSL.Byte_Array;
   subtype Byte_Index is WolfSSL.Byte_Index; use type Byte_Index;

   subtype Port_Type is GNAT.Sockets.Port_Type;

   subtype Level_Type is GNAT.Sockets.Level_Type;

   subtype Socket_Type is GNAT.Sockets.Socket_Type;
   subtype Option_Name is GNAT.Sockets.Option_Name;
   subtype Option_Type is GNAT.Sockets.Option_Type;
   subtype Family_Type is GNAT.Sockets.Family_Type;

   subtype Sock_Addr_Type is GNAT.Sockets.Sock_Addr_Type;
   subtype Inet_Addr_Type is GNAT.Sockets.Inet_Addr_Type;

   Socket_Error : exception renames GNAT.Sockets.Socket_Error;

   Reuse_Address : Option_Name renames GNAT.Sockets.Reuse_Address;

   Socket_Level : Level_Type renames GNAT.Sockets.Socket_Level;

   Family_Inet : Family_Type renames GNAT.Sockets.Family_Inet;
   use type GNAT.Sockets.Family_Type;

   Any_Inet_Addr : Inet_Addr_Type renames GNAT.Sockets.Any_Inet_Addr;

   subtype Subprogram_Result is WolfSSL.Subprogram_Result;
   use type Subprogram_Result;

   Success : Subprogram_Result renames WolfSSL.Success;
   Failure : Subprogram_Result renames WolfSSL.Failure;

   type Optional_Inet_Addr (Exists : Boolean := False) is record
      case Exists is
         when True  => Addr : Inet_Addr_Type;
         when False => null;
      end case;
   end record;

   function Inet_Addr (Image : String) return Optional_Inet_Addr;

   type Optional_Socket (Exists : Boolean := False) is record
      case Exists is
         when True  => Socket : Socket_Type;
         when False => null;
      end case;
   end record;

   procedure Create_Stream_Socket (Socket : in out Optional_Socket) with
      Pre => not Socket.Exists;

   procedure Create_Datagram_Socket (Socket : in out Optional_Socket) with
      Pre => not Socket.Exists;

   function Connect_Socket (Socket : Socket_Type;
                            Server : Sock_Addr_Type)
                            return Subprogram_Result;

   function To_C (Socket : Socket_Type) return Integer with Inline;

   --  Close a socket and more specifically a non-connected socket.
   procedure Close_Socket (Socket : in out Optional_Socket) with
      Pre  => Socket.Exists,
      Post => not Socket.Exists;

   function Set_Socket_Option (Socket : Socket_Type;
                               Level  : Level_Type;
                               Option : Option_Type)
                               return Subprogram_Result;
   --  Manipulate socket options.

   function Bind_Socket (Socket  : Socket_Type;
                         Address : Sock_Addr_Type)
                         return Subprogram_Result;

   function Listen_Socket (Socket : Socket_Type;
                           Length : Natural) return Subprogram_Result;
   --  To accept connections, a socket is first created with
   --  Create_Socket, a willingness to accept incoming connections and
   --  a queue Length for incoming connections are specified.
   --  The queue length of 15 is an example value that should be
   --  appropriate in usual cases. It can be adjusted according to each
   --  application's particular requirements.

   function Receive_Socket (Socket : Socket_Type) return Subprogram_Result;

   procedure Accept_Socket (Server  : Socket_Type;
                            Socket  : out Optional_Socket;
                            Address : out Sock_Addr_Type;
                            Result  : out Subprogram_Result) with
      Post => (if Result = Success then Socket.Exists else not Socket.Exists);

   procedure To_C (Item       : String;
                   Target     : out Byte_Array;
                   Count      : out Byte_Index) with
      Pre  => Item'Length <= Target'Length,
      Post => Count <= Target'Last;

   procedure To_Ada (Item     : Byte_Array;
                     Target   : out String;
                     Count    : out Natural) with
      Pre  => Item'Length <= Target'Length,
      Post => Count <= Target'Last;

end SPARK_Sockets;
